@auto-engineer/server-generator-apollo-emmett 0.8.5 → 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 (148) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +8 -0
  3. package/dist/src/codegen/scaffoldFromSchema.d.ts +3 -3
  4. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
  5. package/dist/src/codegen/scaffoldFromSchema.js +54 -18
  6. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
  7. package/dist/{codegen → src/codegen}/templates/command/commands.specs.ts +1 -2
  8. package/dist/{codegen → src/codegen}/templates/command/decide.specs.specs.ts +3 -3
  9. package/dist/{codegen → src/codegen}/templates/command/decide.specs.ts +1 -5
  10. package/dist/{codegen → src/codegen}/templates/command/events.specs.ts +1 -2
  11. package/dist/{codegen → src/codegen}/templates/command/evolve.specs.ts +1 -2
  12. package/dist/{codegen → src/codegen}/templates/command/handle.specs.ts +1 -3
  13. package/dist/src/codegen/templates/command/mutation.resolver.specs.ts +363 -0
  14. package/dist/src/codegen/templates/command/mutation.resolver.ts.ejs +99 -0
  15. package/dist/{codegen → src/codegen}/templates/command/register.specs.ts +1 -2
  16. package/dist/{codegen → src/codegen}/templates/command/state.specs.ts +1 -2
  17. package/dist/{codegen → src/codegen}/templates/query/projection.specs.specs..ts +3 -3
  18. package/dist/{codegen → src/codegen}/templates/query/projection.specs.ts +5 -9
  19. package/dist/{codegen → src/codegen}/templates/query/query.resolver.specs.ts +194 -23
  20. package/dist/src/codegen/templates/query/query.resolver.ts.ejs +137 -0
  21. package/dist/{codegen → src/codegen}/templates/query/state.specs.ts +1 -2
  22. package/dist/{codegen → src/codegen}/templates/react/react.specs.specs.ts +2 -2
  23. package/dist/{codegen → src/codegen}/templates/react/react.specs.ts +3 -3
  24. package/dist/{codegen → src/codegen}/templates/react/register.specs.ts +3 -3
  25. package/dist/src/codegen/test-data/specVariant1.d.ts +1 -1
  26. package/dist/src/codegen/test-data/specVariant1.d.ts.map +1 -1
  27. package/dist/src/codegen/test-data/specVariant1.js +4 -2
  28. package/dist/src/codegen/test-data/specVariant1.js.map +1 -1
  29. package/dist/src/commands/generate-server.d.ts.map +1 -1
  30. package/dist/src/commands/generate-server.js +34 -24
  31. package/dist/src/commands/generate-server.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +8 -7
  34. package/src/codegen/scaffoldFromSchema.ts +52 -20
  35. package/src/codegen/templates/command/commands.specs.ts +1 -2
  36. package/src/codegen/templates/command/decide.specs.specs.ts +3 -3
  37. package/src/codegen/templates/command/decide.specs.ts +1 -5
  38. package/src/codegen/templates/command/events.specs.ts +1 -2
  39. package/src/codegen/templates/command/evolve.specs.ts +1 -2
  40. package/src/codegen/templates/command/handle.specs.ts +1 -3
  41. package/src/codegen/templates/command/mutation.resolver.specs.ts +247 -8
  42. package/src/codegen/templates/command/mutation.resolver.ts.ejs +86 -12
  43. package/src/codegen/templates/command/register.specs.ts +1 -2
  44. package/src/codegen/templates/command/state.specs.ts +1 -2
  45. package/src/codegen/templates/query/projection.specs.specs..ts +3 -3
  46. package/src/codegen/templates/query/projection.specs.ts +5 -9
  47. package/src/codegen/templates/query/query.resolver.specs.ts +194 -23
  48. package/src/codegen/templates/query/query.resolver.ts.ejs +84 -45
  49. package/src/codegen/templates/query/state.specs.ts +1 -2
  50. package/src/codegen/templates/react/react.specs.specs.ts +2 -2
  51. package/src/codegen/templates/react/react.specs.ts +3 -3
  52. package/src/codegen/templates/react/register.specs.ts +3 -3
  53. package/src/codegen/test-data/specVariant1.ts +5 -3
  54. package/src/commands/generate-server.ts +38 -30
  55. package/tsconfig.test.json +9 -0
  56. package/.turbo/turbo-test.log +0 -14
  57. package/.turbo/turbo-type-check.log +0 -4
  58. package/dist/codegen/templates/command/mutation.resolver.specs.ts +0 -124
  59. package/dist/codegen/templates/command/mutation.resolver.ts.ejs +0 -25
  60. package/dist/codegen/templates/query/query.resolver.ts.ejs +0 -98
  61. package/dist/src/codegen/templates/command/commands.specs.d.ts +0 -2
  62. package/dist/src/codegen/templates/command/commands.specs.d.ts.map +0 -1
  63. package/dist/src/codegen/templates/command/commands.specs.js +0 -96
  64. package/dist/src/codegen/templates/command/commands.specs.js.map +0 -1
  65. package/dist/src/codegen/templates/command/decide.specs.d.ts +0 -2
  66. package/dist/src/codegen/templates/command/decide.specs.d.ts.map +0 -1
  67. package/dist/src/codegen/templates/command/decide.specs.js +0 -567
  68. package/dist/src/codegen/templates/command/decide.specs.js.map +0 -1
  69. package/dist/src/codegen/templates/command/decide.specs.specs.d.ts +0 -2
  70. package/dist/src/codegen/templates/command/decide.specs.specs.d.ts.map +0 -1
  71. package/dist/src/codegen/templates/command/decide.specs.specs.js +0 -278
  72. package/dist/src/codegen/templates/command/decide.specs.specs.js.map +0 -1
  73. package/dist/src/codegen/templates/command/events.specs.d.ts +0 -2
  74. package/dist/src/codegen/templates/command/events.specs.d.ts.map +0 -1
  75. package/dist/src/codegen/templates/command/events.specs.js +0 -112
  76. package/dist/src/codegen/templates/command/events.specs.js.map +0 -1
  77. package/dist/src/codegen/templates/command/evolve.specs.d.ts +0 -2
  78. package/dist/src/codegen/templates/command/evolve.specs.d.ts.map +0 -1
  79. package/dist/src/codegen/templates/command/evolve.specs.js +0 -108
  80. package/dist/src/codegen/templates/command/evolve.specs.js.map +0 -1
  81. package/dist/src/codegen/templates/command/handle.specs.d.ts +0 -2
  82. package/dist/src/codegen/templates/command/handle.specs.d.ts.map +0 -1
  83. package/dist/src/codegen/templates/command/handle.specs.js +0 -326
  84. package/dist/src/codegen/templates/command/handle.specs.js.map +0 -1
  85. package/dist/src/codegen/templates/command/mutation.resolver.specs.d.ts +0 -2
  86. package/dist/src/codegen/templates/command/mutation.resolver.specs.d.ts.map +0 -1
  87. package/dist/src/codegen/templates/command/mutation.resolver.specs.js +0 -121
  88. package/dist/src/codegen/templates/command/mutation.resolver.specs.js.map +0 -1
  89. package/dist/src/codegen/templates/command/register.specs.d.ts +0 -2
  90. package/dist/src/codegen/templates/command/register.specs.d.ts.map +0 -1
  91. package/dist/src/codegen/templates/command/register.specs.js +0 -113
  92. package/dist/src/codegen/templates/command/register.specs.js.map +0 -1
  93. package/dist/src/codegen/templates/command/state.specs.d.ts +0 -2
  94. package/dist/src/codegen/templates/command/state.specs.d.ts.map +0 -1
  95. package/dist/src/codegen/templates/command/state.specs.js +0 -133
  96. package/dist/src/codegen/templates/command/state.specs.js.map +0 -1
  97. package/dist/src/codegen/templates/query/projection.specs.d.ts +0 -2
  98. package/dist/src/codegen/templates/query/projection.specs.d.ts.map +0 -1
  99. package/dist/src/codegen/templates/query/projection.specs.js +0 -363
  100. package/dist/src/codegen/templates/query/projection.specs.js.map +0 -1
  101. package/dist/src/codegen/templates/query/projection.specs.specs..d.ts +0 -2
  102. package/dist/src/codegen/templates/query/projection.specs.specs..d.ts.map +0 -1
  103. package/dist/src/codegen/templates/query/projection.specs.specs..js +0 -293
  104. package/dist/src/codegen/templates/query/projection.specs.specs..js.map +0 -1
  105. package/dist/src/codegen/templates/query/query.resolver.specs.d.ts +0 -2
  106. package/dist/src/codegen/templates/query/query.resolver.specs.d.ts.map +0 -1
  107. package/dist/src/codegen/templates/query/query.resolver.specs.js +0 -249
  108. package/dist/src/codegen/templates/query/query.resolver.specs.js.map +0 -1
  109. package/dist/src/codegen/templates/query/state.specs.d.ts +0 -2
  110. package/dist/src/codegen/templates/query/state.specs.d.ts.map +0 -1
  111. package/dist/src/codegen/templates/query/state.specs.js +0 -67
  112. package/dist/src/codegen/templates/query/state.specs.js.map +0 -1
  113. package/dist/src/codegen/templates/react/react.specs.d.ts +0 -2
  114. package/dist/src/codegen/templates/react/react.specs.d.ts.map +0 -1
  115. package/dist/src/codegen/templates/react/react.specs.js +0 -265
  116. package/dist/src/codegen/templates/react/react.specs.js.map +0 -1
  117. package/dist/src/codegen/templates/react/react.specs.specs.d.ts +0 -2
  118. package/dist/src/codegen/templates/react/react.specs.specs.d.ts.map +0 -1
  119. package/dist/src/codegen/templates/react/react.specs.specs.js +0 -229
  120. package/dist/src/codegen/templates/react/react.specs.specs.js.map +0 -1
  121. package/dist/src/codegen/templates/react/register.specs.d.ts +0 -2
  122. package/dist/src/codegen/templates/react/register.specs.d.ts.map +0 -1
  123. package/dist/src/codegen/templates/react/register.specs.js +0 -246
  124. package/dist/src/codegen/templates/react/register.specs.js.map +0 -1
  125. /package/dist/{codegen → src/codegen}/templates/command/commands.ts.ejs +0 -0
  126. /package/dist/{codegen → src/codegen}/templates/command/decide.specs.ts.ejs +0 -0
  127. /package/dist/{codegen → src/codegen}/templates/command/decide.ts.ejs +0 -0
  128. /package/dist/{codegen → src/codegen}/templates/command/events.ts.ejs +0 -0
  129. /package/dist/{codegen → src/codegen}/templates/command/evolve.ts.ejs +0 -0
  130. /package/dist/{codegen → src/codegen}/templates/command/handle.ts.ejs +0 -0
  131. /package/dist/{codegen → src/codegen}/templates/command/register.ts.ejs +0 -0
  132. /package/dist/{codegen → src/codegen}/templates/command/state.ts.ejs +0 -0
  133. /package/dist/{codegen → src/codegen}/templates/query/projection.specs.ts.ejs +0 -0
  134. /package/dist/{codegen → src/codegen}/templates/query/projection.ts.ejs +0 -0
  135. /package/dist/{codegen → src/codegen}/templates/query/state.ts.ejs +0 -0
  136. /package/dist/{codegen → src/codegen}/templates/react/react.specs.ts.ejs +0 -0
  137. /package/dist/{codegen → src/codegen}/templates/react/react.ts.ejs +0 -0
  138. /package/dist/{codegen → src/codegen}/templates/react/register.ts.ejs +0 -0
  139. /package/dist/{domain → src/domain}/shared/ReadModel.ts +0 -0
  140. /package/dist/{domain → src/domain}/shared/index.ts +0 -0
  141. /package/dist/{domain → src/domain}/shared/reactorSpecification.ts +0 -0
  142. /package/dist/{domain → src/domain}/shared/sendCommand.ts +0 -0
  143. /package/dist/{domain → src/domain}/shared/types.ts +0 -0
  144. /package/dist/{server.ts → src/server.ts} +0 -0
  145. /package/dist/{utils → src/utils}/index.ts +0 -0
  146. /package/dist/{utils → src/utils}/loadProjections.ts +0 -0
  147. /package/dist/{utils → src/utils}/loadRegisterFiles.ts +0 -0
  148. /package/dist/{utils → src/utils}/loadResolvers.ts +0 -0
@@ -1,23 +1,97 @@
1
- import { Mutation, Resolver, Arg, Ctx, Field, InputType } from 'type-graphql';
2
- import { type GraphQLContext, sendCommand, MutationResponse } from '../../../shared';
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
+ <% } %>
3
66
 
4
67
  @InputType()
5
- export class <%= pascalCase(commands[0].type) %>Input {
6
- <% for (const field of commands[0].fields) { -%>
7
- @Field(() => <%= graphqlType(field.tsType) %><%= field.required === false ? ', { nullable: true }' : '' %>)
8
- <%= field.name %><%= field.required === false ? '?' : '' %><%= field.required !== false ? '!' : '' %>: <%- field.tsType %>;
9
- <% } -%>
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
+ <% } } %>
10
84
  }
11
85
 
12
86
  @Resolver()
13
- export class <%= pascalCase(commands[0].type) %>Resolver {
87
+ export class <%= pascalCase(cmd.type) %>Resolver {
14
88
  @Mutation(() => MutationResponse)
15
- async <%= camelCase(commands[0].type) %>(
16
- @Arg('input', () => <%= pascalCase(commands[0].type) %>Input) input: <%= pascalCase(commands[0].type) %>Input,
17
- @Ctx() ctx: GraphQLContext
89
+ async <%= camelCase(cmd.type) %>(
90
+ @Arg('input', () => <%= pascalCase(cmd.type) %>Input) input: <%= pascalCase(cmd.type) %>Input,
91
+ @Ctx() ctx: GraphQLContext,
18
92
  ): Promise<MutationResponse> {
19
93
  return await sendCommand(ctx.messageBus, {
20
- type: '<%= commands[0].type %>',
94
+ type: '<%= cmd.type %>',
21
95
  kind: 'Command',
22
96
  data: { ...input },
23
97
  });
@@ -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) => {
@@ -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('query.resolver.ts.ejs', () => {
6
6
  it('should generate a valid query resolver from request field', async () => {
@@ -26,7 +26,6 @@ describe('query.resolver.ts.ejs', () => {
26
26
  `,
27
27
  client: {
28
28
  description: '',
29
- specs: [],
30
29
  },
31
30
  server: {
32
31
  description: '',
@@ -79,13 +78,13 @@ describe('query.resolver.ts.ejs', () => {
79
78
  @Field(() => String)
80
79
  title!: string;
81
80
 
82
- @Field(() => Number)
81
+ @Field(() => Float)
83
82
  pricePerNight!: number;
84
83
 
85
84
  @Field(() => String)
86
85
  location!: string;
87
86
 
88
- @Field(() => Number)
87
+ @Field(() => Float)
89
88
  maxGuests!: number;
90
89
 
91
90
  [key: string]: unknown;
@@ -97,20 +96,19 @@ describe('query.resolver.ts.ejs', () => {
97
96
  async searchProperties(
98
97
  @Ctx() ctx: GraphQLContext,
99
98
  @Arg('location', () => String, { nullable: true }) location?: string,
100
- @Arg('maxPrice', () => Number, { nullable: true }) maxPrice?: number,
101
- @Arg('minGuests', () => Number, { nullable: true }) minGuests?: number,
99
+ @Arg('maxPrice', () => Float, { nullable: true }) maxPrice?: number,
100
+ @Arg('minGuests', () => Float, { nullable: true }) minGuests?: number,
102
101
  ): Promise<AvailableListings[]> {
103
102
  const model = new ReadModel<AvailableListings>(ctx.eventStore, 'AvailablePropertiesProjection');
104
103
 
105
104
  // ## IMPLEMENTATION INSTRUCTIONS ##
106
105
  // You can query the projection using the ReadModel API:
107
- //
108
106
  // - model.getAll() — fetch all documents
109
107
  // - model.getById(id) — fetch a single document by ID (default key: 'id')
110
108
  // - model.find(filterFn) — filter documents using a predicate
111
109
  // - model.first(filterFn) — fetch the first document matching a predicate
112
110
  //
113
- // Example below uses \`.find()\` to filter
111
+ // Example below uses \\\`.find()\\\` to filter
114
112
  // change the logic for the query as needed to meet the requirements for the current slice.
115
113
 
116
114
  return model.find((item) => {
@@ -150,7 +148,7 @@ describe('query.resolver.ts.ejs', () => {
150
148
  }
151
149
  }
152
150
  `,
153
- client: { description: '', specs: [] },
151
+ client: { description: '' },
154
152
  server: {
155
153
  description: '',
156
154
  data: [
@@ -195,17 +193,6 @@ describe('query.resolver.ts.ejs', () => {
195
193
  "import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID } from 'type-graphql';
196
194
  import { type GraphQLContext, ReadModel } from '../../../shared';
197
195
 
198
- @ObjectType()
199
- export class SuggestedItems {
200
- @Field(() => String)
201
- sessionId!: string;
202
-
203
- @Field(() => [SuggestedItemsItems])
204
- items!: SuggestedItemsItems[];
205
-
206
- [key: string]: unknown;
207
- }
208
-
209
196
  @ObjectType()
210
197
  export class SuggestedItemsItems {
211
198
  @Field(() => String)
@@ -214,13 +201,24 @@ describe('query.resolver.ts.ejs', () => {
214
201
  @Field(() => String)
215
202
  name!: string;
216
203
 
217
- @Field(() => Number)
204
+ @Field(() => Float)
218
205
  quantity!: number;
219
206
 
220
207
  @Field(() => String)
221
208
  reason!: string;
222
209
  }
223
210
 
211
+ @ObjectType()
212
+ export class SuggestedItems {
213
+ @Field(() => String)
214
+ sessionId!: string;
215
+
216
+ @Field(() => [SuggestedItemsItems])
217
+ items!: SuggestedItemsItems[];
218
+
219
+ [key: string]: unknown;
220
+ }
221
+
224
222
  @Resolver()
225
223
  export class ViewsSuggestedItemsQueryResolver {
226
224
  @Query(() => [SuggestedItems])
@@ -232,13 +230,12 @@ describe('query.resolver.ts.ejs', () => {
232
230
 
233
231
  // ## IMPLEMENTATION INSTRUCTIONS ##
234
232
  // You can query the projection using the ReadModel API:
235
- //
236
233
  // - model.getAll() — fetch all documents
237
234
  // - model.getById(id) — fetch a single document by ID (default key: 'id')
238
235
  // - model.find(filterFn) — filter documents using a predicate
239
236
  // - model.first(filterFn) — fetch the first document matching a predicate
240
237
  //
241
- // Example below uses \`.find()\` to filter
238
+ // Example below uses \\\`.find()\\\` to filter
242
239
  // change the logic for the query as needed to meet the requirements for the current slice.
243
240
 
244
241
  return model.find((item) => {
@@ -251,4 +248,178 @@ describe('query.resolver.ts.ejs', () => {
251
248
  "
252
249
  `);
253
250
  });
251
+ it('should generate the query resolver for "views the questionnaire"', async () => {
252
+ const spec: SpecsSchema = {
253
+ variant: 'specs',
254
+ flows: [
255
+ {
256
+ name: 'Questionnaires',
257
+ slices: [
258
+ {
259
+ name: 'views the questionnaire',
260
+ type: 'query',
261
+ client: {
262
+ description: '',
263
+ specs: {
264
+ name: '',
265
+ rules: [
266
+ 'focus on the current question based on the progress state',
267
+ 'display the list of answered questions',
268
+ 'display the list of remaining questions',
269
+ 'show a progress indicator that is always visible as the user scrolls',
270
+ ],
271
+ },
272
+ },
273
+ request:
274
+ 'query QuestionnaireProgress($participantId: ID!) {\n questionnaireProgress(participantId: $participantId) {\n questionnaireId\n participantId\n status\n currentQuestionId\n remainingQuestions\n answers {\n questionId\n value\n }\n }\n}',
275
+ server: {
276
+ description: '',
277
+ data: [
278
+ {
279
+ target: { type: 'State', name: 'QuestionnaireProgress' },
280
+ origin: { type: 'projection', name: 'Questionnaires', idField: 'questionnaire-participantId' },
281
+ },
282
+ ],
283
+ specs: {
284
+ name: '',
285
+ rules: [
286
+ {
287
+ description: 'questionnaires show current progress',
288
+ examples: [
289
+ {
290
+ description: 'a question has already been answered',
291
+ given: [
292
+ {
293
+ eventRef: 'QuestionAnswered',
294
+ exampleData: {
295
+ questionnaireId: 'q-001',
296
+ participantId: 'participant-abc',
297
+ link: 'https://app.example.com/q/q-001?participant=participant-abc',
298
+ sentAt: '2030-01-01T09:00:00.000Z',
299
+ },
300
+ },
301
+ {
302
+ eventRef: 'QuestionAnswered',
303
+ exampleData: {
304
+ questionnaireId: 'q-001',
305
+ participantId: 'participant-abc',
306
+ questionId: 'q1',
307
+ answer: 'Yes',
308
+ savedAt: '2030-01-01T09:05:00.000Z',
309
+ },
310
+ },
311
+ ],
312
+ when: { exampleData: {}, eventRef: 'QuestionnaireLinkSent' },
313
+ then: [
314
+ {
315
+ stateRef: 'QuestionnaireProgress',
316
+ exampleData: {
317
+ questionnaireId: 'q-001',
318
+ participantId: 'participant-abc',
319
+ status: 'in_progress',
320
+ currentQuestionId: 'q2',
321
+ remainingQuestions: ['q2', 'q3'],
322
+ answers: [{ questionId: 'q1', value: 'Yes' }],
323
+ },
324
+ },
325
+ ],
326
+ },
327
+ ],
328
+ },
329
+ ],
330
+ },
331
+ },
332
+ },
333
+ ],
334
+ },
335
+ ],
336
+ messages: [
337
+ {
338
+ type: 'state',
339
+ name: 'QuestionnaireProgress',
340
+ fields: [
341
+ { name: 'questionnaireId', type: 'string', required: true },
342
+ { name: 'participantId', type: 'string', required: true },
343
+ { name: 'status', type: '"in_progress" | "ready_to_submit" | "submitted"', required: true },
344
+ { name: 'currentQuestionId', type: 'string | null', required: true },
345
+ { name: 'remainingQuestions', type: 'Array<string>', required: true },
346
+ { name: 'answers', type: 'Array<{ questionId: string; value: unknown }>', required: true },
347
+ ],
348
+ metadata: { version: 1 },
349
+ },
350
+ ],
351
+ integrations: [],
352
+ };
353
+
354
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
355
+ const queryFile = plans.find(
356
+ (p) => p.outputPath.endsWith('query.resolver.ts') && p.contents.includes('ViewsTheQuestionnaireQueryResolver'),
357
+ );
358
+
359
+ expect(queryFile?.contents).toMatchInlineSnapshot(`
360
+ "import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID } from 'type-graphql';
361
+ import { GraphQLJSON } from 'graphql-type-json';
362
+ import { type GraphQLContext, ReadModel } from '../../../shared';
363
+
364
+ @ObjectType()
365
+ export class QuestionnaireProgressAnswers {
366
+ @Field(() => String)
367
+ questionId!: string;
368
+
369
+ @Field(() => GraphQLJSON)
370
+ value!: unknown;
371
+ }
372
+
373
+ @ObjectType()
374
+ export class QuestionnaireProgress {
375
+ @Field(() => String)
376
+ questionnaireId!: string;
377
+
378
+ @Field(() => String)
379
+ participantId!: string;
380
+
381
+ @Field(() => String)
382
+ status!: 'in_progress' | 'ready_to_submit' | 'submitted';
383
+
384
+ @Field(() => String, { nullable: true })
385
+ currentQuestionId?: string | null;
386
+
387
+ @Field(() => [String])
388
+ remainingQuestions!: string[];
389
+
390
+ @Field(() => [QuestionnaireProgressAnswers])
391
+ answers!: QuestionnaireProgressAnswers[];
392
+
393
+ [key: string]: unknown;
394
+ }
395
+
396
+ @Resolver()
397
+ export class ViewsTheQuestionnaireQueryResolver {
398
+ @Query(() => [QuestionnaireProgress])
399
+ async questionnaireProgress(
400
+ @Ctx() ctx: GraphQLContext,
401
+ @Arg('participantId', () => ID, { nullable: true }) participantId?: string,
402
+ ): Promise<QuestionnaireProgress[]> {
403
+ const model = new ReadModel<QuestionnaireProgress>(ctx.eventStore, 'Questionnaires');
404
+
405
+ // ## IMPLEMENTATION INSTRUCTIONS ##
406
+ // You can query the projection using the ReadModel API:
407
+ // - model.getAll() — fetch all documents
408
+ // - model.getById(id) — fetch a single document by ID (default key: 'id')
409
+ // - model.find(filterFn) — filter documents using a predicate
410
+ // - model.first(filterFn) — fetch the first document matching a predicate
411
+ //
412
+ // Example below uses \\\`.find()\\\` to filter
413
+ // change the logic for the query as needed to meet the requirements for the current slice.
414
+
415
+ return model.find((item) => {
416
+ if (participantId !== undefined && item.participantId !== participantId) return false;
417
+
418
+ return true;
419
+ });
420
+ }
421
+ }
422
+ "
423
+ `);
424
+ });
254
425
  });