@vytches/ddd-aggregates 0.31.0-alpha.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LLMGUIDE.md +15 -14
- package/dist/core/aggregate-root.d.ts +79 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +221 -149
- package/package.json +5 -5
package/LLMGUIDE.md
CHANGED
|
@@ -41,7 +41,8 @@ class Order extends AggregateRoot<string> {
|
|
|
41
41
|
// Usage
|
|
42
42
|
const order = Order.create('c-1', 500);
|
|
43
43
|
const events = order.getDomainEvents(); // [{ eventName: 'OrderCreated', ... }]
|
|
44
|
-
order.
|
|
44
|
+
order.getDomainEvents() === events; // true — same array reference until the next mutation
|
|
45
|
+
order.commit(); // clear pending events after saving; getDomainEvents() now returns a new, empty array
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
## Key API
|
|
@@ -82,19 +83,19 @@ order.commit(); // clear pending events after saving
|
|
|
82
83
|
|
|
83
84
|
### `AggregateRoot` method reference
|
|
84
85
|
|
|
85
|
-
| Method | Description
|
|
86
|
-
| --------------------------------------- |
|
|
87
|
-
| `apply(eventType, payload?, metadata?)` | Record a domain event, increment version, call handler
|
|
88
|
-
| `loadFromHistory(events)` | Replay historical events to restore state without accumulating new events
|
|
89
|
-
| `getDomainEvents()` | Return readonly array of uncommitted events since last `commit()`
|
|
90
|
-
| `commit()` | Clear uncommitted events; call after persisting to storage
|
|
91
|
-
| `hasChanges()` | `true` if there are uncommitted events
|
|
92
|
-
| `getVersion()` | Current version after all applied events
|
|
93
|
-
| `getInitialVersion()` | Version when last loaded from storage (for optimistic locking)
|
|
94
|
-
| `addCapability(cap)` | Attach a capability; calls `cap.attach(this)`
|
|
95
|
-
| `getCapability(CapClass)` | Retrieve capability by constructor, returns `undefined` if absent
|
|
96
|
-
| `hasCapability(CapClass)` | Type-safe capability presence check
|
|
97
|
-
| `registerEventHandler(type, fn)` | Protected; wire event name to state mutation function
|
|
86
|
+
| Method | Description |
|
|
87
|
+
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
88
|
+
| `apply(eventType, payload?, metadata?)` | Record a domain event, increment version, call handler |
|
|
89
|
+
| `loadFromHistory(events)` | Replay historical events to restore state without accumulating new events |
|
|
90
|
+
| `getDomainEvents()` | Return readonly, deep-frozen array of uncommitted events since last `commit()`. Same array reference on repeat calls — rebuilt only on the next mutation (`apply`/`commit`/`loadFromHistory`/`transformDomainEvents`) |
|
|
91
|
+
| `commit()` | Clear uncommitted events; call after persisting to storage |
|
|
92
|
+
| `hasChanges()` | `true` if there are uncommitted events |
|
|
93
|
+
| `getVersion()` | Current version after all applied events |
|
|
94
|
+
| `getInitialVersion()` | Version when last loaded from storage (for optimistic locking) |
|
|
95
|
+
| `addCapability(cap)` | Attach a capability; calls `cap.attach(this)` |
|
|
96
|
+
| `getCapability(CapClass)` | Retrieve capability by constructor, returns `undefined` if absent |
|
|
97
|
+
| `hasCapability(CapClass)` | Type-safe capability presence check |
|
|
98
|
+
| `registerEventHandler(type, fn)` | Protected; wire event name to state mutation function |
|
|
98
99
|
|
|
99
100
|
## Patterns
|
|
100
101
|
|
|
@@ -119,6 +119,20 @@ export declare class AggregateRoot<TId = string> implements IAggregateRoot<TId>
|
|
|
119
119
|
private _version;
|
|
120
120
|
private _initialVersion;
|
|
121
121
|
private _domainEvents;
|
|
122
|
+
/**
|
|
123
|
+
* VP-012b (D2, F15/F5): memoized, deep-frozen view returned by
|
|
124
|
+
* {@link getDomainEvents}. Rebuilt lazily on next read only when
|
|
125
|
+
* `_domainEventsCacheDirty` is true — see `_invalidateDomainEventsCache()`.
|
|
126
|
+
*/
|
|
127
|
+
private _domainEventsCache;
|
|
128
|
+
/**
|
|
129
|
+
* VP-012b (D2): dirty flag for `_domainEventsCache`. Set true by
|
|
130
|
+
* `_invalidateDomainEventsCache()`, the single helper called from every
|
|
131
|
+
* site that mutates `_domainEvents` (apply, commit, transformDomainEvents,
|
|
132
|
+
* loadFromHistory, _internal_setState). Starts true so the first
|
|
133
|
+
* `getDomainEvents()` call always builds the cache.
|
|
134
|
+
*/
|
|
135
|
+
private _domainEventsCacheDirty;
|
|
122
136
|
private _eventHandlers;
|
|
123
137
|
private _capabilities;
|
|
124
138
|
/**
|
|
@@ -191,13 +205,78 @@ export declare class AggregateRoot<TId = string> implements IAggregateRoot<TId>
|
|
|
191
205
|
* data) inside it could still be mutated in place, silently corrupting
|
|
192
206
|
* state shared with `_domainEvents` (the same object references are
|
|
193
207
|
* stored internally — events are never re-cloned after creation).
|
|
208
|
+
*
|
|
209
|
+
* VP-012b (D2, BREAKING behavior change, non-breaking signature): the
|
|
210
|
+
* returned array reference is now **stable between calls** — the same
|
|
211
|
+
* frozen array object is returned on every call until the next mutation
|
|
212
|
+
* (`apply()`, `commit()`, `loadFromHistory()`, or
|
|
213
|
+
* `transformDomainEvents()`), instead of a fresh array being built on
|
|
214
|
+
* every call as before. Content equality (`toEqual`) is unaffected either
|
|
215
|
+
* way. Code that relies on *identity* — e.g. asserting `not.toBe`/`!==`
|
|
216
|
+
* between two `getDomainEvents()` calls to prove "each call returns a new
|
|
217
|
+
* array" — will observe the new stable-reference behavior and must be
|
|
218
|
+
* updated; asserting `.toBe`/`===` between two calls made with no
|
|
219
|
+
* intervening mutation now holds, where it previously did not. This is
|
|
220
|
+
* safe to alias: the returned array and its contents are frozen, so
|
|
221
|
+
* holding onto the reference across a mutation still reflects the state
|
|
222
|
+
* as of the call that produced it (the cache is not that same array
|
|
223
|
+
* anymore after invalidation — a stored old reference is simply stale
|
|
224
|
+
* data, never mutated out from under the caller).
|
|
194
225
|
* @returns {ReadonlyArray<IDomainEvent>} Array of uncommitted domain events
|
|
195
226
|
*/
|
|
196
227
|
getDomainEvents(): ReadonlyArray<IDomainEvent>;
|
|
228
|
+
/**
|
|
229
|
+
* VP-012b (D2): single invalidation point for `_domainEventsCache`,
|
|
230
|
+
* called from every site that mutates `_domainEvents` — `apply()`,
|
|
231
|
+
* `commit()`, `transformDomainEvents()`, `loadFromHistory()`, and
|
|
232
|
+
* `_internal_setState()`. Keeping invalidation in one helper (rather than
|
|
233
|
+
* setting the flag ad hoc at each call site) means a future mutation site
|
|
234
|
+
* cannot silently forget to invalidate.
|
|
235
|
+
*/
|
|
236
|
+
private _invalidateDomainEventsCache;
|
|
197
237
|
/**
|
|
198
238
|
* Clear all uncommitted domain events and update initial version
|
|
199
239
|
*/
|
|
200
240
|
commit(): void;
|
|
241
|
+
/**
|
|
242
|
+
* Rewrite the payload and/or metadata of the uncommitted domain events.
|
|
243
|
+
*
|
|
244
|
+
* This is an infrastructure boundary API, in the same family as `commit()`:
|
|
245
|
+
* call it from a repository immediately before persisting, never from the
|
|
246
|
+
* domain or application layer. An aggregate records events through `apply()`;
|
|
247
|
+
* it does not rewrite its own history.
|
|
248
|
+
*
|
|
249
|
+
* It exists for data that only the persistence boundary knows — a
|
|
250
|
+
* crypto-shredding key id resolved at save time, a correlation id assigned at
|
|
251
|
+
* dispatch, an encrypted payload. Doing this by hand from outside is not
|
|
252
|
+
* possible: `getDomainEvents()` deep-freezes what it returns, and the
|
|
253
|
+
* internal list is private. The transformation has to land here rather than
|
|
254
|
+
* on a local copy inside `save()`, because the event dispatcher reads the
|
|
255
|
+
* aggregate's events again — a local copy would leave the in-process event
|
|
256
|
+
* bus publishing the untransformed originals.
|
|
257
|
+
*
|
|
258
|
+
* Only `payload` and `metadata` can be replaced. `eventName`, identity, and
|
|
259
|
+
* the number and order of events are fixed: changing those would desync
|
|
260
|
+
* handlers on replay or break the version invariant. Return nothing to leave
|
|
261
|
+
* an event as it is.
|
|
262
|
+
*
|
|
263
|
+
* Identity, prototype and `instanceof` survive, and the event's constructor
|
|
264
|
+
* is never called, so event classes with their own constructor arguments are
|
|
265
|
+
* safe.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* // inside a repository's save(), before persisting:
|
|
269
|
+
* order.transformDomainEvents(event => ({
|
|
270
|
+
* payload: encrypt(event.payload, key),
|
|
271
|
+
* metadata: { userSpecificKeyId: key.id },
|
|
272
|
+
* }));
|
|
273
|
+
*
|
|
274
|
+
* @param transform - Called per event; returns the parts to replace, or nothing to keep it
|
|
275
|
+
*/
|
|
276
|
+
transformDomainEvents<P = unknown>(transform: (event: Readonly<IDomainEvent<P>>, index: number) => {
|
|
277
|
+
payload?: P;
|
|
278
|
+
metadata?: Partial<IEventMetadata>;
|
|
279
|
+
} | void): void;
|
|
201
280
|
/**
|
|
202
281
|
* @param {string} eventType - Event type to register handler for
|
|
203
282
|
* @param {IAggregateEventHandler<T>} handler - Event handler function
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("@vytches/ddd-contracts"),S=require("@vytches/ddd-contracts/internal"),h=require("@vytches/ddd-utils"),o=require("@vytches/ddd-domain-primitives");class p extends u.Capability{constructor(){super(...arguments),this.type="versioning",this.upcasters=new Map}static get capabilityType(){return"versioning"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this.upcasters.clear()}registerUpcaster(t,e,i){this.upcasters.has(t)||this.upcasters.set(t,new Map),this.upcasters.get(t).set(e,i)}handleVersionedEvent(t,e){const i=t.metadata?.version||1,a=t.metadata?.targetVersion||i;let n=t;if(i<a&&this.upcasters.has(t.eventName)){const l=this.upcasters.get(t.eventName);for(let c=i;c<a;c++){const E=l.get(c);if(!E){S.internalLogger.warn("VersioningCapability: Missing upcaster for event version — event will be processed without upcast",{eventName:t.eventName,fromVersion:c,toVersion:c+1});continue}const I=E.upcast(n.payload,n.metadata);n={...n,payload:I,metadata:{...n.metadata,version:c+1}}}}const g=e.get(n.eventName);g&&g(n.payload,n.metadata)}getRegisteredEventTypes(){return Array.from(this.upcasters.keys())}hasUpcaster(t,e){return this.upcasters.get(t)?.has(e)||!1}getUpcastersForType(t){return this.upcasters.get(t)}clearUpcastersForType(t){this.upcasters.delete(t)}getTotalUpcasterCount(){let t=0;for(const e of this.upcasters.values())t+=e.size;return t}}const w=Symbol("vytches-ddd-aggregate-internal-state");class d{constructor(t){this._version=0,this._initialVersion=0,this._domainEvents=[],this._eventHandlers=new Map,this._capabilities=new u.CapabilityRegistry,this._id=t.id,this._version=t.version||0,this._initialVersion=t.version||0,this._maxEvents=t.maxEvents,this._onMissingHandler=t.onMissingHandler??"warn"}getId(){return this._id}equals(t){return t==null?!1:t===this?!0:t instanceof d?this._id.equals(t._id):!1}getVersion(){return this._version}getInitialVersion(){return this._initialVersion}hasChanges(){return this._domainEvents.length>0}getDomainEvents(){const t=this._domainEvents.map(e=>h.LibUtils.deepFreeze(e));return Object.freeze(t)}commit(){this._domainEvents=[],this._initialVersion=this._version}registerEventHandler(t,e){return this._eventHandlers.set(t,e),this}apply(t,e,i){if(this._maxEvents!==void 0&&this._domainEvents.length>=this._maxEvents)throw new Error(`Aggregate ${this.constructor.name} (id=${this._id.toString()}) exceeded maxEvents limit of ${this._maxEvents}. This usually indicates a runaway loop or replay of a corrupted event stream. Increase maxEvents in the aggregate constructor params if the limit is too restrictive for your domain.`);const a=this._version+1;let n,g;typeof t=="string"?(n=u.createDomainEvent(t,e,i),g=n.metadata):(n=t,g=i?{...n.metadata,...i}:n.metadata);const l=d.sanitizeMetadata({...g,aggregateId:this._id.toString(),aggregateType:this.constructor.name,aggregateVersion:a,timestamp:g?.timestamp??new Date}),c=Object.assign(Object.create(Object.getPrototypeOf(n)),n,{metadata:l});this._version=a,this._domainEvents.push(c),this.handleEvent(c)}handleEvent(t){const e=this.getCapability(p);if(e)e.handleVersionedEvent(t,this._eventHandlers);else{const i=this._eventHandlers.get(t.eventName);i?i(t.payload,t.metadata):this.reportMissingHandler(t.eventName)}}reportMissingHandler(t){const e=`Aggregate ${this.constructor.name} (id=${this._id.toString()}) has no registered handler for event "${t}". State will not be updated for this event. Register a handler via registerEventHandler('${t}', ...) before applying/replaying this event, or pass onMissingHandler: "warn" explicitly to silence this in the constructor params.`;if(this._onMissingHandler==="throw")throw new Error(e);S.internalLogger.warn(e,{aggregateType:this.constructor.name,aggregateId:this._id.toString(),eventName:t})}loadFromHistory(t){this._version=this._initialVersion,this._domainEvents=[];for(const e of t)this.handleEvent(e),this._version++;this._initialVersion=this._version}addCapability(t){return"attach"in t&&typeof t.attach=="function"&&t.attach(this),this._capabilities.register(t),this}getCapability(t){return this._capabilities.get(t)}hasCapability(t){return this._capabilities.has(t)}removeCapability(t){const e=this._capabilities.get(t);return e&&"detach"in e&&typeof e.detach=="function"&&e.detach(),this._capabilities.remove(t),this}getAllCapabilities(){return this._capabilities.getAll()}getCapabilityTypes(){return this._capabilities.getTypes()}static sanitizeMetadata(t){if(!Object.prototype.hasOwnProperty.call(t,"__proto__")&&!Object.prototype.hasOwnProperty.call(t,"constructor")&&!Object.prototype.hasOwnProperty.call(t,"prototype"))return t;const e={};for(const i of Object.keys(t))i==="__proto__"||i==="constructor"||i==="prototype"||(e[i]=t[i]);return e}_internal_setState(t,e){if(t!==w)throw new TypeError("_internal_setState: invalid or missing internal state token. This method is restricted to capabilities within @vytches/ddd-aggregates.");this._version=e.version,this._initialVersion=e.initialVersion,this._domainEvents=[...e.domainEvents]}}class m extends u.Capability{constructor(){super(...arguments),this.type="audit",this.auditLog=[]}static get capabilityType(){return"audit"}attach(t){this.aggregate=t;const e=this.aggregate;typeof e.apply=="function"&&(this.originalApply=e.apply,this.originalApply&&(this.aggregate.apply=(...i)=>{const a=this.originalApply.call(this.aggregate,...i);return this.recordAuditEvent(),a}))}detach(){this.aggregate&&this.originalApply&&(this.aggregate.apply=this.originalApply),this.aggregate=void 0,this.auditLog=[],this.originalApply=void 0}getAuditLog(){return[...this.auditLog]}clearAuditLog(){this.auditLog=[]}getAuditStatistics(){const t={};let e=0;this.auditLog.forEach((a,n)=>{if(t[a.eventName]=(t[a.eventName]||0)+1,n>0){const g=this.auditLog[n-1];if(g){const l=new Date(g.timestamp).getTime(),c=new Date(a.timestamp).getTime();e+=c-l}}});const i=this.auditLog.length>1?e/(this.auditLog.length-1):0;return{totalEvents:this.auditLog.length,eventsByType:t,averageTimeBetweenEvents:i}}recordAuditEvent(){const t=this.aggregate.getDomainEvents(),e=t[t.length-1];e&&this.recordEvent(e)}recordEvent(t){const e={eventId:t.metadata?.eventId||`audit-${h.LibUtils.getUUID()}`,eventName:t.eventName,aggregateId:this.aggregate.getId().toString(),aggregateType:this.aggregate.constructor.name,aggregateVersion:this.aggregate.getVersion(),timestamp:new Date,payload:t.payload,metadata:{...t.metadata,auditCapability:!0},actor:t.metadata?.userId};this.auditLog.push(e)}getEventsByName(t){return this.auditLog.filter(e=>e.eventName===t)}getEventsByTimeRange(t,e){return this.auditLog.filter(i=>{const a=new Date(i.timestamp).getTime();return a>=t.getTime()&&a<=e.getTime()})}getFirstEvent(){return this.auditLog[0]||null}getLastEvent(){return this.auditLog[this.auditLog.length-1]||null}}class r extends o.IDomainError{static invalidArguments(t,e){const i={code:o.DomainErrorCode.InvalidParameter,data:e};return new r(t,i)}static versionConflict(t,e,i,a){const n=`Version conflict: Aggregate ${t} with ID ${e} has version ${i}, but expected ${a}`,g={code:o.DomainErrorCode.ValidationFailed,data:{aggregateType:t,aggregateId:e,currentVersion:i,expectedVersion:a}};return new r(n,g)}static featureNotEnabled(t){const e=`Feature '${t}' is not enabled on aggregate`,i={code:o.DomainErrorCode.ValidationFailed,data:{feature:t}};return new r(e,i)}static methodNotImplemented(t,e){const i=`Method '${t}' must be implemented by ${e} to use this feature`,a={code:o.DomainErrorCode.MissingValue,data:{methodName:t,aggregateType:e}};return new r(i,a)}static invalidSnapshot(t,e){const i=`Invalid snapshot for aggregate ${t}${e?`: ${e}`:""}`,a={code:o.DomainErrorCode.InvalidFormat,data:{aggregateType:t,reason:e}};return new r(i,a)}static idMismatch(t,e){const i=`ID mismatch: Snapshot is for ID ${t}, but aggregate has ID ${e}`,a={code:o.DomainErrorCode.ValidationFailed,data:{snapshotId:t,aggregateId:e}};return new r(i,a)}static typeMismatch(t,e){const i=`Aggregate type mismatch: Snapshot is for type ${t}, but trying to load into ${e}`,a={code:o.DomainErrorCode.ValidationFailed,data:{snapshotType:t,aggregateType:e}};return new r(i,a)}static duplicateUpcaster(t,e){const i=`Upcaster for event ${t} version ${e} already exists`,a={code:o.DomainErrorCode.DuplicateEntry,data:{eventType:t,sourceVersion:e}};return new r(i,a)}static missingUpcaster(t,e,i){const a=`Missing upcaster for event ${t} from version ${e} to ${i}`,n={code:o.DomainErrorCode.MissingValue,data:{eventType:t,fromVersion:e,toVersion:i}};return new r(a,n)}static configurationError(t,e){const i=`Configuration error in ${t}: ${e}`,a={code:o.DomainErrorCode.ValidationFailed,data:{aggregateType:t,configurationError:e}};return new r(i,a)}static cannotInterceptApplyMethod(t,e){const i=`Cannot intercept apply method - method not found on aggregate ${t}`,a={code:o.DomainErrorCode.InternalError,data:{aggergateId:t,...e}};return new r(i,a)}static eventStoreNotConfigured(t){const e="Event store not configured",i={code:o.DomainErrorCode.MissingValue,data:t};return new r(e,i)}static aggregateDoesNotSupportReplay(t,e){const i=`Aggregate ${t} does not support event replay`,a={code:o.DomainErrorCode.MethodNotSupported,data:{aggregateType:t,...e}};return new r(i,a)}}class f extends u.Capability{constructor(){super(...arguments),this.type="eventSourcing",this.eventStore=null}static get capabilityType(){return"eventSourcing"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this.eventStore=null}setEventStore(t){this.eventStore=t}getEventStore(){return this.eventStore}async loadFromEventStore(t){if(!this.eventStore)throw r.configurationError(this.aggregate.constructor.name,"Event store not configured");const e=await this.eventStore.getEvents(t);if(e.length===0)return;const i=this.aggregate;i.loadFromHistory&&i.loadFromHistory(e)}async saveToEventStore(){if(!this.eventStore)throw r.configurationError(this.aggregate.constructor.name,"Event store not configured");const t=this.aggregate.getDomainEvents();t.length!==0&&await this.eventStore.saveEvents(this.aggregate.getId().toString(),[...t],this.aggregate.getInitialVersion())}hasEventStore(){return this.eventStore!==null}getStreamName(){return`${this.aggregate.constructor.name}-${this.aggregate.getId().toString()}`}async loadFromVersion(t,e){if(!this.eventStore)throw r.configurationError(this.aggregate.constructor.name,"Event store not configured");const i=await this.eventStore.getEventsAfterVersion(t,e),a=this.aggregate;a.loadFromHistory&&i.length>0&&a.loadFromHistory(i)}}class y extends u.Capability{constructor(){super(...arguments),this.type="snapshot",this._snapshot=null}static get capabilityType(){return"snapshot"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this._snapshot=null}createSnapshot(t,e){const i=this.aggregate.getDomainEvents(),n=(i.length>0?i[i.length-1]:null)?.metadata?.eventId,l={aggregateId:this.aggregate.getId().toString(),version:this.aggregate.getVersion(),aggregateType:this.aggregate.constructor.name,state:t(),timestamp:new Date,...n!==void 0&&{lastEventId:n}};return e&&(l.metadata=e()),l}restoreFromSnapshot(t,e,i){if(!t||!t.state)throw r.invalidSnapshot(this.aggregate.constructor.name,"Invalid snapshot");const a=this.aggregate.getId().toString();if(t.aggregateId!==a)throw r.idMismatch(t.aggregateId,a);if(t.aggregateType!==this.aggregate.constructor.name)throw r.typeMismatch(t.aggregateType,this.aggregate.constructor.name);e(t.state),t.metadata&&i&&i(t.metadata),this.aggregate._internal_setState(w,{version:t.version,initialVersion:t.version,domainEvents:[]})}saveTemporaryState(t){this._snapshot=this.createSnapshot(()=>t)}getLastSnapshotTimestamp(){return this._snapshot?.timestamp||null}saveSnapshot(t,e){this._snapshot=this.createSnapshot(t,e)}getPreviousState(){const t=this._snapshot;return this._snapshot=null,t}}class v{constructor(t){this.capabilities=[],this.params=t}static create(t){const e={id:t.id instanceof u.EntityId?t.id:new u.EntityId(t.id,"text"),version:t.version||0};return new v(e)}withSnapshots(){return this.capabilities.push({capability:new y}),this}withVersioning(){return this.capabilities.push({capability:new p}),this}withAudit(){return this.capabilities.push({capability:new m}),this}withEventSourcing(t){const e=new f;return this.capabilities.push({capability:e,configure:i=>{const a=i;t?a.setEventStore(t):this.eventStore&&a.setEventStore(this.eventStore)}}),this}withCapability(t,e){return this.capabilities.push({capability:t,configure:e?i=>e(i):void 0}),this}setEventStore(t){return this.eventStore=t,this}build(t){const e=t||d,i=new e(this.params);for(const{capability:a,configure:n}of this.capabilities)i.addCapability(a),n&&n(a);return i}buildWithAllCapabilities(t){return this.withSnapshots().withVersioning().withAudit().withEventSourcing().build(t)}}function D(s){return v.create(s)}class b{constructor(t){this._id=t}getId(){return this._id}equals(t){return t==null?!1:t===this?!0:t instanceof b?this._id.equals(t._id):!1}}function _(s){return s.hasCapability(y)}function C(s){return s.hasCapability(p)}function A(s){return s.hasCapability(m)}function V(s){return s.hasCapability(f)}function $(s){if(!_(s))throw r.featureNotEnabled("snapshot");return s}function M(s){return _(s)?h.Result.ok(s):h.Result.fail(r.featureNotEnabled("snapshot"))}function N(s){if(!C(s))throw r.featureNotEnabled("versioning");return s}function T(s){return C(s)?h.Result.ok(s):h.Result.fail(r.featureNotEnabled("versioning"))}function L(s){if(!A(s))throw r.featureNotEnabled("audit");return s}function F(s){return A(s)?h.Result.ok(s):h.Result.fail(r.featureNotEnabled("audit"))}function H(s){if(!V(s))throw r.featureNotEnabled("eventSourcing");return s}function x(s){return V(s)?h.Result.ok(s):h.Result.fail(r.featureNotEnabled("eventSourcing"))}function U(s){return s.getCapabilityTypes()}function R(s,t){return t.every(e=>s.hasCapability(e))}exports.AggregateBuilder=v;exports.AggregateError=r;exports.AggregateRoot=d;exports.AuditCapability=m;exports.Entity=b;exports.EventSourcingCapability=f;exports.SnapshotCapability=y;exports.VersioningCapability=p;exports.aggregateBuilder=D;exports.asAuditAggregate=L;exports.asEventSourcingAggregate=H;exports.asSnapshotAggregate=$;exports.asVersioningAggregate=N;exports.getAggregateCapabilities=U;exports.hasAllCapabilities=R;exports.tryAsAuditAggregate=F;exports.tryAsEventSourcingAggregate=x;exports.tryAsSnapshotAggregate=M;exports.tryAsVersioningAggregate=T;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("@vytches/ddd-contracts"),S=require("@vytches/ddd-contracts/internal"),h=require("@vytches/ddd-utils"),o=require("@vytches/ddd-domain-primitives");class p extends l.Capability{constructor(){super(...arguments),this.type="versioning",this.upcasters=new Map}static get capabilityType(){return"versioning"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this.upcasters.clear()}registerUpcaster(t,e,i){this.upcasters.has(t)||this.upcasters.set(t,new Map),this.upcasters.get(t).set(e,i)}handleVersionedEvent(t,e){const i=t.metadata?.version||1,a=t.metadata?.targetVersion||i;let s=t;if(i<a&&this.upcasters.has(t.eventName)){const u=this.upcasters.get(t.eventName);for(let c=i;c<a;c++){const b=u.get(c);if(!b){S.internalLogger.warn("VersioningCapability: Missing upcaster for event version — event will be processed without upcast",{eventName:t.eventName,fromVersion:c,toVersion:c+1});continue}const V=b.upcast(s.payload,s.metadata);s={...s,payload:V,metadata:{...s.metadata,version:c+1}}}}const g=e.get(s.eventName);g&&g(s.payload,s.metadata)}getRegisteredEventTypes(){return Array.from(this.upcasters.keys())}hasUpcaster(t,e){return this.upcasters.get(t)?.has(e)||!1}getUpcastersForType(t){return this.upcasters.get(t)}clearUpcastersForType(t){this.upcasters.delete(t)}getTotalUpcasterCount(){let t=0;for(const e of this.upcasters.values())t+=e.size;return t}}const _=Symbol("vytches-ddd-aggregate-internal-state");class d{constructor(t){this._version=0,this._initialVersion=0,this._domainEvents=[],this._domainEventsCache=Object.freeze([]),this._domainEventsCacheDirty=!0,this._eventHandlers=new Map,this._capabilities=new l.CapabilityRegistry,this._id=t.id,this._version=t.version||0,this._initialVersion=t.version||0,this._maxEvents=t.maxEvents,this._onMissingHandler=t.onMissingHandler??"warn"}getId(){return this._id}equals(t){return t==null?!1:t===this?!0:t instanceof d?this._id.equals(t._id):!1}getVersion(){return this._version}getInitialVersion(){return this._initialVersion}hasChanges(){return this._domainEvents.length>0}getDomainEvents(){if(this._domainEventsCacheDirty){const t=new WeakSet,e=this._domainEvents.map(i=>h.LibUtils.deepFreeze(i,t));this._domainEventsCache=Object.freeze(e),this._domainEventsCacheDirty=!1}return this._domainEventsCache}_invalidateDomainEventsCache(){this._domainEventsCacheDirty=!0}commit(){this._domainEvents=[],this._initialVersion=this._version,this._invalidateDomainEventsCache()}transformDomainEvents(t){this._domainEvents=this._domainEvents.map((e,i)=>{const a=t(e,i);return a?l.enrichEvent(e,a):e}),this._invalidateDomainEventsCache()}registerEventHandler(t,e){return this._eventHandlers.set(t,e),this}apply(t,e,i){if(this._maxEvents!==void 0&&this._domainEvents.length>=this._maxEvents)throw new Error(`Aggregate ${this.constructor.name} (id=${this._id.toString()}) exceeded maxEvents limit of ${this._maxEvents}. This usually indicates a runaway loop or replay of a corrupted event stream. Increase maxEvents in the aggregate constructor params if the limit is too restrictive for your domain.`);const a=this._version+1;let s,g;typeof t=="string"?(s=l.createDomainEvent(t,e,i),g=s.metadata):(s=t,g=i?{...s.metadata,...i}:s.metadata);const u=d.sanitizeMetadata({...g,aggregateId:this._id.toString(),aggregateType:this.constructor.name,aggregateVersion:a,timestamp:g?.timestamp??new Date}),c=Object.assign(Object.create(Object.getPrototypeOf(s)),s,{metadata:u});this._version=a,this._domainEvents.push(c),this._invalidateDomainEventsCache(),this.handleEvent(c)}handleEvent(t){const e=this.getCapability(p);if(e)e.handleVersionedEvent(t,this._eventHandlers);else{const i=this._eventHandlers.get(t.eventName);i?i(t.payload,t.metadata):this.reportMissingHandler(t.eventName)}}reportMissingHandler(t){const e=`Aggregate ${this.constructor.name} (id=${this._id.toString()}) has no registered handler for event "${t}". State will not be updated for this event. Register a handler via registerEventHandler('${t}', ...) before applying/replaying this event, or pass onMissingHandler: "warn" explicitly to silence this in the constructor params.`;if(this._onMissingHandler==="throw")throw new Error(e);S.internalLogger.warn(e,{aggregateType:this.constructor.name,aggregateId:this._id.toString(),eventName:t})}loadFromHistory(t){this._version=this._initialVersion,this._domainEvents=[],this._invalidateDomainEventsCache();for(const e of t)this.handleEvent(e),this._version++;this._initialVersion=this._version}addCapability(t){return"attach"in t&&typeof t.attach=="function"&&t.attach(this),this._capabilities.register(t),this}getCapability(t){return this._capabilities.get(t)}hasCapability(t){return this._capabilities.has(t)}removeCapability(t){const e=this._capabilities.get(t);return e&&"detach"in e&&typeof e.detach=="function"&&e.detach(),this._capabilities.remove(t),this}getAllCapabilities(){return this._capabilities.getAll()}getCapabilityTypes(){return this._capabilities.getTypes()}static sanitizeMetadata(t){if(!Object.prototype.hasOwnProperty.call(t,"__proto__")&&!Object.prototype.hasOwnProperty.call(t,"constructor")&&!Object.prototype.hasOwnProperty.call(t,"prototype"))return t;const e={};for(const i of Object.keys(t))i==="__proto__"||i==="constructor"||i==="prototype"||(e[i]=t[i]);return e}_internal_setState(t,e){if(t!==_)throw new TypeError("_internal_setState: invalid or missing internal state token. This method is restricted to capabilities within @vytches/ddd-aggregates.");this._version=e.version,this._initialVersion=e.initialVersion,this._domainEvents=[...e.domainEvents],this._invalidateDomainEventsCache()}}class m extends l.Capability{constructor(){super(...arguments),this.type="audit",this.auditLog=[]}static get capabilityType(){return"audit"}attach(t){this.aggregate=t;const e=this.aggregate;typeof e.apply=="function"&&(this.originalApply=e.apply,this.originalApply&&(this.aggregate.apply=(...i)=>{const a=this.originalApply.call(this.aggregate,...i);return this.recordAuditEvent(),a}))}detach(){this.aggregate&&this.originalApply&&(this.aggregate.apply=this.originalApply),this.aggregate=void 0,this.auditLog=[],this.originalApply=void 0}getAuditLog(){return[...this.auditLog]}clearAuditLog(){this.auditLog=[]}getAuditStatistics(){const t={};let e=0;this.auditLog.forEach((a,s)=>{if(t[a.eventName]=(t[a.eventName]||0)+1,s>0){const g=this.auditLog[s-1];if(g){const u=new Date(g.timestamp).getTime(),c=new Date(a.timestamp).getTime();e+=c-u}}});const i=this.auditLog.length>1?e/(this.auditLog.length-1):0;return{totalEvents:this.auditLog.length,eventsByType:t,averageTimeBetweenEvents:i}}recordAuditEvent(){const t=this.aggregate.getDomainEvents(),e=t[t.length-1];e&&this.recordEvent(e)}recordEvent(t){const e={eventId:t.metadata?.eventId||`audit-${h.LibUtils.getUUID()}`,eventName:t.eventName,aggregateId:this.aggregate.getId().toString(),aggregateType:this.aggregate.constructor.name,aggregateVersion:this.aggregate.getVersion(),timestamp:new Date,payload:t.payload,metadata:{...t.metadata,auditCapability:!0},actor:t.metadata?.userId};this.auditLog.push(e)}getEventsByName(t){return this.auditLog.filter(e=>e.eventName===t)}getEventsByTimeRange(t,e){return this.auditLog.filter(i=>{const a=new Date(i.timestamp).getTime();return a>=t.getTime()&&a<=e.getTime()})}getFirstEvent(){return this.auditLog[0]||null}getLastEvent(){return this.auditLog[this.auditLog.length-1]||null}}class r extends o.IDomainError{static invalidArguments(t,e){const i={code:o.DomainErrorCode.InvalidParameter,data:e};return new r(t,i)}static versionConflict(t,e,i,a){const s=`Version conflict: Aggregate ${t} with ID ${e} has version ${i}, but expected ${a}`,g={code:o.DomainErrorCode.ValidationFailed,data:{aggregateType:t,aggregateId:e,currentVersion:i,expectedVersion:a}};return new r(s,g)}static featureNotEnabled(t){const e=`Feature '${t}' is not enabled on aggregate`,i={code:o.DomainErrorCode.ValidationFailed,data:{feature:t}};return new r(e,i)}static methodNotImplemented(t,e){const i=`Method '${t}' must be implemented by ${e} to use this feature`,a={code:o.DomainErrorCode.MissingValue,data:{methodName:t,aggregateType:e}};return new r(i,a)}static invalidSnapshot(t,e){const i=`Invalid snapshot for aggregate ${t}${e?`: ${e}`:""}`,a={code:o.DomainErrorCode.InvalidFormat,data:{aggregateType:t,reason:e}};return new r(i,a)}static idMismatch(t,e){const i=`ID mismatch: Snapshot is for ID ${t}, but aggregate has ID ${e}`,a={code:o.DomainErrorCode.ValidationFailed,data:{snapshotId:t,aggregateId:e}};return new r(i,a)}static typeMismatch(t,e){const i=`Aggregate type mismatch: Snapshot is for type ${t}, but trying to load into ${e}`,a={code:o.DomainErrorCode.ValidationFailed,data:{snapshotType:t,aggregateType:e}};return new r(i,a)}static duplicateUpcaster(t,e){const i=`Upcaster for event ${t} version ${e} already exists`,a={code:o.DomainErrorCode.DuplicateEntry,data:{eventType:t,sourceVersion:e}};return new r(i,a)}static missingUpcaster(t,e,i){const a=`Missing upcaster for event ${t} from version ${e} to ${i}`,s={code:o.DomainErrorCode.MissingValue,data:{eventType:t,fromVersion:e,toVersion:i}};return new r(a,s)}static configurationError(t,e){const i=`Configuration error in ${t}: ${e}`,a={code:o.DomainErrorCode.ValidationFailed,data:{aggregateType:t,configurationError:e}};return new r(i,a)}static cannotInterceptApplyMethod(t,e){const i=`Cannot intercept apply method - method not found on aggregate ${t}`,a={code:o.DomainErrorCode.InternalError,data:{aggergateId:t,...e}};return new r(i,a)}static eventStoreNotConfigured(t){const e="Event store not configured",i={code:o.DomainErrorCode.MissingValue,data:t};return new r(e,i)}static aggregateDoesNotSupportReplay(t,e){const i=`Aggregate ${t} does not support event replay`,a={code:o.DomainErrorCode.MethodNotSupported,data:{aggregateType:t,...e}};return new r(i,a)}}class f extends l.Capability{constructor(){super(...arguments),this.type="eventSourcing",this.eventStore=null}static get capabilityType(){return"eventSourcing"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this.eventStore=null}setEventStore(t){this.eventStore=t}getEventStore(){return this.eventStore}async loadFromEventStore(t){if(!this.eventStore)throw r.configurationError(this.aggregate.constructor.name,"Event store not configured");const e=await this.eventStore.getEvents(t);if(e.length===0)return;const i=this.aggregate;i.loadFromHistory&&i.loadFromHistory(e)}async saveToEventStore(){if(!this.eventStore)throw r.configurationError(this.aggregate.constructor.name,"Event store not configured");const t=this.aggregate.getDomainEvents();t.length!==0&&await this.eventStore.saveEvents(this.aggregate.getId().toString(),[...t],this.aggregate.getInitialVersion())}hasEventStore(){return this.eventStore!==null}getStreamName(){return`${this.aggregate.constructor.name}-${this.aggregate.getId().toString()}`}async loadFromVersion(t,e){if(!this.eventStore)throw r.configurationError(this.aggregate.constructor.name,"Event store not configured");const i=await this.eventStore.getEventsAfterVersion(t,e),a=this.aggregate;a.loadFromHistory&&i.length>0&&a.loadFromHistory(i)}}class y extends l.Capability{constructor(){super(...arguments),this.type="snapshot",this._snapshot=null}static get capabilityType(){return"snapshot"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this._snapshot=null}createSnapshot(t,e){const i=this.aggregate.getDomainEvents(),s=(i.length>0?i[i.length-1]:null)?.metadata?.eventId,u={aggregateId:this.aggregate.getId().toString(),version:this.aggregate.getVersion(),aggregateType:this.aggregate.constructor.name,state:t(),timestamp:new Date,...s!==void 0&&{lastEventId:s}};return e&&(u.metadata=e()),u}restoreFromSnapshot(t,e,i){if(!t||!t.state)throw r.invalidSnapshot(this.aggregate.constructor.name,"Invalid snapshot");const a=this.aggregate.getId().toString();if(t.aggregateId!==a)throw r.idMismatch(t.aggregateId,a);if(t.aggregateType!==this.aggregate.constructor.name)throw r.typeMismatch(t.aggregateType,this.aggregate.constructor.name);e(t.state),t.metadata&&i&&i(t.metadata),this.aggregate._internal_setState(_,{version:t.version,initialVersion:t.version,domainEvents:[]})}saveTemporaryState(t){this._snapshot=this.createSnapshot(()=>t)}getLastSnapshotTimestamp(){return this._snapshot?.timestamp||null}saveSnapshot(t,e){this._snapshot=this.createSnapshot(t,e)}getPreviousState(){const t=this._snapshot;return this._snapshot=null,t}}class v{constructor(t){this.capabilities=[],this.params=t}static create(t){const e={id:t.id instanceof l.EntityId?t.id:new l.EntityId(t.id,"text"),version:t.version||0};return new v(e)}withSnapshots(){return this.capabilities.push({capability:new y}),this}withVersioning(){return this.capabilities.push({capability:new p}),this}withAudit(){return this.capabilities.push({capability:new m}),this}withEventSourcing(t){const e=new f;return this.capabilities.push({capability:e,configure:i=>{const a=i;t?a.setEventStore(t):this.eventStore&&a.setEventStore(this.eventStore)}}),this}withCapability(t,e){return this.capabilities.push({capability:t,configure:e?i=>e(i):void 0}),this}setEventStore(t){return this.eventStore=t,this}build(t){const e=t||d,i=new e(this.params);for(const{capability:a,configure:s}of this.capabilities)i.addCapability(a),s&&s(a);return i}buildWithAllCapabilities(t){return this.withSnapshots().withVersioning().withAudit().withEventSourcing().build(t)}}function I(n){return v.create(n)}class E{constructor(t){this._id=t}getId(){return this._id}equals(t){return t==null?!1:t===this?!0:t instanceof E?this._id.equals(t._id):!1}}function w(n){return n.hasCapability(y)}function C(n){return n.hasCapability(p)}function A(n){return n.hasCapability(m)}function D(n){return n.hasCapability(f)}function $(n){if(!w(n))throw r.featureNotEnabled("snapshot");return n}function M(n){return w(n)?h.Result.ok(n):h.Result.fail(r.featureNotEnabled("snapshot"))}function N(n){if(!C(n))throw r.featureNotEnabled("versioning");return n}function T(n){return C(n)?h.Result.ok(n):h.Result.fail(r.featureNotEnabled("versioning"))}function L(n){if(!A(n))throw r.featureNotEnabled("audit");return n}function F(n){return A(n)?h.Result.ok(n):h.Result.fail(r.featureNotEnabled("audit"))}function H(n){if(!D(n))throw r.featureNotEnabled("eventSourcing");return n}function x(n){return D(n)?h.Result.ok(n):h.Result.fail(r.featureNotEnabled("eventSourcing"))}function U(n){return n.getCapabilityTypes()}function O(n,t){return t.every(e=>n.hasCapability(e))}exports.AggregateBuilder=v;exports.AggregateError=r;exports.AggregateRoot=d;exports.AuditCapability=m;exports.Entity=E;exports.EventSourcingCapability=f;exports.SnapshotCapability=y;exports.VersioningCapability=p;exports.aggregateBuilder=I;exports.asAuditAggregate=L;exports.asEventSourcingAggregate=H;exports.asSnapshotAggregate=$;exports.asVersioningAggregate=N;exports.getAggregateCapabilities=U;exports.hasAllCapabilities=O;exports.tryAsAuditAggregate=F;exports.tryAsEventSourcingAggregate=x;exports.tryAsSnapshotAggregate=M;exports.tryAsVersioningAggregate=T;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Capability as d, CapabilityRegistry as M, createDomainEvent as T, EntityId as f } from "@vytches/ddd-contracts";
|
|
1
|
+
import { Capability as d, CapabilityRegistry as N, enrichEvent as M, createDomainEvent as T, EntityId as f } from "@vytches/ddd-contracts";
|
|
2
2
|
import { internalLogger as y } from "@vytches/ddd-contracts/internal";
|
|
3
|
-
import { LibUtils as
|
|
4
|
-
import { IDomainError as L, DomainErrorCode as
|
|
3
|
+
import { LibUtils as E, Result as l } from "@vytches/ddd-utils";
|
|
4
|
+
import { IDomainError as L, DomainErrorCode as c } from "@vytches/ddd-domain-primitives";
|
|
5
5
|
class p extends d {
|
|
6
6
|
constructor() {
|
|
7
7
|
super(...arguments), this.type = "versioning", this.upcasters = /* @__PURE__ */ new Map();
|
|
@@ -44,36 +44,36 @@ class p extends d {
|
|
|
44
44
|
* @param {Map<string, IAggregateEventHandler>} handlers - Map of event handlers
|
|
45
45
|
*/
|
|
46
46
|
handleVersionedEvent(t, e) {
|
|
47
|
-
const i = t.metadata?.version || 1,
|
|
48
|
-
let
|
|
49
|
-
if (i <
|
|
50
|
-
const
|
|
51
|
-
for (let
|
|
52
|
-
const m =
|
|
47
|
+
const i = t.metadata?.version || 1, a = t.metadata?.targetVersion || i;
|
|
48
|
+
let s = t;
|
|
49
|
+
if (i < a && this.upcasters.has(t.eventName)) {
|
|
50
|
+
const g = this.upcasters.get(t.eventName);
|
|
51
|
+
for (let h = i; h < a; h++) {
|
|
52
|
+
const m = g.get(h);
|
|
53
53
|
if (!m) {
|
|
54
54
|
y.warn(
|
|
55
55
|
"VersioningCapability: Missing upcaster for event version — event will be processed without upcast",
|
|
56
56
|
{
|
|
57
57
|
eventName: t.eventName,
|
|
58
|
-
fromVersion:
|
|
59
|
-
toVersion:
|
|
58
|
+
fromVersion: h,
|
|
59
|
+
toVersion: h + 1
|
|
60
60
|
}
|
|
61
61
|
);
|
|
62
62
|
continue;
|
|
63
63
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
...
|
|
67
|
-
payload:
|
|
64
|
+
const D = m.upcast(s.payload, s.metadata);
|
|
65
|
+
s = {
|
|
66
|
+
...s,
|
|
67
|
+
payload: D,
|
|
68
68
|
metadata: {
|
|
69
|
-
...
|
|
70
|
-
version:
|
|
69
|
+
...s.metadata,
|
|
70
|
+
version: h + 1
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
const o = e.get(
|
|
76
|
-
o && o(
|
|
75
|
+
const o = e.get(s.eventName);
|
|
76
|
+
o && o(s.payload, s.metadata);
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
79
|
* @returns {string[]} Array of event types that have registered upcasters
|
|
@@ -112,10 +112,10 @@ class p extends d {
|
|
|
112
112
|
return t;
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
const
|
|
115
|
+
const b = Symbol("vytches-ddd-aggregate-internal-state");
|
|
116
116
|
class u {
|
|
117
117
|
constructor(t) {
|
|
118
|
-
this._version = 0, this._initialVersion = 0, this._domainEvents = [], this._eventHandlers = /* @__PURE__ */ new Map(), this._capabilities = new
|
|
118
|
+
this._version = 0, this._initialVersion = 0, this._domainEvents = [], this._domainEventsCache = Object.freeze([]), this._domainEventsCacheDirty = !0, this._eventHandlers = /* @__PURE__ */ new Map(), this._capabilities = new N(), this._id = t.id, this._version = t.version || 0, this._initialVersion = t.version || 0, this._maxEvents = t.maxEvents, this._onMissingHandler = t.onMissingHandler ?? "warn";
|
|
119
119
|
}
|
|
120
120
|
// ==========================================
|
|
121
121
|
// CORE AGGREGATE FUNCTIONALITY
|
|
@@ -181,17 +181,89 @@ class u {
|
|
|
181
181
|
* data) inside it could still be mutated in place, silently corrupting
|
|
182
182
|
* state shared with `_domainEvents` (the same object references are
|
|
183
183
|
* stored internally — events are never re-cloned after creation).
|
|
184
|
+
*
|
|
185
|
+
* VP-012b (D2, BREAKING behavior change, non-breaking signature): the
|
|
186
|
+
* returned array reference is now **stable between calls** — the same
|
|
187
|
+
* frozen array object is returned on every call until the next mutation
|
|
188
|
+
* (`apply()`, `commit()`, `loadFromHistory()`, or
|
|
189
|
+
* `transformDomainEvents()`), instead of a fresh array being built on
|
|
190
|
+
* every call as before. Content equality (`toEqual`) is unaffected either
|
|
191
|
+
* way. Code that relies on *identity* — e.g. asserting `not.toBe`/`!==`
|
|
192
|
+
* between two `getDomainEvents()` calls to prove "each call returns a new
|
|
193
|
+
* array" — will observe the new stable-reference behavior and must be
|
|
194
|
+
* updated; asserting `.toBe`/`===` between two calls made with no
|
|
195
|
+
* intervening mutation now holds, where it previously did not. This is
|
|
196
|
+
* safe to alias: the returned array and its contents are frozen, so
|
|
197
|
+
* holding onto the reference across a mutation still reflects the state
|
|
198
|
+
* as of the call that produced it (the cache is not that same array
|
|
199
|
+
* anymore after invalidation — a stored old reference is simply stale
|
|
200
|
+
* data, never mutated out from under the caller).
|
|
184
201
|
* @returns {ReadonlyArray<IDomainEvent>} Array of uncommitted domain events
|
|
185
202
|
*/
|
|
186
203
|
getDomainEvents() {
|
|
187
|
-
|
|
188
|
-
|
|
204
|
+
if (this._domainEventsCacheDirty) {
|
|
205
|
+
const t = /* @__PURE__ */ new WeakSet(), e = this._domainEvents.map((i) => E.deepFreeze(i, t));
|
|
206
|
+
this._domainEventsCache = Object.freeze(e), this._domainEventsCacheDirty = !1;
|
|
207
|
+
}
|
|
208
|
+
return this._domainEventsCache;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* VP-012b (D2): single invalidation point for `_domainEventsCache`,
|
|
212
|
+
* called from every site that mutates `_domainEvents` — `apply()`,
|
|
213
|
+
* `commit()`, `transformDomainEvents()`, `loadFromHistory()`, and
|
|
214
|
+
* `_internal_setState()`. Keeping invalidation in one helper (rather than
|
|
215
|
+
* setting the flag ad hoc at each call site) means a future mutation site
|
|
216
|
+
* cannot silently forget to invalidate.
|
|
217
|
+
*/
|
|
218
|
+
_invalidateDomainEventsCache() {
|
|
219
|
+
this._domainEventsCacheDirty = !0;
|
|
189
220
|
}
|
|
190
221
|
/**
|
|
191
222
|
* Clear all uncommitted domain events and update initial version
|
|
192
223
|
*/
|
|
193
224
|
commit() {
|
|
194
|
-
this._domainEvents = [], this._initialVersion = this._version;
|
|
225
|
+
this._domainEvents = [], this._initialVersion = this._version, this._invalidateDomainEventsCache();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Rewrite the payload and/or metadata of the uncommitted domain events.
|
|
229
|
+
*
|
|
230
|
+
* This is an infrastructure boundary API, in the same family as `commit()`:
|
|
231
|
+
* call it from a repository immediately before persisting, never from the
|
|
232
|
+
* domain or application layer. An aggregate records events through `apply()`;
|
|
233
|
+
* it does not rewrite its own history.
|
|
234
|
+
*
|
|
235
|
+
* It exists for data that only the persistence boundary knows — a
|
|
236
|
+
* crypto-shredding key id resolved at save time, a correlation id assigned at
|
|
237
|
+
* dispatch, an encrypted payload. Doing this by hand from outside is not
|
|
238
|
+
* possible: `getDomainEvents()` deep-freezes what it returns, and the
|
|
239
|
+
* internal list is private. The transformation has to land here rather than
|
|
240
|
+
* on a local copy inside `save()`, because the event dispatcher reads the
|
|
241
|
+
* aggregate's events again — a local copy would leave the in-process event
|
|
242
|
+
* bus publishing the untransformed originals.
|
|
243
|
+
*
|
|
244
|
+
* Only `payload` and `metadata` can be replaced. `eventName`, identity, and
|
|
245
|
+
* the number and order of events are fixed: changing those would desync
|
|
246
|
+
* handlers on replay or break the version invariant. Return nothing to leave
|
|
247
|
+
* an event as it is.
|
|
248
|
+
*
|
|
249
|
+
* Identity, prototype and `instanceof` survive, and the event's constructor
|
|
250
|
+
* is never called, so event classes with their own constructor arguments are
|
|
251
|
+
* safe.
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* // inside a repository's save(), before persisting:
|
|
255
|
+
* order.transformDomainEvents(event => ({
|
|
256
|
+
* payload: encrypt(event.payload, key),
|
|
257
|
+
* metadata: { userSpecificKeyId: key.id },
|
|
258
|
+
* }));
|
|
259
|
+
*
|
|
260
|
+
* @param transform - Called per event; returns the parts to replace, or nothing to keep it
|
|
261
|
+
*/
|
|
262
|
+
transformDomainEvents(t) {
|
|
263
|
+
this._domainEvents = this._domainEvents.map((e, i) => {
|
|
264
|
+
const a = t(e, i);
|
|
265
|
+
return a ? M(e, a) : e;
|
|
266
|
+
}), this._invalidateDomainEventsCache();
|
|
195
267
|
}
|
|
196
268
|
// ==========================================
|
|
197
269
|
// EVENT HANDLING - Type-safe approach
|
|
@@ -228,21 +300,21 @@ class u {
|
|
|
228
300
|
throw new Error(
|
|
229
301
|
`Aggregate ${this.constructor.name} (id=${this._id.toString()}) exceeded maxEvents limit of ${this._maxEvents}. This usually indicates a runaway loop or replay of a corrupted event stream. Increase maxEvents in the aggregate constructor params if the limit is too restrictive for your domain.`
|
|
230
302
|
);
|
|
231
|
-
const
|
|
232
|
-
let
|
|
233
|
-
typeof t == "string" ? (
|
|
234
|
-
const
|
|
303
|
+
const a = this._version + 1;
|
|
304
|
+
let s, o;
|
|
305
|
+
typeof t == "string" ? (s = T(t, e, i), o = s.metadata) : (s = t, o = i ? { ...s.metadata, ...i } : s.metadata);
|
|
306
|
+
const g = u.sanitizeMetadata({
|
|
235
307
|
...o,
|
|
236
308
|
aggregateId: this._id.toString(),
|
|
237
309
|
aggregateType: this.constructor.name,
|
|
238
|
-
aggregateVersion:
|
|
310
|
+
aggregateVersion: a,
|
|
239
311
|
timestamp: o?.timestamp ?? /* @__PURE__ */ new Date()
|
|
240
|
-
}),
|
|
241
|
-
Object.create(Object.getPrototypeOf(
|
|
242
|
-
|
|
243
|
-
{ metadata:
|
|
312
|
+
}), h = Object.assign(
|
|
313
|
+
Object.create(Object.getPrototypeOf(s)),
|
|
314
|
+
s,
|
|
315
|
+
{ metadata: g }
|
|
244
316
|
);
|
|
245
|
-
this._version =
|
|
317
|
+
this._version = a, this._domainEvents.push(h), this._invalidateDomainEventsCache(), this.handleEvent(h);
|
|
246
318
|
}
|
|
247
319
|
handleEvent(t) {
|
|
248
320
|
const e = this.getCapability(p);
|
|
@@ -273,7 +345,7 @@ class u {
|
|
|
273
345
|
* @param {IDomainEvent[]} events - Array of events to replay for state reconstruction
|
|
274
346
|
*/
|
|
275
347
|
loadFromHistory(t) {
|
|
276
|
-
this._version = this._initialVersion, this._domainEvents = [];
|
|
348
|
+
this._version = this._initialVersion, this._domainEvents = [], this._invalidateDomainEventsCache();
|
|
277
349
|
for (const e of t)
|
|
278
350
|
this.handleEvent(e), this._version++;
|
|
279
351
|
this._initialVersion = this._version;
|
|
@@ -352,14 +424,14 @@ class u {
|
|
|
352
424
|
* @throws {TypeError} if `token` does not match {@link INTERNAL_STATE_TOKEN}
|
|
353
425
|
*/
|
|
354
426
|
_internal_setState(t, e) {
|
|
355
|
-
if (t !==
|
|
427
|
+
if (t !== b)
|
|
356
428
|
throw new TypeError(
|
|
357
429
|
"_internal_setState: invalid or missing internal state token. This method is restricted to capabilities within @vytches/ddd-aggregates."
|
|
358
430
|
);
|
|
359
|
-
this._version = e.version, this._initialVersion = e.initialVersion, this._domainEvents = [...e.domainEvents];
|
|
431
|
+
this._version = e.version, this._initialVersion = e.initialVersion, this._domainEvents = [...e.domainEvents], this._invalidateDomainEventsCache();
|
|
360
432
|
}
|
|
361
433
|
}
|
|
362
|
-
class
|
|
434
|
+
class _ extends d {
|
|
363
435
|
constructor() {
|
|
364
436
|
super(...arguments), this.type = "audit", this.auditLog = [];
|
|
365
437
|
}
|
|
@@ -373,8 +445,8 @@ class w extends d {
|
|
|
373
445
|
this.aggregate = t;
|
|
374
446
|
const e = this.aggregate;
|
|
375
447
|
typeof e.apply == "function" && (this.originalApply = e.apply, this.originalApply && (this.aggregate.apply = (...i) => {
|
|
376
|
-
const
|
|
377
|
-
return this.recordAuditEvent(),
|
|
448
|
+
const a = this.originalApply.call(this.aggregate, ...i);
|
|
449
|
+
return this.recordAuditEvent(), a;
|
|
378
450
|
}));
|
|
379
451
|
}
|
|
380
452
|
detach() {
|
|
@@ -395,12 +467,12 @@ class w extends d {
|
|
|
395
467
|
getAuditStatistics() {
|
|
396
468
|
const t = {};
|
|
397
469
|
let e = 0;
|
|
398
|
-
this.auditLog.forEach((
|
|
399
|
-
if (t[
|
|
400
|
-
const o = this.auditLog[
|
|
470
|
+
this.auditLog.forEach((a, s) => {
|
|
471
|
+
if (t[a.eventName] = (t[a.eventName] || 0) + 1, s > 0) {
|
|
472
|
+
const o = this.auditLog[s - 1];
|
|
401
473
|
if (o) {
|
|
402
|
-
const
|
|
403
|
-
e +=
|
|
474
|
+
const g = new Date(o.timestamp).getTime(), h = new Date(a.timestamp).getTime();
|
|
475
|
+
e += h - g;
|
|
404
476
|
}
|
|
405
477
|
}
|
|
406
478
|
});
|
|
@@ -423,7 +495,7 @@ class w extends d {
|
|
|
423
495
|
*/
|
|
424
496
|
recordEvent(t) {
|
|
425
497
|
const e = {
|
|
426
|
-
eventId: t.metadata?.eventId || `audit-${
|
|
498
|
+
eventId: t.metadata?.eventId || `audit-${E.getUUID()}`,
|
|
427
499
|
eventName: t.eventName,
|
|
428
500
|
aggregateId: this.aggregate.getId().toString(),
|
|
429
501
|
aggregateType: this.aggregate.constructor.name,
|
|
@@ -452,8 +524,8 @@ class w extends d {
|
|
|
452
524
|
*/
|
|
453
525
|
getEventsByTimeRange(t, e) {
|
|
454
526
|
return this.auditLog.filter((i) => {
|
|
455
|
-
const
|
|
456
|
-
return
|
|
527
|
+
const a = new Date(i.timestamp).getTime();
|
|
528
|
+
return a >= t.getTime() && a <= e.getTime();
|
|
457
529
|
});
|
|
458
530
|
}
|
|
459
531
|
/**
|
|
@@ -475,7 +547,7 @@ class r extends L {
|
|
|
475
547
|
*/
|
|
476
548
|
static invalidArguments(t, e) {
|
|
477
549
|
const i = {
|
|
478
|
-
code:
|
|
550
|
+
code: c.InvalidParameter,
|
|
479
551
|
data: e
|
|
480
552
|
};
|
|
481
553
|
return new r(t, i);
|
|
@@ -483,24 +555,24 @@ class r extends L {
|
|
|
483
555
|
/**
|
|
484
556
|
* Error for version conflicts in the aggregate
|
|
485
557
|
*/
|
|
486
|
-
static versionConflict(t, e, i,
|
|
487
|
-
const
|
|
488
|
-
code:
|
|
558
|
+
static versionConflict(t, e, i, a) {
|
|
559
|
+
const s = `Version conflict: Aggregate ${t} with ID ${e} has version ${i}, but expected ${a}`, o = {
|
|
560
|
+
code: c.ValidationFailed,
|
|
489
561
|
data: {
|
|
490
562
|
aggregateType: t,
|
|
491
563
|
aggregateId: e,
|
|
492
564
|
currentVersion: i,
|
|
493
|
-
expectedVersion:
|
|
565
|
+
expectedVersion: a
|
|
494
566
|
}
|
|
495
567
|
};
|
|
496
|
-
return new r(
|
|
568
|
+
return new r(s, o);
|
|
497
569
|
}
|
|
498
570
|
/**
|
|
499
571
|
* Error for when a required feature is not enabled
|
|
500
572
|
*/
|
|
501
573
|
static featureNotEnabled(t) {
|
|
502
574
|
const e = `Feature '${t}' is not enabled on aggregate`, i = {
|
|
503
|
-
code:
|
|
575
|
+
code: c.ValidationFailed,
|
|
504
576
|
data: {
|
|
505
577
|
feature: t
|
|
506
578
|
}
|
|
@@ -511,113 +583,113 @@ class r extends L {
|
|
|
511
583
|
* Error for when a required method is not implemented
|
|
512
584
|
*/
|
|
513
585
|
static methodNotImplemented(t, e) {
|
|
514
|
-
const i = `Method '${t}' must be implemented by ${e} to use this feature`,
|
|
515
|
-
code:
|
|
586
|
+
const i = `Method '${t}' must be implemented by ${e} to use this feature`, a = {
|
|
587
|
+
code: c.MissingValue,
|
|
516
588
|
data: {
|
|
517
589
|
methodName: t,
|
|
518
590
|
aggregateType: e
|
|
519
591
|
}
|
|
520
592
|
};
|
|
521
|
-
return new r(i,
|
|
593
|
+
return new r(i, a);
|
|
522
594
|
}
|
|
523
595
|
/**
|
|
524
596
|
* Error for invalid snapshot
|
|
525
597
|
*/
|
|
526
598
|
static invalidSnapshot(t, e) {
|
|
527
|
-
const i = `Invalid snapshot for aggregate ${t}${e ? `: ${e}` : ""}`,
|
|
528
|
-
code:
|
|
599
|
+
const i = `Invalid snapshot for aggregate ${t}${e ? `: ${e}` : ""}`, a = {
|
|
600
|
+
code: c.InvalidFormat,
|
|
529
601
|
data: {
|
|
530
602
|
aggregateType: t,
|
|
531
603
|
reason: e
|
|
532
604
|
}
|
|
533
605
|
};
|
|
534
|
-
return new r(i,
|
|
606
|
+
return new r(i, a);
|
|
535
607
|
}
|
|
536
608
|
/**
|
|
537
609
|
* Error for ID mismatch during snapshot restoration
|
|
538
610
|
*/
|
|
539
611
|
static idMismatch(t, e) {
|
|
540
|
-
const i = `ID mismatch: Snapshot is for ID ${t}, but aggregate has ID ${e}`,
|
|
541
|
-
code:
|
|
612
|
+
const i = `ID mismatch: Snapshot is for ID ${t}, but aggregate has ID ${e}`, a = {
|
|
613
|
+
code: c.ValidationFailed,
|
|
542
614
|
data: {
|
|
543
615
|
snapshotId: t,
|
|
544
616
|
aggregateId: e
|
|
545
617
|
}
|
|
546
618
|
};
|
|
547
|
-
return new r(i,
|
|
619
|
+
return new r(i, a);
|
|
548
620
|
}
|
|
549
621
|
/**
|
|
550
622
|
* Error for type mismatch during snapshot restoration
|
|
551
623
|
*/
|
|
552
624
|
static typeMismatch(t, e) {
|
|
553
|
-
const i = `Aggregate type mismatch: Snapshot is for type ${t}, but trying to load into ${e}`,
|
|
554
|
-
code:
|
|
625
|
+
const i = `Aggregate type mismatch: Snapshot is for type ${t}, but trying to load into ${e}`, a = {
|
|
626
|
+
code: c.ValidationFailed,
|
|
555
627
|
data: {
|
|
556
628
|
snapshotType: t,
|
|
557
629
|
aggregateType: e
|
|
558
630
|
}
|
|
559
631
|
};
|
|
560
|
-
return new r(i,
|
|
632
|
+
return new r(i, a);
|
|
561
633
|
}
|
|
562
634
|
/**
|
|
563
635
|
* Error for duplicate upcaster registration
|
|
564
636
|
*/
|
|
565
637
|
static duplicateUpcaster(t, e) {
|
|
566
|
-
const i = `Upcaster for event ${t} version ${e} already exists`,
|
|
567
|
-
code:
|
|
638
|
+
const i = `Upcaster for event ${t} version ${e} already exists`, a = {
|
|
639
|
+
code: c.DuplicateEntry,
|
|
568
640
|
data: {
|
|
569
641
|
eventType: t,
|
|
570
642
|
sourceVersion: e
|
|
571
643
|
}
|
|
572
644
|
};
|
|
573
|
-
return new r(i,
|
|
645
|
+
return new r(i, a);
|
|
574
646
|
}
|
|
575
647
|
/**
|
|
576
648
|
* Error for missing upcaster
|
|
577
649
|
*/
|
|
578
650
|
static missingUpcaster(t, e, i) {
|
|
579
|
-
const
|
|
580
|
-
code:
|
|
651
|
+
const a = `Missing upcaster for event ${t} from version ${e} to ${i}`, s = {
|
|
652
|
+
code: c.MissingValue,
|
|
581
653
|
data: {
|
|
582
654
|
eventType: t,
|
|
583
655
|
fromVersion: e,
|
|
584
656
|
toVersion: i
|
|
585
657
|
}
|
|
586
658
|
};
|
|
587
|
-
return new r(
|
|
659
|
+
return new r(a, s);
|
|
588
660
|
}
|
|
589
661
|
/**
|
|
590
662
|
* Error for configuration problems
|
|
591
663
|
*/
|
|
592
664
|
static configurationError(t, e) {
|
|
593
|
-
const i = `Configuration error in ${t}: ${e}`,
|
|
594
|
-
code:
|
|
665
|
+
const i = `Configuration error in ${t}: ${e}`, a = {
|
|
666
|
+
code: c.ValidationFailed,
|
|
595
667
|
data: {
|
|
596
668
|
aggregateType: t,
|
|
597
669
|
configurationError: e
|
|
598
670
|
}
|
|
599
671
|
};
|
|
600
|
-
return new r(i,
|
|
672
|
+
return new r(i, a);
|
|
601
673
|
}
|
|
602
674
|
/**
|
|
603
675
|
* Error for when apply method interception fails (e.g. method not found)
|
|
604
676
|
*/
|
|
605
677
|
static cannotInterceptApplyMethod(t, e) {
|
|
606
|
-
const i = `Cannot intercept apply method - method not found on aggregate ${t}`,
|
|
607
|
-
code:
|
|
678
|
+
const i = `Cannot intercept apply method - method not found on aggregate ${t}`, a = {
|
|
679
|
+
code: c.InternalError,
|
|
608
680
|
data: {
|
|
609
681
|
aggergateId: t,
|
|
610
682
|
...e
|
|
611
683
|
}
|
|
612
684
|
};
|
|
613
|
-
return new r(i,
|
|
685
|
+
return new r(i, a);
|
|
614
686
|
}
|
|
615
687
|
/**
|
|
616
688
|
* Error for when event store is not configured
|
|
617
689
|
*/
|
|
618
690
|
static eventStoreNotConfigured(t) {
|
|
619
691
|
const e = "Event store not configured", i = {
|
|
620
|
-
code:
|
|
692
|
+
code: c.MissingValue,
|
|
621
693
|
data: t
|
|
622
694
|
};
|
|
623
695
|
return new r(e, i);
|
|
@@ -626,17 +698,17 @@ class r extends L {
|
|
|
626
698
|
* Error for when aggregate does not support event replay
|
|
627
699
|
*/
|
|
628
700
|
static aggregateDoesNotSupportReplay(t, e) {
|
|
629
|
-
const i = `Aggregate ${t} does not support event replay`,
|
|
630
|
-
code:
|
|
701
|
+
const i = `Aggregate ${t} does not support event replay`, a = {
|
|
702
|
+
code: c.MethodNotSupported,
|
|
631
703
|
data: {
|
|
632
704
|
aggregateType: t,
|
|
633
705
|
...e
|
|
634
706
|
}
|
|
635
707
|
};
|
|
636
|
-
return new r(i,
|
|
708
|
+
return new r(i, a);
|
|
637
709
|
}
|
|
638
710
|
}
|
|
639
|
-
class
|
|
711
|
+
class w extends d {
|
|
640
712
|
constructor() {
|
|
641
713
|
super(...arguments), this.type = "eventSourcing", this.eventStore = null;
|
|
642
714
|
}
|
|
@@ -719,11 +791,11 @@ class S extends d {
|
|
|
719
791
|
this.aggregate.constructor.name,
|
|
720
792
|
"Event store not configured"
|
|
721
793
|
);
|
|
722
|
-
const i = await this.eventStore.getEventsAfterVersion(t, e),
|
|
723
|
-
|
|
794
|
+
const i = await this.eventStore.getEventsAfterVersion(t, e), a = this.aggregate;
|
|
795
|
+
a.loadFromHistory && i.length > 0 && a.loadFromHistory(i);
|
|
724
796
|
}
|
|
725
797
|
}
|
|
726
|
-
class
|
|
798
|
+
class S extends d {
|
|
727
799
|
constructor() {
|
|
728
800
|
super(...arguments), this.type = "snapshot", this._snapshot = null;
|
|
729
801
|
}
|
|
@@ -745,15 +817,15 @@ class _ extends d {
|
|
|
745
817
|
* @returns {IAggregateSnapshot<TState, TMeta>} Created snapshot with state and metadata
|
|
746
818
|
*/
|
|
747
819
|
createSnapshot(t, e) {
|
|
748
|
-
const i = this.aggregate.getDomainEvents(),
|
|
820
|
+
const i = this.aggregate.getDomainEvents(), s = (i.length > 0 ? i[i.length - 1] : null)?.metadata?.eventId, g = {
|
|
749
821
|
aggregateId: this.aggregate.getId().toString(),
|
|
750
822
|
version: this.aggregate.getVersion(),
|
|
751
823
|
aggregateType: this.aggregate.constructor.name,
|
|
752
824
|
state: t(),
|
|
753
825
|
timestamp: /* @__PURE__ */ new Date(),
|
|
754
|
-
...
|
|
826
|
+
...s !== void 0 && { lastEventId: s }
|
|
755
827
|
};
|
|
756
|
-
return e && (
|
|
828
|
+
return e && (g.metadata = e()), g;
|
|
757
829
|
}
|
|
758
830
|
/**
|
|
759
831
|
* @param {IAggregateSnapshot<TState, TMeta>} snapshot - Snapshot to restore from
|
|
@@ -764,12 +836,12 @@ class _ extends d {
|
|
|
764
836
|
restoreFromSnapshot(t, e, i) {
|
|
765
837
|
if (!t || !t.state)
|
|
766
838
|
throw r.invalidSnapshot(this.aggregate.constructor.name, "Invalid snapshot");
|
|
767
|
-
const
|
|
768
|
-
if (t.aggregateId !==
|
|
769
|
-
throw r.idMismatch(t.aggregateId,
|
|
839
|
+
const a = this.aggregate.getId().toString();
|
|
840
|
+
if (t.aggregateId !== a)
|
|
841
|
+
throw r.idMismatch(t.aggregateId, a);
|
|
770
842
|
if (t.aggregateType !== this.aggregate.constructor.name)
|
|
771
843
|
throw r.typeMismatch(t.aggregateType, this.aggregate.constructor.name);
|
|
772
|
-
e(t.state), t.metadata && i && i(t.metadata), this.aggregate._internal_setState(
|
|
844
|
+
e(t.state), t.metadata && i && i(t.metadata), this.aggregate._internal_setState(b, {
|
|
773
845
|
version: t.version,
|
|
774
846
|
initialVersion: t.version,
|
|
775
847
|
domainEvents: []
|
|
@@ -817,7 +889,7 @@ class v {
|
|
|
817
889
|
return new v(e);
|
|
818
890
|
}
|
|
819
891
|
withSnapshots() {
|
|
820
|
-
return this.capabilities.push({ capability: new
|
|
892
|
+
return this.capabilities.push({ capability: new S() }), this;
|
|
821
893
|
}
|
|
822
894
|
/**
|
|
823
895
|
* @returns {this} Builder instance for method chaining
|
|
@@ -829,19 +901,19 @@ class v {
|
|
|
829
901
|
* @returns {this} Builder instance for method chaining
|
|
830
902
|
*/
|
|
831
903
|
withAudit() {
|
|
832
|
-
return this.capabilities.push({ capability: new
|
|
904
|
+
return this.capabilities.push({ capability: new _() }), this;
|
|
833
905
|
}
|
|
834
906
|
/**
|
|
835
907
|
* @param {IEventStore} [eventStore] - Optional event store for event sourcing
|
|
836
908
|
* @returns {this} Builder instance for method chaining
|
|
837
909
|
*/
|
|
838
910
|
withEventSourcing(t) {
|
|
839
|
-
const e = new
|
|
911
|
+
const e = new w();
|
|
840
912
|
return this.capabilities.push({
|
|
841
913
|
capability: e,
|
|
842
914
|
configure: (i) => {
|
|
843
|
-
const
|
|
844
|
-
t ?
|
|
915
|
+
const a = i;
|
|
916
|
+
t ? a.setEventStore(t) : this.eventStore && a.setEventStore(this.eventStore);
|
|
845
917
|
}
|
|
846
918
|
}), this;
|
|
847
919
|
}
|
|
@@ -865,8 +937,8 @@ class v {
|
|
|
865
937
|
}
|
|
866
938
|
build(t) {
|
|
867
939
|
const e = t || u, i = new e(this.params);
|
|
868
|
-
for (const { capability:
|
|
869
|
-
i.addCapability(
|
|
940
|
+
for (const { capability: a, configure: s } of this.capabilities)
|
|
941
|
+
i.addCapability(a), s && s(a);
|
|
870
942
|
return i;
|
|
871
943
|
}
|
|
872
944
|
/**
|
|
@@ -877,8 +949,8 @@ class v {
|
|
|
877
949
|
return this.withSnapshots().withVersioning().withAudit().withEventSourcing().build(t);
|
|
878
950
|
}
|
|
879
951
|
}
|
|
880
|
-
function
|
|
881
|
-
return v.create(
|
|
952
|
+
function O(n) {
|
|
953
|
+
return v.create(n);
|
|
882
954
|
}
|
|
883
955
|
class C {
|
|
884
956
|
constructor(t) {
|
|
@@ -902,74 +974,74 @@ class C {
|
|
|
902
974
|
return t == null ? !1 : t === this ? !0 : t instanceof C ? this._id.equals(t._id) : !1;
|
|
903
975
|
}
|
|
904
976
|
}
|
|
905
|
-
function V(
|
|
906
|
-
return
|
|
977
|
+
function V(n) {
|
|
978
|
+
return n.hasCapability(S);
|
|
907
979
|
}
|
|
908
|
-
function I(
|
|
909
|
-
return
|
|
980
|
+
function I(n) {
|
|
981
|
+
return n.hasCapability(p);
|
|
910
982
|
}
|
|
911
|
-
function A(
|
|
912
|
-
return
|
|
983
|
+
function A(n) {
|
|
984
|
+
return n.hasCapability(_);
|
|
913
985
|
}
|
|
914
|
-
function $(
|
|
915
|
-
return
|
|
986
|
+
function $(n) {
|
|
987
|
+
return n.hasCapability(w);
|
|
916
988
|
}
|
|
917
|
-
function
|
|
918
|
-
if (!V(
|
|
989
|
+
function j(n) {
|
|
990
|
+
if (!V(n))
|
|
919
991
|
throw r.featureNotEnabled("snapshot");
|
|
920
|
-
return
|
|
992
|
+
return n;
|
|
921
993
|
}
|
|
922
|
-
function
|
|
923
|
-
return V(
|
|
994
|
+
function k(n) {
|
|
995
|
+
return V(n) ? l.ok(n) : l.fail(r.featureNotEnabled("snapshot"));
|
|
924
996
|
}
|
|
925
|
-
function
|
|
926
|
-
if (!I(
|
|
997
|
+
function P(n) {
|
|
998
|
+
if (!I(n))
|
|
927
999
|
throw r.featureNotEnabled("versioning");
|
|
928
|
-
return
|
|
1000
|
+
return n;
|
|
929
1001
|
}
|
|
930
|
-
function
|
|
931
|
-
return I(
|
|
1002
|
+
function W(n) {
|
|
1003
|
+
return I(n) ? l.ok(n) : l.fail(r.featureNotEnabled("versioning"));
|
|
932
1004
|
}
|
|
933
|
-
function
|
|
934
|
-
if (!A(
|
|
1005
|
+
function z(n) {
|
|
1006
|
+
if (!A(n))
|
|
935
1007
|
throw r.featureNotEnabled("audit");
|
|
936
|
-
return
|
|
1008
|
+
return n;
|
|
937
1009
|
}
|
|
938
|
-
function
|
|
939
|
-
return A(
|
|
1010
|
+
function B(n) {
|
|
1011
|
+
return A(n) ? l.ok(n) : l.fail(r.featureNotEnabled("audit"));
|
|
940
1012
|
}
|
|
941
|
-
function
|
|
942
|
-
if (!$(
|
|
1013
|
+
function R(n) {
|
|
1014
|
+
if (!$(n))
|
|
943
1015
|
throw r.featureNotEnabled("eventSourcing");
|
|
944
|
-
return
|
|
1016
|
+
return n;
|
|
945
1017
|
}
|
|
946
|
-
function
|
|
947
|
-
return $(
|
|
1018
|
+
function q(n) {
|
|
1019
|
+
return $(n) ? l.ok(n) : l.fail(r.featureNotEnabled("eventSourcing"));
|
|
948
1020
|
}
|
|
949
|
-
function
|
|
950
|
-
return
|
|
1021
|
+
function K(n) {
|
|
1022
|
+
return n.getCapabilityTypes();
|
|
951
1023
|
}
|
|
952
|
-
function
|
|
953
|
-
return t.every((e) =>
|
|
1024
|
+
function G(n, t) {
|
|
1025
|
+
return t.every((e) => n.hasCapability(e));
|
|
954
1026
|
}
|
|
955
1027
|
export {
|
|
956
1028
|
v as AggregateBuilder,
|
|
957
1029
|
r as AggregateError,
|
|
958
1030
|
u as AggregateRoot,
|
|
959
|
-
|
|
1031
|
+
_ as AuditCapability,
|
|
960
1032
|
C as Entity,
|
|
961
|
-
|
|
962
|
-
|
|
1033
|
+
w as EventSourcingCapability,
|
|
1034
|
+
S as SnapshotCapability,
|
|
963
1035
|
p as VersioningCapability,
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1036
|
+
O as aggregateBuilder,
|
|
1037
|
+
z as asAuditAggregate,
|
|
1038
|
+
R as asEventSourcingAggregate,
|
|
1039
|
+
j as asSnapshotAggregate,
|
|
1040
|
+
P as asVersioningAggregate,
|
|
1041
|
+
K as getAggregateCapabilities,
|
|
1042
|
+
G as hasAllCapabilities,
|
|
1043
|
+
B as tryAsAuditAggregate,
|
|
1044
|
+
q as tryAsEventSourcingAggregate,
|
|
1045
|
+
k as tryAsSnapshotAggregate,
|
|
1046
|
+
W as tryAsVersioningAggregate
|
|
975
1047
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vytches/ddd-aggregates",
|
|
3
|
-
"version": "0.31.0
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Aggregate root implementations with capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"directory": "packages/aggregates"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@vytches/ddd-contracts": "0.31.0
|
|
38
|
-
"@vytches/ddd-
|
|
39
|
-
"@vytches/ddd-
|
|
40
|
-
"@vytches/ddd-
|
|
37
|
+
"@vytches/ddd-contracts": "0.31.0",
|
|
38
|
+
"@vytches/ddd-utils": "0.31.0",
|
|
39
|
+
"@vytches/ddd-value-objects": "0.31.0",
|
|
40
|
+
"@vytches/ddd-domain-primitives": "0.31.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"typescript": "^5.0.0",
|