@shirudo/ddd-kit 3.0.0-rc.13 → 3.0.0-rc.15

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.
@@ -193,15 +193,16 @@ type ClockFactory = () => Date;
193
193
  /**
194
194
  * Factory function producing a fresh, unique event identifier for each call.
195
195
  *
196
- * The library ships a default that uses Web Crypto `crypto.randomUUID()`
197
- * (works on Node 19+, modern browsers in secure contexts, Deno, Bun,
198
- * Cloudflare Workers, Vercel Edge, and any runtime that implements Web
199
- * Crypto). Note that `crypto.randomUUID()` returns **UUID v4** (purely
200
- * random); for production event stores prefer a **time-ordered** id
201
- * format (UUID v7 / ULID / KSUID) so B-tree indexes on the eventId
202
- * column stay clustered and `ORDER BY eventId` matches creation order.
203
- * Supply one to {@link createDomainEventFactory} to use UUID v7, ULID,
204
- * KSUID, or another collision-safe format without mutating module state.
196
+ * The library ships a default that creates a time-ordered **UUID v7** from
197
+ * Web Crypto random bits (works on Node 19+, modern browsers in secure
198
+ * contexts, Deno, Bun, Cloudflare Workers, Vercel Edge, and any runtime that
199
+ * implements Web Crypto). A B-tree index on the eventId column stays
200
+ * clustered. The id is no ordering authority: ids from several processes
201
+ * interleave within one millisecond, and clock skew can invert them. Order
202
+ * events by aggregate version or stream position. Supply another generator
203
+ * to {@link createDomainEventFactory} to
204
+ * use ULID, KSUID, or another collision-safe format without mutating module
205
+ * state.
205
206
  */
206
207
  type EventIdFactory = () => string;
207
208
  /**
@@ -266,8 +267,8 @@ interface DomainEvent<T extends string, P = void> {
266
267
  /**
267
268
  * Unique identifier for this specific event instance. Used by idempotent
268
269
  * consumers, outbox dispatch tracking, and as the target of
269
- * `metadata.causationId`. Convenience constructors default to
270
- * `crypto.randomUUID()`; strict construction requires the caller to supply it.
270
+ * `metadata.causationId`. Convenience constructors default to a UUID v7;
271
+ * strict construction requires the caller to supply it.
271
272
  */
272
273
  readonly eventId: string;
273
274
  /**
@@ -291,7 +292,7 @@ interface DomainEvent<T extends string, P = void> {
291
292
  */
292
293
  readonly payload: P;
293
294
  /**
294
- * Timestamp when the accepted fact was recorded by the application shell.
295
+ * Timestamp when the application shell stamped the accepted fact.
295
296
  * Put business-relevant time in the payload under a domain name.
296
297
  */
297
298
  readonly occurredAt: Date;
@@ -342,10 +343,11 @@ type AnyUnstampedDomainEvent = UnstampedDomainEvent<string, unknown>;
342
343
  type UnstampedDomainEventOf<TEvent extends AnyDomainEvent> = TEvent extends DomainEvent<infer TType, infer TPayload> ? UnstampedDomainEvent<TType, TPayload> : never;
343
344
  /** An aggregate may hold unstamped decisions and already stamped events together. */
344
345
  type PendingDomainEvent<TEvent extends AnyDomainEvent> = TEvent | UnstampedDomainEventOf<TEvent>;
345
- /** Producer-owned options for an unstamped event. */
346
- interface CreateUnstampedDomainEventOptions {
347
- readonly aggregateId?: string;
348
- readonly aggregateType?: string;
346
+ /**
347
+ * Options of `createEvent` in an aggregate. The aggregate fills its own
348
+ * identity, so only the schema version of the payload is left.
349
+ */
350
+ interface CreateEventOptions {
349
351
  readonly schemaVersion?: number;
350
352
  }
351
353
  /**
@@ -355,7 +357,7 @@ interface CreateDomainEventOptions {
355
357
  /**
356
358
  * Override for the auto-generated `eventId`. Pass an existing id (for
357
359
  * replay, tests, or deterministic event sourcing) instead of letting the
358
- * factory call `crypto.randomUUID()`.
360
+ * factory generate a UUID v7.
359
361
  */
360
362
  eventId?: string;
361
363
  /**
@@ -402,7 +404,7 @@ interface CreateDomainEventStampOptions {
402
404
  }
403
405
  /** Dependencies captured by one immutable domain-event factory instance. */
404
406
  interface DomainEventFactoryOptions {
405
- /** Event-id generator. Defaults to Web Crypto `crypto.randomUUID()`. */
407
+ /** Event-id generator. Defaults to a UUID v7 from Web Crypto random bits. */
406
408
  readonly eventIdFactory?: EventIdFactory;
407
409
  /** Event-recording clock. Defaults to `() => new Date()`. */
408
410
  readonly clock?: ClockFactory;
@@ -442,7 +444,7 @@ interface DomainEventFactory {
442
444
  * The supplied functions are read once and captured by value. The returned
443
445
  * object is frozen, so another request, test, or library cannot replace its
444
446
  * policy. Its {@link DomainEventFactory.createStamp} method is the
445
- * application-shell bridge that records an accepted aggregate decision.
447
+ * application-shell bridge that stamps an accepted aggregate decision.
446
448
  * Passing the factory through `AggregateConfig`
447
449
  * enables the explicitly named convenience methods, whose defaults read time
448
450
  * and randomness.
@@ -453,8 +455,7 @@ interface DomainEventFactory {
453
455
  * eventIdFactory: () => uuidv7(),
454
456
  * clock: () => new Date(),
455
457
  * });
456
- * order.confirm();
457
- * stampPendingEvents(order, domainEvents);
458
+ * const uow = new UnitOfWork({ ...deps, eventStampFactory: domainEvents });
458
459
  * ```
459
460
  */
460
461
  declare function createDomainEventFactory(options?: DomainEventFactoryOptions): DomainEventFactory;
@@ -937,7 +938,7 @@ type Id<Tag extends string> = string & {
937
938
  * The kit makes no attempt to dedupe or detect collisions: a collision
938
939
  * silently overwrites earlier rows (under unique-key constraints) or
939
940
  * silently aliases two different entities (without them). Safe choices:
940
- * `crypto.randomUUID()` (UUIDv4, the default for events), ULID, UUIDv7,
941
+ * UUIDv7 (the default for events), `crypto.randomUUID()` (UUIDv4), ULID,
941
942
  * KSUID: all collision-resistant by design. Unsafe choices: `Date.now()`
942
943
  * alone (duplicates within the same millisecond), a process-local
943
944
  * counter without persistence (resets to 1 on restart, collides with
@@ -1396,6 +1397,43 @@ interface TransactionScope<TCtx> {
1396
1397
  transactional<T>(fn: (ctx: TCtx) => Promise<T>, options?: TransactionalOptions): Promise<T>;
1397
1398
  }
1398
1399
  //#endregion
1400
+ //#region src/application/unit-of-work/stamp-pending-events.d.ts
1401
+ /** Minimal shell role accepted by {@link stampPendingEvents}. */
1402
+ type DomainEventStampFactory = Pick<DomainEventFactory, "createStamp">;
1403
+ /** Per-decision stamp provider for metadata that depends on the event. */
1404
+ type DomainEventStampProvider<TEvent extends AnyDomainEvent> = (event: UnstampedDomainEventOf<TEvent>, index: number) => DomainEventStamp;
1405
+ /**
1406
+ * Stamp options that every decision of one stamping shares: a stamp time
1407
+ * and metadata such as the correlation id of the request. An `eventId` is per
1408
+ * fact, so the factory creates it for each decision.
1409
+ */
1410
+ type SharedDomainEventStampOptions = Omit<CreateDomainEventStampOptions, "eventId">;
1411
+ /**
1412
+ * Stamps every still-unstamped event accepted by an aggregate.
1413
+ *
1414
+ * The commit stamps every still-unstamped event when it registers the
1415
+ * aggregate, so a use case does not need this function. Call it first when
1416
+ * the metadata depends on the concrete decision, for an import, or for a test
1417
+ * that needs fixed ids. The commit keeps the stamps that this function set.
1418
+ *
1419
+ * The function is a command that also returns the stamped batch, a
1420
+ * deliberate exception to command-query separation: the caller hands the
1421
+ * batch on to persistence and needs no second read of `pendingEvents`.
1422
+ *
1423
+ * Stamping is atomic with respect to the aggregate's pending list: if stamp
1424
+ * creation or validation fails, every decision remains unstamped. A
1425
+ * successful second call returns the same event objects and does not read the
1426
+ * factory again, so an instance that a retried attempt registers again keeps
1427
+ * its event identities.
1428
+ *
1429
+ * Pass a `DomainEventFactory` (only its `createStamp` role is required) for one
1430
+ * uniform stamping policy. Add `stampOptions` for metadata that every
1431
+ * decision of the batch shares, such as the correlation id of the request.
1432
+ * Pass a callback instead when metadata depends on the concrete decision.
1433
+ */
1434
+ declare function stampPendingEvents<TId extends Id<string>, TEvent extends AnyDomainEvent>(aggregate: Aggregate<TId, TEvent>, factory: DomainEventStampFactory, stampOptions?: SharedDomainEventStampOptions): ReadonlyArray<TEvent>;
1435
+ declare function stampPendingEvents<TId extends Id<string>, TEvent extends AnyDomainEvent>(aggregate: Aggregate<TId, TEvent>, createStamp: DomainEventStampProvider<TEvent>): ReadonlyArray<TEvent>;
1436
+ //#endregion
1399
1437
  //#region src/application/cqrs/handler.d.ts
1400
1438
  /** Dependencies for {@link withCommit}. */
1401
1439
  interface WithCommitDeps<Evt extends AnyDomainEvent, TCtx> {
@@ -1459,6 +1497,19 @@ interface WithCommitDeps<Evt extends AnyDomainEvent, TCtx> {
1459
1497
  * unless the scope honors the signal.
1460
1498
  */
1461
1499
  signal?: AbortSignal;
1500
+ /**
1501
+ * Stamps every pending event that is still unstamped when its aggregate is
1502
+ * registered: the event id, the stamp time, and the metadata. Defaults to
1503
+ * `createDomainEventFactory()`, a UUID v7 from Web Crypto random bits and
1504
+ * the system clock. An event that is already stamped, for example by an
1505
+ * explicit `stampPendingEvents` call, keeps its stamp.
1506
+ */
1507
+ eventStampFactory?: DomainEventStampFactory;
1508
+ /**
1509
+ * Stamp options that every event stamped in this commit shares, for
1510
+ * example the correlation id of the request.
1511
+ */
1512
+ stampOptions?: SharedDomainEventStampOptions;
1462
1513
  }
1463
1514
  declare const aggregateCommitTokenBrand: unique symbol;
1464
1515
  /**
@@ -2667,6 +2718,10 @@ interface EventStore<Evt extends AnyDomainEvent> extends EventStreamReader<Evt>
2667
2718
  * proves the duplicate/gap rejection. The repository then calls
2668
2719
  * `replayHistory`, whose replay guard rejects any event carrying an
2669
2720
  * aggregate type or id that contradicts this stream key.
2721
+ * 7. **Stamped events only:** reject an event without an `eventId` with
2722
+ * a `TypeError`, before any write. A direct `withCommit` callback can
2723
+ * append before its registration stamps the events; this check makes
2724
+ * that mistake fail instead of storing ids that the outbox never sees.
2670
2725
  *
2671
2726
  * An empty `events` array is a no-op; implementations resolve without
2672
2727
  * touching the store (an ES repository skips `append` for aggregates
@@ -2825,5 +2880,5 @@ interface SnapshotStore<TState = unknown> {
2825
2880
  delete(identity: AggregateIdentity): Promise<void>;
2826
2881
  }
2827
2882
  //#endregion
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 };
2883
+ export { PublishOptions as $, JsonPrimitive as $t, IdempotentCommitRequest as A, CreateEventOptions as At, CommitRegistrationOptions as B, UnstampedDomainEventOf as Bt, IdempotencyClaim as C, CommittedDomainEvent as Ct, IdempotencyReconciliation as D, AnyUnstampedDomainEvent as Dt, IdempotencyOperationErrorContext as E, AnyDomainEvent as Et, DeadLetterDeadline as F, DomainEventStamp as Ft, DomainEventStampProvider as G, defaultDomainEventFactory as Gt, WithCommitWorkResult as H, createDomainEvent as Ht, DeadlineStore as I, EventIdFactory as It, TransactionScope as J, AggregateIdentity as Jt, SharedDomainEventStampOptions as K, mergeMetadata as Kt, DueDeadline as L, EventMetadata as Lt, IdempotentExecution as M, DomainEvent as Mt, WithIdempotentCommitDeps as N, DomainEventFactory as Nt, IdempotencyReconciliationDecision as O, CreateDomainEventOptions as Ot, withIdempotentCommit as P, DomainEventFactoryOptions as Pt, OnceOptions as Q, JsonObject as Qt, AggregateCommitToken as R, PendingDomainEvent as Rt, isPositionAfter as S, CommitPosition as St, IdempotencyLease as T, EventCommitCandidatePosition as Tt, withCommit as U, createDomainEventFactory as Ut, WithCommitDeps as V, copyMetadata as Vt, DomainEventStampFactory as W, createStampedDomainEvent as Wt, EventBus as X, CommandHandler as Xt, TransactionalOptions as Y, Command as Yt, EventHandler as Z, PublishedCommand as Zt, IdentityMap as _, DispatchTrackingOutbox as _t, EventStoreAppendOptions as a, toVersion as at, ProjectionCheckpointStore as b, OutboxWriter as bt, StreamReadResult as c, CommandCommitOriginCandidate as ct, RepositoryTracking as d, CommandOutboxCommitCandidate as dt, JsonValue as en, Aggregate as et, UnitOfWorkIdentityMap as f, CommandOutboxMapper as ft, AggregateClass as g, DeadLetterRecord as gt, PersistenceModel as h, routeEventsToCommandOutbox as ht, EventStore as i, sameVersion as it, IdempotentCommitResult as j, CreateStampedDomainEventOptions as jt, IdempotencyStore as k, CreateDomainEventStampOptions as kt, AggregatePersistenceWrite as l, CommandMessageContent as lt, PersistenceLifecycle as m, DurableCommandMessage as mt, ReplayableStreamPages as n, ReplayableAggregate as nt, EventStreamReader as o, Id as ot, PersistenceChanges as p, CommandOutboxWriter as pt, stampPendingEvents as q, ClockFactory as qt, reconstituteAggregateFromStreamPages as r, Version as rt, ReadStreamOptions as s, IdGenerator as st, SnapshotStore as t, AggregateSnapshot as tt, AggregateWriteIntent as u, CommandMessageRelationships as ut, Projection as v, Outbox as vt, IdempotencyClaimHandle as w, EventCommitCandidate as wt, ProjectionPosition as x, ExecutionContext as xt, ProjectionCheckpoint as y, OutboxRecord as yt, CommitRegistration as z, UnstampedDomainEvent as zt };
2829
2884
  //# sourceMappingURL=snapshot-store.d.ts.map
@@ -492,7 +492,7 @@ function comparePlainObjects(objA, objB, visited) {
492
492
  * path, so memoising the first path's result would be wrong. This is
493
493
  * inherently exponential for diamond-shaped sharing (a node reachable
494
494
  * via 2^n paths is cloned 2^n times); the walk aborts with a descriptive
495
- * error after {@link PATH_SENSITIVE_VISIT_BUDGET} node visits instead of
495
+ * error after a fixed budget of node visits instead of
496
496
  * hanging the process.
497
497
  *
498
498
  * **Prototype-pollution safety.** `__proto__` and `constructor` keys