@auto-engineer/server-generator-nestjs 0.11.16

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 (143) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/LICENSE +10 -0
  3. package/README.md +290 -0
  4. package/dist/src/codegen/entity-consolidation.d.ts +19 -0
  5. package/dist/src/codegen/entity-consolidation.d.ts.map +1 -0
  6. package/dist/src/codegen/entity-consolidation.js +134 -0
  7. package/dist/src/codegen/entity-consolidation.js.map +1 -0
  8. package/dist/src/codegen/extract/commands.d.ts +25 -0
  9. package/dist/src/codegen/extract/commands.d.ts.map +1 -0
  10. package/dist/src/codegen/extract/commands.js +67 -0
  11. package/dist/src/codegen/extract/commands.js.map +1 -0
  12. package/dist/src/codegen/extract/data-sink.d.ts +6 -0
  13. package/dist/src/codegen/extract/data-sink.d.ts.map +1 -0
  14. package/dist/src/codegen/extract/data-sink.js +78 -0
  15. package/dist/src/codegen/extract/data-sink.js.map +1 -0
  16. package/dist/src/codegen/extract/events.d.ts +10 -0
  17. package/dist/src/codegen/extract/events.d.ts.map +1 -0
  18. package/dist/src/codegen/extract/events.js +42 -0
  19. package/dist/src/codegen/extract/events.js.map +1 -0
  20. package/dist/src/codegen/extract/fields.d.ts +3 -0
  21. package/dist/src/codegen/extract/fields.d.ts.map +1 -0
  22. package/dist/src/codegen/extract/fields.js +9 -0
  23. package/dist/src/codegen/extract/fields.js.map +1 -0
  24. package/dist/src/codegen/extract/graphql.d.ts +14 -0
  25. package/dist/src/codegen/extract/graphql.d.ts.map +1 -0
  26. package/dist/src/codegen/extract/graphql.js +81 -0
  27. package/dist/src/codegen/extract/graphql.js.map +1 -0
  28. package/dist/src/codegen/extract/gwt.d.ts +6 -0
  29. package/dist/src/codegen/extract/gwt.d.ts.map +1 -0
  30. package/dist/src/codegen/extract/gwt.js +64 -0
  31. package/dist/src/codegen/extract/gwt.js.map +1 -0
  32. package/dist/src/codegen/extract/imports.d.ts +29 -0
  33. package/dist/src/codegen/extract/imports.d.ts.map +1 -0
  34. package/dist/src/codegen/extract/imports.js +55 -0
  35. package/dist/src/codegen/extract/imports.js.map +1 -0
  36. package/dist/src/codegen/extract/index.d.ts +10 -0
  37. package/dist/src/codegen/extract/index.d.ts.map +1 -0
  38. package/dist/src/codegen/extract/index.js +10 -0
  39. package/dist/src/codegen/extract/index.js.map +1 -0
  40. package/dist/src/codegen/extract/messages.d.ts +17 -0
  41. package/dist/src/codegen/extract/messages.d.ts.map +1 -0
  42. package/dist/src/codegen/extract/messages.js +172 -0
  43. package/dist/src/codegen/extract/messages.js.map +1 -0
  44. package/dist/src/codegen/extract/projection.d.ts +5 -0
  45. package/dist/src/codegen/extract/projection.d.ts.map +1 -0
  46. package/dist/src/codegen/extract/projection.js +44 -0
  47. package/dist/src/codegen/extract/projection.js.map +1 -0
  48. package/dist/src/codegen/extract/query.d.ts +14 -0
  49. package/dist/src/codegen/extract/query.d.ts.map +1 -0
  50. package/dist/src/codegen/extract/query.js +17 -0
  51. package/dist/src/codegen/extract/query.js.map +1 -0
  52. package/dist/src/codegen/extract/states.d.ts +5 -0
  53. package/dist/src/codegen/extract/states.d.ts.map +1 -0
  54. package/dist/src/codegen/extract/states.js +48 -0
  55. package/dist/src/codegen/extract/states.js.map +1 -0
  56. package/dist/src/codegen/extract/step-converter.d.ts +17 -0
  57. package/dist/src/codegen/extract/step-converter.d.ts.map +1 -0
  58. package/dist/src/codegen/extract/step-converter.js +82 -0
  59. package/dist/src/codegen/extract/step-converter.js.map +1 -0
  60. package/dist/src/codegen/extract/step-types.d.ts +29 -0
  61. package/dist/src/codegen/extract/step-types.d.ts.map +1 -0
  62. package/dist/src/codegen/extract/step-types.js +16 -0
  63. package/dist/src/codegen/extract/step-types.js.map +1 -0
  64. package/dist/src/codegen/extract/type-helpers.d.ts +13 -0
  65. package/dist/src/codegen/extract/type-helpers.d.ts.map +1 -0
  66. package/dist/src/codegen/extract/type-helpers.js +98 -0
  67. package/dist/src/codegen/extract/type-helpers.js.map +1 -0
  68. package/dist/src/codegen/scaffoldFromSchema.d.ts +9 -0
  69. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -0
  70. package/dist/src/codegen/scaffoldFromSchema.js +389 -0
  71. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -0
  72. package/dist/src/codegen/templates/command/command.ts.ejs +16 -0
  73. package/dist/src/codegen/templates/command/handler.specs.ts.ejs +50 -0
  74. package/dist/src/codegen/templates/command/handler.ts.ejs +30 -0
  75. package/dist/src/codegen/templates/command/input.ts.ejs +23 -0
  76. package/dist/src/codegen/templates/command/resolver.ts.ejs +56 -0
  77. package/dist/src/codegen/templates/entity/entity.ts.ejs +35 -0
  78. package/dist/src/codegen/templates/entity/index.ts.ejs +6 -0
  79. package/dist/src/codegen/templates/module/app-module.ts.ejs +65 -0
  80. package/dist/src/codegen/templates/module/domain-module.ts.ejs +46 -0
  81. package/dist/src/codegen/templates/query/handler.ts.ejs +25 -0
  82. package/dist/src/codegen/templates/query/query.ts.ejs +1 -0
  83. package/dist/src/codegen/templates/query/resolver.ts.ejs +20 -0
  84. package/dist/src/codegen/templates/query/type.ts.ejs +24 -0
  85. package/dist/src/codegen/types.d.ts +38 -0
  86. package/dist/src/codegen/types.d.ts.map +1 -0
  87. package/dist/src/codegen/types.js +2 -0
  88. package/dist/src/codegen/types.js.map +1 -0
  89. package/dist/src/codegen/utils/path.d.ts +4 -0
  90. package/dist/src/codegen/utils/path.d.ts.map +1 -0
  91. package/dist/src/codegen/utils/path.js +18 -0
  92. package/dist/src/codegen/utils/path.js.map +1 -0
  93. package/dist/src/commands/generate-server.d.ts +28 -0
  94. package/dist/src/commands/generate-server.d.ts.map +1 -0
  95. package/dist/src/commands/generate-server.js +421 -0
  96. package/dist/src/commands/generate-server.js.map +1 -0
  97. package/dist/src/index.d.ts +3 -0
  98. package/dist/src/index.d.ts.map +1 -0
  99. package/dist/src/index.js +3 -0
  100. package/dist/src/index.js.map +1 -0
  101. package/dist/src/shared/graphql-types.ts +19 -0
  102. package/dist/src/shared/main.ts +27 -0
  103. package/dist/src/shared/mikro-orm.config.ts +18 -0
  104. package/dist/tsconfig.tsbuildinfo +1 -0
  105. package/package.json +70 -0
  106. package/src/codegen/entity-consolidation.ts +213 -0
  107. package/src/codegen/extract/commands.ts +108 -0
  108. package/src/codegen/extract/data-sink.ts +93 -0
  109. package/src/codegen/extract/events.ts +63 -0
  110. package/src/codegen/extract/fields.ts +17 -0
  111. package/src/codegen/extract/graphql.ts +103 -0
  112. package/src/codegen/extract/gwt.ts +79 -0
  113. package/src/codegen/extract/imports.ts +71 -0
  114. package/src/codegen/extract/index.ts +9 -0
  115. package/src/codegen/extract/messages.ts +232 -0
  116. package/src/codegen/extract/projection.ts +62 -0
  117. package/src/codegen/extract/query.ts +28 -0
  118. package/src/codegen/extract/states.ts +69 -0
  119. package/src/codegen/extract/step-converter.ts +124 -0
  120. package/src/codegen/extract/step-types.ts +51 -0
  121. package/src/codegen/extract/type-helpers.ts +102 -0
  122. package/src/codegen/scaffoldFromSchema.ts +559 -0
  123. package/src/codegen/templates/command/command.ts.ejs +16 -0
  124. package/src/codegen/templates/command/handler.specs.ts.ejs +50 -0
  125. package/src/codegen/templates/command/handler.ts.ejs +30 -0
  126. package/src/codegen/templates/command/input.ts.ejs +23 -0
  127. package/src/codegen/templates/command/resolver.ts.ejs +56 -0
  128. package/src/codegen/templates/entity/entity.ts.ejs +35 -0
  129. package/src/codegen/templates/entity/index.ts.ejs +6 -0
  130. package/src/codegen/templates/module/app-module.ts.ejs +65 -0
  131. package/src/codegen/templates/module/domain-module.ts.ejs +46 -0
  132. package/src/codegen/templates/query/handler.ts.ejs +25 -0
  133. package/src/codegen/templates/query/query.ts.ejs +1 -0
  134. package/src/codegen/templates/query/resolver.ts.ejs +20 -0
  135. package/src/codegen/templates/query/type.ts.ejs +24 -0
  136. package/src/codegen/types.ts +39 -0
  137. package/src/codegen/utils/path.ts +20 -0
  138. package/src/commands/generate-server.ts +551 -0
  139. package/src/index.ts +10 -0
  140. package/src/shared/graphql-types.ts +19 -0
  141. package/src/shared/main.ts +27 -0
  142. package/src/shared/mikro-orm.config.ts +18 -0
  143. package/tsconfig.json +12 -0
@@ -0,0 +1,93 @@
1
+ import type { Slice } from '@auto-engineer/narrative';
2
+ import { extractGwtSpecsFromSlice, type GwtResult } from './step-converter';
3
+ import type { CommandExample } from './step-types';
4
+
5
+ function resolveStreamId(stream: string, exampleData: Record<string, unknown>): string {
6
+ return stream.replace(/\$\{([^}]+)\}/g, (_, key: string) => String(exampleData?.[key] ?? 'unknown'));
7
+ }
8
+
9
+ function extractExampleDataFromReact(firstSpec: GwtResult): Record<string, unknown> {
10
+ if (Array.isArray(firstSpec.when)) {
11
+ const firstWhen = firstSpec.when[0];
12
+ return typeof firstWhen?.exampleData === 'object' && firstWhen.exampleData !== null ? firstWhen.exampleData : {};
13
+ }
14
+ return {};
15
+ }
16
+
17
+ function extractExampleDataFromCommand(firstSpec: GwtResult): Record<string, unknown> {
18
+ const then = firstSpec.then;
19
+ const firstExample = then.find((t): t is CommandExample => 'exampleData' in t);
20
+ return typeof firstExample?.exampleData === 'object' && firstExample.exampleData !== null
21
+ ? firstExample.exampleData
22
+ : {};
23
+ }
24
+
25
+ function extractExampleDataFromQuery(firstSpec: GwtResult): Record<string, unknown> {
26
+ if (Array.isArray(firstSpec.when)) {
27
+ const firstWhen = firstSpec.when[0];
28
+ return typeof firstWhen?.exampleData === 'object' && firstWhen.exampleData !== null ? firstWhen.exampleData : {};
29
+ }
30
+ return {};
31
+ }
32
+
33
+ function extractExampleDataFromSpecs(slice: Slice, gwtSpecs: GwtResult[]): Record<string, unknown> {
34
+ if (gwtSpecs.length === 0) {
35
+ return {};
36
+ }
37
+
38
+ const firstSpec = gwtSpecs[0];
39
+ switch (slice.type) {
40
+ case 'react':
41
+ return extractExampleDataFromReact(firstSpec);
42
+ case 'command':
43
+ return extractExampleDataFromCommand(firstSpec);
44
+ case 'query':
45
+ return extractExampleDataFromQuery(firstSpec);
46
+ default:
47
+ return {};
48
+ }
49
+ }
50
+
51
+ function isValidStreamSink(item: unknown): item is { destination: { pattern: string } } {
52
+ return (
53
+ typeof item === 'object' &&
54
+ item !== null &&
55
+ 'destination' in item &&
56
+ typeof item.destination === 'object' &&
57
+ item.destination !== null &&
58
+ 'type' in item.destination &&
59
+ item.destination.type === 'stream' &&
60
+ 'pattern' in item.destination &&
61
+ typeof item.destination.pattern === 'string'
62
+ );
63
+ }
64
+
65
+ function processStreamSink(item: unknown, exampleData: Record<string, unknown>) {
66
+ if (!isValidStreamSink(item)) {
67
+ return null;
68
+ }
69
+
70
+ const streamPattern = item.destination.pattern;
71
+ const streamId = streamPattern.length > 0 ? resolveStreamId(streamPattern, exampleData) : undefined;
72
+
73
+ return { streamPattern, streamId };
74
+ }
75
+
76
+ export function getStreamFromSink(slice: Slice): { streamPattern?: string; streamId?: string } {
77
+ if (!('server' in slice)) return {};
78
+ const gwtSpecs = extractGwtSpecsFromSlice(slice);
79
+ const exampleData = extractExampleDataFromSpecs(slice, gwtSpecs);
80
+ if (!('server' in slice) || slice.server == null || !('data' in slice.server) || !Array.isArray(slice.server.data)) {
81
+ return {};
82
+ }
83
+ const serverData = slice.server.data;
84
+
85
+ for (const item of serverData) {
86
+ const result = processStreamSink(item, exampleData);
87
+ if (result) {
88
+ return result;
89
+ }
90
+ }
91
+
92
+ return {};
93
+ }
@@ -0,0 +1,63 @@
1
+ import type { Message, MessageDefinition } from '../types';
2
+ import { extractFieldsFromMessage } from './fields';
3
+ import type { ReactGwtSpec } from './messages';
4
+ import type { EventExample } from './step-types';
5
+
6
+ function createEventMessage(
7
+ eventRef: string | undefined,
8
+ source: 'given' | 'then' | 'when',
9
+ allMessages: MessageDefinition[],
10
+ currentSliceName?: string,
11
+ currentFlowName?: string,
12
+ ): Message | undefined {
13
+ if (eventRef == null) return undefined;
14
+ const fields = extractFieldsFromMessage(eventRef, 'event', allMessages);
15
+ const messageDef = allMessages.find((m) => m.type === 'event' && m.name === eventRef);
16
+ const metadata = messageDef?.metadata as { sourceFlowName?: string; sourceSliceName?: string } | undefined;
17
+ const sourceFlowName = metadata?.sourceFlowName ?? currentFlowName;
18
+ const sourceSliceName = metadata?.sourceSliceName ?? currentSliceName;
19
+
20
+ return {
21
+ type: eventRef,
22
+ fields,
23
+ source,
24
+ sourceFlowName,
25
+ sourceSliceName,
26
+ };
27
+ }
28
+
29
+ export function extractEventsFromThen(
30
+ thenItems: Array<EventExample | { errorType: string; message?: string }>,
31
+ allMessages: MessageDefinition[],
32
+ currentSliceName?: string,
33
+ currentFlowName?: string,
34
+ ): Message[] {
35
+ return thenItems
36
+ .map((then): Message | undefined => {
37
+ if (!('eventRef' in then)) return undefined;
38
+ return createEventMessage(then.eventRef, 'then', allMessages, currentSliceName, currentFlowName);
39
+ })
40
+ .filter((event): event is Message => event !== undefined);
41
+ }
42
+
43
+ export function extractEventsFromGiven(
44
+ givenEvents: EventExample[] | undefined,
45
+ allMessages: MessageDefinition[],
46
+ currentSliceName?: string,
47
+ currentFlowName?: string,
48
+ ): Message[] {
49
+ if (!givenEvents) return [];
50
+
51
+ return givenEvents
52
+ .map((given) => createEventMessage(given.eventRef, 'given', allMessages, currentSliceName, currentFlowName))
53
+ .filter((event): event is Message => event !== undefined);
54
+ }
55
+
56
+ export function extractEventsFromWhen(gwtSpecs: ReactGwtSpec[], allMessages: MessageDefinition[]): Message[] {
57
+ return gwtSpecs.flatMap((gwt) => {
58
+ if (!Array.isArray(gwt.when)) {
59
+ return [];
60
+ }
61
+ return gwt.when.flatMap((eventExample) => extractEventsFromGiven([eventExample], allMessages));
62
+ });
63
+ }
@@ -0,0 +1,17 @@
1
+ import type { Field, MessageDefinition } from '../types';
2
+
3
+ export function extractFieldsFromMessage(
4
+ messageName: string,
5
+ messageType: 'command' | 'event' | 'state',
6
+ allMessages: MessageDefinition[],
7
+ ): Field[] {
8
+ const messageDef = allMessages.find((m) => m.type === messageType && m.name === messageName);
9
+
10
+ return (
11
+ messageDef?.fields?.map((f) => ({
12
+ name: f.name,
13
+ tsType: f.type,
14
+ required: f.required ?? true,
15
+ })) ?? []
16
+ );
17
+ }
@@ -0,0 +1,103 @@
1
+ import { type OperationDefinitionNode, parse, print, type TypeNode } from 'graphql';
2
+
3
+ export interface ParsedArg {
4
+ name: string;
5
+ tsType: string;
6
+ graphqlType: string;
7
+ nullable: boolean;
8
+ }
9
+
10
+ export interface ParsedGraphQlQuery {
11
+ queryName: string;
12
+ args: ParsedArg[];
13
+ returnType: string;
14
+ tsReturnType: string;
15
+ }
16
+
17
+ function getTypeName(typeNode: TypeNode): { graphqlType: string; nullable: boolean } {
18
+ if (typeNode.kind === 'NamedType') {
19
+ return { graphqlType: typeNode.name.value, nullable: true };
20
+ } else if (typeNode.kind === 'NonNullType') {
21
+ const inner = getTypeName(typeNode.type);
22
+ return { ...inner, nullable: false };
23
+ } else {
24
+ return getTypeName(typeNode.type);
25
+ }
26
+ }
27
+
28
+ function graphqlToTs(type: string): string {
29
+ switch (type) {
30
+ case 'String':
31
+ return 'string';
32
+ case 'Int':
33
+ case 'Float':
34
+ case 'Number':
35
+ return 'number';
36
+ case 'Boolean':
37
+ return 'boolean';
38
+ case 'Date':
39
+ return 'Date';
40
+ default:
41
+ return type;
42
+ }
43
+ }
44
+
45
+ function convertJsonAstToSdl(request: string): string {
46
+ // Handle JSON-serialized AST
47
+ if (request.startsWith('{') && request.includes('"kind"')) {
48
+ try {
49
+ const ast = JSON.parse(request) as unknown;
50
+ if (typeof ast === 'object' && ast !== null && 'kind' in ast && ast.kind === 'Document') {
51
+ // Convert AST to SDL string - cast is safe here as we've validated it's a Document
52
+ return print(ast as Parameters<typeof print>[0]);
53
+ }
54
+ } catch {
55
+ // If parsing fails, assume it's already a GraphQL string
56
+ }
57
+ }
58
+ return request;
59
+ }
60
+
61
+ export function parseGraphQlRequest(request: string): ParsedGraphQlQuery {
62
+ const sdlRequest = convertJsonAstToSdl(request);
63
+
64
+ const ast = parse(sdlRequest);
65
+ const op = ast.definitions.find(
66
+ (d): d is OperationDefinitionNode => d.kind === 'OperationDefinition' && d.operation === 'query',
67
+ );
68
+
69
+ if (!op) throw new Error('No query operation found');
70
+
71
+ const queryName = op.name?.value;
72
+ if (queryName == null) throw new Error('Query must have a name');
73
+
74
+ const args: ParsedArg[] = (op.variableDefinitions ?? []).map((def) => {
75
+ const varName = def.variable.name.value;
76
+ const { graphqlType, nullable } = getTypeName(def.type);
77
+ return {
78
+ name: varName,
79
+ graphqlType,
80
+ tsType: graphqlToTs(graphqlType),
81
+ nullable,
82
+ };
83
+ });
84
+
85
+ const field = op.selectionSet.selections[0];
86
+ if (field?.kind !== 'Field' || !field.name.value) {
87
+ throw new Error('Query selection must be a field');
88
+ }
89
+
90
+ const baseName = field.name.value;
91
+ const returnType = pascalCase(baseName) + 'View';
92
+
93
+ return {
94
+ queryName: baseName,
95
+ args,
96
+ returnType,
97
+ tsReturnType: `${returnType}[]`,
98
+ };
99
+ }
100
+
101
+ function pascalCase(input: string): string {
102
+ return input.replace(/(^\w|_\w)/g, (match) => match.replace('_', '').toUpperCase());
103
+ }
@@ -0,0 +1,79 @@
1
+ import type { Slice } from '@auto-engineer/narrative';
2
+ import type { GwtCondition } from '../types';
3
+ import { extractGwtSpecsFromSlice, type GwtConditionWithRule } from './step-converter';
4
+ import type { CommandExample, EventExample } from './step-types';
5
+
6
+ export function buildCommandGwtMapping(slice: Slice): Record<string, (GwtCondition & { failingFields?: string[] })[]> {
7
+ if (slice.type !== 'command') {
8
+ return {};
9
+ }
10
+
11
+ const gwtSpecs = extractGwtSpecsFromSlice(slice);
12
+ const mapping = buildCommandMapping(gwtSpecs);
13
+ return enhanceMapping(mapping);
14
+ }
15
+
16
+ function isCommandRef(when: CommandExample | EventExample[]): when is CommandExample {
17
+ return !Array.isArray(when) && 'commandRef' in when;
18
+ }
19
+
20
+ function buildCommandMapping(gwtSpecs: GwtConditionWithRule[]) {
21
+ const mapping: Record<string, GwtCondition[]> = {};
22
+
23
+ for (const gwt of gwtSpecs) {
24
+ if (!isCommandRef(gwt.when)) {
25
+ continue;
26
+ }
27
+ const command = gwt.when.commandRef;
28
+ if (typeof command === 'string' && command.length > 0) {
29
+ mapping[command] = mapping[command] ?? [];
30
+ mapping[command].push({
31
+ given: gwt.given,
32
+ when: gwt.when,
33
+ then: gwt.then,
34
+ description: gwt.description,
35
+ ruleDescription: gwt.ruleDescription,
36
+ });
37
+ }
38
+ }
39
+
40
+ return mapping;
41
+ }
42
+
43
+ function enhanceMapping(mapping: Record<string, GwtCondition[]>) {
44
+ const enhancedMapping: Record<string, (GwtCondition & { failingFields?: string[] })[]> = {};
45
+
46
+ for (const command in mapping) {
47
+ const conditions = mapping[command];
48
+ const successfulData = findSuccessfulExampleData(conditions);
49
+
50
+ enhancedMapping[command] = conditions.map((gwt) => ({
51
+ ...gwt,
52
+ failingFields: findFailingFields(gwt, successfulData),
53
+ }));
54
+ }
55
+
56
+ return enhancedMapping;
57
+ }
58
+
59
+ function findSuccessfulExampleData(gwts: GwtCondition[]): Record<string, unknown> {
60
+ const successful = gwts.find((gwt) => gwt.then.some((t) => typeof t === 'object' && t !== null && 'eventRef' in t));
61
+ const whenData = Array.isArray(successful?.when) ? successful?.when[0]?.exampleData : successful?.when?.exampleData;
62
+ return typeof whenData === 'object' && whenData !== null ? whenData : {};
63
+ }
64
+
65
+ function findFailingFields(gwt: GwtCondition, successfulData: Record<string, unknown>): string[] {
66
+ const hasError = gwt.then.some((t) => typeof t === 'object' && t !== null && 'errorType' in t);
67
+
68
+ if (!hasError) return [];
69
+
70
+ const whenData = Array.isArray(gwt.when) ? gwt.when[0]?.exampleData : gwt.when?.exampleData;
71
+ if (typeof whenData !== 'object' || whenData === null) return [];
72
+
73
+ return Object.entries(whenData)
74
+ .filter(([key, val]) => {
75
+ const successVal = successfulData[key];
76
+ return val === '' && successVal !== '' && successVal !== undefined;
77
+ })
78
+ .map(([key]) => key);
79
+ }
@@ -0,0 +1,71 @@
1
+ import type { Message } from '../types';
2
+ import { toKebabCase } from '../utils/path';
3
+
4
+ export interface ImportGroup {
5
+ importPath: string;
6
+ eventTypes: string[];
7
+ }
8
+
9
+ export interface CrossSliceImportContext {
10
+ currentSliceName: string;
11
+ events: Message[];
12
+ }
13
+
14
+ /**
15
+ * Groups events by their import paths, handling cross-slice imports correctly.
16
+ * Events from the current slice are imported from './events',
17
+ * while cross-slice events are imported from '../other-slice/events'.
18
+ */
19
+ export function groupEventImports(context: CrossSliceImportContext): ImportGroup[] {
20
+ const { currentSliceName, events } = context;
21
+ const importGroups = new Map<string, string[]>();
22
+
23
+ for (const event of events) {
24
+ if (!event.type) continue;
25
+ let importPath: string;
26
+ const isFromCurrentSlice =
27
+ event.source === 'then' || event.sourceSliceName === currentSliceName || event.sourceSliceName == null;
28
+
29
+ if (isFromCurrentSlice) {
30
+ importPath = './events';
31
+ } else {
32
+ importPath = `../${toKebabCase(event.sourceSliceName ?? currentSliceName)}/events`;
33
+ }
34
+ if (!importGroups.has(importPath)) {
35
+ importGroups.set(importPath, []);
36
+ }
37
+ const eventTypes = importGroups.get(importPath)!;
38
+ if (!eventTypes.includes(event.type)) {
39
+ eventTypes.push(event.type);
40
+ }
41
+ }
42
+ return Array.from(importGroups.entries()).map(([importPath, eventTypes]) => ({
43
+ importPath,
44
+ eventTypes: eventTypes.sort(),
45
+ }));
46
+ }
47
+
48
+ /**
49
+ * Filters events to only include those from the current slice (source === 'then').
50
+ * Used for generating local event definitions.
51
+ */
52
+ export function getLocalEvents(events: Message[]): Message[] {
53
+ return events.filter((event) => event.source === 'then');
54
+ }
55
+
56
+ /**
57
+ * Extracts all unique event types from a list of events.
58
+ */
59
+ export function getAllEventTypes(events: Message[]): string[] {
60
+ const eventTypes = events.map((event) => event.type).filter((type): type is string => Boolean(type));
61
+
62
+ return Array.from(new Set(eventTypes)).sort();
63
+ }
64
+
65
+ /**
66
+ * Creates a TypeScript union type string from event types.
67
+ */
68
+ export function createEventUnionType(events: Message[]): string {
69
+ const eventTypes = getAllEventTypes(events);
70
+ return eventTypes.length > 0 ? eventTypes.join(' | ') : 'never';
71
+ }
@@ -0,0 +1,9 @@
1
+ export * from './commands';
2
+ export * from './events';
3
+ export * from './gwt';
4
+ export * from './imports';
5
+ export * from './messages';
6
+ export * from './projection';
7
+ export * from './query';
8
+ export * from './states';
9
+ export * from './type-helpers';
@@ -0,0 +1,232 @@
1
+ import type { Slice } from '@auto-engineer/narrative';
2
+ import createDebug from 'debug';
3
+ import type { Message, MessageDefinition } from '../types';
4
+ import { extractCommandsFromGwt, extractCommandsFromThen } from './commands';
5
+ import { extractEventsFromGiven, extractEventsFromThen, extractEventsFromWhen } from './events';
6
+ import { extractFieldsFromMessage } from './fields';
7
+ import { extractProjectionIdField, extractProjectionSingleton } from './projection';
8
+ import { extractStatesFromData, extractStatesFromTarget } from './states';
9
+ import { extractGwtSpecsFromSlice, type GwtConditionWithRule } from './step-converter';
10
+ import type { CommandExample, EventExample } from './step-types';
11
+
12
+ const debug = createDebug('auto:server-generator-nestjs:extract:messages');
13
+ const debugCommand = createDebug('auto:server-generator-nestjs:extract:messages:command');
14
+ const debugQuery = createDebug('auto:server-generator-nestjs:extract:messages:query');
15
+ const debugReact = createDebug('auto:server-generator-nestjs:extract:messages:react');
16
+ const debugDedupe = createDebug('auto:server-generator-nestjs:extract:messages:dedupe');
17
+
18
+ export interface ExtractedMessages {
19
+ commands: Message[];
20
+ events: Message[];
21
+ states: Message[];
22
+ commandSchemasByName: Record<string, Message>;
23
+ projectionIdField?: string;
24
+ projectionSingleton?: boolean;
25
+ }
26
+
27
+ export interface ReactGwtSpec {
28
+ when?: EventExample[];
29
+ then: CommandExample[];
30
+ }
31
+
32
+ const EMPTY_EXTRACTED_MESSAGES: ExtractedMessages = {
33
+ commands: [],
34
+ events: [],
35
+ states: [],
36
+ commandSchemasByName: {},
37
+ };
38
+
39
+ function deduplicateMessages<T extends Message>(messages: T[]): T[] {
40
+ debugDedupe('Deduplicating %d messages', messages.length);
41
+ const uniqueMap = new Map<string, T>();
42
+ for (const message of messages) {
43
+ if (!uniqueMap.has(message.type)) {
44
+ uniqueMap.set(message.type, message);
45
+ debugDedupe(' Added unique message: %s', message.type);
46
+ } else {
47
+ debugDedupe(' Skipped duplicate message: %s', message.type);
48
+ }
49
+ }
50
+ const result = Array.from(uniqueMap.values());
51
+ debugDedupe('Result: %d unique messages from %d total', result.length, messages.length);
52
+ return result;
53
+ }
54
+
55
+ function extractMessagesForCommand(slice: Slice, allMessages: MessageDefinition[]): ExtractedMessages {
56
+ debugCommand('Extracting messages for command slice: %s', slice.name);
57
+
58
+ if (slice.type !== 'command') {
59
+ debugCommand(' Slice type is not command, returning empty');
60
+ return EMPTY_EXTRACTED_MESSAGES;
61
+ }
62
+
63
+ const gwtSpecs = extractGwtSpecsFromSlice(slice);
64
+ debugCommand(' Found %d GWT specs', gwtSpecs.length);
65
+
66
+ const { commands, commandSchemasByName } = extractCommandsFromGwt(gwtSpecs, allMessages);
67
+ debugCommand(' Extracted %d commands', commands.length);
68
+ debugCommand(' Command schemas: %o', Object.keys(commandSchemasByName));
69
+
70
+ const events: Message[] = gwtSpecs.flatMap((gwt: GwtConditionWithRule): Message[] => {
71
+ const givenEventsOnly = gwt.given?.filter((item): item is EventExample => 'eventRef' in item);
72
+ const givenEvents = extractEventsFromGiven(givenEventsOnly, allMessages, slice.name);
73
+
74
+ const thenEventsOnly = gwt.then.filter((item): item is EventExample => 'eventRef' in item);
75
+ const thenEvents = extractEventsFromThen(thenEventsOnly, allMessages, slice.name);
76
+ debugCommand(' GWT: given=%d events, then=%d events', givenEvents.length, thenEvents.length);
77
+ return [...givenEvents, ...thenEvents];
78
+ });
79
+ debugCommand(' Total events extracted: %d', events.length);
80
+
81
+ const result = {
82
+ commands,
83
+ events: deduplicateMessages(events),
84
+ states: [],
85
+ commandSchemasByName,
86
+ };
87
+
88
+ debugCommand(' Final result: %d commands, %d events', result.commands.length, result.events.length);
89
+ return result;
90
+ }
91
+
92
+ function extractMessagesForQuery(slice: Slice, allMessages: MessageDefinition[]): ExtractedMessages {
93
+ debugQuery('Extracting messages for query slice: %s', slice.name);
94
+
95
+ if (slice.type !== 'query') {
96
+ debugQuery(' Slice type is not query, returning empty');
97
+ return EMPTY_EXTRACTED_MESSAGES;
98
+ }
99
+
100
+ const gwtSpecs = extractGwtSpecsFromSlice(slice);
101
+ debugQuery(' Found %d GWT specs', gwtSpecs.length);
102
+
103
+ const projectionIdField = extractProjectionIdField(slice);
104
+ debugQuery(' Projection ID field: %s', projectionIdField ?? 'none');
105
+
106
+ const projectionSingleton = extractProjectionSingleton(slice);
107
+ debugQuery(' Projection singleton: %s', projectionSingleton);
108
+
109
+ const events: Message[] = gwtSpecs.flatMap((gwt: GwtConditionWithRule) => {
110
+ const eventsFromGiven = Array.isArray(gwt.given)
111
+ ? gwt.given.filter((item): item is EventExample => 'eventRef' in item)
112
+ : [];
113
+ let eventsFromWhen: EventExample[] = [];
114
+ if (Array.isArray(gwt.when)) {
115
+ eventsFromWhen = gwt.when.filter((item): item is EventExample => 'eventRef' in item);
116
+ } else if (gwt.when != null && typeof gwt.when === 'object' && 'eventRef' in gwt.when) {
117
+ const whenItem = gwt.when as EventExample;
118
+ if (whenItem.eventRef && whenItem.eventRef.trim() !== '') {
119
+ eventsFromWhen = [whenItem];
120
+ }
121
+ }
122
+ const givenEvents = extractEventsFromGiven(eventsFromGiven, allMessages);
123
+ const whenEvents = eventsFromWhen
124
+ .map((eventExample: EventExample) => {
125
+ const fields = extractFieldsFromMessage(eventExample.eventRef, 'event', allMessages);
126
+ const messageDef = allMessages.find((m) => m.type === 'event' && m.name === eventExample.eventRef);
127
+ const metadata = messageDef?.metadata as { sourceFlowName?: string; sourceSliceName?: string } | undefined;
128
+
129
+ return {
130
+ type: eventExample.eventRef,
131
+ fields,
132
+ source: 'when' as const,
133
+ sourceFlowName: metadata?.sourceFlowName,
134
+ sourceSliceName: metadata?.sourceSliceName,
135
+ } as Message;
136
+ })
137
+ .filter((event): event is Message => event.type !== undefined);
138
+
139
+ return [...givenEvents, ...whenEvents];
140
+ });
141
+ debugQuery(' Extracted %d events total', events.length);
142
+
143
+ const states: Message[] = extractStatesFromTarget(slice, allMessages);
144
+ debugQuery(' Extracted %d states from target', states.length);
145
+
146
+ const result = {
147
+ commands: [],
148
+ events: deduplicateMessages(events),
149
+ states,
150
+ commandSchemasByName: {},
151
+ projectionIdField,
152
+ projectionSingleton,
153
+ };
154
+
155
+ debugQuery(' Final result: %d events, %d states', result.events.length, result.states.length);
156
+ return result;
157
+ }
158
+
159
+ function extractMessagesForReact(slice: Slice, allMessages: MessageDefinition[]): ExtractedMessages {
160
+ debugReact('Extracting messages for react slice: %s', slice.name);
161
+
162
+ if (slice.type !== 'react') {
163
+ debugReact(' Slice type is not react, returning empty');
164
+ return EMPTY_EXTRACTED_MESSAGES;
165
+ }
166
+
167
+ const gwtSpecs = extractGwtSpecsFromSlice(slice);
168
+ debugReact(' Found %d GWT specs', gwtSpecs.length);
169
+
170
+ const reactGwtSpecs: ReactGwtSpec[] = gwtSpecs.map((gwt) => ({
171
+ when: Array.isArray(gwt.when) ? gwt.when : undefined,
172
+ then: gwt.then.filter((t): t is CommandExample => 'commandRef' in t),
173
+ }));
174
+
175
+ const events = extractEventsFromWhen(reactGwtSpecs, allMessages);
176
+ debugReact(' Extracted %d events from when', events.length);
177
+
178
+ const { commands, commandSchemasByName } = extractCommandsFromThen(gwtSpecs, allMessages);
179
+ debugReact(' Extracted %d commands from then', commands.length);
180
+ debugReact(' Command schemas: %o', Object.keys(commandSchemasByName));
181
+
182
+ const states = extractStatesFromData(slice, allMessages);
183
+ debugReact(' Extracted %d states from data', states.length);
184
+
185
+ const result = {
186
+ commands,
187
+ events: deduplicateMessages(events),
188
+ states,
189
+ commandSchemasByName,
190
+ };
191
+
192
+ debugReact(
193
+ ' Final result: %d commands, %d events, %d states',
194
+ result.commands.length,
195
+ result.events.length,
196
+ result.states.length,
197
+ );
198
+ return result;
199
+ }
200
+
201
+ export function extractMessagesFromSpecs(slice: Slice, allMessages: MessageDefinition[]): ExtractedMessages {
202
+ debug('Extracting messages from slice: %s (type: %s)', slice.name, slice.type);
203
+ debug(' Total message definitions available: %d', allMessages.length);
204
+
205
+ let result: ExtractedMessages;
206
+
207
+ switch (slice.type) {
208
+ case 'command':
209
+ result = extractMessagesForCommand(slice, allMessages);
210
+ break;
211
+ case 'query':
212
+ result = extractMessagesForQuery(slice, allMessages);
213
+ break;
214
+ case 'react':
215
+ result = extractMessagesForReact(slice, allMessages);
216
+ break;
217
+ default: {
218
+ const unknownSlice = slice as Slice;
219
+ debug(' Unknown slice type: %s, returning empty', unknownSlice.type);
220
+ result = EMPTY_EXTRACTED_MESSAGES;
221
+ }
222
+ }
223
+
224
+ debug(
225
+ ' Extraction complete: %d commands, %d events, %d states',
226
+ result.commands.length,
227
+ result.events.length,
228
+ result.states.length,
229
+ );
230
+
231
+ return result;
232
+ }