@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.
- package/.turbo/turbo-build.log +6 -0
- package/.turbo/turbo-format.log +5 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +22 -0
- package/.turbo/turbo-type-check.log +5 -0
- package/CHANGELOG.md +12 -0
- package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.d.ts +2 -0
- package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.d.ts.map +1 -0
- package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.js +168 -0
- package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.js.map +1 -0
- package/dist/src/codegen/templates/query/projection.specs.ts +1 -1
- package/dist/src/codegen/templates/query/projection.specs.ts.ejs +2 -0
- package/dist/src/codegen/templates/query/query.resolver.specs.ts +190 -5
- package/dist/src/codegen/templates/query/query.resolver.ts.ejs +31 -9
- package/dist/src/codegen/templates/react/react.specs.specs.ts +1 -1
- package/dist/src/codegen/templates/react/react.specs.ts +4 -4
- package/dist/src/codegen/templates/react/react.specs.ts.ejs +1 -1
- package/dist/src/codegen/templates/react/react.ts.ejs +4 -4
- package/dist/src/codegen/templates/react/register.specs.ts +2 -2
- package/dist/src/codegen/templates/react/register.ts.ejs +2 -2
- package/dist/src/commands/generate-server.d.ts.map +1 -1
- package/dist/src/commands/generate-server.js +3 -0
- package/dist/src/commands/generate-server.js.map +1 -1
- package/dist/src/domain/shared/ReadModel.d.ts +2 -2
- package/dist/src/domain/shared/ReadModel.d.ts.map +1 -1
- package/dist/src/domain/shared/ReadModel.js +2 -2
- package/dist/src/domain/shared/ReadModel.js.map +1 -1
- package/dist/src/domain/shared/ReadModel.ts +3 -3
- package/dist/src/domain/shared/types.d.ts +5 -3
- package/dist/src/domain/shared/types.d.ts.map +1 -1
- package/dist/src/domain/shared/types.js.map +1 -1
- package/dist/src/domain/shared/types.ts +5 -3
- package/dist/src/server.js +54 -7
- package/dist/src/server.js.map +1 -1
- package/dist/src/server.ts +53 -15
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -5
- package/src/codegen/templates/query/projection.specs.ts +1 -1
- package/src/codegen/templates/query/projection.specs.ts.ejs +2 -0
- package/src/codegen/templates/query/query.resolver.specs.ts +190 -5
- package/src/codegen/templates/query/query.resolver.ts.ejs +31 -9
- package/src/codegen/templates/react/react.specs.specs.ts +1 -1
- package/src/codegen/templates/react/react.specs.ts +4 -4
- package/src/codegen/templates/react/react.specs.ts.ejs +1 -1
- package/src/codegen/templates/react/react.ts.ejs +4 -4
- package/src/codegen/templates/react/register.specs.ts +2 -2
- package/src/codegen/templates/react/register.ts.ejs +2 -2
- package/src/commands/generate-server.ts +3 -0
- package/src/domain/shared/ReadModel.ts +3 -3
- package/src/domain/shared/types.ts +5 -3
- 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,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
|
+
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/Users/sam/WebstormProjects/top/auto-engineer-3/packages/server-generator-apollo-emmett[39m
|
|
8
|
+
|
|
9
|
+
[?2026h[33m*[39m
|
|
10
|
+
[?2026l[?2026h[K[1A[K[33m*******************************[39m
|
|
11
|
+
[?2026l[?2026h[K[1A[K[33m***[39m[32m···[39m[33m*[39m[32m·[39m[33m*******[39m[32m·[39m[33m**[39m[32m···[39m[33m***[39m[32m·[39m[33m*[39m[32m·[39m[33m****[39m
|
|
12
|
+
[?2026l[?2026h[K[1A[K[33m[39m[32m·[39m[33m**[39m[32m···[39m[33m*[39m[32m··[39m[33m****[39m[32m·[39m[33m*[39m[32m··[39m[33m*[39m[32m···[39m[33m***[39m[32m·····[39m[33m*[39m[32m·[39m
|
|
13
|
+
[?2026l[?2026h[K[1A[K[33m[39m[32m······················[39m[33m**[39m[32m·······[39m
|
|
14
|
+
[?2026l[?2026h[K[1A[K[33m[39m[32m································[39m[2m[90m-[39m[22m
|
|
15
|
+
[?2026l[K[1A[K[33m[39m[32m································[39m[2m[90m-[39m[22m
|
|
16
|
+
|
|
17
|
+
[2m Test Files [22m [1m[32m16 passed[39m[22m[2m | [22m[33m1 skipped[39m[90m (17)[39m
|
|
18
|
+
[2m Tests [22m [1m[32m32 passed[39m[22m[2m | [22m[33m1 skipped[39m[90m (33)[39m
|
|
19
|
+
[2m Start at [22m 11:51:04
|
|
20
|
+
[2m Duration [22m 1.14s[2m (transform 412ms, setup 0ms, collect 2.85s, tests 4.03s, environment 2ms, prepare 2.03s)[22m
|
|
21
|
+
|
|
22
|
+
[?25h
|
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 @@
|
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
<%
|
|
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.
|
|
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
|
|
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:
|
|
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
|
|
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 {
|