@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.
Files changed (104) hide show
  1. package/.turbo/turbo-build.log +6 -0
  2. package/.turbo/turbo-format.log +5 -0
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/.turbo/turbo-test.log +22 -0
  5. package/.turbo/turbo-type-check.log +5 -0
  6. package/CHANGELOG.md +20 -0
  7. package/dist/src/codegen/extract/events.d.ts +2 -2
  8. package/dist/src/codegen/extract/events.d.ts.map +1 -1
  9. package/dist/src/codegen/extract/events.js +16 -6
  10. package/dist/src/codegen/extract/events.js.map +1 -1
  11. package/dist/src/codegen/extract/gwt.js +7 -22
  12. package/dist/src/codegen/extract/gwt.js.map +1 -1
  13. package/dist/src/codegen/extract/imports.d.ts +29 -0
  14. package/dist/src/codegen/extract/imports.d.ts.map +1 -0
  15. package/dist/src/codegen/extract/imports.js +55 -0
  16. package/dist/src/codegen/extract/imports.js.map +1 -0
  17. package/dist/src/codegen/extract/index.d.ts +1 -0
  18. package/dist/src/codegen/extract/index.d.ts.map +1 -1
  19. package/dist/src/codegen/extract/index.js +1 -0
  20. package/dist/src/codegen/extract/index.js.map +1 -1
  21. package/dist/src/codegen/extract/messages.d.ts.map +1 -1
  22. package/dist/src/codegen/extract/messages.js +33 -7
  23. package/dist/src/codegen/extract/messages.js.map +1 -1
  24. package/dist/src/codegen/extract/query.d.ts +3 -1
  25. package/dist/src/codegen/extract/query.d.ts.map +1 -1
  26. package/dist/src/codegen/extract/query.js +12 -12
  27. package/dist/src/codegen/extract/query.js.map +1 -1
  28. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
  29. package/dist/src/codegen/scaffoldFromSchema.js +9 -1
  30. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
  31. package/dist/src/codegen/templates/command/decide.specs.specs.ts +235 -8
  32. package/dist/src/codegen/templates/command/decide.specs.ts +8 -8
  33. package/dist/src/codegen/templates/command/decide.specs.ts.ejs +95 -30
  34. package/dist/src/codegen/templates/command/decide.ts.ejs +2 -2
  35. package/dist/src/codegen/templates/command/events.ts.ejs +2 -2
  36. package/dist/src/codegen/templates/command/evolve.ts.ejs +3 -3
  37. package/dist/src/codegen/templates/command/handle.specs.ts +6 -6
  38. package/dist/src/codegen/templates/command/handle.ts.ejs +3 -3
  39. package/dist/src/codegen/templates/query/projection.specs.specs.ts +623 -0
  40. package/dist/src/codegen/templates/query/projection.specs.ts +1 -1
  41. package/dist/src/codegen/templates/query/projection.specs.ts.ejs +176 -52
  42. package/dist/src/codegen/templates/query/projection.ts.ejs +30 -29
  43. package/dist/src/codegen/templates/query/query.resolver.specs.ts +190 -5
  44. package/dist/src/codegen/templates/query/query.resolver.ts.ejs +31 -9
  45. package/dist/src/codegen/templates/react/react.specs.specs.ts +8 -5
  46. package/dist/src/codegen/templates/react/react.specs.ts +4 -4
  47. package/dist/src/codegen/templates/react/react.specs.ts.ejs +118 -67
  48. package/dist/src/codegen/templates/react/react.ts.ejs +4 -4
  49. package/dist/src/codegen/templates/react/register.specs.ts +2 -2
  50. package/dist/src/codegen/templates/react/register.ts.ejs +2 -2
  51. package/dist/src/codegen/types.d.ts +2 -0
  52. package/dist/src/codegen/types.d.ts.map +1 -1
  53. package/dist/src/commands/generate-server.d.ts.map +1 -1
  54. package/dist/src/commands/generate-server.js +3 -0
  55. package/dist/src/commands/generate-server.js.map +1 -1
  56. package/dist/src/domain/shared/ReadModel.d.ts +2 -2
  57. package/dist/src/domain/shared/ReadModel.d.ts.map +1 -1
  58. package/dist/src/domain/shared/ReadModel.js +2 -2
  59. package/dist/src/domain/shared/ReadModel.js.map +1 -1
  60. package/dist/src/domain/shared/ReadModel.ts +3 -3
  61. package/dist/src/domain/shared/types.d.ts +5 -3
  62. package/dist/src/domain/shared/types.d.ts.map +1 -1
  63. package/dist/src/domain/shared/types.js.map +1 -1
  64. package/dist/src/domain/shared/types.ts +5 -3
  65. package/dist/src/server.js +54 -7
  66. package/dist/src/server.js.map +1 -1
  67. package/dist/src/server.ts +53 -15
  68. package/dist/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +8 -5
  70. package/src/codegen/extract/events.ts +20 -3
  71. package/src/codegen/extract/gwt.ts +10 -26
  72. package/src/codegen/extract/imports.ts +71 -0
  73. package/src/codegen/extract/index.ts +1 -0
  74. package/src/codegen/extract/messages.ts +34 -7
  75. package/src/codegen/extract/query.ts +17 -19
  76. package/src/codegen/scaffoldFromSchema.ts +13 -0
  77. package/src/codegen/templates/command/decide.specs.specs.ts +235 -8
  78. package/src/codegen/templates/command/decide.specs.ts +8 -8
  79. package/src/codegen/templates/command/decide.specs.ts.ejs +95 -30
  80. package/src/codegen/templates/command/decide.ts.ejs +2 -2
  81. package/src/codegen/templates/command/events.ts.ejs +2 -2
  82. package/src/codegen/templates/command/evolve.ts.ejs +3 -3
  83. package/src/codegen/templates/command/handle.specs.ts +6 -6
  84. package/src/codegen/templates/command/handle.ts.ejs +3 -3
  85. package/src/codegen/templates/query/projection.specs.specs.ts +623 -0
  86. package/src/codegen/templates/query/projection.specs.ts +1 -1
  87. package/src/codegen/templates/query/projection.specs.ts.ejs +176 -52
  88. package/src/codegen/templates/query/projection.ts.ejs +30 -29
  89. package/src/codegen/templates/query/query.resolver.specs.ts +190 -5
  90. package/src/codegen/templates/query/query.resolver.ts.ejs +31 -9
  91. package/src/codegen/templates/react/react.specs.specs.ts +8 -5
  92. package/src/codegen/templates/react/react.specs.ts +4 -4
  93. package/src/codegen/templates/react/react.specs.ts.ejs +118 -67
  94. package/src/codegen/templates/react/react.ts.ejs +4 -4
  95. package/src/codegen/templates/react/register.specs.ts +2 -2
  96. package/src/codegen/templates/react/register.ts.ejs +2 -2
  97. package/src/codegen/types.ts +2 -0
  98. package/src/commands/generate-server.ts +3 -0
  99. package/src/domain/shared/ReadModel.ts +3 -3
  100. package/src/domain/shared/types.ts +5 -3
  101. package/src/server.ts +53 -15
  102. package/dist/src/codegen/templates/query/projection.specs.specs..ts +0 -296
  103. package/src/codegen/scaffoldFromSchema.query-slice-register.specs.ts +0 -179
  104. 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
- describe('ManageBookings | SendNotificationToHost', () => {
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<BookingRequested, NotifyHost, ReactorContext>;
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<BookingRequested, NotifyHost, ReactorContext>(
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('should send NotifyHost when BookingRequested is received', async () => {
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: eventStore.database,
231
+ database,
232
232
  },
233
233
  eachMessage: async (event, context): Promise<MessageHandlerResult> => {
234
234
  /**
@@ -236,7 +236,7 @@ describe('handle.ts.ejs (react slice)', () => {
236
236
  *
237
237
  * - Inspect event data to determine if the command should be sent.
238
238
  * - Replace the placeholder logic and \\\`throw\\\` below with real implementation.
239
- * - Send one or more commands via: context.commandSender.send({...})
239
+ * - Send one or more commands via: commandSender.send({...})
240
240
  * - Optionally return a MessageHandlerResult for SKIP or error cases.
241
241
  */
242
242
 
@@ -250,7 +250,7 @@ describe('handle.ts.ejs (react slice)', () => {
250
250
  // };
251
251
  // }
252
252
 
253
- // await context.commandSender.send({
253
+ // await commandSender.send({
254
254
  // type: 'NotifyHost',
255
255
  // kind: 'Command',
256
256
  // data: {
@@ -1,88 +1,139 @@
1
1
  <%
2
- // Extract all examples from specs/rules structure
3
- const specs = slice.server?.specs;
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 eventType = firstWhen?.eventRef;
16
- const commandType = firstThen?.commandRef;
5
+ const allUsedEvents = new Set();
6
+ const allUsedCommands = new Set();
17
7
 
18
- const event = events.find(e => e.type === eventType);
19
- const command = commands.find(c => c.type === commandType);
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 eventImportPath = toKebabCase(event?.sourceSliceName ?? 'unknown');
22
- const commandImportPath = toKebabCase(command?.sourceSliceName ?? 'unknown');
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
- import type { <%= pascalCase(eventType) %> } from '../<%= eventImportPath %>/events';
34
- import type { <%= pascalCase(commandType) %> } from '../<%= commandImportPath %>/commands';
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
- describe('<%= pascalCase(flowName) %> | <%= pascalCase(slice.name) %>', () => {
37
- let eventStore: InMemoryEventStore;
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
- beforeEach(() => {
42
- eventStore = getInMemoryEventStore({});
43
- given = ReactorSpecification.for<<%= pascalCase(eventType) %>, <%= pascalCase(commandType) %>, ReactorContext>(
44
- () => react({ eventStore, commandSender: messageBus }),
45
- (commandSender) => {
46
- messageBus = commandSender;
47
- return {
48
- eventStore,
49
- commandSender,
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 example of gwtList) {
57
- const exampleEvent = Array.isArray(example.when) ? example.when[0] : example.when;
58
- const commands = example.then;
59
- const description = `should send ${commands.map(c => c.commandRef).join(', ')} when ${exampleEvent.eventRef} is received`;
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 (commands.length === 1) {
68
- const commandSchema = commands[0];
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
- .then([
77
- <% for (const cmd of commands) { %>
126
+ .then([
127
+ <% for (const cmd of thenCommands) { %>
78
128
  {
79
- type: '<%= cmd.commandRef %>',
80
- kind: 'Command',
81
- data: <%- formatDataObject(cmd.exampleData, messages.find(m => m.name === cmd.commandRef && m.type === 'command')) %>
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: eventStore.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: context.commandSender.send({...})
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 context.commandSender.send({
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 InMemoryEventStore } from '@event-driven-io/emmett';
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: InMemoryEventStore) {
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 InMemoryEventStore } from '@event-driven-io/emmett';
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: InMemoryEventStore
22
+ eventStore: EventStore
23
23
  ) {
24
24
  messageBus.subscribe(
25
25
  async (event: <%= pascalCase(eventType) %>) => {
@@ -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 { InMemoryEventStore } from '@event-driven-io/emmett';
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(eventStore: InMemoryEventStore, collectionName: string) {
7
- this.collection = eventStore.database.collection<T>(collectionName);
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, InMemoryEventStore } from '@event-driven-io/emmett';
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: InMemoryEventStore;
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: InMemoryEventStore;
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
- getInMemoryEventStore,
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 = getInMemoryEventStore({
19
- projections: projections.inline(loadedProjections),
20
- hooks: {
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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 });