@auto-engineer/narrative 0.13.0 → 0.13.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/CHANGELOG.md +11 -0
- package/dist/src/commands/export-schema-runner.js +1 -1
- package/dist/src/commands/export-schema-runner.js.map +1 -1
- package/dist/src/fluent-builder.js +3 -3
- package/dist/src/fluent-builder.js.map +1 -1
- package/dist/src/getNarratives.specs.js +149 -153
- package/dist/src/getNarratives.specs.js.map +1 -1
- package/dist/src/id/addAutoIds.d.ts.map +1 -1
- package/dist/src/id/addAutoIds.js +23 -10
- package/dist/src/id/addAutoIds.js.map +1 -1
- package/dist/src/id/addAutoIds.specs.js +54 -45
- package/dist/src/id/addAutoIds.specs.js.map +1 -1
- package/dist/src/id/hasAllIds.d.ts.map +1 -1
- package/dist/src/id/hasAllIds.js +8 -3
- package/dist/src/id/hasAllIds.js.map +1 -1
- package/dist/src/id/hasAllIds.specs.js +142 -215
- package/dist/src/id/hasAllIds.specs.js.map +1 -1
- package/dist/src/index.d.ts +6 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/loader/graph.d.ts.map +1 -1
- package/dist/src/loader/graph.js +13 -6
- package/dist/src/loader/graph.js.map +1 -1
- package/dist/src/loader/ts-utils.d.ts +1 -0
- package/dist/src/loader/ts-utils.d.ts.map +1 -1
- package/dist/src/loader/ts-utils.js +95 -16
- package/dist/src/loader/ts-utils.js.map +1 -1
- package/dist/src/model-to-narrative.specs.js +531 -449
- package/dist/src/model-to-narrative.specs.js.map +1 -1
- package/dist/src/narrative-context.d.ts +8 -8
- package/dist/src/narrative-context.d.ts.map +1 -1
- package/dist/src/narrative-context.js +111 -301
- package/dist/src/narrative-context.js.map +1 -1
- package/dist/src/narrative-context.specs.js +15 -55
- package/dist/src/narrative-context.specs.js.map +1 -1
- package/dist/src/narrative.d.ts +19 -22
- package/dist/src/narrative.d.ts.map +1 -1
- package/dist/src/narrative.js +42 -71
- package/dist/src/narrative.js.map +1 -1
- package/dist/src/samples/test-with-ids.narrative.js +13 -29
- package/dist/src/samples/test-with-ids.narrative.js.map +1 -1
- package/dist/src/schema.d.ts +2704 -8293
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +26 -47
- package/dist/src/schema.js.map +1 -1
- package/dist/src/slice-builder.js +3 -3
- package/dist/src/slice-builder.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 +118 -74
- package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts +9 -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 +112 -112
- package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/imports.d.ts +1 -1
- package/dist/src/transformers/model-to-narrative/generators/imports.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/imports.js +13 -9
- package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/index.d.ts.map +1 -1
- package/dist/src/transformers/narrative-to-model/index.js +50 -23
- package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js +100 -90
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/commands/export-schema-runner.ts +3 -1
- package/src/fluent-builder.ts +3 -3
- package/src/getNarratives.specs.ts +168 -176
- package/src/id/addAutoIds.specs.ts +54 -47
- package/src/id/addAutoIds.ts +28 -11
- package/src/id/hasAllIds.specs.ts +147 -245
- package/src/id/hasAllIds.ts +11 -4
- package/src/index.ts +9 -12
- package/src/loader/graph.ts +23 -6
- package/src/loader/ts-utils.ts +169 -26
- package/src/model-to-narrative.specs.ts +531 -449
- package/src/narrative-context.specs.ts +73 -116
- package/src/narrative-context.ts +127 -374
- package/src/narrative.ts +70 -120
- package/src/samples/test-with-ids.narrative.ts +23 -31
- package/src/schema.ts +33 -52
- package/src/slice-builder.ts +3 -3
- package/src/transformers/model-to-narrative/generators/flow.ts +191 -85
- package/src/transformers/model-to-narrative/generators/gwt.ts +195 -178
- package/src/transformers/model-to-narrative/generators/imports.ts +13 -9
- package/src/transformers/narrative-to-model/index.ts +87 -26
- package/src/transformers/narrative-to-model/type-inference.specs.ts +100 -90
|
@@ -35,188 +35,215 @@ export type GWTBlock = {
|
|
|
35
35
|
>;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
function
|
|
38
|
+
function buildStandaloneGivenStatements(
|
|
39
39
|
ts: typeof import('typescript'),
|
|
40
40
|
f: tsNS.NodeFactory,
|
|
41
41
|
g: GWTBlock,
|
|
42
42
|
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
43
|
-
): tsNS.
|
|
44
|
-
|
|
45
|
-
const item = t as Record<string, unknown>;
|
|
46
|
-
|
|
47
|
-
// Handle event references: just return the data (type handled by generic parameter)
|
|
48
|
-
if ('eventRef' in item) {
|
|
49
|
-
const e = t as { eventRef: string; exampleData: Record<string, unknown> };
|
|
50
|
-
const typeInfo = messages ? getFieldTypeInfo(messages, e.eventRef) : undefined;
|
|
51
|
-
return jsonToExpr(ts, f, e.exampleData, typeInfo);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Handle command references: just return the data (type handled by generic parameter)
|
|
55
|
-
if ('commandRef' in item) {
|
|
56
|
-
const c = t as { commandRef: string; exampleData: Record<string, unknown> };
|
|
57
|
-
const typeInfo = messages ? getFieldTypeInfo(messages, c.commandRef) : undefined;
|
|
58
|
-
return jsonToExpr(ts, f, c.exampleData, typeInfo);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Handle state references: just return the data (type handled by generic parameter)
|
|
62
|
-
if ('stateRef' in item) {
|
|
63
|
-
const s = t as { stateRef: string; exampleData: Record<string, unknown> };
|
|
64
|
-
const typeInfo = messages ? getFieldTypeInfo(messages, s.stateRef) : undefined;
|
|
65
|
-
return jsonToExpr(ts, f, s.exampleData, typeInfo);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Handle error objects: { errorType: 'ValidationError', message: '...' }
|
|
69
|
-
if ('errorType' in item) {
|
|
70
|
-
const err = t as { errorType: 'IllegalStateError' | 'ValidationError' | 'NotFoundError'; message?: string };
|
|
71
|
-
return f.createObjectLiteralExpression(
|
|
72
|
-
[
|
|
73
|
-
f.createPropertyAssignment('errorType', f.createStringLiteral(err.errorType)),
|
|
74
|
-
...(err.message !== null && err.message !== undefined
|
|
75
|
-
? [f.createPropertyAssignment('message', f.createStringLiteral(err.message))]
|
|
76
|
-
: []),
|
|
77
|
-
],
|
|
78
|
-
false,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return f.createNull();
|
|
83
|
-
});
|
|
84
|
-
}
|
|
43
|
+
): tsNS.Statement[] {
|
|
44
|
+
const statements: tsNS.Statement[] = [];
|
|
85
45
|
|
|
86
|
-
function getDescriptions(
|
|
87
|
-
g: GWTBlock & { description?: string; ruleDescription?: string; exampleDescription?: string },
|
|
88
|
-
) {
|
|
89
|
-
const ruleDesc =
|
|
90
|
-
g.ruleDescription !== null && g.ruleDescription !== undefined && g.ruleDescription !== ''
|
|
91
|
-
? g.ruleDescription
|
|
92
|
-
: 'Generated rule description';
|
|
93
|
-
|
|
94
|
-
const exampleDesc =
|
|
95
|
-
g.exampleDescription !== null && g.exampleDescription !== undefined && g.exampleDescription !== ''
|
|
96
|
-
? g.exampleDescription
|
|
97
|
-
: g.description !== null && g.description !== undefined && g.description !== ''
|
|
98
|
-
? g.description
|
|
99
|
-
: 'Generated example description';
|
|
100
|
-
|
|
101
|
-
return { ruleDesc, exampleDesc };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function addGivenToChain(
|
|
105
|
-
ts: typeof import('typescript'),
|
|
106
|
-
f: tsNS.NodeFactory,
|
|
107
|
-
exampleChain: tsNS.Expression,
|
|
108
|
-
g: GWTBlock,
|
|
109
|
-
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
110
|
-
): tsNS.Expression {
|
|
111
46
|
if (hasGivenEvents(g) && g.given !== null && g.given !== undefined && g.given.length > 0) {
|
|
112
|
-
// Start with the first given
|
|
113
47
|
const firstGiven = g.given[0];
|
|
114
48
|
const firstTypeInfo = messages ? getFieldTypeInfo(messages, firstGiven.eventRef) : undefined;
|
|
115
|
-
|
|
116
|
-
f.
|
|
117
|
-
|
|
118
|
-
|
|
49
|
+
statements.push(
|
|
50
|
+
f.createExpressionStatement(
|
|
51
|
+
f.createCallExpression(
|
|
52
|
+
f.createIdentifier('given'),
|
|
53
|
+
[f.createTypeReferenceNode(firstGiven.eventRef, undefined)],
|
|
54
|
+
[jsonToExpr(ts, f, firstGiven.exampleData, firstTypeInfo)],
|
|
55
|
+
),
|
|
56
|
+
),
|
|
119
57
|
);
|
|
120
58
|
|
|
121
|
-
// Chain additional givens with .and()
|
|
122
59
|
for (let i = 1; i < g.given.length; i++) {
|
|
123
60
|
const givenEvent = g.given[i];
|
|
124
61
|
const typeInfo = messages ? getFieldTypeInfo(messages, givenEvent.eventRef) : undefined;
|
|
125
|
-
|
|
126
|
-
f.
|
|
127
|
-
|
|
128
|
-
|
|
62
|
+
statements.push(
|
|
63
|
+
f.createExpressionStatement(
|
|
64
|
+
f.createCallExpression(
|
|
65
|
+
f.createIdentifier('and'),
|
|
66
|
+
[f.createTypeReferenceNode(givenEvent.eventRef, undefined)],
|
|
67
|
+
[jsonToExpr(ts, f, givenEvent.exampleData, typeInfo)],
|
|
68
|
+
),
|
|
69
|
+
),
|
|
129
70
|
);
|
|
130
71
|
}
|
|
131
|
-
|
|
132
|
-
return currentChain;
|
|
133
72
|
}
|
|
134
|
-
|
|
73
|
+
|
|
74
|
+
return statements;
|
|
135
75
|
}
|
|
136
76
|
|
|
137
|
-
function
|
|
77
|
+
function createWhenCallExpression(
|
|
138
78
|
ts: typeof import('typescript'),
|
|
139
79
|
f: tsNS.NodeFactory,
|
|
140
|
-
|
|
141
|
-
|
|
80
|
+
typeRef: string,
|
|
81
|
+
exampleData: Record<string, unknown>,
|
|
142
82
|
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
143
|
-
): tsNS.
|
|
144
|
-
const typeInfo = messages ? getFieldTypeInfo(messages,
|
|
145
|
-
return f.
|
|
146
|
-
f.
|
|
147
|
-
|
|
148
|
-
|
|
83
|
+
): tsNS.Statement {
|
|
84
|
+
const typeInfo = messages !== undefined ? getFieldTypeInfo(messages, typeRef) : undefined;
|
|
85
|
+
return f.createExpressionStatement(
|
|
86
|
+
f.createCallExpression(
|
|
87
|
+
f.createIdentifier('when'),
|
|
88
|
+
typeRef !== '' ? [f.createTypeReferenceNode(typeRef, undefined)] : undefined,
|
|
89
|
+
[jsonToExpr(ts, f, exampleData, typeInfo)],
|
|
90
|
+
),
|
|
149
91
|
);
|
|
150
92
|
}
|
|
151
93
|
|
|
152
|
-
function
|
|
94
|
+
function buildCommandWhenStatement(
|
|
153
95
|
ts: typeof import('typescript'),
|
|
154
96
|
f: tsNS.NodeFactory,
|
|
155
|
-
|
|
156
|
-
firstEvent: { eventRef: string; exampleData: Record<string, unknown> },
|
|
97
|
+
when: { commandRef: string; exampleData: Record<string, unknown> },
|
|
157
98
|
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
158
|
-
): tsNS.
|
|
159
|
-
|
|
160
|
-
return f.createCallExpression(
|
|
161
|
-
f.createPropertyAccessExpression(exampleChain, 'when'),
|
|
162
|
-
firstEvent.eventRef ? [f.createTypeReferenceNode(firstEvent.eventRef, undefined)] : undefined,
|
|
163
|
-
[jsonToExpr(ts, f, firstEvent.exampleData, typeInfo)],
|
|
164
|
-
);
|
|
99
|
+
): tsNS.Statement {
|
|
100
|
+
return createWhenCallExpression(ts, f, when.commandRef, when.exampleData, messages);
|
|
165
101
|
}
|
|
166
102
|
|
|
167
|
-
function
|
|
168
|
-
|
|
103
|
+
function buildEventWhenStatement(
|
|
104
|
+
ts: typeof import('typescript'),
|
|
105
|
+
f: tsNS.NodeFactory,
|
|
106
|
+
event: { eventRef: string; exampleData: Record<string, unknown> },
|
|
107
|
+
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
108
|
+
): tsNS.Statement {
|
|
109
|
+
return createWhenCallExpression(ts, f, event.eventRef, event.exampleData, messages);
|
|
169
110
|
}
|
|
170
111
|
|
|
171
|
-
function
|
|
172
|
-
|
|
112
|
+
function isQueryWithSingleEvent(
|
|
113
|
+
sliceKind: string,
|
|
114
|
+
when: GWTBlock['when'],
|
|
115
|
+
): when is { eventRef: string; exampleData: Record<string, unknown> } {
|
|
116
|
+
return sliceKind === 'query' && when !== undefined && !Array.isArray(when) && 'eventRef' in when;
|
|
173
117
|
}
|
|
174
118
|
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
if (Array.isArray(whenData) && whenData.length === 0) return true;
|
|
178
|
-
if (typeof whenData === 'object' && 'eventRef' in whenData) {
|
|
179
|
-
return isEmptyEventWhen(whenData as { eventRef: string; exampleData: Record<string, unknown> });
|
|
180
|
-
}
|
|
181
|
-
if (typeof whenData === 'object' && 'commandRef' in whenData) {
|
|
182
|
-
return isEmptyCommandWhen(whenData as { commandRef: string; exampleData: Record<string, unknown> });
|
|
183
|
-
}
|
|
184
|
-
return false;
|
|
119
|
+
function isReactOrQueryWithEvents(sliceKind: string): boolean {
|
|
120
|
+
return sliceKind === 'react' || sliceKind === 'query';
|
|
185
121
|
}
|
|
186
122
|
|
|
187
|
-
function
|
|
123
|
+
function buildStandaloneWhenStatement(
|
|
188
124
|
ts: typeof import('typescript'),
|
|
189
125
|
f: tsNS.NodeFactory,
|
|
190
|
-
|
|
191
|
-
when: GWTBlock['when'],
|
|
126
|
+
g: GWTBlock,
|
|
192
127
|
sliceKind: 'command' | 'react' | 'query' | 'experience',
|
|
193
128
|
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
194
|
-
): tsNS.
|
|
129
|
+
): tsNS.Statement | null {
|
|
130
|
+
if (isEmptyWhen(g.when)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const when = g.when;
|
|
195
135
|
if (sliceKind === 'command' && isWhenCommand(when)) {
|
|
196
|
-
return
|
|
136
|
+
return buildCommandWhenStatement(ts, f, when, messages);
|
|
197
137
|
}
|
|
198
|
-
if ((sliceKind
|
|
199
|
-
return
|
|
138
|
+
if (isReactOrQueryWithEvents(sliceKind) && isWhenEvents(when) && when.length > 0) {
|
|
139
|
+
return buildEventWhenStatement(ts, f, when[0], messages);
|
|
200
140
|
}
|
|
201
|
-
if (sliceKind
|
|
202
|
-
|
|
203
|
-
return addEventWhenToChain(ts, f, exampleChain, whenEvent, messages);
|
|
141
|
+
if (isQueryWithSingleEvent(sliceKind, when)) {
|
|
142
|
+
return buildEventWhenStatement(ts, f, when, messages);
|
|
204
143
|
}
|
|
205
|
-
return
|
|
144
|
+
return null;
|
|
206
145
|
}
|
|
207
146
|
|
|
208
|
-
function
|
|
147
|
+
function buildStandaloneThenStatement(
|
|
209
148
|
ts: typeof import('typescript'),
|
|
210
149
|
f: tsNS.NodeFactory,
|
|
211
|
-
exampleChain: tsNS.Expression,
|
|
212
150
|
g: GWTBlock,
|
|
213
|
-
sliceKind: 'command' | 'react' | 'query' | 'experience',
|
|
214
151
|
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
152
|
+
): tsNS.Statement {
|
|
153
|
+
const firstThenItem = g.then[0];
|
|
154
|
+
const thenTypeRef = getThenTypeRef(firstThenItem);
|
|
155
|
+
const typeInfo = messages && thenTypeRef ? getFieldTypeInfo(messages, thenTypeRef) : undefined;
|
|
156
|
+
const thenArg = buildThenItem(ts, f, firstThenItem, typeInfo);
|
|
157
|
+
const thenTypeParams = thenTypeRef ? [f.createTypeReferenceNode(thenTypeRef, undefined)] : undefined;
|
|
158
|
+
|
|
159
|
+
return f.createExpressionStatement(f.createCallExpression(f.createIdentifier('then'), thenTypeParams, [thenArg]));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function buildThenItem(
|
|
163
|
+
ts: typeof import('typescript'),
|
|
164
|
+
f: tsNS.NodeFactory,
|
|
165
|
+
t: GWTBlock['then'][0],
|
|
166
|
+
typeInfo?: FieldTypeInfo,
|
|
215
167
|
): tsNS.Expression {
|
|
216
|
-
|
|
217
|
-
|
|
168
|
+
const item = t as Record<string, unknown>;
|
|
169
|
+
|
|
170
|
+
if ('eventRef' in item) {
|
|
171
|
+
const e = t as { eventRef: string; exampleData: Record<string, unknown> };
|
|
172
|
+
return jsonToExpr(ts, f, e.exampleData, typeInfo);
|
|
218
173
|
}
|
|
219
|
-
|
|
174
|
+
|
|
175
|
+
if ('commandRef' in item) {
|
|
176
|
+
const c = t as { commandRef: string; exampleData: Record<string, unknown> };
|
|
177
|
+
return jsonToExpr(ts, f, c.exampleData, typeInfo);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if ('stateRef' in item) {
|
|
181
|
+
const s = t as { stateRef: string; exampleData: Record<string, unknown> };
|
|
182
|
+
return jsonToExpr(ts, f, s.exampleData, typeInfo);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if ('errorType' in item) {
|
|
186
|
+
const err = t as { errorType: 'IllegalStateError' | 'ValidationError' | 'NotFoundError'; message?: string };
|
|
187
|
+
return f.createObjectLiteralExpression(
|
|
188
|
+
[
|
|
189
|
+
f.createPropertyAssignment('errorType', f.createStringLiteral(err.errorType)),
|
|
190
|
+
...(err.message !== null && err.message !== undefined
|
|
191
|
+
? [f.createPropertyAssignment('message', f.createStringLiteral(err.message))]
|
|
192
|
+
: []),
|
|
193
|
+
],
|
|
194
|
+
false,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return f.createNull();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getDescriptions(
|
|
202
|
+
g: GWTBlock & { description?: string; ruleDescription?: string; exampleDescription?: string },
|
|
203
|
+
) {
|
|
204
|
+
const ruleDesc =
|
|
205
|
+
g.ruleDescription !== null && g.ruleDescription !== undefined && g.ruleDescription !== ''
|
|
206
|
+
? g.ruleDescription
|
|
207
|
+
: 'Generated rule description';
|
|
208
|
+
|
|
209
|
+
const exampleDesc =
|
|
210
|
+
g.exampleDescription !== null && g.exampleDescription !== undefined && g.exampleDescription !== ''
|
|
211
|
+
? g.exampleDescription
|
|
212
|
+
: g.description !== null && g.description !== undefined && g.description !== ''
|
|
213
|
+
? g.description
|
|
214
|
+
: 'Generated example description';
|
|
215
|
+
|
|
216
|
+
return { ruleDesc, exampleDesc };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isEmptyEventWhen(when: { eventRef: string; exampleData: Record<string, unknown> }): boolean {
|
|
220
|
+
return Object.keys(when.exampleData).length === 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isEmptyCommandWhen(when: { commandRef: string; exampleData: Record<string, unknown> }): boolean {
|
|
224
|
+
return (!when.commandRef || when.commandRef === '') && Object.keys(when.exampleData).length === 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isEmptyWhen(whenData: GWTBlock['when']): boolean {
|
|
228
|
+
if (!whenData) return true;
|
|
229
|
+
if (Array.isArray(whenData)) {
|
|
230
|
+
if (whenData.length === 0) return true;
|
|
231
|
+
const firstItem = whenData[0];
|
|
232
|
+
if ('eventRef' in firstItem) {
|
|
233
|
+
return isEmptyEventWhen(firstItem as { eventRef: string; exampleData: Record<string, unknown> });
|
|
234
|
+
}
|
|
235
|
+
if ('commandRef' in firstItem) {
|
|
236
|
+
return isEmptyCommandWhen(firstItem as { commandRef: string; exampleData: Record<string, unknown> });
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (typeof whenData === 'object' && 'eventRef' in whenData) {
|
|
241
|
+
return isEmptyEventWhen(whenData as { eventRef: string; exampleData: Record<string, unknown> });
|
|
242
|
+
}
|
|
243
|
+
if (typeof whenData === 'object' && 'commandRef' in whenData) {
|
|
244
|
+
return isEmptyCommandWhen(whenData as { commandRef: string; exampleData: Record<string, unknown> });
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
220
247
|
}
|
|
221
248
|
|
|
222
249
|
function getThenTypeRef(firstThenItem: GWTBlock['then'][0]): string {
|
|
@@ -230,50 +257,37 @@ function getThenTypeRef(firstThenItem: GWTBlock['then'][0]): string {
|
|
|
230
257
|
return '';
|
|
231
258
|
}
|
|
232
259
|
|
|
233
|
-
function addThenToChain(
|
|
234
|
-
ts: typeof import('typescript'),
|
|
235
|
-
f: tsNS.NodeFactory,
|
|
236
|
-
exampleChain: tsNS.Expression,
|
|
237
|
-
g: GWTBlock,
|
|
238
|
-
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
239
|
-
): tsNS.Expression {
|
|
240
|
-
const thenItems = buildThenItems(ts, f, g, messages);
|
|
241
|
-
const firstThenItem = g.then[0];
|
|
242
|
-
const thenTypeRef = getThenTypeRef(firstThenItem);
|
|
243
|
-
|
|
244
|
-
// Always use single object format for .then()
|
|
245
|
-
const thenArg = thenItems[0];
|
|
246
|
-
const thenTypeParams = thenTypeRef ? [f.createTypeReferenceNode(thenTypeRef, undefined)] : undefined;
|
|
247
|
-
|
|
248
|
-
return f.createCallExpression(f.createPropertyAccessExpression(exampleChain, 'then'), thenTypeParams, [thenArg]);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
260
|
export function buildGwtSpecBlock(
|
|
252
261
|
ts: typeof import('typescript'),
|
|
253
262
|
f: tsNS.NodeFactory,
|
|
254
263
|
g: GWTBlock & { description?: string; ruleDescription?: string; exampleDescription?: string; ruleId?: string },
|
|
255
264
|
sliceKind: 'command' | 'react' | 'query' | 'experience',
|
|
265
|
+
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
256
266
|
): tsNS.Statement {
|
|
257
267
|
const { ruleDesc, exampleDesc } = getDescriptions(g);
|
|
258
268
|
|
|
259
|
-
|
|
260
|
-
|
|
269
|
+
const bodyStatements: tsNS.Statement[] = [];
|
|
270
|
+
bodyStatements.push(...buildStandaloneGivenStatements(ts, f, g, messages));
|
|
271
|
+
const whenStmt = buildStandaloneWhenStatement(ts, f, g, sliceKind, messages);
|
|
272
|
+
if (whenStmt) {
|
|
273
|
+
bodyStatements.push(whenStmt);
|
|
274
|
+
}
|
|
275
|
+
bodyStatements.push(buildStandaloneThenStatement(ts, f, g, messages));
|
|
276
|
+
|
|
277
|
+
const exampleCall = f.createCallExpression(f.createIdentifier('example'), undefined, [
|
|
261
278
|
f.createStringLiteral(exampleDesc),
|
|
279
|
+
f.createArrowFunction(
|
|
280
|
+
undefined,
|
|
281
|
+
undefined,
|
|
282
|
+
[],
|
|
283
|
+
undefined,
|
|
284
|
+
f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
|
|
285
|
+
f.createBlock(bodyStatements, true),
|
|
286
|
+
),
|
|
262
287
|
]);
|
|
263
288
|
|
|
264
|
-
// Add .given() if present
|
|
265
|
-
exampleChain = addGivenToChain(ts, f, exampleChain, g);
|
|
266
|
-
|
|
267
|
-
// Add .when()
|
|
268
|
-
exampleChain = addWhenToChain(ts, f, exampleChain, g, sliceKind);
|
|
269
|
-
|
|
270
|
-
// Add .then()
|
|
271
|
-
exampleChain = addThenToChain(ts, f, exampleChain, g);
|
|
272
|
-
|
|
273
|
-
// Create the rule() call containing the example
|
|
274
289
|
const ruleArgs: tsNS.Expression[] = [f.createStringLiteral(ruleDesc)];
|
|
275
290
|
|
|
276
|
-
// Add rule ID if provided
|
|
277
291
|
if (g.ruleId !== null && g.ruleId !== undefined) {
|
|
278
292
|
ruleArgs.push(f.createStringLiteral(g.ruleId));
|
|
279
293
|
}
|
|
@@ -285,7 +299,7 @@ export function buildGwtSpecBlock(
|
|
|
285
299
|
[],
|
|
286
300
|
undefined,
|
|
287
301
|
f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
|
|
288
|
-
f.createBlock([f.createExpressionStatement(
|
|
302
|
+
f.createBlock([f.createExpressionStatement(exampleCall)], true),
|
|
289
303
|
),
|
|
290
304
|
);
|
|
291
305
|
|
|
@@ -323,33 +337,36 @@ export function buildConsolidatedGwtSpecBlock(
|
|
|
323
337
|
sliceKind: 'command' | 'react' | 'query' | 'experience',
|
|
324
338
|
messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
|
|
325
339
|
): tsNS.Statement {
|
|
326
|
-
// Build example chains for each GWT block
|
|
327
340
|
const exampleStatements: tsNS.Statement[] = [];
|
|
328
341
|
|
|
329
342
|
for (const g of gwtBlocks) {
|
|
330
343
|
const { exampleDesc } = getDescriptions(g);
|
|
331
344
|
|
|
332
|
-
|
|
333
|
-
|
|
345
|
+
const bodyStatements: tsNS.Statement[] = [];
|
|
346
|
+
bodyStatements.push(...buildStandaloneGivenStatements(ts, f, g, messages));
|
|
347
|
+
const whenStmt = buildStandaloneWhenStatement(ts, f, g, sliceKind, messages);
|
|
348
|
+
if (whenStmt) {
|
|
349
|
+
bodyStatements.push(whenStmt);
|
|
350
|
+
}
|
|
351
|
+
bodyStatements.push(buildStandaloneThenStatement(ts, f, g, messages));
|
|
352
|
+
|
|
353
|
+
const exampleCall = f.createCallExpression(f.createIdentifier('example'), undefined, [
|
|
334
354
|
f.createStringLiteral(exampleDesc),
|
|
355
|
+
f.createArrowFunction(
|
|
356
|
+
undefined,
|
|
357
|
+
undefined,
|
|
358
|
+
[],
|
|
359
|
+
undefined,
|
|
360
|
+
f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
|
|
361
|
+
f.createBlock(bodyStatements, true),
|
|
362
|
+
),
|
|
335
363
|
]);
|
|
336
364
|
|
|
337
|
-
|
|
338
|
-
exampleChain = addGivenToChain(ts, f, exampleChain, g, messages);
|
|
339
|
-
|
|
340
|
-
// Add .when()
|
|
341
|
-
exampleChain = addWhenToChain(ts, f, exampleChain, g, sliceKind, messages);
|
|
342
|
-
|
|
343
|
-
// Add .then()
|
|
344
|
-
exampleChain = addThenToChain(ts, f, exampleChain, g, messages);
|
|
345
|
-
|
|
346
|
-
exampleStatements.push(f.createExpressionStatement(exampleChain));
|
|
365
|
+
exampleStatements.push(f.createExpressionStatement(exampleCall));
|
|
347
366
|
}
|
|
348
367
|
|
|
349
|
-
// Create the rule() call containing all examples
|
|
350
368
|
const ruleArgs: tsNS.Expression[] = [f.createStringLiteral(rule.description)];
|
|
351
369
|
|
|
352
|
-
// Add rule ID if provided
|
|
353
370
|
if (rule.id !== null && rule.id !== undefined) {
|
|
354
371
|
ruleArgs.push(f.createStringLiteral(rule.id));
|
|
355
372
|
}
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
type BuildImportsOpts = { flowImport: string; integrationImport: string };
|
|
2
2
|
|
|
3
3
|
export const ALL_FLOW_FUNCTION_NAMES = [
|
|
4
|
+
'and',
|
|
4
5
|
'command',
|
|
5
|
-
'
|
|
6
|
-
'react',
|
|
7
|
-
'experience',
|
|
8
|
-
'narrative',
|
|
6
|
+
'data',
|
|
9
7
|
'describe',
|
|
10
|
-
'it',
|
|
11
|
-
'specs',
|
|
12
|
-
'rule',
|
|
13
8
|
'example',
|
|
9
|
+
'experience',
|
|
10
|
+
'given',
|
|
14
11
|
'gql',
|
|
15
|
-
'
|
|
16
|
-
'
|
|
12
|
+
'it',
|
|
13
|
+
'narrative',
|
|
14
|
+
'query',
|
|
15
|
+
'react',
|
|
16
|
+
'rule',
|
|
17
17
|
'sink',
|
|
18
|
+
'source',
|
|
19
|
+
'specs',
|
|
20
|
+
'then',
|
|
21
|
+
'when',
|
|
18
22
|
] as const;
|
|
19
23
|
|
|
20
24
|
export function buildImports(
|
|
@@ -136,39 +136,100 @@ function getServerSpecs(slice: Slice) {
|
|
|
136
136
|
return undefined;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
interface StepWithDocString {
|
|
140
|
+
keyword: 'Given' | 'When' | 'Then' | 'And';
|
|
141
|
+
text: string;
|
|
142
|
+
docString?: Record<string, unknown>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface StepWithError {
|
|
146
|
+
keyword: 'Then';
|
|
147
|
+
error: { type: string; message?: string };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type Step = StepWithDocString | StepWithError;
|
|
151
|
+
|
|
152
|
+
function isStepWithError(step: Step): step is StepWithError {
|
|
153
|
+
return 'error' in step;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function processSteps(
|
|
157
|
+
steps: Step[],
|
|
158
|
+
slice: Slice,
|
|
159
|
+
resolveTypeAndInfo: TypeResolver,
|
|
160
|
+
messages: Map<string, Message>,
|
|
161
|
+
exampleShapeHints: ExampleShapeHints,
|
|
162
|
+
): void {
|
|
163
|
+
let effectiveKeyword: 'Given' | 'When' | 'Then' = 'Given';
|
|
164
|
+
|
|
165
|
+
for (const step of steps) {
|
|
166
|
+
if (isStepWithError(step)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const keyword = step.keyword;
|
|
171
|
+
if (keyword !== 'And') {
|
|
172
|
+
effectiveKeyword = keyword;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const refItem = {
|
|
176
|
+
eventRef: step.text,
|
|
177
|
+
commandRef: step.text,
|
|
178
|
+
stateRef: step.text,
|
|
179
|
+
exampleData: step.docString,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (effectiveKeyword === 'Given') {
|
|
183
|
+
processGiven([refItem], resolveTypeAndInfo, messages, exampleShapeHints);
|
|
184
|
+
} else if (effectiveKeyword === 'When') {
|
|
185
|
+
processWhen([refItem], slice, resolveTypeAndInfo, messages, exampleShapeHints);
|
|
186
|
+
} else if (effectiveKeyword === 'Then') {
|
|
187
|
+
processThen([refItem], resolveTypeAndInfo, messages, exampleShapeHints);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface SpecWithRules {
|
|
193
|
+
rules: Array<{ examples: Array<{ steps?: Step[] }> }>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isSpecWithRules(spec: unknown): spec is SpecWithRules {
|
|
197
|
+
return (
|
|
198
|
+
typeof spec === 'object' && spec !== null && 'rules' in spec && Array.isArray((spec as { rules: unknown[] }).rules)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function processRuleExamples(
|
|
203
|
+
rule: { examples: Array<{ steps?: Step[] }> },
|
|
204
|
+
slice: Slice,
|
|
205
|
+
resolveTypeAndInfo: TypeResolver,
|
|
206
|
+
messages: Map<string, Message>,
|
|
207
|
+
exampleShapeHints: ExampleShapeHints,
|
|
208
|
+
): void {
|
|
209
|
+
if (!Array.isArray(rule.examples)) return;
|
|
210
|
+
for (const example of rule.examples) {
|
|
211
|
+
if (Array.isArray(example.steps)) {
|
|
212
|
+
processSteps(example.steps, slice, resolveTypeAndInfo, messages, exampleShapeHints);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
139
217
|
function processSliceSpecs(
|
|
140
218
|
slice: Slice,
|
|
141
219
|
resolveTypeAndInfo: TypeResolver,
|
|
142
220
|
messages: Map<string, Message>,
|
|
143
221
|
exampleShapeHints: ExampleShapeHints,
|
|
144
222
|
): void {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Array.isArray((rule as { examples: unknown[] }).examples)
|
|
154
|
-
) {
|
|
155
|
-
const ruleObj = rule as { examples: { given?: unknown; when?: unknown; then?: unknown }[] };
|
|
156
|
-
ruleObj.examples.forEach((example) => {
|
|
157
|
-
if (example.given !== undefined && example.given !== null) {
|
|
158
|
-
const givenArray = Array.isArray(example.given) ? example.given : [example.given];
|
|
159
|
-
processGiven(givenArray, resolveTypeAndInfo, messages, exampleShapeHints);
|
|
160
|
-
}
|
|
161
|
-
if (example.when !== undefined && example.when !== null) {
|
|
162
|
-
const whenArray = Array.isArray(example.when) ? example.when : [example.when];
|
|
163
|
-
processWhen(whenArray, slice, resolveTypeAndInfo, messages, exampleShapeHints);
|
|
164
|
-
}
|
|
165
|
-
if (example.then !== undefined && example.then !== null) {
|
|
166
|
-
const thenArray = Array.isArray(example.then) ? example.then : [example.then];
|
|
167
|
-
processThen(thenArray, resolveTypeAndInfo, messages, exampleShapeHints);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
223
|
+
const serverSpecs = getServerSpecs(slice);
|
|
224
|
+
|
|
225
|
+
if (serverSpecs === undefined || !Array.isArray(serverSpecs)) return;
|
|
226
|
+
|
|
227
|
+
for (const spec of serverSpecs) {
|
|
228
|
+
if (isSpecWithRules(spec)) {
|
|
229
|
+
for (const rule of spec.rules) {
|
|
230
|
+
processRuleExamples(rule, slice, resolveTypeAndInfo, messages, exampleShapeHints);
|
|
170
231
|
}
|
|
171
|
-
}
|
|
232
|
+
}
|
|
172
233
|
}
|
|
173
234
|
}
|
|
174
235
|
|