@vytches/ddd-aggregates 0.30.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 +99 -31
- package/README.md +5 -1
- package/dist/aggregate-interfaces.d.ts +16 -184
- package/dist/capabilities/audit-capability.d.ts +1 -1
- package/dist/capabilities/event-sourcing-capability.d.ts +1 -1
- package/dist/capabilities/snapshot-capability.d.ts +1 -1
- package/dist/capabilities/versioning-capability.d.ts +15 -2
- package/dist/core/aggregate-root.builder.d.ts +1 -1
- package/dist/core/aggregate-root.d.ts +171 -5
- package/dist/core/aggregate-utilities.d.ts +2 -2
- package/dist/core/entity.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +298 -139
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Capability as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
class p extends
|
|
1
|
+
import { Capability as d, CapabilityRegistry as N, enrichEvent as M, createDomainEvent as T, EntityId as f } from "@vytches/ddd-contracts";
|
|
2
|
+
import { internalLogger as y } from "@vytches/ddd-contracts/internal";
|
|
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
|
+
class p extends d {
|
|
6
6
|
constructor() {
|
|
7
7
|
super(...arguments), this.type = "versioning", this.upcasters = /* @__PURE__ */ new Map();
|
|
8
8
|
}
|
|
@@ -27,40 +27,53 @@ class p extends l {
|
|
|
27
27
|
this.upcasters.has(t) || this.upcasters.set(t, /* @__PURE__ */ new Map()), this.upcasters.get(t).set(e, i);
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Applies registered upcasters to bring `event` from its stored version up
|
|
31
|
+
* to the aggregate's current version, then invokes the matching handler.
|
|
32
|
+
*
|
|
33
|
+
* **Upcasting only activates if `event.metadata.targetVersion` is set.**
|
|
34
|
+
* This is NOT inferred automatically from registered upcasters or from
|
|
35
|
+
* any aggregate-level "current version" — the caller (typically your
|
|
36
|
+
* event-sourcing replay/load path) must explicitly stamp each historical
|
|
37
|
+
* event's metadata with `targetVersion` before calling this method. If
|
|
38
|
+
* `targetVersion` is absent, `currentVersion` falls back to `eventVersion`
|
|
39
|
+
* (i.e. no upcasting happens — the event is passed through unchanged even
|
|
40
|
+
* if upcasters are registered for it).
|
|
41
|
+
*
|
|
42
|
+
* @param {IDomainEvent} event - Event to process with version handling;
|
|
43
|
+
* set `event.metadata.targetVersion` to enable upcasting
|
|
31
44
|
* @param {Map<string, IAggregateEventHandler>} handlers - Map of event handlers
|
|
32
45
|
*/
|
|
33
46
|
handleVersionedEvent(t, e) {
|
|
34
47
|
const i = t.metadata?.version || 1, a = t.metadata?.targetVersion || i;
|
|
35
|
-
let
|
|
48
|
+
let s = t;
|
|
36
49
|
if (i < a && this.upcasters.has(t.eventName)) {
|
|
37
|
-
const g = this.upcasters.get(t.eventName)
|
|
38
|
-
for (let
|
|
39
|
-
const
|
|
40
|
-
if (!
|
|
41
|
-
|
|
42
|
-
"Missing upcaster for event version — event will be processed without upcast",
|
|
50
|
+
const g = this.upcasters.get(t.eventName);
|
|
51
|
+
for (let h = i; h < a; h++) {
|
|
52
|
+
const m = g.get(h);
|
|
53
|
+
if (!m) {
|
|
54
|
+
y.warn(
|
|
55
|
+
"VersioningCapability: Missing upcaster for event version — event will be processed without upcast",
|
|
43
56
|
{
|
|
44
57
|
eventName: t.eventName,
|
|
45
|
-
fromVersion:
|
|
46
|
-
toVersion:
|
|
58
|
+
fromVersion: h,
|
|
59
|
+
toVersion: h + 1
|
|
47
60
|
}
|
|
48
61
|
);
|
|
49
62
|
continue;
|
|
50
63
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
...
|
|
54
|
-
payload:
|
|
64
|
+
const D = m.upcast(s.payload, s.metadata);
|
|
65
|
+
s = {
|
|
66
|
+
...s,
|
|
67
|
+
payload: D,
|
|
55
68
|
metadata: {
|
|
56
|
-
...
|
|
57
|
-
version:
|
|
69
|
+
...s.metadata,
|
|
70
|
+
version: h + 1
|
|
58
71
|
}
|
|
59
72
|
};
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
75
|
+
const o = e.get(s.eventName);
|
|
76
|
+
o && o(s.payload, s.metadata);
|
|
64
77
|
}
|
|
65
78
|
/**
|
|
66
79
|
* @returns {string[]} Array of event types that have registered upcasters
|
|
@@ -99,9 +112,10 @@ class p extends l {
|
|
|
99
112
|
return t;
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
|
-
|
|
115
|
+
const b = Symbol("vytches-ddd-aggregate-internal-state");
|
|
116
|
+
class u {
|
|
103
117
|
constructor(t) {
|
|
104
|
-
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";
|
|
105
119
|
}
|
|
106
120
|
// ==========================================
|
|
107
121
|
// CORE AGGREGATE FUNCTIONALITY
|
|
@@ -113,6 +127,24 @@ class v {
|
|
|
113
127
|
return this._id;
|
|
114
128
|
}
|
|
115
129
|
/**
|
|
130
|
+
* Identity-based equality, matching {@link Entity.equals}. Two aggregates
|
|
131
|
+
* are equal iff they have the same id (compared via `EntityId.equals()`);
|
|
132
|
+
* attribute/version differences are ignored.
|
|
133
|
+
*
|
|
134
|
+
* VF-023 (D-5, AC10, non-breaking addition): `AggregateRoot` does not
|
|
135
|
+
* extend `Entity` (it implements `IAggregateRoot<TId>` directly), so this
|
|
136
|
+
* mirrors `Entity.equals()` rather than inheriting it.
|
|
137
|
+
*
|
|
138
|
+
* @param other - Aggregate to compare against. May be `null` or `undefined`.
|
|
139
|
+
* @returns `true` if both aggregates have the same id; `false` otherwise.
|
|
140
|
+
*/
|
|
141
|
+
equals(t) {
|
|
142
|
+
return t == null ? !1 : t === this ? !0 : t instanceof u ? this._id.equals(t._id) : !1;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The version increments by one each time `apply()` successfully records a
|
|
146
|
+
* domain event; compare it against the persisted version to detect
|
|
147
|
+
* concurrent modification (optimistic locking).
|
|
116
148
|
*
|
|
117
149
|
* @returns {number} Current version number for optimistic locking
|
|
118
150
|
*/
|
|
@@ -120,6 +152,9 @@ class v {
|
|
|
120
152
|
return this._version;
|
|
121
153
|
}
|
|
122
154
|
/**
|
|
155
|
+
* The version this aggregate had when it was loaded (or as of the last
|
|
156
|
+
* `commit()`), letting callers work out how many events have been applied
|
|
157
|
+
* since without recomputing from the event log.
|
|
123
158
|
*
|
|
124
159
|
* @returns {number} Initial version when aggregate was loaded
|
|
125
160
|
*/
|
|
@@ -127,6 +162,9 @@ class v {
|
|
|
127
162
|
return this._initialVersion;
|
|
128
163
|
}
|
|
129
164
|
/**
|
|
165
|
+
* Whether this aggregate has recorded domain events since its last
|
|
166
|
+
* `commit()` — use it to decide whether persistence/dispatch work is
|
|
167
|
+
* needed at all.
|
|
130
168
|
*
|
|
131
169
|
* @returns {boolean} True if aggregate has uncommitted domain events
|
|
132
170
|
*/
|
|
@@ -134,17 +172,98 @@ class v {
|
|
|
134
172
|
return this._domainEvents.length > 0;
|
|
135
173
|
}
|
|
136
174
|
/**
|
|
175
|
+
* VF-023 (D-3, AC11, BREAKING): the returned array and every event object
|
|
176
|
+
* it contains (including nested `payload`/`metadata`) are now deep-frozen
|
|
177
|
+
* via `LibUtils.deepFreeze` — the same immutability guarantee applied to
|
|
178
|
+
* `BaseValueObject` values (see `base-value-object.ts`). Previously this
|
|
179
|
+
* returned only a shallow array copy: the array itself couldn't be
|
|
180
|
+
* mutated by callers, but the event objects (and their nested payload
|
|
181
|
+
* data) inside it could still be mutated in place, silently corrupting
|
|
182
|
+
* state shared with `_domainEvents` (the same object references are
|
|
183
|
+
* stored internally — events are never re-cloned after creation).
|
|
137
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).
|
|
138
201
|
* @returns {ReadonlyArray<IDomainEvent>} Array of uncommitted domain events
|
|
139
202
|
*/
|
|
140
203
|
getDomainEvents() {
|
|
141
|
-
|
|
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;
|
|
142
220
|
}
|
|
143
221
|
/**
|
|
144
222
|
* Clear all uncommitted domain events and update initial version
|
|
145
223
|
*/
|
|
146
224
|
commit() {
|
|
147
|
-
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();
|
|
148
267
|
}
|
|
149
268
|
// ==========================================
|
|
150
269
|
// EVENT HANDLING - Type-safe approach
|
|
@@ -162,27 +281,40 @@ class v {
|
|
|
162
281
|
* @param {string | IDomainEvent<P>} eventTypeOrEvent - Event type string or complete event object
|
|
163
282
|
* @param {P} payload - Event payload data
|
|
164
283
|
* @param {Partial<IEventMetadata>} metadata - Optional event metadata
|
|
284
|
+
* @throws {Error} when the aggregate's configured maxEvents limit would be exceeded
|
|
285
|
+
* @example Hitting the maxEvents guard
|
|
286
|
+
* ```typescript
|
|
287
|
+
* class Order extends AggregateRoot<string> {
|
|
288
|
+
* record(customerId: string, amount: number): void {
|
|
289
|
+
* this.apply('OrderCreated', { customerId, amount });
|
|
290
|
+
* }
|
|
291
|
+
* }
|
|
292
|
+
*
|
|
293
|
+
* const order = new Order({ id: EntityId.create(), version: 0, maxEvents: 1 });
|
|
294
|
+
* order.record('c-1', 100); // ok, 1 event recorded
|
|
295
|
+
* order.record('c-1', 100); // throws: maxEvents limit of 1 exceeded
|
|
296
|
+
* ```
|
|
165
297
|
*/
|
|
166
298
|
apply(t, e, i) {
|
|
167
|
-
this._version++;
|
|
168
|
-
let a, n;
|
|
169
|
-
typeof t == "string" ? (a = N(t, e, i), n = a.metadata) : (a = t, n = i ? { ...a.metadata, ...i } : a.metadata);
|
|
170
|
-
const c = v.sanitizeMetadata({
|
|
171
|
-
...n,
|
|
172
|
-
aggregateId: this._id.toString(),
|
|
173
|
-
aggregateType: this.constructor.name,
|
|
174
|
-
aggregateVersion: this._version,
|
|
175
|
-
timestamp: n?.timestamp ?? /* @__PURE__ */ new Date()
|
|
176
|
-
}), g = Object.assign(
|
|
177
|
-
Object.create(Object.getPrototypeOf(a)),
|
|
178
|
-
a,
|
|
179
|
-
{ metadata: c }
|
|
180
|
-
);
|
|
181
299
|
if (this._maxEvents !== void 0 && this._domainEvents.length >= this._maxEvents)
|
|
182
300
|
throw new Error(
|
|
183
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.`
|
|
184
302
|
);
|
|
185
|
-
this.
|
|
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({
|
|
307
|
+
...o,
|
|
308
|
+
aggregateId: this._id.toString(),
|
|
309
|
+
aggregateType: this.constructor.name,
|
|
310
|
+
aggregateVersion: a,
|
|
311
|
+
timestamp: o?.timestamp ?? /* @__PURE__ */ new Date()
|
|
312
|
+
}), h = Object.assign(
|
|
313
|
+
Object.create(Object.getPrototypeOf(s)),
|
|
314
|
+
s,
|
|
315
|
+
{ metadata: g }
|
|
316
|
+
);
|
|
317
|
+
this._version = a, this._domainEvents.push(h), this._invalidateDomainEventsCache(), this.handleEvent(h);
|
|
186
318
|
}
|
|
187
319
|
handleEvent(t) {
|
|
188
320
|
const e = this.getCapability(p);
|
|
@@ -190,15 +322,30 @@ class v {
|
|
|
190
322
|
e.handleVersionedEvent(t, this._eventHandlers);
|
|
191
323
|
else {
|
|
192
324
|
const i = this._eventHandlers.get(t.eventName);
|
|
193
|
-
i
|
|
325
|
+
i ? i(t.payload, t.metadata) : this.reportMissingHandler(t.eventName);
|
|
194
326
|
}
|
|
195
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* VF-023 (D-4, AC6): called from `handleEvent()`, which is shared by both
|
|
330
|
+
* `apply()` (live) and `loadFromHistory()` (replay) — so the missing-
|
|
331
|
+
* handler policy applies identically to both paths.
|
|
332
|
+
*/
|
|
333
|
+
reportMissingHandler(t) {
|
|
334
|
+
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.`;
|
|
335
|
+
if (this._onMissingHandler === "throw")
|
|
336
|
+
throw new Error(e);
|
|
337
|
+
y.warn(e, {
|
|
338
|
+
aggregateType: this.constructor.name,
|
|
339
|
+
aggregateId: this._id.toString(),
|
|
340
|
+
eventName: t
|
|
341
|
+
});
|
|
342
|
+
}
|
|
196
343
|
/**
|
|
197
344
|
* Reconstruct aggregate state from historical domain events
|
|
198
345
|
* @param {IDomainEvent[]} events - Array of events to replay for state reconstruction
|
|
199
346
|
*/
|
|
200
347
|
loadFromHistory(t) {
|
|
201
|
-
this._version = this._initialVersion, this._domainEvents = [];
|
|
348
|
+
this._version = this._initialVersion, this._domainEvents = [], this._invalidateDomainEventsCache();
|
|
202
349
|
for (const e of t)
|
|
203
350
|
this.handleEvent(e), this._version++;
|
|
204
351
|
this._initialVersion = this._version;
|
|
@@ -263,14 +410,28 @@ class v {
|
|
|
263
410
|
// INTERNAL ACCESS FOR CAPABILITIES
|
|
264
411
|
// ==========================================
|
|
265
412
|
/**
|
|
266
|
-
* Internal method for capabilities to access private state
|
|
267
|
-
*
|
|
413
|
+
* Internal method for capabilities to access private state.
|
|
414
|
+
*
|
|
415
|
+
* VF-023 (D-2, AC5, BREAKING, CRITICAL F-H4): gated by
|
|
416
|
+
* {@link INTERNAL_STATE_TOKEN}, a module-private `unique symbol` that is
|
|
417
|
+
* exported only from this module (not from the package's public barrel).
|
|
418
|
+
* Callers must supply the exact token reference to invoke this method —
|
|
419
|
+
* consumers of `@vytches/ddd-aggregates` cannot obtain it, so this method
|
|
420
|
+
* is unreachable from outside this package's own source, even via
|
|
421
|
+
* `as unknown as {...}` casts (the token value itself, not just its type,
|
|
422
|
+
* is required and cannot be forged).
|
|
423
|
+
*
|
|
424
|
+
* @throws {TypeError} if `token` does not match {@link INTERNAL_STATE_TOKEN}
|
|
268
425
|
*/
|
|
269
|
-
_internal_setState(t) {
|
|
270
|
-
|
|
426
|
+
_internal_setState(t, e) {
|
|
427
|
+
if (t !== b)
|
|
428
|
+
throw new TypeError(
|
|
429
|
+
"_internal_setState: invalid or missing internal state token. This method is restricted to capabilities within @vytches/ddd-aggregates."
|
|
430
|
+
);
|
|
431
|
+
this._version = e.version, this._initialVersion = e.initialVersion, this._domainEvents = [...e.domainEvents], this._invalidateDomainEventsCache();
|
|
271
432
|
}
|
|
272
433
|
}
|
|
273
|
-
class
|
|
434
|
+
class _ extends d {
|
|
274
435
|
constructor() {
|
|
275
436
|
super(...arguments), this.type = "audit", this.auditLog = [];
|
|
276
437
|
}
|
|
@@ -306,12 +467,12 @@ class b extends l {
|
|
|
306
467
|
getAuditStatistics() {
|
|
307
468
|
const t = {};
|
|
308
469
|
let e = 0;
|
|
309
|
-
this.auditLog.forEach((a,
|
|
310
|
-
if (t[a.eventName] = (t[a.eventName] || 0) + 1,
|
|
311
|
-
const
|
|
312
|
-
if (
|
|
313
|
-
const g = new Date(
|
|
314
|
-
e +=
|
|
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];
|
|
473
|
+
if (o) {
|
|
474
|
+
const g = new Date(o.timestamp).getTime(), h = new Date(a.timestamp).getTime();
|
|
475
|
+
e += h - g;
|
|
315
476
|
}
|
|
316
477
|
}
|
|
317
478
|
});
|
|
@@ -334,7 +495,7 @@ class b extends l {
|
|
|
334
495
|
*/
|
|
335
496
|
recordEvent(t) {
|
|
336
497
|
const e = {
|
|
337
|
-
eventId: t.metadata?.eventId || `audit-${
|
|
498
|
+
eventId: t.metadata?.eventId || `audit-${E.getUUID()}`,
|
|
338
499
|
eventName: t.eventName,
|
|
339
500
|
aggregateId: this.aggregate.getId().toString(),
|
|
340
501
|
aggregateType: this.aggregate.constructor.name,
|
|
@@ -380,13 +541,13 @@ class b extends l {
|
|
|
380
541
|
return this.auditLog[this.auditLog.length - 1] || null;
|
|
381
542
|
}
|
|
382
543
|
}
|
|
383
|
-
class r extends
|
|
544
|
+
class r extends L {
|
|
384
545
|
/**
|
|
385
546
|
* Error for invalid arguments provided to the aggregate
|
|
386
547
|
*/
|
|
387
548
|
static invalidArguments(t, e) {
|
|
388
549
|
const i = {
|
|
389
|
-
code:
|
|
550
|
+
code: c.InvalidParameter,
|
|
390
551
|
data: e
|
|
391
552
|
};
|
|
392
553
|
return new r(t, i);
|
|
@@ -395,8 +556,8 @@ class r extends M {
|
|
|
395
556
|
* Error for version conflicts in the aggregate
|
|
396
557
|
*/
|
|
397
558
|
static versionConflict(t, e, i, a) {
|
|
398
|
-
const
|
|
399
|
-
code:
|
|
559
|
+
const s = `Version conflict: Aggregate ${t} with ID ${e} has version ${i}, but expected ${a}`, o = {
|
|
560
|
+
code: c.ValidationFailed,
|
|
400
561
|
data: {
|
|
401
562
|
aggregateType: t,
|
|
402
563
|
aggregateId: e,
|
|
@@ -404,14 +565,14 @@ class r extends M {
|
|
|
404
565
|
expectedVersion: a
|
|
405
566
|
}
|
|
406
567
|
};
|
|
407
|
-
return new r(
|
|
568
|
+
return new r(s, o);
|
|
408
569
|
}
|
|
409
570
|
/**
|
|
410
571
|
* Error for when a required feature is not enabled
|
|
411
572
|
*/
|
|
412
573
|
static featureNotEnabled(t) {
|
|
413
574
|
const e = `Feature '${t}' is not enabled on aggregate`, i = {
|
|
414
|
-
code:
|
|
575
|
+
code: c.ValidationFailed,
|
|
415
576
|
data: {
|
|
416
577
|
feature: t
|
|
417
578
|
}
|
|
@@ -423,7 +584,7 @@ class r extends M {
|
|
|
423
584
|
*/
|
|
424
585
|
static methodNotImplemented(t, e) {
|
|
425
586
|
const i = `Method '${t}' must be implemented by ${e} to use this feature`, a = {
|
|
426
|
-
code:
|
|
587
|
+
code: c.MissingValue,
|
|
427
588
|
data: {
|
|
428
589
|
methodName: t,
|
|
429
590
|
aggregateType: e
|
|
@@ -436,7 +597,7 @@ class r extends M {
|
|
|
436
597
|
*/
|
|
437
598
|
static invalidSnapshot(t, e) {
|
|
438
599
|
const i = `Invalid snapshot for aggregate ${t}${e ? `: ${e}` : ""}`, a = {
|
|
439
|
-
code:
|
|
600
|
+
code: c.InvalidFormat,
|
|
440
601
|
data: {
|
|
441
602
|
aggregateType: t,
|
|
442
603
|
reason: e
|
|
@@ -449,7 +610,7 @@ class r extends M {
|
|
|
449
610
|
*/
|
|
450
611
|
static idMismatch(t, e) {
|
|
451
612
|
const i = `ID mismatch: Snapshot is for ID ${t}, but aggregate has ID ${e}`, a = {
|
|
452
|
-
code:
|
|
613
|
+
code: c.ValidationFailed,
|
|
453
614
|
data: {
|
|
454
615
|
snapshotId: t,
|
|
455
616
|
aggregateId: e
|
|
@@ -462,7 +623,7 @@ class r extends M {
|
|
|
462
623
|
*/
|
|
463
624
|
static typeMismatch(t, e) {
|
|
464
625
|
const i = `Aggregate type mismatch: Snapshot is for type ${t}, but trying to load into ${e}`, a = {
|
|
465
|
-
code:
|
|
626
|
+
code: c.ValidationFailed,
|
|
466
627
|
data: {
|
|
467
628
|
snapshotType: t,
|
|
468
629
|
aggregateType: e
|
|
@@ -475,7 +636,7 @@ class r extends M {
|
|
|
475
636
|
*/
|
|
476
637
|
static duplicateUpcaster(t, e) {
|
|
477
638
|
const i = `Upcaster for event ${t} version ${e} already exists`, a = {
|
|
478
|
-
code:
|
|
639
|
+
code: c.DuplicateEntry,
|
|
479
640
|
data: {
|
|
480
641
|
eventType: t,
|
|
481
642
|
sourceVersion: e
|
|
@@ -487,22 +648,22 @@ class r extends M {
|
|
|
487
648
|
* Error for missing upcaster
|
|
488
649
|
*/
|
|
489
650
|
static missingUpcaster(t, e, i) {
|
|
490
|
-
const a = `Missing upcaster for event ${t} from version ${e} to ${i}`,
|
|
491
|
-
code:
|
|
651
|
+
const a = `Missing upcaster for event ${t} from version ${e} to ${i}`, s = {
|
|
652
|
+
code: c.MissingValue,
|
|
492
653
|
data: {
|
|
493
654
|
eventType: t,
|
|
494
655
|
fromVersion: e,
|
|
495
656
|
toVersion: i
|
|
496
657
|
}
|
|
497
658
|
};
|
|
498
|
-
return new r(a,
|
|
659
|
+
return new r(a, s);
|
|
499
660
|
}
|
|
500
661
|
/**
|
|
501
662
|
* Error for configuration problems
|
|
502
663
|
*/
|
|
503
664
|
static configurationError(t, e) {
|
|
504
665
|
const i = `Configuration error in ${t}: ${e}`, a = {
|
|
505
|
-
code:
|
|
666
|
+
code: c.ValidationFailed,
|
|
506
667
|
data: {
|
|
507
668
|
aggregateType: t,
|
|
508
669
|
configurationError: e
|
|
@@ -515,7 +676,7 @@ class r extends M {
|
|
|
515
676
|
*/
|
|
516
677
|
static cannotInterceptApplyMethod(t, e) {
|
|
517
678
|
const i = `Cannot intercept apply method - method not found on aggregate ${t}`, a = {
|
|
518
|
-
code:
|
|
679
|
+
code: c.InternalError,
|
|
519
680
|
data: {
|
|
520
681
|
aggergateId: t,
|
|
521
682
|
...e
|
|
@@ -528,7 +689,7 @@ class r extends M {
|
|
|
528
689
|
*/
|
|
529
690
|
static eventStoreNotConfigured(t) {
|
|
530
691
|
const e = "Event store not configured", i = {
|
|
531
|
-
code:
|
|
692
|
+
code: c.MissingValue,
|
|
532
693
|
data: t
|
|
533
694
|
};
|
|
534
695
|
return new r(e, i);
|
|
@@ -538,7 +699,7 @@ class r extends M {
|
|
|
538
699
|
*/
|
|
539
700
|
static aggregateDoesNotSupportReplay(t, e) {
|
|
540
701
|
const i = `Aggregate ${t} does not support event replay`, a = {
|
|
541
|
-
code:
|
|
702
|
+
code: c.MethodNotSupported,
|
|
542
703
|
data: {
|
|
543
704
|
aggregateType: t,
|
|
544
705
|
...e
|
|
@@ -547,7 +708,7 @@ class r extends M {
|
|
|
547
708
|
return new r(i, a);
|
|
548
709
|
}
|
|
549
710
|
}
|
|
550
|
-
class
|
|
711
|
+
class w extends d {
|
|
551
712
|
constructor() {
|
|
552
713
|
super(...arguments), this.type = "eventSourcing", this.eventStore = null;
|
|
553
714
|
}
|
|
@@ -634,7 +795,7 @@ class E extends l {
|
|
|
634
795
|
a.loadFromHistory && i.length > 0 && a.loadFromHistory(i);
|
|
635
796
|
}
|
|
636
797
|
}
|
|
637
|
-
class S extends
|
|
798
|
+
class S extends d {
|
|
638
799
|
constructor() {
|
|
639
800
|
super(...arguments), this.type = "snapshot", this._snapshot = null;
|
|
640
801
|
}
|
|
@@ -656,13 +817,13 @@ class S extends l {
|
|
|
656
817
|
* @returns {IAggregateSnapshot<TState, TMeta>} Created snapshot with state and metadata
|
|
657
818
|
*/
|
|
658
819
|
createSnapshot(t, e) {
|
|
659
|
-
const i = this.aggregate.getDomainEvents(),
|
|
820
|
+
const i = this.aggregate.getDomainEvents(), s = (i.length > 0 ? i[i.length - 1] : null)?.metadata?.eventId, g = {
|
|
660
821
|
aggregateId: this.aggregate.getId().toString(),
|
|
661
822
|
version: this.aggregate.getVersion(),
|
|
662
823
|
aggregateType: this.aggregate.constructor.name,
|
|
663
824
|
state: t(),
|
|
664
825
|
timestamp: /* @__PURE__ */ new Date(),
|
|
665
|
-
...
|
|
826
|
+
...s !== void 0 && { lastEventId: s }
|
|
666
827
|
};
|
|
667
828
|
return e && (g.metadata = e()), g;
|
|
668
829
|
}
|
|
@@ -680,9 +841,7 @@ class S extends l {
|
|
|
680
841
|
throw r.idMismatch(t.aggregateId, a);
|
|
681
842
|
if (t.aggregateType !== this.aggregate.constructor.name)
|
|
682
843
|
throw r.typeMismatch(t.aggregateType, this.aggregate.constructor.name);
|
|
683
|
-
e(t.state), t.metadata && i && i(t.metadata)
|
|
684
|
-
const n = this.aggregate;
|
|
685
|
-
n._internal_setState && n._internal_setState({
|
|
844
|
+
e(t.state), t.metadata && i && i(t.metadata), this.aggregate._internal_setState(b, {
|
|
686
845
|
version: t.version,
|
|
687
846
|
initialVersion: t.version,
|
|
688
847
|
domainEvents: []
|
|
@@ -715,7 +874,7 @@ class S extends l {
|
|
|
715
874
|
return this._snapshot = null, t;
|
|
716
875
|
}
|
|
717
876
|
}
|
|
718
|
-
class
|
|
877
|
+
class v {
|
|
719
878
|
constructor(t) {
|
|
720
879
|
this.capabilities = [], this.params = t;
|
|
721
880
|
}
|
|
@@ -724,10 +883,10 @@ class m {
|
|
|
724
883
|
*/
|
|
725
884
|
static create(t) {
|
|
726
885
|
const e = {
|
|
727
|
-
id: t.id instanceof
|
|
886
|
+
id: t.id instanceof f ? t.id : new f(t.id, "text"),
|
|
728
887
|
version: t.version || 0
|
|
729
888
|
};
|
|
730
|
-
return new
|
|
889
|
+
return new v(e);
|
|
731
890
|
}
|
|
732
891
|
withSnapshots() {
|
|
733
892
|
return this.capabilities.push({ capability: new S() }), this;
|
|
@@ -742,14 +901,14 @@ class m {
|
|
|
742
901
|
* @returns {this} Builder instance for method chaining
|
|
743
902
|
*/
|
|
744
903
|
withAudit() {
|
|
745
|
-
return this.capabilities.push({ capability: new
|
|
904
|
+
return this.capabilities.push({ capability: new _() }), this;
|
|
746
905
|
}
|
|
747
906
|
/**
|
|
748
907
|
* @param {IEventStore} [eventStore] - Optional event store for event sourcing
|
|
749
908
|
* @returns {this} Builder instance for method chaining
|
|
750
909
|
*/
|
|
751
910
|
withEventSourcing(t) {
|
|
752
|
-
const e = new
|
|
911
|
+
const e = new w();
|
|
753
912
|
return this.capabilities.push({
|
|
754
913
|
capability: e,
|
|
755
914
|
configure: (i) => {
|
|
@@ -777,9 +936,9 @@ class m {
|
|
|
777
936
|
return this.eventStore = t, this;
|
|
778
937
|
}
|
|
779
938
|
build(t) {
|
|
780
|
-
const e = t ||
|
|
781
|
-
for (const { capability: a, configure:
|
|
782
|
-
i.addCapability(a),
|
|
939
|
+
const e = t || u, i = new e(this.params);
|
|
940
|
+
for (const { capability: a, configure: s } of this.capabilities)
|
|
941
|
+
i.addCapability(a), s && s(a);
|
|
783
942
|
return i;
|
|
784
943
|
}
|
|
785
944
|
/**
|
|
@@ -790,10 +949,10 @@ class m {
|
|
|
790
949
|
return this.withSnapshots().withVersioning().withAudit().withEventSourcing().build(t);
|
|
791
950
|
}
|
|
792
951
|
}
|
|
793
|
-
function
|
|
794
|
-
return
|
|
952
|
+
function O(n) {
|
|
953
|
+
return v.create(n);
|
|
795
954
|
}
|
|
796
|
-
class
|
|
955
|
+
class C {
|
|
797
956
|
constructor(t) {
|
|
798
957
|
this._id = t;
|
|
799
958
|
}
|
|
@@ -812,77 +971,77 @@ class w {
|
|
|
812
971
|
* @returns `true` if both entities have the same id; `false` otherwise.
|
|
813
972
|
*/
|
|
814
973
|
equals(t) {
|
|
815
|
-
return t == null ? !1 : t === this ? !0 : t instanceof
|
|
974
|
+
return t == null ? !1 : t === this ? !0 : t instanceof C ? this._id.equals(t._id) : !1;
|
|
816
975
|
}
|
|
817
976
|
}
|
|
818
|
-
function
|
|
819
|
-
return
|
|
977
|
+
function V(n) {
|
|
978
|
+
return n.hasCapability(S);
|
|
820
979
|
}
|
|
821
|
-
function
|
|
822
|
-
return
|
|
980
|
+
function I(n) {
|
|
981
|
+
return n.hasCapability(p);
|
|
823
982
|
}
|
|
824
|
-
function
|
|
825
|
-
return
|
|
983
|
+
function A(n) {
|
|
984
|
+
return n.hasCapability(_);
|
|
826
985
|
}
|
|
827
|
-
function
|
|
828
|
-
return
|
|
986
|
+
function $(n) {
|
|
987
|
+
return n.hasCapability(w);
|
|
829
988
|
}
|
|
830
|
-
function
|
|
831
|
-
if (!
|
|
989
|
+
function j(n) {
|
|
990
|
+
if (!V(n))
|
|
832
991
|
throw r.featureNotEnabled("snapshot");
|
|
833
|
-
return
|
|
992
|
+
return n;
|
|
834
993
|
}
|
|
835
|
-
function
|
|
836
|
-
return
|
|
994
|
+
function k(n) {
|
|
995
|
+
return V(n) ? l.ok(n) : l.fail(r.featureNotEnabled("snapshot"));
|
|
837
996
|
}
|
|
838
|
-
function P(
|
|
839
|
-
if (!
|
|
997
|
+
function P(n) {
|
|
998
|
+
if (!I(n))
|
|
840
999
|
throw r.featureNotEnabled("versioning");
|
|
841
|
-
return
|
|
1000
|
+
return n;
|
|
842
1001
|
}
|
|
843
|
-
function
|
|
844
|
-
return
|
|
1002
|
+
function W(n) {
|
|
1003
|
+
return I(n) ? l.ok(n) : l.fail(r.featureNotEnabled("versioning"));
|
|
845
1004
|
}
|
|
846
|
-
function
|
|
847
|
-
if (!
|
|
1005
|
+
function z(n) {
|
|
1006
|
+
if (!A(n))
|
|
848
1007
|
throw r.featureNotEnabled("audit");
|
|
849
|
-
return
|
|
1008
|
+
return n;
|
|
850
1009
|
}
|
|
851
|
-
function B(
|
|
852
|
-
return
|
|
1010
|
+
function B(n) {
|
|
1011
|
+
return A(n) ? l.ok(n) : l.fail(r.featureNotEnabled("audit"));
|
|
853
1012
|
}
|
|
854
|
-
function
|
|
855
|
-
if (
|
|
1013
|
+
function R(n) {
|
|
1014
|
+
if (!$(n))
|
|
856
1015
|
throw r.featureNotEnabled("eventSourcing");
|
|
857
|
-
return
|
|
1016
|
+
return n;
|
|
858
1017
|
}
|
|
859
|
-
function
|
|
860
|
-
return
|
|
1018
|
+
function q(n) {
|
|
1019
|
+
return $(n) ? l.ok(n) : l.fail(r.featureNotEnabled("eventSourcing"));
|
|
861
1020
|
}
|
|
862
|
-
function
|
|
863
|
-
return
|
|
1021
|
+
function K(n) {
|
|
1022
|
+
return n.getCapabilityTypes();
|
|
864
1023
|
}
|
|
865
|
-
function
|
|
866
|
-
return t.every((e) =>
|
|
1024
|
+
function G(n, t) {
|
|
1025
|
+
return t.every((e) => n.hasCapability(e));
|
|
867
1026
|
}
|
|
868
1027
|
export {
|
|
869
|
-
|
|
1028
|
+
v as AggregateBuilder,
|
|
870
1029
|
r as AggregateError,
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1030
|
+
u as AggregateRoot,
|
|
1031
|
+
_ as AuditCapability,
|
|
1032
|
+
C as Entity,
|
|
1033
|
+
w as EventSourcingCapability,
|
|
875
1034
|
S as SnapshotCapability,
|
|
876
1035
|
p as VersioningCapability,
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1036
|
+
O as aggregateBuilder,
|
|
1037
|
+
z as asAuditAggregate,
|
|
1038
|
+
R as asEventSourcingAggregate,
|
|
1039
|
+
j as asSnapshotAggregate,
|
|
881
1040
|
P as asVersioningAggregate,
|
|
882
|
-
|
|
883
|
-
|
|
1041
|
+
K as getAggregateCapabilities,
|
|
1042
|
+
G as hasAllCapabilities,
|
|
884
1043
|
B as tryAsAuditAggregate,
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1044
|
+
q as tryAsEventSourcingAggregate,
|
|
1045
|
+
k as tryAsSnapshotAggregate,
|
|
1046
|
+
W as tryAsVersioningAggregate
|
|
888
1047
|
};
|