@auto-engineer/server-generator-apollo-emmett 0.13.0 → 0.13.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 (92) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/dist/src/codegen/extract/commands.d.ts +7 -14
  4. package/dist/src/codegen/extract/commands.d.ts.map +1 -1
  5. package/dist/src/codegen/extract/commands.js.map +1 -1
  6. package/dist/src/codegen/extract/data-sink.d.ts +1 -1
  7. package/dist/src/codegen/extract/data-sink.d.ts.map +1 -1
  8. package/dist/src/codegen/extract/data-sink.js +9 -31
  9. package/dist/src/codegen/extract/data-sink.js.map +1 -1
  10. package/dist/src/codegen/extract/events.d.ts +4 -8
  11. package/dist/src/codegen/extract/events.d.ts.map +1 -1
  12. package/dist/src/codegen/extract/events.js.map +1 -1
  13. package/dist/src/codegen/extract/gwt.d.ts +2 -2
  14. package/dist/src/codegen/extract/gwt.d.ts.map +1 -1
  15. package/dist/src/codegen/extract/gwt.js +6 -21
  16. package/dist/src/codegen/extract/gwt.js.map +1 -1
  17. package/dist/src/codegen/extract/messages.d.ts +4 -4
  18. package/dist/src/codegen/extract/messages.d.ts.map +1 -1
  19. package/dist/src/codegen/extract/messages.js +24 -47
  20. package/dist/src/codegen/extract/messages.js.map +1 -1
  21. package/dist/src/codegen/extract/query.d.ts +5 -7
  22. package/dist/src/codegen/extract/query.d.ts.map +1 -1
  23. package/dist/src/codegen/extract/query.js +10 -8
  24. package/dist/src/codegen/extract/query.js.map +1 -1
  25. package/dist/src/codegen/extract/slice-normalizer.d.ts +29 -0
  26. package/dist/src/codegen/extract/slice-normalizer.d.ts.map +1 -0
  27. package/dist/src/codegen/extract/slice-normalizer.js +48 -0
  28. package/dist/src/codegen/extract/slice-normalizer.js.map +1 -0
  29. package/dist/src/codegen/extract/step-converter.d.ts +17 -0
  30. package/dist/src/codegen/extract/step-converter.d.ts.map +1 -0
  31. package/dist/src/codegen/extract/step-converter.js +82 -0
  32. package/dist/src/codegen/extract/step-converter.js.map +1 -0
  33. package/dist/src/codegen/extract/step-types.d.ts +30 -0
  34. package/dist/src/codegen/extract/step-types.d.ts.map +1 -0
  35. package/dist/src/codegen/extract/step-types.js +16 -0
  36. package/dist/src/codegen/extract/step-types.js.map +1 -0
  37. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
  38. package/dist/src/codegen/scaffoldFromSchema.js +7 -29
  39. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
  40. package/dist/src/codegen/templates/command/commands.specs.ts +33 -28
  41. package/dist/src/codegen/templates/command/decide.specs.specs.ts +153 -138
  42. package/dist/src/codegen/templates/command/decide.specs.ts +169 -146
  43. package/dist/src/codegen/templates/command/events.specs.ts +43 -38
  44. package/dist/src/codegen/templates/command/evolve.specs.ts +38 -33
  45. package/dist/src/codegen/templates/command/handle.specs.ts +71 -61
  46. package/dist/src/codegen/templates/command/mutation.resolver.specs.ts +122 -102
  47. package/dist/src/codegen/templates/command/register.specs.ts +39 -34
  48. package/dist/src/codegen/templates/command/state.specs.ts +39 -34
  49. package/dist/src/codegen/templates/query/projection.specs.specs.ts +399 -359
  50. package/dist/src/codegen/templates/query/projection.specs.ts +242 -216
  51. package/dist/src/codegen/templates/query/projection.specs.ts.ejs +38 -12
  52. package/dist/src/codegen/templates/query/query.resolver.specs.ts +59 -51
  53. package/dist/src/codegen/templates/query/state.specs.ts +1 -1
  54. package/dist/src/codegen/templates/react/react.specs.specs.ts +93 -85
  55. package/dist/src/codegen/templates/react/react.specs.ts +135 -122
  56. package/dist/src/codegen/templates/react/register.specs.ts +135 -122
  57. package/dist/src/codegen/test-data/specVariant1.d.ts.map +1 -1
  58. package/dist/src/codegen/test-data/specVariant1.js +101 -90
  59. package/dist/src/codegen/test-data/specVariant1.js.map +1 -1
  60. package/dist/src/codegen/types.d.ts +5 -7
  61. package/dist/src/codegen/types.d.ts.map +1 -1
  62. package/dist/tsconfig.tsbuildinfo +1 -1
  63. package/package.json +4 -4
  64. package/src/codegen/extract/commands.ts +7 -8
  65. package/src/codegen/extract/data-sink.ts +16 -43
  66. package/src/codegen/extract/events.ts +4 -5
  67. package/src/codegen/extract/gwt.ts +12 -29
  68. package/src/codegen/extract/messages.ts +43 -70
  69. package/src/codegen/extract/query.ts +14 -12
  70. package/src/codegen/extract/slice-normalizer.ts +88 -0
  71. package/src/codegen/extract/step-converter.ts +124 -0
  72. package/src/codegen/extract/step-types.ts +52 -0
  73. package/src/codegen/scaffoldFromSchema.ts +8 -45
  74. package/src/codegen/templates/command/commands.specs.ts +33 -28
  75. package/src/codegen/templates/command/decide.specs.specs.ts +153 -138
  76. package/src/codegen/templates/command/decide.specs.ts +169 -146
  77. package/src/codegen/templates/command/events.specs.ts +43 -38
  78. package/src/codegen/templates/command/evolve.specs.ts +38 -33
  79. package/src/codegen/templates/command/handle.specs.ts +71 -61
  80. package/src/codegen/templates/command/mutation.resolver.specs.ts +122 -102
  81. package/src/codegen/templates/command/register.specs.ts +39 -34
  82. package/src/codegen/templates/command/state.specs.ts +39 -34
  83. package/src/codegen/templates/query/projection.specs.specs.ts +399 -359
  84. package/src/codegen/templates/query/projection.specs.ts +242 -216
  85. package/src/codegen/templates/query/projection.specs.ts.ejs +38 -12
  86. package/src/codegen/templates/query/query.resolver.specs.ts +59 -51
  87. package/src/codegen/templates/query/state.specs.ts +1 -1
  88. package/src/codegen/templates/react/react.specs.specs.ts +93 -85
  89. package/src/codegen/templates/react/react.specs.ts +135 -122
  90. package/src/codegen/templates/react/register.specs.ts +135 -122
  91. package/src/codegen/test-data/specVariant1.ts +101 -90
  92. package/src/codegen/types.ts +6 -4
package/package.json CHANGED
@@ -31,8 +31,8 @@
31
31
  "graphql-type-json": "^0.3.2",
32
32
  "uuid": "^11.0.0",
33
33
  "web-streams-polyfill": "^4.1.0",
34
- "@auto-engineer/message-bus": "0.13.0",
35
- "@auto-engineer/narrative": "0.13.0"
34
+ "@auto-engineer/message-bus": "0.13.2",
35
+ "@auto-engineer/narrative": "0.13.2"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
@@ -43,9 +43,9 @@
43
43
  "typescript": "^5.8.3",
44
44
  "vitest": "^3.2.4",
45
45
  "tsx": "^4.19.2",
46
- "@auto-engineer/cli": "0.13.0"
46
+ "@auto-engineer/cli": "0.13.2"
47
47
  },
48
- "version": "0.13.0",
48
+ "version": "0.13.2",
49
49
  "scripts": {
50
50
  "generate:server": "tsx src/cli/index.ts",
51
51
  "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",
@@ -1,5 +1,4 @@
1
- import { CommandExample, EventExample } from '@auto-engineer/narrative';
2
- import { Message, MessageDefinition } from '../types';
1
+ import type { Message, MessageDefinition, EventRef, CommandRef, ErrorRef } from '../types';
3
2
  import { extractFieldsFromMessage } from './fields';
4
3
 
5
4
  function createCommandMessage(
@@ -22,9 +21,9 @@ function createCommandMessage(
22
21
 
23
22
  export function extractCommandsFromGwt(
24
23
  gwtSpecs: Array<{
25
- given?: Array<EventExample | unknown>;
26
- when?: CommandExample | EventExample | unknown[];
27
- then: Array<EventExample | unknown | { errorType: string; message?: string }>;
24
+ given?: Array<EventRef | unknown>;
25
+ when?: CommandRef | EventRef | unknown[];
26
+ then: Array<EventRef | unknown | ErrorRef>;
28
27
  }>,
29
28
  allMessages: MessageDefinition[],
30
29
  ): { commands: Message[]; commandSchemasByName: Record<string, Message> } {
@@ -85,9 +84,9 @@ function processCommandExample(
85
84
 
86
85
  export function extractCommandsFromThen(
87
86
  gwtSpecs: Array<{
88
- given?: Array<EventExample | unknown>;
89
- when?: CommandExample | EventExample | unknown[];
90
- then: Array<EventExample | unknown | { errorType: string; message?: string }>;
87
+ given?: Array<EventRef | unknown>;
88
+ when?: CommandRef | EventRef | unknown[];
89
+ then: Array<EventRef | unknown | ErrorRef>;
91
90
  }>,
92
91
  allMessages: MessageDefinition[],
93
92
  ): { commands: Message[]; commandSchemasByName: Record<string, Message> } {
@@ -1,37 +1,26 @@
1
- import { CommandExample, Slice, type Example } from '@auto-engineer/narrative';
1
+ import type { Slice } from '@auto-engineer/narrative';
2
+ import type { CommandRef, ErrorRef } from '../types';
3
+ import { extractGwtSpecsFromSlice, type GwtResult } from './step-converter';
2
4
 
3
5
  function resolveStreamId(stream: string, exampleData: Record<string, unknown>): string {
4
6
  return stream.replace(/\$\{([^}]+)\}/g, (_, key: string) => String(exampleData?.[key] ?? 'unknown'));
5
7
  }
6
8
 
7
- function extractExampleDataFromReact(firstSpec: { when: unknown }): Record<string, unknown> {
9
+ function extractExampleDataFromEventWhen(firstSpec: GwtResult): Record<string, unknown> {
8
10
  if (Array.isArray(firstSpec.when)) {
9
- const firstWhen = firstSpec.when[0] as { exampleData?: Record<string, unknown> } | undefined;
10
- return typeof firstWhen?.exampleData === 'object' && firstWhen.exampleData !== null ? firstWhen.exampleData : {};
11
+ const firstWhen = firstSpec.when[0];
12
+ return firstWhen?.exampleData ?? {};
11
13
  }
12
14
  return {};
13
15
  }
14
16
 
15
- function extractExampleDataFromCommand(firstSpec: { then: unknown }): Record<string, unknown> {
16
- const then = firstSpec.then as (CommandExample | { errorType: string })[];
17
- const firstExample = then.find((t): t is CommandExample => 'exampleData' in t);
18
- return typeof firstExample?.exampleData === 'object' && firstExample.exampleData !== null
19
- ? firstExample.exampleData
20
- : {};
17
+ function extractExampleDataFromCommand(firstSpec: GwtResult): Record<string, unknown> {
18
+ const then = firstSpec.then as (CommandRef | ErrorRef)[];
19
+ const firstExample = then.find((t): t is CommandRef => 'exampleData' in t);
20
+ return firstExample?.exampleData ?? {};
21
21
  }
22
22
 
23
- function extractExampleDataFromQuery(firstSpec: { when: unknown }): Record<string, unknown> {
24
- if (Array.isArray(firstSpec.when)) {
25
- const firstWhen = firstSpec.when[0] as { exampleData?: Record<string, unknown> } | undefined;
26
- return typeof firstWhen?.exampleData === 'object' && firstWhen.exampleData !== null ? firstWhen.exampleData : {};
27
- }
28
- return {};
29
- }
30
-
31
- function extractExampleDataFromSpecs(
32
- slice: Slice,
33
- gwtSpecs: Array<{ given?: unknown; when: unknown; then: unknown }>,
34
- ): Record<string, unknown> {
23
+ function extractExampleDataFromSpecs(slice: Slice, gwtSpecs: GwtResult[]): Record<string, unknown> {
35
24
  if (gwtSpecs.length === 0) {
36
25
  return {};
37
26
  }
@@ -39,31 +28,15 @@ function extractExampleDataFromSpecs(
39
28
  const firstSpec = gwtSpecs[0];
40
29
  switch (slice.type) {
41
30
  case 'react':
42
- return extractExampleDataFromReact(firstSpec);
31
+ case 'query':
32
+ return extractExampleDataFromEventWhen(firstSpec);
43
33
  case 'command':
44
34
  return extractExampleDataFromCommand(firstSpec);
45
- case 'query':
46
- return extractExampleDataFromQuery(firstSpec);
47
35
  default:
48
36
  return {};
49
37
  }
50
38
  }
51
39
 
52
- function extractGwtSpecs(slice: Slice) {
53
- if (!('server' in slice)) return [];
54
- const specs = slice.server?.specs;
55
- const rules = specs?.rules;
56
- return Array.isArray(rules) && rules.length > 0
57
- ? rules.flatMap((rule) =>
58
- rule.examples.map((example: Example) => ({
59
- given: example.given,
60
- when: example.when,
61
- then: example.then,
62
- })),
63
- )
64
- : [];
65
- }
66
-
67
40
  function isValidStreamSink(item: unknown): item is { destination: { pattern: string } } {
68
41
  return (
69
42
  typeof item === 'object' &&
@@ -91,11 +64,11 @@ function processStreamSink(item: unknown, exampleData: Record<string, unknown>)
91
64
 
92
65
  export function getStreamFromSink(slice: Slice): { streamPattern?: string; streamId?: string } {
93
66
  if (!('server' in slice)) return {};
94
- const gwtSpecs = extractGwtSpecs(slice);
95
- const exampleData = extractExampleDataFromSpecs(slice, gwtSpecs);
96
- if (!('server' in slice) || slice.server == null || !('data' in slice.server) || !Array.isArray(slice.server.data)) {
67
+ if (slice.server == null || !('data' in slice.server) || !Array.isArray(slice.server.data)) {
97
68
  return {};
98
69
  }
70
+ const gwtSpecs = extractGwtSpecsFromSlice(slice);
71
+ const exampleData = extractExampleDataFromSpecs(slice, gwtSpecs);
99
72
  const serverData = slice.server.data;
100
73
 
101
74
  for (const item of serverData) {
@@ -1,7 +1,6 @@
1
- import { EventExample } from '@auto-engineer/narrative';
2
- import { Message, MessageDefinition } from '../types';
1
+ import type { Message, MessageDefinition, EventRef, ErrorRef } from '../types';
3
2
  import { extractFieldsFromMessage } from './fields';
4
- import { ReactGwtSpec } from './messages';
3
+ import type { ReactGwtSpec } from './messages';
5
4
 
6
5
  function createEventMessage(
7
6
  eventRef: string | undefined,
@@ -27,7 +26,7 @@ function createEventMessage(
27
26
  }
28
27
 
29
28
  export function extractEventsFromThen(
30
- thenItems: Array<EventExample | { errorType: string; message?: string }>,
29
+ thenItems: Array<EventRef | ErrorRef>,
31
30
  allMessages: MessageDefinition[],
32
31
  currentSliceName?: string,
33
32
  currentFlowName?: string,
@@ -41,7 +40,7 @@ export function extractEventsFromThen(
41
40
  }
42
41
 
43
42
  export function extractEventsFromGiven(
44
- givenEvents: EventExample[] | undefined,
43
+ givenEvents: EventRef[] | undefined,
45
44
  allMessages: MessageDefinition[],
46
45
  currentSliceName?: string,
47
46
  currentFlowName?: string,
@@ -1,52 +1,35 @@
1
- import { Slice, CommandExample, EventExample, StateExample, Example } from '@auto-engineer/narrative';
2
- import { GwtCondition } from '../types';
1
+ import type { Slice } from '@auto-engineer/narrative';
2
+ import type { GwtCondition, CommandRef, EventRef } from '../types';
3
+ import { extractGwtSpecsFromSlice, type GwtConditionWithRule } from './step-converter';
3
4
 
4
5
  export function buildCommandGwtMapping(slice: Slice): Record<string, (GwtCondition & { failingFields?: string[] })[]> {
5
6
  if (slice.type !== 'command') {
6
7
  return {};
7
8
  }
8
9
 
9
- const gwtSpecs = extractGwtSpecs(slice);
10
+ const gwtSpecs = extractGwtSpecsFromSlice(slice);
10
11
  const mapping = buildCommandMapping(gwtSpecs);
11
12
  return enhanceMapping(mapping);
12
13
  }
13
14
 
14
- function extractGwtSpecs(slice: Slice) {
15
- if (!('server' in slice)) return [];
16
- const specs = slice.server?.specs;
17
- const rules = specs?.rules;
18
- return Array.isArray(rules) && rules.length > 0
19
- ? rules.flatMap((rule) =>
20
- rule.examples.map((example: Example) => ({
21
- given: example.given,
22
- when: example.when,
23
- then: example.then,
24
- description: example.description,
25
- ruleDescription: rule.description,
26
- })),
27
- )
28
- : [];
15
+ function isCommandRef(when: CommandRef | EventRef[]): when is CommandRef {
16
+ return !Array.isArray(when) && 'commandRef' in when;
29
17
  }
30
18
 
31
- function buildCommandMapping(
32
- gwtSpecs: Array<{ given: unknown; when: unknown; then: unknown; description?: string; ruleDescription?: string }>,
33
- ) {
19
+ function buildCommandMapping(gwtSpecs: GwtConditionWithRule[]) {
34
20
  const mapping: Record<string, GwtCondition[]> = {};
35
21
 
36
22
  for (const gwt of gwtSpecs) {
37
- let command: string | undefined;
38
- if (Array.isArray(gwt.when)) {
23
+ if (!isCommandRef(gwt.when)) {
39
24
  continue;
40
- } else {
41
- const whenCmd = gwt.when as { commandRef?: string } | undefined;
42
- command = whenCmd?.commandRef;
43
25
  }
26
+ const command = gwt.when.commandRef;
44
27
  if (typeof command === 'string' && command.length > 0) {
45
28
  mapping[command] = mapping[command] ?? [];
46
29
  mapping[command].push({
47
- given: gwt.given as Array<EventExample | StateExample> | undefined,
48
- when: gwt.when as CommandExample | EventExample[],
49
- then: gwt.then as Array<EventExample | StateExample | CommandExample | { errorType: string; message?: string }>,
30
+ given: gwt.given,
31
+ when: gwt.when,
32
+ then: gwt.then,
50
33
  description: gwt.description,
51
34
  ruleDescription: gwt.ruleDescription,
52
35
  });
@@ -1,10 +1,11 @@
1
1
  import { extractCommandsFromGwt, extractCommandsFromThen } from './commands';
2
- import { CommandExample, EventExample, Slice } from '@auto-engineer/narrative';
3
- import { Message, MessageDefinition } from '../types';
2
+ import type { Slice } from '@auto-engineer/narrative';
3
+ import type { Message, MessageDefinition, EventRef, CommandRef } from '../types';
4
4
  import { extractEventsFromGiven, extractEventsFromThen, extractEventsFromWhen } from './events';
5
5
  import { extractFieldsFromMessage } from './fields';
6
6
  import { extractProjectionIdField, extractProjectionSingleton } from './projection';
7
7
  import { extractStatesFromData, extractStatesFromTarget } from './states';
8
+ import { extractGwtFromSpecs } from './step-converter';
8
9
  import createDebug from 'debug';
9
10
 
10
11
  const debug = createDebug('auto:server-generator-apollo-emmett:extract:messages');
@@ -23,8 +24,8 @@ export interface ExtractedMessages {
23
24
  }
24
25
 
25
26
  export interface ReactGwtSpec {
26
- when?: EventExample[];
27
- then: CommandExample[];
27
+ when?: EventRef[];
28
+ then: CommandRef[];
28
29
  }
29
30
 
30
31
  const EMPTY_EXTRACTED_MESSAGES: ExtractedMessages = {
@@ -59,17 +60,12 @@ function extractMessagesForCommand(slice: Slice, allMessages: MessageDefinition[
59
60
  }
60
61
 
61
62
  const specs = slice.server?.specs;
62
- const rules = specs?.rules;
63
- const gwtSpecs =
64
- Array.isArray(rules) && rules.length > 0
65
- ? rules.flatMap((rule) =>
66
- rule.examples.map((example) => ({
67
- given: example.given,
68
- when: example.when,
69
- then: example.then,
70
- })),
71
- )
72
- : [];
63
+ if (!Array.isArray(specs) || specs.length === 0) {
64
+ debugCommand(' No specs found, returning empty');
65
+ return EMPTY_EXTRACTED_MESSAGES;
66
+ }
67
+
68
+ const gwtSpecs = extractGwtFromSpecs(specs, 'command');
73
69
  debugCommand(' Found %d GWT specs', gwtSpecs.length);
74
70
 
75
71
  const { commands, commandSchemasByName } = extractCommandsFromGwt(gwtSpecs, allMessages);
@@ -77,10 +73,9 @@ function extractMessagesForCommand(slice: Slice, allMessages: MessageDefinition[
77
73
  debugCommand(' Command schemas: %o', Object.keys(commandSchemasByName));
78
74
 
79
75
  const events: Message[] = gwtSpecs.flatMap((gwt): Message[] => {
80
- const givenEventsOnly = gwt.given?.filter((item): item is EventExample => 'eventRef' in item);
81
- const givenEvents = extractEventsFromGiven(givenEventsOnly, allMessages, slice.name);
76
+ const givenEvents = extractEventsFromGiven(gwt.given, allMessages, slice.name);
82
77
 
83
- const thenEventsOnly = gwt.then.filter((item): item is EventExample => 'eventRef' in item);
78
+ const thenEventsOnly = gwt.then.filter((item): item is EventRef => 'eventRef' in item);
84
79
  const thenEvents = extractEventsFromThen(thenEventsOnly, allMessages, slice.name);
85
80
  debugCommand(' GWT: given=%d events, then=%d events', givenEvents.length, thenEvents.length);
86
81
  return [...givenEvents, ...thenEvents];
@@ -107,17 +102,9 @@ function extractMessagesForQuery(slice: Slice, allMessages: MessageDefinition[])
107
102
  }
108
103
 
109
104
  const specs = slice.server?.specs;
110
- const rules = specs?.rules;
111
- const gwtSpecs =
112
- Array.isArray(rules) && rules.length > 0
113
- ? rules.flatMap((rule) =>
114
- rule.examples.map((example) => ({
115
- given: example.given,
116
- when: example.when,
117
- then: example.then,
118
- })),
119
- )
120
- : [];
105
+ const hasSpecs = Array.isArray(specs) && specs.length > 0;
106
+
107
+ const gwtSpecs = hasSpecs ? extractGwtFromSpecs(specs, 'query') : [];
121
108
  debugQuery(' Found %d GWT specs', gwtSpecs.length);
122
109
 
123
110
  const projectionIdField = extractProjectionIdField(slice);
@@ -127,35 +114,21 @@ function extractMessagesForQuery(slice: Slice, allMessages: MessageDefinition[])
127
114
  debugQuery(' Projection singleton: %s', projectionSingleton);
128
115
 
129
116
  const events: Message[] = gwtSpecs.flatMap((gwt) => {
130
- const eventsFromGiven = Array.isArray(gwt.given)
131
- ? gwt.given.filter((item): item is EventExample => 'eventRef' in item)
132
- : [];
133
- let eventsFromWhen: EventExample[] = [];
134
- if (Array.isArray(gwt.when)) {
135
- eventsFromWhen = gwt.when.filter((item): item is EventExample => 'eventRef' in item);
136
- } else if (gwt.when != null && typeof gwt.when === 'object' && 'eventRef' in gwt.when) {
137
- // when is a single object, convert to array
138
- const whenItem = gwt.when as EventExample;
139
- if (whenItem.eventRef && whenItem.eventRef.trim() !== '') {
140
- eventsFromWhen = [whenItem];
141
- }
142
- }
143
- const givenEvents = extractEventsFromGiven(eventsFromGiven, allMessages);
144
- const whenEvents = eventsFromWhen
145
- .map((eventExample) => {
146
- const fields = extractFieldsFromMessage(eventExample.eventRef, 'event', allMessages);
147
- const messageDef = allMessages.find((m) => m.type === 'event' && m.name === eventExample.eventRef);
148
- const metadata = messageDef?.metadata as { sourceFlowName?: string; sourceSliceName?: string } | undefined;
149
-
150
- return {
151
- type: eventExample.eventRef,
152
- fields,
153
- source: 'when' as const, // Mark as 'when' source for query events
154
- sourceFlowName: metadata?.sourceFlowName,
155
- sourceSliceName: metadata?.sourceSliceName,
156
- } as Message;
157
- })
158
- .filter((event): event is Message => event.type !== undefined);
117
+ const eventsFromWhen: EventRef[] = Array.isArray(gwt.when) ? gwt.when : [];
118
+ const givenEvents = extractEventsFromGiven(gwt.given, allMessages);
119
+ const whenEvents: Message[] = eventsFromWhen.map((eventExample) => {
120
+ const fields = extractFieldsFromMessage(eventExample.eventRef, 'event', allMessages);
121
+ const messageDef = allMessages.find((m) => m.type === 'event' && m.name === eventExample.eventRef);
122
+ const metadata = messageDef?.metadata as { sourceFlowName?: string; sourceSliceName?: string } | undefined;
123
+
124
+ return {
125
+ type: eventExample.eventRef,
126
+ fields,
127
+ source: 'when',
128
+ sourceFlowName: metadata?.sourceFlowName,
129
+ sourceSliceName: metadata?.sourceSliceName,
130
+ };
131
+ });
159
132
 
160
133
  return [...givenEvents, ...whenEvents];
161
134
  });
@@ -186,20 +159,20 @@ function extractMessagesForReact(slice: Slice, allMessages: MessageDefinition[])
186
159
  }
187
160
 
188
161
  const specs = slice.server?.specs;
189
- const rules = specs?.rules;
190
- const gwtSpecs =
191
- Array.isArray(rules) && rules.length > 0
192
- ? rules.flatMap((rule) =>
193
- rule.examples.map((example) => ({
194
- given: example.given,
195
- when: example.when,
196
- then: example.then,
197
- })),
198
- )
199
- : [];
162
+ if (!Array.isArray(specs) || specs.length === 0) {
163
+ debugReact(' No specs found, returning empty');
164
+ return EMPTY_EXTRACTED_MESSAGES;
165
+ }
166
+
167
+ const gwtSpecs = extractGwtFromSpecs(specs, 'react');
200
168
  debugReact(' Found %d GWT specs', gwtSpecs.length);
201
169
 
202
- const events = extractEventsFromWhen(gwtSpecs as ReactGwtSpec[], allMessages);
170
+ const reactGwtSpecs: ReactGwtSpec[] = gwtSpecs.map((gwt) => ({
171
+ when: Array.isArray(gwt.when) ? gwt.when : undefined,
172
+ then: gwt.then.filter((item): item is CommandRef => 'commandRef' in item),
173
+ }));
174
+
175
+ const events = extractEventsFromWhen(reactGwtSpecs, allMessages);
203
176
  debugReact(' Extracted %d events from when', events.length);
204
177
 
205
178
  const { commands, commandSchemasByName } = extractCommandsFromThen(gwtSpecs, allMessages);
@@ -1,29 +1,31 @@
1
- import { EventExample, Slice } from '@auto-engineer/narrative';
1
+ import type { Slice } from '@auto-engineer/narrative';
2
+ import type { EventRef, StateRef } from '../types';
3
+ import { extractGwtFromSpecs } from './step-converter';
2
4
 
3
5
  interface QueryGwtCondition {
4
6
  description: string;
5
- given?: EventExample[];
6
- when: EventExample[];
7
- then: Array<{ stateRef: string; exampleData: Record<string, unknown> }>;
7
+ given?: EventRef[];
8
+ when: EventRef[];
9
+ then: Array<StateRef>;
8
10
  }
9
11
 
10
12
  export function buildQueryGwtMapping(slice: Slice): QueryGwtCondition[] {
11
13
  if (slice.type !== 'query') return [];
12
14
 
13
15
  const specs = slice.server?.specs;
14
- const rules = specs?.rules;
16
+ if (!Array.isArray(specs) || specs.length === 0) return [];
15
17
 
16
- const examples = Array.isArray(rules) && rules.length > 0 ? rules.flatMap((rule) => rule.examples) : [];
18
+ const gwtResults = extractGwtFromSpecs(specs, 'query');
17
19
 
18
- return examples.map((ex) => {
19
- const givenEvents = Array.isArray(ex.given) ? ex.given.filter((i): i is EventExample => 'eventRef' in i) : [];
20
- const whenEvents = Array.isArray(ex.when) ? ex.when.filter((i): i is EventExample => 'eventRef' in i) : [];
20
+ return gwtResults.map((gwt) => {
21
+ const whenEvents: EventRef[] = Array.isArray(gwt.when) ? gwt.when : [];
22
+ const thenStates = gwt.then.filter((i): i is StateRef => 'stateRef' in i);
21
23
 
22
24
  return {
23
- description: ex.description,
24
- given: givenEvents.length > 0 ? givenEvents : undefined,
25
+ description: gwt.description,
26
+ given: gwt.given.length > 0 ? gwt.given : undefined,
25
27
  when: whenEvents,
26
- then: ex.then.filter((i): i is { stateRef: string; exampleData: Record<string, unknown> } => 'stateRef' in i),
28
+ then: thenStates,
27
29
  };
28
30
  });
29
31
  }
@@ -0,0 +1,88 @@
1
+ import type { Slice, Spec } from '@auto-engineer/narrative';
2
+ import {
3
+ type CommandRef,
4
+ type EventRef,
5
+ type StateRef,
6
+ type ErrorRef,
7
+ type SliceType,
8
+ getSliceType,
9
+ extractGwtFromSpecs,
10
+ type GwtConditionWithRule,
11
+ } from './step-converter';
12
+
13
+ interface NormalizedExample {
14
+ description: string;
15
+ given: EventRef[];
16
+ when: CommandRef | EventRef[];
17
+ then: Array<EventRef | StateRef | CommandRef | ErrorRef>;
18
+ }
19
+
20
+ interface NormalizedRule {
21
+ description: string;
22
+ examples: NormalizedExample[];
23
+ }
24
+
25
+ interface NormalizedSpecs {
26
+ name: string;
27
+ rules: NormalizedRule[];
28
+ }
29
+
30
+ function gwtToNormalizedExample(gwt: GwtConditionWithRule): NormalizedExample {
31
+ return {
32
+ description: gwt.description,
33
+ given: gwt.given,
34
+ when: gwt.when,
35
+ then: gwt.then,
36
+ };
37
+ }
38
+
39
+ function groupGwtByRule(gwtResults: GwtConditionWithRule[]): Map<string, GwtConditionWithRule[]> {
40
+ const grouped = new Map<string, GwtConditionWithRule[]>();
41
+ for (const gwt of gwtResults) {
42
+ const existing = grouped.get(gwt.ruleDescription) ?? [];
43
+ existing.push(gwt);
44
+ grouped.set(gwt.ruleDescription, existing);
45
+ }
46
+ return grouped;
47
+ }
48
+
49
+ function normalizeSpecsForTemplate(specs: Spec[], sliceType: SliceType): NormalizedSpecs | null {
50
+ if (specs.length === 0) return null;
51
+
52
+ const gwtResults = extractGwtFromSpecs(specs, sliceType);
53
+ const groupedByRule = groupGwtByRule(gwtResults);
54
+
55
+ const featureName = specs[0]?.feature ?? '';
56
+
57
+ const rules: NormalizedRule[] = Array.from(groupedByRule.entries()).map(([ruleName, gwts]) => ({
58
+ description: ruleName,
59
+ examples: gwts.map(gwtToNormalizedExample),
60
+ }));
61
+
62
+ return {
63
+ name: featureName,
64
+ rules,
65
+ };
66
+ }
67
+
68
+ type SliceWithServer = Slice & { server?: { specs?: Spec[] } };
69
+ type NormalizedSlice<T extends SliceWithServer> = Omit<T, 'server'> & {
70
+ server?: Omit<T['server'], 'specs'> & { specs?: NormalizedSpecs | null };
71
+ };
72
+
73
+ export function normalizeSliceForTemplate<T extends SliceWithServer>(slice: T): NormalizedSlice<T> {
74
+ if (!('server' in slice) || !slice.server?.specs) {
75
+ return slice as NormalizedSlice<T>;
76
+ }
77
+
78
+ const sliceType = getSliceType(slice);
79
+ const normalizedSpecs = normalizeSpecsForTemplate(slice.server.specs, sliceType);
80
+
81
+ return {
82
+ ...slice,
83
+ server: {
84
+ ...slice.server,
85
+ specs: normalizedSpecs,
86
+ },
87
+ } as NormalizedSlice<T>;
88
+ }
@@ -0,0 +1,124 @@
1
+ import type { Example, Spec, Slice } from '@auto-engineer/narrative';
2
+ import {
3
+ type SliceType,
4
+ type MajorKeyword,
5
+ type CommandRef,
6
+ type EventRef,
7
+ type StateRef,
8
+ type ErrorRef,
9
+ isStepWithDocString,
10
+ isStepWithError,
11
+ resolveMajorKeyword,
12
+ getSliceType,
13
+ } from './step-types';
14
+
15
+ export type { CommandRef, EventRef, StateRef, ErrorRef, SliceType };
16
+ export { getSliceType };
17
+
18
+ export interface GwtResult {
19
+ given: EventRef[];
20
+ when: CommandRef | EventRef[];
21
+ then: Array<EventRef | StateRef | CommandRef | ErrorRef>;
22
+ description: string;
23
+ }
24
+
25
+ interface ParsedSteps {
26
+ given: EventRef[];
27
+ whenItems: Array<{ text: string; exampleData: Record<string, unknown> }>;
28
+ thenItems: Array<{ text: string; exampleData: Record<string, unknown> }>;
29
+ thenErrors: ErrorRef[];
30
+ }
31
+
32
+ function parseSteps(example: Example): ParsedSteps {
33
+ const given: EventRef[] = [];
34
+ const whenItems: Array<{ text: string; exampleData: Record<string, unknown> }> = [];
35
+ const thenItems: Array<{ text: string; exampleData: Record<string, unknown> }> = [];
36
+ const thenErrors: ErrorRef[] = [];
37
+
38
+ let effectiveKeyword: MajorKeyword = 'Given';
39
+
40
+ for (const step of example.steps) {
41
+ if (isStepWithError(step)) {
42
+ thenErrors.push({ errorType: step.error.type, message: step.error.message });
43
+ continue;
44
+ }
45
+
46
+ if (!isStepWithDocString(step)) continue;
47
+
48
+ effectiveKeyword = resolveMajorKeyword(step.keyword, effectiveKeyword);
49
+ const exampleData = step.docString ?? {};
50
+
51
+ if (effectiveKeyword === 'Given') {
52
+ given.push({ eventRef: step.text, exampleData });
53
+ } else if (effectiveKeyword === 'When') {
54
+ whenItems.push({ text: step.text, exampleData });
55
+ } else if (effectiveKeyword === 'Then') {
56
+ thenItems.push({ text: step.text, exampleData });
57
+ }
58
+ }
59
+
60
+ return { given, whenItems, thenItems, thenErrors };
61
+ }
62
+
63
+ function buildGwtResult(sliceType: SliceType, parsed: ParsedSteps, description: string): GwtResult {
64
+ const { given, whenItems, thenItems, thenErrors } = parsed;
65
+
66
+ if (sliceType === 'command') {
67
+ return {
68
+ given,
69
+ when: { commandRef: whenItems[0]?.text ?? '', exampleData: whenItems[0]?.exampleData ?? {} },
70
+ then: [...thenItems.map((t) => ({ eventRef: t.text, exampleData: t.exampleData })), ...thenErrors],
71
+ description,
72
+ };
73
+ }
74
+
75
+ if (sliceType === 'react') {
76
+ return {
77
+ given,
78
+ when: whenItems.map((w) => ({ eventRef: w.text, exampleData: w.exampleData })),
79
+ then: [...thenItems.map((t) => ({ commandRef: t.text, exampleData: t.exampleData })), ...thenErrors],
80
+ description,
81
+ };
82
+ }
83
+
84
+ return {
85
+ given,
86
+ when: whenItems.map((w) => ({ eventRef: w.text, exampleData: w.exampleData })),
87
+ then: [...thenItems.map((t) => ({ stateRef: t.text, exampleData: t.exampleData })), ...thenErrors],
88
+ description,
89
+ };
90
+ }
91
+
92
+ export function stepsToGwt(example: Example, sliceType: SliceType): GwtResult {
93
+ const parsed = parseSteps(example);
94
+ return buildGwtResult(sliceType, parsed, example.name);
95
+ }
96
+
97
+ export interface GwtConditionWithRule extends GwtResult {
98
+ ruleDescription: string;
99
+ }
100
+
101
+ export function extractGwtFromSpecs(specs: Spec[], sliceType: SliceType): GwtConditionWithRule[] {
102
+ const results: GwtConditionWithRule[] = [];
103
+
104
+ for (const spec of specs) {
105
+ for (const rule of spec.rules) {
106
+ for (const example of rule.examples) {
107
+ const gwt = stepsToGwt(example, sliceType);
108
+ results.push({
109
+ ...gwt,
110
+ ruleDescription: rule.name,
111
+ });
112
+ }
113
+ }
114
+ }
115
+
116
+ return results;
117
+ }
118
+
119
+ export function extractGwtSpecsFromSlice(slice: Slice): GwtConditionWithRule[] {
120
+ if (!('server' in slice)) return [];
121
+ const specs = slice.server?.specs;
122
+ if (!Array.isArray(specs) || specs.length === 0) return [];
123
+ return extractGwtFromSpecs(specs, getSliceType(slice));
124
+ }