@shirudo/ddd-kit 2.0.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/README.md +203 -51
- package/dist/chunks/deep-equal-except.js +639 -0
- package/dist/chunks/deep-equal-except.js.map +1 -0
- package/dist/chunks/errors.d.ts +785 -0
- package/dist/chunks/errors.js +822 -0
- package/dist/chunks/errors.js.map +1 -0
- package/dist/chunks/ports.js +891 -0
- package/dist/chunks/ports.js.map +1 -0
- package/dist/chunks/snapshot-store.d.ts +2808 -0
- package/dist/chunks/utils.d.ts +110 -0
- package/dist/http.d.ts +64 -51
- package/dist/http.js +54 -20
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2555 -2174
- package/dist/index.js +6159 -2370
- package/dist/index.js.map +1 -1
- package/dist/money.d.ts +376 -0
- package/dist/money.js +578 -0
- package/dist/money.js.map +1 -0
- package/dist/presentation.d.ts +86 -37
- package/dist/presentation.js +208 -39
- package/dist/presentation.js.map +1 -1
- package/dist/testing.d.ts +543 -239
- package/dist/testing.js +2403 -772
- package/dist/testing.js.map +1 -1
- package/dist/utils.d.ts +2 -105
- package/dist/utils.js +2 -388
- package/package.json +37 -20
- package/dist/aggregate-CePTINEt.d.ts +0 -722
- package/dist/utils.js.map +0 -1
package/dist/testing.d.ts
CHANGED
|
@@ -1,252 +1,556 @@
|
|
|
1
|
-
import {
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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)`.
|
|
9
|
+
*/
|
|
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;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One isolated test environment: a fresh deadline store. The suite
|
|
53
|
+
* creates one per test and tears it down afterwards.
|
|
54
|
+
*/
|
|
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>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
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.
|
|
46
114
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
115
|
+
* Framework-agnostic: bind with
|
|
116
|
+
* `(test.skipped ? it.skip : it)(test.name, test.run)`.
|
|
117
|
+
*/
|
|
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>;
|
|
135
|
+
}
|
|
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;
|
|
145
|
+
}
|
|
146
|
+
type EsRepositoryContractTest = ContractTest;
|
|
147
|
+
/**
|
|
148
|
+
* Contract suite for event-stream adapters using v3 Unit-of-Work receipts.
|
|
53
149
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*/
|
|
149
|
-
interface
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
*
|
|
159
|
-
*
|
|
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.
|
|
154
|
+
*/
|
|
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
|
+
}
|
|
165
|
+
/**
|
|
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.
|
|
172
|
+
*/
|
|
173
|
+
interface EventStoreContractHarness<Evt extends AnyDomainEvent> {
|
|
174
|
+
createEnvironment(): Promise<EventStoreContractEnvironment<Evt>>;
|
|
175
|
+
createCollidingStreamKeys(): readonly [AggregateAddress, AggregateAddress];
|
|
176
|
+
createEvent(stream: AggregateAddress, sequence: number): Evt;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
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.
|
|
192
|
+
*/
|
|
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>;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* What an adapter supplies to run the idempotency-store contract suite.
|
|
227
|
+
*
|
|
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:
|
|
231
|
+
*
|
|
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.
|
|
244
|
+
*/
|
|
245
|
+
interface IdempotencyStoreContractHarness<TCtx> {
|
|
246
|
+
createEnvironment(): Promise<IdempotencyStoreContractEnvironment<TCtx>>;
|
|
247
|
+
/** Which lifecycle family the adapter implements; see above. */
|
|
248
|
+
family: "transactional" | "non-transactional";
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
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.
|
|
160
256
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
257
|
+
* Framework-agnostic: bind with
|
|
258
|
+
* `(test.skipped ? it.skip : it)(test.name, test.run)`.
|
|
259
|
+
*/
|
|
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>;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* What an adapter supplies to run the outbox contract suite.
|
|
168
293
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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.
|
|
171
297
|
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
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.
|
|
175
356
|
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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.
|
|
201
429
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
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.
|
|
208
436
|
*
|
|
209
|
-
*
|
|
210
|
-
* (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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.
|
|
218
494
|
*
|
|
219
|
-
*
|
|
220
|
-
* `
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* 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.
|
|
225
500
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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.
|
|
236
549
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
* covered. A `SELECT … FOR UPDATE`-style repository that blocks
|
|
245
|
-
* instead of conflicting, or a SERIALIZABLE engine surfacing raw
|
|
246
|
-
* serialization failures (Postgres 40001) your adapter must map to
|
|
247
|
-
* `ConcurrencyConflictError`, needs adapter-specific tests on top of
|
|
248
|
-
* this suite.
|
|
249
|
-
*/
|
|
250
|
-
declare function createRepositoryContractTests<TAgg extends IAggregateRoot<Id<string>, Evt>, Evt extends AnyDomainEvent = AnyDomainEvent>(harness: RepositoryContractHarness<TAgg, Evt>): RepositoryContractTest[];
|
|
251
|
-
|
|
252
|
-
export { type ContractRepository, type RepositoryContractEnvironment, type RepositoryContractHarness, type RepositoryContractTest, createRepositoryContractTests };
|
|
550
|
+
* Framework-agnostic: bind with
|
|
551
|
+
* `(test.skipped ? it.skip : it)(test.name, test.run)`.
|
|
552
|
+
*/
|
|
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
|