@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/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/file-store": "1.12.0",
31
- "@auto-engineer/id": "1.12.0",
32
- "@auto-engineer/message-bus": "1.12.0"
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.0",
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 thenArg = buildThenItem(ts, f, firstThenItem, typeInfo);
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,