UNPKG

@envelop/generic-auth

Version:

This plugin allows you to implement custom authentication flow by providing a custom user resolver based on the original HTTP request. The resolved user is injected into the GraphQL execution `context`, and you can use it in your resolvers to fetch the cu

135 lines (134 loc) • 5.42 kB
//#region src/utils.ts /** * Sanitizes a GraphQL document node by removing empty and unused nodes. * This includes: * - Empty inline fragments * - Fields with empty selection sets * - Fragment spreads referencing empty fragments * - Unused fragment definitions * * The sanitization is performed iteratively until the document stabilizes, * ensuring that cascading cleanups (e.g., removing an empty inline fragment * that causes a parent field to become empty) are handled correctly. */ function removeEmptyOrUnusedNodes(documentNode) { let hasChanged = true; let document = { ...documentNode, definitions: [...documentNode.definitions] }; while (hasChanged) { hasChanged = false; const fragmentMap = /* @__PURE__ */ new Map(); for (const definition of document.definitions) if (definition.kind === "FragmentDefinition") fragmentMap.set(definition.name.value, definition); document.definitions = document.definitions.map((def) => { if (def.kind === "OperationDefinition") { const sanitized = sanitizeSelectionSet(def.selectionSet, fragmentMap); if (sanitized.changed) hasChanged = true; return { ...def, selectionSet: sanitized.selectionSet }; } else if (def.kind === "FragmentDefinition") { const sanitized = sanitizeSelectionSet(def.selectionSet, fragmentMap); if (sanitized.changed) hasChanged = true; return { ...def, selectionSet: sanitized.selectionSet }; } return def; }); const updatedFragmentMap = /* @__PURE__ */ new Map(); for (const definition of document.definitions) if (definition.kind === "FragmentDefinition") updatedFragmentMap.set(definition.name.value, definition); const usedFragmentNames = /* @__PURE__ */ new Set(); for (const definition of document.definitions) if (definition.kind === "OperationDefinition") findUsedFragments(definition.selectionSet, updatedFragmentMap, usedFragmentNames); else if (definition.kind === "FragmentDefinition") findUsedFragments(definition.selectionSet, updatedFragmentMap, usedFragmentNames); const filteredDefinitions = document.definitions.filter((def) => { if (def.kind === "FragmentDefinition") return usedFragmentNames.has(def.name.value); return true; }); if (filteredDefinitions.length !== document.definitions.length) hasChanged = true; document = { ...document, definitions: filteredDefinitions }; } return document; } /** * Sanitizes a selection set by removing empty selections. * Performs sanitization bottom-up (recursively sanitizes child selection sets first). */ function sanitizeSelectionSet(selectionSet, fragmentMap) { let selections = [...selectionSet.selections]; let anyChanged = false; selections = selections.map((selection) => { if (selection.kind === "Field") { if (selection.selectionSet) { const sanitizedChildSet = sanitizeSelectionSet(selection.selectionSet, fragmentMap); if (sanitizedChildSet.changed) anyChanged = true; return { ...selection, selectionSet: sanitizedChildSet.selectionSet }; } } else if (selection.kind === "InlineFragment") { const sanitizedChildSet = sanitizeSelectionSet(selection.selectionSet, fragmentMap); if (sanitizedChildSet.changed) anyChanged = true; return { ...selection, selectionSet: sanitizedChildSet.selectionSet }; } return selection; }); const beforeInlineFilterLength = selections.length; selections = selections.filter((selection) => { if (selection.kind === "InlineFragment") return selection.selectionSet.selections.length > 0; return true; }); if (selections.length !== beforeInlineFilterLength) anyChanged = true; const beforeFieldFilterLength = selections.length; selections = selections.filter((selection) => { if (selection.kind === "Field") return !selection.selectionSet || selection.selectionSet.selections.length > 0; return true; }); if (selections.length !== beforeFieldFilterLength) anyChanged = true; const beforeSpreadFilterLength = selections.length; selections = selections.filter((selection) => { if (selection.kind === "FragmentSpread") { const fragment = fragmentMap.get(selection.name.value); return !fragment || fragment.selectionSet.selections.length > 0; } return true; }); if (selections.length !== beforeSpreadFilterLength) anyChanged = true; if (anyChanged || selections.length !== selectionSet.selections.length) return { selectionSet: { ...selectionSet, selections }, changed: true }; return { selectionSet, changed: false }; } /** * Recursively collects all fragment names that are used in a selection set. * This includes fragments referenced directly and fragments referenced by other fragments. */ function findUsedFragments(selectionSet, fragmentMap, usedFragments) { for (const selection of selectionSet.selections) if (selection.kind === "Field" && selection.selectionSet) findUsedFragments(selection.selectionSet, fragmentMap, usedFragments); else if (selection.kind === "InlineFragment" && selection.selectionSet) findUsedFragments(selection.selectionSet, fragmentMap, usedFragments); else if (selection.kind === "FragmentSpread") { const fragmentName = selection.name.value; usedFragments.add(fragmentName); const fragment = fragmentMap.get(fragmentName); if (fragment?.selectionSet) findUsedFragments(fragment.selectionSet, fragmentMap, usedFragments); } } //#endregion export { removeEmptyOrUnusedNodes };