@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
|
@@ -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 %>',
|
|
@@ -1,51 +1,50 @@
|
|
|
1
1
|
<%
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
given: firstExample.given,
|
|
6
|
-
when: firstExample.when,
|
|
7
|
-
then: firstExample.then
|
|
8
|
-
} : null;
|
|
9
|
-
const when = Array.isArray(gwt?.when) ? gwt.when[0] : gwt?.when;
|
|
10
|
-
const then = Array.isArray(gwt?.then) ? gwt.then[0] : gwt?.then;
|
|
2
|
+
if (!eventCommandPairs || eventCommandPairs.length === 0) {
|
|
3
|
+
throw new Error(`react.ts.ejs: slice "${slice.name}" has no event→command pairs — check specs`);
|
|
4
|
+
}
|
|
11
5
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
const importGroups = new Map();
|
|
7
|
+
for (const pair of eventCommandPairs) {
|
|
8
|
+
const event = events.find(e => e.type === pair.eventType);
|
|
9
|
+
const isCrossFlow = event?.sourceFlowName && event.sourceFlowName !== flowName;
|
|
10
|
+
const importBase = isCrossFlow
|
|
11
|
+
? `../../${toKebabCase(event.sourceFlowName)}/${toKebabCase(event.sourceSliceName)}`
|
|
12
|
+
: event?.sourceSliceName ? `../${toKebabCase(event.sourceSliceName)}` : '.';
|
|
13
|
+
if (!importGroups.has(importBase)) importGroups.set(importBase, []);
|
|
14
|
+
const typeName = pascalCase(pair.eventType);
|
|
15
|
+
if (!importGroups.get(importBase).includes(typeName)) {
|
|
16
|
+
importGroups.get(importBase).push(typeName);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const allEventTypeNames = eventCommandPairs.map(p => pascalCase(p.eventType));
|
|
21
|
+
const uniqueEventTypeNames = [...new Set(allEventTypeNames)];
|
|
22
|
+
const eventUnion = uniqueEventTypeNames.join(' | ');
|
|
23
|
+
|
|
24
|
+
const willHaveAnyAggregateStream = eventCommandPairs.some(pair => {
|
|
25
|
+
const eventDef = messages.find(m => m.name === pair.eventType);
|
|
26
|
+
return states.some(state =>
|
|
27
|
+
findPrimitiveLinkingField(state.fields, eventDef?.fields || []) !== undefined
|
|
28
|
+
);
|
|
29
|
+
});
|
|
25
30
|
-%>
|
|
26
31
|
import {
|
|
27
32
|
inMemoryReactor,
|
|
28
33
|
type MessageHandlerResult,
|
|
29
|
-
IllegalStateError,
|
|
30
34
|
} from '@event-driven-io/emmett';
|
|
31
|
-
|
|
35
|
+
<% for (const [importBase, types] of importGroups) { -%>
|
|
36
|
+
import type { <%= types.join(', ') %> } from '<%= importBase %>/events';
|
|
37
|
+
<% } -%>
|
|
32
38
|
import type { ReactorContext } from '../../../shared';
|
|
33
39
|
|
|
34
40
|
export const react = ({ eventStore, commandSender, database }: ReactorContext) =>
|
|
35
|
-
inMemoryReactor<<%=
|
|
41
|
+
inMemoryReactor<<%= eventUnion %>>({
|
|
36
42
|
processorId: '<%= toKebabCase(flowName) %>-<%= toKebabCase(slice.name) %>',
|
|
37
|
-
canHandle: [
|
|
43
|
+
canHandle: [<%= eventCommandPairs.map(p => `'${p.eventType}'`).join(', ') %>],
|
|
38
44
|
connectionOptions: {
|
|
39
45
|
database,
|
|
40
46
|
},
|
|
41
|
-
eachMessage: async (event
|
|
42
|
-
<%
|
|
43
|
-
const eventDef = messages.find(m => m.name === eventType);
|
|
44
|
-
const commandDef = messages.find(m => m.name === commandType && m.type === 'command');
|
|
45
|
-
const willHaveAggregateStream = states.some(state =>
|
|
46
|
-
findPrimitiveLinkingField(state.fields, eventDef?.fields || []) !== undefined
|
|
47
|
-
);
|
|
48
|
-
-%>
|
|
47
|
+
eachMessage: async (event): Promise<MessageHandlerResult> => {
|
|
49
48
|
/**
|
|
50
49
|
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
51
50
|
*
|
|
@@ -57,7 +56,7 @@ const willHaveAggregateStream = states.some(state =>
|
|
|
57
56
|
* NEVER hardcode values copied from test assertions.
|
|
58
57
|
*
|
|
59
58
|
* Preserve all import paths above — they are generated from the model.
|
|
60
|
-
<% if (
|
|
59
|
+
<% if (willHaveAnyAggregateStream) { -%>
|
|
61
60
|
* Do NOT modify or remove aggregateStream calls — they load required state.
|
|
62
61
|
<% } -%>
|
|
63
62
|
*
|
|
@@ -69,31 +68,38 @@ const willHaveAggregateStream = states.some(state =>
|
|
|
69
68
|
*
|
|
70
69
|
* Add business logic (validation, conditional sends) as needed.
|
|
71
70
|
*/
|
|
72
|
-
<%
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const
|
|
71
|
+
<% if (eventCommandPairs.length === 1) {
|
|
72
|
+
const pair = eventCommandPairs[0];
|
|
73
|
+
const eventDef = messages.find(m => m.name === pair.eventType);
|
|
74
|
+
const commandDef = messages.find(m => m.name === pair.commandType && m.type === 'command');
|
|
75
|
+
const commandFields = (commandDef?.fields || []);
|
|
76
|
+
const eventFieldSet = new Set((eventDef?.fields || []).map(f => f.name));
|
|
77
|
+
const stateFieldSources = {};
|
|
76
78
|
-%>
|
|
77
79
|
<% if (eventDef?.fields?.length) { %>
|
|
78
|
-
// Event (<%= eventType %>) fields: <%= eventDef.fields.map(f => f.name + ': ' + (f.tsType || f.type)).join(', ') %>
|
|
80
|
+
// Event (<%= pair.eventType %>) fields: <%= eventDef.fields.map(f => f.name + ': ' + (f.tsType || f.type)).join(', ') %>
|
|
79
81
|
<% } -%>
|
|
80
82
|
<% if (commandDef?.fields?.length) { %>
|
|
81
|
-
// Command (<%= commandType %>) fields: <%= commandDef.fields.map(f => f.name + ': ' + (f.tsType || f.type)).join(', ') %>
|
|
83
|
+
// Command (<%= pair.commandType %>) fields: <%= commandDef.fields.map(f => f.name + ': ' + (f.tsType || f.type)).join(', ') %>
|
|
82
84
|
<% } -%>
|
|
83
85
|
<% if (states.length > 0) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
let hasAggregateStream = false;
|
|
87
|
+
for (const state of states) {
|
|
88
|
+
const linkingField = findPrimitiveLinkingField(state.fields, eventDef?.fields || []);
|
|
89
|
+
if (linkingField) {
|
|
90
|
+
hasAggregateStream = true;
|
|
91
|
+
const varName = camelCase(state.type);
|
|
92
|
+
for (const f of state.fields) {
|
|
93
|
+
if (!stateFieldSources[f.name]) stateFieldSources[f.name] = varName;
|
|
94
|
+
}
|
|
95
|
+
const stateUsedByCommand = commandFields.some(f =>
|
|
96
|
+
!eventFieldSet.has(f.name) && state.fields.some(sf => sf.name === f.name)
|
|
97
|
+
);
|
|
98
|
+
const varPrefix = stateUsedByCommand ? '' : '_';
|
|
93
99
|
-%>
|
|
94
100
|
|
|
95
|
-
const { state: <%= varName %> } = await eventStore.aggregateStream(
|
|
96
|
-
|
|
101
|
+
const { state: <%= varPrefix %><%= varName %> } = await eventStore.aggregateStream(
|
|
102
|
+
`<%= state.type %>-${event.data.<%= linkingField %>}`,
|
|
97
103
|
{
|
|
98
104
|
evolve: (currentState: Record<string, unknown>, evt: { type: string; data: Record<string, unknown> }) => ({ ...currentState, ...evt.data }),
|
|
99
105
|
initialState: (): Record<string, unknown> => ({}),
|
|
@@ -101,35 +107,103 @@ const stateFieldSources = {};
|
|
|
101
107
|
);
|
|
102
108
|
// <%= state.type %> fields: <%= state.fields.map(f => f.name).join(', ') %>
|
|
103
109
|
|
|
104
|
-
<%
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
<% }
|
|
111
|
+
}
|
|
112
|
+
if (!hasAggregateStream) {
|
|
107
113
|
-%>
|
|
108
114
|
|
|
109
|
-
<%
|
|
110
|
-
} else {
|
|
115
|
+
<% }
|
|
116
|
+
} else {
|
|
111
117
|
-%>
|
|
112
118
|
|
|
113
119
|
<% } -%>
|
|
114
120
|
await commandSender.send({
|
|
115
|
-
type: '<%= commandType %>',
|
|
121
|
+
type: '<%= pair.commandType %>',
|
|
116
122
|
kind: 'Command',
|
|
117
123
|
data: {
|
|
118
124
|
<% for (const field of commandFields) {
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
const fieldName = field.name;
|
|
126
|
+
if (eventFieldSet.has(fieldName)) {
|
|
121
127
|
-%>
|
|
122
128
|
<%= fieldName %>: event.data.<%= fieldName %>,
|
|
123
|
-
<%
|
|
129
|
+
<% } else if (stateFieldSources[fieldName]) { -%>
|
|
124
130
|
<%= fieldName %>: <%= stateFieldSources[fieldName] %>.<%= fieldName %>,
|
|
125
|
-
<%
|
|
131
|
+
<% } else { -%>
|
|
126
132
|
<%= fieldName %>: undefined, // TODO: source unknown
|
|
127
|
-
<%
|
|
128
|
-
|
|
133
|
+
<% }
|
|
134
|
+
}
|
|
129
135
|
-%>
|
|
130
136
|
},
|
|
131
137
|
});
|
|
132
138
|
|
|
133
139
|
return;
|
|
140
|
+
<% } else {
|
|
141
|
+
for (let pairIdx = 0; pairIdx < eventCommandPairs.length; pairIdx++) {
|
|
142
|
+
const pair = eventCommandPairs[pairIdx];
|
|
143
|
+
const eventDef = messages.find(m => m.name === pair.eventType);
|
|
144
|
+
const commandDef = messages.find(m => m.name === pair.commandType && m.type === 'command');
|
|
145
|
+
const commandFields = (commandDef?.fields || []);
|
|
146
|
+
const eventFieldSet = new Set((eventDef?.fields || []).map(f => f.name));
|
|
147
|
+
const stateFieldSources = {};
|
|
148
|
+
const condition = pairIdx === 0 ? 'if' : '} else if';
|
|
149
|
+
-%>
|
|
150
|
+
<%= condition %> (event.type === '<%= pair.eventType %>') {
|
|
151
|
+
<% if (eventDef?.fields?.length) { %>
|
|
152
|
+
// Event (<%= pair.eventType %>) fields: <%= eventDef.fields.map(f => f.name + ': ' + (f.tsType || f.type)).join(', ') %>
|
|
153
|
+
<% } -%>
|
|
154
|
+
<% if (commandDef?.fields?.length) { %>
|
|
155
|
+
// Command (<%= pair.commandType %>) fields: <%= commandDef.fields.map(f => f.name + ': ' + (f.tsType || f.type)).join(', ') %>
|
|
156
|
+
<% } -%>
|
|
157
|
+
<% if (states.length > 0) {
|
|
158
|
+
for (const state of states) {
|
|
159
|
+
const linkingField = findPrimitiveLinkingField(state.fields, eventDef?.fields || []);
|
|
160
|
+
if (linkingField) {
|
|
161
|
+
const varName = camelCase(state.type);
|
|
162
|
+
for (const f of state.fields) {
|
|
163
|
+
if (!stateFieldSources[f.name]) stateFieldSources[f.name] = varName;
|
|
164
|
+
}
|
|
165
|
+
const stateUsedByCommand = commandFields.some(f =>
|
|
166
|
+
!eventFieldSet.has(f.name) && state.fields.some(sf => sf.name === f.name)
|
|
167
|
+
);
|
|
168
|
+
const varPrefix = stateUsedByCommand ? '' : '_';
|
|
169
|
+
-%>
|
|
170
|
+
|
|
171
|
+
const { state: <%= varPrefix %><%= varName %> } = await eventStore.aggregateStream(
|
|
172
|
+
`<%= state.type %>-${event.data.<%= linkingField %>}`,
|
|
173
|
+
{
|
|
174
|
+
evolve: (currentState: Record<string, unknown>, evt: { type: string; data: Record<string, unknown> }) => ({ ...currentState, ...evt.data }),
|
|
175
|
+
initialState: (): Record<string, unknown> => ({}),
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
// <%= state.type %> fields: <%= state.fields.map(f => f.name).join(', ') %>
|
|
179
|
+
|
|
180
|
+
<% }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
-%>
|
|
184
|
+
await commandSender.send({
|
|
185
|
+
type: '<%= pair.commandType %>',
|
|
186
|
+
kind: 'Command',
|
|
187
|
+
data: {
|
|
188
|
+
<% for (const field of commandFields) {
|
|
189
|
+
const fieldName = field.name;
|
|
190
|
+
if (eventFieldSet.has(fieldName)) {
|
|
191
|
+
-%>
|
|
192
|
+
<%= fieldName %>: event.data.<%= fieldName %>,
|
|
193
|
+
<% } else if (stateFieldSources[fieldName]) { -%>
|
|
194
|
+
<%= fieldName %>: <%= stateFieldSources[fieldName] %>.<%= fieldName %>,
|
|
195
|
+
<% } else { -%>
|
|
196
|
+
<%= fieldName %>: undefined, // TODO: source unknown
|
|
197
|
+
<% }
|
|
198
|
+
}
|
|
199
|
+
-%>
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
<% }
|
|
203
|
+
-%>
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return;
|
|
207
|
+
<% } -%>
|
|
134
208
|
},
|
|
135
209
|
});
|