@auto-engineer/server-generator-apollo-emmett 0.1.4 → 0.2.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 +117 -171
- package/.turbo/turbo-type-check.log +4 -5
- package/CHANGELOG.md +12 -0
- package/dist/commands/generate-server.d.ts +11 -45
- package/dist/commands/generate-server.d.ts.map +1 -1
- package/dist/commands/generate-server.js +29 -24
- package/dist/commands/generate-server.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/generate-server.ts +29 -28
- package/src/index.ts +8 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/.tmp/server-test-output/server/package.json +0 -26
- package/.tmp/server-test-output/server/scripts/generate-schema.ts +0 -31
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/commands.ts +0 -8
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/decide.specs.ts +0 -36
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/decide.ts +0 -33
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/events.ts +0 -10
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/evolve.ts +0 -28
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/handle.ts +0 -24
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/mutation.resolver.ts +0 -25
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/register.ts +0 -7
- package/.tmp/server-test-output/server/src/domain/flows/add-item/create-item/state.ts +0 -47
- package/.tmp/server-test-output/server/src/domain/flows/add-item/get-available-items/projection.specs.ts +0 -46
- package/.tmp/server-test-output/server/src/domain/flows/add-item/get-available-items/projection.ts +0 -38
- package/.tmp/server-test-output/server/src/domain/flows/add-item/get-available-items/query.resolver.ts +0 -39
- package/.tmp/server-test-output/server/src/domain/flows/add-item/get-available-items/state.ts +0 -5
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/commands.ts +0 -8
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/decide.specs.ts +0 -36
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/decide.ts +0 -33
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/events.ts +0 -3
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/evolve.ts +0 -28
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/handle.ts +0 -24
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/mutation.resolver.ts +0 -25
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/register.ts +0 -7
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-new-item/state.ts +0 -47
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/react.specs.ts +0 -49
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/react.ts +0 -43
- package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/register.ts +0 -24
- package/.tmp/server-test-output/server/src/domain/shared/ReadModel.ts +0 -26
- package/.tmp/server-test-output/server/src/domain/shared/index.ts +0 -4
- package/.tmp/server-test-output/server/src/domain/shared/reactorSpecification.ts +0 -257
- package/.tmp/server-test-output/server/src/domain/shared/sendCommand.ts +0 -21
- package/.tmp/server-test-output/server/src/domain/shared/types.ts +0 -31
- package/.tmp/server-test-output/server/src/server.ts +0 -43
- package/.tmp/server-test-output/server/src/utils/index.ts +0 -3
- package/.tmp/server-test-output/server/src/utils/loadProjections.ts +0 -30
- package/.tmp/server-test-output/server/src/utils/loadRegisterFiles.ts +0 -41
- package/.tmp/server-test-output/server/src/utils/loadResolvers.ts +0 -36
- package/.tmp/server-test-output/server/tsconfig.json +0 -19
- package/.tmp/server-test-output/server/vitest.config.ts +0 -7
- package/.turbo/turbo-format.log +0 -57
- package/.turbo/turbo-lint.log +0 -5
- package/dist/cli-manifest.d.ts +0 -3
- package/dist/cli-manifest.d.ts.map +0 -1
- package/dist/cli-manifest.js +0 -35
- package/dist/cli-manifest.js.map +0 -1
- package/src/cli-manifest.ts +0 -37
package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/react.specs.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'vitest';
|
|
2
|
-
import 'reflect-metadata';
|
|
3
|
-
import { getInMemoryEventStore, type InMemoryEventStore, type CommandSender } from '@event-driven-io/emmett';
|
|
4
|
-
import { type ReactorContext, ReactorSpecification } from '../../../shared';
|
|
5
|
-
import { react } from './react';
|
|
6
|
-
import type { ItemCreated } from '../create-item/events';
|
|
7
|
-
import type { NotifyNewItem } from '../notify-new-item/commands';
|
|
8
|
-
|
|
9
|
-
describe('AddItem | NotifyOnNewItem', () => {
|
|
10
|
-
let eventStore: InMemoryEventStore;
|
|
11
|
-
let given: ReactorSpecification<ItemCreated, NotifyNewItem, ReactorContext>;
|
|
12
|
-
let messageBus: CommandSender;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
eventStore = getInMemoryEventStore({});
|
|
16
|
-
given = ReactorSpecification.for<ItemCreated, NotifyNewItem, ReactorContext>(
|
|
17
|
-
() => react({ eventStore, commandSender: messageBus }),
|
|
18
|
-
(commandSender) => {
|
|
19
|
-
messageBus = commandSender;
|
|
20
|
-
return {
|
|
21
|
-
eventStore,
|
|
22
|
-
commandSender,
|
|
23
|
-
database: eventStore.database,
|
|
24
|
-
};
|
|
25
|
-
},
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should send NotifyNewItem when ItemCreated is received', async () => {
|
|
30
|
-
await given([])
|
|
31
|
-
.when({
|
|
32
|
-
type: 'ItemCreated',
|
|
33
|
-
data: {
|
|
34
|
-
id: 'item_123',
|
|
35
|
-
description: 'A new item',
|
|
36
|
-
addedAt: new Date('2024-01-15T10:00:00.000Z'),
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
.then({
|
|
41
|
-
type: 'NotifyNewItem',
|
|
42
|
-
kind: 'Command',
|
|
43
|
-
data: {
|
|
44
|
-
itemId: 'item_123',
|
|
45
|
-
message: 'A new item was added to the system.',
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
});
|
package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/react.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { inMemoryReactor, type MessageHandlerResult, IllegalStateError } from '@event-driven-io/emmett';
|
|
2
|
-
import type { ItemCreated } from '../create-item/events';
|
|
3
|
-
import type { ReactorContext } from '../../../shared';
|
|
4
|
-
|
|
5
|
-
export const react = ({ eventStore, commandSender }: ReactorContext) =>
|
|
6
|
-
inMemoryReactor<ItemCreated>({
|
|
7
|
-
processorId: 'add-item-notify-on-new-item',
|
|
8
|
-
canHandle: ['ItemCreated'],
|
|
9
|
-
connectionOptions: {
|
|
10
|
-
database: eventStore.database,
|
|
11
|
-
},
|
|
12
|
-
eachMessage: async (event, context): Promise<MessageHandlerResult> => {
|
|
13
|
-
/**
|
|
14
|
-
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
15
|
-
*
|
|
16
|
-
* - Inspect event data to determine if the command should be sent.
|
|
17
|
-
* - Replace the placeholder logic and \`throw\` below with real implementation.
|
|
18
|
-
* - Send one or more commands via: context.commandSender.send({...})
|
|
19
|
-
* - Optionally return a MessageHandlerResult for SKIP or error cases.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
throw new IllegalStateError('Not yet implemented: react in response to ItemCreated');
|
|
23
|
-
|
|
24
|
-
// Example:
|
|
25
|
-
// if (event.data.status !== 'expected') {
|
|
26
|
-
// return {
|
|
27
|
-
// type: 'SKIP',
|
|
28
|
-
// reason: 'Condition not met',
|
|
29
|
-
// };
|
|
30
|
-
// }
|
|
31
|
-
|
|
32
|
-
// await context.commandSender.send({
|
|
33
|
-
// type: 'NotifyNewItem',
|
|
34
|
-
// kind: 'Command',
|
|
35
|
-
// data: {
|
|
36
|
-
// // Map event fields to command fields here
|
|
37
|
-
// // e.g., userId: event.data.userId,
|
|
38
|
-
// },
|
|
39
|
-
// });
|
|
40
|
-
|
|
41
|
-
// return;
|
|
42
|
-
},
|
|
43
|
-
});
|
package/.tmp/server-test-output/server/src/domain/flows/add-item/notify-on-new-item/register.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { type CommandSender, type EventSubscription, type InMemoryEventStore } from '@event-driven-io/emmett';
|
|
2
|
-
import type { ItemCreated } from '../create-item/events';
|
|
3
|
-
|
|
4
|
-
export async function register(messageBus: CommandSender & EventSubscription, eventStore: InMemoryEventStore) {
|
|
5
|
-
messageBus.subscribe(async (event: ItemCreated) => {
|
|
6
|
-
/**
|
|
7
|
-
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
8
|
-
*
|
|
9
|
-
* - Replace the placeholder logic with the real implementation.
|
|
10
|
-
* - Send one or more commands via: messageBus.send({...})
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// await messageBus.send({
|
|
14
|
-
// type: 'NotifyNewItem',
|
|
15
|
-
// kind: 'Command',
|
|
16
|
-
// data: {
|
|
17
|
-
// // Map event fields to command fields here
|
|
18
|
-
// // e.g., userId: event.data.userId,
|
|
19
|
-
// },
|
|
20
|
-
// });
|
|
21
|
-
|
|
22
|
-
return;
|
|
23
|
-
}, 'ItemCreated');
|
|
24
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { InMemoryEventStore } from '@event-driven-io/emmett';
|
|
2
|
-
|
|
3
|
-
export class ReadModel<T extends Record<string, unknown>> {
|
|
4
|
-
private collection;
|
|
5
|
-
|
|
6
|
-
constructor(eventStore: InMemoryEventStore, collectionName: string) {
|
|
7
|
-
this.collection = eventStore.database.collection<T>(collectionName);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async getAll(): Promise<T[]> {
|
|
11
|
-
return this.collection.find();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async getById(id: string, idField: keyof T = 'id' as keyof T): Promise<T | null> {
|
|
15
|
-
return this.collection.findOne((doc) => doc[idField] === id);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async find(filterFn: (item: T) => boolean): Promise<T[]> {
|
|
19
|
-
return this.collection.find(filterFn);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async first(filterFn: (item: T) => boolean): Promise<T | null> {
|
|
23
|
-
const all = await this.collection.find(filterFn);
|
|
24
|
-
return all[0] ?? null;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CommandSender,
|
|
3
|
-
isErrorConstructor,
|
|
4
|
-
type ErrorConstructor,
|
|
5
|
-
AssertionError,
|
|
6
|
-
assertTrue,
|
|
7
|
-
type MessageProcessor,
|
|
8
|
-
} from '@event-driven-io/emmett';
|
|
9
|
-
|
|
10
|
-
interface CommandCheck<CommandType> {
|
|
11
|
-
(command: CommandType): boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface ErrorCheck<ErrorType> {
|
|
15
|
-
(error: ErrorType): boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type ThenThrows<ErrorType extends Error> =
|
|
19
|
-
| (() => void)
|
|
20
|
-
| ((errorConstructor: ErrorConstructor<ErrorType>) => void)
|
|
21
|
-
| ((errorCheck: ErrorCheck<ErrorType>) => void)
|
|
22
|
-
| ((errorConstructor: ErrorConstructor<ErrorType>, errorCheck?: ErrorCheck<ErrorType>) => void);
|
|
23
|
-
|
|
24
|
-
interface ReactorSpecificationReturn<Event, Command, Context> {
|
|
25
|
-
when: (
|
|
26
|
-
event: Event | Event[],
|
|
27
|
-
context?: Context,
|
|
28
|
-
) => {
|
|
29
|
-
then: (expectedCommand: Command | Command[] | CommandCheck<Command> | CommandCheck<Command[]>) => Promise<void>;
|
|
30
|
-
thenNothingHappened: () => Promise<void>;
|
|
31
|
-
thenThrows: <ErrorType extends Error = Error>(...args: Parameters<ThenThrows<ErrorType>>) => Promise<void>;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ReactorSpecification<
|
|
36
|
-
Event,
|
|
37
|
-
Command,
|
|
38
|
-
Context extends { commandSender: CommandSender } = { commandSender: CommandSender },
|
|
39
|
-
> {
|
|
40
|
-
(givenEvents: Event | Event[]): ReactorSpecificationReturn<Event, Command, Context>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const ReactorSpecification = {
|
|
44
|
-
for: reactorSpecificationFor,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
interface MockCommandSender<Command> extends CommandSender {
|
|
48
|
-
sentCommands: Command[];
|
|
49
|
-
reset: () => void;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function createMockCommandSender<Command>(): MockCommandSender<Command> {
|
|
53
|
-
const sentCommands: Command[] = [];
|
|
54
|
-
|
|
55
|
-
const send = async <CommandType extends Command = Command>(command: CommandType): Promise<void> => {
|
|
56
|
-
sentCommands.push(command);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
send,
|
|
61
|
-
sentCommands,
|
|
62
|
-
reset: () => {
|
|
63
|
-
sentCommands.length = 0;
|
|
64
|
-
},
|
|
65
|
-
} as MockCommandSender<Command>;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
type ReactorLike<Event, Context> =
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
-
| { handle: (events: Event[], context: Context) => Promise<any> }
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
-
| { eachMessage: (event: Event, context: Context) => Promise<any> }
|
|
73
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
-
| MessageProcessor<any, any, any>;
|
|
75
|
-
|
|
76
|
-
function reactorSpecificationFor<Event, Command, Context extends { commandSender: CommandSender }>(
|
|
77
|
-
processorOrReactor: ReactorLike<Event, Context> | (() => ReactorLike<Event, Context>),
|
|
78
|
-
createContext: (commandSender: CommandSender) => Context,
|
|
79
|
-
): ReactorSpecification<Event, Command, Context> {
|
|
80
|
-
return (givenEvents: Event | Event[]) => {
|
|
81
|
-
return {
|
|
82
|
-
when: (whenEvent: Event | Event[], contextOverride?: Context) => {
|
|
83
|
-
const mockCommandSender = createMockCommandSender<Command>();
|
|
84
|
-
const defaultContext = createContext(mockCommandSender);
|
|
85
|
-
const context = contextOverride || defaultContext;
|
|
86
|
-
|
|
87
|
-
const handle = async () => {
|
|
88
|
-
const givenArray = Array.isArray(givenEvents) ? givenEvents : givenEvents != null ? [givenEvents] : [];
|
|
89
|
-
const whenArray = Array.isArray(whenEvent) ? whenEvent : [whenEvent];
|
|
90
|
-
const allEvents = [...givenArray, ...whenArray];
|
|
91
|
-
|
|
92
|
-
// Transform events to have metadata
|
|
93
|
-
const eventsWithMetadata = allEvents.map((event, index) => ({
|
|
94
|
-
...event,
|
|
95
|
-
kind: 'Event' as const,
|
|
96
|
-
metadata: {
|
|
97
|
-
streamName: 'test-stream',
|
|
98
|
-
messageId: `test-${index}`,
|
|
99
|
-
streamPosition: BigInt(index + 1),
|
|
100
|
-
globalPosition: BigInt(index + 1),
|
|
101
|
-
},
|
|
102
|
-
}));
|
|
103
|
-
|
|
104
|
-
const contextWithMock = { ...context, commandSender: mockCommandSender };
|
|
105
|
-
|
|
106
|
-
// Get the actual reactor instance
|
|
107
|
-
const reactor = typeof processorOrReactor === 'function' ? processorOrReactor() : processorOrReactor;
|
|
108
|
-
|
|
109
|
-
// Handle different reactor types
|
|
110
|
-
if ('handle' in reactor && typeof reactor.handle === 'function') {
|
|
111
|
-
// It's a MessageProcessor or has a handle method
|
|
112
|
-
await reactor.handle(eventsWithMetadata, contextWithMock);
|
|
113
|
-
} else if ('eachMessage' in reactor && typeof reactor.eachMessage === 'function') {
|
|
114
|
-
// It has eachMessage
|
|
115
|
-
for (const event of eventsWithMetadata) {
|
|
116
|
-
await reactor.eachMessage(event, contextWithMock);
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
throw new Error('Reactor must have either a handle or eachMessage method');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return mockCommandSender.sentCommands;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
then: async (
|
|
127
|
-
expectedCommand: Command | Command[] | CommandCheck<Command> | CommandCheck<Command[]>,
|
|
128
|
-
): Promise<void> => {
|
|
129
|
-
try {
|
|
130
|
-
const sentCommands = await handle();
|
|
131
|
-
|
|
132
|
-
if (typeof expectedCommand === 'function') {
|
|
133
|
-
const checkFn = expectedCommand;
|
|
134
|
-
if (sentCommands.length === 1) {
|
|
135
|
-
assertTrue(
|
|
136
|
-
(checkFn as CommandCheck<Command>)(sentCommands[0]),
|
|
137
|
-
`Sent command did not match the expected condition: ${JSON.stringify(sentCommands[0])}`,
|
|
138
|
-
);
|
|
139
|
-
} else {
|
|
140
|
-
assertTrue(
|
|
141
|
-
(checkFn as CommandCheck<Command[]>)(sentCommands),
|
|
142
|
-
`Sent commands did not match the expected condition: ${JSON.stringify(sentCommands)}`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (Array.isArray(expectedCommand)) {
|
|
149
|
-
assertTrue(
|
|
150
|
-
sentCommands.length === expectedCommand.length,
|
|
151
|
-
`Expected ${expectedCommand.length} command(s) to be sent, but ${sentCommands.length} were sent`,
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
expectedCommand.forEach((expected, index) => {
|
|
155
|
-
const sent = sentCommands[index];
|
|
156
|
-
assertCommandsMatch(sent, expected, index);
|
|
157
|
-
});
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (sentCommands.length === 0) {
|
|
162
|
-
throw new AssertionError('No commands were sent');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (sentCommands.length > 1) {
|
|
166
|
-
throw new AssertionError(`Expected 1 command to be sent, but ${sentCommands.length} were sent`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
assertCommandsMatch(sentCommands[0], expectedCommand);
|
|
170
|
-
} finally {
|
|
171
|
-
mockCommandSender.reset();
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
thenNothingHappened: async (): Promise<void> => {
|
|
176
|
-
try {
|
|
177
|
-
const sentCommands = await handle();
|
|
178
|
-
if (sentCommands.length > 0) {
|
|
179
|
-
throw new AssertionError(
|
|
180
|
-
`Expected no commands to be sent, but ${sentCommands.length} command(s) were sent: ${JSON.stringify(sentCommands)}`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
} finally {
|
|
184
|
-
mockCommandSender.reset();
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
thenThrows: async <ErrorType extends Error>(...args: Parameters<ThenThrows<ErrorType>>): Promise<void> => {
|
|
189
|
-
try {
|
|
190
|
-
await handle();
|
|
191
|
-
throw new AssertionError('Reactor did not fail as expected');
|
|
192
|
-
} catch (error) {
|
|
193
|
-
thenThrowsErrorHandler(error, args);
|
|
194
|
-
} finally {
|
|
195
|
-
mockCommandSender.reset();
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
interface CommandWithMetadata {
|
|
205
|
-
metadata?: Record<string, unknown>;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// eslint-disable-next-line complexity
|
|
209
|
-
function assertCommandsMatch<Command>(actual: Command, expected: Command, index?: number): void {
|
|
210
|
-
const actualCopy = { ...actual } as CommandWithMetadata & Record<string, unknown>;
|
|
211
|
-
const expectedCopy = { ...expected } as CommandWithMetadata & Record<string, unknown>;
|
|
212
|
-
|
|
213
|
-
if (actualCopy.metadata !== undefined && expectedCopy.metadata !== undefined) {
|
|
214
|
-
for (const key in expectedCopy.metadata) {
|
|
215
|
-
const expectedValue = expectedCopy.metadata[key];
|
|
216
|
-
const actualValue = actualCopy.metadata[key];
|
|
217
|
-
|
|
218
|
-
const expectedStr =
|
|
219
|
-
expectedValue === undefined ? 'undefined' : expectedValue === null ? 'null' : String(expectedValue);
|
|
220
|
-
const actualStr = actualValue === undefined ? 'undefined' : actualValue === null ? 'null' : String(actualValue);
|
|
221
|
-
|
|
222
|
-
assertTrue(
|
|
223
|
-
actualValue === expectedValue,
|
|
224
|
-
`Command${index !== undefined ? ` at index ${index}` : ''} metadata.${key} does not match.\nExpected: ${expectedStr}\nActual: ${actualStr}`,
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
delete actualCopy.metadata;
|
|
228
|
-
delete expectedCopy.metadata;
|
|
229
|
-
} else if (actualCopy.metadata !== undefined && expectedCopy.metadata === undefined) {
|
|
230
|
-
delete actualCopy.metadata;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
assertTrue(
|
|
234
|
-
JSON.stringify(actualCopy) === JSON.stringify(expectedCopy),
|
|
235
|
-
`Command${index !== undefined ? ` at index ${index}` : ''} does not match.\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(actual)}`,
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function thenThrowsErrorHandler<ErrorType extends Error>(
|
|
240
|
-
error: unknown,
|
|
241
|
-
args: Parameters<ThenThrows<ErrorType>>,
|
|
242
|
-
): void {
|
|
243
|
-
if (error instanceof AssertionError) throw error;
|
|
244
|
-
|
|
245
|
-
if (args.length === 0) return;
|
|
246
|
-
|
|
247
|
-
if (!isErrorConstructor(args[0])) {
|
|
248
|
-
assertTrue(args[0](error as ErrorType), `Error didn't match the error condition: ${error?.toString()}`);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
|
|
253
|
-
|
|
254
|
-
if (args[1]) {
|
|
255
|
-
assertTrue(args[1](error as ErrorType), `Error didn't match the error condition: ${error?.toString()}`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { Command, CommandSender } from '@event-driven-io/emmett';
|
|
2
|
-
import type { MutationResponse } from './types';
|
|
3
|
-
|
|
4
|
-
export async function sendCommand<T extends Command>(
|
|
5
|
-
commandSender: CommandSender,
|
|
6
|
-
command: T,
|
|
7
|
-
): Promise<MutationResponse> {
|
|
8
|
-
try {
|
|
9
|
-
await commandSender.send(command);
|
|
10
|
-
return { success: true };
|
|
11
|
-
} catch (error: unknown) {
|
|
12
|
-
const err = error as { name?: string; message?: string };
|
|
13
|
-
return {
|
|
14
|
-
success: false,
|
|
15
|
-
error: {
|
|
16
|
-
type: err?.name ?? 'UnknownError',
|
|
17
|
-
message: err?.message ?? 'An unexpected error occurred',
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { CommandSender, InMemoryEventStore } from '@event-driven-io/emmett';
|
|
2
|
-
import { Field, ObjectType } from 'type-graphql';
|
|
3
|
-
|
|
4
|
-
export interface ReactorContext {
|
|
5
|
-
eventStore: InMemoryEventStore;
|
|
6
|
-
commandSender: CommandSender;
|
|
7
|
-
[key: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface GraphQLContext {
|
|
11
|
-
eventStore: InMemoryEventStore;
|
|
12
|
-
messageBus: CommandSender;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
@ObjectType()
|
|
16
|
-
export class MutationError {
|
|
17
|
-
@Field(() => String)
|
|
18
|
-
type!: string;
|
|
19
|
-
|
|
20
|
-
@Field(() => String, { nullable: true })
|
|
21
|
-
message?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
@ObjectType()
|
|
25
|
-
export class MutationResponse {
|
|
26
|
-
@Field(() => Boolean)
|
|
27
|
-
success!: boolean;
|
|
28
|
-
|
|
29
|
-
@Field(() => MutationError, { nullable: true })
|
|
30
|
-
error?: MutationError;
|
|
31
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import 'reflect-metadata';
|
|
2
|
-
import { ApolloServer } from 'apollo-server';
|
|
3
|
-
import { buildSchema } from 'type-graphql';
|
|
4
|
-
import { loadProjections, loadRegisterFiles, loadResolvers } from './utils';
|
|
5
|
-
import {
|
|
6
|
-
getInMemoryEventStore,
|
|
7
|
-
getInMemoryMessageBus,
|
|
8
|
-
projections,
|
|
9
|
-
forwardToMessageBus,
|
|
10
|
-
} from '@event-driven-io/emmett';
|
|
11
|
-
|
|
12
|
-
async function start() {
|
|
13
|
-
const loadedProjections = await loadProjections('src/domain/flows/**/projection.{ts,js}');
|
|
14
|
-
const registrations = await loadRegisterFiles('src/domain/flows/**/register.{ts,js}');
|
|
15
|
-
|
|
16
|
-
const messageBus = getInMemoryMessageBus();
|
|
17
|
-
|
|
18
|
-
const eventStore = getInMemoryEventStore({
|
|
19
|
-
projections: projections.inline(loadedProjections),
|
|
20
|
-
hooks: {
|
|
21
|
-
onAfterCommit: forwardToMessageBus(messageBus),
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
await Promise.all(registrations.map((r) => r.register(messageBus, eventStore)));
|
|
26
|
-
|
|
27
|
-
const resolvers = await loadResolvers('src/domain/flows/**/*.resolver.{ts,js}');
|
|
28
|
-
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>],
|
|
31
|
-
});
|
|
32
|
-
const server = new ApolloServer({
|
|
33
|
-
schema,
|
|
34
|
-
context: () => ({
|
|
35
|
-
eventStore,
|
|
36
|
-
messageBus,
|
|
37
|
-
}),
|
|
38
|
-
});
|
|
39
|
-
const { url } = await server.listen({ port: 4000 });
|
|
40
|
-
console.log(`🚀 GraphQL server ready at ${url}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
void start();
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import fg from 'fast-glob';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import type { ProjectionDefinition } from '@event-driven-io/emmett';
|
|
4
|
-
|
|
5
|
-
export async function loadProjections(source: string): Promise<ProjectionDefinition[]> {
|
|
6
|
-
const files = await fg(source, { absolute: true });
|
|
7
|
-
|
|
8
|
-
const modules = await Promise.all(
|
|
9
|
-
files.map(async (file) => {
|
|
10
|
-
const mod: unknown = await import(pathToFileUrl(file).href);
|
|
11
|
-
|
|
12
|
-
if (typeof mod === 'object' && mod !== null && 'projection' in mod) {
|
|
13
|
-
const projectionModule = mod as { projection?: ProjectionDefinition };
|
|
14
|
-
if (projectionModule.projection == null) {
|
|
15
|
-
console.warn(`⚠️ Projection file "${file}" does not export "projection"`);
|
|
16
|
-
}
|
|
17
|
-
return projectionModule.projection;
|
|
18
|
-
}
|
|
19
|
-
console.warn(`⚠️ Projection file "${file}" does not export "projection"`);
|
|
20
|
-
return undefined;
|
|
21
|
-
}),
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
return modules.filter((p): p is ProjectionDefinition => p != null);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function pathToFileUrl(filePath: string): URL {
|
|
28
|
-
const resolved = path.resolve(filePath);
|
|
29
|
-
return new URL(`file://${resolved}`);
|
|
30
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import fg from 'fast-glob';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import type { CommandProcessor, EventStore } from '@event-driven-io/emmett';
|
|
4
|
-
|
|
5
|
-
export interface SliceRegistration {
|
|
6
|
-
register: (messageBus: CommandProcessor, eventStore: EventStore) => Promise<unknown> | void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function loadRegisterFiles(source: string): Promise<SliceRegistration[]> {
|
|
10
|
-
const files = await fg(source, { absolute: true });
|
|
11
|
-
|
|
12
|
-
const modules = await Promise.all(
|
|
13
|
-
files.map(async (file) => {
|
|
14
|
-
try {
|
|
15
|
-
const mod: unknown = await import(pathToFileUrl(file).href);
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
typeof mod === 'object' &&
|
|
19
|
-
mod !== null &&
|
|
20
|
-
'register' in mod &&
|
|
21
|
-
typeof (mod as SliceRegistration).register === 'function'
|
|
22
|
-
) {
|
|
23
|
-
return mod as SliceRegistration;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
console.warn('⚠️ Skipping invalid register.ts at', file);
|
|
27
|
-
return null;
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error('❌ Failed to import', file, ':', error);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
return modules.filter((m): m is SliceRegistration => m !== null);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function pathToFileUrl(filePath: string): URL {
|
|
39
|
-
const resolved = path.resolve(filePath);
|
|
40
|
-
return new URL(`file://${resolved}`);
|
|
41
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import fg from 'fast-glob';
|
|
2
|
-
|
|
3
|
-
export interface Resolver {
|
|
4
|
-
(...args: unknown[]): unknown;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export async function loadResolvers(source: string): Promise<Resolver[]> {
|
|
8
|
-
const files = await fg(source, {
|
|
9
|
-
absolute: true,
|
|
10
|
-
});
|
|
11
|
-
const modules: unknown[] = await Promise.all(files.map((file) => import(file)));
|
|
12
|
-
const allResolvers: Resolver[] = [];
|
|
13
|
-
|
|
14
|
-
for (const mod of modules) {
|
|
15
|
-
if (typeof mod !== 'object' || mod === null) {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
for (const key of Object.keys(mod)) {
|
|
20
|
-
const exported = (mod as Record<string, unknown>)[key];
|
|
21
|
-
|
|
22
|
-
if (typeof exported === 'function') {
|
|
23
|
-
allResolvers.push(exported as Resolver);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (Array.isArray(exported) && exported.every((r) => typeof r === 'function')) {
|
|
27
|
-
allResolvers.push(...(exported as Resolver[]));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (allResolvers.length === 0) {
|
|
33
|
-
throw new Error('❌ No resolvers found for any slices.');
|
|
34
|
-
}
|
|
35
|
-
return allResolvers;
|
|
36
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
"emitDecoratorMetadata": true,
|
|
9
|
-
"experimentalDecorators": true
|
|
10
|
-
},
|
|
11
|
-
"include": [
|
|
12
|
-
"src/**/*",
|
|
13
|
-
"vitest.config.ts"
|
|
14
|
-
],
|
|
15
|
-
"exclude": [
|
|
16
|
-
"dist",
|
|
17
|
-
"node_modules"
|
|
18
|
-
]
|
|
19
|
-
}
|