@haverstack/adapter-conformance 0.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/LICENSE +7 -0
- package/README.md +62 -0
- package/dist/blob.d.ts +30 -0
- package/dist/blob.d.ts.map +1 -0
- package/dist/blob.js +97 -0
- package/dist/blob.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/helpers.d.ts +15 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +42 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/record.d.ts +40 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +560 -0
- package/dist/record.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
|
2
|
+
|
|
3
|
+
To the extent possible under law, the Haverstack contributors have waived all
|
|
4
|
+
copyright and related or neighboring rights to Haverstack. This work is
|
|
5
|
+
published from the United States.
|
|
6
|
+
|
|
7
|
+
https://creativecommons.org/publicdomain/zero/1.0/
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @haverstack/adapter-conformance
|
|
2
|
+
|
|
3
|
+
A runnable conformance suite for `StackRecordAdapter` and `StackBlobAdapter` implementers.
|
|
4
|
+
|
|
5
|
+
`docs/spec/adapters.md` documents the adapter contract in prose. This package is the checked version: point it at your adapter and it runs the invariants — content-path filtering, `_config` protection, cursor stability, version snapshots, full-text search consistency, capability honesty — that every first-party adapter is held to.
|
|
6
|
+
|
|
7
|
+
> **Status:** Early development. APIs are unstable.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install --save-dev @haverstack/adapter-conformance vitest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { runRecordAdapterConformance } from '@haverstack/adapter-conformance';
|
|
19
|
+
import { MyRecordAdapter, MY_RECORD_CAPABILITIES } from './my-adapter.js';
|
|
20
|
+
|
|
21
|
+
runRecordAdapterConformance({
|
|
22
|
+
name: 'MyRecordAdapter',
|
|
23
|
+
capabilities: MY_RECORD_CAPABILITIES,
|
|
24
|
+
open: () => MyRecordAdapter.initialize({ path: tempDbPath() }),
|
|
25
|
+
close: (adapter) => adapter.close?.(),
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Call this at the top level of a `*.test.ts` file — it registers a normal `describe`/`it` tree via vitest, so it runs alongside your own tests. `open()` is called before every test and `close()` after, so give it a fresh, empty adapter instance each time.
|
|
30
|
+
|
|
31
|
+
`capabilities` is passed explicitly (the same object your adapter's own `capabilities` getter returns) rather than read off an opened instance, because vitest builds its test tree synchronously — before any `beforeEach` has run. Suites gated on a capability you don't declare (full-text search, content-field sort, path-reaching content filters) are skipped, never failed; every filtering test that does run includes a record that must **not** match, so declaring a capability you don't actually honor still fails loudly.
|
|
32
|
+
|
|
33
|
+
For blob adapters:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { runBlobAdapterConformance } from '@haverstack/adapter-conformance';
|
|
37
|
+
import { MyBlobAdapter } from './my-blob-adapter.js';
|
|
38
|
+
|
|
39
|
+
runBlobAdapterConformance({
|
|
40
|
+
name: 'MyBlobAdapter',
|
|
41
|
+
listFiles: true, // omit or set false if you don't implement the optional listFiles()
|
|
42
|
+
open: () => new MyBlobAdapter(tempDir()),
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Errors are asserted against `.code` (the `StackErrorCode` every `StackError` subclass carries) rather than `instanceof`, so the suite runs unmodified against an adapter reached through an RPC boundary — a Durable Object, a remote worker — where a thrown error's prototype chain doesn't survive the trip.
|
|
47
|
+
|
|
48
|
+
## What's covered
|
|
49
|
+
|
|
50
|
+
**Record adapters:** CRUD and not-found/conflict errors, the `_config` singleton's reserved-id exclusion from queries, associations (idempotent, never bump version), version snapshots and `restoreVersion` (content + `parentId` restored, associations untouched), `commitMigration`, cursor pagination (a cursor minted under one sort refused when replayed under another), content-path filtering (array spread, nested objects, `contentPresent`, null-matches-absent), sort by content field, and full-text search (including that a content patch is reflected — no stale matches under the old content).
|
|
51
|
+
|
|
52
|
+
**Blob adapters:** content-addressed `putAttachment` (fileId is the SHA-256 hex of the bytes, identical bytes dedupe), byte-exact `getAttachment`, the `not_found`/`bad_request` fileId error contract, `deleteAttachment`, and the optional `listFiles()`.
|
|
53
|
+
|
|
54
|
+
See `docs/spec/adapters.md` for the full prose contract this suite checks.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
[CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/) — public domain.
|
|
59
|
+
|
|
60
|
+
## Monorepo
|
|
61
|
+
|
|
62
|
+
Part of [haverstack/core](https://github.com/haverstack/core).
|
package/dist/blob.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blob adapter conformance suite
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The blob half of the adapter contract is small (StackBlobAdapter is three
|
|
5
|
+
* required methods plus one optional one) but has the same failure shape as
|
|
6
|
+
* the record half: content-addressing and the fileId error contract are
|
|
7
|
+
* documented in prose and easy to get subtly wrong in a from-scratch
|
|
8
|
+
* implementation — a fileId that isn't actually the SHA-256 of the bytes
|
|
9
|
+
* it names breaks any caller that compares fileIds instead of re-fetching,
|
|
10
|
+
* and a malformed fileId misreported as `not_found` instead of `bad_request`
|
|
11
|
+
* hides a caller bug behind the wrong error class.
|
|
12
|
+
*/
|
|
13
|
+
import type { StackBlobAdapter } from '@haverstack/core/adapter';
|
|
14
|
+
export interface BlobAdapterConformanceOptions {
|
|
15
|
+
/** Used in describe() titles. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Produce a fresh, empty adapter instance. Called before every test. */
|
|
18
|
+
open: () => Promise<StackBlobAdapter> | StackBlobAdapter;
|
|
19
|
+
/** Release what `open()` acquired. Called after every test. */
|
|
20
|
+
close?: (adapter: StackBlobAdapter) => Promise<void> | void;
|
|
21
|
+
/**
|
|
22
|
+
* Whether this adapter implements the optional `listFiles()`. Passed
|
|
23
|
+
* explicitly, like `capabilities` in the record suite, so the listFiles
|
|
24
|
+
* describe block can be registered or skipped at vitest's synchronous
|
|
25
|
+
* collection time rather than probed off an opened instance.
|
|
26
|
+
*/
|
|
27
|
+
listFiles?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function runBlobAdapterConformance(options: BlobAdapterConformanceOptions): void;
|
|
30
|
+
//# sourceMappingURL=blob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob.d.ts","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAYjE,MAAM,WAAW,6BAA6B;IAC5C,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IACzD,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC5D;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,6BAA6B,GAAG,IAAI,CAwFtF"}
|
package/dist/blob.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blob adapter conformance suite
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The blob half of the adapter contract is small (StackBlobAdapter is three
|
|
5
|
+
* required methods plus one optional one) but has the same failure shape as
|
|
6
|
+
* the record half: content-addressing and the fileId error contract are
|
|
7
|
+
* documented in prose and easy to get subtly wrong in a from-scratch
|
|
8
|
+
* implementation — a fileId that isn't actually the SHA-256 of the bytes
|
|
9
|
+
* it names breaks any caller that compares fileIds instead of re-fetching,
|
|
10
|
+
* and a malformed fileId misreported as `not_found` instead of `bad_request`
|
|
11
|
+
* hides a caller bug behind the wrong error class.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
14
|
+
import { expectStackErrorCode } from './errors.js';
|
|
15
|
+
const SHA256_HEX_RE = /^[0-9a-f]{64}$/;
|
|
16
|
+
async function sha256Hex(data) {
|
|
17
|
+
const digest = await crypto.subtle.digest('SHA-256', data);
|
|
18
|
+
return Array.from(new Uint8Array(digest))
|
|
19
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
20
|
+
.join('');
|
|
21
|
+
}
|
|
22
|
+
export function runBlobAdapterConformance(options) {
|
|
23
|
+
const { name, open, close, listFiles = false } = options;
|
|
24
|
+
describe(`blob adapter conformance: ${name}`, () => {
|
|
25
|
+
let adapter;
|
|
26
|
+
// `open()` can reject; `adapter` is then unset (or still holds the
|
|
27
|
+
// previous test's, already closed). Closing either way throws over
|
|
28
|
+
// the top of the real initialization error and hides it.
|
|
29
|
+
let opened = false;
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
opened = false;
|
|
32
|
+
adapter = await open();
|
|
33
|
+
opened = true;
|
|
34
|
+
});
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
if (!opened)
|
|
37
|
+
return;
|
|
38
|
+
opened = false;
|
|
39
|
+
await close?.(adapter);
|
|
40
|
+
});
|
|
41
|
+
test('putAttachment returns the SHA-256 hex of the stored bytes', async () => {
|
|
42
|
+
const data = new TextEncoder().encode('hello attachment');
|
|
43
|
+
const fileId = await adapter.putAttachment(data);
|
|
44
|
+
expect(fileId).toMatch(SHA256_HEX_RE);
|
|
45
|
+
expect(fileId).toBe(await sha256Hex(data));
|
|
46
|
+
});
|
|
47
|
+
test('getAttachment returns exactly the bytes that were stored', async () => {
|
|
48
|
+
const data = new TextEncoder().encode('roundtrip me');
|
|
49
|
+
const fileId = await adapter.putAttachment(data);
|
|
50
|
+
const retrieved = await adapter.getAttachment(fileId);
|
|
51
|
+
expect(new Uint8Array(retrieved)).toEqual(data);
|
|
52
|
+
});
|
|
53
|
+
test('putAttachment is content-addressed — identical bytes yield the same fileId', async () => {
|
|
54
|
+
const data = new TextEncoder().encode('same content twice');
|
|
55
|
+
const first = await adapter.putAttachment(data);
|
|
56
|
+
const second = await adapter.putAttachment(data);
|
|
57
|
+
expect(first).toBe(second);
|
|
58
|
+
});
|
|
59
|
+
test('getAttachment reports not_found for a well-formed but unknown fileId', async () => {
|
|
60
|
+
const neverStored = 'a'.repeat(64);
|
|
61
|
+
await expectStackErrorCode(adapter.getAttachment(neverStored), 'not_found');
|
|
62
|
+
});
|
|
63
|
+
test('getAttachment reports bad_request for a malformed fileId', async () => {
|
|
64
|
+
await expectStackErrorCode(adapter.getAttachment('not-a-sha256-hash'), 'bad_request');
|
|
65
|
+
});
|
|
66
|
+
test('deleteAttachment removes the blob — a later getAttachment reports not_found', async () => {
|
|
67
|
+
const fileId = await adapter.putAttachment(new TextEncoder().encode('temporary'));
|
|
68
|
+
await adapter.deleteAttachment(fileId);
|
|
69
|
+
await expectStackErrorCode(adapter.getAttachment(fileId), 'not_found');
|
|
70
|
+
});
|
|
71
|
+
test('deleteAttachment is non-fatal for a fileId that was never stored', async () => {
|
|
72
|
+
await expect(adapter.deleteAttachment('b'.repeat(64))).resolves.toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
if (listFiles) {
|
|
75
|
+
describe('listFiles', () => {
|
|
76
|
+
test('returns an empty array for an empty store', async () => {
|
|
77
|
+
expect(await adapter.listFiles()).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
test('lists every stored blob with its fileId, size and modifiedAt', async () => {
|
|
80
|
+
const data = new TextEncoder().encode('a stored blob');
|
|
81
|
+
const fileId = await adapter.putAttachment(data);
|
|
82
|
+
const files = await adapter.listFiles();
|
|
83
|
+
const entry = files.find((f) => f.fileId === fileId);
|
|
84
|
+
expect(entry?.size).toBe(data.byteLength);
|
|
85
|
+
expect(entry?.modifiedAt).toBeInstanceOf(Date);
|
|
86
|
+
});
|
|
87
|
+
test('a deleted blob no longer appears', async () => {
|
|
88
|
+
const fileId = await adapter.putAttachment(new TextEncoder().encode('gone soon'));
|
|
89
|
+
await adapter.deleteAttachment(fileId);
|
|
90
|
+
const files = await adapter.listFiles();
|
|
91
|
+
expect(files.some((f) => f.fileId === fileId)).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=blob.js.map
|
package/dist/blob.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob.js","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error assertions
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Checked against `.code` — the wire discriminator every StackError
|
|
5
|
+
* subclass carries — rather than `instanceof`. An RPC-backed adapter (a
|
|
6
|
+
* Durable Object, a remote server process) reconstructs a thrown error on
|
|
7
|
+
* the far side of that boundary without its original prototype chain, so
|
|
8
|
+
* `instanceof StackConflictError` can fail even when the adapter behaved
|
|
9
|
+
* correctly; `.code` is what survives. Using it here is what lets this
|
|
10
|
+
* suite run unmodified against an adapter that lives behind such a
|
|
11
|
+
* boundary, not just one that shares a process with the test runner.
|
|
12
|
+
*/
|
|
13
|
+
import type { StackErrorCode } from '@haverstack/core';
|
|
14
|
+
/** Assert `promise` rejects with a StackError carrying this `code`. */
|
|
15
|
+
export declare function expectStackErrorCode(promise: Promise<unknown>, code: StackErrorCode): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,uEAAuE;AACvE,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EACzB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,IAAI,CAAC,CAWf"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error assertions
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Checked against `.code` — the wire discriminator every StackError
|
|
5
|
+
* subclass carries — rather than `instanceof`. An RPC-backed adapter (a
|
|
6
|
+
* Durable Object, a remote server process) reconstructs a thrown error on
|
|
7
|
+
* the far side of that boundary without its original prototype chain, so
|
|
8
|
+
* `instanceof StackConflictError` can fail even when the adapter behaved
|
|
9
|
+
* correctly; `.code` is what survives. Using it here is what lets this
|
|
10
|
+
* suite run unmodified against an adapter that lives behind such a
|
|
11
|
+
* boundary, not just one that shares a process with the test runner.
|
|
12
|
+
*/
|
|
13
|
+
import { expect } from 'vitest';
|
|
14
|
+
/** Assert `promise` rejects with a StackError carrying this `code`. */
|
|
15
|
+
export async function expectStackErrorCode(promise, code) {
|
|
16
|
+
let caught;
|
|
17
|
+
try {
|
|
18
|
+
await promise;
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
caught = err;
|
|
22
|
+
}
|
|
23
|
+
if (caught === undefined) {
|
|
24
|
+
expect.fail(`expected the call to reject with a StackError coded "${code}", but it resolved`);
|
|
25
|
+
}
|
|
26
|
+
expect(caught.code).toBe(code);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAyB,EACzB,IAAoB;IAEpB,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,OAAO,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,CAAC;IACf,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,wDAAwD,IAAI,oBAAoB,CAAC,CAAC;IAChG,CAAC;IACD,MAAM,CAAE,MAA6B,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture builders shared by the record and blob suites. Deliberately
|
|
3
|
+
* schema-light: content-path filtering and sorting are exercised against
|
|
4
|
+
* raw `content` shapes without a registered StackType wherever a real
|
|
5
|
+
* adapter's own behavior doesn't require one — see the comments at each
|
|
6
|
+
* call site in record.ts for the one exception (content-field sort, which
|
|
7
|
+
* MemoryAdapter resolves through a registered type's schema).
|
|
8
|
+
*/
|
|
9
|
+
import type { StackRecord, StackType } from '@haverstack/core';
|
|
10
|
+
/** A fresh id on every call, unique within a test run. */
|
|
11
|
+
export declare function uniqueId(prefix?: string): string;
|
|
12
|
+
export declare const CONFORMANCE_TYPE_ID = "org.haverstack.conformance/note@1";
|
|
13
|
+
export declare function conformanceType(overrides?: Partial<StackType>): StackType;
|
|
14
|
+
export declare function makeRecord(overrides?: Partial<StackRecord>): StackRecord;
|
|
15
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI/D,0DAA0D;AAC1D,wBAAgB,QAAQ,CAAC,MAAM,SAAQ,GAAG,MAAM,CAG/C;AAED,eAAO,MAAM,mBAAmB,sCAAsC,CAAC;AAEvE,wBAAgB,eAAe,CAAC,SAAS,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,SAAS,CAc7E;AAED,wBAAgB,UAAU,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAU5E"}
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture builders shared by the record and blob suites. Deliberately
|
|
3
|
+
* schema-light: content-path filtering and sorting are exercised against
|
|
4
|
+
* raw `content` shapes without a registered StackType wherever a real
|
|
5
|
+
* adapter's own behavior doesn't require one — see the comments at each
|
|
6
|
+
* call site in record.ts for the one exception (content-field sort, which
|
|
7
|
+
* MemoryAdapter resolves through a registered type's schema).
|
|
8
|
+
*/
|
|
9
|
+
let counter = 0;
|
|
10
|
+
/** A fresh id on every call, unique within a test run. */
|
|
11
|
+
export function uniqueId(prefix = 'rec') {
|
|
12
|
+
counter += 1;
|
|
13
|
+
return `${prefix}-${Date.now().toString(36)}-${counter.toString(36)}`;
|
|
14
|
+
}
|
|
15
|
+
export const CONFORMANCE_TYPE_ID = 'org.haverstack.conformance/note@1';
|
|
16
|
+
export function conformanceType(overrides = {}) {
|
|
17
|
+
return {
|
|
18
|
+
id: CONFORMANCE_TYPE_ID,
|
|
19
|
+
baseId: 'org.haverstack.conformance/note',
|
|
20
|
+
version: 1,
|
|
21
|
+
name: 'Conformance Note',
|
|
22
|
+
schema: {
|
|
23
|
+
title: { kind: 'string' },
|
|
24
|
+
priority: { kind: 'number' },
|
|
25
|
+
},
|
|
26
|
+
schemaHash: 'conformance-note-v1',
|
|
27
|
+
createdAt: new Date(),
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function makeRecord(overrides = {}) {
|
|
32
|
+
return {
|
|
33
|
+
id: uniqueId(),
|
|
34
|
+
typeId: CONFORMANCE_TYPE_ID,
|
|
35
|
+
createdAt: new Date(),
|
|
36
|
+
updatedAt: new Date(),
|
|
37
|
+
content: { title: 'Untitled' },
|
|
38
|
+
version: 1,
|
|
39
|
+
...overrides,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,IAAI,OAAO,GAAG,CAAC,CAAC;AAEhB,0DAA0D;AAC1D,MAAM,UAAU,QAAQ,CAAC,MAAM,GAAG,KAAK;IACrC,OAAO,IAAI,CAAC,CAAC;IACb,OAAO,GAAG,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,mCAAmC,CAAC;AAEvE,MAAM,UAAU,eAAe,CAAC,YAAgC,EAAE;IAChE,OAAO;QACL,EAAE,EAAE,mBAAmB;QACvB,MAAM,EAAE,iCAAiC;QACzC,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE;YACN,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACzB,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC7B;QACD,UAAU,EAAE,qBAAqB;QACjC,SAAS,EAAE,IAAI,IAAI,EAAE;QACrB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,YAAkC,EAAE;IAC7D,OAAO;QACL,EAAE,EAAE,QAAQ,EAAE;QACd,MAAM,EAAE,mBAAmB;QAC3B,SAAS,EAAE,IAAI,IAAI,EAAE;QACrB,SAAS,EAAE,IAAI,IAAI,EAAE;QACrB,OAAO,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE;QAC9B,OAAO,EAAE,CAAC;QACV,GAAG,SAAS;KACb,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { runRecordAdapterConformance } from './record.js';
|
|
2
|
+
export type { RecordAdapterConformanceOptions } from './record.js';
|
|
3
|
+
export { runBlobAdapterConformance } from './blob.js';
|
|
4
|
+
export type { BlobAdapterConformanceOptions } from './blob.js';
|
|
5
|
+
export { expectStackErrorCode } from './errors.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AACtD,YAAY,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record adapter conformance suite
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The middle ground docs/spec/adapters.md describes in prose and, until
|
|
5
|
+
* now, nothing runnable checked a third-party StackRecordAdapter against:
|
|
6
|
+
* an adapter author calls runRecordAdapterConformance() with a way to open
|
|
7
|
+
* their adapter and the capabilities it declares, and gets back the
|
|
8
|
+
* invariants every first-party adapter is held to — FTS5-style search
|
|
9
|
+
* consistency, content-path semantics, version snapshots, `_config`
|
|
10
|
+
* protection, cursor stability, and capability honesty.
|
|
11
|
+
*
|
|
12
|
+
* Capability-gated: `capabilities` is read once, before any test is
|
|
13
|
+
* registered, because vitest builds its test tree synchronously at
|
|
14
|
+
* collection time — long before a `beforeEach` opening a fresh adapter
|
|
15
|
+
* instance has ever run. An adapter that doesn't declare a capability is
|
|
16
|
+
* never penalized for lacking it; declaring one wrong (claiming
|
|
17
|
+
* `filter.content: 'path'` while only matching whole field names, say)
|
|
18
|
+
* is caught because every filtering test here includes a record that
|
|
19
|
+
* must NOT match, not just one that should — the same "unfiltered
|
|
20
|
+
* superset presented as a filtered result" failure
|
|
21
|
+
* assertQueryCapabilities() exists to prevent.
|
|
22
|
+
*/
|
|
23
|
+
import type { StackRecordAdapter, AdapterCapabilities } from '@haverstack/core/adapter';
|
|
24
|
+
export interface RecordAdapterConformanceOptions {
|
|
25
|
+
/** Used in describe() titles, so pick something that reads well there. */
|
|
26
|
+
name: string;
|
|
27
|
+
/** Produce a fresh, empty adapter instance. Called before every test. */
|
|
28
|
+
open: () => Promise<StackRecordAdapter> | StackRecordAdapter;
|
|
29
|
+
/** Release what `open()` acquired. Called after every test. */
|
|
30
|
+
close?: (adapter: StackRecordAdapter) => Promise<void> | void;
|
|
31
|
+
/**
|
|
32
|
+
* The capabilities this adapter declares — the same object its own
|
|
33
|
+
* `capabilities` getter returns. Passed explicitly rather than read off
|
|
34
|
+
* an opened instance so suite registration stays synchronous; see the
|
|
35
|
+
* module doc above.
|
|
36
|
+
*/
|
|
37
|
+
capabilities: AdapterCapabilities;
|
|
38
|
+
}
|
|
39
|
+
export declare function runRecordAdapterConformance(options: RecordAdapterConformanceOptions): void;
|
|
40
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAIxF,MAAM,WAAW,+BAA+B;IAC9C,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IAC7D,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9D;;;;;OAKG;IACH,YAAY,EAAE,mBAAmB,CAAC;CACnC;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,+BAA+B,GAAG,IAAI,CAopB1F"}
|
package/dist/record.js
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record adapter conformance suite
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The middle ground docs/spec/adapters.md describes in prose and, until
|
|
5
|
+
* now, nothing runnable checked a third-party StackRecordAdapter against:
|
|
6
|
+
* an adapter author calls runRecordAdapterConformance() with a way to open
|
|
7
|
+
* their adapter and the capabilities it declares, and gets back the
|
|
8
|
+
* invariants every first-party adapter is held to — FTS5-style search
|
|
9
|
+
* consistency, content-path semantics, version snapshots, `_config`
|
|
10
|
+
* protection, cursor stability, and capability honesty.
|
|
11
|
+
*
|
|
12
|
+
* Capability-gated: `capabilities` is read once, before any test is
|
|
13
|
+
* registered, because vitest builds its test tree synchronously at
|
|
14
|
+
* collection time — long before a `beforeEach` opening a fresh adapter
|
|
15
|
+
* instance has ever run. An adapter that doesn't declare a capability is
|
|
16
|
+
* never penalized for lacking it; declaring one wrong (claiming
|
|
17
|
+
* `filter.content: 'path'` while only matching whole field names, say)
|
|
18
|
+
* is caught because every filtering test here includes a record that
|
|
19
|
+
* must NOT match, not just one that should — the same "unfiltered
|
|
20
|
+
* superset presented as a filtered result" failure
|
|
21
|
+
* assertQueryCapabilities() exists to prevent.
|
|
22
|
+
*/
|
|
23
|
+
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
24
|
+
import { SYSTEM_TYPES } from '@haverstack/core';
|
|
25
|
+
import { expectStackErrorCode } from './errors.js';
|
|
26
|
+
import { CONFORMANCE_TYPE_ID, conformanceType, makeRecord, uniqueId } from './helpers.js';
|
|
27
|
+
export function runRecordAdapterConformance(options) {
|
|
28
|
+
const { name, open, close, capabilities } = options;
|
|
29
|
+
describe(`record adapter conformance: ${name}`, () => {
|
|
30
|
+
let adapter;
|
|
31
|
+
// `open()` can reject; `adapter` is then unset (or still holds the
|
|
32
|
+
// previous test's, already closed). Closing either way throws over
|
|
33
|
+
// the top of the real initialization error and hides it.
|
|
34
|
+
let opened = false;
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
opened = false;
|
|
37
|
+
adapter = await open();
|
|
38
|
+
opened = true;
|
|
39
|
+
});
|
|
40
|
+
afterEach(async () => {
|
|
41
|
+
if (!opened)
|
|
42
|
+
return;
|
|
43
|
+
opened = false;
|
|
44
|
+
await close?.(adapter);
|
|
45
|
+
});
|
|
46
|
+
// -----------------------------------------------------------------
|
|
47
|
+
// Capabilities
|
|
48
|
+
// -----------------------------------------------------------------
|
|
49
|
+
describe('capabilities', () => {
|
|
50
|
+
test('declares a well-formed AdapterCapabilities object', () => {
|
|
51
|
+
expect(['none', 'field', 'path']).toContain(capabilities.filter.content);
|
|
52
|
+
expect(typeof capabilities.filter.contentPresent).toBe('boolean');
|
|
53
|
+
expect(typeof capabilities.filter.search).toBe('boolean');
|
|
54
|
+
expect(Array.isArray(capabilities.sort.fields)).toBe(true);
|
|
55
|
+
expect(typeof capabilities.sort.contentField).toBe('boolean');
|
|
56
|
+
});
|
|
57
|
+
test('filter.contentPresent is false whenever filter.content is "none"', () => {
|
|
58
|
+
// contentPresent asks whether a value is there at all — meaningless
|
|
59
|
+
// to promise from an adapter that reaches no content to look at.
|
|
60
|
+
if (capabilities.filter.content === 'none') {
|
|
61
|
+
expect(capabilities.filter.contentPresent).toBe(false);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
// -----------------------------------------------------------------
|
|
66
|
+
// Types
|
|
67
|
+
// -----------------------------------------------------------------
|
|
68
|
+
describe('types', () => {
|
|
69
|
+
test('saveType and getType roundtrip', async () => {
|
|
70
|
+
const type = conformanceType();
|
|
71
|
+
await adapter.saveType(type);
|
|
72
|
+
const stored = await adapter.getType(type.id);
|
|
73
|
+
expect(stored?.id).toBe(type.id);
|
|
74
|
+
expect(stored?.schema).toEqual(type.schema);
|
|
75
|
+
});
|
|
76
|
+
test('getType returns null for an unknown id', async () => {
|
|
77
|
+
expect(await adapter.getType('org.haverstack.conformance/nope@1')).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
test('listTypes returns every saved type', async () => {
|
|
80
|
+
await adapter.saveType(conformanceType());
|
|
81
|
+
await adapter.saveType(conformanceType({ id: 'org.haverstack.conformance/other@1' }));
|
|
82
|
+
const types = await adapter.listTypes();
|
|
83
|
+
expect(types.map((t) => t.id).sort()).toEqual(['org.haverstack.conformance/other@1', CONFORMANCE_TYPE_ID].sort());
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
// -----------------------------------------------------------------
|
|
87
|
+
// CRUD
|
|
88
|
+
// -----------------------------------------------------------------
|
|
89
|
+
describe('records — CRUD', () => {
|
|
90
|
+
test('createRecord and getRecord roundtrip', async () => {
|
|
91
|
+
const record = makeRecord({ content: { title: 'Hello' } });
|
|
92
|
+
await adapter.createRecord(record);
|
|
93
|
+
const stored = await adapter.getRecord(record.id);
|
|
94
|
+
expect(stored?.id).toBe(record.id);
|
|
95
|
+
expect(stored?.content).toEqual({ title: 'Hello' });
|
|
96
|
+
});
|
|
97
|
+
test('getRecord returns null for an unknown id', async () => {
|
|
98
|
+
expect(await adapter.getRecord(uniqueId('missing'))).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
test('createRecord throws a conflict for a duplicate id, never a silent overwrite', async () => {
|
|
101
|
+
const record = makeRecord();
|
|
102
|
+
await adapter.createRecord(record);
|
|
103
|
+
await expectStackErrorCode(adapter.createRecord(makeRecord({ id: record.id })), 'conflict');
|
|
104
|
+
expect((await adapter.getRecord(record.id))?.content).toEqual(record.content);
|
|
105
|
+
});
|
|
106
|
+
test('mutateRecord merges the content patch at the top level and bumps version', async () => {
|
|
107
|
+
const record = makeRecord({ content: { title: 'Before', priority: 1 } });
|
|
108
|
+
await adapter.createRecord(record);
|
|
109
|
+
const updated = await adapter.mutateRecord(record.id, {
|
|
110
|
+
contentPatch: { title: 'After' },
|
|
111
|
+
});
|
|
112
|
+
expect(updated.content).toEqual({ title: 'After', priority: 1 });
|
|
113
|
+
expect(updated.version).toBe(record.version + 1);
|
|
114
|
+
});
|
|
115
|
+
test('soft deleteRecord sets deletedAt; undeleteRecord clears it', async () => {
|
|
116
|
+
const record = makeRecord();
|
|
117
|
+
await adapter.createRecord(record);
|
|
118
|
+
const deleted = await adapter.deleteRecord(record.id);
|
|
119
|
+
expect(deleted?.deletedAt).toBeInstanceOf(Date);
|
|
120
|
+
const undeleted = await adapter.undeleteRecord(record.id);
|
|
121
|
+
expect(undeleted.deletedAt).toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
test('hard deleteRecord removes the record and its version history', async () => {
|
|
124
|
+
const record = makeRecord();
|
|
125
|
+
await adapter.createRecord(record);
|
|
126
|
+
await adapter.saveVersion(record.id, {
|
|
127
|
+
version: record.version,
|
|
128
|
+
typeId: record.typeId,
|
|
129
|
+
content: record.content,
|
|
130
|
+
updatedAt: record.updatedAt,
|
|
131
|
+
});
|
|
132
|
+
await adapter.deleteRecord(record.id, { hard: true });
|
|
133
|
+
expect(await adapter.getRecord(record.id)).toBeNull();
|
|
134
|
+
expect(await adapter.getVersions(record.id)).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
// -----------------------------------------------------------------
|
|
138
|
+
// The `_config` singleton — reserved, never enumerable
|
|
139
|
+
// -----------------------------------------------------------------
|
|
140
|
+
describe('the `_config` record', () => {
|
|
141
|
+
test('queryRecords never returns a record whose id is the reserved `_config` id', async () => {
|
|
142
|
+
// Every first-party adapter creates this singleton during its own
|
|
143
|
+
// initialization, outside this interface — nothing here writes it.
|
|
144
|
+
// The invariant holds either way: if the adapter under test has one,
|
|
145
|
+
// a generic query must still never surface it.
|
|
146
|
+
const { records } = await adapter.queryRecords({});
|
|
147
|
+
expect(records.some((r) => r.id === SYSTEM_TYPES.CONFIG)).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
// -----------------------------------------------------------------
|
|
151
|
+
// Associations
|
|
152
|
+
// -----------------------------------------------------------------
|
|
153
|
+
describe('associations', () => {
|
|
154
|
+
test('associate adds a tag; dissociate removes it; neither bumps version', async () => {
|
|
155
|
+
const record = makeRecord();
|
|
156
|
+
await adapter.createRecord(record);
|
|
157
|
+
const tagged = await adapter.associate(record.id, { kind: 'tag', label: 'important' });
|
|
158
|
+
expect(tagged.associations).toContainEqual({ kind: 'tag', label: 'important' });
|
|
159
|
+
expect(tagged.version).toBe(record.version);
|
|
160
|
+
const untagged = await adapter.dissociate(record.id, {
|
|
161
|
+
kind: 'tag',
|
|
162
|
+
label: 'important',
|
|
163
|
+
});
|
|
164
|
+
expect(untagged.associations ?? []).not.toContainEqual({
|
|
165
|
+
kind: 'tag',
|
|
166
|
+
label: 'important',
|
|
167
|
+
});
|
|
168
|
+
expect(untagged.version).toBe(record.version);
|
|
169
|
+
});
|
|
170
|
+
test('associate is idempotent — a duplicate does not create a second entry', async () => {
|
|
171
|
+
const record = makeRecord();
|
|
172
|
+
await adapter.createRecord(record);
|
|
173
|
+
await adapter.associate(record.id, { kind: 'tag', label: 'dup' });
|
|
174
|
+
const twice = await adapter.associate(record.id, { kind: 'tag', label: 'dup' });
|
|
175
|
+
expect(twice.associations?.filter((a) => a.kind === 'tag' && a.label === 'dup')).toHaveLength(1);
|
|
176
|
+
});
|
|
177
|
+
test('associate on a nonexistent record reports not_found rather than creating an orphan', async () => {
|
|
178
|
+
await expectStackErrorCode(adapter.associate(uniqueId('missing'), { kind: 'tag', label: 'x' }), 'not_found');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
// -----------------------------------------------------------------
|
|
182
|
+
// Versions — snapshots and restore
|
|
183
|
+
// -----------------------------------------------------------------
|
|
184
|
+
describe('versions', () => {
|
|
185
|
+
test('saveVersion and getVersion roundtrip', async () => {
|
|
186
|
+
const record = makeRecord();
|
|
187
|
+
await adapter.createRecord(record);
|
|
188
|
+
await adapter.saveVersion(record.id, {
|
|
189
|
+
version: record.version,
|
|
190
|
+
typeId: record.typeId,
|
|
191
|
+
content: record.content,
|
|
192
|
+
updatedAt: record.updatedAt,
|
|
193
|
+
});
|
|
194
|
+
const version = await adapter.getVersion(record.id, record.version);
|
|
195
|
+
expect(version?.content).toEqual(record.content);
|
|
196
|
+
});
|
|
197
|
+
test('restoreVersion restores content and parentId, bumps version, and never touches associations', async () => {
|
|
198
|
+
const record = makeRecord({ content: { title: 'v1' } });
|
|
199
|
+
await adapter.createRecord(record);
|
|
200
|
+
await adapter.saveVersion(record.id, {
|
|
201
|
+
version: record.version,
|
|
202
|
+
typeId: record.typeId,
|
|
203
|
+
content: record.content,
|
|
204
|
+
updatedAt: record.updatedAt,
|
|
205
|
+
});
|
|
206
|
+
await adapter.mutateRecord(record.id, { contentPatch: { title: 'v2' } });
|
|
207
|
+
await adapter.associate(record.id, { kind: 'tag', label: 'keep-me' });
|
|
208
|
+
const restored = await adapter.restoreVersion(record.id, record.version);
|
|
209
|
+
expect(restored.content).toEqual({ title: 'v1' });
|
|
210
|
+
expect(restored.version).toBe(record.version + 2); // patch, then restore
|
|
211
|
+
expect(restored.associations).toContainEqual({ kind: 'tag', label: 'keep-me' });
|
|
212
|
+
});
|
|
213
|
+
test('restoreVersion throws not_found for an unknown version', async () => {
|
|
214
|
+
const record = makeRecord();
|
|
215
|
+
await adapter.createRecord(record);
|
|
216
|
+
await expectStackErrorCode(adapter.restoreVersion(record.id, 999), 'not_found');
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
// -----------------------------------------------------------------
|
|
220
|
+
// Change journal — the second durable tier
|
|
221
|
+
// -----------------------------------------------------------------
|
|
222
|
+
describe('change journal', () => {
|
|
223
|
+
test('getJournal is implemented, and an empty log means nothing changed', async () => {
|
|
224
|
+
// Not an optional method: "nothing changed" and "this adapter does
|
|
225
|
+
// not remember" are different answers, and an empty log has to mean
|
|
226
|
+
// the first unconditionally. See docs/spec/journal.md § Reading it.
|
|
227
|
+
expect(typeof adapter.getJournal).toBe('function');
|
|
228
|
+
const record = makeRecord();
|
|
229
|
+
await adapter.createRecord(record);
|
|
230
|
+
expect(await adapter.getJournal(record.id)).toEqual([]);
|
|
231
|
+
});
|
|
232
|
+
test('an entry is stamped from the row the write produced, not from the caller', async () => {
|
|
233
|
+
const record = makeRecord({ content: { title: 'v1' } });
|
|
234
|
+
await adapter.createRecord(record, {
|
|
235
|
+
journal: { ops: ['create'], kind: 'created' },
|
|
236
|
+
});
|
|
237
|
+
await adapter.mutateRecord(record.id, { contentPatch: { title: 'v2' } }, { bumpsVersion: true, journal: { ops: ['patch'], kind: 'changed' } });
|
|
238
|
+
const log = await adapter.getJournal(record.id);
|
|
239
|
+
expect(log).toHaveLength(2);
|
|
240
|
+
// version, typeId and parentId are the adapter's to stamp: Stack
|
|
241
|
+
// supplies only the half the record cannot report afterwards.
|
|
242
|
+
expect(log[0].version).toBe(record.version);
|
|
243
|
+
expect(log[1].version).toBe(record.version + 1);
|
|
244
|
+
expect(log[0].typeId).toBe(record.typeId);
|
|
245
|
+
expect(log[0].at).toBeInstanceOf(Date);
|
|
246
|
+
});
|
|
247
|
+
test('seq is dense from 1, ascending, and counted per record', async () => {
|
|
248
|
+
const first = makeRecord();
|
|
249
|
+
const second = makeRecord();
|
|
250
|
+
await adapter.createRecord(first, { journal: { ops: ['create'], kind: 'created' } });
|
|
251
|
+
await adapter.createRecord(second, { journal: { ops: ['create'], kind: 'created' } });
|
|
252
|
+
await adapter.associate(first.id, { kind: 'tag', label: 'a' }, { journal: { ops: ['associate'], kind: 'changed' } });
|
|
253
|
+
await adapter.associate(first.id, { kind: 'tag', label: 'b' }, { journal: { ops: ['associate'], kind: 'changed' } });
|
|
254
|
+
expect((await adapter.getJournal(first.id)).map((e) => e.seq)).toEqual([1, 2, 3]);
|
|
255
|
+
// Per record, so a busy neighbour never advances this one's log.
|
|
256
|
+
expect((await adapter.getJournal(second.id)).map((e) => e.seq)).toEqual([1]);
|
|
257
|
+
});
|
|
258
|
+
test('an association change journals a version that stands still', async () => {
|
|
259
|
+
const record = makeRecord();
|
|
260
|
+
await adapter.createRecord(record);
|
|
261
|
+
await adapter.associate(record.id, { kind: 'tag', label: 'starred' }, {
|
|
262
|
+
journal: {
|
|
263
|
+
ops: ['associate'],
|
|
264
|
+
kind: 'changed',
|
|
265
|
+
associationsAdded: [{ kind: 'tag', label: 'starred' }],
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
await adapter.dissociate(record.id, { kind: 'tag', label: 'starred' }, {
|
|
269
|
+
journal: {
|
|
270
|
+
ops: ['dissociate'],
|
|
271
|
+
kind: 'changed',
|
|
272
|
+
associationsRemoved: [{ kind: 'tag', label: 'starred' }],
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
const log = await adapter.getJournal(record.id);
|
|
276
|
+
expect(log.map((e) => e.version)).toEqual([record.version, record.version]);
|
|
277
|
+
expect(log[0].associationsAdded).toEqual([{ kind: 'tag', label: 'starred' }]);
|
|
278
|
+
expect(log[1].associationsRemoved).toEqual([{ kind: 'tag', label: 'starred' }]);
|
|
279
|
+
});
|
|
280
|
+
test('associationsReplaced survives the round trip', async () => {
|
|
281
|
+
// The one field with no counterpart on the change feed, and the
|
|
282
|
+
// whole reason this tier exists: nothing else retains the
|
|
283
|
+
// attachmentRecordId a re-point discarded.
|
|
284
|
+
const record = makeRecord();
|
|
285
|
+
await adapter.createRecord(record);
|
|
286
|
+
const replaced = {
|
|
287
|
+
kind: 'attachment',
|
|
288
|
+
label: 'avatar',
|
|
289
|
+
fileId: 'a'.repeat(64),
|
|
290
|
+
attachmentRecordId: uniqueId('old'),
|
|
291
|
+
};
|
|
292
|
+
await adapter.associate(record.id, { kind: 'attachment', label: 'avatar', fileId: 'a'.repeat(64) }, {
|
|
293
|
+
journal: {
|
|
294
|
+
ops: ['associate'],
|
|
295
|
+
kind: 'changed',
|
|
296
|
+
associationsAdded: [{ kind: 'attachment', label: 'avatar', fileId: 'a'.repeat(64) }],
|
|
297
|
+
associationsReplaced: [replaced],
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
const [entry] = await adapter.getJournal(record.id);
|
|
301
|
+
expect(entry.associationsReplaced).toEqual([replaced]);
|
|
302
|
+
});
|
|
303
|
+
test('previousParentId tells "did not move" apart from "moved off the root"', async () => {
|
|
304
|
+
// The one field on any entry where null is a value rather than an
|
|
305
|
+
// input spelling. Collapsing both to absent loses which happened.
|
|
306
|
+
const parent = makeRecord();
|
|
307
|
+
await adapter.createRecord(parent);
|
|
308
|
+
const record = makeRecord();
|
|
309
|
+
await adapter.createRecord(record, { journal: { ops: ['create'], kind: 'created' } });
|
|
310
|
+
await adapter.mutateRecord(record.id, { parentId: parent.id }, {
|
|
311
|
+
bumpsVersion: true,
|
|
312
|
+
journal: { ops: ['reparent'], kind: 'changed', previousParentId: null },
|
|
313
|
+
});
|
|
314
|
+
const log = await adapter.getJournal(record.id);
|
|
315
|
+
expect(log[0].previousParentId).toBeUndefined();
|
|
316
|
+
expect(log[1].previousParentId).toBeNull();
|
|
317
|
+
expect(log[1].parentId).toBe(parent.id);
|
|
318
|
+
});
|
|
319
|
+
test('sinceSeq is exclusive, limit bounds, and omitting both reads the whole log', async () => {
|
|
320
|
+
const record = makeRecord();
|
|
321
|
+
await adapter.createRecord(record, { journal: { ops: ['create'], kind: 'created' } });
|
|
322
|
+
for (const label of ['a', 'b', 'c']) {
|
|
323
|
+
await adapter.associate(record.id, { kind: 'tag', label }, { journal: { ops: ['associate'], kind: 'changed' } });
|
|
324
|
+
}
|
|
325
|
+
expect(await adapter.getJournal(record.id)).toHaveLength(4);
|
|
326
|
+
expect((await adapter.getJournal(record.id, { sinceSeq: 2 })).map((e) => e.seq)).toEqual([
|
|
327
|
+
3, 4,
|
|
328
|
+
]);
|
|
329
|
+
expect((await adapter.getJournal(record.id, { limit: 2 })).map((e) => e.seq)).toEqual([
|
|
330
|
+
1, 2,
|
|
331
|
+
]);
|
|
332
|
+
expect((await adapter.getJournal(record.id, { sinceSeq: 1, limit: 2 })).map((e) => e.seq)).toEqual([2, 3]);
|
|
333
|
+
expect(await adapter.getJournal(record.id, { sinceSeq: 99 })).toEqual([]);
|
|
334
|
+
});
|
|
335
|
+
test('a failed mutation leaves no entry behind', async () => {
|
|
336
|
+
const missing = uniqueId('missing');
|
|
337
|
+
await expectStackErrorCode(adapter.mutateRecord(missing, { contentPatch: { title: 'x' } }, { bumpsVersion: true, journal: { ops: ['patch'], kind: 'changed' } }), 'not_found');
|
|
338
|
+
expect(await adapter.getJournal(missing)).toEqual([]);
|
|
339
|
+
});
|
|
340
|
+
test('a soft delete keeps the journal; a hard delete destroys it', async () => {
|
|
341
|
+
const record = makeRecord();
|
|
342
|
+
await adapter.createRecord(record, { journal: { ops: ['create'], kind: 'created' } });
|
|
343
|
+
await adapter.deleteRecord(record.id, {
|
|
344
|
+
journal: { ops: ['delete'], kind: 'deleted' },
|
|
345
|
+
});
|
|
346
|
+
// A tombstone is recoverable, and its journal is part of what
|
|
347
|
+
// recovers it.
|
|
348
|
+
expect(await adapter.getJournal(record.id)).toHaveLength(2);
|
|
349
|
+
await adapter.deleteRecord(record.id, { hard: true });
|
|
350
|
+
expect(await adapter.getJournal(record.id)).toEqual([]);
|
|
351
|
+
// And the log restarts: a purge that left rows behind would hand
|
|
352
|
+
// the next record at this id a history that is not its own.
|
|
353
|
+
await adapter.createRecord(makeRecord({ id: record.id }), {
|
|
354
|
+
journal: { ops: ['create'], kind: 'created' },
|
|
355
|
+
});
|
|
356
|
+
expect((await adapter.getJournal(record.id)).map((e) => e.seq)).toEqual([1]);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
// -----------------------------------------------------------------
|
|
360
|
+
// Migration
|
|
361
|
+
// -----------------------------------------------------------------
|
|
362
|
+
describe('commitMigration', () => {
|
|
363
|
+
test('changes typeId and content together, and bumps version', async () => {
|
|
364
|
+
const record = makeRecord({ typeId: 'org.haverstack.conformance/note@1' });
|
|
365
|
+
await adapter.createRecord(record);
|
|
366
|
+
const migrated = await adapter.commitMigration(record.id, 'org.haverstack.conformance/note@2', { title: 'migrated' });
|
|
367
|
+
expect(migrated.typeId).toBe('org.haverstack.conformance/note@2');
|
|
368
|
+
expect(migrated.content).toEqual({ title: 'migrated' });
|
|
369
|
+
expect(migrated.version).toBe(record.version + 1);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
// -----------------------------------------------------------------
|
|
373
|
+
// Cursor pagination
|
|
374
|
+
// -----------------------------------------------------------------
|
|
375
|
+
describe('cursor pagination', () => {
|
|
376
|
+
test('pages through more records than fit in one page', async () => {
|
|
377
|
+
const ids = [];
|
|
378
|
+
for (let i = 0; i < 5; i++) {
|
|
379
|
+
const record = makeRecord();
|
|
380
|
+
ids.push(record.id);
|
|
381
|
+
await adapter.createRecord(record);
|
|
382
|
+
}
|
|
383
|
+
const seen = new Set();
|
|
384
|
+
let cursor;
|
|
385
|
+
do {
|
|
386
|
+
const page = await adapter.queryRecords({ limit: 2, cursor: cursor ?? undefined });
|
|
387
|
+
expect(page.records.length).toBeLessThanOrEqual(2);
|
|
388
|
+
for (const r of page.records)
|
|
389
|
+
seen.add(r.id);
|
|
390
|
+
cursor = page.cursor;
|
|
391
|
+
} while (cursor);
|
|
392
|
+
for (const id of ids)
|
|
393
|
+
expect(seen.has(id)).toBe(true);
|
|
394
|
+
});
|
|
395
|
+
test('a malformed cursor is refused rather than misread', async () => {
|
|
396
|
+
await expectStackErrorCode(adapter.queryRecords({ cursor: 'not-a-real-cursor' }), 'bad_request');
|
|
397
|
+
});
|
|
398
|
+
test('a cursor minted under one sort field is refused when replayed under a different one', async () => {
|
|
399
|
+
await adapter.createRecord(makeRecord());
|
|
400
|
+
await adapter.createRecord(makeRecord());
|
|
401
|
+
const minted = await adapter.queryRecords({
|
|
402
|
+
limit: 1,
|
|
403
|
+
sort: { field: 'createdAt' },
|
|
404
|
+
});
|
|
405
|
+
expect(minted.cursor).not.toBeNull();
|
|
406
|
+
await expectStackErrorCode(adapter.queryRecords({
|
|
407
|
+
limit: 1,
|
|
408
|
+
cursor: minted.cursor,
|
|
409
|
+
sort: { field: 'updatedAt' },
|
|
410
|
+
}), 'bad_request');
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
// -----------------------------------------------------------------
|
|
414
|
+
// Content filtering — capability-gated on filter.content
|
|
415
|
+
// -----------------------------------------------------------------
|
|
416
|
+
if (capabilities.filter.content !== 'none') {
|
|
417
|
+
describe('content filtering', () => {
|
|
418
|
+
test('filters by a top-level scalar field, excluding what does not match', async () => {
|
|
419
|
+
const match = makeRecord({ content: { title: 'a', priority: 1 } });
|
|
420
|
+
const decoy = makeRecord({ content: { title: 'b', priority: 2 } });
|
|
421
|
+
await adapter.createRecord(match);
|
|
422
|
+
await adapter.createRecord(decoy);
|
|
423
|
+
const result = await adapter.queryRecords({ filter: { content: { priority: 1 } } });
|
|
424
|
+
// The positive assertion first: `not.toContain` and `every` are
|
|
425
|
+
// both vacuously true on an empty result, so without this an
|
|
426
|
+
// adapter that declares filter.content and returns nothing at
|
|
427
|
+
// all would pass the very test meant to catch it.
|
|
428
|
+
expect(result.records.map((r) => r.id)).toContain(match.id);
|
|
429
|
+
expect(result.records.map((r) => r.id)).not.toContain(decoy.id);
|
|
430
|
+
expect(result.records.every((r) => r.content.priority === 1)).toBe(true);
|
|
431
|
+
});
|
|
432
|
+
test('a null filter value matches records where the field is absent or stored as null', async () => {
|
|
433
|
+
const absent = makeRecord({ content: { title: 'no priority' } });
|
|
434
|
+
const explicitNull = makeRecord({ content: { title: 'null priority', priority: null } });
|
|
435
|
+
const present = makeRecord({ content: { title: 'has one', priority: 1 } });
|
|
436
|
+
await adapter.createRecord(absent);
|
|
437
|
+
await adapter.createRecord(explicitNull);
|
|
438
|
+
await adapter.createRecord(present);
|
|
439
|
+
const result = await adapter.queryRecords({ filter: { content: { priority: null } } });
|
|
440
|
+
const ids = result.records.map((r) => r.id).sort();
|
|
441
|
+
expect(ids).toEqual([absent.id, explicitNull.id].sort());
|
|
442
|
+
expect(ids).not.toContain(present.id);
|
|
443
|
+
});
|
|
444
|
+
test('a scalar filter value never matches an object or array stored at the same path', async () => {
|
|
445
|
+
const scalarMatch = makeRecord({ content: { title: 'x', priority: 1 } });
|
|
446
|
+
const nested = makeRecord({ content: { title: 'y', priority: { high: true } } });
|
|
447
|
+
await adapter.createRecord(scalarMatch);
|
|
448
|
+
await adapter.createRecord(nested);
|
|
449
|
+
const result = await adapter.queryRecords({ filter: { content: { priority: 1 } } });
|
|
450
|
+
expect(result.records.map((r) => r.id)).toContain(scalarMatch.id);
|
|
451
|
+
expect(result.records.map((r) => r.id)).not.toContain(nested.id);
|
|
452
|
+
});
|
|
453
|
+
if (capabilities.filter.contentPresent) {
|
|
454
|
+
test('contentPresent matches records holding a non-null value at the path', async () => {
|
|
455
|
+
const present = makeRecord({ content: { title: 'x', priority: 1 } });
|
|
456
|
+
const absent = makeRecord({ content: { title: 'y' } });
|
|
457
|
+
const nullValue = makeRecord({ content: { title: 'z', priority: null } });
|
|
458
|
+
await adapter.createRecord(present);
|
|
459
|
+
await adapter.createRecord(absent);
|
|
460
|
+
await adapter.createRecord(nullValue);
|
|
461
|
+
const result = await adapter.queryRecords({ filter: { contentPresent: ['priority'] } });
|
|
462
|
+
expect(result.records.map((r) => r.id)).toEqual([present.id]);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (capabilities.filter.content === 'path') {
|
|
466
|
+
test('an array is spread element-wise when filtering a nested path', async () => {
|
|
467
|
+
const match = makeRecord({
|
|
468
|
+
content: { title: 'x', emails: [{ value: 'a@b.c' }, { value: 'd@e.f' }] },
|
|
469
|
+
});
|
|
470
|
+
const noMatch = makeRecord({
|
|
471
|
+
content: { title: 'y', emails: [{ value: 'other@x.y' }] },
|
|
472
|
+
});
|
|
473
|
+
await adapter.createRecord(match);
|
|
474
|
+
await adapter.createRecord(noMatch);
|
|
475
|
+
const result = await adapter.queryRecords({
|
|
476
|
+
filter: { content: { 'emails.value': 'a@b.c' } },
|
|
477
|
+
});
|
|
478
|
+
expect(result.records.map((r) => r.id)).toEqual([match.id]);
|
|
479
|
+
});
|
|
480
|
+
test('a nested path filter reaches a property inside a plain (non-array) object', async () => {
|
|
481
|
+
const match = makeRecord({ content: { title: 'x', address: { city: 'Berlin' } } });
|
|
482
|
+
const noMatch = makeRecord({ content: { title: 'y', address: { city: 'Lyon' } } });
|
|
483
|
+
await adapter.createRecord(match);
|
|
484
|
+
await adapter.createRecord(noMatch);
|
|
485
|
+
const result = await adapter.queryRecords({
|
|
486
|
+
filter: { content: { 'address.city': 'Berlin' } },
|
|
487
|
+
});
|
|
488
|
+
expect(result.records.map((r) => r.id)).toEqual([match.id]);
|
|
489
|
+
});
|
|
490
|
+
test('a filter path descending through a scalar matches nothing, rather than throwing', async () => {
|
|
491
|
+
await adapter.createRecord(makeRecord({ content: { title: 'x', priority: 1 } }));
|
|
492
|
+
const result = await adapter.queryRecords({
|
|
493
|
+
filter: { content: { 'priority.nested': 'anything' } },
|
|
494
|
+
});
|
|
495
|
+
expect(result.records).toEqual([]);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
// -----------------------------------------------------------------
|
|
501
|
+
// Sort by content field — capability-gated on sort.contentField
|
|
502
|
+
// -----------------------------------------------------------------
|
|
503
|
+
if (capabilities.sort.contentField) {
|
|
504
|
+
describe('sort by content field', () => {
|
|
505
|
+
test('orders records by a declared content field', async () => {
|
|
506
|
+
// Registered so an adapter that resolves the field's sort kind
|
|
507
|
+
// through the type schema (rather than off the raw JSON value)
|
|
508
|
+
// sorts numerically instead of falling back to no ordering.
|
|
509
|
+
await adapter.saveType(conformanceType());
|
|
510
|
+
await adapter.createRecord(makeRecord({ content: { title: 'x', priority: 3 } }));
|
|
511
|
+
await adapter.createRecord(makeRecord({ content: { title: 'y', priority: 1 } }));
|
|
512
|
+
await adapter.createRecord(makeRecord({ content: { title: 'z', priority: 2 } }));
|
|
513
|
+
const result = await adapter.queryRecords({
|
|
514
|
+
sort: { contentField: 'priority', direction: 'asc' },
|
|
515
|
+
});
|
|
516
|
+
expect(result.records.map((r) => r.content.priority)).toEqual([
|
|
517
|
+
1, 2, 3,
|
|
518
|
+
]);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
// -----------------------------------------------------------------
|
|
523
|
+
// Full-text search — capability-gated on filter.search
|
|
524
|
+
// -----------------------------------------------------------------
|
|
525
|
+
if (capabilities.filter.search) {
|
|
526
|
+
describe('full-text search', () => {
|
|
527
|
+
test('finds a record containing the search term', async () => {
|
|
528
|
+
const match = makeRecord({ content: { title: 'the quick brown fox' } });
|
|
529
|
+
const noMatch = makeRecord({ content: { title: 'something unrelated' } });
|
|
530
|
+
await adapter.createRecord(match);
|
|
531
|
+
await adapter.createRecord(noMatch);
|
|
532
|
+
const result = await adapter.queryRecords({ filter: { search: 'brown' } });
|
|
533
|
+
expect(result.records.map((r) => r.id)).toContain(match.id);
|
|
534
|
+
expect(result.records.map((r) => r.id)).not.toContain(noMatch.id);
|
|
535
|
+
});
|
|
536
|
+
// The invariant the issue's docs/spec/adapters.md § FTS5 names
|
|
537
|
+
// explicitly: an external-content index's remove step must run
|
|
538
|
+
// before the row's content changes, or a stale entry stays
|
|
539
|
+
// searchable under content the record no longer holds.
|
|
540
|
+
test('a content patch is reflected in search — the old content is no longer searchable', async () => {
|
|
541
|
+
const record = makeRecord({ content: { title: 'original wording' } });
|
|
542
|
+
await adapter.createRecord(record);
|
|
543
|
+
await adapter.mutateRecord(record.id, { contentPatch: { title: 'updated wording' } });
|
|
544
|
+
const stale = await adapter.queryRecords({ filter: { search: 'original' } });
|
|
545
|
+
expect(stale.records.map((r) => r.id)).not.toContain(record.id);
|
|
546
|
+
const fresh = await adapter.queryRecords({ filter: { search: 'updated' } });
|
|
547
|
+
expect(fresh.records.map((r) => r.id)).toContain(record.id);
|
|
548
|
+
});
|
|
549
|
+
test('a hard-deleted record is no longer found by search', async () => {
|
|
550
|
+
const record = makeRecord({ content: { title: 'ephemeral content' } });
|
|
551
|
+
await adapter.createRecord(record);
|
|
552
|
+
await adapter.deleteRecord(record.id, { hard: true });
|
|
553
|
+
const result = await adapter.queryRecords({ filter: { search: 'ephemeral' } });
|
|
554
|
+
expect(result.records.map((r) => r.id)).not.toContain(record.id);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haverstack/adapter-conformance",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Runnable conformance suite for StackRecordAdapter/StackBlobAdapter implementers — the invariants docs/spec/adapters.md documents, as tests you point at your own adapter",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/haverstack/core.git",
|
|
20
|
+
"directory": "packages/adapter-conformance"
|
|
21
|
+
},
|
|
22
|
+
"license": "CC0-1.0",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"haverstack",
|
|
25
|
+
"conformance",
|
|
26
|
+
"adapter",
|
|
27
|
+
"storage",
|
|
28
|
+
"testing"
|
|
29
|
+
],
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"vitest": "^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@haverstack/core": "^0.33.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"typescript": "^5.5.0",
|
|
39
|
+
"vitest": "^4.1.11"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.build.json",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"lint": "eslint src tests"
|
|
46
|
+
}
|
|
47
|
+
}
|