@auto-engineer/server-generator-apollo-emmett 1.123.0 → 1.125.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 +1 -1
- package/.turbo/turbo-test.log +5 -5
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +99 -0
- package/dist/src/codegen/extract/type-helpers.d.ts.map +1 -1
- package/dist/src/codegen/extract/type-helpers.js +7 -5
- package/dist/src/codegen/extract/type-helpers.js.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +19 -1
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/decide.specs.ts +14 -6
- package/dist/src/codegen/templates/command/decide.ts.ejs +5 -3
- package/dist/src/codegen/templates/command/mutation.resolver.specs.ts +0 -1
- package/dist/src/codegen/templates/command/state.specs.ts +1 -1
- package/dist/src/codegen/templates/command/state.ts.ejs +1 -1
- package/dist/src/codegen/templates/query/projection.specs.specs.ts +2 -0
- package/dist/src/codegen/templates/query/projection.specs.ts +6 -0
- package/dist/src/codegen/templates/query/projection.specs.ts.ejs +4 -1
- package/dist/src/codegen/templates/query/projection.ts.ejs +2 -0
- package/dist/src/codegen/templates/query/query.resolver.specs.ts +8 -9
- package/dist/src/codegen/templates/query/query.resolver.ts.ejs +9 -3
- package/dist/src/codegen/templates/react/react.specs.specs.ts +3 -3
- package/dist/src/codegen/templates/react/react.specs.ts +2 -2
- package/dist/src/codegen/templates/react/react.specs.ts.ejs +2 -2
- package/dist/src/codegen/templates/react/react.ts.ejs +138 -64
- package/dist/src/codegen/templates/react/react.ts.specs.ts +243 -1
- package/dist/src/codegen/templates/react/register.specs.ts +281 -14
- package/dist/src/codegen/templates/react/register.ts.ejs +100 -48
- package/dist/src/commands/generate-server.d.ts +1 -0
- package/dist/src/commands/generate-server.d.ts.map +1 -1
- package/dist/src/commands/generate-server.js +18 -0
- package/dist/src/commands/generate-server.js.map +1 -1
- package/dist/src/domain/shared/reactorSpecification.d.ts +5 -5
- package/dist/src/domain/shared/reactorSpecification.d.ts.map +1 -1
- package/dist/src/domain/shared/reactorSpecification.js +1 -2
- package/dist/src/domain/shared/reactorSpecification.js.map +1 -1
- package/dist/src/domain/shared/reactorSpecification.ts +7 -10
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +4 -30
- package/package.json +4 -4
- package/src/codegen/extract/type-helpers.specs.ts +50 -1
- package/src/codegen/extract/type-helpers.ts +6 -3
- package/src/codegen/scaffoldFromSchema.ts +21 -1
- package/src/codegen/templates/command/decide.specs.ts +14 -6
- package/src/codegen/templates/command/decide.ts.ejs +5 -3
- package/src/codegen/templates/command/mutation.resolver.specs.ts +0 -1
- package/src/codegen/templates/command/state.specs.ts +1 -1
- package/src/codegen/templates/command/state.ts.ejs +1 -1
- package/src/codegen/templates/query/projection.specs.specs.ts +2 -0
- package/src/codegen/templates/query/projection.specs.ts +6 -0
- package/src/codegen/templates/query/projection.specs.ts.ejs +4 -1
- package/src/codegen/templates/query/projection.ts.ejs +2 -0
- package/src/codegen/templates/query/query.resolver.specs.ts +8 -9
- package/src/codegen/templates/query/query.resolver.ts.ejs +9 -3
- package/src/codegen/templates/react/react.specs.specs.ts +3 -3
- package/src/codegen/templates/react/react.specs.ts +2 -2
- package/src/codegen/templates/react/react.specs.ts.ejs +2 -2
- package/src/codegen/templates/react/react.ts.ejs +138 -64
- package/src/codegen/templates/react/react.ts.specs.ts +243 -1
- package/src/codegen/templates/react/register.specs.ts +281 -14
- package/src/codegen/templates/react/register.ts.ejs +100 -48
- package/src/commands/generate-server.specs.ts +32 -0
- package/src/commands/generate-server.ts +20 -0
- package/src/domain/shared/reactorSpecification.ts +7 -10
package/ketchup-plan.md
CHANGED
|
@@ -1,37 +1,11 @@
|
|
|
1
|
-
# Ketchup Plan: Fix
|
|
1
|
+
# Ketchup Plan: Fix Outstanding Issues from Typical Server Generation
|
|
2
2
|
|
|
3
3
|
## TODO
|
|
4
4
|
|
|
5
5
|
(none)
|
|
6
6
|
|
|
7
|
-
## DONE (Round 3)
|
|
8
|
-
|
|
9
|
-
- [x] Burst 30: Fix 12 — Exclude custom GraphQL input types from enum imports in query.resolver.ts.ejs
|
|
10
|
-
- [x] Burst 29: Fix 11 — SKIPPED: requires modifying standalone types.ts (blocked by type-organization rule); output is identical so no behavioral impact
|
|
11
|
-
- [x] Burst 28: Fix 10 — Remove "valid" qualifier from decide.specs.ts.ejs test descriptions
|
|
12
|
-
- [x] Burst 27: Fix 9 — Map custom GraphQL input type args to GraphQLJSON in query.resolver.ts.ejs
|
|
13
|
-
|
|
14
|
-
- [x] Burst 26: Add fart-model test for query-arg-differs-from-projection case
|
|
15
|
-
- [x] Burst 25: Exclude query arg fields from projection spec expected state (6da49a58)
|
|
16
|
-
|
|
17
7
|
## DONE
|
|
18
8
|
|
|
19
|
-
- [x] Burst
|
|
20
|
-
- [x] Burst
|
|
21
|
-
- [x] Burst
|
|
22
|
-
- [x] Burst 21: Add discriminated union guidance to evolve template (7abc038c)
|
|
23
|
-
- [x] Burst 20: Fill missing inline object fields with type defaults (b39bfd1b)
|
|
24
|
-
- [x] Burst 19: Fix formatTsValue to handle inline object types and Array<T> syntax (df459448)
|
|
25
|
-
|
|
26
|
-
- [x] Burst 18: Filter events from react Then assertions (cb94eb5d)
|
|
27
|
-
- [x] Burst 1: Slim ReadModel to find + findOne, update EJS template API docs, update all inline snapshots
|
|
28
|
-
- [x] Burst 8: Return array `idField` natively from extraction (d4633c71)
|
|
29
|
-
- [x] Burst 9: Fix `findOne` fallback with safe value resolution (8d3ee9cd)
|
|
30
|
-
- [x] Burst 10: Derive stable `metadata.now` with narrow trigger (b7bfd7bf)
|
|
31
|
-
- [x] Burst 11: Omit non-command fields from Then assertions + scaffold annotations (c6953364)
|
|
32
|
-
- [x] Burst 12: Implementer system prompt — anti-hardcoding rules for decide functions
|
|
33
|
-
- [x] Burst 13: Fix formatSpecValue and formatTsValueSimple value-type guards (2412e231)
|
|
34
|
-
- [x] Burst 14: Embed implementation instructions in scaffold templates (f211bd90)
|
|
35
|
-
- [x] Burst 15: Add expectEvents typing shim to decide.specs.ts.ejs (681db5a5)
|
|
36
|
-
- [x] Burst 16: Type-annotate aggregateStream callbacks + dynamic error listing + strengthen instructions (8693a4b3)
|
|
37
|
-
- [x] Burst 17: Filter linking field to primitive types only — skip array/object fields in react template linking (d48982d5)
|
|
9
|
+
- [x] Burst 1: Align ReactorLike return type with MessageHandlerResult (3b1217fc)
|
|
10
|
+
- [x] Burst 2: Rename `then` → `thenSends` to eliminate thenable risk (c65fa28d)
|
|
11
|
+
- [x] Burst 3: Prefix unused aggregateStream state var with `_` (04fd4cbb)
|
package/package.json
CHANGED
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"uuid": "^13.0.0",
|
|
33
33
|
"web-streams-polyfill": "^4.1.0",
|
|
34
34
|
"zod": "^3.22.4",
|
|
35
|
-
"@auto-engineer/narrative": "1.
|
|
36
|
-
"@auto-engineer/message-bus": "1.
|
|
35
|
+
"@auto-engineer/narrative": "1.125.0",
|
|
36
|
+
"@auto-engineer/message-bus": "1.125.0"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"typescript": "^5.8.3",
|
|
45
45
|
"vitest": "^3.2.4",
|
|
46
46
|
"tsx": "^4.19.2",
|
|
47
|
-
"@auto-engineer/cli": "1.
|
|
47
|
+
"@auto-engineer/cli": "1.125.0"
|
|
48
48
|
},
|
|
49
|
-
"version": "1.
|
|
49
|
+
"version": "1.125.0",
|
|
50
50
|
"scripts": {
|
|
51
51
|
"generate:server": "tsx src/cli/index.ts",
|
|
52
52
|
"build": "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",
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { parseInlineObjectFields } from '@auto-engineer/narrative';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createFieldUsesJSON,
|
|
5
|
+
findPrimitiveLinkingField,
|
|
6
|
+
isPrimitiveTsType,
|
|
7
|
+
isValidTsIdentifier,
|
|
8
|
+
sanitizeFieldType,
|
|
9
|
+
} from './type-helpers';
|
|
4
10
|
|
|
5
11
|
describe('parseInlineObjectFields', () => {
|
|
6
12
|
it('should parse simple inline object fields', () => {
|
|
@@ -165,3 +171,46 @@ describe('findPrimitiveLinkingField', () => {
|
|
|
165
171
|
expect(findPrimitiveLinkingField([{ name: 'id', type: 'string' }], [{ name: 'id', type: 'string' }])).toBe('id');
|
|
166
172
|
});
|
|
167
173
|
});
|
|
174
|
+
|
|
175
|
+
describe('createFieldUsesJSON', () => {
|
|
176
|
+
const stubGraphqlType = (ts: string): string => {
|
|
177
|
+
if (ts === 'unknown' || ts === 'any' || ts === 'object') return 'GraphQLJSON';
|
|
178
|
+
if (ts.startsWith('Record<')) return 'GraphQLJSON';
|
|
179
|
+
if (ts.startsWith('{') || ts.startsWith('Array<{')) return 'GraphQLJSON';
|
|
180
|
+
return ts;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const fieldUsesJSON = createFieldUsesJSON(stubGraphqlType);
|
|
184
|
+
|
|
185
|
+
it('returns false for inline object array without JSON subfields', () => {
|
|
186
|
+
expect(fieldUsesJSON('Array<{ exercise: string; sets: number }>')).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('returns true for inline object with unknown subfield', () => {
|
|
190
|
+
expect(fieldUsesJSON('{ data: unknown }')).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('returns true for non-inline JSON type', () => {
|
|
194
|
+
expect(fieldUsesJSON('unknown')).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('returns true for Record with unknown value', () => {
|
|
198
|
+
expect(fieldUsesJSON('Record<string, unknown>')).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('returns false for primitive type', () => {
|
|
202
|
+
expect(fieldUsesJSON('string')).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('returns true for inline object array with nested inline object subfield', () => {
|
|
206
|
+
expect(fieldUsesJSON('Array<{ workoutId: string; exercises: Array<{ exercise: string }> }>')).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('returns true for inline object with nested inline object subfield', () => {
|
|
210
|
+
expect(fieldUsesJSON('{ sessions: Array<{ id: string; reps: number }> }')).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('returns false for inline object with only primitive subfields', () => {
|
|
214
|
+
expect(fieldUsesJSON('{ name: string; count: number }')).toBe(false);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -58,10 +58,13 @@ export function createFieldUsesDate(graphqlType: (ts: string) => string) {
|
|
|
58
58
|
export function createFieldUsesJSON(graphqlType: (ts: string) => string) {
|
|
59
59
|
return (ts: string): boolean => {
|
|
60
60
|
const b = baseTs(ts);
|
|
61
|
+
if (isInlineObject(b) || isInlineObjectArray(b)) {
|
|
62
|
+
if (/:\s*(unknown|any|object)\b/.test(b)) return true;
|
|
63
|
+
const fields = parseInlineObjectFields(b);
|
|
64
|
+
return fields.some((f) => isInlineObject(f.tsType) || isInlineObjectArray(f.tsType));
|
|
65
|
+
}
|
|
61
66
|
const gqlType = graphqlType(b);
|
|
62
|
-
|
|
63
|
-
if (isInlineObject(b) || isInlineObjectArray(b)) return /:\s*(unknown|any|object)\b/.test(b);
|
|
64
|
-
return false;
|
|
67
|
+
return gqlType.includes('GraphQLJSON') || gqlType.includes('JSON');
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -676,10 +676,29 @@ async function prepareTemplateData(
|
|
|
676
676
|
messagesByName,
|
|
677
677
|
);
|
|
678
678
|
|
|
679
|
+
const normalizedSlice = normalizeSliceForTemplate(slice, allMessages);
|
|
680
|
+
|
|
681
|
+
const normalizedRules = normalizedSlice.server?.specs?.rules ?? [];
|
|
682
|
+
const eventCommandPairs: Array<{ eventType: string; commandType: string }> = [];
|
|
683
|
+
const seenPairKeys = new Set<string>();
|
|
684
|
+
for (const rule of normalizedRules) {
|
|
685
|
+
for (const example of rule.examples ?? []) {
|
|
686
|
+
const w = Array.isArray(example.when) ? example.when[0] : example.when;
|
|
687
|
+
const t = Array.isArray(example.then) ? example.then[0] : example.then;
|
|
688
|
+
if (w && 'eventRef' in w && t && 'commandRef' in t) {
|
|
689
|
+
const key = `${w.eventRef}\u2192${t.commandRef}`;
|
|
690
|
+
if (!seenPairKeys.has(key)) {
|
|
691
|
+
seenPairKeys.add(key);
|
|
692
|
+
eventCommandPairs.push({ eventType: w.eventRef, commandType: t.commandRef });
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
679
698
|
return {
|
|
680
699
|
flowName: flow.name,
|
|
681
700
|
sliceName: slice.name,
|
|
682
|
-
slice:
|
|
701
|
+
slice: normalizedSlice,
|
|
683
702
|
stream: { pattern: streamPattern, id: streamId },
|
|
684
703
|
commands: filteredCommands,
|
|
685
704
|
events,
|
|
@@ -703,6 +722,7 @@ async function prepareTemplateData(
|
|
|
703
722
|
allEventTypesArray,
|
|
704
723
|
localEvents,
|
|
705
724
|
referencedTypes,
|
|
725
|
+
eventCommandPairs,
|
|
706
726
|
};
|
|
707
727
|
}
|
|
708
728
|
|
|
@@ -113,10 +113,12 @@ describe('decide.ts.ejs', () => {
|
|
|
113
113
|
|
|
114
114
|
// All event fields come from command input — use ...command.data to pass them through.
|
|
115
115
|
|
|
116
|
-
//
|
|
116
|
+
// IMPLEMENT: Use a typed variable to prevent type widening:
|
|
117
|
+
// const result: ListingCreated = {
|
|
117
118
|
// type: 'ListingCreated',
|
|
118
119
|
// data: { ...command.data },
|
|
119
120
|
// };
|
|
121
|
+
// return result;
|
|
120
122
|
|
|
121
123
|
throw new IllegalStateError('Not yet implemented: ' + command.type);
|
|
122
124
|
}
|
|
@@ -258,10 +260,12 @@ describe('decide.ts.ejs', () => {
|
|
|
258
260
|
// Fields NOT in command input → produce dynamically (never hardcode):
|
|
259
261
|
// removedAt: Date — derive from _state, generate at runtime (e.g., crypto.randomUUID()), or compute from command.data
|
|
260
262
|
|
|
261
|
-
//
|
|
263
|
+
// IMPLEMENT: Use a typed variable to prevent type widening:
|
|
264
|
+
// const result: ListingRemoved = {
|
|
262
265
|
// type: 'ListingRemoved',
|
|
263
|
-
// data: { ...command.data, /* +
|
|
266
|
+
// data: { ...command.data, /* + produce: removedAt */ },
|
|
264
267
|
// };
|
|
268
|
+
// return result;
|
|
265
269
|
|
|
266
270
|
throw new IllegalStateError('Not yet implemented: ' + command.type);
|
|
267
271
|
}
|
|
@@ -421,10 +425,12 @@ describe('decide.ts.ejs', () => {
|
|
|
421
425
|
|
|
422
426
|
// All event fields come from command input — use ...command.data to pass them through.
|
|
423
427
|
|
|
424
|
-
//
|
|
428
|
+
// IMPLEMENT: Use a typed variable to prevent type widening:
|
|
429
|
+
// const result: ListingCreated = {
|
|
425
430
|
// type: 'ListingCreated',
|
|
426
431
|
// data: { ...command.data },
|
|
427
432
|
// };
|
|
433
|
+
// return result;
|
|
428
434
|
|
|
429
435
|
throw new IllegalStateError('Not yet implemented: ' + command.type);
|
|
430
436
|
}
|
|
@@ -635,10 +641,12 @@ describe('decide.ts.ejs', () => {
|
|
|
635
641
|
// Fields NOT in command input → produce dynamically (never hardcode):
|
|
636
642
|
// items: Array<object> — derive from _state, generate at runtime (e.g., crypto.randomUUID()), or compute from command.data
|
|
637
643
|
|
|
638
|
-
//
|
|
644
|
+
// IMPLEMENT: Use a typed variable to prevent type widening:
|
|
645
|
+
// const result: ItemsSuggested = {
|
|
639
646
|
// type: 'ItemsSuggested',
|
|
640
|
-
// data: { ...command.data, /* +
|
|
647
|
+
// data: { ...command.data, /* + produce: items */ },
|
|
641
648
|
// };
|
|
649
|
+
// return result;
|
|
642
650
|
|
|
643
651
|
throw new IllegalStateError('Not yet implemented: ' + command.type);
|
|
644
652
|
}
|
|
@@ -127,10 +127,12 @@ for (const scenario of scenarios) {
|
|
|
127
127
|
// All event fields come from command input — use ...command.data to pass them through.
|
|
128
128
|
<% } -%>
|
|
129
129
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
130
|
+
// IMPLEMENT: Use a typed variable to prevent type widening:
|
|
131
|
+
// const result: <%= pascalCase(fallbackEventTypes[0] ?? 'TODO_EVENT_TYPE') %> = {
|
|
132
|
+
// type: '<%= fallbackEventTypes[0] %>',
|
|
133
|
+
// data: { ...command.data<%= nonCommandFields.length > 0 ? `, /* + produce: ${nonCommandFields.map(f => f.name).join(', ')} */` : '' %> },
|
|
133
134
|
// };
|
|
135
|
+
// return result;
|
|
134
136
|
|
|
135
137
|
throw new IllegalStateError('Not yet implemented: ' + command.type);
|
|
136
138
|
}
|
|
@@ -333,7 +333,6 @@ describe('mutation.resolver.ts.ejs', () => {
|
|
|
333
333
|
|
|
334
334
|
expect(mutationFile?.contents).toMatchInlineSnapshot(`
|
|
335
335
|
"import { Mutation, Resolver, Arg, Ctx, Field, InputType, Float } from 'type-graphql';
|
|
336
|
-
import { GraphQLJSON } from 'graphql-type-json';
|
|
337
336
|
import { type GraphQLContext, sendCommand, MutationResponse } from '../../../shared';
|
|
338
337
|
|
|
339
338
|
@InputType()
|
|
@@ -132,7 +132,7 @@ describe('state.ts.ejs', () => {
|
|
|
132
132
|
*/
|
|
133
133
|
|
|
134
134
|
// TODO: Replace with a discriminated union of domain states for the current slice
|
|
135
|
-
export type State =
|
|
135
|
+
export type State = Record<string, never>;
|
|
136
136
|
|
|
137
137
|
// TODO: Replace the Return with the initial domain state of the current slice
|
|
138
138
|
export const initialState = (): State => {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
45
|
// TODO: Replace with a discriminated union of domain states for the current slice
|
|
46
|
-
export type State =
|
|
46
|
+
export type State = Record<string, never>;
|
|
47
47
|
|
|
48
48
|
// TODO: Replace the Return with the initial domain state of the current slice
|
|
49
49
|
export const initialState = (): State => {
|
|
@@ -1772,6 +1772,8 @@ describe('projection.specs.ts.ejs', () => {
|
|
|
1772
1772
|
|
|
1773
1773
|
type AllEvents = WorkoutRecorded;
|
|
1774
1774
|
|
|
1775
|
+
// Auto-generated — do not change this function, its imports, or type parameters.
|
|
1776
|
+
// Only implement the case bodies inside evolve.
|
|
1775
1777
|
export const projection = inMemorySingleStreamProjection<WorkoutHistory, AllEvents>({
|
|
1776
1778
|
collectionName: 'WorkoutHistoryProjection',
|
|
1777
1779
|
canHandle: ['WorkoutRecorded'],
|
|
@@ -227,6 +227,8 @@ describe('projection.ts.ejs', () => {
|
|
|
227
227
|
|
|
228
228
|
type AllEvents = ListingCreated | ListingRemoved;
|
|
229
229
|
|
|
230
|
+
// Auto-generated — do not change this function, its imports, or type parameters.
|
|
231
|
+
// Only implement the case bodies inside evolve.
|
|
230
232
|
export const projection = inMemorySingleStreamProjection<AvailableListings, AllEvents>({
|
|
231
233
|
collectionName: 'AvailablePropertiesProjection',
|
|
232
234
|
canHandle: ['ListingCreated', 'ListingRemoved'],
|
|
@@ -582,6 +584,8 @@ describe('projection.ts.ejs', () => {
|
|
|
582
584
|
|
|
583
585
|
type AllEvents = TodoAdded;
|
|
584
586
|
|
|
587
|
+
// Auto-generated — do not change this function, its imports, or type parameters.
|
|
588
|
+
// Only implement the case bodies inside evolve.
|
|
585
589
|
export const projection = inMemorySingleStreamProjection<TodoSummary, AllEvents>({
|
|
586
590
|
collectionName: 'TodoSummaryProjection',
|
|
587
591
|
canHandle: ['TodoAdded'],
|
|
@@ -809,6 +813,8 @@ describe('projection.ts.ejs', () => {
|
|
|
809
813
|
|
|
810
814
|
type AllEvents = UserJoinedProject;
|
|
811
815
|
|
|
816
|
+
// Auto-generated — do not change this function, its imports, or type parameters.
|
|
817
|
+
// Only implement the case bodies inside evolve.
|
|
812
818
|
export const projection = inMemorySingleStreamProjection<UserProject, AllEvents>({
|
|
813
819
|
collectionName: 'UserProjectsProjection',
|
|
814
820
|
canHandle: ['UserJoinedProject'],
|
|
@@ -34,7 +34,10 @@ function isQueryAction(whenText, queryName) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function formatSpecValue(value, tsType) {
|
|
37
|
-
if (value === null || value === undefined)
|
|
37
|
+
if (value === null || value === undefined) {
|
|
38
|
+
if (tsType === 'Date') return "new Date('2000-01-01T00:00:00.000Z')";
|
|
39
|
+
return 'null';
|
|
40
|
+
}
|
|
38
41
|
if ((tsType === 'string' || tsType === 'ID') && typeof value === 'string') return `'${value}'`;
|
|
39
42
|
if (tsType === 'number' || tsType === 'boolean') return String(value);
|
|
40
43
|
if (tsType === 'Date') return `new Date('${value}')`;
|
|
@@ -59,6 +59,8 @@ interface Internal<%= pascalCase(targetName || 'State') %> extends <%= pascalCas
|
|
|
59
59
|
<% } %>
|
|
60
60
|
type AllEvents = <%= allEventTypes %>;
|
|
61
61
|
|
|
62
|
+
// Auto-generated — do not change this function, its imports, or type parameters.
|
|
63
|
+
// Only implement the case bodies inside evolve.
|
|
62
64
|
export const projection = inMemorySingleStreamProjection<
|
|
63
65
|
<%= pascalCase(slice.server?.data?.items?.[0]?.target?.name || 'UnknownState') %>,
|
|
64
66
|
AllEvents
|
|
@@ -99,8 +99,8 @@ describe('query.resolver.ts.ejs', () => {
|
|
|
99
99
|
async searchProperties(
|
|
100
100
|
@Ctx() ctx: GraphQLContext,
|
|
101
101
|
@Arg('location', () => String, { nullable: true }) location?: string,
|
|
102
|
-
@Arg('maxPrice', () => Float, { nullable: true })
|
|
103
|
-
@Arg('minGuests', () => Float, { nullable: true })
|
|
102
|
+
@Arg('maxPrice', () => Float, { nullable: true }) _maxPrice?: number,
|
|
103
|
+
@Arg('minGuests', () => Float, { nullable: true }) _minGuests?: number,
|
|
104
104
|
): Promise<AvailableListings[]> {
|
|
105
105
|
const model = new ReadModel<AvailableListings>(ctx.database, 'AvailablePropertiesProjection');
|
|
106
106
|
|
|
@@ -193,7 +193,6 @@ describe('query.resolver.ts.ejs', () => {
|
|
|
193
193
|
|
|
194
194
|
expect(resolverFile?.contents).toMatchInlineSnapshot(`
|
|
195
195
|
"import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID, Float } from 'type-graphql';
|
|
196
|
-
import { GraphQLJSON } from 'graphql-type-json';
|
|
197
196
|
import { type GraphQLContext, ReadModel } from '../../../shared';
|
|
198
197
|
|
|
199
198
|
@ObjectType()
|
|
@@ -923,7 +922,7 @@ describe('query.resolver.ts.ejs', () => {
|
|
|
923
922
|
@Query(() => [RecipeMatchesRecipes])
|
|
924
923
|
async recipeMatches(
|
|
925
924
|
@Ctx() ctx: GraphQLContext,
|
|
926
|
-
@Arg('pantryId', () => ID, { nullable: true })
|
|
925
|
+
@Arg('pantryId', () => ID, { nullable: true }) _pantryId?: string,
|
|
927
926
|
): Promise<RecipeMatchesRecipes[]> {
|
|
928
927
|
const model = new ReadModel<RecipeMatchesRecipes>(ctx.database, 'RecipeMatchesProjection');
|
|
929
928
|
|
|
@@ -934,7 +933,7 @@ describe('query.resolver.ts.ejs', () => {
|
|
|
934
933
|
// The scaffolded code below uses find() returning an array.
|
|
935
934
|
// If this query should return a single item, switch to findOne().
|
|
936
935
|
|
|
937
|
-
return model.find((
|
|
936
|
+
return model.find((_item) => {
|
|
938
937
|
// NOTE: 'pantryId' has no matching field on the state type — add custom filter logic if needed.
|
|
939
938
|
|
|
940
939
|
return true;
|
|
@@ -1165,7 +1164,7 @@ describe('query.resolver.ts.ejs', () => {
|
|
|
1165
1164
|
@Query(() => [RecipeMatchesRecipes])
|
|
1166
1165
|
async recipeMatches(
|
|
1167
1166
|
@Ctx() ctx: GraphQLContext,
|
|
1168
|
-
@Arg('pantryId', () => ID, { nullable: true })
|
|
1167
|
+
@Arg('pantryId', () => ID, { nullable: true }) _pantryId?: string,
|
|
1169
1168
|
): Promise<RecipeMatchesRecipes[]> {
|
|
1170
1169
|
const model = new ReadModel<RecipeMatchesRecipes>(ctx.database, 'RecipeMatchesProjection');
|
|
1171
1170
|
|
|
@@ -1176,7 +1175,7 @@ describe('query.resolver.ts.ejs', () => {
|
|
|
1176
1175
|
// The scaffolded code below uses find() returning an array.
|
|
1177
1176
|
// If this query should return a single item, switch to findOne().
|
|
1178
1177
|
|
|
1179
|
-
return model.find((
|
|
1178
|
+
return model.find((_item) => {
|
|
1180
1179
|
// NOTE: 'pantryId' has no matching field on the state type — add custom filter logic if needed.
|
|
1181
1180
|
|
|
1182
1181
|
return true;
|
|
@@ -1246,9 +1245,9 @@ describe('query.resolver.ts.ejs', () => {
|
|
|
1246
1245
|
|
|
1247
1246
|
expect(resolverFile?.contents).toContain("import { GraphQLJSON } from 'graphql-type-json';");
|
|
1248
1247
|
expect(resolverFile?.contents).toContain(
|
|
1249
|
-
"@Arg('filter', () => GraphQLJSON, { nullable: true })
|
|
1248
|
+
"@Arg('filter', () => GraphQLJSON, { nullable: true }) _filter?: Record<string, unknown>",
|
|
1250
1249
|
);
|
|
1251
|
-
expect(resolverFile?.contents).toContain("@Arg('limit', () => Float, { nullable: true })
|
|
1250
|
+
expect(resolverFile?.contents).toContain("@Arg('limit', () => Float, { nullable: true }) _limit?: number");
|
|
1252
1251
|
expect(resolverFile?.contents).not.toContain('ListWorkoutsFilterInput');
|
|
1253
1252
|
});
|
|
1254
1253
|
});
|
|
@@ -36,6 +36,12 @@ for (const field of messageFields) {
|
|
|
36
36
|
}
|
|
37
37
|
const hasArgs = parsedRequest?.args?.length > 0;
|
|
38
38
|
|
|
39
|
+
const stateFieldNames = new Set(messageFields.map(f => f.name));
|
|
40
|
+
const usedArgNames = isSingleton
|
|
41
|
+
? new Set()
|
|
42
|
+
: new Set((parsedRequest?.args ?? []).filter(a => stateFieldNames.has(a.name)).map(a => a.name));
|
|
43
|
+
const hasMatchingArgs = usedArgNames.size > 0;
|
|
44
|
+
|
|
39
45
|
const resolveArgTypes = (arg) => {
|
|
40
46
|
const isCustom = !KNOWN_GQL_SCALARS.has(arg.graphqlType);
|
|
41
47
|
return {
|
|
@@ -118,7 +124,7 @@ async <%= queryName %>(
|
|
|
118
124
|
<% for (let i = 0; i < parsedRequest.args.length; i++) {
|
|
119
125
|
const arg = parsedRequest.args[i];
|
|
120
126
|
const { gqlType, tsType } = resolveArgTypes(arg);
|
|
121
|
-
%> @Arg('<%= arg.name %>', () => <%= gqlType %>, { nullable: true }) <%= arg.name %>?: <%= tsType %><%= i < parsedRequest.args.length - 1 ? ',' : '' %>
|
|
127
|
+
%> @Arg('<%= arg.name %>', () => <%= gqlType %>, { nullable: true }) <%= usedArgNames.has(arg.name) ? arg.name : '_' + arg.name %>?: <%= tsType %><%= i < parsedRequest.args.length - 1 ? ',' : '' %>
|
|
122
128
|
<% } } %>
|
|
123
129
|
): Promise<<%= viewType %>> {
|
|
124
130
|
const result = await ctx.database.collection<<%= viewType %>>('<%= collectionName %>').findOne();
|
|
@@ -160,7 +166,7 @@ async <%= queryName %>(
|
|
|
160
166
|
<% for (let i = 0; i < parsedRequest.args.length; i++) {
|
|
161
167
|
const arg = parsedRequest.args[i];
|
|
162
168
|
const { gqlType, tsType } = resolveArgTypes(arg);
|
|
163
|
-
%> @Arg('<%= arg.name %>', () => <%= gqlType %>, { nullable: true }) <%= arg.name %>?: <%= tsType %><%= i < parsedRequest.args.length - 1 ? ',' : '' %>
|
|
169
|
+
%> @Arg('<%= arg.name %>', () => <%= gqlType %>, { nullable: true }) <%= usedArgNames.has(arg.name) ? arg.name : '_' + arg.name %>?: <%= tsType %><%= i < parsedRequest.args.length - 1 ? ',' : '' %>
|
|
164
170
|
<% } } %>
|
|
165
171
|
): Promise<<%= viewType %>[]> {
|
|
166
172
|
const model = new ReadModel<<%= viewType %>>(ctx.database, '<%= collectionName %>');
|
|
@@ -172,7 +178,7 @@ const model = new ReadModel<<%= viewType %>>(ctx.database, '<%= collectionName %
|
|
|
172
178
|
// The scaffolded code below uses find() returning an array.
|
|
173
179
|
// If this query should return a single item, switch to findOne().
|
|
174
180
|
|
|
175
|
-
return model.find((<%=
|
|
181
|
+
return model.find((<%= hasMatchingArgs ? 'item' : '_item' %>) => {
|
|
176
182
|
<% if (parsedRequest?.args?.length) {
|
|
177
183
|
const stateFieldNames = new Set(messageFields.map(f => f.name));
|
|
178
184
|
for (const arg of parsedRequest.args) {
|
|
@@ -217,7 +217,7 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
217
217
|
},
|
|
218
218
|
})
|
|
219
219
|
|
|
220
|
-
.
|
|
220
|
+
.thenSends({
|
|
221
221
|
type: 'NotifyHost',
|
|
222
222
|
kind: 'Command',
|
|
223
223
|
data: {
|
|
@@ -457,7 +457,7 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
457
457
|
expect(specFile?.contents).toContain('type ReactorCommand = SendNotification;');
|
|
458
458
|
expect(specFile?.contents).not.toContain('MilestoneNotified');
|
|
459
459
|
expect(specFile?.contents).toContain("type: 'SendNotification'");
|
|
460
|
-
expect(specFile?.contents).toContain('.
|
|
460
|
+
expect(specFile?.contents).toContain('.thenSends({');
|
|
461
461
|
});
|
|
462
462
|
|
|
463
463
|
it('should seed event store with Given state data via appendToStream', async () => {
|
|
@@ -637,7 +637,7 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
637
637
|
},
|
|
638
638
|
})
|
|
639
639
|
|
|
640
|
-
.
|
|
640
|
+
.thenSends({
|
|
641
641
|
type: 'NotifyBarber',
|
|
642
642
|
kind: 'Command',
|
|
643
643
|
data: {
|
|
@@ -233,7 +233,7 @@ describe('handle.ts.ejs (react slice)', () => {
|
|
|
233
233
|
const handleFile = plans.find((p) => p.outputPath.endsWith('react.ts'));
|
|
234
234
|
|
|
235
235
|
expect(handleFile?.contents).toMatchInlineSnapshot(`
|
|
236
|
-
"import { inMemoryReactor, type MessageHandlerResult
|
|
236
|
+
"import { inMemoryReactor, type MessageHandlerResult } from '@event-driven-io/emmett';
|
|
237
237
|
import type { BookingRequested } from '../guest-submits-booking-request/events';
|
|
238
238
|
import type { ReactorContext } from '../../../shared';
|
|
239
239
|
|
|
@@ -244,7 +244,7 @@ describe('handle.ts.ejs (react slice)', () => {
|
|
|
244
244
|
connectionOptions: {
|
|
245
245
|
database,
|
|
246
246
|
},
|
|
247
|
-
eachMessage: async (event
|
|
247
|
+
eachMessage: async (event): Promise<MessageHandlerResult> => {
|
|
248
248
|
/**
|
|
249
249
|
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
250
250
|
*
|
|
@@ -162,13 +162,13 @@ describe('<%= ruleDescription %>', () => {
|
|
|
162
162
|
<% if (thenCommands.length === 1) {
|
|
163
163
|
const commandSchema = thenCommands[0];
|
|
164
164
|
%>
|
|
165
|
-
.
|
|
165
|
+
.thenSends({
|
|
166
166
|
type: '<%= commandSchema.commandRef %>',
|
|
167
167
|
kind: 'Command',
|
|
168
168
|
data: <%- formatDataObject(commandSchema.exampleData, commands.find(c => c.type === commandSchema.commandRef)) %>
|
|
169
169
|
});
|
|
170
170
|
<% } else { %>
|
|
171
|
-
.
|
|
171
|
+
.thenSends([
|
|
172
172
|
<% for (const cmd of thenCommands) { %>
|
|
173
173
|
{
|
|
174
174
|
type: '<%= cmd.commandRef %>',
|