@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,276 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
+ import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
4
+
5
+ describe('projection.specs.ts.ejs', () => {
6
+ it('should generate a valid test spec for a query slice projection', async () => {
7
+ const spec: SpecsSchema = {
8
+ variant: 'specs',
9
+ flows: [
10
+ {
11
+ name: 'listing-flow',
12
+ slices: [
13
+ {
14
+ type: 'command',
15
+ name: 'CreateListing',
16
+ stream: 'listing-${propertyId}',
17
+ client: { description: '', specs: [] },
18
+ server: {
19
+ description: '',
20
+ gwt: [
21
+ {
22
+ when: {
23
+ commandRef: 'CreateListing',
24
+ exampleData: {
25
+ propertyId: 'listing_123',
26
+ title: 'Sea View Flat',
27
+ pricePerNight: 120,
28
+ location: 'Brighton',
29
+ maxGuests: 4,
30
+ },
31
+ },
32
+ then: [
33
+ {
34
+ eventRef: 'ListingCreated',
35
+ exampleData: {
36
+ propertyId: 'listing_123',
37
+ title: 'Sea View Flat',
38
+ pricePerNight: 120,
39
+ location: 'Brighton',
40
+ maxGuests: 4,
41
+ },
42
+ },
43
+ ],
44
+ },
45
+ {
46
+ when: {
47
+ commandRef: 'RemoveListing',
48
+ exampleData: {
49
+ propertyId: 'listing_123',
50
+ },
51
+ },
52
+ then: [
53
+ {
54
+ eventRef: 'ListingRemoved',
55
+ exampleData: {},
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ },
61
+ },
62
+ {
63
+ type: 'query',
64
+ name: 'search-listings',
65
+ stream: 'listings',
66
+ client: { description: '', specs: [] },
67
+ server: {
68
+ description: '',
69
+ data: [
70
+ {
71
+ origin: {
72
+ type: 'projection',
73
+ idField: 'propertyId',
74
+ name: 'AvailablePropertiesProjection',
75
+ },
76
+ target: {
77
+ type: 'State',
78
+ name: 'AvailableListings',
79
+ },
80
+ },
81
+ ],
82
+ gwt: [
83
+ {
84
+ given: [
85
+ {
86
+ eventRef: 'ListingCreated',
87
+ exampleData: {
88
+ propertyId: 'listing_123',
89
+ title: 'Sea View Flat',
90
+ pricePerNight: 120,
91
+ location: 'Brighton',
92
+ maxGuests: 4,
93
+ },
94
+ },
95
+ ],
96
+ then: [
97
+ {
98
+ stateRef: 'AvailableListings',
99
+ exampleData: {
100
+ propertyId: 'listing_123',
101
+ title: 'Sea View Flat',
102
+ pricePerNight: 120,
103
+ location: 'Brighton',
104
+ maxGuests: 4,
105
+ },
106
+ },
107
+ ],
108
+ },
109
+ {
110
+ given: [
111
+ {
112
+ eventRef: 'ListingRemoved',
113
+ exampleData: {
114
+ propertyId: 'listing_123',
115
+ },
116
+ },
117
+ ],
118
+ then: [
119
+ {
120
+ stateRef: 'AvailableListings',
121
+ exampleData: {},
122
+ },
123
+ ],
124
+ },
125
+ ],
126
+ },
127
+ },
128
+ ],
129
+ },
130
+ ],
131
+ messages: [
132
+ {
133
+ type: 'command',
134
+ name: 'CreateListing',
135
+ fields: [
136
+ { name: 'propertyId', type: 'string', required: true },
137
+ { name: 'title', type: 'string', required: true },
138
+ { name: 'pricePerNight', type: 'number', required: true },
139
+ { name: 'location', type: 'string', required: true },
140
+ { name: 'maxGuests', type: 'number', required: true },
141
+ ],
142
+ },
143
+ {
144
+ type: 'command',
145
+ name: 'RemoveListing',
146
+ fields: [{ name: 'propertyId', type: 'string', required: true }],
147
+ },
148
+ {
149
+ type: 'event',
150
+ name: 'ListingCreated',
151
+ source: 'internal',
152
+ fields: [
153
+ { name: 'propertyId', type: 'string', required: true },
154
+ { name: 'title', type: 'string', required: true },
155
+ { name: 'pricePerNight', type: 'number', required: true },
156
+ { name: 'location', type: 'string', required: true },
157
+ { name: 'maxGuests', type: 'number', required: true },
158
+ ],
159
+ },
160
+ {
161
+ type: 'event',
162
+ name: 'ListingRemoved',
163
+ source: 'internal',
164
+ fields: [{ name: 'propertyId', type: 'string', required: true }],
165
+ },
166
+ {
167
+ type: 'state',
168
+ name: 'AvailableListings',
169
+ fields: [
170
+ { name: 'propertyId', type: 'string', required: true },
171
+ { name: 'title', type: 'string', required: true },
172
+ { name: 'pricePerNight', type: 'number', required: true },
173
+ { name: 'location', type: 'string', required: true },
174
+ { name: 'maxGuests', type: 'number', required: true },
175
+ ],
176
+ },
177
+ ],
178
+ };
179
+
180
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
181
+ const specFile = plans.find((p) => p.outputPath.endsWith('projection.spec.ts'));
182
+
183
+ expect(specFile?.contents).toMatchInlineSnapshot(`
184
+ "import { describe, it, beforeEach, expect } from 'vitest';
185
+ import { v4 as uuid } from 'uuid';
186
+ import {
187
+ InMemoryProjectionSpec,
188
+ eventsInStream,
189
+ newEventsInStream
190
+ } from '@event-driven-io/emmett';
191
+ import { projection } from './projection';
192
+
193
+ import type { ListingCreated, ListingRemoved } from '../create-listing/events';
194
+ import type { AvailableListings } from './state';
195
+
196
+ type AllEvents = ListingCreated | ListingRemoved;
197
+
198
+ describe('AvailableListings projection', () => {
199
+ let given: InMemoryProjectionSpec<AllEvents>;
200
+ let propertyId: string;
201
+
202
+ beforeEach(() => {
203
+ propertyId = \`listing-\${uuid()}\`;
204
+ given = InMemoryProjectionSpec.for({ projection });
205
+ });
206
+
207
+ it('handles ListingCreated', () =>
208
+ given([])
209
+ .when([
210
+ {
211
+ type: 'ListingCreated',
212
+ data: {
213
+ propertyId,
214
+ title: 'Sea View Flat',
215
+ pricePerNight: 120,
216
+ location: 'Brighton',
217
+ maxGuests: 4
218
+ },
219
+ metadata: {
220
+ streamName: propertyId,
221
+ streamPosition: 1n,
222
+ globalPosition: 1n
223
+ }
224
+ }
225
+ ])
226
+ .then(async (state) => {
227
+ const document = await state.database
228
+ .collection<AvailableListings>('available-properties-projection')
229
+ .findOne((doc) => doc.propertyId === propertyId);
230
+
231
+ const expected: AvailableListings = {
232
+ propertyId,
233
+ title: 'Sea View Flat',
234
+ pricePerNight: 120,
235
+ location: 'Brighton',
236
+ maxGuests: 4
237
+ };
238
+
239
+ expect(document).toMatchObject(expected);
240
+ }));
241
+
242
+ it('handles ListingRemoved', () =>
243
+ given(
244
+ eventsInStream(propertyId, [
245
+ {
246
+ type: 'ListingCreated',
247
+ data: {
248
+ propertyId,
249
+ title: 'Sea View Flat',
250
+ pricePerNight: 120,
251
+ location: 'Brighton',
252
+ maxGuests: 4
253
+ }
254
+ }
255
+ ])
256
+ )
257
+ .when(
258
+ newEventsInStream(propertyId, [
259
+ {
260
+ type: 'ListingRemoved',
261
+ data: {
262
+ propertyId
263
+ }
264
+ }
265
+ ])
266
+ )
267
+ .then(async (state) => {
268
+ const document = await state.database
269
+ .collection<AvailableListings>('available-properties-projection')
270
+ .findOne((doc) => doc.propertyId === propertyId);
271
+ expect(document).toBeNull();
272
+ }));
273
+ });
274
+ `);
275
+ });
276
+ });
@@ -0,0 +1,348 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
+ import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
4
+
5
+ describe('projection.ts.ejs', () => {
6
+ it('should generate a valid projection file with correct relative event import path from producing slice', async () => {
7
+ const spec: SpecsSchema = {
8
+ variant: 'specs',
9
+ flows: [
10
+ {
11
+ name: 'listing-flow',
12
+ slices: [
13
+ {
14
+ type: 'command',
15
+ name: 'create-listing',
16
+ stream: 'listing-${propertyId}',
17
+ client: {
18
+ description: 'create listing UI',
19
+ specs: [],
20
+ },
21
+ server: {
22
+ description: 'handles create/remove listing',
23
+ gwt: [
24
+ {
25
+ when: {
26
+ commandRef: 'CreateListing',
27
+ exampleData: {
28
+ propertyId: 'listing_123',
29
+ title: 'Sea View Flat',
30
+ pricePerNight: 120,
31
+ location: 'Brighton',
32
+ maxGuests: 4,
33
+ },
34
+ },
35
+ then: [
36
+ {
37
+ eventRef: 'ListingCreated',
38
+ exampleData: {
39
+ propertyId: 'listing_123',
40
+ title: 'Sea View Flat',
41
+ pricePerNight: 120,
42
+ location: 'Brighton',
43
+ maxGuests: 4,
44
+ },
45
+ },
46
+ ],
47
+ },
48
+ {
49
+ when: {
50
+ commandRef: 'RemoveListing',
51
+ exampleData: {
52
+ propertyId: 'listing_123',
53
+ },
54
+ },
55
+ then: [
56
+ {
57
+ eventRef: 'ListingRemoved',
58
+ exampleData: {},
59
+ },
60
+ ],
61
+ },
62
+ ],
63
+ },
64
+ },
65
+ {
66
+ type: 'query',
67
+ name: 'search-listings',
68
+ stream: 'listings',
69
+ client: {
70
+ description: 'search listings UI',
71
+ specs: [],
72
+ },
73
+ server: {
74
+ description: 'projection for available listings',
75
+ data: [
76
+ {
77
+ target: {
78
+ type: 'State',
79
+ name: 'AvailableListings',
80
+ },
81
+ origin: {
82
+ type: 'projection',
83
+ name: 'AvailablePropertiesProjection',
84
+ idField: 'propertyId',
85
+ },
86
+ },
87
+ ],
88
+ gwt: [
89
+ {
90
+ given: [
91
+ {
92
+ eventRef: 'ListingCreated',
93
+ exampleData: {
94
+ propertyId: 'listing_123',
95
+ title: 'Sea View Flat',
96
+ pricePerNight: 120,
97
+ location: 'Brighton',
98
+ maxGuests: 4,
99
+ },
100
+ },
101
+ ],
102
+ then: [
103
+ {
104
+ stateRef: 'AvailableListings',
105
+ exampleData: {
106
+ propertyId: 'listing_123',
107
+ title: 'Sea View Flat',
108
+ pricePerNight: 120,
109
+ location: 'Brighton',
110
+ maxGuests: 4,
111
+ },
112
+ },
113
+ ],
114
+ },
115
+ {
116
+ given: [
117
+ {
118
+ eventRef: 'ListingRemoved',
119
+ exampleData: {
120
+ propertyId: 'listing_123',
121
+ },
122
+ },
123
+ ],
124
+ then: [
125
+ {
126
+ stateRef: 'AvailableListings',
127
+ exampleData: {},
128
+ },
129
+ ],
130
+ },
131
+ ],
132
+ },
133
+ },
134
+ ],
135
+ },
136
+ ],
137
+ messages: [
138
+ {
139
+ type: 'command',
140
+ name: 'CreateListing',
141
+ fields: [
142
+ { name: 'propertyId', type: 'string', required: true },
143
+ { name: 'title', type: 'string', required: true },
144
+ { name: 'pricePerNight', type: 'number', required: true },
145
+ { name: 'location', type: 'string', required: true },
146
+ { name: 'maxGuests', type: 'number', required: true },
147
+ ],
148
+ },
149
+ {
150
+ type: 'command',
151
+ name: 'RemoveListing',
152
+ fields: [{ name: 'propertyId', type: 'string', required: true }],
153
+ },
154
+ {
155
+ type: 'event',
156
+ name: 'ListingCreated',
157
+ source: 'internal',
158
+ fields: [
159
+ { name: 'propertyId', type: 'string', required: true },
160
+ { name: 'title', type: 'string', required: true },
161
+ { name: 'pricePerNight', type: 'number', required: true },
162
+ { name: 'location', type: 'string', required: true },
163
+ { name: 'maxGuests', type: 'number', required: true },
164
+ ],
165
+ },
166
+ {
167
+ type: 'event',
168
+ name: 'ListingRemoved',
169
+ source: 'internal',
170
+ fields: [{ name: 'propertyId', type: 'string', required: true }],
171
+ },
172
+ {
173
+ type: 'state',
174
+ name: 'AvailableListings',
175
+ fields: [
176
+ { name: 'propertyId', type: 'string', required: true },
177
+ { name: 'title', type: 'string', required: true },
178
+ { name: 'pricePerNight', type: 'number', required: true },
179
+ { name: 'location', type: 'string', required: true },
180
+ { name: 'maxGuests', type: 'number', required: true },
181
+ ],
182
+ },
183
+ ],
184
+ };
185
+
186
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
187
+ const projectionFile = plans.find((p) => p.outputPath.endsWith('projection.ts'));
188
+
189
+ expect(projectionFile?.contents).toMatchInlineSnapshot(`
190
+ "import {
191
+ inMemorySingleStreamProjection,
192
+ type ReadEvent,
193
+ type InMemoryReadEventMetadata,
194
+ } from '@event-driven-io/emmett';
195
+ import type { AvailableListings } from './state';
196
+ import type { ListingCreated, ListingRemoved } from '../create-listing/events';
197
+
198
+ type AllEvents = ListingCreated | ListingRemoved;
199
+
200
+ export const projection = inMemorySingleStreamProjection<AvailableListings, AllEvents>({
201
+ collectionName: 'AvailablePropertiesProjection',
202
+ canHandle: ['ListingCreated', 'ListingRemoved'],
203
+ getDocumentId: (event) => event.data.propertyId,
204
+ evolve: (
205
+ document: AvailableListings | null,
206
+ event: ReadEvent<AllEvents, InMemoryReadEventMetadata>,
207
+ ): AvailableListings | null => {
208
+ switch (event.type) {
209
+ case 'ListingCreated': {
210
+ /**
211
+ * ## IMPLEMENTATION INSTRUCTIONS ##
212
+ * This event adds or updates the document.
213
+ * Implement the correct fields as needed for your read model.
214
+ */
215
+ return {
216
+ propertyId: /* TODO: map from event.data */ '',
217
+ title: /* TODO: map from event.data */ '',
218
+ pricePerNight: /* TODO: map from event.data */ 0,
219
+ location: /* TODO: map from event.data */ '',
220
+ maxGuests: /* TODO: map from event.data */ 0,
221
+ };
222
+ }
223
+
224
+ case 'ListingRemoved': {
225
+ /**
226
+ * ## IMPLEMENTATION INSTRUCTIONS ##
227
+ * This event might indicate removal of a AvailableListings.
228
+ *
229
+ * - If the intent is to **remove the document**, return \`null\`.
230
+ * - If the intent is to **soft delete**, consider adding a \`status\` field (e.g., \`status: 'removed'\`).
231
+ * - Ensure consumers of this projection (e.g., UI) handle the chosen approach appropriately.
232
+ */
233
+ return null;
234
+ }
235
+ default:
236
+ return document;
237
+ }
238
+ },
239
+ });
240
+
241
+ export default projection;
242
+ "
243
+ `);
244
+ });
245
+ it('should generate a valid query resolver using ID type', async () => {
246
+ const spec: SpecsSchema = {
247
+ variant: 'specs',
248
+ flows: [
249
+ {
250
+ name: 'wishlist-flow',
251
+ slices: [
252
+ {
253
+ type: 'query',
254
+ name: 'view-wishlist',
255
+ request: `
256
+ query GetWishlist($sessionId: ID!) {
257
+ wishlist(sessionId: $sessionId) {
258
+ sessionId
259
+ items
260
+ }
261
+ }
262
+ `,
263
+ client: {
264
+ description: '',
265
+ specs: [],
266
+ },
267
+ server: {
268
+ description: '',
269
+ data: [
270
+ {
271
+ origin: {
272
+ type: 'projection',
273
+ idField: 'sessionId',
274
+ name: 'WishlistProjection',
275
+ },
276
+ target: {
277
+ type: 'State',
278
+ name: 'Wishlist',
279
+ },
280
+ },
281
+ ],
282
+ gwt: [],
283
+ },
284
+ },
285
+ ],
286
+ },
287
+ ],
288
+ messages: [
289
+ {
290
+ type: 'state',
291
+ name: 'Wishlist',
292
+ fields: [
293
+ { name: 'sessionId', type: 'string', required: true },
294
+ { name: 'items', type: 'string', required: true },
295
+ ],
296
+ },
297
+ ],
298
+ };
299
+
300
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
301
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
302
+
303
+ expect(resolverFile?.contents).toMatchInlineSnapshot(`
304
+ "import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID } from 'type-graphql';
305
+ import { type GraphQLContext, ReadModel } from '../../../shared';
306
+
307
+ @ObjectType()
308
+ export class Wishlist {
309
+ @Field(() => String)
310
+ sessionId!: string;
311
+
312
+ @Field(() => String)
313
+ items!: string;
314
+
315
+ [key: string]: unknown;
316
+ }
317
+
318
+ @Resolver()
319
+ export class ViewWishlistQueryResolver {
320
+ @Query(() => [Wishlist])
321
+ async wishlist(
322
+ @Ctx() ctx: GraphQLContext,
323
+ @Arg('sessionId', () => ID, { nullable: true }) sessionId?: string,
324
+ ): Promise<Wishlist[]> {
325
+ const model = new ReadModel<Wishlist>(ctx.eventStore, 'WishlistProjection');
326
+
327
+ // ## IMPLEMENTATION INSTRUCTIONS ##
328
+ // You can query the projection using the ReadModel API:
329
+ //
330
+ // - model.getAll() — fetch all documents
331
+ // - model.getById(id) — fetch a single document by ID (default key: 'id')
332
+ // - model.find(filterFn) — filter documents using a predicate
333
+ // - model.first(filterFn) — fetch the first document matching a predicate
334
+ //
335
+ // Example below uses \`.find()\` to filter
336
+ // change the logic for the query as needed to meet the requirements for the current slice.
337
+
338
+ return model.find((item) => {
339
+ if (sessionId !== undefined && item.sessionId !== sessionId) return false;
340
+
341
+ return true;
342
+ });
343
+ }
344
+ }
345
+ "
346
+ `);
347
+ });
348
+ });
@@ -0,0 +1,71 @@
1
+ <%_
2
+ const idField = projectionIdField ?? 'id';
3
+ const projectionType = states[0]?.type ?? 'UnknownState';
4
+ const projectionCollection = projectionName ?? 'unknown-collection';
5
+ const testCases = queryGwtMapping ?? [];
6
+ const eventTypes = Array.from(new Set(events.map(e => e.type)));
7
+ _%>
8
+
9
+ import { describe, it, beforeEach, expect } from 'vitest';
10
+ import {
11
+ InMemoryProjectionSpec,
12
+ } from '@event-driven-io/emmett';
13
+ import { projection } from './projection';
14
+ import type { <%= eventTypes.join(', ') %> } from '../<%= toKebabCase(events[0]?.sourceSliceName || 'unknown') %>/events';
15
+ import { <%= projectionType %> } from './state';
16
+
17
+ <% if (eventTypes.length > 0) { -%>
18
+ type ProjectionEvent = <%= eventTypes.join(' | ') %>;
19
+ <% } else { -%>
20
+ type ProjectionEvent = never;
21
+ <% } -%>
22
+
23
+ describe('<%= projectionName %> Projection', () => {
24
+ let given: InMemoryProjectionSpec<ProjectionEvent>;
25
+
26
+ beforeEach(() => {
27
+ given = InMemoryProjectionSpec.for({ projection });
28
+ });
29
+
30
+ <%_ for (const [index, gwt] of testCases.entries()) {
31
+ const isRemoval = gwt.then[0]?.exampleData && Object.keys(gwt.then[0].exampleData).length === 0;
32
+ const expectedIdValue = JSON.stringify(gwt.then[0]?.exampleData?.[idField] ?? 'unknown-id');
33
+ _%>
34
+ it('<%= isRemoval ? 'removes' : 'creates or updates' %> <%= projectionType %> document - case <%= index + 1 %>', () =>
35
+ given([
36
+ <%_ for (const event of gwt.given) { _%>
37
+ {
38
+ type: '<%= event.eventRef %>',
39
+ data: {
40
+ <%_ for (const [key, val] of Object.entries(event.exampleData || {})) { _%>
41
+ <%= key %>: <%- formatTsValue(val, (events.find(e => e.type === event.eventRef)?.fields.find(f => f.name === key)?.tsType || 'string')) %>,
42
+ <%_ } _%>
43
+ },
44
+ metadata: {
45
+ streamName: 'ignored-stream',
46
+ streamPosition: 1n,
47
+ globalPosition: 1n,
48
+ },
49
+ },
50
+ <%_ } _%>
51
+ ])
52
+ .when([])
53
+ .then(async (state) => {
54
+ const document = await state.database
55
+ .collection<<%= projectionType %>>('<%= projectionCollection %>')
56
+ .findOne((doc) => doc.<%= idField %> === <%- expectedIdValue %>);
57
+
58
+ <%_ if (isRemoval) { _%>
59
+ expect(document).toBeNull();
60
+ <%_ } else { _%>
61
+ const expected: <%= projectionType %> = {
62
+ <%_ for (const [key, val] of Object.entries(gwt.then[0].exampleData || {})) { _%>
63
+ <%= key %>: <%- formatTsValue(val, (states[0]?.fields.find(f => f.name === key)?.tsType || 'string')) %>,
64
+ <%_ } _%>
65
+ };
66
+
67
+ expect(document).toMatchObject(expected);
68
+ <%_ } _%>
69
+ }));
70
+ <%_ } _%>
71
+ });