@pepys/testing 0.2.0 → 0.3.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 +26 -0
- package/package.json +4 -4
- package/src/fact-index/collect-scope-facts.ts +2 -2
- package/src/fact-index/types.ts +2 -2
- package/src/links/remove-link.ts +12 -2
- package/src/links/write-link.ts +12 -4
- package/src/scope-replace/pick-scope-changes.ts +3 -3
- package/src/scope-replace/read-scope-state.ts +2 -2
- package/src/scope-replace/replace-scope.ts +2 -2
- package/src/scope-replace/types.ts +3 -2
- package/src/scope-replace/write-scope-changes.ts +2 -2
- package/src/store-database/types.ts +2 -2
- package/src/test-store/run-consumers.ts +4 -4
- package/src/utils/types.ts +3 -3
package/README.md
CHANGED
|
@@ -25,6 +25,32 @@ bun add -d @pepys/testing
|
|
|
25
25
|
|
|
26
26
|
Set the compiler options from the [kit's README](../kit/README.md#configuration).
|
|
27
27
|
|
|
28
|
+
Then test a query on rows written for the test:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { expect, test } from 'bun:test';
|
|
32
|
+
import type { EntityID } from '@pepys/kit';
|
|
33
|
+
import { createTestStore } from '@pepys/testing';
|
|
34
|
+
import { toTagged } from '@zgeoff/std';
|
|
35
|
+
import { shelfBookConsumer, shelfPlugin, shelfTitleQuery } from './shelf-plugin.ts';
|
|
36
|
+
|
|
37
|
+
test('it answers a query from rows written for the test', async () => {
|
|
38
|
+
await using store = await createTestStore({ plugins: [shelfPlugin] });
|
|
39
|
+
|
|
40
|
+
await store.writeRows(shelfBookConsumer, 'shelf_book', [
|
|
41
|
+
{ id: toTagged<EntityID>('shelf:book:atlas'), title: 'The Coral Atlas' },
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
expect(await store.query(shelfTitleQuery, { id: 'shelf:book:atlas' })).toStrictEqual({
|
|
45
|
+
title: 'The Coral Atlas',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
bun test
|
|
52
|
+
```
|
|
53
|
+
|
|
28
54
|
## Features
|
|
29
55
|
|
|
30
56
|
- `createTestStore`: apply facts or write rows, then run a query, read rows and read links.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pepys/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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,9 +19,9 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@pepys/ingest": "0.
|
|
23
|
-
"@pepys/kit": "0.
|
|
24
|
-
"@pepys/kysely-turso": "0.
|
|
22
|
+
"@pepys/ingest": "0.3.1",
|
|
23
|
+
"@pepys/kit": "0.3.1",
|
|
24
|
+
"@pepys/kysely-turso": "0.3.1",
|
|
25
25
|
"@tursodatabase/database": "0.8.3",
|
|
26
26
|
"kysely": "0.29.8",
|
|
27
27
|
"tiny-invariant": "1.3.3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FactEnvelope } from '@pepys/kit';
|
|
1
|
+
import type { EntityID, FactEnvelope } from '@pepys/kit';
|
|
2
2
|
import { compareCodePoints } from '../utils/compare-code-points.ts';
|
|
3
3
|
import type { FactIndex } from './types.ts';
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ import type { FactIndex } from './types.ts';
|
|
|
6
6
|
// ID, sorted by time, then stream, then sequence number.
|
|
7
7
|
export function collectScopeFacts(
|
|
8
8
|
index: FactIndex,
|
|
9
|
-
scope:
|
|
9
|
+
scope: EntityID,
|
|
10
10
|
streams: readonly string[],
|
|
11
11
|
): FactEnvelope<unknown>[] {
|
|
12
12
|
return [...index.firstCopies.values()]
|
package/src/fact-index/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FactEnvelope } from '@pepys/kit';
|
|
1
|
+
import type { FactEnvelope, FactID } from '@pepys/kit';
|
|
2
2
|
|
|
3
3
|
// One record of the test store's log: a fact in its stream, at its sequence number.
|
|
4
4
|
export interface LoggedFact {
|
|
@@ -11,5 +11,5 @@ export interface LoggedFact {
|
|
|
11
11
|
// fact ID, and a module reads synchronously, so the test store keeps the index out of the database.
|
|
12
12
|
export interface FactIndex {
|
|
13
13
|
log: readonly LoggedFact[];
|
|
14
|
-
firstCopies: ReadonlyMap<
|
|
14
|
+
firstCopies: ReadonlyMap<FactID, LoggedFact>;
|
|
15
15
|
}
|
package/src/links/remove-link.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
|
+
import { isEntityID } from '@pepys/kit';
|
|
1
2
|
import type { StoreDatabase } from '../store-database/types.ts';
|
|
2
3
|
import type { WireLink } from '../utils/types.ts';
|
|
3
4
|
|
|
5
|
+
// No check runs on an unlink, and a stored link always joins two entity IDs, so an end that is no
|
|
6
|
+
// entity ID matches no link.
|
|
4
7
|
export async function removeLink(
|
|
5
8
|
db: StoreDatabase,
|
|
6
9
|
origin: string,
|
|
7
10
|
link: Pick<WireLink, 'type' | 'from' | 'to'>,
|
|
8
11
|
): Promise<void> {
|
|
12
|
+
const from = link.from;
|
|
13
|
+
const to = link.to;
|
|
14
|
+
|
|
15
|
+
if (!isEntityID(from) || !isEntityID(to)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
await db
|
|
10
20
|
.deleteFrom('link')
|
|
11
21
|
.where('origin', '=', origin)
|
|
12
22
|
.where('type', '=', link.type)
|
|
13
|
-
.where('from_id', '=',
|
|
14
|
-
.where('to_id', '=',
|
|
23
|
+
.where('from_id', '=', from)
|
|
24
|
+
.where('to_id', '=', to)
|
|
15
25
|
.execute();
|
|
16
26
|
}
|
package/src/links/write-link.ts
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
+
import { isEntityID } from '@pepys/kit';
|
|
2
|
+
import type { EntityID } from '@pepys/kit';
|
|
3
|
+
import invariant from 'tiny-invariant';
|
|
1
4
|
import type { StoreDatabase } from '../store-database/types.ts';
|
|
2
5
|
import type { WireLink } from '../utils/types.ts';
|
|
3
6
|
|
|
4
7
|
// Writes the link of `origin`, the consumer's name, with the scope that owns it, or null in fact
|
|
5
|
-
// mode.
|
|
8
|
+
// mode. The link has passed the link check.
|
|
6
9
|
export async function writeLink(
|
|
7
10
|
db: StoreDatabase,
|
|
8
11
|
origin: string,
|
|
9
|
-
scope:
|
|
12
|
+
scope: EntityID | null,
|
|
10
13
|
link: WireLink,
|
|
11
14
|
): Promise<void> {
|
|
15
|
+
const from = link.from;
|
|
16
|
+
const to = link.to;
|
|
17
|
+
|
|
18
|
+
invariant(isEntityID(from) && isEntityID(to), 'a checked link joins two entity IDs');
|
|
19
|
+
|
|
12
20
|
await db
|
|
13
21
|
.replaceInto('link')
|
|
14
22
|
.values({
|
|
15
23
|
type: link.type,
|
|
16
|
-
from_id:
|
|
17
|
-
to_id:
|
|
24
|
+
from_id: from,
|
|
25
|
+
to_id: to,
|
|
18
26
|
scope,
|
|
19
27
|
origin,
|
|
20
28
|
confidence: link.confidence,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Result } from '@pepys/kit';
|
|
1
|
+
import type { EntityID, Result } from '@pepys/kit';
|
|
2
2
|
import { compareCodePoints } from '../utils/compare-code-points.ts';
|
|
3
3
|
import type { ApplyFailure, ChangeEntry, WireLink } from '../utils/types.ts';
|
|
4
4
|
import { buildLinkKey } from './build-link-key.ts';
|
|
@@ -10,7 +10,7 @@ import type { KeyedRow, ScopeChanges, ScopeResult, ScopeState } from './types.ts
|
|
|
10
10
|
// entries come as rows written, links written, rows deleted, links deleted, each in key order.
|
|
11
11
|
export function pickScopeChanges(
|
|
12
12
|
consumer: string,
|
|
13
|
-
scope:
|
|
13
|
+
scope: EntityID,
|
|
14
14
|
result: ScopeResult,
|
|
15
15
|
state: ScopeState,
|
|
16
16
|
): Result<ScopeChanges, ApplyFailure> {
|
|
@@ -89,7 +89,7 @@ function compareKeys(left: readonly string[], right: readonly string[]): number
|
|
|
89
89
|
|
|
90
90
|
function findConflict(
|
|
91
91
|
consumer: string,
|
|
92
|
-
scope:
|
|
92
|
+
scope: EntityID,
|
|
93
93
|
result: ScopeResult,
|
|
94
94
|
state: ScopeState,
|
|
95
95
|
): ApplyFailure | undefined {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConsumerDeclaration } from '@pepys/kit';
|
|
1
|
+
import type { ConsumerDeclaration, EntityID } from '@pepys/kit';
|
|
2
2
|
import { readLinkRows } from '../links/read-link-rows.ts';
|
|
3
3
|
import { readTableRows } from '../rows/read-table-rows.ts';
|
|
4
4
|
import type { StoreDatabase } from '../store-database/types.ts';
|
|
@@ -10,7 +10,7 @@ import type { ScopeResult, ScopeState, StoredLink } from './types.ts';
|
|
|
10
10
|
export async function readScopeState(
|
|
11
11
|
db: StoreDatabase,
|
|
12
12
|
consumer: ConsumerDeclaration,
|
|
13
|
-
scope:
|
|
13
|
+
scope: EntityID,
|
|
14
14
|
result: ScopeResult,
|
|
15
15
|
): Promise<ScopeState> {
|
|
16
16
|
const owned = await db
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConsumerDeclaration } from '@pepys/kit';
|
|
1
|
+
import type { ConsumerDeclaration, EntityID } from '@pepys/kit';
|
|
2
2
|
import { parseOperations } from '../operations/parse-operations.ts';
|
|
3
3
|
import type { StoreDatabase } from '../store-database/types.ts';
|
|
4
4
|
import { ApplyError } from '../utils/apply-error.ts';
|
|
@@ -13,7 +13,7 @@ import { writeScopeChanges } from './write-scope-changes.ts';
|
|
|
13
13
|
export async function replaceScope(
|
|
14
14
|
db: StoreDatabase,
|
|
15
15
|
consumer: ConsumerDeclaration,
|
|
16
|
-
scope:
|
|
16
|
+
scope: EntityID,
|
|
17
17
|
output: string,
|
|
18
18
|
): Promise<readonly ChangeEntry[]> {
|
|
19
19
|
const operations = parseOperations(consumer, output);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EntityID } from '@pepys/kit';
|
|
1
2
|
import type { ChangeEntry, Row, WireLink } from '../utils/types.ts';
|
|
2
3
|
|
|
3
4
|
export interface KeyedRow {
|
|
@@ -13,7 +14,7 @@ export interface ScopeResult {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface StoredLink {
|
|
16
|
-
scope:
|
|
17
|
+
scope: EntityID | null;
|
|
17
18
|
link: WireLink;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -21,7 +22,7 @@ export interface StoredLink {
|
|
|
21
22
|
// `buildRowID`, links by the JSON text of their key.
|
|
22
23
|
export interface ScopeState {
|
|
23
24
|
// the scope of each row that a scope owns
|
|
24
|
-
rowOwners: ReadonlyMap<string,
|
|
25
|
+
rowOwners: ReadonlyMap<string, EntityID>;
|
|
25
26
|
|
|
26
27
|
// the stored row of each key that the result names or that the scope owns
|
|
27
28
|
storedRows: ReadonlyMap<string, Row>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConsumerDeclaration } from '@pepys/kit';
|
|
1
|
+
import type { ConsumerDeclaration, EntityID } from '@pepys/kit';
|
|
2
2
|
import { removeLink } from '../links/remove-link.ts';
|
|
3
3
|
import { writeLink } from '../links/write-link.ts';
|
|
4
4
|
import { removeRow } from '../rows/remove-row.ts';
|
|
@@ -9,7 +9,7 @@ import type { ScopeChanges } from './types.ts';
|
|
|
9
9
|
export async function writeScopeChanges(
|
|
10
10
|
db: StoreDatabase,
|
|
11
11
|
consumer: ConsumerDeclaration,
|
|
12
|
-
scope:
|
|
12
|
+
scope: EntityID,
|
|
13
13
|
changes: ScopeChanges,
|
|
14
14
|
): Promise<void> {
|
|
15
15
|
for (const row of changes.writtenRows) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LinkRow, SharedViews } from '@pepys/kit';
|
|
1
|
+
import type { EntityID, LinkRow, SharedViews } from '@pepys/kit';
|
|
2
2
|
import type { Kysely } from 'kysely';
|
|
3
3
|
|
|
4
4
|
// The tables that the test store keeps beside the consumers' own. The host keeps `link` and
|
|
@@ -6,7 +6,7 @@ import type { Kysely } from 'kysely';
|
|
|
6
6
|
// consumer's name.
|
|
7
7
|
export interface StoreTables extends SharedViews {
|
|
8
8
|
link: LinkRow;
|
|
9
|
-
scope_row: { consumer: string; table_name: string; key: string; scope:
|
|
9
|
+
scope_row: { consumer: string; table_name: string; key: string; scope: EntityID };
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export type StoreDatabase = Kysely<StoreTables>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseEntityID } from '@pepys/kit';
|
|
2
|
-
import type { ConsumerDeclaration } from '@pepys/kit';
|
|
2
|
+
import type { ConsumerDeclaration, FactID } from '@pepys/kit';
|
|
3
3
|
import invariant from 'tiny-invariant';
|
|
4
4
|
import { buildReadFunctions } from '../consumer-entries/build-read-functions.ts';
|
|
5
5
|
import { runEntry } from '../consumer-entries/run-entry.ts';
|
|
@@ -14,7 +14,7 @@ import { ApplyError } from '../utils/apply-error.ts';
|
|
|
14
14
|
import { compareCodePoints } from '../utils/compare-code-points.ts';
|
|
15
15
|
|
|
16
16
|
// The fact IDs each fact-mode consumer has applied, by consumer name.
|
|
17
|
-
export type AppliedFactIDs = ReadonlyMap<string, ReadonlySet<
|
|
17
|
+
export type AppliedFactIDs = ReadonlyMap<string, ReadonlySet<FactID>>;
|
|
18
18
|
|
|
19
19
|
interface ConsumerRun {
|
|
20
20
|
index: FactIndex;
|
|
@@ -57,8 +57,8 @@ async function runFactConsumer(
|
|
|
57
57
|
db: StoreDatabase,
|
|
58
58
|
consumer: ConsumerDeclaration,
|
|
59
59
|
facts: readonly LoggedFact[],
|
|
60
|
-
applied: ReadonlySet<
|
|
61
|
-
): Promise<ReadonlySet<
|
|
60
|
+
applied: ReadonlySet<FactID>,
|
|
61
|
+
): Promise<ReadonlySet<FactID>> {
|
|
62
62
|
const seen = new Set(applied);
|
|
63
63
|
|
|
64
64
|
for (const record of facts) {
|
package/src/utils/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LinkProblem } from '@pepys/kit';
|
|
1
|
+
import type { EntityID, LinkProblem } from '@pepys/kit';
|
|
2
2
|
|
|
3
3
|
// Why the test store refused an apply. The kinds from `bad-operation` on are the host's refusals
|
|
4
4
|
// of a module's output, which the scope replace case file names.
|
|
@@ -18,10 +18,10 @@ export type ApplyFailure =
|
|
|
18
18
|
| {
|
|
19
19
|
kind: 'scope-conflict';
|
|
20
20
|
consumer: string;
|
|
21
|
-
scope:
|
|
21
|
+
scope: EntityID;
|
|
22
22
|
table: string;
|
|
23
23
|
key: string;
|
|
24
|
-
owner:
|
|
24
|
+
owner: EntityID | null;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
// A row as the module writes it and as the test store reads it back: booleans as booleans and a
|