@arki/event-sourcing 0.1.5 → 0.2.1
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 +91 -40
- package/dist/builders/command.d.ts +8 -1
- package/dist/builders/command.d.ts.map +1 -1
- package/dist/builders/command.js +38 -1
- package/dist/builders/command.js.map +1 -1
- package/dist/builders/event.d.ts +8 -1
- package/dist/builders/event.d.ts.map +1 -1
- package/dist/builders/event.js +38 -1
- package/dist/builders/event.js.map +1 -1
- package/dist/dot-action.d.ts +31 -0
- package/dist/dot-action.d.ts.map +1 -0
- package/dist/dot-action.js +73 -0
- package/dist/dot-action.js.map +1 -0
- package/dist/dot.d.ts +79 -11
- package/dist/dot.d.ts.map +1 -1
- package/dist/dot.js +199 -17
- package/dist/dot.js.map +1 -1
- package/dist/projection.d.ts +53 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/projection.js +76 -0
- package/dist/projection.js.map +1 -0
- package/package.json +8 -3
- package/src/builders/command.ts +59 -2
- package/src/builders/event.ts +59 -2
- package/src/dot-action.ts +96 -0
- package/src/dot.ts +297 -22
- package/src/projection.ts +135 -0
package/README.md
CHANGED
|
@@ -22,20 +22,105 @@ Peer-installs:
|
|
|
22
22
|
|
|
23
23
|
- **Event store** — `getEventStore(connectionString, options)` opens a PostgreSQL event store with a clean `close()` that releases the pool.
|
|
24
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
|
+
- **DOT adapter** — `eventSourcing()` opens the store, publishes `eventStore` + `messageBus`, collects feature-local ES bundles, and registers the `event-catalog` projection.
|
|
25
26
|
- **Process managers** — `createProcessManager` (per-batch), `createStatefulProcessManager` (per-batch with state), and `createSimpleProcessManager` (per-message with a pre-bound context).
|
|
26
|
-
- **Builders** —
|
|
27
|
+
- **Builders** — `defineCommand`, `defineEvent`, `defineDecider`, `defineProjection`, `defineCommandHandler`, `defineProcessManager`, `defineStatefulProcessManager` for type-safe authoring.
|
|
27
28
|
- **Drizzle projection helpers** — `drizzleProjection` / `drizzleWithRepoProjection` so projection handlers receive a typed Drizzle database (or repository registry) instead of a raw client.
|
|
28
29
|
|
|
29
30
|
## Quick start
|
|
30
31
|
|
|
31
|
-
###
|
|
32
|
+
### DOT app setup
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { z } from '@arki/contracts';
|
|
36
|
+
import { defineApp, pip, provide, token } from '@arki/dot';
|
|
37
|
+
import { eventSourcing, es, type EsBundle } from '@arki/event-sourcing/dot';
|
|
38
|
+
import { defineCommand, defineCommandHandler, defineEvent } from '@arki/event-sourcing/builders';
|
|
39
|
+
|
|
40
|
+
const placeOrder = defineCommand({
|
|
41
|
+
type: 'PlaceOrder',
|
|
42
|
+
inputSchema: z.object({ orderId: z.string(), total: z.number() }),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const orderPlaced = defineEvent({
|
|
46
|
+
type: 'OrderPlaced',
|
|
47
|
+
dataSchema: z.object({ orderId: z.string(), total: z.number() }),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const placeOrderHandler = defineCommandHandler({
|
|
51
|
+
initialState: () => ({ placed: false }),
|
|
52
|
+
evolve: (state, event) => (event.type === 'OrderPlaced' ? { placed: true } : state),
|
|
53
|
+
decide: (command, state) => {
|
|
54
|
+
if (state.placed) throw new Error('Order already placed');
|
|
55
|
+
return [orderPlaced({ orderId: command.data.orderId, total: command.data.total })];
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const OrdersEs = token<EsBundle>()('orders.es');
|
|
60
|
+
|
|
61
|
+
export const ordersEsPip = pip({
|
|
62
|
+
name: 'orders-es',
|
|
63
|
+
actions: [placeOrder, orderPlaced],
|
|
64
|
+
boot: () =>
|
|
65
|
+
provide(
|
|
66
|
+
OrdersEs,
|
|
67
|
+
es.bundle({
|
|
68
|
+
handlers: [es.handle(placeOrder, placeOrderHandler, command => `order-${command.data.orderId}`)],
|
|
69
|
+
readModels: [],
|
|
70
|
+
}),
|
|
71
|
+
),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const app = defineApp('shop-api')
|
|
75
|
+
.use(ordersEsPip)
|
|
76
|
+
.use(eventSourcing({ bundles: [OrdersEs], dbUrl: process.env.DB_URL }));
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`eventSourcing()` may be mounted with no options for append-only mode. It resolves the store URL from `EVENT_STORE_URL`, `EVENTSTORE_URL`, or `EVENT_DB_URL`; apps that use a different env name should pass `dbUrl` explicitly.
|
|
80
|
+
|
|
81
|
+
Commands and events produced by `defineCommand` / `defineEvent` contribute DOT actions. `dot explain --as event-catalog` renders their JSON Schemas without booting the app. Deprecated dynamic `commandHandlers` still run, but are intentionally absent from the manifest and catalog.
|
|
82
|
+
|
|
83
|
+
### Composition order
|
|
84
|
+
|
|
85
|
+
Feature pips that handle commands publish ES bundles. Feature pips that send commands need the `messageBus` service. Mount them in this order:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
es-feature pips -> eventSourcing() -> http/queue feature pips that need messageBus -> http()/queue runtime
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A feature that both handles `PlaceOrder` and exposes `POST /orders` should be split into two pips: `orders-es` publishes `OrdersEs`; `orders-http` needs `messageBus` and binds the HTTP route.
|
|
92
|
+
|
|
93
|
+
### HTTP and queue recipes
|
|
94
|
+
|
|
95
|
+
Expose a command over HTTP explicitly:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
routes().bind(placeOrderRoute, async ({ body }, { messageBus }) => {
|
|
99
|
+
await messageBus.send(placeOrder(body));
|
|
100
|
+
return { ok: true };
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use `@arki/queue` for durable/async command transport:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
jobs.worker('orders.place', async ({ payload }, { messageBus }) => {
|
|
108
|
+
await messageBus.send(placeOrder(payload));
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The message bus published by this package is strictly in-process synchronous dispatch. It is not durable, does not distribute work across instances, and does not survive restarts or rolling deploys.
|
|
113
|
+
|
|
114
|
+
### Programmatic bootstrap
|
|
115
|
+
|
|
116
|
+
Non-DOT composition roots can still use the lower-level helpers:
|
|
32
117
|
|
|
33
118
|
```ts
|
|
34
119
|
import { eventSourcingFeatures } from '@arki/event-sourcing';
|
|
35
120
|
|
|
36
121
|
const { eventStore, close } = eventSourcingFeatures.initEventSourcing(
|
|
37
122
|
[
|
|
38
|
-
/* projections produced by defineProjection / drizzleProjection / postgreSQLProjection */
|
|
123
|
+
/* read-model projections produced by defineProjection / drizzleProjection / postgreSQLProjection */
|
|
39
124
|
],
|
|
40
125
|
process.env.EVENT_STORE_URL,
|
|
41
126
|
);
|
|
@@ -44,46 +129,10 @@ const messageBus = eventSourcingFeatures.initMessageBus(eventStore, [
|
|
|
44
129
|
/* { commandType, handler, getStreamName } registrations */
|
|
45
130
|
]);
|
|
46
131
|
|
|
47
|
-
// Later, on shutdown:
|
|
48
132
|
await close();
|
|
49
133
|
```
|
|
50
134
|
|
|
51
|
-
|
|
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
|
|
135
|
+
### Process managers
|
|
87
136
|
|
|
88
137
|
Use `createProcessManager` for batch processing, or `createSimpleProcessManager` when the handler closes over a fixed context and processes one event at a time:
|
|
89
138
|
|
|
@@ -114,6 +163,8 @@ await eventSourcingFeatures.setupProcessManagers(eventStore, [sendOrderEmails]);
|
|
|
114
163
|
- `@arki/event-sourcing/builders` — fluent builders only.
|
|
115
164
|
- `@arki/event-sourcing/store` — `getEventStore`, `drizzleProjection`, `postgreSQLProjection`, projection context types.
|
|
116
165
|
- `@arki/event-sourcing/bus` — `getInMemoryMessageBus` and message bus types re-exported from Emmett.
|
|
166
|
+
- `@arki/event-sourcing/dot` — DOT pip, ES bundle helpers, and ES pip error codes.
|
|
167
|
+
- `@arki/event-sourcing/projection` — pure `event-catalog` projection for DOT.
|
|
117
168
|
|
|
118
169
|
## Documentation
|
|
119
170
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { z } from '@arki/contracts';
|
|
2
2
|
import type { Command, DefaultCommandMetadata } from '../command.js';
|
|
3
|
+
import type { EventSourcingActionDeclaration } from '../dot-action.js';
|
|
3
4
|
import type { DefaultRecord } from '../types.js';
|
|
4
5
|
/**
|
|
5
6
|
* Configuration for defining a domain command factory.
|
|
@@ -12,6 +13,12 @@ export type CommandConfig<TType extends string, TInput extends DefaultRecord, TM
|
|
|
12
13
|
/** Optional Zod schema for validating command metadata */
|
|
13
14
|
metadataSchema?: z.ZodType<TMetadata>;
|
|
14
15
|
};
|
|
16
|
+
export type CommandFactory<TType extends string, TInput extends DefaultRecord, TMetadata extends DefaultCommandMetadata | undefined = undefined> = ((input: TInput, metadata?: TMetadata) => Command<TType, TInput, TMetadata>) & EventSourcingActionDeclaration & {
|
|
17
|
+
readonly type: TType;
|
|
18
|
+
readonly inputSchema: z.ZodType<TInput>;
|
|
19
|
+
readonly metadataSchema?: z.ZodType<TMetadata>;
|
|
20
|
+
toDotAction(): EventSourcingActionDeclaration;
|
|
21
|
+
};
|
|
15
22
|
/**
|
|
16
23
|
* Creates a strongly-typed command factory function.
|
|
17
24
|
*
|
|
@@ -35,5 +42,5 @@ export type CommandConfig<TType extends string, TInput extends DefaultRecord, TM
|
|
|
35
42
|
* );
|
|
36
43
|
* ```
|
|
37
44
|
*/
|
|
38
|
-
export declare function defineCommand<TType extends string, TInput extends DefaultRecord, TMetadata extends DefaultCommandMetadata | undefined = undefined>(config: CommandConfig<TType, TInput, TMetadata>):
|
|
45
|
+
export declare function defineCommand<TType extends string, TInput extends DefaultRecord, TMetadata extends DefaultCommandMetadata | undefined = undefined>(config: CommandConfig<TType, TInput, TMetadata>): CommandFactory<TType, TInput, TMetadata>;
|
|
39
46
|
//# sourceMappingURL=command.d.ts.map
|
|
@@ -1 +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
|
|
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,8BAA8B,EAAc,MAAM,kBAAkB,CAAC;AAEnF,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,MAAM,MAAM,cAAc,CACxB,KAAK,SAAS,MAAM,EACpB,MAAM,SAAS,aAAa,EAC5B,SAAS,SAAS,sBAAsB,GAAG,SAAS,GAAG,SAAS,IAC9D,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,GAC9E,8BAA8B,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/C,WAAW,IAAI,8BAA8B,CAAC;CAC/C,CAAC;AAmBJ;;;;;;;;;;;;;;;;;;;;;;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,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CA+C1C"}
|
package/dist/builders/command.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import { EVENT_SOURCING_ACTION_META_SCHEMA, schemaToJsonObject } from '../dot-action.js';
|
|
2
|
+
function commandAction(type, inputSchema) {
|
|
3
|
+
return {
|
|
4
|
+
id: type,
|
|
5
|
+
binding: 'es',
|
|
6
|
+
direction: 'in',
|
|
7
|
+
address: type,
|
|
8
|
+
metaSchema: EVENT_SOURCING_ACTION_META_SCHEMA,
|
|
9
|
+
meta: {
|
|
10
|
+
kind: 'command',
|
|
11
|
+
input: schemaToJsonObject(inputSchema, `command "${type}" input`),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
1
15
|
/**
|
|
2
16
|
* Creates a strongly-typed command factory function.
|
|
3
17
|
*
|
|
@@ -23,7 +37,8 @@
|
|
|
23
37
|
*/
|
|
24
38
|
export function defineCommand(config) {
|
|
25
39
|
const { type, inputSchema, metadataSchema } = config;
|
|
26
|
-
|
|
40
|
+
let cachedAction;
|
|
41
|
+
const factory = (input, metadata) => {
|
|
27
42
|
const validatedInput = inputSchema.parse(input);
|
|
28
43
|
const validatedMetadata = metadataSchema ? metadataSchema.parse(metadata) : metadata;
|
|
29
44
|
// Conditionally construct the command object based on whether metadata is provided
|
|
@@ -40,5 +55,27 @@ export function defineCommand(config) {
|
|
|
40
55
|
kind: 'Command',
|
|
41
56
|
});
|
|
42
57
|
};
|
|
58
|
+
const getAction = () => {
|
|
59
|
+
cachedAction ??= commandAction(type, inputSchema);
|
|
60
|
+
return cachedAction;
|
|
61
|
+
};
|
|
62
|
+
Object.defineProperties(factory, {
|
|
63
|
+
type: { value: type, enumerable: true },
|
|
64
|
+
inputSchema: { value: inputSchema },
|
|
65
|
+
...(metadataSchema === undefined ? {} : { metadataSchema: { value: metadataSchema } }),
|
|
66
|
+
id: { value: type, enumerable: true },
|
|
67
|
+
binding: { value: 'es', enumerable: true },
|
|
68
|
+
direction: { value: 'in', enumerable: true },
|
|
69
|
+
address: { value: type, enumerable: true },
|
|
70
|
+
metaSchema: { value: EVENT_SOURCING_ACTION_META_SCHEMA, enumerable: true },
|
|
71
|
+
meta: {
|
|
72
|
+
enumerable: true,
|
|
73
|
+
get() {
|
|
74
|
+
return getAction().meta;
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
toDotAction: { value: getAction },
|
|
78
|
+
});
|
|
79
|
+
return factory;
|
|
43
80
|
}
|
|
44
81
|
//# sourceMappingURL=command.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/builders/command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/builders/command.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iCAAiC,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AA+BzF,SAAS,aAAa,CACpB,IAAW,EACX,WAAsB;IAEtB,OAAO;QACL,EAAE,EAAE,IAAI;QACR,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,iCAAiC;QAC7C,IAAI,EAAE;YACJ,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,kBAAkB,CAAC,WAAW,EAAE,YAAY,IAAI,SAAS,CAAC;SAClE;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,aAAa,CAK3B,MAA+C;IAE/C,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;IACrD,IAAI,YAAwD,CAAC;IAE7D,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,QAAoB,EAAE,EAAE;QACtD,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;IAEF,MAAM,SAAS,GAAG,GAAmC,EAAE;QACrD,YAAY,KAAK,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAClD,OAAO,YAAY,CAAC;IACtB,CAAC,CAAC;IAEF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE;QAC/B,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QACvC,WAAW,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;QACnC,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,CAAC;QACtF,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QACrC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QAC1C,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QAC5C,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QAC1C,UAAU,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,UAAU,EAAE,IAAI,EAAE;QAC1E,IAAI,EAAE;YACJ,UAAU,EAAE,IAAI;YAChB,GAAG;gBACD,OAAO,SAAS,EAAE,CAAC,IAAI,CAAC;YAC1B,CAAC;SACF;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;KAClC,CAAC,CAAC;IAEH,OAAO,OAAmD,CAAC;AAC7D,CAAC"}
|
package/dist/builders/event.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { z } from '@arki/contracts';
|
|
2
2
|
import type { Event } from '../event.js';
|
|
3
|
+
import type { EventSourcingActionDeclaration } from '../dot-action.js';
|
|
3
4
|
import type { DefaultRecord } from '../types.js';
|
|
4
5
|
/**
|
|
5
6
|
* Configuration for defining a domain event factory.
|
|
@@ -12,6 +13,12 @@ export type EventConfig<TType extends string, TData extends DefaultRecord, TMeta
|
|
|
12
13
|
/** Optional Zod schema for validating event metadata */
|
|
13
14
|
metadataSchema?: z.ZodType<TMetadata>;
|
|
14
15
|
};
|
|
16
|
+
export type EventFactory<TType extends string, TData extends DefaultRecord, TMetadata extends DefaultRecord | undefined = undefined> = ((data: TData, metadata?: TMetadata) => Event<TType, TData, TMetadata>) & EventSourcingActionDeclaration & {
|
|
17
|
+
readonly type: TType;
|
|
18
|
+
readonly dataSchema: z.ZodType<TData>;
|
|
19
|
+
readonly metadataSchema?: z.ZodType<TMetadata>;
|
|
20
|
+
toDotAction(): EventSourcingActionDeclaration;
|
|
21
|
+
};
|
|
15
22
|
/**
|
|
16
23
|
* Creates a strongly-typed event factory function.
|
|
17
24
|
*
|
|
@@ -35,5 +42,5 @@ export type EventConfig<TType extends string, TData extends DefaultRecord, TMeta
|
|
|
35
42
|
* );
|
|
36
43
|
* ```
|
|
37
44
|
*/
|
|
38
|
-
export declare function defineEvent<TType extends string, TData extends DefaultRecord, TMetadata extends DefaultRecord | undefined = undefined>(config: EventConfig<TType, TData, TMetadata>):
|
|
45
|
+
export declare function defineEvent<TType extends string, TData extends DefaultRecord, TMetadata extends DefaultRecord | undefined = undefined>(config: EventConfig<TType, TData, TMetadata>): EventFactory<TType, TData, TMetadata>;
|
|
39
46
|
//# sourceMappingURL=event.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/builders/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,WAAW,CACrB,KAAK,SAAS,MAAM,EACpB,KAAK,SAAS,aAAa,EAC3B,SAAS,SAAS,aAAa,GAAG,SAAS,GAAG,SAAS,IACrD;IACF,kDAAkD;IAClD,IAAI,EAAE,KAAK,CAAC;IACZ,2CAA2C;IAC3C,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,wDAAwD;IACxD,cAAc,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;CACvC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/builders/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,8BAA8B,EAAc,MAAM,kBAAkB,CAAC;AAEnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,WAAW,CACrB,KAAK,SAAS,MAAM,EACpB,KAAK,SAAS,aAAa,EAC3B,SAAS,SAAS,aAAa,GAAG,SAAS,GAAG,SAAS,IACrD;IACF,kDAAkD;IAClD,IAAI,EAAE,KAAK,CAAC;IACZ,2CAA2C;IAC3C,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,wDAAwD;IACxD,cAAc,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,YAAY,CACtB,KAAK,SAAS,MAAM,EACpB,KAAK,SAAS,aAAa,EAC3B,SAAS,SAAS,aAAa,GAAG,SAAS,GAAG,SAAS,IACrD,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,GACzE,8BAA8B,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/C,WAAW,IAAI,8BAA8B,CAAC;CAC/C,CAAC;AAmBJ;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CACzB,KAAK,SAAS,MAAM,EACpB,KAAK,SAAS,aAAa,EAC3B,SAAS,SAAS,aAAa,GAAG,SAAS,GAAG,SAAS,EACvD,MAAM,EAAE,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CA6CrF"}
|
package/dist/builders/event.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import { EVENT_SOURCING_ACTION_META_SCHEMA, schemaToJsonObject } from '../dot-action.js';
|
|
2
|
+
function eventAction(type, dataSchema) {
|
|
3
|
+
return {
|
|
4
|
+
id: type,
|
|
5
|
+
binding: 'es',
|
|
6
|
+
direction: 'out',
|
|
7
|
+
address: type,
|
|
8
|
+
metaSchema: EVENT_SOURCING_ACTION_META_SCHEMA,
|
|
9
|
+
meta: {
|
|
10
|
+
kind: 'event',
|
|
11
|
+
data: schemaToJsonObject(dataSchema, `event "${type}" data`),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
1
15
|
/**
|
|
2
16
|
* Creates a strongly-typed event factory function.
|
|
3
17
|
*
|
|
@@ -23,7 +37,8 @@
|
|
|
23
37
|
*/
|
|
24
38
|
export function defineEvent(config) {
|
|
25
39
|
const { type, dataSchema, metadataSchema } = config;
|
|
26
|
-
|
|
40
|
+
let cachedAction;
|
|
41
|
+
const factory = (data, metadata) => {
|
|
27
42
|
const validatedData = dataSchema.parse(data);
|
|
28
43
|
const validatedMetadata = metadataSchema ? metadataSchema.parse(metadata) : metadata;
|
|
29
44
|
// Conditionally construct the event object based on whether metadata is provided
|
|
@@ -38,5 +53,27 @@ export function defineEvent(config) {
|
|
|
38
53
|
metadata: validatedMetadata,
|
|
39
54
|
});
|
|
40
55
|
};
|
|
56
|
+
const getAction = () => {
|
|
57
|
+
cachedAction ??= eventAction(type, dataSchema);
|
|
58
|
+
return cachedAction;
|
|
59
|
+
};
|
|
60
|
+
Object.defineProperties(factory, {
|
|
61
|
+
type: { value: type, enumerable: true },
|
|
62
|
+
dataSchema: { value: dataSchema },
|
|
63
|
+
...(metadataSchema === undefined ? {} : { metadataSchema: { value: metadataSchema } }),
|
|
64
|
+
id: { value: type, enumerable: true },
|
|
65
|
+
binding: { value: 'es', enumerable: true },
|
|
66
|
+
direction: { value: 'out', enumerable: true },
|
|
67
|
+
address: { value: type, enumerable: true },
|
|
68
|
+
metaSchema: { value: EVENT_SOURCING_ACTION_META_SCHEMA, enumerable: true },
|
|
69
|
+
meta: {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
get() {
|
|
72
|
+
return getAction().meta;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
toDotAction: { value: getAction },
|
|
76
|
+
});
|
|
77
|
+
return factory;
|
|
41
78
|
}
|
|
42
79
|
//# sourceMappingURL=event.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/builders/event.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/builders/event.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iCAAiC,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AA+BzF,SAAS,WAAW,CAClB,IAAW,EACX,UAAqB;IAErB,OAAO;QACL,EAAE,EAAE,IAAI;QACR,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,KAAK;QAChB,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,iCAAiC;QAC7C,IAAI,EAAE;YACJ,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,kBAAkB,CAAC,UAAU,EAAE,UAAU,IAAI,QAAQ,CAAC;SAC7D;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,WAAW,CAIzB,MAA4C;IAC5C,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;IACpD,IAAI,YAAwD,CAAC;IAE7D,MAAM,OAAO,GAAG,CAAC,IAAW,EAAE,QAAoB,EAAE,EAAE;QACpD,MAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,iBAAiB,GAAG,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAErF,iFAAiF;QACjF,OAAO,CAAC,iBAAiB,KAAK,SAAS;YACrC,CAAC,CAAC;gBACE,IAAI;gBACJ,IAAI,EAAE,aAAa;aACpB;YACH,CAAC,CAAC;gBACE,IAAI;gBACJ,IAAI,EAAE,aAAa;gBACnB,QAAQ,EAAE,iBAAiB;aAC5B,CAA8C,CAAC;IACtD,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,GAAmC,EAAE;QACrD,YAAY,KAAK,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC/C,OAAO,YAAY,CAAC;IACtB,CAAC,CAAC;IAEF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE;QAC/B,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QACvC,UAAU,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE;QACjC,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,CAAC;QACtF,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QACrC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QAC1C,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;QAC7C,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QAC1C,UAAU,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,UAAU,EAAE,IAAI,EAAE;QAC1E,IAAI,EAAE;YACJ,UAAU,EAAE,IAAI;YAChB,GAAG;gBACD,OAAO,SAAS,EAAE,CAAC,IAAI,CAAC;YAC1B,CAAC;SACF;QACD,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;KAClC,CAAC,CAAC;IAEH,OAAO,OAAgD,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from '@arki/contracts';
|
|
2
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
export type JsonObject = {
|
|
6
|
+
[key: string]: JsonValue;
|
|
7
|
+
};
|
|
8
|
+
export declare const EVENT_SOURCING_ACTION_META_SCHEMA = "@arki/event-sourcing/action-meta@1";
|
|
9
|
+
export declare const EVENT_SOURCING_SCHEMA_REJECTED_CODE = "EVENT_SOURCING_PLUGIN_E007";
|
|
10
|
+
export type EventSourcingActionMeta = {
|
|
11
|
+
readonly kind: 'command';
|
|
12
|
+
readonly input: JsonObject;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'event';
|
|
15
|
+
readonly data: JsonObject;
|
|
16
|
+
};
|
|
17
|
+
export type EventSourcingActionDeclaration = {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly binding: 'es';
|
|
20
|
+
readonly direction: 'in' | 'out';
|
|
21
|
+
readonly address: string;
|
|
22
|
+
readonly metaSchema: typeof EVENT_SOURCING_ACTION_META_SCHEMA;
|
|
23
|
+
readonly meta: EventSourcingActionMeta;
|
|
24
|
+
};
|
|
25
|
+
export declare class EventSourcingActionError extends Error {
|
|
26
|
+
readonly code = "EVENT_SOURCING_PLUGIN_E007";
|
|
27
|
+
constructor(message: string, cause: unknown);
|
|
28
|
+
}
|
|
29
|
+
export declare function isJsonObject(value: unknown): value is JsonObject;
|
|
30
|
+
export declare function schemaToJsonObject(schema: z.ZodType, label: string): JsonObject;
|
|
31
|
+
//# sourceMappingURL=dot-action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dot-action.d.ts","sourceRoot":"","sources":["../src/dot-action.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAC;AAEpC,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AACtG,MAAM,MAAM,UAAU,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEtD,eAAO,MAAM,iCAAiC,uCAAuC,CAAC;AACtF,eAAO,MAAM,mCAAmC,+BAA+B,CAAC;AAEhF,MAAM,MAAM,uBAAuB,GAC/B;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEN,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,IAAI,GAAG,KAAK,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,iCAAiC,CAAC;IAC9D,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;CACxC,CAAC;AAEF,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,IAAI,gCAAuC;gBAExC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAI5C;AAmBD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE;AA4BD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAY/E"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from '@arki/contracts';
|
|
2
|
+
export const EVENT_SOURCING_ACTION_META_SCHEMA = '@arki/event-sourcing/action-meta@1';
|
|
3
|
+
export const EVENT_SOURCING_SCHEMA_REJECTED_CODE = 'EVENT_SOURCING_PLUGIN_E007';
|
|
4
|
+
export class EventSourcingActionError extends Error {
|
|
5
|
+
code = EVENT_SOURCING_SCHEMA_REJECTED_CODE;
|
|
6
|
+
constructor(message, cause) {
|
|
7
|
+
super(message, { cause });
|
|
8
|
+
this.name = 'EventSourcingActionError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function isJsonPrimitive(value) {
|
|
12
|
+
return value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number';
|
|
13
|
+
}
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
16
|
+
return false;
|
|
17
|
+
const prototype = Object.getPrototypeOf(value);
|
|
18
|
+
return prototype === Object.prototype || prototype === null;
|
|
19
|
+
}
|
|
20
|
+
function isJsonValue(value) {
|
|
21
|
+
if (isJsonPrimitive(value))
|
|
22
|
+
return typeof value !== 'number' || Number.isFinite(value);
|
|
23
|
+
if (Array.isArray(value))
|
|
24
|
+
return value.every(isJsonValue);
|
|
25
|
+
if (!isPlainObject(value))
|
|
26
|
+
return false;
|
|
27
|
+
return Object.values(value).every(isJsonValue);
|
|
28
|
+
}
|
|
29
|
+
export function isJsonObject(value) {
|
|
30
|
+
return isPlainObject(value) && Object.values(value).every(isJsonValue);
|
|
31
|
+
}
|
|
32
|
+
function jsonDeepEqual(left, right) {
|
|
33
|
+
if (isJsonPrimitive(left) || isJsonPrimitive(right))
|
|
34
|
+
return Object.is(left, right);
|
|
35
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
36
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
|
|
37
|
+
return false;
|
|
38
|
+
return left.every((value, index) => jsonDeepEqual(value, right[index]));
|
|
39
|
+
}
|
|
40
|
+
if (!isPlainObject(left) || !isPlainObject(right))
|
|
41
|
+
return false;
|
|
42
|
+
const leftEntries = Object.entries(left);
|
|
43
|
+
const rightEntries = Object.entries(right);
|
|
44
|
+
if (leftEntries.length !== rightEntries.length)
|
|
45
|
+
return false;
|
|
46
|
+
for (const [key, value] of leftEntries) {
|
|
47
|
+
if (!Object.hasOwn(right, key) || !jsonDeepEqual(value, right[key]))
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
function toJsonObject(value) {
|
|
53
|
+
const serialized = JSON.stringify(value);
|
|
54
|
+
if (serialized === undefined)
|
|
55
|
+
throw new TypeError('Value must be a JSON-serializable object.');
|
|
56
|
+
const parsed = JSON.parse(serialized);
|
|
57
|
+
if (!isJsonObject(parsed) || !jsonDeepEqual(value, parsed)) {
|
|
58
|
+
throw new TypeError('Value must be a JSON-serializable object without lossy coercions.');
|
|
59
|
+
}
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
export function schemaToJsonObject(schema, label) {
|
|
63
|
+
try {
|
|
64
|
+
const json = z.toJSONSchema(schema);
|
|
65
|
+
const rest = { ...json };
|
|
66
|
+
delete rest['$schema'];
|
|
67
|
+
return toJsonObject(rest);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
throw new EventSourcingActionError(`[event-sourcing] ${label} schema could not be converted to JSON Schema.`, error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=dot-action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dot-action.js","sourceRoot":"","sources":["../src/dot-action.ts"],"names":[],"mappings":"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"}
|
package/dist/dot.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* DOT adapter for `@arki/event-sourcing`.
|
|
3
3
|
*
|
|
4
4
|
* Wraps `eventSourcingFeatures.initEventSourcing` + `initMessageBus` as a
|
|
5
|
-
* single `
|
|
5
|
+
* single `DotPlugin`. The plugin opens the PostgreSQL event store in
|
|
6
6
|
* `boot`, attaches command handlers to an in-memory message bus, publishes
|
|
7
7
|
* both as `services.eventStore` and `services.messageBus`, and closes the
|
|
8
8
|
* event store pool in `dispose` (reverse declaration order).
|
|
@@ -36,31 +36,98 @@
|
|
|
36
36
|
* Importing this adapter without `@arki/dot` installed will fail at module
|
|
37
37
|
* load — that is intentional: the adapter only makes sense in a DOT app.
|
|
38
38
|
*/
|
|
39
|
-
import type { EventStore, MessageBus } from '@event-driven-io/emmett';
|
|
40
|
-
import type {
|
|
39
|
+
import type { Command, Event, EventStore, MessageBus } from '@event-driven-io/emmett';
|
|
40
|
+
import type { Plugin, Token } from '@arki/dot/plugin';
|
|
41
41
|
import type { CommandHandlerRegistration } from './command.js';
|
|
42
42
|
import type { PostgreSQLProjectionInput } from './event-sourcing-features.js';
|
|
43
|
+
import type { CommandHandler } from './builders/command-handler.js';
|
|
43
44
|
/**
|
|
44
|
-
* Stable error codes thrown by the event-sourcing
|
|
45
|
+
* Stable error codes thrown by the event-sourcing plugin. Exported so consumers
|
|
45
46
|
* and coding agents can match against them — never parse the message.
|
|
46
47
|
*
|
|
47
48
|
* @see packages/dot/docs/principles.md — principle 1.3 ("errors are part
|
|
48
49
|
* of the API") and principle 4 ("agent-discoverable everywhere").
|
|
49
50
|
*/
|
|
50
|
-
export declare const
|
|
51
|
+
export declare const EVENT_SOURCING_PLUGIN_ERROR_CODES: {
|
|
51
52
|
/** boot was called without a configured event-store URL. */
|
|
52
|
-
readonly dbUrlNotConfigured: "
|
|
53
|
+
readonly dbUrlNotConfigured: "EVENT_SOURCING_PLUGIN_E001";
|
|
54
|
+
/** two collected handlers claim the same command type. */
|
|
55
|
+
readonly duplicateCommandHandler: "EVENT_SOURCING_PLUGIN_E002";
|
|
56
|
+
/** a bundle token listed in options.bundles was not provided. */
|
|
57
|
+
readonly bundleMissing: "EVENT_SOURCING_PLUGIN_E003";
|
|
58
|
+
/** a command was dispatched with no registered handler. */
|
|
59
|
+
readonly commandHandlerMissing: "EVENT_SOURCING_PLUGIN_E004";
|
|
60
|
+
/** a command was dispatched after the dispatcher closed. */
|
|
61
|
+
readonly dispatcherClosed: "EVENT_SOURCING_PLUGIN_E005";
|
|
62
|
+
/** reserved for a future declared-action/handler parity diagnostic. */
|
|
63
|
+
readonly declaredActionUnbound: "EVENT_SOURCING_PLUGIN_E006";
|
|
64
|
+
/** command/event schema could not be converted to JSON Schema. */
|
|
65
|
+
readonly schemaRejected: "EVENT_SOURCING_PLUGIN_E007";
|
|
66
|
+
};
|
|
67
|
+
/** A token publishing an {@link EsBundle} — what `options.bundles` lists. */
|
|
68
|
+
export type BundleToken = Token<EsBundle, string>;
|
|
69
|
+
/** Wire-needs record derived from bundle tokens. */
|
|
70
|
+
export type BundleNeeds<TBundles extends readonly BundleToken[]> = {
|
|
71
|
+
readonly [Tok in TBundles[number] as Tok extends Token<EsBundle, infer K> ? K : never]: EsBundle;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* A feature token whose slice MAY carry an ES bundle under the `es` key —
|
|
75
|
+
* what `options.features` lists (the feature-plugins slice protocol).
|
|
76
|
+
* Features without an `es` slice contribute nothing, by design.
|
|
77
|
+
*/
|
|
78
|
+
export type EsFeatureToken = Token<{
|
|
79
|
+
readonly es?: EsBundle;
|
|
80
|
+
}, string>;
|
|
81
|
+
/** Wire-needs record derived from feature tokens. */
|
|
82
|
+
export type EsFeatureNeeds<TFeatures extends readonly EsFeatureToken[]> = {
|
|
83
|
+
readonly [Tok in TFeatures[number] as Tok extends Token<unknown, infer K> ? K : never]: {
|
|
84
|
+
readonly es?: EsBundle;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
type CommandFactory<C extends Command = Command> = {
|
|
88
|
+
readonly type: C['type'];
|
|
89
|
+
} & ((input: never, metadata?: never) => C);
|
|
90
|
+
/** Feature-local event-sourcing registrations collected by `eventSourcing()`. */
|
|
91
|
+
export type EsBundle = {
|
|
92
|
+
/** Command handlers attached to the in-process message bus at boot. */
|
|
93
|
+
readonly handlers: readonly CommandHandlerRegistration[];
|
|
94
|
+
/** Inline PostgreSQL read-model projections registered on the event store. */
|
|
95
|
+
readonly readModels: readonly PostgreSQLProjectionInput[];
|
|
96
|
+
};
|
|
97
|
+
export type EsBundleInput = {
|
|
98
|
+
readonly handlers?: readonly CommandHandlerRegistration[];
|
|
99
|
+
readonly readModels?: readonly PostgreSQLProjectionInput[];
|
|
100
|
+
};
|
|
101
|
+
declare function bundle(input?: EsBundleInput): EsBundle;
|
|
102
|
+
declare function handle<C extends Command, State, StreamEvent extends Event>(command: CommandFactory<C>, handler: CommandHandler<State, C, StreamEvent>, getStreamName: (command: C) => string): CommandHandlerRegistration<C, State, StreamEvent>;
|
|
103
|
+
/** Namespace for feature-local ES bundle helpers. */
|
|
104
|
+
export declare const es: {
|
|
105
|
+
bundle: typeof bundle;
|
|
106
|
+
handle: typeof handle;
|
|
53
107
|
};
|
|
54
108
|
/**
|
|
55
109
|
* Options for the event-sourcing DOT adapter.
|
|
56
110
|
*/
|
|
57
|
-
export type EventSourcingDotOptions = {
|
|
111
|
+
export type EventSourcingDotOptions<TBundles extends readonly BundleToken[] = readonly BundleToken[], TFeatures extends readonly EsFeatureToken[] = readonly EsFeatureToken[]> = {
|
|
58
112
|
/**
|
|
59
113
|
* PostgreSQL projection definitions registered inline on the event store.
|
|
60
114
|
* Accepts the three projection builder patterns documented on
|
|
61
115
|
* {@link PostgreSQLProjectionInput}.
|
|
62
116
|
*/
|
|
63
|
-
readonly projections
|
|
117
|
+
readonly projections?: readonly PostgreSQLProjectionInput[];
|
|
118
|
+
/**
|
|
119
|
+
* Feature-local bundles to collect. Each token becomes a typed DOT need,
|
|
120
|
+
* so the app builder rejects an `eventSourcing({ bundles })` collector
|
|
121
|
+
* unless an earlier plugin publishes every listed bundle.
|
|
122
|
+
*/
|
|
123
|
+
readonly bundles?: TBundles;
|
|
124
|
+
/**
|
|
125
|
+
* Feature-plugin slice tokens to collect (`{ es?: EsBundle }`). Each token
|
|
126
|
+
* becomes a typed DOT need like `bundles`; a feature without an `es`
|
|
127
|
+
* slice contributes nothing (partial features are zero-config — a
|
|
128
|
+
* debug line records the empty contribution).
|
|
129
|
+
*/
|
|
130
|
+
readonly features?: TFeatures;
|
|
64
131
|
/**
|
|
65
132
|
* Command handlers wired into the in-memory message bus. Each handler's
|
|
66
133
|
* `getStreamName` callback maps an incoming command to its target stream.
|
|
@@ -70,7 +137,7 @@ export type EventSourcingDotOptions = {
|
|
|
70
137
|
*/
|
|
71
138
|
readonly commandHandlers?: readonly CommandHandlerRegistration[];
|
|
72
139
|
/**
|
|
73
|
-
* Connection URL for the event store. When omitted, the
|
|
140
|
+
* Connection URL for the event store. When omitted, the plugin reads
|
|
74
141
|
* `EVENT_STORE_URL`, `EVENTSTORE_URL`, or `EVENT_DB_URL` from
|
|
75
142
|
* `process.env`. If none are set, `boot` throws.
|
|
76
143
|
*/
|
|
@@ -87,10 +154,11 @@ export type EventSourcingServices = {
|
|
|
87
154
|
readonly messageBus: MessageBus;
|
|
88
155
|
};
|
|
89
156
|
/**
|
|
90
|
-
* Build a DOT
|
|
157
|
+
* Build a DOT plugin that opens the event store, wires command handlers
|
|
91
158
|
* into an in-memory message bus, and publishes both as services. The
|
|
92
159
|
* kernel calls `dispose` in reverse declaration order to release the
|
|
93
160
|
* underlying PG pool.
|
|
94
161
|
*/
|
|
95
|
-
export declare function eventSourcing(options
|
|
162
|
+
export declare function eventSourcing<const TBundles extends readonly BundleToken[] = readonly [], const TFeatures extends readonly EsFeatureToken[] = readonly []>(options?: EventSourcingDotOptions<TBundles, TFeatures>): Plugin<BundleNeeds<TBundles> & EsFeatureNeeds<TFeatures>, EventSourcingServices>;
|
|
163
|
+
export {};
|
|
96
164
|
//# sourceMappingURL=dot.d.ts.map
|
package/dist/dot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../src/dot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../src/dot.ts"],"names":[],"mappings":"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"}
|