create-auto-app 0.1.0 → 0.1.2
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 +189 -85
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/shopping-app/.context/auto-ia-scheme.json +175 -0
- package/templates/shopping-app/.context/design-system.md +118 -0
- package/templates/shopping-app/.context/figma-variables.json +14448 -0
- package/templates/shopping-app/.context/schema.graphql +46 -0
- package/templates/shopping-app/.context/schema.json +583 -0
- package/templates/shopping-app/.context/shadcn-filter.ts +31 -0
- package/templates/shopping-app/.env +8 -0
- package/templates/shopping-app/auto.config.ts +21 -0
- package/templates/shopping-app/client/auto-configure.ts +107 -0
- package/templates/shopping-app/client/codegen.ts +16 -0
- package/templates/shopping-app/client/components.json +20 -0
- package/templates/shopping-app/client/design-system-principles.md +19 -0
- package/templates/shopping-app/client/eslint.config.ts +57 -0
- package/templates/shopping-app/client/index.html +26 -0
- package/templates/shopping-app/client/package.json +100 -0
- package/templates/shopping-app/client/postcss.config.js +6 -0
- package/templates/shopping-app/client/public/favicon.ico +0 -0
- package/templates/shopping-app/client/schema.graphql +46 -0
- package/templates/shopping-app/client/src/App.css +42 -0
- package/templates/shopping-app/client/src/App.tsx +20 -0
- package/templates/shopping-app/client/src/apolloClient.ts +8 -0
- package/templates/shopping-app/client/src/components/atoms/accordion.tsx +51 -0
- package/templates/shopping-app/client/src/components/atoms/alert-dialog.tsx +111 -0
- package/templates/shopping-app/client/src/components/atoms/alert.tsx +49 -0
- package/templates/shopping-app/client/src/components/atoms/aspect-ratio.tsx +7 -0
- package/templates/shopping-app/client/src/components/atoms/avatar.tsx +32 -0
- package/templates/shopping-app/client/src/components/atoms/badge.tsx +36 -0
- package/templates/shopping-app/client/src/components/atoms/breadcrumb.tsx +92 -0
- package/templates/shopping-app/client/src/components/atoms/button.tsx +50 -0
- package/templates/shopping-app/client/src/components/atoms/calendar.tsx +156 -0
- package/templates/shopping-app/client/src/components/atoms/card.tsx +56 -0
- package/templates/shopping-app/client/src/components/atoms/carousel.tsx +214 -0
- package/templates/shopping-app/client/src/components/atoms/chart.tsx +290 -0
- package/templates/shopping-app/client/src/components/atoms/checkbox.tsx +27 -0
- package/templates/shopping-app/client/src/components/atoms/collapsible.tsx +15 -0
- package/templates/shopping-app/client/src/components/atoms/command.tsx +135 -0
- package/templates/shopping-app/client/src/components/atoms/context-menu.tsx +209 -0
- package/templates/shopping-app/client/src/components/atoms/dialog.tsx +123 -0
- package/templates/shopping-app/client/src/components/atoms/drawer.tsx +106 -0
- package/templates/shopping-app/client/src/components/atoms/dropdown-menu.tsx +217 -0
- package/templates/shopping-app/client/src/components/atoms/hover-card.tsx +36 -0
- package/templates/shopping-app/client/src/components/atoms/input-otp.tsx +66 -0
- package/templates/shopping-app/client/src/components/atoms/input.tsx +21 -0
- package/templates/shopping-app/client/src/components/atoms/label.tsx +19 -0
- package/templates/shopping-app/client/src/components/atoms/logo.tsx +33 -0
- package/templates/shopping-app/client/src/components/atoms/menubar.tsx +234 -0
- package/templates/shopping-app/client/src/components/atoms/navigation-menu.tsx +142 -0
- package/templates/shopping-app/client/src/components/atoms/pagination.tsx +100 -0
- package/templates/shopping-app/client/src/components/atoms/popover.tsx +40 -0
- package/templates/shopping-app/client/src/components/atoms/progress.tsx +22 -0
- package/templates/shopping-app/client/src/components/atoms/radio-group.tsx +31 -0
- package/templates/shopping-app/client/src/components/atoms/resizable.tsx +46 -0
- package/templates/shopping-app/client/src/components/atoms/scroll-area.tsx +46 -0
- package/templates/shopping-app/client/src/components/atoms/select.tsx +158 -0
- package/templates/shopping-app/client/src/components/atoms/separator.tsx +26 -0
- package/templates/shopping-app/client/src/components/atoms/sheet.tsx +101 -0
- package/templates/shopping-app/client/src/components/atoms/sidebar.tsx +675 -0
- package/templates/shopping-app/client/src/components/atoms/skeleton.tsx +7 -0
- package/templates/shopping-app/client/src/components/atoms/slider.tsx +54 -0
- package/templates/shopping-app/client/src/components/atoms/sonner.tsx +23 -0
- package/templates/shopping-app/client/src/components/atoms/switch.tsx +26 -0
- package/templates/shopping-app/client/src/components/atoms/table.tsx +73 -0
- package/templates/shopping-app/client/src/components/atoms/tabs.tsx +40 -0
- package/templates/shopping-app/client/src/components/atoms/textarea.tsx +18 -0
- package/templates/shopping-app/client/src/components/atoms/toggle-group.tsx +65 -0
- package/templates/shopping-app/client/src/components/atoms/toggle.tsx +39 -0
- package/templates/shopping-app/client/src/components/atoms/tooltip.tsx +48 -0
- package/templates/shopping-app/client/src/components/molecules/QuantitySelector.tsx +8 -0
- package/templates/shopping-app/client/src/components/molecules/ShoppingCriteriaForm.tsx +8 -0
- package/templates/shopping-app/client/src/components/molecules/SuggestedItemCard.tsx +10 -0
- package/templates/shopping-app/client/src/components/organisms/AssistantChatInterface.tsx +14 -0
- package/templates/shopping-app/client/src/components/organisms/PageHeader.tsx +6 -0
- package/templates/shopping-app/client/src/components/organisms/SuggestedItemsList.tsx +15 -0
- package/templates/shopping-app/client/src/gql/fragment-masking.ts +48 -0
- package/templates/shopping-app/client/src/gql/gql.ts +47 -0
- package/templates/shopping-app/client/src/gql/graphql.ts +106 -0
- package/templates/shopping-app/client/src/gql/index.ts +2 -0
- package/templates/shopping-app/client/src/graphql/mutations.ts +13 -0
- package/templates/shopping-app/client/src/graphql/queries.ts +14 -0
- package/templates/shopping-app/client/src/hooks/use-mobile.tsx +19 -0
- package/templates/shopping-app/client/src/hooks/use-toast.ts +186 -0
- package/templates/shopping-app/client/src/index.css +153 -0
- package/templates/shopping-app/client/src/lib/utils.ts +6 -0
- package/templates/shopping-app/client/src/main.tsx +5 -0
- package/templates/shopping-app/client/src/mockApolloClient.ts +93 -0
- package/templates/shopping-app/client/src/pages/AssistantChatPage.tsx +8 -0
- package/templates/shopping-app/client/src/pages/Index.tsx +10 -0
- package/templates/shopping-app/client/src/pages/NotFound.tsx +22 -0
- package/templates/shopping-app/client/src/pages/SuggestedItemsPage.tsx +8 -0
- package/templates/shopping-app/client/tailwind.config.ts +92 -0
- package/templates/shopping-app/client/tsconfig.json +49 -0
- package/templates/shopping-app/client/vite.config.ts +17 -0
- package/templates/shopping-app/flows/shopping-assistant.flow.ts +369 -0
- package/templates/shopping-app/package.json +25 -0
- package/templates/shopping-app/pnpm-workspace.yaml +2 -0
- package/templates/shopping-app/server/package.json +25 -0
- package/templates/shopping-app/server/scripts/generate-schema.ts +31 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/commands.ts +8 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/decide.specs.ts +46 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/decide.ts +36 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/events.ts +10 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/evolve.ts +28 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/handle.ts +31 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/mutation.resolver.ts +29 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/register.ts +10 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/state.ts +47 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/react.specs.ts +63 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/react.ts +49 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/register.ts +31 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/commands.ts +8 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/decide.specs.ts +38 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/decide.ts +36 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/events.ts +10 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/evolve.ts +28 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/handle.ts +31 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/mutation.resolver.ts +30 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/register.ts +10 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/state.ts +47 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/commands.ts +8 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/decide.specs.ts +61 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/decide.ts +45 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/events.ts +14 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/evolve.ts +28 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/handle.ts +59 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/mutation.resolver.ts +30 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/register.ts +10 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/state.ts +47 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/projection.specs.ts +94 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/projection.ts +40 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/query.resolver.ts +59 -0
- package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/state.ts +9 -0
- package/templates/shopping-app/server/src/domain/shared/ReadModel.d.ts +10 -0
- package/templates/shopping-app/server/src/domain/shared/ReadModel.d.ts.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/ReadModel.js +19 -0
- package/templates/shopping-app/server/src/domain/shared/ReadModel.js.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/ReadModel.ts +26 -0
- package/templates/shopping-app/server/src/domain/shared/index.d.ts +5 -0
- package/templates/shopping-app/server/src/domain/shared/index.d.ts.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/index.js +5 -0
- package/templates/shopping-app/server/src/domain/shared/index.js.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/index.ts +4 -0
- package/templates/shopping-app/server/src/domain/shared/reactorSpecification.d.ts +35 -0
- package/templates/shopping-app/server/src/domain/shared/reactorSpecification.d.ts.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/reactorSpecification.js +155 -0
- package/templates/shopping-app/server/src/domain/shared/reactorSpecification.js.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/reactorSpecification.ts +257 -0
- package/templates/shopping-app/server/src/domain/shared/sendCommand.d.ts +4 -0
- package/templates/shopping-app/server/src/domain/shared/sendCommand.d.ts.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/sendCommand.js +17 -0
- package/templates/shopping-app/server/src/domain/shared/sendCommand.js.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/sendCommand.ts +21 -0
- package/templates/shopping-app/server/src/domain/shared/types.d.ts +19 -0
- package/templates/shopping-app/server/src/domain/shared/types.d.ts.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/types.js +39 -0
- package/templates/shopping-app/server/src/domain/shared/types.js.map +1 -0
- package/templates/shopping-app/server/src/domain/shared/types.ts +31 -0
- package/templates/shopping-app/server/src/integrations/ai-integration.ts +76 -0
- package/templates/shopping-app/server/src/integrations/cart-integration.ts +178 -0
- package/templates/shopping-app/server/src/integrations/index.ts +3 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue-integration.ts +363 -0
- package/templates/shopping-app/server/src/server.ts +43 -0
- package/templates/shopping-app/server/src/utils/index.d.ts +4 -0
- package/templates/shopping-app/server/src/utils/index.d.ts.map +1 -0
- package/templates/shopping-app/server/src/utils/index.js +4 -0
- package/templates/shopping-app/server/src/utils/index.js.map +1 -0
- package/templates/shopping-app/server/src/utils/index.ts +3 -0
- package/templates/shopping-app/server/src/utils/loadProjections.d.ts +3 -0
- package/templates/shopping-app/server/src/utils/loadProjections.d.ts.map +1 -0
- package/templates/shopping-app/server/src/utils/loadProjections.js +23 -0
- package/templates/shopping-app/server/src/utils/loadProjections.js.map +1 -0
- package/templates/shopping-app/server/src/utils/loadProjections.ts +30 -0
- package/templates/shopping-app/server/src/utils/loadRegisterFiles.d.ts +6 -0
- package/templates/shopping-app/server/src/utils/loadRegisterFiles.d.ts.map +1 -0
- package/templates/shopping-app/server/src/utils/loadRegisterFiles.js +28 -0
- package/templates/shopping-app/server/src/utils/loadRegisterFiles.js.map +1 -0
- package/templates/shopping-app/server/src/utils/loadRegisterFiles.ts +41 -0
- package/templates/shopping-app/server/src/utils/loadResolvers.d.ts +5 -0
- package/templates/shopping-app/server/src/utils/loadResolvers.d.ts.map +1 -0
- package/templates/shopping-app/server/src/utils/loadResolvers.js +27 -0
- package/templates/shopping-app/server/src/utils/loadResolvers.js.map +1 -0
- package/templates/shopping-app/server/src/utils/loadResolvers.ts +36 -0
- package/templates/shopping-app/server/tsconfig.json +19 -0
- package/templates/shopping-app/server/vitest.config.ts +7 -0
- package/templates/shopping-app/tsconfig.json +21 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { isErrorConstructor, AssertionError, assertTrue, } from '@event-driven-io/emmett';
|
|
2
|
+
export const ReactorSpecification = {
|
|
3
|
+
for: reactorSpecificationFor,
|
|
4
|
+
};
|
|
5
|
+
function createMockCommandSender() {
|
|
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
|
+
}
|
|
18
|
+
function reactorSpecificationFor(processorOrReactor, createContext) {
|
|
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),
|
|
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
|
+
};
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// eslint-disable-next-line complexity
|
|
122
|
+
function assertCommandsMatch(actual, expected, index) {
|
|
123
|
+
const actualCopy = { ...actual };
|
|
124
|
+
const expectedCopy = { ...expected };
|
|
125
|
+
if (actualCopy.metadata !== undefined && expectedCopy.metadata !== undefined) {
|
|
126
|
+
for (const key in expectedCopy.metadata) {
|
|
127
|
+
const expectedValue = expectedCopy.metadata[key];
|
|
128
|
+
const actualValue = actualCopy.metadata[key];
|
|
129
|
+
const expectedStr = expectedValue === undefined ? 'undefined' : expectedValue === null ? 'null' : String(expectedValue);
|
|
130
|
+
const actualStr = actualValue === undefined ? 'undefined' : actualValue === null ? 'null' : String(actualValue);
|
|
131
|
+
assertTrue(actualValue === expectedValue, `Command${index !== undefined ? ` at index ${index}` : ''} metadata.${key} does not match.\nExpected: ${expectedStr}\nActual: ${actualStr}`);
|
|
132
|
+
}
|
|
133
|
+
delete actualCopy.metadata;
|
|
134
|
+
delete expectedCopy.metadata;
|
|
135
|
+
}
|
|
136
|
+
else if (actualCopy.metadata !== undefined && expectedCopy.metadata === undefined) {
|
|
137
|
+
delete actualCopy.metadata;
|
|
138
|
+
}
|
|
139
|
+
assertTrue(JSON.stringify(actualCopy) === JSON.stringify(expectedCopy), `Command${index !== undefined ? ` at index ${index}` : ''} does not match.\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(actual)}`);
|
|
140
|
+
}
|
|
141
|
+
function thenThrowsErrorHandler(error, args) {
|
|
142
|
+
if (error instanceof AssertionError)
|
|
143
|
+
throw error;
|
|
144
|
+
if (args.length === 0)
|
|
145
|
+
return;
|
|
146
|
+
if (!isErrorConstructor(args[0])) {
|
|
147
|
+
assertTrue(args[0](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
|
|
151
|
+
if (args[1]) {
|
|
152
|
+
assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=reactorSpecification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactorSpecification.js","sourceRoot":"","sources":["../../../src/domain/shared/reactorSpecification.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,257 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Command, CommandSender } from '@event-driven-io/emmett';
|
|
2
|
+
import type { MutationResponse } from './types';
|
|
3
|
+
export declare function sendCommand<T extends Command>(commandSender: CommandSender, command: T): Promise<MutationResponse>;
|
|
4
|
+
//# sourceMappingURL=sendCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sendCommand.d.ts","sourceRoot":"","sources":["../../../src/domain/shared/sendCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAsB,WAAW,CAAC,CAAC,SAAS,OAAO,EACjD,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,CAAC,GACT,OAAO,CAAC,gBAAgB,CAAC,CAc3B"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function sendCommand(commandSender, command) {
|
|
2
|
+
try {
|
|
3
|
+
await commandSender.send(command);
|
|
4
|
+
return { success: true };
|
|
5
|
+
}
|
|
6
|
+
catch (error) {
|
|
7
|
+
const err = error;
|
|
8
|
+
return {
|
|
9
|
+
success: false,
|
|
10
|
+
error: {
|
|
11
|
+
type: err?.name ?? 'UnknownError',
|
|
12
|
+
message: err?.message ?? 'An unexpected error occurred',
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=sendCommand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sendCommand.js","sourceRoot":"","sources":["../../../src/domain/shared/sendCommand.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,aAA4B,EAC5B,OAAU;IAEV,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,KAA4C,CAAC;QACzD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,GAAG,EAAE,IAAI,IAAI,cAAc;gBACjC,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,8BAA8B;aACxD;SACF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CommandSender, InMemoryEventStore } from '@event-driven-io/emmett';
|
|
2
|
+
export interface ReactorContext {
|
|
3
|
+
eventStore: InMemoryEventStore;
|
|
4
|
+
commandSender: CommandSender;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface GraphQLContext {
|
|
8
|
+
eventStore: InMemoryEventStore;
|
|
9
|
+
messageBus: CommandSender;
|
|
10
|
+
}
|
|
11
|
+
export declare class MutationError {
|
|
12
|
+
type: string;
|
|
13
|
+
message?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class MutationResponse {
|
|
16
|
+
success: boolean;
|
|
17
|
+
error?: MutationError;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/domain/shared/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG5E,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,aAAa,EAAE,aAAa,CAAC;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,aAAa,CAAC;CAC3B;AAED,qBACa,aAAa;IAExB,IAAI,EAAG,MAAM,CAAC;IAGd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBACa,gBAAgB;IAE3B,OAAO,EAAG,OAAO,CAAC;IAGlB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Field, ObjectType } from 'type-graphql';
|
|
11
|
+
let MutationError = class MutationError {
|
|
12
|
+
};
|
|
13
|
+
__decorate([
|
|
14
|
+
Field(() => String),
|
|
15
|
+
__metadata("design:type", String)
|
|
16
|
+
], MutationError.prototype, "type", void 0);
|
|
17
|
+
__decorate([
|
|
18
|
+
Field(() => String, { nullable: true }),
|
|
19
|
+
__metadata("design:type", String)
|
|
20
|
+
], MutationError.prototype, "message", void 0);
|
|
21
|
+
MutationError = __decorate([
|
|
22
|
+
ObjectType()
|
|
23
|
+
], MutationError);
|
|
24
|
+
export { MutationError };
|
|
25
|
+
let MutationResponse = class MutationResponse {
|
|
26
|
+
};
|
|
27
|
+
__decorate([
|
|
28
|
+
Field(() => Boolean),
|
|
29
|
+
__metadata("design:type", Boolean)
|
|
30
|
+
], MutationResponse.prototype, "success", void 0);
|
|
31
|
+
__decorate([
|
|
32
|
+
Field(() => MutationError, { nullable: true }),
|
|
33
|
+
__metadata("design:type", MutationError)
|
|
34
|
+
], MutationResponse.prototype, "error", void 0);
|
|
35
|
+
MutationResponse = __decorate([
|
|
36
|
+
ObjectType()
|
|
37
|
+
], MutationResponse);
|
|
38
|
+
export { MutationResponse };
|
|
39
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/domain/shared/types.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAc1C,IAAM,aAAa,GAAnB,MAAM,aAAa;CAMzB,CAAA;AAJC;IADC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC;;2CACN;AAGd;IADC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CACvB;AALN,aAAa;IADzB,UAAU,EAAE;GACA,aAAa,CAMzB;;AAGM,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAM5B,CAAA;AAJC;IADC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC;;iDACH;AAGlB;IADC,KAAK,CAAC,GAAG,EAAE,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BACvC,aAAa;+CAAC;AALX,gBAAgB;IAD5B,UAAU,EAAE;GACA,gBAAgB,CAM5B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Integration, Event, Command } from '@auto-engineer/flowlang';
|
|
2
|
+
import {
|
|
3
|
+
generateStructuredDataWithAI,
|
|
4
|
+
AIProvider,
|
|
5
|
+
z,
|
|
6
|
+
getSchemaByName,
|
|
7
|
+
generateTextWithAI,
|
|
8
|
+
} from '@auto-engineer/ai-gateway';
|
|
9
|
+
|
|
10
|
+
export type ChatCompleted = Event<
|
|
11
|
+
'ChatCompleted',
|
|
12
|
+
{
|
|
13
|
+
sessionId: string;
|
|
14
|
+
suggestedItems: { productId: string; name: string; quantity: number; reason: string }[];
|
|
15
|
+
timestamp: Date;
|
|
16
|
+
}
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
export type DoChat = Command<
|
|
20
|
+
'DoChat',
|
|
21
|
+
{
|
|
22
|
+
sessionId: string;
|
|
23
|
+
prompt: string;
|
|
24
|
+
systemPrompt?: string;
|
|
25
|
+
schemaName?: string;
|
|
26
|
+
}
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
type AICommands = {
|
|
30
|
+
DoChat: <T>(command: DoChat) => Promise<T>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const AI: Integration<'ai', Record<string, never>, AICommands> = {
|
|
34
|
+
__brand: 'Integration' as const,
|
|
35
|
+
type: 'ai' as const,
|
|
36
|
+
name: 'AI',
|
|
37
|
+
Commands: {
|
|
38
|
+
schema: {
|
|
39
|
+
DoChat: z.object({
|
|
40
|
+
sessionId: z.string(),
|
|
41
|
+
prompt: z.string(),
|
|
42
|
+
systemPrompt: z.string().optional(),
|
|
43
|
+
schemaName: z.string(),
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
DoChat: async <T>(command: DoChat): Promise<T> => {
|
|
47
|
+
const { prompt, systemPrompt, schemaName } = command.data;
|
|
48
|
+
|
|
49
|
+
const fullPrompt = systemPrompt?.trim() != null ? `${systemPrompt.trim()}\n\n${prompt.trim()}` : prompt.trim();
|
|
50
|
+
|
|
51
|
+
if (schemaName != null) {
|
|
52
|
+
const schema = getSchemaByName(schemaName);
|
|
53
|
+
|
|
54
|
+
if (schema) {
|
|
55
|
+
return await generateStructuredDataWithAI(fullPrompt, AIProvider.Anthropic, {
|
|
56
|
+
schema: schema as z.ZodSchema<T>,
|
|
57
|
+
schemaName,
|
|
58
|
+
schemaDescription: `AI output matching schema '${schemaName}'`,
|
|
59
|
+
includeTools: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const raw = await generateTextWithAI(fullPrompt, AIProvider.Anthropic, {
|
|
64
|
+
includeTools: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(raw) as T;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Failed to parse unstructured AI response: ${err instanceof Error ? err.message : String(err)}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|