@auto-engineer/narrative 1.3.4 → 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.
Files changed (51) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +6 -6
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +16 -0
  5. package/dist/src/index.d.ts +3 -2
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +2 -1
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/loader/ts-utils.d.ts +2 -2
  10. package/dist/src/loader/ts-utils.d.ts.map +1 -1
  11. package/dist/src/loader/ts-utils.js +7 -1
  12. package/dist/src/loader/ts-utils.js.map +1 -1
  13. package/dist/src/schema.d.ts +226 -20
  14. package/dist/src/schema.d.ts.map +1 -1
  15. package/dist/src/schema.js +6 -3
  16. package/dist/src/schema.js.map +1 -1
  17. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts.map +1 -1
  18. package/dist/src/transformers/model-to-narrative/generators/imports.js +1 -0
  19. package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
  20. package/dist/src/transformers/model-to-narrative/generators/types.d.ts +1 -1
  21. package/dist/src/transformers/model-to-narrative/generators/types.d.ts.map +1 -1
  22. package/dist/src/transformers/model-to-narrative/generators/types.js +2 -1
  23. package/dist/src/transformers/model-to-narrative/generators/types.js.map +1 -1
  24. package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
  25. package/dist/src/transformers/narrative-to-model/messages.d.ts +1 -1
  26. package/dist/src/transformers/narrative-to-model/messages.d.ts.map +1 -1
  27. package/dist/src/transformers/narrative-to-model/messages.js +9 -1
  28. package/dist/src/transformers/narrative-to-model/messages.js.map +1 -1
  29. package/dist/src/transformers/narrative-to-model/spec-processors.d.ts +22 -1
  30. package/dist/src/transformers/narrative-to-model/spec-processors.d.ts.map +1 -1
  31. package/dist/src/transformers/narrative-to-model/spec-processors.js +81 -0
  32. package/dist/src/transformers/narrative-to-model/spec-processors.js.map +1 -1
  33. package/dist/src/transformers/narrative-to-model/type-inference.d.ts +1 -1
  34. package/dist/src/transformers/narrative-to-model/type-inference.d.ts.map +1 -1
  35. package/dist/src/transformers/narrative-to-model/type-inference.js.map +1 -1
  36. package/dist/src/types.d.ts +4 -0
  37. package/dist/src/types.d.ts.map +1 -1
  38. package/dist/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +4 -4
  40. package/src/index.ts +4 -0
  41. package/src/loader/ts-utils.ts +16 -10
  42. package/src/model-to-narrative.specs.ts +53 -0
  43. package/src/schema.ts +7 -2
  44. package/src/transformers/model-to-narrative/generators/imports.ts +1 -0
  45. package/src/transformers/model-to-narrative/generators/types.ts +4 -5
  46. package/src/transformers/narrative-to-model/index.ts +5 -5
  47. package/src/transformers/narrative-to-model/messages.ts +12 -3
  48. package/src/transformers/narrative-to-model/spec-processors.specs.ts +241 -0
  49. package/src/transformers/narrative-to-model/spec-processors.ts +91 -4
  50. package/src/transformers/narrative-to-model/type-inference.ts +4 -4
  51. package/src/types.ts +5 -0
@@ -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;