@dzhechkov/memory 0.2.18 → 0.2.20

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.
@@ -0,0 +1,94 @@
1
+ /**
2
+ * A single, shared opener for READ-ONLY SQLite access (ADR-001,
3
+ * `features/store-readonly-reads/03_adr/001-read-opener-ladder-in-place-then-tmp-copy.md`).
4
+ *
5
+ * `{ readonly: true }` alone does not read a WAL database on a directory that cannot be written
6
+ * to: the wal-mode reader still needs to create (or find) `-shm`/`-wal` next to the file, and on
7
+ * a read-only-mounted directory that create fails with `unable to open database file` — MEASURED
8
+ * 2026-09-12 against better-sqlite3 11.10.0 / SQLite 3.49.2 (`chattr +i` on a `/var/tmp`
9
+ * directory holding a checkpointed WAL database). `openSqliteReadOnly` climbs a three-step ladder
10
+ * instead of assuming step 1 always works:
11
+ *
12
+ * 1. **In place.** `{ readonly: true, fileMustExist: true }`, then a real read (the WAL error
13
+ * surfaces on the first query, not on open) — this is what a live writer on the same host
14
+ * gets: it sees uncheckpointed rows too.
15
+ * 2. **Copy.** Only on `SQLITE_CANTOPEN` / "unable to open database file": copy the database
16
+ * (+ its `-wal`, if any — never `-shm`, which is derived and recreated) into a fresh
17
+ * `mkdtemp` directory and open THAT read-only.
18
+ * 3. **Honest failure.** Any other error, or step 2 itself failing, throws — naming the path
19
+ * and the original cause. Never a swallowed `{ hits: [] }`.
20
+ *
21
+ * @packageDocumentation
22
+ */
23
+ import type { MemoryQuery, MemoryRecord } from './backend.js';
24
+ /** Constructor shape `openSqliteReadOnly` needs from `better-sqlite3` (or a caller-supplied one). */
25
+ type DatabaseCtor = new (p: string, o?: {
26
+ readonly?: boolean;
27
+ fileMustExist?: boolean;
28
+ }) => any;
29
+ /** The handle returned by {@link openSqliteReadOnly}. */
30
+ export interface ReadOnlyHandle {
31
+ /** better-sqlite3 `Database`, opened read-only. */
32
+ readonly db: any;
33
+ /** Which rung of the ladder answered: the file itself, or a temporary copy of it. */
34
+ readonly source: 'in-place' | 'tmp-copy';
35
+ /** Removes the temporary copy's directory. No-op for `'in-place'`. Idempotent — safe to call more than once. */
36
+ readonly cleanup: () => void;
37
+ }
38
+ /** Options for {@link openSqliteReadOnly}. */
39
+ export interface OpenReadOnlyOptions {
40
+ /**
41
+ * The `better-sqlite3` constructor to use. Callers that resolve their own copy of the native
42
+ * module (`book-kb.ts`, `agentdb-index.ts` — both resolve from the TARGET project, not from
43
+ * this package) MUST pass it here rather than let this opener resolve its own: a silently
44
+ * different native-module instance is the same class of bug as "the tool in PATH decides the
45
+ * verdict". Omitted only by callers that are fine with the memory package's own resolution
46
+ * (`SqliteBackend.openReadOnly`, which resolves via `createRequire(import.meta.url)` — the same
47
+ * resolution `SqliteBackend.open` already uses).
48
+ */
49
+ readonly Database?: DatabaseCtor;
50
+ }
51
+ /**
52
+ * Open `filePath` for reading only, climbing the ladder described above.
53
+ *
54
+ * Deliberately does NOT: `mkdirSync` any directory, run any DDL/DML, set `journal_mode` or
55
+ * `synchronous`, or check `existsSync` itself (FR-5 keeps that check with the caller, before this
56
+ * is called — `fileMustExist: true` below is only the second line of defence against a race).
57
+ */
58
+ export declare function openSqliteReadOnly(filePath: string, opts?: OpenReadOnlyOptions): ReadOnlyHandle;
59
+ /** The read-only surface a caller gets back from {@link openSqliteReadOnly} + the search logic. */
60
+ export interface ReadOnlyStore {
61
+ querySync(query: MemoryQuery): MemoryRecord[];
62
+ allSync(): MemoryRecord[];
63
+ countSync(): number;
64
+ close(): void;
65
+ }
66
+ /**
67
+ * `ReadOnlyStore` over a {@link ReadOnlyHandle}. Prepares the SAME statement text
68
+ * `SqliteBackend`'s writer constructor prepares (imported, not forked) and shares its search
69
+ * logic via `searchPreparedRecords` — so a reader can never rank differently than the writer.
70
+ * Runs NO `INIT_SQL`, NO `FTS5_SQL`, NO FTS rebuild; discovers FTS5 by reading `sqlite_master`.
71
+ * Mutating methods throw — this store has no `put`/`remove` sibling by construction, not by
72
+ * convention.
73
+ */
74
+ export declare class SqliteReadOnlyStore implements ReadOnlyStore {
75
+ private readonly handle;
76
+ private readonly hasFts5;
77
+ private readonly ftsSearchStmt;
78
+ private readonly ftsSearchSkillStmt;
79
+ private readonly allStmt;
80
+ private readonly countStmt;
81
+ private readonly bySkillStmt;
82
+ constructor(handle: ReadOnlyHandle);
83
+ querySync(query: MemoryQuery): MemoryRecord[];
84
+ allSync(): MemoryRecord[];
85
+ countSync(): number;
86
+ /** `db.close()` first, THEN `handle.cleanup()` in `finally` — the tmp-copy must be removed even if `close()` throws. */
87
+ close(): void;
88
+ put(): never;
89
+ putMany(): never;
90
+ remove(): never;
91
+ removeSync(): never;
92
+ }
93
+ export {};
94
+ //# sourceMappingURL=sqlite-readonly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-readonly.d.ts","sourceRoot":"","sources":["../src/sqlite-readonly.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAa9D,qGAAqG;AACrG,KAAK,YAAY,GAAG,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,GAAG,CAAC;AAEhG,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC;IACjB,qFAAqF;IACrF,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAAC;IACzC,gHAAgH;IAChH,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;CAClC;AAyCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,cAAc,CA6C/F;AAED,mGAAmG;AACnG,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,EAAE,CAAC;IAC9C,OAAO,IAAI,YAAY,EAAE,CAAC;IAC1B,SAAS,IAAI,MAAM,CAAC;IACpB,KAAK,IAAI,IAAI,CAAC;CACf;AAKD;;;;;;;GAOG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkB;IAChD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAkB;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAM;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAM;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAM;gBAEtB,MAAM,EAAE,cAAc;IAclC,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,EAAE;IAQ7C,OAAO,IAAI,YAAY,EAAE;IAIzB,SAAS,IAAI,MAAM;IAInB,wHAAwH;IACxH,KAAK,IAAI,IAAI;IAQb,GAAG,IAAI,KAAK;IAIZ,OAAO,IAAI,KAAK;IAIhB,MAAM,IAAI,KAAK;IAIf,UAAU,IAAI,KAAK;CAGpB"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * A single, shared opener for READ-ONLY SQLite access (ADR-001,
3
+ * `features/store-readonly-reads/03_adr/001-read-opener-ladder-in-place-then-tmp-copy.md`).
4
+ *
5
+ * `{ readonly: true }` alone does not read a WAL database on a directory that cannot be written
6
+ * to: the wal-mode reader still needs to create (or find) `-shm`/`-wal` next to the file, and on
7
+ * a read-only-mounted directory that create fails with `unable to open database file` — MEASURED
8
+ * 2026-09-12 against better-sqlite3 11.10.0 / SQLite 3.49.2 (`chattr +i` on a `/var/tmp`
9
+ * directory holding a checkpointed WAL database). `openSqliteReadOnly` climbs a three-step ladder
10
+ * instead of assuming step 1 always works:
11
+ *
12
+ * 1. **In place.** `{ readonly: true, fileMustExist: true }`, then a real read (the WAL error
13
+ * surfaces on the first query, not on open) — this is what a live writer on the same host
14
+ * gets: it sees uncheckpointed rows too.
15
+ * 2. **Copy.** Only on `SQLITE_CANTOPEN` / "unable to open database file": copy the database
16
+ * (+ its `-wal`, if any — never `-shm`, which is derived and recreated) into a fresh
17
+ * `mkdtemp` directory and open THAT read-only.
18
+ * 3. **Honest failure.** Any other error, or step 2 itself failing, throws — naming the path
19
+ * and the original cause. Never a swallowed `{ hits: [] }`.
20
+ *
21
+ * @packageDocumentation
22
+ */
23
+ import { createRequire } from 'node:module';
24
+ import { existsSync, copyFileSync, mkdtempSync, rmSync } from 'node:fs';
25
+ import { tmpdir } from 'node:os';
26
+ import { basename, join } from 'node:path';
27
+ import { searchPreparedRecords, FTS5_SEARCH_SQL, FTS5_SEARCH_SKILL_SQL, ALL_SQL, COUNT_SQL, BY_SKILL_SQL, rowToRecord, } from './sqlite-backend.js';
28
+ const require = createRequire(import.meta.url);
29
+ function defaultDatabaseCtor() {
30
+ // Dynamic require — better-sqlite3 must be available at runtime, same discipline as
31
+ // `SqliteBackend.open` (sqlite-backend.ts).
32
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
33
+ return require('better-sqlite3');
34
+ }
35
+ function errorMessage(err) {
36
+ return err instanceof Error ? err.message : String(err);
37
+ }
38
+ /** Step-1 → step-2 trigger: ONLY a can't-open-the-file failure retries as a copy. */
39
+ function isCantOpenError(err) {
40
+ const code = err?.code;
41
+ const msg = errorMessage(err);
42
+ return code === 'SQLITE_CANTOPEN' || /unable to open database file/i.test(msg);
43
+ }
44
+ /** A cheap, real read — the WAL "can't create -shm/-wal here" failure surfaces on a query, not on open. */
45
+ function probe(db) {
46
+ db.prepare('SELECT count(*) AS n FROM sqlite_master').get();
47
+ }
48
+ function closeQuietly(db) {
49
+ try {
50
+ db.close();
51
+ }
52
+ catch {
53
+ // already failing on the caller's side — a close failure here must not mask the real cause
54
+ }
55
+ }
56
+ function removeQuietly(dir) {
57
+ try {
58
+ rmSync(dir, { recursive: true, force: true });
59
+ }
60
+ catch {
61
+ // best-effort — the honest-failure path below still reports the real cause
62
+ }
63
+ }
64
+ /**
65
+ * Open `filePath` for reading only, climbing the ladder described above.
66
+ *
67
+ * Deliberately does NOT: `mkdirSync` any directory, run any DDL/DML, set `journal_mode` or
68
+ * `synchronous`, or check `existsSync` itself (FR-5 keeps that check with the caller, before this
69
+ * is called — `fileMustExist: true` below is only the second line of defence against a race).
70
+ */
71
+ export function openSqliteReadOnly(filePath, opts) {
72
+ const Database = opts?.Database ?? defaultDatabaseCtor();
73
+ // Step 1 — in place.
74
+ let inPlaceDb;
75
+ try {
76
+ inPlaceDb = new Database(filePath, { readonly: true, fileMustExist: true });
77
+ probe(inPlaceDb);
78
+ return { db: inPlaceDb, source: 'in-place', cleanup: () => { } };
79
+ }
80
+ catch (stepOneErr) {
81
+ if (inPlaceDb !== undefined)
82
+ closeQuietly(inPlaceDb);
83
+ if (!isCantOpenError(stepOneErr)) {
84
+ throw new Error(`failed to open ${filePath} for reading: ${errorMessage(stepOneErr)}`);
85
+ }
86
+ // Step 2 — copy into a fresh tmp directory.
87
+ let tmpDir;
88
+ let copyDb;
89
+ try {
90
+ tmpDir = mkdtempSync(join(tmpdir(), 'dz-ro-'));
91
+ const dest = join(tmpDir, basename(filePath));
92
+ copyFileSync(filePath, dest);
93
+ const walSource = `${filePath}-wal`;
94
+ if (existsSync(walSource)) {
95
+ copyFileSync(walSource, `${dest}-wal`);
96
+ }
97
+ // `-shm` is intentionally NOT copied — it is derived and SQLite recreates it; a stale one
98
+ // next to a fresh copy is worse than none (ADR-001, Решение 1, ступень 2).
99
+ copyDb = new Database(dest, { readonly: true, fileMustExist: true });
100
+ probe(copyDb);
101
+ const dir = tmpDir;
102
+ return {
103
+ db: copyDb,
104
+ source: 'tmp-copy',
105
+ cleanup: () => removeQuietly(dir),
106
+ };
107
+ }
108
+ catch (stepTwoErr) {
109
+ // `copyDb` may have opened successfully and THEN failed in `probe()` — an unclosed handle
110
+ // here leaks a file descriptor and, on a platform that refuses to delete an open file,
111
+ // leaves the tmp directory behind too. Close before removing (fix round 1, HIGH #1).
112
+ if (copyDb !== undefined)
113
+ closeQuietly(copyDb);
114
+ if (tmpDir !== undefined)
115
+ removeQuietly(tmpDir);
116
+ throw new Error(`failed to open ${filePath} for reading: ${errorMessage(stepTwoErr)}`);
117
+ }
118
+ }
119
+ }
120
+ /** `sqlite_master` lookup used to discover FTS5 presence WITHOUT attempting to (re)create it. */
121
+ const FTS5_TABLE_PRESENT_SQL = `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'memory_fts'`;
122
+ /**
123
+ * `ReadOnlyStore` over a {@link ReadOnlyHandle}. Prepares the SAME statement text
124
+ * `SqliteBackend`'s writer constructor prepares (imported, not forked) and shares its search
125
+ * logic via `searchPreparedRecords` — so a reader can never rank differently than the writer.
126
+ * Runs NO `INIT_SQL`, NO `FTS5_SQL`, NO FTS rebuild; discovers FTS5 by reading `sqlite_master`.
127
+ * Mutating methods throw — this store has no `put`/`remove` sibling by construction, not by
128
+ * convention.
129
+ */
130
+ export class SqliteReadOnlyStore {
131
+ handle;
132
+ hasFts5;
133
+ ftsSearchStmt;
134
+ ftsSearchSkillStmt;
135
+ allStmt;
136
+ countStmt;
137
+ bySkillStmt;
138
+ constructor(handle) {
139
+ this.handle = handle;
140
+ const db = handle.db;
141
+ const ftsRow = db.prepare(FTS5_TABLE_PRESENT_SQL).get();
142
+ this.hasFts5 = ftsRow?.name === 'memory_fts';
143
+ if (this.hasFts5) {
144
+ this.ftsSearchStmt = db.prepare(FTS5_SEARCH_SQL);
145
+ this.ftsSearchSkillStmt = db.prepare(FTS5_SEARCH_SKILL_SQL);
146
+ }
147
+ this.allStmt = db.prepare(ALL_SQL);
148
+ this.countStmt = db.prepare(COUNT_SQL);
149
+ this.bySkillStmt = db.prepare(BY_SKILL_SQL);
150
+ }
151
+ querySync(query) {
152
+ return searchPreparedRecords({ fts: this.ftsSearchStmt, ftsSkill: this.ftsSearchSkillStmt, all: this.allStmt, bySkill: this.bySkillStmt }, this.hasFts5, query);
153
+ }
154
+ allSync() {
155
+ return this.allStmt.all().map(rowToRecord);
156
+ }
157
+ countSync() {
158
+ return this.countStmt.get().cnt;
159
+ }
160
+ /** `db.close()` first, THEN `handle.cleanup()` in `finally` — the tmp-copy must be removed even if `close()` throws. */
161
+ close() {
162
+ try {
163
+ this.handle.db.close();
164
+ }
165
+ finally {
166
+ this.handle.cleanup();
167
+ }
168
+ }
169
+ put() {
170
+ throw new Error('read-only backend: put is not available');
171
+ }
172
+ putMany() {
173
+ throw new Error('read-only backend: putMany is not available');
174
+ }
175
+ remove() {
176
+ throw new Error('read-only backend: remove is not available');
177
+ }
178
+ removeSync() {
179
+ throw new Error('read-only backend: removeSync is not available');
180
+ }
181
+ }
182
+ //# sourceMappingURL=sqlite-readonly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-readonly.js","sourceRoot":"","sources":["../src/sqlite-readonly.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/memory",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "Harness memory layer - backend cascade, Reflexion feedback, and the host-memory bridge.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,10 +43,13 @@
43
43
  },
44
44
  "repository": {
45
45
  "type": "git",
46
- "url": "https://github.com/djd1m/dz-harness-hub.git",
46
+ "url": "git+https://github.com/djd1m/dz-harness.git",
47
47
  "directory": "packages/@dzhechkov/memory"
48
48
  },
49
- "homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/memory#readme",
49
+ "homepage": "https://github.com/djd1m/dz-harness/tree/main/packages/@dzhechkov/memory#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/djd1m/dz-harness/issues"
52
+ },
50
53
  "scripts": {
51
54
  "build": "tsc -p tsconfig.json",
52
55
  "test": "vitest run",
package/sbom.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "hashes": [
26
26
  {
27
27
  "alg": "SHA-256",
28
- "content": "7598ab3dbd07f39d442c5005282323bb6519b56e14b7ae979ef6565ee2dd4a30"
28
+ "content": "9f48bf4e1b45d539be0478df0abeb43c10bffc6a8dc43a247d41a521254536da"
29
29
  }
30
30
  ]
31
31
  },
@@ -195,7 +195,7 @@
195
195
  "hashes": [
196
196
  {
197
197
  "alg": "SHA-256",
198
- "content": "9d855df88b28e860102b42a0b899f7a5a561c3e3d8cf3fc48cb3366a15049c30"
198
+ "content": "c4e8be344baf02933c8e453bdba9ec3f95062d28672c8d0dabdd8035ef39e58e"
199
199
  }
200
200
  ]
201
201
  },
@@ -205,7 +205,7 @@
205
205
  "hashes": [
206
206
  {
207
207
  "alg": "SHA-256",
208
- "content": "5a0b747b1858bdaacab8c086a24005ce0ec98d8d4ca60ea42e8c5f79ce9c3719"
208
+ "content": "9576e873ac4003f6b217e0eff04f2cd5f8633397e004164abe7f10f9d7886511"
209
209
  }
210
210
  ]
211
211
  },
@@ -215,7 +215,7 @@
215
215
  "hashes": [
216
216
  {
217
217
  "alg": "SHA-256",
218
- "content": "8b73ac944a586829e8d73e81d9c3c210dc13124067fb5742c953cc4fda1fcba9"
218
+ "content": "b6f72385ca8f3fbde2a5c11cf0deb98f58ea9836d216377e6585dc22b4b629b5"
219
219
  }
220
220
  ]
221
221
  },
@@ -225,7 +225,7 @@
225
225
  "hashes": [
226
226
  {
227
227
  "alg": "SHA-256",
228
- "content": "a86663fb3aeccf123b6b2adbc6a9a4a70da9a3887c75514a49d1bf95837253d3"
228
+ "content": "92e221dc762befc999d234a173b09b2e5c09c7e927518fccdd8bbebe15923712"
229
229
  }
230
230
  ]
231
231
  },
@@ -315,7 +315,7 @@
315
315
  "hashes": [
316
316
  {
317
317
  "alg": "SHA-256",
318
- "content": "bcee527422ab9692082ad26f6276f47f9bce86ed5279439f67a0823da1b93387"
318
+ "content": "c077c7e0b1001aaf46d7312dbf572eb497fadc1dc69e8ff6750c1e324ec854f5"
319
319
  }
320
320
  ]
321
321
  },
@@ -325,7 +325,7 @@
325
325
  "hashes": [
326
326
  {
327
327
  "alg": "SHA-256",
328
- "content": "784903497cd0ec1e84358c3b96cfc36e34d9077a7626351f8792bc69ad735f68"
328
+ "content": "0df025e4853d8420efc87d2b7aa8ea9f36e5b9a0a0be4351a7eb7614caf62b4c"
329
329
  }
330
330
  ]
331
331
  },
@@ -335,7 +335,7 @@
335
335
  "hashes": [
336
336
  {
337
337
  "alg": "SHA-256",
338
- "content": "ebff426dfa8028097fbdcc350ba698d3c5472dc881dfc82604d8e23408d6f00d"
338
+ "content": "4f2a5c61fc27a8aa39f1adfb06215af80bbde0236f1f2a7f73ab65d3ab3f3652"
339
339
  }
340
340
  ]
341
341
  },
@@ -345,7 +345,7 @@
345
345
  "hashes": [
346
346
  {
347
347
  "alg": "SHA-256",
348
- "content": "92b0105282fb9069efde0fcb45d5b91c4ae0fd7e65afbdb077443ea9fec0f551"
348
+ "content": "4106d2a7cfa2ad50ea5497dab8ad18d2102019f05e5a3aca2469ab2ec325ce48"
349
349
  }
350
350
  ]
351
351
  },
@@ -389,6 +389,46 @@
389
389
  }
390
390
  ]
391
391
  },
392
+ {
393
+ "type": "file",
394
+ "name": "dist/sqlite-readonly.d.ts",
395
+ "hashes": [
396
+ {
397
+ "alg": "SHA-256",
398
+ "content": "58f6de024b2cd030c79fa056353537eb595f443868cdc1351d06be28c2dda367"
399
+ }
400
+ ]
401
+ },
402
+ {
403
+ "type": "file",
404
+ "name": "dist/sqlite-readonly.d.ts.map",
405
+ "hashes": [
406
+ {
407
+ "alg": "SHA-256",
408
+ "content": "09688dd92c8d5956865795394ebc47d69108997281a891aa54ce81d5a00c6e36"
409
+ }
410
+ ]
411
+ },
412
+ {
413
+ "type": "file",
414
+ "name": "dist/sqlite-readonly.js",
415
+ "hashes": [
416
+ {
417
+ "alg": "SHA-256",
418
+ "content": "7dbb93d4bd754e761e73b82ea7cd944743d41b44e4d376af871145555e136f45"
419
+ }
420
+ ]
421
+ },
422
+ {
423
+ "type": "file",
424
+ "name": "dist/sqlite-readonly.js.map",
425
+ "hashes": [
426
+ {
427
+ "alg": "SHA-256",
428
+ "content": "03ddeec35c1389f249b0595f2612b8c3f86e5069b4c3f1be1e30eb6f6949bf1c"
429
+ }
430
+ ]
431
+ },
392
432
  {
393
433
  "type": "file",
394
434
  "name": "package.json",
@@ -399,7 +439,7 @@
399
439
  },
400
440
  {
401
441
  "name": "dz:canonical-json-sha256-v2",
402
- "value": "9c5898133bec11759644919c8db1f5218699b1736f73e8bfad60b3688ab844bd"
442
+ "value": "308ba5ec95c7f2403d52522c4a92e8aac51bd0c514a68517658b4906900bbcd3"
403
443
  }
404
444
  ]
405
445
  },
@@ -449,7 +489,7 @@
449
489
  "hashes": [
450
490
  {
451
491
  "alg": "SHA-256",
452
- "content": "34c68dbea4be8c8108dd6aff0e49690484894c56f53967e07c92205b8d57ff64"
492
+ "content": "9152ce2677f8b209bb26a90cf15b1f97dccd7e5fcdcc95e7cf417a698798fc56"
453
493
  }
454
494
  ]
455
495
  },
@@ -479,7 +519,7 @@
479
519
  "hashes": [
480
520
  {
481
521
  "alg": "SHA-256",
482
- "content": "b2f4195a9db186e788aebafb50734078c2eec5cf74fdb45dc53ca6ec4d0208e1"
522
+ "content": "08b618bf7fd351120aac5c4140ae84c2d207c45d708dc42a98f175a99938a11c"
483
523
  }
484
524
  ]
485
525
  },
@@ -492,6 +532,16 @@
492
532
  "content": "28bc13308f61bc553fc36a9c0efcf382522769de20769f3fcef8c3bbb4734b7e"
493
533
  }
494
534
  ]
535
+ },
536
+ {
537
+ "type": "file",
538
+ "name": "src/sqlite-readonly.ts",
539
+ "hashes": [
540
+ {
541
+ "alg": "SHA-256",
542
+ "content": "c973b0eeae5567af4c519c178ccb48d496fa1cf2ad226363b26ecc2e7d11dc45"
543
+ }
544
+ ]
495
545
  }
496
546
  ]
497
547
  }
package/src/index.ts CHANGED
@@ -17,6 +17,8 @@ export { selectBackend } from './cascade.js';
17
17
  export type { BackendProbe, CascadeResult } from './cascade.js';
18
18
  export { SqliteBackend } from './sqlite-backend.js';
19
19
  export type { SqliteBackendOptions } from './sqlite-backend.js';
20
+ export { openSqliteReadOnly, SqliteReadOnlyStore } from './sqlite-readonly.js';
21
+ export type { ReadOnlyHandle, ReadOnlyStore, OpenReadOnlyOptions } from './sqlite-readonly.js';
20
22
  export { SqliteProbe } from './sqlite-probe.js';
21
23
  export type { SqliteProbeOptions } from './sqlite-probe.js';
22
24
  export { Reflexion } from './reflexion.js';