@arki/event-sourcing 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/dist/builders/command-handler.d.ts +48 -0
  4. package/dist/builders/command-handler.d.ts.map +1 -0
  5. package/dist/builders/command-handler.js +40 -0
  6. package/dist/builders/command-handler.js.map +1 -0
  7. package/dist/builders/command.d.ts +39 -0
  8. package/dist/builders/command.d.ts.map +1 -0
  9. package/dist/builders/command.js +44 -0
  10. package/dist/builders/command.js.map +1 -0
  11. package/dist/builders/decider.d.ts +188 -0
  12. package/dist/builders/decider.d.ts.map +1 -0
  13. package/dist/builders/decider.js +252 -0
  14. package/dist/builders/decider.js.map +1 -0
  15. package/dist/builders/event.d.ts +39 -0
  16. package/dist/builders/event.d.ts.map +1 -0
  17. package/dist/builders/event.js +42 -0
  18. package/dist/builders/event.js.map +1 -0
  19. package/dist/builders/index.d.ts +40 -0
  20. package/dist/builders/index.d.ts.map +1 -0
  21. package/dist/builders/index.js +40 -0
  22. package/dist/builders/index.js.map +1 -0
  23. package/dist/builders/process-manager.d.ts +94 -0
  24. package/dist/builders/process-manager.d.ts.map +1 -0
  25. package/dist/builders/process-manager.js +131 -0
  26. package/dist/builders/process-manager.js.map +1 -0
  27. package/dist/builders/projection.d.ts +92 -0
  28. package/dist/builders/projection.d.ts.map +1 -0
  29. package/dist/builders/projection.js +129 -0
  30. package/dist/builders/projection.js.map +1 -0
  31. package/dist/builders/stateful-process-manager.d.ts +181 -0
  32. package/dist/builders/stateful-process-manager.d.ts.map +1 -0
  33. package/dist/builders/stateful-process-manager.js +280 -0
  34. package/dist/builders/stateful-process-manager.js.map +1 -0
  35. package/dist/bus.d.ts +3 -0
  36. package/dist/bus.d.ts.map +1 -0
  37. package/dist/bus.js +2 -0
  38. package/dist/bus.js.map +1 -0
  39. package/dist/command-flow.d.ts +87 -0
  40. package/dist/command-flow.d.ts.map +1 -0
  41. package/dist/command-flow.js +233 -0
  42. package/dist/command-flow.js.map +1 -0
  43. package/dist/command.d.ts +38 -0
  44. package/dist/command.d.ts.map +1 -0
  45. package/dist/command.js +2 -0
  46. package/dist/command.js.map +1 -0
  47. package/dist/debug.d.ts +31 -0
  48. package/dist/debug.d.ts.map +1 -0
  49. package/dist/debug.js +31 -0
  50. package/dist/debug.js.map +1 -0
  51. package/dist/decide.d.ts +11 -0
  52. package/dist/decide.d.ts.map +1 -0
  53. package/dist/decide.js +2 -0
  54. package/dist/decide.js.map +1 -0
  55. package/dist/dot.d.ts +90 -0
  56. package/dist/dot.d.ts.map +1 -0
  57. package/dist/dot.js +91 -0
  58. package/dist/dot.js.map +1 -0
  59. package/dist/error.d.ts +2 -0
  60. package/dist/error.d.ts.map +1 -0
  61. package/dist/error.js +2 -0
  62. package/dist/error.js.map +1 -0
  63. package/dist/event-sourcing-features.d.ts +81 -0
  64. package/dist/event-sourcing-features.d.ts.map +1 -0
  65. package/dist/event-sourcing-features.js +139 -0
  66. package/dist/event-sourcing-features.js.map +1 -0
  67. package/dist/event.d.ts +25 -0
  68. package/dist/event.d.ts.map +1 -0
  69. package/dist/event.js +2 -0
  70. package/dist/event.js.map +1 -0
  71. package/dist/index.d.ts +7 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +6 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/package.json +5 -0
  76. package/dist/process-manager.d.ts +113 -0
  77. package/dist/process-manager.d.ts.map +1 -0
  78. package/dist/process-manager.js +155 -0
  79. package/dist/process-manager.js.map +1 -0
  80. package/dist/simple-process-manager.d.ts +37 -0
  81. package/dist/simple-process-manager.d.ts.map +1 -0
  82. package/dist/simple-process-manager.js +55 -0
  83. package/dist/simple-process-manager.js.map +1 -0
  84. package/dist/src/builders/command-handler.d.ts +48 -0
  85. package/dist/src/builders/command-handler.d.ts.map +1 -0
  86. package/dist/src/builders/command.d.ts +39 -0
  87. package/dist/src/builders/command.d.ts.map +1 -0
  88. package/dist/src/builders/decider.d.ts +188 -0
  89. package/dist/src/builders/decider.d.ts.map +1 -0
  90. package/dist/src/builders/event.d.ts +39 -0
  91. package/dist/src/builders/event.d.ts.map +1 -0
  92. package/dist/src/builders/index.d.ts +40 -0
  93. package/dist/src/builders/index.d.ts.map +1 -0
  94. package/dist/src/builders/process-manager.d.ts +94 -0
  95. package/dist/src/builders/process-manager.d.ts.map +1 -0
  96. package/dist/src/builders/projection.d.ts +92 -0
  97. package/dist/src/builders/projection.d.ts.map +1 -0
  98. package/dist/src/builders/stateful-process-manager.d.ts +181 -0
  99. package/dist/src/builders/stateful-process-manager.d.ts.map +1 -0
  100. package/dist/src/bus.d.ts +3 -0
  101. package/dist/src/bus.d.ts.map +1 -0
  102. package/dist/src/command-flow.d.ts +87 -0
  103. package/dist/src/command-flow.d.ts.map +1 -0
  104. package/dist/src/command.d.ts +16 -0
  105. package/dist/src/command.d.ts.map +1 -0
  106. package/dist/src/debug.d.ts +31 -0
  107. package/dist/src/debug.d.ts.map +1 -0
  108. package/dist/src/decide.d.ts +11 -0
  109. package/dist/src/decide.d.ts.map +1 -0
  110. package/dist/src/error.d.ts +2 -0
  111. package/dist/src/error.d.ts.map +1 -0
  112. package/dist/src/event.d.ts +25 -0
  113. package/dist/src/event.d.ts.map +1 -0
  114. package/dist/src/index.d.ts +5 -0
  115. package/dist/src/index.d.ts.map +1 -0
  116. package/dist/src/process-manager.d.ts +113 -0
  117. package/dist/src/process-manager.d.ts.map +1 -0
  118. package/dist/src/store.d.ts +44 -0
  119. package/dist/src/store.d.ts.map +1 -0
  120. package/dist/src/types.d.ts +10 -0
  121. package/dist/src/types.d.ts.map +1 -0
  122. package/dist/store.d.ts +44 -0
  123. package/dist/store.d.ts.map +1 -0
  124. package/dist/store.js +94 -0
  125. package/dist/store.js.map +1 -0
  126. package/dist/types.d.ts +10 -0
  127. package/dist/types.d.ts.map +1 -0
  128. package/dist/types.js +2 -0
  129. package/dist/types.js.map +1 -0
  130. package/package.json +100 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ARKI Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @arki/event-sourcing
2
+
3
+ Event sourcing primitives for the ARKI package family — event store, message bus, command handlers, projections, and process managers, built on top of [Emmett](https://event-driven-io.github.io/emmett/) with `@event-driven-io/emmett-postgresql`.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install @arki/event-sourcing
9
+ # or
10
+ bun add @arki/event-sourcing
11
+ # or
12
+ pnpm add @arki/event-sourcing
13
+ ```
14
+
15
+ Peer-installs:
16
+
17
+ - `@event-driven-io/emmett` (re-exported core types)
18
+ - `@event-driven-io/emmett-postgresql` (PostgreSQL event store)
19
+ - `drizzle-orm` (for projections that write through a Drizzle handle)
20
+
21
+ ## What you get
22
+
23
+ - **Event store** — `getEventStore(connectionString, options)` opens a PostgreSQL event store with a clean `close()` that releases the pool.
24
+ - **Message bus** — `eventSourcingFeatures.initMessageBus(eventStore, handlers)` builds an in-memory message bus, registers command handlers, and wraps each handler with stream-name resolution + logging.
25
+ - **Process managers** — `createProcessManager` (per-batch), `createStatefulProcessManager` (per-batch with state), and `createSimpleProcessManager` (per-message with a pre-bound context).
26
+ - **Builders** — fluent `defineCommand`, `defineEvent`, `defineDecider`, `defineProjection`, `defineCommandHandler`, `defineProcessManager`, `defineStatefulProcessManager` for type-safe authoring.
27
+ - **Drizzle projection helpers** — `drizzleProjection` / `drizzleWithRepoProjection` so projection handlers receive a typed Drizzle database (or repository registry) instead of a raw client.
28
+
29
+ ## Quick start
30
+
31
+ ### 1. Bootstrap an event store and message bus
32
+
33
+ ```ts
34
+ import { eventSourcingFeatures } from '@arki/event-sourcing';
35
+
36
+ const { eventStore, close } = eventSourcingFeatures.initEventSourcing(
37
+ [
38
+ /* projections produced by defineProjection / drizzleProjection / postgreSQLProjection */
39
+ ],
40
+ process.env.EVENT_STORE_URL,
41
+ );
42
+
43
+ const messageBus = eventSourcingFeatures.initMessageBus(eventStore, [
44
+ /* { commandType, handler, getStreamName } registrations */
45
+ ]);
46
+
47
+ // Later, on shutdown:
48
+ await close();
49
+ ```
50
+
51
+ `initEventSourcing` throws a descriptive error if the connection string is undefined, naming `EVENT_STORE_URL`, `EVENTSTORE_URL`, and `EVENT_DB_URL` as the recognised env var names.
52
+
53
+ ### 2. Define a command, a decider, and a handler
54
+
55
+ ```ts
56
+ import { defineCommand, defineEvent, defineDecider, defineCommandHandler, z } from '@arki/event-sourcing';
57
+ // (or import each builder from '@arki/event-sourcing/builders')
58
+
59
+ const PlaceOrder = defineCommand({
60
+ type: 'PlaceOrder',
61
+ inputSchema: z.object({ orderId: z.string(), total: z.number() }),
62
+ metadataSchema: z.object({ userId: z.string() }),
63
+ });
64
+
65
+ const OrderPlaced = defineEvent({
66
+ type: 'OrderPlaced',
67
+ inputSchema: z.object({ orderId: z.string(), total: z.number() }),
68
+ metadataSchema: z.object({ userId: z.string(), at: z.string() }),
69
+ });
70
+
71
+ const orderDecider = defineDecider()
72
+ .initialState(() => ({ placed: false }))
73
+ .evolve((state, event) => (event.type === 'OrderPlaced' ? { placed: true } : state))
74
+ .decide((command, state) => {
75
+ if (state.placed) throw new Error('Order already placed');
76
+ return OrderPlaced({ orderId: command.data.orderId, total: command.data.total }, {
77
+ userId: command.metadata.userId,
78
+ at: new Date().toISOString(),
79
+ });
80
+ })
81
+ .build();
82
+
83
+ export const placeOrderHandler = defineCommandHandler(orderDecider);
84
+ ```
85
+
86
+ ### 3. Define a process manager
87
+
88
+ Use `createProcessManager` for batch processing, or `createSimpleProcessManager` when the handler closes over a fixed context and processes one event at a time:
89
+
90
+ ```ts
91
+ import { createSimpleProcessManager } from '@arki/event-sourcing';
92
+
93
+ const sendOrderEmails = createSimpleProcessManager(
94
+ { emailer, repo },
95
+ 'order-emails',
96
+ ['OrderPlaced', 'OrderCancelled'],
97
+ async (event, ctx) => {
98
+ if (event.type === 'OrderPlaced') {
99
+ await ctx.emailer.send('order-placed', event.data);
100
+ } else {
101
+ await ctx.emailer.send('order-cancelled', event.data);
102
+ }
103
+ },
104
+ );
105
+
106
+ await eventSourcingFeatures.setupProcessManagers(eventStore, [sendOrderEmails]);
107
+ ```
108
+
109
+ `setupProcessManagers` starts each process manager as a consumer of the event store and installs a SIGTERM hook that stops and closes the consumers before exiting.
110
+
111
+ ## Subpath exports
112
+
113
+ - `@arki/event-sourcing` — main surface (commands, events, deciders, process managers, features).
114
+ - `@arki/event-sourcing/builders` — fluent builders only.
115
+ - `@arki/event-sourcing/store` — `getEventStore`, `drizzleProjection`, `postgreSQLProjection`, projection context types.
116
+ - `@arki/event-sourcing/bus` — `getInMemoryMessageBus` and message bus types re-exported from Emmett.
117
+
118
+ ## Documentation
119
+
120
+ For an overview of the package's design principles, see [`docs/design.md`](./docs/design.md). For command flow patterns, see [`docs/command-flows.md`](./docs/command-flows.md).
121
+
122
+ ## License
123
+
124
+ MIT © ARKI Contributors
@@ -0,0 +1,48 @@
1
+ import type { CommandHandlerResult, Event, EventStore, HandleOptions } from '@event-driven-io/emmett';
2
+ type DeciderConfig<State, CommandType, StreamEvent> = {
3
+ initialState: () => State;
4
+ evolve: (state: State, event: StreamEvent) => State;
5
+ decide: (command: CommandType, state: State) => StreamEvent | StreamEvent[];
6
+ };
7
+ /**
8
+ * Store-connected command handler function type.
9
+ *
10
+ * Given an event store, stream id, and one or more commands, it loads the
11
+ * current aggregate state from the stream, applies the decider's `decide`
12
+ * function, and appends the resulting events.
13
+ */
14
+ export type CommandHandler<State, CommandType, StreamEvent extends Event> = <Store extends EventStore>(eventStore: Store, id: string, commands: CommandType | CommandType[], handleOptions?: HandleOptions<Store>) => Promise<CommandHandlerResult<State, StreamEvent, Store>>;
15
+ /**
16
+ * Creates a store-connected command handler from a decider configuration.
17
+ *
18
+ * This is the recommended way to create command handlers. It wraps Emmett's
19
+ * `DeciderCommandHandler` while lifting the `CommandType extends Command`
20
+ * constraint that prevents commands with custom metadata from type-checking.
21
+ *
22
+ * Emmett's `Command` type uses a conditional type for metadata:
23
+ * - When metadata is `undefined`, the `metadata` property is optional.
24
+ * - When metadata is defined, it becomes required.
25
+ *
26
+ * TypeScript cannot prove that a concrete command (with custom metadata)
27
+ * satisfies `extends Command` across these conditional branches. This wrapper
28
+ * bridges that gap with a single, localised type assertion — keeping all
29
+ * call sites fully type-safe.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const handler = defineCommandHandler({
34
+ * initialState,
35
+ * evolve,
36
+ * decide,
37
+ * });
38
+ *
39
+ * export const bookCommandHandlers = BookCommands.map(commandType => ({
40
+ * commandType,
41
+ * handler,
42
+ * getStreamName,
43
+ * }));
44
+ * ```
45
+ */
46
+ export declare function defineCommandHandler<State, CommandType, StreamEvent extends Event>(config: DeciderConfig<State, CommandType, StreamEvent>): CommandHandler<State, CommandType, StreamEvent>;
47
+ export {};
48
+ //# sourceMappingURL=command-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-handler.d.ts","sourceRoot":"","sources":["../../src/builders/command-handler.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,oBAAoB,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAI7G,KAAK,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,IAAI;IACpD,YAAY,EAAE,MAAM,KAAK,CAAC;IAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,KAAK,KAAK,CAAC;IACpD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,KAAK,WAAW,GAAG,WAAW,EAAE,CAAC;CAC7E,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,SAAS,KAAK,IAAI,CAAC,KAAK,SAAS,UAAU,EACnG,UAAU,EAAE,KAAK,EACjB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,WAAW,GAAG,WAAW,EAAE,EACrC,aAAa,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,KACjC,OAAO,CAAC,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,SAAS,KAAK,EAChF,MAAM,EAAE,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,CAAC,GACrD,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,CAAC,CAWjD"}
@@ -0,0 +1,40 @@
1
+ import { DeciderCommandHandler } from '@event-driven-io/emmett';
2
+ import { debugBuilder } from '../debug.js';
3
+ /**
4
+ * Creates a store-connected command handler from a decider configuration.
5
+ *
6
+ * This is the recommended way to create command handlers. It wraps Emmett's
7
+ * `DeciderCommandHandler` while lifting the `CommandType extends Command`
8
+ * constraint that prevents commands with custom metadata from type-checking.
9
+ *
10
+ * Emmett's `Command` type uses a conditional type for metadata:
11
+ * - When metadata is `undefined`, the `metadata` property is optional.
12
+ * - When metadata is defined, it becomes required.
13
+ *
14
+ * TypeScript cannot prove that a concrete command (with custom metadata)
15
+ * satisfies `extends Command` across these conditional branches. This wrapper
16
+ * bridges that gap with a single, localised type assertion — keeping all
17
+ * call sites fully type-safe.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const handler = defineCommandHandler({
22
+ * initialState,
23
+ * evolve,
24
+ * decide,
25
+ * });
26
+ *
27
+ * export const bookCommandHandlers = BookCommands.map(commandType => ({
28
+ * commandType,
29
+ * handler,
30
+ * getStreamName,
31
+ * }));
32
+ * ```
33
+ */
34
+ export function defineCommandHandler(config) {
35
+ debugBuilder('[command-handler] Creating command handler');
36
+ const emmettHandler = DeciderCommandHandler(config);
37
+ debugBuilder('[command-handler] Command handler created successfully');
38
+ return emmettHandler;
39
+ }
40
+ //# sourceMappingURL=command-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-handler.js","sourceRoot":"","sources":["../../src/builders/command-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EAMtB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsB3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAsD;IAEtD,YAAY,CAAC,4CAA4C,CAAC,CAAC;IAM3D,MAAM,aAAa,GAAG,qBAAqB,CAAC,MAAsB,CAAC,CAAC;IAEpE,YAAY,CAAC,wDAAwD,CAAC,CAAC;IACvE,OAAO,aAAgE,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { z } from '@arki/contracts';
2
+ import type { Command, DefaultCommandMetadata } from '../command.js';
3
+ import type { DefaultRecord } from '../types.js';
4
+ /**
5
+ * Configuration for defining a domain command factory.
6
+ */
7
+ export type CommandConfig<TType extends string, TInput extends DefaultRecord, TMetadata extends DefaultCommandMetadata | undefined = undefined> = {
8
+ /** The command type string (e.g., 'CreateUser') */
9
+ type: TType;
10
+ /** Zod schema for validating command input */
11
+ inputSchema: z.ZodType<TInput>;
12
+ /** Optional Zod schema for validating command metadata */
13
+ metadataSchema?: z.ZodType<TMetadata>;
14
+ };
15
+ /**
16
+ * Creates a strongly-typed command factory function.
17
+ *
18
+ * This factory validates input and metadata using Zod schemas before
19
+ * creating commands that can be dispatched through a command flow or aggregate.
20
+ *
21
+ * @param config - Configuration object containing type, input schema, and optional metadata schema
22
+ * @returns A factory function that validates and creates commands
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const createUserCommand = defineCommand({
27
+ * type: 'CreateUser',
28
+ * inputSchema: z.object({ name: z.string(), email: z.email() }),
29
+ * metadataSchema: z.object({ now: z.date(), issuedBy: z.string().uuid() }),
30
+ * });
31
+ *
32
+ * const command = createUserCommand(
33
+ * { name: 'Ada', email: 'ada@example.org' },
34
+ * { now: new Date(), issuedBy: 'usr_123' },
35
+ * );
36
+ * ```
37
+ */
38
+ export declare function defineCommand<TType extends string, TInput extends DefaultRecord, TMetadata extends DefaultCommandMetadata | undefined = undefined>(config: CommandConfig<TType, TInput, TMetadata>): (input: TInput, metadata?: TMetadata) => Command<TType, TInput, TMetadata>;
39
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/builders/command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,KAAK,EAAE,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,aAAa,CACvB,KAAK,SAAS,MAAM,EACpB,MAAM,SAAS,aAAa,EAC5B,SAAS,SAAS,sBAAsB,GAAG,SAAS,GAAG,SAAS,IAC9D;IACF,mDAAmD;IACnD,IAAI,EAAE,KAAK,CAAC;IACZ,8CAA8C;IAC9C,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,0DAA0D;IAC1D,cAAc,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;CACvC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,KAAK,SAAS,MAAM,EACpB,MAAM,SAAS,aAAa,EAC5B,SAAS,SAAS,sBAAsB,GAAG,SAAS,GAAG,SAAS,EAEhE,MAAM,EAAE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,GAC9C,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAqB5E"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Creates a strongly-typed command factory function.
3
+ *
4
+ * This factory validates input and metadata using Zod schemas before
5
+ * creating commands that can be dispatched through a command flow or aggregate.
6
+ *
7
+ * @param config - Configuration object containing type, input schema, and optional metadata schema
8
+ * @returns A factory function that validates and creates commands
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const createUserCommand = defineCommand({
13
+ * type: 'CreateUser',
14
+ * inputSchema: z.object({ name: z.string(), email: z.email() }),
15
+ * metadataSchema: z.object({ now: z.date(), issuedBy: z.string().uuid() }),
16
+ * });
17
+ *
18
+ * const command = createUserCommand(
19
+ * { name: 'Ada', email: 'ada@example.org' },
20
+ * { now: new Date(), issuedBy: 'usr_123' },
21
+ * );
22
+ * ```
23
+ */
24
+ export function defineCommand(config) {
25
+ const { type, inputSchema, metadataSchema } = config;
26
+ return (input, metadata) => {
27
+ const validatedInput = inputSchema.parse(input);
28
+ const validatedMetadata = metadataSchema ? metadataSchema.parse(metadata) : metadata;
29
+ // Conditionally construct the command object based on whether metadata is provided
30
+ return (validatedMetadata === undefined
31
+ ? {
32
+ type,
33
+ data: validatedInput,
34
+ kind: 'Command',
35
+ }
36
+ : {
37
+ type,
38
+ data: validatedInput,
39
+ metadata: validatedMetadata,
40
+ kind: 'Command',
41
+ });
42
+ };
43
+ }
44
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/builders/command.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,aAAa,CAK3B,MAA+C;IAE/C,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;IAErD,OAAO,CAAC,KAAa,EAAE,QAAoB,EAAE,EAAE;QAC7C,MAAM,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,iBAAiB,GAAG,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAErF,mFAAmF;QACnF,OAAO,CAAC,iBAAiB,KAAK,SAAS;YACrC,CAAC,CAAC;gBACE,IAAI;gBACJ,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,SAAkB;aACzB;YACH,CAAC,CAAC;gBACE,IAAI;gBACJ,IAAI,EAAE,cAAc;gBACpB,QAAQ,EAAE,iBAAiB;gBAC3B,IAAI,EAAE,SAAkB;aACzB,CAAiD,CAAC;IACzD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,188 @@
1
+ import type { Event as EmmettEvent } from '@event-driven-io/emmett';
2
+ import type { z } from '@arki/contracts';
3
+ import type { AnyEvent } from '../event.js';
4
+ import type { CommandHandler } from './command-handler.js';
5
+ /**
6
+ * Applies a domain event to the current aggregate state and produces the next state.
7
+ */
8
+ type Evolve<State, StreamEvent extends AnyEvent> = (state: State, event: StreamEvent) => State;
9
+ /**
10
+ * Computes the domain events that should be emitted for an incoming command,
11
+ * based on the aggregate's current state.
12
+ */
13
+ type Decide<State, Command, StreamEvent extends AnyEvent> = (command: Command, state: State) => StreamEvent[];
14
+ /**
15
+ * Fluent builder for aggregate behaviour (aka "decider") definitions.
16
+ *
17
+ * In many event-sourcing texts this role is called an *aggregate*: an object that owns
18
+ * the authoritative state, knows how to rebuild itself from events (`evolve`), and
19
+ * decides which new events should be recorded when commands arrive (`decide`).
20
+ *
21
+ * We keep the historical Emmett name `Decider` to match upstream types, but the builder
22
+ * intentionally documents the aggregate semantics so domain code can align on that
23
+ * mental model.
24
+ *
25
+ * Capabilities:
26
+ * - Type safety through TypeScript generics
27
+ * - Optional Zod schemas to validate state evolution at runtime
28
+ * - Chainable configuration that terminates with `.handler()`, returning the executed aggregate contract
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const cartAggregate = defineDecider()
33
+ * .stateSchema(z.object({ items: z.array(z.string()), total: z.number() }))
34
+ * .initialState(() => ({ items: [], total: 0 }))
35
+ * .evolve((state, event) => {
36
+ * if (event.type === 'ItemAdded') {
37
+ * return {
38
+ * items: [...state.items, event.data.item],
39
+ * total: state.total + event.data.price,
40
+ * };
41
+ * }
42
+ * return state;
43
+ * })
44
+ * .decide((command, state) => {
45
+ * if (command.type === 'AddItem') {
46
+ * return [
47
+ * {
48
+ * type: 'ItemAdded',
49
+ * data: { item: command.data.item, price: command.data.price },
50
+ * },
51
+ * ];
52
+ * }
53
+ * return [];
54
+ * })
55
+ * .handler();
56
+ *
57
+ * const initial = cartAggregate.initialState();
58
+ * const events = cartAggregate.decide(addItemCommand, initial);
59
+ * const next = cartAggregate.evolve(initial, events[0]);
60
+ * ```
61
+ */
62
+ declare class DeciderBuilder<State = never, Command = never, StreamEvent extends AnyEvent = never> {
63
+ private _stateSchema?;
64
+ private _initialStateFn?;
65
+ private _evolveFn?;
66
+ private _decideFn?;
67
+ /**
68
+ * Creates a deep copy of the builder to ensure immutability
69
+ * @private
70
+ */
71
+ private clone;
72
+ /**
73
+ * Sets the state schema for state validation
74
+ * @param schema - Zod schema for validating state
75
+ * @returns A new DeciderBuilder instance with the state schema set
76
+ */
77
+ stateSchema<TNewState>(schema: z.ZodType<TNewState>): DeciderBuilder<TNewState, Command, StreamEvent>;
78
+ /**
79
+ * Sets the initial state function
80
+ * @param fn - Function that returns the initial state for a new aggregate
81
+ * @returns A new DeciderBuilder instance with the initial state function set
82
+ */
83
+ initialState(fn: () => State): DeciderBuilder<State, Command, StreamEvent>;
84
+ /**
85
+ * Sets the evolve function
86
+ *
87
+ * This method is generic and will infer the StreamEvent type from the function parameter,
88
+ * allowing TypeScript to properly infer complex discriminated union types.
89
+ *
90
+ * @template TStreamEvent - The union type of all events (inferred from fn parameter)
91
+ * @param fn - Function that applies an event to state to compute the next state
92
+ * @returns A new DeciderBuilder instance with the evolve function and inferred event types set
93
+ */
94
+ evolve<TStreamEvent extends AnyEvent>(fn: Evolve<State, TStreamEvent>): DeciderBuilder<State, Command, TStreamEvent>;
95
+ /**
96
+ * Sets the decide function
97
+ *
98
+ * This method is generic and will infer the Command type from the function parameter,
99
+ * allowing TypeScript to properly infer complex discriminated union types.
100
+ *
101
+ * @template TCommand - The union type of all commands (inferred from fn parameter)
102
+ * @param fn - Function that determines which events to emit for a given command and state
103
+ * @returns A new DeciderBuilder instance with the decide function and inferred command types set
104
+ */
105
+ decide<TCommand>(fn: Decide<State, TCommand, StreamEvent>): DeciderBuilder<State, TCommand, StreamEvent>;
106
+ /**
107
+ * Terminal method that creates the decider
108
+ *
109
+ * Validates that all required functions are set and returns an object
110
+ * with three validated functions:
111
+ * - initialState: Returns the validated initial state
112
+ * - evolve: Applies an event to state with validation
113
+ * - decide: Determines events to emit with validation
114
+ *
115
+ * @returns An object containing the three core decider functions
116
+ * @throws Error if state schema, initial state function, evolve function, or decide function are not defined
117
+ */
118
+ handler(): {
119
+ initialState: () => State;
120
+ evolve: Evolve<State, StreamEvent>;
121
+ decide: Decide<State, Command, StreamEvent>;
122
+ };
123
+ /**
124
+ * Terminal method that creates a store-connected command handler.
125
+ *
126
+ * Unlike `.handler()`, this does **not** require a state schema — it creates
127
+ * a handler that can be registered directly with an event store. Internally
128
+ * it delegates to `defineCommandHandler`, which bridges the Emmett type
129
+ * constraint on command metadata.
130
+ *
131
+ * Use this when you need a handler that reads/writes events from a store
132
+ * (the common case for aggregates), rather than the raw decider functions.
133
+ *
134
+ * @returns A store-connected command handler function
135
+ * @throws Error if initial state, evolve, or decide functions are not defined
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * const handler = defineDecider()
140
+ * .initialState(() => null as BookState)
141
+ * .evolve(evolve)
142
+ * .decide(decide)
143
+ * .commandHandler();
144
+ * ```
145
+ */
146
+ commandHandler(): StreamEvent extends EmmettEvent ? CommandHandler<State, Command, StreamEvent> : never;
147
+ }
148
+ /**
149
+ * Factory function to create a new DeciderBuilder
150
+ *
151
+ * **Recommended API**: This builder API supports complex discriminated unions
152
+ * through parameter-based type inference. The `.evolve()` and `.decide()` methods
153
+ * are generic and will automatically infer types from your function parameters.
154
+ *
155
+ * **Use cases:**
156
+ * - ✅ All cases (simple and complex)
157
+ * - ✅ Complex discriminated unions (10+ command/event types)
158
+ * - ✅ Fluent, chainable API preference
159
+ * - ✅ Automatic type inference from function parameters
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * // Works for both simple and complex union types
164
+ * const decider = defineDecider()
165
+ * .stateSchema(myStateSchema)
166
+ * .initialState(() => ({ ... }))
167
+ * .evolve((state, event) => {
168
+ * // TypeScript infers event type from this function
169
+ * switch (event.type) {
170
+ * case 'EventA': return { ... };
171
+ * case 'EventB': return { ... };
172
+ * // ... handles 10+ event types correctly
173
+ * }
174
+ * })
175
+ * .decide((command, state) => {
176
+ * // TypeScript infers command type from this function
177
+ * switch (command.type) {
178
+ * case 'CommandA': return [{ type: 'EventA', ... }];
179
+ * case 'CommandB': return [{ type: 'EventB', ... }];
180
+ * // ... handles 10+ command types correctly
181
+ * }
182
+ * })
183
+ * .handler();
184
+ * ```
185
+ */
186
+ export declare function defineDecider(): DeciderBuilder;
187
+ export {};
188
+ //# sourceMappingURL=decider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decider.d.ts","sourceRoot":"","sources":["../../src/builders/decider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,aAAa,CAAC;AAGnD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,sBAAsB,CAAC;AAEzD;;GAEG;AACH,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,SAAS,QAAQ,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,KAAK,KAAK,CAAC;AAE/F;;;GAGG;AACH,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,SAAS,QAAQ,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,WAAW,EAAE,CAAC;AAE9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,cAAM,cAAc,CAAC,KAAK,GAAG,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,WAAW,SAAS,QAAQ,GAAG,KAAK;IACvF,OAAO,CAAC,YAAY,CAAC,CAAmB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAc;IACtC,OAAO,CAAC,SAAS,CAAC,CAA6B;IAC/C,OAAO,CAAC,SAAS,CAAC,CAAsC;IAExD;;;OAGG;IACH,OAAO,CAAC,KAAK;IASb;;;;OAIG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,WAAW,CAAC;IAUrG;;;;OAIG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC;IAO1E;;;;;;;;;OASG;IACH,MAAM,CAAC,YAAY,SAAS,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,GAAG,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC;IAUpH;;;;;;;;;OASG;IACH,MAAM,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,GAAG,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC;IAUxG;;;;;;;;;;;OAWG;IACH,OAAO,IAAI;QACT,YAAY,EAAE,MAAM,KAAK,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QACnC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;KAC7C;IAqCD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,cAAc,IAAI,WAAW,SAAS,WAAW,GAAG,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,GAAG,KAAK;CAexG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,aAAa,IAAI,cAAc,CAG9C"}