@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
JavaScript
//#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 };