@auto-engineer/narrative 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 (89) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +22 -0
  3. package/dist/src/commands/export-schema-runner.js +1 -1
  4. package/dist/src/commands/export-schema-runner.js.map +1 -1
  5. package/dist/src/fluent-builder.js +3 -3
  6. package/dist/src/fluent-builder.js.map +1 -1
  7. package/dist/src/getNarratives.specs.js +149 -153
  8. package/dist/src/getNarratives.specs.js.map +1 -1
  9. package/dist/src/id/addAutoIds.d.ts.map +1 -1
  10. package/dist/src/id/addAutoIds.js +50 -12
  11. package/dist/src/id/addAutoIds.js.map +1 -1
  12. package/dist/src/id/addAutoIds.specs.js +396 -45
  13. package/dist/src/id/addAutoIds.specs.js.map +1 -1
  14. package/dist/src/id/hasAllIds.d.ts.map +1 -1
  15. package/dist/src/id/hasAllIds.js +28 -5
  16. package/dist/src/id/hasAllIds.js.map +1 -1
  17. package/dist/src/id/hasAllIds.specs.js +407 -214
  18. package/dist/src/id/hasAllIds.specs.js.map +1 -1
  19. package/dist/src/index.d.ts +6 -8
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +3 -3
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/loader/graph.d.ts.map +1 -1
  24. package/dist/src/loader/graph.js +13 -6
  25. package/dist/src/loader/graph.js.map +1 -1
  26. package/dist/src/loader/ts-utils.d.ts +1 -0
  27. package/dist/src/loader/ts-utils.d.ts.map +1 -1
  28. package/dist/src/loader/ts-utils.js +95 -16
  29. package/dist/src/loader/ts-utils.js.map +1 -1
  30. package/dist/src/model-to-narrative.specs.js +531 -449
  31. package/dist/src/model-to-narrative.specs.js.map +1 -1
  32. package/dist/src/narrative-context.d.ts +8 -8
  33. package/dist/src/narrative-context.d.ts.map +1 -1
  34. package/dist/src/narrative-context.js +111 -301
  35. package/dist/src/narrative-context.js.map +1 -1
  36. package/dist/src/narrative-context.specs.js +15 -55
  37. package/dist/src/narrative-context.specs.js.map +1 -1
  38. package/dist/src/narrative.d.ts +19 -22
  39. package/dist/src/narrative.d.ts.map +1 -1
  40. package/dist/src/narrative.js +42 -71
  41. package/dist/src/narrative.js.map +1 -1
  42. package/dist/src/samples/test-with-ids.narrative.js +13 -29
  43. package/dist/src/samples/test-with-ids.narrative.js.map +1 -1
  44. package/dist/src/schema.d.ts +3205 -8287
  45. package/dist/src/schema.d.ts.map +1 -1
  46. package/dist/src/schema.js +29 -47
  47. package/dist/src/schema.js.map +1 -1
  48. package/dist/src/slice-builder.js +3 -3
  49. package/dist/src/slice-builder.js.map +1 -1
  50. package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
  51. package/dist/src/transformers/model-to-narrative/generators/flow.js +118 -74
  52. package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
  53. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts +9 -1
  54. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts.map +1 -1
  55. package/dist/src/transformers/model-to-narrative/generators/gwt.js +112 -112
  56. package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
  57. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts +1 -1
  58. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts.map +1 -1
  59. package/dist/src/transformers/model-to-narrative/generators/imports.js +13 -9
  60. package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
  61. package/dist/src/transformers/narrative-to-model/index.d.ts.map +1 -1
  62. package/dist/src/transformers/narrative-to-model/index.js +50 -23
  63. package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
  64. package/dist/src/transformers/narrative-to-model/type-inference.specs.js +100 -90
  65. package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +1 -1
  66. package/dist/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +5 -5
  68. package/src/commands/export-schema-runner.ts +3 -1
  69. package/src/fluent-builder.ts +3 -3
  70. package/src/getNarratives.specs.ts +168 -176
  71. package/src/id/addAutoIds.specs.ts +424 -47
  72. package/src/id/addAutoIds.ts +57 -13
  73. package/src/id/hasAllIds.specs.ts +400 -223
  74. package/src/id/hasAllIds.ts +32 -6
  75. package/src/index.ts +9 -12
  76. package/src/loader/graph.ts +23 -6
  77. package/src/loader/ts-utils.ts +169 -26
  78. package/src/model-to-narrative.specs.ts +531 -449
  79. package/src/narrative-context.specs.ts +73 -116
  80. package/src/narrative-context.ts +127 -374
  81. package/src/narrative.ts +70 -120
  82. package/src/samples/test-with-ids.narrative.ts +23 -31
  83. package/src/schema.ts +36 -52
  84. package/src/slice-builder.ts +3 -3
  85. package/src/transformers/model-to-narrative/generators/flow.ts +191 -85
  86. package/src/transformers/model-to-narrative/generators/gwt.ts +195 -178
  87. package/src/transformers/model-to-narrative/generators/imports.ts +13 -9
  88. package/src/transformers/narrative-to-model/index.ts +87 -26
  89. package/src/transformers/narrative-to-model/type-inference.specs.ts +100 -90
@@ -35,188 +35,215 @@ export type GWTBlock = {
35
35
  >;
36
36
  };
37
37
 
38
- function buildThenItems(
38
+ function buildStandaloneGivenStatements(
39
39
  ts: typeof import('typescript'),
40
40
  f: tsNS.NodeFactory,
41
41
  g: GWTBlock,
42
42
  messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
43
- ): tsNS.Expression[] {
44
- return g.then.map((t) => {
45
- const item = t as Record<string, unknown>;
46
-
47
- // Handle event references: just return the data (type handled by generic parameter)
48
- if ('eventRef' in item) {
49
- const e = t as { eventRef: string; exampleData: Record<string, unknown> };
50
- const typeInfo = messages ? getFieldTypeInfo(messages, e.eventRef) : undefined;
51
- return jsonToExpr(ts, f, e.exampleData, typeInfo);
52
- }
53
-
54
- // Handle command references: just return the data (type handled by generic parameter)
55
- if ('commandRef' in item) {
56
- const c = t as { commandRef: string; exampleData: Record<string, unknown> };
57
- const typeInfo = messages ? getFieldTypeInfo(messages, c.commandRef) : undefined;
58
- return jsonToExpr(ts, f, c.exampleData, typeInfo);
59
- }
60
-
61
- // Handle state references: just return the data (type handled by generic parameter)
62
- if ('stateRef' in item) {
63
- const s = t as { stateRef: string; exampleData: Record<string, unknown> };
64
- const typeInfo = messages ? getFieldTypeInfo(messages, s.stateRef) : undefined;
65
- return jsonToExpr(ts, f, s.exampleData, typeInfo);
66
- }
67
-
68
- // Handle error objects: { errorType: 'ValidationError', message: '...' }
69
- if ('errorType' in item) {
70
- const err = t as { errorType: 'IllegalStateError' | 'ValidationError' | 'NotFoundError'; message?: string };
71
- return f.createObjectLiteralExpression(
72
- [
73
- f.createPropertyAssignment('errorType', f.createStringLiteral(err.errorType)),
74
- ...(err.message !== null && err.message !== undefined
75
- ? [f.createPropertyAssignment('message', f.createStringLiteral(err.message))]
76
- : []),
77
- ],
78
- false,
79
- );
80
- }
81
-
82
- return f.createNull();
83
- });
84
- }
43
+ ): tsNS.Statement[] {
44
+ const statements: tsNS.Statement[] = [];
85
45
 
86
- function getDescriptions(
87
- g: GWTBlock & { description?: string; ruleDescription?: string; exampleDescription?: string },
88
- ) {
89
- const ruleDesc =
90
- g.ruleDescription !== null && g.ruleDescription !== undefined && g.ruleDescription !== ''
91
- ? g.ruleDescription
92
- : 'Generated rule description';
93
-
94
- const exampleDesc =
95
- g.exampleDescription !== null && g.exampleDescription !== undefined && g.exampleDescription !== ''
96
- ? g.exampleDescription
97
- : g.description !== null && g.description !== undefined && g.description !== ''
98
- ? g.description
99
- : 'Generated example description';
100
-
101
- return { ruleDesc, exampleDesc };
102
- }
103
-
104
- function addGivenToChain(
105
- ts: typeof import('typescript'),
106
- f: tsNS.NodeFactory,
107
- exampleChain: tsNS.Expression,
108
- g: GWTBlock,
109
- messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
110
- ): tsNS.Expression {
111
46
  if (hasGivenEvents(g) && g.given !== null && g.given !== undefined && g.given.length > 0) {
112
- // Start with the first given
113
47
  const firstGiven = g.given[0];
114
48
  const firstTypeInfo = messages ? getFieldTypeInfo(messages, firstGiven.eventRef) : undefined;
115
- let currentChain = f.createCallExpression(
116
- f.createPropertyAccessExpression(exampleChain, 'given'),
117
- [f.createTypeReferenceNode(firstGiven.eventRef, undefined)],
118
- [jsonToExpr(ts, f, firstGiven.exampleData, firstTypeInfo)],
49
+ statements.push(
50
+ f.createExpressionStatement(
51
+ f.createCallExpression(
52
+ f.createIdentifier('given'),
53
+ [f.createTypeReferenceNode(firstGiven.eventRef, undefined)],
54
+ [jsonToExpr(ts, f, firstGiven.exampleData, firstTypeInfo)],
55
+ ),
56
+ ),
119
57
  );
120
58
 
121
- // Chain additional givens with .and()
122
59
  for (let i = 1; i < g.given.length; i++) {
123
60
  const givenEvent = g.given[i];
124
61
  const typeInfo = messages ? getFieldTypeInfo(messages, givenEvent.eventRef) : undefined;
125
- currentChain = f.createCallExpression(
126
- f.createPropertyAccessExpression(currentChain, 'and'),
127
- [f.createTypeReferenceNode(givenEvent.eventRef, undefined)],
128
- [jsonToExpr(ts, f, givenEvent.exampleData, typeInfo)],
62
+ statements.push(
63
+ f.createExpressionStatement(
64
+ f.createCallExpression(
65
+ f.createIdentifier('and'),
66
+ [f.createTypeReferenceNode(givenEvent.eventRef, undefined)],
67
+ [jsonToExpr(ts, f, givenEvent.exampleData, typeInfo)],
68
+ ),
69
+ ),
129
70
  );
130
71
  }
131
-
132
- return currentChain;
133
72
  }
134
- return exampleChain;
73
+
74
+ return statements;
135
75
  }
136
76
 
137
- function addCommandWhenToChain(
77
+ function createWhenCallExpression(
138
78
  ts: typeof import('typescript'),
139
79
  f: tsNS.NodeFactory,
140
- exampleChain: tsNS.Expression,
141
- when: { commandRef: string; exampleData: Record<string, unknown> },
80
+ typeRef: string,
81
+ exampleData: Record<string, unknown>,
142
82
  messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
143
- ): tsNS.Expression {
144
- const typeInfo = messages ? getFieldTypeInfo(messages, when.commandRef) : undefined;
145
- return f.createCallExpression(
146
- f.createPropertyAccessExpression(exampleChain, 'when'),
147
- when.commandRef ? [f.createTypeReferenceNode(when.commandRef, undefined)] : undefined,
148
- [jsonToExpr(ts, f, when.exampleData, typeInfo)],
83
+ ): tsNS.Statement {
84
+ const typeInfo = messages !== undefined ? getFieldTypeInfo(messages, typeRef) : undefined;
85
+ return f.createExpressionStatement(
86
+ f.createCallExpression(
87
+ f.createIdentifier('when'),
88
+ typeRef !== '' ? [f.createTypeReferenceNode(typeRef, undefined)] : undefined,
89
+ [jsonToExpr(ts, f, exampleData, typeInfo)],
90
+ ),
149
91
  );
150
92
  }
151
93
 
152
- function addEventWhenToChain(
94
+ function buildCommandWhenStatement(
153
95
  ts: typeof import('typescript'),
154
96
  f: tsNS.NodeFactory,
155
- exampleChain: tsNS.Expression,
156
- firstEvent: { eventRef: string; exampleData: Record<string, unknown> },
97
+ when: { commandRef: string; exampleData: Record<string, unknown> },
157
98
  messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
158
- ): tsNS.Expression {
159
- const typeInfo = messages ? getFieldTypeInfo(messages, firstEvent.eventRef) : undefined;
160
- return f.createCallExpression(
161
- f.createPropertyAccessExpression(exampleChain, 'when'),
162
- firstEvent.eventRef ? [f.createTypeReferenceNode(firstEvent.eventRef, undefined)] : undefined,
163
- [jsonToExpr(ts, f, firstEvent.exampleData, typeInfo)],
164
- );
99
+ ): tsNS.Statement {
100
+ return createWhenCallExpression(ts, f, when.commandRef, when.exampleData, messages);
165
101
  }
166
102
 
167
- function isEmptyEventWhen(when: { eventRef: string; exampleData: Record<string, unknown> }): boolean {
168
- return (!when.eventRef || when.eventRef === '') && Object.keys(when.exampleData).length === 0;
103
+ function buildEventWhenStatement(
104
+ ts: typeof import('typescript'),
105
+ f: tsNS.NodeFactory,
106
+ event: { eventRef: string; exampleData: Record<string, unknown> },
107
+ messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
108
+ ): tsNS.Statement {
109
+ return createWhenCallExpression(ts, f, event.eventRef, event.exampleData, messages);
169
110
  }
170
111
 
171
- function isEmptyCommandWhen(when: { commandRef: string; exampleData: Record<string, unknown> }): boolean {
172
- return (!when.commandRef || when.commandRef === '') && Object.keys(when.exampleData).length === 0;
112
+ function isQueryWithSingleEvent(
113
+ sliceKind: string,
114
+ when: GWTBlock['when'],
115
+ ): when is { eventRef: string; exampleData: Record<string, unknown> } {
116
+ return sliceKind === 'query' && when !== undefined && !Array.isArray(when) && 'eventRef' in when;
173
117
  }
174
118
 
175
- function isEmptyWhen(whenData: GWTBlock['when']): boolean {
176
- if (!whenData) return true;
177
- if (Array.isArray(whenData) && whenData.length === 0) return true;
178
- if (typeof whenData === 'object' && 'eventRef' in whenData) {
179
- return isEmptyEventWhen(whenData as { eventRef: string; exampleData: Record<string, unknown> });
180
- }
181
- if (typeof whenData === 'object' && 'commandRef' in whenData) {
182
- return isEmptyCommandWhen(whenData as { commandRef: string; exampleData: Record<string, unknown> });
183
- }
184
- return false;
119
+ function isReactOrQueryWithEvents(sliceKind: string): boolean {
120
+ return sliceKind === 'react' || sliceKind === 'query';
185
121
  }
186
122
 
187
- function processWhenForSliceKind(
123
+ function buildStandaloneWhenStatement(
188
124
  ts: typeof import('typescript'),
189
125
  f: tsNS.NodeFactory,
190
- exampleChain: tsNS.Expression,
191
- when: GWTBlock['when'],
126
+ g: GWTBlock,
192
127
  sliceKind: 'command' | 'react' | 'query' | 'experience',
193
128
  messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
194
- ): tsNS.Expression {
129
+ ): tsNS.Statement | null {
130
+ if (isEmptyWhen(g.when)) {
131
+ return null;
132
+ }
133
+
134
+ const when = g.when;
195
135
  if (sliceKind === 'command' && isWhenCommand(when)) {
196
- return addCommandWhenToChain(ts, f, exampleChain, when, messages);
136
+ return buildCommandWhenStatement(ts, f, when, messages);
197
137
  }
198
- if ((sliceKind === 'react' || sliceKind === 'query') && isWhenEvents(when)) {
199
- return addEventWhenToChain(ts, f, exampleChain, when[0], messages);
138
+ if (isReactOrQueryWithEvents(sliceKind) && isWhenEvents(when) && when.length > 0) {
139
+ return buildEventWhenStatement(ts, f, when[0], messages);
200
140
  }
201
- if (sliceKind === 'query' && when && !Array.isArray(when) && 'eventRef' in when) {
202
- const whenEvent = when as { eventRef: string; exampleData: Record<string, unknown> };
203
- return addEventWhenToChain(ts, f, exampleChain, whenEvent, messages);
141
+ if (isQueryWithSingleEvent(sliceKind, when)) {
142
+ return buildEventWhenStatement(ts, f, when, messages);
204
143
  }
205
- return exampleChain;
144
+ return null;
206
145
  }
207
146
 
208
- function addWhenToChain(
147
+ function buildStandaloneThenStatement(
209
148
  ts: typeof import('typescript'),
210
149
  f: tsNS.NodeFactory,
211
- exampleChain: tsNS.Expression,
212
150
  g: GWTBlock,
213
- sliceKind: 'command' | 'react' | 'query' | 'experience',
214
151
  messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
152
+ ): tsNS.Statement {
153
+ const firstThenItem = g.then[0];
154
+ const thenTypeRef = getThenTypeRef(firstThenItem);
155
+ const typeInfo = messages && thenTypeRef ? getFieldTypeInfo(messages, thenTypeRef) : undefined;
156
+ const thenArg = buildThenItem(ts, f, firstThenItem, typeInfo);
157
+ const thenTypeParams = thenTypeRef ? [f.createTypeReferenceNode(thenTypeRef, undefined)] : undefined;
158
+
159
+ return f.createExpressionStatement(f.createCallExpression(f.createIdentifier('then'), thenTypeParams, [thenArg]));
160
+ }
161
+
162
+ function buildThenItem(
163
+ ts: typeof import('typescript'),
164
+ f: tsNS.NodeFactory,
165
+ t: GWTBlock['then'][0],
166
+ typeInfo?: FieldTypeInfo,
215
167
  ): tsNS.Expression {
216
- if (isEmptyWhen(g.when)) {
217
- return exampleChain;
168
+ const item = t as Record<string, unknown>;
169
+
170
+ if ('eventRef' in item) {
171
+ const e = t as { eventRef: string; exampleData: Record<string, unknown> };
172
+ return jsonToExpr(ts, f, e.exampleData, typeInfo);
218
173
  }
219
- return processWhenForSliceKind(ts, f, exampleChain, g.when, sliceKind, messages);
174
+
175
+ if ('commandRef' in item) {
176
+ const c = t as { commandRef: string; exampleData: Record<string, unknown> };
177
+ return jsonToExpr(ts, f, c.exampleData, typeInfo);
178
+ }
179
+
180
+ if ('stateRef' in item) {
181
+ const s = t as { stateRef: string; exampleData: Record<string, unknown> };
182
+ return jsonToExpr(ts, f, s.exampleData, typeInfo);
183
+ }
184
+
185
+ if ('errorType' in item) {
186
+ const err = t as { errorType: 'IllegalStateError' | 'ValidationError' | 'NotFoundError'; message?: string };
187
+ return f.createObjectLiteralExpression(
188
+ [
189
+ f.createPropertyAssignment('errorType', f.createStringLiteral(err.errorType)),
190
+ ...(err.message !== null && err.message !== undefined
191
+ ? [f.createPropertyAssignment('message', f.createStringLiteral(err.message))]
192
+ : []),
193
+ ],
194
+ false,
195
+ );
196
+ }
197
+
198
+ return f.createNull();
199
+ }
200
+
201
+ function getDescriptions(
202
+ g: GWTBlock & { description?: string; ruleDescription?: string; exampleDescription?: string },
203
+ ) {
204
+ const ruleDesc =
205
+ g.ruleDescription !== null && g.ruleDescription !== undefined && g.ruleDescription !== ''
206
+ ? g.ruleDescription
207
+ : 'Generated rule description';
208
+
209
+ const exampleDesc =
210
+ g.exampleDescription !== null && g.exampleDescription !== undefined && g.exampleDescription !== ''
211
+ ? g.exampleDescription
212
+ : g.description !== null && g.description !== undefined && g.description !== ''
213
+ ? g.description
214
+ : 'Generated example description';
215
+
216
+ return { ruleDesc, exampleDesc };
217
+ }
218
+
219
+ function isEmptyEventWhen(when: { eventRef: string; exampleData: Record<string, unknown> }): boolean {
220
+ return Object.keys(when.exampleData).length === 0;
221
+ }
222
+
223
+ function isEmptyCommandWhen(when: { commandRef: string; exampleData: Record<string, unknown> }): boolean {
224
+ return (!when.commandRef || when.commandRef === '') && Object.keys(when.exampleData).length === 0;
225
+ }
226
+
227
+ function isEmptyWhen(whenData: GWTBlock['when']): boolean {
228
+ if (!whenData) return true;
229
+ if (Array.isArray(whenData)) {
230
+ if (whenData.length === 0) return true;
231
+ const firstItem = whenData[0];
232
+ if ('eventRef' in firstItem) {
233
+ return isEmptyEventWhen(firstItem as { eventRef: string; exampleData: Record<string, unknown> });
234
+ }
235
+ if ('commandRef' in firstItem) {
236
+ return isEmptyCommandWhen(firstItem as { commandRef: string; exampleData: Record<string, unknown> });
237
+ }
238
+ return false;
239
+ }
240
+ if (typeof whenData === 'object' && 'eventRef' in whenData) {
241
+ return isEmptyEventWhen(whenData as { eventRef: string; exampleData: Record<string, unknown> });
242
+ }
243
+ if (typeof whenData === 'object' && 'commandRef' in whenData) {
244
+ return isEmptyCommandWhen(whenData as { commandRef: string; exampleData: Record<string, unknown> });
245
+ }
246
+ return false;
220
247
  }
221
248
 
222
249
  function getThenTypeRef(firstThenItem: GWTBlock['then'][0]): string {
@@ -230,50 +257,37 @@ function getThenTypeRef(firstThenItem: GWTBlock['then'][0]): string {
230
257
  return '';
231
258
  }
232
259
 
233
- function addThenToChain(
234
- ts: typeof import('typescript'),
235
- f: tsNS.NodeFactory,
236
- exampleChain: tsNS.Expression,
237
- g: GWTBlock,
238
- messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
239
- ): tsNS.Expression {
240
- const thenItems = buildThenItems(ts, f, g, messages);
241
- const firstThenItem = g.then[0];
242
- const thenTypeRef = getThenTypeRef(firstThenItem);
243
-
244
- // Always use single object format for .then()
245
- const thenArg = thenItems[0];
246
- const thenTypeParams = thenTypeRef ? [f.createTypeReferenceNode(thenTypeRef, undefined)] : undefined;
247
-
248
- return f.createCallExpression(f.createPropertyAccessExpression(exampleChain, 'then'), thenTypeParams, [thenArg]);
249
- }
250
-
251
260
  export function buildGwtSpecBlock(
252
261
  ts: typeof import('typescript'),
253
262
  f: tsNS.NodeFactory,
254
263
  g: GWTBlock & { description?: string; ruleDescription?: string; exampleDescription?: string; ruleId?: string },
255
264
  sliceKind: 'command' | 'react' | 'query' | 'experience',
265
+ messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
256
266
  ): tsNS.Statement {
257
267
  const { ruleDesc, exampleDesc } = getDescriptions(g);
258
268
 
259
- // Build the example chain: example('desc').given().when().then()
260
- let exampleChain: tsNS.Expression = f.createCallExpression(f.createIdentifier('example'), undefined, [
269
+ const bodyStatements: tsNS.Statement[] = [];
270
+ bodyStatements.push(...buildStandaloneGivenStatements(ts, f, g, messages));
271
+ const whenStmt = buildStandaloneWhenStatement(ts, f, g, sliceKind, messages);
272
+ if (whenStmt) {
273
+ bodyStatements.push(whenStmt);
274
+ }
275
+ bodyStatements.push(buildStandaloneThenStatement(ts, f, g, messages));
276
+
277
+ const exampleCall = f.createCallExpression(f.createIdentifier('example'), undefined, [
261
278
  f.createStringLiteral(exampleDesc),
279
+ f.createArrowFunction(
280
+ undefined,
281
+ undefined,
282
+ [],
283
+ undefined,
284
+ f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
285
+ f.createBlock(bodyStatements, true),
286
+ ),
262
287
  ]);
263
288
 
264
- // Add .given() if present
265
- exampleChain = addGivenToChain(ts, f, exampleChain, g);
266
-
267
- // Add .when()
268
- exampleChain = addWhenToChain(ts, f, exampleChain, g, sliceKind);
269
-
270
- // Add .then()
271
- exampleChain = addThenToChain(ts, f, exampleChain, g);
272
-
273
- // Create the rule() call containing the example
274
289
  const ruleArgs: tsNS.Expression[] = [f.createStringLiteral(ruleDesc)];
275
290
 
276
- // Add rule ID if provided
277
291
  if (g.ruleId !== null && g.ruleId !== undefined) {
278
292
  ruleArgs.push(f.createStringLiteral(g.ruleId));
279
293
  }
@@ -285,7 +299,7 @@ export function buildGwtSpecBlock(
285
299
  [],
286
300
  undefined,
287
301
  f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
288
- f.createBlock([f.createExpressionStatement(exampleChain)], true),
302
+ f.createBlock([f.createExpressionStatement(exampleCall)], true),
289
303
  ),
290
304
  );
291
305
 
@@ -323,33 +337,36 @@ export function buildConsolidatedGwtSpecBlock(
323
337
  sliceKind: 'command' | 'react' | 'query' | 'experience',
324
338
  messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
325
339
  ): tsNS.Statement {
326
- // Build example chains for each GWT block
327
340
  const exampleStatements: tsNS.Statement[] = [];
328
341
 
329
342
  for (const g of gwtBlocks) {
330
343
  const { exampleDesc } = getDescriptions(g);
331
344
 
332
- // Build the example chain: example('desc').given().when().then()
333
- let exampleChain: tsNS.Expression = f.createCallExpression(f.createIdentifier('example'), undefined, [
345
+ const bodyStatements: tsNS.Statement[] = [];
346
+ bodyStatements.push(...buildStandaloneGivenStatements(ts, f, g, messages));
347
+ const whenStmt = buildStandaloneWhenStatement(ts, f, g, sliceKind, messages);
348
+ if (whenStmt) {
349
+ bodyStatements.push(whenStmt);
350
+ }
351
+ bodyStatements.push(buildStandaloneThenStatement(ts, f, g, messages));
352
+
353
+ const exampleCall = f.createCallExpression(f.createIdentifier('example'), undefined, [
334
354
  f.createStringLiteral(exampleDesc),
355
+ f.createArrowFunction(
356
+ undefined,
357
+ undefined,
358
+ [],
359
+ undefined,
360
+ f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
361
+ f.createBlock(bodyStatements, true),
362
+ ),
335
363
  ]);
336
364
 
337
- // Add .given() if present
338
- exampleChain = addGivenToChain(ts, f, exampleChain, g, messages);
339
-
340
- // Add .when()
341
- exampleChain = addWhenToChain(ts, f, exampleChain, g, sliceKind, messages);
342
-
343
- // Add .then()
344
- exampleChain = addThenToChain(ts, f, exampleChain, g, messages);
345
-
346
- exampleStatements.push(f.createExpressionStatement(exampleChain));
365
+ exampleStatements.push(f.createExpressionStatement(exampleCall));
347
366
  }
348
367
 
349
- // Create the rule() call containing all examples
350
368
  const ruleArgs: tsNS.Expression[] = [f.createStringLiteral(rule.description)];
351
369
 
352
- // Add rule ID if provided
353
370
  if (rule.id !== null && rule.id !== undefined) {
354
371
  ruleArgs.push(f.createStringLiteral(rule.id));
355
372
  }
@@ -1,20 +1,24 @@
1
1
  type BuildImportsOpts = { flowImport: string; integrationImport: string };
2
2
 
3
3
  export const ALL_FLOW_FUNCTION_NAMES = [
4
+ 'and',
4
5
  'command',
5
- 'query',
6
- 'react',
7
- 'experience',
8
- 'narrative',
6
+ 'data',
9
7
  'describe',
10
- 'it',
11
- 'specs',
12
- 'rule',
13
8
  'example',
9
+ 'experience',
10
+ 'given',
14
11
  'gql',
15
- 'source',
16
- 'data',
12
+ 'it',
13
+ 'narrative',
14
+ 'query',
15
+ 'react',
16
+ 'rule',
17
17
  'sink',
18
+ 'source',
19
+ 'specs',
20
+ 'then',
21
+ 'when',
18
22
  ] as const;
19
23
 
20
24
  export function buildImports(
@@ -136,39 +136,100 @@ function getServerSpecs(slice: Slice) {
136
136
  return undefined;
137
137
  }
138
138
 
139
+ interface StepWithDocString {
140
+ keyword: 'Given' | 'When' | 'Then' | 'And';
141
+ text: string;
142
+ docString?: Record<string, unknown>;
143
+ }
144
+
145
+ interface StepWithError {
146
+ keyword: 'Then';
147
+ error: { type: string; message?: string };
148
+ }
149
+
150
+ type Step = StepWithDocString | StepWithError;
151
+
152
+ function isStepWithError(step: Step): step is StepWithError {
153
+ return 'error' in step;
154
+ }
155
+
156
+ function processSteps(
157
+ steps: Step[],
158
+ slice: Slice,
159
+ resolveTypeAndInfo: TypeResolver,
160
+ messages: Map<string, Message>,
161
+ exampleShapeHints: ExampleShapeHints,
162
+ ): void {
163
+ let effectiveKeyword: 'Given' | 'When' | 'Then' = 'Given';
164
+
165
+ for (const step of steps) {
166
+ if (isStepWithError(step)) {
167
+ continue;
168
+ }
169
+
170
+ const keyword = step.keyword;
171
+ if (keyword !== 'And') {
172
+ effectiveKeyword = keyword;
173
+ }
174
+
175
+ const refItem = {
176
+ eventRef: step.text,
177
+ commandRef: step.text,
178
+ stateRef: step.text,
179
+ exampleData: step.docString,
180
+ };
181
+
182
+ if (effectiveKeyword === 'Given') {
183
+ processGiven([refItem], resolveTypeAndInfo, messages, exampleShapeHints);
184
+ } else if (effectiveKeyword === 'When') {
185
+ processWhen([refItem], slice, resolveTypeAndInfo, messages, exampleShapeHints);
186
+ } else if (effectiveKeyword === 'Then') {
187
+ processThen([refItem], resolveTypeAndInfo, messages, exampleShapeHints);
188
+ }
189
+ }
190
+ }
191
+
192
+ interface SpecWithRules {
193
+ rules: Array<{ examples: Array<{ steps?: Step[] }> }>;
194
+ }
195
+
196
+ function isSpecWithRules(spec: unknown): spec is SpecWithRules {
197
+ return (
198
+ typeof spec === 'object' && spec !== null && 'rules' in spec && Array.isArray((spec as { rules: unknown[] }).rules)
199
+ );
200
+ }
201
+
202
+ function processRuleExamples(
203
+ rule: { examples: Array<{ steps?: Step[] }> },
204
+ slice: Slice,
205
+ resolveTypeAndInfo: TypeResolver,
206
+ messages: Map<string, Message>,
207
+ exampleShapeHints: ExampleShapeHints,
208
+ ): void {
209
+ if (!Array.isArray(rule.examples)) return;
210
+ for (const example of rule.examples) {
211
+ if (Array.isArray(example.steps)) {
212
+ processSteps(example.steps, slice, resolveTypeAndInfo, messages, exampleShapeHints);
213
+ }
214
+ }
215
+ }
216
+
139
217
  function processSliceSpecs(
140
218
  slice: Slice,
141
219
  resolveTypeAndInfo: TypeResolver,
142
220
  messages: Map<string, Message>,
143
221
  exampleShapeHints: ExampleShapeHints,
144
222
  ): void {
145
- const serverSpec = getServerSpecs(slice);
146
-
147
- if (serverSpec !== undefined && Array.isArray(serverSpec.rules)) {
148
- serverSpec.rules.forEach((rule: unknown) => {
149
- if (
150
- typeof rule === 'object' &&
151
- rule !== null &&
152
- 'examples' in rule &&
153
- Array.isArray((rule as { examples: unknown[] }).examples)
154
- ) {
155
- const ruleObj = rule as { examples: { given?: unknown; when?: unknown; then?: unknown }[] };
156
- ruleObj.examples.forEach((example) => {
157
- if (example.given !== undefined && example.given !== null) {
158
- const givenArray = Array.isArray(example.given) ? example.given : [example.given];
159
- processGiven(givenArray, resolveTypeAndInfo, messages, exampleShapeHints);
160
- }
161
- if (example.when !== undefined && example.when !== null) {
162
- const whenArray = Array.isArray(example.when) ? example.when : [example.when];
163
- processWhen(whenArray, slice, resolveTypeAndInfo, messages, exampleShapeHints);
164
- }
165
- if (example.then !== undefined && example.then !== null) {
166
- const thenArray = Array.isArray(example.then) ? example.then : [example.then];
167
- processThen(thenArray, resolveTypeAndInfo, messages, exampleShapeHints);
168
- }
169
- });
223
+ const serverSpecs = getServerSpecs(slice);
224
+
225
+ if (serverSpecs === undefined || !Array.isArray(serverSpecs)) return;
226
+
227
+ for (const spec of serverSpecs) {
228
+ if (isSpecWithRules(spec)) {
229
+ for (const rule of spec.rules) {
230
+ processRuleExamples(rule, slice, resolveTypeAndInfo, messages, exampleShapeHints);
170
231
  }
171
- });
232
+ }
172
233
  }
173
234
  }
174
235