@auto-engineer/server-generator-apollo-emmett 0.8.4 → 0.8.6

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 (245) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +16 -0
  3. package/dist/src/codegen/extract/commands.d.ts.map +1 -0
  4. package/dist/src/codegen/extract/commands.js.map +1 -0
  5. package/dist/src/codegen/extract/data-sink.d.ts.map +1 -0
  6. package/dist/{codegen → src/codegen}/extract/data-sink.js +33 -10
  7. package/dist/src/codegen/extract/data-sink.js.map +1 -0
  8. package/dist/src/codegen/extract/events.d.ts.map +1 -0
  9. package/dist/src/codegen/extract/events.js.map +1 -0
  10. package/dist/src/codegen/extract/fields.d.ts.map +1 -0
  11. package/dist/src/codegen/extract/fields.js.map +1 -0
  12. package/dist/src/codegen/extract/graphql.d.ts.map +1 -0
  13. package/dist/src/codegen/extract/graphql.js.map +1 -0
  14. package/dist/src/codegen/extract/gwt.d.ts.map +1 -0
  15. package/dist/src/codegen/extract/gwt.js.map +1 -0
  16. package/dist/src/codegen/extract/index.d.ts.map +1 -0
  17. package/dist/src/codegen/extract/index.js.map +1 -0
  18. package/dist/src/codegen/extract/messages.d.ts.map +1 -0
  19. package/dist/src/codegen/extract/messages.js.map +1 -0
  20. package/dist/src/codegen/extract/projection.d.ts.map +1 -0
  21. package/dist/src/codegen/extract/projection.js.map +1 -0
  22. package/dist/src/codegen/extract/query.d.ts.map +1 -0
  23. package/dist/src/codegen/extract/query.js.map +1 -0
  24. package/dist/src/codegen/extract/states.d.ts.map +1 -0
  25. package/dist/src/codegen/extract/states.js.map +1 -0
  26. package/dist/src/codegen/scaffoldFromSchema.d.ts +9 -0
  27. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -0
  28. package/dist/src/codegen/scaffoldFromSchema.integration.specs.d.ts.map +1 -0
  29. package/dist/src/codegen/scaffoldFromSchema.integration.specs.js.map +1 -0
  30. package/dist/{codegen → src/codegen}/scaffoldFromSchema.js +54 -18
  31. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -0
  32. package/dist/{codegen → src/codegen}/templates/command/commands.specs.ts +1 -2
  33. package/dist/{codegen → src/codegen}/templates/command/decide.specs.specs.ts +3 -3
  34. package/dist/{codegen → src/codegen}/templates/command/decide.specs.ts +1 -5
  35. package/dist/{codegen → src/codegen}/templates/command/events.specs.ts +1 -2
  36. package/dist/{codegen → src/codegen}/templates/command/evolve.specs.ts +1 -2
  37. package/dist/{codegen → src/codegen}/templates/command/handle.specs.ts +1 -3
  38. package/dist/src/codegen/templates/command/mutation.resolver.specs.ts +363 -0
  39. package/dist/src/codegen/templates/command/mutation.resolver.ts.ejs +99 -0
  40. package/dist/{codegen → src/codegen}/templates/command/register.specs.ts +1 -2
  41. package/dist/{codegen → src/codegen}/templates/command/state.specs.ts +1 -2
  42. package/dist/{codegen → src/codegen}/templates/query/projection.specs.specs..ts +3 -3
  43. package/dist/{codegen → src/codegen}/templates/query/projection.specs.ts +5 -9
  44. package/dist/{codegen → src/codegen}/templates/query/query.resolver.specs.ts +194 -23
  45. package/dist/src/codegen/templates/query/query.resolver.ts.ejs +137 -0
  46. package/dist/{codegen → src/codegen}/templates/query/state.specs.ts +1 -2
  47. package/dist/{codegen → src/codegen}/templates/react/react.specs.specs.ts +2 -2
  48. package/dist/{codegen → src/codegen}/templates/react/react.specs.ts +3 -3
  49. package/dist/{codegen → src/codegen}/templates/react/register.specs.ts +3 -3
  50. package/dist/src/codegen/test-data/specVariant1.d.ts +4 -0
  51. package/dist/src/codegen/test-data/specVariant1.d.ts.map +1 -0
  52. package/dist/{codegen → src/codegen}/test-data/specVariant1.js +4 -2
  53. package/dist/src/codegen/test-data/specVariant1.js.map +1 -0
  54. package/dist/src/codegen/types.d.ts.map +1 -0
  55. package/dist/{codegen → src/codegen}/types.js.map +1 -1
  56. package/dist/src/codegen/utils/path.d.ts.map +1 -0
  57. package/dist/src/codegen/utils/path.js.map +1 -0
  58. package/dist/src/commands/generate-server.d.ts.map +1 -0
  59. package/dist/{commands → src/commands}/generate-server.js +34 -24
  60. package/dist/src/commands/generate-server.js.map +1 -0
  61. package/dist/src/domain/shared/ReadModel.d.ts.map +1 -0
  62. package/dist/src/domain/shared/ReadModel.js.map +1 -0
  63. package/dist/src/domain/shared/index.d.ts.map +1 -0
  64. package/dist/src/domain/shared/index.js.map +1 -0
  65. package/dist/src/domain/shared/reactorSpecification.d.ts.map +1 -0
  66. package/dist/src/domain/shared/reactorSpecification.js.map +1 -0
  67. package/dist/src/domain/shared/sendCommand.d.ts.map +1 -0
  68. package/dist/src/domain/shared/sendCommand.js.map +1 -0
  69. package/dist/src/domain/shared/types.d.ts.map +1 -0
  70. package/dist/src/domain/shared/types.js.map +1 -0
  71. package/dist/src/index.d.ts.map +1 -0
  72. package/dist/src/index.js.map +1 -0
  73. package/dist/src/server.d.ts.map +1 -0
  74. package/dist/src/server.js.map +1 -0
  75. package/dist/src/utils/index.d.ts.map +1 -0
  76. package/dist/src/utils/index.js.map +1 -0
  77. package/dist/src/utils/loadProjections.d.ts.map +1 -0
  78. package/dist/src/utils/loadProjections.js.map +1 -0
  79. package/dist/src/utils/loadRegisterFiles.d.ts.map +1 -0
  80. package/dist/src/utils/loadRegisterFiles.js.map +1 -0
  81. package/dist/src/utils/loadResolvers.d.ts.map +1 -0
  82. package/dist/src/utils/loadResolvers.js.map +1 -0
  83. package/dist/tsconfig.tsbuildinfo +1 -0
  84. package/package.json +10 -10
  85. package/src/codegen/extract/data-sink.ts +49 -23
  86. package/src/codegen/scaffoldFromSchema.ts +52 -20
  87. package/src/codegen/templates/command/commands.specs.ts +1 -2
  88. package/src/codegen/templates/command/decide.specs.specs.ts +3 -3
  89. package/src/codegen/templates/command/decide.specs.ts +1 -5
  90. package/src/codegen/templates/command/events.specs.ts +1 -2
  91. package/src/codegen/templates/command/evolve.specs.ts +1 -2
  92. package/src/codegen/templates/command/handle.specs.ts +1 -3
  93. package/src/codegen/templates/command/mutation.resolver.specs.ts +247 -8
  94. package/src/codegen/templates/command/mutation.resolver.ts.ejs +86 -12
  95. package/src/codegen/templates/command/register.specs.ts +1 -2
  96. package/src/codegen/templates/command/state.specs.ts +1 -2
  97. package/src/codegen/templates/query/projection.specs.specs..ts +3 -3
  98. package/src/codegen/templates/query/projection.specs.ts +5 -9
  99. package/src/codegen/templates/query/query.resolver.specs.ts +194 -23
  100. package/src/codegen/templates/query/query.resolver.ts.ejs +84 -45
  101. package/src/codegen/templates/query/state.specs.ts +1 -2
  102. package/src/codegen/templates/react/react.specs.specs.ts +2 -2
  103. package/src/codegen/templates/react/react.specs.ts +3 -3
  104. package/src/codegen/templates/react/register.specs.ts +3 -3
  105. package/src/codegen/test-data/specVariant1.ts +5 -3
  106. package/src/commands/generate-server.ts +38 -30
  107. package/tsconfig.json +3 -2
  108. package/tsconfig.test.json +9 -0
  109. package/.turbo/turbo-test.log +0 -14
  110. package/.turbo/turbo-type-check.log +0 -4
  111. package/dist/codegen/extract/commands.d.ts.map +0 -1
  112. package/dist/codegen/extract/commands.js.map +0 -1
  113. package/dist/codegen/extract/data-sink.d.ts.map +0 -1
  114. package/dist/codegen/extract/data-sink.js.map +0 -1
  115. package/dist/codegen/extract/events.d.ts.map +0 -1
  116. package/dist/codegen/extract/events.js.map +0 -1
  117. package/dist/codegen/extract/fields.d.ts.map +0 -1
  118. package/dist/codegen/extract/fields.js.map +0 -1
  119. package/dist/codegen/extract/graphql.d.ts.map +0 -1
  120. package/dist/codegen/extract/graphql.js.map +0 -1
  121. package/dist/codegen/extract/gwt.d.ts.map +0 -1
  122. package/dist/codegen/extract/gwt.js.map +0 -1
  123. package/dist/codegen/extract/index.d.ts.map +0 -1
  124. package/dist/codegen/extract/index.js.map +0 -1
  125. package/dist/codegen/extract/messages.d.ts.map +0 -1
  126. package/dist/codegen/extract/messages.js.map +0 -1
  127. package/dist/codegen/extract/projection.d.ts.map +0 -1
  128. package/dist/codegen/extract/projection.js.map +0 -1
  129. package/dist/codegen/extract/query.d.ts.map +0 -1
  130. package/dist/codegen/extract/query.js.map +0 -1
  131. package/dist/codegen/extract/states.d.ts.map +0 -1
  132. package/dist/codegen/extract/states.js.map +0 -1
  133. package/dist/codegen/scaffoldFromSchema.d.ts +0 -9
  134. package/dist/codegen/scaffoldFromSchema.d.ts.map +0 -1
  135. package/dist/codegen/scaffoldFromSchema.integration.specs.d.ts.map +0 -1
  136. package/dist/codegen/scaffoldFromSchema.integration.specs.js.map +0 -1
  137. package/dist/codegen/scaffoldFromSchema.js.map +0 -1
  138. package/dist/codegen/templates/command/mutation.resolver.specs.ts +0 -124
  139. package/dist/codegen/templates/command/mutation.resolver.ts.ejs +0 -25
  140. package/dist/codegen/templates/query/query.resolver.ts.ejs +0 -98
  141. package/dist/codegen/test-data/specVariant1.d.ts +0 -4
  142. package/dist/codegen/test-data/specVariant1.d.ts.map +0 -1
  143. package/dist/codegen/test-data/specVariant1.js.map +0 -1
  144. package/dist/codegen/types.d.ts.map +0 -1
  145. package/dist/codegen/utils/path.d.ts.map +0 -1
  146. package/dist/codegen/utils/path.js.map +0 -1
  147. package/dist/commands/generate-server.d.ts.map +0 -1
  148. package/dist/commands/generate-server.js.map +0 -1
  149. package/dist/domain/shared/ReadModel.d.ts.map +0 -1
  150. package/dist/domain/shared/ReadModel.js.map +0 -1
  151. package/dist/domain/shared/index.d.ts.map +0 -1
  152. package/dist/domain/shared/index.js.map +0 -1
  153. package/dist/domain/shared/reactorSpecification.d.ts.map +0 -1
  154. package/dist/domain/shared/reactorSpecification.js.map +0 -1
  155. package/dist/domain/shared/sendCommand.d.ts.map +0 -1
  156. package/dist/domain/shared/sendCommand.js.map +0 -1
  157. package/dist/domain/shared/types.d.ts.map +0 -1
  158. package/dist/domain/shared/types.js.map +0 -1
  159. package/dist/index.d.ts.map +0 -1
  160. package/dist/index.js.map +0 -1
  161. package/dist/server.d.ts.map +0 -1
  162. package/dist/server.js.map +0 -1
  163. package/dist/utils/index.d.ts.map +0 -1
  164. package/dist/utils/index.js.map +0 -1
  165. package/dist/utils/loadProjections.d.ts.map +0 -1
  166. package/dist/utils/loadProjections.js.map +0 -1
  167. package/dist/utils/loadRegisterFiles.d.ts.map +0 -1
  168. package/dist/utils/loadRegisterFiles.js.map +0 -1
  169. package/dist/utils/loadResolvers.d.ts.map +0 -1
  170. package/dist/utils/loadResolvers.js.map +0 -1
  171. package/tsconfig.tsbuildinfo +0 -1
  172. /package/dist/{codegen → src/codegen}/extract/commands.d.ts +0 -0
  173. /package/dist/{codegen → src/codegen}/extract/commands.js +0 -0
  174. /package/dist/{codegen → src/codegen}/extract/data-sink.d.ts +0 -0
  175. /package/dist/{codegen → src/codegen}/extract/events.d.ts +0 -0
  176. /package/dist/{codegen → src/codegen}/extract/events.js +0 -0
  177. /package/dist/{codegen → src/codegen}/extract/fields.d.ts +0 -0
  178. /package/dist/{codegen → src/codegen}/extract/fields.js +0 -0
  179. /package/dist/{codegen → src/codegen}/extract/graphql.d.ts +0 -0
  180. /package/dist/{codegen → src/codegen}/extract/graphql.js +0 -0
  181. /package/dist/{codegen → src/codegen}/extract/gwt.d.ts +0 -0
  182. /package/dist/{codegen → src/codegen}/extract/gwt.js +0 -0
  183. /package/dist/{codegen → src/codegen}/extract/index.d.ts +0 -0
  184. /package/dist/{codegen → src/codegen}/extract/index.js +0 -0
  185. /package/dist/{codegen → src/codegen}/extract/messages.d.ts +0 -0
  186. /package/dist/{codegen → src/codegen}/extract/messages.js +0 -0
  187. /package/dist/{codegen → src/codegen}/extract/projection.d.ts +0 -0
  188. /package/dist/{codegen → src/codegen}/extract/projection.js +0 -0
  189. /package/dist/{codegen → src/codegen}/extract/query.d.ts +0 -0
  190. /package/dist/{codegen → src/codegen}/extract/query.js +0 -0
  191. /package/dist/{codegen → src/codegen}/extract/states.d.ts +0 -0
  192. /package/dist/{codegen → src/codegen}/extract/states.js +0 -0
  193. /package/dist/{codegen → src/codegen}/scaffoldFromSchema.integration.specs.d.ts +0 -0
  194. /package/dist/{codegen → src/codegen}/scaffoldFromSchema.integration.specs.js +0 -0
  195. /package/dist/{codegen → src/codegen}/templates/command/commands.ts.ejs +0 -0
  196. /package/dist/{codegen → src/codegen}/templates/command/decide.specs.ts.ejs +0 -0
  197. /package/dist/{codegen → src/codegen}/templates/command/decide.ts.ejs +0 -0
  198. /package/dist/{codegen → src/codegen}/templates/command/events.ts.ejs +0 -0
  199. /package/dist/{codegen → src/codegen}/templates/command/evolve.ts.ejs +0 -0
  200. /package/dist/{codegen → src/codegen}/templates/command/handle.ts.ejs +0 -0
  201. /package/dist/{codegen → src/codegen}/templates/command/register.ts.ejs +0 -0
  202. /package/dist/{codegen → src/codegen}/templates/command/state.ts.ejs +0 -0
  203. /package/dist/{codegen → src/codegen}/templates/query/projection.specs.ts.ejs +0 -0
  204. /package/dist/{codegen → src/codegen}/templates/query/projection.ts.ejs +0 -0
  205. /package/dist/{codegen → src/codegen}/templates/query/state.ts.ejs +0 -0
  206. /package/dist/{codegen → src/codegen}/templates/react/react.specs.ts.ejs +0 -0
  207. /package/dist/{codegen → src/codegen}/templates/react/react.ts.ejs +0 -0
  208. /package/dist/{codegen → src/codegen}/templates/react/register.ts.ejs +0 -0
  209. /package/dist/{codegen → src/codegen}/types.d.ts +0 -0
  210. /package/dist/{codegen → src/codegen}/types.js +0 -0
  211. /package/dist/{codegen → src/codegen}/utils/path.d.ts +0 -0
  212. /package/dist/{codegen → src/codegen}/utils/path.js +0 -0
  213. /package/dist/{commands → src/commands}/generate-server.d.ts +0 -0
  214. /package/dist/{domain → src/domain}/shared/ReadModel.d.ts +0 -0
  215. /package/dist/{domain → src/domain}/shared/ReadModel.js +0 -0
  216. /package/dist/{domain → src/domain}/shared/ReadModel.ts +0 -0
  217. /package/dist/{domain → src/domain}/shared/index.d.ts +0 -0
  218. /package/dist/{domain → src/domain}/shared/index.js +0 -0
  219. /package/dist/{domain → src/domain}/shared/index.ts +0 -0
  220. /package/dist/{domain → src/domain}/shared/reactorSpecification.d.ts +0 -0
  221. /package/dist/{domain → src/domain}/shared/reactorSpecification.js +0 -0
  222. /package/dist/{domain → src/domain}/shared/reactorSpecification.ts +0 -0
  223. /package/dist/{domain → src/domain}/shared/sendCommand.d.ts +0 -0
  224. /package/dist/{domain → src/domain}/shared/sendCommand.js +0 -0
  225. /package/dist/{domain → src/domain}/shared/sendCommand.ts +0 -0
  226. /package/dist/{domain → src/domain}/shared/types.d.ts +0 -0
  227. /package/dist/{domain → src/domain}/shared/types.js +0 -0
  228. /package/dist/{domain → src/domain}/shared/types.ts +0 -0
  229. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  230. /package/dist/{index.js → src/index.js} +0 -0
  231. /package/dist/{server.d.ts → src/server.d.ts} +0 -0
  232. /package/dist/{server.js → src/server.js} +0 -0
  233. /package/dist/{server.ts → src/server.ts} +0 -0
  234. /package/dist/{utils → src/utils}/index.d.ts +0 -0
  235. /package/dist/{utils → src/utils}/index.js +0 -0
  236. /package/dist/{utils → src/utils}/index.ts +0 -0
  237. /package/dist/{utils → src/utils}/loadProjections.d.ts +0 -0
  238. /package/dist/{utils → src/utils}/loadProjections.js +0 -0
  239. /package/dist/{utils → src/utils}/loadProjections.ts +0 -0
  240. /package/dist/{utils → src/utils}/loadRegisterFiles.d.ts +0 -0
  241. /package/dist/{utils → src/utils}/loadRegisterFiles.js +0 -0
  242. /package/dist/{utils → src/utils}/loadRegisterFiles.ts +0 -0
  243. /package/dist/{utils → src/utils}/loadResolvers.d.ts +0 -0
  244. /package/dist/{utils → src/utils}/loadResolvers.js +0 -0
  245. /package/dist/{utils → src/utils}/loadResolvers.ts +0 -0
@@ -0,0 +1,363 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
+ import { Model as SpecsSchema } from '@auto-engineer/flow';
4
+
5
+ describe('mutation.resolver.ts.ejs', () => {
6
+ it('should generate a valid mutation resolver file', async () => {
7
+ const spec: SpecsSchema = {
8
+ variant: 'specs',
9
+ flows: [
10
+ {
11
+ name: 'Host creates a listing',
12
+ slices: [
13
+ {
14
+ type: 'command',
15
+ name: 'Create listing',
16
+ client: {
17
+ description: 'A form that allows users to add a new listing',
18
+ },
19
+ server: {
20
+ description: 'Handles listing creation',
21
+ specs: {
22
+ name: 'Create listing command',
23
+ rules: [
24
+ {
25
+ description: 'Should create listing successfully',
26
+ examples: [
27
+ {
28
+ description: 'User creates listing with valid data',
29
+ when: {
30
+ commandRef: 'CreateListing',
31
+ exampleData: {
32
+ propertyId: 'listing_123',
33
+ title: 'Modern Downtown Apartment',
34
+ pricePerNight: 250,
35
+ maxGuests: 4,
36
+ amenities: ['wifi', 'kitchen'],
37
+ available: true,
38
+ tags: ['sea view', 'balcony'],
39
+ rating: 4.8,
40
+ metadata: { petsAllowed: true },
41
+ listedAt: '2024-01-15T10:00:00Z',
42
+ },
43
+ },
44
+ then: [],
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ },
50
+ },
51
+ },
52
+ ],
53
+ },
54
+ ],
55
+ messages: [
56
+ {
57
+ type: 'command',
58
+ name: 'CreateListing',
59
+ fields: [
60
+ { name: 'propertyId', type: 'string', required: true },
61
+ { name: 'title', type: 'string', required: true },
62
+ { name: 'pricePerNight', type: 'number', required: true },
63
+ { name: 'maxGuests', type: 'number', required: true },
64
+ { name: 'amenities', type: 'string[]', required: true },
65
+ { name: 'available', type: 'boolean', required: true },
66
+ { name: 'tags', type: 'string[]', required: true },
67
+ { name: 'rating', type: 'number', required: true },
68
+ { name: 'metadata', type: 'object', required: true },
69
+ { name: 'listedAt', type: 'Date', required: true },
70
+ ],
71
+ },
72
+ ],
73
+ };
74
+
75
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
76
+ const mutationFile = plans.find((p) => p.outputPath.endsWith('mutation.resolver.ts'));
77
+
78
+ expect(mutationFile?.contents).toMatchInlineSnapshot(`
79
+ "import { Mutation, Resolver, Arg, Ctx, Field, InputType, GraphQLISODateTime } from 'type-graphql';
80
+ import { GraphQLJSON } from 'graphql-type-json';
81
+ import { type GraphQLContext, sendCommand, MutationResponse } from '../../../shared';
82
+
83
+ @InputType()
84
+ export class CreateListingInput {
85
+ @Field(() => String)
86
+ propertyId!: string;
87
+
88
+ @Field(() => String)
89
+ title!: string;
90
+
91
+ @Field(() => Float)
92
+ pricePerNight!: number;
93
+
94
+ @Field(() => Float)
95
+ maxGuests!: number;
96
+
97
+ @Field(() => [String])
98
+ amenities!: string[];
99
+
100
+ @Field(() => Boolean)
101
+ available!: boolean;
102
+
103
+ @Field(() => [String])
104
+ tags!: string[];
105
+
106
+ @Field(() => Float)
107
+ rating!: number;
108
+
109
+ @Field(() => JSON)
110
+ metadata!: object;
111
+
112
+ @Field(() => GraphQLISODateTime)
113
+ listedAt!: Date;
114
+ }
115
+
116
+ @Resolver()
117
+ export class CreateListingResolver {
118
+ @Mutation(() => MutationResponse)
119
+ async createListing(
120
+ @Arg('input', () => CreateListingInput) input: CreateListingInput,
121
+ @Ctx() ctx: GraphQLContext,
122
+ ): Promise<MutationResponse> {
123
+ return await sendCommand(ctx.messageBus, {
124
+ type: 'CreateListing',
125
+ kind: 'Command',
126
+ data: { ...input },
127
+ });
128
+ }
129
+ }
130
+ "
131
+ `);
132
+ });
133
+
134
+ it('should generate the mutation resolver for AnswerQuestion', async () => {
135
+ const spec: SpecsSchema = {
136
+ variant: 'specs',
137
+ flows: [
138
+ {
139
+ name: 'Questionnaires',
140
+ slices: [
141
+ {
142
+ name: 'submits a questionnaire answer',
143
+ type: 'command',
144
+ client: {
145
+ description: '',
146
+ specs: {
147
+ name: '',
148
+ rules: [
149
+ 'display a success message when the answer is submitted',
150
+ 'display an error message when the answer submission is rejected',
151
+ ],
152
+ },
153
+ },
154
+ request:
155
+ 'mutation AnswerQuestion($input: AnswerQuestionInput!) {\\n answerQuestion(input: $input) {\\n success\\n }\\n}',
156
+ server: {
157
+ description: '',
158
+ data: [
159
+ {
160
+ target: { type: 'Event', name: 'QuestionAnswered' },
161
+ destination: { type: 'stream', pattern: 'questionnaire-participantId' },
162
+ },
163
+ {
164
+ target: { type: 'Event', name: 'QuestionnaireEditRejected' },
165
+ destination: { type: 'stream', pattern: 'questionnaire-participantId' },
166
+ },
167
+ ],
168
+ specs: {
169
+ name: '',
170
+ rules: [
171
+ {
172
+ description: 'answers are allowed while the questionnaire has not been submitted',
173
+ examples: [
174
+ {
175
+ description: 'no questions have been answered yet',
176
+ when: {
177
+ commandRef: 'AnswerQuestion',
178
+ exampleData: {
179
+ questionnaireId: 'q-001',
180
+ participantId: 'participant-abc',
181
+ questionId: 'q1',
182
+ answer: 'Yes',
183
+ },
184
+ },
185
+ then: [
186
+ {
187
+ eventRef: 'QuestionAnswered',
188
+ exampleData: {
189
+ questionnaireId: 'q-001',
190
+ participantId: 'participant-abc',
191
+ questionId: 'q1',
192
+ answer: 'Yes',
193
+ savedAt: '2030-01-01T09:05:00.000Z',
194
+ },
195
+ },
196
+ ],
197
+ },
198
+ ],
199
+ },
200
+ ],
201
+ },
202
+ },
203
+ },
204
+ ],
205
+ },
206
+ ],
207
+ messages: [
208
+ {
209
+ type: 'command',
210
+ name: 'AnswerQuestion',
211
+ fields: [
212
+ { name: 'questionnaireId', type: 'string', required: true },
213
+ { name: 'participantId', type: 'string', required: true },
214
+ { name: 'questionId', type: 'string', required: true },
215
+ { name: 'answer', type: 'unknown', required: true },
216
+ ],
217
+ metadata: { version: 1 },
218
+ },
219
+ ],
220
+ integrations: [],
221
+ };
222
+
223
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
224
+ const mutationFile = plans.find(
225
+ (p) =>
226
+ p.outputPath.endsWith('mutation.resolver.ts') && p.contents.includes('export class AnswerQuestionResolver'),
227
+ );
228
+
229
+ expect(mutationFile?.contents).toMatchInlineSnapshot(`
230
+ "import { Mutation, Resolver, Arg, Ctx, Field, InputType } from 'type-graphql';
231
+ import { GraphQLJSON } from 'graphql-type-json';
232
+ import { type GraphQLContext, sendCommand, MutationResponse } from '../../../shared';
233
+
234
+ @InputType()
235
+ export class AnswerQuestionInput {
236
+ @Field(() => String)
237
+ questionnaireId!: string;
238
+
239
+ @Field(() => String)
240
+ participantId!: string;
241
+
242
+ @Field(() => String)
243
+ questionId!: string;
244
+
245
+ @Field(() => GraphQLJSON)
246
+ answer!: unknown;
247
+ }
248
+
249
+ @Resolver()
250
+ export class AnswerQuestionResolver {
251
+ @Mutation(() => MutationResponse)
252
+ async answerQuestion(
253
+ @Arg('input', () => AnswerQuestionInput) input: AnswerQuestionInput,
254
+ @Ctx() ctx: GraphQLContext,
255
+ ): Promise<MutationResponse> {
256
+ return await sendCommand(ctx.messageBus, {
257
+ type: 'AnswerQuestion',
258
+ kind: 'Command',
259
+ data: { ...input },
260
+ });
261
+ }
262
+ }
263
+ "
264
+ `);
265
+ });
266
+
267
+ it('generates nested input types for inline object arrays in a mutation', async () => {
268
+ const spec: SpecsSchema = {
269
+ variant: 'specs',
270
+ flows: [
271
+ {
272
+ name: 'Cart',
273
+ slices: [
274
+ {
275
+ type: 'command',
276
+ name: 'Add items to cart',
277
+ client: { description: '' },
278
+ server: {
279
+ description: '',
280
+ specs: {
281
+ name: '',
282
+ rules: [
283
+ {
284
+ description: 'add items',
285
+ examples: [
286
+ {
287
+ description: 'happy path',
288
+ when: {
289
+ commandRef: 'AddItemsToCart',
290
+ exampleData: {
291
+ sessionId: 's-1',
292
+ items: [{ productId: 'p1', quantity: 2 }],
293
+ },
294
+ },
295
+ then: [],
296
+ },
297
+ ],
298
+ },
299
+ ],
300
+ },
301
+ },
302
+ },
303
+ ],
304
+ },
305
+ ],
306
+ messages: [
307
+ {
308
+ type: 'command',
309
+ name: 'AddItemsToCart',
310
+ fields: [
311
+ { name: 'sessionId', type: 'string', required: true },
312
+ { name: 'items', type: 'Array<{ productId: string; quantity: number }>', required: true },
313
+ ],
314
+ },
315
+ ],
316
+ };
317
+
318
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
319
+ const mutationFile = plans.find(
320
+ (p) =>
321
+ p.outputPath.endsWith('mutation.resolver.ts') && p.contents.includes('export class AddItemsToCartResolver'),
322
+ );
323
+
324
+ expect(mutationFile?.contents).toMatchInlineSnapshot(`
325
+ "import { Mutation, Resolver, Arg, Ctx, Field, InputType } from 'type-graphql';
326
+ import { type GraphQLContext, sendCommand, MutationResponse } from '../../../shared';
327
+
328
+ @InputType()
329
+ export class AddItemsToCartItemsInput {
330
+ @Field(() => String)
331
+ productId!: string;
332
+
333
+ @Field(() => Float)
334
+ quantity!: number;
335
+ }
336
+
337
+ @InputType()
338
+ export class AddItemsToCartInput {
339
+ @Field(() => String)
340
+ sessionId!: string;
341
+
342
+ @Field(() => [AddItemsToCartItemsInput])
343
+ items!: AddItemsToCartItemsInput[];
344
+ }
345
+
346
+ @Resolver()
347
+ export class AddItemsToCartResolver {
348
+ @Mutation(() => MutationResponse)
349
+ async addItemsToCart(
350
+ @Arg('input', () => AddItemsToCartInput) input: AddItemsToCartInput,
351
+ @Ctx() ctx: GraphQLContext,
352
+ ): Promise<MutationResponse> {
353
+ return await sendCommand(ctx.messageBus, {
354
+ type: 'AddItemsToCart',
355
+ kind: 'Command',
356
+ data: { ...input },
357
+ });
358
+ }
359
+ }
360
+ "
361
+ `);
362
+ });
363
+ });
@@ -0,0 +1,99 @@
1
+ <%
2
+ function isInlineObject(ts) {
3
+ return /^\{[\s\S]*\}$/.test((ts ?? '').trim());
4
+ }
5
+ function isInlineObjectArray(ts) {
6
+ const t = (ts ?? '').trim();
7
+ return /^Array<\{[\s\S]*\}>$/.test(t) || /^\{[\s\S]*\}\[\]$/.test(t);
8
+ }
9
+ function baseTs(ts) {
10
+ return (ts ?? 'string').replace(/\s*\|\s*null\b/g, '').trim();
11
+ }
12
+ function fieldUsesDate(ts) {
13
+ const b = baseTs(ts);
14
+ if (b === 'Date') return true;
15
+ if (isInlineObject(b) || isInlineObjectArray(b)) return /:\s*Date\b/.test(b);
16
+ return false;
17
+ }
18
+ function fieldUsesJSON(ts) {
19
+ const b = baseTs(ts);
20
+ if (b === 'unknown' || b === 'any' || b === 'object') return true;
21
+ if (isInlineObject(b) || isInlineObjectArray(b)) return /:\s*(unknown|any|object)\b/.test(b);
22
+ return false;
23
+ }
24
+ const cmd = commands[0];
25
+ const usesDate = cmd.fields.some(f => fieldUsesDate(f.tsType));
26
+ const usesJSON = cmd.fields.some(f => fieldUsesJSON(f.tsType));
27
+
28
+ const embeddedInputs = [];
29
+ for (const f of cmd.fields) {
30
+ const tsType = f.tsType ?? 'string';
31
+ if (isInlineObjectArray(tsType) || isInlineObject(tsType)) {
32
+ embeddedInputs.push({
33
+ typeName: `${pascalCase(cmd.type)}${pascalCase(f.name)}Input`,
34
+ tsType,
35
+ });
36
+ }
37
+ }
38
+ %>
39
+ import { Mutation, Resolver, Arg, Ctx, Field, InputType<% if (usesDate) { %>, GraphQLISODateTime<% } %> } from 'type-graphql';
40
+ <% if (usesJSON) { %>import { GraphQLJSON } from 'graphql-type-json';
41
+ <% } %>import { type GraphQLContext, sendCommand, MutationResponse } from '../../../shared';
42
+
43
+ <% for (const { typeName, tsType } of embeddedInputs) {
44
+ const inner = tsType.trim().startsWith('Array<')
45
+ ? tsType.trim().replace(/^Array<\{/, '{').replace(/}>$/, '}')
46
+ : tsType.trim().replace(/\[\]$/, '');
47
+ const match = inner.match(/^\{([\s\S]*)\}$/);
48
+ const body = match ? match[1] : '';
49
+ const rawFields = body.split(/[,;]\s*/).filter(Boolean);
50
+ const parsedFields = rawFields.map(f => {
51
+ const parts = f.split(':');
52
+ const name = parts[0]?.trim();
53
+ const type = parts.slice(1).join(':').trim();
54
+ if (!name || !type) return null;
55
+ return { name, tsType: type, gqlType: graphqlType(type), nullable: isNullable(type) };
56
+ }).filter(Boolean);
57
+ %>
58
+ @InputType()
59
+ export class <%= typeName %> {
60
+ <% for (const f of parsedFields) { %>
61
+ @Field(() => <%= f.gqlType %><%= f.nullable ? ', { nullable: true }' : '' %>)
62
+ <%= f.name %><%= f.nullable ? '?' : '!' %>: <%= toTsFieldType(f.tsType) %>;
63
+ <% } %>
64
+ }
65
+ <% } %>
66
+
67
+ @InputType()
68
+ export class <%= pascalCase(cmd.type) %>Input {
69
+ <% for (const field of cmd.fields) {
70
+ const tsType = field.tsType ?? 'string';
71
+ const gqlType = graphqlType(tsType);
72
+ const nestedName = `${pascalCase(cmd.type)}${pascalCase(field.name)}Input`;
73
+
74
+ if (isInlineObjectArray(tsType)) { %>
75
+ @Field(() => [<%= nestedName %>]<%= (field.required === false || isNullable(tsType)) ? ', { nullable: true }' : '' %>)
76
+ <%= field.name %><%= field.required === false ? '?' : '!' %>: <%= nestedName %>[];
77
+ <% } else if (isInlineObject(tsType)) { %>
78
+ @Field(() => <%= nestedName %><%= (field.required === false || isNullable(tsType)) ? ', { nullable: true }' : '' %>)
79
+ <%= field.name %><%= field.required === false ? '?' : '!' %>: <%= nestedName %>;
80
+ <% } else { %>
81
+ @Field(() => <%= gqlType %><%= (field.required === false || isNullable(tsType)) ? ', { nullable: true }' : '' %>)
82
+ <%= field.name %><%= field.required === false ? '?' : '!' %>: <%= toTsFieldType(tsType) %>;
83
+ <% } } %>
84
+ }
85
+
86
+ @Resolver()
87
+ export class <%= pascalCase(cmd.type) %>Resolver {
88
+ @Mutation(() => MutationResponse)
89
+ async <%= camelCase(cmd.type) %>(
90
+ @Arg('input', () => <%= pascalCase(cmd.type) %>Input) input: <%= pascalCase(cmd.type) %>Input,
91
+ @Ctx() ctx: GraphQLContext,
92
+ ): Promise<MutationResponse> {
93
+ return await sendCommand(ctx.messageBus, {
94
+ type: '<%= cmd.type %>',
95
+ kind: 'Command',
96
+ data: { ...input },
97
+ });
98
+ }
99
+ }
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
- import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
3
+ import { Model as SpecsSchema } from '@auto-engineer/flow';
4
4
 
5
5
  describe('generateScaffoldFilePlans', () => {
6
6
  it('should generate a valid register file', async () => {
@@ -16,7 +16,6 @@ describe('generateScaffoldFilePlans', () => {
16
16
  stream: 'listings-${propertyId}',
17
17
  client: {
18
18
  description: 'test',
19
- specs: [],
20
19
  },
21
20
  server: {
22
21
  description: 'test',
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
- import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
3
+ import { Model as SpecsSchema } from '@auto-engineer/flow';
4
4
 
5
5
  describe('state.ts.ejs', () => {
6
6
  it('should generate an initial state', async () => {
@@ -15,7 +15,6 @@ describe('state.ts.ejs', () => {
15
15
  name: 'Create listing',
16
16
  client: {
17
17
  description: 'test',
18
- specs: [],
19
18
  },
20
19
  server: {
21
20
  description: 'test',
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
- import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
3
+ import { Model as SpecsSchema } from '@auto-engineer/flow';
4
4
 
5
5
  describe('projection.specs.ts.ejs', () => {
6
6
  it('should generate a valid test spec for a query slice projection', async () => {
@@ -14,7 +14,7 @@ describe('projection.specs.ts.ejs', () => {
14
14
  type: 'command',
15
15
  name: 'CreateListing',
16
16
  stream: 'listing-${propertyId}',
17
- client: { description: '', specs: [] },
17
+ client: { description: '' },
18
18
  server: {
19
19
  description: '',
20
20
  specs: {
@@ -73,7 +73,7 @@ describe('projection.specs.ts.ejs', () => {
73
73
  type: 'query',
74
74
  name: 'search-listings',
75
75
  stream: 'listings',
76
- client: { description: '', specs: [] },
76
+ client: { description: '' },
77
77
  server: {
78
78
  description: '',
79
79
  data: [
@@ -1,10 +1,10 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { generateScaffoldFilePlans } from '../../scaffoldFromSchema';
3
- import { SpecsSchemaType as SpecsSchema } from '@auto-engineer/flow';
3
+ import { Model } from '@auto-engineer/flow';
4
4
 
5
5
  describe('projection.ts.ejs', () => {
6
6
  it('should generate a valid projection file with correct relative event import path from producing slice', async () => {
7
- const spec: SpecsSchema = {
7
+ const flows: Model = {
8
8
  variant: 'specs',
9
9
  flows: [
10
10
  {
@@ -16,7 +16,6 @@ describe('projection.ts.ejs', () => {
16
16
  stream: 'listing-${propertyId}',
17
17
  client: {
18
18
  description: 'create listing UI',
19
- specs: [],
20
19
  },
21
20
  server: {
22
21
  description: 'handles create/remove listing',
@@ -78,7 +77,6 @@ describe('projection.ts.ejs', () => {
78
77
  stream: 'listings',
79
78
  client: {
80
79
  description: 'search listings UI',
81
- specs: [],
82
80
  },
83
81
  server: {
84
82
  description: 'projection for available listings',
@@ -203,7 +201,7 @@ describe('projection.ts.ejs', () => {
203
201
  ],
204
202
  };
205
203
 
206
- const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
204
+ const plans = await generateScaffoldFilePlans(flows.flows, flows.messages, undefined, 'src/domain/flows');
207
205
  const projectionFile = plans.find((p) => p.outputPath.endsWith('projection.ts'));
208
206
 
209
207
  expect(projectionFile?.contents).toMatchInlineSnapshot(`
@@ -263,7 +261,7 @@ describe('projection.ts.ejs', () => {
263
261
  `);
264
262
  });
265
263
  it('should generate a valid query resolver using ID type', async () => {
266
- const spec: SpecsSchema = {
264
+ const spec: Model = {
267
265
  variant: 'specs',
268
266
  flows: [
269
267
  {
@@ -282,7 +280,6 @@ describe('projection.ts.ejs', () => {
282
280
  `,
283
281
  client: {
284
282
  description: '',
285
- specs: [],
286
283
  },
287
284
  server: {
288
285
  description: '',
@@ -346,13 +343,12 @@ describe('projection.ts.ejs', () => {
346
343
 
347
344
  // ## IMPLEMENTATION INSTRUCTIONS ##
348
345
  // You can query the projection using the ReadModel API:
349
- //
350
346
  // - model.getAll() — fetch all documents
351
347
  // - model.getById(id) — fetch a single document by ID (default key: 'id')
352
348
  // - model.find(filterFn) — filter documents using a predicate
353
349
  // - model.first(filterFn) — fetch the first document matching a predicate
354
350
  //
355
- // Example below uses \`.find()\` to filter
351
+ // Example below uses \\\`.find()\\\` to filter
356
352
  // change the logic for the query as needed to meet the requirements for the current slice.
357
353
 
358
354
  return model.find((item) => {