@shirudo/ddd-kit 2.2.0 → 3.0.0-rc.3

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/testing.d.ts CHANGED
@@ -1,374 +1,556 @@
1
- import { I as IAggregateRoot, a as Id, A as AnyDomainEvent } from './aggregate-DFi6HlEh.js';
2
- import '@shirudo/result';
3
- import '@shirudo/base-error';
1
+ import { At as AnyDomainEvent, Et as IAggregateRoot, G as DispatchTrackingOutbox, L as CommandOutboxCommitCandidate, U as CommittedDomainEvent, Z as Outbox, a as StreamReadResult, b as IdempotencyStore, c as ProjectionCheckpointStore, en as Id, f as DeadlineStore, h as IdempotencyClaimHandle, i as ReadStreamOptions, it as PublishedCommand, n as EventStore, q as EventCommitCandidate, t as SnapshotStore, wt as AggregateAddress, z as CommandOutboxWriter } from "./chunks/snapshot-store.js";
4
2
 
3
+ //#region src/testing/contract-assertions.d.ts
5
4
  /**
6
- * The repository surface the event-sourced contract suite exercises.
7
- * Deliberately smaller than the state-stored `ContractRepository`: pure
8
- * event-sourced aggregates rarely have a meaningful `delete` (the
9
- * lifecycle ends with a `Closed` / `Terminated` event in the stream),
10
- * so the suite does not require one.
5
+ * One entry of a contract test suite. Every suite (repository,
6
+ * event-sourced repository, outbox, idempotency store) returns a list
7
+ * of these; bind them with
8
+ * `(test.skipped ? it.skip : it)(test.name, test.run)`.
11
9
  */
12
- interface EsContractRepository<TAgg extends IAggregateRoot<Id<string>, AnyDomainEvent>> {
13
- getById(id: TAgg["id"]): Promise<TAgg | null>;
14
- save(aggregate: TAgg): Promise<void>;
10
+ interface ContractTest {
11
+ name: string;
12
+ run: () => Promise<void>;
13
+ /** Present when the harness lacks the capability this test needs. */
14
+ skipped?: {
15
+ capability: string;
16
+ };
17
+ }
18
+ //#endregion
19
+ //#region src/testing/command-outbox-contract.d.ts
20
+ interface CommandOutboxContractEnvironment<C extends PublishedCommand> {
21
+ readonly outbox: CommandOutboxWriter<C>;
22
+ readonly addCommitted: (commits: ReadonlyArray<CommandOutboxCommitCandidate<C>>) => Promise<void>;
23
+ readonly addRolledBack?: (commits: ReadonlyArray<CommandOutboxCommitCandidate<C>>) => Promise<void>;
24
+ readonly readAll: () => Promise<ReadonlyArray<CommandOutboxCommitCandidate<C>>>;
25
+ readonly teardown?: () => Promise<void>;
26
+ }
27
+ interface CommandOutboxContractHarness<C extends PublishedCommand> {
28
+ readonly createEnvironment: () => Promise<CommandOutboxContractEnvironment<C>>;
29
+ /**
30
+ * Builds one command for the given seed. The suite derives conflicting
31
+ * commits from different seeds, so `createCommand` MUST return distinct
32
+ * command content per seed (put the seed in the payload). A constant
33
+ * command makes a manufactured conflict deep-equal to its original, and
34
+ * the conflict tests then fail a compliant adapter that deduplicates
35
+ * the exact retry.
36
+ */
37
+ readonly createCommand: (seed: number) => C;
38
+ readonly providesRolledBackAdds?: boolean;
39
+ }
40
+ type CommandOutboxContractTest = ContractTest;
41
+ declare function createCommandOutboxContractTests<C extends PublishedCommand>(harness: CommandOutboxContractHarness<C>): ReadonlyArray<CommandOutboxContractTest>;
42
+ //#endregion
43
+ //#region src/testing/deadline-store-contract.d.ts
44
+ /** One contract test; bind with `(test.skipped ? it.skip : it)(test.name, test.run)`. */
45
+ type DeadlineStoreContractTest = ContractTest;
46
+ /** The plain-data payload shape the suite round-trips. */
47
+ interface SuitePayload {
48
+ kind: string;
49
+ step?: number;
15
50
  }
16
51
  /**
17
- * One isolated test environment: fresh event store, fresh outbox. The
18
- * suite creates one per test and tears it down afterwards.
52
+ * One isolated test environment: a fresh deadline store. The suite
53
+ * creates one per test and tears it down afterwards.
19
54
  */
20
- interface EsRepositoryContractEnvironment<TAgg extends IAggregateRoot<Id<string>, Evt>, Evt extends AnyDomainEvent = AnyDomainEvent> {
21
- /**
22
- * Execute one unit of work against the adapter under test. Wire this
23
- * through your real `UnitOfWork` / `withCommit` setup: the commit
24
- * boundary IS part of what the suite proves (outbox harvest,
25
- * `markPersisted`, rollback purity).
26
- */
27
- run<R>(work: (ctx: {
28
- repository: EsContractRepository<TAgg>;
29
- }) => Promise<R>): Promise<R>;
30
- /**
31
- * All events currently persisted in the outbox (committed writes
32
- * only; a rolled-back transaction's events must not appear here).
33
- */
34
- committedOutboxEvents(): Promise<ReadonlyArray<Evt>>;
35
- /**
36
- * The COMMITTED stream for the given aggregate id, in stream order,
37
- * optionally only the events after `fromVersion` (the snapshot
38
- * catch-up read). Implement this through your adapter's
39
- * `EventStore.readStream` so the suite's ordering and slicing
40
- * assertions exercise your real read path. A rolled-back
41
- * transaction's events must not appear here.
42
- */
43
- committedStreamEvents(id: TAgg["id"], fromVersion?: number): Promise<ReadonlyArray<Evt>>;
44
- /** Release connections, drop schemas, etc. Called in a finally. */
45
- teardown?(): Promise<void>;
55
+ interface DeadlineStoreContractEnvironment {
56
+ /** The adapter under test. */
57
+ store: DeadlineStore<SuitePayload>;
58
+ /**
59
+ * Runs `work` (schedule/cancel calls) the way production does:
60
+ * inside a transaction that COMMITS. For a non-transactional store
61
+ * this simply invokes `work`.
62
+ */
63
+ run<R>(work: () => Promise<R>): Promise<R>;
64
+ /**
65
+ * Optional capability: runs `work` inside a transaction that ROLLS
66
+ * BACK. Enables the rollback tests: a rolled-back schedule must not
67
+ * leave a deadline behind (a ghost input for a state change that
68
+ * never happened), and a rolled-back cancel must not have removed
69
+ * one. Transactional adapters should always provide this.
70
+ */
71
+ runRolledBack?<R>(work: () => Promise<R>): Promise<R>;
72
+ /** Release connections, drop schemas, etc. Called in a finally. */
73
+ teardown?(): Promise<void>;
46
74
  }
47
75
  /**
48
- * What an adapter supplies to run the event-sourced contract suite.
49
- *
50
- * The harness MUST provide isolation per environment. **For SQL-backed
51
- * event stores this must run against a real database** (testcontainers
52
- * or equivalent): the mandatory two-writer test proves YOUR
53
- * expectedVersion guard, and an in-memory stand-in proves only itself.
76
+ * What an adapter supplies to run the deadline-store contract suite.
77
+ * For SQL adapters, run against a real database (testcontainers or
78
+ * equivalent): the rollback tests prove YOUR transaction wiring, and
79
+ * schedule/cancel joining the write transaction is the port's central
80
+ * correctness rule.
81
+ */
82
+ interface DeadlineStoreContractHarness {
83
+ createEnvironment(): Promise<DeadlineStoreContractEnvironment>;
84
+ /**
85
+ * The adapter's attempt ceiling: how many `markFailed` reports move
86
+ * a deadline to the dead-letter set. Must be at least 2 so the
87
+ * attempts-surfacing test can observe a survivor.
88
+ */
89
+ failuresToDeadLetter: number;
90
+ /**
91
+ * Declare `true` when environments provide {@link
92
+ * DeadlineStoreContractEnvironment.runRolledBack}. Without it, the
93
+ * rollback tests are marked skipped: the honest state of an
94
+ * in-memory fake, and a loud gap for a transactional adapter.
95
+ */
96
+ providesRolledBackRuns?: boolean;
97
+ /**
98
+ * Declare `true` when the adapter's `due` CLAIMS the returned
99
+ * records for competing pollers (lease, visibility timeout), as the
100
+ * port sanctions. Tests that re-poll records an earlier poll
101
+ * returned without resolving them (attempts surfacing, neighbor
102
+ * flow after a dead-letter, successor visibility during a
103
+ * reschedule race) assume a non-claiming read and are marked
104
+ * skipped for claiming adapters; prove your claim/expiry semantics
105
+ * in your own suite.
106
+ */
107
+ claimsOnDue?: boolean;
108
+ }
109
+ /**
110
+ * The deadline-store contract test suite: the proof that an adapter
111
+ * delivers the schedule/cancel/due/acknowledge semantics the port
112
+ * documents. Store semantics are an **adapter contract, not a kit
113
+ * guarantee**; this suite is how an adapter demonstrates them.
54
114
  *
55
- * Aggregate arithmetic the suite relies on:
56
- * - `createAggregate()` returns a fresh aggregate with exactly ONE
57
- * applied creation event (version 1, `persistedVersion === undefined`).
58
- * - `mutate()` applies exactly ONE event (+1 version).
115
+ * Framework-agnostic: bind with
116
+ * `(test.skipped ? it.skip : it)(test.name, test.run)`.
59
117
  */
60
- interface EsRepositoryContractHarness<TAgg extends IAggregateRoot<Id<string>, Evt>, Evt extends AnyDomainEvent = AnyDomainEvent> {
61
- createEnvironment(): Promise<EsRepositoryContractEnvironment<TAgg, Evt>>;
62
- /**
63
- * A brand-new aggregate: exactly one applied creation event, unique
64
- * id, version 1, `persistedVersion === undefined`.
65
- */
66
- createAggregate(): TAgg;
67
- /** Apply exactly ONE domain event via `apply()` (+1 version). */
68
- mutate(aggregate: TAgg): void;
69
- /**
70
- * Optional: construct a NEW (never-persisted) aggregate carrying a
71
- * SPECIFIC id, with its creation event applied. Enables the
72
- * duplicate-create conflict test (two creators racing on one stream).
73
- */
74
- createAggregateWithId?(id: TAgg["id"]): TAgg;
75
- /**
76
- * Optional: a plain-data projection of the aggregate's state,
77
- * compared with deep equality. Enables the state assertions in the
78
- * mandatory and replay-equality tests. Must be roundtrip-stable
79
- * across your store (see the state-stored harness JSDoc for the
80
- * normalization checklist).
81
- */
82
- snapshotState?(aggregate: TAgg): unknown;
118
+ declare function createDeadlineStoreContractTests(harness: DeadlineStoreContractHarness): DeadlineStoreContractTest[];
119
+ //#endregion
120
+ //#region src/testing/es-repository-contract.d.ts
121
+ /** Event-sourced repositories normally expose no physical removal. */
122
+ interface EsContractRepository<TAggregate extends IAggregateRoot<Id<string>, AnyDomainEvent>> {
123
+ findById(id: TAggregate["id"]): Promise<TAggregate | undefined>;
124
+ add(aggregate: TAggregate): void;
125
+ update(aggregate: TAggregate): void;
126
+ }
127
+ interface EsRepositoryContractEnvironment<TAggregate extends IAggregateRoot<Id<string>, TEvent>, TEvent extends AnyDomainEvent = AnyDomainEvent> {
128
+ run<R>(work: (context: {
129
+ repository: EsContractRepository<TAggregate>;
130
+ }) => Promise<R>): Promise<R>;
131
+ committedOutboxEvents(): Promise<ReadonlyArray<CommittedDomainEvent<TEvent>>>;
132
+ failNextOutboxWrite(error: Error): void;
133
+ committedStreamEvents(stream: AggregateAddress<TAggregate["id"]>, options: ReadStreamOptions): Promise<StreamReadResult<TEvent>>;
134
+ teardown?(): Promise<void>;
83
135
  }
84
- /** One named contract test; see the state-stored suite for the binding pattern. */
85
- interface EsRepositoryContractTest {
86
- name: string;
87
- run: () => Promise<void>;
88
- /** Present when the harness lacks the capability this test needs. */
89
- skipped?: {
90
- capability: string;
91
- };
136
+ interface EsRepositoryContractHarness<TAggregate extends IAggregateRoot<Id<string>, TEvent>, TEvent extends AnyDomainEvent = AnyDomainEvent> {
137
+ createEnvironment(): Promise<EsRepositoryContractEnvironment<TAggregate, TEvent>>;
138
+ /** Fresh aggregate with exactly one recorded creation event. */
139
+ createAggregate(): TAggregate;
140
+ createAggregateWithId?(id: TAggregate["id"]): TAggregate;
141
+ streamKeyFor(id: TAggregate["id"]): AggregateAddress<TAggregate["id"]>;
142
+ /** Applies exactly one event and advances the aggregate version by one. */
143
+ mutate(aggregate: TAggregate): void;
144
+ snapshotState?(aggregate: TAggregate): unknown;
92
145
  }
146
+ type EsRepositoryContractTest = ContractTest;
93
147
  /**
94
- * The event-sourced repository contract test suite: the proof that an
95
- * adapter delivers what the kit's `EventStore` port and Unit of Work
96
- * document. The kit is store-agnostic: the expectedVersion guard lives
97
- * in YOUR adapter's append. That makes stream OCC a **repository
98
- * contract, not a kit guarantee**; an adapter that has not passed this
99
- * suite (against a real store, for SQL-backed adapters) has not
100
- * demonstrated it.
101
- *
102
- * What each test proves:
103
- * - The MANDATORY two-writer test proves your append's expectedVersion
104
- * guard and its atomicity.
105
- * - The replay/lifecycle tests prove your read path (fold order,
106
- * identity map wiring) and the commit lifecycle (outbox harvest,
107
- * `markPersisted`, rollback purity).
108
- * - The duplicate-create and fromVersion tests prove the create race
109
- * and the snapshot catch-up read.
148
+ * Contract suite for event-stream adapters using v3 Unit-of-Work receipts.
110
149
  *
111
- * Error matching is by NAME along the `cause` chain, not `instanceof`
112
- * (same rationale as the state-stored suite). Binding:
113
- *
114
- * ```ts
115
- * for (const test of createEsRepositoryContractTests(harness)) {
116
- * (test.skipped ? it.skip : it)(test.name, test.run);
117
- * }
118
- * ```
119
- *
120
- * **Known limitation:** like the state-stored suite, the two-writer
121
- * test is sequential-deterministic; lock interaction and raw
122
- * serialization failures (Postgres 40001) need adapter-specific tests
123
- * on top.
150
+ * `add` and `update` register intent only. At commit, the adapter appends the
151
+ * receipt's exact event batch with the Unit of Work's expected version, in the
152
+ * same transaction as the outbox. `run` must support overlapping calls so the
153
+ * mandatory stale-writer proof exercises a real stream OCC predicate.
124
154
  */
125
- declare function createEsRepositoryContractTests<TAgg extends IAggregateRoot<Id<string>, Evt>, Evt extends AnyDomainEvent = AnyDomainEvent>(harness: EsRepositoryContractHarness<TAgg, Evt>): EsRepositoryContractTest[];
126
-
155
+ declare function createEsRepositoryContractTests<TAggregate extends IAggregateRoot<Id<string>, TEvent>, TEvent extends AnyDomainEvent = AnyDomainEvent>(harness: EsRepositoryContractHarness<TAggregate, TEvent>): EsRepositoryContractTest[];
156
+ //#endregion
157
+ //#region src/testing/event-store-contract.d.ts
158
+ /** One named contract test for an EventStore adapter. */
159
+ type EventStoreContractTest = ContractTest;
160
+ /** One isolated adapter instance. The suite creates one per test. */
161
+ interface EventStoreContractEnvironment<Evt extends AnyDomainEvent> {
162
+ readonly store: EventStore<Evt>;
163
+ teardown?(): Promise<void>;
164
+ }
127
165
  /**
128
- * The repository surface the contract suite exercises: the minimal
129
- * structural subset of the canonical `IUnitOfWorkRepository` (exported
130
- * from the main entry) that the tests need. `getById` is typed over
131
- * the aggregate's own branded id (`TAgg["id"]`), so concrete adapters,
132
- * including arrow-function-property style repositories, which are
133
- * checked contravariantly, match without casts.
166
+ * Inputs needed to prove the EventStore's observable port contract.
167
+ *
168
+ * `createCollidingStreamKeys` must return two valid stream keys with the same
169
+ * raw aggregate id and different aggregate types. `createEvent` must return an
170
+ * event addressed to the supplied key; different sequence values must produce
171
+ * different event ids.
134
172
  */
135
- interface ContractRepository<TAgg extends IAggregateRoot<Id<string>, AnyDomainEvent>> {
136
- getById(id: TAgg["id"]): Promise<TAgg | null>;
137
- save(aggregate: TAgg): Promise<void>;
138
- delete(aggregate: TAgg): Promise<void>;
173
+ interface EventStoreContractHarness<Evt extends AnyDomainEvent> {
174
+ createEnvironment(): Promise<EventStoreContractEnvironment<Evt>>;
175
+ createCollidingStreamKeys(): readonly [AggregateAddress, AggregateAddress];
176
+ createEvent(stream: AggregateAddress, sequence: number): Evt;
139
177
  }
140
178
  /**
141
- * One isolated test environment: fresh storage, fresh outbox. The
142
- * suite creates one per test via {@link RepositoryContractHarness} and
143
- * tears it down afterwards (a teardown failure never masks an
144
- * in-flight contract violation).
179
+ * Reusable proof of an EventStore adapter's portable semantics: qualified
180
+ * value identity, ordered reads and slicing, OCC error mapping and atomicity,
181
+ * no-op empty appends, and detached return arrays. Physical-position
182
+ * corruption needs adapter-specific fixture support and is tested there.
183
+ */
184
+ declare function createEventStoreContractTests<Evt extends AnyDomainEvent>(harness: EventStoreContractHarness<Evt>): EventStoreContractTest[];
185
+ //#endregion
186
+ //#region src/testing/idempotency-store-contract.d.ts
187
+ /** One contract test; bind with `(test.skipped ? it.skip : it)(test.name, test.run)`. */
188
+ type IdempotencyStoreContractTest = ContractTest;
189
+ /**
190
+ * One isolated test environment: a fresh idempotency store. The suite
191
+ * creates one per test and tears it down afterwards.
145
192
  */
146
- interface RepositoryContractEnvironment<TAgg extends IAggregateRoot<Id<string>, Evt>, Evt extends AnyDomainEvent = AnyDomainEvent> {
147
- /**
148
- * Execute one unit of work against the adapter under test: open the
149
- * transaction, hand the suite a tx-bound repository, commit on
150
- * resolve, roll back on throw, and run the post-commit lifecycle
151
- * (event harvest into the outbox, `markPersisted`). Wire this
152
- * through your real `UnitOfWork` / `withCommit` setup: the commit
153
- * boundary IS part of what the suite proves.
154
- */
155
- run<R>(work: (ctx: {
156
- repository: ContractRepository<TAgg>;
157
- }) => Promise<R>): Promise<R>;
158
- /**
159
- * All events currently persisted in the outbox (committed writes
160
- * only; a rolled-back transaction's events must not appear here).
161
- */
162
- committedOutboxEvents(): Promise<ReadonlyArray<Evt>>;
163
- /** Release connections, drop schemas, etc. Called in a finally. */
164
- teardown?(): Promise<void>;
193
+ interface IdempotencyStoreContractEnvironment<TCtx> {
194
+ /** The adapter under test. */
195
+ store: IdempotencyStore<TCtx>;
196
+ /**
197
+ * Runs `work` inside a transaction that COMMITS, handing it the
198
+ * transaction context the store methods expect, the way
199
+ * `withIdempotentCommit` calls them in production. For a
200
+ * non-transactional store this simply invokes `work` with a dummy
201
+ * context.
202
+ */
203
+ run<R>(work: (ctx: TCtx) => Promise<R>): Promise<R>;
204
+ /**
205
+ * For the `"transactional"` family: runs `work` inside a transaction
206
+ * that ROLLS BACK. Required there; the rollback-releases-the-claim
207
+ * test is that family's core proof. Irrelevant for the
208
+ * `"non-transactional"` family.
209
+ */
210
+ runRolledBack?<R>(work: (ctx: TCtx) => Promise<R>): Promise<R>;
211
+ /**
212
+ * For the `"non-transactional"` family: advances or edits adapter test
213
+ * state so this exact claim's CURRENT lease is expired. Required there;
214
+ * a fake clock or test-only row update keeps the suite deterministic.
215
+ */
216
+ expireLease?(claim: IdempotencyClaimHandle): Promise<void>;
217
+ /**
218
+ * Moves the adapter's test clock to an exact instant. Required by the
219
+ * non-transactional family so renewal is proved without wall-clock sleeps.
220
+ */
221
+ advanceTimeTo?(instant: Date): Promise<void>;
222
+ /** Release connections, drop schemas, etc. Called in a finally. */
223
+ teardown?(): Promise<void>;
165
224
  }
166
225
  /**
167
- * What an adapter supplies to run the contract suite.
226
+ * What an adapter supplies to run the idempotency-store contract suite.
168
227
  *
169
- * The harness MUST provide isolation per environment (fresh
170
- * tables/keyspace or a truncate); tests assume they see only their
171
- * own writes. **For SQL/ORM adapters this must run against a real
172
- * database** (testcontainers or equivalent), not an in-memory fake:
173
- * the mandatory two-writer test proves YOUR `WHERE version = ?`
174
- * predicate, and an in-memory stand-in proves only itself.
228
+ * The port (`IdempotencyStore`) deliberately supports two adapter
229
+ * families with different lifecycle semantics, and the suite follows
230
+ * the declared family instead of forcing one onto the other:
175
231
  *
176
- * Optional capabilities widen the suite: tests for an absent
177
- * capability come back **marked `skipped`** with a `run()` that
178
- * rejects loudly: bind them with `it.skip` so the gap stays visible
179
- * in every report (see {@link RepositoryContractTest}); a naive
180
- * binding fails instead of green-no-op'ing. Capabilities are captured
181
- * once at suite creation. Provide every capability your adapter can
182
- * support; each one closes a real OCC hole.
232
+ * - `"transactional"` (the single-transaction pattern): the record
233
+ * lives in the same database as the aggregates; a committed
234
+ * `complete` is final and replayable, a rollback releases everything,
235
+ * and `renew`/`confirm`/`abandon`/`reconcile` are no-ops. The family's core proof is the
236
+ * rollback test, so environments MUST provide `runRolledBack`, and
237
+ * run against a real database for SQL adapters.
238
+ * - `"non-transactional"` (the leased two-phase pattern, e.g. the
239
+ * in-memory reference): the store cannot see commits, so `complete` only
240
+ * STAGES the outcome, `confirm` finalizes it post-commit, `abandon`
241
+ * compensates failed attempts, and expired staged records require
242
+ * reconciliation. Environments MUST provide deterministic `expireLease` and
243
+ * `advanceTimeTo` controls. The rollback test is skipped.
183
244
  */
184
- interface RepositoryContractHarness<TAgg extends IAggregateRoot<Id<string>, Evt>, Evt extends AnyDomainEvent = AnyDomainEvent> {
185
- createEnvironment(): Promise<RepositoryContractEnvironment<TAgg, Evt>>;
186
- /**
187
- * A brand-new aggregate (never persisted, unique id,
188
- * `persistedVersion === undefined`).
189
- */
190
- createAggregate(): TAgg;
191
- /**
192
- * Apply exactly ONE version-bumping domain mutation that records at
193
- * least one domain event (a `commit()`-style state change). The
194
- * suite relies on the +1-per-call arithmetic and on the event for
195
- * its outbox assertions.
196
- */
197
- mutate(aggregate: TAgg): void;
198
- /**
199
- * Optional: a version-bumping mutation whose state is deep-equal to
200
- * the previous state (`setState({...state}, true)`). Enables the
201
- * version-only-change-still-persists test: the skip-save/OCC-desync
202
- * trap.
203
- */
204
- mutateVersionOnly?(aggregate: TAgg): void;
205
- /**
206
- * Optional: a mutation that changes ONLY a child collection (a
207
- * non-root-row `changedKeys` entry). Enables the
208
- * child-change-bumps-root-version test for partial-write
209
- * repositories.
210
- */
211
- mutateChildCollection?(aggregate: TAgg): void;
212
- /**
213
- * Optional: construct a NEW (never-persisted) aggregate instance
214
- * carrying a SPECIFIC id. Enables TWO tests: deletion-is-final-
215
- * across-instances (resurrection via a factory after delete) and
216
- * the duplicate-insert test (see
217
- * {@link insertsAreDuplicateChecked} to opt out of the latter
218
- * independently).
219
- */
220
- createAggregateWithId?(id: TAgg["id"]): TAgg;
221
- /**
222
- * Semantic opt-OUT (default `true`): whether `save()`'s INSERT path
223
- * rejects an existing id with `DuplicateAggregateError` (mapping the
224
- * driver's unique-violation: Postgres `23505`, MySQL `1062`, SQLite
225
- * `SQLITE_CONSTRAINT_UNIQUE`). This is the near-mandatory contract:
226
- * `save()` is insert-or-update, never upsert. Create-idempotency
227
- * belongs in the USE CASE (load, then decide), not in the save path.
228
- * Set `false` ONLY for a deliberately upserting adapter
229
- * (idempotent-create design); the duplicate-insert test is then
230
- * reported as skipped under this capability name, without costing
231
- * the deletion-finality coverage that `createAggregateWithId` also
232
- * gates.
233
- */
234
- insertsAreDuplicateChecked?: boolean;
235
- /**
236
- * Optional: a plain-data projection of the aggregate's persisted
237
- * state, compared with deep equality. Enables the mandatory test's
238
- * state assertion (without it, only the version and the outbox are
239
- * compared, and an adapter whose predicate guards the version write
240
- * but not the state write would slip through).
241
- *
242
- * **The projection must be roundtrip-stable**: it compares a
243
- * DB-reloaded aggregate against an in-memory one, so normalize
244
- * anything your store changes in transit: dates to ISO strings at
245
- * your store's precision (MySQL DATETIME truncates millis), no
246
- * `undefined`-valued keys (JSON columns drop them), decimals/bigints
247
- * to one consistent representation. A mismatch here fails the
248
- * mandatory test; the message names the projection as a suspect.
249
- */
250
- snapshotState?(aggregate: TAgg): unknown;
251
- /**
252
- * Optional flag: declare it when your `delete(aggregate)` runs an
253
- * OCC predicate (`DELETE … WHERE id = ? AND version = ?`). Enables
254
- * the stale-delete conflict test. Unpredicated deletes are
255
- * last-write-wins by construction: acceptable for GC-style
256
- * cleanup, rarely for user-initiated deletion of contended
257
- * aggregates (see the repository guide).
258
- */
259
- deletesAreVersionChecked?: boolean;
245
+ interface IdempotencyStoreContractHarness<TCtx> {
246
+ createEnvironment(): Promise<IdempotencyStoreContractEnvironment<TCtx>>;
247
+ /** Which lifecycle family the adapter implements; see above. */
248
+ family: "transactional" | "non-transactional";
260
249
  }
261
250
  /**
262
- * One named contract test; `run` rejects with a descriptive Error on
263
- * violation. When the harness lacks the capability a test needs, the
264
- * entry is still returned with {@link skipped} set and a `run` that
265
- * REJECTS with an explanatory error: bind it with your runner's skip
266
- * (`(test.skipped ? it.skip : it)(test.name, test.run)`) so the gap is
267
- * visible in every test report: a missing capability must never look
268
- * like green coverage, and a naive binding that ignores `skipped`
269
- * fails loud instead of passing silently.
251
+ * The idempotency-store contract test suite: the proof that an adapter
252
+ * delivers the claim/renew/complete/confirm/abandon/reconcile lifecycle
253
+ * `withIdempotentCommit` documents, for its declared family. Store
254
+ * semantics are an **adapter contract, not a kit guarantee**; this
255
+ * suite is how an adapter demonstrates them.
256
+ *
257
+ * Framework-agnostic: bind with
258
+ * `(test.skipped ? it.skip : it)(test.name, test.run)`.
270
259
  */
271
- interface RepositoryContractTest {
272
- name: string;
273
- run: () => Promise<void>;
274
- /** Present when the harness lacks the capability this test needs. */
275
- skipped?: {
276
- capability: string;
277
- };
260
+ declare function createIdempotencyStoreContractTests<TCtx>(harness: IdempotencyStoreContractHarness<TCtx>): IdempotencyStoreContractTest[];
261
+ //#endregion
262
+ //#region src/testing/outbox-contract.d.ts
263
+ /** One contract test; bind with `(test.skipped ? it.skip : it)(test.name, test.run)`. */
264
+ type OutboxContractTest = ContractTest;
265
+ /**
266
+ * One isolated test environment: a fresh outbox store. The suite
267
+ * creates one per test and tears it down afterwards.
268
+ */
269
+ interface OutboxContractEnvironment<Evt extends AnyDomainEvent> {
270
+ /** The adapter under test. */
271
+ outbox: Outbox<Evt> | DispatchTrackingOutbox<Evt>;
272
+ /**
273
+ * Runs `outbox.add(candidates)` inside a transaction that COMMITS, the
274
+ * way `withCommit` calls it in production. The suite supplies complete
275
+ * candidates with explicit source, aggregate version, zero-based commit
276
+ * sequence, and commit size. For a non-transactional store this is simply
277
+ * `outbox.add(candidates)`.
278
+ */
279
+ addCommitted(events: ReadonlyArray<EventCommitCandidate<Evt>>): Promise<void>;
280
+ /**
281
+ * Optional capability: runs `outbox.add(candidates)` inside a
282
+ * transaction that ROLLS BACK. Enables the rollback-purity test: a
283
+ * rolled-back add must leave nothing behind. Transactional adapters
284
+ * should always provide this; it is the half of the outbox promise
285
+ * that in-memory fakes cannot keep.
286
+ */
287
+ addRolledBack?(events: ReadonlyArray<EventCommitCandidate<Evt>>): Promise<void>;
288
+ /** Release connections, drop schemas, etc. Called in a finally. */
289
+ teardown?(): Promise<void>;
278
290
  }
279
291
  /**
280
- * The repository contract test suite: the proof that an adapter
281
- * actually delivers the guarantees the kit's Unit of Work documents.
282
- *
283
- * The kit is ORM-agnostic: the OCC version predicate lives in YOUR
284
- * repository's SQL. That makes optimistic concurrency a **repository
285
- * contract, not a kit guarantee**: the kit ships the boundary, the
286
- * `persistedVersion` baseline, `ConcurrencyConflictError`, and this
287
- * suite; your adapter must pass it. An adapter that has not passed the
288
- * suite (against a real database, for SQL adapters) has not
289
- * demonstrated OCC.
292
+ * What an adapter supplies to run the outbox contract suite.
290
293
  *
291
- * Framework-agnostic: assertions throw plain `Error`s, so the suite
292
- * binds to vitest, jest, or `node:test` the same way:
294
+ * For SQL adapters, run against a real database (testcontainers or
295
+ * equivalent): the commit-order and rollback tests prove YOUR schema
296
+ * and transaction wiring, not the kit's.
293
297
  *
294
- * ```ts
295
- * import { describe, it } from "vitest";
296
- * import { createRepositoryContractTests } from "@shirudo/ddd-kit/testing";
298
+ * Note on claiming: multi-instance safety (`getPending` claiming via
299
+ * `FOR UPDATE SKIP LOCKED` or equivalent) is part of the port contract
300
+ * for competing dispatchers but is not covered here; concurrency
301
+ * cannot be proven portably by a generic suite. Test it in your
302
+ * adapter's own suite if you run more than one dispatcher.
303
+ */
304
+ interface OutboxContractHarness<Evt extends AnyDomainEvent> {
305
+ createEnvironment(): Promise<OutboxContractEnvironment<Evt>>;
306
+ /**
307
+ * Deterministic event factory: the same `seed` yields an event with
308
+ * the SAME `eventId` (the suite uses this for the dedupe test), and
309
+ * different seeds yield distinct `eventId`s.
310
+ */
311
+ createEvent(seed: number): Evt;
312
+ /**
313
+ * For a `DispatchTrackingOutbox` adapter: how many `markFailed`
314
+ * reports move a record to the dead-letter set (the adapter's
315
+ * configured attempt ceiling). Omit for plain `Outbox` adapters;
316
+ * the dispatch-tracking tests are then marked skipped. With a
317
+ * ceiling of 1 the attempts-surfacing test is marked skipped too:
318
+ * observing attempts on a PENDING record needs a record that
319
+ * survives one failure.
320
+ */
321
+ failuresToDeadLetter?: number;
322
+ /**
323
+ * Declare `true` when environments provide {@link
324
+ * OutboxContractEnvironment.addRolledBack}. Without it, the
325
+ * rollback-purity test is marked skipped: the honest state of an
326
+ * in-memory fake, and a loud gap for a transactional adapter.
327
+ */
328
+ providesRolledBackAdds?: boolean;
329
+ /**
330
+ * Declare `true` when the adapter's `getPending` CLAIMS the returned
331
+ * records for competing dispatchers (lease, visibility timeout,
332
+ * `FOR UPDATE SKIP LOCKED`), as the port sanctions. Every test that
333
+ * re-polls records a previous poll returned without resolving them
334
+ * (head stability, re-ack non-disturbance, attempts surfacing)
335
+ * assumes a non-claiming read and is marked skipped for claiming
336
+ * adapters; prove your claim/expiry semantics in your own suite.
337
+ */
338
+ claimsOnGetPending?: boolean;
339
+ /**
340
+ * Declare `true` when `add()` dedupes on `eventId` (the unique-key
341
+ * constraint the port RECOMMENDS). The dedupe test is gated on this:
342
+ * an adapter without the constraint satisfies the port's normative
343
+ * requirements and must not fail the suite, but the skip stays
344
+ * visible as the unproven recommendation it is.
345
+ */
346
+ dedupesOnEventId?: boolean;
347
+ }
348
+ /**
349
+ * The outbox contract test suite: the proof that an adapter delivers
350
+ * the guarantees `withCommit` and `OutboxDispatcher` document. The kit
351
+ * is store-agnostic, so commit-order reads, qualified source-position
352
+ * identity, eventful-predecessor linkage, idempotent acks, and rollback
353
+ * purity are an **adapter contract, not a kit guarantee**; this suite is how
354
+ * an adapter demonstrates them. Its source-law tests also prove that colliding
355
+ * raw ids stay isolated by aggregate type and aggregate id.
297
356
  *
298
- * const harness: RepositoryContractHarness<Order, OrderEvent> = {
299
- * createEnvironment: async () => {
300
- * const schema = await provisionTestSchema(); // testcontainers etc.
301
- * const uowDeps = {
302
- * scope: schema.scope,
303
- * outbox: schema.outbox,
304
- * repositories: {
305
- * orders: (tx, session) => new DrizzleOrderRepository(tx, session),
306
- * },
307
- * };
308
- * return {
309
- * run: (work) =>
310
- * new UnitOfWork(uowDeps).run(({ repositories }) =>
311
- * work({ repository: repositories.orders })),
312
- * committedOutboxEvents: () => schema.readOutboxEvents(),
313
- * teardown: () => schema.drop(),
314
- * };
315
- * },
316
- * createAggregate: () => Order.draft(orderIds.next()),
317
- * mutate: (order) => order.changeNote(`note-${counter++}`), // ONE bump + event
318
- * // provide every optional capability your adapter supports:
319
- * createAggregateWithId: (id) => Order.draft(id),
320
- * snapshotState: (order) => normalizeForRoundtrip(order.state),
321
- * deletesAreVersionChecked: true,
322
- * };
357
+ * Framework-agnostic: bind with
358
+ * `(test.skipped ? it.skip : it)(test.name, test.run)`.
359
+ */
360
+ declare function createOutboxContractTests<Evt extends AnyDomainEvent>(harness: OutboxContractHarness<Evt>): OutboxContractTest[];
361
+ //#endregion
362
+ //#region src/testing/projection-checkpoint-contract.d.ts
363
+ /** One contract test; bind with `(test.skipped ? it.skip : it)(test.name, test.run)`. */
364
+ type ProjectionCheckpointStoreContractTest = ContractTest;
365
+ /**
366
+ * One isolated test environment: a fresh checkpoint store. The suite
367
+ * creates one per test and tears it down afterwards.
368
+ */
369
+ interface ProjectionCheckpointStoreContractEnvironment<TCtx> {
370
+ /** The adapter under test. */
371
+ store: ProjectionCheckpointStore<TCtx>;
372
+ /**
373
+ * Runs `work` inside a transaction that COMMITS, handing it the
374
+ * transaction context the store methods expect, the way the
375
+ * `Projector` calls them in production. For a non-transactional
376
+ * store this simply invokes `work` with a dummy context.
377
+ */
378
+ run<R>(work: (ctx: TCtx) => Promise<R>): Promise<R>;
379
+ /**
380
+ * Optional capability: starts every supplied transaction independently and
381
+ * concurrently. Each callback must receive its own transaction context for
382
+ * database adapters (normally a separate pooled connection). Enables the
383
+ * missing-key/existing-key exclusion test; implementing this as sequential
384
+ * calls would make that proof meaningless.
385
+ */
386
+ runConcurrently?<R>(works: ReadonlyArray<(ctx: TCtx) => Promise<R>>): Promise<R[]>;
387
+ /**
388
+ * Optional capability: runs `work` inside a transaction that ROLLS
389
+ * BACK. Enables the rollback test: a rolled-back save must leave no
390
+ * checkpoint behind, the half of the atomic update+checkpoint
391
+ * promise the store contributes. Transactional adapters should
392
+ * always provide this; in-memory fakes cannot.
393
+ */
394
+ runRolledBack?<R>(work: (ctx: TCtx) => Promise<R>): Promise<R>;
395
+ /** Release connections, drop schemas, etc. Called in a finally. */
396
+ teardown?(): Promise<void>;
397
+ }
398
+ /**
399
+ * What an adapter supplies to run the projection-checkpoint-store
400
+ * contract suite. For SQL adapters, run against a real database
401
+ * (testcontainers or equivalent): concurrent runs prove missing-key locking,
402
+ * the rollback test proves YOUR transaction wiring, and the checkpoint table
403
+ * must live in the same database as the read models it accounts for.
404
+ */
405
+ interface ProjectionCheckpointStoreContractHarness<TCtx> {
406
+ createEnvironment(): Promise<ProjectionCheckpointStoreContractEnvironment<TCtx>>;
407
+ /**
408
+ * Declare `true` when environments provide {@link
409
+ * ProjectionCheckpointStoreContractEnvironment.runRolledBack}.
410
+ * Without it, the rollback test is marked skipped: the honest state
411
+ * of an in-memory fake, and a loud gap for a transactional adapter.
412
+ */
413
+ providesRolledBackRuns?: boolean;
414
+ /**
415
+ * Declare `true` when environments provide {@link
416
+ * ProjectionCheckpointStoreContractEnvironment.runConcurrently}. Without
417
+ * it, missing-key lock safety is marked skipped and remains an explicitly
418
+ * unproven adapter guarantee.
419
+ */
420
+ providesConcurrentRuns?: boolean;
421
+ }
422
+ /**
423
+ * The projection-checkpoint-store contract test suite: the proof that
424
+ * an adapter delivers the watermark semantics the `Projector`
425
+ * documents. Checkpoint semantics are an **adapter contract, not a
426
+ * kit guarantee**; this suite is how an adapter demonstrates them. Enable its
427
+ * concurrent-runs capability to prove genesis-safe exclusion rather than
428
+ * leaving that guarantee visibly skipped.
323
429
  *
324
- * describe("DrizzleOrderRepository: repository contract", () => {
325
- * for (const test of createRepositoryContractTests(harness)) {
326
- * (test.skipped ? it.skip : it)(test.name, test.run);
327
- * }
328
- * });
329
- * ```
430
+ * The concurrent test exercises commit visibility, but cannot deterministically
431
+ * hold an adapter between return from `withCheckpointLocks` and its surrounding
432
+ * transaction commit. It can therefore expose an early lock release only when
433
+ * a waiter enters during that window; holding database locks through commit or
434
+ * rollback remains an explicit adapter responsibility, not a complete proof
435
+ * supplied by this suite.
330
436
  *
331
- * **`env.run` must provide unit-of-work semantics.** Three core tests
332
- * (identity-map sameness, getById-null-after-delete, deletion
333
- * finality) exercise the session machinery: `session.identityMap`,
334
- * the `isDeleted` probe, the deleted-gate. Wiring `run` through the
335
- * kit's `UnitOfWork` gives you all of it; a hand-rolled `withCommit`
336
- * wiring must provide equivalents or those tests will fail. A
337
- * `withCommit`-only setup that deliberately makes no identity-map /
338
- * deletion-finality claims is outside this suite's scope; the suite
339
- * is the compliance bar for unit-of-work repositories.
437
+ * Framework-agnostic: bind with
438
+ * `(test.skipped ? it.skip : it)(test.name, test.run)`.
439
+ */
440
+ declare function createProjectionCheckpointStoreContractTests<TCtx>(harness: ProjectionCheckpointStoreContractHarness<TCtx>): ProjectionCheckpointStoreContractTest[];
441
+ //#endregion
442
+ //#region src/testing/repository-contract.d.ts
443
+ /** Application-facing state-stored repository exercised by the suite. */
444
+ interface ContractRepository<TAggregate extends IAggregateRoot<Id<string>, AnyDomainEvent>> {
445
+ findById(id: TAggregate["id"]): Promise<TAggregate | undefined>;
446
+ add(aggregate: TAggregate): void;
447
+ update(aggregate: TAggregate): void;
448
+ /** Physical removal is an optional persistence capability. */
449
+ remove?(aggregate: TAggregate): void;
450
+ }
451
+ /** One isolated real-adapter environment. `run` must permit overlapping calls. */
452
+ interface RepositoryContractEnvironment<TAggregate extends IAggregateRoot<Id<string>, TEvent>, TEvent extends AnyDomainEvent = AnyDomainEvent> {
453
+ run<R>(work: (context: {
454
+ repository: ContractRepository<TAggregate>;
455
+ }) => Promise<R>): Promise<R>;
456
+ committedOutboxEvents(): Promise<ReadonlyArray<CommittedDomainEvent<TEvent>>>;
457
+ /** Makes the next transactional outbox write fail for atomicity proof. */
458
+ failNextOutboxWrite(error: Error): void;
459
+ teardown?(): Promise<void>;
460
+ }
461
+ /** Fixtures and observable projections supplied by an adapter package. */
462
+ interface RepositoryContractHarness<TAggregate extends IAggregateRoot<Id<string>, TEvent>, TEvent extends AnyDomainEvent = AnyDomainEvent> {
463
+ createEnvironment(): Promise<RepositoryContractEnvironment<TAggregate, TEvent>>;
464
+ /** A fresh aggregate with a unique id. */
465
+ createAggregate(): TAggregate;
466
+ /** One version-bumping decision that records at least one event. */
467
+ mutate(aggregate: TAggregate): void;
468
+ /** Required for duplicate-add and same-UoW deletion-finality proofs. */
469
+ createAggregateWithId?(id: TAggregate["id"]): TAggregate;
470
+ /**
471
+ * A version-bumping decision with no event whose resulting state is
472
+ * deep-equal to the previous state (`setState({ ...state })`). The
473
+ * deep-equal requirement is load-bearing: it forces a diff-based
474
+ * `PersistenceModel` to derive an EMPTY change set, so the suite proves
475
+ * that an adapter persists the bumped version even when
476
+ * `changes.empty` is true. Skipping that write desyncs the persisted
477
+ * version and produces false concurrency conflicts later.
478
+ */
479
+ mutateVersionOnly?(aggregate: TAggregate): void;
480
+ /** A decision that changes a nested collection. */
481
+ mutateChildCollection?(aggregate: TAggregate): void;
482
+ /** Round-trip-stable adapter persistence projection. */
483
+ snapshotState?(aggregate: TAggregate): unknown;
484
+ /** Opt out only for an intentionally upserting add implementation. */
485
+ insertsAreDuplicateChecked?: boolean;
486
+ /** Enables physical-remove behavior and stale-remove OCC tests. */
487
+ removesAreSupported?: boolean;
488
+ /** The remove flush predicates on the version captured at load. */
489
+ removesAreVersionChecked?: boolean;
490
+ }
491
+ type RepositoryContractTest = ContractTest;
492
+ /**
493
+ * Contract suite for the v3 explicit-intent, commit-time-flush protocol.
340
494
  *
341
- * **Error matching is by NAME along the `cause` chain, not by
342
- * `instanceof`.** The suite ships in its own bundle entry; comparing
343
- * class identity would spuriously fail whenever the adapter's errors
344
- * come from a different copy of the kit (the main entry's bundle, or a
345
- * second installed version). `error.name === "ConcurrencyConflictError"`
346
- * anywhere in the chain is the contract.
495
+ * The harness must use the public `UnitOfWork` with a real adapter. In
496
+ * particular, `run` must create a fresh Unit of Work and transaction for each
497
+ * call and allow two calls to overlap; the mandatory stale-writer proof keeps
498
+ * writer B open while writer A commits. SQL/ORM adapters therefore need a
499
+ * real database and connection pool. An in-memory harness proves only itself.
347
500
  *
348
- * **What each test proves.** The OCC, routing, rollback, and outbox
349
- * tests prove YOUR adapter's SQL and transaction wiring. The
350
- * identity-map, deletion-finality, and event-lifecycle tests prove
351
- * your READ-PATH and unit-of-work WIRING (they exercise kit-provided
352
- * machinery, namely `session.identityMap`, the deleted-gate, and
353
- * `withCommit`'s harvest, and fail when your repository bypasses or
354
- * mis-wires it).
355
- * A deletion-finality failure usually means a missing
356
- * `identityMap.isDeleted` check or an `enrollSaved` placed after the
357
- * row write, not a broken DELETE statement.
501
+ * Writes are synchronous registrations. Durable adapter I/O happens after the
502
+ * callback returns, while the transaction is still open. A test that passes
503
+ * because `add` or `update` writes early is not a conforming implementation.
504
+ */
505
+ declare function createRepositoryContractTests<TAggregate extends IAggregateRoot<Id<string>, TEvent>, TEvent extends AnyDomainEvent = AnyDomainEvent>(harness: RepositoryContractHarness<TAggregate, TEvent>): RepositoryContractTest[];
506
+ //#endregion
507
+ //#region src/testing/snapshot-store-contract.d.ts
508
+ /** One contract test; bind with `(test.skipped ? it.skip : it)(test.name, test.run)`. */
509
+ type SnapshotStoreContractTest = ContractTest;
510
+ /** The plain-data state shape the suite round-trips. */
511
+ interface SuiteState {
512
+ total: number;
513
+ items: Array<{
514
+ sku: string;
515
+ qty: number;
516
+ }>;
517
+ note?: string;
518
+ }
519
+ /**
520
+ * One isolated test environment: a fresh snapshot store. The suite
521
+ * creates one per test and tears it down afterwards. No transaction
522
+ * wrapper: the port is transaction-free by design (snapshots are
523
+ * derived data written after the commit; see `SnapshotStore`).
524
+ */
525
+ interface SnapshotStoreContractEnvironment {
526
+ /** The adapter under test. */
527
+ store: SnapshotStore<SuiteState>;
528
+ /** Release connections, drop schemas, etc. Called in a finally. */
529
+ teardown?(): Promise<void>;
530
+ }
531
+ /**
532
+ * What an adapter supplies to run the snapshot-store contract suite.
533
+ * For SQL adapters, run against a real database (testcontainers or
534
+ * equivalent). Note the fidelity demands the suite enforces:
535
+ * `snapshotAt` must survive with millisecond precision (store it as
536
+ * ISO-8601 text or epoch milliseconds; MySQL `DATETIME` without
537
+ * fractional seconds truncates), and an ABSENT `schemaVersion` must
538
+ * come back absent, not as `0` or `null`-coerced.
539
+ */
540
+ interface SnapshotStoreContractHarness {
541
+ createEnvironment(): Promise<SnapshotStoreContractEnvironment>;
542
+ }
543
+ /**
544
+ * The snapshot-store contract test suite: the proof that an adapter
545
+ * delivers the round-trip and isolation semantics the
546
+ * snapshot-plus-recent-events load path relies on. Store semantics are
547
+ * an **adapter contract, not a kit guarantee**; this suite is how an
548
+ * adapter demonstrates them.
358
549
  *
359
- * **Known limitation: no truly concurrent runs.** The mandatory
360
- * two-writer test is deliberately sequential-deterministic: writer B
361
- * loads, writer A loads/mutates/commits, then B commits its stale
362
- * instance. The stale `persistedVersion` baseline travels with B's
363
- * instance, so the version predicate is exercised exactly as in a true
364
- * race, without depending on lock timing, pool sizes, or
365
- * engine-specific blocking. The flip side: lock interaction is NOT
366
- * covered. A `SELECT … FOR UPDATE`-style repository that blocks
367
- * instead of conflicting, or a SERIALIZABLE engine surfacing raw
368
- * serialization failures (Postgres 40001) your adapter must map to
369
- * `ConcurrencyConflictError`, needs adapter-specific tests on top of
370
- * this suite.
550
+ * Framework-agnostic: bind with
551
+ * `(test.skipped ? it.skip : it)(test.name, test.run)`.
371
552
  */
372
- declare function createRepositoryContractTests<TAgg extends IAggregateRoot<Id<string>, Evt>, Evt extends AnyDomainEvent = AnyDomainEvent>(harness: RepositoryContractHarness<TAgg, Evt>): RepositoryContractTest[];
373
-
374
- export { type ContractRepository, type EsContractRepository, type EsRepositoryContractEnvironment, type EsRepositoryContractHarness, type EsRepositoryContractTest, type RepositoryContractEnvironment, type RepositoryContractHarness, type RepositoryContractTest, createEsRepositoryContractTests, createRepositoryContractTests };
553
+ declare function createSnapshotStoreContractTests(harness: SnapshotStoreContractHarness): SnapshotStoreContractTest[];
554
+ //#endregion
555
+ export { type CommandOutboxContractEnvironment, type CommandOutboxContractHarness, type CommandOutboxContractTest, type ContractRepository, type DeadlineStoreContractEnvironment, type DeadlineStoreContractHarness, type DeadlineStoreContractTest, type EsContractRepository, type EsRepositoryContractEnvironment, type EsRepositoryContractHarness, type EsRepositoryContractTest, type EventStoreContractEnvironment, type EventStoreContractHarness, type EventStoreContractTest, type IdempotencyStoreContractEnvironment, type IdempotencyStoreContractHarness, type IdempotencyStoreContractTest, type OutboxContractEnvironment, type OutboxContractHarness, type OutboxContractTest, type ProjectionCheckpointStoreContractEnvironment, type ProjectionCheckpointStoreContractHarness, type ProjectionCheckpointStoreContractTest, type RepositoryContractEnvironment, type RepositoryContractHarness, type RepositoryContractTest, type SnapshotStoreContractEnvironment, type SnapshotStoreContractHarness, type SnapshotStoreContractTest, createCommandOutboxContractTests, createDeadlineStoreContractTests, createEsRepositoryContractTests, createEventStoreContractTests, createIdempotencyStoreContractTests, createOutboxContractTests, createProjectionCheckpointStoreContractTests, createRepositoryContractTests, createSnapshotStoreContractTests };
556
+ //# sourceMappingURL=testing.d.ts.map