create-auto-app 0.1.4 → 0.1.6
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/dist/index.js +125 -111
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/templates/shopping-app/.context/auto-ia-scheme.json +8 -29
- package/templates/shopping-app/.context/design-system.md +59 -35
- package/templates/shopping-app/.context/figma-variables.json +335 -1024
- package/templates/shopping-app/.context/schema.json +3 -7
- package/templates/shopping-app/.context/shadcn-filter.ts +2 -4
- package/templates/shopping-app/auto.config.ts +2 -2
- package/templates/shopping-app/client/src/gql/fragment-masking.ts +22 -22
- package/templates/shopping-app/client/src/gql/gql.ts +12 -5
- package/templates/shopping-app/client/src/gql/graphql.ts +125 -11
- package/templates/shopping-app/client/src/gql/index.ts +2 -2
- package/templates/shopping-app/client/src/graphql/mutations.ts +9 -9
- package/templates/shopping-app/client/src/graphql/queries.ts +10 -10
- package/templates/shopping-app/flows/shopping-assistant.flow.ts +2 -2
- package/templates/shopping-app/package.json +1 -1
- package/templates/shopping-app/pnpm-workspace.yaml +2 -2
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/commands.ts +2 -2
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/decide.specs.ts +20 -20
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/decide.ts +8 -11
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/events.ts +2 -2
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/evolve.ts +3 -3
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/handle.ts +8 -15
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/mutation.resolver.ts +5 -9
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/register.ts +4 -7
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/react.specs.ts +19 -31
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/react.ts +6 -12
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/register.ts +4 -11
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/commands.ts +2 -2
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/decide.specs.ts +14 -14
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/decide.ts +8 -11
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/events.ts +2 -2
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/evolve.ts +3 -3
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/handle.ts +8 -15
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/mutation.resolver.ts +5 -9
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/register.ts +4 -7
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/commands.ts +2 -2
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/decide.specs.ts +23 -23
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/decide.ts +9 -13
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/events.ts +2 -2
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/evolve.ts +3 -3
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/handle.ts +11 -18
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/mutation.resolver.ts +5 -9
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/register.ts +4 -7
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/projection.specs.ts +35 -35
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/projection.ts +8 -11
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/query.resolver.ts +4 -7
- package/templates/shopping-app/server/src/domain/shared/ReadModel.d.ts +7 -7
- package/templates/shopping-app/server/src/domain/shared/ReadModel.js +17 -17
- package/templates/shopping-app/server/src/domain/shared/index.d.ts +1 -1
- package/templates/shopping-app/server/src/domain/shared/index.js +1 -1
- package/templates/shopping-app/server/src/domain/shared/reactorSpecification.d.ts +42 -21
- package/templates/shopping-app/server/src/domain/shared/reactorSpecification.js +148 -140
- package/templates/shopping-app/server/src/domain/shared/sendCommand.d.ts +5 -2
- package/templates/shopping-app/server/src/domain/shared/sendCommand.js +14 -15
- package/templates/shopping-app/server/src/domain/shared/types.d.ts +10 -10
- package/templates/shopping-app/server/src/domain/shared/types.js +37 -36
- package/templates/shopping-app/server/src/integrations/ai-integration.ts +1 -1
- package/templates/shopping-app/server/src/integrations/cart-integration.ts +1 -1
- package/templates/shopping-app/server/src/integrations/index.ts +1 -1
- package/templates/shopping-app/server/src/integrations/product-catalogue-integration.ts +1 -1
- package/templates/shopping-app/server/src/utils/index.d.ts +1 -1
- package/templates/shopping-app/server/src/utils/index.js +1 -1
- package/templates/shopping-app/server/src/utils/loadProjections.d.ts +1 -1
- package/templates/shopping-app/server/src/utils/loadProjections.js +18 -16
- package/templates/shopping-app/server/src/utils/loadRegisterFiles.d.ts +2 -2
- package/templates/shopping-app/server/src/utils/loadRegisterFiles.js +19 -21
- package/templates/shopping-app/server/src/utils/loadResolvers.d.ts +2 -2
- package/templates/shopping-app/server/src/utils/loadResolvers.js +22 -22
- package/templates/shopping-app/server/tsconfig.json +2 -8
- package/templates/shopping-app/tsconfig.json +2 -8
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import { describe, it, beforeEach, expect } from
|
|
2
|
-
import { InMemoryProjectionSpec } from
|
|
3
|
-
import { projection } from
|
|
4
|
-
import type { ShoppingItemsSuggested } from
|
|
5
|
-
import { SuggestedItems } from
|
|
1
|
+
import { describe, it, beforeEach, expect } from 'vitest';
|
|
2
|
+
import { InMemoryProjectionSpec } from '@event-driven-io/emmett';
|
|
3
|
+
import { projection } from './projection';
|
|
4
|
+
import type { ShoppingItemsSuggested } from '../selects-items-relevant-to-the-shopping-criteria-/events';
|
|
5
|
+
import { SuggestedItems } from './state';
|
|
6
6
|
|
|
7
7
|
type ProjectionEvent = ShoppingItemsSuggested;
|
|
8
8
|
|
|
9
|
-
describe(
|
|
9
|
+
describe('SuggestedItemsProjection Projection', () => {
|
|
10
10
|
let given: InMemoryProjectionSpec<ProjectionEvent>;
|
|
11
11
|
|
|
12
12
|
beforeEach(() => {
|
|
13
13
|
given = InMemoryProjectionSpec.for({ projection });
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
it(
|
|
16
|
+
it('creates or updates SuggestedItems document - case 1', () =>
|
|
17
17
|
given([
|
|
18
18
|
{
|
|
19
|
-
type:
|
|
19
|
+
type: 'ShoppingItemsSuggested',
|
|
20
20
|
data: {
|
|
21
|
-
sessionId:
|
|
21
|
+
sessionId: 'session-abc',
|
|
22
22
|
suggestedItems: [
|
|
23
23
|
{
|
|
24
|
-
productId:
|
|
25
|
-
name:
|
|
24
|
+
productId: 'prod-soccer-ball',
|
|
25
|
+
name: 'Super Soccer Ball',
|
|
26
26
|
quantity: 1,
|
|
27
|
-
reason:
|
|
27
|
+
reason: 'Perfect for your daughter who loves soccer',
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
productId:
|
|
31
|
-
name:
|
|
30
|
+
productId: 'prod-craft-kit',
|
|
31
|
+
name: 'Deluxe Craft Kit',
|
|
32
32
|
quantity: 1,
|
|
33
|
-
reason:
|
|
33
|
+
reason: 'Great for creative activities and crafts',
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
|
-
productId:
|
|
37
|
-
name:
|
|
36
|
+
productId: 'prod-laptop-bag',
|
|
37
|
+
name: 'Tech Laptop Backpack',
|
|
38
38
|
quantity: 1,
|
|
39
39
|
reason: "Essential for your son's school computer needs",
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
productId:
|
|
43
|
-
name:
|
|
42
|
+
productId: 'prod-mtg-starter',
|
|
43
|
+
name: 'Magic the Gathering Starter Set',
|
|
44
44
|
quantity: 1,
|
|
45
|
-
reason:
|
|
45
|
+
reason: 'Ideal starter set for Magic the Gathering enthusiasts',
|
|
46
46
|
},
|
|
47
47
|
],
|
|
48
48
|
},
|
|
49
49
|
metadata: {
|
|
50
|
-
streamName:
|
|
50
|
+
streamName: 'ignored-stream',
|
|
51
51
|
streamPosition: 1n,
|
|
52
52
|
globalPosition: 1n,
|
|
53
53
|
},
|
|
@@ -56,35 +56,35 @@ describe("SuggestedItemsProjection Projection", () => {
|
|
|
56
56
|
.when([])
|
|
57
57
|
.then(async (state) => {
|
|
58
58
|
const document = await state.database
|
|
59
|
-
.collection<SuggestedItems>(
|
|
60
|
-
.findOne((doc) => doc.sessionId ===
|
|
59
|
+
.collection<SuggestedItems>('SuggestedItemsProjection')
|
|
60
|
+
.findOne((doc) => doc.sessionId === 'session-abc');
|
|
61
61
|
|
|
62
62
|
const expected: SuggestedItems = {
|
|
63
|
-
sessionId:
|
|
63
|
+
sessionId: 'session-abc',
|
|
64
64
|
items: [
|
|
65
65
|
{
|
|
66
|
-
productId:
|
|
67
|
-
name:
|
|
66
|
+
productId: 'prod-soccer-ball',
|
|
67
|
+
name: 'Super Soccer Ball',
|
|
68
68
|
quantity: 1,
|
|
69
|
-
reason:
|
|
69
|
+
reason: 'Perfect for your daughter who loves soccer',
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
|
-
productId:
|
|
73
|
-
name:
|
|
72
|
+
productId: 'prod-craft-kit',
|
|
73
|
+
name: 'Deluxe Craft Kit',
|
|
74
74
|
quantity: 1,
|
|
75
|
-
reason:
|
|
75
|
+
reason: 'Great for creative activities and crafts',
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
|
-
productId:
|
|
79
|
-
name:
|
|
78
|
+
productId: 'prod-laptop-bag',
|
|
79
|
+
name: 'Tech Laptop Backpack',
|
|
80
80
|
quantity: 1,
|
|
81
81
|
reason: "Essential for your son's school computer needs",
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
|
-
productId:
|
|
85
|
-
name:
|
|
84
|
+
productId: 'prod-mtg-starter',
|
|
85
|
+
name: 'Magic the Gathering Starter Set',
|
|
86
86
|
quantity: 1,
|
|
87
|
-
reason:
|
|
87
|
+
reason: 'Ideal starter set for Magic the Gathering enthusiasts',
|
|
88
88
|
},
|
|
89
89
|
],
|
|
90
90
|
};
|
|
@@ -2,32 +2,29 @@ import {
|
|
|
2
2
|
inMemorySingleStreamProjection,
|
|
3
3
|
type ReadEvent,
|
|
4
4
|
type InMemoryReadEventMetadata,
|
|
5
|
-
} from
|
|
6
|
-
import type { SuggestedItems } from
|
|
7
|
-
import type { ShoppingItemsSuggested } from
|
|
5
|
+
} from '@event-driven-io/emmett';
|
|
6
|
+
import type { SuggestedItems } from './state';
|
|
7
|
+
import type { ShoppingItemsSuggested } from '../selects-items-relevant-to-the-shopping-criteria-/events';
|
|
8
8
|
|
|
9
9
|
type AllEvents = ShoppingItemsSuggested;
|
|
10
10
|
|
|
11
|
-
export const projection = inMemorySingleStreamProjection<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
>({
|
|
15
|
-
collectionName: "SuggestedItemsProjection",
|
|
16
|
-
canHandle: ["ShoppingItemsSuggested"],
|
|
11
|
+
export const projection = inMemorySingleStreamProjection<SuggestedItems, AllEvents>({
|
|
12
|
+
collectionName: 'SuggestedItemsProjection',
|
|
13
|
+
canHandle: ['ShoppingItemsSuggested'],
|
|
17
14
|
getDocumentId: (event) => event.data.sessionId,
|
|
18
15
|
evolve: (
|
|
19
16
|
document: SuggestedItems | null,
|
|
20
17
|
event: ReadEvent<AllEvents, InMemoryReadEventMetadata>,
|
|
21
18
|
): SuggestedItems | null => {
|
|
22
19
|
switch (event.type) {
|
|
23
|
-
case
|
|
20
|
+
case 'ShoppingItemsSuggested': {
|
|
24
21
|
/**
|
|
25
22
|
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
26
23
|
* This event adds or updates the document.
|
|
27
24
|
* Implement the correct fields as needed for your read model.
|
|
28
25
|
*/
|
|
29
26
|
return {
|
|
30
|
-
sessionId: /* TODO: map from event.data */
|
|
27
|
+
sessionId: /* TODO: map from event.data */ '',
|
|
31
28
|
items: /* TODO: map from event.data */ [],
|
|
32
29
|
};
|
|
33
30
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID } from
|
|
2
|
-
import { type GraphQLContext, ReadModel } from
|
|
1
|
+
import { Query, Resolver, Arg, Ctx, ObjectType, Field, ID } from 'type-graphql';
|
|
2
|
+
import { type GraphQLContext, ReadModel } from '../../../shared';
|
|
3
3
|
|
|
4
4
|
@ObjectType()
|
|
5
5
|
export class SuggestedItems {
|
|
@@ -32,12 +32,9 @@ export class ViewsSuggestedItemsQueryResolver {
|
|
|
32
32
|
@Query(() => [SuggestedItems])
|
|
33
33
|
async suggestedItems(
|
|
34
34
|
@Ctx() ctx: GraphQLContext,
|
|
35
|
-
@Arg(
|
|
35
|
+
@Arg('sessionId', () => ID, { nullable: true }) sessionId?: string,
|
|
36
36
|
): Promise<SuggestedItems[]> {
|
|
37
|
-
const model = new ReadModel<SuggestedItems>(
|
|
38
|
-
ctx.eventStore,
|
|
39
|
-
"SuggestedItemsProjection",
|
|
40
|
-
);
|
|
37
|
+
const model = new ReadModel<SuggestedItems>(ctx.eventStore, 'SuggestedItemsProjection');
|
|
41
38
|
|
|
42
39
|
// ## IMPLEMENTATION INSTRUCTIONS ##
|
|
43
40
|
// You can query the projection using the ReadModel API:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { InMemoryEventStore } from '@event-driven-io/emmett';
|
|
2
2
|
export declare class ReadModel<T extends Record<string, unknown>> {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
private collection;
|
|
4
|
+
constructor(eventStore: InMemoryEventStore, collectionName: string);
|
|
5
|
+
getAll(): Promise<T[]>;
|
|
6
|
+
getById(id: string, idField?: keyof T): Promise<T | null>;
|
|
7
|
+
find(filterFn: (item: T) => boolean): Promise<T[]>;
|
|
8
|
+
first(filterFn: (item: T) => boolean): Promise<T | null>;
|
|
9
9
|
}
|
|
10
|
-
//# sourceMappingURL=ReadModel.d.ts.map
|
|
10
|
+
//# sourceMappingURL=ReadModel.d.ts.map
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
export class ReadModel {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
constructor(eventStore, collectionName) {
|
|
3
|
+
this.collection = eventStore.database.collection(collectionName);
|
|
4
|
+
}
|
|
5
|
+
async getAll() {
|
|
6
|
+
return this.collection.find();
|
|
7
|
+
}
|
|
8
|
+
async getById(id, idField = 'id') {
|
|
9
|
+
return this.collection.findOne((doc) => doc[idField] === id);
|
|
10
|
+
}
|
|
11
|
+
async find(filterFn) {
|
|
12
|
+
return this.collection.find(filterFn);
|
|
13
|
+
}
|
|
14
|
+
async first(filterFn) {
|
|
15
|
+
const all = await this.collection.find(filterFn);
|
|
16
|
+
return all[0] ?? null;
|
|
17
|
+
}
|
|
18
18
|
}
|
|
19
|
-
//# sourceMappingURL=ReadModel.js.map
|
|
19
|
+
//# sourceMappingURL=ReadModel.js.map
|
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
import { CommandSender, type ErrorConstructor, type MessageProcessor } from '@event-driven-io/emmett';
|
|
2
2
|
interface CommandCheck<CommandType> {
|
|
3
|
-
|
|
3
|
+
(command: CommandType): boolean;
|
|
4
4
|
}
|
|
5
5
|
interface ErrorCheck<ErrorType> {
|
|
6
|
-
|
|
6
|
+
(error: ErrorType): boolean;
|
|
7
7
|
}
|
|
8
|
-
export type ThenThrows<ErrorType extends Error> =
|
|
8
|
+
export type ThenThrows<ErrorType extends Error> =
|
|
9
|
+
| (() => void)
|
|
10
|
+
| ((errorConstructor: ErrorConstructor<ErrorType>) => void)
|
|
11
|
+
| ((errorCheck: ErrorCheck<ErrorType>) => void)
|
|
12
|
+
| ((errorConstructor: ErrorConstructor<ErrorType>, errorCheck?: ErrorCheck<ErrorType>) => void);
|
|
9
13
|
interface ReactorSpecificationReturn<Event, Command, Context> {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
when: (
|
|
15
|
+
event: Event | Event[],
|
|
16
|
+
context?: Context,
|
|
17
|
+
) => {
|
|
18
|
+
then: (expectedCommand: Command | Command[] | CommandCheck<Command> | CommandCheck<Command[]>) => Promise<void>;
|
|
19
|
+
thenNothingHappened: () => Promise<void>;
|
|
20
|
+
thenThrows: <ErrorType extends Error = Error>(...args: Parameters<ThenThrows<ErrorType>>) => Promise<void>;
|
|
21
|
+
};
|
|
15
22
|
}
|
|
16
|
-
export interface ReactorSpecification<
|
|
23
|
+
export interface ReactorSpecification<
|
|
24
|
+
Event,
|
|
25
|
+
Command,
|
|
26
|
+
Context extends {
|
|
17
27
|
commandSender: CommandSender;
|
|
18
|
-
} = {
|
|
28
|
+
} = {
|
|
19
29
|
commandSender: CommandSender;
|
|
20
|
-
}
|
|
21
|
-
|
|
30
|
+
},
|
|
31
|
+
> {
|
|
32
|
+
(givenEvents: Event | Event[]): ReactorSpecificationReturn<Event, Command, Context>;
|
|
22
33
|
}
|
|
23
34
|
export declare const ReactorSpecification: {
|
|
24
|
-
|
|
35
|
+
for: typeof reactorSpecificationFor;
|
|
25
36
|
};
|
|
26
|
-
type ReactorLike<Event, Context> =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
type ReactorLike<Event, Context> =
|
|
38
|
+
| {
|
|
39
|
+
handle: (events: Event[], context: Context) => Promise<any>;
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
eachMessage: (event: Event, context: Context) => Promise<any>;
|
|
43
|
+
}
|
|
44
|
+
| MessageProcessor<any, any, any>;
|
|
45
|
+
declare function reactorSpecificationFor<
|
|
46
|
+
Event,
|
|
47
|
+
Command,
|
|
48
|
+
Context extends {
|
|
32
49
|
commandSender: CommandSender;
|
|
33
|
-
}
|
|
50
|
+
},
|
|
51
|
+
>(
|
|
52
|
+
processorOrReactor: ReactorLike<Event, Context> | (() => ReactorLike<Event, Context>),
|
|
53
|
+
createContext: (commandSender: CommandSender) => Context,
|
|
54
|
+
): ReactorSpecification<Event, Command, Context>;
|
|
34
55
|
export {};
|
|
35
|
-
//# sourceMappingURL=reactorSpecification.d.ts.map
|
|
56
|
+
//# sourceMappingURL=reactorSpecification.d.ts.map
|
|
@@ -1,155 +1,163 @@
|
|
|
1
|
-
import { isErrorConstructor, AssertionError, assertTrue
|
|
1
|
+
import { isErrorConstructor, AssertionError, assertTrue } from '@event-driven-io/emmett';
|
|
2
2
|
export const ReactorSpecification = {
|
|
3
|
-
|
|
3
|
+
for: reactorSpecificationFor,
|
|
4
4
|
};
|
|
5
5
|
function createMockCommandSender() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
const sentCommands = [];
|
|
7
|
+
const send = async (command) => {
|
|
8
|
+
sentCommands.push(command);
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
send,
|
|
12
|
+
sentCommands,
|
|
13
|
+
reset: () => {
|
|
14
|
+
sentCommands.length = 0;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
17
|
}
|
|
18
18
|
function reactorSpecificationFor(processorOrReactor, createContext) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
}));
|
|
40
|
-
const contextWithMock = { ...context, commandSender: mockCommandSender };
|
|
41
|
-
// Get the actual reactor instance
|
|
42
|
-
const reactor = typeof processorOrReactor === 'function' ? processorOrReactor() : processorOrReactor;
|
|
43
|
-
// Handle different reactor types
|
|
44
|
-
if ('handle' in reactor && typeof reactor.handle === 'function') {
|
|
45
|
-
// It's a MessageProcessor or has a handle method
|
|
46
|
-
await reactor.handle(eventsWithMetadata, contextWithMock);
|
|
47
|
-
}
|
|
48
|
-
else if ('eachMessage' in reactor && typeof reactor.eachMessage === 'function') {
|
|
49
|
-
// It has eachMessage
|
|
50
|
-
for (const event of eventsWithMetadata) {
|
|
51
|
-
await reactor.eachMessage(event, contextWithMock);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
throw new Error('Reactor must have either a handle or eachMessage method');
|
|
56
|
-
}
|
|
57
|
-
return mockCommandSender.sentCommands;
|
|
58
|
-
};
|
|
59
|
-
return {
|
|
60
|
-
then: async (expectedCommand) => {
|
|
61
|
-
try {
|
|
62
|
-
const sentCommands = await handle();
|
|
63
|
-
if (typeof expectedCommand === 'function') {
|
|
64
|
-
const checkFn = expectedCommand;
|
|
65
|
-
if (sentCommands.length === 1) {
|
|
66
|
-
assertTrue(checkFn(sentCommands[0]), `Sent command did not match the expected condition: ${JSON.stringify(sentCommands[0])}`);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
assertTrue(checkFn(sentCommands), `Sent commands did not match the expected condition: ${JSON.stringify(sentCommands)}`);
|
|
70
|
-
}
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (Array.isArray(expectedCommand)) {
|
|
74
|
-
assertTrue(sentCommands.length === expectedCommand.length, `Expected ${expectedCommand.length} command(s) to be sent, but ${sentCommands.length} were sent`);
|
|
75
|
-
expectedCommand.forEach((expected, index) => {
|
|
76
|
-
const sent = sentCommands[index];
|
|
77
|
-
assertCommandsMatch(sent, expected, index);
|
|
78
|
-
});
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (sentCommands.length === 0) {
|
|
82
|
-
throw new AssertionError('No commands were sent');
|
|
83
|
-
}
|
|
84
|
-
if (sentCommands.length > 1) {
|
|
85
|
-
throw new AssertionError(`Expected 1 command to be sent, but ${sentCommands.length} were sent`);
|
|
86
|
-
}
|
|
87
|
-
assertCommandsMatch(sentCommands[0], expectedCommand);
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
mockCommandSender.reset();
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
thenNothingHappened: async () => {
|
|
94
|
-
try {
|
|
95
|
-
const sentCommands = await handle();
|
|
96
|
-
if (sentCommands.length > 0) {
|
|
97
|
-
throw new AssertionError(`Expected no commands to be sent, but ${sentCommands.length} command(s) were sent: ${JSON.stringify(sentCommands)}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
mockCommandSender.reset();
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
thenThrows: async (...args) => {
|
|
105
|
-
try {
|
|
106
|
-
await handle();
|
|
107
|
-
throw new AssertionError('Reactor did not fail as expected');
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
thenThrowsErrorHandler(error, args);
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
mockCommandSender.reset();
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
};
|
|
19
|
+
return (givenEvents) => {
|
|
20
|
+
return {
|
|
21
|
+
when: (whenEvent, contextOverride) => {
|
|
22
|
+
const mockCommandSender = createMockCommandSender();
|
|
23
|
+
const defaultContext = createContext(mockCommandSender);
|
|
24
|
+
const context = contextOverride || defaultContext;
|
|
25
|
+
const handle = async () => {
|
|
26
|
+
const givenArray = Array.isArray(givenEvents) ? givenEvents : givenEvents != null ? [givenEvents] : [];
|
|
27
|
+
const whenArray = Array.isArray(whenEvent) ? whenEvent : [whenEvent];
|
|
28
|
+
const allEvents = [...givenArray, ...whenArray];
|
|
29
|
+
// Transform events to have metadata
|
|
30
|
+
const eventsWithMetadata = allEvents.map((event, index) => ({
|
|
31
|
+
...event,
|
|
32
|
+
kind: 'Event',
|
|
33
|
+
metadata: {
|
|
34
|
+
streamName: 'test-stream',
|
|
35
|
+
messageId: `test-${index}`,
|
|
36
|
+
streamPosition: BigInt(index + 1),
|
|
37
|
+
globalPosition: BigInt(index + 1),
|
|
117
38
|
},
|
|
39
|
+
}));
|
|
40
|
+
const contextWithMock = { ...context, commandSender: mockCommandSender };
|
|
41
|
+
// Get the actual reactor instance
|
|
42
|
+
const reactor = typeof processorOrReactor === 'function' ? processorOrReactor() : processorOrReactor;
|
|
43
|
+
// Handle different reactor types
|
|
44
|
+
if ('handle' in reactor && typeof reactor.handle === 'function') {
|
|
45
|
+
// It's a MessageProcessor or has a handle method
|
|
46
|
+
await reactor.handle(eventsWithMetadata, contextWithMock);
|
|
47
|
+
} else if ('eachMessage' in reactor && typeof reactor.eachMessage === 'function') {
|
|
48
|
+
// It has eachMessage
|
|
49
|
+
for (const event of eventsWithMetadata) {
|
|
50
|
+
await reactor.eachMessage(event, contextWithMock);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
throw new Error('Reactor must have either a handle or eachMessage method');
|
|
54
|
+
}
|
|
55
|
+
return mockCommandSender.sentCommands;
|
|
118
56
|
};
|
|
57
|
+
return {
|
|
58
|
+
then: async (expectedCommand) => {
|
|
59
|
+
try {
|
|
60
|
+
const sentCommands = await handle();
|
|
61
|
+
if (typeof expectedCommand === 'function') {
|
|
62
|
+
const checkFn = expectedCommand;
|
|
63
|
+
if (sentCommands.length === 1) {
|
|
64
|
+
assertTrue(
|
|
65
|
+
checkFn(sentCommands[0]),
|
|
66
|
+
`Sent command did not match the expected condition: ${JSON.stringify(sentCommands[0])}`,
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
assertTrue(
|
|
70
|
+
checkFn(sentCommands),
|
|
71
|
+
`Sent commands did not match the expected condition: ${JSON.stringify(sentCommands)}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(expectedCommand)) {
|
|
77
|
+
assertTrue(
|
|
78
|
+
sentCommands.length === expectedCommand.length,
|
|
79
|
+
`Expected ${expectedCommand.length} command(s) to be sent, but ${sentCommands.length} were sent`,
|
|
80
|
+
);
|
|
81
|
+
expectedCommand.forEach((expected, index) => {
|
|
82
|
+
const sent = sentCommands[index];
|
|
83
|
+
assertCommandsMatch(sent, expected, index);
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (sentCommands.length === 0) {
|
|
88
|
+
throw new AssertionError('No commands were sent');
|
|
89
|
+
}
|
|
90
|
+
if (sentCommands.length > 1) {
|
|
91
|
+
throw new AssertionError(`Expected 1 command to be sent, but ${sentCommands.length} were sent`);
|
|
92
|
+
}
|
|
93
|
+
assertCommandsMatch(sentCommands[0], expectedCommand);
|
|
94
|
+
} finally {
|
|
95
|
+
mockCommandSender.reset();
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
thenNothingHappened: async () => {
|
|
99
|
+
try {
|
|
100
|
+
const sentCommands = await handle();
|
|
101
|
+
if (sentCommands.length > 0) {
|
|
102
|
+
throw new AssertionError(
|
|
103
|
+
`Expected no commands to be sent, but ${sentCommands.length} command(s) were sent: ${JSON.stringify(sentCommands)}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
mockCommandSender.reset();
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
thenThrows: async (...args) => {
|
|
111
|
+
try {
|
|
112
|
+
await handle();
|
|
113
|
+
throw new AssertionError('Reactor did not fail as expected');
|
|
114
|
+
} catch (error) {
|
|
115
|
+
thenThrowsErrorHandler(error, args);
|
|
116
|
+
} finally {
|
|
117
|
+
mockCommandSender.reset();
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
},
|
|
119
122
|
};
|
|
123
|
+
};
|
|
120
124
|
}
|
|
121
125
|
// eslint-disable-next-line complexity
|
|
122
126
|
function assertCommandsMatch(actual, expected, index) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
else if (actualCopy.metadata !== undefined && expectedCopy.metadata === undefined) {
|
|
137
|
-
delete actualCopy.metadata;
|
|
127
|
+
const actualCopy = { ...actual };
|
|
128
|
+
const expectedCopy = { ...expected };
|
|
129
|
+
if (actualCopy.metadata !== undefined && expectedCopy.metadata !== undefined) {
|
|
130
|
+
for (const key in expectedCopy.metadata) {
|
|
131
|
+
const expectedValue = expectedCopy.metadata[key];
|
|
132
|
+
const actualValue = actualCopy.metadata[key];
|
|
133
|
+
const expectedStr =
|
|
134
|
+
expectedValue === undefined ? 'undefined' : expectedValue === null ? 'null' : String(expectedValue);
|
|
135
|
+
const actualStr = actualValue === undefined ? 'undefined' : actualValue === null ? 'null' : String(actualValue);
|
|
136
|
+
assertTrue(
|
|
137
|
+
actualValue === expectedValue,
|
|
138
|
+
`Command${index !== undefined ? ` at index ${index}` : ''} metadata.${key} does not match.\nExpected: ${expectedStr}\nActual: ${actualStr}`,
|
|
139
|
+
);
|
|
138
140
|
}
|
|
139
|
-
|
|
141
|
+
delete actualCopy.metadata;
|
|
142
|
+
delete expectedCopy.metadata;
|
|
143
|
+
} else if (actualCopy.metadata !== undefined && expectedCopy.metadata === undefined) {
|
|
144
|
+
delete actualCopy.metadata;
|
|
145
|
+
}
|
|
146
|
+
assertTrue(
|
|
147
|
+
JSON.stringify(actualCopy) === JSON.stringify(expectedCopy),
|
|
148
|
+
`Command${index !== undefined ? ` at index ${index}` : ''} does not match.\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(actual)}`,
|
|
149
|
+
);
|
|
140
150
|
}
|
|
141
151
|
function thenThrowsErrorHandler(error, args) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
assertTrue(
|
|
151
|
-
|
|
152
|
-
assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
153
|
-
}
|
|
152
|
+
if (error instanceof AssertionError) throw error;
|
|
153
|
+
if (args.length === 0) return;
|
|
154
|
+
if (!isErrorConstructor(args[0])) {
|
|
155
|
+
assertTrue(args[0](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
|
|
159
|
+
if (args[1]) {
|
|
160
|
+
assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
161
|
+
}
|
|
154
162
|
}
|
|
155
|
-
//# sourceMappingURL=reactorSpecification.js.map
|
|
163
|
+
//# sourceMappingURL=reactorSpecification.js.map
|