@auto-engineer/server-generator-apollo-emmett 1.87.0 → 1.89.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.
Files changed (72) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +5 -5
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +97 -0
  5. package/dist/src/codegen/extract/messages.d.ts +1 -1
  6. package/dist/src/codegen/extract/messages.d.ts.map +1 -1
  7. package/dist/src/codegen/extract/projection.d.ts +1 -1
  8. package/dist/src/codegen/extract/projection.d.ts.map +1 -1
  9. package/dist/src/codegen/extract/projection.js +14 -1
  10. package/dist/src/codegen/extract/projection.js.map +1 -1
  11. package/dist/src/codegen/extract/slice-normalizer.d.ts.map +1 -1
  12. package/dist/src/codegen/extract/slice-normalizer.js +14 -0
  13. package/dist/src/codegen/extract/slice-normalizer.js.map +1 -1
  14. package/dist/src/codegen/extract/type-helpers.d.ts +10 -0
  15. package/dist/src/codegen/extract/type-helpers.d.ts.map +1 -1
  16. package/dist/src/codegen/extract/type-helpers.js +17 -0
  17. package/dist/src/codegen/extract/type-helpers.js.map +1 -1
  18. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
  19. package/dist/src/codegen/scaffoldFromSchema.js +6 -4
  20. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
  21. package/dist/src/codegen/templates/command/decide.specs.specs.ts +599 -34
  22. package/dist/src/codegen/templates/command/decide.specs.ts +38 -18
  23. package/dist/src/codegen/templates/command/decide.specs.ts.ejs +65 -6
  24. package/dist/src/codegen/templates/command/decide.ts.ejs +33 -5
  25. package/dist/src/codegen/templates/command/mutation.resolver.specs.ts +72 -1
  26. package/dist/src/codegen/templates/command/mutation.resolver.ts.ejs +1 -1
  27. package/dist/src/codegen/templates/query/projection.specs.specs.ts +298 -1
  28. package/dist/src/codegen/templates/query/projection.specs.ts +20 -0
  29. package/dist/src/codegen/templates/query/projection.specs.ts.ejs +43 -13
  30. package/dist/src/codegen/templates/query/projection.ts.ejs +5 -0
  31. package/dist/src/codegen/templates/query/query.resolver.ts.ejs +1 -1
  32. package/dist/src/codegen/templates/react/react.specs.specs.ts +115 -0
  33. package/dist/src/codegen/templates/react/react.specs.ts +9 -2
  34. package/dist/src/codegen/templates/react/react.specs.ts.ejs +1 -3
  35. package/dist/src/codegen/templates/react/react.ts.ejs +22 -9
  36. package/dist/src/codegen/templates/react/react.ts.specs.ts +253 -0
  37. package/dist/src/codegen/templates/react/register.specs.ts +27 -23
  38. package/dist/src/codegen/templates/react/register.ts.ejs +5 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/ketchup-plan.md +14 -1
  41. package/package.json +4 -4
  42. package/src/codegen/extract/messages.ts +1 -1
  43. package/src/codegen/extract/projection.ts +13 -3
  44. package/src/codegen/extract/slice-normalizer.specs.ts +83 -0
  45. package/src/codegen/extract/slice-normalizer.ts +15 -0
  46. package/src/codegen/extract/type-helpers.specs.ts +77 -1
  47. package/src/codegen/extract/type-helpers.ts +23 -0
  48. package/src/codegen/formatTsValueSimple.specs.ts +8 -0
  49. package/src/codegen/scaffoldFromSchema.ts +8 -4
  50. package/src/codegen/templates/command/decide.specs.specs.ts +599 -34
  51. package/src/codegen/templates/command/decide.specs.ts +38 -18
  52. package/src/codegen/templates/command/decide.specs.ts.ejs +65 -6
  53. package/src/codegen/templates/command/decide.ts.ejs +33 -5
  54. package/src/codegen/templates/command/mutation.resolver.specs.ts +72 -1
  55. package/src/codegen/templates/command/mutation.resolver.ts.ejs +1 -1
  56. package/src/codegen/templates/query/projection.specs.specs.ts +298 -1
  57. package/src/codegen/templates/query/projection.specs.ts +20 -0
  58. package/src/codegen/templates/query/projection.specs.ts.ejs +43 -13
  59. package/src/codegen/templates/query/projection.ts.ejs +5 -0
  60. package/src/codegen/templates/query/query.resolver.ts.ejs +1 -1
  61. package/src/codegen/templates/react/react.specs.specs.ts +115 -0
  62. package/src/codegen/templates/react/react.specs.ts +9 -2
  63. package/src/codegen/templates/react/react.specs.ts.ejs +1 -3
  64. package/src/codegen/templates/react/react.ts.ejs +22 -9
  65. package/src/codegen/templates/react/react.ts.specs.ts +253 -0
  66. package/src/codegen/templates/react/register.specs.ts +27 -23
  67. package/src/codegen/templates/react/register.ts.ejs +5 -1
  68. package/dist/src/codegen/extract/graphql.d.ts +0 -14
  69. package/dist/src/codegen/extract/graphql.d.ts.map +0 -1
  70. package/dist/src/codegen/extract/graphql.js +0 -81
  71. package/dist/src/codegen/extract/graphql.js.map +0 -1
  72. package/src/codegen/extract/graphql.ts +0 -103
@@ -39,15 +39,30 @@ connectionOptions: {
39
39
  database,
40
40
  },
41
41
  eachMessage: async (event, context): Promise<MessageHandlerResult> => {
42
+ <%
43
+ const eventDef = messages.find(m => m.name === eventType);
44
+ const commandDef = messages.find(m => m.name === commandType && m.type === 'command');
45
+ const willHaveAggregateStream = states.some(state =>
46
+ findPrimitiveLinkingField(state.fields, eventDef?.fields || []) !== undefined
47
+ );
48
+ -%>
42
49
  /**
43
50
  * ## IMPLEMENTATION INSTRUCTIONS ##
44
51
  *
45
- * - Review the generated send call below and adjust if needed.
46
- * - Add business logic (validation, conditional sends) as required.
52
+ * Complete the command send below. Field sources are pre-classified:
53
+ * - `event.data.<field>` already wired from the triggering event.
54
+ * - `<stateVar>.<field>` → already wired from loaded aggregate state.
55
+ * - `undefined, // TODO: source unknown` → MUST be dynamically derived:
56
+ * compute from event.data, loaded state, or runtime logic.
57
+ * NEVER hardcode values copied from test assertions.
58
+ *
59
+ * Preserve all import paths above — they are generated from the model.
60
+ <% if (willHaveAggregateStream) { -%>
61
+ * Do NOT modify or remove aggregateStream calls — they load required state.
62
+ <% } -%>
63
+ * Add business logic (validation, conditional sends) as needed.
47
64
  */
48
65
  <%
49
- const eventDef = messages.find(m => m.name === eventType);
50
- const commandDef = messages.find(m => m.name === commandType && m.type === 'command');
51
66
  const commandFields = (commandDef?.fields || []);
52
67
  const eventFieldSet = new Set((eventDef?.fields || []).map(f => f.name));
53
68
  const stateFieldSources = {};
@@ -61,9 +76,7 @@ const stateFieldSources = {};
61
76
  <% if (states.length > 0) {
62
77
  let hasAggregateStream = false;
63
78
  for (const state of states) {
64
- const stateFieldNames = state.fields.map(f => f.name);
65
- const eventFieldNames = (eventDef?.fields || []).map(f => f.name);
66
- const linkingField = stateFieldNames.find(f => eventFieldNames.includes(f));
79
+ const linkingField = findPrimitiveLinkingField(state.fields, eventDef?.fields || []);
67
80
  if (linkingField) {
68
81
  hasAggregateStream = true;
69
82
  const varName = camelCase(state.type);
@@ -75,8 +88,8 @@ const stateFieldSources = {};
75
88
  const { state: <%= varName %> } = await eventStore.aggregateStream(
76
89
  '<%= state.type %>-' + event.data.<%= linkingField %>,
77
90
  {
78
- evolve: (currentState, evt) => ({ ...currentState, ...evt.data }),
79
- initialState: () => ({}),
91
+ evolve: (currentState: Record<string, unknown>, evt: { type: string; data: Record<string, unknown> }) => ({ ...currentState, ...evt.data }),
92
+ initialState: (): Record<string, unknown> => ({}),
80
93
  },
81
94
  );
82
95
  // <%= state.type %> fields: <%= state.fields.map(f => f.name).join(', ') %>
@@ -194,6 +194,8 @@ describe('react.ts.ejs', () => {
194
194
  expect(reactFile?.contents).toContain('commandSender.send({');
195
195
  expect(reactFile?.contents).toContain('event.data.orderId');
196
196
  expect(reactFile?.contents).toContain('undefined, // TODO: source unknown');
197
+ expect(reactFile?.contents).toContain('NEVER hardcode values copied from test assertions');
198
+ expect(reactFile?.contents).toContain('Preserve all import paths');
197
199
  expect(reactFile?.contents).not.toContain('throw new IllegalStateError');
198
200
  });
199
201
 
@@ -551,6 +553,8 @@ export type BarberNotified = Event<
551
553
  const reactFile = plans.find((p) => p.outputPath.endsWith('notify-barber-of-cancellation/react.ts'));
552
554
 
553
555
  expect(reactFile?.contents).toContain('aggregateStream');
556
+ expect(reactFile?.contents).toContain('Record<string, unknown>');
557
+ expect(reactFile?.contents).toContain('Do NOT modify or remove aggregateStream');
554
558
  expect(reactFile?.contents).toContain('event.data.barberId');
555
559
  expect(reactFile?.contents).toContain('appointment');
556
560
  expect(reactFile?.contents).not.toContain('readable from database');
@@ -562,4 +566,253 @@ export type BarberNotified = Event<
562
566
  expect(reactFile?.contents).not.toContain('throw new IllegalStateError');
563
567
  expect(reactFile?.contents).not.toContain('// Example:');
564
568
  });
569
+
570
+ it('should ignore event And-steps in react Then, using only commands', async () => {
571
+ const spec: SpecsSchema = {
572
+ variant: 'specs',
573
+ narratives: [
574
+ {
575
+ name: 'notification flow',
576
+ slices: [
577
+ {
578
+ type: 'command',
579
+ name: 'earn points',
580
+ client: { specs: [] },
581
+ server: {
582
+ description: '',
583
+ specs: [
584
+ {
585
+ type: 'gherkin',
586
+ feature: 'Earn points',
587
+ rules: [
588
+ {
589
+ name: 'Should earn',
590
+ examples: [
591
+ {
592
+ name: 'Points earned',
593
+ steps: [
594
+ { keyword: 'When', text: 'EarnPoints', docString: { memberId: 'm1', amount: 10 } },
595
+ { keyword: 'Then', text: 'PointsEarned', docString: { memberId: 'm1', amount: 10 } },
596
+ ],
597
+ },
598
+ ],
599
+ },
600
+ ],
601
+ },
602
+ ],
603
+ },
604
+ },
605
+ {
606
+ type: 'react',
607
+ name: 'notify milestone',
608
+ server: {
609
+ description: 'Notifies on milestone',
610
+ data: {
611
+ items: [
612
+ {
613
+ target: { type: 'Command', name: 'SendNotification' },
614
+ destination: { type: 'stream', pattern: 'notif-${memberId}' },
615
+ },
616
+ ],
617
+ },
618
+ specs: [
619
+ {
620
+ type: 'gherkin',
621
+ feature: 'Milestone notification',
622
+ rules: [
623
+ {
624
+ name: 'Should notify',
625
+ examples: [
626
+ {
627
+ name: 'Milestone reached',
628
+ steps: [
629
+ { keyword: 'When', text: 'PointsEarned', docString: { memberId: 'm1', amount: 10 } },
630
+ { keyword: 'Then', text: 'MilestoneNotified', docString: { memberId: 'm1' } },
631
+ { keyword: 'And', text: 'SendNotification', docString: { memberId: 'm1' } },
632
+ ],
633
+ },
634
+ ],
635
+ },
636
+ ],
637
+ },
638
+ ],
639
+ },
640
+ },
641
+ ],
642
+ },
643
+ ],
644
+ messages: [
645
+ {
646
+ type: 'command',
647
+ name: 'EarnPoints',
648
+ fields: [
649
+ { name: 'memberId', type: 'string', required: true },
650
+ { name: 'amount', type: 'number', required: true },
651
+ ],
652
+ },
653
+ {
654
+ type: 'event',
655
+ name: 'PointsEarned',
656
+ source: 'internal',
657
+ fields: [
658
+ { name: 'memberId', type: 'string', required: true },
659
+ { name: 'amount', type: 'number', required: true },
660
+ ],
661
+ },
662
+ {
663
+ type: 'command',
664
+ name: 'SendNotification',
665
+ fields: [{ name: 'memberId', type: 'string', required: true }],
666
+ },
667
+ {
668
+ type: 'event',
669
+ name: 'MilestoneNotified',
670
+ source: 'internal',
671
+ fields: [{ name: 'memberId', type: 'string', required: true }],
672
+ },
673
+ ],
674
+ };
675
+
676
+ const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
677
+ const reactFile = plans.find((p) => p.outputPath.endsWith('notify-milestone/react.ts'));
678
+
679
+ expect(reactFile?.contents).toContain("type: 'SendNotification'");
680
+ expect(reactFile?.contents).not.toContain('MilestoneNotified');
681
+ });
682
+
683
+ it('should use primitive linking field, skipping array/object fields', async () => {
684
+ const spec: SpecsSchema = {
685
+ variant: 'specs',
686
+ narratives: [
687
+ {
688
+ name: 'fitness flow',
689
+ slices: [
690
+ {
691
+ type: 'command',
692
+ name: 'log workout',
693
+ client: { specs: [] },
694
+ server: {
695
+ description: '',
696
+ specs: [
697
+ {
698
+ type: 'gherkin',
699
+ feature: 'Log workout',
700
+ rules: [
701
+ {
702
+ name: 'Should log workout',
703
+ examples: [
704
+ {
705
+ name: 'Workout logged',
706
+ steps: [
707
+ {
708
+ keyword: 'When',
709
+ text: 'LogWorkout',
710
+ docString: { memberId: 'm1', exercises: [{ exercise: 'squat', sets: 3 }] },
711
+ },
712
+ {
713
+ keyword: 'Then',
714
+ text: 'WorkoutLogged',
715
+ docString: { memberId: 'm1', exercises: [{ exercise: 'squat', sets: 3 }] },
716
+ },
717
+ ],
718
+ },
719
+ ],
720
+ },
721
+ ],
722
+ },
723
+ ],
724
+ },
725
+ },
726
+ {
727
+ type: 'react',
728
+ name: 'check record updates',
729
+ server: {
730
+ description: 'Updates records after workout',
731
+ data: {
732
+ items: [
733
+ {
734
+ target: { type: 'Command', name: 'UpdateRecord' },
735
+ destination: { type: 'stream', pattern: 'records-${memberId}' },
736
+ },
737
+ ],
738
+ },
739
+ specs: [
740
+ {
741
+ type: 'gherkin',
742
+ feature: 'Check record updates',
743
+ rules: [
744
+ {
745
+ name: 'Should update records',
746
+ examples: [
747
+ {
748
+ name: 'Record updated',
749
+ steps: [
750
+ {
751
+ keyword: 'Given',
752
+ text: 'WorkoutSummary',
753
+ docString: { memberId: 'm1', exercises: [{ exercise: 'squat', sets: 3 }] },
754
+ },
755
+ {
756
+ keyword: 'When',
757
+ text: 'WorkoutLogged',
758
+ docString: { memberId: 'm1', exercises: [{ exercise: 'squat', sets: 3 }] },
759
+ },
760
+ {
761
+ keyword: 'Then',
762
+ text: 'UpdateRecord',
763
+ docString: { memberId: 'm1' },
764
+ },
765
+ ],
766
+ },
767
+ ],
768
+ },
769
+ ],
770
+ },
771
+ ],
772
+ },
773
+ },
774
+ ],
775
+ },
776
+ ],
777
+ messages: [
778
+ {
779
+ type: 'command',
780
+ name: 'LogWorkout',
781
+ fields: [
782
+ { name: 'memberId', type: 'string', required: true },
783
+ { name: 'exercises', type: '{exercise:string,sets:number}[]', required: true },
784
+ ],
785
+ },
786
+ {
787
+ type: 'event',
788
+ name: 'WorkoutLogged',
789
+ source: 'internal',
790
+ fields: [
791
+ { name: 'memberId', type: 'string', required: true },
792
+ { name: 'exercises', type: '{exercise:string,sets:number}[]', required: true },
793
+ ],
794
+ },
795
+ {
796
+ type: 'command',
797
+ name: 'UpdateRecord',
798
+ fields: [{ name: 'memberId', type: 'string', required: true }],
799
+ },
800
+ {
801
+ type: 'state',
802
+ name: 'WorkoutSummary',
803
+ fields: [
804
+ { name: 'memberId', type: 'string', required: true },
805
+ { name: 'exercises', type: '{exercise:string,sets:number}[]', required: true },
806
+ ],
807
+ },
808
+ ],
809
+ };
810
+
811
+ const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
812
+ const reactFile = plans.find((p) => p.outputPath.endsWith('check-record-updates/react.ts'));
813
+
814
+ expect(reactFile?.contents).toContain("'WorkoutSummary-' + event.data.memberId");
815
+ expect(reactFile?.contents).toContain('aggregateStream');
816
+ expect(reactFile?.contents).not.toContain('event.data.exercises');
817
+ });
565
818
  });
@@ -232,31 +232,35 @@ describe('register.ts.ejs (react slice)', () => {
232
232
  const registerFile = plans.find((p) => p.outputPath.endsWith('send-notification-to-host/register.ts'));
233
233
 
234
234
  expect(registerFile?.contents).toMatchInlineSnapshot(`
235
- "import { type CommandSender, type EventSubscription, type EventStore } from '@event-driven-io/emmett';
236
- import type { BookingRequested } from '../guest-submits-booking-request/events';
235
+ "import { type CommandSender, type EventSubscription, type EventStore } from '@event-driven-io/emmett';
236
+ import type { BookingRequested } from '../guest-submits-booking-request/events';
237
237
 
238
- export async function register(messageBus: CommandSender & EventSubscription, eventStore: EventStore) {
239
- messageBus.subscribe(async (event: BookingRequested) => {
240
- /**
241
- * ## IMPLEMENTATION INSTRUCTIONS ##
242
- *
243
- * - Replace the placeholder logic with the real implementation.
244
- * - Send one or more commands via: messageBus.send({...})
245
- */
238
+ export async function register(messageBus: CommandSender & EventSubscription, eventStore: EventStore) {
239
+ messageBus.subscribe(async (event: BookingRequested) => {
240
+ /**
241
+ * ## IMPLEMENTATION INSTRUCTIONS ##
242
+ *
243
+ * - Replace the placeholder logic with the real implementation.
244
+ * - Send one or more commands via: messageBus.send({...})
245
+ * - If calling eventStore.aggregateStream(), type the evolve callback:
246
+ * evolve: (s: Record<string, unknown>, e: { type: string; data: Record<string, unknown> }) => ...
247
+ * - NEVER hardcode values copied from test assertions.
248
+ * - Preserve all import paths above — they are generated from the model.
249
+ */
246
250
 
247
- // await messageBus.send({
248
- // type: 'NotifyHost',
249
- // kind: 'Command',
250
- // data: {
251
- // // Map event fields to command fields here
252
- // // e.g., userId: event.data.userId,
253
- // },
254
- // });
251
+ // await messageBus.send({
252
+ // type: 'NotifyHost',
253
+ // kind: 'Command',
254
+ // data: {
255
+ // // Map event fields to command fields here
256
+ // // e.g., userId: event.data.userId,
257
+ // },
258
+ // });
255
259
 
256
- return;
257
- }, 'BookingRequested');
258
- }
259
- "
260
- `);
260
+ return;
261
+ }, 'BookingRequested');
262
+ }
263
+ "
264
+ `);
261
265
  });
262
266
  });
@@ -32,8 +32,12 @@ async (event: <%= pascalCase(eventType) %>) => {
32
32
  /**
33
33
  * ## IMPLEMENTATION INSTRUCTIONS ##
34
34
  *
35
- * - Replace the placeholder logic with the real implementation.
35
+ * - Replace the placeholder logic with the real implementation.
36
36
  * - Send one or more commands via: messageBus.send({...})
37
+ * - If calling eventStore.aggregateStream(), type the evolve callback:
38
+ * evolve: (s: Record<string, unknown>, e: { type: string; data: Record<string, unknown> }) => ...
39
+ * - NEVER hardcode values copied from test assertions.
40
+ * - Preserve all import paths above — they are generated from the model.
37
41
  */
38
42
 
39
43
  // await messageBus.send({
@@ -1,14 +0,0 @@
1
- export interface ParsedArg {
2
- name: string;
3
- tsType: string;
4
- graphqlType: string;
5
- nullable: boolean;
6
- }
7
- export interface ParsedGraphQlQuery {
8
- queryName: string;
9
- args: ParsedArg[];
10
- returnType: string;
11
- tsReturnType: string;
12
- }
13
- export declare function parseGraphQlRequest(request: string): ParsedGraphQlQuery;
14
- //# sourceMappingURL=graphql.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../../../src/codegen/extract/graphql.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AA8CD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAsCvE"}
@@ -1,81 +0,0 @@
1
- import { parse, print } from 'graphql';
2
- function getTypeName(typeNode) {
3
- if (typeNode.kind === 'NamedType') {
4
- return { graphqlType: typeNode.name.value, nullable: true };
5
- }
6
- else if (typeNode.kind === 'NonNullType') {
7
- const inner = getTypeName(typeNode.type);
8
- return { ...inner, nullable: false };
9
- }
10
- else {
11
- return getTypeName(typeNode.type);
12
- }
13
- }
14
- function graphqlToTs(type) {
15
- switch (type) {
16
- case 'String':
17
- return 'string';
18
- case 'Int':
19
- case 'Float':
20
- case 'Number':
21
- return 'number';
22
- case 'Boolean':
23
- return 'boolean';
24
- case 'Date':
25
- return 'Date';
26
- default:
27
- return type;
28
- }
29
- }
30
- function convertJsonAstToSdl(request) {
31
- // Handle JSON-serialized AST
32
- if (request.startsWith('{') && request.includes('"kind"')) {
33
- try {
34
- const ast = JSON.parse(request);
35
- if (typeof ast === 'object' && ast !== null && 'kind' in ast && ast.kind === 'Document') {
36
- // Convert AST to SDL string - cast is safe here as we've validated it's a Document
37
- return print(ast);
38
- }
39
- }
40
- catch {
41
- // If parsing fails, assume it's already a GraphQL string
42
- }
43
- }
44
- return request;
45
- }
46
- export function parseGraphQlRequest(request) {
47
- const sdlRequest = convertJsonAstToSdl(request);
48
- const ast = parse(sdlRequest);
49
- const op = ast.definitions.find((d) => d.kind === 'OperationDefinition' && d.operation === 'query');
50
- if (!op)
51
- throw new Error('No query operation found');
52
- const queryName = op.name?.value;
53
- if (queryName == null)
54
- throw new Error('Query must have a name');
55
- const args = (op.variableDefinitions ?? []).map((def) => {
56
- const varName = def.variable.name.value;
57
- const { graphqlType, nullable } = getTypeName(def.type);
58
- return {
59
- name: varName,
60
- graphqlType,
61
- tsType: graphqlToTs(graphqlType),
62
- nullable,
63
- };
64
- });
65
- const field = op.selectionSet.selections[0];
66
- if (field?.kind !== 'Field' || !field.name.value) {
67
- throw new Error('Query selection must be a field');
68
- }
69
- const baseName = field.name.value;
70
- const returnType = `${pascalCase(baseName)}View`;
71
- return {
72
- queryName: baseName,
73
- args,
74
- returnType,
75
- tsReturnType: `${returnType}[]`,
76
- };
77
- }
78
- function pascalCase(input) {
79
- return input.replace(/(^\w|_\w)/g, (match) => match.replace('_', '').toUpperCase());
80
- }
81
- //# sourceMappingURL=graphql.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"graphql.js","sourceRoot":"","sources":["../../../../src/codegen/extract/graphql.ts"],"names":[],"mappings":"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"}
@@ -1,103 +0,0 @@
1
- import { type OperationDefinitionNode, parse, print, type TypeNode } from 'graphql';
2
-
3
- export interface ParsedArg {
4
- name: string;
5
- tsType: string;
6
- graphqlType: string;
7
- nullable: boolean;
8
- }
9
-
10
- export interface ParsedGraphQlQuery {
11
- queryName: string;
12
- args: ParsedArg[];
13
- returnType: string;
14
- tsReturnType: string;
15
- }
16
-
17
- function getTypeName(typeNode: TypeNode): { graphqlType: string; nullable: boolean } {
18
- if (typeNode.kind === 'NamedType') {
19
- return { graphqlType: typeNode.name.value, nullable: true };
20
- } else if (typeNode.kind === 'NonNullType') {
21
- const inner = getTypeName(typeNode.type);
22
- return { ...inner, nullable: false };
23
- } else {
24
- return getTypeName(typeNode.type);
25
- }
26
- }
27
-
28
- function graphqlToTs(type: string): string {
29
- switch (type) {
30
- case 'String':
31
- return 'string';
32
- case 'Int':
33
- case 'Float':
34
- case 'Number':
35
- return 'number';
36
- case 'Boolean':
37
- return 'boolean';
38
- case 'Date':
39
- return 'Date';
40
- default:
41
- return type;
42
- }
43
- }
44
-
45
- function convertJsonAstToSdl(request: string): string {
46
- // Handle JSON-serialized AST
47
- if (request.startsWith('{') && request.includes('"kind"')) {
48
- try {
49
- const ast = JSON.parse(request) as unknown;
50
- if (typeof ast === 'object' && ast !== null && 'kind' in ast && ast.kind === 'Document') {
51
- // Convert AST to SDL string - cast is safe here as we've validated it's a Document
52
- return print(ast as Parameters<typeof print>[0]);
53
- }
54
- } catch {
55
- // If parsing fails, assume it's already a GraphQL string
56
- }
57
- }
58
- return request;
59
- }
60
-
61
- export function parseGraphQlRequest(request: string): ParsedGraphQlQuery {
62
- const sdlRequest = convertJsonAstToSdl(request);
63
-
64
- const ast = parse(sdlRequest);
65
- const op = ast.definitions.find(
66
- (d): d is OperationDefinitionNode => d.kind === 'OperationDefinition' && d.operation === 'query',
67
- );
68
-
69
- if (!op) throw new Error('No query operation found');
70
-
71
- const queryName = op.name?.value;
72
- if (queryName == null) throw new Error('Query must have a name');
73
-
74
- const args: ParsedArg[] = (op.variableDefinitions ?? []).map((def) => {
75
- const varName = def.variable.name.value;
76
- const { graphqlType, nullable } = getTypeName(def.type);
77
- return {
78
- name: varName,
79
- graphqlType,
80
- tsType: graphqlToTs(graphqlType),
81
- nullable,
82
- };
83
- });
84
-
85
- const field = op.selectionSet.selections[0];
86
- if (field?.kind !== 'Field' || !field.name.value) {
87
- throw new Error('Query selection must be a field');
88
- }
89
-
90
- const baseName = field.name.value;
91
- const returnType = `${pascalCase(baseName)}View`;
92
-
93
- return {
94
- queryName: baseName,
95
- args,
96
- returnType,
97
- tsReturnType: `${returnType}[]`,
98
- };
99
- }
100
-
101
- function pascalCase(input: string): string {
102
- return input.replace(/(^\w|_\w)/g, (match) => match.replace('_', '').toUpperCase());
103
- }