@auto-engineer/narrative 1.150.0 → 1.153.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 +5 -5
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +92 -0
- package/dist/src/fluent-builder.d.ts +4 -0
- package/dist/src/fluent-builder.d.ts.map +1 -1
- package/dist/src/fluent-builder.js +15 -0
- package/dist/src/fluent-builder.js.map +1 -1
- package/dist/src/schema.d.ts +1236 -4878
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +15 -1
- package/dist/src/schema.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 +9 -0
- package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/fluent-builder.specs.ts +48 -2
- package/src/fluent-builder.ts +22 -1
- package/src/model-to-narrative.specs.ts +38 -0
- package/src/schema.specs.ts +41 -11
- package/src/schema.ts +17 -1
- package/src/transformers/model-to-narrative/generators/flow.ts +15 -0
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.153.0",
|
|
30
|
+
"@auto-engineer/id": "1.153.0",
|
|
31
|
+
"@auto-engineer/message-bus": "1.153.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.153.0",
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsx scripts/build.ts",
|
|
44
44
|
"test": "vitest run --reporter=dot",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { command, react } from './fluent-builder';
|
|
3
|
-
import { clearCurrentScene, startScene } from './narrative-context';
|
|
2
|
+
import { command, experience, query, react } from './fluent-builder';
|
|
3
|
+
import { clearCurrentScene, getCurrentScene, startScene } from './narrative-context';
|
|
4
4
|
import { createIntegration } from './types';
|
|
5
5
|
|
|
6
6
|
// Test integrations
|
|
@@ -34,3 +34,49 @@ describe('via method', () => {
|
|
|
34
34
|
expect(slice).toBeDefined();
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
|
+
|
|
38
|
+
describe('ui method', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
startScene('test-flow');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
clearCurrentScene();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('sets client.ui on a command moment', () => {
|
|
48
|
+
const uiBlock = { layoutId: 'centered-narrow', surface: 'route' as const };
|
|
49
|
+
command('Submit Order').ui(uiBlock);
|
|
50
|
+
|
|
51
|
+
const scene = getCurrentScene();
|
|
52
|
+
const moment = scene!.moments[0];
|
|
53
|
+
expect(moment.type).toBe('command');
|
|
54
|
+
if (moment.type === 'command') {
|
|
55
|
+
expect(moment.client.ui).toEqual(uiBlock);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('sets client.ui on a query moment', () => {
|
|
60
|
+
const uiBlock = { layoutId: 'two-column', surface: 'route' as const };
|
|
61
|
+
query('View Items').ui(uiBlock);
|
|
62
|
+
|
|
63
|
+
const scene = getCurrentScene();
|
|
64
|
+
const moment = scene!.moments[0];
|
|
65
|
+
expect(moment.type).toBe('query');
|
|
66
|
+
if (moment.type === 'query') {
|
|
67
|
+
expect(moment.client.ui).toEqual(uiBlock);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('sets client.ui on an experience moment', () => {
|
|
72
|
+
const uiBlock = { layoutId: 'full-width', surface: 'ephemeral' as const };
|
|
73
|
+
experience('Welcome Tour').ui(uiBlock);
|
|
74
|
+
|
|
75
|
+
const scene = getCurrentScene();
|
|
76
|
+
const moment = scene!.moments[0];
|
|
77
|
+
expect(moment.type).toBe('experience');
|
|
78
|
+
if (moment.type === 'experience') {
|
|
79
|
+
expect(moment.client.ui).toEqual(uiBlock);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
package/src/fluent-builder.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import createDebug from 'debug';
|
|
2
2
|
import { type ASTNode, print } from 'graphql';
|
|
3
|
-
import type { CommandMoment, ExperienceMoment, QueryMoment, ReactMoment } from './index';
|
|
3
|
+
import type { CommandMoment, ExperienceMoment, QueryMoment, ReactMoment, UiBlock } from './index';
|
|
4
4
|
import {
|
|
5
5
|
addMoment,
|
|
6
6
|
endClientBlock,
|
|
@@ -38,6 +38,7 @@ export interface FluentCommandMomentBuilder {
|
|
|
38
38
|
client(description: string, fn: () => void): FluentCommandMomentBuilder;
|
|
39
39
|
server(fn: () => void): FluentCommandMomentBuilder;
|
|
40
40
|
server(description: string, fn: () => void): FluentCommandMomentBuilder;
|
|
41
|
+
ui(spec: UiBlock): FluentCommandMomentBuilder;
|
|
41
42
|
via(integration: Integration | Integration[]): FluentCommandMomentBuilder;
|
|
42
43
|
retries(count: number): FluentCommandMomentBuilder;
|
|
43
44
|
request(mutation: unknown): FluentCommandMomentBuilder;
|
|
@@ -48,6 +49,7 @@ export interface FluentQueryMomentBuilder {
|
|
|
48
49
|
client(description: string, fn: () => void): FluentQueryMomentBuilder;
|
|
49
50
|
server(fn: () => void): FluentQueryMomentBuilder;
|
|
50
51
|
server(description: string, fn: () => void): FluentQueryMomentBuilder;
|
|
52
|
+
ui(spec: UiBlock): FluentQueryMomentBuilder;
|
|
51
53
|
request(query: unknown): FluentQueryMomentBuilder;
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -61,6 +63,7 @@ export interface FluentReactionMomentBuilder {
|
|
|
61
63
|
export interface FluentExperienceMomentBuilder {
|
|
62
64
|
client(fn: () => void): FluentExperienceMomentBuilder;
|
|
63
65
|
client(description: string, fn: () => void): FluentExperienceMomentBuilder;
|
|
66
|
+
ui(spec: UiBlock): FluentExperienceMomentBuilder;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
class CommandMomentBuilderImpl implements FluentCommandMomentBuilder {
|
|
@@ -132,6 +135,12 @@ class CommandMomentBuilderImpl implements FluentCommandMomentBuilder {
|
|
|
132
135
|
return this;
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
ui(spec: UiBlock): FluentCommandMomentBuilder {
|
|
139
|
+
debugCommand('Setting client.ui for moment %s', this.moment.name);
|
|
140
|
+
this.moment.client.ui = spec;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
|
|
135
144
|
via(integration: Integration | Integration[]): FluentCommandMomentBuilder {
|
|
136
145
|
const integrations = Array.isArray(integration) ? integration : [integration];
|
|
137
146
|
this.moment.via = integrations.map((i) => i.name);
|
|
@@ -232,6 +241,12 @@ class QueryMomentBuilderImpl implements FluentQueryMomentBuilder {
|
|
|
232
241
|
return this;
|
|
233
242
|
}
|
|
234
243
|
|
|
244
|
+
ui(spec: UiBlock): FluentQueryMomentBuilder {
|
|
245
|
+
debugQuery('Setting client.ui for moment %s', this.moment.name);
|
|
246
|
+
this.moment.client.ui = spec;
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
|
|
235
250
|
request(query: unknown): FluentQueryMomentBuilder {
|
|
236
251
|
debugQuery('Setting request for moment %s', this.moment.name);
|
|
237
252
|
if (typeof query === 'string') {
|
|
@@ -346,6 +361,12 @@ class ExperienceMomentBuilderImpl implements FluentExperienceMomentBuilder {
|
|
|
346
361
|
|
|
347
362
|
return this;
|
|
348
363
|
}
|
|
364
|
+
|
|
365
|
+
ui(spec: UiBlock): FluentExperienceMomentBuilder {
|
|
366
|
+
debugExperience('Setting client.ui for moment %s', this.moment.name);
|
|
367
|
+
this.moment.client.ui = spec;
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
349
370
|
}
|
|
350
371
|
|
|
351
372
|
export const command = (name: string, id?: string): FluentCommandMomentBuilder => {
|
|
@@ -3569,4 +3569,42 @@ scene('All Projection Types', 'ALL-PROJ', () => {
|
|
|
3569
3569
|
expect(code).toContain("type FitnessGoalCreated = Event<'FitnessGoalCreated'");
|
|
3570
3570
|
expect(code).toContain("type FitnessGoalsView = State<'FitnessGoalsView'");
|
|
3571
3571
|
});
|
|
3572
|
+
|
|
3573
|
+
it('emits .ui() when moment has client.ui', async () => {
|
|
3574
|
+
const model: Model = {
|
|
3575
|
+
variant: 'specs',
|
|
3576
|
+
scenes: [
|
|
3577
|
+
{
|
|
3578
|
+
name: 'Checkout',
|
|
3579
|
+
moments: [
|
|
3580
|
+
{
|
|
3581
|
+
type: 'command',
|
|
3582
|
+
name: 'Submit Order',
|
|
3583
|
+
client: {
|
|
3584
|
+
specs: [],
|
|
3585
|
+
ui: {
|
|
3586
|
+
layoutId: 'centered-narrow',
|
|
3587
|
+
surface: 'route',
|
|
3588
|
+
spec: { root: 'layout-root', elements: {}, state: {} },
|
|
3589
|
+
},
|
|
3590
|
+
},
|
|
3591
|
+
server: { description: '', specs: [] },
|
|
3592
|
+
},
|
|
3593
|
+
],
|
|
3594
|
+
},
|
|
3595
|
+
],
|
|
3596
|
+
messages: [],
|
|
3597
|
+
integrations: [],
|
|
3598
|
+
modules: [],
|
|
3599
|
+
narratives: [],
|
|
3600
|
+
};
|
|
3601
|
+
|
|
3602
|
+
const result = await modelToNarrative(model);
|
|
3603
|
+
const code = getCode(result);
|
|
3604
|
+
|
|
3605
|
+
expect(code).toContain('.ui(');
|
|
3606
|
+
expect(code).toContain("layoutId: 'centered-narrow'");
|
|
3607
|
+
expect(code).toContain("surface: 'route'");
|
|
3608
|
+
expect(code).toContain("root: 'layout-root'");
|
|
3609
|
+
});
|
|
3572
3610
|
});
|
package/src/schema.specs.ts
CHANGED
|
@@ -57,6 +57,39 @@ describe('CommandMomentSchema', () => {
|
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
describe('CommandMomentSchema client.ui', () => {
|
|
61
|
+
it('accepts ui block in client alongside specs', () => {
|
|
62
|
+
const moment = {
|
|
63
|
+
type: 'command' as const,
|
|
64
|
+
name: 'Submit Order',
|
|
65
|
+
client: {
|
|
66
|
+
specs: [],
|
|
67
|
+
ui: {
|
|
68
|
+
layoutId: 'centered-narrow',
|
|
69
|
+
mode: 'as-is',
|
|
70
|
+
regions: { main: [{ id: 'r1', name: 'hero' }] },
|
|
71
|
+
spec: { root: 'layout-root', elements: {}, state: {} },
|
|
72
|
+
surface: 'route',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
server: { description: 'Submits order', specs: [] },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const result = CommandMomentSchema.safeParse(moment);
|
|
79
|
+
|
|
80
|
+
expect(result.success).toBe(true);
|
|
81
|
+
if (result.success) {
|
|
82
|
+
expect(result.data.client.ui).toEqual({
|
|
83
|
+
layoutId: 'centered-narrow',
|
|
84
|
+
mode: 'as-is',
|
|
85
|
+
regions: { main: [{ id: 'r1', name: 'hero' }] },
|
|
86
|
+
spec: { root: 'layout-root', elements: {}, state: {} },
|
|
87
|
+
surface: 'route',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
60
93
|
describe('SceneRouteSchema', () => {
|
|
61
94
|
it('should accept valid route types', () => {
|
|
62
95
|
for (const type of ['dedicated', 'nested', 'no-route'] as const) {
|
|
@@ -645,21 +678,18 @@ describe('ComponentDefinitionSchema', () => {
|
|
|
645
678
|
});
|
|
646
679
|
});
|
|
647
680
|
|
|
648
|
-
describe('DesignSchema
|
|
649
|
-
it('
|
|
681
|
+
describe('DesignSchema', () => {
|
|
682
|
+
it('does not include ui (ui lives in client.ui now)', () => {
|
|
650
683
|
const design = {
|
|
651
|
-
ui: {
|
|
652
|
-
|
|
653
|
-
root: 'page',
|
|
654
|
-
elements: { page: { type: 'Stack', children: [] } },
|
|
655
|
-
state: { title: 'Dashboard' },
|
|
656
|
-
},
|
|
657
|
-
},
|
|
684
|
+
ui: { spec: { root: 'page', elements: {}, state: {} } },
|
|
685
|
+
imageAsset: { url: 'https://example.com/img.png' },
|
|
658
686
|
};
|
|
659
|
-
|
|
687
|
+
const parsed = DesignSchema.parse(design);
|
|
688
|
+
expect(parsed).toEqual({ imageAsset: { url: 'https://example.com/img.png' } });
|
|
689
|
+
expect('ui' in parsed).toBe(false);
|
|
660
690
|
});
|
|
661
691
|
|
|
662
|
-
it('
|
|
692
|
+
it('accepts design without ui', () => {
|
|
663
693
|
expect(DesignSchema.parse({})).toEqual({});
|
|
664
694
|
});
|
|
665
695
|
});
|
package/src/schema.ts
CHANGED
|
@@ -262,7 +262,6 @@ export const DesignSchema = z
|
|
|
262
262
|
.object({
|
|
263
263
|
imageAsset: ImageAssetSchema.optional().describe('Primary image asset for this entity'),
|
|
264
264
|
metadata: z.record(z.unknown()).optional().describe('Flexible design metadata'),
|
|
265
|
-
ui: UISchema.optional().describe('UI composition for this entity'),
|
|
266
265
|
})
|
|
267
266
|
.describe('Design fields for visual representation');
|
|
268
267
|
|
|
@@ -351,10 +350,24 @@ export const ClientSpecNodeSchema: z.ZodType<ClientSpecNode> = z.lazy(() =>
|
|
|
351
350
|
|
|
352
351
|
export const ClientSpecSchema = z.array(ClientSpecNodeSchema).default([]);
|
|
353
352
|
|
|
353
|
+
export const UiBlockSchema = z
|
|
354
|
+
.object({
|
|
355
|
+
layoutId: z.string().optional().describe('Layout template identifier'),
|
|
356
|
+
mode: z.string().optional().describe('Rendering mode'),
|
|
357
|
+
regions: z
|
|
358
|
+
.record(z.array(z.object({ id: z.string(), name: z.string(), slots: z.record(z.unknown()).optional() })))
|
|
359
|
+
.optional()
|
|
360
|
+
.describe('Layout regions with placed components'),
|
|
361
|
+
spec: z.record(z.unknown()).optional().describe('UI specification tree'),
|
|
362
|
+
surface: z.enum(['route', 'overlay', 'ephemeral']).optional().describe('Presentation context'),
|
|
363
|
+
})
|
|
364
|
+
.describe('UI composition block for a moment');
|
|
365
|
+
|
|
354
366
|
const CommandMomentSchema = BaseMomentSchema.extend({
|
|
355
367
|
type: z.literal('command'),
|
|
356
368
|
client: z.object({
|
|
357
369
|
specs: ClientSpecSchema,
|
|
370
|
+
ui: UiBlockSchema.optional().describe('UI composition for this moment'),
|
|
358
371
|
}),
|
|
359
372
|
request: z.string().describe('Command request (GraphQL, REST endpoint, or other query format)').optional(),
|
|
360
373
|
mappings: z.array(MappingEntrySchema).optional().describe('Field mappings between Command/Event/State messages'),
|
|
@@ -369,6 +382,7 @@ const QueryMomentSchema = BaseMomentSchema.extend({
|
|
|
369
382
|
type: z.literal('query'),
|
|
370
383
|
client: z.object({
|
|
371
384
|
specs: ClientSpecSchema,
|
|
385
|
+
ui: UiBlockSchema.optional().describe('UI composition for this moment'),
|
|
372
386
|
}),
|
|
373
387
|
request: z.string().describe('Query request (GraphQL, REST endpoint, or other query format)').optional(),
|
|
374
388
|
mappings: z.array(MappingEntrySchema).optional().describe('Field mappings between Command/Event/State messages'),
|
|
@@ -392,6 +406,7 @@ const ExperienceMomentSchema = BaseMomentSchema.extend({
|
|
|
392
406
|
type: z.literal('experience'),
|
|
393
407
|
client: z.object({
|
|
394
408
|
specs: ClientSpecSchema,
|
|
409
|
+
ui: UiBlockSchema.optional().describe('UI composition for this moment'),
|
|
395
410
|
}),
|
|
396
411
|
}).describe('Experience moment for user interactions and UI behavior');
|
|
397
412
|
|
|
@@ -628,3 +643,4 @@ export type UIElement = z.infer<typeof UIElementSchema>;
|
|
|
628
643
|
export type UISpec = z.infer<typeof UISpecSchema>;
|
|
629
644
|
export type UI = z.infer<typeof UISchema>;
|
|
630
645
|
export type ComponentDefinition = z.infer<typeof ComponentDefinitionSchema>;
|
|
646
|
+
export type UiBlock = z.infer<typeof UiBlockSchema>;
|
|
@@ -424,6 +424,20 @@ function addClientToChain(
|
|
|
424
424
|
return chain;
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
function addUiToChain(
|
|
428
|
+
ts: typeof import('typescript'),
|
|
429
|
+
f: tsNS.NodeFactory,
|
|
430
|
+
chain: tsNS.Expression,
|
|
431
|
+
slice: Moment,
|
|
432
|
+
): tsNS.Expression {
|
|
433
|
+
if ('client' in slice && slice.client?.ui !== undefined) {
|
|
434
|
+
return f.createCallExpression(f.createPropertyAccessExpression(chain, f.createIdentifier('ui')), undefined, [
|
|
435
|
+
jsonToExpr(ts, f, slice.client.ui),
|
|
436
|
+
]);
|
|
437
|
+
}
|
|
438
|
+
return chain;
|
|
439
|
+
}
|
|
440
|
+
|
|
427
441
|
function addRequestToChain(
|
|
428
442
|
f: tsNS.NodeFactory,
|
|
429
443
|
chain: tsNS.Expression,
|
|
@@ -767,6 +781,7 @@ function buildMoment(
|
|
|
767
781
|
let chain: tsNS.Expression = f.createCallExpression(f.createIdentifier(sliceCtor), undefined, args);
|
|
768
782
|
|
|
769
783
|
chain = addClientToChain(ts, f, chain, slice);
|
|
784
|
+
chain = addUiToChain(ts, f, chain, slice);
|
|
770
785
|
chain = addRequestToChain(f, chain, slice);
|
|
771
786
|
chain = addServerToChain(ts, f, chain, slice, messages);
|
|
772
787
|
|