@gqloom/core 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,305 @@
1
+ import { __export } from "./chunk-Cl8Af3a2.js";
2
+ import { Kind, isInterfaceType, isListType, isNonNullType, isObjectType } from "graphql";
3
+
4
+ //#region src/utils/constants.ts
5
+ const DERIVED_DEPENDENCIES = "loom.derived-from-dependencies";
6
+
7
+ //#endregion
8
+ //#region src/utils/type.ts
9
+ function unwrapType(gqlType) {
10
+ if (isNonNullType(gqlType)) return unwrapType(gqlType.ofType);
11
+ if (isListType(gqlType)) return unwrapType(gqlType.ofType);
12
+ return gqlType;
13
+ }
14
+
15
+ //#endregion
16
+ //#region src/utils/parse-resolving-fields.ts
17
+ /**
18
+ * Analyzes and processes field resolution in a GraphQL query deeply.
19
+ *
20
+ * @param payload - The resolver payload containing the current field resolution context
21
+ * @param [maxDepth=Infinity] - Maximum depth of nested fields to parse
22
+ * @returns A map of field paths to their resolving fields
23
+ */
24
+ function getDeepResolvingFields(payload, maxDepth = Infinity) {
25
+ const result = /* @__PURE__ */ new Map();
26
+ const requestedFieldsByPath = ResolvingFieldsParser.parseDeep(payload.info, maxDepth);
27
+ const rootType = unwrapType(payload.info.returnType);
28
+ for (const [path, requestedFields] of requestedFieldsByPath.entries()) {
29
+ let currentType = rootType;
30
+ if (path) {
31
+ const pathParts = path.split(".");
32
+ let tempType = rootType;
33
+ for (const part of pathParts) {
34
+ const unwrapped = unwrapType(tempType);
35
+ if (isObjectType(unwrapped) || isInterfaceType(unwrapped)) {
36
+ const field = unwrapped.getFields()[part];
37
+ if (field) tempType = field.type;
38
+ else {
39
+ tempType = void 0;
40
+ break;
41
+ }
42
+ } else {
43
+ tempType = void 0;
44
+ break;
45
+ }
46
+ }
47
+ if (!tempType) continue;
48
+ currentType = tempType;
49
+ }
50
+ const unwrappedCurrentType = unwrapType(currentType);
51
+ if (isObjectType(unwrappedCurrentType)) {
52
+ const derivedFields = /* @__PURE__ */ new Set();
53
+ const derivedDependencies = /* @__PURE__ */ new Set();
54
+ const objectFields = unwrappedCurrentType.getFields();
55
+ for (const requestedFieldName of requestedFields) {
56
+ const field = objectFields[requestedFieldName];
57
+ if (field) {
58
+ const deps = field.extensions?.[DERIVED_DEPENDENCIES];
59
+ if (deps && Array.isArray(deps) && deps.length > 0) {
60
+ derivedFields.add(requestedFieldName);
61
+ for (const d of deps) derivedDependencies.add(d);
62
+ }
63
+ }
64
+ }
65
+ const selectedFields = new Set(requestedFields);
66
+ for (const f of derivedFields) selectedFields.delete(f);
67
+ for (const d of derivedDependencies) selectedFields.add(d);
68
+ result.set(path, {
69
+ requestedFields,
70
+ derivedFields,
71
+ derivedDependencies,
72
+ selectedFields
73
+ });
74
+ }
75
+ }
76
+ return result;
77
+ }
78
+ /**
79
+ * Analyzes and processes field resolution in a GraphQL query.
80
+ *
81
+ * @param payload - The resolver payload containing the current field resolution context
82
+ * @returns An object containing sets of different field types
83
+ */
84
+ function getResolvingFields(payload) {
85
+ const requestedFields = parseResolvingFields(payload.info);
86
+ const derivedFields = /* @__PURE__ */ new Set();
87
+ const derivedDependencies = /* @__PURE__ */ new Set();
88
+ const resolvingObject = unwrapType(payload.info.returnType);
89
+ if (isObjectType(resolvingObject)) {
90
+ const objectFields = resolvingObject.getFields();
91
+ for (const fieldName of requestedFields) {
92
+ const field = objectFields[fieldName];
93
+ if (field) {
94
+ const deps = field.extensions?.[DERIVED_DEPENDENCIES];
95
+ if (deps && Array.isArray(deps)) {
96
+ derivedFields.add(fieldName);
97
+ for (const d of deps) derivedDependencies.add(d);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ const selectedFields = new Set(requestedFields);
103
+ for (const f of derivedFields) selectedFields.delete(f);
104
+ for (const d of derivedDependencies) selectedFields.add(d);
105
+ return {
106
+ requestedFields,
107
+ derivedFields,
108
+ derivedDependencies,
109
+ selectedFields
110
+ };
111
+ }
112
+ /**
113
+ * Parses the GraphQL resolve info to extract all requested fields.
114
+ * Returns a Set of field paths where nested fields are represented as 'parent.field'.
115
+ * Supports @include, @skip directives, fragments, and variables.
116
+ *
117
+ * @param info - The GraphQL resolve info object containing the query information
118
+ * @param maxDepth - Maximum depth of nested fields to parse (default: 1)
119
+ * @returns A Set of field paths
120
+ */
121
+ function parseResolvingFields(info, maxDepth = 1) {
122
+ return ResolvingFieldsParser.parse(info, maxDepth);
123
+ }
124
+ /**
125
+ * Class responsible for parsing GraphQL resolve info to extract all requested fields.
126
+ */
127
+ var ResolvingFieldsParser = class ResolvingFieldsParser {
128
+ /** Store unique field paths grouped by parent path */
129
+ fields = /* @__PURE__ */ new Map();
130
+ /** Track visited fragments to prevent circular references */
131
+ visitedFragments = /* @__PURE__ */ new Set();
132
+ /** The GraphQL resolve info object */
133
+ info;
134
+ /** Maximum depth of nested fields to parse */
135
+ maxDepth;
136
+ static parse(info, maxDepth) {
137
+ return new ResolvingFieldsParser(info, maxDepth).parse();
138
+ }
139
+ static parseDeep(info, maxDepth) {
140
+ return new ResolvingFieldsParser(info, maxDepth).parseDeep();
141
+ }
142
+ constructor(info, maxDepth) {
143
+ this.info = info;
144
+ this.maxDepth = maxDepth;
145
+ for (const fieldNode of this.info.fieldNodes) this.collectFields(fieldNode.selectionSet, "", 0);
146
+ }
147
+ /**
148
+ * Parses the GraphQL resolve info to extract all requested fields into a flat set.
149
+ * @returns A Set of field paths
150
+ */
151
+ parse() {
152
+ const flatFields = /* @__PURE__ */ new Set();
153
+ for (const [path, fieldSet] of this.fields.entries()) for (const fieldName of fieldSet) {
154
+ const fullPath = path ? `${path}.${fieldName}` : fieldName;
155
+ flatFields.add(fullPath);
156
+ }
157
+ for (const path of this.fields.keys()) if (path) flatFields.add(path);
158
+ return flatFields;
159
+ }
160
+ /**
161
+ * Parses the GraphQL resolve info to extract all requested fields.
162
+ * @returns A map of field paths to their requested fields
163
+ */
164
+ parseDeep() {
165
+ return this.fields;
166
+ }
167
+ /**
168
+ * Recursively collects fields from a selection set.
169
+ * Handles fields, inline fragments, and fragment spreads.
170
+ *
171
+ * @param selectionSet - The selection set to process
172
+ * @param parentPath - The path of the parent field (for nested fields)
173
+ * @param currentDepth - Current depth of recursion
174
+ */
175
+ collectFields(selectionSet, parentPath = "", currentDepth = 0) {
176
+ if (!selectionSet?.selections.length || currentDepth >= this.maxDepth) return;
177
+ if (!this.fields.has(parentPath)) this.fields.set(parentPath, /* @__PURE__ */ new Set());
178
+ const currentFields = this.fields.get(parentPath);
179
+ for (const selection of selectionSet.selections) {
180
+ if (!this.shouldIncludeNode(selection)) continue;
181
+ switch (selection.kind) {
182
+ case Kind.FIELD: {
183
+ const fieldName = selection.name.value;
184
+ currentFields.add(fieldName);
185
+ const hasSelectionSet = selection.selectionSet != null;
186
+ if (hasSelectionSet) {
187
+ const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
188
+ this.collectFields(selection.selectionSet, fieldPath, currentDepth + 1);
189
+ }
190
+ break;
191
+ }
192
+ case Kind.INLINE_FRAGMENT:
193
+ if (selection.selectionSet) this.collectFields(selection.selectionSet, parentPath, currentDepth);
194
+ break;
195
+ case Kind.FRAGMENT_SPREAD: {
196
+ const fragmentName = selection.name.value;
197
+ if (this.visitedFragments.has(fragmentName)) continue;
198
+ this.visitedFragments.add(fragmentName);
199
+ const fragment = this.info.fragments[fragmentName];
200
+ if (fragment) this.collectFields(fragment.selectionSet, parentPath, currentDepth);
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ }
206
+ /**
207
+ * Extracts the boolean value from a directive's 'if' argument.
208
+ * Handles both literal boolean values and variables.
209
+ *
210
+ * @param directive - The directive node to extract value from
211
+ * @returns The boolean value of the directive's condition
212
+ */
213
+ getDirectiveValue(directive) {
214
+ const ifArg = directive.arguments?.find((arg) => arg.name.value === "if");
215
+ if (!ifArg) return true;
216
+ const value = ifArg.value;
217
+ if (value.kind === Kind.BOOLEAN) return value.value;
218
+ if (value.kind === Kind.VARIABLE) {
219
+ const variableName = value.name.value;
220
+ const variableValue = this.info.variableValues?.[variableName];
221
+ return variableValue === true;
222
+ }
223
+ return true;
224
+ }
225
+ /**
226
+ * Determines if a selection node should be included based on its directives.
227
+ * Handles both @include and @skip directives.
228
+ *
229
+ * @param node - The selection node to check
230
+ * @returns Whether the node should be included
231
+ */
232
+ shouldIncludeNode(node) {
233
+ if (!node.directives?.length) return true;
234
+ return node.directives.every((directive) => {
235
+ const isIncludeDirective = directive.name.value === "include";
236
+ if (isIncludeDirective) return this.getDirectiveValue(directive);
237
+ if (directive.name.value === "skip") return !this.getDirectiveValue(directive);
238
+ return true;
239
+ });
240
+ }
241
+ };
242
+
243
+ //#endregion
244
+ //#region src/utils/symbols.ts
245
+ var symbols_exports = {};
246
+ __export(symbols_exports, {
247
+ CONTEXT_MAP_KEY: () => CONTEXT_MAP_KEY,
248
+ FIELD_HIDDEN: () => FIELD_HIDDEN,
249
+ GET_GRAPHQL_TYPE: () => GET_GRAPHQL_TYPE,
250
+ IS_RESOLVER: () => IS_RESOLVER,
251
+ RESOLVER_OPTIONS_KEY: () => RESOLVER_OPTIONS_KEY,
252
+ WEAVER_CONFIG: () => WEAVER_CONFIG
253
+ });
254
+ /**
255
+ * The symbol to get GraphQL type for schema
256
+ */
257
+ const GET_GRAPHQL_TYPE = Symbol.for("gqloom.get_graphql_type");
258
+ /**
259
+ * The symbol to get and store weaver config
260
+ */
261
+ const WEAVER_CONFIG = Symbol.for("gqloom.weaver_config");
262
+ /**
263
+ * The symbol to get resolver options
264
+ */
265
+ const RESOLVER_OPTIONS_KEY = Symbol.for("gqloom.resolver-options");
266
+ /**
267
+ * The symbol to check if an object is a resolver
268
+ */
269
+ const IS_RESOLVER = Symbol.for("gqloom.is-resolver");
270
+ /**
271
+ * The symbol to assign a WeakMap to an object
272
+ */
273
+ const CONTEXT_MAP_KEY = Symbol.for("gqloom.context-map");
274
+ /**
275
+ * Set fields to be hidden
276
+ */
277
+ const FIELD_HIDDEN = false;
278
+
279
+ //#endregion
280
+ //#region src/utils/context.ts
281
+ /**
282
+ * Create an empty memoization payload for the resolver
283
+ * @returns the empty memoization payload
284
+ */
285
+ function onlyMemoization() {
286
+ return {
287
+ memoization: /* @__PURE__ */ new WeakMap(),
288
+ isMemoization: true
289
+ };
290
+ }
291
+ function isOnlyMemoryPayload(payload) {
292
+ return payload.isMemoization === true;
293
+ }
294
+ function getMemoizationMap(payload) {
295
+ if (isOnlyMemoryPayload(payload)) return payload.memoization;
296
+ if (typeof payload.context === "undefined") Object.defineProperty(payload, "context", { value: {} });
297
+ return assignContextMap(payload.context);
298
+ }
299
+ function assignContextMap(target) {
300
+ target[CONTEXT_MAP_KEY] ??= /* @__PURE__ */ new WeakMap();
301
+ return target[CONTEXT_MAP_KEY];
302
+ }
303
+
304
+ //#endregion
305
+ export { DERIVED_DEPENDENCIES, FIELD_HIDDEN, GET_GRAPHQL_TYPE, IS_RESOLVER, WEAVER_CONFIG, assignContextMap, getDeepResolvingFields, getMemoizationMap, getResolvingFields, isOnlyMemoryPayload, onlyMemoization, parseResolvingFields, symbols_exports };