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.
Files changed (187) hide show
  1. package/dist/index.js +189 -85
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/shopping-app/.context/auto-ia-scheme.json +175 -0
  5. package/templates/shopping-app/.context/design-system.md +118 -0
  6. package/templates/shopping-app/.context/figma-variables.json +14448 -0
  7. package/templates/shopping-app/.context/schema.graphql +46 -0
  8. package/templates/shopping-app/.context/schema.json +583 -0
  9. package/templates/shopping-app/.context/shadcn-filter.ts +31 -0
  10. package/templates/shopping-app/.env +8 -0
  11. package/templates/shopping-app/auto.config.ts +21 -0
  12. package/templates/shopping-app/client/auto-configure.ts +107 -0
  13. package/templates/shopping-app/client/codegen.ts +16 -0
  14. package/templates/shopping-app/client/components.json +20 -0
  15. package/templates/shopping-app/client/design-system-principles.md +19 -0
  16. package/templates/shopping-app/client/eslint.config.ts +57 -0
  17. package/templates/shopping-app/client/index.html +26 -0
  18. package/templates/shopping-app/client/package.json +100 -0
  19. package/templates/shopping-app/client/postcss.config.js +6 -0
  20. package/templates/shopping-app/client/public/favicon.ico +0 -0
  21. package/templates/shopping-app/client/schema.graphql +46 -0
  22. package/templates/shopping-app/client/src/App.css +42 -0
  23. package/templates/shopping-app/client/src/App.tsx +20 -0
  24. package/templates/shopping-app/client/src/apolloClient.ts +8 -0
  25. package/templates/shopping-app/client/src/components/atoms/accordion.tsx +51 -0
  26. package/templates/shopping-app/client/src/components/atoms/alert-dialog.tsx +111 -0
  27. package/templates/shopping-app/client/src/components/atoms/alert.tsx +49 -0
  28. package/templates/shopping-app/client/src/components/atoms/aspect-ratio.tsx +7 -0
  29. package/templates/shopping-app/client/src/components/atoms/avatar.tsx +32 -0
  30. package/templates/shopping-app/client/src/components/atoms/badge.tsx +36 -0
  31. package/templates/shopping-app/client/src/components/atoms/breadcrumb.tsx +92 -0
  32. package/templates/shopping-app/client/src/components/atoms/button.tsx +50 -0
  33. package/templates/shopping-app/client/src/components/atoms/calendar.tsx +156 -0
  34. package/templates/shopping-app/client/src/components/atoms/card.tsx +56 -0
  35. package/templates/shopping-app/client/src/components/atoms/carousel.tsx +214 -0
  36. package/templates/shopping-app/client/src/components/atoms/chart.tsx +290 -0
  37. package/templates/shopping-app/client/src/components/atoms/checkbox.tsx +27 -0
  38. package/templates/shopping-app/client/src/components/atoms/collapsible.tsx +15 -0
  39. package/templates/shopping-app/client/src/components/atoms/command.tsx +135 -0
  40. package/templates/shopping-app/client/src/components/atoms/context-menu.tsx +209 -0
  41. package/templates/shopping-app/client/src/components/atoms/dialog.tsx +123 -0
  42. package/templates/shopping-app/client/src/components/atoms/drawer.tsx +106 -0
  43. package/templates/shopping-app/client/src/components/atoms/dropdown-menu.tsx +217 -0
  44. package/templates/shopping-app/client/src/components/atoms/hover-card.tsx +36 -0
  45. package/templates/shopping-app/client/src/components/atoms/input-otp.tsx +66 -0
  46. package/templates/shopping-app/client/src/components/atoms/input.tsx +21 -0
  47. package/templates/shopping-app/client/src/components/atoms/label.tsx +19 -0
  48. package/templates/shopping-app/client/src/components/atoms/logo.tsx +33 -0
  49. package/templates/shopping-app/client/src/components/atoms/menubar.tsx +234 -0
  50. package/templates/shopping-app/client/src/components/atoms/navigation-menu.tsx +142 -0
  51. package/templates/shopping-app/client/src/components/atoms/pagination.tsx +100 -0
  52. package/templates/shopping-app/client/src/components/atoms/popover.tsx +40 -0
  53. package/templates/shopping-app/client/src/components/atoms/progress.tsx +22 -0
  54. package/templates/shopping-app/client/src/components/atoms/radio-group.tsx +31 -0
  55. package/templates/shopping-app/client/src/components/atoms/resizable.tsx +46 -0
  56. package/templates/shopping-app/client/src/components/atoms/scroll-area.tsx +46 -0
  57. package/templates/shopping-app/client/src/components/atoms/select.tsx +158 -0
  58. package/templates/shopping-app/client/src/components/atoms/separator.tsx +26 -0
  59. package/templates/shopping-app/client/src/components/atoms/sheet.tsx +101 -0
  60. package/templates/shopping-app/client/src/components/atoms/sidebar.tsx +675 -0
  61. package/templates/shopping-app/client/src/components/atoms/skeleton.tsx +7 -0
  62. package/templates/shopping-app/client/src/components/atoms/slider.tsx +54 -0
  63. package/templates/shopping-app/client/src/components/atoms/sonner.tsx +23 -0
  64. package/templates/shopping-app/client/src/components/atoms/switch.tsx +26 -0
  65. package/templates/shopping-app/client/src/components/atoms/table.tsx +73 -0
  66. package/templates/shopping-app/client/src/components/atoms/tabs.tsx +40 -0
  67. package/templates/shopping-app/client/src/components/atoms/textarea.tsx +18 -0
  68. package/templates/shopping-app/client/src/components/atoms/toggle-group.tsx +65 -0
  69. package/templates/shopping-app/client/src/components/atoms/toggle.tsx +39 -0
  70. package/templates/shopping-app/client/src/components/atoms/tooltip.tsx +48 -0
  71. package/templates/shopping-app/client/src/components/molecules/QuantitySelector.tsx +8 -0
  72. package/templates/shopping-app/client/src/components/molecules/ShoppingCriteriaForm.tsx +8 -0
  73. package/templates/shopping-app/client/src/components/molecules/SuggestedItemCard.tsx +10 -0
  74. package/templates/shopping-app/client/src/components/organisms/AssistantChatInterface.tsx +14 -0
  75. package/templates/shopping-app/client/src/components/organisms/PageHeader.tsx +6 -0
  76. package/templates/shopping-app/client/src/components/organisms/SuggestedItemsList.tsx +15 -0
  77. package/templates/shopping-app/client/src/gql/fragment-masking.ts +48 -0
  78. package/templates/shopping-app/client/src/gql/gql.ts +47 -0
  79. package/templates/shopping-app/client/src/gql/graphql.ts +106 -0
  80. package/templates/shopping-app/client/src/gql/index.ts +2 -0
  81. package/templates/shopping-app/client/src/graphql/mutations.ts +13 -0
  82. package/templates/shopping-app/client/src/graphql/queries.ts +14 -0
  83. package/templates/shopping-app/client/src/hooks/use-mobile.tsx +19 -0
  84. package/templates/shopping-app/client/src/hooks/use-toast.ts +186 -0
  85. package/templates/shopping-app/client/src/index.css +153 -0
  86. package/templates/shopping-app/client/src/lib/utils.ts +6 -0
  87. package/templates/shopping-app/client/src/main.tsx +5 -0
  88. package/templates/shopping-app/client/src/mockApolloClient.ts +93 -0
  89. package/templates/shopping-app/client/src/pages/AssistantChatPage.tsx +8 -0
  90. package/templates/shopping-app/client/src/pages/Index.tsx +10 -0
  91. package/templates/shopping-app/client/src/pages/NotFound.tsx +22 -0
  92. package/templates/shopping-app/client/src/pages/SuggestedItemsPage.tsx +8 -0
  93. package/templates/shopping-app/client/tailwind.config.ts +92 -0
  94. package/templates/shopping-app/client/tsconfig.json +49 -0
  95. package/templates/shopping-app/client/vite.config.ts +17 -0
  96. package/templates/shopping-app/flows/shopping-assistant.flow.ts +369 -0
  97. package/templates/shopping-app/package.json +25 -0
  98. package/templates/shopping-app/pnpm-workspace.yaml +2 -0
  99. package/templates/shopping-app/server/package.json +25 -0
  100. package/templates/shopping-app/server/scripts/generate-schema.ts +31 -0
  101. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/commands.ts +8 -0
  102. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/decide.specs.ts +46 -0
  103. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/decide.ts +36 -0
  104. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/events.ts +10 -0
  105. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/evolve.ts +28 -0
  106. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/handle.ts +31 -0
  107. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/mutation.resolver.ts +29 -0
  108. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/register.ts +10 -0
  109. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/accepts-items-and-adds-to-their-cart/state.ts +47 -0
  110. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/react.specs.ts +63 -0
  111. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/react.ts +49 -0
  112. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/creates-a-chat-session-/register.ts +31 -0
  113. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/commands.ts +8 -0
  114. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/decide.specs.ts +38 -0
  115. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/decide.ts +36 -0
  116. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/events.ts +10 -0
  117. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/evolve.ts +28 -0
  118. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/handle.ts +31 -0
  119. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/mutation.resolver.ts +30 -0
  120. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/register.ts +10 -0
  121. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/enters-shopping-criteria-into-assistant/state.ts +47 -0
  122. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/commands.ts +8 -0
  123. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/decide.specs.ts +61 -0
  124. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/decide.ts +45 -0
  125. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/events.ts +14 -0
  126. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/evolve.ts +28 -0
  127. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/handle.ts +59 -0
  128. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/mutation.resolver.ts +30 -0
  129. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/register.ts +10 -0
  130. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/selects-items-relevant-to-the-shopping-criteria-/state.ts +47 -0
  131. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/projection.specs.ts +94 -0
  132. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/projection.ts +40 -0
  133. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/query.resolver.ts +59 -0
  134. package/templates/shopping-app/server/src/domain/flows/seasonal-assistant/views-suggested-items/state.ts +9 -0
  135. package/templates/shopping-app/server/src/domain/shared/ReadModel.d.ts +10 -0
  136. package/templates/shopping-app/server/src/domain/shared/ReadModel.d.ts.map +1 -0
  137. package/templates/shopping-app/server/src/domain/shared/ReadModel.js +19 -0
  138. package/templates/shopping-app/server/src/domain/shared/ReadModel.js.map +1 -0
  139. package/templates/shopping-app/server/src/domain/shared/ReadModel.ts +26 -0
  140. package/templates/shopping-app/server/src/domain/shared/index.d.ts +5 -0
  141. package/templates/shopping-app/server/src/domain/shared/index.d.ts.map +1 -0
  142. package/templates/shopping-app/server/src/domain/shared/index.js +5 -0
  143. package/templates/shopping-app/server/src/domain/shared/index.js.map +1 -0
  144. package/templates/shopping-app/server/src/domain/shared/index.ts +4 -0
  145. package/templates/shopping-app/server/src/domain/shared/reactorSpecification.d.ts +35 -0
  146. package/templates/shopping-app/server/src/domain/shared/reactorSpecification.d.ts.map +1 -0
  147. package/templates/shopping-app/server/src/domain/shared/reactorSpecification.js +155 -0
  148. package/templates/shopping-app/server/src/domain/shared/reactorSpecification.js.map +1 -0
  149. package/templates/shopping-app/server/src/domain/shared/reactorSpecification.ts +257 -0
  150. package/templates/shopping-app/server/src/domain/shared/sendCommand.d.ts +4 -0
  151. package/templates/shopping-app/server/src/domain/shared/sendCommand.d.ts.map +1 -0
  152. package/templates/shopping-app/server/src/domain/shared/sendCommand.js +17 -0
  153. package/templates/shopping-app/server/src/domain/shared/sendCommand.js.map +1 -0
  154. package/templates/shopping-app/server/src/domain/shared/sendCommand.ts +21 -0
  155. package/templates/shopping-app/server/src/domain/shared/types.d.ts +19 -0
  156. package/templates/shopping-app/server/src/domain/shared/types.d.ts.map +1 -0
  157. package/templates/shopping-app/server/src/domain/shared/types.js +39 -0
  158. package/templates/shopping-app/server/src/domain/shared/types.js.map +1 -0
  159. package/templates/shopping-app/server/src/domain/shared/types.ts +31 -0
  160. package/templates/shopping-app/server/src/integrations/ai-integration.ts +76 -0
  161. package/templates/shopping-app/server/src/integrations/cart-integration.ts +178 -0
  162. package/templates/shopping-app/server/src/integrations/index.ts +3 -0
  163. package/templates/shopping-app/server/src/integrations/product-catalogue-integration.ts +363 -0
  164. package/templates/shopping-app/server/src/server.ts +43 -0
  165. package/templates/shopping-app/server/src/utils/index.d.ts +4 -0
  166. package/templates/shopping-app/server/src/utils/index.d.ts.map +1 -0
  167. package/templates/shopping-app/server/src/utils/index.js +4 -0
  168. package/templates/shopping-app/server/src/utils/index.js.map +1 -0
  169. package/templates/shopping-app/server/src/utils/index.ts +3 -0
  170. package/templates/shopping-app/server/src/utils/loadProjections.d.ts +3 -0
  171. package/templates/shopping-app/server/src/utils/loadProjections.d.ts.map +1 -0
  172. package/templates/shopping-app/server/src/utils/loadProjections.js +23 -0
  173. package/templates/shopping-app/server/src/utils/loadProjections.js.map +1 -0
  174. package/templates/shopping-app/server/src/utils/loadProjections.ts +30 -0
  175. package/templates/shopping-app/server/src/utils/loadRegisterFiles.d.ts +6 -0
  176. package/templates/shopping-app/server/src/utils/loadRegisterFiles.d.ts.map +1 -0
  177. package/templates/shopping-app/server/src/utils/loadRegisterFiles.js +28 -0
  178. package/templates/shopping-app/server/src/utils/loadRegisterFiles.js.map +1 -0
  179. package/templates/shopping-app/server/src/utils/loadRegisterFiles.ts +41 -0
  180. package/templates/shopping-app/server/src/utils/loadResolvers.d.ts +5 -0
  181. package/templates/shopping-app/server/src/utils/loadResolvers.d.ts.map +1 -0
  182. package/templates/shopping-app/server/src/utils/loadResolvers.js +27 -0
  183. package/templates/shopping-app/server/src/utils/loadResolvers.js.map +1 -0
  184. package/templates/shopping-app/server/src/utils/loadResolvers.ts +36 -0
  185. package/templates/shopping-app/server/tsconfig.json +19 -0
  186. package/templates/shopping-app/server/vitest.config.ts +7 -0
  187. package/templates/shopping-app/tsconfig.json +21 -0
@@ -0,0 +1,36 @@
1
+ import { IllegalStateError } from "@event-driven-io/emmett";
2
+ import type { State } from "./state";
3
+ import type { AddItemsToCart } from "./commands";
4
+ import type { ItemsAddedToCart } from "./events";
5
+
6
+ export const decide = (
7
+ command: AddItemsToCart,
8
+ state: State,
9
+ ): ItemsAddedToCart => {
10
+ switch (command.type) {
11
+ case "AddItemsToCart": {
12
+ /**
13
+ * ## IMPLEMENTATION INSTRUCTIONS ##
14
+ *
15
+ * This command can directly emit one or more events based on the input.
16
+ *
17
+ * You should:
18
+ * - Validate the command input fields
19
+ * - Inspect the current domain `state` to determine if the command is allowed
20
+ * - If invalid, throw one of the following domain errors: `NotFoundError`, `ValidationError`, or `IllegalStateError`
21
+ * - If valid, return one or more events with the correct structure
22
+ *
23
+ * ⚠️ Only read from inputs — never mutate them. `evolve.ts` handles state updates.
24
+ */
25
+
26
+ // return {
27
+ // type: 'ItemsAddedToCart',
28
+ // data: { ...command.data },
29
+ // } as ItemsAddedToCart;
30
+
31
+ throw new IllegalStateError("Not yet implemented: " + command.type);
32
+ }
33
+ default:
34
+ throw new IllegalStateError("Unexpected command type: " + command.type);
35
+ }
36
+ };
@@ -0,0 +1,10 @@
1
+ import type { Event } from "@event-driven-io/emmett";
2
+
3
+ export type ItemsAddedToCart = Event<
4
+ "ItemsAddedToCart",
5
+ {
6
+ sessionId: string;
7
+ items: Array<{ productId: string; quantity: number }>;
8
+ timestamp: Date;
9
+ }
10
+ >;
@@ -0,0 +1,28 @@
1
+ import type { State } from "./state";
2
+ import type { ItemsAddedToCart } 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: ItemsAddedToCart): State => {
18
+ switch (event.type) {
19
+ case "ItemsAddedToCart": {
20
+ // TODO: Update state based on ItemsAddedToCart
21
+ return {
22
+ ...state,
23
+ };
24
+ }
25
+ default:
26
+ return state;
27
+ }
28
+ };
@@ -0,0 +1,31 @@
1
+ import {
2
+ CommandHandler,
3
+ type EventStore,
4
+ type MessageHandlerResult,
5
+ } from "@event-driven-io/emmett";
6
+ import { evolve } from "./evolve";
7
+ import { initialState } from "./state";
8
+ import { decide } from "./decide";
9
+ import type { AddItemsToCart } from "./commands";
10
+
11
+ const handler = CommandHandler({
12
+ evolve,
13
+ initialState,
14
+ });
15
+
16
+ export const handle = async (
17
+ eventStore: EventStore,
18
+ command: AddItemsToCart,
19
+ ): Promise<MessageHandlerResult> => {
20
+ const streamId = `shopping-session-${command.data.sessionId}`;
21
+
22
+ try {
23
+ await handler(eventStore, streamId, (state) => decide(command, state));
24
+ return; // success (returns void)
25
+ } catch (error: any) {
26
+ return {
27
+ type: "SKIP",
28
+ reason: `Command failed: ${error?.message ?? "Unknown"}`,
29
+ };
30
+ }
31
+ };
@@ -0,0 +1,29 @@
1
+ import { Mutation, Resolver, Arg, Ctx, Field, InputType } from "type-graphql";
2
+ import {
3
+ type GraphQLContext,
4
+ sendCommand,
5
+ MutationResponse,
6
+ } from "../../../shared";
7
+
8
+ @InputType()
9
+ export class AddItemsToCartInput {
10
+ @Field(() => String)
11
+ sessionId!: string;
12
+ @Field(() => String)
13
+ items!: Array<{ productId: string; quantity: number }>;
14
+ }
15
+
16
+ @Resolver()
17
+ export class AddItemsToCartResolver {
18
+ @Mutation(() => MutationResponse)
19
+ async addItemsToCart(
20
+ @Arg("input", () => AddItemsToCartInput) input: AddItemsToCartInput,
21
+ @Ctx() ctx: GraphQLContext,
22
+ ): Promise<MutationResponse> {
23
+ return await sendCommand(ctx.messageBus, {
24
+ type: "AddItemsToCart",
25
+ kind: "Command",
26
+ data: { ...input },
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,10 @@
1
+ import type { CommandProcessor, EventStore } from "@event-driven-io/emmett";
2
+ import { handle } from "./handle";
3
+ import type { AddItemsToCart } from "./commands";
4
+
5
+ export function register(messageBus: CommandProcessor, eventStore: EventStore) {
6
+ messageBus.handle(
7
+ (command: AddItemsToCart) => handle(eventStore, command),
8
+ "AddItemsToCart",
9
+ );
10
+ }
@@ -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
+ };
@@ -0,0 +1,63 @@
1
+ import { describe, it, beforeEach } from "vitest";
2
+ import "reflect-metadata";
3
+ import {
4
+ getInMemoryEventStore,
5
+ type InMemoryEventStore,
6
+ type CommandSender,
7
+ } from "@event-driven-io/emmett";
8
+ import { type ReactorContext, ReactorSpecification } from "../../../shared";
9
+ import { react } from "./react";
10
+ import type { ShoppingCriteriaEntered } from "../enters-shopping-criteria-into-assistant/events";
11
+ import type { SuggestShoppingItems } from "../selects-items-relevant-to-the-shopping-criteria-/commands";
12
+
13
+ describe("SeasonalAssistant | CreatesAChatSession", () => {
14
+ let eventStore: InMemoryEventStore;
15
+ let given: ReactorSpecification<
16
+ ShoppingCriteriaEntered,
17
+ SuggestShoppingItems,
18
+ ReactorContext
19
+ >;
20
+ let messageBus: CommandSender;
21
+
22
+ beforeEach(() => {
23
+ eventStore = getInMemoryEventStore({});
24
+ given = ReactorSpecification.for<
25
+ ShoppingCriteriaEntered,
26
+ SuggestShoppingItems,
27
+ ReactorContext
28
+ >(
29
+ () => react({ eventStore, commandSender: messageBus }),
30
+ (commandSender) => {
31
+ messageBus = commandSender;
32
+ return {
33
+ eventStore,
34
+ commandSender,
35
+ database: eventStore.database,
36
+ };
37
+ },
38
+ );
39
+ });
40
+
41
+ it("should send SuggestShoppingItems when ShoppingCriteriaEntered is received", async () => {
42
+ await given([])
43
+ .when({
44
+ type: "ShoppingCriteriaEntered",
45
+ data: {
46
+ sessionId: "session-abc",
47
+ criteria:
48
+ "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.",
49
+ timestamp: new Date("2025-08-28T04:56:47.408Z"),
50
+ },
51
+ })
52
+
53
+ .then({
54
+ type: "SuggestShoppingItems",
55
+ kind: "Command",
56
+ data: {
57
+ sessionId: "session-abc",
58
+ prompt:
59
+ "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.",
60
+ },
61
+ });
62
+ });
63
+ });
@@ -0,0 +1,49 @@
1
+ import {
2
+ inMemoryReactor,
3
+ type MessageHandlerResult,
4
+ IllegalStateError,
5
+ } from "@event-driven-io/emmett";
6
+ import type { ShoppingCriteriaEntered } from "../enters-shopping-criteria-into-assistant/events";
7
+ import type { ReactorContext } from "../../../shared";
8
+
9
+ export const react = ({ eventStore, commandSender }: ReactorContext) =>
10
+ inMemoryReactor<ShoppingCriteriaEntered>({
11
+ processorId: "seasonal-assistant-creates-a-chat-session-",
12
+ canHandle: ["ShoppingCriteriaEntered"],
13
+ connectionOptions: {
14
+ database: eventStore.database,
15
+ },
16
+ eachMessage: async (event, context): Promise<MessageHandlerResult> => {
17
+ /**
18
+ * ## IMPLEMENTATION INSTRUCTIONS ##
19
+ *
20
+ * - Inspect event data to determine if the command should be sent.
21
+ * - Replace the placeholder logic and \`throw\` below with real implementation.
22
+ * - Send one or more commands via: context.commandSender.send({...})
23
+ * - Optionally return a MessageHandlerResult for SKIP or error cases.
24
+ */
25
+
26
+ throw new IllegalStateError(
27
+ "Not yet implemented: react in response to ShoppingCriteriaEntered",
28
+ );
29
+
30
+ // Example:
31
+ // if (event.data.status !== 'expected') {
32
+ // return {
33
+ // type: 'SKIP',
34
+ // reason: 'Condition not met',
35
+ // };
36
+ // }
37
+
38
+ // await context.commandSender.send({
39
+ // type: 'SuggestShoppingItems',
40
+ // kind: 'Command',
41
+ // data: {
42
+ // // Map event fields to command fields here
43
+ // // e.g., userId: event.data.userId,
44
+ // },
45
+ // });
46
+
47
+ // return;
48
+ },
49
+ });
@@ -0,0 +1,31 @@
1
+ import {
2
+ type CommandSender,
3
+ type EventSubscription,
4
+ type InMemoryEventStore,
5
+ } from "@event-driven-io/emmett";
6
+ import type { ShoppingCriteriaEntered } from "../enters-shopping-criteria-into-assistant/events";
7
+
8
+ export async function register(
9
+ messageBus: CommandSender & EventSubscription,
10
+ eventStore: InMemoryEventStore,
11
+ ) {
12
+ messageBus.subscribe(async (event: ShoppingCriteriaEntered) => {
13
+ /**
14
+ * ## IMPLEMENTATION INSTRUCTIONS ##
15
+ *
16
+ * - Replace the placeholder logic with the real implementation.
17
+ * - Send one or more commands via: messageBus.send({...})
18
+ */
19
+
20
+ // await messageBus.send({
21
+ // type: 'SuggestShoppingItems',
22
+ // kind: 'Command',
23
+ // data: {
24
+ // // Map event fields to command fields here
25
+ // // e.g., userId: event.data.userId,
26
+ // },
27
+ // });
28
+
29
+ return;
30
+ }, "ShoppingCriteriaEntered");
31
+ }
@@ -0,0 +1,8 @@
1
+ import { Command } from "@event-driven-io/emmett";
2
+ export type EnterShoppingCriteria = Command<
3
+ "EnterShoppingCriteria",
4
+ {
5
+ sessionId: string;
6
+ criteria: string;
7
+ }
8
+ >;
@@ -0,0 +1,38 @@
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 | enters shopping criteria into assistant", () => {
8
+ const given = DeciderSpecification.for({
9
+ decide,
10
+ evolve,
11
+ initialState,
12
+ });
13
+
14
+ it("should emit ShoppingCriteriaEntered for valid EnterShoppingCriteria", () => {
15
+ given([])
16
+ .when({
17
+ type: "EnterShoppingCriteria",
18
+ data: {
19
+ sessionId: "shopper-123",
20
+ criteria:
21
+ "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.",
22
+ },
23
+ metadata: { now: new Date() },
24
+ })
25
+
26
+ .then([
27
+ {
28
+ type: "ShoppingCriteriaEntered",
29
+ data: {
30
+ sessionId: "shopper-123",
31
+ criteria:
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-08-28T04:56:47.408Z"),
34
+ },
35
+ },
36
+ ]);
37
+ });
38
+ });
@@ -0,0 +1,36 @@
1
+ import { IllegalStateError } from "@event-driven-io/emmett";
2
+ import type { State } from "./state";
3
+ import type { EnterShoppingCriteria } from "./commands";
4
+ import type { ShoppingCriteriaEntered } from "./events";
5
+
6
+ export const decide = (
7
+ command: EnterShoppingCriteria,
8
+ state: State,
9
+ ): ShoppingCriteriaEntered => {
10
+ switch (command.type) {
11
+ case "EnterShoppingCriteria": {
12
+ /**
13
+ * ## IMPLEMENTATION INSTRUCTIONS ##
14
+ *
15
+ * This command can directly emit one or more events based on the input.
16
+ *
17
+ * You should:
18
+ * - Validate the command input fields
19
+ * - Inspect the current domain `state` to determine if the command is allowed
20
+ * - If invalid, throw one of the following domain errors: `NotFoundError`, `ValidationError`, or `IllegalStateError`
21
+ * - If valid, return one or more events with the correct structure
22
+ *
23
+ * ⚠️ Only read from inputs — never mutate them. `evolve.ts` handles state updates.
24
+ */
25
+
26
+ // return {
27
+ // type: 'ShoppingCriteriaEntered',
28
+ // data: { ...command.data },
29
+ // } as ShoppingCriteriaEntered;
30
+
31
+ throw new IllegalStateError("Not yet implemented: " + command.type);
32
+ }
33
+ default:
34
+ throw new IllegalStateError("Unexpected command type: " + command.type);
35
+ }
36
+ };
@@ -0,0 +1,10 @@
1
+ import type { Event } from "@event-driven-io/emmett";
2
+
3
+ export type ShoppingCriteriaEntered = Event<
4
+ "ShoppingCriteriaEntered",
5
+ {
6
+ sessionId: string;
7
+ criteria: string;
8
+ timestamp: Date;
9
+ }
10
+ >;
@@ -0,0 +1,28 @@
1
+ import type { State } from "./state";
2
+ import type { ShoppingCriteriaEntered } 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: ShoppingCriteriaEntered): State => {
18
+ switch (event.type) {
19
+ case "ShoppingCriteriaEntered": {
20
+ // TODO: Update state based on ShoppingCriteriaEntered
21
+ return {
22
+ ...state,
23
+ };
24
+ }
25
+ default:
26
+ return state;
27
+ }
28
+ };
@@ -0,0 +1,31 @@
1
+ import {
2
+ CommandHandler,
3
+ type EventStore,
4
+ type MessageHandlerResult,
5
+ } from "@event-driven-io/emmett";
6
+ import { evolve } from "./evolve";
7
+ import { initialState } from "./state";
8
+ import { decide } from "./decide";
9
+ import type { EnterShoppingCriteria } from "./commands";
10
+
11
+ const handler = CommandHandler({
12
+ evolve,
13
+ initialState,
14
+ });
15
+
16
+ export const handle = async (
17
+ eventStore: EventStore,
18
+ command: EnterShoppingCriteria,
19
+ ): Promise<MessageHandlerResult> => {
20
+ const streamId = `shopping-session-${command.data.sessionId}`;
21
+
22
+ try {
23
+ await handler(eventStore, streamId, (state) => decide(command, state));
24
+ return; // success (returns void)
25
+ } catch (error: any) {
26
+ return {
27
+ type: "SKIP",
28
+ reason: `Command failed: ${error?.message ?? "Unknown"}`,
29
+ };
30
+ }
31
+ };
@@ -0,0 +1,30 @@
1
+ import { Mutation, Resolver, Arg, Ctx, Field, InputType } from "type-graphql";
2
+ import {
3
+ type GraphQLContext,
4
+ sendCommand,
5
+ MutationResponse,
6
+ } from "../../../shared";
7
+
8
+ @InputType()
9
+ export class EnterShoppingCriteriaInput {
10
+ @Field(() => String)
11
+ sessionId!: string;
12
+ @Field(() => String)
13
+ criteria!: string;
14
+ }
15
+
16
+ @Resolver()
17
+ export class EnterShoppingCriteriaResolver {
18
+ @Mutation(() => MutationResponse)
19
+ async enterShoppingCriteria(
20
+ @Arg("input", () => EnterShoppingCriteriaInput)
21
+ input: EnterShoppingCriteriaInput,
22
+ @Ctx() ctx: GraphQLContext,
23
+ ): Promise<MutationResponse> {
24
+ return await sendCommand(ctx.messageBus, {
25
+ type: "EnterShoppingCriteria",
26
+ kind: "Command",
27
+ data: { ...input },
28
+ });
29
+ }
30
+ }
@@ -0,0 +1,10 @@
1
+ import type { CommandProcessor, EventStore } from "@event-driven-io/emmett";
2
+ import { handle } from "./handle";
3
+ import type { EnterShoppingCriteria } from "./commands";
4
+
5
+ export function register(messageBus: CommandProcessor, eventStore: EventStore) {
6
+ messageBus.handle(
7
+ (command: EnterShoppingCriteria) => handle(eventStore, command),
8
+ "EnterShoppingCriteria",
9
+ );
10
+ }
@@ -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
+ };
@@ -0,0 +1,8 @@
1
+ import { Command } from "@event-driven-io/emmett";
2
+ export type SuggestShoppingItems = Command<
3
+ "SuggestShoppingItems",
4
+ {
5
+ sessionId: string;
6
+ prompt: string;
7
+ }
8
+ >;
@@ -0,0 +1,61 @@
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
+ .when({
17
+ type: "SuggestShoppingItems",
18
+ data: {
19
+ sessionId: "session-abc",
20
+ prompt:
21
+ "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.",
22
+ },
23
+ metadata: { now: new Date() },
24
+ })
25
+
26
+ .then([
27
+ {
28
+ type: "ShoppingItemsSuggested",
29
+ data: {
30
+ sessionId: "session-abc",
31
+ suggestedItems: [
32
+ {
33
+ productId: "prod-soccer-ball",
34
+ name: "Super Soccer Ball",
35
+ quantity: 1,
36
+ reason: "Perfect for your daughter who loves soccer",
37
+ },
38
+ {
39
+ productId: "prod-craft-kit",
40
+ name: "Deluxe Craft Kit",
41
+ quantity: 1,
42
+ reason: "Great for creative activities and crafts",
43
+ },
44
+ {
45
+ productId: "prod-laptop-bag",
46
+ name: "Tech Laptop Backpack",
47
+ quantity: 1,
48
+ reason: "Essential for your son's school computer needs",
49
+ },
50
+ {
51
+ productId: "prod-mtg-starter",
52
+ name: "Magic the Gathering Starter Set",
53
+ quantity: 1,
54
+ reason: "Ideal starter set for Magic the Gathering enthusiasts",
55
+ },
56
+ ],
57
+ },
58
+ },
59
+ ]);
60
+ });
61
+ });