@luvio/graphql-parser 0.62.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.
Files changed (42) hide show
  1. package/babel.config.js +1 -0
  2. package/dist/argument-node.d.ts +4 -0
  3. package/dist/ast.d.ts +95 -0
  4. package/dist/constants.d.ts +12 -0
  5. package/dist/directive-node.d.ts +5 -0
  6. package/dist/document.d.ts +3 -0
  7. package/dist/field-node.d.ts +4 -0
  8. package/dist/fragment-spread-node.d.ts +4 -0
  9. package/dist/fragment.d.ts +3 -0
  10. package/dist/inline-fragment-node.d.ts +4 -0
  11. package/dist/luvioGraphqlParser.js +3887 -0
  12. package/dist/main.d.ts +4 -0
  13. package/dist/operation/index.d.ts +3 -0
  14. package/dist/operation/query/index.d.ts +6 -0
  15. package/dist/type-node.d.ts +3 -0
  16. package/dist/value-node.d.ts +4 -0
  17. package/dist/variable-definition.d.ts +4 -0
  18. package/dist/visitor.d.ts +4 -0
  19. package/jest.config.js +8 -0
  20. package/package.json +19 -0
  21. package/rollup.config.js +19 -0
  22. package/scripts/cli.mjs +18 -0
  23. package/src/__tests__/ast.spec.ts +109 -0
  24. package/src/__tests__/main.spec.ts +600 -0
  25. package/src/__tests__/type-node.spec.ts +82 -0
  26. package/src/argument-node.ts +18 -0
  27. package/src/ast.ts +200 -0
  28. package/src/constants.ts +14 -0
  29. package/src/directive-node.ts +36 -0
  30. package/src/document.ts +33 -0
  31. package/src/field-node.ts +72 -0
  32. package/src/fragment-spread-node.ts +28 -0
  33. package/src/fragment.ts +46 -0
  34. package/src/inline-fragment-node.ts +31 -0
  35. package/src/main.ts +58 -0
  36. package/src/operation/index.ts +12 -0
  37. package/src/operation/query/index.ts +78 -0
  38. package/src/type-node.ts +39 -0
  39. package/src/value-node.ts +71 -0
  40. package/src/variable-definition.ts +37 -0
  41. package/src/visitor.ts +62 -0
  42. package/tsconfig.json +9 -0
@@ -0,0 +1,600 @@
1
+ import * as parser from '../main';
2
+
3
+ describe('LDS GraphQL Parser', () => {
4
+ describe('parseAndVisit', () => {
5
+ it('transforms GraphQL operation into a custom AST', () => {
6
+ const source = /* GraphQL */ `
7
+ query {
8
+ uiapi {
9
+ query {
10
+ Account(where: { Name: { like: "Account1" } }) @connection {
11
+ edges {
12
+ node @resource(type: "Record") {
13
+ Name {
14
+ value
15
+ displayValue
16
+ }
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
23
+ `;
24
+
25
+ const expected = {
26
+ kind: 'Document',
27
+ definitions: [
28
+ {
29
+ kind: 'OperationDefinition',
30
+ operation: 'query',
31
+ luvioSelections: [
32
+ {
33
+ kind: 'ObjectFieldSelection',
34
+ name: 'uiapi',
35
+ luvioSelections: [
36
+ {
37
+ kind: 'ObjectFieldSelection',
38
+ name: 'query',
39
+ luvioSelections: [
40
+ {
41
+ kind: 'CustomFieldSelection',
42
+ name: 'Account',
43
+ type: 'Connection',
44
+ luvioSelections: [
45
+ {
46
+ kind: 'ObjectFieldSelection',
47
+ name: 'edges',
48
+ luvioSelections: [
49
+ {
50
+ kind: 'CustomFieldSelection',
51
+ name: 'node',
52
+ type: 'Record',
53
+ luvioSelections: [
54
+ {
55
+ kind: 'ObjectFieldSelection',
56
+ name: 'Name',
57
+ luvioSelections: [
58
+ {
59
+ kind: 'ScalarFieldSelection',
60
+ name: 'value',
61
+ },
62
+ {
63
+ kind: 'ScalarFieldSelection',
64
+ name: 'displayValue',
65
+ },
66
+ ],
67
+ },
68
+ ],
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ arguments: [
74
+ {
75
+ kind: 'Argument',
76
+ name: 'where',
77
+ value: {
78
+ kind: 'ObjectValue',
79
+ fields: {
80
+ Name: {
81
+ kind: 'ObjectValue',
82
+ fields: {
83
+ like: {
84
+ kind: 'StringValue',
85
+ value: 'Account1',
86
+ },
87
+ },
88
+ },
89
+ },
90
+ },
91
+ },
92
+ ],
93
+ },
94
+ ],
95
+ },
96
+ ],
97
+ },
98
+ ],
99
+ },
100
+ ],
101
+ };
102
+
103
+ const target = parser.parseAndVisit(source);
104
+
105
+ expect(target).toStrictEqual(expected);
106
+ });
107
+
108
+ it('transforms GraphQL connection directive on child property', () => {
109
+ const source = /* GraphQL */ `
110
+ query {
111
+ uiapi {
112
+ query {
113
+ Account(where: { Name: { like: "Account1" } }) @connection {
114
+ edges {
115
+ node @resource(type: "Record") {
116
+ Child @connection {
117
+ edges {
118
+ Id
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ `;
128
+
129
+ const expected = {
130
+ kind: 'Document',
131
+ definitions: [
132
+ {
133
+ kind: 'OperationDefinition',
134
+ operation: 'query',
135
+ luvioSelections: [
136
+ {
137
+ kind: 'ObjectFieldSelection',
138
+ name: 'uiapi',
139
+ luvioSelections: [
140
+ {
141
+ kind: 'ObjectFieldSelection',
142
+ name: 'query',
143
+ luvioSelections: [
144
+ {
145
+ kind: 'CustomFieldSelection',
146
+ name: 'Account',
147
+ type: 'Connection',
148
+ luvioSelections: [
149
+ {
150
+ kind: 'ObjectFieldSelection',
151
+ name: 'edges',
152
+ luvioSelections: [
153
+ {
154
+ kind: 'CustomFieldSelection',
155
+ name: 'node',
156
+ type: 'Record',
157
+ luvioSelections: [
158
+ {
159
+ kind: 'CustomFieldSelection',
160
+ name: 'Child',
161
+ type: 'Connection',
162
+ luvioSelections: [
163
+ {
164
+ kind: 'ObjectFieldSelection',
165
+ name: 'edges',
166
+ luvioSelections: [
167
+ {
168
+ kind: 'ScalarFieldSelection',
169
+ name: 'Id',
170
+ },
171
+ ],
172
+ },
173
+ ],
174
+ },
175
+ ],
176
+ },
177
+ ],
178
+ },
179
+ ],
180
+ arguments: [
181
+ {
182
+ kind: 'Argument',
183
+ name: 'where',
184
+ value: {
185
+ kind: 'ObjectValue',
186
+ fields: {
187
+ Name: {
188
+ kind: 'ObjectValue',
189
+ fields: {
190
+ like: {
191
+ kind: 'StringValue',
192
+ value: 'Account1',
193
+ },
194
+ },
195
+ },
196
+ },
197
+ },
198
+ },
199
+ ],
200
+ },
201
+ ],
202
+ },
203
+ ],
204
+ },
205
+ ],
206
+ },
207
+ ],
208
+ };
209
+
210
+ const target = parser.parseAndVisit(source);
211
+
212
+ expect(target).toStrictEqual(expected);
213
+ });
214
+
215
+ it('transforms GraphQL fragment into a custom AST', () => {
216
+ const source = /* GraphQL */ `
217
+ fragment requiredFields on Record {
218
+ id
219
+ WeakEtag
220
+ ApiName
221
+ DisplayValue
222
+ LastModifiedById {
223
+ value
224
+ }
225
+ LastModifiedDate {
226
+ value
227
+ }
228
+ SystemModstamp {
229
+ value
230
+ }
231
+ RecordTypeId {
232
+ value
233
+ }
234
+ }
235
+ `;
236
+
237
+ const expected = {
238
+ kind: 'Document',
239
+ definitions: [
240
+ {
241
+ kind: 'FragmentDefinition',
242
+ name: 'requiredFields',
243
+ typeCondition: { kind: 'NamedType', name: 'Record' },
244
+ luvioSelections: [
245
+ { kind: 'ScalarFieldSelection', name: 'id' },
246
+ { kind: 'ScalarFieldSelection', name: 'WeakEtag' },
247
+ { kind: 'ScalarFieldSelection', name: 'ApiName' },
248
+ { kind: 'ScalarFieldSelection', name: 'DisplayValue' },
249
+ {
250
+ kind: 'ObjectFieldSelection',
251
+ name: 'LastModifiedById',
252
+ luvioSelections: [{ kind: 'ScalarFieldSelection', name: 'value' }],
253
+ },
254
+ {
255
+ kind: 'ObjectFieldSelection',
256
+ name: 'LastModifiedDate',
257
+ luvioSelections: [{ kind: 'ScalarFieldSelection', name: 'value' }],
258
+ },
259
+ {
260
+ kind: 'ObjectFieldSelection',
261
+ name: 'SystemModstamp',
262
+ luvioSelections: [{ kind: 'ScalarFieldSelection', name: 'value' }],
263
+ },
264
+ {
265
+ kind: 'ObjectFieldSelection',
266
+ name: 'RecordTypeId',
267
+ luvioSelections: [{ kind: 'ScalarFieldSelection', name: 'value' }],
268
+ },
269
+ ],
270
+ },
271
+ ],
272
+ };
273
+
274
+ const target = parser.parseAndVisit(source);
275
+ expect(target).toStrictEqual(expected);
276
+ });
277
+
278
+ it('throws an error for unsupported GraphQL definition', () => {
279
+ const source = /* GraphQL */ `
280
+ type Character {
281
+ name: String!
282
+ appearsIn: [Episode!]!
283
+ }
284
+ `;
285
+
286
+ expect(() => parser.parseAndVisit(source)).toThrowError(
287
+ 'Unsupported ObjectTypeDefinition definition. Only OperationDefinition and FragmentDefinition are supported in a GraphQL Document'
288
+ );
289
+ });
290
+
291
+ it('throws an error for unsupported GraphQL operation definition', () => {
292
+ const source = /* GraphQL */ `
293
+ mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
294
+ createReview(episode: $ep, review: $review) {
295
+ stars
296
+ commentary
297
+ }
298
+ }
299
+ `;
300
+
301
+ expect(() => parser.parseAndVisit(source)).toThrowError(
302
+ 'Unsupported mutation operation. Only query operation is supported'
303
+ );
304
+ });
305
+
306
+ it('transforms variable definitions', () => {
307
+ const source = /* GraphQL */ `
308
+ query HeroNameAndFriends($episode: Episode = JEDI) {
309
+ hero(episode: $episode) {
310
+ name
311
+ friends {
312
+ name
313
+ }
314
+ }
315
+ }
316
+ `;
317
+
318
+ const expected = {
319
+ kind: 'Document',
320
+ definitions: [
321
+ {
322
+ kind: 'OperationDefinition',
323
+ operation: 'query',
324
+ name: 'HeroNameAndFriends',
325
+ luvioSelections: [
326
+ {
327
+ kind: 'ObjectFieldSelection',
328
+ name: 'hero',
329
+ luvioSelections: [
330
+ { kind: 'ScalarFieldSelection', name: 'name' },
331
+ {
332
+ kind: 'ObjectFieldSelection',
333
+ name: 'friends',
334
+ luvioSelections: [
335
+ { kind: 'ScalarFieldSelection', name: 'name' },
336
+ ],
337
+ },
338
+ ],
339
+ arguments: [
340
+ {
341
+ kind: 'Argument',
342
+ name: 'episode',
343
+ value: { kind: 'Variable', name: 'episode' },
344
+ },
345
+ ],
346
+ },
347
+ ],
348
+ variableDefinitions: [
349
+ {
350
+ kind: 'VariableDefinition',
351
+ variable: { kind: 'Variable', name: 'episode' },
352
+ type: { kind: 'NamedType', name: 'Episode' },
353
+ defaultValue: { kind: 'EnumValue', value: 'JEDI' },
354
+ },
355
+ ],
356
+ },
357
+ ],
358
+ };
359
+
360
+ const target = parser.parseAndVisit(source);
361
+
362
+ expect(target).toStrictEqual(expected);
363
+ });
364
+
365
+ it("throws an error when variable definition isn't used", () => {
366
+ const source = /* GraphQL */ `
367
+ query HeroNameAndFriends($episode: Episode = JEDI) {
368
+ hero(episode: JEDI) {
369
+ name
370
+ friends {
371
+ name
372
+ }
373
+ }
374
+ }
375
+ `;
376
+
377
+ expect(() => parser.parseAndVisit(source)).toThrowError(
378
+ 'Variable $episode was defined but never used.'
379
+ );
380
+ });
381
+
382
+ it("throws an error when variable used isn't defined", () => {
383
+ const source = /* GraphQL */ `
384
+ query HeroNameAndFriends {
385
+ hero(episode: $episode) {
386
+ name
387
+ friends {
388
+ name
389
+ }
390
+ }
391
+ }
392
+ `;
393
+
394
+ expect(() => parser.parseAndVisit(source)).toThrowError(
395
+ 'Variable $episode was used but never defined.'
396
+ );
397
+ });
398
+
399
+ it('transform directives', () => {
400
+ const source = /* GraphQL */ `
401
+ query Hero($episode: Episode) {
402
+ hero(episode: $episode) {
403
+ name
404
+ friends @include(if: true) {
405
+ name
406
+ }
407
+ }
408
+ }
409
+ `;
410
+
411
+ const expected = {
412
+ kind: 'Document',
413
+ definitions: [
414
+ {
415
+ kind: 'OperationDefinition',
416
+ operation: 'query',
417
+ name: 'Hero',
418
+ luvioSelections: [
419
+ {
420
+ kind: 'ObjectFieldSelection',
421
+ name: 'hero',
422
+ luvioSelections: [
423
+ { kind: 'ScalarFieldSelection', name: 'name' },
424
+ {
425
+ kind: 'ObjectFieldSelection',
426
+ name: 'friends',
427
+ luvioSelections: [
428
+ { kind: 'ScalarFieldSelection', name: 'name' },
429
+ ],
430
+ directives: [
431
+ {
432
+ kind: 'Directive',
433
+ name: 'include',
434
+ arguments: [
435
+ {
436
+ kind: 'Argument',
437
+ name: 'if',
438
+ value: {
439
+ kind: 'BooleanValue',
440
+ value: true,
441
+ },
442
+ },
443
+ ],
444
+ },
445
+ ],
446
+ },
447
+ ],
448
+ arguments: [
449
+ {
450
+ kind: 'Argument',
451
+ name: 'episode',
452
+ value: { kind: 'Variable', name: 'episode' },
453
+ },
454
+ ],
455
+ },
456
+ ],
457
+ variableDefinitions: [
458
+ {
459
+ kind: 'VariableDefinition',
460
+ variable: { kind: 'Variable', name: 'episode' },
461
+ type: { kind: 'NamedType', name: 'Episode' },
462
+ },
463
+ ],
464
+ },
465
+ ],
466
+ };
467
+
468
+ const target = parser.parseAndVisit(source);
469
+
470
+ expect(target).toStrictEqual(expected);
471
+ });
472
+
473
+ it('transform fragment spread', () => {
474
+ const source = /* GraphQL */ `
475
+ {
476
+ aliasTest: hero(episode: EMPIRE) {
477
+ ...nameField
478
+ appearsIn
479
+ }
480
+ }
481
+
482
+ fragment nameField on Character {
483
+ name
484
+ }
485
+ `;
486
+
487
+ const expected = {
488
+ kind: 'Document',
489
+ definitions: [
490
+ {
491
+ kind: 'OperationDefinition',
492
+ operation: 'query',
493
+ luvioSelections: [
494
+ {
495
+ kind: 'ObjectFieldSelection',
496
+ name: 'hero',
497
+ luvioSelections: [
498
+ { kind: 'FragmentSpread', name: 'nameField' },
499
+ { kind: 'ScalarFieldSelection', name: 'appearsIn' },
500
+ ],
501
+ arguments: [
502
+ {
503
+ kind: 'Argument',
504
+ name: 'episode',
505
+ value: { kind: 'EnumValue', value: 'EMPIRE' },
506
+ },
507
+ ],
508
+ alias: 'aliasTest',
509
+ },
510
+ ],
511
+ },
512
+ {
513
+ kind: 'FragmentDefinition',
514
+ name: 'nameField',
515
+ typeCondition: { kind: 'NamedType', name: 'Character' },
516
+ luvioSelections: [{ kind: 'ScalarFieldSelection', name: 'name' }],
517
+ },
518
+ ],
519
+ };
520
+
521
+ const target = parser.parseAndVisit(source);
522
+
523
+ expect(target).toStrictEqual(expected);
524
+ });
525
+
526
+ it('transform inline fragment', () => {
527
+ const source = /* GraphQL */ `
528
+ query HeroForEpisode($ep: Episode!) {
529
+ hero(episode: $ep) {
530
+ name
531
+ ... on Droid {
532
+ primaryFunction
533
+ }
534
+ ... on Human {
535
+ height
536
+ }
537
+ }
538
+ }
539
+ `;
540
+
541
+ const expected = {
542
+ kind: 'Document',
543
+ definitions: [
544
+ {
545
+ kind: 'OperationDefinition',
546
+ operation: 'query',
547
+ luvioSelections: [
548
+ {
549
+ kind: 'ObjectFieldSelection',
550
+ name: 'hero',
551
+ luvioSelections: [
552
+ { kind: 'ScalarFieldSelection', name: 'name' },
553
+ {
554
+ kind: 'InlineFragment',
555
+ luvioSelections: [
556
+ {
557
+ kind: 'ScalarFieldSelection',
558
+ name: 'primaryFunction',
559
+ },
560
+ ],
561
+ typeCondition: { kind: 'NamedType', name: 'Droid' },
562
+ },
563
+ {
564
+ kind: 'InlineFragment',
565
+ luvioSelections: [
566
+ { kind: 'ScalarFieldSelection', name: 'height' },
567
+ ],
568
+ typeCondition: { kind: 'NamedType', name: 'Human' },
569
+ },
570
+ ],
571
+ arguments: [
572
+ {
573
+ kind: 'Argument',
574
+ name: 'episode',
575
+ value: { kind: 'Variable', name: 'ep' },
576
+ },
577
+ ],
578
+ },
579
+ ],
580
+ name: 'HeroForEpisode',
581
+ variableDefinitions: [
582
+ {
583
+ kind: 'VariableDefinition',
584
+ variable: { kind: 'Variable', name: 'ep' },
585
+ type: {
586
+ kind: 'NonNullType',
587
+ type: { kind: 'NamedType', name: 'Episode' },
588
+ },
589
+ },
590
+ ],
591
+ },
592
+ ],
593
+ };
594
+
595
+ const target = parser.parseAndVisit(source);
596
+
597
+ expect(target).toStrictEqual(expected);
598
+ });
599
+ });
600
+ });
@@ -0,0 +1,82 @@
1
+ import { NamedTypeNode, TypeNode } from 'graphql/language';
2
+ import { transform } from '../type-node';
3
+
4
+ describe('Luvio GraphQL TypeNode transform', () => {
5
+ it('returns LuvioTypeNode when input is GraphQL NamedTypeNode', () => {
6
+ expect(
7
+ transform({
8
+ kind: 'NamedType',
9
+ name: {
10
+ kind: 'Name',
11
+ value: 'test',
12
+ },
13
+ })
14
+ ).toStrictEqual({
15
+ kind: 'NamedType',
16
+ name: 'test',
17
+ });
18
+ });
19
+
20
+ it('returns LuvioTypeNode when input is GraphQL ListTypeNode', () => {
21
+ expect(
22
+ transform({
23
+ kind: 'ListType',
24
+ type: {
25
+ kind: 'NamedType',
26
+ name: {
27
+ kind: 'Name',
28
+ value: 'test',
29
+ },
30
+ },
31
+ })
32
+ ).toStrictEqual({
33
+ kind: 'ListType',
34
+ type: {
35
+ kind: 'NamedType',
36
+ name: 'test',
37
+ },
38
+ });
39
+ });
40
+
41
+ it('returns LuvioTypeNode when input is GraphQL NonNullTypeNode', () => {
42
+ expect(
43
+ transform({
44
+ kind: 'NonNullType',
45
+ type: {
46
+ kind: 'NamedType',
47
+ name: {
48
+ kind: 'Name',
49
+ value: 'test',
50
+ },
51
+ },
52
+ })
53
+ ).toStrictEqual({
54
+ kind: 'NonNullType',
55
+ type: {
56
+ kind: 'NamedType',
57
+ name: 'test',
58
+ },
59
+ });
60
+ });
61
+
62
+ it('throws when input is unsupported TypeNode', () => {
63
+ expect(() =>
64
+ transform({
65
+ kind: 'Unknown',
66
+ name: 'test',
67
+ } as unknown as TypeNode)
68
+ ).toThrowError('Unsupported TypeNode');
69
+ });
70
+
71
+ it('throws when input is unsupported NonNullTypeNode', () => {
72
+ expect(() =>
73
+ transform({
74
+ kind: 'NonNullType',
75
+ type: {
76
+ kind: 'Unknown',
77
+ name: 'test',
78
+ } as unknown as NamedTypeNode,
79
+ })
80
+ ).toThrowError('Unsupported NonNullTypeNode');
81
+ });
82
+ });