@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,121 @@
1
+ import {
2
+ inMemorySingleStreamProjection,
3
+ type ReadEvent,
4
+ type InMemoryReadEventMetadata,
5
+ } from '@event-driven-io/emmett';
6
+ import type { <%= pascalCase(slice.server?.data?.[0]?.target?.name || 'UnknownState') %> } from './state';<%
7
+ if (events.length > 0) {
8
+ const importGroups = new Map();
9
+ for (const event of events) {
10
+ const fromSameFlow = event.sourceFlowName === flowName;
11
+ const basePath = fromSameFlow
12
+ ? `../${toKebabCase(event.sourceSliceName ?? 'unknown')}/events`
13
+ : `../${toKebabCase(event.sourceFlowName ?? 'unknown')}/${toKebabCase(event.sourceSliceName ?? 'unknown')}/events`;
14
+
15
+ if (!importGroups.has(basePath)) {
16
+ importGroups.set(basePath, []);
17
+ }
18
+ importGroups.get(basePath).push(event.type);
19
+ }
20
+
21
+ for (const [importPath, typeNames] of importGroups.entries()) {
22
+ %>
23
+ import type { <%= typeNames.join(', ') %> } from '<%= importPath %>';
24
+ <%
25
+ }
26
+ } -%>
27
+
28
+ type AllEvents = <%= events.length > 0 ? events.map(e => e.type).join(' | ') : 'never' %>;
29
+
30
+ export const projection = inMemorySingleStreamProjection<
31
+ <%= pascalCase(slice.server?.data?.[0]?.target?.name || 'UnknownState') %>,
32
+ AllEvents
33
+ >({
34
+ collectionName: '<%= pascalCase(slice.server?.data?.[0]?.origin?.name || "unknown-collection") %>',
35
+ canHandle: [<%- events.map(e => `'${e.type}'`).join(', ') %>],
36
+ getDocumentId: (event) => event.data.<%= slice.server?.data?.[0]?.origin?.idField ?? 'id' %>,
37
+ evolve: (
38
+ document: <%= pascalCase(slice.server?.data?.[0]?.target?.name || 'UnknownState') %> | null,
39
+ event: ReadEvent<AllEvents, InMemoryReadEventMetadata>
40
+ ): <%= pascalCase(slice.server?.data?.[0]?.target?.name || 'UnknownState') %> | null => {
41
+ switch (event.type) {
42
+ <% for (const event of events) {
43
+ const targetName = slice.server?.data?.[0]?.target?.name;
44
+ const queryGwt = slice.type === 'query'
45
+ ? queryGwtMapping.find(gwt =>
46
+ gwt.given.some(g => g.eventRef === event.type)
47
+ )
48
+ : undefined;
49
+ const example = slice.type === 'query'
50
+ ? queryGwt?.then.find(t => t.stateRef === targetName)?.exampleData
51
+ : gwtMapping[event.type]?.[0]?.then?.[0]?.exampleData;
52
+ const targetDef = messages.find(m => m.name === slice.server?.data?.[0]?.target?.name);
53
+ const targetFields = Object.fromEntries((targetDef?.fields ?? []).map(f => [f.name, f]));
54
+ let usedFields = [];
55
+ let isRemovalEvent = false;
56
+ if (typeof example !== 'undefined') {
57
+ if (example && Object.keys(example).length > 0) {
58
+ usedFields = Object.keys(example);
59
+ } else {
60
+ isRemovalEvent = true;
61
+ }
62
+ } else if (targetFields) {
63
+ usedFields = Object.keys(targetFields);
64
+ }
65
+ const eventNameSuggestsRemoval = event.type.toLowerCase().includes('remove') ||
66
+ event.type.toLowerCase().includes('delete');
67
+ %>
68
+ case '<%= event.type %>': {
69
+ /**
70
+ * ## IMPLEMENTATION INSTRUCTIONS ##
71
+ <% if (isRemovalEvent || eventNameSuggestsRemoval) { -%>
72
+ * This event might indicate removal of a <%= targetName || 'document' %>.
73
+ *
74
+ * - If the intent is to **remove the document**, return `null`.
75
+ * - If the intent is to **soft delete**, consider adding a `status` field (e.g., `status: 'removed'`).
76
+ * - Ensure consumers of this projection (e.g., UI) handle the chosen approach appropriately.
77
+ <% } else { -%>
78
+ * This event adds or updates the document.
79
+ * Implement the correct fields as needed for your read model.
80
+ <% } -%>
81
+ */
82
+ <% if (isRemovalEvent) { -%>
83
+ return null;
84
+ <% } else if (usedFields.length === 0) { -%>
85
+ // No fields specified - returning null
86
+ return null;
87
+ <% } else { -%>
88
+ return {
89
+ <% for (let i = 0; i < usedFields.length; i++) {
90
+ const field = usedFields[i];
91
+ const isLast = i === usedFields.length - 1;
92
+ const def = (targetFields ?? {})[field];
93
+ const type = def?.type ?? 'string';
94
+
95
+ let placeholder = 'undefined';
96
+ if (type === 'string' || type === 'ID') {
97
+ placeholder = "''";
98
+ } else if (type === 'number') {
99
+ placeholder = '0';
100
+ } else if (type === 'boolean') {
101
+ placeholder = 'false';
102
+ } else if (type === 'Date') {
103
+ placeholder = 'new Date()';
104
+ } else if (type.startsWith('Array<')) {
105
+ placeholder = '[]';
106
+ } else {
107
+ placeholder = '{} as any';
108
+ }
109
+ %> <%= field %>: /* TODO: map from event.data */ <%- placeholder %><%= isLast ? '' : ',' %>
110
+ <% } -%>
111
+ };
112
+ <% } -%>
113
+ }
114
+ <% } -%>
115
+ default:
116
+ return document;
117
+ }
118
+ },
119
+ });
120
+
121
+ export default projection;
@@ -0,0 +1,254 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
+ import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
4
+
5
+ describe('query.resolver.ts.ejs', () => {
6
+ it('should generate a valid query resolver from request field', async () => {
7
+ const spec: SpecsSchema = {
8
+ variant: 'specs',
9
+ flows: [
10
+ {
11
+ name: 'listing-flow',
12
+ slices: [
13
+ {
14
+ type: 'query',
15
+ name: 'search-listings',
16
+ request: `
17
+ query SearchProperties($location: String, $maxPrice: Float, $minGuests: Int) {
18
+ searchProperties(location: $location, maxPrice: $maxPrice, minGuests: $minGuests) {
19
+ propertyId
20
+ title
21
+ pricePerNight
22
+ location
23
+ maxGuests
24
+ }
25
+ }
26
+ `,
27
+ client: {
28
+ description: '',
29
+ specs: [],
30
+ },
31
+ server: {
32
+ description: '',
33
+ data: [
34
+ {
35
+ origin: {
36
+ type: 'projection',
37
+ idField: 'propertyId',
38
+ name: 'AvailablePropertiesProjection',
39
+ },
40
+ target: {
41
+ type: 'State',
42
+ name: 'AvailableListings',
43
+ },
44
+ },
45
+ ],
46
+ gwt: [],
47
+ },
48
+ },
49
+ ],
50
+ },
51
+ ],
52
+ messages: [
53
+ {
54
+ type: 'state',
55
+ name: 'AvailableListings',
56
+ fields: [
57
+ { name: 'propertyId', type: 'string', required: true },
58
+ { name: 'title', type: 'string', required: true },
59
+ { name: 'pricePerNight', type: 'number', required: true },
60
+ { name: 'location', type: 'string', required: true },
61
+ { name: 'maxGuests', type: 'number', required: true },
62
+ ],
63
+ },
64
+ ],
65
+ };
66
+
67
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
68
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
69
+
70
+ expect(resolverFile?.contents).toMatchInlineSnapshot(`
71
+ "import { Query, Resolver, Arg, Ctx, ObjectType, Field } from 'type-graphql';
72
+ import { type GraphQLContext, ReadModel } from '../../../shared';
73
+
74
+ @ObjectType()
75
+ export class AvailableListings {
76
+ @Field(() => String)
77
+ propertyId!: string;
78
+
79
+ @Field(() => String)
80
+ title!: string;
81
+
82
+ @Field(() => Number)
83
+ pricePerNight!: number;
84
+
85
+ @Field(() => String)
86
+ location!: string;
87
+
88
+ @Field(() => Number)
89
+ maxGuests!: number;
90
+
91
+ [key: string]: unknown;
92
+ }
93
+
94
+ @Resolver()
95
+ export class SearchListingsQueryResolver {
96
+ @Query(() => [AvailableListings])
97
+ async searchProperties(
98
+ @Ctx() ctx: GraphQLContext,
99
+ @Arg('location', () => String, { nullable: true }) location?: string,
100
+ @Arg('maxPrice', () => Number, { nullable: true }) maxPrice?: number,
101
+ @Arg('minGuests', () => Number, { nullable: true }) minGuests?: number,
102
+ ): Promise<AvailableListings[]> {
103
+ const model = new ReadModel<AvailableListings>(ctx.eventStore, 'AvailablePropertiesProjection');
104
+
105
+ // ## IMPLEMENTATION INSTRUCTIONS ##
106
+ // You can query the projection using the ReadModel API:
107
+ //
108
+ // - model.getAll() — fetch all documents
109
+ // - model.getById(id) — fetch a single document by ID (default key: 'id')
110
+ // - model.find(filterFn) — filter documents using a predicate
111
+ // - model.first(filterFn) — fetch the first document matching a predicate
112
+ //
113
+ // Example below uses \`.find()\` to filter
114
+ // change the logic for the query as needed to meet the requirements for the current slice.
115
+
116
+ return model.find((item) => {
117
+ if (location !== undefined && item.location !== location) return false;
118
+
119
+ if (maxPrice !== undefined && item.maxPrice !== maxPrice) return false;
120
+
121
+ if (minGuests !== undefined && item.minGuests !== minGuests) return false;
122
+
123
+ return true;
124
+ });
125
+ }
126
+ }
127
+ "
128
+ `);
129
+ });
130
+ it('should generate a valid query resolver with array of inline object field', async () => {
131
+ const spec: SpecsSchema = {
132
+ variant: 'specs',
133
+ flows: [
134
+ {
135
+ name: 'assistant-flow',
136
+ slices: [
137
+ {
138
+ type: 'query',
139
+ name: 'views-suggested-items',
140
+ request: `
141
+ query GetSuggestedItems($sessionId: ID!) {
142
+ suggestedItems(sessionId: $sessionId) {
143
+ sessionId
144
+ items {
145
+ productId
146
+ name
147
+ quantity
148
+ reason
149
+ }
150
+ }
151
+ }
152
+ `,
153
+ client: { description: '', specs: [] },
154
+ server: {
155
+ description: '',
156
+ data: [
157
+ {
158
+ origin: {
159
+ type: 'projection',
160
+ idField: 'sessionId',
161
+ name: 'SuggestedItemsProjection',
162
+ },
163
+ target: {
164
+ type: 'State',
165
+ name: 'SuggestedItems',
166
+ },
167
+ },
168
+ ],
169
+ gwt: [],
170
+ },
171
+ },
172
+ ],
173
+ },
174
+ ],
175
+ messages: [
176
+ {
177
+ type: 'state',
178
+ name: 'SuggestedItems',
179
+ fields: [
180
+ { name: 'sessionId', type: 'string', required: true },
181
+ {
182
+ name: 'items',
183
+ type: 'Array<{ productId: string; name: string; quantity: number; reason: string }>',
184
+ required: true,
185
+ },
186
+ ],
187
+ },
188
+ ],
189
+ };
190
+
191
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
192
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
193
+
194
+ expect(resolverFile?.contents).toMatchInlineSnapshot(`
195
+ "import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID } from 'type-graphql';
196
+ import { type GraphQLContext, ReadModel } from '../../../shared';
197
+
198
+ @ObjectType()
199
+ export class SuggestedItems {
200
+ @Field(() => String)
201
+ sessionId!: string;
202
+
203
+ @Field(() => [SuggestedItemsItems])
204
+ items!: SuggestedItemsItems[];
205
+
206
+ [key: string]: unknown;
207
+ }
208
+
209
+ @ObjectType()
210
+ export class SuggestedItemsItems {
211
+ @Field(() => String)
212
+ productId!: string;
213
+
214
+ @Field(() => String)
215
+ name!: string;
216
+
217
+ @Field(() => Number)
218
+ quantity!: number;
219
+
220
+ @Field(() => String)
221
+ reason!: string;
222
+ }
223
+
224
+ @Resolver()
225
+ export class ViewsSuggestedItemsQueryResolver {
226
+ @Query(() => [SuggestedItems])
227
+ async suggestedItems(
228
+ @Ctx() ctx: GraphQLContext,
229
+ @Arg('sessionId', () => ID, { nullable: true }) sessionId?: string,
230
+ ): Promise<SuggestedItems[]> {
231
+ const model = new ReadModel<SuggestedItems>(ctx.eventStore, 'SuggestedItemsProjection');
232
+
233
+ // ## IMPLEMENTATION INSTRUCTIONS ##
234
+ // You can query the projection using the ReadModel API:
235
+ //
236
+ // - model.getAll() — fetch all documents
237
+ // - model.getById(id) — fetch a single document by ID (default key: 'id')
238
+ // - model.find(filterFn) — filter documents using a predicate
239
+ // - model.first(filterFn) — fetch the first document matching a predicate
240
+ //
241
+ // Example below uses \`.find()\` to filter
242
+ // change the logic for the query as needed to meet the requirements for the current slice.
243
+
244
+ return model.find((item) => {
245
+ if (sessionId !== undefined && item.sessionId !== sessionId) return false;
246
+
247
+ return true;
248
+ });
249
+ }
250
+ }
251
+ "
252
+ `);
253
+ });
254
+ });
@@ -0,0 +1,98 @@
1
+ <%
2
+ const target = slice?.server?.data?.[0]?.target;
3
+ const projection = slice?.server?.data?.[0]?.origin;
4
+ const queryName = parsedRequest?.queryName ?? camelCase(sliceName);
5
+ const viewType = target?.name ? pascalCase(target.name) : 'UnknownView';
6
+ const projectionType = projection?.name ? pascalCase(projection.name) : 'UnknownProjection';
7
+ const message = messages?.find(m => m.name === viewType);
8
+ const resolverClassName = `${pascalCase(slice.name)}QueryResolver`;
9
+ const usesID = parsedRequest?.args?.some(arg => graphqlType(arg.tsType) === 'ID');
10
+
11
+ const embeddedTypes = [];
12
+ function getEmbeddedObjectTypeName(parentName, fieldName) {
13
+ return `${parentName}${pascalCase(fieldName)}`;
14
+ }
15
+ %>
16
+ import { Query, Resolver, Arg, Ctx, ObjectType, Field<% if (usesID) { %>, ID<% } %> } from 'type-graphql';
17
+ import { type GraphQLContext, ReadModel } from '../../../shared';
18
+
19
+ @ObjectType()
20
+ export class <%= viewType %> {
21
+ <% if (message?.fields?.length) {
22
+ for (const field of message.fields) {
23
+ const gqlType = graphqlType(field.type ?? 'string');
24
+ const tsType = field.type ?? 'string';
25
+ const isInlineArray = tsType.startsWith('Array<{') || tsType.startsWith('{') || tsType.includes('[]');
26
+ const fieldTypeName = getEmbeddedObjectTypeName(viewType, field.name);
27
+
28
+ if (isInlineArray) {
29
+ embeddedTypes.push({ parentName: viewType, fieldName: field.name, typeName: fieldTypeName, tsType });
30
+ %>
31
+ @Field(() => [<%= fieldTypeName %>])
32
+ <%= field.name %>!: <%= fieldTypeName %>[];
33
+ <% } else { %>
34
+ @Field(() => <%= gqlType %>)
35
+ <%= field.name %>!: <%= tsType %>;
36
+ <% }
37
+ } %>
38
+ [key: string]: unknown;
39
+ <% } else { %>
40
+ [key: string]: unknown;
41
+ <% } %>
42
+ }
43
+
44
+ <% for (const { typeName, tsType } of embeddedTypes) {
45
+ const match = tsType.match(/Array<\{([^}]*)\}>/);
46
+ const rawFields = match?.[1]?.split(/[,;]/) ?? [];
47
+ const parsedFields = rawFields
48
+ .map(f => {
49
+ const [name, type] = f.trim().split(':').map(x => x.trim());
50
+ if (!name || !type) return null;
51
+ return { name, tsType: type, gqlType: graphqlType(type) };
52
+ })
53
+ .filter(Boolean);
54
+ %>
55
+
56
+ @ObjectType()
57
+ export class <%= typeName %> {
58
+ <% for (const f of parsedFields) { %>
59
+ @Field(() => <%= f.gqlType %>)
60
+ <%= f.name %>!: <%= f.tsType %>;
61
+ <% } %>
62
+ }
63
+ <% } %>
64
+
65
+ @Resolver()
66
+ export class <%= resolverClassName %> {
67
+ @Query(() => [<%= viewType %>])
68
+ async <%= queryName %>(
69
+ @Ctx() ctx: GraphQLContext<% if (parsedRequest?.args?.length) { %>,
70
+ <% for (let i = 0; i < parsedRequest.args.length; i++) {
71
+ const arg = parsedRequest.args[i];
72
+ const gqlType = graphqlType(arg.tsType);
73
+ const tsType = arg.tsType === 'ID' ? 'string' : arg.tsType;
74
+ %> @Arg('<%= arg.name %>', () => <%= gqlType %>, { nullable: true }) <%= arg.name %>?: <%= tsType %><%= i < parsedRequest.args.length - 1 ? ',' : '' %>
75
+ <% } } %>
76
+ ): Promise<<%= viewType %>[]> {
77
+ const model = new ReadModel<<%= viewType %>>(ctx.eventStore, '<%= projectionType %>');
78
+
79
+ // ## IMPLEMENTATION INSTRUCTIONS ##
80
+ // You can query the projection using the ReadModel API:
81
+ //
82
+ // - model.getAll() — fetch all documents
83
+ // - model.getById(id) — fetch a single document by ID (default key: 'id')
84
+ // - model.find(filterFn) — filter documents using a predicate
85
+ // - model.first(filterFn) — fetch the first document matching a predicate
86
+ //
87
+ // Example below uses `.find()` to filter
88
+ // change the logic for the query as needed to meet the requirements for the current slice.
89
+
90
+ return model.find((item) => {
91
+ <% if (parsedRequest?.args?.length) {
92
+ for (const arg of parsedRequest.args) { %>
93
+ if (<%= arg.name %> !== undefined && item.<%= arg.name %> !== <%= arg.name %>) return false;
94
+ <% } } %>
95
+ return true;
96
+ });
97
+ }
98
+ }
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
+ import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
4
+
5
+ describe('state.ts.ejs', () => {
6
+ it('should generate a valid state definition file for a query slice with a state message', async () => {
7
+ const spec: SpecsSchema = {
8
+ variant: 'specs',
9
+ flows: [
10
+ {
11
+ name: 'Inventory management',
12
+ slices: [
13
+ {
14
+ type: 'query',
15
+ name: 'Get available items',
16
+ client: {
17
+ description: 'Client view of available items',
18
+ specs: [],
19
+ },
20
+ server: {
21
+ description: 'Projects available items',
22
+ data: [
23
+ {
24
+ origin: {
25
+ type: 'projection',
26
+ name: 'ItemCreated',
27
+ idField: 'id',
28
+ },
29
+ target: {
30
+ type: 'State',
31
+ name: 'AvailableItems',
32
+ },
33
+ },
34
+ ],
35
+ gwt: [],
36
+ },
37
+ },
38
+ ],
39
+ },
40
+ ],
41
+ messages: [
42
+ {
43
+ type: 'state',
44
+ name: 'AvailableItems',
45
+ fields: [
46
+ { name: 'id', type: 'string', required: true },
47
+ { name: 'name', type: 'string', required: true },
48
+ { name: 'price', type: 'number', required: true },
49
+ { name: 'inStock', type: 'boolean', required: true },
50
+ { name: 'addedAt', type: 'Date', required: true },
51
+ ],
52
+ },
53
+ ],
54
+ };
55
+
56
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
57
+ const stateFile = plans.find((p) => p.outputPath.endsWith('state.ts'));
58
+
59
+ expect(stateFile?.contents).toMatchInlineSnapshot(`
60
+ "export type AvailableItems = {
61
+ id: string;
62
+ name: string;
63
+ price: number;
64
+ inStock: boolean;
65
+ addedAt: Date;
66
+ };
67
+ "
68
+ `);
69
+ });
70
+ });
@@ -0,0 +1,7 @@
1
+ <% for (const state of states) { -%>
2
+ export type <%= pascalCase(state.type) %> = {
3
+ <% for (const field of state.fields) { -%>
4
+ <%- field.name %><%= field.required ? '' : '?' %>: <%- field.tsType %>;
5
+ <% } -%>
6
+ };
7
+ <% } -%>