@luvio/graphql 0.157.3 → 0.157.4

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.
@@ -148,7 +148,7 @@ function calculateRequestedFieldsForType(typename, selectionSet, namedFragmentsM
148
148
  return acc;
149
149
  }, new Map());
150
150
  }
151
- function mergeSelectionSets(set1, set2) {
151
+ function mergeSelectionSets(set1 = { kind: 'SelectionSet', selections: [] }, set2) {
152
152
  if (!set2) {
153
153
  return set1;
154
154
  }
@@ -304,6 +304,124 @@ function isExactSameField(field1, field2) {
304
304
  areArgumentsEqual(field1.arguments, field2.arguments) &&
305
305
  areDirectivesEqual(field1.directives, field2.directives));
306
306
  }
307
+ function injectSelectionSet(selectionSet, queryTransformHelper, fragmentMap, getQueryTransformerForType, processedNodes = new Set(), currentFragmentName) {
308
+ if (!queryTransformHelper || !selectionSet) {
309
+ return;
310
+ }
311
+ const { selections, fragmentDefinitions, shortCircuit } = queryTransformHelper.getMinimumSelections();
312
+ // Add new fragment definitions to the map
313
+ if (fragmentDefinitions) {
314
+ Object.entries(fragmentDefinitions).forEach(([name, fragment]) => {
315
+ if (!fragmentMap[name]) {
316
+ fragmentMap[name] = fragment;
317
+ }
318
+ });
319
+ }
320
+ const minimumSelectionSet = {
321
+ kind: 'SelectionSet',
322
+ selections: selections.filter((selection) => {
323
+ // Filter out self-referential fragment spreads
324
+ if (selection.kind === 'FragmentSpread' && currentFragmentName) {
325
+ return selection.name.value !== currentFragmentName;
326
+ }
327
+ return true;
328
+ }),
329
+ };
330
+ // Process each selection in the selection set
331
+ const mergedSelections = mergeSelectionSets(selectionSet, minimumSelectionSet);
332
+ Object.assign(selectionSet, mergedSelections);
333
+ // Some injected fragments or fields will make the query invalid if we continue processing them recursively, so we need to short circuit.
334
+ if (shortCircuit) {
335
+ return;
336
+ }
337
+ mergedSelections.selections.forEach((selection) => {
338
+ if (processedNodes.has(selection)) {
339
+ return;
340
+ }
341
+ if (selection.kind === 'Field') {
342
+ const fieldType = queryTransformHelper.getFieldType(selection);
343
+ if (fieldType) {
344
+ const { typename } = fieldType;
345
+ const fieldQueryTransformHelper = getQueryTransformerForType(typename, fragmentMap);
346
+ if (fieldQueryTransformHelper) {
347
+ // Process nested selection set if it exists
348
+ if (!selection.selectionSet) {
349
+ Object.assign(selection, {
350
+ selectionSet: {
351
+ kind: 'SelectionSet',
352
+ selections: [],
353
+ },
354
+ });
355
+ }
356
+ if (selection.selectionSet) {
357
+ injectSelectionSet(selection.selectionSet, fieldQueryTransformHelper, fragmentMap, getQueryTransformerForType, processedNodes, currentFragmentName);
358
+ }
359
+ }
360
+ }
361
+ }
362
+ else if (selection.kind === 'FragmentSpread') {
363
+ // Skip processing if it's a self-referential fragment spread
364
+ if (currentFragmentName && selection.name.value === currentFragmentName) {
365
+ return;
366
+ }
367
+ const fragment = fragmentMap[selection.name.value];
368
+ if (!fragment)
369
+ return; // FragmentSpread on an undefined fragment
370
+ const fragmentType = queryTransformHelper.getInContextFragmentType(selection, fragmentMap);
371
+ if (!fragmentType)
372
+ return; // We don't know what type this fragment is, so we can't inject anything.
373
+ const fragmentTransformHelper = getQueryTransformerForType(fragmentType, fragmentMap);
374
+ if (!fragmentTransformHelper)
375
+ return; // The Type doesn't exist from build time
376
+ injectSelectionSet(fragment.selectionSet, fragmentTransformHelper, fragmentMap, getQueryTransformerForType, processedNodes, selection.name.value // Fragment name to prevent infinite recursion
377
+ );
378
+ }
379
+ else if (selection.kind === 'InlineFragment') {
380
+ const fragmentType = queryTransformHelper.getInContextFragmentType(selection, fragmentMap);
381
+ const fragmentSelections = selection.selectionSet;
382
+ if (fragmentType === undefined) {
383
+ // Untyped or unknown fragment
384
+ injectSelectionSet(fragmentSelections, queryTransformHelper, fragmentMap, getQueryTransformerForType, processedNodes, currentFragmentName);
385
+ }
386
+ else {
387
+ const fragmentTransformHelper = getQueryTransformerForType(fragmentType, fragmentMap);
388
+ if (fragmentTransformHelper === undefined) {
389
+ return;
390
+ }
391
+ injectSelectionSet(fragmentSelections, fragmentTransformHelper, fragmentMap, getQueryTransformerForType, processedNodes, currentFragmentName);
392
+ }
393
+ }
394
+ processedNodes.add(selection);
395
+ });
396
+ }
397
+ function applyMinimumFieldsToDocument(document, options, getQueryTransformerForType, operationName) {
398
+ // Return original document for non-query operations
399
+ const operation = getOperationFromDocument(document, operationName);
400
+ if (!operation || operation.kind !== 'OperationDefinition' || operation.operation !== 'query') {
401
+ return document;
402
+ }
403
+ const fragmentMap = createFragmentMap(document);
404
+ const queryTransformHelper = getQueryTransformerForType(options.queryTypeName, fragmentMap);
405
+ if (queryTransformHelper) {
406
+ injectSelectionSet(operation.selectionSet, queryTransformHelper, fragmentMap, getQueryTransformerForType);
407
+ }
408
+ // Only add new fragments that weren't in the original document
409
+ const newFragmentDefinitions = Object.entries(fragmentMap)
410
+ .filter(([name]) => !document.definitions.some((d) => d.kind === 'FragmentDefinition' && d.name.value === name))
411
+ .map(([name, fragment]) => ({
412
+ kind: 'FragmentDefinition',
413
+ name: { kind: 'Name', value: name },
414
+ typeCondition: fragment.typeCondition,
415
+ selectionSet: fragment.selectionSet,
416
+ }));
417
+ if (newFragmentDefinitions.length > 0) {
418
+ return {
419
+ ...document,
420
+ definitions: [...document.definitions, ...newFragmentDefinitions],
421
+ };
422
+ }
423
+ return document;
424
+ }
307
425
 
308
426
  function serializeOperationNode(operationNode, variables, fragmentMap) {
309
427
  return `${serializeSelectionSet(operationNode.selectionSet, variables, fragmentMap)}`;
@@ -415,4 +533,4 @@ function deepMerge(target, ...sources) {
415
533
  return target;
416
534
  }
417
535
 
418
- export { areArgumentsEqual, areDirectivesEqual, areTypeConditionsEqual, areValuesEqual, buildFieldState, buildQueryTypeStringKey, buildQueryTypeStructuredKey, createFragmentMap, deepMerge, getOperationFromDocument, getRequestedField, getRequestedFieldsForType, isExactSameField, mergeFragmentWithExistingSelections, mergeSelectionSets, serializeFieldArguments, serializeOperationNode };
536
+ export { applyMinimumFieldsToDocument, areArgumentsEqual, areDirectivesEqual, areTypeConditionsEqual, areValuesEqual, buildFieldState, buildQueryTypeStringKey, buildQueryTypeStructuredKey, createFragmentMap, deepMerge, getOperationFromDocument, getRequestedField, getRequestedFieldsForType, injectSelectionSet, isExactSameField, mergeFragmentWithExistingSelections, mergeSelectionSets, serializeFieldArguments, serializeOperationNode };
@@ -1,5 +1,5 @@
1
+ import type { DocumentNode, FieldNode, FragmentSpreadNode, InlineFragmentNode, NamedTypeNode, SelectionNode, SelectionSetNode, ValueNode, ArgumentNode, DirectiveNode } from '@luvio/graphql-parser';
1
2
  import type { NormalizedKeyMetadata } from '@luvio/engine';
2
- import type { ArgumentNode, DirectiveNode, FieldNode, SelectionSetNode, ValueNode, NamedTypeNode } from '@luvio/graphql-parser';
3
3
  import type { GraphQLFragmentMap, GraphQLVariables, IsFragmentApplicableType } from './main';
4
4
  export declare function buildFieldState<T = Record<string, any>>(state: T, fullPath: string | NormalizedKeyMetadata, data: any): T & {
5
5
  data: any;
@@ -12,9 +12,58 @@ export declare function buildFieldState<T = Record<string, any>>(state: T, fullP
12
12
  export declare function serializeFieldArguments(argumentNodes: Readonly<ArgumentNode[]>, variables: GraphQLVariables): string;
13
13
  export declare function getRequestedFieldsForType(typename: string, selectionSet: SelectionSetNode, namedFragmentsMap: GraphQLFragmentMap, isFragmentApplicable: IsFragmentApplicableType): Map<string, FieldNode>;
14
14
  export declare function getRequestedField(responseDataFieldName: string, requestedFields: FieldNode[]): FieldNode | undefined;
15
- export declare function mergeSelectionSets(set1: SelectionSetNode | undefined, set2: SelectionSetNode | undefined): SelectionSetNode | undefined;
15
+ export declare function mergeSelectionSets(set1: SelectionSetNode | undefined, set2: SelectionSetNode | undefined): SelectionSetNode;
16
16
  export declare function areArgumentsEqual(args1?: ReadonlyArray<ArgumentNode>, args2?: ReadonlyArray<ArgumentNode>): boolean;
17
17
  export declare function areDirectivesEqual(dirs1: readonly DirectiveNode[] | undefined, dirs2: readonly DirectiveNode[] | undefined): boolean;
18
18
  export declare function areValuesEqual(valueNode1: ValueNode, valueNode2: ValueNode): boolean;
19
19
  export declare function areTypeConditionsEqual(typeCondition1?: NamedTypeNode, typeCondition2?: NamedTypeNode): boolean;
20
20
  export declare function isExactSameField(field1: FieldNode, field2: FieldNode): boolean;
21
+ export type GraphQLTypeDetails = {
22
+ isArray: boolean;
23
+ typename: string;
24
+ };
25
+ export interface GetFieldTypeFunction {
26
+ (field: FieldNode): GraphQLTypeDetails | undefined;
27
+ }
28
+ export interface GetInContextFragmentTypeFunction {
29
+ (fragment: FragmentSpreadNode | InlineFragmentNode, fragmentMap: GraphQLFragmentMap): string | undefined;
30
+ }
31
+ /**
32
+ * Returns the minimum selections (fields/fragments) that must be present for a given type.
33
+ *
34
+ * @returns An object containing:
35
+ * - selections: The minimum set of SelectionNode objects (fields, fragments, etc.) that must be present for this type.
36
+ * - fragmentDefinitions: (Optional) Any named fragment definitions that should be added to the document as a result of this injection.
37
+ * - shortCircuit: (Advanced) If true, signals that no further recursive field injection should be performed for this selection set.
38
+ * Use with extreme caution! Setting this will prevent all further minimum field injection for this subtree, which may result in missing required fields for nested types.
39
+ * Only use this if you are certain that no further injection is needed and you fully understand the implications for query correctness and downstream processing.
40
+ */
41
+ export interface GetMinimumSelectionsFunction {
42
+ (): {
43
+ /**
44
+ * The minimum set of selections (fields, fragment spreads, etc.) that must be present for this type.
45
+ * These will be merged into the selection set for the current type.
46
+ */
47
+ selections: SelectionNode[];
48
+ /**
49
+ * (Optional) Any named fragment definitions that should be added to the document as a result of this injection.
50
+ * These fragments will be added to the document if not already present, but will only be processed if referenced by a FragmentSpread.
51
+ */
52
+ fragmentDefinitions?: GraphQLFragmentMap;
53
+ /**
54
+ * (Optional)(Advanced) If true, signals that no further recursive field injection should be performed for this selection set.
55
+ * Use with extreme caution! Setting this will prevent all further minimum field injection for this subtree, which may result in missing required fields for nested types.
56
+ * Only use this if you are certain that no further injection is needed and you fully understand the implications for query correctness and downstream processing.
57
+ */
58
+ shortCircuit?: boolean;
59
+ };
60
+ }
61
+ export interface QueryTransformHelper {
62
+ getInContextFragmentType: GetInContextFragmentTypeFunction;
63
+ getFieldType: GetFieldTypeFunction;
64
+ getMinimumSelections: GetMinimumSelectionsFunction;
65
+ }
66
+ export declare function injectSelectionSet(selectionSet: SelectionSetNode | undefined, queryTransformHelper: QueryTransformHelper | undefined, fragmentMap: GraphQLFragmentMap, getQueryTransformerForType: (typename: string, fragmentMap: GraphQLFragmentMap) => QueryTransformHelper | undefined, processedNodes?: Set<SelectionNode>, currentFragmentName?: string): void;
67
+ export declare function applyMinimumFieldsToDocument(document: DocumentNode, options: {
68
+ queryTypeName: string;
69
+ }, getQueryTransformerForType: (typename: string, fragmentMap: GraphQLFragmentMap) => QueryTransformHelper | undefined, operationName?: string): DocumentNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luvio/graphql",
3
- "version": "0.157.3",
3
+ "version": "0.157.4",
4
4
  "description": "GraphQL utilities for Luvio GraphQL adapter support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,8 +27,8 @@
27
27
  "test:size": "luvioBundlesize"
28
28
  },
29
29
  "dependencies": {
30
- "@luvio/engine": "^0.157.3",
31
- "@luvio/graphql-parser": "^0.157.3"
30
+ "@luvio/engine": "^0.157.4",
31
+ "@luvio/graphql-parser": "^0.157.4"
32
32
  },
33
33
  "volta": {
34
34
  "extends": "../../../package.json"
@@ -40,7 +40,7 @@
40
40
  {
41
41
  "path": "./dist/luvioGraphql.js",
42
42
  "maxSize": {
43
- "none": "18 kB",
43
+ "none": "25 kB",
44
44
  "min": "10 kB",
45
45
  "compressed": "5 kB"
46
46
  }