@auto-engineer/narrative 1.12.0 → 1.12.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/.turbo/turbo-test.log +6 -6
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/src/transformers/model-to-narrative/ast/emit-helpers.d.ts +2 -1
- package/dist/src/transformers/model-to-narrative/ast/emit-helpers.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/ast/emit-helpers.js +13 -13
- package/dist/src/transformers/model-to-narrative/ast/emit-helpers.js.map +1 -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 +19 -8
- package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/transformers/model-to-narrative/ast/emit-helpers.specs.ts +37 -0
- package/src/transformers/model-to-narrative/ast/emit-helpers.ts +15 -9
- package/src/transformers/model-to-narrative/generators/gwt.ts +22 -8
package/package.json
CHANGED
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"typescript": "^5.9.2",
|
|
28
28
|
"zod": "^3.22.4",
|
|
29
29
|
"zod-to-json-schema": "^3.22.3",
|
|
30
|
-
"@auto-engineer/
|
|
31
|
-
"@auto-engineer/
|
|
32
|
-
"@auto-engineer/
|
|
30
|
+
"@auto-engineer/message-bus": "1.12.1",
|
|
31
|
+
"@auto-engineer/file-store": "1.12.1",
|
|
32
|
+
"@auto-engineer/id": "1.12.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^20.0.0",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
|
-
"version": "1.12.
|
|
42
|
+
"version": "1.12.1",
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsx scripts/build.ts",
|
|
45
45
|
"test": "vitest run --reporter=dot",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { type FieldTypeInfo, jsonToExpr } from './emit-helpers';
|
|
4
|
+
|
|
5
|
+
function printNode(node: ts.Node): string {
|
|
6
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
7
|
+
const sourceFile = ts.createSourceFile('test.ts', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
8
|
+
return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('jsonToExpr', () => {
|
|
12
|
+
it('should use typeResolver for array elements with type alias containing Date fields', () => {
|
|
13
|
+
const data = {
|
|
14
|
+
memberId: 'mem_001',
|
|
15
|
+
sessions: [{ savedAt: '2030-01-15T10:00:00.000Z', name: 'Morning workout' }],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const typeInfo: FieldTypeInfo = {
|
|
19
|
+
memberId: 'string',
|
|
20
|
+
sessions: 'Array<SessionData>',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const typeResolver = (typeName: string): FieldTypeInfo | undefined => {
|
|
24
|
+
if (typeName === 'SessionData') {
|
|
25
|
+
return { savedAt: 'Date', name: 'string' };
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const result = jsonToExpr(ts, ts.factory, data, typeInfo, typeResolver);
|
|
31
|
+
const code = printNode(result);
|
|
32
|
+
|
|
33
|
+
expect(code).toEqual(
|
|
34
|
+
`{ memberId: "mem_001", sessions: [{ savedAt: new Date("2030-01-15T10:00:00.000Z"), name: "Morning workout" }] }`,
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -7,6 +7,8 @@ export interface FieldTypeInfo {
|
|
|
7
7
|
[fieldName: string]: string; // field name -> type (e.g., 'Date', 'string', etc.)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export type TypeResolver = (typeName: string) => FieldTypeInfo | undefined;
|
|
11
|
+
|
|
10
12
|
const ARRAY_TYPE_REGEX = /^Array<(.+)>$/;
|
|
11
13
|
|
|
12
14
|
const VALID_IDENTIFIER_REGEX = /^[A-Za-z_]\w*$/;
|
|
@@ -70,13 +72,14 @@ function convertArrayField(
|
|
|
70
72
|
f: tsNS.NodeFactory,
|
|
71
73
|
arr: unknown[],
|
|
72
74
|
fieldType: string,
|
|
75
|
+
typeResolver?: TypeResolver,
|
|
73
76
|
): tsNS.Expression {
|
|
74
77
|
const elementType = parseArrayElementType(fieldType);
|
|
75
78
|
if (elementType === 'Date') {
|
|
76
79
|
return f.createArrayLiteralExpression(arr.map((elem) => convertDateValue(f, elem) ?? jsonToExpr(ts, f, elem)));
|
|
77
80
|
}
|
|
78
|
-
const elementTypeInfo = elementType ? parseObjectTypeInfo(elementType) : undefined;
|
|
79
|
-
return f.createArrayLiteralExpression(arr.map((elem) => jsonToExpr(ts, f, elem, elementTypeInfo)));
|
|
81
|
+
const elementTypeInfo = elementType ? (parseObjectTypeInfo(elementType) ?? typeResolver?.(elementType)) : undefined;
|
|
82
|
+
return f.createArrayLiteralExpression(arr.map((elem) => jsonToExpr(ts, f, elem, elementTypeInfo, typeResolver)));
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
function convertObjectField(
|
|
@@ -84,9 +87,10 @@ function convertObjectField(
|
|
|
84
87
|
f: tsNS.NodeFactory,
|
|
85
88
|
obj: Record<string, unknown>,
|
|
86
89
|
fieldType: string,
|
|
90
|
+
typeResolver?: TypeResolver,
|
|
87
91
|
): tsNS.Expression {
|
|
88
|
-
const nestedTypeInfo = parseObjectTypeInfo(fieldType);
|
|
89
|
-
return jsonToExpr(ts, f, obj, nestedTypeInfo);
|
|
92
|
+
const nestedTypeInfo = parseObjectTypeInfo(fieldType) ?? typeResolver?.(fieldType);
|
|
93
|
+
return jsonToExpr(ts, f, obj, nestedTypeInfo, typeResolver);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
function computeFieldExpr(
|
|
@@ -95,16 +99,17 @@ function computeFieldExpr(
|
|
|
95
99
|
x: unknown,
|
|
96
100
|
fieldType: string | undefined,
|
|
97
101
|
typeInfo: FieldTypeInfo | undefined,
|
|
102
|
+
typeResolver?: TypeResolver,
|
|
98
103
|
): tsNS.Expression {
|
|
99
104
|
if (fieldType === 'Date') {
|
|
100
105
|
const dateExpr = convertDateValue(f, x);
|
|
101
106
|
if (dateExpr) return dateExpr;
|
|
102
107
|
}
|
|
103
108
|
if (fieldType && typeof x === 'object' && x !== null) {
|
|
104
|
-
if (Array.isArray(x)) return convertArrayField(ts, f, x, fieldType);
|
|
105
|
-
return convertObjectField(ts, f, x as Record<string, unknown>, fieldType);
|
|
109
|
+
if (Array.isArray(x)) return convertArrayField(ts, f, x, fieldType, typeResolver);
|
|
110
|
+
return convertObjectField(ts, f, x as Record<string, unknown>, fieldType, typeResolver);
|
|
106
111
|
}
|
|
107
|
-
return jsonToExpr(ts, f, x, typeInfo);
|
|
112
|
+
return jsonToExpr(ts, f, x, typeInfo, typeResolver);
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
/**
|
|
@@ -115,6 +120,7 @@ export function jsonToExpr(
|
|
|
115
120
|
f: tsNS.NodeFactory,
|
|
116
121
|
v: unknown,
|
|
117
122
|
typeInfo?: FieldTypeInfo,
|
|
123
|
+
typeResolver?: TypeResolver,
|
|
118
124
|
): tsNS.Expression {
|
|
119
125
|
if (v === null) return f.createNull();
|
|
120
126
|
switch (typeof v) {
|
|
@@ -132,14 +138,14 @@ export function jsonToExpr(
|
|
|
132
138
|
return createDateNewExpr(f, v.toISOString());
|
|
133
139
|
}
|
|
134
140
|
if (Array.isArray(v)) {
|
|
135
|
-
return f.createArrayLiteralExpression(v.map((x) => jsonToExpr(ts, f, x, typeInfo)));
|
|
141
|
+
return f.createArrayLiteralExpression(v.map((x) => jsonToExpr(ts, f, x, typeInfo, typeResolver)));
|
|
136
142
|
}
|
|
137
143
|
const entries = Object.entries(v as Record<string, unknown>);
|
|
138
144
|
return f.createObjectLiteralExpression(
|
|
139
145
|
entries.map(([k, x]) =>
|
|
140
146
|
f.createPropertyAssignment(
|
|
141
147
|
VALID_IDENTIFIER_REGEX.test(k) ? f.createIdentifier(k) : f.createStringLiteral(k),
|
|
142
|
-
computeFieldExpr(ts, f, x, typeInfo?.[k], typeInfo),
|
|
148
|
+
computeFieldExpr(ts, f, x, typeInfo?.[k], typeInfo, typeResolver),
|
|
143
149
|
),
|
|
144
150
|
),
|
|
145
151
|
false,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type tsNS from 'typescript';
|
|
2
|
-
import { type FieldTypeInfo, jsonToExpr } from '../ast/emit-helpers';
|
|
2
|
+
import { type FieldTypeInfo, jsonToExpr, type TypeResolver } from '../ast/emit-helpers';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Build a single specs() block for a GWT entry, adapting to slice type:
|
|
@@ -46,13 +46,14 @@ function chainGivenCalls(
|
|
|
46
46
|
return base;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
const resolver = createTypeResolver(messages);
|
|
49
50
|
const firstGiven = g.given[0];
|
|
50
51
|
const firstTypeInfo = messages ? getFieldTypeInfo(messages, firstGiven.eventRef) : undefined;
|
|
51
52
|
|
|
52
53
|
let result = f.createCallExpression(
|
|
53
54
|
f.createPropertyAccessExpression(base, f.createIdentifier('given')),
|
|
54
55
|
[f.createTypeReferenceNode(firstGiven.eventRef, undefined)],
|
|
55
|
-
[jsonToExpr(ts, f, firstGiven.exampleData, firstTypeInfo)],
|
|
56
|
+
[jsonToExpr(ts, f, firstGiven.exampleData, firstTypeInfo, resolver)],
|
|
56
57
|
);
|
|
57
58
|
|
|
58
59
|
for (let i = 1; i < g.given.length; i++) {
|
|
@@ -61,7 +62,7 @@ function chainGivenCalls(
|
|
|
61
62
|
result = f.createCallExpression(
|
|
62
63
|
f.createPropertyAccessExpression(result, f.createIdentifier('and')),
|
|
63
64
|
[f.createTypeReferenceNode(givenEvent.eventRef, undefined)],
|
|
64
|
-
[jsonToExpr(ts, f, givenEvent.exampleData, typeInfo)],
|
|
65
|
+
[jsonToExpr(ts, f, givenEvent.exampleData, typeInfo, resolver)],
|
|
65
66
|
);
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -109,10 +110,11 @@ function chainWhenCall(
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
const typeInfo = messages !== undefined ? getFieldTypeInfo(messages, typeRef) : undefined;
|
|
113
|
+
const resolver = createTypeResolver(messages);
|
|
112
114
|
return f.createCallExpression(
|
|
113
115
|
f.createPropertyAccessExpression(base, f.createIdentifier('when')),
|
|
114
116
|
typeRef !== '' ? [f.createTypeReferenceNode(typeRef, undefined)] : undefined,
|
|
115
|
-
[jsonToExpr(ts, f, exampleData, typeInfo)],
|
|
117
|
+
[jsonToExpr(ts, f, exampleData, typeInfo, resolver)],
|
|
116
118
|
);
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -129,7 +131,8 @@ function chainThenCall(
|
|
|
129
131
|
const firstThenItem = g.then[0];
|
|
130
132
|
const thenTypeRef = getThenTypeRef(firstThenItem);
|
|
131
133
|
const typeInfo = messages && thenTypeRef ? getFieldTypeInfo(messages, thenTypeRef) : undefined;
|
|
132
|
-
const
|
|
134
|
+
const resolver = createTypeResolver(messages);
|
|
135
|
+
const thenArg = buildThenItem(ts, f, firstThenItem, typeInfo, resolver);
|
|
133
136
|
const thenTypeParams = thenTypeRef ? [f.createTypeReferenceNode(thenTypeRef, undefined)] : undefined;
|
|
134
137
|
|
|
135
138
|
return f.createCallExpression(f.createPropertyAccessExpression(base, f.createIdentifier('then')), thenTypeParams, [
|
|
@@ -142,22 +145,23 @@ function buildThenItem(
|
|
|
142
145
|
f: tsNS.NodeFactory,
|
|
143
146
|
t: GWTBlock['then'][0],
|
|
144
147
|
typeInfo?: FieldTypeInfo,
|
|
148
|
+
typeResolver?: TypeResolver,
|
|
145
149
|
): tsNS.Expression {
|
|
146
150
|
const item = t as Record<string, unknown>;
|
|
147
151
|
|
|
148
152
|
if ('eventRef' in item) {
|
|
149
153
|
const e = t as { eventRef: string; exampleData: Record<string, unknown> };
|
|
150
|
-
return jsonToExpr(ts, f, e.exampleData, typeInfo);
|
|
154
|
+
return jsonToExpr(ts, f, e.exampleData, typeInfo, typeResolver);
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
if ('commandRef' in item) {
|
|
154
158
|
const c = t as { commandRef: string; exampleData: Record<string, unknown> };
|
|
155
|
-
return jsonToExpr(ts, f, c.exampleData, typeInfo);
|
|
159
|
+
return jsonToExpr(ts, f, c.exampleData, typeInfo, typeResolver);
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
if ('stateRef' in item) {
|
|
159
163
|
const s = t as { stateRef: string; exampleData: Record<string, unknown> };
|
|
160
|
-
return jsonToExpr(ts, f, s.exampleData, typeInfo);
|
|
164
|
+
return jsonToExpr(ts, f, s.exampleData, typeInfo, typeResolver);
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
if ('errorType' in item) {
|
|
@@ -309,6 +313,16 @@ function getFieldTypeInfo(
|
|
|
309
313
|
return typeInfo;
|
|
310
314
|
}
|
|
311
315
|
|
|
316
|
+
function createTypeResolver(
|
|
317
|
+
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
318
|
+
): TypeResolver | undefined {
|
|
319
|
+
if (!messages) return undefined;
|
|
320
|
+
return (typeName: string) => {
|
|
321
|
+
const info = getFieldTypeInfo(messages, typeName);
|
|
322
|
+
return Object.keys(info).length > 0 ? info : undefined;
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
312
326
|
export function buildConsolidatedGwtSpecBlock(
|
|
313
327
|
ts: typeof import('typescript'),
|
|
314
328
|
f: tsNS.NodeFactory,
|