@rotorsoft/act-tck 1.0.0 → 1.2.0

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/cache-tck.ts
2
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { afterEach, beforeEach as beforeEach2, describe, expect, it } from "vitest";
3
3
  var entry = (event_id, state = {}) => ({
4
4
  state,
5
5
  version: event_id,
@@ -10,7 +10,7 @@ var entry = (event_id, state = {}) => ({
10
10
  var runCacheTck = (options) => {
11
11
  describe(`TCK / Cache / ${options.name}`, () => {
12
12
  let cache;
13
- beforeEach(() => {
13
+ beforeEach2(() => {
14
14
  cache = options.factory();
15
15
  });
16
16
  afterEach(async () => {
@@ -129,13 +129,13 @@ var collect = async (store, query) => {
129
129
  };
130
130
 
131
131
  // src/logger-tck.ts
132
- import { afterEach as afterEach2, beforeEach as beforeEach2, describe as describe2, expect as expect2, it as it2 } from "vitest";
132
+ import { afterEach as afterEach2, beforeEach as beforeEach3, describe as describe2, expect as expect2, it as it2 } from "vitest";
133
133
  var LEVELS = ["fatal", "error", "warn", "info", "debug", "trace"];
134
134
  var runLoggerTck = (options) => {
135
135
  describe2(`TCK / Logger / ${options.name}`, () => {
136
136
  let logger;
137
137
  let originalStdout;
138
- beforeEach2(() => {
138
+ beforeEach3(() => {
139
139
  logger = options.factory();
140
140
  originalStdout = process.stdout.write.bind(process.stdout);
141
141
  process.stdout.write = (() => true);
@@ -189,12 +189,18 @@ var runLoggerTck = (options) => {
189
189
  };
190
190
 
191
191
  // src/store-tck.ts
192
- import { ConcurrencyError, SNAP_EVENT, TOMBSTONE_EVENT } from "@rotorsoft/act";
192
+ import {
193
+ act,
194
+ ConcurrencyError,
195
+ InMemoryCache,
196
+ SNAP_EVENT,
197
+ TOMBSTONE_EVENT
198
+ } from "@rotorsoft/act";
193
199
  import { afterAll, beforeAll, describe as describe3, expect as expect3, it as it3 } from "vitest";
194
200
  var runStoreTck = (options) => {
195
201
  describe3(`TCK / Store / ${options.name}`, () => {
196
202
  let store;
197
- const caps = options.capabilities ?? {};
203
+ const caps = { ...options.capabilities };
198
204
  beforeAll(async () => {
199
205
  store = await options.factory();
200
206
  await store.drop();
@@ -1504,6 +1510,373 @@ var runStoreTck = (options) => {
1504
1510
  }
1505
1511
  });
1506
1512
  });
1513
+ describe3.skipIf(!caps.restore)("restore (capability)", () => {
1514
+ beforeEach(async () => {
1515
+ await store.drop();
1516
+ await store.seed();
1517
+ });
1518
+ const asSource = (events) => (async function* () {
1519
+ for (const e of events) yield e;
1520
+ })();
1521
+ const event = (originalId, stream, version, name, created, data = {}) => ({
1522
+ id: originalId,
1523
+ name,
1524
+ data,
1525
+ stream,
1526
+ version,
1527
+ created,
1528
+ meta: { correlation: "restore-tck", causation: {} }
1529
+ });
1530
+ const restore = async (source, opts = {}) => {
1531
+ const cache = new InMemoryCache();
1532
+ const app = act().build({ scoped: { store, cache } });
1533
+ try {
1534
+ return await app.restore(source, opts);
1535
+ } finally {
1536
+ await cache.dispose();
1537
+ }
1538
+ };
1539
+ it3("returns kept=0 on an empty source", async () => {
1540
+ const result = await restore(asSource([]));
1541
+ expect3(result.kept).toBe(0);
1542
+ expect3(result.duration_ms).toBeGreaterThanOrEqual(0);
1543
+ expect3(result.dropped).toEqual({
1544
+ closed_streams: 0,
1545
+ snapshots: 0,
1546
+ empty_streams: 0
1547
+ });
1548
+ const events = await collect(store, { limit: 10 });
1549
+ expect3(events).toHaveLength(0);
1550
+ });
1551
+ it3("rebuilds a single stream and preserves `created` verbatim", async () => {
1552
+ const s = `restore-single-${uid()}`;
1553
+ const t0 = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
1554
+ const t1 = /* @__PURE__ */ new Date("2020-01-02T00:00:00.000Z");
1555
+ const t2 = /* @__PURE__ */ new Date("2020-01-03T00:00:00.000Z");
1556
+ const events = [
1557
+ event(1, s, 0, "Incremented", t0, { amount: 1 }),
1558
+ event(2, s, 1, "Incremented", t1, { amount: 2 }),
1559
+ event(3, s, 2, "Decremented", t2, { amount: 1 })
1560
+ ];
1561
+ const result = await restore(asSource(events));
1562
+ expect3(result.kept).toBe(3);
1563
+ const back = [];
1564
+ await store.query(
1565
+ (e) => {
1566
+ back.push(e);
1567
+ },
1568
+ { stream: s, stream_exact: true }
1569
+ );
1570
+ expect3(back).toHaveLength(3);
1571
+ expect3(
1572
+ back.map((e) => ({
1573
+ stream: e.stream,
1574
+ version: e.version,
1575
+ name: e.name,
1576
+ created: e.created.toISOString(),
1577
+ data: e.data
1578
+ }))
1579
+ ).toEqual([
1580
+ {
1581
+ stream: s,
1582
+ version: 0,
1583
+ name: "Incremented",
1584
+ created: t0.toISOString(),
1585
+ data: { amount: 1 }
1586
+ },
1587
+ {
1588
+ stream: s,
1589
+ version: 1,
1590
+ name: "Incremented",
1591
+ created: t1.toISOString(),
1592
+ data: { amount: 2 }
1593
+ },
1594
+ {
1595
+ stream: s,
1596
+ version: 2,
1597
+ name: "Decremented",
1598
+ created: t2.toISOString(),
1599
+ data: { amount: 1 }
1600
+ }
1601
+ ]);
1602
+ });
1603
+ it3("rebuilds multiple streams interleaved", async () => {
1604
+ const a = `restore-multi-a-${uid()}`;
1605
+ const b = `restore-multi-b-${uid()}`;
1606
+ const t = /* @__PURE__ */ new Date("2020-06-01T00:00:00.000Z");
1607
+ const events = [
1608
+ event(1, a, 0, "Incremented", t, { amount: 10 }),
1609
+ event(2, b, 0, "Incremented", t, { amount: 20 }),
1610
+ event(3, a, 1, "Decremented", t, { amount: 5 }),
1611
+ event(4, b, 1, "Incremented", t, { amount: 30 })
1612
+ ];
1613
+ const result = await restore(asSource(events));
1614
+ expect3(result.kept).toBe(4);
1615
+ const aBack = [];
1616
+ const bBack = [];
1617
+ await store.query(
1618
+ (e) => {
1619
+ aBack.push(e);
1620
+ },
1621
+ { stream: a, stream_exact: true }
1622
+ );
1623
+ await store.query(
1624
+ (e) => {
1625
+ bBack.push(e);
1626
+ },
1627
+ { stream: b, stream_exact: true }
1628
+ );
1629
+ expect3(aBack.map((e) => e.version)).toEqual([0, 1]);
1630
+ expect3(bBack.map((e) => e.version)).toEqual([0, 1]);
1631
+ });
1632
+ it3("preserves Date `created` verbatim", async () => {
1633
+ const s = `restore-isoc-${uid()}`;
1634
+ const iso = "2021-07-15T12:34:56.789Z";
1635
+ await restore(
1636
+ asSource([
1637
+ {
1638
+ id: 1,
1639
+ stream: s,
1640
+ version: 0,
1641
+ name: "Incremented",
1642
+ data: { amount: 1 },
1643
+ created: new Date(iso),
1644
+ meta: { correlation: "restore-tck", causation: {} }
1645
+ }
1646
+ ])
1647
+ );
1648
+ const back = [];
1649
+ await store.query(
1650
+ (e) => {
1651
+ back.push(e);
1652
+ },
1653
+ { stream: s, stream_exact: true }
1654
+ );
1655
+ expect3(back).toHaveLength(1);
1656
+ expect3(back[0].created.toISOString()).toBe(iso);
1657
+ });
1658
+ it3("wipes pre-existing events before inserting", async () => {
1659
+ const old = `restore-old-${uid()}`;
1660
+ await store.commit(
1661
+ old,
1662
+ [inc(1), inc(2)],
1663
+ makeMeta({ stream: old })
1664
+ );
1665
+ const fresh = `restore-fresh-${uid()}`;
1666
+ const t = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
1667
+ await restore(
1668
+ asSource([event(1, fresh, 0, "Incremented", t, { amount: 99 })])
1669
+ );
1670
+ const oldBack = await collect(store, {
1671
+ stream: old,
1672
+ stream_exact: true
1673
+ });
1674
+ expect3(oldBack).toHaveLength(0);
1675
+ const freshBack = await collect(store, {
1676
+ stream: fresh,
1677
+ stream_exact: true
1678
+ });
1679
+ expect3(freshBack).toHaveLength(1);
1680
+ });
1681
+ it3("clears subscription/stream-position metadata", async () => {
1682
+ const sub = `restore-sub-${uid()}`;
1683
+ await store.subscribe([{ stream: sub, source: "anything" }]);
1684
+ const collectStreams = async () => {
1685
+ const out = [];
1686
+ await store.query_streams((p) => {
1687
+ out.push(p.stream);
1688
+ });
1689
+ return out;
1690
+ };
1691
+ const before = await collectStreams();
1692
+ expect3(before.includes(sub)).toBe(true);
1693
+ await restore(asSource([]));
1694
+ const after = await collectStreams();
1695
+ expect3(after.includes(sub)).toBe(false);
1696
+ });
1697
+ it3("preserves snapshot events through restore", async () => {
1698
+ const s = `restore-snap-${uid()}`;
1699
+ const t = /* @__PURE__ */ new Date("2020-04-01T00:00:00.000Z");
1700
+ await restore(
1701
+ asSource([
1702
+ {
1703
+ id: 1,
1704
+ stream: s,
1705
+ version: 0,
1706
+ name: SNAP_EVENT,
1707
+ data: { count: 42 },
1708
+ created: t,
1709
+ meta: { correlation: "snap", causation: {} }
1710
+ }
1711
+ ])
1712
+ );
1713
+ const back = await collect(store, {
1714
+ stream: s,
1715
+ stream_exact: true,
1716
+ with_snaps: true
1717
+ });
1718
+ expect3(back).toHaveLength(1);
1719
+ expect3(back[0].name).toBe(SNAP_EVENT);
1720
+ });
1721
+ it3("rewrites causation refs through the old\u2192new id map", async () => {
1722
+ const s = `restore-caus-${uid()}`;
1723
+ const t = /* @__PURE__ */ new Date("2020-08-01T00:00:00.000Z");
1724
+ const events = [
1725
+ {
1726
+ id: 5,
1727
+ stream: s,
1728
+ version: 0,
1729
+ name: "Incremented",
1730
+ data: { amount: 1 },
1731
+ created: t,
1732
+ meta: { correlation: "c", causation: {} }
1733
+ },
1734
+ {
1735
+ id: 7,
1736
+ stream: s,
1737
+ version: 1,
1738
+ name: "Incremented",
1739
+ data: { amount: 2 },
1740
+ created: t,
1741
+ meta: {
1742
+ correlation: "c",
1743
+ causation: {
1744
+ event: { id: 5, name: "Incremented", stream: s }
1745
+ }
1746
+ }
1747
+ },
1748
+ {
1749
+ id: 9,
1750
+ stream: s,
1751
+ version: 2,
1752
+ name: "Decremented",
1753
+ data: { amount: 1 },
1754
+ created: t,
1755
+ meta: {
1756
+ correlation: "c",
1757
+ causation: {
1758
+ event: { id: 7, name: "Incremented", stream: s }
1759
+ }
1760
+ }
1761
+ }
1762
+ ];
1763
+ await restore(asSource(events));
1764
+ const back = [];
1765
+ await store.query(
1766
+ (e) => {
1767
+ back.push(e);
1768
+ },
1769
+ { stream: s, stream_exact: true }
1770
+ );
1771
+ expect3(back).toHaveLength(3);
1772
+ expect3(back[0].meta.causation.event).toBeUndefined();
1773
+ expect3(back[1].meta.causation.event?.id).toBe(back[0].id);
1774
+ expect3(back[2].meta.causation.event?.id).toBe(back[1].id);
1775
+ });
1776
+ it3("leaves causation refs unmapped when the target isn't in the source", async () => {
1777
+ const s = `restore-orphan-${uid()}`;
1778
+ const t = /* @__PURE__ */ new Date("2020-09-01T00:00:00.000Z");
1779
+ await restore(
1780
+ asSource([
1781
+ {
1782
+ id: 1,
1783
+ stream: s,
1784
+ version: 0,
1785
+ name: "Incremented",
1786
+ data: { amount: 1 },
1787
+ created: t,
1788
+ meta: {
1789
+ correlation: "c",
1790
+ causation: {
1791
+ event: { id: 999, name: "Phantom", stream: "ghost" }
1792
+ }
1793
+ }
1794
+ }
1795
+ ])
1796
+ );
1797
+ const back = [];
1798
+ await store.query(
1799
+ (e) => {
1800
+ back.push(e);
1801
+ },
1802
+ { stream: s, stream_exact: true }
1803
+ );
1804
+ expect3(back[0].meta.causation.event?.id).toBe(999);
1805
+ });
1806
+ it3("rolls back atomically when the source throws mid-iteration", async () => {
1807
+ const original = `restore-pre-${uid()}`;
1808
+ const committed = await store.commit(
1809
+ original,
1810
+ [inc(1), inc(2), inc(3)],
1811
+ makeMeta({ stream: original })
1812
+ );
1813
+ const explosive = (async function* () {
1814
+ yield event(
1815
+ 1,
1816
+ `restore-explode-${uid()}`,
1817
+ 0,
1818
+ "Incremented",
1819
+ /* @__PURE__ */ new Date(),
1820
+ { amount: 1 }
1821
+ );
1822
+ throw new Error("boom");
1823
+ })();
1824
+ await expect3(restore(explosive)).rejects.toThrow("boom");
1825
+ const back = [];
1826
+ await store.query(
1827
+ (e) => {
1828
+ back.push(e);
1829
+ },
1830
+ { stream: original, stream_exact: true }
1831
+ );
1832
+ expect3(back).toHaveLength(3);
1833
+ expect3(back.map((e) => e.id)).toEqual(committed.map((c) => c.id));
1834
+ });
1835
+ it3("drop_snapshots: skips SNAP_EVENT rows and counts them", async () => {
1836
+ const s = `restore-drop-snap-${uid()}`;
1837
+ const t = /* @__PURE__ */ new Date("2020-10-01T00:00:00.000Z");
1838
+ const result = await restore(
1839
+ asSource([
1840
+ event(1, s, 0, "Incremented", t, { amount: 1 }),
1841
+ {
1842
+ id: 2,
1843
+ stream: s,
1844
+ version: 1,
1845
+ name: SNAP_EVENT,
1846
+ data: { count: 1 },
1847
+ created: t,
1848
+ meta: { correlation: "snap", causation: {} }
1849
+ },
1850
+ event(3, s, 2, "Incremented", t, { amount: 2 })
1851
+ ]),
1852
+ { drop_snapshots: true }
1853
+ );
1854
+ expect3(result.kept).toBe(2);
1855
+ expect3(result.dropped.snapshots).toBe(1);
1856
+ const back = await collect(store, {
1857
+ stream: s,
1858
+ stream_exact: true,
1859
+ with_snaps: true
1860
+ });
1861
+ expect3(back).toHaveLength(2);
1862
+ expect3(
1863
+ back.every((e) => e.name !== SNAP_EVENT)
1864
+ ).toBe(true);
1865
+ });
1866
+ it3("on_progress fires once per event (caller throttles)", async () => {
1867
+ const calls = [];
1868
+ const s = `restore-progress-${uid()}`;
1869
+ const t = /* @__PURE__ */ new Date("2021-02-01T00:00:00.000Z");
1870
+ await restore(
1871
+ asSource([
1872
+ event(1, s, 0, "Incremented", t, { amount: 1 }),
1873
+ event(2, s, 1, "Incremented", t, { amount: 2 })
1874
+ ]),
1875
+ { on_progress: (p) => calls.push(p.processed) }
1876
+ );
1877
+ expect3(calls).toEqual([1, 2]);
1878
+ });
1879
+ });
1507
1880
  if (caps.notify) {
1508
1881
  describe3("notify (capability)", () => {
1509
1882
  it3("delivers a notification when a different instance commits", async () => {