@auto-engineer/narrative 0.13.0 → 0.13.1

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 +11 -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 +23 -10
  11. package/dist/src/id/addAutoIds.js.map +1 -1
  12. package/dist/src/id/addAutoIds.specs.js +54 -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 +8 -3
  16. package/dist/src/id/hasAllIds.js.map +1 -1
  17. package/dist/src/id/hasAllIds.specs.js +142 -215
  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 +2704 -8293
  45. package/dist/src/schema.d.ts.map +1 -1
  46. package/dist/src/schema.js +26 -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 +54 -47
  72. package/src/id/addAutoIds.ts +28 -11
  73. package/src/id/hasAllIds.specs.ts +147 -245
  74. package/src/id/hasAllIds.ts +11 -4
  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 +33 -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,47 @@ 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
+ keyword: z.enum(['Given', 'When', 'Then', 'And']).describe('Gherkin keyword'),
162
+ text: z.string().describe('The type name (e.g., AddTodo, TodoAdded)'),
163
+ docString: z.record(z.unknown()).optional().describe('The example data'),
164
+ });
165
+
166
+ const StepWithErrorSchema = z.object({
167
+ keyword: z.literal('Then').describe('Error steps use Then keyword'),
168
+ error: StepErrorSchema.describe('Error details'),
169
+ });
170
+
171
+ const StepSchema = z.union([StepWithDocStringSchema, StepWithErrorSchema]).describe('A Gherkin step');
185
172
 
186
173
  const ExampleSchema = z
187
174
  .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'),
175
+ id: z.string().optional().describe('Unique example identifier'),
176
+ name: z.string().describe('Example name'),
177
+ steps: z.array(StepSchema).describe('Gherkin steps for this example'),
200
178
  })
201
- .describe('BDD example with given-when-then structure');
179
+ .describe('BDD example with Gherkin steps');
202
180
 
203
181
  const RuleSchema = z
204
182
  .object({
205
- id: z.string().optional().describe('Optional rule identifier'),
206
- description: z.string().describe('Rule description'),
183
+ id: z.string().optional().describe('Unique rule identifier'),
184
+ name: z.string().describe('Rule name'),
207
185
  examples: z.array(ExampleSchema).describe('Examples demonstrating the rule'),
208
186
  })
209
187
  .describe('Business rule with examples');
210
188
 
211
189
  const SpecSchema = z
212
190
  .object({
213
- name: z.string().describe('Spec name/feature name'),
191
+ type: z.literal('gherkin').describe('Specification type'),
192
+ feature: z.string().describe('Feature name'),
214
193
  rules: z.array(RuleSchema).describe('Business rules for this spec'),
215
194
  })
216
- .describe('Specification with business rules');
195
+ .describe('Gherkin specification with business rules');
217
196
 
218
197
  const ItNode = z
219
198
  .object({
@@ -252,7 +231,7 @@ const CommandSliceSchema = BaseSliceSchema.extend({
252
231
  server: z.object({
253
232
  description: z.string(),
254
233
  data: z.array(DataSinkSchema).optional().describe('Data sinks for command slices'),
255
- specs: SpecSchema.describe('Server-side specifications with rules and examples'),
234
+ specs: z.array(SpecSchema).describe('Server-side specifications with rules and examples'),
256
235
  }),
257
236
  }).describe('Command slice handling user actions and business logic');
258
237
 
@@ -265,7 +244,7 @@ const QuerySliceSchema = BaseSliceSchema.extend({
265
244
  server: z.object({
266
245
  description: z.string(),
267
246
  data: z.array(DataSourceSchema).optional().describe('Data sources for query slices'),
268
- specs: SpecSchema.describe('Server-side specifications with rules and examples'),
247
+ specs: z.array(SpecSchema).describe('Server-side specifications with rules and examples'),
269
248
  }),
270
249
  }).describe('Query slice for reading data and maintaining projections');
271
250
 
@@ -277,7 +256,7 @@ const ReactSliceSchema = BaseSliceSchema.extend({
277
256
  .array(z.union([DataSinkSchema, DataSourceSchema]))
278
257
  .optional()
279
258
  .describe('Data items for react slices (mix of sinks and sources)'),
280
- specs: SpecSchema.describe('Server-side specifications with rules and examples'),
259
+ specs: z.array(SpecSchema).describe('Server-side specifications with rules and examples'),
281
260
  }),
282
261
  }).describe('React slice for automated responses to events');
283
262
 
@@ -397,9 +376,7 @@ export const modelSchema = z
397
376
  export type { ClientSpecNode };
398
377
 
399
378
  export {
400
- StateExampleSchema,
401
379
  MessageFieldSchema,
402
- ErrorExampleSchema,
403
380
  MessageSchema,
404
381
  CommandSchema,
405
382
  EventSchema,
@@ -416,4 +393,8 @@ export {
416
393
  SpecSchema,
417
394
  DataSinkSchema,
418
395
  DataSourceSchema,
396
+ StepSchema,
397
+ StepErrorSchema,
398
+ StepWithDocStringSchema,
399
+ StepWithErrorSchema,
419
400
  };
@@ -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] }),