@auto-engineer/narrative 1.148.0 → 1.149.0

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @auto-engineer/narrative@1.148.0 build /home/runner/work/auto-engineer/auto-engineer/packages/narrative
2
+ > @auto-engineer/narrative@1.149.0 build /home/runner/work/auto-engineer/auto-engineer/packages/narrative
3
3
  > tsx scripts/build.ts
4
4
 
5
5
  Running: tsc
@@ -1,5 +1,5 @@
1
1
 
2
- > @auto-engineer/narrative@1.147.0 test /home/runner/work/auto-engineer/auto-engineer/packages/narrative
2
+ > @auto-engineer/narrative@1.148.0 test /home/runner/work/auto-engineer/auto-engineer/packages/narrative
3
3
  > vitest run --reporter=dot
4
4
 
5
5
 
@@ -9,6 +9,6 @@
9
9
 
10
10
   Test Files  22 passed (22)
11
11
   Tests  352 passed (352)
12
-  Start at  21:35:57
13
-  Duration  45.58s (transform 10.50s, setup 0ms, collect 62.59s, tests 31.92s, environment 40ms, prepare 15.54s)
12
+  Start at  07:22:59
13
+  Duration  43.00s (transform 7.80s, setup 0ms, collect 53.89s, tests 31.69s, environment 49ms, prepare 14.81s)
14
14
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @auto-engineer/narrative@1.147.0 type-check /home/runner/work/auto-engineer/auto-engineer/packages/narrative
2
+ > @auto-engineer/narrative@1.148.0 type-check /home/runner/work/auto-engineer/auto-engineer/packages/narrative
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @auto-engineer/flow
2
2
 
3
+ ## 1.149.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`e1eebbd`](https://github.com/BeOnAuto/auto-engineer/commit/e1eebbdf4f209780e790094d2e6887c4fa809f98) Thanks [@github-actions[bot]](https://github.com/github-actions%5Bbot%5D)! - - **server-generator-apollo-emmett**: add Given state ref hints to state.ts.ejs
8
+ - **server-generator-apollo-emmett**: context-aware nonCommandField instructions
9
+ - **server-generator-apollo-emmett**: add state context instruction
10
+ - **server-generator-apollo-emmett**: extract shared template helpers
11
+ - **server-generator-apollo-emmett**: filter state refs from hasGivenEvents in decide.ts.ejs
12
+
13
+ ### Patch Changes
14
+
15
+ - [`d38c81e`](https://github.com/BeOnAuto/auto-engineer/commit/d38c81e7bb442a39626564cf4f6d8d55b60d0a38) Thanks [@SamHatoum](https://github.com/SamHatoum)! -
16
+
17
+ - Updated dependencies [[`d38c81e`](https://github.com/BeOnAuto/auto-engineer/commit/d38c81e7bb442a39626564cf4f6d8d55b60d0a38), [`e1eebbd`](https://github.com/BeOnAuto/auto-engineer/commit/e1eebbdf4f209780e790094d2e6887c4fa809f98)]:
18
+ - @auto-engineer/file-store@1.149.0
19
+ - @auto-engineer/id@1.149.0
20
+ - @auto-engineer/message-bus@1.149.0
21
+
3
22
  ## 1.148.0
4
23
 
5
24
  ### Minor Changes
package/README.md CHANGED
@@ -1,16 +1,25 @@
1
- # @auto-engineer/narrative
2
-
3
- TypeScript DSL for defining business scenes using BDD patterns with Given/When/Then syntax.
4
-
5
- ---
1
+ # @auto-engineer/narrative — Fluent TypeScript DSL for defining behavioral specifications as scenes, moments, and examples
6
2
 
7
3
  ## Purpose
8
4
 
9
- Without `@auto-engineer/narrative`, you would have to manually structure behavioral specifications, write boilerplate for command/query definitions, and handle the conversion between specification code and JSON models.
5
+ Without `@auto-engineer/narrative`, you would have to hand-write JSON model files describing your system's scenes, moments, messages, data flows, and BDD specifications — then manually keep them in sync with your TypeScript types. This package provides a fluent DSL that lets you author `.narrative.ts` files, automatically extracts type information from your code, and bidirectionally transforms between the DSL representation and a canonical JSON model.
6
+
7
+ ## Key Concepts
10
8
 
11
- This package enables developers to express system behavior through scenes containing moments (commands, queries, reactions, experiences). Each moment supports client and server specifications using Gherkin-style Given/When/Then syntax.
9
+ - **Scene** A top-level container grouping related moments (e.g., "Place order"). Defined with the `scene()` function.
10
+ - **Moment** — A single interaction point within a scene. Four types exist: `command` (user-triggered state changes), `query` (data reads), `react` (automated responses to events), and `experience` (client-only UI behavior).
11
+ - **Spec / Rule / Example** — BDD-style specifications nested inside moments. A spec contains rules; a rule contains examples; an example contains Given/When/Then steps.
12
+ - **Data flow** — `sink`, `source`, and `target` builders describe how messages route between streams, projections, databases, integrations, and topics.
13
+ - **Model** — The canonical JSON representation (`Model` type) holding scenes, messages, integrations, modules, and narratives. Validated by Zod schemas.
14
+ - **Integration** — An external service (e.g., MailChimp, Twilio) wrapped with `createIntegration()` and referenced via the `via()` builder method.
12
15
 
13
- ---
16
+ ```mermaid
17
+ graph TD
18
+ A[".narrative.ts files"] -->|getScenes / executeAST| B["Scene[]"]
19
+ B -->|scenesToModel| C["Model (JSON)"]
20
+ C -->|modelToNarrative| A
21
+ C -->|Zod schemas| D["Validation"]
22
+ ```
14
23
 
15
24
  ## Installation
16
25
 
@@ -21,173 +30,535 @@ pnpm add @auto-engineer/narrative
21
30
  ## Quick Start
22
31
 
23
32
  ```typescript
24
- import { flow, command, specs, rule, example, type Event, type Command } from '@auto-engineer/narrative';
33
+ import {
34
+ scene,
35
+ command,
36
+ specs,
37
+ rule,
38
+ example,
39
+ data,
40
+ sink,
41
+ source,
42
+ describe,
43
+ it,
44
+ } from '@auto-engineer/narrative';
25
45
 
26
- type OrderPlaced = Event<'OrderPlaced', { orderId: string }>;
27
- type PlaceOrder = Command<'PlaceOrder', { productId: string }>;
46
+ // Define message types alongside your narrative
47
+ interface PlaceOrder {
48
+ type: 'PlaceOrder';
49
+ data: { productId: string; quantity: number };
50
+ }
28
51
 
29
- flow('Orders', () => {
30
- command('Place order')
52
+ interface OrderPlaced {
53
+ type: 'OrderPlaced';
54
+ data: { orderId: string; productId: string; quantity: number };
55
+ }
56
+
57
+ scene('Place order', () => {
58
+ command('Submit order')
59
+ .stream('order-${orderId}')
60
+ .client(() => {
61
+ describe('Order submission form', () => {
62
+ it('allows product selection');
63
+ it('allows quantity input');
64
+ });
65
+ })
31
66
  .server(() => {
32
- specs('Order placement', () => {
33
- rule('Valid orders are processed', () => {
34
- example('User places order')
35
- .when<PlaceOrder>({ productId: 'p-001' })
36
- .then<OrderPlaced>({ orderId: 'o-001' });
67
+ data([
68
+ sink().event('OrderPlaced').toStream('order-${orderId}'),
69
+ source().state('OrderSummary').fromProjection('OrderSummary', 'orderId'),
70
+ ]);
71
+
72
+ specs('User submits a new order', () => {
73
+ rule('Valid orders should be processed', () => {
74
+ example('User places order for available product')
75
+ .when<PlaceOrder>({ productId: 'product_789', quantity: 3 })
76
+ .then<OrderPlaced>({
77
+ orderId: 'order_001',
78
+ productId: 'product_789',
79
+ quantity: 3,
80
+ });
37
81
  });
38
82
  });
39
83
  });
40
84
  });
41
85
  ```
42
86
 
43
- ---
44
-
45
87
  ## How-to Guides
46
88
 
47
- ### Define a Command Moment
89
+ ### Define a query moment
48
90
 
49
91
  ```typescript
50
- import { flow, command, specs, rule, example } from '@auto-engineer/narrative';
92
+ import { scene, query, specs, rule, example, data, source } from '@auto-engineer/narrative';
51
93
 
52
- flow('Users', () => {
53
- command('Create user')
54
- .stream('user-${userId}')
94
+ scene('View orders', () => {
95
+ query('Get order summary')
96
+ .client(() => {
97
+ describe('Order list', () => {
98
+ it('displays order details');
99
+ });
100
+ })
55
101
  .server(() => {
56
- specs('User creation', () => {
57
- rule('Valid users created', () => {
58
- example('New user')
59
- .when({ name: 'John' })
60
- .then({ userId: 'u-001' });
102
+ data([
103
+ source().state('OrderSummary').fromProjection('OrderSummary', 'orderId'),
104
+ ]);
105
+
106
+ specs('Fetching order data', () => {
107
+ rule('Returns matching orders', () => {
108
+ example('Order exists')
109
+ .given<OrderSummary>({ orderId: 'order_001', productId: 'p1', quantity: 2 })
110
+ .when<GetOrderSummary>({ orderId: 'order_001' })
111
+ .then<OrderSummary>({ orderId: 'order_001', productId: 'p1', quantity: 2 });
61
112
  });
62
113
  });
63
114
  });
64
115
  });
65
116
  ```
66
117
 
67
- ### Define a Query Moment
118
+ ### Define a reaction moment
68
119
 
69
120
  ```typescript
70
- import { flow, query, describe, it, data, source } from '@auto-engineer/narrative';
121
+ import { scene, react, specs, rule, example, createIntegration } from '@auto-engineer/narrative';
71
122
 
72
- flow('Products', () => {
73
- query('View products')
74
- .client(() => {
75
- describe('Product list', () => {
76
- it('displays all products');
77
- });
78
- })
123
+ const emailService = createIntegration('email', 'EmailService');
124
+
125
+ scene('Order confirmation', () => {
126
+ react('Send confirmation email')
127
+ .via(emailService)
79
128
  .server(() => {
80
- data([source().state('Products').fromProjection('ProductsProjection', 'id')]);
129
+ specs('Email sent on order placement', () => {
130
+ rule('Confirmation email is sent', () => {
131
+ example('Order placed triggers email')
132
+ .given<OrderPlaced>({ orderId: 'order_001', productId: 'p1', quantity: 2 })
133
+ .then<ConfirmationEmailSent>({ orderId: 'order_001' });
134
+ });
135
+ });
81
136
  });
82
137
  });
83
138
  ```
84
139
 
85
- ### Define Data Sinks and Sources
140
+ ### Define data sinks and sources
86
141
 
87
142
  ```typescript
88
- import { flow, command, data, sink, source } from '@auto-engineer/narrative';
143
+ import { data, sink, source, target } from '@auto-engineer/narrative';
89
144
 
90
- flow('Payments', () => {
91
- command('Process payment')
92
- .server(() => {
93
- data([
94
- sink().event('PaymentProcessed').toStream('payment-${paymentId}'),
95
- source().state('Account').fromProjection('Accounts', 'accountId'),
96
- ]);
97
- });
145
+ // Event to stream
146
+ sink().event('OrderPlaced').toStream('order-${orderId}');
147
+
148
+ // Event to integration
149
+ sink().event('OrderPlaced').toIntegration(emailService);
150
+
151
+ // Event to database
152
+ sink().event('OrderPlaced').toDatabase('orders');
153
+
154
+ // Command to integration with message routing
155
+ sink().command('SendEmail').toIntegration(emailService, 'SendEmail', 'command');
156
+
157
+ // State from projection
158
+ source().state('OrderSummary').fromProjection('OrderSummary', 'orderId');
159
+
160
+ // State from singleton projection
161
+ source().state('DashboardStats').fromSingletonProjection('DashboardStats');
162
+
163
+ // State from API
164
+ source().state('ExternalData').fromApi('/api/data', 'GET');
165
+
166
+ // Event target (declaration only, no routing)
167
+ target().event('OrderPlaced');
168
+ ```
169
+
170
+ ### Load narrative files and produce a model
171
+
172
+ ```typescript
173
+ import { getScenes } from '@auto-engineer/narrative';
174
+
175
+ const result = await getScenes({
176
+ vfs, // IFileStore instance
177
+ root: '/path/to/narratives',
98
178
  });
179
+
180
+ console.log(result.scenes); // Scene[]
181
+ const model = result.toModel(); // Model (full JSON specification)
182
+ ```
183
+
184
+ ### Convert a model back to narrative files
185
+
186
+ ```typescript
187
+ import { modelToNarrative } from '@auto-engineer/narrative';
188
+
189
+ const generated = await modelToNarrative(model);
190
+ for (const file of generated.files) {
191
+ console.log(file.path, file.code);
192
+ }
99
193
  ```
100
194
 
101
- ### Load Scenes from File System
195
+ ### Add auto-generated IDs to a model
102
196
 
103
197
  ```typescript
104
- import { getNarratives } from '@auto-engineer/narrative';
105
- import { NodeFileStore } from '@auto-engineer/file-store';
198
+ import { addAutoIds, hasAllIds } from '@auto-engineer/narrative';
106
199
 
107
- const vfs = new NodeFileStore();
108
- const result = await getNarratives({ vfs, root: '/path/to/src' });
109
- const model = result.toModel();
200
+ if (!hasAllIds(model)) {
201
+ const withIds = addAutoIds(model);
202
+ }
110
203
  ```
111
204
 
112
- ---
205
+ ### Validate moment requests against the model
206
+
207
+ ```typescript
208
+ import { validateMomentRequests } from '@auto-engineer/narrative';
209
+
210
+ const errors = validateMomentRequests(model);
211
+ if (errors.length > 0) {
212
+ errors.forEach((e) => console.error(`${e.sceneName}/${e.momentName}: ${e.message}`));
213
+ }
214
+ ```
113
215
 
114
216
  ## API Reference
115
217
 
116
- ### Package Exports
218
+ ### Subpath exports
219
+
220
+ | Import path | Description |
221
+ |---|---|
222
+ | `@auto-engineer/narrative` | Main entry — DSL functions, builders, transformers, and all schema re-exports |
223
+ | `@auto-engineer/narrative/schema` | Zod schemas and inferred TypeScript types for the model |
224
+ | `@auto-engineer/narrative/node` | Re-exports everything from the main entry (Node.js convenience alias) |
225
+
226
+ ### Scene and moment DSL
117
227
 
118
228
  ```typescript
119
- import {
120
- flow, narrative,
121
- command, query, react, experience,
122
- specs, rule, example, describe, it, should,
123
- client, server, data,
124
- sink, source,
125
- getNarratives, modelToNarrative,
126
- addAutoIds, hasAllIds,
127
- gql,
128
- type Event, type Command, type State,
129
- } from '@auto-engineer/narrative';
229
+ function scene(name: string, fn: () => void): void;
230
+ function scene(name: string, id: string, fn: () => void): void;
231
+
232
+ function command(name: string, id?: string): FluentCommandMomentBuilder;
233
+ function query(name: string, id?: string): FluentQueryMomentBuilder;
234
+ function react(name: string, id?: string): FluentReactionMomentBuilder;
235
+ function experience(name: string, id?: string): FluentExperienceMomentBuilder;
236
+
237
+ // Aliases
238
+ function decide(name: string, id?: string): FluentCommandMomentBuilder;
239
+ function evolve(name: string, id?: string): FluentQueryMomentBuilder;
240
+ ```
241
+
242
+ ### Fluent moment builders
243
+
244
+ ```typescript
245
+ interface FluentCommandMomentBuilder {
246
+ stream(name: string): FluentCommandMomentBuilder;
247
+ client(fn: () => void): FluentCommandMomentBuilder;
248
+ client(description: string, fn: () => void): FluentCommandMomentBuilder;
249
+ server(fn: () => void): FluentCommandMomentBuilder;
250
+ server(description: string, fn: () => void): FluentCommandMomentBuilder;
251
+ via(integration: Integration | Integration[]): FluentCommandMomentBuilder;
252
+ retries(count: number): FluentCommandMomentBuilder;
253
+ request(mutation: unknown): FluentCommandMomentBuilder;
254
+ }
255
+
256
+ interface FluentQueryMomentBuilder {
257
+ client(fn: () => void): FluentQueryMomentBuilder;
258
+ client(description: string, fn: () => void): FluentQueryMomentBuilder;
259
+ server(fn: () => void): FluentQueryMomentBuilder;
260
+ server(description: string, fn: () => void): FluentQueryMomentBuilder;
261
+ request(query: unknown): FluentQueryMomentBuilder;
262
+ }
263
+
264
+ interface FluentReactionMomentBuilder {
265
+ server(fn: () => void): FluentReactionMomentBuilder;
266
+ server(description: string, fn: () => void): FluentReactionMomentBuilder;
267
+ via(integration: Integration | Integration[]): FluentReactionMomentBuilder;
268
+ retries(count: number): FluentReactionMomentBuilder;
269
+ }
270
+
271
+ interface FluentExperienceMomentBuilder {
272
+ client(fn: () => void): FluentExperienceMomentBuilder;
273
+ client(description: string, fn: () => void): FluentExperienceMomentBuilder;
274
+ }
275
+ ```
276
+
277
+ ### Spec DSL
278
+
279
+ ```typescript
280
+ function specs(feature: string, fn: () => void): void;
281
+ function specs(fn: () => void): void;
282
+ function rule(name: string, fn: () => void): void;
283
+ function rule(name: string, id: string, fn: () => void): void;
284
+ function example(name: string, id?: string): ExampleBuilder;
285
+ function thenError(errorType: 'IllegalStateError' | 'ValidationError' | 'NotFoundError', message?: string): void;
286
+
287
+ interface ExampleBuilder {
288
+ given<T>(data: T): GivenBuilder;
289
+ when<W>(data: W): WhenBuilder;
290
+ }
291
+
292
+ interface GivenBuilder {
293
+ and<T>(data: T): GivenBuilder;
294
+ when<W>(data: W): WhenBuilder;
295
+ then<T>(data: T): ThenBuilder;
296
+ }
297
+
298
+ interface WhenBuilder {
299
+ then<T>(data: T): ThenBuilder;
300
+ and<T>(data: T): WhenBuilder;
301
+ }
302
+
303
+ interface ThenBuilder {
304
+ and<T>(data: T): ThenBuilder;
305
+ }
306
+ ```
307
+
308
+ ### Client spec DSL
309
+
310
+ ```typescript
311
+ function describe(title: string, fn: () => void): void;
312
+ function describe(title: string, id: string, fn: () => void): void;
313
+ function it(title: string, id?: string): void;
314
+ function should(title: string, id?: string): void; // alias for it
315
+ ```
316
+
317
+ ### Data flow builders
318
+
319
+ ```typescript
320
+ function sink(id?: string): DataSinkBuilder;
321
+ function source(id?: string): DataSourceBuilder;
322
+ function target(id?: string): DataTargetBuilder;
323
+ function data(config: Data | (DataItem | DataTargetItem)[]): void;
324
+
325
+ class DataSinkBuilder {
326
+ event(name: string): EventSinkBuilder;
327
+ command(name: string): CommandSinkBuilder;
328
+ state(name: string): StateSinkBuilder;
329
+ }
330
+
331
+ class EventSinkBuilder {
332
+ toStream(pattern: string): ChainableSink;
333
+ toIntegration(...systems: Integration[]): ChainableSink;
334
+ toDatabase(collection: string): ChainableSink;
335
+ toTopic(name: string): ChainableSink;
336
+ }
337
+
338
+ class CommandSinkBuilder {
339
+ withState(source: DataSourceItem): this;
340
+ toIntegration(system: Integration | string, messageName: string, messageType: 'command' | 'query' | 'reaction'): ChainableSink;
341
+ toDatabase(collection: string): ChainableSink;
342
+ toTopic(name: string): ChainableSink;
343
+ hints(hint: string): ChainableSink;
344
+ }
345
+
346
+ class StateSinkBuilder {
347
+ toDatabase(collection: string): ChainableSink;
348
+ toStream(pattern: string): ChainableSink;
349
+ }
350
+
351
+ class DataSourceBuilder {
352
+ state<S>(name: string): StateSourceBuilder<S>;
353
+ }
354
+
355
+ class StateSourceBuilder<S> {
356
+ fromProjection(name: string, idField: string): ChainableSource;
357
+ fromSingletonProjection(name: string): ChainableSource;
358
+ fromCompositeProjection(name: string, idFields: string[]): ChainableSource;
359
+ fromReadModel(name: string): ChainableSource;
360
+ fromDatabase(collection: string, query?: Record<string, unknown>): ChainableSource;
361
+ fromApi(endpoint: string, method?: string): ChainableSource;
362
+ fromIntegration(...systems: (Integration | string)[]): ChainableSource;
363
+ }
364
+
365
+ class DataTargetBuilder {
366
+ event(name: string): DataTargetItem;
367
+ }
368
+ ```
130
369
 
131
- import { COMMANDS, exportSchemaCommandHandler } from '@auto-engineer/narrative/node';
370
+ ### Integration
371
+
372
+ ```typescript
373
+ function createIntegration<T extends string>(type: T, name: string): Integration<T>;
374
+
375
+ interface Integration<Type extends string> {
376
+ readonly type: Type;
377
+ readonly name: string;
378
+ readonly Queries?: Record<string, (...args: any[]) => Promise<any>>;
379
+ readonly Commands?: Record<string, (...args: any[]) => Promise<any>>;
380
+ readonly Reactions?: Record<string, (...args: any[]) => Promise<any>>;
381
+ }
132
382
  ```
133
383
 
134
- ### Entry Points
384
+ ### Scene loading and model conversion
135
385
 
136
- | Entry Point | Import Path | Description |
137
- |-------------|-------------|-------------|
138
- | Main | `@auto-engineer/narrative` | Core DSL and types |
139
- | Node | `@auto-engineer/narrative/node` | Command handlers |
386
+ ```typescript
387
+ function getScenes(opts: GetScenesOptions): Promise<{
388
+ scenes: Scene[];
389
+ vfsFiles: string[];
390
+ externals: string[];
391
+ typings: Record<string, string[]>;
392
+ typeMap: Map<string, string>;
393
+ typesByFile: Map<string, Map<string, unknown>>;
394
+ givenTypesByFile: Map<string, unknown[]>;
395
+ toModel: () => Model;
396
+ }>;
397
+
398
+ interface GetScenesOptions {
399
+ vfs: IFileStore;
400
+ root: string;
401
+ pattern?: RegExp; // default: /\.(narrative|integration)\.(ts|tsx|js|jsx|mjs|cjs)$/
402
+ importMap?: Record<string, unknown>;
403
+ fastFsScan?: boolean;
404
+ }
405
+
406
+ function modelToNarrative(model: Model): Promise<GeneratedScenes>;
407
+
408
+ interface GeneratedScenes {
409
+ files: Array<{ path: string; code: string }>;
410
+ }
411
+ ```
140
412
 
141
- ### Moment Types
413
+ ### ID utilities
142
414
 
143
- | Type | Description | Client | Server |
144
- |------|-------------|--------|--------|
145
- | `command` | User actions that modify state | Yes | Yes |
146
- | `query` | Read operations | Yes | Yes |
147
- | `react` | Event reactions | No | Yes |
148
- | `experience` | UI-only behaviors | Yes | No |
415
+ ```typescript
416
+ function addAutoIds(model: Model): Model;
417
+ function hasAllIds(model: Model): boolean;
418
+ ```
149
419
 
150
- ### Message Types
420
+ ### GraphQL request parsing
151
421
 
152
422
  ```typescript
153
- type Event<Type extends string, Data> = { type: Type; data: Data };
154
- type Command<Type extends string, Data> = { type: Type; data: Data };
155
- type State<Type extends string, Data> = { type: Type; data: Data };
423
+ function parseGraphQlRequest(request: string): ParsedGraphQlOperation;
424
+ function parseMomentRequest(moment: { request?: string }): ParsedGraphQlOperation | undefined;
425
+
426
+ interface ParsedGraphQlOperation {
427
+ operationName: string;
428
+ args: ParsedArg[];
429
+ }
430
+
431
+ interface ParsedArg {
432
+ name: string;
433
+ tsType: string;
434
+ graphqlType: string;
435
+ nullable: boolean;
436
+ }
156
437
  ```
157
438
 
158
- ---
439
+ ### Validation
440
+
441
+ ```typescript
442
+ function validateMomentRequests(model: Model): MomentRequestValidationError[];
443
+
444
+ interface MomentRequestValidationError {
445
+ type: string;
446
+ message: string;
447
+ sceneName: string;
448
+ momentName: string;
449
+ }
450
+ ```
451
+
452
+ ### Message types
453
+
454
+ ```typescript
455
+ type Command<T extends string, D extends Record<string, unknown>> = {
456
+ readonly type: T;
457
+ readonly data: Readonly<D>;
458
+ readonly kind?: 'Command';
459
+ };
460
+
461
+ type Event<T extends string, D extends Record<string, unknown>> = {
462
+ readonly type: T;
463
+ readonly data: D;
464
+ readonly kind?: 'Event';
465
+ };
466
+
467
+ type State<T extends string, D extends Record<string, unknown>> = {
468
+ readonly type: T;
469
+ readonly data: D;
470
+ readonly kind?: 'State';
471
+ };
472
+
473
+ type Query<T extends string, D extends Record<string, unknown>> = {
474
+ type: T;
475
+ data: D;
476
+ };
477
+ ```
478
+
479
+ ### Key Zod schemas (`@auto-engineer/narrative/schema`)
480
+
481
+ | Schema | Validates |
482
+ |---|---|
483
+ | `modelSchema` | Complete `Model` with scenes, messages, modules, narratives |
484
+ | `SceneSchema` | A scene with moments |
485
+ | `MomentSchema` | Discriminated union of command/query/react/experience moments |
486
+ | `CommandMomentSchema` | Command moment with client/server blocks |
487
+ | `QueryMomentSchema` | Query moment with client/server blocks |
488
+ | `ReactMomentSchema` | Reaction moment with server block |
489
+ | `ExperienceMomentSchema` | Experience moment with client block |
490
+ | `MessageSchema` | Discriminated union of command/event/state/query messages |
491
+ | `SpecSchema` | Gherkin specification with rules and examples |
492
+ | `DataSchema` | Data configuration with sinks, sources, and targets |
493
+ | `NarrativeSchema` | Narrative grouping scenes into an ordered flow |
494
+ | `ModuleSchema` | Module for type ownership and file grouping |
495
+ | `DesignSchema` | Design fields for visual representation (image assets, UI specs) |
496
+ | `UISpecSchema` | Flat element-map UI specification |
497
+ | `NarrativePlanningSchema` | Progressive disclosure variant for planning |
498
+ | `SceneNamesSchema` | Scene names only (initial ideation) |
499
+ | `MomentNamesSchema` | Scene + moment names (structure planning) |
500
+ | `ClientServerNamesSchema` | Scene + moment + client/server descriptions |
159
501
 
160
502
  ## Architecture
161
503
 
504
+ ### File tree
505
+
162
506
  ```
163
507
  src/
164
- ├── index.ts
165
- ├── node.ts
166
- ├── narrative.ts
167
- ├── fluent-builder.ts
168
- ├── schema.ts
169
- ├── types.ts
170
- ├── data-narrative-builders.ts
171
- ├── loader/
172
- ├── transformers/
173
- ├── id/
174
- └── commands/
175
- ```
176
-
177
- ### Key Concepts
178
-
179
- - **Scenes**: Business capabilities containing moments
180
- - **Moments**: Behavioral units (command, query, react, experience)
181
- - **Specifications**: BDD-style Given/When/Then assertions
182
- - **Data Flow**: Sinks (outbound) and Sources (inbound)
183
-
184
- ### Dependencies
185
-
186
- | Package | Usage |
187
- |---------|-------|
188
- | `@auto-engineer/file-store` | Virtual file system |
189
- | `@auto-engineer/id` | ID generation |
190
- | `@auto-engineer/message-bus` | Command/Event types |
191
- | `zod` | Schema validation |
192
- | `typescript` | AST parsing |
193
- | `graphql` | GraphQL query parsing |
508
+ index.ts Main entry, re-exports all public API
509
+ schema.ts Zod schemas and inferred types for the model
510
+ node.ts Node.js convenience re-export
511
+ narrative.ts Core DSL: scene, specs, rule, example, data
512
+ fluent-builder.ts Fluent moment builders: command, query, react, experience
513
+ narrative-context.ts Mutable execution context tracking current scene/moment/spec
514
+ narrative-registry.ts Global singleton registry collecting scenes
515
+ data-narrative-builders.ts sink/source/target builder chains
516
+ types.ts TypeScript types: Command, Event, State, Query, Integration
517
+ getScenes.ts File discovery, compilation, caching, and model assembly
518
+ parse-graphql-request.ts GraphQL request string parser
519
+ validate-slice-requests.ts Model-level request validation
520
+ testing.ts Legacy testing helpers (deprecated)
521
+ ts-type-helpers.ts Inline object type parsing utilities
522
+ slice-builder.ts Alternative moment builder (MomentBuilder pattern)
523
+ id/
524
+ addAutoIds.ts Assigns auto-generated IDs to all model nodes
525
+ hasAllIds.ts Checks whether every node has an ID
526
+ generators.ts ID generation functions
527
+ loader/
528
+ index.ts executeAST: TS compilation + CJS execution pipeline
529
+ graph.ts Dependency graph builder
530
+ resolver.ts Module resolution
531
+ runtime-cjs.ts CJS runtime for executing compiled modules
532
+ importmap.ts Import map creation with built-in shims
533
+ ts-utils.ts TypeScript AST utilities for type extraction
534
+ vfs-compiler-host.ts Virtual filesystem compiler host
535
+ transformers/
536
+ narrative-to-model/
537
+ index.ts scenesToModel: Scene[] -> Model
538
+ assemble.ts Final model assembly (modules, narratives)
539
+ spec-processors.ts Given/When/Then step processing
540
+ type-inference.ts Type resolution from AST info
541
+ derive-modules.ts Auto-derive module structure from source files
542
+ inlining.ts Inline type references in message fields
543
+ integrations.ts Extract integration metadata from data items
544
+ model-to-narrative/
545
+ index.ts modelToNarrative: Model -> .narrative.ts files
546
+ generators/ Code generators for imports, types, GWT, flows
547
+ formatting/ Prettier formatting and type sorting
548
+ ```
549
+
550
+ ### Dependency table
551
+
552
+ | Dependency | Role |
553
+ |---|---|
554
+ | `@auto-engineer/file-store` | Virtual filesystem abstraction for reading narrative files |
555
+ | `@auto-engineer/id` | ID generation utilities |
556
+ | `@auto-engineer/message-bus` | Message bus integration |
557
+ | `zod` | Schema definition and runtime validation |
558
+ | `zod-to-json-schema` | Convert Zod schemas to JSON Schema |
559
+ | `typescript` | TypeScript compiler API for AST analysis and type extraction |
560
+ | `graphql` / `graphql-tag` | GraphQL parsing for request definitions |
561
+ | `prettier` | Code formatting for generated narrative files |
562
+ | `js-sha256` | Content hashing for compilation caching |
563
+ | `fast-glob` | File discovery |
564
+ | `debug` | Debug logging (`auto:narrative:*` namespace) |
package/package.json CHANGED
@@ -26,9 +26,9 @@
26
26
  "typescript": "^5.9.2",
27
27
  "zod": "^3.22.4",
28
28
  "zod-to-json-schema": "^3.22.3",
29
- "@auto-engineer/file-store": "1.148.0",
30
- "@auto-engineer/id": "1.148.0",
31
- "@auto-engineer/message-bus": "1.148.0"
29
+ "@auto-engineer/file-store": "1.149.0",
30
+ "@auto-engineer/id": "1.149.0",
31
+ "@auto-engineer/message-bus": "1.149.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^20.0.0",
@@ -38,7 +38,7 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "version": "1.148.0",
41
+ "version": "1.149.0",
42
42
  "scripts": {
43
43
  "build": "tsx scripts/build.ts",
44
44
  "test": "vitest run --reporter=dot",