@auto-engineer/server-generator-apollo-emmett 0.1.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.
Files changed (272) hide show
  1. package/.tmp/server-test-output/server/package.json +26 -0
  2. package/.tmp/server-test-output/server/scripts/generate-schema.ts +31 -0
  3. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/commands.ts +8 -0
  4. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/decide.specs.ts +36 -0
  5. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/decide.ts +33 -0
  6. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/events.ts +10 -0
  7. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/evolve.ts +28 -0
  8. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/handle.ts +24 -0
  9. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/mutation.resolver.ts +25 -0
  10. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/register.ts +7 -0
  11. package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/state.ts +47 -0
  12. package/.tmp/server-test-output/server/src/domain/flows/add-item/get-available-items/projection.specs.ts +46 -0
  13. package/.tmp/server-test-output/server/src/domain/flows/add-item/get-available-items/projection.ts +38 -0
  14. package/.tmp/server-test-output/server/src/domain/flows/add-item/get-available-items/query.resolver.ts +39 -0
  15. package/.tmp/server-test-output/server/src/domain/flows/add-item/get-available-items/state.ts +5 -0
  16. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/commands.ts +8 -0
  17. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/decide.specs.ts +36 -0
  18. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/decide.ts +33 -0
  19. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/events.ts +3 -0
  20. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/evolve.ts +28 -0
  21. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/handle.ts +24 -0
  22. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/mutation.resolver.ts +25 -0
  23. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/register.ts +7 -0
  24. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/state.ts +47 -0
  25. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/react.specs.ts +49 -0
  26. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/react.ts +43 -0
  27. package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/register.ts +24 -0
  28. package/.tmp/server-test-output/server/src/domain/shared/ReadModel.ts +26 -0
  29. package/.tmp/server-test-output/server/src/domain/shared/index.ts +4 -0
  30. package/.tmp/server-test-output/server/src/domain/shared/reactorSpecification.ts +257 -0
  31. package/.tmp/server-test-output/server/src/domain/shared/sendCommand.ts +21 -0
  32. package/.tmp/server-test-output/server/src/domain/shared/types.ts +31 -0
  33. package/.tmp/server-test-output/server/src/server.ts +43 -0
  34. package/.tmp/server-test-output/server/src/utils/index.ts +3 -0
  35. package/.tmp/server-test-output/server/src/utils/loadProjections.ts +30 -0
  36. package/.tmp/server-test-output/server/src/utils/loadRegisterFiles.ts +41 -0
  37. package/.tmp/server-test-output/server/src/utils/loadResolvers.ts +36 -0
  38. package/.tmp/server-test-output/server/tsconfig.json +19 -0
  39. package/.tmp/server-test-output/server/vitest.config.ts +7 -0
  40. package/.turbo/turbo-build.log +5 -0
  41. package/.turbo/turbo-format.log +57 -0
  42. package/.turbo/turbo-lint.log +5 -0
  43. package/.turbo/turbo-test.log +113 -0
  44. package/.turbo/turbo-type-check.log +4 -0
  45. package/CHANGELOG.md +141 -0
  46. package/DEBUG.md +177 -0
  47. package/LICENSE +10 -0
  48. package/README.md +185 -0
  49. package/dist/cli-manifest.d.ts +3 -0
  50. package/dist/cli-manifest.d.ts.map +1 -0
  51. package/dist/cli-manifest.js +8 -0
  52. package/dist/cli-manifest.js.map +1 -0
  53. package/dist/codegen/extract/commands.d.ts +19 -0
  54. package/dist/codegen/extract/commands.d.ts.map +1 -0
  55. package/dist/codegen/extract/commands.js +52 -0
  56. package/dist/codegen/extract/commands.js.map +1 -0
  57. package/dist/codegen/extract/data-sink.d.ts +6 -0
  58. package/dist/codegen/extract/data-sink.d.ts.map +1 -0
  59. package/dist/codegen/extract/data-sink.js +39 -0
  60. package/dist/codegen/extract/data-sink.js.map +1 -0
  61. package/dist/codegen/extract/events.d.ts +10 -0
  62. package/dist/codegen/extract/events.d.ts.map +1 -0
  63. package/dist/codegen/extract/events.js +32 -0
  64. package/dist/codegen/extract/events.js.map +1 -0
  65. package/dist/codegen/extract/fields.d.ts +3 -0
  66. package/dist/codegen/extract/fields.d.ts.map +1 -0
  67. package/dist/codegen/extract/fields.js +9 -0
  68. package/dist/codegen/extract/fields.js.map +1 -0
  69. package/dist/codegen/extract/graphql.d.ts +14 -0
  70. package/dist/codegen/extract/graphql.d.ts.map +1 -0
  71. package/dist/codegen/extract/graphql.js +81 -0
  72. package/dist/codegen/extract/graphql.js.map +1 -0
  73. package/dist/codegen/extract/gwt.d.ts +6 -0
  74. package/dist/codegen/extract/gwt.d.ts.map +1 -0
  75. package/dist/codegen/extract/gwt.js +61 -0
  76. package/dist/codegen/extract/gwt.js.map +1 -0
  77. package/dist/codegen/extract/index.d.ts +8 -0
  78. package/dist/codegen/extract/index.d.ts.map +1 -0
  79. package/dist/codegen/extract/index.js +8 -0
  80. package/dist/codegen/extract/index.js.map +1 -0
  81. package/dist/codegen/extract/messages.d.ts +24 -0
  82. package/dist/codegen/extract/messages.d.ts.map +1 -0
  83. package/dist/codegen/extract/messages.js +131 -0
  84. package/dist/codegen/extract/messages.js.map +1 -0
  85. package/dist/codegen/extract/projection.d.ts +4 -0
  86. package/dist/codegen/extract/projection.d.ts.map +1 -0
  87. package/dist/codegen/extract/projection.js +30 -0
  88. package/dist/codegen/extract/projection.js.map +1 -0
  89. package/dist/codegen/extract/query.d.ts +11 -0
  90. package/dist/codegen/extract/query.d.ts.map +1 -0
  91. package/dist/codegen/extract/query.js +11 -0
  92. package/dist/codegen/extract/query.js.map +1 -0
  93. package/dist/codegen/extract/states.d.ts +5 -0
  94. package/dist/codegen/extract/states.d.ts.map +1 -0
  95. package/dist/codegen/extract/states.js +35 -0
  96. package/dist/codegen/extract/states.js.map +1 -0
  97. package/dist/codegen/scaffoldFromSchema.d.ts +9 -0
  98. package/dist/codegen/scaffoldFromSchema.d.ts.map +1 -0
  99. package/dist/codegen/scaffoldFromSchema.integration.specs.d.ts +2 -0
  100. package/dist/codegen/scaffoldFromSchema.integration.specs.d.ts.map +1 -0
  101. package/dist/codegen/scaffoldFromSchema.integration.specs.js +59 -0
  102. package/dist/codegen/scaffoldFromSchema.integration.specs.js.map +1 -0
  103. package/dist/codegen/scaffoldFromSchema.js +326 -0
  104. package/dist/codegen/scaffoldFromSchema.js.map +1 -0
  105. package/dist/codegen/templates/command/commands.specs.ts +90 -0
  106. package/dist/codegen/templates/command/commands.ts.ejs +11 -0
  107. package/dist/codegen/templates/command/decide.specs.specs.ts +265 -0
  108. package/dist/codegen/templates/command/decide.specs.ts +542 -0
  109. package/dist/codegen/templates/command/decide.specs.ts.ejs +51 -0
  110. package/dist/codegen/templates/command/decide.ts.ejs +107 -0
  111. package/dist/codegen/templates/command/events.specs.ts +106 -0
  112. package/dist/codegen/templates/command/events.ts.ejs +14 -0
  113. package/dist/codegen/templates/command/evolve.specs.ts +102 -0
  114. package/dist/codegen/templates/command/evolve.ts.ejs +39 -0
  115. package/dist/codegen/templates/command/handle.specs.ts +313 -0
  116. package/dist/codegen/templates/command/handle.ts.ejs +111 -0
  117. package/dist/codegen/templates/command/mutation.resolver.specs.ts +115 -0
  118. package/dist/codegen/templates/command/mutation.resolver.ts.ejs +25 -0
  119. package/dist/codegen/templates/command/register.specs.ts +107 -0
  120. package/dist/codegen/templates/command/register.ts.ejs +12 -0
  121. package/dist/codegen/templates/command/state.specs.ts +127 -0
  122. package/dist/codegen/templates/command/state.ts.ejs +47 -0
  123. package/dist/codegen/templates/query/projection.specs.specs..ts +276 -0
  124. package/dist/codegen/templates/query/projection.specs.ts +348 -0
  125. package/dist/codegen/templates/query/projection.specs.ts.ejs +71 -0
  126. package/dist/codegen/templates/query/projection.ts.ejs +121 -0
  127. package/dist/codegen/templates/query/query.resolver.specs.ts +254 -0
  128. package/dist/codegen/templates/query/query.resolver.ts.ejs +98 -0
  129. package/dist/codegen/templates/query/state.specs.ts +70 -0
  130. package/dist/codegen/templates/query/state.ts.ejs +7 -0
  131. package/dist/codegen/templates/react/react.specs.specs.ts +214 -0
  132. package/dist/codegen/templates/react/react.specs.ts +241 -0
  133. package/dist/codegen/templates/react/react.specs.ts.ejs +80 -0
  134. package/dist/codegen/templates/react/react.ts.ejs +56 -0
  135. package/dist/codegen/templates/react/register.specs.ts +222 -0
  136. package/dist/codegen/templates/react/register.ts.ejs +39 -0
  137. package/dist/codegen/test-data/specVariant1.d.ts +4 -0
  138. package/dist/codegen/test-data/specVariant1.d.ts.map +1 -0
  139. package/dist/codegen/test-data/specVariant1.js +185 -0
  140. package/dist/codegen/test-data/specVariant1.js.map +1 -0
  141. package/dist/codegen/types.d.ts +35 -0
  142. package/dist/codegen/types.d.ts.map +1 -0
  143. package/dist/codegen/types.js +2 -0
  144. package/dist/codegen/types.js.map +1 -0
  145. package/dist/codegen/utils/path.d.ts +4 -0
  146. package/dist/codegen/utils/path.d.ts.map +1 -0
  147. package/dist/codegen/utils/path.js +18 -0
  148. package/dist/codegen/utils/path.js.map +1 -0
  149. package/dist/commands/generate-server.d.ts +81 -0
  150. package/dist/commands/generate-server.d.ts.map +1 -0
  151. package/dist/commands/generate-server.js +383 -0
  152. package/dist/commands/generate-server.js.map +1 -0
  153. package/dist/domain/shared/ReadModel.d.ts +10 -0
  154. package/dist/domain/shared/ReadModel.d.ts.map +1 -0
  155. package/dist/domain/shared/ReadModel.js +19 -0
  156. package/dist/domain/shared/ReadModel.js.map +1 -0
  157. package/dist/domain/shared/ReadModel.ts +26 -0
  158. package/dist/domain/shared/index.d.ts +5 -0
  159. package/dist/domain/shared/index.d.ts.map +1 -0
  160. package/dist/domain/shared/index.js +5 -0
  161. package/dist/domain/shared/index.js.map +1 -0
  162. package/dist/domain/shared/index.ts +4 -0
  163. package/dist/domain/shared/reactorSpecification.d.ts +35 -0
  164. package/dist/domain/shared/reactorSpecification.d.ts.map +1 -0
  165. package/dist/domain/shared/reactorSpecification.js +155 -0
  166. package/dist/domain/shared/reactorSpecification.js.map +1 -0
  167. package/dist/domain/shared/reactorSpecification.ts +257 -0
  168. package/dist/domain/shared/sendCommand.d.ts +4 -0
  169. package/dist/domain/shared/sendCommand.d.ts.map +1 -0
  170. package/dist/domain/shared/sendCommand.js +17 -0
  171. package/dist/domain/shared/sendCommand.js.map +1 -0
  172. package/dist/domain/shared/sendCommand.ts +21 -0
  173. package/dist/domain/shared/types.d.ts +19 -0
  174. package/dist/domain/shared/types.d.ts.map +1 -0
  175. package/dist/domain/shared/types.js +39 -0
  176. package/dist/domain/shared/types.js.map +1 -0
  177. package/dist/domain/shared/types.ts +31 -0
  178. package/dist/index.d.ts +3 -0
  179. package/dist/index.d.ts.map +1 -0
  180. package/dist/index.js +3 -0
  181. package/dist/index.js.map +1 -0
  182. package/dist/server.d.ts +2 -0
  183. package/dist/server.d.ts.map +1 -0
  184. package/dist/server.js +33 -0
  185. package/dist/server.js.map +1 -0
  186. package/dist/server.ts +43 -0
  187. package/dist/utils/index.d.ts +4 -0
  188. package/dist/utils/index.d.ts.map +1 -0
  189. package/dist/utils/index.js +4 -0
  190. package/dist/utils/index.js.map +1 -0
  191. package/dist/utils/index.ts +3 -0
  192. package/dist/utils/loadProjections.d.ts +3 -0
  193. package/dist/utils/loadProjections.d.ts.map +1 -0
  194. package/dist/utils/loadProjections.js +23 -0
  195. package/dist/utils/loadProjections.js.map +1 -0
  196. package/dist/utils/loadProjections.ts +30 -0
  197. package/dist/utils/loadRegisterFiles.d.ts +6 -0
  198. package/dist/utils/loadRegisterFiles.d.ts.map +1 -0
  199. package/dist/utils/loadRegisterFiles.js +28 -0
  200. package/dist/utils/loadRegisterFiles.js.map +1 -0
  201. package/dist/utils/loadRegisterFiles.ts +41 -0
  202. package/dist/utils/loadResolvers.d.ts +5 -0
  203. package/dist/utils/loadResolvers.d.ts.map +1 -0
  204. package/dist/utils/loadResolvers.js +27 -0
  205. package/dist/utils/loadResolvers.js.map +1 -0
  206. package/dist/utils/loadResolvers.ts +36 -0
  207. package/package.json +55 -0
  208. package/src/cli-manifest.ts +9 -0
  209. package/src/codegen/extract/commands.ts +79 -0
  210. package/src/codegen/extract/data-sink.ts +45 -0
  211. package/src/codegen/extract/events.ts +46 -0
  212. package/src/codegen/extract/fields.ts +17 -0
  213. package/src/codegen/extract/graphql.ts +103 -0
  214. package/src/codegen/extract/gwt.ts +75 -0
  215. package/src/codegen/extract/index.ts +7 -0
  216. package/src/codegen/extract/messages.ts +196 -0
  217. package/src/codegen/extract/projection.ts +47 -0
  218. package/src/codegen/extract/query.ts +18 -0
  219. package/src/codegen/extract/states.ts +45 -0
  220. package/src/codegen/scaffoldFromSchema.integration.specs.ts +71 -0
  221. package/src/codegen/scaffoldFromSchema.ts +440 -0
  222. package/src/codegen/templates/command/commands.specs.ts +90 -0
  223. package/src/codegen/templates/command/commands.ts.ejs +11 -0
  224. package/src/codegen/templates/command/decide.specs.specs.ts +265 -0
  225. package/src/codegen/templates/command/decide.specs.ts +542 -0
  226. package/src/codegen/templates/command/decide.specs.ts.ejs +51 -0
  227. package/src/codegen/templates/command/decide.ts.ejs +107 -0
  228. package/src/codegen/templates/command/events.specs.ts +106 -0
  229. package/src/codegen/templates/command/events.ts.ejs +14 -0
  230. package/src/codegen/templates/command/evolve.specs.ts +102 -0
  231. package/src/codegen/templates/command/evolve.ts.ejs +39 -0
  232. package/src/codegen/templates/command/handle.specs.ts +313 -0
  233. package/src/codegen/templates/command/handle.ts.ejs +111 -0
  234. package/src/codegen/templates/command/mutation.resolver.specs.ts +115 -0
  235. package/src/codegen/templates/command/mutation.resolver.ts.ejs +25 -0
  236. package/src/codegen/templates/command/register.specs.ts +107 -0
  237. package/src/codegen/templates/command/register.ts.ejs +12 -0
  238. package/src/codegen/templates/command/state.specs.ts +127 -0
  239. package/src/codegen/templates/command/state.ts.ejs +47 -0
  240. package/src/codegen/templates/query/projection.specs.specs..ts +276 -0
  241. package/src/codegen/templates/query/projection.specs.ts +348 -0
  242. package/src/codegen/templates/query/projection.specs.ts.ejs +71 -0
  243. package/src/codegen/templates/query/projection.ts.ejs +121 -0
  244. package/src/codegen/templates/query/query.resolver.specs.ts +254 -0
  245. package/src/codegen/templates/query/query.resolver.ts.ejs +98 -0
  246. package/src/codegen/templates/query/state.specs.ts +70 -0
  247. package/src/codegen/templates/query/state.ts.ejs +7 -0
  248. package/src/codegen/templates/react/react.specs.specs.ts +214 -0
  249. package/src/codegen/templates/react/react.specs.ts +241 -0
  250. package/src/codegen/templates/react/react.specs.ts.ejs +80 -0
  251. package/src/codegen/templates/react/react.ts.ejs +56 -0
  252. package/src/codegen/templates/react/register.specs.ts +222 -0
  253. package/src/codegen/templates/react/register.ts.ejs +39 -0
  254. package/src/codegen/test-data/specVariant1.json +212 -0
  255. package/src/codegen/test-data/specVariant1.ts +188 -0
  256. package/src/codegen/test-data/specVariant2.json +396 -0
  257. package/src/codegen/types.ts +35 -0
  258. package/src/codegen/utils/path.ts +20 -0
  259. package/src/commands/generate-server.ts +517 -0
  260. package/src/domain/shared/ReadModel.ts +26 -0
  261. package/src/domain/shared/index.ts +4 -0
  262. package/src/domain/shared/reactorSpecification.ts +257 -0
  263. package/src/domain/shared/sendCommand.ts +21 -0
  264. package/src/domain/shared/types.ts +31 -0
  265. package/src/index.ts +2 -0
  266. package/src/server.ts +43 -0
  267. package/src/utils/index.ts +3 -0
  268. package/src/utils/loadProjections.ts +30 -0
  269. package/src/utils/loadRegisterFiles.ts +41 -0
  270. package/src/utils/loadResolvers.ts +36 -0
  271. package/tsconfig.json +12 -0
  272. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,440 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+ import ejs from 'ejs';
6
+ import { ensureDirExists, ensureDirPath, toKebabCase } from './utils/path';
7
+ import { camelCase, pascalCase } from 'change-case';
8
+ import prettier from 'prettier';
9
+ import { Flow, Slice, SpecsSchemaType } from '@auto-engineer/flow';
10
+ import createDebug from 'debug';
11
+
12
+ const debug = createDebug('emmett:scaffold');
13
+ const debugTemplate = createDebug('emmett:scaffold:template');
14
+ const debugFiles = createDebug('emmett:scaffold:files');
15
+ const debugFlow = createDebug('emmett:scaffold:flow');
16
+ const debugSlice = createDebug('emmett:scaffold:slice');
17
+ const debugPlan = createDebug('emmett:scaffold:plan');
18
+ const debugFormat = createDebug('emmett:scaffold:format');
19
+ import {
20
+ buildCommandGwtMapping,
21
+ buildQueryGwtMapping,
22
+ extractMessagesFromSpecs,
23
+ extractProjectionName,
24
+ } from './extract';
25
+ import { Message, MessageDefinition, GwtCondition } from './types';
26
+ import { parseGraphQlRequest } from './extract/graphql';
27
+ import { getStreamFromSink } from './extract/data-sink';
28
+
29
+ const defaultFilesByType: Record<string, string[]> = {
30
+ command: [
31
+ 'commands.ts.ejs',
32
+ 'events.ts.ejs',
33
+ 'state.ts.ejs',
34
+ 'decide.ts.ejs',
35
+ 'evolve.ts.ejs',
36
+ 'handle.ts.ejs',
37
+ 'mutation.resolver.ts.ejs',
38
+ 'decide.specs.ts.ejs',
39
+ 'register.ts.ejs',
40
+ ],
41
+ query: ['projection.ts.ejs', 'state.ts.ejs', 'projection.specs.ts.ejs', 'query.resolver.ts.ejs'],
42
+ react: ['react.ts.ejs', 'react.specs.ts.ejs', 'register.ts.ejs'],
43
+ };
44
+
45
+ export interface FilePlan {
46
+ outputPath: string;
47
+ contents: string;
48
+ }
49
+
50
+ async function renderTemplate(templatePath: string, data: Record<string, unknown>): Promise<string> {
51
+ debugTemplate('Rendering template: %s', templatePath);
52
+ debugTemplate('Data keys: %o', Object.keys(data));
53
+
54
+ const content = await fs.readFile(templatePath, 'utf8');
55
+ debugTemplate('Template content loaded, size: %d bytes', content.length);
56
+ const template = ejs.compile(content, {
57
+ async: true,
58
+ escape: (text: string): string => text,
59
+ });
60
+ debugTemplate('Template compiled successfully');
61
+
62
+ const graphqlType = (tsType: string): string => {
63
+ debugTemplate('Converting TS type to GraphQL: %s', tsType);
64
+ if (!tsType) return 'String';
65
+ if (tsType === 'ID') return 'ID';
66
+ if (tsType === 'string') return 'String';
67
+ if (tsType === 'number') return 'Number';
68
+ if (tsType === 'boolean') return 'Boolean';
69
+ if (tsType === 'Date') return 'Date';
70
+ if (tsType === 'object') return 'Object';
71
+ if (tsType.endsWith('[]')) {
72
+ const inner = tsType.slice(0, -2);
73
+ return `[${graphqlType(inner)}]`;
74
+ }
75
+ return 'String';
76
+ };
77
+
78
+ const result = await template({
79
+ ...data,
80
+ pascalCase,
81
+ toKebabCase,
82
+ camelCase,
83
+ graphqlType,
84
+ formatTsValue,
85
+ formatDataObject,
86
+ messages: data.messages,
87
+ message: data.message,
88
+ });
89
+
90
+ debugTemplate('Template rendered, output size: %d bytes', result.length);
91
+ return result;
92
+ }
93
+
94
+ function formatTsValueSimple(value: unknown, tsType: string): string {
95
+ if (tsType === 'Date') {
96
+ return `new Date(${JSON.stringify(value)})`;
97
+ }
98
+ if (tsType === 'string') {
99
+ return JSON.stringify(value);
100
+ }
101
+ if (tsType === 'number') {
102
+ return String(value);
103
+ }
104
+ if (tsType === 'boolean') {
105
+ return value === true ? 'true' : 'false';
106
+ }
107
+ return JSON.stringify(value);
108
+ }
109
+
110
+ function formatTsValue(value: unknown, tsType: string): string {
111
+ if (tsType.endsWith('[]') && Array.isArray(value)) {
112
+ const innerType = tsType.slice(0, -2);
113
+ return `[${value.map((v) => formatTsValue(v, innerType)).join(', ')}]`;
114
+ }
115
+
116
+ if (tsType === 'object' && typeof value === 'object' && value !== null) {
117
+ const entries = Object.entries(value as Record<string, unknown>).map(
118
+ ([k, v]) => `${k}: ${formatTsValue(v, 'string')}`,
119
+ );
120
+ return `{ ${entries.join(', ')} }`;
121
+ }
122
+
123
+ return formatTsValueSimple(value, tsType);
124
+ }
125
+
126
+ function formatDataObject(exampleData: Record<string, unknown>, schema: Message | undefined): string {
127
+ debugFormat('Formatting data object with %d fields', Object.keys(exampleData).length);
128
+ const lines = Object.entries(exampleData).map(([key, val]) => {
129
+ const typeDef = schema?.fields?.find((f) => f.name === key);
130
+ const tsType = typeDef?.tsType ?? 'string';
131
+ debugFormat(' Field %s: type=%s, value=%o', key, tsType, val);
132
+ return `${key}: ${formatTsValue(val, tsType)}`;
133
+ });
134
+ return `{
135
+ ${lines.join(',\n ')}
136
+ }`;
137
+ }
138
+
139
+ async function generateFileForTemplate(
140
+ templateFile: string,
141
+ slice: Slice,
142
+ sliceDir: string,
143
+ templateData: Record<string, unknown>,
144
+ ): Promise<FilePlan> {
145
+ debugFiles('Generating file from template: %s', templateFile);
146
+ debugFiles(' Slice type: %s', slice.type);
147
+ debugFiles(' Slice directory: %s', sliceDir);
148
+
149
+ const __dirname = dirname(fileURLToPath(import.meta.url));
150
+ const templatePath = path.join(__dirname, './templates', slice.type, templateFile);
151
+ const fileName = templateFile.replace(/\.ts\.ejs$/, '.ts');
152
+ const outputPath = path.join(sliceDir, fileName);
153
+
154
+ debugFiles(' Template path: %s', templatePath);
155
+ debugFiles(' Output path: %s', outputPath);
156
+
157
+ const contents = await renderTemplate(templatePath, templateData);
158
+ debugFiles(' Rendered content size: %d bytes', contents.length);
159
+
160
+ debugFiles(' Formatting with Prettier...');
161
+ const prettierConfig = await prettier.resolveConfig(outputPath);
162
+ const formattedContents = await prettier.format(contents, {
163
+ ...prettierConfig,
164
+ parser: 'typescript',
165
+ filepath: outputPath,
166
+ });
167
+ debugFiles(' Formatted content size: %d bytes', formattedContents.length);
168
+
169
+ const plan = { outputPath, contents: formattedContents };
170
+ debugFiles(' File plan created for: %s', fileName);
171
+ return plan;
172
+ }
173
+
174
+ function extractUsedErrors(gwtMapping: Record<string, (GwtCondition & { failingFields?: string[] })[]>): string[] {
175
+ debug('Extracting used errors from GWT mapping');
176
+ const usedErrors = new Set<string>();
177
+
178
+ for (const commandName in gwtMapping) {
179
+ debug(' Processing command: %s', commandName);
180
+ for (const gwt of gwtMapping[commandName]) {
181
+ for (const t of gwt.then) {
182
+ if (typeof t === 'object' && t !== null && 'errorType' in t) {
183
+ debug(' Found error type: %s', t.errorType);
184
+ usedErrors.add(t.errorType);
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ const errors = Array.from(usedErrors);
191
+ debug(' Total unique errors found: %d', errors.length);
192
+ return errors;
193
+ }
194
+
195
+ async function prepareTemplateData(
196
+ slice: Slice,
197
+ flow: Flow,
198
+ commands: Message[],
199
+ events: Message[],
200
+ states: Message[],
201
+ commandSchemasByName: Record<string, Message>,
202
+ projectionIdField: string | undefined,
203
+ allMessages?: MessageDefinition[],
204
+ integrations?: SpecsSchemaType['integrations'],
205
+ ): Promise<Record<string, unknown>> {
206
+ debug('Preparing template data for slice: %s (flow: %s)', slice.name, flow.name);
207
+ debug(' Commands: %d', commands.length);
208
+ debug(' Events: %d', events.length);
209
+ debug(' States: %d', states.length);
210
+ debug(' Messages: %d', allMessages?.length ?? 0);
211
+ const { streamPattern, streamId } = getStreamFromSink(slice);
212
+ debug(' Stream pattern: %s, ID: %s', streamPattern, streamId);
213
+
214
+ const gwtMapping = buildCommandGwtMapping(slice);
215
+ debug(' GWT mapping built with %d commands', Object.keys(gwtMapping).length);
216
+
217
+ const queryGwtMapping = buildQueryGwtMapping(slice);
218
+ debug(' Query GWT mapping built');
219
+
220
+ const usedErrors = extractUsedErrors(gwtMapping);
221
+ debug(' Used errors: %o', usedErrors);
222
+
223
+ const projectionName = extractProjectionName(slice);
224
+ debug(' Projection name: %s', projectionName ?? 'none');
225
+
226
+ const uniqueCommands = Array.from(new Map(commands.map((c) => [c.type, c])).values());
227
+ debug(' Unique commands: %d', uniqueCommands.length);
228
+
229
+ return {
230
+ flowName: flow.name,
231
+ sliceName: slice.name,
232
+ slice,
233
+ stream: { pattern: streamPattern, id: streamId },
234
+ commands: uniqueCommands,
235
+ events,
236
+ states,
237
+ gwtMapping,
238
+ queryGwtMapping,
239
+ usedErrors,
240
+ commandSchemasByName,
241
+ projectionIdField,
242
+ projectionName,
243
+ projectionType: projectionName != null ? pascalCase(projectionName) : undefined,
244
+ parsedRequest: slice.type === 'query' && slice.request != null ? parseGraphQlRequest(slice.request) : undefined,
245
+ messages: allMessages,
246
+ message:
247
+ slice.type === 'query' && allMessages
248
+ ? allMessages.find((m) => m.name === slice.server?.data?.[0]?.target?.name)
249
+ : undefined,
250
+ integrations,
251
+ };
252
+ }
253
+
254
+ function annotateEventSources(
255
+ events: Message[],
256
+ flows: Flow[],
257
+ fallbackFlowName: string,
258
+ fallbackSliceName: string,
259
+ ): void {
260
+ debug('Annotating event sources for %d events', events.length);
261
+ for (const event of events) {
262
+ const match = findEventSource(flows, event.type);
263
+ event.sourceFlowName = match?.flowName ?? fallbackFlowName;
264
+ event.sourceSliceName = match?.sliceName ?? fallbackSliceName;
265
+ debug(' Event %s: flow=%s, slice=%s', event.type, event.sourceFlowName, event.sourceSliceName);
266
+ }
267
+ }
268
+
269
+ function findEventSource(flows: Flow[], eventType: string): { flowName: string; sliceName: string } | null {
270
+ debugSlice('Finding source for event: %s', eventType);
271
+ for (const flow of flows) {
272
+ for (const slice of flow.slices) {
273
+ if (!['command', 'react'].includes(slice.type)) continue;
274
+
275
+ const gwt = slice.server?.gwt ?? [];
276
+ if (gwt.some((g) => g.then.some((t) => 'eventRef' in t && t.eventRef === eventType))) {
277
+ debugSlice(' Found event source in flow: %s, slice: %s', flow.name, slice.name);
278
+ return { flowName: flow.name, sliceName: slice.name };
279
+ }
280
+ }
281
+ }
282
+ debugSlice(' No source found for event: %s', eventType);
283
+ return null;
284
+ }
285
+
286
+ function annotateCommandSources(
287
+ commands: Message[],
288
+ flows: Flow[],
289
+ fallbackFlowName: string,
290
+ fallbackSliceName: string,
291
+ ): void {
292
+ debug('Annotating command sources for %d commands', commands.length);
293
+ for (const command of commands) {
294
+ const match = findCommandSource(flows, command.type);
295
+ command.sourceFlowName = match?.flowName ?? fallbackFlowName;
296
+ command.sourceSliceName = match?.sliceName ?? fallbackSliceName;
297
+ debug(' Command %s: flow=%s, slice=%s', command.type, command.sourceFlowName, command.sourceSliceName);
298
+ }
299
+ }
300
+
301
+ function findCommandSource(flows: Flow[], commandType: string): { flowName: string; sliceName: string } | null {
302
+ debugSlice('Finding source for command: %s', commandType);
303
+ for (const flow of flows) {
304
+ for (const slice of flow.slices) {
305
+ if (slice.type !== 'command') continue;
306
+
307
+ const gwt = slice.server?.gwt ?? [];
308
+ if (gwt.some((g) => 'commandRef' in g.when && g.when.commandRef === commandType)) {
309
+ debugSlice(' Found command source in flow: %s, slice: %s', flow.name, slice.name);
310
+ return { flowName: flow.name, sliceName: slice.name };
311
+ }
312
+ }
313
+ }
314
+ debugSlice(' No source found for command: %s', commandType);
315
+ return null;
316
+ }
317
+
318
+ async function generateFilesForSlice(
319
+ slice: Slice,
320
+ flow: Flow,
321
+ sliceDir: string,
322
+ messages: MessageDefinition[],
323
+ flows: Flow[],
324
+ integrations?: SpecsSchemaType['integrations'],
325
+ ): Promise<FilePlan[]> {
326
+ debugSlice('Generating files for slice: %s (type: %s)', slice.name, slice.type);
327
+ debugSlice(' Flow: %s', flow.name);
328
+ debugSlice(' Output directory: %s', sliceDir);
329
+
330
+ const templates = defaultFilesByType[slice.type];
331
+ if (!templates?.length) {
332
+ debugSlice(' No templates found for slice type: %s', slice.type);
333
+ return [];
334
+ }
335
+ debugSlice(' Found %d templates for slice type', templates.length);
336
+
337
+ const extracted = extractMessagesFromSpecs(slice, messages);
338
+ debugSlice(' Extracted messages:');
339
+ debugSlice(' Commands: %d', extracted.commands.length);
340
+ debugSlice(' Events: %d', extracted.events.length);
341
+ debugSlice(' States: %d', extracted.states.length);
342
+ console.log(
343
+ '💡 Events for slice',
344
+ slice.name,
345
+ extracted.events.map((e) => e.type),
346
+ );
347
+ annotateEventSources(extracted.events, flows, flow.name, slice.name);
348
+ annotateCommandSources(extracted.commands, flows, flow.name, slice.name);
349
+
350
+ const templateData = await prepareTemplateData(
351
+ slice,
352
+ flow,
353
+ extracted.commands,
354
+ extracted.events,
355
+ extracted.states,
356
+ extracted.commandSchemasByName,
357
+ extracted.projectionIdField,
358
+ messages,
359
+ integrations,
360
+ );
361
+
362
+ debugSlice(' Generating %d files from templates', templates.length);
363
+ const plans = await Promise.all(
364
+ templates.map((template) => generateFileForTemplate(template, slice, sliceDir, templateData)),
365
+ );
366
+ debugSlice(' Generated %d file plans for slice: %s', plans.length, slice.name);
367
+ return plans;
368
+ }
369
+
370
+ export async function generateScaffoldFilePlans(
371
+ flows: Flow[],
372
+ messages: SpecsSchemaType['messages'],
373
+ integrations?: SpecsSchemaType['integrations'],
374
+ baseDir = 'src/domain/flows',
375
+ ): Promise<FilePlan[]> {
376
+ debug('Generating scaffold file plans');
377
+ debug(' Number of flows: %d', flows.length);
378
+ debug(' Number of messages: %d', messages?.length ?? 0);
379
+ debug(' Base directory: %s', baseDir);
380
+
381
+ const allPlans: FilePlan[] = [];
382
+
383
+ for (const flow of flows) {
384
+ debugFlow('Processing flow: %s', flow.name);
385
+ const flowDir = ensureDirPath(baseDir, toKebabCase(flow.name));
386
+ debugFlow(' Flow directory: %s', flowDir);
387
+ debugFlow(' Number of slices: %d', flow.slices.length);
388
+
389
+ for (const slice of flow.slices) {
390
+ debugFlow(' Processing slice: %s (type: %s)', slice.name, slice.type);
391
+ const sliceDir = ensureDirPath(flowDir, toKebabCase(slice.name));
392
+ debugFlow(' Slice directory: %s', sliceDir);
393
+ const plans = await generateFilesForSlice(slice, flow, sliceDir, messages, flows, integrations);
394
+ debugFlow(' Generated %d plans for slice', plans.length);
395
+ allPlans.push(...plans);
396
+ }
397
+ debugFlow(' Completed flow: %s', flow.name);
398
+ }
399
+
400
+ debug('Total file plans generated: %d', allPlans.length);
401
+ return allPlans;
402
+ }
403
+
404
+ export async function writeScaffoldFilePlans(plans: FilePlan[]) {
405
+ debugPlan('Writing %d scaffold file plans', plans.length);
406
+ let writtenCount = 0;
407
+
408
+ for (const { outputPath, contents } of plans) {
409
+ debugPlan(' Writing file: %s', outputPath);
410
+ debugPlan(' Content size: %d bytes', contents.length);
411
+
412
+ const dir = path.dirname(outputPath);
413
+ debugPlan(' Ensuring directory exists: %s', dir);
414
+ await ensureDirExists(dir);
415
+
416
+ await fs.writeFile(outputPath, contents, 'utf8');
417
+ writtenCount++;
418
+ debugPlan(' File written successfully (%d/%d)', writtenCount, plans.length);
419
+ console.log(`✅ Created: ${outputPath}`);
420
+ }
421
+
422
+ debugPlan('All %d files written successfully', writtenCount);
423
+ }
424
+
425
+ export async function scaffoldFromSchema(
426
+ flows: Flow[],
427
+ messages: SpecsSchemaType['messages'],
428
+ baseDir = 'src/domain/flows',
429
+ ): Promise<void> {
430
+ debug('Starting scaffold from schema');
431
+ debug(' Flows: %d', flows.length);
432
+ debug(' Messages: %d', messages?.length ?? 0);
433
+ debug(' Base directory: %s', baseDir);
434
+
435
+ const plans = await generateScaffoldFilePlans(flows, messages, undefined, baseDir);
436
+ debug('Generated %d file plans, writing to disk...', plans.length);
437
+
438
+ await writeScaffoldFilePlans(plans);
439
+ debug('Scaffold from schema completed');
440
+ }
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
3
+ import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
4
+
5
+ describe('commands.ts.ejs', () => {
6
+ it('should generate correct command file', async () => {
7
+ const spec: SpecsSchema = {
8
+ variant: 'specs',
9
+ flows: [
10
+ {
11
+ name: 'Host creates a listing',
12
+ slices: [
13
+ {
14
+ type: 'command',
15
+ name: 'Create listing',
16
+ client: {
17
+ description: 'test',
18
+ specs: [],
19
+ },
20
+ server: {
21
+ description: 'test',
22
+ gwt: [
23
+ {
24
+ when: {
25
+ commandRef: 'CreateListing',
26
+ exampleData: {
27
+ propertyId: 'listing_123',
28
+ title: 'nice apartment',
29
+ pricePerNight: 250,
30
+ maxGuests: 4,
31
+ amenities: ['wifi', 'kitchen', 'parking'],
32
+ available: true,
33
+ tags: ['sea view', 'balcony'],
34
+ rating: 4.8,
35
+ metadata: { petsAllowed: true },
36
+ listedAt: '2024-01-15T10:00:00Z',
37
+ },
38
+ },
39
+ then: [],
40
+ },
41
+ ],
42
+ },
43
+ },
44
+ ],
45
+ },
46
+ ],
47
+ messages: [
48
+ {
49
+ type: 'command',
50
+ name: 'CreateListing',
51
+ fields: [
52
+ { name: 'propertyId', type: 'string', required: true },
53
+ { name: 'title', type: 'string', required: true },
54
+ { name: 'pricePerNight', type: 'number', required: true },
55
+ { name: 'maxGuests', type: 'number', required: true },
56
+ { name: 'amenities', type: 'string[]', required: true },
57
+ { name: 'available', type: 'boolean', required: true },
58
+ { name: 'tags', type: 'string[]', required: true },
59
+ { name: 'rating', type: 'number', required: true },
60
+ { name: 'metadata', type: 'object', required: true },
61
+ { name: 'listedAt', type: 'Date', required: true },
62
+ ],
63
+ },
64
+ ],
65
+ };
66
+
67
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
68
+ const commandFile = plans.find((p) => p.outputPath.endsWith('commands.ts'));
69
+
70
+ expect(commandFile?.contents).toMatchInlineSnapshot(`
71
+ "import { Command } from '@event-driven-io/emmett';
72
+ export type CreateListing = Command<
73
+ 'CreateListing',
74
+ {
75
+ propertyId: string;
76
+ title: string;
77
+ pricePerNight: number;
78
+ maxGuests: number;
79
+ amenities: string[];
80
+ available: boolean;
81
+ tags: string[];
82
+ rating: number;
83
+ metadata: object;
84
+ listedAt: Date;
85
+ }
86
+ >;
87
+ "
88
+ `);
89
+ });
90
+ });
@@ -0,0 +1,11 @@
1
+ import { Command } from "@event-driven-io/emmett";
2
+ <% for (const command of commands) { -%>
3
+ export type <%= pascalCase(command.type) %> = Command<
4
+ '<%= command.type %>',
5
+ {
6
+ <% for (const field of command.fields) { -%>
7
+ <%- field.name %>: <%- field.tsType %>;
8
+ <% } -%>
9
+ }
10
+ >;
11
+ <% } -%>