@shirudo/ddd-kit 3.0.0-rc.11 → 3.0.0-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/dist/chunks/kit-errors.d.ts +25 -26
- package/dist/chunks/kit-errors.js +51 -52
- package/dist/chunks/kit-errors.js.map +1 -1
- package/dist/chunks/read-stream-pages.js +218 -1443
- package/dist/chunks/read-stream-pages.js.map +1 -1
- package/dist/chunks/snapshot-store.d.ts +123 -118
- package/dist/chunks/value-object.js +1275 -0
- package/dist/chunks/value-object.js.map +1 -0
- package/dist/index.d.ts +142 -395
- package/dist/index.js +200 -868
- package/dist/index.js.map +1 -1
- package/dist/money.d.ts +3 -3
- package/dist/money.js +3 -3
- package/dist/money.js.map +1 -1
- package/dist/state-machine.d.ts +227 -0
- package/dist/state-machine.js +673 -0
- package/dist/state-machine.js.map +1 -0
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +12 -11
- package/dist/testing.js.map +1 -1
- package/package.json +5 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as DomainError,
|
|
1
|
+
import { f as DomainError, st as ReplayRejectedError } from "./kit-errors.js";
|
|
2
2
|
import { Result } from "@shirudo/result";
|
|
3
3
|
//#region src/internal/json-value.d.ts
|
|
4
4
|
/** A primitive value represented without loss by JSON. */
|
|
@@ -250,7 +250,7 @@ interface EventMetadata {
|
|
|
250
250
|
* Events are immutable and carry information about what occurred.
|
|
251
251
|
*
|
|
252
252
|
* **Events are PLAIN DATA objects**, constructed via `createDomainEvent`
|
|
253
|
-
* (or the aggregate's `createEvent` plus application-shell
|
|
253
|
+
* (or the aggregate's `createEvent` plus application-shell stamping)
|
|
254
254
|
* and deeply frozen. Class-based
|
|
255
255
|
* event objects that satisfy this shape structurally via prototype
|
|
256
256
|
* members are unsupported.
|
|
@@ -321,29 +321,29 @@ interface DomainEvent<T extends string, P = void> {
|
|
|
321
321
|
*/
|
|
322
322
|
type AnyDomainEvent = DomainEvent<string, unknown>;
|
|
323
323
|
/**
|
|
324
|
-
* A domain event accepted by an aggregate but not yet given its
|
|
325
|
-
*
|
|
324
|
+
* A domain event accepted by an aggregate but not yet given its event id,
|
|
325
|
+
* stamp time, or delivery metadata.
|
|
326
326
|
*
|
|
327
327
|
* The aggregate owns the event type, payload, source aggregate identity,
|
|
328
328
|
* and payload schema version because those values describe the business
|
|
329
329
|
* fact it produced.
|
|
330
330
|
* The application shell later turns this value into a {@link DomainEvent}.
|
|
331
331
|
*/
|
|
332
|
-
interface
|
|
332
|
+
interface UnstampedDomainEvent<T extends string, P = void> {
|
|
333
333
|
readonly type: T;
|
|
334
334
|
readonly aggregateId?: string;
|
|
335
335
|
readonly aggregateType?: string;
|
|
336
336
|
readonly payload: P;
|
|
337
337
|
readonly schemaVersion: number;
|
|
338
338
|
}
|
|
339
|
-
/** Upper-bound alias for any
|
|
340
|
-
type
|
|
341
|
-
/** Derives the
|
|
342
|
-
type
|
|
343
|
-
/** An aggregate may hold unstamped decisions and already
|
|
344
|
-
type PendingDomainEvent<TEvent extends AnyDomainEvent> = TEvent |
|
|
345
|
-
/** Producer-owned options for an
|
|
346
|
-
interface
|
|
339
|
+
/** Upper-bound alias for any unstamped domain-event shape. */
|
|
340
|
+
type AnyUnstampedDomainEvent = UnstampedDomainEvent<string, unknown>;
|
|
341
|
+
/** Derives the unstamped shape represented by a concrete event or event union. */
|
|
342
|
+
type UnstampedDomainEventOf<TEvent extends AnyDomainEvent> = TEvent extends DomainEvent<infer TType, infer TPayload> ? UnstampedDomainEvent<TType, TPayload> : never;
|
|
343
|
+
/** An aggregate may hold unstamped decisions and already stamped events together. */
|
|
344
|
+
type PendingDomainEvent<TEvent extends AnyDomainEvent> = TEvent | UnstampedDomainEventOf<TEvent>;
|
|
345
|
+
/** Producer-owned options for an unstamped event. */
|
|
346
|
+
interface CreateUnstampedDomainEventOptions {
|
|
347
347
|
readonly aggregateId?: string;
|
|
348
348
|
readonly aggregateType?: string;
|
|
349
349
|
readonly schemaVersion?: number;
|
|
@@ -379,7 +379,7 @@ interface CreateDomainEventOptions {
|
|
|
379
379
|
*/
|
|
380
380
|
metadata?: EventMetadata;
|
|
381
381
|
}
|
|
382
|
-
/** Technical
|
|
382
|
+
/** Technical stamp data attached by the application shell. */
|
|
383
383
|
interface DomainEventStamp {
|
|
384
384
|
/** Stable identity for this event instance. */
|
|
385
385
|
readonly eventId: string;
|
|
@@ -389,7 +389,7 @@ interface DomainEventStamp {
|
|
|
389
389
|
readonly metadata?: EventMetadata;
|
|
390
390
|
}
|
|
391
391
|
/** Full strict-construction options, including producer-owned event fields. */
|
|
392
|
-
interface
|
|
392
|
+
interface CreateStampedDomainEventOptions extends DomainEventStamp {
|
|
393
393
|
readonly aggregateId?: string;
|
|
394
394
|
readonly aggregateType?: string;
|
|
395
395
|
readonly schemaVersion?: number;
|
|
@@ -407,11 +407,11 @@ interface DomainEventFactoryOptions {
|
|
|
407
407
|
/** Event-recording clock. Defaults to `() => new Date()`. */
|
|
408
408
|
readonly clock?: ClockFactory;
|
|
409
409
|
/**
|
|
410
|
-
* Origin stamped on every event this factory
|
|
410
|
+
* Origin stamped on every event this factory builds, unless the call site
|
|
411
411
|
* names one itself.
|
|
412
412
|
*
|
|
413
413
|
* A plain value, not a factory like the two above. Those produce a new
|
|
414
|
-
* value for each event. An origin identifies the system that
|
|
414
|
+
* value for each event. An origin identifies the system that builds them
|
|
415
415
|
* and does not change between two of them.
|
|
416
416
|
*/
|
|
417
417
|
readonly source?: string;
|
|
@@ -423,7 +423,7 @@ interface DomainEventFactoryOptions {
|
|
|
423
423
|
*/
|
|
424
424
|
interface DomainEventFactory {
|
|
425
425
|
/**
|
|
426
|
-
* Creates immutable technical
|
|
426
|
+
* Creates immutable technical stamp data in the application shell.
|
|
427
427
|
*/
|
|
428
428
|
readonly createStamp: (options?: CreateDomainEventStampOptions) => DomainEventStamp;
|
|
429
429
|
readonly create: {
|
|
@@ -454,7 +454,7 @@ interface DomainEventFactory {
|
|
|
454
454
|
* clock: () => new Date(),
|
|
455
455
|
* });
|
|
456
456
|
* order.confirm();
|
|
457
|
-
*
|
|
457
|
+
* stampPendingEvents(order, domainEvents);
|
|
458
458
|
* ```
|
|
459
459
|
*/
|
|
460
460
|
declare function createDomainEventFactory(options?: DomainEventFactoryOptions): DomainEventFactory;
|
|
@@ -464,30 +464,70 @@ declare function createDomainEventFactory(options?: DomainEventFactoryOptions):
|
|
|
464
464
|
* with {@link createDomainEventFactory} for custom policy.
|
|
465
465
|
*/
|
|
466
466
|
declare const defaultDomainEventFactory: DomainEventFactory;
|
|
467
|
-
declare function createUncommittedDomainEvent<T extends string>(type: T, payload?: undefined, options?: CreateUncommittedDomainEventOptions): UncommittedDomainEvent<T, void>;
|
|
468
|
-
declare function createUncommittedDomainEvent<T extends string, P>(type: T, payload: P, options?: CreateUncommittedDomainEventOptions): UncommittedDomainEvent<T, P>;
|
|
469
467
|
/**
|
|
470
|
-
*
|
|
468
|
+
* Creates a domain event with default values.
|
|
469
|
+
* Sets occurredAt to current date and schemaVersion to 1 if not provided.
|
|
470
|
+
*
|
|
471
|
+
* **Input ownership.** The event is deeply frozen, and `payload` and
|
|
472
|
+
* `metadata` are deep-cloned first, so the caller's own objects are never
|
|
473
|
+
* frozen in place and later mutation of them does not bleed into the event
|
|
474
|
+
* (same contract as `valueObject()`). The event data rule checks the data before the
|
|
475
|
+
* copy and the copy after it. So the copy loses no data without an error,
|
|
476
|
+
* and no getter of the data runs. The in-memory event store applies the
|
|
477
|
+
* same rule on append. The checks catch accidents, not a hostile `Proxy`: a
|
|
478
|
+
* trap that changes the data during the check can hide a loss. An error
|
|
479
|
+
* that a `Proxy` trap throws passes unchanged, so a coded error of the
|
|
480
|
+
* caller keeps its code. These values throw a `TypeError`: a function, a
|
|
481
|
+
* Promise, a WeakMap or a WeakSet, an `Error`, a host object such as a
|
|
482
|
+
* `File`, a binary buffer, a `RegExp`, and shared memory. The same holds
|
|
483
|
+
* for what the copy would lose or change: a class instance, an accessor, an
|
|
484
|
+
* enumerable symbol-keyed property, a non-enumerable data property, and an
|
|
485
|
+
* expando on a built-in. A non-enumerable symbol key is metadata by
|
|
486
|
+
* convention, and the event does not get it. Map a value object to plain
|
|
487
|
+
* data before it goes into an event. `toJSON()` of a `ValueObject` is
|
|
488
|
+
* shallow, so map a nested value object too.
|
|
489
|
+
*
|
|
490
|
+
* **For aggregate-internal events, prefer `this.createEvent(...)` on
|
|
491
|
+
* `StateStoredAggregate` / `EventSourcedAggregate`.** That helper auto-injects
|
|
492
|
+
* `aggregateId` (from `this.id`) and `aggregateType` (from the
|
|
493
|
+
* aggregate's declared `aggregateType` property), which downstream
|
|
494
|
+
* consumers (outbox dispatchers, projection handlers, audit logs)
|
|
495
|
+
* route by. The commit boundary validates that both fields are present
|
|
496
|
+
* and throws if they are missing, so a direct `createDomainEvent(...)`
|
|
497
|
+
* call inside an aggregate that forgets the options is caught at
|
|
498
|
+
* runtime. Stamp pending decisions in the application
|
|
499
|
+
* shell before repository persistence or outbox collection.
|
|
500
|
+
*
|
|
501
|
+
* Use `createDomainEvent(...)` directly for events that don't belong to
|
|
502
|
+
* an aggregate: system events, integration events, configuration events,
|
|
503
|
+
* test fixtures. For those, set `aggregateId` / `aggregateType` in
|
|
504
|
+
* `options` if downstream consumers expect routing metadata.
|
|
505
|
+
*
|
|
506
|
+
* @param type - The event type
|
|
507
|
+
* @param payload - The event payload
|
|
508
|
+
* @param options - Optional event configuration (including `aggregateId`
|
|
509
|
+
* and `aggregateType` for routing)
|
|
510
|
+
* @returns A domain event
|
|
471
511
|
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
512
|
+
* @example
|
|
513
|
+
* ```typescript
|
|
514
|
+
* const event = createDomainEvent("OrderCreated", { orderId: "123" });
|
|
515
|
+
* ```
|
|
475
516
|
*/
|
|
476
|
-
declare function recordDomainEvent<T extends string, P>(event: UncommittedDomainEvent<T, P>, stamp: DomainEventStamp): DomainEvent<T, P>;
|
|
477
517
|
declare function createDomainEvent<T extends string>(type: T, payload?: undefined, options?: CreateDomainEventOptions): DomainEvent<T, void>;
|
|
478
518
|
declare function createDomainEvent<T extends string, P>(type: T, payload: P, options?: CreateDomainEventOptions): DomainEvent<T, P>;
|
|
479
519
|
/**
|
|
480
|
-
* Creates
|
|
520
|
+
* Creates a kit-built, stamped domain event exclusively from explicit envelope
|
|
481
521
|
* facts. Unlike {@link createDomainEvent}, it has no clock or event-id fallback
|
|
482
522
|
* and is useful when replay, migration, or a caller-owned boundary already has
|
|
483
523
|
* the final identity and occurrence time.
|
|
484
524
|
*
|
|
485
|
-
* Aggregate behavior normally creates an {@link
|
|
525
|
+
* Aggregate behavior normally creates an {@link UnstampedDomainEvent} through
|
|
486
526
|
* its protected `createEvent` helper. The application shell later records that
|
|
487
527
|
* pending fact with caller-owned time and identity.
|
|
488
528
|
*/
|
|
489
|
-
declare function
|
|
490
|
-
declare function
|
|
529
|
+
declare function createStampedDomainEvent<T extends string>(type: T, payload: undefined, options: CreateStampedDomainEventOptions): DomainEvent<T, void>;
|
|
530
|
+
declare function createStampedDomainEvent<T extends string, P>(type: T, payload: P, options: CreateStampedDomainEventOptions): DomainEvent<T, P>;
|
|
491
531
|
/**
|
|
492
532
|
* Copies metadata from a source event to a new event.
|
|
493
533
|
* Useful for maintaining correlation chains in event-driven architectures.
|
|
@@ -1010,7 +1050,7 @@ interface ReplayableAggregate<TId extends Id<string>, TEvent extends AnyDomainEv
|
|
|
1010
1050
|
* decisions, or a fold records one
|
|
1011
1051
|
* @throws MissingFoldError when no fold is declared for an event type
|
|
1012
1052
|
* @throws FoldReturnedNoStateError when a fold returns `undefined`
|
|
1013
|
-
* @throws
|
|
1053
|
+
* @throws PrototypePollutionError when the folded state carries an own
|
|
1014
1054
|
* `__proto__` key
|
|
1015
1055
|
*/
|
|
1016
1056
|
replayHistory(history: ReadonlyArray<TEvent>): Result<void, DomainError>;
|
|
@@ -1422,9 +1462,9 @@ interface WithCommitDeps<Evt extends AnyDomainEvent, TCtx> {
|
|
|
1422
1462
|
}
|
|
1423
1463
|
declare const aggregateCommitTokenBrand: unique symbol;
|
|
1424
1464
|
/**
|
|
1425
|
-
* Opaque receipt that one aggregate was explicitly
|
|
1426
|
-
* {@link withCommit} invocation. Tokens are
|
|
1427
|
-
* {@link
|
|
1465
|
+
* Opaque receipt that one aggregate was explicitly registered in the current
|
|
1466
|
+
* {@link withCommit} invocation. Tokens are issued only by the invocation's
|
|
1467
|
+
* {@link CommitRegistration} capability and are bound to that invocation at
|
|
1428
1468
|
* runtime; a forged token or one retained from an earlier call is rejected
|
|
1429
1469
|
* inside the transaction.
|
|
1430
1470
|
*/
|
|
@@ -1432,26 +1472,26 @@ interface AggregateCommitToken<Evt extends AnyDomainEvent = AnyDomainEvent> {
|
|
|
1432
1472
|
readonly [aggregateCommitTokenBrand]: Evt;
|
|
1433
1473
|
}
|
|
1434
1474
|
/**
|
|
1435
|
-
* Invocation-scoped
|
|
1436
|
-
* callback. Call `
|
|
1475
|
+
* Invocation-scoped registration capability handed to a {@link withCommit}
|
|
1476
|
+
* callback. Call `registerSaved` only for an aggregate participating in the
|
|
1437
1477
|
* repository write, and return every resulting token in `commits`. Omitting
|
|
1438
|
-
* any token rejects the transaction:
|
|
1439
|
-
* its event
|
|
1478
|
+
* any token rejects the transaction: a registered write may not commit without
|
|
1479
|
+
* its event collection and post-commit acknowledgement. Registrable instances
|
|
1440
1480
|
* must extend `StateStoredAggregate` or `EventSourcedAggregate`; structural
|
|
1441
1481
|
* `Aggregate` lookalikes have no internal lifecycle capability and fail
|
|
1442
1482
|
* before commit.
|
|
1443
1483
|
*/
|
|
1444
|
-
interface
|
|
1445
|
-
|
|
1484
|
+
interface CommitRegistration<Evt extends AnyDomainEvent> {
|
|
1485
|
+
registerSaved(aggregate: Aggregate<Id<string>, Evt>, options?: CommitRegistrationOptions): AggregateCommitToken<Evt>;
|
|
1446
1486
|
/**
|
|
1447
|
-
*
|
|
1448
|
-
* Its events are
|
|
1487
|
+
* Register an aggregate whose row the current transaction removes.
|
|
1488
|
+
* Its events are collected and discarded after commit, but the saved-only
|
|
1449
1489
|
* application `onPersisted` observer is not called.
|
|
1450
1490
|
*/
|
|
1451
|
-
|
|
1491
|
+
registerRemoved(aggregate: Aggregate<Id<string>, Evt>, options?: CommitRegistrationOptions): AggregateCommitToken<Evt>;
|
|
1452
1492
|
}
|
|
1453
|
-
/** OCC baseline associated with one exact commit
|
|
1454
|
-
interface
|
|
1493
|
+
/** OCC baseline associated with one exact commit registration. */
|
|
1494
|
+
interface CommitRegistrationOptions {
|
|
1455
1495
|
/** Absent for a new aggregate; captured at load for update or removal. */
|
|
1456
1496
|
readonly expectedVersion?: Version;
|
|
1457
1497
|
}
|
|
@@ -1459,8 +1499,8 @@ interface CommitEnrollmentOptions {
|
|
|
1459
1499
|
interface WithCommitWorkResult<Evt extends AnyDomainEvent, R> {
|
|
1460
1500
|
result: R;
|
|
1461
1501
|
/**
|
|
1462
|
-
* Commit tokens returned by the invocation's
|
|
1463
|
-
* Every token
|
|
1502
|
+
* Commit tokens returned by the invocation's registration capability.
|
|
1503
|
+
* Every token issued during the callback must appear at least once.
|
|
1464
1504
|
* Naked aggregates are intentionally not accepted: touching an aggregate
|
|
1465
1505
|
* does not prove that its repository write participated in the transaction.
|
|
1466
1506
|
*/
|
|
@@ -1469,29 +1509,29 @@ interface WithCommitWorkResult<Evt extends AnyDomainEvent, R> {
|
|
|
1469
1509
|
/**
|
|
1470
1510
|
* Helper for executing a write Use Case inside a transaction scope.
|
|
1471
1511
|
*
|
|
1472
|
-
* The use-case callback receives an invocation-scoped
|
|
1512
|
+
* The use-case callback receives an invocation-scoped registration capability
|
|
1473
1513
|
* and returns opaque commit tokens for the repository writes that completed
|
|
1474
|
-
* in the transaction. `withCommit` owns the post-commit lifecycle (
|
|
1514
|
+
* in the transaction. `withCommit` owns the post-commit lifecycle (collection,
|
|
1475
1515
|
* outbox, mark-persisted, publish). A naked aggregate is not commit evidence:
|
|
1476
1516
|
* merely touching or constructing one must never make it look persisted.
|
|
1477
1517
|
*
|
|
1478
|
-
* **Trust boundary.** A token proves invocation-local
|
|
1518
|
+
* **Trust boundary.** A token proves invocation-local registration, not that the
|
|
1479
1519
|
* kit inspected a database write; a generic transaction helper cannot observe
|
|
1480
|
-
* adapter internals. Repository code must
|
|
1520
|
+
* adapter internals. Repository code must register only writes participating in
|
|
1481
1521
|
* this transaction. `UnitOfWork` centralizes that rule in repository methods.
|
|
1482
1522
|
* The opaque, scoped token prevents accidental aggregate smuggling and stale
|
|
1483
1523
|
* reuse; it is not a security boundary against code that deliberately lies to
|
|
1484
1524
|
* its own persistence capability.
|
|
1485
1525
|
*
|
|
1486
1526
|
* Order of operations:
|
|
1487
|
-
* 1. `fn(ctx,
|
|
1527
|
+
* 1. `fn(ctx, registration)` runs inside `scope.transactional(...)`; domain
|
|
1488
1528
|
* mutations + repo writes happen here. After a repository write has
|
|
1489
|
-
*
|
|
1529
|
+
* registered an aggregate, the callback includes that opaque token in its
|
|
1490
1530
|
* `commits` result. Tokens are invocation-bound: forged or stale tokens
|
|
1491
|
-
* fail before
|
|
1531
|
+
* fail before collection. `ctx` is whatever transaction handle the `scope`
|
|
1492
1532
|
* exposes (Drizzle `tx`, Prisma `tx`, Mongo session, or `undefined` for
|
|
1493
1533
|
* context-free scopes).
|
|
1494
|
-
* 2. **Still inside the transaction**, `withCommit`
|
|
1534
|
+
* 2. **Still inside the transaction**, `withCommit` collects every
|
|
1495
1535
|
* aggregate's `pendingEvents` and writes them via `outbox.add` (so
|
|
1496
1536
|
* events persist atomically with the state change). Skipped when no
|
|
1497
1537
|
* events were recorded. Each bare domain event is composed into an
|
|
@@ -1501,7 +1541,7 @@ interface WithCommitWorkResult<Evt extends AnyDomainEvent, R> {
|
|
|
1501
1541
|
* resulting `CommittedDomainEvent`. The domain event itself is never
|
|
1502
1542
|
* stamped or copied.
|
|
1503
1543
|
*
|
|
1504
|
-
* **
|
|
1544
|
+
* **Collection order.** Events are concatenated in the order
|
|
1505
1545
|
* tokens appear in the returned `commits` array, then in
|
|
1506
1546
|
* each aggregate's `pendingEvents` order (insertion order via
|
|
1507
1547
|
* `apply` / `commit` / `addDomainEvent`). So tokens for `[a, b]`
|
|
@@ -1525,7 +1565,7 @@ interface WithCommitWorkResult<Evt extends AnyDomainEvent, R> {
|
|
|
1525
1565
|
* across aggregates at delivery time.
|
|
1526
1566
|
* 3. The transaction commits.
|
|
1527
1567
|
* 4. **After** the commit, a non-exported capability acknowledges every
|
|
1528
|
-
* saved
|
|
1568
|
+
* saved registration and discards pending events for deleted registrations.
|
|
1529
1569
|
* Only after the complete commit set is clean does the optional
|
|
1530
1570
|
* application-shell `onPersisted(aggregate, version, context)` observer run for
|
|
1531
1571
|
* saved aggregates. Deleted rows never trigger that observer.
|
|
@@ -1555,16 +1595,16 @@ interface WithCommitWorkResult<Evt extends AnyDomainEvent, R> {
|
|
|
1555
1595
|
* If the transaction rolls back, no acknowledgement occurs: the aggregate
|
|
1556
1596
|
* keeps its pending events, so the caller can retry or discard the instance.
|
|
1557
1597
|
*
|
|
1558
|
-
*
|
|
1598
|
+
* Registration captures an exact version and event batch. Re-registering the same
|
|
1559
1599
|
* aggregate after it changes rejects. `UnitOfWork` additionally seals the
|
|
1560
|
-
* adapter persistence
|
|
1561
|
-
* direct `withCommit` use, make domain decisions first, write, and
|
|
1600
|
+
* adapter persistence capture and rejects later mutation before flush. For
|
|
1601
|
+
* direct `withCommit` use, make domain decisions first, write, and register last.
|
|
1562
1602
|
*
|
|
1563
|
-
* **Duplicate
|
|
1603
|
+
* **Duplicate registration is idempotent by reference.** Registering the same
|
|
1564
1604
|
* instance repeatedly returns the same token, and a repeated token in
|
|
1565
|
-
* `commits` is
|
|
1605
|
+
* `commits` is collected once. A repeat call that omits `expectedVersion`
|
|
1566
1606
|
* makes no OCC assertion; only a supplied value that contradicts the
|
|
1567
|
-
*
|
|
1607
|
+
* registration-time baseline rejects. Each event lands in the outbox exactly once
|
|
1568
1608
|
* and post-commit acknowledgement runs exactly once. Two
|
|
1569
1609
|
* *different* instances with the same logical id cannot be detected
|
|
1570
1610
|
* at this layer; that is a Repository contract violation (failure to
|
|
@@ -1575,17 +1615,17 @@ interface WithCommitWorkResult<Evt extends AnyDomainEvent, R> {
|
|
|
1575
1615
|
*
|
|
1576
1616
|
* @example Tx-bound repos (Drizzle, Prisma, Mongo, …)
|
|
1577
1617
|
* ```typescript
|
|
1578
|
-
* const result = await withCommit({ outbox, bus, scope }, async (tx,
|
|
1618
|
+
* const result = await withCommit({ outbox, bus, scope }, async (tx, registration) => {
|
|
1579
1619
|
* const orderRepository = makeOrderRepository(tx); // your factory binds tx to the repo
|
|
1580
1620
|
* const order = await orderRepository.getById(orderId);
|
|
1581
1621
|
* order.confirm();
|
|
1582
1622
|
* await persistOrder(tx, order); // low-level adapter write
|
|
1583
|
-
* const commit =
|
|
1623
|
+
* const commit = registration.registerSaved(order); // attest the repository write
|
|
1584
1624
|
* return { result: order.id, commits: [commit] };
|
|
1585
1625
|
* });
|
|
1586
1626
|
* ```
|
|
1587
1627
|
*/
|
|
1588
|
-
declare function withCommit<Evt extends AnyDomainEvent, R, TCtx>(deps: WithCommitDeps<Evt, TCtx>, fn: (ctx: TCtx,
|
|
1628
|
+
declare function withCommit<Evt extends AnyDomainEvent, R, TCtx>(deps: WithCommitDeps<Evt, TCtx>, fn: (ctx: TCtx, registration: CommitRegistration<Evt>) => Promise<WithCommitWorkResult<Evt, R>>): Promise<R>;
|
|
1589
1629
|
//#endregion
|
|
1590
1630
|
//#region src/application/deadlines/deadline-store.d.ts
|
|
1591
1631
|
/**
|
|
@@ -1761,7 +1801,7 @@ interface IdempotencyLease {
|
|
|
1761
1801
|
/** Delay after which the wrapper should renew this lease. */
|
|
1762
1802
|
readonly renewAfterMs: number;
|
|
1763
1803
|
}
|
|
1764
|
-
/** Store-
|
|
1804
|
+
/** Store-issued ownership receipt for one successful claim. */
|
|
1765
1805
|
interface IdempotencyClaimHandle {
|
|
1766
1806
|
readonly key: string;
|
|
1767
1807
|
/** Unique across ownership generations for this key; treat as opaque. */
|
|
@@ -1820,7 +1860,7 @@ type IdempotencyClaim = {
|
|
|
1820
1860
|
*
|
|
1821
1861
|
* A NON-transactional store (the in-memory reference on its own, a separate
|
|
1822
1862
|
* durable store) cannot see commits or rollbacks. Every fresh claim
|
|
1823
|
-
* therefore returns a store-
|
|
1863
|
+
* therefore returns a store-issued token and bounded lease. The wrapper
|
|
1824
1864
|
* renews it while the transaction runs; `complete`, `renew`, `confirm`,
|
|
1825
1865
|
* `abandon`, and `reconcile` compare the token so a stale owner cannot
|
|
1826
1866
|
* mutate a successor claim. An expired PENDING claim may be replaced. An
|
|
@@ -1980,12 +2020,12 @@ interface WithIdempotentCommitDeps<Evt extends AnyDomainEvent, TCtx> extends Wit
|
|
|
1980
2020
|
* 2. A fresh claim carries an opaque ownership token. For a leased store the
|
|
1981
2021
|
* wrapper renews it at `renewAfterMs` until the transaction callback is
|
|
1982
2022
|
* ready to commit. A renewal failure rejects before commit and releases
|
|
1983
|
-
* the claim. `fn(ctx,
|
|
2023
|
+
* the claim. `fn(ctx, registration, execution)` receives the same token so a
|
|
1984
2024
|
* source-side marker can make later reconciliation conclusive.
|
|
1985
2025
|
* 3. `store.complete(ctx, claim, fn's result)` stages or completes the outcome
|
|
1986
|
-
* in the same transaction as aggregate writes and outbox. The
|
|
2026
|
+
* in the same transaction as aggregate writes and outbox. The registration
|
|
1987
2027
|
* capability is sealed and its token array copied before `complete` can
|
|
1988
|
-
* yield, so leaked callback state cannot change the
|
|
2028
|
+
* yield, so leaked callback state cannot change the collection receipt.
|
|
1989
2029
|
* 4. After commit, `store.confirm(claim)` finalizes a leased store's staged
|
|
1990
2030
|
* outcome; it is a no-op for transactional stores. A failure cannot reject
|
|
1991
2031
|
* an already committed write, so it is sent to `onIdempotencyError` and the
|
|
@@ -2008,7 +2048,7 @@ interface WithIdempotentCommitDeps<Evt extends AnyDomainEvent, TCtx> extends Wit
|
|
|
2008
2048
|
* production default. Leases make the non-transactional family recoverable;
|
|
2009
2049
|
* they do not manufacture an atomic exactly-once boundary across two stores.
|
|
2010
2050
|
*/
|
|
2011
|
-
declare function withIdempotentCommit<Evt extends AnyDomainEvent, R, TCtx>(deps: WithIdempotentCommitDeps<Evt, TCtx>, request: IdempotentCommitRequest, fn: (ctx: TCtx,
|
|
2051
|
+
declare function withIdempotentCommit<Evt extends AnyDomainEvent, R, TCtx>(deps: WithIdempotentCommitDeps<Evt, TCtx>, request: IdempotentCommitRequest, fn: (ctx: TCtx, registration: CommitRegistration<Evt>, execution: IdempotentExecution) => Promise<WithCommitWorkResult<Evt, R>>): Promise<IdempotentCommitResult<R>>;
|
|
2012
2052
|
//#endregion
|
|
2013
2053
|
//#region src/application/projections/ports.d.ts
|
|
2014
2054
|
/**
|
|
@@ -2193,7 +2233,7 @@ type AggregateClass<TAgg> = Function & {
|
|
|
2193
2233
|
* `AggregatePersistence` implementations: two `findById(id)` calls in the same
|
|
2194
2234
|
* unit of work MUST return the same instance, because commit-token
|
|
2195
2235
|
* write registration dedupes by JavaScript object identity. Two instances for
|
|
2196
|
-
* one logical aggregate can otherwise produce two tokens, two
|
|
2236
|
+
* one logical aggregate can otherwise produce two tokens, two collections,
|
|
2197
2237
|
* and two post-commit lifecycle calls.
|
|
2198
2238
|
*
|
|
2199
2239
|
* Storage is two-level (per-type stores created lazily), so
|
|
@@ -2254,7 +2294,7 @@ declare class IdentityMap {
|
|
|
2254
2294
|
* - Registering a DIFFERENT instance for an occupied type+id throws:
|
|
2255
2295
|
* that is precisely the identity-map violation this class exists
|
|
2256
2296
|
* to prevent (the repository hydrated twice instead of checking
|
|
2257
|
-
* {@link get} first), and letting it pass would double-
|
|
2297
|
+
* {@link get} first), and letting it pass would double-collect
|
|
2258
2298
|
* events downstream.
|
|
2259
2299
|
* - Registering a type+id that was {@link delete}d in this unit of
|
|
2260
2300
|
* work throws `AggregateDeletedError`: deletion is final within
|
|
@@ -2267,7 +2307,7 @@ declare class IdentityMap {
|
|
|
2267
2307
|
* Registered instances that have recorded MORE pending events than they
|
|
2268
2308
|
* carried when first registered (loaded). Used by the unit of work's
|
|
2269
2309
|
* end-of-run guard: an aggregate that gained events after load but was
|
|
2270
|
-
* never
|
|
2310
|
+
* never registered would silently drop them. A read-only load, or a
|
|
2271
2311
|
* reconstitution that already carried events, shows no increase and is
|
|
2272
2312
|
* not reported.
|
|
2273
2313
|
*/
|
|
@@ -2299,16 +2339,16 @@ declare class IdentityMap {
|
|
|
2299
2339
|
/** Whether a baseline represents an existing row or a pending insert. */
|
|
2300
2340
|
type PersistenceLifecycle = "loaded" | "new";
|
|
2301
2341
|
/**
|
|
2302
|
-
* Adapter-owned
|
|
2342
|
+
* Adapter-owned capture and change derivation for one aggregate type.
|
|
2303
2343
|
*
|
|
2304
2344
|
* The domain model does not implement this contract. A repository adapter
|
|
2305
|
-
* chooses what it persists, how
|
|
2345
|
+
* chooses what it persists, how it captures the aggregate at load, and
|
|
2306
2346
|
* whether a change set is a partial diff or a full replacement.
|
|
2307
2347
|
*/
|
|
2308
2348
|
interface PersistenceModel<TAggregate, TBaseline, TChangeSet> {
|
|
2309
2349
|
/**
|
|
2310
|
-
* Captures the
|
|
2311
|
-
* Return a
|
|
2350
|
+
* Captures the aggregate for the adapter at the current moment.
|
|
2351
|
+
* Return a copied value or an immutable value object: the Unit of Work
|
|
2312
2352
|
* retains it as a baseline and cannot make an arbitrary adapter type safe.
|
|
2313
2353
|
*
|
|
2314
2354
|
* Capture must be deterministic for an unchanged aggregate: the Unit of
|
|
@@ -2327,7 +2367,7 @@ interface PersistenceModel<TAggregate, TBaseline, TChangeSet> {
|
|
|
2327
2367
|
*/
|
|
2328
2368
|
capture(aggregate: TAggregate): TBaseline;
|
|
2329
2369
|
/**
|
|
2330
|
-
* Adapter-owned equality for two captures
|
|
2370
|
+
* Adapter-owned equality for two captures.
|
|
2331
2371
|
* Optional: the default is the package's structural deep equality (Set
|
|
2332
2372
|
* members and Map keys by reference). Supply it when the capture shape
|
|
2333
2373
|
* needs domain-specific comparison, for example rebuilt value-object Set
|
|
@@ -2346,46 +2386,11 @@ interface PersistenceModel<TAggregate, TBaseline, TChangeSet> {
|
|
|
2346
2386
|
/** Tells orchestration whether the derived state write is empty. */
|
|
2347
2387
|
isEmpty(changes: TChangeSet): boolean;
|
|
2348
2388
|
}
|
|
2349
|
-
declare const persistenceBaselineBrand: unique symbol;
|
|
2350
|
-
/**
|
|
2351
|
-
* Opaque, typed receipt for an adapter-owned persistence baseline.
|
|
2352
|
-
*
|
|
2353
|
-
* It intentionally exposes no data. The Unit of Work may retain the token and
|
|
2354
|
-
* ask the owning adapter capability to derive changes, but cannot branch on or
|
|
2355
|
-
* couple itself to the baseline's shape.
|
|
2356
|
-
*/
|
|
2357
|
-
interface PersistenceBaseline<TAggregate, TChangeSet> {
|
|
2358
|
-
readonly [persistenceBaselineBrand]: (aggregate: TAggregate) => TChangeSet;
|
|
2359
|
-
}
|
|
2360
2389
|
/** A derived adapter change set plus its adapter-defined emptiness result. */
|
|
2361
2390
|
interface PersistenceChanges<TChangeSet> {
|
|
2362
2391
|
readonly value: TChangeSet;
|
|
2363
2392
|
readonly empty: boolean;
|
|
2364
2393
|
}
|
|
2365
|
-
/** Captures a baseline for an aggregate restored by a repository adapter. */
|
|
2366
|
-
declare function capturePersistenceBaseline<TAggregate, TBaseline, TChangeSet>(model: PersistenceModel<TAggregate, TBaseline, TChangeSet>, aggregate: TAggregate): PersistenceBaseline<TAggregate, TChangeSet>;
|
|
2367
|
-
/** Creates the explicit no-row baseline for a newly added aggregate. */
|
|
2368
|
-
declare function insertPersistenceBaseline<TAggregate, TBaseline, TChangeSet>(model: PersistenceModel<TAggregate, TBaseline, TChangeSet>): PersistenceBaseline<TAggregate, TChangeSet>;
|
|
2369
|
-
/**
|
|
2370
|
-
* Captures the aggregate's current adapter projection using the capability
|
|
2371
|
-
* carried by an existing baseline. Used to seal persistence-last registration.
|
|
2372
|
-
*/
|
|
2373
|
-
declare function recapturePersistenceBaseline<TAggregate, TChangeSet>(baseline: PersistenceBaseline<TAggregate, TChangeSet>, aggregate: TAggregate): PersistenceBaseline<TAggregate, TChangeSet>;
|
|
2374
|
-
/**
|
|
2375
|
-
* Recaptures the adapter projection and reports whether it drifted from the
|
|
2376
|
-
* baseline's stored capture, using the model's `captureEquals` when supplied
|
|
2377
|
-
* and structural deep equality otherwise.
|
|
2378
|
-
*
|
|
2379
|
-
* This, not `changes()`/`isEmpty()`, is the mutation detector: the
|
|
2380
|
-
* `PersistenceModel` contract explicitly permits a full-replacement change
|
|
2381
|
-
* set whose `isEmpty` is never true, so a non-empty change set proves
|
|
2382
|
-
* nothing about mutation. Comparing capture to capture asks the honest
|
|
2383
|
-
* question independent of the model's diffing strategy. A `"new"` lifecycle
|
|
2384
|
-
* baseline has no stored capture and never reports drift.
|
|
2385
|
-
*/
|
|
2386
|
-
declare function persistenceProjectionDrifted<TAggregate, TChangeSet>(baseline: PersistenceBaseline<TAggregate, TChangeSet>, aggregate: TAggregate): boolean;
|
|
2387
|
-
/** Derives a typed adapter change set without exposing the stored baseline. */
|
|
2388
|
-
declare function derivePersistenceChanges<TAggregate, TChangeSet>(baseline: PersistenceBaseline<TAggregate, TChangeSet>, aggregate: TAggregate): PersistenceChanges<TChangeSet>;
|
|
2389
2394
|
//#endregion
|
|
2390
2395
|
//#region src/application/unit-of-work/persistence-contract.d.ts
|
|
2391
2396
|
/** The explicit persistence intent registered for one tracked aggregate. */
|
|
@@ -2399,7 +2404,7 @@ type UnitOfWorkIdentityMap = Pick<IdentityMap, "get" | "has" | "isDeleted">;
|
|
|
2399
2404
|
* restored aggregate.
|
|
2400
2405
|
* Write methods exposed to application code are replaced by Unit-of-Work-owned
|
|
2401
2406
|
* `add`, `update`, and `remove` registrations, so an adapter implementation of
|
|
2402
|
-
* those methods cannot perform durable I/O early or skip event
|
|
2407
|
+
* those methods cannot perform durable I/O early or skip event collection.
|
|
2403
2408
|
*
|
|
2404
2409
|
* Contract for repository implementations:
|
|
2405
2410
|
* - `findById(id)` checks `identityMap.get` BEFORE hydrating, treats
|
|
@@ -2785,7 +2790,7 @@ declare function reconstituteAggregateFromStreamPages<TAggregate extends Replaya
|
|
|
2785
2790
|
* `createSnapshotStoreContractTests` from `@shirudo/ddd-kit/testing`):
|
|
2786
2791
|
* the snapshot round-trips verbatim (`state` as plain data,
|
|
2787
2792
|
* `version`, `snapshotAt` with millisecond fidelity, `schemaVersion`
|
|
2788
|
-
* including its absence), loads return
|
|
2793
|
+
* including its absence), loads return copies (never live
|
|
2789
2794
|
* internal state), and keys are isolated per aggregate type AND id
|
|
2790
2795
|
* (one table may serve every aggregate type).
|
|
2791
2796
|
*
|
|
@@ -2820,5 +2825,5 @@ interface SnapshotStore<TState = unknown> {
|
|
|
2820
2825
|
delete(identity: AggregateIdentity): Promise<void>;
|
|
2821
2826
|
}
|
|
2822
2827
|
//#endregion
|
|
2823
|
-
export {
|
|
2828
|
+
export { Version as $, IdempotentCommitRequest as A, DomainEventFactoryOptions as At, CommitRegistrationOptions as B, createStampedDomainEvent as Bt, IdempotencyClaim as C, AnyUnstampedDomainEvent as Ct, IdempotencyReconciliation as D, CreateUnstampedDomainEventOptions as Dt, IdempotencyOperationErrorContext as E, CreateStampedDomainEventOptions as Et, DeadLetterDeadline as F, UnstampedDomainEvent as Ft, TransactionalOptions as G, Command as Gt, WithCommitWorkResult as H, mergeMetadata as Ht, DeadlineStore as I, UnstampedDomainEventOf as It, OnceOptions as J, JsonObject as Jt, EventBus as K, CommandHandler as Kt, DueDeadline as L, copyMetadata as Lt, IdempotentExecution as M, EventIdFactory as Mt, WithIdempotentCommitDeps as N, EventMetadata as Nt, IdempotencyReconciliationDecision as O, DomainEvent as Ot, withIdempotentCommit as P, PendingDomainEvent as Pt, ReplayableAggregate as Q, AggregateCommitToken as R, createDomainEvent as Rt, isPositionAfter as S, AnyDomainEvent as St, IdempotencyLease as T, CreateDomainEventStampOptions as Tt, withCommit as U, ClockFactory as Ut, WithCommitDeps as V, defaultDomainEventFactory as Vt, TransactionScope as W, AggregateIdentity as Wt, Aggregate as X, JsonValue as Xt, PublishOptions as Y, JsonPrimitive as Yt, AggregateSnapshot as Z, IdentityMap as _, ExecutionContext as _t, EventStoreAppendOptions as a, CommandMessageContent as at, ProjectionCheckpointStore as b, EventCommitCandidate as bt, StreamReadResult as c, CommandOutboxMapper as ct, RepositoryTracking as d, routeEventsToCommandOutbox as dt, sameVersion as et, UnitOfWorkIdentityMap as f, DeadLetterRecord as ft, AggregateClass as g, OutboxWriter as gt, PersistenceModel as h, OutboxRecord as ht, EventStore as i, CommandCommitOriginCandidate as it, IdempotentCommitResult as j, DomainEventStamp as jt, IdempotencyStore as k, DomainEventFactory as kt, AggregatePersistenceWrite as l, CommandOutboxWriter as lt, PersistenceLifecycle as m, Outbox as mt, ReplayableStreamPages as n, Id as nt, EventStreamReader as o, CommandMessageRelationships as ot, PersistenceChanges as p, DispatchTrackingOutbox as pt, EventHandler as q, PublishedCommand as qt, reconstituteAggregateFromStreamPages as r, IdGenerator as rt, ReadStreamOptions as s, CommandOutboxCommitCandidate as st, SnapshotStore as t, toVersion as tt, AggregateWriteIntent as u, DurableCommandMessage as ut, Projection as v, CommitPosition as vt, IdempotencyClaimHandle as w, CreateDomainEventOptions as wt, ProjectionPosition as x, EventCommitCandidatePosition as xt, ProjectionCheckpoint as y, CommittedDomainEvent as yt, CommitRegistration as z, createDomainEventFactory as zt };
|
|
2824
2829
|
//# sourceMappingURL=snapshot-store.d.ts.map
|