@substrat-run/adapter-sqlite 0.122.1 → 0.125.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +178 -27
- package/dist/index.js.map +1 -1
- package/package.json +14 -5
- package/testing.cjs +131 -0
- package/testing.d.cts +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@substrat-run/adapter-sqlite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.125.0",
|
|
4
4
|
"description": "Pure-SQLite scope host — real kernel semantics with no Cloudflare dependency (D-14): local dev, CI, self-host/escrow",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -23,21 +23,30 @@
|
|
|
23
23
|
"browser": null,
|
|
24
24
|
"types": "./dist/vertical-broker.d.ts",
|
|
25
25
|
"default": "./dist/vertical-broker.js"
|
|
26
|
+
},
|
|
27
|
+
"./testing": {
|
|
28
|
+
"workerd": null,
|
|
29
|
+
"worker": null,
|
|
30
|
+
"browser": null,
|
|
31
|
+
"types": "./testing.d.cts",
|
|
32
|
+
"default": "./testing.cjs"
|
|
26
33
|
}
|
|
27
34
|
},
|
|
28
35
|
"files": [
|
|
29
|
-
"dist"
|
|
36
|
+
"dist",
|
|
37
|
+
"testing.cjs",
|
|
38
|
+
"testing.d.cts"
|
|
30
39
|
],
|
|
31
40
|
"dependencies": {
|
|
32
41
|
"better-sqlite3": "^13.0.3",
|
|
33
|
-
"@substrat-run/contracts": "^0.
|
|
34
|
-
"@substrat-run/kernel": "^0.
|
|
42
|
+
"@substrat-run/contracts": "^0.125.0",
|
|
43
|
+
"@substrat-run/kernel": "^0.125.0"
|
|
35
44
|
},
|
|
36
45
|
"devDependencies": {
|
|
37
46
|
"@types/better-sqlite3": "^9.6.0",
|
|
38
47
|
"typescript": "^7.0.0",
|
|
39
48
|
"vitest": "^3.2.7",
|
|
40
|
-
"@substrat-run/contract-tests": "^0.
|
|
49
|
+
"@substrat-run/contract-tests": "^0.125.0"
|
|
41
50
|
},
|
|
42
51
|
"publishConfig": {
|
|
43
52
|
"access": "public"
|
package/testing.cjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A node SQLite that refuses what a Durable Object refuses: a LIKE or GLOB pattern longer
|
|
3
|
+
* than 50 bytes (#1655, #1770).
|
|
4
|
+
*
|
|
5
|
+
* workerd's SQLite sets `SQLITE_LIMIT_LIKE_PATTERN_LENGTH` to 50. Stock SQLite allows 50000,
|
|
6
|
+
* and `better-sqlite3` exposes no `sqlite3_limit` to the JS side, so a vertical's own test
|
|
7
|
+
* suite runs a pattern hosted production would refuse — the incident class of #1646. The one
|
|
8
|
+
* hook a connection does give is `db.function()`, and SQLite documents that an
|
|
9
|
+
* application-defined `like()` / `glob()` REPLACES the built-in one the operators call. So
|
|
10
|
+
* this installs both: refuse a pattern over the limit with the message a Durable Object gives,
|
|
11
|
+
* otherwise hand the very same arguments to a pristine built-in on a scratch connection, so the
|
|
12
|
+
* matching semantics stay SQLite's own.
|
|
13
|
+
*
|
|
14
|
+
* This is test-only and opt-in — importing it (for its side effect) patches
|
|
15
|
+
* `Database.prototype`, so it reaches a connection however it was opened and whichever module
|
|
16
|
+
* opened it, for the lifetime of the process. It costs a JavaScript call per row of every
|
|
17
|
+
* `LIKE`/`GLOB` plus a second query on a scratch connection, and turns off SQLite's `LIKE`
|
|
18
|
+
* prefix-index optimisation — affordable in a test process, not in what self-hosters and escrow
|
|
19
|
+
* users run in production, which is why `packages/adapter-sqlite`'s own runtime code never
|
|
20
|
+
* imports this module. Pull it into a vitest `setupFiles` entry:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* // vitest.config.ts
|
|
24
|
+
* export default defineConfig({ test: { setupFiles: ['@substrat-run/adapter-sqlite/testing'] } });
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* It patches the SAME `better-sqlite3` copy `@substrat-run/adapter-sqlite` itself resolves,
|
|
28
|
+
* because it is required from inside this package rather than resolved by path from somewhere
|
|
29
|
+
* else — the open question the issue that asked for this left for its placement.
|
|
30
|
+
*
|
|
31
|
+
* PLAIN, HAND-WRITTEN COMMONJS — DELIBERATELY, NOT COMPILED FROM TYPESCRIPT (#1770 review).
|
|
32
|
+
* A build-time dependency (`tsc` emitting a `dist/testing.js` this file was compiled from once)
|
|
33
|
+
* meant every process this repo's own preload (`tools/vitest/like-pattern-limit.cjs`) is
|
|
34
|
+
* `--require`d into needed `packages/adapter-sqlite` BUILT — including CI shards and bare
|
|
35
|
+
* `pnpm --filter <pkg> test` runs that never touch this package at all. `apps/social-relay`
|
|
36
|
+
* found the gap concretely: it opens its own, unrelated `better-sqlite3` connections (an OAuth
|
|
37
|
+
* client store) with no workspace dependency on `@substrat-run/adapter-sqlite`, so
|
|
38
|
+
* `tools/ci-scope.mjs`'s build filter never built it for a social-relay-only PR, and the whole
|
|
39
|
+
* preload chain — needed by EVERY process regardless of scope — broke. This file ships AS-IS,
|
|
40
|
+
* `require`-able the moment `pnpm install` has run, needing no build step at all. Its shape
|
|
41
|
+
* (a `.cjs` extension, unambiguous CommonJS regardless of this package's own `"type": "module"`)
|
|
42
|
+
* is `require`-able directly and `import`-able from ESM (Node reads a CommonJS module either
|
|
43
|
+
* way), so one file serves both `./testing`'s `import` and `require` paths — see `testing.d.cts`
|
|
44
|
+
* for its hand-written types, and `package.json`'s `exports` map for the subpath itself.
|
|
45
|
+
*/
|
|
46
|
+
'use strict';
|
|
47
|
+
|
|
48
|
+
const Database = require('better-sqlite3');
|
|
49
|
+
|
|
50
|
+
const LIMIT = 50;
|
|
51
|
+
const MESSAGE = 'LIKE or GLOB pattern too complex';
|
|
52
|
+
|
|
53
|
+
// Node's module cache does NOT guarantee this file is evaluated once. vitest's own SSR loader
|
|
54
|
+
// "inlines" a workspace file — evaluates a dynamic `import()` of this file in ITS OWN transform
|
|
55
|
+
// pipeline — while `createRequire(...)(...)` bypasses that pipeline and goes through Node's
|
|
56
|
+
// NATIVE `require`; the two keep separate registries, so `import()` and `require()` of the
|
|
57
|
+
// SAME resolved path give two distinct evaluations, each with its own closure (proven in
|
|
58
|
+
// `test/testing-shared-state.test.ts`, which asserts the two `liftLimit` exports differ before
|
|
59
|
+
// trusting anything else). `better-sqlite3` is an external dependency, not something vitest
|
|
60
|
+
// inlines, so both evaluations still resolve the SAME native `Database` class and therefore the
|
|
61
|
+
// SAME `Database.prototype` — a `seen` WeakSet scoped to each evaluation's own closure would
|
|
62
|
+
// desync there: `liftLimit` called on one instance marks a connection nobody else's `install()`
|
|
63
|
+
// ever reads, and whichever instance is active re-limits it on the very next `prepare()`. A
|
|
64
|
+
// well-known `Symbol.for` key on the shared prototype is what makes every evaluation agree.
|
|
65
|
+
const SEEN = Symbol.for('substrat.adapter-sqlite.testing.seen');
|
|
66
|
+
const protoRegistry = Database.prototype;
|
|
67
|
+
const seen = protoRegistry[SEEN] || (protoRegistry[SEEN] = new WeakSet());
|
|
68
|
+
const scratch = new Database(':memory:');
|
|
69
|
+
seen.add(scratch);
|
|
70
|
+
const pristine = {
|
|
71
|
+
like: scratch.prepare('SELECT like(?, ?) AS r').pluck(),
|
|
72
|
+
like3: scratch.prepare('SELECT like(?, ?, ?) AS r').pluck(),
|
|
73
|
+
glob: scratch.prepare('SELECT glob(?, ?) AS r').pluck(),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const tooLong = (pattern) => typeof pattern === 'string' && Buffer.byteLength(pattern, 'utf8') > LIMIT;
|
|
77
|
+
|
|
78
|
+
const register = (db, limited) => {
|
|
79
|
+
const guard = (pattern) => {
|
|
80
|
+
if (limited && tooLong(pattern)) throw new Error(MESSAGE);
|
|
81
|
+
};
|
|
82
|
+
// Two registrations, not one `varargs`: SQLite keys a function on its name AND arity, so a
|
|
83
|
+
// call with any other number of arguments still fails "wrong number of arguments" as it does
|
|
84
|
+
// on a real connection, instead of being answered by a callback that ignores the extras.
|
|
85
|
+
db.function('like', { deterministic: true }, (pattern, value) => {
|
|
86
|
+
guard(pattern);
|
|
87
|
+
return pristine.like.get(pattern, value);
|
|
88
|
+
});
|
|
89
|
+
db.function('like', { deterministic: true }, (pattern, value, escape) => {
|
|
90
|
+
guard(pattern);
|
|
91
|
+
return pristine.like3.get(pattern, value, escape);
|
|
92
|
+
});
|
|
93
|
+
db.function('glob', { deterministic: true }, (pattern, value) => {
|
|
94
|
+
guard(pattern);
|
|
95
|
+
return pristine.glob.get(pattern, value);
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const install = (db) => {
|
|
100
|
+
if (seen.has(db)) return;
|
|
101
|
+
seen.add(db);
|
|
102
|
+
register(db, true);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** Take the limit off one connection — for a test whose ORACLE is a pattern a Durable Object
|
|
106
|
+
* would refuse (a split guard compared with the whole pattern it replaced). Say so where it is
|
|
107
|
+
* called: a suite that lifts the limit is a suite that no longer sees what production sees. */
|
|
108
|
+
const liftLimit = (db) => {
|
|
109
|
+
seen.add(db);
|
|
110
|
+
register(db, false);
|
|
111
|
+
return db;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (!Database.prototype.__likePatternLimit) {
|
|
115
|
+
Database.prototype.__likePatternLimit = LIMIT;
|
|
116
|
+
// A connection is patched the first time anything is asked of it: the constructor is a
|
|
117
|
+
// native one that cannot be wrapped from here, and no statement can run before this.
|
|
118
|
+
for (const method of ['prepare', 'exec', 'pragma', 'transaction']) {
|
|
119
|
+
const original = Database.prototype[method];
|
|
120
|
+
Database.prototype[method] = function patched(...args) {
|
|
121
|
+
install(this);
|
|
122
|
+
return original.apply(this, args);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
/** The limit this module enforces — a Durable Object's own `SQLITE_LIMIT_LIKE_PATTERN_LENGTH`. */
|
|
129
|
+
LIKE_PATTERN_LIMIT: LIMIT,
|
|
130
|
+
liftLimit,
|
|
131
|
+
};
|
package/testing.d.cts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Hand-written declarations for testing.cjs (#1770 review) — that file is plain, uncompiled
|
|
2
|
+
// CommonJS on purpose (see its own header), so nothing emits this. `.d.cts` is the extension
|
|
3
|
+
// TypeScript's Node16/NodeNext resolution matches against a `.cjs` implementation file.
|
|
4
|
+
// `better-sqlite3`'s own types are `export =`, so a `.d.cts` (CommonJS-implied) file imports it
|
|
5
|
+
// the CommonJS way rather than `import type … from`. A consumer typechecking against this
|
|
6
|
+
// subpath needs `@types/better-sqlite3` itself as a (dev)dependency for `Database.Database` to
|
|
7
|
+
// resolve — the scaffold from `npm create substrat` already carries it.
|
|
8
|
+
import Database = require('better-sqlite3');
|
|
9
|
+
|
|
10
|
+
/** The limit this module enforces — a Durable Object's own `SQLITE_LIMIT_LIKE_PATTERN_LENGTH`. */
|
|
11
|
+
export const LIKE_PATTERN_LIMIT: number;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Take the limit off one connection — for a test whose ORACLE is a pattern a Durable Object
|
|
15
|
+
* would refuse (a split guard compared with the whole pattern it replaced). Say so where it is
|
|
16
|
+
* called: a suite that lifts the limit is a suite that no longer sees what production sees.
|
|
17
|
+
*/
|
|
18
|
+
export function liftLimit(db: Database.Database): Database.Database;
|