@auto-engineer/server-generator-apollo-emmett 0.10.4 → 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 +20 -0
- package/dist/src/codegen/extract/events.d.ts +2 -2
- package/dist/src/codegen/extract/events.d.ts.map +1 -1
- package/dist/src/codegen/extract/events.js +16 -6
- package/dist/src/codegen/extract/events.js.map +1 -1
- package/dist/src/codegen/extract/gwt.js +7 -22
- package/dist/src/codegen/extract/gwt.js.map +1 -1
- package/dist/src/codegen/extract/imports.d.ts +29 -0
- package/dist/src/codegen/extract/imports.d.ts.map +1 -0
- package/dist/src/codegen/extract/imports.js +55 -0
- package/dist/src/codegen/extract/imports.js.map +1 -0
- package/dist/src/codegen/extract/index.d.ts +1 -0
- package/dist/src/codegen/extract/index.d.ts.map +1 -1
- package/dist/src/codegen/extract/index.js +1 -0
- package/dist/src/codegen/extract/index.js.map +1 -1
- package/dist/src/codegen/extract/messages.d.ts.map +1 -1
- package/dist/src/codegen/extract/messages.js +33 -7
- package/dist/src/codegen/extract/messages.js.map +1 -1
- package/dist/src/codegen/extract/query.d.ts +3 -1
- package/dist/src/codegen/extract/query.d.ts.map +1 -1
- package/dist/src/codegen/extract/query.js +12 -12
- package/dist/src/codegen/extract/query.js.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +9 -1
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/decide.specs.specs.ts +235 -8
- package/dist/src/codegen/templates/command/decide.specs.ts +8 -8
- package/dist/src/codegen/templates/command/decide.specs.ts.ejs +95 -30
- package/dist/src/codegen/templates/command/decide.ts.ejs +2 -2
- package/dist/src/codegen/templates/command/events.ts.ejs +2 -2
- package/dist/src/codegen/templates/command/evolve.ts.ejs +3 -3
- package/dist/src/codegen/templates/command/handle.specs.ts +6 -6
- package/dist/src/codegen/templates/command/handle.ts.ejs +3 -3
- package/dist/src/codegen/templates/query/projection.specs.specs.ts +623 -0
- package/dist/src/codegen/templates/query/projection.specs.ts +1 -1
- package/dist/src/codegen/templates/query/projection.specs.ts.ejs +176 -52
- package/dist/src/codegen/templates/query/projection.ts.ejs +30 -29
- 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 +8 -5
- package/dist/src/codegen/templates/react/react.specs.ts +4 -4
- package/dist/src/codegen/templates/react/react.specs.ts.ejs +118 -67
- 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/codegen/types.d.ts +2 -0
- package/dist/src/codegen/types.d.ts.map +1 -1
- 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/extract/events.ts +20 -3
- package/src/codegen/extract/gwt.ts +10 -26
- package/src/codegen/extract/imports.ts +71 -0
- package/src/codegen/extract/index.ts +1 -0
- package/src/codegen/extract/messages.ts +34 -7
- package/src/codegen/extract/query.ts +17 -19
- package/src/codegen/scaffoldFromSchema.ts +13 -0
- package/src/codegen/templates/command/decide.specs.specs.ts +235 -8
- package/src/codegen/templates/command/decide.specs.ts +8 -8
- package/src/codegen/templates/command/decide.specs.ts.ejs +95 -30
- package/src/codegen/templates/command/decide.ts.ejs +2 -2
- package/src/codegen/templates/command/events.ts.ejs +2 -2
- package/src/codegen/templates/command/evolve.ts.ejs +3 -3
- package/src/codegen/templates/command/handle.specs.ts +6 -6
- package/src/codegen/templates/command/handle.ts.ejs +3 -3
- package/src/codegen/templates/query/projection.specs.specs.ts +623 -0
- package/src/codegen/templates/query/projection.specs.ts +1 -1
- package/src/codegen/templates/query/projection.specs.ts.ejs +176 -52
- package/src/codegen/templates/query/projection.ts.ejs +30 -29
- 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 +8 -5
- package/src/codegen/templates/react/react.specs.ts +4 -4
- package/src/codegen/templates/react/react.specs.ts.ejs +118 -67
- 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/codegen/types.ts +2 -0
- 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
- package/dist/src/codegen/templates/query/projection.specs.specs..ts +0 -296
- package/src/codegen/scaffoldFromSchema.query-slice-register.specs.ts +0 -179
- package/src/codegen/templates/query/projection.specs.specs..ts +0 -296
|
@@ -173,15 +173,18 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
173
173
|
import type { BookingRequested } from '../guest-submits-booking-request/events';
|
|
174
174
|
import type { NotifyHost } from '../send-notification-to-host/commands';
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
type ReactorEvent = BookingRequested;
|
|
177
|
+
type ReactorCommand = NotifyHost;
|
|
178
|
+
|
|
179
|
+
describe('Should send host notification on booking request', () => {
|
|
177
180
|
let eventStore: InMemoryEventStore;
|
|
178
|
-
let given: ReactorSpecification<
|
|
181
|
+
let given: ReactorSpecification<ReactorEvent, ReactorCommand, ReactorContext>;
|
|
179
182
|
let messageBus: CommandSender;
|
|
180
183
|
|
|
181
184
|
beforeEach(() => {
|
|
182
185
|
eventStore = getInMemoryEventStore({});
|
|
183
|
-
given = ReactorSpecification.for<
|
|
184
|
-
() => react({ eventStore, commandSender: messageBus }),
|
|
186
|
+
given = ReactorSpecification.for<ReactorEvent, ReactorCommand, ReactorContext>(
|
|
187
|
+
() => react({ eventStore, commandSender: messageBus, database: eventStore.database }),
|
|
185
188
|
(commandSender) => {
|
|
186
189
|
messageBus = commandSender;
|
|
187
190
|
return {
|
|
@@ -193,7 +196,7 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
193
196
|
);
|
|
194
197
|
});
|
|
195
198
|
|
|
196
|
-
it('
|
|
199
|
+
it('Booking request triggers host notification', async () => {
|
|
197
200
|
await given([])
|
|
198
201
|
.when({
|
|
199
202
|
type: 'BookingRequested',
|
|
@@ -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: {
|
|
@@ -1,88 +1,139 @@
|
|
|
1
1
|
<%
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const gwtList = specs?.rules?.flatMap(rule =>
|
|
5
|
-
rule.examples.map(example => ({
|
|
6
|
-
given: example.given,
|
|
7
|
-
when: example.when,
|
|
8
|
-
then: example.then
|
|
9
|
-
}))
|
|
10
|
-
) ?? [];
|
|
11
|
-
const firstGwt = gwtList[0];
|
|
12
|
-
const firstWhen = Array.isArray(firstGwt?.when) ? firstGwt.when[0] : firstGwt?.when;
|
|
13
|
-
const firstThen = Array.isArray(firstGwt?.then) ? firstGwt.then[0] : firstGwt?.then;
|
|
2
|
+
const ruleGroups = new Map();
|
|
3
|
+
const rules = slice.server?.specs?.rules || [];
|
|
14
4
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
5
|
+
const allUsedEvents = new Set();
|
|
6
|
+
const allUsedCommands = new Set();
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
const
|
|
8
|
+
for (const rule of rules) {
|
|
9
|
+
const ruleDescription = rule.description || `${flowName} | ${sliceName}`;
|
|
10
|
+
if (!ruleGroups.has(ruleDescription)) {
|
|
11
|
+
ruleGroups.set(ruleDescription, []);
|
|
12
|
+
}
|
|
20
13
|
|
|
21
|
-
const
|
|
22
|
-
|
|
14
|
+
for (const example of rule.examples || []) {
|
|
15
|
+
ruleGroups.get(ruleDescription).push({
|
|
16
|
+
description: example.description || 'should react correctly',
|
|
17
|
+
given: example.given || [],
|
|
18
|
+
when: example.when || [],
|
|
19
|
+
then: example.then || []
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const whenEvents = Array.isArray(example.when) ? example.when : (example.when ? [example.when] : []);
|
|
23
|
+
for (const evt of whenEvents) {
|
|
24
|
+
if (evt.eventRef) allUsedEvents.add(evt.eventRef);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const thenCommands = Array.isArray(example.then) ? example.then : (example.then ? [example.then] : []);
|
|
28
|
+
for (const cmd of thenCommands) {
|
|
29
|
+
if (cmd.commandRef) allUsedCommands.add(cmd.commandRef);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const eventImportGroups = new Map();
|
|
35
|
+
const commandImportGroups = new Map();
|
|
36
|
+
|
|
37
|
+
for (const eventType of allUsedEvents) {
|
|
38
|
+
const event = events.find(e => e.type === eventType);
|
|
39
|
+
if (event) {
|
|
40
|
+
const importPath = event.sourceSliceName ? `../${toKebabCase(event.sourceSliceName)}/events` : './events';
|
|
41
|
+
if (!eventImportGroups.has(importPath)) {
|
|
42
|
+
eventImportGroups.set(importPath, []);
|
|
43
|
+
}
|
|
44
|
+
eventImportGroups.get(importPath).push(pascalCase(eventType));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const commandType of allUsedCommands) {
|
|
49
|
+
const command = commands.find(c => c.type === commandType);
|
|
50
|
+
if (command) {
|
|
51
|
+
const importPath = command.sourceSliceName ? `../${toKebabCase(command.sourceSliceName)}/commands` : './commands';
|
|
52
|
+
if (!commandImportGroups.has(importPath)) {
|
|
53
|
+
commandImportGroups.set(importPath, []);
|
|
54
|
+
}
|
|
55
|
+
commandImportGroups.get(importPath).push(pascalCase(commandType));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const allEventTypes = Array.from(allUsedEvents).map(e => pascalCase(e)).sort();
|
|
60
|
+
const allCommandTypes = Array.from(allUsedCommands).map(c => pascalCase(c)).sort();
|
|
23
61
|
%>
|
|
24
62
|
import { describe, it, beforeEach } from 'vitest';
|
|
25
63
|
import 'reflect-metadata';
|
|
26
64
|
import {
|
|
27
|
-
getInMemoryEventStore,
|
|
28
|
-
type InMemoryEventStore,
|
|
29
|
-
type CommandSender,
|
|
65
|
+
getInMemoryEventStore,
|
|
66
|
+
type InMemoryEventStore,
|
|
67
|
+
type CommandSender,
|
|
30
68
|
} from '@event-driven-io/emmett';
|
|
31
69
|
import { type ReactorContext, ReactorSpecification } from '../../../shared';
|
|
32
70
|
import { react } from './react';
|
|
33
|
-
|
|
34
|
-
import type { <%=
|
|
71
|
+
<% for (const [importPath, typeNames] of eventImportGroups.entries()) { -%>
|
|
72
|
+
import type { <%= typeNames.sort().join(', ') %> } from '<%= importPath %>';
|
|
73
|
+
<% } -%>
|
|
74
|
+
<% for (const [importPath, typeNames] of commandImportGroups.entries()) { -%>
|
|
75
|
+
import type { <%= typeNames.sort().join(', ') %> } from '<%= importPath %>';
|
|
76
|
+
<% } -%>
|
|
35
77
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
let given: ReactorSpecification<<%= pascalCase(eventType) %>, <%= pascalCase(commandType) %>, ReactorContext>;
|
|
39
|
-
let messageBus: CommandSender;
|
|
78
|
+
type ReactorEvent = <%= allEventTypes.length ? allEventTypes.join(' | ') : 'never' %>;
|
|
79
|
+
type ReactorCommand = <%= allCommandTypes.length ? allCommandTypes.join(' | ') : 'never' %>;
|
|
40
80
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
eventStore
|
|
49
|
-
|
|
50
|
-
database: eventStore.database,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
<% for (const [ruleDescription, ruleTests] of ruleGroups.entries()) { %>
|
|
82
|
+
describe('<%= ruleDescription %>', () => {
|
|
83
|
+
let eventStore: InMemoryEventStore;
|
|
84
|
+
let given: ReactorSpecification<ReactorEvent, ReactorCommand, ReactorContext>;
|
|
85
|
+
let messageBus: CommandSender;
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
eventStore = getInMemoryEventStore({});
|
|
89
|
+
given = ReactorSpecification.for<ReactorEvent, ReactorCommand, ReactorContext>(
|
|
90
|
+
() => react({ eventStore, commandSender: messageBus, database: eventStore.database }),
|
|
91
|
+
(commandSender) => {
|
|
92
|
+
messageBus = commandSender;
|
|
93
|
+
return {
|
|
94
|
+
eventStore,
|
|
95
|
+
commandSender,
|
|
96
|
+
database: eventStore.database,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
});
|
|
55
101
|
|
|
56
|
-
<% for (const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
102
|
+
<% for (const testCase of ruleTests) {
|
|
103
|
+
const whenEvents = Array.isArray(testCase.when) ? testCase.when : (testCase.when ? [testCase.when] : []);
|
|
104
|
+
const thenCommands = Array.isArray(testCase.then) ? testCase.then : (testCase.then ? [testCase.then] : []);
|
|
105
|
+
|
|
106
|
+
if (whenEvents.length > 0 && thenCommands.length > 0) {
|
|
107
|
+
const exampleEvent = whenEvents[0];
|
|
108
|
+
const description = testCase.description ||
|
|
109
|
+
`should send ${thenCommands.map(c => c.commandRef).join(', ')} when ${exampleEvent.eventRef} is received`;
|
|
60
110
|
%>
|
|
61
|
-
it('<%= description %>', async () => {
|
|
62
|
-
await given([])
|
|
63
|
-
.when({
|
|
64
|
-
type: '<%= exampleEvent.eventRef %>',
|
|
65
|
-
data: <%- formatDataObject(exampleEvent.exampleData, events.find(e => e.type === exampleEvent.eventRef)) %>
|
|
66
|
-
})
|
|
67
|
-
<% if (
|
|
68
|
-
const commandSchema =
|
|
111
|
+
it('<%= description %>', async () => {
|
|
112
|
+
await given([])
|
|
113
|
+
.when({
|
|
114
|
+
type: '<%= exampleEvent.eventRef %>',
|
|
115
|
+
data: <%- formatDataObject(exampleEvent.exampleData, events.find(e => e.type === exampleEvent.eventRef)) %>
|
|
116
|
+
})
|
|
117
|
+
<% if (thenCommands.length === 1) {
|
|
118
|
+
const commandSchema = thenCommands[0];
|
|
69
119
|
%>
|
|
70
|
-
.then({
|
|
71
|
-
type: '<%= commandSchema.commandRef %>',
|
|
72
|
-
kind: 'Command',
|
|
73
|
-
data: <%- formatDataObject(commandSchema.exampleData, messages.find(m => m.name === commandSchema.commandRef && m.type === 'command')) %>
|
|
74
|
-
});
|
|
120
|
+
.then({
|
|
121
|
+
type: '<%= commandSchema.commandRef %>',
|
|
122
|
+
kind: 'Command',
|
|
123
|
+
data: <%- formatDataObject(commandSchema.exampleData, messages.find(m => m.name === commandSchema.commandRef && m.type === 'command')) %>
|
|
124
|
+
});
|
|
75
125
|
<% } else { %>
|
|
76
|
-
|
|
77
|
-
|
|
126
|
+
.then([
|
|
127
|
+
<% for (const cmd of thenCommands) { %>
|
|
78
128
|
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
129
|
+
type: '<%= cmd.commandRef %>',
|
|
130
|
+
kind: 'Command',
|
|
131
|
+
data: <%- formatDataObject(cmd.exampleData, messages.find(m => m.name === cmd.commandRef && m.type === 'command')) %>
|
|
82
132
|
},
|
|
83
|
-
<% } %>
|
|
84
|
-
]);
|
|
85
133
|
<% } %>
|
|
86
|
-
|
|
134
|
+
]);
|
|
87
135
|
<% } %>
|
|
88
|
-
});
|
|
136
|
+
});
|
|
137
|
+
<% } } %>
|
|
138
|
+
});
|
|
139
|
+
<% } -%>
|
|
@@ -22,12 +22,12 @@ IllegalStateError,
|
|
|
22
22
|
import type { <%= pascalCase(eventType) %> } from '../<%= toKebabCase(event?.sourceSliceName ?? 'unknown') %>/events';
|
|
23
23
|
import type { ReactorContext } from '../../../shared';
|
|
24
24
|
|
|
25
|
-
export const react = ({ eventStore, commandSender }: ReactorContext) =>
|
|
25
|
+
export const react = ({ eventStore, commandSender, database }: ReactorContext) =>
|
|
26
26
|
inMemoryReactor<<%= pascalCase(eventType) %>>({
|
|
27
27
|
processorId: '<%= toKebabCase(flowName) %>-<%= toKebabCase(slice.name) %>',
|
|
28
28
|
canHandle: ['<%= eventType %>'],
|
|
29
29
|
connectionOptions: {
|
|
30
|
-
database
|
|
30
|
+
database,
|
|
31
31
|
},
|
|
32
32
|
eachMessage: async (event, context): Promise<MessageHandlerResult> => {
|
|
33
33
|
/**
|
|
@@ -35,7 +35,7 @@ eachMessage: async (event, context): Promise<MessageHandlerResult> => {
|
|
|
35
35
|
*
|
|
36
36
|
* - Inspect event data to determine if the command should be sent.
|
|
37
37
|
* - Replace the placeholder logic and \`throw\` below with real implementation.
|
|
38
|
-
* - Send one or more commands via:
|
|
38
|
+
* - Send one or more commands via: commandSender.send({...})
|
|
39
39
|
* - Optionally return a MessageHandlerResult for SKIP or error cases.
|
|
40
40
|
*/
|
|
41
41
|
|
|
@@ -49,7 +49,7 @@ eachMessage: async (event, context): Promise<MessageHandlerResult> => {
|
|
|
49
49
|
// };
|
|
50
50
|
// }
|
|
51
51
|
|
|
52
|
-
// await
|
|
52
|
+
// await commandSender.send({
|
|
53
53
|
// type: '<%= commandType %>',
|
|
54
54
|
// kind: 'Command',
|
|
55
55
|
// data: {
|
|
@@ -219,10 +219,10 @@ describe('register.ts.ejs (react slice)', () => {
|
|
|
219
219
|
const registerFile = plans.find((p) => p.outputPath.endsWith('send-notification-to-host/register.ts'));
|
|
220
220
|
|
|
221
221
|
expect(registerFile?.contents).toMatchInlineSnapshot(`
|
|
222
|
-
"import { type CommandSender, type EventSubscription, type
|
|
222
|
+
"import { type CommandSender, type EventSubscription, type EventStore } from '@event-driven-io/emmett';
|
|
223
223
|
import type { BookingRequested } from '../guest-submits-booking-request/events';
|
|
224
224
|
|
|
225
|
-
export async function register(messageBus: CommandSender & EventSubscription, eventStore:
|
|
225
|
+
export async function register(messageBus: CommandSender & EventSubscription, eventStore: EventStore) {
|
|
226
226
|
messageBus.subscribe(async (event: BookingRequested) => {
|
|
227
227
|
/**
|
|
228
228
|
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
@@ -14,12 +14,12 @@ const eventType = when?.eventRef;
|
|
|
14
14
|
const commandType = then?.commandRef;
|
|
15
15
|
const event = events.find(e => e.type === eventType);
|
|
16
16
|
%>
|
|
17
|
-
import { type CommandSender, type EventSubscription, type
|
|
17
|
+
import { type CommandSender, type EventSubscription, type EventStore } from '@event-driven-io/emmett';
|
|
18
18
|
import type { <%= pascalCase(eventType) %> } from '../<%= toKebabCase(event?.sourceSliceName ?? 'unknown') %>/events';
|
|
19
19
|
|
|
20
20
|
export async function register(
|
|
21
21
|
messageBus: CommandSender & EventSubscription,
|
|
22
|
-
eventStore:
|
|
22
|
+
eventStore: EventStore
|
|
23
23
|
) {
|
|
24
24
|
messageBus.subscribe(
|
|
25
25
|
async (event: <%= pascalCase(eventType) %>) => {
|
package/src/codegen/types.ts
CHANGED
|
@@ -32,4 +32,6 @@ export interface GwtCondition {
|
|
|
32
32
|
given?: Array<EventExample | StateExample>;
|
|
33
33
|
when: CommandExample | EventExample[];
|
|
34
34
|
then: Array<EventExample | StateExample | CommandExample | { errorType: string; message?: string }>;
|
|
35
|
+
description?: string;
|
|
36
|
+
ruleDescription?: string;
|
|
35
37
|
}
|
|
@@ -415,9 +415,11 @@ async function writePackage(dest: string): Promise<void> {
|
|
|
415
415
|
'type-check': 'tsc --noEmit',
|
|
416
416
|
test: 'vitest run',
|
|
417
417
|
dev: 'tsx --no-deprecation src/server.ts',
|
|
418
|
+
postinstall: 'npm rebuild sqlite3 2>/dev/null || true',
|
|
418
419
|
},
|
|
419
420
|
dependencies: resolveDeps([
|
|
420
421
|
'@event-driven-io/emmett',
|
|
422
|
+
'@event-driven-io/emmett-sqlite',
|
|
421
423
|
'type-graphql',
|
|
422
424
|
'graphql-type-json',
|
|
423
425
|
'graphql',
|
|
@@ -426,6 +428,7 @@ async function writePackage(dest: string): Promise<void> {
|
|
|
426
428
|
'zod',
|
|
427
429
|
'apollo-server',
|
|
428
430
|
'uuid',
|
|
431
|
+
'sqlite3',
|
|
429
432
|
]),
|
|
430
433
|
devDependencies: resolveDeps(['typescript', 'vitest', 'tsx']),
|
|
431
434
|
} as const;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InMemoryDatabase } from '@event-driven-io/emmett';
|
|
2
2
|
|
|
3
3
|
export class ReadModel<T extends Record<string, unknown>> {
|
|
4
4
|
private collection;
|
|
5
5
|
|
|
6
|
-
constructor(
|
|
7
|
-
this.collection =
|
|
6
|
+
constructor(database: InMemoryDatabase, collectionName: string) {
|
|
7
|
+
this.collection = database.collection<T>(collectionName);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async getAll(): Promise<T[]> {
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { CommandSender,
|
|
1
|
+
import { CommandSender, EventStore, type InMemoryDatabase } from '@event-driven-io/emmett';
|
|
2
2
|
import { Field, ObjectType } from 'type-graphql';
|
|
3
3
|
|
|
4
4
|
export interface ReactorContext {
|
|
5
|
-
eventStore:
|
|
5
|
+
eventStore: EventStore;
|
|
6
6
|
commandSender: CommandSender;
|
|
7
|
+
database: InMemoryDatabase;
|
|
7
8
|
[key: string]: unknown;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export interface GraphQLContext {
|
|
11
|
-
eventStore:
|
|
12
|
+
eventStore: EventStore;
|
|
12
13
|
messageBus: CommandSender;
|
|
14
|
+
database: InMemoryDatabase;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
@ObjectType()
|
package/src/server.ts
CHANGED
|
@@ -2,38 +2,76 @@ import 'reflect-metadata';
|
|
|
2
2
|
import { ApolloServer } from 'apollo-server';
|
|
3
3
|
import { buildSchema } from 'type-graphql';
|
|
4
4
|
import { loadProjections, loadRegisterFiles, loadResolvers } from './utils';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
getInMemoryMessageBus,
|
|
8
|
-
projections,
|
|
9
|
-
forwardToMessageBus,
|
|
10
|
-
} from '@event-driven-io/emmett';
|
|
5
|
+
import { getInMemoryMessageBus, getInMemoryDatabase, handleInMemoryProjections } from '@event-driven-io/emmett';
|
|
6
|
+
import { getSQLiteEventStore } from '@event-driven-io/emmett-sqlite';
|
|
11
7
|
|
|
12
8
|
async function start() {
|
|
13
9
|
const loadedProjections = await loadProjections('src/domain/flows/**/projection.{ts,js}');
|
|
14
10
|
const registrations = await loadRegisterFiles('src/domain/flows/**/register.{ts,js}');
|
|
15
11
|
|
|
16
12
|
const messageBus = getInMemoryMessageBus();
|
|
13
|
+
const database = getInMemoryDatabase();
|
|
17
14
|
|
|
18
|
-
const eventStore =
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
onAfterCommit: forwardToMessageBus(messageBus),
|
|
22
|
-
},
|
|
15
|
+
const eventStore = getSQLiteEventStore({
|
|
16
|
+
fileName: './event-store.sqlite',
|
|
17
|
+
schema: { autoMigration: 'CreateOrUpdate' },
|
|
23
18
|
});
|
|
24
|
-
|
|
19
|
+
try {
|
|
20
|
+
await eventStore.readStream('__init__');
|
|
21
|
+
} catch {
|
|
22
|
+
// Expected on fresh DB - schema gets created on first operation
|
|
23
|
+
}
|
|
25
24
|
await Promise.all(registrations.map((r) => r.register(messageBus, eventStore)));
|
|
26
|
-
|
|
25
|
+
const consumer = eventStore.consumer();
|
|
26
|
+
consumer.processor({
|
|
27
|
+
processorId: 'projection-updater',
|
|
28
|
+
startFrom: 'BEGINNING',
|
|
29
|
+
eachMessage: async (event) => {
|
|
30
|
+
await handleInMemoryProjections({
|
|
31
|
+
projections: loadedProjections,
|
|
32
|
+
database,
|
|
33
|
+
events: [event],
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
consumer.processor({
|
|
38
|
+
processorId: 'forward-to-message-bus',
|
|
39
|
+
startFrom: 'BEGINNING',
|
|
40
|
+
eachMessage: async (evt) => {
|
|
41
|
+
await messageBus.publish(evt);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
consumer.start().catch((err) => {
|
|
45
|
+
console.error('Consumer crashed:', err);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
const shutdown = async () => {
|
|
49
|
+
console.log('Shutting down...');
|
|
50
|
+
try {
|
|
51
|
+
await consumer.stop?.();
|
|
52
|
+
} catch {
|
|
53
|
+
/* empty */
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await consumer.close?.();
|
|
57
|
+
} catch {
|
|
58
|
+
/* empty */
|
|
59
|
+
}
|
|
60
|
+
process.exit(0);
|
|
61
|
+
};
|
|
62
|
+
process.on('SIGINT', () => void shutdown());
|
|
63
|
+
process.on('SIGTERM', () => void shutdown());
|
|
27
64
|
const resolvers = await loadResolvers('src/domain/flows/**/*.resolver.{ts,js}');
|
|
65
|
+
type ResolverClass = new (...args: unknown[]) => unknown;
|
|
28
66
|
const schema = await buildSchema({
|
|
29
|
-
|
|
30
|
-
resolvers: resolvers as unknown as [(...args: any[]) => any, ...Array<(...args: any[]) => any>],
|
|
67
|
+
resolvers: resolvers as unknown as [ResolverClass, ...ResolverClass[]],
|
|
31
68
|
});
|
|
32
69
|
const server = new ApolloServer({
|
|
33
70
|
schema,
|
|
34
71
|
context: () => ({
|
|
35
72
|
eventStore,
|
|
36
73
|
messageBus,
|
|
74
|
+
database,
|
|
37
75
|
}),
|
|
38
76
|
});
|
|
39
77
|
const { url } = await server.listen({ port: 4000 });
|