@pepys/testing 0.1.0 → 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.1.0",
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.1.0",
23
- "@pepys/kysely-turso": "0.1.0",
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';
@@ -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>();