@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.
Files changed (54) 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 +31 -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/module-code.js +10 -2
  21. package/dist/src/transformers/model-to-narrative/generators/module-code.js.map +1 -1
  22. package/dist/src/transformers/model-to-narrative/generators/types.d.ts +1 -1
  23. package/dist/src/transformers/model-to-narrative/generators/types.d.ts.map +1 -1
  24. package/dist/src/transformers/model-to-narrative/generators/types.js +2 -1
  25. package/dist/src/transformers/model-to-narrative/generators/types.js.map +1 -1
  26. package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
  27. package/dist/src/transformers/narrative-to-model/messages.d.ts +1 -1
  28. package/dist/src/transformers/narrative-to-model/messages.d.ts.map +1 -1
  29. package/dist/src/transformers/narrative-to-model/messages.js +9 -1
  30. package/dist/src/transformers/narrative-to-model/messages.js.map +1 -1
  31. package/dist/src/transformers/narrative-to-model/spec-processors.d.ts +22 -1
  32. package/dist/src/transformers/narrative-to-model/spec-processors.d.ts.map +1 -1
  33. package/dist/src/transformers/narrative-to-model/spec-processors.js +81 -0
  34. package/dist/src/transformers/narrative-to-model/spec-processors.js.map +1 -1
  35. package/dist/src/transformers/narrative-to-model/type-inference.d.ts +1 -1
  36. package/dist/src/transformers/narrative-to-model/type-inference.d.ts.map +1 -1
  37. package/dist/src/transformers/narrative-to-model/type-inference.js.map +1 -1
  38. package/dist/src/types.d.ts +4 -0
  39. package/dist/src/types.d.ts.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +4 -4
  42. package/src/index.ts +4 -0
  43. package/src/loader/ts-utils.ts +16 -10
  44. package/src/model-to-narrative.specs.ts +123 -0
  45. package/src/schema.ts +7 -2
  46. package/src/transformers/model-to-narrative/generators/imports.ts +1 -0
  47. package/src/transformers/model-to-narrative/generators/module-code.ts +11 -2
  48. package/src/transformers/model-to-narrative/generators/types.ts +4 -5
  49. package/src/transformers/narrative-to-model/index.ts +5 -5
  50. package/src/transformers/narrative-to-model/messages.ts +12 -3
  51. package/src/transformers/narrative-to-model/spec-processors.specs.ts +241 -0
  52. package/src/transformers/narrative-to-model/spec-processors.ts +91 -4
  53. package/src/transformers/narrative-to-model/type-inference.ts +4 -4
  54. package/src/types.ts +5 -0
package/package.json CHANGED
@@ -23,9 +23,9 @@
23
23
  "typescript": "^5.9.2",
24
24
  "zod": "^3.22.4",
25
25
  "zod-to-json-schema": "^3.22.3",
26
- "@auto-engineer/file-store": "1.3.3",
27
- "@auto-engineer/id": "1.3.3",
28
- "@auto-engineer/message-bus": "1.3.3"
26
+ "@auto-engineer/file-store": "1.4.0",
27
+ "@auto-engineer/id": "1.4.0",
28
+ "@auto-engineer/message-bus": "1.4.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.0.0",
@@ -35,7 +35,7 @@
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
- "version": "1.3.3",
38
+ "version": "1.4.0",
39
39
  "scripts": {
40
40
  "build": "tsx scripts/build.ts",
41
41
  "test": "vitest run --reporter=dot",
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export type {
19
19
  Integration,
20
20
  MessageTarget,
21
21
  Origin,
22
+ Query,
22
23
  State,
23
24
  } from './types';
24
25
  export { createIntegration } from './types';
@@ -81,6 +82,7 @@ export {
81
82
  modelSchema,
82
83
  NarrativeNamesSchema as NarrativeNamesSystemSchema,
83
84
  NarrativeSchema,
85
+ QuerySchema,
84
86
  QuerySliceSchema,
85
87
  ReactSliceSchema,
86
88
  RuleSchema,
@@ -135,3 +137,5 @@ export type MessageRef = z.infer<typeof MessageRefSchema>;
135
137
  // ID assignment utilities
136
138
  export { addAutoIds, hasAllIds } from './id';
137
139
  export type { ClientSpecNode } from './schema';
140
+
141
+ export { detectQueryAction, extractQueryNameFromRequest } from './transformers/narrative-to-model/spec-processors';
@@ -77,7 +77,7 @@ export function parseImports(ts: typeof import('typescript'), fileName: string,
77
77
 
78
78
  export interface TypeInfo {
79
79
  stringLiteral: string;
80
- classification?: 'command' | 'event' | 'state';
80
+ classification?: 'command' | 'event' | 'state' | 'query';
81
81
  dataFields?: { name: string; type: string; required: boolean }[];
82
82
  }
83
83
 
@@ -93,7 +93,7 @@ export interface GivenTypeInfo {
93
93
  column: number;
94
94
  ordinal: number; // Sequential position within the file
95
95
  typeName: string;
96
- classification: 'command' | 'event' | 'state';
96
+ classification: 'command' | 'event' | 'state' | 'query';
97
97
  }
98
98
 
99
99
  function extractDataFieldsFromTypeLiteral(
@@ -136,7 +136,7 @@ function processTypeAlias(
136
136
  const baseName = getBaseName(typeRef.typeName);
137
137
  if (typeof baseName !== 'string') return;
138
138
 
139
- if (!['Command', 'Event', 'State'].includes(baseName)) return;
139
+ if (!['Command', 'Event', 'State', 'Query'].includes(baseName)) return;
140
140
 
141
141
  const typeArgs = typeRef.typeArguments ?? [];
142
142
  if (typeArgs.length === 0) return;
@@ -145,7 +145,7 @@ function processTypeAlias(
145
145
  if (!ts.isLiteralTypeNode(firstArg) || !ts.isStringLiteral(firstArg.literal)) return;
146
146
 
147
147
  const stringLiteral = firstArg.literal.text;
148
- const classification = baseName.toLowerCase() as 'command' | 'event' | 'state';
148
+ const classification = baseName.toLowerCase() as 'command' | 'event' | 'state' | 'query';
149
149
 
150
150
  // Try to extract the data fields from the 2nd generic arg (if present)
151
151
  let dataFields: { name: string; type: string; required: boolean }[] | undefined;
@@ -271,7 +271,7 @@ function extractDataFields(
271
271
  return dataFields;
272
272
  }
273
273
 
274
- function inferClassificationFromName(stringLiteral: string): 'command' | 'event' | 'state' | undefined {
274
+ function inferClassificationFromName(stringLiteral: string): 'command' | 'event' | 'state' | 'query' | undefined {
275
275
  const eventPatterns = ['ed', 'Created', 'Updated', 'Deleted', 'Placed', 'Added', 'Removed', 'Changed'];
276
276
  if (eventPatterns.some((pattern) => stringLiteral.endsWith(pattern))) {
277
277
  return 'event';
@@ -282,6 +282,11 @@ function inferClassificationFromName(stringLiteral: string): 'command' | 'event'
282
282
  return 'command';
283
283
  }
284
284
 
285
+ // Query patterns: View*, Get*, List*, Find*, Search*, Fetch*
286
+ if (/^(View|Get|List|Find|Search|Fetch)[A-Z]/.test(stringLiteral)) {
287
+ return 'query';
288
+ }
289
+
285
290
  const statePatterns = ['Summary', 'View', 'Items', 'List', 'Data', 'Info'];
286
291
  if (statePatterns.some((pattern) => stringLiteral.endsWith(pattern))) {
287
292
  return 'state';
@@ -463,7 +468,7 @@ function classifyBaseGeneric(
463
468
  ts: typeof import('typescript'),
464
469
  checker: import('typescript').TypeChecker,
465
470
  typeRef: import('typescript').TypeReferenceNode,
466
- ): 'event' | 'command' | 'state' | null {
471
+ ): 'event' | 'command' | 'state' | 'query' | null {
467
472
  // Resolve base symbol (handles aliases and qualified names)
468
473
  let sym: import('typescript').Symbol | undefined;
469
474
  if (ts.isIdentifier(typeRef.typeName) || ts.isQualifiedName(typeRef.typeName)) {
@@ -476,6 +481,7 @@ function classifyBaseGeneric(
476
481
  if (base.endsWith('Event')) return 'event';
477
482
  if (base.endsWith('Command')) return 'command';
478
483
  if (base.endsWith('State')) return 'state';
484
+ if (base.endsWith('Query')) return 'query';
479
485
  return null;
480
486
  }
481
487
 
@@ -483,7 +489,7 @@ function tryUnwrapDirectGeneric(
483
489
  ts: typeof import('typescript'),
484
490
  typeArg: import('typescript').TypeReferenceNode,
485
491
  checker: import('typescript').TypeChecker,
486
- ): { typeName: string; classification: 'event' | 'command' | 'state' } | null {
492
+ ): { typeName: string; classification: 'event' | 'command' | 'state' | 'query' } | null {
487
493
  if (typeArg.typeArguments === undefined || typeArg.typeArguments.length === 0) return null;
488
494
 
489
495
  const kind = classifyBaseGeneric(ts, checker, typeArg);
@@ -504,7 +510,7 @@ function tryUnwrapTypeAlias(
504
510
  typeArg: import('typescript').TypeReferenceNode,
505
511
  typeMap: Map<string, TypeInfo>,
506
512
  typesByFile: Map<string, Map<string, TypeInfo>>,
507
- ): { typeName: string; classification: 'event' | 'command' | 'state' } | null {
513
+ ): { typeName: string; classification: 'event' | 'command' | 'state' | 'query' } | null {
508
514
  if (!ts.isIdentifier(typeArg.typeName)) return null;
509
515
 
510
516
  const typeName = typeArg.typeName.text;
@@ -526,7 +532,7 @@ function tryUnwrapGeneric(
526
532
  checker: import('typescript').TypeChecker,
527
533
  typeMap: Map<string, TypeInfo>,
528
534
  typesByFile: Map<string, Map<string, TypeInfo>>,
529
- ): { typeName: string; classification: 'event' | 'command' | 'state' } | null {
535
+ ): { typeName: string; classification: 'event' | 'command' | 'state' | 'query' } | null {
530
536
  if (!ts.isTypeReferenceNode(typeArg)) return null;
531
537
 
532
538
  return tryUnwrapDirectGeneric(ts, typeArg, checker) ?? tryUnwrapTypeAlias(ts, typeArg, typeMap, typesByFile);
@@ -554,7 +560,7 @@ function createGivenTypeInfo(
554
560
  node: import('typescript').CallExpression,
555
561
  ordinal: number,
556
562
  typeName: string,
557
- classification: 'event' | 'command' | 'state',
563
+ classification: 'event' | 'command' | 'state' | 'query',
558
564
  ): GivenTypeInfo {
559
565
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
560
566
  return {
@@ -658,6 +658,59 @@ narrative('Test Flow with Rule IDs', 'FLOW-456', () => {
658
658
  `);
659
659
  });
660
660
 
661
+ it('should correctly generate Query type alias for query messages', async () => {
662
+ const modelWithQueryMessage: Model = {
663
+ variant: 'specs',
664
+ narratives: [
665
+ {
666
+ name: 'Workout Flow',
667
+ id: 'FLOW-001',
668
+ slices: [],
669
+ },
670
+ ],
671
+ messages: [
672
+ {
673
+ type: 'query',
674
+ name: 'GetWorkoutHistory',
675
+ fields: [
676
+ { name: 'memberId', type: 'string', required: true },
677
+ { name: 'limit', type: 'number', required: false },
678
+ ],
679
+ metadata: { version: 1 },
680
+ },
681
+ {
682
+ type: 'event',
683
+ name: 'WorkoutRecorded',
684
+ fields: [{ name: 'workoutId', type: 'string', required: true }],
685
+ source: 'internal',
686
+ metadata: { version: 1 },
687
+ },
688
+ ],
689
+ integrations: [],
690
+ modules: [],
691
+ };
692
+
693
+ const code = getCode(await modelToNarrative(modelWithQueryMessage));
694
+
695
+ expect(code).toEqual(`import { narrative } from '@auto-engineer/narrative';
696
+ import type { Event, Query } from '@auto-engineer/narrative';
697
+ type GetWorkoutHistory = Query<
698
+ 'GetWorkoutHistory',
699
+ {
700
+ memberId: string;
701
+ limit?: number;
702
+ }
703
+ >;
704
+ type WorkoutRecorded = Event<
705
+ 'WorkoutRecorded',
706
+ {
707
+ workoutId: string;
708
+ }
709
+ >;
710
+ narrative('Workout Flow', 'FLOW-001', () => {});
711
+ `);
712
+ });
713
+
661
714
  it('should correctly resolve Date types in messages', async () => {
662
715
  const modelWithDateTypes: Model = {
663
716
  variant: 'specs',
@@ -3370,4 +3423,74 @@ narrative('All Projection Types', 'ALL-PROJ', () => {
3370
3423
  expect(code).toContain("command('Perform Check-In'");
3371
3424
  });
3372
3425
  });
3426
+
3427
+ it('generates all declared types for authored modules regardless of usage analysis', async () => {
3428
+ const model: Model = {
3429
+ variant: 'specs',
3430
+ narratives: [
3431
+ {
3432
+ id: 'narrative-1',
3433
+ name: 'Gym Goal Setting',
3434
+ slices: [
3435
+ {
3436
+ type: 'command',
3437
+ name: 'Set Fitness Goal',
3438
+ client: { specs: [] },
3439
+ server: {
3440
+ description: 'Set a fitness goal',
3441
+ specs: [
3442
+ {
3443
+ type: 'gherkin',
3444
+ feature: 'Goal Setting',
3445
+ rules: [
3446
+ {
3447
+ name: 'Create goal',
3448
+ examples: [
3449
+ {
3450
+ name: 'Create fitness goal',
3451
+ steps: [
3452
+ { keyword: 'When', text: 'SetFitnessGoal', docString: { name: 'Lose weight' } },
3453
+ { keyword: 'Then', text: 'FitnessGoalCreated', docString: { goalId: '123' } },
3454
+ ],
3455
+ },
3456
+ ],
3457
+ },
3458
+ ],
3459
+ },
3460
+ ],
3461
+ },
3462
+ },
3463
+ ],
3464
+ sourceFile: '/narratives/goal-setting.narrative.ts',
3465
+ },
3466
+ ],
3467
+ messages: [
3468
+ { type: 'command', name: 'SetFitnessGoal', fields: [] },
3469
+ { type: 'event', name: 'FitnessGoalCreated', fields: [], source: 'internal' },
3470
+ { type: 'state', name: 'FitnessGoalsView', fields: [] },
3471
+ ],
3472
+ modules: [
3473
+ {
3474
+ sourceFile: '/narratives/goal-setting.narrative.ts',
3475
+ isDerived: false,
3476
+ contains: { narrativeIds: ['narrative-1'] },
3477
+ declares: {
3478
+ messages: [
3479
+ { kind: 'command', name: 'SetFitnessGoal' },
3480
+ { kind: 'event', name: 'FitnessGoalCreated' },
3481
+ { kind: 'state', name: 'FitnessGoalsView' },
3482
+ ],
3483
+ },
3484
+ },
3485
+ ],
3486
+ };
3487
+
3488
+ const result = await modelToNarrative(model);
3489
+ const code = getCode(result);
3490
+
3491
+ // All 3 declared types should be generated
3492
+ expect(code).toContain("type SetFitnessGoal = Command<'SetFitnessGoal'");
3493
+ expect(code).toContain("type FitnessGoalCreated = Event<'FitnessGoalCreated'");
3494
+ expect(code).toContain("type FitnessGoalsView = State<'FitnessGoalsView'");
3495
+ });
3373
3496
  });
package/src/schema.ts CHANGED
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  // Message reference for module type ownership
4
4
  export const MessageRefSchema = z
5
5
  .object({
6
- kind: z.enum(['command', 'event', 'state']).describe('Message kind'),
6
+ kind: z.enum(['command', 'event', 'state', 'query']).describe('Message kind'),
7
7
  name: z.string().describe('Message name'),
8
8
  })
9
9
  .describe('Reference to a message type');
@@ -174,7 +174,11 @@ const StateSchema = BaseMessageSchema.extend({
174
174
  type: z.literal('state'),
175
175
  }).describe('State/Read Model representing a view of data');
176
176
 
177
- const MessageSchema = z.discriminatedUnion('type', [CommandSchema, EventSchema, StateSchema]);
177
+ const QuerySchema = BaseMessageSchema.extend({
178
+ type: z.literal('query'),
179
+ }).describe('Query representing a read operation');
180
+
181
+ const MessageSchema = z.discriminatedUnion('type', [CommandSchema, EventSchema, StateSchema, QuerySchema]);
178
182
 
179
183
  const BaseSliceSchema = z
180
184
  .object({
@@ -417,6 +421,7 @@ export {
417
421
  CommandSchema,
418
422
  EventSchema,
419
423
  StateSchema,
424
+ QuerySchema,
420
425
  IntegrationSchema,
421
426
  CommandSliceSchema,
422
427
  QuerySliceSchema,
@@ -35,6 +35,7 @@ export function buildImports(
35
35
  command: 'Command',
36
36
  event: 'Event',
37
37
  state: 'State',
38
+ query: 'Query',
38
39
  };
39
40
 
40
41
  const flowTypeNames = Array.from(usedMessageTypes)
@@ -102,13 +102,22 @@ function generateModuleCode(
102
102
 
103
103
  const usedMessages = messages.filter((msg) => {
104
104
  const isImportedFromIntegration = usedTypeIntegrationNames.includes(msg.name);
105
- const isUsedInFlow = usageAnalysis.usedTypes.has(msg.name);
106
- const hasEmptyFlowSlices = narratives.length === 0 || narratives.every((flow) => flow.slices.length === 0);
107
105
 
106
+ // Don't generate local definitions for types imported from integrations
108
107
  if (isImportedFromIntegration) {
109
108
  return false;
110
109
  }
111
110
 
111
+ // For authored modules, trust the declares list - all declared messages should be generated
112
+ // (messages is already filtered to only include declared messages at line 60-61)
113
+ if (!module.isDerived) {
114
+ return true;
115
+ }
116
+
117
+ // For derived modules, only include types that are actually used in flow code
118
+ // or when there's no flow code (hasEmptyFlowSlices)
119
+ const isUsedInFlow = usageAnalysis.usedTypes.has(msg.name);
120
+ const hasEmptyFlowSlices = narratives.length === 0 || narratives.every((flow) => flow.slices.length === 0);
112
121
  return isUsedInFlow || hasEmptyFlowSlices;
113
122
  });
114
123
 
@@ -2,7 +2,7 @@ import type tsNS from 'typescript';
2
2
  import { typeFromString } from '../ast/emit-helpers';
3
3
 
4
4
  type Message = {
5
- type: 'command' | 'event' | 'state';
5
+ type: 'command' | 'event' | 'state' | 'query';
6
6
  name: string;
7
7
  fields: { name: string; type: string; required: boolean }[];
8
8
  };
@@ -33,10 +33,9 @@ export function buildTypeAliases(ts: typeof tsNS, messages: Message[]): tsNS.Sta
33
33
 
34
34
  const name = f.createIdentifier(m.name);
35
35
 
36
- const rhs = f.createTypeReferenceNode(
37
- m.type === 'event' ? 'Event' : m.type === 'command' ? 'Command' : 'State',
38
- typeArgs,
39
- );
36
+ const baseTypeName =
37
+ m.type === 'event' ? 'Event' : m.type === 'command' ? 'Command' : m.type === 'query' ? 'Query' : 'State';
38
+ const rhs = f.createTypeReferenceNode(baseTypeName, typeArgs);
40
39
 
41
40
  return f.createTypeAliasDeclaration(
42
41
  undefined, // No export keyword
@@ -12,7 +12,7 @@ import { resolveInferredType } from './type-inference';
12
12
 
13
13
  type TypeResolver = (
14
14
  t: string,
15
- expected?: 'command' | 'event' | 'state',
15
+ expected?: 'command' | 'event' | 'state' | 'query',
16
16
  exampleData?: unknown,
17
17
  ) => { resolvedName: string; typeInfo: TypeInfo | undefined };
18
18
 
@@ -47,7 +47,7 @@ function getTypesForNarrative(
47
47
  function tryResolveFromNarrativeTypes(
48
48
  t: string,
49
49
  narrativeSpecificTypes: Map<string, TypeInfo>,
50
- expected?: 'command' | 'event' | 'state',
50
+ expected?: 'command' | 'event' | 'state' | 'query',
51
51
  exampleData?: unknown,
52
52
  ): { resolvedName: string; typeInfo: TypeInfo | undefined } {
53
53
  if (t !== 'InferredType') {
@@ -68,7 +68,7 @@ function tryFallbackToUnionTypes(
68
68
  resolvedName: string,
69
69
  typeInfo: TypeInfo | undefined,
70
70
  unionTypes: Map<string, TypeInfo>,
71
- expected?: 'command' | 'event' | 'state',
71
+ expected?: 'command' | 'event' | 'state' | 'query',
72
72
  exampleData?: unknown,
73
73
  ): { resolvedName: string; typeInfo: TypeInfo | undefined } {
74
74
  if (resolvedName !== 'InferredType' && typeInfo) {
@@ -88,7 +88,7 @@ function tryFallbackToUnionTypes(
88
88
  function tryResolveFromUnionTypes(
89
89
  t: string,
90
90
  unionTypes: Map<string, TypeInfo>,
91
- expected?: 'command' | 'event' | 'state',
91
+ expected?: 'command' | 'event' | 'state' | 'query',
92
92
  exampleData?: unknown,
93
93
  ): { resolvedName: string; typeInfo: TypeInfo | undefined } {
94
94
  if (t !== 'InferredType') {
@@ -110,7 +110,7 @@ function createTypeResolver(
110
110
  ) {
111
111
  return (
112
112
  t: string,
113
- expected?: 'command' | 'event' | 'state',
113
+ expected?: 'command' | 'event' | 'state' | 'query',
114
114
  exampleData?: unknown,
115
115
  ): { resolvedName: string; typeInfo: TypeInfo | undefined } => {
116
116
  if (narrativeSpecificTypes) {
@@ -1,9 +1,9 @@
1
1
  import type { Message } from '../../index';
2
2
  import type { TypeInfo } from '../../loader/ts-utils';
3
3
 
4
- function mapKindToMessageType(k: 'command' | 'query' | 'reaction'): 'command' | 'event' | 'state' {
4
+ function mapKindToMessageType(k: 'command' | 'query' | 'reaction'): 'command' | 'event' | 'state' | 'query' {
5
5
  if (k === 'command') return 'command';
6
- if (k === 'query') return 'state';
6
+ if (k === 'query') return 'query';
7
7
  return 'event';
8
8
  }
9
9
 
@@ -52,7 +52,7 @@ function processStateFields(
52
52
  export function createMessage(
53
53
  name: string,
54
54
  typeInfo: TypeInfo | undefined,
55
- messageType: 'command' | 'event' | 'state',
55
+ messageType: 'command' | 'event' | 'state' | 'query',
56
56
  ): Message {
57
57
  let fields = buildInitialFields(typeInfo);
58
58
 
@@ -81,6 +81,15 @@ export function createMessage(
81
81
  };
82
82
  }
83
83
 
84
+ if (messageType === 'query') {
85
+ return {
86
+ type: 'query',
87
+ name,
88
+ fields,
89
+ metadata,
90
+ };
91
+ }
92
+
84
93
  return {
85
94
  type: 'state',
86
95
  name,