create-auto-app 0.3.0 → 0.8.5
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/README.md +197 -0
- package/package.json +2 -3
- package/templates/shopping-app/.context/schema.json +426 -215
- package/templates/shopping-app/.context/server/.context/auto-ia-scheme.json +79 -0
- package/templates/shopping-app/.context/server/server/package.json +1 -1
- package/templates/shopping-app/.context/server/server/scripts/generate-schema.ts +1 -1
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/decide.specs.ts +1 -1
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/creates-a-chat-session/react.specs.ts +51 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/creates-a-chat-session/react.ts +43 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/creates-a-chat-session/register.ts +24 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/react.specs.ts +1 -1
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/decide.specs.ts +1 -1
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/commands.ts +8 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/decide.specs.ts +101 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/decide.ts +42 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/events.ts +9 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/evolve.ts +28 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/handle.ts +52 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/mutation.resolver.ts +25 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/register.ts +7 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria/state.ts +47 -0
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/decide.specs.ts +41 -1
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/decide.ts +9 -1
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/handle.ts +4 -1
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/views-suggested-items/projection.specs.ts +3 -41
- package/templates/shopping-app/.context/server/server/src/domain/flows/seasonal-assistant/views-suggested-items/projection.ts +2 -15
- package/templates/shopping-app/auto.config.ts +20 -20
- package/templates/shopping-app/client/package.json +4 -4
- package/templates/shopping-app/example-integrations/ai-chat-completion/package.json +5 -6
- package/templates/shopping-app/example-integrations/ai-chat-completion/src/ai-integration.ts +2 -2
- package/templates/shopping-app/example-integrations/cart/package.json +5 -6
- package/templates/shopping-app/example-integrations/cart-api/package.json +3 -3
- package/templates/shopping-app/example-integrations/product-catalogue/package.json +5 -5
- package/templates/shopping-app/example-integrations/product-catalogue/src/product-catalogue-integration.ts +0 -132
- package/templates/shopping-app/example-integrations/product-catalogue-api/package.json +3 -3
- package/templates/shopping-app/flows/shopping-assistant.flow.ts +195 -195
- package/templates/shopping-app/package.json +6 -2
- package/templates/shopping-app/server/package.json +5 -4
- package/templates/shopping-app/server/src/integrations/ai-integration.ts +2 -2
- package/templates/shopping-app/server/src/integrations/index.ts +1 -1
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/client/client.gen.ts +199 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/client/index.ts +25 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/client/types.gen.ts +232 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/client/utils.gen.ts +419 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/client.gen.ts +18 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/core/auth.gen.ts +42 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/core/bodySerializer.gen.ts +92 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/core/params.gen.ts +153 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/core/pathSerializer.gen.ts +181 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/core/types.gen.ts +120 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/index.ts +2 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/types.gen.ts +133 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/generated/product-catalog/zod.gen.ts +62 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/index.ts +1 -0
- package/templates/shopping-app/server/src/integrations/product-catalogue/product-catalogue-integration.ts +232 -0
- package/templates/shopping-app/tsconfig.json +2 -2
- package/templates/shopping-app/server/src/integrations/product-catalogue-integration.ts +0 -363
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": {
|
|
3
|
+
"atoms": {
|
|
4
|
+
"items": {}
|
|
5
|
+
},
|
|
6
|
+
"molecules": {
|
|
7
|
+
"items": {
|
|
8
|
+
"ShoppingCriteriaInput": {
|
|
9
|
+
"description": "Input component for shoppers to describe their shopping criteria.",
|
|
10
|
+
"composition": {
|
|
11
|
+
"atoms": ["TextInput", "Button"],
|
|
12
|
+
"molecules": []
|
|
13
|
+
},
|
|
14
|
+
"specs": [
|
|
15
|
+
"allow shopper to describe their shopping needs in natural language",
|
|
16
|
+
"provide a text input for entering criteria",
|
|
17
|
+
"show examples of what to include (age, interests, budget)",
|
|
18
|
+
"show a button to submit the criteria"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"SuggestedItemsList": {
|
|
22
|
+
"description": "Displays a list of suggested shopping items with quantity selectors.",
|
|
23
|
+
"composition": {
|
|
24
|
+
"atoms": ["Text", "QuantitySelector", "Button"],
|
|
25
|
+
"molecules": []
|
|
26
|
+
},
|
|
27
|
+
"specs": [
|
|
28
|
+
"display all suggested items with names and reasons",
|
|
29
|
+
"show quantity selectors for each item",
|
|
30
|
+
"have an 'Add to Cart' button for selected items",
|
|
31
|
+
"allow removing items from the suggestions"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"organisms": {
|
|
37
|
+
"items": {
|
|
38
|
+
"ShoppingAssistant": {
|
|
39
|
+
"description": "Main component that manages the shopping criteria input and suggested items display.",
|
|
40
|
+
"composition": {
|
|
41
|
+
"molecules": ["ShoppingCriteriaInput", "SuggestedItemsList"]
|
|
42
|
+
},
|
|
43
|
+
"data_requirements": [
|
|
44
|
+
{
|
|
45
|
+
"type": "query",
|
|
46
|
+
"description": "Fetch suggested items based on shopper's criteria.",
|
|
47
|
+
"trigger": "When shopping criteria is entered.",
|
|
48
|
+
"details": {
|
|
49
|
+
"source": "AI integration",
|
|
50
|
+
"gql": "query GetSuggestedItems($sessionId: ID!) { suggestedItems(sessionId: $sessionId) { items { productId name quantity reason } } }"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"pages": {
|
|
59
|
+
"ShoppingAssistantPage": {
|
|
60
|
+
"route": "/shopping-assistant",
|
|
61
|
+
"description": "Page for shoppers to interact with the shopping assistant.",
|
|
62
|
+
"layout": {
|
|
63
|
+
"organisms": ["ShoppingAssistant"]
|
|
64
|
+
},
|
|
65
|
+
"navigation": [],
|
|
66
|
+
"data_requirements": [
|
|
67
|
+
{
|
|
68
|
+
"type": "mutation",
|
|
69
|
+
"description": "Enter shopping criteria.",
|
|
70
|
+
"trigger": "When shopper submits their criteria.",
|
|
71
|
+
"details": {
|
|
72
|
+
"source": "User input",
|
|
73
|
+
"gql": "mutation EnterShoppingCriteria($input: EnterShoppingCriteriaInput!) { enterShoppingCriteria(input: $input) { success error { type message } } }"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -15,7 +15,7 @@ async function main() {
|
|
|
15
15
|
const printedSchema = printSchema(schema);
|
|
16
16
|
|
|
17
17
|
const contextDir = path.resolve(
|
|
18
|
-
'/Users/
|
|
18
|
+
'/Users/ramihatoum/WebstormProjects/xolvio/auto-engineer/examples/shopping-app/.context/server',
|
|
19
19
|
'.context',
|
|
20
20
|
);
|
|
21
21
|
const schemaPath = path.join(contextDir, 'schema.graphql');
|
|
@@ -38,7 +38,7 @@ describe('Seasonal Assistant | accepts items and adds to their cart', () => {
|
|
|
38
38
|
{ productId: 'prod-laptop-bag', quantity: 1 },
|
|
39
39
|
{ productId: 'prod-mtg-starter', quantity: 1 },
|
|
40
40
|
],
|
|
41
|
-
timestamp: new Date('2025-09-
|
|
41
|
+
timestamp: new Date('2025-09-04T21:20:20.310Z'),
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
]);
|
|
@@ -0,0 +1,51 @@
|
|
|
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 { ShoppingCriteriaEntered } from '../enters-shopping-criteria-into-assistant/events';
|
|
7
|
+
import type { SuggestShoppingItems } from '../selects-items-relevant-to-the-shopping-criteria/commands';
|
|
8
|
+
|
|
9
|
+
describe('SeasonalAssistant | CreatesAChatSession', () => {
|
|
10
|
+
let eventStore: InMemoryEventStore;
|
|
11
|
+
let given: ReactorSpecification<ShoppingCriteriaEntered, SuggestShoppingItems, ReactorContext>;
|
|
12
|
+
let messageBus: CommandSender;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
eventStore = getInMemoryEventStore({});
|
|
16
|
+
given = ReactorSpecification.for<ShoppingCriteriaEntered, SuggestShoppingItems, 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 SuggestShoppingItems when ShoppingCriteriaEntered is received', async () => {
|
|
30
|
+
await given([])
|
|
31
|
+
.when({
|
|
32
|
+
type: 'ShoppingCriteriaEntered',
|
|
33
|
+
data: {
|
|
34
|
+
sessionId: 'session-abc',
|
|
35
|
+
criteria:
|
|
36
|
+
'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
|
|
37
|
+
timestamp: new Date('2025-09-04T21:20:20.309Z'),
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
.then({
|
|
42
|
+
type: 'SuggestShoppingItems',
|
|
43
|
+
kind: 'Command',
|
|
44
|
+
data: {
|
|
45
|
+
sessionId: 'session-abc',
|
|
46
|
+
prompt:
|
|
47
|
+
'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { inMemoryReactor, type MessageHandlerResult, IllegalStateError } from '@event-driven-io/emmett';
|
|
2
|
+
import type { ShoppingCriteriaEntered } from '../enters-shopping-criteria-into-assistant/events';
|
|
3
|
+
import type { ReactorContext } from '../../../shared';
|
|
4
|
+
|
|
5
|
+
export const react = ({ eventStore, commandSender }: ReactorContext) =>
|
|
6
|
+
inMemoryReactor<ShoppingCriteriaEntered>({
|
|
7
|
+
processorId: 'seasonal-assistant-creates-a-chat-session',
|
|
8
|
+
canHandle: ['ShoppingCriteriaEntered'],
|
|
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 ShoppingCriteriaEntered');
|
|
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: 'SuggestShoppingItems',
|
|
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
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type CommandSender, type EventSubscription, type InMemoryEventStore } from '@event-driven-io/emmett';
|
|
2
|
+
import type { ShoppingCriteriaEntered } from '../enters-shopping-criteria-into-assistant/events';
|
|
3
|
+
|
|
4
|
+
export async function register(messageBus: CommandSender & EventSubscription, eventStore: InMemoryEventStore) {
|
|
5
|
+
messageBus.subscribe(async (event: ShoppingCriteriaEntered) => {
|
|
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: 'SuggestShoppingItems',
|
|
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
|
+
}, 'ShoppingCriteriaEntered');
|
|
24
|
+
}
|
|
@@ -34,7 +34,7 @@ describe('SeasonalAssistant | CreatesAChatSession', () => {
|
|
|
34
34
|
sessionId: 'session-abc',
|
|
35
35
|
criteria:
|
|
36
36
|
'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
|
|
37
|
-
timestamp: new Date('2025-09-
|
|
37
|
+
timestamp: new Date('2025-09-04T20:53:48.700Z'),
|
|
38
38
|
},
|
|
39
39
|
})
|
|
40
40
|
|
|
@@ -30,7 +30,7 @@ describe('Seasonal Assistant | enters shopping criteria into assistant', () => {
|
|
|
30
30
|
sessionId: 'shopper-123',
|
|
31
31
|
criteria:
|
|
32
32
|
'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
|
|
33
|
-
timestamp: new Date('2025-09-
|
|
33
|
+
timestamp: new Date('2025-09-04T21:20:20.309Z'),
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
]);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, it } from 'vitest';
|
|
2
|
+
import { DeciderSpecification } from '@event-driven-io/emmett';
|
|
3
|
+
import { decide } from './decide';
|
|
4
|
+
import { evolve } from './evolve';
|
|
5
|
+
import { initialState } from './state';
|
|
6
|
+
|
|
7
|
+
describe('Seasonal Assistant | selects items relevant to the shopping criteria', () => {
|
|
8
|
+
const given = DeciderSpecification.for({
|
|
9
|
+
decide,
|
|
10
|
+
evolve,
|
|
11
|
+
initialState,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should emit ShoppingItemsSuggested for valid SuggestShoppingItems', () => {
|
|
15
|
+
given([
|
|
16
|
+
{
|
|
17
|
+
type: 'undefined',
|
|
18
|
+
data: {
|
|
19
|
+
products: [
|
|
20
|
+
{
|
|
21
|
+
productId: 'prod-soccer-ball',
|
|
22
|
+
name: 'Super Soccer Ball',
|
|
23
|
+
category: 'Sports',
|
|
24
|
+
price: 10,
|
|
25
|
+
tags: ['soccer', 'sports'],
|
|
26
|
+
imageUrl: 'https://example.com/soccer-ball.jpg',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
productId: 'prod-craft-kit',
|
|
30
|
+
name: 'Deluxe Craft Kit',
|
|
31
|
+
category: 'Arts & Crafts',
|
|
32
|
+
price: 25,
|
|
33
|
+
tags: ['crafts', 'art', 'creative'],
|
|
34
|
+
imageUrl: 'https://example.com/craft-kit.jpg',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
productId: 'prod-laptop-bag',
|
|
38
|
+
name: 'Tech Laptop Backpack',
|
|
39
|
+
category: 'School Supplies',
|
|
40
|
+
price: 45,
|
|
41
|
+
tags: ['computers', 'tech', 'school'],
|
|
42
|
+
imageUrl: 'https://example.com/laptop-bag.jpg',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
productId: 'prod-mtg-starter',
|
|
46
|
+
name: 'Magic the Gathering Starter Set',
|
|
47
|
+
category: 'Games',
|
|
48
|
+
price: 30,
|
|
49
|
+
tags: ['magic', 'tcg', 'games'],
|
|
50
|
+
imageUrl: 'https://example.com/mtg-starter.jpg',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
])
|
|
56
|
+
.when({
|
|
57
|
+
type: 'SuggestShoppingItems',
|
|
58
|
+
data: {
|
|
59
|
+
sessionId: 'session-abc',
|
|
60
|
+
prompt:
|
|
61
|
+
'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
|
|
62
|
+
},
|
|
63
|
+
metadata: { now: new Date() },
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
.then([
|
|
67
|
+
{
|
|
68
|
+
type: 'ShoppingItemsSuggested',
|
|
69
|
+
data: {
|
|
70
|
+
sessionId: 'session-abc',
|
|
71
|
+
suggestedItems: [
|
|
72
|
+
{
|
|
73
|
+
productId: 'prod-soccer-ball',
|
|
74
|
+
name: 'Super Soccer Ball',
|
|
75
|
+
quantity: 1,
|
|
76
|
+
reason: 'Perfect for your daughter who loves soccer',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
productId: 'prod-craft-kit',
|
|
80
|
+
name: 'Deluxe Craft Kit',
|
|
81
|
+
quantity: 1,
|
|
82
|
+
reason: 'Great for creative activities and crafts',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
productId: 'prod-laptop-bag',
|
|
86
|
+
name: 'Tech Laptop Backpack',
|
|
87
|
+
quantity: 1,
|
|
88
|
+
reason: "Essential for your son's school computer needs",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
productId: 'prod-mtg-starter',
|
|
92
|
+
name: 'Magic the Gathering Starter Set',
|
|
93
|
+
quantity: 1,
|
|
94
|
+
reason: 'Ideal starter set for Magic the Gathering enthusiasts',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { IllegalStateError } from '@event-driven-io/emmett';
|
|
2
|
+
import type { State } from './state';
|
|
3
|
+
import type { SuggestShoppingItems } from './commands';
|
|
4
|
+
import type { ShoppingItemsSuggested } from './events';
|
|
5
|
+
import type { Products } from '@auto-engineer/product-catalog-integration';
|
|
6
|
+
|
|
7
|
+
export const decide = (command: SuggestShoppingItems, state: State, products?: Products): ShoppingItemsSuggested => {
|
|
8
|
+
switch (command.type) {
|
|
9
|
+
case 'SuggestShoppingItems': {
|
|
10
|
+
/**
|
|
11
|
+
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
12
|
+
*
|
|
13
|
+
* This command requires evaluating prior state to determine if it can proceed.
|
|
14
|
+
*
|
|
15
|
+
* You should:
|
|
16
|
+
* - Validate the command input fields
|
|
17
|
+
* - Inspect the current domain `state` to determine if the command is allowed
|
|
18
|
+
* - Use `products` (integration result) to enrich or filter the output
|
|
19
|
+
* - If invalid, throw one of the following domain errors: `NotFoundError`, `ValidationError`, or `IllegalStateError`
|
|
20
|
+
* - If valid, return one or more events with the correct structure
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ Only read from inputs — never mutate them. `evolve.ts` handles state updates.
|
|
23
|
+
*
|
|
24
|
+
* Integration result shape (Products):
|
|
25
|
+
* products?.data = {
|
|
26
|
+
* products: Array<{
|
|
27
|
+
* productId: string; name;
|
|
28
|
+
* }>;
|
|
29
|
+
* }
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// return {
|
|
33
|
+
// type: 'ShoppingItemsSuggested',
|
|
34
|
+
// data: { ...command.data },
|
|
35
|
+
// } as ShoppingItemsSuggested;
|
|
36
|
+
|
|
37
|
+
throw new IllegalStateError('Not yet implemented: ' + command.type);
|
|
38
|
+
}
|
|
39
|
+
default:
|
|
40
|
+
throw new IllegalStateError('Unexpected command type: ' + command.type);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { State } from './state';
|
|
2
|
+
import type { ShoppingItemsSuggested } from './events';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
6
|
+
*
|
|
7
|
+
* This function defines how the domain state evolves in response to events.
|
|
8
|
+
*
|
|
9
|
+
* Guidelines:
|
|
10
|
+
* - Apply only the **minimal** necessary changes for future decisions in `decide.ts`.
|
|
11
|
+
* - Ignore any event fields not required for decision-making logic.
|
|
12
|
+
* - If the event doesn’t change decision-relevant state, return the existing `state`.
|
|
13
|
+
* - Prefer immutability: always return a **new state object**.
|
|
14
|
+
* - Avoid spreading all of `event.data` unless all fields are relevant.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const evolve = (state: State, event: ShoppingItemsSuggested): State => {
|
|
18
|
+
switch (event.type) {
|
|
19
|
+
case 'ShoppingItemsSuggested': {
|
|
20
|
+
// TODO: Update state based on ShoppingItemsSuggested
|
|
21
|
+
return {
|
|
22
|
+
...state,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
default:
|
|
26
|
+
return state;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import '@auto-engineer/product-catalog-integration';
|
|
2
|
+
|
|
3
|
+
import { AI } from '@auto-engineer/ai-integration';
|
|
4
|
+
|
|
5
|
+
import { Products } from '@auto-engineer/product-catalog-integration';
|
|
6
|
+
|
|
7
|
+
import { CommandHandler, type EventStore, type MessageHandlerResult } from '@event-driven-io/emmett';
|
|
8
|
+
import { evolve } from './evolve';
|
|
9
|
+
import { initialState } from './state';
|
|
10
|
+
import { decide } from './decide';
|
|
11
|
+
import type { SuggestShoppingItems } from './commands';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
15
|
+
* add the following to the DoChat: schemaName: Products, systemPrompt: use the PRODUCT_CATALOGUE_PRODUCTS MCP tool to get product data
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const handler = CommandHandler({
|
|
19
|
+
evolve,
|
|
20
|
+
initialState,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const handle = async (eventStore: EventStore, command: SuggestShoppingItems): Promise<MessageHandlerResult> => {
|
|
24
|
+
const streamId = `shopping-session-${command.data.sessionId}`;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// TODO: Map fields from the incoming command to this integration input.
|
|
28
|
+
// - Use relevant fields from `command.data` to populate the required inputs below.
|
|
29
|
+
// - Some fields may require transformation or enrichment.
|
|
30
|
+
// - If additional context is needed, construct it here.
|
|
31
|
+
// const products: Products | undefined = await AI.Commands?.DoChat<Products>({
|
|
32
|
+
// type: 'DoChat',
|
|
33
|
+
// data: {
|
|
34
|
+
// // sessionId: ???
|
|
35
|
+
// prompt: ???
|
|
36
|
+
// systemPrompt: ???
|
|
37
|
+
// schemaName: ???
|
|
38
|
+
// },
|
|
39
|
+
// });
|
|
40
|
+
|
|
41
|
+
await handler(eventStore, streamId, (state) =>
|
|
42
|
+
// TODO: add products as a parameter to decide once implemented above
|
|
43
|
+
decide(command, state /* products */),
|
|
44
|
+
);
|
|
45
|
+
return; // success (returns void)
|
|
46
|
+
} catch (error: any) {
|
|
47
|
+
return {
|
|
48
|
+
type: 'SKIP',
|
|
49
|
+
reason: `Command failed: ${error?.message ?? 'Unknown'}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Mutation, Resolver, Arg, Ctx, Field, InputType } from 'type-graphql';
|
|
2
|
+
import { type GraphQLContext, sendCommand, MutationResponse } from '../../../shared';
|
|
3
|
+
|
|
4
|
+
@InputType()
|
|
5
|
+
export class SuggestShoppingItemsInput {
|
|
6
|
+
@Field(() => String)
|
|
7
|
+
sessionId!: string;
|
|
8
|
+
@Field(() => String)
|
|
9
|
+
prompt!: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@Resolver()
|
|
13
|
+
export class SuggestShoppingItemsResolver {
|
|
14
|
+
@Mutation(() => MutationResponse)
|
|
15
|
+
async suggestShoppingItems(
|
|
16
|
+
@Arg('input', () => SuggestShoppingItemsInput) input: SuggestShoppingItemsInput,
|
|
17
|
+
@Ctx() ctx: GraphQLContext,
|
|
18
|
+
): Promise<MutationResponse> {
|
|
19
|
+
return await sendCommand(ctx.messageBus, {
|
|
20
|
+
type: 'SuggestShoppingItems',
|
|
21
|
+
kind: 'Command',
|
|
22
|
+
data: { ...input },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CommandProcessor, EventStore } from '@event-driven-io/emmett';
|
|
2
|
+
import { handle } from './handle';
|
|
3
|
+
import type { SuggestShoppingItems } from './commands';
|
|
4
|
+
|
|
5
|
+
export function register(messageBus: CommandProcessor, eventStore: EventStore) {
|
|
6
|
+
messageBus.handle((command: SuggestShoppingItems) => handle(eventStore, command), 'SuggestShoppingItems');
|
|
7
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
3
|
+
*
|
|
4
|
+
* Define the shape of the domain state for the current slice below. This state is used by `decide.ts`
|
|
5
|
+
* to determine whether a command is valid.
|
|
6
|
+
*
|
|
7
|
+
* The state is evolved over time by applying domain events (in `evolve.ts`).
|
|
8
|
+
* Each event updates the state incrementally based on business rules.
|
|
9
|
+
*
|
|
10
|
+
* Guidelines:
|
|
11
|
+
* - Include only fields that are **read** during command validation.
|
|
12
|
+
* - Use discriminated unions (e.g., `status: 'Pending' | 'Done'`) to model state transitions.
|
|
13
|
+
* - Prefer primitive types: `string`, `boolean`, `number`.
|
|
14
|
+
* - Use objects or maps only when structure is essential for decision logic.
|
|
15
|
+
*
|
|
16
|
+
* Do NOT include:
|
|
17
|
+
* - Redundant data already emitted in events unless required to enforce business rules.
|
|
18
|
+
* - Fields used only for projections, UI, or query purposes.
|
|
19
|
+
*
|
|
20
|
+
* ### Example (for a Task domain):
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* export type PendingTask = {
|
|
24
|
+
* status: 'Pending';
|
|
25
|
+
* };
|
|
26
|
+
*
|
|
27
|
+
* export type InProgressTask = {
|
|
28
|
+
* status: 'InProgress';
|
|
29
|
+
* startedAt: string;
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
* export type CompletedTask = {
|
|
33
|
+
* status: 'Completed';
|
|
34
|
+
* completedAt: string;
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* export type State = PendingTask | InProgressTask | CompletedTask;
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// TODO: Replace with a discriminated union of domain states for the current slice
|
|
42
|
+
export type State = {};
|
|
43
|
+
|
|
44
|
+
// TODO: Replace the Return with the initial domain state of the current slice
|
|
45
|
+
export const initialState = (): State => {
|
|
46
|
+
return {};
|
|
47
|
+
};
|
|
@@ -12,7 +12,47 @@ describe('Seasonal Assistant | selects items relevant to the shopping criteria
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it('should emit ShoppingItemsSuggested for valid SuggestShoppingItems', () => {
|
|
15
|
-
given([
|
|
15
|
+
given([
|
|
16
|
+
{
|
|
17
|
+
type: 'undefined',
|
|
18
|
+
data: {
|
|
19
|
+
products: [
|
|
20
|
+
{
|
|
21
|
+
productId: 'prod-soccer-ball',
|
|
22
|
+
name: 'Super Soccer Ball',
|
|
23
|
+
category: 'Sports',
|
|
24
|
+
price: 10,
|
|
25
|
+
tags: ['soccer', 'sports'],
|
|
26
|
+
imageUrl: 'https://example.com/soccer-ball.jpg',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
productId: 'prod-craft-kit',
|
|
30
|
+
name: 'Deluxe Craft Kit',
|
|
31
|
+
category: 'Arts & Crafts',
|
|
32
|
+
price: 25,
|
|
33
|
+
tags: ['crafts', 'art', 'creative'],
|
|
34
|
+
imageUrl: 'https://example.com/craft-kit.jpg',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
productId: 'prod-laptop-bag',
|
|
38
|
+
name: 'Tech Laptop Backpack',
|
|
39
|
+
category: 'School Supplies',
|
|
40
|
+
price: 45,
|
|
41
|
+
tags: ['computers', 'tech', 'school'],
|
|
42
|
+
imageUrl: 'https://example.com/laptop-bag.jpg',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
productId: 'prod-mtg-starter',
|
|
46
|
+
name: 'Magic the Gathering Starter Set',
|
|
47
|
+
category: 'Games',
|
|
48
|
+
price: 30,
|
|
49
|
+
tags: ['magic', 'tcg', 'games'],
|
|
50
|
+
imageUrl: 'https://example.com/mtg-starter.jpg',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
])
|
|
16
56
|
.when({
|
|
17
57
|
type: 'SuggestShoppingItems',
|
|
18
58
|
data: {
|
|
@@ -10,7 +10,7 @@ export const decide = (command: SuggestShoppingItems, state: State, products?: P
|
|
|
10
10
|
/**
|
|
11
11
|
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
12
12
|
*
|
|
13
|
-
* This command
|
|
13
|
+
* This command requires evaluating prior state to determine if it can proceed.
|
|
14
14
|
*
|
|
15
15
|
* You should:
|
|
16
16
|
* - Validate the command input fields
|
|
@@ -20,6 +20,14 @@ export const decide = (command: SuggestShoppingItems, state: State, products?: P
|
|
|
20
20
|
* - If valid, return one or more events with the correct structure
|
|
21
21
|
*
|
|
22
22
|
* ⚠️ Only read from inputs — never mutate them. `evolve.ts` handles state updates.
|
|
23
|
+
*
|
|
24
|
+
* Integration result shape (Products):
|
|
25
|
+
* products?.data = {
|
|
26
|
+
* type: "Products";
|
|
27
|
+
* products: Array<{
|
|
28
|
+
* productId: string; name;
|
|
29
|
+
* }>;
|
|
30
|
+
* }
|
|
23
31
|
*/
|
|
24
32
|
|
|
25
33
|
// return {
|
|
@@ -31,7 +31,10 @@ export const handle = async (eventStore: EventStore, command: SuggestShoppingIte
|
|
|
31
31
|
// const products: Products | undefined = await AI.Commands?.DoChat<Products>({
|
|
32
32
|
// type: 'DoChat',
|
|
33
33
|
// data: {
|
|
34
|
-
//
|
|
34
|
+
// // sessionId: ???
|
|
35
|
+
// prompt: ???
|
|
36
|
+
// systemPrompt: ???
|
|
37
|
+
// schemaName: ???
|
|
35
38
|
// },
|
|
36
39
|
// });
|
|
37
40
|
|