@auto-engineer/server-generator-apollo-emmett 1.45.1 → 1.45.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/ketchup-plan.md CHANGED
@@ -1,17 +1,7 @@
1
- # Ketchup Plan: Separate InitializeServer from GenerateServer
1
+ # Ketchup Plan: Fix `@Field(() => JSON)` uses JS built-in instead of GraphQL scalar
2
2
 
3
3
  ## TODO
4
4
 
5
5
  ## DONE
6
6
 
7
- - [x] Burst 1: Export 4 config-writing functions from generate-server.ts (03543045)
8
- - [x] Burst 2: InitializeServer creates all files in empty dir + emits ServerInitialized (cc8fe3c0)
9
- - [x] Burst 3: InitializeServer is idempotent (doesn't overwrite existing files) (e8137a58)
10
- - [x] Burst 4: InitializeServer emits ServerInitializationFailed on error (2f320556)
11
- - [x] Burst 5: Update index.ts exports + auto.config.ts pipeline wiring (849fa106)
12
- - [x] Burst 6: GenerateServer preserves health resolver after clean (8d37de62)
13
- - [x] Burst 1: Command slices — Given-step state refs get local event type definitions (261a0d41)
14
- - [x] Burst 2: findEventSource — check react data target events (9ac929f9)
15
- - [x] Burst 3: extractMessagesForReact — extract data target events with source 'then' (f7aee89b)
16
- - [x] Burst 4: Add events.ts.ejs template for react slices + defaultFilesByType entry (d2e5e5b8)
17
- - [x] Burst 5: formatSpecValue — handle stringified JSON arrays (6cb406c3)
7
+ - [x] Burst 1: Fix graphqlType() to return 'GraphQLJSON' for object/inline-object types and update snapshots (c810efdd)
package/package.json CHANGED
@@ -32,8 +32,8 @@
32
32
  "uuid": "^11.0.0",
33
33
  "web-streams-polyfill": "^4.1.0",
34
34
  "zod": "^3.22.4",
35
- "@auto-engineer/narrative": "1.45.1",
36
- "@auto-engineer/message-bus": "1.45.1"
35
+ "@auto-engineer/narrative": "1.45.2",
36
+ "@auto-engineer/message-bus": "1.45.2"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
@@ -44,9 +44,9 @@
44
44
  "typescript": "^5.8.3",
45
45
  "vitest": "^3.2.4",
46
46
  "tsx": "^4.19.2",
47
- "@auto-engineer/cli": "1.45.1"
47
+ "@auto-engineer/cli": "1.45.2"
48
48
  },
49
- "version": "1.45.1",
49
+ "version": "1.45.2",
50
50
  "scripts": {
51
51
  "generate:server": "tsx src/cli/index.ts",
52
52
  "build": "tsc && tsx ../../scripts/fix-esm-imports.ts && rm -rf dist/src/codegen/templates && mkdir -p dist/src/codegen && cp -r src/codegen/templates dist/src/codegen/templates && cp src/server.ts dist/src && cp -r src/utils dist/src && cp -r src/domain dist/src",
@@ -311,8 +311,8 @@ async function renderTemplate(
311
311
  if (arr2 !== null) return `[${graphqlType(arr2[1].trim())}]`;
312
312
 
313
313
  if (base === 'unknown' || base === 'any') return 'GraphQLJSON';
314
- if (base === 'object') return 'JSON';
315
- if (isInlineObject(base)) return 'JSON';
314
+ if (base === 'object') return 'GraphQLJSON';
315
+ if (isInlineObject(base)) return 'GraphQLJSON';
316
316
  if (isStringLiteralUnion(base)) return resolveEnumOrString(base);
317
317
 
318
318
  return convertPrimitiveType(base);
@@ -111,7 +111,7 @@ describe('mutation.resolver.ts.ejs', () => {
111
111
  @Field(() => Float)
112
112
  rating!: number;
113
113
 
114
- @Field(() => JSON)
114
+ @Field(() => GraphQLJSON)
115
115
  metadata!: object;
116
116
 
117
117
  @Field(() => GraphQLISODateTime)
@@ -831,4 +831,136 @@ describe('projection.ts.ejs', () => {
831
831
  "
832
832
  `);
833
833
  });
834
+
835
+ it('should not treat mixed union type with quotes as enum import', async () => {
836
+ const flows: Model = {
837
+ variant: 'specs',
838
+ narratives: [
839
+ {
840
+ name: 'order-flow',
841
+ slices: [
842
+ {
843
+ type: 'command',
844
+ name: 'place-order',
845
+ stream: 'order-${orderId}',
846
+ client: { specs: [] },
847
+ server: {
848
+ description: 'handles order placement',
849
+ specs: [
850
+ {
851
+ type: 'gherkin',
852
+ feature: 'Place order',
853
+ rules: [
854
+ {
855
+ name: 'Should handle order placement',
856
+ examples: [
857
+ {
858
+ name: 'User places order',
859
+ steps: [
860
+ {
861
+ keyword: 'When',
862
+ text: 'PlaceOrder',
863
+ docString: { orderId: 'order_1', total: 100 },
864
+ },
865
+ {
866
+ keyword: 'Then',
867
+ text: 'OrderPlaced',
868
+ docString: { orderId: 'order_1', total: 100 },
869
+ },
870
+ ],
871
+ },
872
+ ],
873
+ },
874
+ ],
875
+ },
876
+ ],
877
+ },
878
+ },
879
+ {
880
+ type: 'query',
881
+ name: 'view-order-status',
882
+ stream: 'orders',
883
+ client: { specs: [] },
884
+ server: {
885
+ description: 'projection for order status',
886
+ data: {
887
+ items: [
888
+ {
889
+ target: { type: 'State', name: 'OrderStatus' },
890
+ origin: {
891
+ type: 'projection',
892
+ name: 'OrderStatusProjection',
893
+ idField: 'orderId',
894
+ },
895
+ },
896
+ ],
897
+ },
898
+ specs: [
899
+ {
900
+ type: 'gherkin',
901
+ feature: 'View order status',
902
+ rules: [
903
+ {
904
+ name: 'Should project order status',
905
+ examples: [
906
+ {
907
+ name: 'Order placed shows in status',
908
+ steps: [
909
+ {
910
+ keyword: 'When',
911
+ text: 'OrderPlaced',
912
+ docString: { orderId: 'order_1', total: 100 },
913
+ },
914
+ {
915
+ keyword: 'Then',
916
+ text: 'OrderStatus',
917
+ docString: { orderId: 'order_1', mixedStatus: 0 },
918
+ },
919
+ ],
920
+ },
921
+ ],
922
+ },
923
+ ],
924
+ },
925
+ ],
926
+ },
927
+ },
928
+ ],
929
+ },
930
+ ],
931
+ messages: [
932
+ {
933
+ type: 'command',
934
+ name: 'PlaceOrder',
935
+ fields: [
936
+ { name: 'orderId', type: 'string', required: true },
937
+ { name: 'total', type: 'number', required: true },
938
+ ],
939
+ },
940
+ {
941
+ type: 'event',
942
+ name: 'OrderPlaced',
943
+ source: 'internal',
944
+ fields: [
945
+ { name: 'orderId', type: 'string', required: true },
946
+ { name: 'total', type: 'number', required: true },
947
+ ],
948
+ },
949
+ {
950
+ type: 'state',
951
+ name: 'OrderStatus',
952
+ fields: [
953
+ { name: 'orderId', type: 'string', required: true },
954
+ { name: 'mixedStatus', type: 'number | "pending"', required: true },
955
+ ],
956
+ },
957
+ ],
958
+ };
959
+
960
+ const plans = await generateScaffoldFilePlans(flows.narratives, flows.messages, undefined, 'src/domain/flows');
961
+ const projectionFile = plans.find((p) => p.outputPath.endsWith('view-order-status/projection.ts'));
962
+
963
+ expect(projectionFile?.contents).not.toContain('number | "pending"');
964
+ expect(projectionFile?.contents).toContain('mixedStatus: /* TODO: map from event.data */ undefined as any');
965
+ });
834
966
  });
@@ -26,7 +26,7 @@ if (targetName && messages) {
26
26
  if (targetDef?.fields) {
27
27
  for (const field of targetDef.fields) {
28
28
  const fieldType = (field.type || '').replace(/\s*\|\s*null/g, '').trim();
29
- if (fieldType.includes('|') && (fieldType.includes('"') || fieldType.includes("'"))) {
29
+ if (isEnumType(fieldType)) {
30
30
  const enumName = toTsFieldType(field.type);
31
31
  if (enumName && enumName !== 'String' && !stateEnums.includes(enumName)) {
32
32
  stateEnums.push(enumName);
@@ -223,7 +223,7 @@ case '<%= event.type %>': {
223
223
  placeholder = isNullable ? '/* TODO: map from event.data */ null' : '/* TODO: map from event.data */ new Date()';
224
224
  } else if (baseType.startsWith('Array<')) {
225
225
  placeholder = isNullable ? '/* TODO: map from event.data */ null' : '/* TODO: map from event.data */ []';
226
- } else if (baseType.includes('|') && (baseType.includes('"') || baseType.includes("'"))) {
226
+ } else if (isEnumType(baseType)) {
227
227
  const enumName = toTsFieldType(type);
228
228
  const firstValue = baseType.match(/['"]([^'"]+)['"]/)?.[1] ?? '';
229
229
  const enumConstant = firstValue ? firstValue.toUpperCase().replace(/[^A-Z0-9]/g, '_') : '';
@@ -703,6 +703,67 @@ describe('query.resolver.ts.ejs', () => {
703
703
  expect(resolverFile?.contents).not.toContain(', ReadModel');
704
704
  });
705
705
 
706
+ it('should not treat mixed union type with quotes as enum in singleton default values', async () => {
707
+ const spec: SpecsSchema = {
708
+ variant: 'specs',
709
+ narratives: [
710
+ {
711
+ name: 'order-flow',
712
+ slices: [
713
+ {
714
+ type: 'query',
715
+ name: 'views-order-summary',
716
+ request: `
717
+ query GetOrderSummary {
718
+ orderSummary {
719
+ totalOrders
720
+ mixedStatus
721
+ }
722
+ }
723
+ `,
724
+ client: { specs: [] },
725
+ server: {
726
+ description: '',
727
+ data: {
728
+ items: [
729
+ {
730
+ origin: {
731
+ type: 'projection',
732
+ name: 'OrderSummaryProjection',
733
+ singleton: true,
734
+ },
735
+ target: {
736
+ type: 'State',
737
+ name: 'OrderSummary',
738
+ },
739
+ },
740
+ ],
741
+ },
742
+ specs: [],
743
+ },
744
+ },
745
+ ],
746
+ },
747
+ ],
748
+ messages: [
749
+ {
750
+ type: 'state',
751
+ name: 'OrderSummary',
752
+ fields: [
753
+ { name: 'totalOrders', type: 'number', required: true },
754
+ { name: 'mixedStatus', type: 'number | "pending"', required: true },
755
+ ],
756
+ },
757
+ ],
758
+ };
759
+
760
+ const plans = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
761
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
762
+
763
+ expect(resolverFile?.contents).not.toContain("mixedStatus: 'pending'");
764
+ expect(resolverFile?.contents).toContain('mixedStatus: 0');
765
+ });
766
+
706
767
  it('should handle nested Array<{...}> in embedded ObjectType without breaking', async () => {
707
768
  const spec: SpecsSchema = {
708
769
  variant: 'specs',
@@ -770,5 +831,6 @@ describe('query.resolver.ts.ejs', () => {
770
831
  expect(resolverFile?.contents).toContain('export class WorkoutSessionPerformance');
771
832
  expect(resolverFile?.contents).toContain('exerciseId!: string;');
772
833
  expect(resolverFile?.contents).toContain('sets!: { reps: number; weight: number }[];');
834
+ expect(resolverFile?.contents).toContain('@Field(() => [GraphQLJSON])');
773
835
  });
774
836
  });
@@ -112,7 +112,7 @@ async <%= queryName %>(
112
112
  defaultValue = 'new Date()';
113
113
  } else if (baseType.startsWith('Array<')) {
114
114
  defaultValue = '[]';
115
- } else if (baseType.includes('|') && (baseType.includes('"') || baseType.includes("'"))) {
115
+ } else if (isEnumType(baseType)) {
116
116
  const firstValue = baseType.match(/['"]([^'"]+)['"]/)?.[1] ?? '';
117
117
  defaultValue = `'${firstValue}'`;
118
118
  }