@shirudo/ddd-kit 3.0.0-rc.10 → 3.0.0-rc.11

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.js CHANGED
@@ -1,12 +1,7 @@
1
- import { N as deepEqual, _ as isRecordedDomainEvent, i as runBoundedExecution, t as isDispatchTrackingOutbox } from "./chunks/ports.js";
1
+ import { I as deepFreeze, U as deepEqual, a as isDispatchTrackingOutbox, c as runBoundedExecution, f as assertPositiveSafeInteger, n as readStreamPages, w as isRecordedDomainEvent } from "./chunks/read-stream-pages.js";
2
2
 
3
3
  //#region src/testing/contract-assertions.ts
4
4
  /**
5
- * Assertion, error-matching, and suite-runner helpers shared by the
6
- * repository contract suites (state-stored and event-sourced). Internal
7
- * to the testing entry: not re-exported from `@shirudo/ddd-kit/testing`.
8
- */
9
- /**
10
5
  * Runs one contract-test body against a fresh environment and tears it
11
6
  * down in a finally-like discipline with one subtle, load-bearing rule:
12
7
  * a teardown failure (dropping a schema on an aborted pool) must never
@@ -281,6 +276,25 @@ function assertChainContainsKitError(rejection, codes, message) {
281
276
  throw new Error(`Contract violated: ${message}`);
282
277
  }
283
278
  /**
279
+ * Asserts that the first `CONCURRENCY_CONFLICT` in the cause chain names the
280
+ * write that failed. The unit of work sets the intent on every conflict that
281
+ * leaves a flush, so an adapter that wraps the conflict keeps it.
282
+ */
283
+ function assertChainConflictNamesIntent(rejection, intent, message) {
284
+ let observed = "no CONCURRENCY_CONFLICT";
285
+ walkCauseChain(rejection, (node) => {
286
+ if (!errorMatchesName(node, "CONCURRENCY_CONFLICT")) return false;
287
+ try {
288
+ observed = node.intent;
289
+ } catch {
290
+ observed = "an intent that cannot be read";
291
+ }
292
+ return true;
293
+ });
294
+ if (observed === intent) return;
295
+ throw new Error(`Contract violated: ${message}; got ${typeof observed === "string" ? observed : String(observed)}`);
296
+ }
297
+ /**
284
298
  * Walks the `cause` chain (cycle-safe, hostile-getter-safe) looking for
285
299
  * `retryable === true`: the same loose, property-based contract the
286
300
  * kit's retry classifier (`someChainRetryable`) applies. Suites assert
@@ -353,13 +367,14 @@ function causeChainNames(error) {
353
367
  //#region src/testing/command-outbox-contract.ts
354
368
  function createCommandOutboxContractTests(harness) {
355
369
  const inEnv = bindContractEnvironment(harness.createEnvironment);
356
- const commit = (seed, commandSeeds = [seed]) => ({
370
+ const processSource = {
371
+ aggregateType: "CheckoutProcess",
372
+ aggregateId: "order-1"
373
+ };
374
+ const commit = (seed, commandSeeds = [seed], source = processSource) => ({
357
375
  origin: {
358
376
  eventId: `process-event-${seed}`,
359
- source: {
360
- aggregateType: "CheckoutProcess",
361
- aggregateId: "order-1"
362
- },
377
+ source,
363
378
  position: {
364
379
  aggregateVersion: seed,
365
380
  commitSequence: 0,
@@ -551,6 +566,55 @@ function createCommandOutboxContractTests(harness) {
551
566
  assertEqual(stored[0]?.messages.length, 0, "the retained empty receipt must contain no invented command");
552
567
  assert(deepEqual(stored.map(({ origin }) => origin.position.aggregateVersion), [1, 2]), "the source cursor must advance through the empty receipt");
553
568
  })
569
+ },
570
+ {
571
+ name: "rejects a new commit of an ended source",
572
+ run: inEnv(async (env) => {
573
+ const otherProcess = {
574
+ aggregateType: "CheckoutProcess",
575
+ aggregateId: "order-2"
576
+ };
577
+ const otherType = {
578
+ aggregateType: "ShippingProcess",
579
+ aggregateId: "order-1"
580
+ };
581
+ await env.addCommitted([commit(1)]);
582
+ await env.addCommitted([commit(4, [4], otherType)]);
583
+ await env.endEventSourcesCommitted([processSource]);
584
+ const secondEnd = await captureRejection(env.endEventSourcesCommitted([processSource]));
585
+ assert(secondEnd === void 0, `ending a source twice must not be an error. Got: ${describeError(secondEnd)}`);
586
+ const rejection = await captureRejection(env.addCommitted([commit(2)]));
587
+ assert(rejection !== void 0, "a new commit of an ended source must reject");
588
+ for (const [seed, source] of [[3, otherProcess], [5, otherType]]) {
589
+ const siblingRejection = await captureRejection(env.addCommitted([commit(seed, [seed], source)]));
590
+ assert(siblingRejection === void 0, `ending one source must not end another with the same id or the same type. Got: ${describeError(siblingRejection)}`);
591
+ }
592
+ assert(deepEqual((await env.readAll()).map(({ origin }) => origin.eventId).sort(), [
593
+ "process-event-1",
594
+ "process-event-3",
595
+ "process-event-4",
596
+ "process-event-5"
597
+ ]), "the rejected commit must leave no receipt");
598
+ })
599
+ },
600
+ {
601
+ name: "deduplicates an exact retry of a commit of an ended source",
602
+ run: inEnv(async (env) => {
603
+ const original = commit(1);
604
+ await env.addCommitted([original]);
605
+ await env.endEventSourcesCommitted([processSource]);
606
+ const rejection = await captureRejection(env.addCommitted([original]));
607
+ assert(rejection === void 0, `an exact retry must stay idempotent after its source ended. Got: ${describeError(rejection)}`);
608
+ assertEqual((await env.readAll()).length, 1, "the retry must not append another receipt");
609
+ })
610
+ },
611
+ {
612
+ name: "ending a source without a cursor leaves it open for its first commit",
613
+ run: inEnv(async (env) => {
614
+ await env.endEventSourcesCommitted([processSource]);
615
+ await env.addCommitted([commit(1)]);
616
+ assertEqual((await env.readAll()).length, 1, "a source without a cursor has nothing to protect, so its first commit is accepted");
617
+ })
554
618
  }
555
619
  ];
556
620
  tests.push(gatedContractTest({
@@ -563,6 +627,18 @@ function createCommandOutboxContractTests(harness) {
563
627
  await env.addRolledBack([commit(1)]);
564
628
  assertEqual((await env.readAll()).length, 0, "a rolled-back transaction must persist no command receipt");
565
629
  })
630
+ }), gatedContractTest({
631
+ capability: "providesRolledBackEnds",
632
+ satisfiedBy: harness.providesRolledBackEnds === true
633
+ }, {
634
+ name: "a rolled-back end leaves the source open",
635
+ run: inEnv(async (env) => {
636
+ if (!env.endEventSourcesRolledBack) throw new Error("Contract violated: harness declared providesRolledBackEnds but the environment lacks endEventSourcesRolledBack");
637
+ await env.addCommitted([commit(1)]);
638
+ await env.endEventSourcesRolledBack([processSource]);
639
+ await env.addCommitted([commit(2)]);
640
+ assertEqual((await env.readAll()).length, 2, "an end in a rolled-back transaction must not end the source");
641
+ })
566
642
  }));
567
643
  return tests;
568
644
  }
@@ -882,6 +958,7 @@ function createDeadlineStoreContractTests(harness) {
882
958
  dueAt: at(T0),
883
959
  payload: { kind: "ghost" }
884
960
  })).catch(() => {});
961
+ await env.run(async () => {});
885
962
  assertEqual((await env.store.due(at(T2), 10)).length, 0, "a deadline from a rolled-back transaction is a ghost input and must not exist");
886
963
  })
887
964
  }),
@@ -899,6 +976,7 @@ function createDeadlineStoreContractTests(harness) {
899
976
  payload: { kind: "x" }
900
977
  }));
901
978
  await env.runRolledBack(() => env.store.cancel("s", "k")).catch(() => {});
979
+ await env.run(async () => {});
902
980
  assertEqual((await env.store.due(at(T1), 10)).length, 1, "a cancel from a rolled-back transaction must not have removed the deadline");
903
981
  })
904
982
  })
@@ -975,6 +1053,7 @@ function createEsRepositoryContractTests(harness) {
975
1053
  writerB.release();
976
1054
  const rejection = await captureRejection(writerB.call);
977
1055
  assertChainContainsKitError(rejection, ["CONCURRENCY_CONFLICT"], `stale append must reject with ConcurrencyConflictError; got ${describeError(rejection)}`);
1056
+ assertChainConflictNamesIntent(rejection, "update", "the stale append conflict must name the update in its intent");
978
1057
  const finalStream = await environment.committedStreamEvents(streamFor(seeded.id), readAll);
979
1058
  assert(finalStream.exists && deepEqual(ids(finalStream.events), ids(streamAfterWinner.events)), "a rejected multi-event append must leave no prefix in the stream");
980
1059
  const reloaded = await environment.run(({ repository }) => load(repository, seeded.id));
@@ -1545,11 +1624,11 @@ function createEventStoreContractTests(harness) {
1545
1624
  const hasSameEventIds = (actual, expected) => actual.length === expected.length && actual.every((event, index) => event.eventId === expected[index]?.eventId);
1546
1625
  return [
1547
1626
  {
1548
- name: "unknown stream: read reports explicit absence at version zero",
1627
+ name: "absent stream: read reports explicit absence at version zero",
1549
1628
  run: inEnv(async ({ store }) => {
1550
1629
  const [firstKey] = harness.createCollidingStreamKeys();
1551
- const missing = await store.readStream({ ...firstKey }, fixtureRead);
1552
- assert(!missing.exists && missing.lastVersion === 0 && missing.events.length === 0, "an unknown qualified stream must return the explicit missing state");
1630
+ const absent = await store.readStream({ ...firstKey }, fixtureRead);
1631
+ assert(!absent.exists && absent.lastVersion === 0 && absent.events.length === 0, "an absent qualified stream must return the explicit absent state");
1553
1632
  })
1554
1633
  },
1555
1634
  {
@@ -1576,7 +1655,7 @@ function createEventStoreContractTests(harness) {
1576
1655
  await store.append(secondKey, [secondEvent], { expectedVersion: 0 });
1577
1656
  const firstStream = await store.readStream({ ...firstKey }, fixtureRead);
1578
1657
  const secondStream = await store.readStream({ ...secondKey }, fixtureRead);
1579
- assert(firstStream.exists && firstStream.events.length === 1 && firstStream.events[0]?.eventId === firstEvent.eventId, "the first aggregate type must retain only its own event; key objects are value addresses, not identity tokens");
1658
+ assert(firstStream.exists && firstStream.events.length === 1 && firstStream.events[0]?.eventId === firstEvent.eventId, "the first aggregate type must retain only its own event; key objects compare by value, not by object reference");
1580
1659
  assert(secondStream.exists && secondStream.events.length === 1 && secondStream.events[0]?.eventId === secondEvent.eventId, "the second aggregate type must retain only its own event when the raw id collides");
1581
1660
  })
1582
1661
  },
@@ -1638,6 +1717,111 @@ function createEventStoreContractTests(harness) {
1638
1717
  assert(atEnd.exists && atEnd.events.length === 0, "continuing at the pinned head must return an existing empty page");
1639
1718
  })
1640
1719
  },
1720
+ {
1721
+ name: "readStreamPages: the pages walk the pinned prefix and stop before a later append",
1722
+ run: inEnv(async ({ store }) => {
1723
+ const [firstKey, secondKey] = harness.createCollidingStreamKeys();
1724
+ const events = [
1725
+ 1,
1726
+ 2,
1727
+ 3,
1728
+ 4,
1729
+ 5
1730
+ ].map((sequence) => harness.createEvent(firstKey, sequence));
1731
+ await store.append(firstKey, events, { expectedVersion: 0 });
1732
+ const absent = await readStreamPages(store, { ...secondKey }, { limit: 2 });
1733
+ assert(!absent.exists, "readStreamPages must report an absent stream as absent");
1734
+ const read = await readStreamPages(store, { ...firstKey }, { limit: 2 });
1735
+ assert(read.reachable, "a full read of an existing stream must be reachable");
1736
+ assert(read.targetVersion === events.length, "the first page must pin the actual stream head");
1737
+ const collected = [];
1738
+ for await (const page of read.pages) {
1739
+ assert(page.length > 0 && page.length <= 2, "every page must hold at least one event and at most the limit");
1740
+ collected.push(...page);
1741
+ if (collected.length === page.length) await store.append(firstKey, [harness.createEvent(firstKey, 6)], { expectedVersion: events.length });
1742
+ }
1743
+ assert(hasSameEventIds(collected, events), "the pages must reproduce the pinned prefix in append order and leave the later append out");
1744
+ })
1745
+ },
1746
+ {
1747
+ name: "readStreamPages: toVersion below the head is the target version and the pages end there",
1748
+ run: inEnv(async ({ store }) => {
1749
+ const [firstKey] = harness.createCollidingStreamKeys();
1750
+ const events = [
1751
+ 1,
1752
+ 2,
1753
+ 3,
1754
+ 4,
1755
+ 5
1756
+ ].map((sequence) => harness.createEvent(firstKey, sequence));
1757
+ await store.append(firstKey, events, { expectedVersion: 0 });
1758
+ const pointInTime = await readStreamPages(store, { ...firstKey }, {
1759
+ toVersion: 3,
1760
+ limit: 2
1761
+ });
1762
+ assert(pointInTime.reachable, "a target below the head must be reachable");
1763
+ assert(pointInTime.targetVersion === 3, "the read must pin toVersion as the target version");
1764
+ const collected = [];
1765
+ for await (const page of pointInTime.pages) collected.push(...page);
1766
+ assert(hasSameEventIds(collected, events.slice(0, 3)), "the pages must stop at toVersion");
1767
+ })
1768
+ },
1769
+ {
1770
+ name: "readStreamPages: toVersion beyond the head is unreachable and names the head",
1771
+ run: inEnv(async ({ store }) => {
1772
+ const [firstKey] = harness.createCollidingStreamKeys();
1773
+ const events = [
1774
+ 1,
1775
+ 2,
1776
+ 3
1777
+ ].map((sequence) => harness.createEvent(firstKey, sequence));
1778
+ await store.append(firstKey, events, { expectedVersion: 0 });
1779
+ const beyondHead = await readStreamPages(store, { ...firstKey }, {
1780
+ toVersion: 9,
1781
+ limit: 2
1782
+ });
1783
+ assert(beyondHead.exists && !beyondHead.reachable && beyondHead.lastVersion === events.length, "a target beyond the head must be unreachable and name the actual head");
1784
+ })
1785
+ },
1786
+ {
1787
+ name: "readStreamPages: a cursor inside the stream reads only the events after it",
1788
+ run: inEnv(async ({ store }) => {
1789
+ const [firstKey] = harness.createCollidingStreamKeys();
1790
+ const events = [
1791
+ 1,
1792
+ 2,
1793
+ 3,
1794
+ 4,
1795
+ 5
1796
+ ].map((sequence) => harness.createEvent(firstKey, sequence));
1797
+ await store.append(firstKey, events, { expectedVersion: 0 });
1798
+ const afterTwo = await readStreamPages(store, { ...firstKey }, {
1799
+ fromVersion: 2,
1800
+ limit: 2
1801
+ });
1802
+ assert(afterTwo.reachable && afterTwo.fromVersion === 2 && afterTwo.targetVersion === events.length, "a cursor inside the stream must be reachable and pin the head as the target version");
1803
+ const collected = [];
1804
+ for await (const page of afterTwo.pages) collected.push(...page);
1805
+ assert(hasSameEventIds(collected, events.slice(2)), "the pages must hold only the events after the cursor");
1806
+ })
1807
+ },
1808
+ {
1809
+ name: "readStreamPages: a cursor beyond the head is unreachable and names the head",
1810
+ run: inEnv(async ({ store }) => {
1811
+ const [firstKey] = harness.createCollidingStreamKeys();
1812
+ const events = [
1813
+ 1,
1814
+ 2,
1815
+ 3
1816
+ ].map((sequence) => harness.createEvent(firstKey, sequence));
1817
+ await store.append(firstKey, events, { expectedVersion: 0 });
1818
+ const beyondHead = await readStreamPages(store, { ...firstKey }, {
1819
+ fromVersion: 9,
1820
+ limit: 2
1821
+ });
1822
+ assert(beyondHead.exists && !beyondHead.reachable && beyondHead.fromVersion === 9 && beyondHead.lastVersion === events.length, "a cursor beyond the head must be unreachable and name the actual head");
1823
+ })
1824
+ },
1641
1825
  {
1642
1826
  name: "read options: invalid limits and stream positions fail loudly",
1643
1827
  run: inEnv(async ({ store }) => {
@@ -1746,6 +1930,53 @@ function createEventStoreContractTests(harness) {
1746
1930
  for (const result of [atHead, beyondHead]) assert(result.exists && result.lastVersion === 2 && result.events.length === 0, "an empty read window must retain stream existence and report the actual head, even when fromVersion is beyond it");
1747
1931
  })
1748
1932
  },
1933
+ {
1934
+ name: "page bounds: an existing stream reports a head of at least 1 and no page holds more events than its window",
1935
+ run: inEnv(async ({ store }) => {
1936
+ const [firstKey] = harness.createCollidingStreamKeys();
1937
+ const events = [
1938
+ 1,
1939
+ 2,
1940
+ 3,
1941
+ 4,
1942
+ 5
1943
+ ].map((sequence) => harness.createEvent(firstKey, sequence));
1944
+ await store.append(firstKey, events, { expectedVersion: 0 });
1945
+ for (const window of [
1946
+ { limit: 100 },
1947
+ { limit: 2 },
1948
+ {
1949
+ fromVersion: 2,
1950
+ limit: 100
1951
+ },
1952
+ {
1953
+ fromVersion: 3,
1954
+ toVersion: 4,
1955
+ limit: 100
1956
+ },
1957
+ {
1958
+ fromVersion: 4,
1959
+ toVersion: 99,
1960
+ limit: 100
1961
+ },
1962
+ {
1963
+ fromVersion: 99,
1964
+ limit: 100
1965
+ },
1966
+ {
1967
+ fromVersion: 3,
1968
+ toVersion: 1,
1969
+ limit: 100
1970
+ }
1971
+ ]) {
1972
+ const page = await store.readStream({ ...firstKey }, window);
1973
+ assert(page.exists && page.lastVersion >= 1, "an existing stream must report a head of at least 1; report a stream without events as absent");
1974
+ const windowEnd = Math.min(window.toVersion ?? page.lastVersion, page.lastVersion);
1975
+ const room = Math.max(0, windowEnd - (window.fromVersion ?? 0));
1976
+ assert(page.events.length <= room && page.events.length <= window.limit, `a page must hold at most the ${room} events of its window (${window.fromVersion ?? 0}, ${windowEnd}] and at most the limit ${window.limit}; got ${page.events.length}`);
1977
+ }
1978
+ })
1979
+ },
1749
1980
  {
1750
1981
  name: "qualified fromVersion: slicing one type cannot observe a colliding raw id",
1751
1982
  run: inEnv(async ({ store }) => {
@@ -1795,7 +2026,7 @@ function createEventStoreContractTests(harness) {
1795
2026
  })
1796
2027
  },
1797
2028
  {
1798
- name: "OCC: an expectedVersion ahead of an unknown stream conflicts without creating it",
2029
+ name: "OCC: an expectedVersion ahead of an absent stream conflicts without creating it",
1799
2030
  run: inEnv(async ({ store }) => {
1800
2031
  const [firstKey] = harness.createCollidingStreamKeys();
1801
2032
  const rejection = await captureRejection(store.append(firstKey, [harness.createEvent(firstKey, 60)], { expectedVersion: 3 }));
@@ -2177,6 +2408,57 @@ function createOutboxContractTests(harness) {
2177
2408
  assert(records.every((record, index) => record.source.aggregateType === sources[index]?.aggregateType && record.source.aggregateId === sources[index]?.aggregateId && record.position.previousEventfulAggregateVersion === null), "colliding raw ids or aggregate types must each retain an independent genesis head");
2178
2409
  })
2179
2410
  },
2411
+ {
2412
+ name: "rejects a new event of an ended event source",
2413
+ run: inEnv(async (env) => {
2414
+ const removed = {
2415
+ aggregateType: "Order",
2416
+ aggregateId: "1"
2417
+ };
2418
+ const sibling = {
2419
+ aggregateType: "Payment",
2420
+ aggregateId: "1"
2421
+ };
2422
+ const sameType = {
2423
+ aggregateType: "Order",
2424
+ aggregateId: "2"
2425
+ };
2426
+ const first = harness.createEvent(1);
2427
+ const siblingFirst = harness.createEvent(2);
2428
+ const siblingNext = harness.createEvent(4);
2429
+ const sameTypeFirst = harness.createEvent(5);
2430
+ const sameTypeNext = harness.createEvent(6);
2431
+ await env.addCommitted(commit([first], 1, removed));
2432
+ await env.addCommitted(commit([siblingFirst], 1, sibling));
2433
+ await env.addCommitted(commit([sameTypeFirst], 1, sameType));
2434
+ await env.endEventSourcesCommitted([removed]);
2435
+ const secondEnd = await captureRejection(env.endEventSourcesCommitted([removed]));
2436
+ assert(secondEnd === void 0, `ending an event source twice must not be an error. Got: ${describeError(secondEnd)}`);
2437
+ const rejection = await captureRejection(env.addCommitted(commit([harness.createEvent(3)], 5, removed)));
2438
+ assert(rejection !== void 0, "a new event of an ended event source must reject, also above its head");
2439
+ for (const [event, source] of [[siblingNext, sibling], [sameTypeNext, sameType]]) {
2440
+ const siblingRejection = await captureRejection(env.addCommitted(commit([event], 2, source)));
2441
+ assert(siblingRejection === void 0, `ending one source must not end another with the same id or the same type. Got: ${describeError(siblingRejection)}`);
2442
+ }
2443
+ const records = await takeAndAck(env, 5);
2444
+ assert(deepEqual(records.map(({ event }) => event.eventId), [
2445
+ first.eventId,
2446
+ siblingFirst.eventId,
2447
+ sameTypeFirst.eventId,
2448
+ siblingNext.eventId,
2449
+ sameTypeNext.eventId
2450
+ ]), "the rejected event must leave no record, and ending one source must not end another with the same id or the same type");
2451
+ })
2452
+ },
2453
+ {
2454
+ name: "ending an event source without events leaves it open for its first event",
2455
+ run: inEnv(async (env) => {
2456
+ await env.endEventSourcesCommitted([defaultSource]);
2457
+ await env.addCommitted(commit([harness.createEvent(1)], 1));
2458
+ const [record] = await takeAndAck(env, 1);
2459
+ assertEqual(record?.position.previousEventfulAggregateVersion, null, "a source without a head has no events to protect, so its first event starts a genesis head");
2460
+ })
2461
+ },
2180
2462
  {
2181
2463
  name: "getPending returns records in commit order, across separate committed adds",
2182
2464
  run: inEnv(async (env) => {
@@ -2268,6 +2550,20 @@ function createOutboxContractTests(harness) {
2268
2550
  const pending = await env.outbox.getPending(10);
2269
2551
  assertEqual(pending.length, 1, "the same eventId must yield one record (unique-key dedupe)");
2270
2552
  })
2553
+ }),
2554
+ gatedContractTest({
2555
+ capability: "dedupesOnEventId",
2556
+ satisfiedBy: harness.dedupesOnEventId === true
2557
+ }, {
2558
+ name: "an exact retry of a stored event of an ended source is deduped, not rejected",
2559
+ run: inEnv(async (env) => {
2560
+ const original = commit([harness.createEvent(1)]);
2561
+ await env.addCommitted(original);
2562
+ await env.endEventSourcesCommitted([defaultSource]);
2563
+ const rejection = await captureRejection(env.addCommitted(original));
2564
+ assert(rejection === void 0, `an exact retry of a stored event must stay idempotent after its source ended. Got: ${describeError(rejection)}`);
2565
+ assertEqual((await env.outbox.getPending(10)).length, 1, "the retry must not add a second record");
2566
+ })
2271
2567
  })
2272
2568
  ];
2273
2569
  tests.push(gatedContractTest({
@@ -2281,7 +2577,24 @@ function createOutboxContractTests(harness) {
2281
2577
  assertEqual((await env.outbox.getPending(10)).length, 0, "events added in a rolled-back transaction must not appear");
2282
2578
  await env.addCommitted(commit([harness.createEvent(2)], 2));
2283
2579
  const [afterRollback] = await takeAndAck(env, 1);
2580
+ assertEqual(afterRollback?.event.eventId, harness.createEvent(2).eventId, "the only pending record after a rolled-back add must be the committed event");
2284
2581
  assertEqual(afterRollback?.position.previousEventfulAggregateVersion, null, "a rolled-back add must not advance the event-source head");
2582
+ assertEqual((await env.outbox.getPending(10)).length, 0, "a rolled-back add must leave no record behind");
2583
+ })
2584
+ }));
2585
+ tests.push(gatedContractTest({
2586
+ capability: "providesRolledBackEnds",
2587
+ satisfiedBy: harness.providesRolledBackEnds === true
2588
+ }, {
2589
+ name: "a rolled-back end leaves the event source open (transactional participation)",
2590
+ run: inEnv(async (env) => {
2591
+ if (!env.endEventSourcesRolledBack) throw new Error("Contract violated: harness declared providesRolledBackEnds but the environment lacks endEventSourcesRolledBack");
2592
+ await env.addCommitted(commit([harness.createEvent(1)], 1));
2593
+ await env.endEventSourcesRolledBack([defaultSource]);
2594
+ const rejection = await captureRejection(env.addCommitted(commit([harness.createEvent(2)], 2)));
2595
+ assert(rejection === void 0, `an end in a rolled-back transaction must not end the event source, but the next event of the source was rejected: ${describeError(rejection)}`);
2596
+ const [, next] = await takeAndAck(env, 2);
2597
+ assertEqual(next?.position.previousEventfulAggregateVersion, 1, "an end in a rolled-back transaction must not end the event source");
2285
2598
  })
2286
2599
  }));
2287
2600
  const trackingEnabled = harness.failuresToDeadLetter !== void 0;
@@ -2415,32 +2728,32 @@ function createProjectionCheckpointStoreContractTests(harness) {
2415
2728
  const runConcurrently = env.runConcurrently;
2416
2729
  if (!runConcurrently) throw new Error("Contract violated: harness declared providesConcurrentRuns but the environment lacks runConcurrently");
2417
2730
  const contenders = 8;
2418
- const address = order("o-locked");
2731
+ const identity = order("o-locked");
2419
2732
  const advanceOnce = async (expectedVersion, nextVersion) => {
2420
- return (await runConcurrently(Array.from({ length: contenders }, () => (ctx) => env.store.withCheckpointLocks(ctx, "order-list", [address], async () => {
2421
- const stored = await env.store.load(ctx, "order-list", address);
2733
+ return (await runConcurrently(Array.from({ length: contenders }, () => (ctx) => env.store.withCheckpointLocks(ctx, "order-list", [identity], async () => {
2734
+ const stored = await env.store.load(ctx, "order-list", identity);
2422
2735
  if (expectedVersion === void 0 ? stored !== void 0 : stored?.position.aggregateVersion !== expectedVersion) return false;
2423
2736
  await Promise.resolve();
2424
- await env.store.save(ctx, "order-list", address, checkpoint(pos(nextVersion, 0), `evt-v${nextVersion}`));
2737
+ await env.store.save(ctx, "order-list", identity, checkpoint(pos(nextVersion, 0), `evt-v${nextVersion}`));
2425
2738
  return true;
2426
2739
  })))).filter((advanced) => advanced).length;
2427
2740
  };
2428
2741
  assertEqual(await advanceOnce(void 0, 1), 1, "exactly one competing callback may advance a missing checkpoint key; genesis has no row that SELECT FOR UPDATE could lock");
2429
2742
  assertEqual(await advanceOnce(1, 2), 1, "exactly one competing callback may advance an existing checkpoint key from the observed watermark");
2430
- const stored = await env.run((ctx) => env.store.load(ctx, "order-list", address));
2743
+ const stored = await env.run((ctx) => env.store.load(ctx, "order-list", identity));
2431
2744
  assert(stored?.position.aggregateVersion === 2, "serialized genesis and existing-row advances must leave the final watermark visible");
2432
2745
  })
2433
2746
  }),
2434
2747
  {
2435
2748
  name: "checkpoint locks release after a rejected critical section",
2436
2749
  run: inEnv(async (env) => {
2437
- const address = order("o-rejected-lock");
2438
- const rejection = await captureRejection(env.run((ctx) => env.store.withCheckpointLocks(ctx, "order-list", [address], async () => {
2750
+ const identity = order("o-rejected-lock");
2751
+ const rejection = await captureRejection(env.run((ctx) => env.store.withCheckpointLocks(ctx, "order-list", [identity], async () => {
2439
2752
  throw new Error("projection failed");
2440
2753
  })));
2441
2754
  assert(rejection !== void 0, "the store must propagate a rejected critical section");
2442
2755
  let retried = false;
2443
- await env.run((ctx) => env.store.withCheckpointLocks(ctx, "order-list", [address], async () => {
2756
+ await env.run((ctx) => env.store.withCheckpointLocks(ctx, "order-list", [identity], async () => {
2444
2757
  retried = true;
2445
2758
  }));
2446
2759
  assert(retried, "a rejected callback must release its key so redelivery can enter");
@@ -2516,7 +2829,7 @@ function createProjectionCheckpointStoreContractTests(harness) {
2516
2829
  })
2517
2830
  },
2518
2831
  {
2519
- name: "address encoding is collision-free even with separator-like characters in either half",
2832
+ name: "aggregate identity encoding is collision-free even with separator-like characters in either half",
2520
2833
  run: inEnv(async (env) => {
2521
2834
  const inType = {
2522
2835
  aggregateType: "A\0B",
@@ -2531,7 +2844,7 @@ function createProjectionCheckpointStoreContractTests(harness) {
2531
2844
  await env.store.save(ctx, "order-list", inId, checkpoint(pos(1, 0)));
2532
2845
  });
2533
2846
  const [first, second] = await env.run((ctx) => Promise.all([env.store.load(ctx, "order-list", inType), env.store.load(ctx, "order-list", inId)]));
2534
- assert(first?.position.aggregateVersion === 10 && second?.position.aggregateVersion === 1, "two addresses that differ only in where a hostile separator sits must not share a watermark");
2847
+ assert(first?.position.aggregateVersion === 10 && second?.position.aggregateVersion === 1, "two aggregate identities that differ only in where a hostile separator sits must not share a watermark");
2535
2848
  })
2536
2849
  },
2537
2850
  {
@@ -2574,6 +2887,185 @@ function createProjectionCheckpointStoreContractTests(harness) {
2574
2887
  ];
2575
2888
  }
2576
2889
 
2890
+ //#endregion
2891
+ //#region src/testing/replayable-stream-pages.ts
2892
+ /**
2893
+ * Builds a replayable stream read from an in-memory tail, for a test of the
2894
+ * code that consumes a read: a repository, or the replay over a fixed window.
2895
+ * It stands in for `readStreamPages` or an adapter that pages on its own,
2896
+ * and it tests neither. The tail is copied once and frozen deeply, and it
2897
+ * comes back in the same pages on every iteration. So a consumer that
2898
+ * changes an event in place fails at the write in its own test. An empty
2899
+ * tail yields no page, as `readStreamPages` does.
2900
+ */
2901
+ function createReplayableStreamPages(stream, options) {
2902
+ const limit = options.limit ?? Math.max(options.tail.length, 1);
2903
+ assertPositiveSafeInteger("createReplayableStreamPages", "limit", limit);
2904
+ const pages = sliceIntoPages(deepFreeze(structuredClone([...options.tail])), limit);
2905
+ return {
2906
+ stream,
2907
+ fromVersion: options.fromVersion,
2908
+ targetVersion: options.targetVersion,
2909
+ pages: { [Symbol.asyncIterator]: () => yieldEachPage(pages) }
2910
+ };
2911
+ }
2912
+ function sliceIntoPages(tail, limit) {
2913
+ const pages = [];
2914
+ for (let start = 0; start < tail.length; start += limit) pages.push(Object.freeze(tail.slice(start, start + limit)));
2915
+ return pages;
2916
+ }
2917
+ async function* yieldEachPage(pages) {
2918
+ yield* pages;
2919
+ }
2920
+
2921
+ //#endregion
2922
+ //#region src/testing/replayable-stream-pages-contract.ts
2923
+ /**
2924
+ * Reusable proof of code that builds `ReplayableStreamPages` values:
2925
+ * `readStreamPages`, or an adapter that pages on its own.
2926
+ * The suite proves that the pages hold the events after the cursor
2927
+ * through the target version in append order, that no page is empty or
2928
+ * larger than the limit, that every iteration yields the same prefix, and
2929
+ * that an append during the iteration stays out. It also proves that an
2930
+ * absent stream and a window outside the stream give no replayable read.
2931
+ */
2932
+ function createReplayableStreamPagesContractTests(harness) {
2933
+ const inEnv = bindContractEnvironment(() => harness.createEnvironment());
2934
+ const seed = async (env, count) => {
2935
+ const stream = harness.createStream();
2936
+ const events = Array.from({ length: count }, (_, index) => harness.createEvent(stream, index + 1));
2937
+ await env.append(stream, events);
2938
+ return {
2939
+ stream,
2940
+ events
2941
+ };
2942
+ };
2943
+ const readOrFail = async (env, stream, window) => {
2944
+ const read = await env.read({ ...stream }, window);
2945
+ assert(read !== void 0, `a window (${window.fromVersion}, ${window.toVersion ?? "head"}] inside the stream must give a replayable read`);
2946
+ return read;
2947
+ };
2948
+ const collectPages = async (read, limit) => {
2949
+ const collected = [];
2950
+ for await (const page of read.pages) {
2951
+ assert(page.length > 0 && page.length <= limit, `every page must hold at least one event and at most the limit ${limit}; got ${page.length}`);
2952
+ collected.push(...page);
2953
+ }
2954
+ return collected;
2955
+ };
2956
+ const sameEventIds = (actual, expected) => actual.length === expected.length && actual.every((event, index) => event.eventId === expected[index]?.eventId);
2957
+ return [
2958
+ {
2959
+ name: "full read: the pages hold every event through the head in append order",
2960
+ run: inEnv(async (env) => {
2961
+ const { stream, events } = await seed(env, 5);
2962
+ const read = await readOrFail(env, stream, {
2963
+ fromVersion: 0,
2964
+ limit: 2
2965
+ });
2966
+ assert(read.stream.aggregateType === stream.aggregateType && read.stream.aggregateId === stream.aggregateId, "the read must name the stream it reads");
2967
+ assert(read.fromVersion === 0 && read.targetVersion === events.length, `a full read must run from 0 to the head ${events.length}; got (${read.fromVersion}, ${read.targetVersion}]`);
2968
+ assert(sameEventIds(await collectPages(read, 2), events), "the pages must hold every event of the stream in append order");
2969
+ })
2970
+ },
2971
+ {
2972
+ name: "catch-up read: the pages start after fromVersion",
2973
+ run: inEnv(async (env) => {
2974
+ const { stream, events } = await seed(env, 5);
2975
+ const read = await readOrFail(env, stream, {
2976
+ fromVersion: 2,
2977
+ limit: 2
2978
+ });
2979
+ assert(read.fromVersion === 2 && read.targetVersion === events.length, `a catch-up read must run from 2 to the head ${events.length}; got (${read.fromVersion}, ${read.targetVersion}]`);
2980
+ assert(sameEventIds(await collectPages(read, 2), events.slice(2)), "the pages must hold only the events after fromVersion");
2981
+ })
2982
+ },
2983
+ {
2984
+ name: "point-in-time read: toVersion is the target version and the pages end there",
2985
+ run: inEnv(async (env) => {
2986
+ const { stream, events } = await seed(env, 5);
2987
+ const read = await readOrFail(env, stream, {
2988
+ fromVersion: 0,
2989
+ toVersion: 3,
2990
+ limit: 2
2991
+ });
2992
+ assert(read.targetVersion === 3, `a read to version 3 must pin 3 as the target version; got ${read.targetVersion}`);
2993
+ assert(sameEventIds(await collectPages(read, 2), events.slice(0, 3)), "the pages must end at the target version");
2994
+ })
2995
+ },
2996
+ {
2997
+ name: "empty window: a read at the head yields no page",
2998
+ run: inEnv(async (env) => {
2999
+ const { stream, events } = await seed(env, 3);
3000
+ const read = await readOrFail(env, stream, {
3001
+ fromVersion: events.length,
3002
+ limit: 2
3003
+ });
3004
+ assert(read.targetVersion === events.length, `a read at the head must pin the head ${events.length}; got ${read.targetVersion}`);
3005
+ assert((await collectPages(read, 2)).length === 0, "a read at the head must yield no page, not an empty one");
3006
+ })
3007
+ },
3008
+ {
3009
+ name: "re-iteration: every iteration yields the same prefix",
3010
+ run: inEnv(async (env) => {
3011
+ const { stream, events } = await seed(env, 5);
3012
+ const read = await readOrFail(env, stream, {
3013
+ fromVersion: 0,
3014
+ limit: 2
3015
+ });
3016
+ const first = await collectPages(read, 2);
3017
+ const second = await collectPages(read, 2);
3018
+ assert(sameEventIds(first, events) && sameEventIds(second, events), "a second iteration must start again from the first page and yield the same events");
3019
+ })
3020
+ },
3021
+ {
3022
+ name: "target as upper bound: an append during the iteration stays out of the pages",
3023
+ run: inEnv(async (env) => {
3024
+ const { stream, events } = await seed(env, 5);
3025
+ const read = await readOrFail(env, stream, {
3026
+ fromVersion: 0,
3027
+ limit: 2
3028
+ });
3029
+ const collected = [];
3030
+ for await (const page of read.pages) {
3031
+ if (collected.length === 0) await env.append(stream, [harness.createEvent(stream, 6)]);
3032
+ collected.push(...page);
3033
+ }
3034
+ assert(read.targetVersion === events.length, "an append after the read must not move the target version");
3035
+ assert(sameEventIds(collected, events), "the pages must end at the target version and leave the later append out");
3036
+ })
3037
+ },
3038
+ {
3039
+ name: "no replayable read: an absent stream and a window outside the stream",
3040
+ run: inEnv(async (env) => {
3041
+ const { stream, events } = await seed(env, 3);
3042
+ const absent = harness.createStream();
3043
+ const outside = [
3044
+ {
3045
+ fromVersion: 0,
3046
+ toVersion: events.length + 1,
3047
+ limit: 2
3048
+ },
3049
+ {
3050
+ fromVersion: events.length + 1,
3051
+ limit: 2
3052
+ },
3053
+ {
3054
+ fromVersion: 3,
3055
+ toVersion: 2,
3056
+ limit: 2
3057
+ }
3058
+ ];
3059
+ assert(await env.read({ ...absent }, {
3060
+ fromVersion: 0,
3061
+ limit: 2
3062
+ }) === void 0, "an absent stream must give no replayable read");
3063
+ for (const window of outside) assert(await env.read({ ...stream }, window) === void 0, `the window (${window.fromVersion}, ${window.toVersion ?? "head"}] lies outside a stream at ${events.length} and must give no replayable read`);
3064
+ })
3065
+ }
3066
+ ];
3067
+ }
3068
+
2577
3069
  //#endregion
2578
3070
  //#region src/testing/repository-contract.ts
2579
3071
  /**
@@ -2679,6 +3171,7 @@ function createRepositoryContractTests(harness) {
2679
3171
  writerB.release();
2680
3172
  const rejection = await captureRejection(writerB.call);
2681
3173
  assertChainContainsKitError(rejection, ["CONCURRENCY_CONFLICT"], `stale update must reject with ConcurrencyConflictError; got ${describeError(rejection)}`);
3174
+ assertChainConflictNamesIntent(rejection, "update", "the stale update conflict must name the update in its intent");
2682
3175
  const final = await reload(environment, seeded.id);
2683
3176
  assertEqual(final.version, committedA.version, "the stale writer must not replace writer A's version");
2684
3177
  if (snapshotState) assert(deepEqual(snapshotState.call(harness, final), snapshotState.call(harness, committedA)), "the stale writer must not replace writer A's state");
@@ -2835,6 +3328,7 @@ function createRepositoryContractTests(harness) {
2835
3328
  staleRemove.release();
2836
3329
  const rejection = await captureRejection(staleRemove.call);
2837
3330
  assertChainContainsKitError(rejection, ["CONCURRENCY_CONFLICT"], `stale remove must reject with ConcurrencyConflictError; got ${describeError(rejection)}`);
3331
+ assertChainConflictNamesIntent(rejection, "remove", "the stale remove conflict must name the remove in its intent");
2838
3332
  assert(await reload(environment, seeded.id) !== void 0, "stale remove must not delete the concurrent winner");
2839
3333
  })
2840
3334
  })));
@@ -2853,7 +3347,7 @@ function snapshot(version, state, schemaVersion) {
2853
3347
  };
2854
3348
  }
2855
3349
  const id = (value) => value;
2856
- const address = (aggregateType, aggregateId) => ({
3350
+ const identity = (aggregateType, aggregateId) => ({
2857
3351
  aggregateType,
2858
3352
  aggregateId: id(aggregateId)
2859
3353
  });
@@ -2873,7 +3367,7 @@ function createSnapshotStoreContractTests(harness) {
2873
3367
  {
2874
3368
  name: "an aggregate without a snapshot loads undefined",
2875
3369
  run: inEnv(async (env) => {
2876
- assertEqual(await env.store.load(address("Order", "o-1")), void 0, "a fresh store must report no snapshot; the repository falls back to full replay");
3370
+ assertEqual(await env.store.load(identity("Order", "o-1")), void 0, "a fresh store must report no snapshot; the repository falls back to full replay");
2877
3371
  })
2878
3372
  },
2879
3373
  {
@@ -2887,8 +3381,8 @@ function createSnapshotStoreContractTests(harness) {
2887
3381
  }],
2888
3382
  note: "hi"
2889
3383
  }, 3);
2890
- await env.store.save(address("Order", "o-1"), stored);
2891
- const loaded = await env.store.load(address("Order", "o-1"));
3384
+ await env.store.save(identity("Order", "o-1"), stored);
3385
+ const loaded = await env.store.load(identity("Order", "o-1"));
2892
3386
  assert(loaded !== void 0, "the saved snapshot must load");
2893
3387
  assert(deepEqual(loaded.state, stored.state), "the state must round-trip deep-equal as plain data");
2894
3388
  assertEqual(loaded.version, 42, "the aggregate version must round-trip");
@@ -2900,67 +3394,67 @@ function createSnapshotStoreContractTests(harness) {
2900
3394
  {
2901
3395
  name: "an absent schemaVersion round-trips as absent",
2902
3396
  run: inEnv(async (env) => {
2903
- await env.store.save(address("Order", "o-1"), snapshot(1, {
3397
+ await env.store.save(identity("Order", "o-1"), snapshot(1, {
2904
3398
  total: 0,
2905
3399
  items: []
2906
3400
  }));
2907
- const loaded = await env.store.load(address("Order", "o-1"));
3401
+ const loaded = await env.store.load(identity("Order", "o-1"));
2908
3402
  assertEqual(loaded?.schemaVersion, void 0, "a snapshot stored without schemaVersion must not come back with a fabricated one; restore treats absence as schema 1");
2909
3403
  })
2910
3404
  },
2911
3405
  {
2912
3406
  name: "save replaces the previous snapshot: latest wins, no history",
2913
3407
  run: inEnv(async (env) => {
2914
- await env.store.save(address("Order", "o-1"), snapshot(10, {
3408
+ await env.store.save(identity("Order", "o-1"), snapshot(10, {
2915
3409
  total: 1,
2916
3410
  items: []
2917
3411
  }));
2918
- await env.store.save(address("Order", "o-1"), snapshot(20, {
3412
+ await env.store.save(identity("Order", "o-1"), snapshot(20, {
2919
3413
  total: 2,
2920
3414
  items: []
2921
3415
  }));
2922
- const loaded = await env.store.load(address("Order", "o-1"));
3416
+ const loaded = await env.store.load(identity("Order", "o-1"));
2923
3417
  assert(loaded?.version === 20 && loaded.state.total === 2, "load must return the latest snapshot only");
2924
3418
  })
2925
3419
  },
2926
3420
  {
2927
3421
  name: "snapshots are isolated per aggregate type AND per aggregate id",
2928
3422
  run: inEnv(async (env) => {
2929
- await env.store.save(address("Order", "x-1"), snapshot(1, {
3423
+ await env.store.save(identity("Order", "x-1"), snapshot(1, {
2930
3424
  total: 1,
2931
3425
  items: []
2932
3426
  }));
2933
- await env.store.save(address("Invoice", "x-1"), snapshot(2, {
3427
+ await env.store.save(identity("Invoice", "x-1"), snapshot(2, {
2934
3428
  total: 2,
2935
3429
  items: []
2936
3430
  }));
2937
- await env.store.save(address("Order", "x-2"), snapshot(3, {
3431
+ await env.store.save(identity("Order", "x-2"), snapshot(3, {
2938
3432
  total: 3,
2939
3433
  items: []
2940
3434
  }));
2941
3435
  const [orderX1, invoiceX1, orderX2] = await Promise.all([
2942
- env.store.load(address("Order", "x-1")),
2943
- env.store.load(address("Invoice", "x-1")),
2944
- env.store.load(address("Order", "x-2"))
3436
+ env.store.load(identity("Order", "x-1")),
3437
+ env.store.load(identity("Invoice", "x-1")),
3438
+ env.store.load(identity("Order", "x-2"))
2945
3439
  ]);
2946
3440
  assert(orderX1?.version === 1 && invoiceX1?.version === 2 && orderX2?.version === 3, "the key is the (aggregateType, aggregateId) pair; neither half may bleed into the other");
2947
3441
  })
2948
3442
  },
2949
3443
  {
2950
- name: "delete removes exactly the addressed snapshot and tolerates unknown keys",
3444
+ name: "delete removes exactly the snapshot of the given aggregate identity and tolerates unknown keys",
2951
3445
  run: inEnv(async (env) => {
2952
- await env.store.save(address("Order", "o-1"), snapshot(1, {
3446
+ await env.store.save(identity("Order", "o-1"), snapshot(1, {
2953
3447
  total: 1,
2954
3448
  items: []
2955
3449
  }));
2956
- await env.store.save(address("Order", "o-2"), snapshot(2, {
3450
+ await env.store.save(identity("Order", "o-2"), snapshot(2, {
2957
3451
  total: 2,
2958
3452
  items: []
2959
3453
  }));
2960
- await env.store.delete(address("Order", "o-1"));
2961
- await env.store.delete(address("Order", "never-saved"));
2962
- assertEqual(await env.store.load(address("Order", "o-1")), void 0, "the deleted snapshot must be gone (schema-migration fallback and erasure both rely on it)");
2963
- assertEqual((await env.store.load(address("Order", "o-2")))?.version, 2, "a sibling snapshot must survive the delete");
3454
+ await env.store.delete(identity("Order", "o-1"));
3455
+ await env.store.delete(identity("Order", "never-saved"));
3456
+ assertEqual(await env.store.load(identity("Order", "o-1")), void 0, "the deleted snapshot must be gone (schema-migration fallback and erasure both rely on it)");
3457
+ assertEqual((await env.store.load(identity("Order", "o-2")))?.version, 2, "a sibling snapshot must survive the delete");
2964
3458
  })
2965
3459
  },
2966
3460
  {
@@ -2973,16 +3467,16 @@ function createSnapshotStoreContractTests(harness) {
2973
3467
  qty: 1
2974
3468
  }]
2975
3469
  });
2976
- await env.store.save(address("Order", "o-1"), input);
3470
+ await env.store.save(identity("Order", "o-1"), input);
2977
3471
  input.state.items.push({
2978
3472
  sku: "hacked",
2979
3473
  qty: 99
2980
3474
  });
2981
- const loaded = await env.store.load(address("Order", "o-1"));
3475
+ const loaded = await env.store.load(identity("Order", "o-1"));
2982
3476
  assert(loaded !== void 0, "expected the saved snapshot");
2983
3477
  assertEqual(loaded.state.items.length, 1, "save must capture the snapshot by value, not hold the caller's reference");
2984
3478
  loaded.state.total = 999;
2985
- const reloaded = await env.store.load(address("Order", "o-1"));
3479
+ const reloaded = await env.store.load(identity("Order", "o-1"));
2986
3480
  assertEqual(reloaded?.state.total, 5, "load must hand out a detached copy, never live internal state");
2987
3481
  })
2988
3482
  }
@@ -2990,5 +3484,5 @@ function createSnapshotStoreContractTests(harness) {
2990
3484
  }
2991
3485
 
2992
3486
  //#endregion
2993
- export { createCommandOutboxContractTests, createDeadlineStoreContractTests, createEsRepositoryContractTests, createEventBusContractTests, createEventStoreContractTests, createIdempotencyStoreContractTests, createOutboxContractTests, createProjectionCheckpointStoreContractTests, createRepositoryContractTests, createSnapshotStoreContractTests };
3487
+ export { createCommandOutboxContractTests, createDeadlineStoreContractTests, createEsRepositoryContractTests, createEventBusContractTests, createEventStoreContractTests, createIdempotencyStoreContractTests, createOutboxContractTests, createProjectionCheckpointStoreContractTests, createReplayableStreamPages, createReplayableStreamPagesContractTests, createRepositoryContractTests, createSnapshotStoreContractTests };
2994
3488
  //# sourceMappingURL=testing.js.map