@pepys/testing 0.0.1 → 0.1.1

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 CHANGED
@@ -28,6 +28,9 @@ Set the compiler options from the [kit's README](../kit/README.md#configuration)
28
28
  ## Features
29
29
 
30
30
  - `createTestStore`: apply facts or write rows, then run a query, read rows and read links.
31
+ - `applyFactLog`: apply the facts that `createIngest` of `@pepys/ingest` stored in a memory fact log
32
+ to a test store, in place of the host. A second call applies only the records the log stored after
33
+ the first.
31
34
  - `applyInBothOrders`: apply facts in two orders and compare the stores.
32
35
  - The memory adapter of the store reader, for a test of a core query.
33
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pepys/testing",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "Test a pepys plug-in on Bun: an in-memory store that runs its consumers and queries",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,8 +19,9 @@
19
19
  "access": "public"
20
20
  },
21
21
  "dependencies": {
22
- "@pepys/kit": "0.0.1",
23
- "@pepys/kysely-turso": "0.0.1",
22
+ "@pepys/ingest": "0.1.1",
23
+ "@pepys/kit": "0.1.1",
24
+ "@pepys/kysely-turso": "0.1.1",
24
25
  "@tursodatabase/database": "0.8.3",
25
26
  "kysely": "0.29.8",
26
27
  "tiny-invariant": "1.3.3",
@@ -0,0 +1,58 @@
1
+ import type { MemoryFactLog } from '@pepys/ingest';
2
+ import { toStreamName, toStreamOffset } from '@pepys/kit';
3
+ import type { CheckedDeployment, FactEnvelope, StreamName } from '@pepys/kit';
4
+ import invariant from 'tiny-invariant';
5
+ import { storeHooks } from '../test-store/store-hooks.ts';
6
+ import type { TestStore } from '../test-store/types.ts';
7
+
8
+ // The host's part for a test: the records of every stream a consumer reads go to the store as one
9
+ // batch, whole streams in the order the consumers name them. A refusal throws an ApplyError and
10
+ // moves no offset, so the next call reads the same records again.
11
+ export async function applyFactLog(
12
+ store: TestStore,
13
+ log: MemoryFactLog,
14
+ deployment: CheckedDeployment,
15
+ ): Promise<void> {
16
+ const hooks = storeHooks.get(store);
17
+
18
+ if (hooks === undefined) {
19
+ throw new Error('the store is not the object that createTestStore returned');
20
+ }
21
+
22
+ const streams = new Set(deployment.consumers.flatMap((consumer) => consumer.input.streams));
23
+
24
+ const records: { stream: StreamName; envelope: FactEnvelope<unknown> }[] = [];
25
+
26
+ const tails = new Map<StreamName, number>();
27
+
28
+ for (const declared of streams) {
29
+ const stream = toStreamName(declared);
30
+ const from = hooks.nextOffsets.get(stream) ?? 0;
31
+
32
+ const tail = await log.readTail(stream);
33
+
34
+ if (!tail.ok) {
35
+ throw new Error(`the fact log has no stream ${declared}, which a consumer reads`);
36
+ }
37
+
38
+ if (tail.value === from) {
39
+ continue;
40
+ }
41
+
42
+ const read = await log.read(stream, toStreamOffset(from), tail.value - from);
43
+
44
+ invariant(read.ok, 'a stream with a tail can be read');
45
+
46
+ for (const stored of read.value) {
47
+ records.push({ stream, envelope: stored.fact });
48
+ }
49
+
50
+ tails.set(stream, tail.value);
51
+ }
52
+
53
+ await hooks.applyEnvelopes(records);
54
+
55
+ for (const [stream, tail] of tails) {
56
+ hooks.nextOffsets.set(stream, tail);
57
+ }
58
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { applyFactLog } from './fact-log/apply-fact-log.ts';
1
2
  export { applyInBothOrders } from './orders/apply-in-both-orders.ts';
2
3
  export type { OrderReport, StoreSnapshot } from './orders/apply-in-both-orders.ts';
3
4
  export type { MemoryStoreReader } from './memory-store-reader/create-memory-store-reader.ts';
@@ -70,6 +70,34 @@
70
70
  },
71
71
  "primaryKey": ["id"],
72
72
  "indexes": [["account_id"]]
73
+ },
74
+ "ledger_rate": {
75
+ "columns": {
76
+ "rate": {
77
+ "kind": "real",
78
+ "nullable": false
79
+ },
80
+ "label": {
81
+ "kind": "text",
82
+ "nullable": false
83
+ }
84
+ },
85
+ "primaryKey": ["rate"],
86
+ "indexes": []
87
+ },
88
+ "ledger_tag": {
89
+ "columns": {
90
+ "tag": {
91
+ "kind": "json",
92
+ "nullable": false
93
+ },
94
+ "label": {
95
+ "kind": "text",
96
+ "nullable": false
97
+ }
98
+ },
99
+ "primaryKey": ["tag"],
100
+ "indexes": []
73
101
  }
74
102
  },
75
103
  "links": [
@@ -166,7 +194,9 @@
166
194
  },
167
195
  "posted_at": "2026-11-01T09:00:00.000Z"
168
196
  }
169
- ]
197
+ ],
198
+ "ledger_rate": [],
199
+ "ledger_tag": []
170
200
  },
171
201
  "links": [
172
202
  {
@@ -273,7 +303,9 @@
273
303
  },
274
304
  "posted_at": "2026-11-01T09:00:00.000Z"
275
305
  }
276
- ]
306
+ ],
307
+ "ledger_rate": [],
308
+ "ledger_tag": []
277
309
  },
278
310
  "links": [
279
311
  {
@@ -360,7 +392,9 @@
360
392
  "updated_at": "2026-11-01T09:00:00.000Z"
361
393
  }
362
394
  ],
363
- "ledger_entry": []
395
+ "ledger_entry": [],
396
+ "ledger_rate": [],
397
+ "ledger_tag": []
364
398
  },
365
399
  "links": []
366
400
  },
@@ -471,7 +505,9 @@
471
505
  "updated_at": "2026-11-01T09:00:00.000Z"
472
506
  }
473
507
  ],
474
- "ledger_entry": []
508
+ "ledger_entry": [],
509
+ "ledger_rate": [],
510
+ "ledger_tag": []
475
511
  },
476
512
  "links": []
477
513
  },
@@ -713,7 +749,9 @@
713
749
  },
714
750
  "posted_at": "2026-11-01T09:00:00.000Z"
715
751
  }
716
- ]
752
+ ],
753
+ "ledger_rate": [],
754
+ "ledger_tag": []
717
755
  },
718
756
  "links": [
719
757
  {
@@ -805,7 +843,9 @@
805
843
  },
806
844
  "posted_at": "2026-11-01T09:00:00.000Z"
807
845
  }
808
- ]
846
+ ],
847
+ "ledger_rate": [],
848
+ "ledger_tag": []
809
849
  },
810
850
  "links": [
811
851
  {
@@ -876,7 +916,9 @@
876
916
  },
877
917
  "posted_at": "2026-11-01T09:00:00.000Z"
878
918
  }
879
- ]
919
+ ],
920
+ "ledger_rate": [],
921
+ "ledger_tag": []
880
922
  },
881
923
  "links": []
882
924
  },
@@ -947,7 +989,9 @@
947
989
  "updated_at": "2026-11-01T09:00:00.000Z"
948
990
  }
949
991
  ],
950
- "ledger_entry": []
992
+ "ledger_entry": [],
993
+ "ledger_rate": [],
994
+ "ledger_tag": []
951
995
  },
952
996
  "links": []
953
997
  },
@@ -1006,7 +1050,9 @@
1006
1050
  },
1007
1051
  "posted_at": "2026-11-01T09:00:00.000Z"
1008
1052
  }
1009
- ]
1053
+ ],
1054
+ "ledger_rate": [],
1055
+ "ledger_tag": []
1010
1056
  },
1011
1057
  "links": []
1012
1058
  },
@@ -1049,7 +1095,9 @@
1049
1095
  ],
1050
1096
  "rows": {
1051
1097
  "ledger_account": [],
1052
- "ledger_entry": []
1098
+ "ledger_entry": [],
1099
+ "ledger_rate": [],
1100
+ "ledger_tag": []
1053
1101
  },
1054
1102
  "links": [
1055
1103
  {
@@ -1091,7 +1139,9 @@
1091
1139
  ],
1092
1140
  "rows": {
1093
1141
  "ledger_account": [],
1094
- "ledger_entry": []
1142
+ "ledger_entry": [],
1143
+ "ledger_rate": [],
1144
+ "ledger_tag": []
1095
1145
  },
1096
1146
  "links": [
1097
1147
  {
@@ -1120,7 +1170,9 @@
1120
1170
  ],
1121
1171
  "rows": {
1122
1172
  "ledger_account": [],
1123
- "ledger_entry": []
1173
+ "ledger_entry": [],
1174
+ "ledger_rate": [],
1175
+ "ledger_tag": []
1124
1176
  },
1125
1177
  "links": []
1126
1178
  },
@@ -1139,7 +1191,9 @@
1139
1191
  ],
1140
1192
  "rows": {
1141
1193
  "ledger_account": [],
1142
- "ledger_entry": []
1194
+ "ledger_entry": [],
1195
+ "ledger_rate": [],
1196
+ "ledger_tag": []
1143
1197
  },
1144
1198
  "links": []
1145
1199
  },
@@ -1162,7 +1216,9 @@
1162
1216
  ],
1163
1217
  "rows": {
1164
1218
  "ledger_account": [],
1165
- "ledger_entry": []
1219
+ "ledger_entry": [],
1220
+ "ledger_rate": [],
1221
+ "ledger_tag": []
1166
1222
  },
1167
1223
  "links": []
1168
1224
  },
@@ -1181,7 +1237,9 @@
1181
1237
  ],
1182
1238
  "rows": {
1183
1239
  "ledger_account": [],
1184
- "ledger_entry": []
1240
+ "ledger_entry": [],
1241
+ "ledger_rate": [],
1242
+ "ledger_tag": []
1185
1243
  },
1186
1244
  "links": []
1187
1245
  },
@@ -1200,7 +1258,9 @@
1200
1258
  ],
1201
1259
  "rows": {
1202
1260
  "ledger_account": [],
1203
- "ledger_entry": []
1261
+ "ledger_entry": [],
1262
+ "ledger_rate": [],
1263
+ "ledger_tag": []
1204
1264
  },
1205
1265
  "links": []
1206
1266
  },
@@ -1219,7 +1279,9 @@
1219
1279
  ],
1220
1280
  "rows": {
1221
1281
  "ledger_account": [],
1222
- "ledger_entry": []
1282
+ "ledger_entry": [],
1283
+ "ledger_rate": [],
1284
+ "ledger_tag": []
1223
1285
  },
1224
1286
  "links": []
1225
1287
  },
@@ -1238,7 +1300,9 @@
1238
1300
  ],
1239
1301
  "rows": {
1240
1302
  "ledger_account": [],
1241
- "ledger_entry": []
1303
+ "ledger_entry": [],
1304
+ "ledger_rate": [],
1305
+ "ledger_tag": []
1242
1306
  },
1243
1307
  "links": []
1244
1308
  },
@@ -1257,7 +1321,9 @@
1257
1321
  ],
1258
1322
  "rows": {
1259
1323
  "ledger_account": [],
1260
- "ledger_entry": []
1324
+ "ledger_entry": [],
1325
+ "ledger_rate": [],
1326
+ "ledger_tag": []
1261
1327
  },
1262
1328
  "links": []
1263
1329
  },
@@ -1276,7 +1342,9 @@
1276
1342
  ],
1277
1343
  "rows": {
1278
1344
  "ledger_account": [],
1279
- "ledger_entry": []
1345
+ "ledger_entry": [],
1346
+ "ledger_rate": [],
1347
+ "ledger_tag": []
1280
1348
  },
1281
1349
  "links": []
1282
1350
  },
@@ -1295,7 +1363,9 @@
1295
1363
  ],
1296
1364
  "rows": {
1297
1365
  "ledger_account": [],
1298
- "ledger_entry": []
1366
+ "ledger_entry": [],
1367
+ "ledger_rate": [],
1368
+ "ledger_tag": []
1299
1369
  },
1300
1370
  "links": []
1301
1371
  },
@@ -1314,7 +1384,9 @@
1314
1384
  ],
1315
1385
  "rows": {
1316
1386
  "ledger_account": [],
1317
- "ledger_entry": []
1387
+ "ledger_entry": [],
1388
+ "ledger_rate": [],
1389
+ "ledger_tag": []
1318
1390
  },
1319
1391
  "links": []
1320
1392
  },
@@ -1333,7 +1405,9 @@
1333
1405
  ],
1334
1406
  "rows": {
1335
1407
  "ledger_account": [],
1336
- "ledger_entry": []
1408
+ "ledger_entry": [],
1409
+ "ledger_rate": [],
1410
+ "ledger_tag": []
1337
1411
  },
1338
1412
  "links": []
1339
1413
  },
@@ -1352,7 +1426,318 @@
1352
1426
  ],
1353
1427
  "rows": {
1354
1428
  "ledger_account": [],
1355
- "ledger_entry": []
1429
+ "ledger_entry": [],
1430
+ "ledger_rate": [],
1431
+ "ledger_tag": []
1432
+ },
1433
+ "links": []
1434
+ },
1435
+ {
1436
+ "title": "a bad row before a bad link is refused as a bad link, because every link is checked first",
1437
+ "batches": [
1438
+ {
1439
+ "scopes": [
1440
+ {
1441
+ "scope": "ledger:account:a",
1442
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":5.0,\"is_closed\":false,\"opened_at\":\"2026-11-01T09:00:00.000Z\",\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}},{\"link\":{\"type\":\"paid_by\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}}]"
1443
+ }
1444
+ ],
1445
+ "refused": "bad-link"
1446
+ }
1447
+ ],
1448
+ "rows": {
1449
+ "ledger_account": [],
1450
+ "ledger_entry": [],
1451
+ "ledger_rate": [],
1452
+ "ledger_tag": []
1453
+ },
1454
+ "links": []
1455
+ },
1456
+ {
1457
+ "title": "a bad link before an upsert to a table that the consumer does not own is refused as not owned, because the parse checks each table first",
1458
+ "batches": [
1459
+ {
1460
+ "scopes": [
1461
+ {
1462
+ "scope": "ledger:account:a",
1463
+ "output": "[{\"link\":{\"type\":\"paid_by\",\"from\":\"ledger:entry:e1\",\"to\":\"ledger:account:a\",\"confidence\":1,\"evidence\":\"event:ledger:e1\"}},{\"upsert\":{\"harbour_berth\":{\"id\":\"harbour:berth:1\"}}}]"
1464
+ }
1465
+ ],
1466
+ "refused": "not-owned"
1467
+ }
1468
+ ],
1469
+ "rows": {
1470
+ "ledger_account": [],
1471
+ "ledger_entry": [],
1472
+ "ledger_rate": [],
1473
+ "ledger_tag": []
1474
+ },
1475
+ "links": []
1476
+ },
1477
+ {
1478
+ "title": "a real key written as 2 and then as 2.0 is one key, so the second result changes nothing",
1479
+ "batches": [
1480
+ {
1481
+ "scopes": [
1482
+ {
1483
+ "scope": "ledger:account:a",
1484
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":2,\"label\":\"Two\"}}}]"
1485
+ }
1486
+ ],
1487
+ "changes": [
1488
+ {
1489
+ "type": "ledger_rate",
1490
+ "key": "[2]",
1491
+ "value": {
1492
+ "rate": 2,
1493
+ "label": "Two"
1494
+ },
1495
+ "headers": {
1496
+ "operation": "insert"
1497
+ }
1498
+ }
1499
+ ]
1500
+ },
1501
+ {
1502
+ "scopes": [
1503
+ {
1504
+ "scope": "ledger:account:a",
1505
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":2.0,\"label\":\"Two\"}}}]"
1506
+ }
1507
+ ],
1508
+ "changes": []
1509
+ }
1510
+ ],
1511
+ "rows": {
1512
+ "ledger_account": [],
1513
+ "ledger_entry": [],
1514
+ "ledger_rate": [
1515
+ {
1516
+ "rate": 2,
1517
+ "label": "Two"
1518
+ }
1519
+ ],
1520
+ "ledger_tag": []
1521
+ },
1522
+ "links": []
1523
+ },
1524
+ {
1525
+ "title": "a real key that another scope wrote as 2 is a scope conflict when written as 2.0",
1526
+ "batches": [
1527
+ {
1528
+ "scopes": [
1529
+ {
1530
+ "scope": "ledger:account:a",
1531
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":2,\"label\":\"Two\"}}}]"
1532
+ }
1533
+ ],
1534
+ "changes": [
1535
+ {
1536
+ "type": "ledger_rate",
1537
+ "key": "[2]",
1538
+ "value": {
1539
+ "rate": 2,
1540
+ "label": "Two"
1541
+ },
1542
+ "headers": {
1543
+ "operation": "insert"
1544
+ }
1545
+ }
1546
+ ]
1547
+ },
1548
+ {
1549
+ "scopes": [
1550
+ {
1551
+ "scope": "ledger:account:b",
1552
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":2.0,\"label\":\"Deux\"}}}]"
1553
+ }
1554
+ ],
1555
+ "refused": "scope-conflict"
1556
+ }
1557
+ ],
1558
+ "rows": {
1559
+ "ledger_account": [],
1560
+ "ledger_entry": [],
1561
+ "ledger_rate": [
1562
+ {
1563
+ "rate": 2,
1564
+ "label": "Two"
1565
+ }
1566
+ ],
1567
+ "ledger_tag": []
1568
+ },
1569
+ "links": []
1570
+ },
1571
+ {
1572
+ "title": "an integer column refuses a number past the safe integers as a bad row",
1573
+ "batches": [
1574
+ {
1575
+ "scopes": [
1576
+ {
1577
+ "scope": "ledger:account:a",
1578
+ "output": "[{\"upsert\":{\"ledger_account\":{\"id\":\"ledger:account:a\",\"name\":\"Alpha\",\"balance\":9007199254740993,\"is_closed\":false,\"opened_at\":null,\"updated_at\":\"2026-11-01T09:00:00.000Z\"}}}]"
1579
+ }
1580
+ ],
1581
+ "refused": "bad-row"
1582
+ }
1583
+ ],
1584
+ "rows": {
1585
+ "ledger_account": [],
1586
+ "ledger_entry": [],
1587
+ "ledger_rate": [],
1588
+ "ledger_tag": []
1589
+ },
1590
+ "links": []
1591
+ },
1592
+ {
1593
+ "title": "a real key keeps the double that JSON.parse gives, near 2^53 and with a long fraction, so a result without the rows deletes each",
1594
+ "batches": [
1595
+ {
1596
+ "scopes": [
1597
+ {
1598
+ "scope": "ledger:account:a",
1599
+ "output": "[{\"upsert\":{\"ledger_rate\":{\"rate\":1000000000000000.2,\"label\":\"Wide\"}}},{\"upsert\":{\"ledger_rate\":{\"rate\":9007199254740993,\"label\":\"Edge\"}}},{\"upsert\":{\"ledger_rate\":{\"rate\":3713064193725.5859,\"label\":\"Long\"}}}]"
1600
+ }
1601
+ ],
1602
+ "changes": [
1603
+ {
1604
+ "type": "ledger_rate",
1605
+ "key": "[1000000000000000.2]",
1606
+ "value": {
1607
+ "rate": 1000000000000000.2,
1608
+ "label": "Wide"
1609
+ },
1610
+ "headers": {
1611
+ "operation": "insert"
1612
+ }
1613
+ },
1614
+ {
1615
+ "type": "ledger_rate",
1616
+ "key": "[3713064193725.586]",
1617
+ "value": {
1618
+ "rate": 3713064193725.586,
1619
+ "label": "Long"
1620
+ },
1621
+ "headers": {
1622
+ "operation": "insert"
1623
+ }
1624
+ },
1625
+ {
1626
+ "type": "ledger_rate",
1627
+ "key": "[9007199254740992]",
1628
+ "value": {
1629
+ "rate": 9007199254740992,
1630
+ "label": "Edge"
1631
+ },
1632
+ "headers": {
1633
+ "operation": "insert"
1634
+ }
1635
+ }
1636
+ ]
1637
+ },
1638
+ {
1639
+ "scopes": [
1640
+ {
1641
+ "scope": "ledger:account:a",
1642
+ "output": "[]"
1643
+ }
1644
+ ],
1645
+ "changes": [
1646
+ {
1647
+ "type": "ledger_rate",
1648
+ "key": "[1000000000000000.2]",
1649
+ "old_value": {
1650
+ "rate": 1000000000000000.2,
1651
+ "label": "Wide"
1652
+ },
1653
+ "headers": {
1654
+ "operation": "delete"
1655
+ }
1656
+ },
1657
+ {
1658
+ "type": "ledger_rate",
1659
+ "key": "[3713064193725.586]",
1660
+ "old_value": {
1661
+ "rate": 3713064193725.586,
1662
+ "label": "Long"
1663
+ },
1664
+ "headers": {
1665
+ "operation": "delete"
1666
+ }
1667
+ },
1668
+ {
1669
+ "type": "ledger_rate",
1670
+ "key": "[9007199254740992]",
1671
+ "old_value": {
1672
+ "rate": 9007199254740992,
1673
+ "label": "Edge"
1674
+ },
1675
+ "headers": {
1676
+ "operation": "delete"
1677
+ }
1678
+ }
1679
+ ]
1680
+ }
1681
+ ],
1682
+ "rows": {
1683
+ "ledger_account": [],
1684
+ "ledger_entry": [],
1685
+ "ledger_rate": [],
1686
+ "ledger_tag": []
1687
+ },
1688
+ "links": []
1689
+ },
1690
+ {
1691
+ "title": "a json key written as 2.0 is stored and keyed as 2, so a result without the row deletes it",
1692
+ "batches": [
1693
+ {
1694
+ "scopes": [
1695
+ {
1696
+ "scope": "ledger:account:a",
1697
+ "output": "[{\"upsert\":{\"ledger_tag\":{\"tag\":2.0,\"label\":\"Two\"}}}]"
1698
+ }
1699
+ ],
1700
+ "changes": [
1701
+ {
1702
+ "type": "ledger_tag",
1703
+ "key": "[2]",
1704
+ "value": {
1705
+ "tag": 2,
1706
+ "label": "Two"
1707
+ },
1708
+ "headers": {
1709
+ "operation": "insert"
1710
+ }
1711
+ }
1712
+ ]
1713
+ },
1714
+ {
1715
+ "scopes": [
1716
+ {
1717
+ "scope": "ledger:account:a",
1718
+ "output": "[]"
1719
+ }
1720
+ ],
1721
+ "changes": [
1722
+ {
1723
+ "type": "ledger_tag",
1724
+ "key": "[2]",
1725
+ "old_value": {
1726
+ "tag": 2,
1727
+ "label": "Two"
1728
+ },
1729
+ "headers": {
1730
+ "operation": "delete"
1731
+ }
1732
+ }
1733
+ ]
1734
+ }
1735
+ ],
1736
+ "rows": {
1737
+ "ledger_account": [],
1738
+ "ledger_entry": [],
1739
+ "ledger_rate": [],
1740
+ "ledger_tag": []
1356
1741
  },
1357
1742
  "links": []
1358
1743
  }
@@ -11,6 +11,8 @@ import { ApplyError } from '../utils/apply-error.ts';
11
11
  import { parseAppliedFacts } from './parse-applied-facts.ts';
12
12
  import { runConsumers } from './run-consumers.ts';
13
13
  import type { AppliedFactIDs } from './run-consumers.ts';
14
+ import { storeHooks } from './store-hooks.ts';
15
+ import type { StoreHooks } from './store-hooks.ts';
14
16
  import type { TestStore } from './types.ts';
15
17
 
16
18
  interface TestStoreOptions {
@@ -48,7 +50,19 @@ export async function createTestStore(options: TestStoreOptions): Promise<TestSt
48
50
  return table;
49
51
  };
50
52
 
51
- return {
53
+ const applyEnvelopes: StoreHooks['applyEnvelopes'] = async (records) => {
54
+ const next = buildFactIndex(index, records);
55
+
56
+ applied = await db
57
+ .transaction()
58
+ .execute((transaction) =>
59
+ runConsumers(transaction, deployment.consumers, { ...next, applied }),
60
+ );
61
+
62
+ index = next.index;
63
+ };
64
+
65
+ const testStore: TestStore = {
52
66
  writeRows: async (_consumer, table, rows) => {
53
67
  for (const row of rows) {
54
68
  await writeRow(db, table, getTable(table), row);
@@ -61,15 +75,7 @@ export async function createTestStore(options: TestStoreOptions): Promise<TestSt
61
75
  throw new ApplyError(envelopes.failure);
62
76
  }
63
77
 
64
- const next = buildFactIndex(index, envelopes.value);
65
-
66
- applied = await db
67
- .transaction()
68
- .execute((transaction) =>
69
- runConsumers(transaction, deployment.consumers, { ...next, applied }),
70
- );
71
-
72
- index = next.index;
78
+ await applyEnvelopes(envelopes.value);
73
79
  },
74
80
  query: async (query, input) => {
75
81
  const parsed = query.input.parse(input);
@@ -91,4 +97,8 @@ export async function createTestStore(options: TestStoreOptions): Promise<TestSt
91
97
  reader,
92
98
  [Symbol.asyncDispose]: () => db.destroy(),
93
99
  };
100
+
101
+ storeHooks.set(testStore, { applyEnvelopes, nextOffsets: new Map() });
102
+
103
+ return testStore;
94
104
  }
@@ -0,0 +1,16 @@
1
+ import type { FactEnvelope, StreamName } from '@pepys/kit';
2
+ import type { TestStore } from './types.ts';
3
+
4
+ export interface StoreHooks {
5
+ // Runs the consumers on the records as one batch in one transaction. A refusal throws an
6
+ // ApplyError, and the batch then writes nothing.
7
+ applyEnvelopes: (
8
+ records: readonly { stream: string; envelope: FactEnvelope<unknown> }[],
9
+ ) => Promise<void>;
10
+
11
+ // the offset of the next record to apply from a fact log, by stream
12
+ nextOffsets: Map<StreamName, number>;
13
+ }
14
+
15
+ // What a test store gives the package's own functions and keeps out of its public type.
16
+ export const storeHooks = new WeakMap<TestStore, StoreHooks>();