@robinmalfait/event-source 0.0.15 → 0.0.17

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 CHANGED
@@ -1,36 +1,286 @@
1
1
  # Event Source
2
2
 
3
- ![Node CI](https://github.com/RobinMalfait/event-source/workflows/Node%20CI/badge.svg)
3
+ A TypeScript library for building event-sourced applications in Node.js. This library provides the foundational building blocks for implementing CQRS (Command Query Responsibility Segregation) and Event Sourcing patterns.
4
4
 
5
- A node library for writing event sourced applications.
5
+ ## Installation
6
6
 
7
- ## Usage
7
+ ```bash
8
+ npm install @robinmalfait/event-source
9
+ # or
10
+ pnpm add @robinmalfait/event-source
11
+ ```
12
+
13
+ ## Core Concepts
14
+
15
+ ### Events
16
+
17
+ Events are immutable records of something that happened in your domain. They contain an aggregate ID, payload, metadata, and version information.
18
+
19
+ ```typescript
20
+ import { Event } from '@robinmalfait/event-source'
8
21
 
9
- ```js
10
- import {
11
- Aggregate,
12
- Command,
13
- Event,
14
- abort,
15
- createEventMapper,
16
- createEventSource,
17
- createProjector,
18
- createTestEventStore,
19
- } from '@robinmalfait/event-source'
22
+ function accountOpened(id: string, owner: string) {
23
+ return Event('ACCOUNT_OPENED', id, { owner })
24
+ }
25
+
26
+ function moneyDeposited(id: string, amount: number) {
27
+ return Event('MONEY_DEPOSITED', id, { amount })
28
+ }
20
29
  ```
21
30
 
22
- ## Local Development
31
+ ### Commands
32
+
33
+ Commands represent an intent to perform an action. They carry a type and payload.
34
+
35
+ ```typescript
36
+ import { Command } from '@robinmalfait/event-source'
37
+
38
+ function openAccount(id: string, owner: string) {
39
+ return Command('OPEN_ACCOUNT', { id, owner })
40
+ }
41
+
42
+ function depositMoney(accountId: string, amount: number) {
43
+ return Command('DEPOSIT_MONEY', { accountId, amount })
44
+ }
45
+ ```
46
+
47
+ ### Aggregates
48
+
49
+ Aggregates are domain entities that emit events and rebuild their state from event history. They extend the base `Aggregate` class and define `apply` handlers for each event type.
50
+
51
+ ```typescript
52
+ import { Aggregate, abort } from '@robinmalfait/event-source'
53
+
54
+ class Account extends Aggregate {
55
+ private owner: string = ''
56
+ private balance: number = 0
57
+ private closed: boolean = false
58
+
59
+ // Static factory methods for creating new aggregates
60
+ static open(id: string, owner: string) {
61
+ return new Account().recordThat(accountOpened(id, owner))
62
+ }
63
+
64
+ // Instance methods for operations on existing aggregates
65
+ deposit(amount: number) {
66
+ if (this.closed) {
67
+ abort('Cannot deposit to a closed account')
68
+ }
69
+ return this.recordThat(moneyDeposited(this.aggregateId, amount))
70
+ }
71
+
72
+ // Apply handlers rebuild state from events
73
+ apply = {
74
+ ACCOUNT_OPENED: (event) => {
75
+ this.owner = event.payload.owner
76
+ },
77
+ MONEY_DEPOSITED: (event) => {
78
+ this.balance += event.payload.amount
79
+ },
80
+ ACCOUNT_CLOSED: () => {
81
+ this.closed = true
82
+ },
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Setting Up EventSource
88
+
89
+ The `EventSource` class is the central coordinator that connects everything together. Use the builder pattern to configure it:
90
+
91
+ ```typescript
92
+ import { EventSource } from '@robinmalfait/event-source'
93
+
94
+ const eventSource = EventSource.builder(myEventStore)
95
+ .addCommandHandler('OPEN_ACCOUNT', openAccountHandler)
96
+ .addCommandHandler('DEPOSIT_MONEY', depositMoneyHandler)
97
+ .addProjector(new BalanceProjector())
98
+ .addEventHandler(sendNotificationHandler)
99
+ .metadata(() => ({ userId: getCurrentUserId() }))
100
+ .build()
101
+
102
+ // Dispatch commands
103
+ await eventSource.dispatch(openAccount('acc-123', 'John Doe'))
104
+ await eventSource.dispatch(depositMoney('acc-123', 1000))
105
+ ```
106
+
107
+ ### Command Handlers
108
+
109
+ Command handlers receive a command and the event source instance, and return an aggregate to persist:
110
+
111
+ ```typescript
112
+ import type { CommandHandler } from '@robinmalfait/event-source'
113
+
114
+ const openAccountHandler: CommandHandler<
115
+ ReturnType<typeof openAccount>
116
+ > = async (command, es) => {
117
+ return es.persist(Account.open(command.payload.id, command.payload.owner))
118
+ }
119
+
120
+ const depositMoneyHandler: CommandHandler<
121
+ ReturnType<typeof depositMoney>
122
+ > = async (command, es) => {
123
+ let account = await es.load(Account, command.payload.accountId)
124
+ return es.persist(account.deposit(command.payload.amount))
125
+ }
126
+ ```
127
+
128
+ ### EventStore Interface
129
+
130
+ To use the library, implement the `EventStore` interface with your preferred storage:
131
+
132
+ ```typescript
133
+ import type { EventStore, EventType } from '@robinmalfait/event-source'
134
+
135
+ class MyEventStore implements EventStore {
136
+ async persist(events: EventType[]): Promise<void> {
137
+ // Store events in your database
138
+ }
139
+
140
+ async load(aggregateId: string): Promise<EventType[]> {
141
+ // Load all events for an aggregate
142
+ }
143
+
144
+ async loadEvents(): Promise<EventType[]> {
145
+ // Load all events (for rebuilding projections)
146
+ }
147
+ }
148
+ ```
149
+
150
+ See the [`examples/mysql-event-store`](./examples/mysql-event-store) directory for a MySQL implementation using Knex.js.
23
151
 
24
- Below is a list of commands you will probably find useful.
152
+ ### Projectors
153
+
154
+ Projectors build read models from events. They process events sequentially and maintain derived state:
155
+
156
+ ```typescript
157
+ import { Projector, createEventMapper } from '@robinmalfait/event-source'
158
+
159
+ class BalanceProjector extends Projector {
160
+ name = 'balance-projector'
161
+ private balances = new Map<string, number>()
162
+
163
+ apply = createEventMapper({
164
+ ACCOUNT_OPENED: (event) => {
165
+ this.balances.set(event.aggregateId, 0)
166
+ },
167
+ MONEY_DEPOSITED: (event) => {
168
+ let current = this.balances.get(event.aggregateId) ?? 0
169
+ this.balances.set(event.aggregateId, current + event.payload.amount)
170
+ },
171
+ })
172
+
173
+ async initializer() {
174
+ // Load initial state if needed
175
+ }
176
+
177
+ async project(event: EventType) {
178
+ // Called for each new event
179
+ this.apply(event)
180
+ }
181
+
182
+ getBalance(accountId: string) {
183
+ return this.balances.get(accountId) ?? 0
184
+ }
185
+ }
186
+ ```
187
+
188
+ ## Testing
189
+
190
+ The library provides BDD-style testing utilities with a `given/when/then` pattern:
191
+
192
+ ```typescript
193
+ import { createTestEventStore } from '@robinmalfait/event-source'
194
+
195
+ describe('deposit money', () => {
196
+ let { given, when, then, ___ } = createTestEventStore({
197
+ DEPOSIT_MONEY: depositMoneyHandler,
198
+ })
199
+
200
+ it('should deposit money to an open account', async () => {
201
+ await given([accountOpened('acc-123', 'John Doe')])
202
+
203
+ await when(depositMoney('acc-123', 500))
204
+
205
+ await then([moneyDeposited('acc-123', 500)])
206
+ })
207
+
208
+ it('should fail when depositing to a closed account', async () => {
209
+ await given([
210
+ accountOpened('acc-123', 'John Doe'),
211
+ accountClosed('acc-123'),
212
+ ])
213
+
214
+ await when(depositMoney('acc-123', 500))
215
+
216
+ await then(new Error('Cannot deposit to a closed account'))
217
+ })
218
+
219
+ it('should use placeholders for values we do not care about', async () => {
220
+ await given([accountOpened('acc-123', 'John Doe')])
221
+
222
+ await when(depositMoney('acc-123', 500))
223
+
224
+ // Use ___ as a placeholder for any value
225
+ await then([Event('MONEY_DEPOSITED', ___, { amount: 500 })])
226
+ })
227
+ })
228
+ ```
229
+
230
+ ## Utilities
231
+
232
+ ### abort
233
+
234
+ Throw errors with clean stack traces and custom attributes:
235
+
236
+ ```typescript
237
+ import { abort } from '@robinmalfait/event-source'
238
+
239
+ if (balance < amount) {
240
+ abort('Insufficient funds', { balance, requested: amount })
241
+ }
242
+ ```
243
+
244
+ ### Type Utilities
245
+
246
+ Extract types from events and commands:
247
+
248
+ ```typescript
249
+ import type { PayloadOf, TypeOf } from '@robinmalfait/event-source'
250
+
251
+ type AccountOpenedPayload = PayloadOf<ReturnType<typeof accountOpened>>
252
+ // { owner: string }
253
+
254
+ type AccountOpenedType = TypeOf<ReturnType<typeof accountOpened>>
255
+ // 'ACCOUNT_OPENED'
256
+ ```
257
+
258
+ ## Examples
259
+
260
+ See the [`examples/bank`](./examples/bank) directory for a complete bank account domain implementation demonstrating:
261
+
262
+ - Domain events and commands
263
+ - Account aggregate with business rules
264
+ - Command handlers
265
+ - Test cases using the given/when/then pattern
266
+
267
+ ## Local Development
25
268
 
26
- ### `pnpm start`
269
+ ### Prerequisites
27
270
 
28
- Start the build in watch mode, which makes it easy to make incremental builds.
271
+ - Node.js 24+ (see `.nvmrc`)
272
+ - pnpm
29
273
 
30
- ### `pnpm build`
274
+ ### Commands
31
275
 
32
- Build the package!
276
+ | Command | Description |
277
+ | ------------- | ----------------------------------------------------- |
278
+ | `pnpm start` | Build in watch mode for development |
279
+ | `pnpm build` | Production build (ESM + CJS + TypeScript definitions) |
280
+ | `pnpm test` | Run all tests |
281
+ | `pnpm tdd` | Run tests in watch mode |
282
+ | `pnpm format` | Format code with Prettier |
33
283
 
34
- ### `pnpm test`
284
+ ## License
35
285
 
36
- Runn all the tests!
286
+ MIT
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var I=Object.create;var h=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var Q=Object.getPrototypeOf,R=Object.prototype.hasOwnProperty;var F=(r,e)=>{for(var t in e)h(r,t,{get:e[t],enumerable:!0})},C=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of J(e))!R.call(r,a)&&a!==t&&h(r,a,{get:()=>e[a],enumerable:!(n=_(e,a))||n.enumerable});return r};var V=(r,e,t)=>(t=r!=null?I(Q(r)):{},C(e||!r||!r.__esModule?h(t,"default",{value:r,enumerable:!0}):t,r)),Y=r=>C(h({},"__esModule",{value:!0}),r);var G={};F(G,{Aggregate:()=>A,Command:()=>W,Event:()=>Z,EventSource:()=>y,Projector:()=>m,abort:()=>c,createEventMapper:()=>k,createTestEventStore:()=>B,objectToYaml:()=>f});module.exports=Y(G);function j(r,e){let t=Object.assign(new Error(r),e);return t.stack,Error.captureStackTrace&&Error.captureStackTrace(t,j),t}function c(r,e){let t=j(r,e);throw Error.captureStackTrace&&Error.captureStackTrace(t,c),t}function S(r){Object.freeze(r);for(let e of Object.getOwnPropertyNames(r))r.hasOwnProperty(e)&&r[e]!==null&&(typeof r[e]=="object"||typeof r[e]=="function")&&!Object.isFrozen(r[e])&&S(r[e]);return r}var U={NODE_ENV:process.env.NODE_ENV},A=class{#e=0;#t=[];replayEvents(e=[]){for(let t of e)this.applyAnEvent(t);return this}applyAnEvent(e){U.NODE_ENV==="test"&&S(e);let t=this.apply[e.eventName];t==null&&(e.eventName.match(/^[$A-Z_][0-9A-Z_$]*$/i)?c(`Aggregate "${this.constructor.name}" has no method:
1
+ "use strict";var L=Object.create;var h=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var _=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var R=(n,e)=>{for(var t in e)h(n,t,{get:e[t],enumerable:!0})},H=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of I(e))!J.call(n,a)&&a!==t&&h(n,a,{get:()=>e[a],enumerable:!(r=D(e,a))||r.enumerable});return n};var B=(n,e,t)=>(t=n!=null?L(_(n)):{},H(e||!n||!n.__esModule?h(t,"default",{value:n,enumerable:!0}):t,n)),Q=n=>H(h({},"__esModule",{value:!0}),n);var W={};R(W,{Aggregate:()=>A,Command:()=>V,Event:()=>Y,EventSource:()=>E,Projector:()=>m,abort:()=>c,createEventMapper:()=>C,createTestEventStore:()=>U,objectToYaml:()=>f});module.exports=Q(W);function M(n,e){let t=Object.assign(new Error(n),e);return t.stack,Error.captureStackTrace&&Error.captureStackTrace(t,M),t}function c(n,e){let t=M(n,e);throw Error.captureStackTrace&&Error.captureStackTrace(t,c),t}function P(n){Object.freeze(n);for(let e of Object.getOwnPropertyNames(n))n.hasOwnProperty(e)&&n[e]!==null&&(typeof n[e]=="object"||typeof n[e]=="function")&&!Object.isFrozen(n[e])&&P(n[e]);return n}var F={NODE_ENV:typeof process<"u"?process.env?.NODE_ENV:void 0},A=class{#e=0;#t=[];replayEvents(e=[]){for(let t of e)this.applyAnEvent(t);return this}applyAnEvent(e){F.NODE_ENV==="test"&&P(e);let t=this.apply[e.eventName];return t==null&&(e.eventName.match(/^[$A-Z_][0-9A-Z_$]*$/i)?c(`Aggregate "${this.constructor.name}" has no method:
2
2
 
3
3
  apply = {
4
4
  ${e.eventName}(event) {
@@ -12,25 +12,22 @@ apply = {
12
12
  // Code goes here...
13
13
  }
14
14
  // ...
15
- }`));try{t(e)}catch(n){n instanceof Error&&console.error(`An error occurred inside your "%s" function:
16
- `,e.eventName,n.stack?.split(`
17
- `).map(a=>` ${a}`).join(`
18
- `))}finally{this.#e++}return this}recordThat(e){let t={...e,version:this.#e};return this.applyAnEvent(t),this.#t.push(t),this}releaseEvents(){return this.#t.splice(0)}};function W(r,e=null){return{type:r,payload:e}}var z=require("crypto");function Z(r,e,t=null,n=null){return{aggregateId:e,eventId:(0,z.randomUUID)(),eventName:r,payload:t,metadata:n,recordedAt:new Date,version:-1}}var L=V(require("yamlify-object"),1),q={indent:" ",colors:{date:v,error:v,symbol:v,string:v,number:v,boolean:v,null:v,undefined:v}};function v(r){return r}function f(r){return r instanceof Error?f({...r}):(0,L.default)(r,q).split(`
15
+ }`)),t(e),this.#e++,this}recordThat(e){let t={...e,version:this.#e};return this.applyAnEvent(t),this.#t.push(t),this}releaseEvents(){return this.#t.splice(0)}};function V(n,e=null){return{type:n,payload:e}}function Y(n,e,t=null,r=null){return{aggregateId:e,eventId:globalThis.crypto.randomUUID(),eventName:n,payload:t,metadata:r,recordedAt:new Date,version:-1}}var k=B(require("yamlify-object"),1),q={indent:" ",colors:{date:v,error:v,symbol:v,string:v,number:v,boolean:v,null:v,undefined:v}};function v(n){return n}function f(n){return n instanceof Error?f({...n}):(0,k.default)(n,q).split(`
19
16
  `).slice(1).join(`
20
- `)}var T=new WeakMap;function g(r,e){if(T.has(r)){let t=T.get(r);for(let n in e)t[n]=e[n]}else T.set(r,e)}function b(r){return T.get(r)}var x=class{constructor(){g(this,{jobs:[],state:0})}get length(){return b(this).jobs.length}async start(){let{state:e,jobs:t}=b(this);if(!(e===1||t.length<=0)){for(g(this,{state:1});t.length>0;){let n=t.shift();await Promise.resolve().then(n.handle).then(n.resolve,n.reject)}g(this,{state:0})}}push(e){return new Promise((t,n)=>{let{jobs:a}=b(this);a.push({handle:e,resolve:t,reject:n}),setImmediate(()=>this.start())})}};var m=class{apply;initializer(){}#e=new x;async init(e){this.initializer&&await this.#e.push(()=>this.initializer?.());let t=await e.loadEvents();await Promise.all(t.map(n=>this.applyEvent(n)))}async applyEvent(e){await this.#e.push(()=>this.apply?.[e.eventName]?.(e)),await this.#e.push(()=>this.project(e))}project(e){}},y=class r{constructor(e,t,n,a,o){this.store=e;this.commandHandlers=t;this.projectors=n;this.eventHandlers=a;this.eventMetadataEnhancers=o}static builder(e){return new M(e)}static new(e,t,n,a,o){return new r(e,t,n,a,o)}async resetProjections(){await Promise.all(this.projectors.map(e=>e.init(this)))}async dispatch(e){return this.commandHandlers.has(e.type)||c(`There is no command handler for the "${e.type}" command`),await this.commandHandlers.get(e.type)(e,this),e}async loadEvents(){return this.store.loadEvents()}async load(e,t){let n=await this.store.load(t);return n.length<=0&&c(`Aggregate(${e.constructor.name}) with ID(${t}) does not exist.`,{aggregate:e.constructor.name,aggregateId:t}),e.replayEvents(n)}async persist(e){let t=e.releaseEvents();if(this.eventMetadataEnhancers.length>0)for(let n of t){let a=n.metadata;n.metadata={};for(let o of this.eventMetadataEnhancers)Object.assign(n.metadata,await o(n));typeof a=="object"&&a!==null&&Object.assign(n.metadata,a)}await this.store.persist(t);for(let n of t)await Promise.all(this.projectors.map(async a=>{try{await a.applyEvent(n)}catch(o){throw o instanceof Error&&console.error(`An error occurred in one of your projections: ${a.name}, given an event`,o.stack?.split(`
21
- `).map(E=>` ${E}`).join(`
22
- `)),o}})),await Promise.all(this.eventHandlers.map(async a=>{try{await a(n,this)}catch(o){throw o instanceof Error&&console.error(`An error occurred in one of your event handlers: ${a.name}, given an event`,o.stack?.split(`
23
- `).map(E=>` ${E}`).join(`
24
- `)),o}}))}async loadPersist(e,t,n){return await this.load(e,t),await n(e),this.persist(e)}},M=class{constructor(e){this.store=e}commandHandlers=new Map;projectors=[];eventHandlers=[];eventMetadataEnhancers=[];build(){return y.new(this.store,this.commandHandlers,this.projectors,this.eventHandlers,this.eventMetadataEnhancers)}addCommandHandler(e,t){return this.commandHandlers.has(e)&&c(`A command handler for the "${e}" command already exists`),this.commandHandlers.set(e,t),this}addProjector(e){return this.projectors.push(e),this}addEventHandler(e){return this.eventHandlers.push(e),this}metadata(e){return this.eventMetadataEnhancers.push(e),this}};function k(r){return(e,t)=>r[e.eventName]?.(e,t)}var N=Symbol("__placeholder__");function D(r,e){try{return r()}catch(t){throw Error.captureStackTrace&&t instanceof Error&&Error.captureStackTrace(t,e),t}}var $=class extends m{constructor(t=[],n=[]){super();this.db=t;this.producedEvents=n}apply={};name="test-recording-projector";initializer(){this.db.splice(0)}project(t){this.producedEvents.push(t)}};function B(r,e=[]){let t=new $,n=y.builder({load(s){return t.db.filter(i=>i.aggregateId===s)},loadEvents(){return t.db},persist(s){t.db.push(...s)}});for(let s of e)n.addProjector(s);n.addProjector(t);for(let[s,i]of Object.entries(r))n.addCommandHandler(s,i);let a=n.build(),o,E=!1,w={___:N,async given(s=[]){t.db.push(...s)},async when(s){try{return await a.dispatch(typeof s=="function"?s():s)}catch(i){return i instanceof Error&&(o=i),i}},async then(s){if(E=!0,s instanceof Error){let u=s;D(()=>{if(o?.message!==u?.message)throw new Error(`Expected error message to be:
17
+ `)}var T=new WeakMap;function g(n,e){if(T.has(n)){let t=T.get(n);for(let r in e)t[r]=e[r]}else T.set(n,e)}function x(n){return T.get(n)}var b=class{constructor(){g(this,{jobs:[],state:0})}get length(){return x(this).jobs.length}async start(){let{state:e,jobs:t}=x(this);if(!(e===1||t.length<=0)){for(g(this,{state:1});t.length>0;){let r=t.shift();await Promise.resolve().then(r.handle).then(r.resolve,r.reject)}g(this,{state:0})}}push(e){return new Promise((t,r)=>{let{jobs:a}=x(this);a.push({handle:e,resolve:t,reject:r}),queueMicrotask(()=>this.start())})}};var m=class{apply;initializer(){}#e=new b;async init(e){this.initializer&&await this.#e.push(()=>this.initializer?.());for(let t of e)await this.applyEvent(t)}async applyEvent(e){await this.#e.push(()=>this.apply?.[e.eventName]?.(e)),await this.#e.push(()=>this.project(e))}project(e){}},E=class n{constructor(e,t,r,a,o){this.store=e;this.commandHandlers=t;this.projectors=r;this.eventHandlers=a;this.eventMetadataEnhancers=o}static builder(e){return new j(e)}static new(e,t,r,a,o){return new n(e,t,r,a,o)}async resetProjections(){let e=await this.store.loadEvents();await Promise.all(this.projectors.map(t=>t.init(e)))}async dispatch(e){return this.commandHandlers.has(e.type)||c(`There is no command handler for the "${e.type}" command`),await this.commandHandlers.get(e.type)(e,this),e}async loadEvents(){return this.store.loadEvents()}async load(e,t){let r=await this.store.load(t);return r.length<=0&&c(`Aggregate(${e.constructor.name}) with ID(${t}) does not exist.`,{aggregate:e.constructor.name,aggregateId:t}),e.replayEvents(r)}async persist(e){let t=e.releaseEvents();if(this.eventMetadataEnhancers.length>0){let r={};for(let a of this.eventMetadataEnhancers)Object.assign(r,await a());for(let a of t){let o=a.metadata??{};a.metadata=Object.assign({},r,o)}}await this.store.persist(t);for(let r of t)await Promise.all(this.projectors.map(async a=>{try{await a.applyEvent(r)}catch(o){throw o instanceof Error&&console.error(`An error occurred in one of your projections: ${a.name}, given an event`,o.stack?.split(`
18
+ `).map(y=>` ${y}`).join(`
19
+ `)),o}})),await Promise.all(this.eventHandlers.map(async a=>{try{await a(r,this)}catch(o){throw o instanceof Error&&console.error(`An error occurred in one of your event handlers: ${a.name}, given an event`,o.stack?.split(`
20
+ `).map(y=>` ${y}`).join(`
21
+ `)),o}}))}async loadPersist(e,t,r){return await this.load(e,t),await r(e),this.persist(e)}},j=class{constructor(e){this.store=e}commandHandlers=new Map;projectors=[];eventHandlers=[];eventMetadataEnhancers=[];build(){return E.new(this.store,this.commandHandlers,this.projectors,this.eventHandlers,this.eventMetadataEnhancers)}addCommandHandler(e,t){return this.commandHandlers.has(e)&&c(`A command handler for the "${e}" command already exists`),this.commandHandlers.set(e,t),this}addProjector(e){return this.projectors.push(e),this}addEventHandler(e){return this.eventHandlers.push(e),this}metadata(e){return this.eventMetadataEnhancers.push(e),this}};function C(n){return(e,t)=>n[e.eventName]?.(e,t)}var S=Symbol("__placeholder__");function z(n,e){try{return n()}catch(t){throw Error.captureStackTrace&&t instanceof Error&&Error.captureStackTrace(t,e),t}}var N=class extends m{constructor(t=[],r=[]){super();this.db=t;this.producedEvents=r}apply={};name="test-recording-projector";initializer(){this.db.splice(0)}project(t){this.producedEvents.push(t)}};function U(n,e=[]){let t=new N,r=E.builder({load(s){return t.db.filter(i=>i.aggregateId===s)},loadEvents(){return t.db},persist(s){t.db.push(...s)}});for(let s of e)r.addProjector(s);r.addProjector(t);for(let[s,i]of Object.entries(n))r.addCommandHandler(s,i);let a=r.build(),o,y={___:S,async given(s=[]){t.db.push(...s)},async when(s){try{return await a.dispatch(typeof s=="function"?s():s)}catch(i){return i instanceof Error&&(o=i),i}},async then(s){if(s instanceof Error){let u=s;z(()=>{if(o?.message!==u?.message)throw new Error(`Expected error message to be:
25
22
 
26
23
  ${u.message}
27
24
 
28
25
  But got:
29
26
 
30
- ${o?.message}`)},w.then);return}let i=s;if(o)throw Object.keys(o).length>0&&(o.message=["With properties:",`
27
+ ${o?.message}`)},y.then);return}let i=s;if(o)throw Object.keys(o).length>0&&(o.message=["With properties:",`
31
28
  ${f(o)}
32
29
 
33
30
  ---
34
31
 
35
32
  `].join(`
36
- `)),o;D(()=>{if(i.length!==t.producedEvents.length)throw new Error(`Expected ${i.length} events, but got ${t.producedEvents.length} events.`);for(let[u,p]of i.entries()){let{aggregateId:H,eventName:O,payload:d}=t.producedEvents[u];if(p.aggregateId===N)throw new Error("Expected an `aggregateId`, but got `___` instead.");if(p.aggregateId!==H)throw new Error(`Expected aggregateId to be ${p.aggregateId}, but got ${H}.`);if(p.eventName!==O)throw new Error(`Expected eventName to be ${p.eventName}, but got ${O}.`);if((p.payload===null||p.payload===void 0)&&JSON.stringify(p.payload)!==JSON.stringify(d))throw new Error(`Expected payload to be ${JSON.stringify(p.payload)}, but got ${JSON.stringify(d)}.`);for(let l in p.payload){let P=p.payload[l];if(P===N){if(!(l in d))throw new Error(`Expected payload to have property ${l}, but it does not.`);if(d[l]===null||d[l]===void 0)throw new Error(`Expected payload to have property ${l}, but it is ${d[l]}.`)}else if(d[l]!==P)throw new Error(`Expected payload.${l} to be ${P}, but got ${d[l]}.`)}}},w.then)}};return w}0&&(module.exports={Aggregate,Command,Event,EventSource,Projector,abort,createEventMapper,createTestEventStore,objectToYaml});
33
+ `)),o;z(()=>{if(i.length!==t.producedEvents.length)throw new Error(`Expected ${i.length} events, but got ${t.producedEvents.length} events.`);for(let[u,p]of i.entries()){let{aggregateId:$,eventName:O,payload:l}=t.producedEvents[u];if(p.aggregateId===S)throw new Error("Expected an `aggregateId`, but got `___` instead.");if(p.aggregateId!==$)throw new Error(`Expected aggregateId to be ${p.aggregateId}, but got ${$}.`);if(p.eventName!==O)throw new Error(`Expected eventName to be ${p.eventName}, but got ${O}.`);if((p.payload===null||p.payload===void 0)&&JSON.stringify(p.payload)!==JSON.stringify(l))throw new Error(`Expected payload to be ${JSON.stringify(p.payload)}, but got ${JSON.stringify(l)}.`);for(let d in p.payload){let w=p.payload[d];if(w===S){if(!(d in l))throw new Error(`Expected payload to have property ${d}, but it does not.`);if(l[d]===null||l[d]===void 0)throw new Error(`Expected payload to have property ${d}, but it is ${l[d]}.`)}else if(l[d]!==w)throw new Error(`Expected payload.${d} to be ${w}, but got ${l[d]}.`)}}},y.then)}};return y}0&&(module.exports={Aggregate,Command,Event,EventSource,Projector,abort,createEventMapper,createTestEventStore,objectToYaml});
package/dist/index.d.cts CHANGED
@@ -7,20 +7,23 @@ interface EventType<T extends string = any, P = any, M = any> {
7
7
  recordedAt: Date;
8
8
  version: number;
9
9
  }
10
- declare function Event<const T extends string, P = null, M = null>(eventName: T, aggregateId: string, payload?: P, metadata?: M): EventType<T, P>;
10
+ declare function Event<const T extends string, P = null, M = null>(eventName: T, aggregateId: string, payload?: P, metadata?: M): EventType<T, P, M>;
11
11
 
12
- type ApplyConcreteEvents<Events extends EventType> = Events extends EventType<infer EventName, any> ? {
13
- [T in EventName]: (event: Extract<Events, {
12
+ type Merge$1<A, B> = B extends Record<string, any> ? A extends Record<string, any> ? Omit<A, keyof B> & B : B : A;
13
+ type ApplyConcreteEvents<Events extends EventType, M = unknown> = Events extends EventType<infer EventName, any, any> ? {
14
+ [T in EventName]: (event: EventType<T, Extract<Events, {
14
15
  eventName: T;
15
- }>) => void;
16
+ }>['payload'], Merge$1<M, Extract<Events, {
17
+ eventName: T;
18
+ }>['metadata']>>) => void;
16
19
  } : never;
17
20
  type Lazy$1<T> = (...args: any[]) => T;
18
- type ApplyLazyEvents<Events extends Lazy$1<EventType>> = ApplyConcreteEvents<ReturnType<Events>>;
21
+ type ApplyLazyEvents<Events extends Lazy$1<EventType>, M = unknown> = ApplyConcreteEvents<ReturnType<Events>, M>;
19
22
  type MaybeLazy$1<T> = T | Lazy$1<T>;
20
- type ApplyEvents<Events extends MaybeLazy$1<EventType> = any> = Events extends Lazy$1<EventType> ? ApplyLazyEvents<Events> : Events extends EventType ? ApplyConcreteEvents<Events> : never;
23
+ type ApplyEvents<Events extends MaybeLazy$1<EventType> = any, M = unknown> = Events extends Lazy$1<EventType> ? ApplyLazyEvents<Events, M> : Events extends EventType ? ApplyConcreteEvents<Events, M> : never;
21
24
  declare abstract class Aggregate {
22
25
  #private;
23
- abstract apply: ApplyEvents;
26
+ abstract apply: Record<string, (event: any) => void>;
24
27
  replayEvents(events?: EventType[]): this;
25
28
  private applyAnEvent;
26
29
  protected recordThat<T extends EventType>(event: T): this;
@@ -59,17 +62,20 @@ interface EventHandler {
59
62
  (event: EventType, es: EventSource): MaybePromise<unknown>;
60
63
  }
61
64
  interface MetadataEnhancer {
62
- (event: EventType): MaybePromise<JSON>;
65
+ (): MaybePromise<JSON>;
63
66
  }
64
- type ApplyConcreteProjectorEvents<Events extends EventType> = Events extends EventType<infer EventName, any> ? {
65
- [T in EventName]: (event: Extract<Events, {
67
+ type Merge<A, B> = B extends Record<string, any> ? A extends Record<string, any> ? Omit<A, keyof B> & B : B : A;
68
+ type ApplyConcreteProjectorEvents<Events extends EventType, M = unknown> = Events extends EventType<infer EventName, any, any> ? {
69
+ [T in EventName]: (event: EventType<T, Extract<Events, {
70
+ eventName: T;
71
+ }>['payload'], Merge<M, Extract<Events, {
66
72
  eventName: T;
67
- }>) => Promise<void>;
73
+ }>['metadata']>>) => Promise<void> | void;
68
74
  } : never;
69
75
  type Lazy<T> = (...args: any[]) => T;
70
- type ApplyLazyProjectorEvents<Events extends Lazy<EventType>> = ApplyConcreteProjectorEvents<ReturnType<Events>>;
76
+ type ApplyLazyProjectorEvents<Events extends Lazy<EventType>, M = unknown> = ApplyConcreteProjectorEvents<ReturnType<Events>, M>;
71
77
  type MaybeLazy<T> = T | Lazy<T>;
72
- type ApplyProjectorEvents<Events extends MaybeLazy<EventType> = any> = Events extends Lazy<EventType> ? ApplyLazyProjectorEvents<Events> : Events extends EventType ? ApplyConcreteProjectorEvents<Events> : never;
78
+ type ApplyProjectorEvents<Events extends MaybeLazy<EventType> = any, M = unknown> = Events extends Lazy<EventType> ? ApplyLazyProjectorEvents<Events, M> : Events extends EventType ? ApplyConcreteProjectorEvents<Events, M> : never;
73
79
  declare abstract class Projector<T extends ApplyProjectorEvents<any> = any> {
74
80
  #private;
75
81
  /**
@@ -87,7 +93,7 @@ declare abstract class Projector<T extends ApplyProjectorEvents<any> = any> {
87
93
  * date.
88
94
  */
89
95
  initializer?(): void | Promise<void>;
90
- init(es: EventSource): Promise<void>;
96
+ init(events: EventType[]): Promise<void>;
91
97
  applyEvent(event: EventType): Promise<void>;
92
98
  project(event: EventType): void | Promise<void>;
93
99
  }
package/dist/index.d.ts CHANGED
@@ -7,20 +7,23 @@ interface EventType<T extends string = any, P = any, M = any> {
7
7
  recordedAt: Date;
8
8
  version: number;
9
9
  }
10
- declare function Event<const T extends string, P = null, M = null>(eventName: T, aggregateId: string, payload?: P, metadata?: M): EventType<T, P>;
10
+ declare function Event<const T extends string, P = null, M = null>(eventName: T, aggregateId: string, payload?: P, metadata?: M): EventType<T, P, M>;
11
11
 
12
- type ApplyConcreteEvents<Events extends EventType> = Events extends EventType<infer EventName, any> ? {
13
- [T in EventName]: (event: Extract<Events, {
12
+ type Merge$1<A, B> = B extends Record<string, any> ? A extends Record<string, any> ? Omit<A, keyof B> & B : B : A;
13
+ type ApplyConcreteEvents<Events extends EventType, M = unknown> = Events extends EventType<infer EventName, any, any> ? {
14
+ [T in EventName]: (event: EventType<T, Extract<Events, {
14
15
  eventName: T;
15
- }>) => void;
16
+ }>['payload'], Merge$1<M, Extract<Events, {
17
+ eventName: T;
18
+ }>['metadata']>>) => void;
16
19
  } : never;
17
20
  type Lazy$1<T> = (...args: any[]) => T;
18
- type ApplyLazyEvents<Events extends Lazy$1<EventType>> = ApplyConcreteEvents<ReturnType<Events>>;
21
+ type ApplyLazyEvents<Events extends Lazy$1<EventType>, M = unknown> = ApplyConcreteEvents<ReturnType<Events>, M>;
19
22
  type MaybeLazy$1<T> = T | Lazy$1<T>;
20
- type ApplyEvents<Events extends MaybeLazy$1<EventType> = any> = Events extends Lazy$1<EventType> ? ApplyLazyEvents<Events> : Events extends EventType ? ApplyConcreteEvents<Events> : never;
23
+ type ApplyEvents<Events extends MaybeLazy$1<EventType> = any, M = unknown> = Events extends Lazy$1<EventType> ? ApplyLazyEvents<Events, M> : Events extends EventType ? ApplyConcreteEvents<Events, M> : never;
21
24
  declare abstract class Aggregate {
22
25
  #private;
23
- abstract apply: ApplyEvents;
26
+ abstract apply: Record<string, (event: any) => void>;
24
27
  replayEvents(events?: EventType[]): this;
25
28
  private applyAnEvent;
26
29
  protected recordThat<T extends EventType>(event: T): this;
@@ -59,17 +62,20 @@ interface EventHandler {
59
62
  (event: EventType, es: EventSource): MaybePromise<unknown>;
60
63
  }
61
64
  interface MetadataEnhancer {
62
- (event: EventType): MaybePromise<JSON>;
65
+ (): MaybePromise<JSON>;
63
66
  }
64
- type ApplyConcreteProjectorEvents<Events extends EventType> = Events extends EventType<infer EventName, any> ? {
65
- [T in EventName]: (event: Extract<Events, {
67
+ type Merge<A, B> = B extends Record<string, any> ? A extends Record<string, any> ? Omit<A, keyof B> & B : B : A;
68
+ type ApplyConcreteProjectorEvents<Events extends EventType, M = unknown> = Events extends EventType<infer EventName, any, any> ? {
69
+ [T in EventName]: (event: EventType<T, Extract<Events, {
70
+ eventName: T;
71
+ }>['payload'], Merge<M, Extract<Events, {
66
72
  eventName: T;
67
- }>) => Promise<void>;
73
+ }>['metadata']>>) => Promise<void> | void;
68
74
  } : never;
69
75
  type Lazy<T> = (...args: any[]) => T;
70
- type ApplyLazyProjectorEvents<Events extends Lazy<EventType>> = ApplyConcreteProjectorEvents<ReturnType<Events>>;
76
+ type ApplyLazyProjectorEvents<Events extends Lazy<EventType>, M = unknown> = ApplyConcreteProjectorEvents<ReturnType<Events>, M>;
71
77
  type MaybeLazy<T> = T | Lazy<T>;
72
- type ApplyProjectorEvents<Events extends MaybeLazy<EventType> = any> = Events extends Lazy<EventType> ? ApplyLazyProjectorEvents<Events> : Events extends EventType ? ApplyConcreteProjectorEvents<Events> : never;
78
+ type ApplyProjectorEvents<Events extends MaybeLazy<EventType> = any, M = unknown> = Events extends Lazy<EventType> ? ApplyLazyProjectorEvents<Events, M> : Events extends EventType ? ApplyConcreteProjectorEvents<Events, M> : never;
73
79
  declare abstract class Projector<T extends ApplyProjectorEvents<any> = any> {
74
80
  #private;
75
81
  /**
@@ -87,7 +93,7 @@ declare abstract class Projector<T extends ApplyProjectorEvents<any> = any> {
87
93
  * date.
88
94
  */
89
95
  initializer?(): void | Promise<void>;
90
- init(es: EventSource): Promise<void>;
96
+ init(events: EventType[]): Promise<void>;
91
97
  applyEvent(event: EventType): Promise<void>;
92
98
  project(event: EventType): void | Promise<void>;
93
99
  }
package/dist/index.js CHANGED
@@ -1,36 +1,33 @@
1
- function w(r,e){let t=Object.assign(new Error(r),e);return t.stack,Error.captureStackTrace&&Error.captureStackTrace(t,w),t}function d(r,e){let t=w(r,e);throw Error.captureStackTrace&&Error.captureStackTrace(t,d),t}function P(r){Object.freeze(r);for(let e of Object.getOwnPropertyNames(r))r.hasOwnProperty(e)&&r[e]!==null&&(typeof r[e]=="object"||typeof r[e]=="function")&&!Object.isFrozen(r[e])&&P(r[e]);return r}var C={NODE_ENV:process.env.NODE_ENV},H=class{#e=0;#t=[];replayEvents(e=[]){for(let t of e)this.applyAnEvent(t);return this}applyAnEvent(e){C.NODE_ENV==="test"&&P(e);let t=this.apply[e.eventName];t==null&&(e.eventName.match(/^[$A-Z_][0-9A-Z_$]*$/i)?d(`Aggregate "${this.constructor.name}" has no method:
1
+ function b(n,e){let t=Object.assign(new Error(n),e);return t.stack,Error.captureStackTrace&&Error.captureStackTrace(t,b),t}function l(n,e){let t=b(n,e);throw Error.captureStackTrace&&Error.captureStackTrace(t,l),t}function w(n){Object.freeze(n);for(let e of Object.getOwnPropertyNames(n))n.hasOwnProperty(e)&&n[e]!==null&&(typeof n[e]=="object"||typeof n[e]=="function")&&!Object.isFrozen(n[e])&&w(n[e]);return n}var H={NODE_ENV:typeof process<"u"?process.env?.NODE_ENV:void 0},$=class{#e=0;#t=[];replayEvents(e=[]){for(let t of e)this.applyAnEvent(t);return this}applyAnEvent(e){H.NODE_ENV==="test"&&w(e);let t=this.apply[e.eventName];return t==null&&(e.eventName.match(/^[$A-Z_][0-9A-Z_$]*$/i)?l(`Aggregate "${this.constructor.name}" has no method:
2
2
 
3
3
  apply = {
4
4
  ${e.eventName}(event) {
5
5
  // Code goes here...
6
6
  }
7
7
  // ...
8
- }`):d(`Aggregate "${this.constructor.name}" has no method:
8
+ }`):l(`Aggregate "${this.constructor.name}" has no method:
9
9
 
10
10
  apply = {
11
11
  ['${e.eventName}'](event) {
12
12
  // Code goes here...
13
13
  }
14
14
  // ...
15
- }`));try{t(e)}catch(n){n instanceof Error&&console.error(`An error occurred inside your "%s" function:
16
- `,e.eventName,n.stack?.split(`
17
- `).map(o=>` ${o}`).join(`
18
- `))}finally{this.#e++}return this}recordThat(e){let t={...e,version:this.#e};return this.applyAnEvent(t),this.#t.push(t),this}releaseEvents(){return this.#t.splice(0)}};function Y(r,e=null){return{type:r,payload:e}}import{randomUUID as z}from"node:crypto";function Z(r,e,t=null,n=null){return{aggregateId:e,eventId:z(),eventName:r,payload:t,metadata:n,recordedAt:new Date,version:-1}}import L from"yamlify-object";var k={indent:" ",colors:{date:v,error:v,symbol:v,string:v,number:v,boolean:v,null:v,undefined:v}};function v(r){return r}function j(r){return r instanceof Error?j({...r}):L(r,k).split(`
15
+ }`)),t(e),this.#e++,this}recordThat(e){let t={...e,version:this.#e};return this.applyAnEvent(t),this.#t.push(t),this}releaseEvents(){return this.#t.splice(0)}};function Q(n,e=null){return{type:n,payload:e}}function V(n,e,t=null,r=null){return{aggregateId:e,eventId:globalThis.crypto.randomUUID(),eventName:n,payload:t,metadata:r,recordedAt:new Date,version:-1}}import k from"yamlify-object";var C={indent:" ",colors:{date:v,error:v,symbol:v,string:v,number:v,boolean:v,null:v,undefined:v}};function v(n){return n}function M(n){return n instanceof Error?M({...n}):k(n,C).split(`
19
16
  `).slice(1).join(`
20
- `)}var u=new WeakMap;function h(r,e){if(u.has(r)){let t=u.get(r);for(let n in e)t[n]=e[n]}else u.set(r,e)}function f(r){return u.get(r)}var T=class{constructor(){h(this,{jobs:[],state:0})}get length(){return f(this).jobs.length}async start(){let{state:e,jobs:t}=f(this);if(!(e===1||t.length<=0)){for(h(this,{state:1});t.length>0;){let n=t.shift();await Promise.resolve().then(n.handle).then(n.resolve,n.reject)}h(this,{state:0})}}push(e){return new Promise((t,n)=>{let{jobs:o}=f(this);o.push({handle:e,resolve:t,reject:n}),setImmediate(()=>this.start())})}};var g=class{apply;initializer(){}#e=new T;async init(e){this.initializer&&await this.#e.push(()=>this.initializer?.());let t=await e.loadEvents();await Promise.all(t.map(n=>this.applyEvent(n)))}async applyEvent(e){await this.#e.push(()=>this.apply?.[e.eventName]?.(e)),await this.#e.push(()=>this.project(e))}project(e){}},E=class r{constructor(e,t,n,o,a){this.store=e;this.commandHandlers=t;this.projectors=n;this.eventHandlers=o;this.eventMetadataEnhancers=a}static builder(e){return new S(e)}static new(e,t,n,o,a){return new r(e,t,n,o,a)}async resetProjections(){await Promise.all(this.projectors.map(e=>e.init(this)))}async dispatch(e){return this.commandHandlers.has(e.type)||d(`There is no command handler for the "${e.type}" command`),await this.commandHandlers.get(e.type)(e,this),e}async loadEvents(){return this.store.loadEvents()}async load(e,t){let n=await this.store.load(t);return n.length<=0&&d(`Aggregate(${e.constructor.name}) with ID(${t}) does not exist.`,{aggregate:e.constructor.name,aggregateId:t}),e.replayEvents(n)}async persist(e){let t=e.releaseEvents();if(this.eventMetadataEnhancers.length>0)for(let n of t){let o=n.metadata;n.metadata={};for(let a of this.eventMetadataEnhancers)Object.assign(n.metadata,await a(n));typeof o=="object"&&o!==null&&Object.assign(n.metadata,o)}await this.store.persist(t);for(let n of t)await Promise.all(this.projectors.map(async o=>{try{await o.applyEvent(n)}catch(a){throw a instanceof Error&&console.error(`An error occurred in one of your projections: ${o.name}, given an event`,a.stack?.split(`
17
+ `)}var u=new WeakMap;function h(n,e){if(u.has(n)){let t=u.get(n);for(let r in e)t[r]=e[r]}else u.set(n,e)}function f(n){return u.get(n)}var T=class{constructor(){h(this,{jobs:[],state:0})}get length(){return f(this).jobs.length}async start(){let{state:e,jobs:t}=f(this);if(!(e===1||t.length<=0)){for(h(this,{state:1});t.length>0;){let r=t.shift();await Promise.resolve().then(r.handle).then(r.resolve,r.reject)}h(this,{state:0})}}push(e){return new Promise((t,r)=>{let{jobs:o}=f(this);o.push({handle:e,resolve:t,reject:r}),queueMicrotask(()=>this.start())})}};var g=class{apply;initializer(){}#e=new T;async init(e){this.initializer&&await this.#e.push(()=>this.initializer?.());for(let t of e)await this.applyEvent(t)}async applyEvent(e){await this.#e.push(()=>this.apply?.[e.eventName]?.(e)),await this.#e.push(()=>this.project(e))}project(e){}},E=class n{constructor(e,t,r,o,a){this.store=e;this.commandHandlers=t;this.projectors=r;this.eventHandlers=o;this.eventMetadataEnhancers=a}static builder(e){return new P(e)}static new(e,t,r,o,a){return new n(e,t,r,o,a)}async resetProjections(){let e=await this.store.loadEvents();await Promise.all(this.projectors.map(t=>t.init(e)))}async dispatch(e){return this.commandHandlers.has(e.type)||l(`There is no command handler for the "${e.type}" command`),await this.commandHandlers.get(e.type)(e,this),e}async loadEvents(){return this.store.loadEvents()}async load(e,t){let r=await this.store.load(t);return r.length<=0&&l(`Aggregate(${e.constructor.name}) with ID(${t}) does not exist.`,{aggregate:e.constructor.name,aggregateId:t}),e.replayEvents(r)}async persist(e){let t=e.releaseEvents();if(this.eventMetadataEnhancers.length>0){let r={};for(let o of this.eventMetadataEnhancers)Object.assign(r,await o());for(let o of t){let a=o.metadata??{};o.metadata=Object.assign({},r,a)}}await this.store.persist(t);for(let r of t)await Promise.all(this.projectors.map(async o=>{try{await o.applyEvent(r)}catch(a){throw a instanceof Error&&console.error(`An error occurred in one of your projections: ${o.name}, given an event`,a.stack?.split(`
21
18
  `).map(y=>` ${y}`).join(`
22
- `)),a}})),await Promise.all(this.eventHandlers.map(async o=>{try{await o(n,this)}catch(a){throw a instanceof Error&&console.error(`An error occurred in one of your event handlers: ${o.name}, given an event`,a.stack?.split(`
19
+ `)),a}})),await Promise.all(this.eventHandlers.map(async o=>{try{await o(r,this)}catch(a){throw a instanceof Error&&console.error(`An error occurred in one of your event handlers: ${o.name}, given an event`,a.stack?.split(`
23
20
  `).map(y=>` ${y}`).join(`
24
- `)),a}}))}async loadPersist(e,t,n){return await this.load(e,t),await n(e),this.persist(e)}},S=class{constructor(e){this.store=e}commandHandlers=new Map;projectors=[];eventHandlers=[];eventMetadataEnhancers=[];build(){return E.new(this.store,this.commandHandlers,this.projectors,this.eventHandlers,this.eventMetadataEnhancers)}addCommandHandler(e,t){return this.commandHandlers.has(e)&&d(`A command handler for the "${e}" command already exists`),this.commandHandlers.set(e,t),this}addProjector(e){return this.projectors.push(e),this}addEventHandler(e){return this.eventHandlers.push(e),this}metadata(e){return this.eventMetadataEnhancers.push(e),this}};function D(r){return(e,t)=>r[e.eventName]?.(e,t)}var A=Symbol("__placeholder__");function O(r,e){try{return r()}catch(t){throw Error.captureStackTrace&&t instanceof Error&&Error.captureStackTrace(t,e),t}}var M=class extends g{constructor(t=[],n=[]){super();this.db=t;this.producedEvents=n}apply={};name="test-recording-projector";initializer(){this.db.splice(0)}project(t){this.producedEvents.push(t)}};function pe(r,e=[]){let t=new M,n=E.builder({load(s){return t.db.filter(i=>i.aggregateId===s)},loadEvents(){return t.db},persist(s){t.db.push(...s)}});for(let s of e)n.addProjector(s);n.addProjector(t);for(let[s,i]of Object.entries(r))n.addCommandHandler(s,i);let o=n.build(),a,y=!1,b={___:A,async given(s=[]){t.db.push(...s)},async when(s){try{return await o.dispatch(typeof s=="function"?s():s)}catch(i){return i instanceof Error&&(a=i),i}},async then(s){if(y=!0,s instanceof Error){let m=s;O(()=>{if(a?.message!==m?.message)throw new Error(`Expected error message to be:
21
+ `)),a}}))}async loadPersist(e,t,r){return await this.load(e,t),await r(e),this.persist(e)}},P=class{constructor(e){this.store=e}commandHandlers=new Map;projectors=[];eventHandlers=[];eventMetadataEnhancers=[];build(){return E.new(this.store,this.commandHandlers,this.projectors,this.eventHandlers,this.eventMetadataEnhancers)}addCommandHandler(e,t){return this.commandHandlers.has(e)&&l(`A command handler for the "${e}" command already exists`),this.commandHandlers.set(e,t),this}addProjector(e){return this.projectors.push(e),this}addEventHandler(e){return this.eventHandlers.push(e),this}metadata(e){return this.eventMetadataEnhancers.push(e),this}};function z(n){return(e,t)=>n[e.eventName]?.(e,t)}var A=Symbol("__placeholder__");function O(n,e){try{return n()}catch(t){throw Error.captureStackTrace&&t instanceof Error&&Error.captureStackTrace(t,e),t}}var j=class extends g{constructor(t=[],r=[]){super();this.db=t;this.producedEvents=r}apply={};name="test-recording-projector";initializer(){this.db.splice(0)}project(t){this.producedEvents.push(t)}};function oe(n,e=[]){let t=new j,r=E.builder({load(s){return t.db.filter(i=>i.aggregateId===s)},loadEvents(){return t.db},persist(s){t.db.push(...s)}});for(let s of e)r.addProjector(s);r.addProjector(t);for(let[s,i]of Object.entries(n))r.addCommandHandler(s,i);let o=r.build(),a,y={___:A,async given(s=[]){t.db.push(...s)},async when(s){try{return await o.dispatch(typeof s=="function"?s():s)}catch(i){return i instanceof Error&&(a=i),i}},async then(s){if(s instanceof Error){let m=s;O(()=>{if(a?.message!==m?.message)throw new Error(`Expected error message to be:
25
22
 
26
23
  ${m.message}
27
24
 
28
25
  But got:
29
26
 
30
- ${a?.message}`)},b.then);return}let i=s;if(a)throw Object.keys(a).length>0&&(a.message=["With properties:",`
31
- ${j(a)}
27
+ ${a?.message}`)},y.then);return}let i=s;if(a)throw Object.keys(a).length>0&&(a.message=["With properties:",`
28
+ ${M(a)}
32
29
 
33
30
  ---
34
31
 
35
32
  `].join(`
36
- `)),a;O(()=>{if(i.length!==t.producedEvents.length)throw new Error(`Expected ${i.length} events, but got ${t.producedEvents.length} events.`);for(let[m,p]of i.entries()){let{aggregateId:N,eventName:$,payload:c}=t.producedEvents[m];if(p.aggregateId===A)throw new Error("Expected an `aggregateId`, but got `___` instead.");if(p.aggregateId!==N)throw new Error(`Expected aggregateId to be ${p.aggregateId}, but got ${N}.`);if(p.eventName!==$)throw new Error(`Expected eventName to be ${p.eventName}, but got ${$}.`);if((p.payload===null||p.payload===void 0)&&JSON.stringify(p.payload)!==JSON.stringify(c))throw new Error(`Expected payload to be ${JSON.stringify(p.payload)}, but got ${JSON.stringify(c)}.`);for(let l in p.payload){let x=p.payload[l];if(x===A){if(!(l in c))throw new Error(`Expected payload to have property ${l}, but it does not.`);if(c[l]===null||c[l]===void 0)throw new Error(`Expected payload to have property ${l}, but it is ${c[l]}.`)}else if(c[l]!==x)throw new Error(`Expected payload.${l} to be ${x}, but got ${c[l]}.`)}}},b.then)}};return b}export{H as Aggregate,Y as Command,Z as Event,E as EventSource,g as Projector,d as abort,D as createEventMapper,pe as createTestEventStore,j as objectToYaml};
33
+ `)),a;O(()=>{if(i.length!==t.producedEvents.length)throw new Error(`Expected ${i.length} events, but got ${t.producedEvents.length} events.`);for(let[m,p]of i.entries()){let{aggregateId:S,eventName:N,payload:c}=t.producedEvents[m];if(p.aggregateId===A)throw new Error("Expected an `aggregateId`, but got `___` instead.");if(p.aggregateId!==S)throw new Error(`Expected aggregateId to be ${p.aggregateId}, but got ${S}.`);if(p.eventName!==N)throw new Error(`Expected eventName to be ${p.eventName}, but got ${N}.`);if((p.payload===null||p.payload===void 0)&&JSON.stringify(p.payload)!==JSON.stringify(c))throw new Error(`Expected payload to be ${JSON.stringify(p.payload)}, but got ${JSON.stringify(c)}.`);for(let d in p.payload){let x=p.payload[d];if(x===A){if(!(d in c))throw new Error(`Expected payload to have property ${d}, but it does not.`);if(c[d]===null||c[d]===void 0)throw new Error(`Expected payload to have property ${d}, but it is ${c[d]}.`)}else if(c[d]!==x)throw new Error(`Expected payload.${d} to be ${x}, but got ${c[d]}.`)}}},y.then)}};return y}export{$ as Aggregate,Q as Command,V as Event,E as EventSource,g as Projector,l as abort,z as createEventMapper,oe as createTestEventStore,M as objectToYaml};
package/package.json CHANGED
@@ -1,6 +1,10 @@
1
1
  {
2
- "version": "0.0.15",
2
+ "version": "0.0.17",
3
3
  "name": "@robinmalfait/event-source",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/RobinMalfait/event-source.git"
7
+ },
4
8
  "publishConfig": {
5
9
  "access": "public"
6
10
  },
@@ -9,9 +13,21 @@
9
13
  "name": "Robin Malfait"
10
14
  },
11
15
  "type": "module",
12
- "main": "dist/index.js",
13
- "module": "dist/index.mjs",
16
+ "main": "dist/index.cjs",
17
+ "module": "dist/index.js",
14
18
  "typings": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.cts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ }
30
+ },
15
31
  "files": [
16
32
  "dist"
17
33
  ],