@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 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.commit(); // clear pending events after saving
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 b, Result as l } from "@vytches/ddd-utils";
4
- import { IDomainError as L, DomainErrorCode as g } from "@vytches/ddd-domain-primitives";
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, s = t.metadata?.targetVersion || i;
48
- let n = t;
49
- if (i < s && this.upcasters.has(t.eventName)) {
50
- const h = this.upcasters.get(t.eventName);
51
- for (let c = i; c < s; c++) {
52
- const m = h.get(c);
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: c,
59
- toVersion: c + 1
58
+ fromVersion: h,
59
+ toVersion: h + 1
60
60
  }
61
61
  );
62
62
  continue;
63
63
  }
64
- const N = m.upcast(n.payload, n.metadata);
65
- n = {
66
- ...n,
67
- payload: N,
64
+ const D = m.upcast(s.payload, s.metadata);
65
+ s = {
66
+ ...s,
67
+ payload: D,
68
68
  metadata: {
69
- ...n.metadata,
70
- version: c + 1
69
+ ...s.metadata,
70
+ version: h + 1
71
71
  }
72
72
  };
73
73
  }
74
74
  }
75
- const o = e.get(n.eventName);
76
- o && o(n.payload, n.metadata);
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 E = Symbol("vytches-ddd-aggregate-internal-state");
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 M(), this._id = t.id, this._version = t.version || 0, this._initialVersion = t.version || 0, this._maxEvents = t.maxEvents, this._onMissingHandler = t.onMissingHandler ?? "warn";
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
- const t = this._domainEvents.map((e) => b.deepFreeze(e));
188
- return Object.freeze(t);
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 s = this._version + 1;
232
- let n, o;
233
- typeof t == "string" ? (n = T(t, e, i), o = n.metadata) : (n = t, o = i ? { ...n.metadata, ...i } : n.metadata);
234
- const h = u.sanitizeMetadata({
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: s,
310
+ aggregateVersion: a,
239
311
  timestamp: o?.timestamp ?? /* @__PURE__ */ new Date()
240
- }), c = Object.assign(
241
- Object.create(Object.getPrototypeOf(n)),
242
- n,
243
- { metadata: h }
312
+ }), h = Object.assign(
313
+ Object.create(Object.getPrototypeOf(s)),
314
+ s,
315
+ { metadata: g }
244
316
  );
245
- this._version = s, this._domainEvents.push(c), this.handleEvent(c);
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 !== E)
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 w extends d {
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 s = this.originalApply.call(this.aggregate, ...i);
377
- return this.recordAuditEvent(), s;
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((s, n) => {
399
- if (t[s.eventName] = (t[s.eventName] || 0) + 1, n > 0) {
400
- const o = this.auditLog[n - 1];
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 h = new Date(o.timestamp).getTime(), c = new Date(s.timestamp).getTime();
403
- e += c - h;
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-${b.getUUID()}`,
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 s = new Date(i.timestamp).getTime();
456
- return s >= t.getTime() && s <= e.getTime();
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: g.InvalidParameter,
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, s) {
487
- const n = `Version conflict: Aggregate ${t} with ID ${e} has version ${i}, but expected ${s}`, o = {
488
- code: g.ValidationFailed,
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: s
565
+ expectedVersion: a
494
566
  }
495
567
  };
496
- return new r(n, o);
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: g.ValidationFailed,
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`, s = {
515
- code: g.MissingValue,
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, s);
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}` : ""}`, s = {
528
- code: g.InvalidFormat,
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, s);
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}`, s = {
541
- code: g.ValidationFailed,
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, s);
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}`, s = {
554
- code: g.ValidationFailed,
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, s);
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`, s = {
567
- code: g.DuplicateEntry,
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, s);
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 s = `Missing upcaster for event ${t} from version ${e} to ${i}`, n = {
580
- code: g.MissingValue,
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(s, n);
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}`, s = {
594
- code: g.ValidationFailed,
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, s);
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}`, s = {
607
- code: g.InternalError,
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, s);
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: g.MissingValue,
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`, s = {
630
- code: g.MethodNotSupported,
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, s);
708
+ return new r(i, a);
637
709
  }
638
710
  }
639
- class S extends d {
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), s = this.aggregate;
723
- s.loadFromHistory && i.length > 0 && s.loadFromHistory(i);
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 _ extends d {
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(), n = (i.length > 0 ? i[i.length - 1] : null)?.metadata?.eventId, h = {
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
- ...n !== void 0 && { lastEventId: n }
826
+ ...s !== void 0 && { lastEventId: s }
755
827
  };
756
- return e && (h.metadata = e()), h;
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 s = this.aggregate.getId().toString();
768
- if (t.aggregateId !== s)
769
- throw r.idMismatch(t.aggregateId, s);
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(E, {
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 _() }), this;
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 w() }), this;
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 S();
911
+ const e = new w();
840
912
  return this.capabilities.push({
841
913
  capability: e,
842
914
  configure: (i) => {
843
- const s = i;
844
- t ? s.setEventStore(t) : this.eventStore && s.setEventStore(this.eventStore);
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: s, configure: n } of this.capabilities)
869
- i.addCapability(s), n && n(s);
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 U(a) {
881
- return v.create(a);
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(a) {
906
- return a.hasCapability(_);
977
+ function V(n) {
978
+ return n.hasCapability(S);
907
979
  }
908
- function I(a) {
909
- return a.hasCapability(p);
980
+ function I(n) {
981
+ return n.hasCapability(p);
910
982
  }
911
- function A(a) {
912
- return a.hasCapability(w);
983
+ function A(n) {
984
+ return n.hasCapability(_);
913
985
  }
914
- function $(a) {
915
- return a.hasCapability(S);
986
+ function $(n) {
987
+ return n.hasCapability(w);
916
988
  }
917
- function O(a) {
918
- if (!V(a))
989
+ function j(n) {
990
+ if (!V(n))
919
991
  throw r.featureNotEnabled("snapshot");
920
- return a;
992
+ return n;
921
993
  }
922
- function j(a) {
923
- return V(a) ? l.ok(a) : l.fail(r.featureNotEnabled("snapshot"));
994
+ function k(n) {
995
+ return V(n) ? l.ok(n) : l.fail(r.featureNotEnabled("snapshot"));
924
996
  }
925
- function k(a) {
926
- if (!I(a))
997
+ function P(n) {
998
+ if (!I(n))
927
999
  throw r.featureNotEnabled("versioning");
928
- return a;
1000
+ return n;
929
1001
  }
930
- function P(a) {
931
- return I(a) ? l.ok(a) : l.fail(r.featureNotEnabled("versioning"));
1002
+ function W(n) {
1003
+ return I(n) ? l.ok(n) : l.fail(r.featureNotEnabled("versioning"));
932
1004
  }
933
- function W(a) {
934
- if (!A(a))
1005
+ function z(n) {
1006
+ if (!A(n))
935
1007
  throw r.featureNotEnabled("audit");
936
- return a;
1008
+ return n;
937
1009
  }
938
- function z(a) {
939
- return A(a) ? l.ok(a) : l.fail(r.featureNotEnabled("audit"));
1010
+ function B(n) {
1011
+ return A(n) ? l.ok(n) : l.fail(r.featureNotEnabled("audit"));
940
1012
  }
941
- function B(a) {
942
- if (!$(a))
1013
+ function R(n) {
1014
+ if (!$(n))
943
1015
  throw r.featureNotEnabled("eventSourcing");
944
- return a;
1016
+ return n;
945
1017
  }
946
- function R(a) {
947
- return $(a) ? l.ok(a) : l.fail(r.featureNotEnabled("eventSourcing"));
1018
+ function q(n) {
1019
+ return $(n) ? l.ok(n) : l.fail(r.featureNotEnabled("eventSourcing"));
948
1020
  }
949
- function q(a) {
950
- return a.getCapabilityTypes();
1021
+ function K(n) {
1022
+ return n.getCapabilityTypes();
951
1023
  }
952
- function K(a, t) {
953
- return t.every((e) => a.hasCapability(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
- w as AuditCapability,
1031
+ _ as AuditCapability,
960
1032
  C as Entity,
961
- S as EventSourcingCapability,
962
- _ as SnapshotCapability,
1033
+ w as EventSourcingCapability,
1034
+ S as SnapshotCapability,
963
1035
  p as VersioningCapability,
964
- U as aggregateBuilder,
965
- W as asAuditAggregate,
966
- B as asEventSourcingAggregate,
967
- O as asSnapshotAggregate,
968
- k as asVersioningAggregate,
969
- q as getAggregateCapabilities,
970
- K as hasAllCapabilities,
971
- z as tryAsAuditAggregate,
972
- R as tryAsEventSourcingAggregate,
973
- j as tryAsSnapshotAggregate,
974
- P as tryAsVersioningAggregate
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-alpha.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-alpha.0",
38
- "@vytches/ddd-value-objects": "0.31.0-alpha.0",
39
- "@vytches/ddd-domain-primitives": "0.31.0-alpha.0",
40
- "@vytches/ddd-utils": "0.31.0-alpha.0"
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",