@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
package/src/narrative.ts CHANGED
@@ -14,11 +14,8 @@ import {
14
14
  setSliceData,
15
15
  recordRule,
16
16
  recordExample,
17
- recordGivenData,
18
- recordAndGivenData,
19
- recordWhenData,
20
- recordThenData,
21
- recordAndThenData,
17
+ recordStep,
18
+ recordErrorStep,
22
19
  } from './narrative-context';
23
20
  import type { DataItem } from './types';
24
21
  import createDebug from 'debug';
@@ -26,7 +23,7 @@ import createDebug from 'debug';
26
23
  const debug = createDebug('auto:narrative:narrative');
27
24
  if ('color' in debug && typeof debug === 'object') {
28
25
  (debug as { color: string }).color = '6';
29
- } // cyan
26
+ }
30
27
 
31
28
  export function narrative(name: string, fn: () => void): void;
32
29
  export function narrative(name: string, id: string, fn: () => void): void;
@@ -107,159 +104,112 @@ export function should(idOrTitle: string, title?: string): void {
107
104
  recordIt(hasId ? idOrTitle : undefined, hasId ? title : idOrTitle);
108
105
  }
109
106
 
110
- export function specs(description: string, fn: () => void): void;
107
+ export function specs(feature: string, fn: () => void): void;
111
108
  export function specs(fn: () => void): void;
112
- export function specs(descriptionOrFn: string | (() => void), fn?: () => void): void {
113
- const description = typeof descriptionOrFn === 'string' ? descriptionOrFn : '';
114
- const callback = typeof descriptionOrFn === 'function' ? descriptionOrFn : fn!;
109
+ export function specs(featureOrFn: string | (() => void), fn?: () => void): void {
110
+ const feature = typeof featureOrFn === 'string' ? featureOrFn : '';
111
+ const callback = typeof featureOrFn === 'function' ? featureOrFn : fn!;
115
112
 
116
- pushSpec(description);
113
+ pushSpec(feature);
117
114
  callback();
118
115
  }
119
116
 
120
- export function rule(description: string, fn: () => void): void;
121
- export function rule(description: string, id: string, fn: () => void): void;
122
- export function rule(description: string, idOrFn: string | (() => void), fn?: () => void): void {
117
+ export function rule(name: string, fn: () => void): void;
118
+ export function rule(name: string, id: string, fn: () => void): void;
119
+ export function rule(name: string, idOrFn: string | (() => void), fn?: () => void): void {
123
120
  const id = typeof idOrFn === 'string' ? idOrFn : undefined;
124
- const callback = typeof idOrFn === 'function' ? idOrFn : fn;
125
-
126
- if (!callback) {
127
- throw new Error(`rule() requires a callback function. Got: ${typeof idOrFn}, ${typeof fn}`);
128
- }
129
-
130
- recordRule(description, id);
121
+ const callback = typeof idOrFn === 'function' ? idOrFn : fn!;
122
+ recordRule(name, id);
131
123
  callback();
132
124
  }
133
125
 
134
- export const example = (description: string): TypedExampleBuilder => {
135
- recordExample(description);
136
- return createExampleBuilder();
137
- };
138
-
139
126
  type ExtractData<T> = T extends { data: infer D } ? D : T;
140
- type ContextFor<T> = Partial<Record<keyof ExtractData<T>, string>>;
141
-
142
- function normalizeContext(context?: Partial<Record<string, string>>): Record<string, string> | undefined {
143
- if (!context) return undefined;
144
-
145
- const filtered: Record<string, string> = {};
146
- for (const [key, value] of Object.entries(context)) {
147
- if (value !== undefined) {
148
- filtered[key] = value;
149
- }
150
- }
151
-
152
- return Object.keys(filtered).length > 0 ? filtered : undefined;
153
- }
154
-
155
- interface TypedExampleBuilder {
156
- given<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedGivenBuilder<T>;
157
- when<W>(data: ExtractData<W> | ExtractData<W>[], context?: ContextFor<W>): TypedWhenBuilder<W>;
158
- }
159
127
 
160
- interface TypedGivenBuilder<G> {
161
- and<U>(data: ExtractData<U> | ExtractData<U>[], context?: ContextFor<U>): TypedGivenBuilder<G | U>;
162
- when<W>(data: ExtractData<W> | ExtractData<W>[], context?: ContextFor<W>): TypedGivenWhenBuilder<G, W>;
163
- then<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedGivenThenBuilder<G, never, T>;
128
+ export interface ThenBuilder {
129
+ and<T>(data: ExtractData<T>): ThenBuilder;
164
130
  }
165
131
 
166
- interface TypedWhenBuilder<W> {
167
- then<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedThenBuilder<W, T>;
132
+ export interface WhenBuilder {
133
+ then<T>(data: ExtractData<T>): ThenBuilder;
134
+ and<T>(data: ExtractData<T>): WhenBuilder;
168
135
  }
169
136
 
170
- interface TypedGivenWhenBuilder<G, W> {
171
- then<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedGivenThenBuilder<G, W, T>;
137
+ export interface GivenBuilder {
138
+ and<T>(data: ExtractData<T>): GivenBuilder;
139
+ when<W>(data: ExtractData<W>): WhenBuilder;
140
+ then<T>(data: ExtractData<T>): ThenBuilder;
172
141
  }
173
142
 
174
- interface TypedThenBuilder<W, T> {
175
- and<A>(data: ExtractData<A> | ExtractData<A>[], context?: ContextFor<A>): TypedThenBuilder<W, T | A>;
143
+ export interface ExampleBuilder {
144
+ given<T>(data: ExtractData<T>): GivenBuilder;
145
+ when<W>(data: ExtractData<W>): WhenBuilder;
176
146
  }
177
147
 
178
- interface TypedGivenThenBuilder<G, W, T> {
179
- and<A>(data: ExtractData<A> | ExtractData<A>[], context?: ContextFor<A>): TypedGivenThenBuilder<G, W, T | A>;
148
+ function createThenBuilder(): ThenBuilder {
149
+ return {
150
+ and<T>(data: ExtractData<T>): ThenBuilder {
151
+ recordStep('And', 'InferredType', data);
152
+ return createThenBuilder();
153
+ },
154
+ };
180
155
  }
181
156
 
182
- function createThenBuilder<W, T>(): TypedThenBuilder<W, T> {
157
+ function createWhenBuilder(): WhenBuilder {
183
158
  return {
184
- and<A>(data: ExtractData<A> | ExtractData<A>[], context?: ContextFor<A>): TypedThenBuilder<W, T | A> {
185
- const andItems = Array.isArray(data) ? data : [data];
186
- recordAndThenData(andItems, normalizeContext(context as Partial<Record<string, string>>));
187
- return createThenBuilder<W, T | A>();
159
+ then<T>(data: ExtractData<T>): ThenBuilder {
160
+ recordStep('Then', 'InferredType', data);
161
+ return createThenBuilder();
162
+ },
163
+ and<T>(data: ExtractData<T>): WhenBuilder {
164
+ recordStep('And', 'InferredType', data);
165
+ return createWhenBuilder();
188
166
  },
189
167
  };
190
168
  }
191
169
 
192
- function createGivenBuilder<G>(): TypedGivenBuilder<G> {
170
+ function createGivenBuilder(): GivenBuilder {
193
171
  return {
194
- and<U>(data: ExtractData<U> | ExtractData<U>[], context?: ContextFor<U>): TypedGivenBuilder<G | U> {
195
- const andItems = Array.isArray(data) ? data : [data];
196
- recordAndGivenData(andItems, normalizeContext(context as Partial<Record<string, string>>));
197
- return createGivenBuilder<G | U>();
172
+ and<T>(data: ExtractData<T>): GivenBuilder {
173
+ recordStep('And', 'InferredType', data);
174
+ return createGivenBuilder();
198
175
  },
199
- when<W>(data: ExtractData<W> | ExtractData<W>[], context?: ContextFor<W>): TypedGivenWhenBuilder<G, W> {
200
- const whenData = Array.isArray(data) ? data : [data];
201
- recordWhenData(
202
- whenData.length === 1 ? whenData[0] : whenData,
203
- normalizeContext(context as Partial<Record<string, string>>),
204
- );
205
- return {
206
- then<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedGivenThenBuilder<G, W, T> {
207
- const thenItems = Array.isArray(data) ? data : [data];
208
- recordThenData(thenItems, normalizeContext(context as Partial<Record<string, string>>));
209
- return {
210
- and<A>(
211
- data: ExtractData<A> | ExtractData<A>[],
212
- context?: ContextFor<A>,
213
- ): TypedGivenThenBuilder<G, W, T | A> {
214
- const andItems = Array.isArray(data) ? data : [data];
215
- recordAndThenData(andItems, normalizeContext(context as Partial<Record<string, string>>));
216
- return createThenBuilder<W, T | A>() as TypedGivenThenBuilder<G, W, T | A>;
217
- },
218
- };
219
- },
220
- };
176
+ when<W>(data: ExtractData<W>): WhenBuilder {
177
+ recordStep('When', 'InferredType', data);
178
+ return createWhenBuilder();
221
179
  },
222
- then<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedGivenThenBuilder<G, never, T> {
223
- const thenItems = Array.isArray(data) ? data : [data];
224
- recordThenData(thenItems, normalizeContext(context as Partial<Record<string, string>>));
225
- return {
226
- and<A>(
227
- data: ExtractData<A> | ExtractData<A>[],
228
- context?: ContextFor<A>,
229
- ): TypedGivenThenBuilder<G, never, T | A> {
230
- const andItems = Array.isArray(data) ? data : [data];
231
- recordAndThenData(andItems, normalizeContext(context as Partial<Record<string, string>>));
232
- return createThenBuilder<never, T | A>() as TypedGivenThenBuilder<G, never, T | A>;
233
- },
234
- };
180
+ then<T>(data: ExtractData<T>): ThenBuilder {
181
+ recordStep('Then', 'InferredType', data);
182
+ return createThenBuilder();
235
183
  },
236
184
  };
237
185
  }
238
186
 
239
- function createExampleBuilder(): TypedExampleBuilder {
187
+ function createExampleBuilder(): ExampleBuilder {
240
188
  return {
241
- given<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedGivenBuilder<T> {
242
- const items = Array.isArray(data) ? data : [data];
243
- recordGivenData(items, normalizeContext(context as Partial<Record<string, string>>));
244
- return createGivenBuilder<T>();
189
+ given<T>(data: ExtractData<T>): GivenBuilder {
190
+ recordStep('Given', 'InferredType', data);
191
+ return createGivenBuilder();
245
192
  },
246
- when<W>(data: ExtractData<W> | ExtractData<W>[], context?: ContextFor<W>): TypedWhenBuilder<W> {
247
- const whenData = Array.isArray(data) ? data : [data];
248
- recordWhenData(
249
- whenData.length === 1 ? whenData[0] : whenData,
250
- normalizeContext(context as Partial<Record<string, string>>),
251
- );
252
- return {
253
- then<Z>(data: ExtractData<Z> | ExtractData<Z>[], context?: ContextFor<Z>): TypedThenBuilder<W, Z> {
254
- const thenItems = Array.isArray(data) ? data : [data];
255
- recordThenData(thenItems, normalizeContext(context as Partial<Record<string, string>>));
256
- return createThenBuilder<W, Z>();
257
- },
258
- };
193
+ when<W>(data: ExtractData<W>): WhenBuilder {
194
+ recordStep('When', 'InferredType', data);
195
+ return createWhenBuilder();
259
196
  },
260
197
  };
261
198
  }
262
199
 
200
+ export function example(name: string): ExampleBuilder;
201
+ export function example(name: string, id: string): ExampleBuilder;
202
+ export function example(name: string, id?: string): ExampleBuilder {
203
+ recordExample(name, id);
204
+ return createExampleBuilder();
205
+ }
206
+
207
+ type ErrorType = 'IllegalStateError' | 'ValidationError' | 'NotFoundError';
208
+
209
+ export function thenError(errorType: ErrorType, message?: string): void {
210
+ recordErrorStep(errorType, message);
211
+ }
212
+
263
213
  export const SliceType = {
264
214
  COMMAND: 'command' as const,
265
215
  QUERY: 'query' as const,
@@ -1,4 +1,4 @@
1
- import { flow, specs, rule, example } from '../narrative';
1
+ import { flow, specs, rule, example, thenError } from '../narrative';
2
2
  import { command, query, react } from '../fluent-builder';
3
3
  import { type Event, type Command, type State } from '../types';
4
4
 
@@ -27,6 +27,14 @@ type TestItemState = State<
27
27
  }
28
28
  >;
29
29
 
30
+ type SendNotification = Event<
31
+ 'SendNotification',
32
+ {
33
+ message: string;
34
+ recipientId: string;
35
+ }
36
+ >;
37
+
30
38
  flow('Test Flow with IDs', 'FLOW-001', () => {
31
39
  command('Create test item', 'SLICE-001')
32
40
  .client(() => {})
@@ -46,17 +54,11 @@ flow('Test Flow with IDs', 'FLOW-001', () => {
46
54
  });
47
55
 
48
56
  rule('Invalid test items should be rejected', 'RULE-002', () => {
49
- example('User tries to create item with empty name')
50
- .when<CreateTestItem>({
51
- itemId: 'test_456',
52
- name: '',
53
- })
54
- .then([
55
- {
56
- errorType: 'ValidationError' as const,
57
- message: 'Item name cannot be empty',
58
- },
59
- ]);
57
+ example('User tries to create item with empty name').when<CreateTestItem>({
58
+ itemId: 'test_456',
59
+ name: '',
60
+ });
61
+ thenError('ValidationError', 'Item name cannot be empty');
60
62
  });
61
63
  });
62
64
  });
@@ -84,25 +86,15 @@ flow('Test Flow with IDs', 'FLOW-001', () => {
84
86
  specs('Test event reaction specs', () => {
85
87
  rule('System should react to test item creation', 'RULE-004', () => {
86
88
  example('Notification sent when test item is created')
87
- .when([
88
- {
89
- eventRef: 'TestItemCreated',
90
- exampleData: {
91
- id: 'test_789',
92
- name: 'Another Test Item',
93
- createdAt: new Date('2024-01-16T10:00:00Z'),
94
- },
95
- },
96
- ])
97
- .then([
98
- {
99
- commandRef: 'SendNotification',
100
- exampleData: {
101
- message: 'New test item created: Another Test Item',
102
- recipientId: 'admin',
103
- },
104
- },
105
- ]);
89
+ .when<TestItemCreated>({
90
+ id: 'test_789',
91
+ name: 'Another Test Item',
92
+ createdAt: new Date('2024-01-16T10:00:00Z'),
93
+ })
94
+ .then<SendNotification>({
95
+ message: 'New test item created: Another Test Item',
96
+ recipientId: 'admin',
97
+ });
106
98
  });
107
99
  });
108
100
  });
package/src/schema.ts CHANGED
@@ -141,30 +141,6 @@ const StateSchema = BaseMessageSchema.extend({
141
141
 
142
142
  const MessageSchema = z.discriminatedUnion('type', [CommandSchema, EventSchema, StateSchema]);
143
143
 
144
- export const EventExampleSchema = z
145
- .object({
146
- eventRef: z.string().describe('Reference to event message by name'),
147
- exampleData: z.record(z.unknown()).describe('Example data matching the event schema'),
148
- context: z.record(z.string()).optional().describe('Optional field descriptions and context'),
149
- })
150
- .describe('Event example with reference and data');
151
-
152
- export const CommandExampleSchema = z
153
- .object({
154
- commandRef: z.string().describe('Reference to command message by name'),
155
- exampleData: z.record(z.unknown()).describe('Example data matching the command schema'),
156
- context: z.record(z.string()).optional().describe('Optional field descriptions and context'),
157
- })
158
- .describe('Command example with reference and data');
159
-
160
- const StateExampleSchema = z
161
- .object({
162
- stateRef: z.string().describe('Reference to state message by name'),
163
- exampleData: z.record(z.unknown()).describe('Example data matching the state schema'),
164
- context: z.record(z.string()).optional().describe('Optional field descriptions and context'),
165
- })
166
- .describe('State example with reference and data');
167
-
168
144
  const BaseSliceSchema = z
169
145
  .object({
170
146
  name: z.string(),
@@ -176,44 +152,50 @@ const BaseSliceSchema = z
176
152
  })
177
153
  .describe('Base properties shared by all slice types');
178
154
 
179
- const ErrorExampleSchema = z
180
- .object({
181
- errorType: z.enum(['IllegalStateError', 'ValidationError', 'NotFoundError']).describe('Expected error'),
182
- message: z.string().optional().describe('Optional error message'),
183
- })
184
- .describe('Error outcome');
155
+ const StepErrorSchema = z.object({
156
+ type: z.enum(['IllegalStateError', 'ValidationError', 'NotFoundError']).describe('Error type'),
157
+ message: z.string().optional().describe('Optional error message'),
158
+ });
159
+
160
+ const StepWithDocStringSchema = z.object({
161
+ id: z.string().optional().describe('Optional unique identifier for the step'),
162
+ keyword: z.enum(['Given', 'When', 'Then', 'And']).describe('Gherkin keyword'),
163
+ text: z.string().describe('The type name (e.g., AddTodo, TodoAdded)'),
164
+ docString: z.record(z.unknown()).optional().describe('The example data'),
165
+ });
166
+
167
+ const StepWithErrorSchema = z.object({
168
+ id: z.string().optional().describe('Optional unique identifier for the step'),
169
+ keyword: z.literal('Then').describe('Error steps use Then keyword'),
170
+ error: StepErrorSchema.describe('Error details'),
171
+ });
172
+
173
+ const StepSchema = z.union([StepWithDocStringSchema, StepWithErrorSchema]).describe('A Gherkin step');
185
174
 
186
175
  const ExampleSchema = z
187
176
  .object({
188
- description: z.string().describe('Example description'),
189
- given: z
190
- .array(z.union([EventExampleSchema, StateExampleSchema]))
191
- .optional()
192
- .describe('Given conditions'),
193
- when: z
194
- .union([CommandExampleSchema, EventExampleSchema, z.array(CommandExampleSchema), z.array(EventExampleSchema)])
195
- .optional()
196
- .describe('When action or events occur'),
197
- then: z
198
- .array(z.union([EventExampleSchema, StateExampleSchema, CommandExampleSchema, ErrorExampleSchema]))
199
- .describe('Then expected outcomes'),
177
+ id: z.string().optional().describe('Unique example identifier'),
178
+ name: z.string().describe('Example name'),
179
+ steps: z.array(StepSchema).describe('Gherkin steps for this example'),
200
180
  })
201
- .describe('BDD example with given-when-then structure');
181
+ .describe('BDD example with Gherkin steps');
202
182
 
203
183
  const RuleSchema = z
204
184
  .object({
205
- id: z.string().optional().describe('Optional rule identifier'),
206
- description: z.string().describe('Rule description'),
185
+ id: z.string().optional().describe('Unique rule identifier'),
186
+ name: z.string().describe('Rule name'),
207
187
  examples: z.array(ExampleSchema).describe('Examples demonstrating the rule'),
208
188
  })
209
189
  .describe('Business rule with examples');
210
190
 
211
191
  const SpecSchema = z
212
192
  .object({
213
- name: z.string().describe('Spec name/feature name'),
193
+ id: z.string().optional().describe('Optional unique identifier for the spec'),
194
+ type: z.literal('gherkin').describe('Specification type'),
195
+ feature: z.string().describe('Feature name'),
214
196
  rules: z.array(RuleSchema).describe('Business rules for this spec'),
215
197
  })
216
- .describe('Specification with business rules');
198
+ .describe('Gherkin specification with business rules');
217
199
 
218
200
  const ItNode = z
219
201
  .object({
@@ -252,7 +234,7 @@ const CommandSliceSchema = BaseSliceSchema.extend({
252
234
  server: z.object({
253
235
  description: z.string(),
254
236
  data: z.array(DataSinkSchema).optional().describe('Data sinks for command slices'),
255
- specs: SpecSchema.describe('Server-side specifications with rules and examples'),
237
+ specs: z.array(SpecSchema).describe('Server-side specifications with rules and examples'),
256
238
  }),
257
239
  }).describe('Command slice handling user actions and business logic');
258
240
 
@@ -265,7 +247,7 @@ const QuerySliceSchema = BaseSliceSchema.extend({
265
247
  server: z.object({
266
248
  description: z.string(),
267
249
  data: z.array(DataSourceSchema).optional().describe('Data sources for query slices'),
268
- specs: SpecSchema.describe('Server-side specifications with rules and examples'),
250
+ specs: z.array(SpecSchema).describe('Server-side specifications with rules and examples'),
269
251
  }),
270
252
  }).describe('Query slice for reading data and maintaining projections');
271
253
 
@@ -277,7 +259,7 @@ const ReactSliceSchema = BaseSliceSchema.extend({
277
259
  .array(z.union([DataSinkSchema, DataSourceSchema]))
278
260
  .optional()
279
261
  .describe('Data items for react slices (mix of sinks and sources)'),
280
- specs: SpecSchema.describe('Server-side specifications with rules and examples'),
262
+ specs: z.array(SpecSchema).describe('Server-side specifications with rules and examples'),
281
263
  }),
282
264
  }).describe('React slice for automated responses to events');
283
265
 
@@ -397,9 +379,7 @@ export const modelSchema = z
397
379
  export type { ClientSpecNode };
398
380
 
399
381
  export {
400
- StateExampleSchema,
401
382
  MessageFieldSchema,
402
- ErrorExampleSchema,
403
383
  MessageSchema,
404
384
  CommandSchema,
405
385
  EventSchema,
@@ -416,4 +396,8 @@ export {
416
396
  SpecSchema,
417
397
  DataSinkSchema,
418
398
  DataSourceSchema,
399
+ StepSchema,
400
+ StepErrorSchema,
401
+ StepWithDocStringSchema,
402
+ StepWithErrorSchema,
419
403
  };
@@ -48,7 +48,7 @@ export const createSliceBuilder = (config: SliceConfig = {}): SliceBuilder => ({
48
48
  type: 'command',
49
49
  name,
50
50
  client: { specs: [] },
51
- server: { description: '', specs: { name: '', rules: [] }, data: undefined },
51
+ server: { description: '', specs: [], data: undefined },
52
52
  // Optional fields
53
53
  ...(config.eventStream != null && { stream: config.eventStream }),
54
54
  ...(config.integration && { via: [config.integration.name] }),
@@ -67,7 +67,7 @@ export const createSliceBuilder = (config: SliceConfig = {}): SliceBuilder => ({
67
67
  type: 'query',
68
68
  name,
69
69
  client: { specs: [] },
70
- server: { description: '', specs: { name: '', rules: [] }, data: undefined },
70
+ server: { description: '', specs: [], data: undefined },
71
71
  // Optional fields
72
72
  ...(config.eventStream != null && { stream: config.eventStream }),
73
73
  ...(config.integration && { via: [config.integration.name] }),
@@ -88,7 +88,7 @@ export const createSliceBuilder = (config: SliceConfig = {}): SliceBuilder => ({
88
88
  const slice: ReactSlice = {
89
89
  type: 'react',
90
90
  name,
91
- server: { specs: { name: '', rules: [] }, data: undefined },
91
+ server: { specs: [], data: undefined },
92
92
  // Optional fields
93
93
  ...(config.eventStream != null && { stream: config.eventStream }),
94
94
  ...(config.integration && { via: [config.integration.name] }),