@auto-engineer/narrative 1.3.3 → 1.4.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 +6 -6
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +31 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/loader/ts-utils.d.ts +2 -2
- package/dist/src/loader/ts-utils.d.ts.map +1 -1
- package/dist/src/loader/ts-utils.js +7 -1
- package/dist/src/loader/ts-utils.js.map +1 -1
- package/dist/src/schema.d.ts +226 -20
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +6 -3
- package/dist/src/schema.js.map +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 +1 -0
- package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/module-code.js +10 -2
- package/dist/src/transformers/model-to-narrative/generators/module-code.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/types.d.ts +1 -1
- package/dist/src/transformers/model-to-narrative/generators/types.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/types.js +2 -1
- package/dist/src/transformers/model-to-narrative/generators/types.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/messages.d.ts +1 -1
- package/dist/src/transformers/narrative-to-model/messages.d.ts.map +1 -1
- package/dist/src/transformers/narrative-to-model/messages.js +9 -1
- package/dist/src/transformers/narrative-to-model/messages.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/spec-processors.d.ts +22 -1
- package/dist/src/transformers/narrative-to-model/spec-processors.d.ts.map +1 -1
- package/dist/src/transformers/narrative-to-model/spec-processors.js +81 -0
- package/dist/src/transformers/narrative-to-model/spec-processors.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/type-inference.d.ts +1 -1
- package/dist/src/transformers/narrative-to-model/type-inference.d.ts.map +1 -1
- package/dist/src/transformers/narrative-to-model/type-inference.js.map +1 -1
- package/dist/src/types.d.ts +4 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/index.ts +4 -0
- package/src/loader/ts-utils.ts +16 -10
- package/src/model-to-narrative.specs.ts +123 -0
- package/src/schema.ts +7 -2
- package/src/transformers/model-to-narrative/generators/imports.ts +1 -0
- package/src/transformers/model-to-narrative/generators/module-code.ts +11 -2
- package/src/transformers/model-to-narrative/generators/types.ts +4 -5
- package/src/transformers/narrative-to-model/index.ts +5 -5
- package/src/transformers/narrative-to-model/messages.ts +12 -3
- package/src/transformers/narrative-to-model/spec-processors.specs.ts +241 -0
- package/src/transformers/narrative-to-model/spec-processors.ts +91 -4
- package/src/transformers/narrative-to-model/type-inference.ts +4 -4
- package/src/types.ts +5 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { Message } from '../../index';
|
|
3
|
+
import type { ExampleShapeHints } from './example-shapes';
|
|
4
|
+
import { detectQueryAction, extractQueryNameFromRequest, processWhen } from './spec-processors';
|
|
5
|
+
|
|
6
|
+
describe('spec-processors', () => {
|
|
7
|
+
describe('extractQueryNameFromRequest', () => {
|
|
8
|
+
it('should extract query name from simple GraphQL query', () => {
|
|
9
|
+
const request = 'query ViewWorkoutPlan { workoutPlan { id } }';
|
|
10
|
+
expect(extractQueryNameFromRequest(request)).toBe('ViewWorkoutPlan');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should extract query name from GraphQL query with variables', () => {
|
|
14
|
+
const request = 'query ViewWorkoutPlan($workoutId: ID!) { workoutPlan(id: $workoutId) { id name } }';
|
|
15
|
+
expect(extractQueryNameFromRequest(request)).toBe('ViewWorkoutPlan');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should extract query name case-insensitively', () => {
|
|
19
|
+
const request = 'QUERY GetUserProfile { user { id } }';
|
|
20
|
+
expect(extractQueryNameFromRequest(request)).toBe('GetUserProfile');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return null for undefined request', () => {
|
|
24
|
+
expect(extractQueryNameFromRequest(undefined)).toBe(null);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return null for empty string', () => {
|
|
28
|
+
expect(extractQueryNameFromRequest('')).toBe(null);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should return null for mutation', () => {
|
|
32
|
+
const request = 'mutation CreateWorkout { createWorkout { id } }';
|
|
33
|
+
expect(extractQueryNameFromRequest(request)).toBe(null);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should extract query name from JSON AST format', () => {
|
|
37
|
+
const ast = {
|
|
38
|
+
kind: 'Document',
|
|
39
|
+
definitions: [
|
|
40
|
+
{
|
|
41
|
+
kind: 'OperationDefinition',
|
|
42
|
+
operation: 'query',
|
|
43
|
+
name: { value: 'ListWorkouts' },
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
expect(extractQueryNameFromRequest(JSON.stringify(ast))).toBe('ListWorkouts');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('detectQueryAction', () => {
|
|
52
|
+
it('should return true when whenText exactly matches query name from request', () => {
|
|
53
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan($id: ID!) { workoutPlan { id } }' };
|
|
54
|
+
expect(detectQueryAction('ViewWorkoutPlan', slice)).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should return true when whenText matches query name case-insensitively', () => {
|
|
58
|
+
const slice = { type: 'query', request: 'query viewWorkoutPlan { workoutPlan { id } }' };
|
|
59
|
+
expect(detectQueryAction('ViewWorkoutPlan', slice)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should return false for command slices', () => {
|
|
63
|
+
const slice = { type: 'command', request: 'mutation CreateWorkout { createWorkout { id } }' };
|
|
64
|
+
expect(detectQueryAction('ViewWorkoutPlan', slice)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return false when no request field is provided (no naming convention fallback)', () => {
|
|
68
|
+
const slice = { type: 'query' };
|
|
69
|
+
// Without request field, cannot determine if it's a query action
|
|
70
|
+
expect(detectQueryAction('ViewWorkoutPlan', slice)).toBe(false);
|
|
71
|
+
expect(detectQueryAction('GetUserProfile', slice)).toBe(false);
|
|
72
|
+
expect(detectQueryAction('ListWorkouts', slice)).toBe(false);
|
|
73
|
+
expect(detectQueryAction('FindUserById', slice)).toBe(false);
|
|
74
|
+
expect(detectQueryAction('SearchProducts', slice)).toBe(false);
|
|
75
|
+
expect(detectQueryAction('FetchOrders', slice)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should return false for event names in query slices', () => {
|
|
79
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan { workoutPlan { id } }' };
|
|
80
|
+
expect(detectQueryAction('WorkoutPlanCreated', slice)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return true for case-insensitive match to query name from request', () => {
|
|
84
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan { workoutPlan { id } }' };
|
|
85
|
+
// Case-insensitive matching works when request field is present
|
|
86
|
+
expect(detectQueryAction('viewWorkoutPlan', slice)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should return false for empty whenText', () => {
|
|
90
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan { workoutPlan { id } }' };
|
|
91
|
+
expect(detectQueryAction('', slice)).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('processWhen', () => {
|
|
96
|
+
const mockTypeResolver = (t: string) => ({
|
|
97
|
+
resolvedName: t,
|
|
98
|
+
typeInfo: undefined,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const createEmptyHints = (): ExampleShapeHints => new Map();
|
|
102
|
+
|
|
103
|
+
describe('query action detection', () => {
|
|
104
|
+
it('should create a query message when When is a query action matching request', () => {
|
|
105
|
+
const messages: Map<string, Message> = new Map();
|
|
106
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan($id: ID!) { workoutPlan { id } }' };
|
|
107
|
+
const when = { commandRef: 'ViewWorkoutPlan', exampleData: { workoutId: 'wrk_123' } };
|
|
108
|
+
|
|
109
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
110
|
+
|
|
111
|
+
// Query message should be created for query actions
|
|
112
|
+
expect(messages.size).toBe(1);
|
|
113
|
+
expect(messages.has('ViewWorkoutPlan')).toBe(true);
|
|
114
|
+
expect(messages.get('ViewWorkoutPlan')?.type).toBe('query');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should create a message when no request field (cannot detect query action)', () => {
|
|
118
|
+
const messages: Map<string, Message> = new Map();
|
|
119
|
+
const slice = { type: 'query' }; // No request field
|
|
120
|
+
const when = { commandRef: 'ViewWorkoutHistory', exampleData: { userId: 'user_456' } };
|
|
121
|
+
|
|
122
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
123
|
+
|
|
124
|
+
// Without request field, cannot detect query action - treats as event
|
|
125
|
+
expect(messages.size).toBe(1);
|
|
126
|
+
expect(messages.has('ViewWorkoutHistory')).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should create a query message when When matches query name from request', () => {
|
|
130
|
+
const messages: Map<string, Message> = new Map();
|
|
131
|
+
const slice = { type: 'query', request: 'query GetUserProfile { user { id } }' };
|
|
132
|
+
const when = { commandRef: 'GetUserProfile', exampleData: { userId: 'user_123' } };
|
|
133
|
+
|
|
134
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
135
|
+
|
|
136
|
+
// Query action detected via request field - query message created
|
|
137
|
+
expect(messages.size).toBe(1);
|
|
138
|
+
expect(messages.has('GetUserProfile')).toBe(true);
|
|
139
|
+
expect(messages.get('GetUserProfile')?.type).toBe('query');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should create a message when When is an event name in query slice', () => {
|
|
143
|
+
const messages: Map<string, Message> = new Map();
|
|
144
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan { workoutPlan { id } }' };
|
|
145
|
+
const when = { commandRef: 'WorkoutPlanUpdated', exampleData: { workoutId: 'wrk_123' } };
|
|
146
|
+
|
|
147
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
148
|
+
|
|
149
|
+
// Event messages SHOULD be created
|
|
150
|
+
expect(messages.size).toBe(1);
|
|
151
|
+
expect(messages.has('WorkoutPlanUpdated')).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should create a message for command slices regardless of naming', () => {
|
|
155
|
+
const messages: Map<string, Message> = new Map();
|
|
156
|
+
const slice = { type: 'command' };
|
|
157
|
+
const when = { commandRef: 'ViewWorkoutPlan', exampleData: { workoutId: 'wrk_123' } };
|
|
158
|
+
|
|
159
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
160
|
+
|
|
161
|
+
// Command messages SHOULD be created even if it looks like a query action name
|
|
162
|
+
expect(messages.size).toBe(1);
|
|
163
|
+
expect(messages.has('ViewWorkoutPlan')).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('array of When items', () => {
|
|
168
|
+
it('should create query messages for query action items matching request', () => {
|
|
169
|
+
const messages: Map<string, Message> = new Map();
|
|
170
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan { workoutPlan { id } }' };
|
|
171
|
+
const when = [{ commandRef: 'ViewWorkoutPlan', exampleData: { workoutId: 'wrk_123' } }];
|
|
172
|
+
|
|
173
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
174
|
+
|
|
175
|
+
// Query action matches request - query message created
|
|
176
|
+
expect(messages.size).toBe(1);
|
|
177
|
+
expect(messages.has('ViewWorkoutPlan')).toBe(true);
|
|
178
|
+
expect(messages.get('ViewWorkoutPlan')?.type).toBe('query');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should create messages when no request field (cannot detect query action)', () => {
|
|
182
|
+
const messages: Map<string, Message> = new Map();
|
|
183
|
+
const slice = { type: 'query' }; // No request field
|
|
184
|
+
const when = [{ commandRef: 'ViewWorkoutPlan', exampleData: { workoutId: 'wrk_123' } }];
|
|
185
|
+
|
|
186
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
187
|
+
|
|
188
|
+
// Without request field, cannot detect query action - message created
|
|
189
|
+
expect(messages.size).toBe(1);
|
|
190
|
+
expect(messages.has('ViewWorkoutPlan')).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should create messages for event items in array', () => {
|
|
194
|
+
const messages: Map<string, Message> = new Map();
|
|
195
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan { workoutPlan { id } }' };
|
|
196
|
+
const when = [
|
|
197
|
+
{ commandRef: 'WorkoutPlanCreated', exampleData: { workoutId: 'wrk_123' } },
|
|
198
|
+
{ commandRef: 'WorkoutPlanUpdated', exampleData: { workoutId: 'wrk_123' } },
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
202
|
+
|
|
203
|
+
expect(messages.size).toBe(2);
|
|
204
|
+
expect(messages.has('WorkoutPlanCreated')).toBe(true);
|
|
205
|
+
expect(messages.has('WorkoutPlanUpdated')).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should create query message for query action and event message in mixed array', () => {
|
|
209
|
+
const messages: Map<string, Message> = new Map();
|
|
210
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan { workoutPlan { id } }' };
|
|
211
|
+
const when = [
|
|
212
|
+
{ commandRef: 'ViewWorkoutPlan', exampleData: { workoutId: 'wrk_123' } }, // Query action - create query message
|
|
213
|
+
{ commandRef: 'WorkoutPlanUpdated', exampleData: { workoutId: 'wrk_123' } }, // Event - create event message
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
processWhen(when, slice, mockTypeResolver, messages, createEmptyHints());
|
|
217
|
+
|
|
218
|
+
expect(messages.size).toBe(2);
|
|
219
|
+
expect(messages.has('ViewWorkoutPlan')).toBe(true);
|
|
220
|
+
expect(messages.get('ViewWorkoutPlan')?.type).toBe('query');
|
|
221
|
+
expect(messages.has('WorkoutPlanUpdated')).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('example shape hints collection', () => {
|
|
226
|
+
it('should collect example hints for query actions', () => {
|
|
227
|
+
const messages: Map<string, Message> = new Map();
|
|
228
|
+
const slice = { type: 'query', request: 'query ViewWorkoutPlan { workoutPlan { id } }' };
|
|
229
|
+
const when = { commandRef: 'ViewWorkoutPlan', exampleData: { workoutId: 'wrk_123', userId: 'user_456' } };
|
|
230
|
+
const hints = createEmptyHints();
|
|
231
|
+
|
|
232
|
+
processWhen(when, slice, mockTypeResolver, messages, hints);
|
|
233
|
+
|
|
234
|
+
// Query message created and hints should be collected
|
|
235
|
+
expect(messages.size).toBe(1);
|
|
236
|
+
expect(messages.get('ViewWorkoutPlan')?.type).toBe('query');
|
|
237
|
+
expect(hints.has('ViewWorkoutPlan')).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
@@ -7,9 +7,62 @@ import { preferNewFields } from './normalize';
|
|
|
7
7
|
|
|
8
8
|
const log = debug('auto:flow:spec-processors');
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Extracts the query name from a GraphQL request string.
|
|
12
|
+
* Supports both SDL strings and JSON-serialized AST.
|
|
13
|
+
*/
|
|
14
|
+
export function extractQueryNameFromRequest(request: string | undefined): string | null {
|
|
15
|
+
if (!request) return null;
|
|
16
|
+
const queryMatch = request.match(/query\s+(\w+)/i);
|
|
17
|
+
if (queryMatch) {
|
|
18
|
+
return queryMatch[1];
|
|
19
|
+
}
|
|
20
|
+
if (request.startsWith('{') && request.includes('"kind"')) {
|
|
21
|
+
try {
|
|
22
|
+
const ast = JSON.parse(request) as unknown;
|
|
23
|
+
if (
|
|
24
|
+
typeof ast === 'object' &&
|
|
25
|
+
ast !== null &&
|
|
26
|
+
'definitions' in ast &&
|
|
27
|
+
Array.isArray((ast as { definitions: unknown[] }).definitions)
|
|
28
|
+
) {
|
|
29
|
+
const definitions = (ast as { definitions: Array<{ kind?: string; name?: { value?: string } }> }).definitions;
|
|
30
|
+
const opDef = definitions.find((d) => d.kind === 'OperationDefinition');
|
|
31
|
+
if (opDef?.name?.value) {
|
|
32
|
+
return opDef.name.value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detects if the "When" text in a query slice represents a query action (query name)
|
|
43
|
+
* rather than an event name.
|
|
44
|
+
*
|
|
45
|
+
* Detection is based solely on matching the query name extracted from slice.request.
|
|
46
|
+
* If no query name can be extracted, returns false (treats as event - safe default).
|
|
47
|
+
*
|
|
48
|
+
* @param whenText - The text from the "When" step
|
|
49
|
+
* @param slice - The slice object containing type and optional request field
|
|
50
|
+
* @returns true if the "When" text matches the query name from slice.request
|
|
51
|
+
*/
|
|
52
|
+
export function detectQueryAction(whenText: string, slice: { type: string; request?: string }): boolean {
|
|
53
|
+
if (slice.type !== 'query') return false;
|
|
54
|
+
if (!whenText) return false;
|
|
55
|
+
|
|
56
|
+
const queryName = extractQueryNameFromRequest(slice.request);
|
|
57
|
+
if (!queryName) return false;
|
|
58
|
+
|
|
59
|
+
// Exact match or case-insensitive match
|
|
60
|
+
return whenText === queryName || whenText.toLowerCase() === queryName.toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
|
|
10
63
|
type TypeResolver = (
|
|
11
64
|
t: string,
|
|
12
|
-
expected?: 'command' | 'event' | 'state',
|
|
65
|
+
expected?: 'command' | 'event' | 'state' | 'query',
|
|
13
66
|
exampleData?: unknown,
|
|
14
67
|
) => { resolvedName: string; typeInfo: TypeInfo | undefined };
|
|
15
68
|
|
|
@@ -316,7 +369,7 @@ function processSingleWhen(
|
|
|
316
369
|
stateRef?: string;
|
|
317
370
|
exampleData?: unknown;
|
|
318
371
|
},
|
|
319
|
-
slice: { type: string },
|
|
372
|
+
slice: { type: string; request?: string },
|
|
320
373
|
resolveTypeAndInfo: TypeResolver,
|
|
321
374
|
messages: Map<string, Message>,
|
|
322
375
|
exampleShapeHints: ExampleShapeHints,
|
|
@@ -326,6 +379,23 @@ function processSingleWhen(
|
|
|
326
379
|
}
|
|
327
380
|
|
|
328
381
|
const originalCommandRef = when.commandRef;
|
|
382
|
+
|
|
383
|
+
// Check if this is a query action
|
|
384
|
+
// Query actions represent the act of executing the query - create a query message for them
|
|
385
|
+
if (detectQueryAction(originalCommandRef, slice)) {
|
|
386
|
+
log('DEBUG processSingleWhen: detected query action, creating query message:', originalCommandRef);
|
|
387
|
+
const { typeInfo } = resolveTypeAndInfo(originalCommandRef, 'query', when.exampleData);
|
|
388
|
+
const msg = createMessage(originalCommandRef, typeInfo, 'query');
|
|
389
|
+
const existing = messages.get(originalCommandRef);
|
|
390
|
+
if (!existing || preferNewFields(msg.fields, existing.fields)) {
|
|
391
|
+
messages.set(originalCommandRef, msg);
|
|
392
|
+
}
|
|
393
|
+
if (when.exampleData !== undefined) {
|
|
394
|
+
collectExampleHintsForData(originalCommandRef, when.exampleData, exampleShapeHints);
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
329
399
|
const expected = slice.type === 'command' ? 'command' : 'event';
|
|
330
400
|
|
|
331
401
|
log('DEBUG processSingleWhen:', {
|
|
@@ -418,7 +488,7 @@ function processCommandRefInArray(
|
|
|
418
488
|
stateRef?: string;
|
|
419
489
|
exampleData?: unknown;
|
|
420
490
|
},
|
|
421
|
-
slice: { type: string },
|
|
491
|
+
slice: { type: string; request?: string },
|
|
422
492
|
resolveTypeAndInfo: TypeResolver,
|
|
423
493
|
messages: Map<string, Message>,
|
|
424
494
|
exampleShapeHints: ExampleShapeHints,
|
|
@@ -428,6 +498,23 @@ function processCommandRefInArray(
|
|
|
428
498
|
}
|
|
429
499
|
|
|
430
500
|
const originalCommandRef = item.commandRef;
|
|
501
|
+
|
|
502
|
+
// Check if this is a query action (query name like ViewWorkoutPlan)
|
|
503
|
+
// Query actions represent the act of executing the query - create a query message for them
|
|
504
|
+
if (detectQueryAction(originalCommandRef, slice)) {
|
|
505
|
+
log('DEBUG processCommandRefInArray: detected query action, creating query message:', originalCommandRef);
|
|
506
|
+
const { typeInfo } = resolveTypeAndInfo(originalCommandRef, 'query', item.exampleData);
|
|
507
|
+
const msg = createMessage(originalCommandRef, typeInfo, 'query');
|
|
508
|
+
const existing = messages.get(originalCommandRef);
|
|
509
|
+
if (!existing || preferNewFields(msg.fields, existing.fields)) {
|
|
510
|
+
messages.set(originalCommandRef, msg);
|
|
511
|
+
}
|
|
512
|
+
if (item.exampleData !== undefined) {
|
|
513
|
+
collectExampleHintsForData(originalCommandRef, item.exampleData, exampleShapeHints);
|
|
514
|
+
}
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
431
518
|
const expected = slice.type === 'command' ? 'command' : 'event';
|
|
432
519
|
const { resolvedName, typeInfo } = resolveTypeAndInfo(originalCommandRef, expected, item.exampleData);
|
|
433
520
|
|
|
@@ -497,7 +584,7 @@ export function processWhen(
|
|
|
497
584
|
stateRef?: string;
|
|
498
585
|
exampleData?: unknown;
|
|
499
586
|
}>,
|
|
500
|
-
slice: { type: string },
|
|
587
|
+
slice: { type: string; request?: string },
|
|
501
588
|
resolveTypeAndInfo: TypeResolver,
|
|
502
589
|
messages: Map<string, Message>,
|
|
503
590
|
exampleShapeHints: ExampleShapeHints,
|
|
@@ -64,7 +64,7 @@ function tryMatchCandidates(candidates: TypeInfo[], dataKeys: Set<string>): stri
|
|
|
64
64
|
function tryResolveByExampleData(
|
|
65
65
|
candidates: TypeInfo[],
|
|
66
66
|
all: TypeInfo[],
|
|
67
|
-
expectedMessageType: 'command' | 'event' | 'state' | undefined,
|
|
67
|
+
expectedMessageType: 'command' | 'event' | 'state' | 'query' | undefined,
|
|
68
68
|
exampleData: unknown,
|
|
69
69
|
): string | null {
|
|
70
70
|
if (exampleData === null || typeof exampleData !== 'object' || exampleData === undefined) {
|
|
@@ -85,7 +85,7 @@ function tryResolveByExampleData(
|
|
|
85
85
|
|
|
86
86
|
function tryResolveByExpectedType(
|
|
87
87
|
candidates: TypeInfo[],
|
|
88
|
-
expectedMessageType: 'command' | 'event' | 'state' | undefined,
|
|
88
|
+
expectedMessageType: 'command' | 'event' | 'state' | 'query' | undefined,
|
|
89
89
|
): string | null {
|
|
90
90
|
if (!expectedMessageType || candidates.length === 0) {
|
|
91
91
|
return null;
|
|
@@ -98,7 +98,7 @@ function tryResolveByExpectedType(
|
|
|
98
98
|
function resolveFromCandidates(
|
|
99
99
|
candidates: TypeInfo[],
|
|
100
100
|
all: TypeInfo[],
|
|
101
|
-
expectedMessageType?: 'command' | 'event' | 'state',
|
|
101
|
+
expectedMessageType?: 'command' | 'event' | 'state' | 'query',
|
|
102
102
|
exampleData?: unknown,
|
|
103
103
|
): string | null {
|
|
104
104
|
if (candidates.length === 1) return candidates[0].stringLiteral;
|
|
@@ -112,7 +112,7 @@ function resolveFromCandidates(
|
|
|
112
112
|
export function resolveInferredType(
|
|
113
113
|
typeName: string,
|
|
114
114
|
flowTypeMap?: Map<string, TypeInfo>,
|
|
115
|
-
expectedMessageType?: 'command' | 'event' | 'state',
|
|
115
|
+
expectedMessageType?: 'command' | 'event' | 'state' | 'query',
|
|
116
116
|
exampleData?: unknown,
|
|
117
117
|
): string {
|
|
118
118
|
if (typeName !== 'InferredType' || flowTypeMap === undefined) return typeName;
|
package/src/types.ts
CHANGED
|
@@ -228,4 +228,9 @@ export type Event<
|
|
|
228
228
|
readonly kind?: 'Event';
|
|
229
229
|
};
|
|
230
230
|
|
|
231
|
+
export type Query<QueryType extends string, QueryData extends Record<string, unknown> = Record<string, unknown>> = {
|
|
232
|
+
type: QueryType;
|
|
233
|
+
data: QueryData;
|
|
234
|
+
};
|
|
235
|
+
|
|
231
236
|
export type ExtractStateData<T> = T extends State<string, infer Data, DefaultRecord | undefined> ? Data : never;
|