@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,542 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
3
+ import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
4
+
5
+ describe('decide.ts.ejs', () => {
6
+ it('should generate a valid decide file when both command and event exist', 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: 'Some apartment',
29
+ listedAt: '2024-01-15T10:00:00Z',
30
+ rating: 4.8,
31
+ metadata: { foo: 'bar' },
32
+ },
33
+ },
34
+ then: [
35
+ {
36
+ eventRef: 'ListingCreated',
37
+ exampleData: {
38
+ propertyId: 'listing_123',
39
+ listedAt: '2024-01-15T10:00:00Z',
40
+ rating: 4.8,
41
+ metadata: { foo: 'bar' },
42
+ },
43
+ },
44
+ ],
45
+ },
46
+ ],
47
+ },
48
+ },
49
+ ],
50
+ },
51
+ ],
52
+ messages: [
53
+ {
54
+ type: 'command',
55
+ name: 'CreateListing',
56
+ fields: [],
57
+ },
58
+ {
59
+ type: 'event',
60
+ name: 'ListingCreated',
61
+ source: 'internal',
62
+ fields: [],
63
+ },
64
+ ],
65
+ };
66
+
67
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
68
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
69
+
70
+ expect(decideFile?.contents).toMatchInlineSnapshot(`
71
+ "import { IllegalStateError } from '@event-driven-io/emmett';
72
+ import type { State } from './state';
73
+ import type { CreateListing } from './commands';
74
+ import type { ListingCreated } from './events';
75
+
76
+ export const decide = (command: CreateListing, state: State): ListingCreated => {
77
+ switch (command.type) {
78
+ case 'CreateListing': {
79
+ /**
80
+ * ## IMPLEMENTATION INSTRUCTIONS ##
81
+ *
82
+ * This command can directly emit one or more events based on the input.
83
+ *
84
+ * You should:
85
+ * - Validate the command input fields
86
+ * - Inspect the current domain \`state\` to determine if the command is allowed
87
+ * - If invalid, throw one of the following domain errors: \`NotFoundError\`, \`ValidationError\`, or \`IllegalStateError\`
88
+ * - If valid, return one or more events with the correct structure
89
+ *
90
+ * ⚠️ Only read from inputs — never mutate them. \`evolve.ts\` handles state updates.
91
+ */
92
+
93
+ // return {
94
+ // type: 'ListingCreated',
95
+ // data: { ...command.data },
96
+ // } as ListingCreated;
97
+
98
+ throw new IllegalStateError('Not yet implemented: ' + command.type);
99
+ }
100
+ default:
101
+ throw new IllegalStateError('Unexpected command type: ' + command.type);
102
+ }
103
+ };
104
+ "
105
+ `);
106
+ });
107
+
108
+ it('should include implementation instructions when prior events are required', async () => {
109
+ const spec: SpecsSchema = {
110
+ variant: 'specs',
111
+ flows: [
112
+ {
113
+ name: 'Host removes a listing',
114
+ slices: [
115
+ {
116
+ type: 'command',
117
+ name: 'Remove listing',
118
+ client: {
119
+ description: 'test',
120
+ specs: [],
121
+ },
122
+ server: {
123
+ description: 'test',
124
+ gwt: [
125
+ {
126
+ given: [
127
+ {
128
+ eventRef: 'ListingCreated',
129
+ exampleData: {
130
+ propertyId: 'listing_123',
131
+ listedAt: '2024-01-15T10:00:00Z',
132
+ },
133
+ },
134
+ ],
135
+ when: {
136
+ commandRef: 'RemoveListing',
137
+ exampleData: {
138
+ propertyId: 'listing_123',
139
+ },
140
+ },
141
+ then: [
142
+ {
143
+ eventRef: 'ListingRemoved',
144
+ exampleData: {
145
+ propertyId: 'listing_123',
146
+ removedAt: '2024-01-16T12:00:00Z',
147
+ },
148
+ },
149
+ ],
150
+ },
151
+ ],
152
+ },
153
+ },
154
+ ],
155
+ },
156
+ ],
157
+ messages: [
158
+ {
159
+ type: 'command',
160
+ name: 'RemoveListing',
161
+ fields: [{ name: 'propertyId', type: 'string', required: true }],
162
+ },
163
+ {
164
+ type: 'event',
165
+ name: 'ListingCreated',
166
+ source: 'internal',
167
+ fields: [
168
+ { name: 'propertyId', type: 'string', required: true },
169
+ { name: 'listedAt', type: 'Date', required: true },
170
+ ],
171
+ },
172
+ {
173
+ type: 'event',
174
+ name: 'ListingRemoved',
175
+ source: 'internal',
176
+ fields: [
177
+ { name: 'propertyId', type: 'string', required: true },
178
+ { name: 'removedAt', type: 'Date', required: true },
179
+ ],
180
+ },
181
+ ],
182
+ };
183
+
184
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
185
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
186
+
187
+ expect(decideFile?.contents).toMatchInlineSnapshot(`
188
+ "import { IllegalStateError } from '@event-driven-io/emmett';
189
+ import type { State } from './state';
190
+ import type { RemoveListing } from './commands';
191
+ import type { ListingRemoved } from './events';
192
+
193
+ export const decide = (command: RemoveListing, state: State): ListingRemoved => {
194
+ switch (command.type) {
195
+ case 'RemoveListing': {
196
+ /**
197
+ * ## IMPLEMENTATION INSTRUCTIONS ##
198
+ *
199
+ * This command requires evaluating prior state to determine if it can proceed.
200
+ *
201
+ * You should:
202
+ * - Validate the command input fields
203
+ * - Inspect the current domain \`state\` to determine if the command is allowed
204
+ * - If invalid, throw one of the following domain errors: \`NotFoundError\`, \`ValidationError\`, or \`IllegalStateError\`
205
+ * - If valid, return one or more events with the correct structure
206
+ *
207
+ * ⚠️ Only read from inputs — never mutate them. \`evolve.ts\` handles state updates.
208
+ */
209
+
210
+ // return {
211
+ // type: 'ListingRemoved',
212
+ // data: { ...command.data },
213
+ // } as ListingRemoved;
214
+
215
+ throw new IllegalStateError('Not yet implemented: ' + command.type);
216
+ }
217
+ default:
218
+ throw new IllegalStateError('Unexpected command type: ' + command.type);
219
+ }
220
+ };
221
+ "
222
+ `);
223
+ });
224
+
225
+ it('should generate a decide file that handles multiple GWTs including an error', async () => {
226
+ const spec: SpecsSchema = {
227
+ variant: 'specs',
228
+ flows: [
229
+ {
230
+ name: 'Host creates a listing',
231
+ slices: [
232
+ {
233
+ type: 'command',
234
+ name: 'Create listing',
235
+ client: {
236
+ description: 'test',
237
+ specs: [],
238
+ },
239
+ server: {
240
+ description: 'test',
241
+ gwt: [
242
+ {
243
+ when: {
244
+ commandRef: 'CreateListing',
245
+ exampleData: {
246
+ propertyId: 'listing_123',
247
+ title: 'Some Apartment',
248
+ listedAt: '2024-01-15T10:00:00Z',
249
+ rating: 4.8,
250
+ metadata: { foo: 'bar' },
251
+ },
252
+ },
253
+ then: [
254
+ {
255
+ eventRef: 'ListingCreated',
256
+ exampleData: {
257
+ propertyId: 'listing_123',
258
+ listedAt: '2024-01-15T10:00:00Z',
259
+ rating: 4.8,
260
+ metadata: { foo: 'bar' },
261
+ },
262
+ },
263
+ ],
264
+ },
265
+ {
266
+ when: {
267
+ commandRef: 'CreateListing',
268
+ exampleData: {
269
+ propertyId: 'listing_123',
270
+ title: '',
271
+ listedAt: '2024-01-15T10:00:00Z',
272
+ rating: 4.8,
273
+ metadata: {},
274
+ },
275
+ },
276
+ then: [
277
+ {
278
+ errorType: 'ValidationError',
279
+ message: 'Title must not be empty',
280
+ },
281
+ ],
282
+ },
283
+ ],
284
+ },
285
+ },
286
+ ],
287
+ },
288
+ ],
289
+ messages: [
290
+ {
291
+ type: 'command',
292
+ name: 'CreateListing',
293
+ fields: [
294
+ { name: 'propertyId', type: 'string', required: true },
295
+ { name: 'title', type: 'string', required: true },
296
+ { name: 'listedAt', type: 'Date', required: true },
297
+ { name: 'rating', type: 'number', required: true },
298
+ { name: 'metadata', type: 'object', required: true },
299
+ ],
300
+ },
301
+ {
302
+ type: 'event',
303
+ name: 'ListingCreated',
304
+ source: 'internal',
305
+ fields: [
306
+ { name: 'propertyId', type: 'string', required: true },
307
+ { name: 'listedAt', type: 'Date', required: true },
308
+ { name: 'rating', type: 'number', required: true },
309
+ { name: 'metadata', type: 'object', required: true },
310
+ ],
311
+ },
312
+ ],
313
+ };
314
+
315
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
316
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
317
+
318
+ expect(decideFile?.contents).toMatchInlineSnapshot(`
319
+ "import { IllegalStateError, ValidationError } from '@event-driven-io/emmett';
320
+ import type { State } from './state';
321
+ import type { CreateListing } from './commands';
322
+ import type { ListingCreated } from './events';
323
+
324
+ export const decide = (command: CreateListing, state: State): ListingCreated => {
325
+ switch (command.type) {
326
+ case 'CreateListing': {
327
+ /**
328
+ * ## IMPLEMENTATION INSTRUCTIONS ##
329
+ *
330
+ * This command can directly emit one or more events based on the input.
331
+ *
332
+ * You should:
333
+ * - Validate the command input fields
334
+ * - Inspect the current domain \`state\` to determine if the command is allowed
335
+ * - If invalid, throw one of the following domain errors: \`NotFoundError\`, \`ValidationError\`, or \`IllegalStateError\`
336
+ * - If valid, return one or more events with the correct structure
337
+ *
338
+ * ⚠️ Only read from inputs — never mutate them. \`evolve.ts\` handles state updates.
339
+ */
340
+
341
+ if (command.data.title === '') {
342
+ throw new ValidationError('Title must not be empty');
343
+ }
344
+
345
+ // return {
346
+ // type: 'ListingCreated',
347
+ // data: { ...command.data },
348
+ // } as ListingCreated;
349
+
350
+ throw new IllegalStateError('Not yet implemented: ' + command.type);
351
+ }
352
+ default:
353
+ throw new IllegalStateError('Unexpected command type: ' + command.type);
354
+ }
355
+ };
356
+ "
357
+ `);
358
+ });
359
+
360
+ it('should include integration return type and usage in decide function', async () => {
361
+ const spec: SpecsSchema = {
362
+ variant: 'specs',
363
+ flows: [
364
+ {
365
+ name: 'Assistant suggests items',
366
+ slices: [
367
+ {
368
+ type: 'command',
369
+ name: 'Suggest Items',
370
+ stream: 'session-${sessionId}',
371
+ client: {
372
+ description: 'test',
373
+ specs: [],
374
+ },
375
+ server: {
376
+ description: '',
377
+ data: [
378
+ {
379
+ target: {
380
+ type: 'Command',
381
+ name: 'SuggestItems',
382
+ },
383
+ destination: {
384
+ type: 'integration',
385
+ systems: ['AI'],
386
+ message: {
387
+ name: 'DoChat',
388
+ type: 'command',
389
+ },
390
+ },
391
+ _additionalInstructions: 'Ensure systemPrompt includes product catalogue guidance',
392
+ _withState: {
393
+ target: {
394
+ type: 'State',
395
+ name: 'Products',
396
+ },
397
+ origin: {
398
+ type: 'integration',
399
+ systems: ['product-catalog'],
400
+ },
401
+ },
402
+ },
403
+ {
404
+ target: {
405
+ type: 'Event',
406
+ name: 'ItemsSuggested',
407
+ },
408
+ destination: {
409
+ type: 'stream',
410
+ pattern: 'session-${sessionId}',
411
+ },
412
+ },
413
+ ],
414
+ gwt: [
415
+ {
416
+ when: {
417
+ commandRef: 'SuggestItems',
418
+ exampleData: {
419
+ sessionId: 'session-123',
420
+ prompt: 'What should I buy?',
421
+ },
422
+ },
423
+ then: [
424
+ {
425
+ eventRef: 'ItemsSuggested',
426
+ exampleData: {
427
+ sessionId: 'session-123',
428
+ items: [],
429
+ },
430
+ },
431
+ ],
432
+ },
433
+ ],
434
+ },
435
+ },
436
+ ],
437
+ },
438
+ ],
439
+ messages: [
440
+ {
441
+ type: 'command',
442
+ name: 'SuggestItems',
443
+ fields: [
444
+ { name: 'sessionId', type: 'string', required: true },
445
+ { name: 'prompt', type: 'string', required: true },
446
+ ],
447
+ },
448
+ {
449
+ type: 'command',
450
+ name: 'DoChat',
451
+ fields: [
452
+ { name: 'sessionId', type: 'string', required: true },
453
+ { name: 'prompt', type: 'string', required: true },
454
+ { name: 'systemPrompt', type: 'string', required: false },
455
+ ],
456
+ },
457
+ {
458
+ type: 'event',
459
+ name: 'ItemsSuggested',
460
+ source: 'internal',
461
+ fields: [
462
+ { name: 'sessionId', type: 'string', required: true },
463
+ { name: 'items', type: 'Array<object>', required: true },
464
+ ],
465
+ },
466
+ {
467
+ type: 'state',
468
+ name: 'Products',
469
+ fields: [
470
+ {
471
+ name: 'products',
472
+ type: 'Array<{ id: string, name: string }>',
473
+ required: true,
474
+ },
475
+ ],
476
+ },
477
+ ],
478
+ integrations: [
479
+ {
480
+ name: 'AI',
481
+ source: '@auto-engineer/ai-integration',
482
+ description: '',
483
+ },
484
+ {
485
+ name: 'product-catalog',
486
+ source: '@auto-engineer/product-catalogue-integration',
487
+ description: '',
488
+ },
489
+ ],
490
+ };
491
+
492
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, spec.integrations);
493
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
494
+
495
+ expect(decideFile?.contents).toMatchInlineSnapshot(`
496
+ "import { IllegalStateError } from '@event-driven-io/emmett';
497
+ import type { State } from './state';
498
+ import type { SuggestItems } from './commands';
499
+ import type { ItemsSuggested } from './events';
500
+ import type { Products } from '@auto-engineer/product-catalogue-integration';
501
+
502
+ export const decide = (command: SuggestItems, state: State, products?: Products): ItemsSuggested => {
503
+ switch (command.type) {
504
+ case 'SuggestItems': {
505
+ /**
506
+ * ## IMPLEMENTATION INSTRUCTIONS ##
507
+ *
508
+ * This command can directly emit one or more events based on the input.
509
+ *
510
+ * You should:
511
+ * - Validate the command input fields
512
+ * - Inspect the current domain \`state\` to determine if the command is allowed
513
+ * - Use \`products\` (integration result) to enrich or filter the output
514
+ * - If invalid, throw one of the following domain errors: \`NotFoundError\`, \`ValidationError\`, or \`IllegalStateError\`
515
+ * - If valid, return one or more events with the correct structure
516
+ *
517
+ * ⚠️ Only read from inputs — never mutate them. \`evolve.ts\` handles state updates.
518
+ *
519
+ * Integration result shape (Products):
520
+ * products?.data = {
521
+ * products: Array<{
522
+ * id: string;
523
+ * name: string };
524
+ * }>;
525
+ * }
526
+ */
527
+
528
+ // return {
529
+ // type: 'ItemsSuggested',
530
+ // data: { ...command.data },
531
+ // } as ItemsSuggested;
532
+
533
+ throw new IllegalStateError('Not yet implemented: ' + command.type);
534
+ }
535
+ default:
536
+ throw new IllegalStateError('Unexpected command type: ' + command.type);
537
+ }
538
+ };
539
+ "
540
+ `);
541
+ });
542
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, it } from 'vitest';
2
+ import { DeciderSpecification } from '@event-driven-io/emmett';
3
+ import { decide } from './decide';
4
+ import { evolve } from './evolve';
5
+ import { initialState } from './state';
6
+
7
+ describe('<%= flowName %> | <%= sliceName %>', () => {
8
+ const given = DeciderSpecification.for({
9
+ decide,
10
+ evolve,
11
+ initialState,
12
+ });
13
+
14
+ <% for (const commandName in gwtMapping) {
15
+ const cases = gwtMapping[commandName];
16
+ const schema = commandSchemasByName[commandName];
17
+ for (const [i, gwt] of cases.entries()) {
18
+ const example = gwt.when;
19
+ const eventResults = gwt.then.filter(t => 'eventRef' in t);
20
+ const errorResult = gwt.then.find(t => 'errorType' in t);
21
+ const testName = errorResult
22
+ ? `should throw ${errorResult.errorType} when ${gwt.failingFields?.join(', ') || 'invalid input'}`
23
+ : `should emit ${eventResults.map(e => e.eventRef).join(', ')} for valid ${commandName}`;
24
+ %>
25
+ it('<%- testName %>', () => {
26
+ given([
27
+ <%_ if (gwt.given && gwt.given.length) { _%>
28
+ <%- gwt.given.map(g => `{
29
+ type: '${g.eventRef}',
30
+ data: ${formatDataObject(g.exampleData, events.find(e => e.type === g.eventRef))}
31
+ }`).join(',\n ') %>
32
+ <%_ } _%>
33
+ ])
34
+ .when({
35
+ type: '<%= example.commandRef %>',
36
+ data: <%- formatDataObject(example.exampleData, schema) %>,
37
+ metadata: { now: new Date() }
38
+ })
39
+ <% if (errorResult) { %>
40
+ .thenThrows((err) => err instanceof <%= errorResult.errorType %> && err.message === '<%= errorResult.message || '' %>');
41
+ <% } else { %>
42
+ .then([
43
+ <%- eventResults.map(e => `{
44
+ type: '${e.eventRef}',
45
+ data: ${formatDataObject(e.exampleData, events.find(evt => evt.type === e.eventRef))}
46
+ }`).join(',\n ') %>
47
+ ]);
48
+ <% } %>
49
+ });
50
+ <% }} %>
51
+ });
@@ -0,0 +1,107 @@
1
+ import {
2
+ IllegalStateError<% if (usedErrors.includes('ValidationError')) { %>, ValidationError<% } %><% if (usedErrors.includes('NotFoundError')) { %>, NotFoundError<% } %>
3
+ } from '@event-driven-io/emmett';
4
+ import type { State } from './state';
5
+ import type { <%= Object.keys(gwtMapping).map(pascalCase).join(', ') %> } from './commands';
6
+ <%
7
+ const fallbackEvents = Object.values(gwtMapping).flatMap(scenarios =>
8
+ scenarios.flatMap(s => s.then?.filter(t => 'eventRef' in t) || [])
9
+ );
10
+ const uniqueEventTypes = [...new Set(fallbackEvents.map(e => pascalCase(e.eventRef)))];
11
+
12
+ const integrationData = (slice.server?.data ?? []).filter(d => d.destination?.type === 'integration');
13
+ const integration = integrationData[0];
14
+ const integrationReturnType = integration?._withState?.target?.name;
15
+ const integrationReturnSystem = integration?._withState?.origin?.systems?.[0];
16
+ const integrationReturnImportSource = integrations?.find(i => i.name === integrationReturnSystem)?.source;
17
+ const integrationReturnFields = messages?.find(m => m.name === integrationReturnType && m.type === 'state')?.fields ?? [];
18
+
19
+ function formatFieldDocLine(field) {
20
+ const optional = field.required ? '' : '?';
21
+ const name = `${field.name}${optional}`;
22
+ const type = field.type;
23
+
24
+ if (type.startsWith('Array<{') && type.endsWith('}>')) {
25
+ const inner = type.slice(7, -1);
26
+ const entries = inner
27
+ .split(',')
28
+ .map(s => s.trim())
29
+ .map(pair => {
30
+ const [k, v] = pair.split(':').map(p => p.trim());
31
+ return `* ${k}: ${v};`;
32
+ });
33
+ return [`* ${name}: Array<{`, ...entries, `* }>;`];
34
+ }
35
+
36
+ return [`* ${name}: ${type};`];
37
+ }
38
+ -%>
39
+ <% if (uniqueEventTypes.length > 0) { -%>
40
+ import type { <%= uniqueEventTypes.join(', ') %> } from './events';
41
+ <% } -%>
42
+ <% if (integrationReturnType && integrationReturnImportSource) { -%>
43
+ import type { <%= integrationReturnType %> } from '<%= integrationReturnImportSource %>';
44
+ <% } -%>
45
+
46
+ export const decide = (
47
+ command: <%= Object.keys(gwtMapping).map(pascalCase).join(' | ') %>,
48
+ state: State<%= integrationReturnType ? `,\n ${camelCase(integrationReturnType)}?: ${integrationReturnType}` : '' %>
49
+ ): <%= uniqueEventTypes.length === 0
50
+ ? 'never'
51
+ : uniqueEventTypes.length === 1
52
+ ? uniqueEventTypes[0]
53
+ : `(${uniqueEventTypes.join(' | ')}) | (${uniqueEventTypes.join(' | ')})[]` %> => {
54
+ switch (command.type) {
55
+ <% for (const command of Object.keys(gwtMapping)) {
56
+ const scenarios = gwtMapping?.[command] ?? [];
57
+ const hasGivenEvents = scenarios.some(s => s.given?.length > 0);
58
+ const fallbackEvents = scenarios.flatMap(s => s.then.filter(t => 'eventRef' in t));
59
+ const fallbackEventTypes = [...new Set(fallbackEvents.map(e => e.eventRef))];
60
+ -%>
61
+ case '<%= command %>': {
62
+ /**
63
+ * ## IMPLEMENTATION INSTRUCTIONS ##
64
+ *
65
+ * This command <%= hasGivenEvents ? 'requires evaluating prior state to determine if it can proceed' : 'can directly emit one or more events based on the input' %>.
66
+ *
67
+ * You should:
68
+ * - Validate the command input fields
69
+ * - Inspect the current domain `state` to determine if the command is allowed
70
+ <% if (integrationReturnType) { -%>
71
+ * - Use `<%= camelCase(integrationReturnType) %>` (integration result) to enrich or filter the output
72
+ <% } -%>
73
+ * - If invalid, throw one of the following domain errors: `NotFoundError`, `ValidationError`, or `IllegalStateError`
74
+ * - If valid, return one or more events with the correct structure
75
+ *
76
+ * ⚠️ Only read from inputs — never mutate them. `evolve.ts` handles state updates.
77
+ <% if (integrationReturnFields.length > 0) { -%>
78
+ *
79
+ * Integration result shape (<%= integrationReturnType %>):
80
+ * <%= camelCase(integrationReturnType) %>?.data = {
81
+ <%= integrationReturnFields.flatMap(formatFieldDocLine).join('\n') %>
82
+ * }
83
+ <% } -%>
84
+ */
85
+
86
+ <% for (const gwt of scenarios) {
87
+ const error = gwt.then.find(t => 'errorType' in t);
88
+ if (error && gwt.failingFields?.length) {
89
+ const condition = gwt.failingFields.map(field => `command.data.${field} === ''`).join(' || ');
90
+ -%>
91
+ if (<%- condition %>) {
92
+ throw new <%= error.errorType %>('<%- error.message ?? 'Validation failed' %>');
93
+ }
94
+ <% } } -%>
95
+
96
+ // return {
97
+ // type: '<%= fallbackEventTypes[0] ?? 'TODO_EVENT_TYPE' %>',
98
+ // data: { ...command.data },
99
+ // } as <%= fallbackEventTypes[0] ? pascalCase(fallbackEventTypes[0]) : 'TODO_EVENT_TYPE' %>;
100
+
101
+ throw new IllegalStateError('Not yet implemented: ' + command.type);
102
+ }
103
+ <% } -%>
104
+ default:
105
+ throw new IllegalStateError('Unexpected command type: ' + command.type);
106
+ }
107
+ };