@auto-engineer/narrative 1.147.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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +3 -3
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +55 -0
- package/README.md +488 -117
- package/package.json +4 -4
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @auto-engineer/narrative@1.
|
|
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
|
[2m Test Files [22m [1m[32m22 passed[39m[22m[90m (22)[39m
|
|
11
11
|
[2m Tests [22m [1m[32m352 passed[39m[22m[90m (352)[39m
|
|
12
|
-
[2m Start at [22m
|
|
13
|
-
[2m Duration [22m
|
|
12
|
+
[2m Start at [22m 07:22:59
|
|
13
|
+
[2m Duration [22m 43.00s[2m (transform 7.80s, setup 0ms, collect 53.89s, tests 31.69s, environment 49ms, prepare 14.81s)[22m
|
|
14
14
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
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
|
+
|
|
22
|
+
## 1.148.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- [`d5ba3a0`](https://github.com/BeOnAuto/auto-engineer/commit/d5ba3a0e3fb0f6a9ad7a3a8b1815590ea77a5b42) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - Added state context instruction to generated decide handlers, preventing unnecessary narrowing when Given steps contain only state references
|
|
27
|
+
|
|
28
|
+
- [`e0cdc4e`](https://github.com/BeOnAuto/auto-engineer/commit/e0cdc4e3363ad84d4bc49996a600ac75c97ccc38) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - Added context-aware classification of non-command fields in generated decide.ts scaffolds, distinguishing between date-derived, state-derived, and not-yet-tested fields
|
|
29
|
+
|
|
30
|
+
- [`9195db7`](https://github.com/BeOnAuto/auto-engineer/commit/9195db78cb707d658866cee99a1c73d34fb4efde) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - Extracted shared template helpers into a dedicated module for cleaner code generation
|
|
31
|
+
|
|
32
|
+
- [`abb6540`](https://github.com/BeOnAuto/auto-engineer/commit/abb6540db7196ed7935c8a8610695828f9035fc3) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - Added status variant hints from Given state references to the state template, helping implementers create matching discriminated union variants
|
|
33
|
+
|
|
34
|
+
- [`9195db7`](https://github.com/BeOnAuto/auto-engineer/commit/9195db78cb707d658866cee99a1c73d34fb4efde) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - Extracted shared template helper functions into a dedicated module for better code reuse across generators
|
|
35
|
+
- Simplified template specs by removing inline duplicate definitions in favor of the shared helpers
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- [`88fb1da`](https://github.com/BeOnAuto/auto-engineer/commit/88fb1da2b222de04dd4959d87657395ee960a6ce) Thanks [@github-actions[bot]](https://github.com/github-actions%5Bbot%5D)! - - **server-generator-apollo-emmett**: skip empty file plans in scaffold output
|
|
40
|
+
- **server-generator-apollo-emmett**: filter state refs from given() in decide.specs.ts.ejs
|
|
41
|
+
- **server-generator-apollo-emmett**: move CS Given states from events to states array
|
|
42
|
+
- **global**: version packages
|
|
43
|
+
- **server-generator-apollo-emmett**: mark G1+G2 ketchup plan complete
|
|
44
|
+
|
|
45
|
+
- [`4255f6d`](https://github.com/BeOnAuto/auto-engineer/commit/4255f6db0d128979e573244a615886482ce799b0) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - Updated ketchup plan for state reference fix in decide template generator
|
|
46
|
+
- Marked generator bug fix milestones G1 and G2 as complete
|
|
47
|
+
|
|
48
|
+
- [`62f1ea3`](https://github.com/BeOnAuto/auto-engineer/commit/62f1ea3dd1b4275211574e3df9d9a6571ae9b27a) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - Fixed scaffold generation to correctly distinguish between event and state references in decision handlers
|
|
49
|
+
- Prevented contradictory instructions from appearing in generated code when Given clauses contain only state references
|
|
50
|
+
|
|
51
|
+
- [`ba4f5c9`](https://github.com/BeOnAuto/auto-engineer/commit/ba4f5c9749fb1c15d444e78ca9a2689817f039cb) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - Added implementation plan for decide.ts code generation fixes in the Apollo Emmett server generator
|
|
52
|
+
|
|
53
|
+
- Updated dependencies [[`88fb1da`](https://github.com/BeOnAuto/auto-engineer/commit/88fb1da2b222de04dd4959d87657395ee960a6ce), [`d5ba3a0`](https://github.com/BeOnAuto/auto-engineer/commit/d5ba3a0e3fb0f6a9ad7a3a8b1815590ea77a5b42), [`e0cdc4e`](https://github.com/BeOnAuto/auto-engineer/commit/e0cdc4e3363ad84d4bc49996a600ac75c97ccc38), [`4255f6d`](https://github.com/BeOnAuto/auto-engineer/commit/4255f6db0d128979e573244a615886482ce799b0), [`9195db7`](https://github.com/BeOnAuto/auto-engineer/commit/9195db78cb707d658866cee99a1c73d34fb4efde), [`abb6540`](https://github.com/BeOnAuto/auto-engineer/commit/abb6540db7196ed7935c8a8610695828f9035fc3), [`9195db7`](https://github.com/BeOnAuto/auto-engineer/commit/9195db78cb707d658866cee99a1c73d34fb4efde), [`62f1ea3`](https://github.com/BeOnAuto/auto-engineer/commit/62f1ea3dd1b4275211574e3df9d9a6571ae9b27a), [`ba4f5c9`](https://github.com/BeOnAuto/auto-engineer/commit/ba4f5c9749fb1c15d444e78ca9a2689817f039cb)]:
|
|
54
|
+
- @auto-engineer/file-store@1.148.0
|
|
55
|
+
- @auto-engineer/id@1.148.0
|
|
56
|
+
- @auto-engineer/message-bus@1.148.0
|
|
57
|
+
|
|
3
58
|
## 1.147.0
|
|
4
59
|
|
|
5
60
|
### 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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
27
|
-
|
|
46
|
+
// Define message types alongside your narrative
|
|
47
|
+
interface PlaceOrder {
|
|
48
|
+
type: 'PlaceOrder';
|
|
49
|
+
data: { productId: string; quantity: number };
|
|
50
|
+
}
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
89
|
+
### Define a query moment
|
|
48
90
|
|
|
49
91
|
```typescript
|
|
50
|
-
import {
|
|
92
|
+
import { scene, query, specs, rule, example, data, source } from '@auto-engineer/narrative';
|
|
51
93
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
118
|
+
### Define a reaction moment
|
|
68
119
|
|
|
69
120
|
```typescript
|
|
70
|
-
import {
|
|
121
|
+
import { scene, react, specs, rule, example, createIntegration } from '@auto-engineer/narrative';
|
|
71
122
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
140
|
+
### Define data sinks and sources
|
|
86
141
|
|
|
87
142
|
```typescript
|
|
88
|
-
import {
|
|
143
|
+
import { data, sink, source, target } from '@auto-engineer/narrative';
|
|
89
144
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
###
|
|
195
|
+
### Add auto-generated IDs to a model
|
|
102
196
|
|
|
103
197
|
```typescript
|
|
104
|
-
import {
|
|
105
|
-
import { NodeFileStore } from '@auto-engineer/file-store';
|
|
198
|
+
import { addAutoIds, hasAllIds } from '@auto-engineer/narrative';
|
|
106
199
|
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
384
|
+
### Scene loading and model conversion
|
|
135
385
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
###
|
|
413
|
+
### ID utilities
|
|
142
414
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
###
|
|
420
|
+
### GraphQL request parsing
|
|
151
421
|
|
|
152
422
|
```typescript
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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.
|
|
30
|
-
"@auto-engineer/id": "1.
|
|
31
|
-
"@auto-engineer/message-bus": "1.
|
|
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.
|
|
41
|
+
"version": "1.149.0",
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsx scripts/build.ts",
|
|
44
44
|
"test": "vitest run --reporter=dot",
|