@robinmalfait/event-source 0.0.18 → 0.0.19
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 +26 -2
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -175,10 +175,10 @@ class BalanceProjector extends Projector {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
apply: ApplyEvents<typeof accountOpened | typeof moneyDeposited> = {
|
|
178
|
-
ACCOUNT_OPENED: (event) => {
|
|
178
|
+
ACCOUNT_OPENED: (event, es) => {
|
|
179
179
|
this.balances.set(event.aggregateId, 0)
|
|
180
180
|
},
|
|
181
|
-
MONEY_DEPOSITED: (event) => {
|
|
181
|
+
MONEY_DEPOSITED: (event, es) => {
|
|
182
182
|
let current = this.balances.get(event.aggregateId) ?? 0
|
|
183
183
|
this.balances.set(event.aggregateId, current + event.payload.amount)
|
|
184
184
|
},
|
|
@@ -190,6 +190,30 @@ class BalanceProjector extends Projector {
|
|
|
190
190
|
}
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
+
#### Accessing Aggregate State in Projectors
|
|
194
|
+
|
|
195
|
+
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.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
class TransactionHistoryProjector extends Projector {
|
|
199
|
+
name = 'transaction-history-projector'
|
|
200
|
+
|
|
201
|
+
apply: ApplyEvents<typeof accountClosed> = {
|
|
202
|
+
ACCOUNT_CLOSED: async (event, es) => {
|
|
203
|
+
// Reconstruct the aggregate to access its full state
|
|
204
|
+
let account = await es.load(new Account(), event.aggregateId)
|
|
205
|
+
|
|
206
|
+
await db.transactionHistory.insert({
|
|
207
|
+
accountId: event.aggregateId,
|
|
208
|
+
type: 'CLOSED',
|
|
209
|
+
finalBalance: account.balance, // Data not in the event
|
|
210
|
+
closedAt: event.recordedAt,
|
|
211
|
+
})
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
193
217
|
## Testing
|
|
194
218
|
|
|
195
219
|
The library provides BDD-style testing utilities with a `given/when/then` pattern:
|
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
|
|
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)}},
|
|
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===
|
|
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']
|
|
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>;
|
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']
|
|
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>;
|
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
|
|
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
|
|
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:
|
|
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