@luvio/graphql 0.145.0 → 0.145.2
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.
- package/dist/luvioGraphql.js +277 -1
- package/dist/types/fields.d.ts +10 -2
- package/dist/types/fragments.d.ts +3 -1
- package/dist/types/main.d.ts +1 -0
- package/dist/types/utils.d.ts +1 -0
- package/package.json +4 -4
package/dist/luvioGraphql.js
CHANGED
|
@@ -7,6 +7,21 @@ function createFragmentMap(documentNode) {
|
|
|
7
7
|
});
|
|
8
8
|
return fragments;
|
|
9
9
|
}
|
|
10
|
+
function mergeFragmentWithExistingSelections(fragmentFieldSelection, existingSelections) {
|
|
11
|
+
// Check if there is an existing selection for this field we can merge with
|
|
12
|
+
const existingField = getRequestedField(fragmentFieldSelection.name.value, existingSelections);
|
|
13
|
+
if (existingField === undefined) {
|
|
14
|
+
existingSelections.push(fragmentFieldSelection);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
if (existingField.selectionSet) {
|
|
18
|
+
const mergedSelections = mergeSelectionSets(existingField.selectionSet, fragmentFieldSelection.selectionSet);
|
|
19
|
+
existingField.selectionSet.selections = mergedSelections
|
|
20
|
+
? mergedSelections.selections
|
|
21
|
+
: [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
10
25
|
|
|
11
26
|
function buildFieldState(state, fullPath, data) {
|
|
12
27
|
return {
|
|
@@ -75,6 +90,220 @@ function serializeValueNode(valueNode, variables) {
|
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
92
|
}
|
|
93
|
+
let selectionSetRequestedFieldsWeakMap = new WeakMap();
|
|
94
|
+
function getRequestedFieldsForType(typename, selectionSet, namedFragmentsMap, isFragmentApplicable) {
|
|
95
|
+
let cachedRequestedFieldsConfigurations = selectionSetRequestedFieldsWeakMap.get(selectionSet);
|
|
96
|
+
if (cachedRequestedFieldsConfigurations === undefined) {
|
|
97
|
+
cachedRequestedFieldsConfigurations = new Map();
|
|
98
|
+
selectionSetRequestedFieldsWeakMap.set(selectionSet, cachedRequestedFieldsConfigurations);
|
|
99
|
+
}
|
|
100
|
+
const cachedRequestedFieldsForType = cachedRequestedFieldsConfigurations.get(typename);
|
|
101
|
+
if (cachedRequestedFieldsForType !== undefined) {
|
|
102
|
+
return cachedRequestedFieldsForType;
|
|
103
|
+
}
|
|
104
|
+
const selections = calculateRequestedFieldsForType(typename, selectionSet, namedFragmentsMap, isFragmentApplicable);
|
|
105
|
+
cachedRequestedFieldsConfigurations.set(typename, selections);
|
|
106
|
+
return selections;
|
|
107
|
+
}
|
|
108
|
+
function getRequestedField(responseDataFieldName, requestedFields) {
|
|
109
|
+
return requestedFields.find((fieldNode) => (fieldNode.alias && fieldNode.alias.value === responseDataFieldName) ||
|
|
110
|
+
(!fieldNode.alias && fieldNode.name.value === responseDataFieldName));
|
|
111
|
+
}
|
|
112
|
+
function calculateRequestedFieldsForType(typename, selectionSet, namedFragmentsMap, isFragmentApplicable) {
|
|
113
|
+
const selections = [];
|
|
114
|
+
selectionSet.selections.forEach((selection) => {
|
|
115
|
+
if (selection.kind === 'Field') {
|
|
116
|
+
selections.push(selection);
|
|
117
|
+
}
|
|
118
|
+
if (selection.kind === 'InlineFragment' && isFragmentApplicable(selection, typename)) {
|
|
119
|
+
// loops over the map to get the values.
|
|
120
|
+
getRequestedFieldsForType(typename, selection.selectionSet, namedFragmentsMap, isFragmentApplicable).forEach((fragmentFieldSelection) => {
|
|
121
|
+
mergeFragmentWithExistingSelections(fragmentFieldSelection, selections);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (selection.kind === 'FragmentSpread') {
|
|
125
|
+
const namedFragment = namedFragmentsMap[selection.name.value];
|
|
126
|
+
if (namedFragment && isFragmentApplicable(namedFragment, typename)) {
|
|
127
|
+
// loops over the map to get the values.
|
|
128
|
+
getRequestedFieldsForType(typename, namedFragment.selectionSet, namedFragmentsMap, isFragmentApplicable).forEach((fragmentFieldSelection) => {
|
|
129
|
+
mergeFragmentWithExistingSelections(fragmentFieldSelection, selections);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
// Needs to happen after the selections are merged.
|
|
135
|
+
return selections.reduce((acc, fieldNode) => {
|
|
136
|
+
const fieldName = fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value;
|
|
137
|
+
// TODO: W-13485835. Some fields are not being merged, and this logic reproduces the current behavior in which the first field with name is being used.
|
|
138
|
+
if (!acc.has(fieldName)) {
|
|
139
|
+
acc.set(fieldName, fieldNode);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const existingFieldNode = acc.get(fieldName);
|
|
143
|
+
acc.set(fieldName, {
|
|
144
|
+
...existingFieldNode,
|
|
145
|
+
selectionSet: mergeSelectionSets(existingFieldNode === null || existingFieldNode === void 0 ? void 0 : existingFieldNode.selectionSet, fieldNode.selectionSet),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return acc;
|
|
149
|
+
}, new Map());
|
|
150
|
+
}
|
|
151
|
+
function mergeSelectionSets(set1, set2) {
|
|
152
|
+
if (!set2) {
|
|
153
|
+
return set1;
|
|
154
|
+
}
|
|
155
|
+
const set1Selections = !set1 ? [] : set1.selections;
|
|
156
|
+
const mergedSelections = [...set1Selections];
|
|
157
|
+
for (const selection of set2.selections) {
|
|
158
|
+
if (selection.kind === 'Field') {
|
|
159
|
+
const existingFieldIndex = mergedSelections.findIndex((sel) => {
|
|
160
|
+
return sel.kind === 'Field' && isExactSameField(sel, selection);
|
|
161
|
+
});
|
|
162
|
+
if (existingFieldIndex > -1) {
|
|
163
|
+
const existingField = mergedSelections[existingFieldIndex];
|
|
164
|
+
const mergedSelectionSet = mergeSelectionSets(existingField.selectionSet, selection.selectionSet);
|
|
165
|
+
if (mergedSelectionSet) {
|
|
166
|
+
mergedSelections[existingFieldIndex] = {
|
|
167
|
+
...existingField,
|
|
168
|
+
selectionSet: mergedSelectionSet,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
mergedSelections.push(selection);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (selection.kind === 'FragmentSpread') {
|
|
177
|
+
if (!mergedSelections.some((sel) => sel.kind === 'FragmentSpread' &&
|
|
178
|
+
sel.name.value === selection.name.value &&
|
|
179
|
+
areDirectivesEqual(sel.directives, selection.directives))) {
|
|
180
|
+
mergedSelections.push(selection);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (selection.kind === 'InlineFragment') {
|
|
184
|
+
const existingFragmentIndex = mergedSelections.findIndex((sel) => {
|
|
185
|
+
return (sel.kind === 'InlineFragment' &&
|
|
186
|
+
areTypeConditionsEqual(sel.typeCondition, selection.typeCondition) &&
|
|
187
|
+
areDirectivesEqual(sel.directives, selection.directives));
|
|
188
|
+
});
|
|
189
|
+
if (existingFragmentIndex > -1) {
|
|
190
|
+
const existingFragment = mergedSelections[existingFragmentIndex];
|
|
191
|
+
const mergedSelectionSet = mergeSelectionSets(existingFragment.selectionSet, selection.selectionSet);
|
|
192
|
+
if (mergedSelectionSet) {
|
|
193
|
+
mergedSelections[existingFragmentIndex] = {
|
|
194
|
+
...existingFragment,
|
|
195
|
+
selectionSet: mergedSelectionSet,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
mergedSelections.push(selection);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { kind: 'SelectionSet', selections: mergedSelections };
|
|
205
|
+
}
|
|
206
|
+
function areArgumentsEqual(args1, args2) {
|
|
207
|
+
if (!args1 && !args2) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
// length 0 arguments is semantically equivalent to falsy arguments.
|
|
211
|
+
if ((!args1 || args1.length === 0) && (!args2 || args2.length === 0)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if ((!args1 && args2) || (args1 && !args2)) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
if (args1.length !== args2.length) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
for (const arg of args1) {
|
|
221
|
+
const matchingArg = args2.find((a) => a.name.value === arg.name.value && areValuesEqual(a.value, arg.value));
|
|
222
|
+
if (!matchingArg) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
function areDirectivesEqual(dirs1, dirs2) {
|
|
229
|
+
if (!dirs1 && !dirs2) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if ((!dirs1 || dirs1.length === 0) && (!dirs2 || dirs2.length === 0)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if ((!dirs1 && dirs2) || (dirs1 && !dirs2)) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (dirs1.length !== dirs2.length) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
for (const dir of dirs1) {
|
|
242
|
+
const matchingDir = dirs2.find((d) => d.name.value === dir.name.value && areArgumentsEqual(d.arguments, dir.arguments));
|
|
243
|
+
if (!matchingDir) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
function areValuesEqual(valueNode1, valueNode2) {
|
|
250
|
+
if (valueNode1.kind !== valueNode2.kind) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
// Typescript doesn't understand that the above check means that the switch applies to both values. So there will be casts here.
|
|
254
|
+
switch (valueNode1.kind) {
|
|
255
|
+
case 'StringValue':
|
|
256
|
+
case 'IntValue':
|
|
257
|
+
case 'FloatValue':
|
|
258
|
+
case 'BooleanValue':
|
|
259
|
+
case 'EnumValue':
|
|
260
|
+
return valueNode1.value === valueNode2.value;
|
|
261
|
+
case 'NullValue':
|
|
262
|
+
return true; // Both Null value nodes? Always true.
|
|
263
|
+
case 'ListValue':
|
|
264
|
+
if (valueNode1.values.length !== valueNode2.values.length) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
for (let i = 0; i < valueNode1.values.length; i++) {
|
|
268
|
+
if (!areValuesEqual(valueNode1.values[i], valueNode2.values[i])) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
case 'ObjectValue':
|
|
274
|
+
if (valueNode1.fields.length !== valueNode2.fields.length) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
for (let i = 0; i < valueNode1.fields.length; i++) {
|
|
278
|
+
const field1 = valueNode1.fields[i];
|
|
279
|
+
const field2 = valueNode2.fields.find((f) => f.name.value === field1.name.value);
|
|
280
|
+
if (!field2 || !areValuesEqual(field1.value, field2.value)) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
case 'Variable':
|
|
286
|
+
return valueNode1.name.value === valueNode2.name.value;
|
|
287
|
+
default:
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function areTypeConditionsEqual(typeCondition1, typeCondition2) {
|
|
292
|
+
if (typeCondition1 === undefined && typeCondition2 === undefined) {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
if ((!typeCondition1 && typeCondition2) || (typeCondition1 && !typeCondition2)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return typeCondition1.name.value === typeCondition2.name.value;
|
|
299
|
+
}
|
|
300
|
+
function isExactSameField(field1, field2) {
|
|
301
|
+
var _a, _b;
|
|
302
|
+
return (field1.name.value === field2.name.value &&
|
|
303
|
+
((_a = field1.alias) === null || _a === void 0 ? void 0 : _a.value) === ((_b = field2.alias) === null || _b === void 0 ? void 0 : _b.value) &&
|
|
304
|
+
areArgumentsEqual(field1.arguments, field2.arguments) &&
|
|
305
|
+
areDirectivesEqual(field1.directives, field2.directives));
|
|
306
|
+
}
|
|
78
307
|
|
|
79
308
|
function serializeOperationNode(operationNode, variables, fragmentMap) {
|
|
80
309
|
return `${serializeSelectionSet(operationNode.selectionSet, variables, fragmentMap)}`;
|
|
@@ -139,4 +368,51 @@ function getOperationFromDocument(document, operationName) {
|
|
|
139
368
|
return operations[0]; // If a named operation is not provided, we return the first one
|
|
140
369
|
}
|
|
141
370
|
|
|
142
|
-
|
|
371
|
+
const { hasOwnProperty: ObjectPrototypeHasOwnProperty } = Object.prototype;
|
|
372
|
+
// Deep merge that works on GraphQL data. It:
|
|
373
|
+
// - recursively merges any Object properties that are present in both
|
|
374
|
+
// - recursively merges arrays by deepMerging each Object item in the array (by index).
|
|
375
|
+
function deepMerge(target, ...sources) {
|
|
376
|
+
for (const source of sources) {
|
|
377
|
+
for (const key in source) {
|
|
378
|
+
if (ObjectPrototypeHasOwnProperty.call(source, key)) {
|
|
379
|
+
const targetValue = target[key];
|
|
380
|
+
const sourceValue = source[key];
|
|
381
|
+
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
|
|
382
|
+
const maxLength = Math.max(targetValue.length, sourceValue.length);
|
|
383
|
+
target[key] = Array.from({ length: maxLength }, (_, index) => {
|
|
384
|
+
const targetItem = targetValue[index];
|
|
385
|
+
const sourceItem = sourceValue[index];
|
|
386
|
+
if (targetItem === undefined) {
|
|
387
|
+
return sourceItem;
|
|
388
|
+
}
|
|
389
|
+
else if (sourceItem === undefined) {
|
|
390
|
+
return targetItem;
|
|
391
|
+
}
|
|
392
|
+
else if (targetItem instanceof Object &&
|
|
393
|
+
!Array.isArray(targetItem) &&
|
|
394
|
+
sourceItem instanceof Object &&
|
|
395
|
+
!Array.isArray(sourceItem)) {
|
|
396
|
+
return deepMerge({}, targetItem, sourceItem);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
return sourceItem;
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
else if (targetValue instanceof Object &&
|
|
404
|
+
!Array.isArray(targetValue) &&
|
|
405
|
+
sourceValue instanceof Object &&
|
|
406
|
+
!Array.isArray(sourceValue)) {
|
|
407
|
+
deepMerge(targetValue, sourceValue);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
target[key] = sourceValue;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return target;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export { areArgumentsEqual, areDirectivesEqual, areTypeConditionsEqual, areValuesEqual, buildFieldState, buildQueryTypeStringKey, buildQueryTypeStructuredKey, createFragmentMap, deepMerge, getOperationFromDocument, getRequestedField, getRequestedFieldsForType, isExactSameField, mergeFragmentWithExistingSelections, mergeSelectionSets, serializeFieldArguments, serializeOperationNode };
|
package/dist/types/fields.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NormalizedKeyMetadata } from '@luvio/engine';
|
|
2
|
-
import type { ArgumentNode } from '@luvio/graphql-parser';
|
|
3
|
-
import type { GraphQLVariables } from './main';
|
|
2
|
+
import type { ArgumentNode, DirectiveNode, FieldNode, SelectionSetNode, ValueNode, NamedTypeNode } from '@luvio/graphql-parser';
|
|
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;
|
|
6
6
|
path: {
|
|
@@ -10,3 +10,11 @@ export declare function buildFieldState<T = Record<string, any>>(state: T, fullP
|
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
12
|
export declare function serializeFieldArguments(argumentNodes: Readonly<ArgumentNode[]>, variables: GraphQLVariables): string;
|
|
13
|
+
export declare function getRequestedFieldsForType(typename: string, selectionSet: SelectionSetNode, namedFragmentsMap: GraphQLFragmentMap, isFragmentApplicable: IsFragmentApplicableType): Map<string, FieldNode>;
|
|
14
|
+
export declare function getRequestedField(responseDataFieldName: string, requestedFields: FieldNode[]): FieldNode | undefined;
|
|
15
|
+
export declare function mergeSelectionSets(set1: SelectionSetNode | undefined, set2: SelectionSetNode | undefined): SelectionSetNode | undefined;
|
|
16
|
+
export declare function areArgumentsEqual(args1?: ReadonlyArray<ArgumentNode>, args2?: ReadonlyArray<ArgumentNode>): boolean;
|
|
17
|
+
export declare function areDirectivesEqual(dirs1: readonly DirectiveNode[] | undefined, dirs2: readonly DirectiveNode[] | undefined): boolean;
|
|
18
|
+
export declare function areValuesEqual(valueNode1: ValueNode, valueNode2: ValueNode): boolean;
|
|
19
|
+
export declare function areTypeConditionsEqual(typeCondition1?: NamedTypeNode, typeCondition2?: NamedTypeNode): boolean;
|
|
20
|
+
export declare function isExactSameField(field1: FieldNode, field2: FieldNode): boolean;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import type { DocumentNode } from '@luvio/graphql-parser';
|
|
1
|
+
import type { FieldNode, DocumentNode, FragmentDefinitionNode, InlineFragmentNode } from '@luvio/graphql-parser';
|
|
2
2
|
import type { GraphQLFragmentMap } from './types';
|
|
3
|
+
export type IsFragmentApplicableType = (fragment: FragmentDefinitionNode | InlineFragmentNode, typename: string) => boolean;
|
|
3
4
|
export declare function createFragmentMap(documentNode: DocumentNode): GraphQLFragmentMap;
|
|
5
|
+
export declare function mergeFragmentWithExistingSelections(fragmentFieldSelection: FieldNode, existingSelections: FieldNode[]): void;
|
package/dist/types/main.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deepMerge(target: any, ...sources: any): any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luvio/graphql",
|
|
3
|
-
"version": "0.145.
|
|
3
|
+
"version": "0.145.2",
|
|
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.145.
|
|
31
|
-
"@luvio/graphql-parser": "^0.145.
|
|
30
|
+
"@luvio/engine": "^0.145.2",
|
|
31
|
+
"@luvio/graphql-parser": "^0.145.2"
|
|
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": "
|
|
43
|
+
"none": "18 kB",
|
|
44
44
|
"min": "10 kB",
|
|
45
45
|
"compressed": "5 kB"
|
|
46
46
|
}
|