@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +11 -0
- package/dist/src/commands/export-schema-runner.js +1 -1
- package/dist/src/commands/export-schema-runner.js.map +1 -1
- package/dist/src/fluent-builder.js +3 -3
- package/dist/src/fluent-builder.js.map +1 -1
- package/dist/src/getNarratives.specs.js +149 -153
- package/dist/src/getNarratives.specs.js.map +1 -1
- package/dist/src/id/addAutoIds.d.ts.map +1 -1
- package/dist/src/id/addAutoIds.js +23 -10
- package/dist/src/id/addAutoIds.js.map +1 -1
- package/dist/src/id/addAutoIds.specs.js +54 -45
- package/dist/src/id/addAutoIds.specs.js.map +1 -1
- package/dist/src/id/hasAllIds.d.ts.map +1 -1
- package/dist/src/id/hasAllIds.js +8 -3
- package/dist/src/id/hasAllIds.js.map +1 -1
- package/dist/src/id/hasAllIds.specs.js +142 -215
- package/dist/src/id/hasAllIds.specs.js.map +1 -1
- package/dist/src/index.d.ts +6 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/loader/graph.d.ts.map +1 -1
- package/dist/src/loader/graph.js +13 -6
- package/dist/src/loader/graph.js.map +1 -1
- package/dist/src/loader/ts-utils.d.ts +1 -0
- package/dist/src/loader/ts-utils.d.ts.map +1 -1
- package/dist/src/loader/ts-utils.js +95 -16
- package/dist/src/loader/ts-utils.js.map +1 -1
- package/dist/src/model-to-narrative.specs.js +531 -449
- package/dist/src/model-to-narrative.specs.js.map +1 -1
- package/dist/src/narrative-context.d.ts +8 -8
- package/dist/src/narrative-context.d.ts.map +1 -1
- package/dist/src/narrative-context.js +111 -301
- package/dist/src/narrative-context.js.map +1 -1
- package/dist/src/narrative-context.specs.js +15 -55
- package/dist/src/narrative-context.specs.js.map +1 -1
- package/dist/src/narrative.d.ts +19 -22
- package/dist/src/narrative.d.ts.map +1 -1
- package/dist/src/narrative.js +42 -71
- package/dist/src/narrative.js.map +1 -1
- package/dist/src/samples/test-with-ids.narrative.js +13 -29
- package/dist/src/samples/test-with-ids.narrative.js.map +1 -1
- package/dist/src/schema.d.ts +2704 -8293
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +26 -47
- package/dist/src/schema.js.map +1 -1
- package/dist/src/slice-builder.js +3 -3
- package/dist/src/slice-builder.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/flow.js +118 -74
- package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts +9 -1
- package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/gwt.js +112 -112
- package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/imports.d.ts +1 -1
- package/dist/src/transformers/model-to-narrative/generators/imports.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/imports.js +13 -9
- package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/index.d.ts.map +1 -1
- package/dist/src/transformers/narrative-to-model/index.js +50 -23
- package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js +100 -90
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/commands/export-schema-runner.ts +3 -1
- package/src/fluent-builder.ts +3 -3
- package/src/getNarratives.specs.ts +168 -176
- package/src/id/addAutoIds.specs.ts +54 -47
- package/src/id/addAutoIds.ts +28 -11
- package/src/id/hasAllIds.specs.ts +147 -245
- package/src/id/hasAllIds.ts +11 -4
- package/src/index.ts +9 -12
- package/src/loader/graph.ts +23 -6
- package/src/loader/ts-utils.ts +169 -26
- package/src/model-to-narrative.specs.ts +531 -449
- package/src/narrative-context.specs.ts +73 -116
- package/src/narrative-context.ts +127 -374
- package/src/narrative.ts +70 -120
- package/src/samples/test-with-ids.narrative.ts +23 -31
- package/src/schema.ts +33 -52
- package/src/slice-builder.ts +3 -3
- package/src/transformers/model-to-narrative/generators/flow.ts +191 -85
- package/src/transformers/model-to-narrative/generators/gwt.ts +195 -178
- package/src/transformers/model-to-narrative/generators/imports.ts +13 -9
- package/src/transformers/narrative-to-model/index.ts +87 -26
- 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
|
-
|
|
18
|
-
|
|
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
|
-
}
|
|
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(
|
|
107
|
+
export function specs(feature: string, fn: () => void): void;
|
|
111
108
|
export function specs(fn: () => void): void;
|
|
112
|
-
export function specs(
|
|
113
|
-
const
|
|
114
|
-
const callback = typeof
|
|
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(
|
|
113
|
+
pushSpec(feature);
|
|
117
114
|
callback();
|
|
118
115
|
}
|
|
119
116
|
|
|
120
|
-
export function rule(
|
|
121
|
-
export function rule(
|
|
122
|
-
export function rule(
|
|
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
|
|
161
|
-
and<
|
|
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
|
|
167
|
-
then<T>(data: ExtractData<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
|
|
171
|
-
|
|
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
|
|
175
|
-
|
|
143
|
+
export interface ExampleBuilder {
|
|
144
|
+
given<T>(data: ExtractData<T>): GivenBuilder;
|
|
145
|
+
when<W>(data: ExtractData<W>): WhenBuilder;
|
|
176
146
|
}
|
|
177
147
|
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
157
|
+
function createWhenBuilder(): WhenBuilder {
|
|
183
158
|
return {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
170
|
+
function createGivenBuilder(): GivenBuilder {
|
|
193
171
|
return {
|
|
194
|
-
and<
|
|
195
|
-
|
|
196
|
-
|
|
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>
|
|
200
|
-
|
|
201
|
-
|
|
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>
|
|
223
|
-
|
|
224
|
-
|
|
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():
|
|
187
|
+
function createExampleBuilder(): ExampleBuilder {
|
|
240
188
|
return {
|
|
241
|
-
given<T>(data: ExtractData<T>
|
|
242
|
-
|
|
243
|
-
|
|
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>
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
180
|
-
.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
179
|
+
.describe('BDD example with Gherkin steps');
|
|
202
180
|
|
|
203
181
|
const RuleSchema = z
|
|
204
182
|
.object({
|
|
205
|
-
id: z.string().optional().describe('
|
|
206
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
};
|
package/src/slice-builder.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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] }),
|