@shirudo/ddd-kit 1.0.1 → 1.1.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/dist/index.d.ts +106 -11
- package/dist/index.js +520 -79
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +15 -3
- package/dist/utils.js +158 -53
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -709,6 +709,14 @@ declare abstract class Entity<TState, TId extends Id<string>> implements IEntity
|
|
|
709
709
|
* Subclasses can mutate this directly or use helper methods.
|
|
710
710
|
*/
|
|
711
711
|
protected _state: TState;
|
|
712
|
+
/**
|
|
713
|
+
* **State ownership.** Plain-object and array states are shallow-copied
|
|
714
|
+
* before the freeze, so the caller's own object stays mutable. A CLASS
|
|
715
|
+
* INSTANCE passed as state is an ownership transfer: it is frozen
|
|
716
|
+
* in place (a copy would strip its prototype) — do not keep mutating
|
|
717
|
+
* the instance after handing it to the entity. The same contract
|
|
718
|
+
* applies to {@link setState}.
|
|
719
|
+
*/
|
|
712
720
|
protected constructor(id: TId, initialState: TState);
|
|
713
721
|
/**
|
|
714
722
|
* Optional validation hook to ensure state invariants. Called during
|
|
@@ -737,6 +745,10 @@ declare abstract class Entity<TState, TId extends Id<string>> implements IEntity
|
|
|
737
745
|
* This is a convenience method for state mutations.
|
|
738
746
|
* Automatically validates the newState using `validateState()`.
|
|
739
747
|
*
|
|
748
|
+
* Plain-object and array states are shallow-copied before the freeze
|
|
749
|
+
* (the caller's object stays mutable); a class-instance state is an
|
|
750
|
+
* ownership transfer and is frozen in place — see the constructor.
|
|
751
|
+
*
|
|
740
752
|
* @param newState - The new state
|
|
741
753
|
*/
|
|
742
754
|
protected setState(newState: TState): void;
|
|
@@ -917,8 +929,12 @@ declare function entityIds<TId extends Id<string>, T extends Identifiable<TId>>(
|
|
|
917
929
|
* @template TEvent - The domain-event union. Defaults to `never` so
|
|
918
930
|
* aggregates without a declared event type cannot emit events
|
|
919
931
|
* (emitting any event becomes a compile error).
|
|
932
|
+
* @template TSnapshotState - The plain-data shape stored in snapshots.
|
|
933
|
+
* Defaults to `TState` for plain-data states. Aggregates whose state
|
|
934
|
+
* carries class-based child entities declare a plain DTO shape here
|
|
935
|
+
* and override {@link toSnapshotState} / {@link fromSnapshotState}.
|
|
920
936
|
*/
|
|
921
|
-
declare abstract class BaseAggregate<TState, TId extends Id<string>, TEvent extends AnyDomainEvent = never> extends Entity<TState, TId> implements IAggregateRoot<TId, TEvent> {
|
|
937
|
+
declare abstract class BaseAggregate<TState, TId extends Id<string>, TEvent extends AnyDomainEvent = never, TSnapshotState = TState> extends Entity<TState, TId> implements IAggregateRoot<TId, TEvent> {
|
|
922
938
|
/**
|
|
923
939
|
* The aggregate's domain type as a string, used to populate
|
|
924
940
|
* `aggregateType` on events recorded via {@link recordEvent}.
|
|
@@ -1028,6 +1044,12 @@ declare abstract class BaseAggregate<TState, TId extends Id<string>, TEvent exte
|
|
|
1028
1044
|
* call `super.onPersisted(version)` — there is nothing in the parent
|
|
1029
1045
|
* implementation to preserve.
|
|
1030
1046
|
*
|
|
1047
|
+
* **Observer contract: errors are swallowed.** `withCommit` invokes
|
|
1048
|
+
* `markPersisted` after the transaction has committed; a throwing hook
|
|
1049
|
+
* must neither abort the loop for peer aggregates nor make the
|
|
1050
|
+
* committed write look failed, so `withCommit` catches and discards
|
|
1051
|
+
* hook errors. Handle failures inside the hook if you need them.
|
|
1052
|
+
*
|
|
1031
1053
|
* **`onPersisted` deliberately receives only the version, not the
|
|
1032
1054
|
* drained events.** Event-driven post-persist logic (aggregate-level
|
|
1033
1055
|
* audit logging, per-event-type side effects) belongs in `EventBus`
|
|
@@ -1063,8 +1085,35 @@ declare abstract class BaseAggregate<TState, TId extends Id<string>, TEvent exte
|
|
|
1063
1085
|
* Creates a snapshot of the current aggregate state — the state at
|
|
1064
1086
|
* this moment plus the version. Useful for ES snapshot policies and
|
|
1065
1087
|
* for state-stored backup / restore.
|
|
1088
|
+
*
|
|
1089
|
+
* The state is converted via {@link toSnapshotState}; the default
|
|
1090
|
+
* requires plain, serialisable data and fails fast otherwise.
|
|
1091
|
+
*/
|
|
1092
|
+
createSnapshot(): AggregateSnapshot<TSnapshotState>;
|
|
1093
|
+
/**
|
|
1094
|
+
* Converts live aggregate state into the plain-data shape stored in a
|
|
1095
|
+
* snapshot. The default validates that the state graph is plain,
|
|
1096
|
+
* serialisable data (no class instances, functions, Promise/WeakMap/
|
|
1097
|
+
* WeakSet) and then `structuredClone`s it — class instances would
|
|
1098
|
+
* silently lose their prototype here AND on every snapshot-store
|
|
1099
|
+
* round-trip, so the default fails fast with the offending path
|
|
1100
|
+
* instead of producing a snapshot that breaks on first method call
|
|
1101
|
+
* after restore.
|
|
1102
|
+
*
|
|
1103
|
+
* Override this together with {@link fromSnapshotState} (and the
|
|
1104
|
+
* `TSnapshotState` generic) when the state carries class-based child
|
|
1105
|
+
* entities. The override owns isolation: return fresh objects, not
|
|
1106
|
+
* references into live state.
|
|
1107
|
+
*/
|
|
1108
|
+
protected toSnapshotState(state: TState): TSnapshotState;
|
|
1109
|
+
/**
|
|
1110
|
+
* Converts the plain-data snapshot shape back into live aggregate
|
|
1111
|
+
* state. The default `structuredClone`s the stored state so the
|
|
1112
|
+
* restored aggregate never aliases the snapshot object. Override
|
|
1113
|
+
* together with {@link toSnapshotState} to reconstruct class-based
|
|
1114
|
+
* child entities.
|
|
1066
1115
|
*/
|
|
1067
|
-
|
|
1116
|
+
protected fromSnapshotState(stored: TSnapshotState): TState;
|
|
1068
1117
|
/**
|
|
1069
1118
|
* Sugar for `createDomainEvent` that auto-injects `aggregateId`
|
|
1070
1119
|
* (from `this.id`) and `aggregateType` (from {@link aggregateType})
|
|
@@ -1162,7 +1211,7 @@ interface AggregateConfig {
|
|
|
1162
1211
|
* }
|
|
1163
1212
|
* ```
|
|
1164
1213
|
*/
|
|
1165
|
-
declare abstract class AggregateRoot<TState, TId extends Id<string>, TEvent extends AnyDomainEvent = never> extends BaseAggregate<TState, TId, TEvent> {
|
|
1214
|
+
declare abstract class AggregateRoot<TState, TId extends Id<string>, TEvent extends AnyDomainEvent = never, TSnapshotState = TState> extends BaseAggregate<TState, TId, TEvent, TSnapshotState> {
|
|
1166
1215
|
private readonly _autoVersionBump;
|
|
1167
1216
|
protected constructor(id: TId, initialState: TState, config?: AggregateConfig);
|
|
1168
1217
|
/**
|
|
@@ -1225,7 +1274,7 @@ declare abstract class AggregateRoot<TState, TId extends Id<string>, TEvent exte
|
|
|
1225
1274
|
*
|
|
1226
1275
|
* @param snapshot - The snapshot to restore from
|
|
1227
1276
|
*/
|
|
1228
|
-
restoreFromSnapshot(snapshot: AggregateSnapshot<
|
|
1277
|
+
restoreFromSnapshot(snapshot: AggregateSnapshot<TSnapshotState>): void;
|
|
1229
1278
|
}
|
|
1230
1279
|
|
|
1231
1280
|
type Handler<TState, TEvent extends AnyDomainEvent> = (state: TState, event: TEvent) => TState;
|
|
@@ -1282,7 +1331,7 @@ type Handler<TState, TEvent extends AnyDomainEvent> = (state: TState, event: TEv
|
|
|
1282
1331
|
* }
|
|
1283
1332
|
* ```
|
|
1284
1333
|
*/
|
|
1285
|
-
declare abstract class EventSourcedAggregate<TState, TEvent extends AnyDomainEvent, TId extends Id<string
|
|
1334
|
+
declare abstract class EventSourcedAggregate<TState, TEvent extends AnyDomainEvent, TId extends Id<string>, TSnapshotState = TState> extends BaseAggregate<TState, TId, TEvent, TSnapshotState> implements IEventSourcedAggregate<TId, TEvent> {
|
|
1286
1335
|
/**
|
|
1287
1336
|
* Validates an event before it is applied. Default is no-op.
|
|
1288
1337
|
* Subclasses override to throw a concrete `DomainError` subclass when
|
|
@@ -1324,6 +1373,12 @@ declare abstract class EventSourcedAggregate<TState, TEvent extends AnyDomainEve
|
|
|
1324
1373
|
* infrastructure boundary, where event-stream corruption is an expected
|
|
1325
1374
|
* recoverable failure. Unexpected (non-DomainError) throws propagate.
|
|
1326
1375
|
*
|
|
1376
|
+
* All-or-nothing: if any event mid-stream throws, the aggregate's state
|
|
1377
|
+
* is rolled back to its pre-call value — same contract as
|
|
1378
|
+
* `restoreFromSnapshotWithEvents`. Partial replay is never observable.
|
|
1379
|
+
* (Version needs no rollback: replay dispatches with `isNew = false`,
|
|
1380
|
+
* which never bumps it; only the final `markRestored` advances it.)
|
|
1381
|
+
*
|
|
1327
1382
|
* Version advances additively: the aggregate's pre-existing version plus
|
|
1328
1383
|
* `history.length`. A fresh aggregate (v=0) loading 3 events ends at v=3;
|
|
1329
1384
|
* an aggregate already at v=1 (e.g. after a creation event) loading
|
|
@@ -1340,7 +1395,7 @@ declare abstract class EventSourcedAggregate<TState, TEvent extends AnyDomainEve
|
|
|
1340
1395
|
* aggregate is rolled back to its pre-call state + version. Partial
|
|
1341
1396
|
* restoration is never observable to the caller.
|
|
1342
1397
|
*/
|
|
1343
|
-
restoreFromSnapshotWithEvents(snapshot: AggregateSnapshot<
|
|
1398
|
+
restoreFromSnapshotWithEvents(snapshot: AggregateSnapshot<TSnapshotState>, eventsAfterSnapshot: ReadonlyArray<TEvent>): Result<void, DomainError>;
|
|
1344
1399
|
/**
|
|
1345
1400
|
* A map of event types to their corresponding handlers.
|
|
1346
1401
|
* Subclasses MUST implement this property.
|
|
@@ -1838,6 +1893,16 @@ interface TransactionScope<TCtx> {
|
|
|
1838
1893
|
* outbox still holds the events and an outbox-dispatcher will deliver
|
|
1839
1894
|
* them (eventual consistency).
|
|
1840
1895
|
*
|
|
1896
|
+
* **A `bus.publish` failure never rejects `withCommit`.** Once the
|
|
1897
|
+
* transaction has committed, the write succeeded — surfacing a subscriber
|
|
1898
|
+
* failure as a rejection would hand the caller a use-case failure for a
|
|
1899
|
+
* committed write (a typical caller retries, double-executing it). The
|
|
1900
|
+
* in-process fast path is best-effort by design; the error is reported to
|
|
1901
|
+
* the optional `onPublishError(error, events)` hook (wire it to your
|
|
1902
|
+
* logger/metrics) and otherwise dropped — delivery is still guaranteed via
|
|
1903
|
+
* the outbox. The hook is an observer: if it throws, its error is
|
|
1904
|
+
* swallowed so the post-commit invariant holds.
|
|
1905
|
+
*
|
|
1841
1906
|
* If the transaction rolls back, `markPersisted` is **not** called — the
|
|
1842
1907
|
* aggregate keeps its pending events, so the caller can retry or discard.
|
|
1843
1908
|
*
|
|
@@ -1869,6 +1934,12 @@ declare function withCommit<Evt extends AnyDomainEvent, R, TCtx>(deps: {
|
|
|
1869
1934
|
outbox: Outbox<Evt>;
|
|
1870
1935
|
bus?: EventBus<Evt>;
|
|
1871
1936
|
scope: TransactionScope<TCtx>;
|
|
1937
|
+
/**
|
|
1938
|
+
* Observer for post-commit `bus.publish` failures. Called with the
|
|
1939
|
+
* error and the events that were published. Must not be relied on
|
|
1940
|
+
* for delivery — the outbox dispatcher is the reliable path.
|
|
1941
|
+
*/
|
|
1942
|
+
onPublishError?: (error: unknown, events: ReadonlyArray<Evt>) => void;
|
|
1872
1943
|
}, fn: (ctx: TCtx) => Promise<{
|
|
1873
1944
|
result: R;
|
|
1874
1945
|
aggregates: ReadonlyArray<IAggregateRoot<Id<string>, Evt>>;
|
|
@@ -2348,15 +2419,32 @@ type VO<T> = Readonly<T>;
|
|
|
2348
2419
|
* Note: `deepFreeze` mutates its argument in place — it sets `[[Frozen]]`
|
|
2349
2420
|
* on the object you pass in. Callers that need to avoid touching the
|
|
2350
2421
|
* input (e.g. `vo()`) should deep-clone first.
|
|
2422
|
+
*
|
|
2423
|
+
* Date/Map/Set keep internal-slot mutability under `Object.freeze`
|
|
2424
|
+
* (`setTime`, `set`, `add`, … still work on frozen instances), so their
|
|
2425
|
+
* mutator methods are shadowed with throwing own properties and Map/Set
|
|
2426
|
+
* contents are frozen recursively. The shadows are non-enumerable —
|
|
2427
|
+
* invisible to `Object.keys`, spread, `deepEqual`, and `structuredClone`.
|
|
2428
|
+
*
|
|
2429
|
+
* The shadowing is deny-by-enumeration: only the mutators known at
|
|
2430
|
+
* release time are blocked. If the runtime grows a NEW mutator (e.g. the
|
|
2431
|
+
* stage-3 `Map.prototype.getOrInsert` upsert proposal), it is not blocked
|
|
2432
|
+
* until the list is updated — treat the mutator blocking as a guard rail,
|
|
2433
|
+
* not a security boundary.
|
|
2434
|
+
*
|
|
2435
|
+
* Limitation: ArrayBuffer views (TypedArrays, DataView) are passed through
|
|
2436
|
+
* unfrozen — the spec forbids freezing a view with elements, and freezing
|
|
2437
|
+
* cannot protect the underlying buffer. Their contents remain mutable.
|
|
2351
2438
|
*/
|
|
2352
2439
|
declare function deepFreeze<T>(obj: T, visited?: WeakSet<object>): Readonly<T>;
|
|
2353
2440
|
/**
|
|
2354
2441
|
* Creates a deeply immutable value object from the given data.
|
|
2355
2442
|
*
|
|
2356
|
-
* The input is first deep-cloned
|
|
2357
|
-
*
|
|
2358
|
-
*
|
|
2359
|
-
*
|
|
2443
|
+
* The input is first deep-cloned, then the clone is frozen — so calling
|
|
2444
|
+
* `vo(input)` never freezes the caller's own object graph as a
|
|
2445
|
+
* side-effect. Mutating the input afterwards does not bleed into the VO.
|
|
2446
|
+
* Symbol-keyed properties are preserved (matching `voEquals`); function
|
|
2447
|
+
* values are rejected (Value Objects are data, not behaviour).
|
|
2360
2448
|
*
|
|
2361
2449
|
* @example
|
|
2362
2450
|
* ```typescript
|
|
@@ -2441,6 +2529,11 @@ declare function voEqualsExcept<T>(a: VO<T>, b: VO<T>, options: DeepEqualExceptO
|
|
|
2441
2529
|
* Creates a value object with optional validation.
|
|
2442
2530
|
* Returns a Result type instead of throwing an error.
|
|
2443
2531
|
*
|
|
2532
|
+
* Note: the Result covers VALIDATION failures only. Non-data values in
|
|
2533
|
+
* the input (functions, Promise/WeakMap/WeakSet) still throw a
|
|
2534
|
+
* `TypeError` from `vo()` — they cannot occur in parsed JSON and signal
|
|
2535
|
+
* a programming error, not a validation failure.
|
|
2536
|
+
*
|
|
2444
2537
|
* @param t - The data to convert into a value object
|
|
2445
2538
|
* @param validate - Validation function that returns true if valid
|
|
2446
2539
|
* @param errorMessage - Optional custom error message if validation fails
|
|
@@ -2505,7 +2598,9 @@ declare abstract class ValueObject<T extends object> implements IValueObject<T>
|
|
|
2505
2598
|
readonly props: Readonly<T>;
|
|
2506
2599
|
/**
|
|
2507
2600
|
* Creates a new ValueObject.
|
|
2508
|
-
* The properties are
|
|
2601
|
+
* The properties are deep-cloned (prototype-preserving) and then deeply
|
|
2602
|
+
* frozen — the caller's own object graph is never frozen or mutated,
|
|
2603
|
+
* and later mutation of the input does not bleed into the value object.
|
|
2509
2604
|
*
|
|
2510
2605
|
* @param props - The properties of the value object
|
|
2511
2606
|
* @example
|