@robinmalfait/event-source 0.0.18 → 0.0.20

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
@@ -163,7 +163,10 @@ Projectors build read models from events. They process events sequentially and m
163
163
  This means projectors work well in serverless environments - projections are persisted to your database and don't need rebuilding on every cold start.
164
164
 
165
165
  ```typescript
166
- import { Projector, type ApplyEvents } from '@robinmalfait/event-source'
166
+ import {
167
+ Projector,
168
+ type ApplyProjectorEvents,
169
+ } from '@robinmalfait/event-source'
167
170
 
168
171
  class BalanceProjector extends Projector {
169
172
  name = 'balance-projector'
@@ -174,11 +177,11 @@ class BalanceProjector extends Projector {
174
177
  this.balances.clear()
175
178
  }
176
179
 
177
- apply: ApplyEvents<typeof accountOpened | typeof moneyDeposited> = {
178
- ACCOUNT_OPENED: (event) => {
180
+ apply: ApplyProjectorEvents<typeof accountOpened | typeof moneyDeposited> = {
181
+ ACCOUNT_OPENED: (event, es) => {
179
182
  this.balances.set(event.aggregateId, 0)
180
183
  },
181
- MONEY_DEPOSITED: (event) => {
184
+ MONEY_DEPOSITED: (event, es) => {
182
185
  let current = this.balances.get(event.aggregateId) ?? 0
183
186
  this.balances.set(event.aggregateId, current + event.payload.amount)
184
187
  },
@@ -190,6 +193,30 @@ class BalanceProjector extends Projector {
190
193
  }
191
194
  ```
192
195
 
196
+ #### Accessing Aggregate State in Projectors
197
+
198
+ Each `apply` handler receives the `EventSource` instance as the second argument. This allows projectors to reconstruct aggregate state when needed — useful when an event doesn't contain all the data required for the projection.
199
+
200
+ ```typescript
201
+ class TransactionHistoryProjector extends Projector {
202
+ name = 'transaction-history-projector'
203
+
204
+ apply: ApplyProjectorEvents<typeof accountClosed> = {
205
+ ACCOUNT_CLOSED: async (event, es) => {
206
+ // Reconstruct the aggregate to access its full state
207
+ let account = await es.load(new Account(), event.aggregateId)
208
+
209
+ await db.transactionHistory.insert({
210
+ accountId: event.aggregateId,
211
+ type: 'CLOSED',
212
+ finalBalance: account.balance, // Data not in the event
213
+ closedAt: event.recordedAt,
214
+ })
215
+ },
216
+ }
217
+ }
218
+ ```
219
+
193
220
  ## Testing
194
221
 
195
222
  The library provides BDD-style testing utilities with a `given/when/then` pattern:
@@ -248,6 +275,39 @@ if (balance < amount) {
248
275
 
249
276
  ### Type Utilities
250
277
 
278
+ #### ApplyEvents / ApplyProjectorEvents
279
+
280
+ Type-safe handlers for applying events to aggregates and projectors:
281
+
282
+ ```typescript
283
+ import type {
284
+ ApplyEvents,
285
+ ApplyProjectorEvents,
286
+ } from '@robinmalfait/event-source'
287
+
288
+ // For Aggregates - handlers receive only the event
289
+ apply: ApplyEvents<typeof accountOpened | typeof moneyDeposited> = {
290
+ ACCOUNT_OPENED: (event) => {
291
+ /* ... */
292
+ },
293
+ MONEY_DEPOSITED: (event) => {
294
+ /* ... */
295
+ },
296
+ }
297
+
298
+ // For Projectors - handlers receive the event and EventSource
299
+ apply: ApplyProjectorEvents<typeof accountOpened | typeof moneyDeposited> = {
300
+ ACCOUNT_OPENED: (event, es) => {
301
+ /* ... */
302
+ },
303
+ MONEY_DEPOSITED: (event, es) => {
304
+ /* ... */
305
+ },
306
+ }
307
+ ```
308
+
309
+ #### PayloadOf / TypeOf
310
+
251
311
  Extract types from events and commands:
252
312
 
253
313
  ```typescript
package/dist/index.cjs CHANGED
@@ -14,11 +14,11 @@ apply = {
14
14
  // ...
15
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(r,e=null){return{type:r,payload:e}}function Y(r,e,t=null,n=null){return{aggregateId:e,eventId:globalThis.crypto.randomUUID(),eventName:r,payload:t,metadata:n,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(r){return r}function f(r){return r instanceof Error?f({...r}):(0,k.default)(r,q).split(`
16
16
  `).slice(1).join(`
17
- `)}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 x(r){return T.get(r)}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 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}=x(this);a.push({handle:e,resolve:t,reject:n}),queueMicrotask(()=>this.start())})}};var m=class{apply;reset(){}#e=new b;async init(e){this.reset&&await this.#e.push(()=>this.reset?.());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 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 j(e)}static new(e,t,n,a,o){return new r(e,t,n,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 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){let n={};for(let a of this.eventMetadataEnhancers)Object.assign(n,await a());for(let a of t){let o=a.metadata??{};a.metadata=Object.assign({},n,o)}}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(`
17
+ `)}var T=new WeakMap;function g(r,e){if(T.has(r)){let t=T.get(r);for(let n of Object.keys(e))t[n]=e[n]}else T.set(r,e)}function x(r){return T.get(r)}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 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}=x(this);a.push({handle:e,resolve:t,reject:n}),queueMicrotask(()=>this.start())})}};var m=class{apply;reset(){}#e=new b;async init(e,t){this.reset&&await this.#e.push(()=>this.reset?.());for(let n of e)await this.applyEvent(n,t)}async applyEvent(e,t){await this.#e.push(()=>this.apply?.[e.eventName]?.(e,t)),await this.#e.push(()=>this.project(e,t))}project(e,t){}},E=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 S(e)}static new(e,t,n,a,o){return new r(e,t,n,a,o)}async resetProjections(){let e=await this.store.loadEvents();await Promise.all(this.projectors.map(t=>t.init(e,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){let n={};for(let a of this.eventMetadataEnhancers)Object.assign(n,await a());for(let a of t){let o=a.metadata??{};a.metadata=Object.assign({},n,o)}}await this.store.persist(t);for(let n of t)await Promise.all(this.projectors.map(async a=>{try{await a.applyEvent(n,this)}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
18
  `).map(y=>` ${y}`).join(`
19
19
  `)),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(`
20
20
  `).map(y=>` ${y}`).join(`
21
- `)),o}}))}async loadPersist(e,t,n){return await this.load(e,t),await n(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(r){return(e,t)=>r[e.eventName]?.(e,t)}var S=Symbol("__placeholder__");function L(r,e){try{return r()}catch(t){throw Error.captureStackTrace&&t instanceof Error&&Error.captureStackTrace(t,e),t}}var N=class extends m{constructor(t=[],n=[]){super();this.db=t;this.producedEvents=n}apply={};name="test-recording-projector";reset(){this.db.splice(0)}project(t){this.producedEvents.push(t)}};function U(r,e=[]){let t=new N,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 a=n.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;L(()=>{if(o?.message!==u?.message)throw new Error(`Expected error message to be:
21
+ `)),o}}))}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)&&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(r){return(e,t)=>r[e.eventName]?.(e,t)}var j=Symbol("__placeholder__");function L(r,e){try{return r()}catch(t){throw Error.captureStackTrace&&t instanceof Error&&Error.captureStackTrace(t,e),t}}var N=class extends m{constructor(t=[],n=[]){super();this.db=t;this.producedEvents=n}apply={};name="test-recording-projector";reset(){this.db.splice(0)}project(t){this.producedEvents.push(t)}};function U(r,e=[]){let t=new N,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 a=n.build(),o,y={___:j,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;L(()=>{if(o?.message!==u?.message)throw new Error(`Expected error message to be:
22
22
 
23
23
  ${u.message}
24
24
 
@@ -30,4 +30,4 @@ ${f(o)}
30
30
  ---
31
31
 
32
32
  `].join(`
33
- `)),o;L(()=>{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});
33
+ `)),o;L(()=>{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===j)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===j){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
@@ -70,7 +70,7 @@ type ApplyConcreteProjectorEvents<Events extends EventType, M = unknown> = Event
70
70
  eventName: T;
71
71
  }>['payload'], Merge<M, Extract<Events, {
72
72
  eventName: T;
73
- }>['metadata']>>) => Promise<void> | void;
73
+ }>['metadata']>>, es: EventSource) => Promise<void> | void;
74
74
  } : never;
75
75
  type Lazy<T> = (...args: any[]) => T;
76
76
  type ApplyLazyProjectorEvents<Events extends Lazy<EventType>, M = unknown> = ApplyConcreteProjectorEvents<ReturnType<Events>, M>;
@@ -93,9 +93,9 @@ declare abstract class Projector<T extends ApplyProjectorEvents<any> = any> {
93
93
  * clear any existing projection data (e.g., truncate tables).
94
94
  */
95
95
  reset?(): void | Promise<void>;
96
- init(events: EventType[]): Promise<void>;
97
- applyEvent(event: EventType): Promise<void>;
98
- project(event: EventType): void | Promise<void>;
96
+ init(events: EventType[], es: EventSource): Promise<void>;
97
+ applyEvent(event: EventType, es: EventSource): Promise<void>;
98
+ project(event: EventType, es: EventSource): void | Promise<void>;
99
99
  }
100
100
  interface CommandHandler {
101
101
  (command: CommandType, es: EventSource): MaybePromise<void>;
@@ -140,4 +140,4 @@ declare function createTestEventStore(commandHandlers: Record<string, CommandHan
140
140
  then(expectation: EventType[] | Error): Promise<void>;
141
141
  };
142
142
 
143
- export { Aggregate, type ApplyEvents, Command, type CommandHandler, type CommandType, Event, type EventHandler, EventSource, type EventStore, type EventType, type MetadataEnhancer, type PayloadOf, Projector, type TypeOf, abort, createEventMapper, createTestEventStore, objectToYaml };
143
+ export { Aggregate, type ApplyEvents, type ApplyProjectorEvents, Command, type CommandHandler, type CommandType, Event, type EventHandler, EventSource, type EventStore, type EventType, type MetadataEnhancer, type PayloadOf, Projector, type TypeOf, abort, createEventMapper, createTestEventStore, objectToYaml };
package/dist/index.d.ts CHANGED
@@ -70,7 +70,7 @@ type ApplyConcreteProjectorEvents<Events extends EventType, M = unknown> = Event
70
70
  eventName: T;
71
71
  }>['payload'], Merge<M, Extract<Events, {
72
72
  eventName: T;
73
- }>['metadata']>>) => Promise<void> | void;
73
+ }>['metadata']>>, es: EventSource) => Promise<void> | void;
74
74
  } : never;
75
75
  type Lazy<T> = (...args: any[]) => T;
76
76
  type ApplyLazyProjectorEvents<Events extends Lazy<EventType>, M = unknown> = ApplyConcreteProjectorEvents<ReturnType<Events>, M>;
@@ -93,9 +93,9 @@ declare abstract class Projector<T extends ApplyProjectorEvents<any> = any> {
93
93
  * clear any existing projection data (e.g., truncate tables).
94
94
  */
95
95
  reset?(): void | Promise<void>;
96
- init(events: EventType[]): Promise<void>;
97
- applyEvent(event: EventType): Promise<void>;
98
- project(event: EventType): void | Promise<void>;
96
+ init(events: EventType[], es: EventSource): Promise<void>;
97
+ applyEvent(event: EventType, es: EventSource): Promise<void>;
98
+ project(event: EventType, es: EventSource): void | Promise<void>;
99
99
  }
100
100
  interface CommandHandler {
101
101
  (command: CommandType, es: EventSource): MaybePromise<void>;
@@ -140,4 +140,4 @@ declare function createTestEventStore(commandHandlers: Record<string, CommandHan
140
140
  then(expectation: EventType[] | Error): Promise<void>;
141
141
  };
142
142
 
143
- export { Aggregate, type ApplyEvents, Command, type CommandHandler, type CommandType, Event, type EventHandler, EventSource, type EventStore, type EventType, type MetadataEnhancer, type PayloadOf, Projector, type TypeOf, abort, createEventMapper, createTestEventStore, objectToYaml };
143
+ export { Aggregate, type ApplyEvents, type ApplyProjectorEvents, Command, type CommandHandler, type CommandType, Event, type EventHandler, EventSource, type EventStore, type EventType, type MetadataEnhancer, type PayloadOf, Projector, type TypeOf, abort, createEventMapper, createTestEventStore, objectToYaml };
package/dist/index.js CHANGED
@@ -14,11 +14,11 @@ apply = {
14
14
  // ...
15
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(r,e=null){return{type:r,payload:e}}function V(r,e,t=null,n=null){return{aggregateId:e,eventId:globalThis.crypto.randomUUID(),eventName:r,payload:t,metadata:n,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(r){return r}function M(r){return r instanceof Error?M({...r}):k(r,C).split(`
16
16
  `).slice(1).join(`
17
- `)}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}),queueMicrotask(()=>this.start())})}};var g=class{apply;reset(){}#e=new T;async init(e){this.reset&&await this.#e.push(()=>this.reset?.());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 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 P(e)}static new(e,t,n,o,a){return new r(e,t,n,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 n=await this.store.load(t);return n.length<=0&&l(`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){let n={};for(let o of this.eventMetadataEnhancers)Object.assign(n,await o());for(let o of t){let a=o.metadata??{};o.metadata=Object.assign({},n,a)}}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(r,e){if(u.has(r)){let t=u.get(r);for(let n of Object.keys(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}),queueMicrotask(()=>this.start())})}};var g=class{apply;reset(){}#e=new T;async init(e,t){this.reset&&await this.#e.push(()=>this.reset?.());for(let n of e)await this.applyEvent(n,t)}async applyEvent(e,t){await this.#e.push(()=>this.apply?.[e.eventName]?.(e,t)),await this.#e.push(()=>this.project(e,t))}project(e,t){}},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 P(e)}static new(e,t,n,o,a){return new r(e,t,n,o,a)}async resetProjections(){let e=await this.store.loadEvents();await Promise.all(this.projectors.map(t=>t.init(e,this)))}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 n=await this.store.load(t);return n.length<=0&&l(`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){let n={};for(let o of this.eventMetadataEnhancers)Object.assign(n,await o());for(let o of t){let a=o.metadata??{};o.metadata=Object.assign({},n,a)}}await this.store.persist(t);for(let n of t)await Promise.all(this.projectors.map(async o=>{try{await o.applyEvent(n,this)}catch(a){throw a instanceof Error&&console.error(`An error occurred in one of your projections: ${o.name}, given an event`,a.stack?.split(`
18
18
  `).map(y=>` ${y}`).join(`
19
19
  `)),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(`
20
20
  `).map(y=>` ${y}`).join(`
21
- `)),a}}))}async loadPersist(e,t,n){return await this.load(e,t),await n(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 L(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 j=class extends g{constructor(t=[],n=[]){super();this.db=t;this.producedEvents=n}apply={};name="test-recording-projector";reset(){this.db.splice(0)}project(t){this.producedEvents.push(t)}};function oe(r,e=[]){let t=new j,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={___: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:
21
+ `)),a}}))}async loadPersist(e,t,n){return await this.load(e,t),await n(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 L(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 S=class extends g{constructor(t=[],n=[]){super();this.db=t;this.producedEvents=n}apply={};name="test-recording-projector";reset(){this.db.splice(0)}project(t){this.producedEvents.push(t)}};function oe(r,e=[]){let t=new S,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={___: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:
22
22
 
23
23
  ${m.message}
24
24
 
@@ -30,4 +30,4 @@ ${M(a)}
30
30
  ---
31
31
 
32
32
  `].join(`
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,L as createEventMapper,oe as createTestEventStore,M 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:j,eventName:N,payload:c}=t.producedEvents[m];if(p.aggregateId===A)throw new Error("Expected an `aggregateId`, but got `___` instead.");if(p.aggregateId!==j)throw new Error(`Expected aggregateId to be ${p.aggregateId}, but got ${j}.`);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,L as createEventMapper,oe as createTestEventStore,M as objectToYaml};
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.18",
2
+ "version": "0.0.20",
3
3
  "name": "@robinmalfait/event-source",
4
4
  "repository": {
5
5
  "type": "git",