@auto-engineer/server-generator-apollo-emmett 0.10.5 → 0.11.0

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 (51) hide show
  1. package/.turbo/turbo-build.log +6 -0
  2. package/.turbo/turbo-format.log +5 -0
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/.turbo/turbo-test.log +22 -0
  5. package/.turbo/turbo-type-check.log +5 -0
  6. package/CHANGELOG.md +12 -0
  7. package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.d.ts +2 -0
  8. package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.d.ts.map +1 -0
  9. package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.js +168 -0
  10. package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.js.map +1 -0
  11. package/dist/src/codegen/templates/query/projection.specs.ts +1 -1
  12. package/dist/src/codegen/templates/query/projection.specs.ts.ejs +2 -0
  13. package/dist/src/codegen/templates/query/query.resolver.specs.ts +190 -5
  14. package/dist/src/codegen/templates/query/query.resolver.ts.ejs +31 -9
  15. package/dist/src/codegen/templates/react/react.specs.specs.ts +1 -1
  16. package/dist/src/codegen/templates/react/react.specs.ts +4 -4
  17. package/dist/src/codegen/templates/react/react.specs.ts.ejs +1 -1
  18. package/dist/src/codegen/templates/react/react.ts.ejs +4 -4
  19. package/dist/src/codegen/templates/react/register.specs.ts +2 -2
  20. package/dist/src/codegen/templates/react/register.ts.ejs +2 -2
  21. package/dist/src/commands/generate-server.d.ts.map +1 -1
  22. package/dist/src/commands/generate-server.js +3 -0
  23. package/dist/src/commands/generate-server.js.map +1 -1
  24. package/dist/src/domain/shared/ReadModel.d.ts +2 -2
  25. package/dist/src/domain/shared/ReadModel.d.ts.map +1 -1
  26. package/dist/src/domain/shared/ReadModel.js +2 -2
  27. package/dist/src/domain/shared/ReadModel.js.map +1 -1
  28. package/dist/src/domain/shared/ReadModel.ts +3 -3
  29. package/dist/src/domain/shared/types.d.ts +5 -3
  30. package/dist/src/domain/shared/types.d.ts.map +1 -1
  31. package/dist/src/domain/shared/types.js.map +1 -1
  32. package/dist/src/domain/shared/types.ts +5 -3
  33. package/dist/src/server.js +54 -7
  34. package/dist/src/server.js.map +1 -1
  35. package/dist/src/server.ts +53 -15
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +8 -5
  38. package/src/codegen/templates/query/projection.specs.ts +1 -1
  39. package/src/codegen/templates/query/projection.specs.ts.ejs +2 -0
  40. package/src/codegen/templates/query/query.resolver.specs.ts +190 -5
  41. package/src/codegen/templates/query/query.resolver.ts.ejs +31 -9
  42. package/src/codegen/templates/react/react.specs.specs.ts +1 -1
  43. package/src/codegen/templates/react/react.specs.ts +4 -4
  44. package/src/codegen/templates/react/react.specs.ts.ejs +1 -1
  45. package/src/codegen/templates/react/react.ts.ejs +4 -4
  46. package/src/codegen/templates/react/register.specs.ts +2 -2
  47. package/src/codegen/templates/react/register.ts.ejs +2 -2
  48. package/src/commands/generate-server.ts +3 -0
  49. package/src/domain/shared/ReadModel.ts +3 -3
  50. package/src/domain/shared/types.ts +5 -3
  51. package/src/server.ts +53 -15
@@ -0,0 +1,6 @@
1
+
2
+ 
3
+ > @auto-engineer/server-generator-apollo-emmett@0.11.0 build /Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-generator-apollo-emmett
4
+ > tsc && tsx ../../scripts/fix-esm-imports.ts && rm -rf dist/src/codegen/templates && mkdir -p dist/src/codegen && cp -r src/codegen/templates dist/src/codegen/templates && cp src/server.ts dist/src && cp -r src/utils dist/src && cp -r src/domain dist/src
5
+
6
+ Fixed ESM imports in dist/
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > @auto-engineer/server-generator-apollo-emmett@0.10.5 format /Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-generator-apollo-emmett
4
+ > prettier --write "**/*.{js,ts,json,md,yml,yaml}" --ignore-path ../../.prettierignore --log-level warn
5
+
@@ -0,0 +1,4 @@
1
+
2
+ > @auto-engineer/server-generator-apollo-emmett@0.10.5 lint /Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-generator-apollo-emmett
3
+ > eslint 'src/**/*.ts' --ignore-pattern '**/*.specs.ts' --ignore-pattern '**/.tmp/**' --max-warnings 0 --config ../../eslint.config.ts
4
+
@@ -0,0 +1,22 @@
1
+
2
+ 
3
+ > @auto-engineer/server-generator-apollo-emmett@0.11.0 test /Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-generator-apollo-emmett
4
+ > vitest run --reporter=dot
5
+
6
+ [?25l
7
+  RUN  v3.2.4 /Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-generator-apollo-emmett
8
+
9
+ [?2026h*
10
+ [?2026l[?2026h*******************************
11
+ [?2026l[?2026h***···*·*******·**···***·*·****
12
+ [?2026l[?2026h·**···*··****·*··*···***·····*·
13
+ [?2026l[?2026h······················**·······
14
+ [?2026l[?2026h································-
15
+ [?2026l································-
16
+
17
+  Test Files  16 passed | 1 skipped (17)
18
+  Tests  32 passed | 1 skipped (33)
19
+  Start at  11:51:04
20
+  Duration  1.14s (transform 412ms, setup 0ms, collect 2.85s, tests 4.03s, environment 2ms, prepare 2.03s)
21
+
22
+ [?25h
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > @auto-engineer/server-generator-apollo-emmett@0.11.0 type-check /Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-generator-apollo-emmett
4
+ > tsc --noEmit --project tsconfig.json
5
+
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @auto-engineer/server-generator-apollo-emmett
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Version bump
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies []:
12
+ - @auto-engineer/message-bus@0.11.0
13
+ - @auto-engineer/flow@0.11.0
14
+
3
15
  ## 0.10.5
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scaffoldFromSchema.query-slice-register.specs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffoldFromSchema.query-slice-register.specs.d.ts","sourceRoot":"","sources":["../../../src/codegen/scaffoldFromSchema.query-slice-register.specs.ts"],"names":[],"mappings":""}
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateScaffoldFilePlans } from './scaffoldFromSchema.js';
3
+ describe('Query slice register file generation', () => {
4
+ it('should not generate register.ts for query slices with data projections', async () => {
5
+ // Create a minimal model with a query slice that has data projections
6
+ const model = {
7
+ variant: 'specs',
8
+ messages: [], // Empty messages array to avoid undefined error
9
+ flows: [
10
+ {
11
+ id: 'flow-1',
12
+ name: 'Questionnaire Flow',
13
+ slices: [
14
+ {
15
+ id: 'AUTO-V7n8Rq5M',
16
+ type: 'query',
17
+ name: 'views the questionnaire',
18
+ client: {
19
+ description: 'Client for viewing questionnaire',
20
+ },
21
+ server: {
22
+ description: 'Views questionnaire progress',
23
+ specs: {
24
+ name: 'questionnaire progress specs',
25
+ rules: [
26
+ {
27
+ id: 'AUTO-r1A3Bp9W',
28
+ description: 'questionnaires show current progress',
29
+ examples: [
30
+ {
31
+ description: 'a question has already been answered',
32
+ given: [
33
+ {
34
+ eventRef: 'QuestionnaireLinkSent',
35
+ exampleData: {
36
+ questionnaireId: 'q-001',
37
+ participantId: 'participant-abc',
38
+ },
39
+ },
40
+ ],
41
+ when: {
42
+ eventRef: 'ViewQuestionnaire',
43
+ exampleData: { questionnaireId: 'q-001' },
44
+ },
45
+ then: [
46
+ {
47
+ stateRef: 'QuestionnaireProgress',
48
+ exampleData: {
49
+ questionnaireId: 'q-001',
50
+ participantId: 'participant-abc',
51
+ status: 'in_progress',
52
+ },
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ },
58
+ ],
59
+ },
60
+ data: [
61
+ {
62
+ target: {
63
+ type: 'State',
64
+ name: 'QuestionnaireProgress',
65
+ },
66
+ origin: {
67
+ type: 'projection',
68
+ name: 'Questionnaires',
69
+ idField: 'questionnaireId',
70
+ },
71
+ },
72
+ ],
73
+ },
74
+ },
75
+ ],
76
+ },
77
+ ],
78
+ };
79
+ // Generate scaffold plans for the model
80
+ const plans = await generateScaffoldFilePlans(model.flows, model.messages, model.integrations, '/tmp/test');
81
+ // Extract just the filenames from the generated files
82
+ const generatedFileNames = plans.map((plan) => {
83
+ const parts = plan.outputPath.split('/');
84
+ return parts[parts.length - 1];
85
+ });
86
+ // Query slices should NOT generate register.ts files
87
+ expect(generatedFileNames).not.toContain('register.ts');
88
+ // Query slices should generate projection-related files
89
+ expect(generatedFileNames).toContain('projection.ts');
90
+ expect(generatedFileNames).toContain('query.resolver.ts');
91
+ // Query slices should NOT generate command-related files
92
+ expect(generatedFileNames).not.toContain('commands.ts');
93
+ expect(generatedFileNames).not.toContain('handle.ts');
94
+ expect(generatedFileNames).not.toContain('decide.ts');
95
+ expect(generatedFileNames).not.toContain('mutation.resolver.ts');
96
+ });
97
+ it('should generate register.ts for command slices', async () => {
98
+ // Create a minimal model with a command slice
99
+ const model = {
100
+ variant: 'specs',
101
+ messages: [], // Empty messages array to avoid undefined error
102
+ flows: [
103
+ {
104
+ id: 'flow-2',
105
+ name: 'Command Flow',
106
+ slices: [
107
+ {
108
+ id: 'AUTO-CMD123',
109
+ type: 'command',
110
+ name: 'submit answer',
111
+ client: {
112
+ description: 'Submit answer client',
113
+ },
114
+ server: {
115
+ description: 'Submits an answer',
116
+ specs: {
117
+ name: 'submit answer specs',
118
+ rules: [
119
+ {
120
+ id: 'AUTO-rule123',
121
+ description: 'should accept valid answers',
122
+ examples: [
123
+ {
124
+ description: 'valid answer submission',
125
+ when: {
126
+ commandRef: 'AnswerQuestion',
127
+ exampleData: {
128
+ questionnaireId: 'q-001',
129
+ answer: 'Yes',
130
+ },
131
+ },
132
+ then: [
133
+ {
134
+ eventRef: 'QuestionAnswered',
135
+ exampleData: {
136
+ questionnaireId: 'q-001',
137
+ answer: 'Yes',
138
+ },
139
+ },
140
+ ],
141
+ },
142
+ ],
143
+ },
144
+ ],
145
+ },
146
+ },
147
+ },
148
+ ],
149
+ },
150
+ ],
151
+ };
152
+ // Generate scaffold plans for the model
153
+ const plans = await generateScaffoldFilePlans(model.flows, model.messages, model.integrations, '/tmp/test');
154
+ // Extract just the filenames from the generated files
155
+ const generatedFileNames = plans.map((plan) => {
156
+ const parts = plan.outputPath.split('/');
157
+ return parts[parts.length - 1];
158
+ });
159
+ // Command slices SHOULD generate register.ts files
160
+ expect(generatedFileNames).toContain('register.ts');
161
+ // Command slices should generate command-related files
162
+ expect(generatedFileNames).toContain('commands.ts');
163
+ expect(generatedFileNames).toContain('handle.ts');
164
+ expect(generatedFileNames).toContain('decide.ts');
165
+ expect(generatedFileNames).toContain('mutation.resolver.ts');
166
+ });
167
+ });
168
+ //# sourceMappingURL=scaffoldFromSchema.query-slice-register.specs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffoldFromSchema.query-slice-register.specs.js","sourceRoot":"","sources":["../../../src/codegen/scaffoldFromSchema.query-slice-register.specs.ts"],"names":[],"mappings":"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"}
@@ -339,7 +339,7 @@ describe('projection.ts.ejs', () => {
339
339
  @Ctx() ctx: GraphQLContext,
340
340
  @Arg('sessionId', () => ID, { nullable: true }) sessionId?: string,
341
341
  ): Promise<Wishlist[]> {
342
- const model = new ReadModel<Wishlist>(ctx.eventStore, 'WishlistProjection');
342
+ const model = new ReadModel<Wishlist>(ctx.database, 'WishlistProjection');
343
343
 
344
344
  // ## IMPLEMENTATION INSTRUCTIONS ##
345
345
  // You can query the projection using the ReadModel API:
@@ -176,6 +176,8 @@ if (idField.includes('-')) {
176
176
  formattedValue = `'${value}'`;
177
177
  } else if (tsType === 'number' || tsType === 'boolean') {
178
178
  formattedValue = String(value);
179
+ } else if (tsType === 'Date') {
180
+ formattedValue = `new Date('${value}')`;
179
181
  } else if (Array.isArray(value)) {
180
182
  formattedValue = JSON.stringify(value);
181
183
  } else {
@@ -67,7 +67,7 @@ describe('query.resolver.ts.ejs', () => {
67
67
  const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
68
68
 
69
69
  expect(resolverFile?.contents).toMatchInlineSnapshot(`
70
- "import { Query, Resolver, Arg, Ctx, ObjectType, Field } from 'type-graphql';
70
+ "import { Query, Resolver, Arg, Ctx, ObjectType, Field, Float } from 'type-graphql';
71
71
  import { type GraphQLContext, ReadModel } from '../../../shared';
72
72
 
73
73
  @ObjectType()
@@ -99,7 +99,7 @@ describe('query.resolver.ts.ejs', () => {
99
99
  @Arg('maxPrice', () => Float, { nullable: true }) maxPrice?: number,
100
100
  @Arg('minGuests', () => Float, { nullable: true }) minGuests?: number,
101
101
  ): Promise<AvailableListings[]> {
102
- const model = new ReadModel<AvailableListings>(ctx.eventStore, 'AvailablePropertiesProjection');
102
+ const model = new ReadModel<AvailableListings>(ctx.database, 'AvailablePropertiesProjection');
103
103
 
104
104
  // ## IMPLEMENTATION INSTRUCTIONS ##
105
105
  // You can query the projection using the ReadModel API:
@@ -190,7 +190,8 @@ describe('query.resolver.ts.ejs', () => {
190
190
  const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
191
191
 
192
192
  expect(resolverFile?.contents).toMatchInlineSnapshot(`
193
- "import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID } from 'type-graphql';
193
+ "import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID, Float } from 'type-graphql';
194
+ import { GraphQLJSON } from 'graphql-type-json';
194
195
  import { type GraphQLContext, ReadModel } from '../../../shared';
195
196
 
196
197
  @ObjectType()
@@ -226,7 +227,7 @@ describe('query.resolver.ts.ejs', () => {
226
227
  @Ctx() ctx: GraphQLContext,
227
228
  @Arg('sessionId', () => ID, { nullable: true }) sessionId?: string,
228
229
  ): Promise<SuggestedItems[]> {
229
- const model = new ReadModel<SuggestedItems>(ctx.eventStore, 'SuggestedItemsProjection');
230
+ const model = new ReadModel<SuggestedItems>(ctx.database, 'SuggestedItemsProjection');
230
231
 
231
232
  // ## IMPLEMENTATION INSTRUCTIONS ##
232
233
  // You can query the projection using the ReadModel API:
@@ -400,7 +401,7 @@ describe('query.resolver.ts.ejs', () => {
400
401
  @Ctx() ctx: GraphQLContext,
401
402
  @Arg('participantId', () => ID, { nullable: true }) participantId?: string,
402
403
  ): Promise<QuestionnaireProgress[]> {
403
- const model = new ReadModel<QuestionnaireProgress>(ctx.eventStore, 'Questionnaires');
404
+ const model = new ReadModel<QuestionnaireProgress>(ctx.database, 'Questionnaires');
404
405
 
405
406
  // ## IMPLEMENTATION INSTRUCTIONS ##
406
407
  // You can query the projection using the ReadModel API:
@@ -422,4 +423,188 @@ describe('query.resolver.ts.ejs', () => {
422
423
  "
423
424
  `);
424
425
  });
426
+ it('should import Float when Float fields are used', async () => {
427
+ const spec: SpecsSchema = {
428
+ variant: 'specs',
429
+ flows: [
430
+ {
431
+ name: 'product-flow',
432
+ slices: [
433
+ {
434
+ type: 'query',
435
+ name: 'get-product-price',
436
+ request: `
437
+ query GetProductPrice($productId: ID!) {
438
+ productPrice(productId: $productId) {
439
+ productId
440
+ price
441
+ discount
442
+ }
443
+ }
444
+ `,
445
+ client: {
446
+ description: '',
447
+ },
448
+ server: {
449
+ description: '',
450
+ data: [
451
+ {
452
+ origin: {
453
+ type: 'projection',
454
+ idField: 'productId',
455
+ name: 'ProductPricesProjection',
456
+ },
457
+ target: {
458
+ type: 'State',
459
+ name: 'ProductPrice',
460
+ },
461
+ },
462
+ ],
463
+ specs: { name: '', rules: [] },
464
+ },
465
+ },
466
+ ],
467
+ },
468
+ ],
469
+ messages: [
470
+ {
471
+ type: 'state',
472
+ name: 'ProductPrice',
473
+ fields: [
474
+ { name: 'productId', type: 'string', required: true },
475
+ { name: 'price', type: 'number', required: true },
476
+ { name: 'discount', type: 'number', required: true },
477
+ ],
478
+ },
479
+ ],
480
+ };
481
+
482
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
483
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
484
+
485
+ expect(resolverFile?.contents).toContain(
486
+ "import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID, Float } from 'type-graphql';",
487
+ );
488
+ expect(resolverFile?.contents).toContain('@Field(() => Float)');
489
+ });
490
+ it('should import Float when array of numbers is used', async () => {
491
+ const spec: SpecsSchema = {
492
+ variant: 'specs',
493
+ flows: [
494
+ {
495
+ name: 'stats-flow',
496
+ slices: [
497
+ {
498
+ type: 'query',
499
+ name: 'get-stats',
500
+ request: `
501
+ query GetStats($userId: ID!) {
502
+ stats(userId: $userId) {
503
+ userId
504
+ scores
505
+ }
506
+ }
507
+ `,
508
+ client: {
509
+ description: '',
510
+ },
511
+ server: {
512
+ description: '',
513
+ data: [
514
+ {
515
+ origin: {
516
+ type: 'projection',
517
+ idField: 'userId',
518
+ name: 'StatsProjection',
519
+ },
520
+ target: {
521
+ type: 'State',
522
+ name: 'Stats',
523
+ },
524
+ },
525
+ ],
526
+ specs: { name: '', rules: [] },
527
+ },
528
+ },
529
+ ],
530
+ },
531
+ ],
532
+ messages: [
533
+ {
534
+ type: 'state',
535
+ name: 'Stats',
536
+ fields: [
537
+ { name: 'userId', type: 'string', required: true },
538
+ { name: 'scores', type: 'Array<number>', required: true },
539
+ ],
540
+ },
541
+ ],
542
+ };
543
+
544
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
545
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
546
+
547
+ expect(resolverFile?.contents).toContain('Float');
548
+ expect(resolverFile?.contents).toContain('@Field(() => [Float])');
549
+ });
550
+ it('should import Float when Float query arg is used', async () => {
551
+ const spec: SpecsSchema = {
552
+ variant: 'specs',
553
+ flows: [
554
+ {
555
+ name: 'search-flow',
556
+ slices: [
557
+ {
558
+ type: 'query',
559
+ name: 'search-products',
560
+ request: `
561
+ query SearchProducts($minPrice: Float, $maxPrice: Float) {
562
+ searchProducts(minPrice: $minPrice, maxPrice: $maxPrice) {
563
+ productId
564
+ name
565
+ }
566
+ }
567
+ `,
568
+ client: {
569
+ description: '',
570
+ },
571
+ server: {
572
+ description: '',
573
+ data: [
574
+ {
575
+ origin: {
576
+ type: 'projection',
577
+ idField: 'productId',
578
+ name: 'ProductsProjection',
579
+ },
580
+ target: {
581
+ type: 'State',
582
+ name: 'Product',
583
+ },
584
+ },
585
+ ],
586
+ specs: { name: '', rules: [] },
587
+ },
588
+ },
589
+ ],
590
+ },
591
+ ],
592
+ messages: [
593
+ {
594
+ type: 'state',
595
+ name: 'Product',
596
+ fields: [
597
+ { name: 'productId', type: 'string', required: true },
598
+ { name: 'name', type: 'string', required: true },
599
+ ],
600
+ },
601
+ ],
602
+ };
603
+
604
+ const plans = await generateScaffoldFilePlans(spec.flows, spec.messages, undefined, 'src/domain/flows');
605
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
606
+
607
+ expect(resolverFile?.contents).toContain('Float');
608
+ expect(resolverFile?.contents).toContain("@Arg('minPrice', () => Float");
609
+ });
425
610
  });
@@ -20,23 +20,46 @@ function baseTs(ts) {
20
20
  }
21
21
  function fieldUsesDate(ts) {
22
22
  const b = baseTs(ts);
23
- if (b === 'Date') return true;
23
+ const gqlType = graphqlType(b);
24
+ if (gqlType.includes('GraphQLISODateTime')) return true;
24
25
  if (isInlineObject(b) || isInlineObjectArray(b)) return /:\s*Date\b/.test(b);
25
26
  return false;
26
27
  }
27
28
  function fieldUsesJSON(ts) {
28
29
  const b = baseTs(ts);
29
- if (b === 'unknown' || b === 'any' || b === 'object') return true;
30
+ const gqlType = graphqlType(b);
31
+ if (gqlType.includes('GraphQLJSON') || gqlType.includes('JSON')) return true;
30
32
  if (isInlineObject(b) || isInlineObjectArray(b)) return /:\s*(unknown|any|object)\b/.test(b);
31
33
  return false;
32
34
  }
35
+ function fieldUsesFloat(ts) {
36
+ const b = baseTs(ts);
37
+ const gqlType = graphqlType(b);
38
+ if (gqlType.includes('Float')) return true;
39
+ if (isInlineObject(b) || isInlineObjectArray(b)) {
40
+ const inner = b.trim().startsWith('Array<')
41
+ ? b.trim().replace(/^Array<\{/, '{').replace(/}>$/, '}')
42
+ : b.trim().replace(/\[\]$/, '');
43
+ const match = inner.match(/^\{([\s\S]*)\}$/);
44
+ const body = match ? match[1] : '';
45
+ const rawFields = body.split(/[,;]\s*/).filter(Boolean);
46
+ return rawFields.some(f => {
47
+ const parts = f.split(':');
48
+ const type = parts.slice(1).join(':').trim();
49
+ return type && graphqlType(type).includes('Float');
50
+ });
51
+ }
52
+ return false;
53
+ }
33
54
 
34
55
  const messageFields = message?.fields ?? [];
35
56
  const usesDate = messageFields.some(f => fieldUsesDate(f.type)) ||
36
- (parsedRequest?.args ?? []).some(a => baseTs(a.tsType) === 'Date');
37
- const usesJSON = messageFields.some(f => fieldUsesJSON(f.type));
57
+ (parsedRequest?.args ?? []).some(a => fieldUsesDate(a.tsType));
58
+ const usesJSON = messageFields.some(f => fieldUsesJSON(f.type)) ||
59
+ (parsedRequest?.args ?? []).some(a => fieldUsesJSON(a.tsType));
60
+ const usesFloat = messageFields.some(f => fieldUsesFloat(f.type)) ||
61
+ (parsedRequest?.args ?? []).some(a => fieldUsesFloat(a.tsType));
38
62
 
39
- // Collect embedded types up-front so we can emit them before the parent
40
63
  const embeddedTypes = [];
41
64
  for (const field of messageFields) {
42
65
  const tsType = field.type ?? 'string';
@@ -48,13 +71,12 @@ for (const field of messageFields) {
48
71
  }
49
72
  }
50
73
  %>
51
- import { Query, Resolver, Arg, Ctx, ObjectType, Field<% if (usesID) { %>, ID<% } %><% if (usesDate) { %>, GraphQLISODateTime<% } %> } from 'type-graphql';
74
+ import { Query, Resolver, Arg, Ctx, ObjectType, Field<% if (usesID) { %>, ID<% } %><% if (usesFloat) { %>, Float<% } %><% if (usesDate) { %>, GraphQLISODateTime<% } %> } from 'type-graphql';
52
75
  <% if (usesJSON) { %>import { GraphQLJSON } from 'graphql-type-json';
53
76
  <% } %>import { type GraphQLContext, ReadModel } from '../../../shared';
54
77
 
55
- <% // Emit embedded types FIRST — this matches your snapshot order
78
+ <%
56
79
  for (const { typeName, tsType } of embeddedTypes) {
57
- // Extract inner "{ ... }" whether Array<{...}> or "{...}[]"
58
80
  const inner = tsType.trim().startsWith('Array<')
59
81
  ? tsType.trim().replace(/^Array<\{/, '{').replace(/}>$/, '}')
60
82
  : tsType.trim().replace(/\[\]$/, '');
@@ -114,7 +136,7 @@ async <%= queryName %>(
114
136
  %> @Arg('<%= arg.name %>', () => <%= gqlType %>, { nullable: true }) <%= arg.name %>?: <%= tsType %><%= i < parsedRequest.args.length - 1 ? ',' : '' %>
115
137
  <% } } %>
116
138
  ): Promise<<%= viewType %>[]> {
117
- const model = new ReadModel<<%= viewType %>>(ctx.eventStore, '<%= projectionType %>');
139
+ const model = new ReadModel<<%= viewType %>>(ctx.database, '<%= projectionType %>');
118
140
 
119
141
  // ## IMPLEMENTATION INSTRUCTIONS ##
120
142
  // You can query the projection using the ReadModel API:
@@ -184,7 +184,7 @@ describe('react.specs.ts.ejs (react slice)', () => {
184
184
  beforeEach(() => {
185
185
  eventStore = getInMemoryEventStore({});
186
186
  given = ReactorSpecification.for<ReactorEvent, ReactorCommand, ReactorContext>(
187
- () => react({ eventStore, commandSender: messageBus }),
187
+ () => react({ eventStore, commandSender: messageBus, database: eventStore.database }),
188
188
  (commandSender) => {
189
189
  messageBus = commandSender;
190
190
  return {
@@ -223,12 +223,12 @@ describe('handle.ts.ejs (react slice)', () => {
223
223
  import type { BookingRequested } from '../guest-submits-booking-request/events';
224
224
  import type { ReactorContext } from '../../../shared';
225
225
 
226
- export const react = ({ eventStore, commandSender }: ReactorContext) =>
226
+ export const react = ({ eventStore, commandSender, database }: ReactorContext) =>
227
227
  inMemoryReactor<BookingRequested>({
228
228
  processorId: 'manage-bookings-send-notification-to-host',
229
229
  canHandle: ['BookingRequested'],
230
230
  connectionOptions: {
231
- database: eventStore.database,
231
+ database,
232
232
  },
233
233
  eachMessage: async (event, context): Promise<MessageHandlerResult> => {
234
234
  /**
@@ -236,7 +236,7 @@ describe('handle.ts.ejs (react slice)', () => {
236
236
  *
237
237
  * - Inspect event data to determine if the command should be sent.
238
238
  * - Replace the placeholder logic and \\\`throw\\\` below with real implementation.
239
- * - Send one or more commands via: context.commandSender.send({...})
239
+ * - Send one or more commands via: commandSender.send({...})
240
240
  * - Optionally return a MessageHandlerResult for SKIP or error cases.
241
241
  */
242
242
 
@@ -250,7 +250,7 @@ describe('handle.ts.ejs (react slice)', () => {
250
250
  // };
251
251
  // }
252
252
 
253
- // await context.commandSender.send({
253
+ // await commandSender.send({
254
254
  // type: 'NotifyHost',
255
255
  // kind: 'Command',
256
256
  // data: {
@@ -87,7 +87,7 @@ describe('<%= ruleDescription %>', () => {
87
87
  beforeEach(() => {
88
88
  eventStore = getInMemoryEventStore({});
89
89
  given = ReactorSpecification.for<ReactorEvent, ReactorCommand, ReactorContext>(
90
- () => react({ eventStore, commandSender: messageBus }),
90
+ () => react({ eventStore, commandSender: messageBus, database: eventStore.database }),
91
91
  (commandSender) => {
92
92
  messageBus = commandSender;
93
93
  return {