@earendil-works/pi-durable 1.2.0 → 1.4.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/CHANGELOG.md +56 -0
- package/README.md +128 -3
- package/dist/codemode/codemode.d.ts.map +1 -1
- package/dist/codemode/codemode.js +12 -4
- package/dist/codemode/codemode.js.map +1 -1
- package/dist/codemode/models.d.ts.map +1 -1
- package/dist/codemode/models.js +2 -0
- package/dist/codemode/models.js.map +1 -1
- package/dist/documents.d.ts +1 -1
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js.map +1 -1
- package/dist/env/index.d.ts +1 -0
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +11 -3
- package/dist/env/node.js.map +1 -1
- package/dist/harness/agent.d.ts +13 -2
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +39 -5
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.d.ts +3 -1
- package/dist/harness/compaction.d.ts.map +1 -1
- package/dist/harness/compaction.js +19 -4
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +5 -0
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +46 -12
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +67 -27
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts +5 -2
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +78 -13
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +2 -2
- package/dist/harness/inbox.d.ts.map +1 -1
- package/dist/harness/inbox.js +3 -3
- package/dist/harness/inbox.js.map +1 -1
- package/dist/harness/prompt.d.ts +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +1 -1
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/scheduler.d.ts +55 -10
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +617 -124
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +7 -2
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +48 -17
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +18 -1
- package/dist/harness/task-graph.d.ts.map +1 -1
- package/dist/harness/task-graph.js +50 -11
- package/dist/harness/task-graph.js.map +1 -1
- package/dist/harness/telemetry.d.ts +307 -0
- package/dist/harness/telemetry.d.ts.map +1 -0
- package/dist/harness/telemetry.js +136 -0
- package/dist/harness/telemetry.js.map +1 -0
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +54 -8
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/types.d.ts +110 -15
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/util.d.ts +4 -0
- package/dist/harness/util.d.ts.map +1 -1
- package/dist/harness/util.js +4 -0
- package/dist/harness/util.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/session/session.d.ts +17 -6
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +64 -32
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +5 -1
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +26 -1
- package/dist/session/transaction.js.map +1 -1
- package/dist/storage/indexeddb.d.ts +60 -0
- package/dist/storage/indexeddb.d.ts.map +1 -0
- package/dist/storage/indexeddb.js +831 -0
- package/dist/storage/indexeddb.js.map +1 -0
- package/dist/storage/jsonl/storage.d.ts +16 -0
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/cloudflare.d.ts +6 -43
- package/dist/storage/sqlite/cloudflare.d.ts.map +1 -1
- package/dist/storage/sqlite/cloudflare.js +6 -101
- package/dist/storage/sqlite/cloudflare.js.map +1 -1
- package/dist/storage/sqlite/database.d.ts +1 -36
- package/dist/storage/sqlite/database.d.ts.map +1 -1
- package/dist/storage/sqlite/database.js.map +1 -1
- package/dist/storage/sqlite/index.d.ts +2 -2
- package/dist/storage/sqlite/index.d.ts.map +1 -1
- package/dist/storage/sqlite/index.js.map +1 -1
- package/dist/storage/sqlite/migrations.d.ts +7 -6
- package/dist/storage/sqlite/migrations.d.ts.map +1 -1
- package/dist/storage/sqlite/migrations.js +116 -106
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/storage/sqlite/node.d.ts +6 -39
- package/dist/storage/sqlite/node.d.ts.map +1 -1
- package/dist/storage/sqlite/node.js +8 -175
- package/dist/storage/sqlite/node.js.map +1 -1
- package/dist/storage/sqlite/schema.d.ts +45 -0
- package/dist/storage/sqlite/schema.d.ts.map +1 -0
- package/dist/storage/sqlite/schema.js +50 -0
- package/dist/storage/sqlite/schema.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +10 -2
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +46 -43
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +17 -0
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/timers.d.ts +21 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +72 -0
- package/dist/timers.js.map +1 -0
- package/dist/tools/image-processor.d.ts +4 -1
- package/dist/tools/image-processor.d.ts.map +1 -1
- package/dist/tools/image-processor.js +8 -18
- package/dist/tools/image-processor.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +1 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/truncate.d.ts +3 -2
- package/dist/truncate.d.ts.map +1 -1
- package/dist/truncate.js +4 -34
- package/dist/truncate.js.map +1 -1
- package/dist/types.d.ts +130 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +15 -5
- package/dist/harness/output.d.ts +0 -69
- package/dist/harness/output.d.ts.map +0 -1
- package/dist/harness/output.js +0 -318
- package/dist/harness/output.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/storage/sqlite/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,GAEjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/storage/sqlite/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,GAEjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAA6B,MAAM,cAAc,CAAC","sourcesContent":["export type { SqliteDatabase, SqliteExecutor, SqliteValue } from \"@earendil-works/chord/sqlite\";\nexport {\n\tapplySqliteMigrations,\n\tCURRENT_SQLITE_SCHEMA_VERSION,\n\tSQLITE_MIGRATIONS,\n\ttype SqliteMigration,\n} from \"./migrations.ts\";\nexport { SqliteStorage, type SqliteStorageOptions } from \"./storage.ts\";\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/** Immutable, ordered schema history. Append new migrations after the initial schema ships. */
|
|
1
|
+
import { type SqliteDatabase, type SqliteMigration } from "@earendil-works/chord/sqlite";
|
|
2
|
+
import { type SqliteSchema } from "./schema.ts";
|
|
3
|
+
export type { SqliteMigration } from "@earendil-works/chord/sqlite";
|
|
4
|
+
/** Build immutable, ordered schema history for a set of SQLite identifiers. */
|
|
5
|
+
export declare function createSqliteMigrations(schema: SqliteSchema): readonly SqliteMigration[];
|
|
7
6
|
export declare const SQLITE_MIGRATIONS: readonly SqliteMigration[];
|
|
8
7
|
export declare const CURRENT_SQLITE_SCHEMA_VERSION: number;
|
|
9
8
|
/** Apply all pending schema migrations atomically. */
|
|
10
9
|
export declare function applySqliteMigrations(database: SqliteDatabase, migrations?: readonly SqliteMigration[]): Promise<void>;
|
|
10
|
+
/** Apply migrations rendered for a particular schema. */
|
|
11
|
+
export declare function applySqliteMigrationsForSchema(database: SqliteDatabase, schema: SqliteSchema, migrations?: readonly SqliteMigration[]): Promise<void>;
|
|
11
12
|
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAEvE,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,eAAe,EAAE,CA0GvF;AAED,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EAAkD,CAAC;AAE3G,eAAO,MAAM,6BAA6B,QAAyC,CAAC;AAEpF,sDAAsD;AACtD,wBAAsB,qBAAqB,CAC1C,QAAQ,EAAE,cAAc,EACxB,UAAU,GAAE,SAAS,eAAe,EAAsB,GACxD,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,yDAAyD;AACzD,wBAAsB,8BAA8B,CACnD,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,GAAE,SAAS,eAAe,EAAmC,GACrE,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
@@ -1,111 +1,121 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
import { applySqliteMigrations as applyMigrations, } from "@earendil-works/chord/sqlite";
|
|
2
|
+
import { DEFAULT_SQLITE_SCHEMA } from "./schema.js";
|
|
3
|
+
/** Build immutable, ordered schema history for a set of SQLite identifiers. */
|
|
4
|
+
export function createSqliteMigrations(schema) {
|
|
5
|
+
const { tables, indexes } = schema;
|
|
6
|
+
// next_id is TEXT because node:sqlite rejects INTEGER results outside JavaScript's safe integer range.
|
|
7
|
+
const initialSchema = [
|
|
8
|
+
`CREATE TABLE ${tables.durableMetadata} (
|
|
9
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
10
|
+
next_id TEXT NOT NULL,
|
|
11
|
+
next_seq INTEGER NOT NULL
|
|
12
|
+
) STRICT`,
|
|
13
|
+
`INSERT INTO ${tables.durableMetadata} (singleton, next_id, next_seq) VALUES (1, '2', 1)`,
|
|
14
|
+
`CREATE TABLE ${tables.recordIds} (
|
|
15
|
+
id INTEGER PRIMARY KEY,
|
|
16
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('conversation', 'entry', 'task', 'submission', 'document'))
|
|
17
|
+
) STRICT`,
|
|
18
|
+
`CREATE TABLE ${tables.conversations} (
|
|
19
|
+
id INTEGER PRIMARY KEY,
|
|
20
|
+
owner_conversation_id INTEGER,
|
|
21
|
+
owner_task_id INTEGER,
|
|
22
|
+
record TEXT NOT NULL CHECK (json_valid(record))
|
|
23
|
+
) STRICT`,
|
|
24
|
+
`CREATE INDEX ${indexes.conversationsByOwnerConversation} ON ${tables.conversations} (owner_conversation_id, id)`,
|
|
25
|
+
`CREATE INDEX ${indexes.conversationsByOwnerTask} ON ${tables.conversations} (owner_task_id, id)`,
|
|
26
|
+
`CREATE TABLE ${tables.entries} (
|
|
27
|
+
id INTEGER PRIMARY KEY,
|
|
28
|
+
conversation_id INTEGER NOT NULL,
|
|
29
|
+
head INTEGER,
|
|
30
|
+
commit_seq INTEGER NOT NULL,
|
|
31
|
+
record TEXT NOT NULL CHECK (json_valid(record))
|
|
32
|
+
) STRICT`,
|
|
33
|
+
`CREATE INDEX ${indexes.entriesByConversation} ON ${tables.entries} (conversation_id, id DESC)`,
|
|
34
|
+
`CREATE INDEX ${indexes.entryHeadsByConversation} ON ${tables.entries} (conversation_id, id DESC) WHERE head IS NOT NULL`,
|
|
35
|
+
`CREATE TABLE ${tables.tasks} (
|
|
36
|
+
id INTEGER PRIMARY KEY,
|
|
37
|
+
conversation_id INTEGER NOT NULL,
|
|
38
|
+
kind TEXT NOT NULL,
|
|
39
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'waiting', 'completing', 'terminal')),
|
|
40
|
+
abort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)),
|
|
41
|
+
background INTEGER NOT NULL CHECK (background IN (0, 1)),
|
|
42
|
+
record TEXT NOT NULL CHECK (json_valid(record))
|
|
43
|
+
) STRICT`,
|
|
44
|
+
`CREATE INDEX ${indexes.tasksByStatus} ON ${tables.tasks} (status, id)`,
|
|
45
|
+
`CREATE INDEX ${indexes.tasksByConversation} ON ${tables.tasks} (conversation_id, id)`,
|
|
46
|
+
`CREATE INDEX ${indexes.tasksByKind} ON ${tables.tasks} (kind, id)`,
|
|
47
|
+
`CREATE INDEX ${indexes.tasksByAbortRequested} ON ${tables.tasks} (abort_requested, id)`,
|
|
48
|
+
`CREATE INDEX ${indexes.tasksByBackground} ON ${tables.tasks} (background, id)`,
|
|
49
|
+
`CREATE TABLE ${tables.submissions} (
|
|
50
|
+
id INTEGER PRIMARY KEY,
|
|
51
|
+
conversation_id INTEGER NOT NULL,
|
|
52
|
+
request_id TEXT,
|
|
53
|
+
status TEXT NOT NULL CHECK (status IN ('queued', 'placed', 'done', 'unanswered')),
|
|
54
|
+
record TEXT NOT NULL CHECK (json_valid(record))
|
|
55
|
+
) STRICT`,
|
|
56
|
+
`CREATE INDEX ${indexes.submissionsByRequest} ON ${tables.submissions} (conversation_id, request_id)`,
|
|
57
|
+
`CREATE INDEX ${indexes.submissionsByConversation} ON ${tables.submissions} (conversation_id, id)`,
|
|
58
|
+
`CREATE INDEX ${indexes.submissionsByStatus} ON ${tables.submissions} (status, id)`,
|
|
59
|
+
`CREATE TABLE ${tables.documents} (
|
|
60
|
+
id INTEGER PRIMARY KEY,
|
|
61
|
+
kind TEXT NOT NULL,
|
|
62
|
+
family INTEGER NOT NULL CHECK (family IN (0, 1)),
|
|
63
|
+
key_value TEXT NOT NULL,
|
|
64
|
+
scope_kind TEXT NOT NULL CHECK (scope_kind IN ('session', 'conversation', 'task')),
|
|
65
|
+
owner_id INTEGER NOT NULL,
|
|
66
|
+
created_at INTEGER NOT NULL,
|
|
67
|
+
retired_at INTEGER,
|
|
68
|
+
record TEXT NOT NULL CHECK (json_valid(record))
|
|
69
|
+
) STRICT`,
|
|
70
|
+
`CREATE INDEX ${indexes.documentsByAddress}
|
|
71
|
+
ON ${tables.documents} (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at)`,
|
|
72
|
+
`CREATE INDEX ${indexes.documentsByScope} ON ${tables.documents} (scope_kind, owner_id, id)`,
|
|
73
|
+
`CREATE INDEX ${indexes.documentsByScopeKind} ON ${tables.documents} (scope_kind, owner_id, kind, id)`,
|
|
74
|
+
`CREATE TABLE ${tables.documentRevisions} (
|
|
75
|
+
document_id INTEGER NOT NULL,
|
|
76
|
+
seq INTEGER NOT NULL,
|
|
77
|
+
kind TEXT NOT NULL CHECK (kind IN ('base', 'delta')),
|
|
78
|
+
version INTEGER NOT NULL,
|
|
79
|
+
content TEXT NOT NULL CHECK (json_valid(content)),
|
|
80
|
+
PRIMARY KEY (document_id, seq)
|
|
81
|
+
) STRICT`,
|
|
82
|
+
`CREATE INDEX ${indexes.documentRevisionsByKind} ON ${tables.documentRevisions} (document_id, kind, seq DESC)`,
|
|
83
|
+
];
|
|
84
|
+
// Version 2 adds the `sleeping` task status. SQLite cannot change a CHECK constraint in place, so the table is rebuilt.
|
|
85
|
+
const rebuiltTasks = `${tables.tasks}_v2`;
|
|
86
|
+
const sleepingStatus = [
|
|
87
|
+
`CREATE TABLE ${rebuiltTasks} (
|
|
88
|
+
id INTEGER PRIMARY KEY,
|
|
89
|
+
conversation_id INTEGER NOT NULL,
|
|
90
|
+
kind TEXT NOT NULL,
|
|
91
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'waiting', 'sleeping', 'completing', 'terminal')),
|
|
92
|
+
abort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)),
|
|
93
|
+
background INTEGER NOT NULL CHECK (background IN (0, 1)),
|
|
94
|
+
record TEXT NOT NULL CHECK (json_valid(record))
|
|
95
|
+
) STRICT`,
|
|
96
|
+
`INSERT INTO ${rebuiltTasks} (id, conversation_id, kind, status, abort_requested, background, record)
|
|
97
|
+
SELECT id, conversation_id, kind, status, abort_requested, background, record FROM ${tables.tasks}`,
|
|
98
|
+
`DROP TABLE ${tables.tasks}`,
|
|
99
|
+
`ALTER TABLE ${rebuiltTasks} RENAME TO ${tables.tasks}`,
|
|
100
|
+
`CREATE INDEX ${indexes.tasksByStatus} ON ${tables.tasks} (status, id)`,
|
|
101
|
+
`CREATE INDEX ${indexes.tasksByConversation} ON ${tables.tasks} (conversation_id, id)`,
|
|
102
|
+
`CREATE INDEX ${indexes.tasksByKind} ON ${tables.tasks} (kind, id)`,
|
|
103
|
+
`CREATE INDEX ${indexes.tasksByAbortRequested} ON ${tables.tasks} (abort_requested, id)`,
|
|
104
|
+
`CREATE INDEX ${indexes.tasksByBackground} ON ${tables.tasks} (background, id)`,
|
|
105
|
+
];
|
|
106
|
+
return [
|
|
107
|
+
{ version: 1, statements: initialSchema },
|
|
108
|
+
{ version: 2, statements: sleepingStatus },
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
export const SQLITE_MIGRATIONS = createSqliteMigrations(DEFAULT_SQLITE_SCHEMA);
|
|
81
112
|
export const CURRENT_SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.at(-1)?.version ?? 0;
|
|
82
113
|
/** Apply all pending schema migrations atomically. */
|
|
83
114
|
export async function applySqliteMigrations(database, migrations = SQLITE_MIGRATIONS) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
await database.transaction(async (transaction) => {
|
|
90
|
-
await transaction.exec(`CREATE TABLE IF NOT EXISTS durable_schema (
|
|
91
|
-
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
92
|
-
version INTEGER NOT NULL CHECK (version >= 0)
|
|
93
|
-
) STRICT`);
|
|
94
|
-
await transaction.run("INSERT OR IGNORE INTO durable_schema (singleton, version) VALUES (1, 0)");
|
|
95
|
-
const row = await transaction.get("SELECT version FROM durable_schema WHERE singleton = 1");
|
|
96
|
-
if (row === undefined)
|
|
97
|
-
throw new Error("Durable SQLite schema metadata is missing");
|
|
98
|
-
const currentVersion = migrations.at(-1)?.version ?? 0;
|
|
99
|
-
if (row.version > currentVersion) {
|
|
100
|
-
throw new Error(`Durable SQLite schema version ${row.version} is newer than supported version ${currentVersion}`);
|
|
101
|
-
}
|
|
102
|
-
for (const migration of migrations) {
|
|
103
|
-
if (migration.version <= row.version)
|
|
104
|
-
continue;
|
|
105
|
-
for (const statement of migration.statements)
|
|
106
|
-
await transaction.exec(statement);
|
|
107
|
-
await transaction.run("UPDATE durable_schema SET version = ? WHERE singleton = 1", migration.version);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
115
|
+
await applySqliteMigrationsForSchema(database, DEFAULT_SQLITE_SCHEMA, migrations);
|
|
116
|
+
}
|
|
117
|
+
/** Apply migrations rendered for a particular schema. */
|
|
118
|
+
export async function applySqliteMigrationsForSchema(database, schema, migrations = createSqliteMigrations(schema)) {
|
|
119
|
+
await applyMigrations(database, migrations, { table: schema.tables.durableSchema });
|
|
110
120
|
}
|
|
111
121
|
//# sourceMappingURL=migrations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../../src/storage/sqlite/migrations.ts"],"names":[],"mappings":"AAOA,uGAAuG;AACvG,MAAM,cAAc,GAAsB;IACzC;;;;UAIS;IACT,gFAAgF;IAChF;;;UAGS;IACT;;;;;UAKS;IACT,+FAA+F;IAC/F,+EAA+E;IAC/E;;;;;;UAMS;IACT,4EAA4E;IAC5E,uGAAuG;IACvG;;;;;;;;UAQS;IACT,oDAAoD;IACpD,mEAAmE;IACnE,gDAAgD;IAChD,sEAAsE;IACtE,4DAA4D;IAC5D;;;;;;UAMS;IACT,kFAAkF;IAClF,+EAA+E;IAC/E,gEAAgE;IAChE;;;;;;;;;;UAUS;IACT;4FAC2F;IAC3F,yEAAyE;IACzE,oFAAoF;IACpF;;;;;;;UAOS;IACT,6FAA6F;CAC7F,CAAC;AAEF,+FAA+F;AAC/F,MAAM,CAAC,MAAM,iBAAiB,GAA+B,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC,CAAC;AAE1G,MAAM,CAAC,MAAM,6BAA6B,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC;AAIpF,sDAAsD;AACtD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,QAAwB,EACxB,UAAU,GAA+B,iBAAiB;IAE1D,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACxD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;QAC1F,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE;QAChD,MAAM,WAAW,CAAC,IAAI,CAAC;;;WAGd,CAAC,CAAC;QACX,MAAM,WAAW,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;QACjG,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,GAAG,CAAY,wDAAwD,CAAC,CAAC;QACvG,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACpF,MAAM,cAAc,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC;QACvD,IAAI,GAAG,CAAC,OAAO,GAAG,cAAc,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACd,iCAAiC,GAAG,CAAC,OAAO,oCAAoC,cAAc,EAAE,CAChG,CAAC;QACH,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACpC,IAAI,SAAS,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO;gBAAE,SAAS;YAC/C,KAAK,MAAM,SAAS,IAAI,SAAS,CAAC,UAAU;gBAAE,MAAM,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAChF,MAAM,WAAW,CAAC,GAAG,CAAC,2DAA2D,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;QACvG,CAAC;IACF,CAAC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { SqliteDatabase } from \"./database.ts\";\n\nexport type SqliteMigration = {\n\treadonly version: number;\n\treadonly statements: readonly string[];\n};\n\n// next_id is TEXT because node:sqlite rejects INTEGER results outside JavaScript's safe integer range.\nconst INITIAL_SCHEMA: readonly string[] = [\n\t`CREATE TABLE durable_metadata (\n\t\tsingleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n\t\tnext_id TEXT NOT NULL,\n\t\tnext_seq INTEGER NOT NULL\n\t) STRICT`,\n\t`INSERT INTO durable_metadata (singleton, next_id, next_seq) VALUES (1, '2', 1)`,\n\t`CREATE TABLE record_ids (\n\t\tid INTEGER PRIMARY KEY,\n\t\trecord_type TEXT NOT NULL CHECK (record_type IN ('conversation', 'entry', 'task', 'submission', 'document'))\n\t) STRICT`,\n\t`CREATE TABLE conversations (\n\t\tid INTEGER PRIMARY KEY,\n\t\towner_conversation_id INTEGER,\n\t\towner_task_id INTEGER,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX conversations_by_owner_conversation ON conversations (owner_conversation_id, id)\",\n\t\"CREATE INDEX conversations_by_owner_task ON conversations (owner_task_id, id)\",\n\t`CREATE TABLE entries (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\thead INTEGER,\n\t\tcommit_seq INTEGER NOT NULL,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX entries_by_conversation ON entries (conversation_id, id DESC)\",\n\t\"CREATE INDEX entry_heads_by_conversation ON entries (conversation_id, id DESC) WHERE head IS NOT NULL\",\n\t`CREATE TABLE tasks (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\tkind TEXT NOT NULL,\n\t\tstatus TEXT NOT NULL CHECK (status IN ('pending', 'running', 'waiting', 'completing', 'terminal')),\n\t\tabort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)),\n\t\tbackground INTEGER NOT NULL CHECK (background IN (0, 1)),\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX tasks_by_status ON tasks (status, id)\",\n\t\"CREATE INDEX tasks_by_conversation ON tasks (conversation_id, id)\",\n\t\"CREATE INDEX tasks_by_kind ON tasks (kind, id)\",\n\t\"CREATE INDEX tasks_by_abort_requested ON tasks (abort_requested, id)\",\n\t\"CREATE INDEX tasks_by_background ON tasks (background, id)\",\n\t`CREATE TABLE submissions (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\trequest_id TEXT,\n\t\tstatus TEXT NOT NULL CHECK (status IN ('queued', 'placed', 'done', 'unanswered')),\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX submissions_by_request ON submissions (conversation_id, request_id)\",\n\t\"CREATE INDEX submissions_by_conversation ON submissions (conversation_id, id)\",\n\t\"CREATE INDEX submissions_by_status ON submissions (status, id)\",\n\t`CREATE TABLE documents (\n\t\tid INTEGER PRIMARY KEY,\n\t\tkind TEXT NOT NULL,\n\t\tfamily INTEGER NOT NULL CHECK (family IN (0, 1)),\n\t\tkey_value TEXT NOT NULL,\n\t\tscope_kind TEXT NOT NULL CHECK (scope_kind IN ('session', 'conversation', 'task')),\n\t\towner_id INTEGER NOT NULL,\n\t\tcreated_at INTEGER NOT NULL,\n\t\tretired_at INTEGER,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t`CREATE INDEX documents_by_address\n\t\tON documents (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at)`,\n\t\"CREATE INDEX documents_by_scope ON documents (scope_kind, owner_id, id)\",\n\t\"CREATE INDEX documents_by_scope_kind ON documents (scope_kind, owner_id, kind, id)\",\n\t`CREATE TABLE document_revisions (\n\t\tdocument_id INTEGER NOT NULL,\n\t\tseq INTEGER NOT NULL,\n\t\tkind TEXT NOT NULL CHECK (kind IN ('base', 'delta')),\n\t\tversion INTEGER NOT NULL,\n\t\tcontent TEXT NOT NULL CHECK (json_valid(content)),\n\t\tPRIMARY KEY (document_id, seq)\n\t) STRICT`,\n\t\"CREATE INDEX document_revisions_by_kind ON document_revisions (document_id, kind, seq DESC)\",\n];\n\n/** Immutable, ordered schema history. Append new migrations after the initial schema ships. */\nexport const SQLITE_MIGRATIONS: readonly SqliteMigration[] = [{ version: 1, statements: INITIAL_SCHEMA }];\n\nexport const CURRENT_SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.at(-1)?.version ?? 0;\n\ntype SchemaRow = { readonly version: number };\n\n/** Apply all pending schema migrations atomically. */\nexport async function applySqliteMigrations(\n\tdatabase: SqliteDatabase,\n\tmigrations: readonly SqliteMigration[] = SQLITE_MIGRATIONS,\n): Promise<void> {\n\tfor (let index = 0; index < migrations.length; index++) {\n\t\tif (migrations[index]?.version !== index + 1) {\n\t\t\tthrow new Error(\"Durable SQLite migrations must have contiguous versions starting at 1\");\n\t\t}\n\t}\n\n\tawait database.transaction(async (transaction) => {\n\t\tawait transaction.exec(`CREATE TABLE IF NOT EXISTS durable_schema (\n\t\t\tsingleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n\t\t\tversion INTEGER NOT NULL CHECK (version >= 0)\n\t\t) STRICT`);\n\t\tawait transaction.run(\"INSERT OR IGNORE INTO durable_schema (singleton, version) VALUES (1, 0)\");\n\t\tconst row = await transaction.get<SchemaRow>(\"SELECT version FROM durable_schema WHERE singleton = 1\");\n\t\tif (row === undefined) throw new Error(\"Durable SQLite schema metadata is missing\");\n\t\tconst currentVersion = migrations.at(-1)?.version ?? 0;\n\t\tif (row.version > currentVersion) {\n\t\t\tthrow new Error(\n\t\t\t\t`Durable SQLite schema version ${row.version} is newer than supported version ${currentVersion}`,\n\t\t\t);\n\t\t}\n\t\tfor (const migration of migrations) {\n\t\t\tif (migration.version <= row.version) continue;\n\t\t\tfor (const statement of migration.statements) await transaction.exec(statement);\n\t\t\tawait transaction.run(\"UPDATE durable_schema SET version = ? WHERE singleton = 1\", migration.version);\n\t\t}\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../../src/storage/sqlite/migrations.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tapplySqliteMigrations as applyMigrations,\n\ttype SqliteDatabase,\n\ttype SqliteMigration,\n} from \"@earendil-works/chord/sqlite\";\nimport { DEFAULT_SQLITE_SCHEMA, type SqliteSchema } from \"./schema.ts\";\n\nexport type { SqliteMigration } from \"@earendil-works/chord/sqlite\";\n\n/** Build immutable, ordered schema history for a set of SQLite identifiers. */\nexport function createSqliteMigrations(schema: SqliteSchema): readonly SqliteMigration[] {\n\tconst { tables, indexes } = schema;\n\t// next_id is TEXT because node:sqlite rejects INTEGER results outside JavaScript's safe integer range.\n\tconst initialSchema: readonly string[] = [\n\t\t`CREATE TABLE ${tables.durableMetadata} (\n\t\t\tsingleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n\t\t\tnext_id TEXT NOT NULL,\n\t\t\tnext_seq INTEGER NOT NULL\n\t\t) STRICT`,\n\t\t`INSERT INTO ${tables.durableMetadata} (singleton, next_id, next_seq) VALUES (1, '2', 1)`,\n\t\t`CREATE TABLE ${tables.recordIds} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\trecord_type TEXT NOT NULL CHECK (record_type IN ('conversation', 'entry', 'task', 'submission', 'document'))\n\t\t) STRICT`,\n\t\t`CREATE TABLE ${tables.conversations} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\towner_conversation_id INTEGER,\n\t\t\towner_task_id INTEGER,\n\t\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t\t) STRICT`,\n\t\t`CREATE INDEX ${indexes.conversationsByOwnerConversation} ON ${tables.conversations} (owner_conversation_id, id)`,\n\t\t`CREATE INDEX ${indexes.conversationsByOwnerTask} ON ${tables.conversations} (owner_task_id, id)`,\n\t\t`CREATE TABLE ${tables.entries} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\tconversation_id INTEGER NOT NULL,\n\t\t\thead INTEGER,\n\t\t\tcommit_seq INTEGER NOT NULL,\n\t\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t\t) STRICT`,\n\t\t`CREATE INDEX ${indexes.entriesByConversation} ON ${tables.entries} (conversation_id, id DESC)`,\n\t\t`CREATE INDEX ${indexes.entryHeadsByConversation} ON ${tables.entries} (conversation_id, id DESC) WHERE head IS NOT NULL`,\n\t\t`CREATE TABLE ${tables.tasks} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\tconversation_id INTEGER NOT NULL,\n\t\t\tkind TEXT NOT NULL,\n\t\t\tstatus TEXT NOT NULL CHECK (status IN ('pending', 'running', 'waiting', 'completing', 'terminal')),\n\t\t\tabort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)),\n\t\t\tbackground INTEGER NOT NULL CHECK (background IN (0, 1)),\n\t\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t\t) STRICT`,\n\t\t`CREATE INDEX ${indexes.tasksByStatus} ON ${tables.tasks} (status, id)`,\n\t\t`CREATE INDEX ${indexes.tasksByConversation} ON ${tables.tasks} (conversation_id, id)`,\n\t\t`CREATE INDEX ${indexes.tasksByKind} ON ${tables.tasks} (kind, id)`,\n\t\t`CREATE INDEX ${indexes.tasksByAbortRequested} ON ${tables.tasks} (abort_requested, id)`,\n\t\t`CREATE INDEX ${indexes.tasksByBackground} ON ${tables.tasks} (background, id)`,\n\t\t`CREATE TABLE ${tables.submissions} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\tconversation_id INTEGER NOT NULL,\n\t\t\trequest_id TEXT,\n\t\t\tstatus TEXT NOT NULL CHECK (status IN ('queued', 'placed', 'done', 'unanswered')),\n\t\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t\t) STRICT`,\n\t\t`CREATE INDEX ${indexes.submissionsByRequest} ON ${tables.submissions} (conversation_id, request_id)`,\n\t\t`CREATE INDEX ${indexes.submissionsByConversation} ON ${tables.submissions} (conversation_id, id)`,\n\t\t`CREATE INDEX ${indexes.submissionsByStatus} ON ${tables.submissions} (status, id)`,\n\t\t`CREATE TABLE ${tables.documents} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\tkind TEXT NOT NULL,\n\t\t\tfamily INTEGER NOT NULL CHECK (family IN (0, 1)),\n\t\t\tkey_value TEXT NOT NULL,\n\t\t\tscope_kind TEXT NOT NULL CHECK (scope_kind IN ('session', 'conversation', 'task')),\n\t\t\towner_id INTEGER NOT NULL,\n\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\tretired_at INTEGER,\n\t\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t\t) STRICT`,\n\t\t`CREATE INDEX ${indexes.documentsByAddress}\n\t\t\tON ${tables.documents} (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at)`,\n\t\t`CREATE INDEX ${indexes.documentsByScope} ON ${tables.documents} (scope_kind, owner_id, id)`,\n\t\t`CREATE INDEX ${indexes.documentsByScopeKind} ON ${tables.documents} (scope_kind, owner_id, kind, id)`,\n\t\t`CREATE TABLE ${tables.documentRevisions} (\n\t\t\tdocument_id INTEGER NOT NULL,\n\t\t\tseq INTEGER NOT NULL,\n\t\t\tkind TEXT NOT NULL CHECK (kind IN ('base', 'delta')),\n\t\t\tversion INTEGER NOT NULL,\n\t\t\tcontent TEXT NOT NULL CHECK (json_valid(content)),\n\t\t\tPRIMARY KEY (document_id, seq)\n\t\t) STRICT`,\n\t\t`CREATE INDEX ${indexes.documentRevisionsByKind} ON ${tables.documentRevisions} (document_id, kind, seq DESC)`,\n\t];\n\t// Version 2 adds the `sleeping` task status. SQLite cannot change a CHECK constraint in place, so the table is rebuilt.\n\tconst rebuiltTasks = `${tables.tasks}_v2`;\n\tconst sleepingStatus: readonly string[] = [\n\t\t`CREATE TABLE ${rebuiltTasks} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\tconversation_id INTEGER NOT NULL,\n\t\t\tkind TEXT NOT NULL,\n\t\t\tstatus TEXT NOT NULL CHECK (status IN ('pending', 'running', 'waiting', 'sleeping', 'completing', 'terminal')),\n\t\t\tabort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)),\n\t\t\tbackground INTEGER NOT NULL CHECK (background IN (0, 1)),\n\t\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t\t) STRICT`,\n\t\t`INSERT INTO ${rebuiltTasks} (id, conversation_id, kind, status, abort_requested, background, record)\n\t\t\tSELECT id, conversation_id, kind, status, abort_requested, background, record FROM ${tables.tasks}`,\n\t\t`DROP TABLE ${tables.tasks}`,\n\t\t`ALTER TABLE ${rebuiltTasks} RENAME TO ${tables.tasks}`,\n\t\t`CREATE INDEX ${indexes.tasksByStatus} ON ${tables.tasks} (status, id)`,\n\t\t`CREATE INDEX ${indexes.tasksByConversation} ON ${tables.tasks} (conversation_id, id)`,\n\t\t`CREATE INDEX ${indexes.tasksByKind} ON ${tables.tasks} (kind, id)`,\n\t\t`CREATE INDEX ${indexes.tasksByAbortRequested} ON ${tables.tasks} (abort_requested, id)`,\n\t\t`CREATE INDEX ${indexes.tasksByBackground} ON ${tables.tasks} (background, id)`,\n\t];\n\treturn [\n\t\t{ version: 1, statements: initialSchema },\n\t\t{ version: 2, statements: sleepingStatus },\n\t];\n}\n\nexport const SQLITE_MIGRATIONS: readonly SqliteMigration[] = createSqliteMigrations(DEFAULT_SQLITE_SCHEMA);\n\nexport const CURRENT_SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.at(-1)?.version ?? 0;\n\n/** Apply all pending schema migrations atomically. */\nexport async function applySqliteMigrations(\n\tdatabase: SqliteDatabase,\n\tmigrations: readonly SqliteMigration[] = SQLITE_MIGRATIONS,\n): Promise<void> {\n\tawait applySqliteMigrationsForSchema(database, DEFAULT_SQLITE_SCHEMA, migrations);\n}\n\n/** Apply migrations rendered for a particular schema. */\nexport async function applySqliteMigrationsForSchema(\n\tdatabase: SqliteDatabase,\n\tschema: SqliteSchema,\n\tmigrations: readonly SqliteMigration[] = createSqliteMigrations(schema),\n): Promise<void> {\n\tawait applyMigrations(database, migrations, { table: schema.tables.durableSchema });\n}\n"]}
|
|
@@ -1,41 +1,8 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/** SQLite WAL auto-checkpoint threshold. SQLite and this adapter default to 1,000 pages; 0 disables it. */
|
|
8
|
-
readonly walAutoCheckpointPages?: number;
|
|
9
|
-
/** Time SQLite waits for a competing file lock. SQLite defaults to 0; this adapter defaults to 5,000 ms. */
|
|
10
|
-
readonly busyTimeoutMs?: number;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Executes SQL on one connection. Prepared statements are cached per connection by SQL text, so the
|
|
14
|
-
* database and its transaction handles share them across transactions.
|
|
15
|
-
*/
|
|
16
|
-
declare abstract class NodeSqliteExecutor implements SqliteExecutor {
|
|
17
|
-
protected readonly database: DatabaseSync;
|
|
18
|
-
protected readonly statements: Map<string, StatementSync>;
|
|
19
|
-
constructor(database: DatabaseSync, statements: Map<string, StatementSync>);
|
|
20
|
-
exec(sql: string): Promise<void>;
|
|
21
|
-
run(sql: string, ...params: SqliteValue[]): Promise<void>;
|
|
22
|
-
get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined>;
|
|
23
|
-
all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]>;
|
|
24
|
-
protected abstract runOperation<T>(operation: () => T): Promise<T>;
|
|
25
|
-
private statement;
|
|
26
|
-
}
|
|
27
|
-
/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */
|
|
28
|
-
export declare class NodeSqliteDatabase extends NodeSqliteExecutor implements SqliteDatabase {
|
|
29
|
-
private readonly access;
|
|
30
|
-
private closed;
|
|
31
|
-
constructor(database: DatabaseSync);
|
|
32
|
-
transaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T>;
|
|
33
|
-
close(): Promise<void>;
|
|
34
|
-
protected runOperation<T>(operation: () => T): Promise<T>;
|
|
35
|
-
}
|
|
36
|
-
/** Open and configure a Node-backed SQLite database facade. */
|
|
37
|
-
export declare function openNodeSqliteDatabase(path: string, options?: NodeSqliteStorageOptions): Promise<NodeSqliteDatabase>;
|
|
38
|
-
/** Open or create file-backed durable storage using Node's built-in SQLite. */
|
|
1
|
+
import { type NodeSqliteDatabaseOptions } from "@earendil-works/chord/sqlite/node";
|
|
2
|
+
import { SqliteStorage, type SqliteStorageOptions } from "./storage.ts";
|
|
3
|
+
export { NodeSqliteDatabase, type NodeSqliteDatabaseOptions, openNodeSqliteDatabase, } from "@earendil-works/chord/sqlite/node";
|
|
4
|
+
/** Node SQLite connection settings plus durable storage options. */
|
|
5
|
+
export type NodeSqliteStorageOptions = NodeSqliteDatabaseOptions & SqliteStorageOptions;
|
|
6
|
+
/** Open or create file-backed durable storage using Node's built-in SQLite, optionally under a table prefix. */
|
|
39
7
|
export declare function openNodeSqliteStorage(path: string, options?: NodeSqliteStorageOptions): Promise<SqliteStorage>;
|
|
40
|
-
export {};
|
|
41
8
|
//# sourceMappingURL=node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,yBAAyB,EAA0B,MAAM,mCAAmC,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAExE,OAAO,EACN,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,sBAAsB,GACtB,MAAM,mCAAmC,CAAC;AAE3C,oEAAoE;AACpE,MAAM,MAAM,wBAAwB,GAAG,yBAAyB,GAAG,oBAAoB,CAAC;AAExF,gHAAgH;AAChH,wBAAsB,qBAAqB,CAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,wBAA6B,GACpC,OAAO,CAAC,aAAa,CAAC,CAIxB"}
|
|
@@ -1,179 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { DatabaseSync } from "node:sqlite";
|
|
1
|
+
import { validateSqliteSchemaPrefix } from "@earendil-works/chord/sqlite";
|
|
2
|
+
import { openNodeSqliteDatabase } from "@earendil-works/chord/sqlite/node";
|
|
4
3
|
import { SqliteStorage } from "./storage.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const ignore = () => { };
|
|
8
|
-
/**
|
|
9
|
-
* Runs operations in call order. An operation starts immediately when nothing is running or waiting;
|
|
10
|
-
* otherwise it waits for everything before it. An asynchronous operation holds the queue until it settles.
|
|
11
|
-
*/
|
|
12
|
-
class SerialOperationQueue {
|
|
13
|
-
tail = Promise.resolve();
|
|
14
|
-
pending = 0;
|
|
15
|
-
run(operation) {
|
|
16
|
-
if (this.pending > 0)
|
|
17
|
-
return this.enqueue(operation);
|
|
18
|
-
try {
|
|
19
|
-
return Promise.resolve(operation());
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
return Promise.reject(error);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
runAsync(operation) {
|
|
26
|
-
if (this.pending > 0)
|
|
27
|
-
return this.enqueue(operation);
|
|
28
|
-
this.pending++;
|
|
29
|
-
// Publish the barrier before the operation starts, so calls it makes synchronously wait behind it.
|
|
30
|
-
const { promise: barrier, resolve: releaseBarrier } = Promise.withResolvers();
|
|
31
|
-
this.tail = barrier;
|
|
32
|
-
let started;
|
|
33
|
-
try {
|
|
34
|
-
started = operation();
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
started = Promise.reject(error);
|
|
38
|
-
}
|
|
39
|
-
return started.finally(() => {
|
|
40
|
-
this.pending--;
|
|
41
|
-
releaseBarrier();
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
enqueue(operation) {
|
|
45
|
-
this.pending++;
|
|
46
|
-
return this.release(this.tail.then(operation));
|
|
47
|
-
}
|
|
48
|
-
release(operation) {
|
|
49
|
-
const settled = operation.finally(() => {
|
|
50
|
-
this.pending--;
|
|
51
|
-
});
|
|
52
|
-
this.tail = settled.then(ignore, ignore);
|
|
53
|
-
return settled;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Executes SQL on one connection. Prepared statements are cached per connection by SQL text, so the
|
|
58
|
-
* database and its transaction handles share them across transactions.
|
|
59
|
-
*/
|
|
60
|
-
class NodeSqliteExecutor {
|
|
61
|
-
database;
|
|
62
|
-
statements;
|
|
63
|
-
constructor(database, statements) {
|
|
64
|
-
this.database = database;
|
|
65
|
-
this.statements = statements;
|
|
66
|
-
}
|
|
67
|
-
exec(sql) {
|
|
68
|
-
return this.runOperation(() => {
|
|
69
|
-
this.database.exec(sql);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
run(sql, ...params) {
|
|
73
|
-
return this.runOperation(() => {
|
|
74
|
-
this.statement(sql).run(...params);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
get(sql, ...params) {
|
|
78
|
-
return this.runOperation(() => this.statement(sql).get(...params));
|
|
79
|
-
}
|
|
80
|
-
all(sql, ...params) {
|
|
81
|
-
return this.runOperation(() => this.statement(sql).all(...params));
|
|
82
|
-
}
|
|
83
|
-
statement(sql) {
|
|
84
|
-
let statement = this.statements.get(sql);
|
|
85
|
-
if (statement === undefined) {
|
|
86
|
-
statement = this.database.prepare(sql);
|
|
87
|
-
this.statements.set(sql, statement);
|
|
88
|
-
}
|
|
89
|
-
return statement;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
class NodeSqliteTransaction extends NodeSqliteExecutor {
|
|
93
|
-
scope;
|
|
94
|
-
constructor(database, statements, scope) {
|
|
95
|
-
super(database, statements);
|
|
96
|
-
this.scope = scope;
|
|
97
|
-
}
|
|
98
|
-
async runOperation(operation) {
|
|
99
|
-
if (!this.scope.active)
|
|
100
|
-
throw new Error("SQLite transaction handle is no longer active");
|
|
101
|
-
return operation();
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */
|
|
105
|
-
export class NodeSqliteDatabase extends NodeSqliteExecutor {
|
|
106
|
-
access = new SerialOperationQueue();
|
|
107
|
-
closed = false;
|
|
108
|
-
constructor(database) {
|
|
109
|
-
super(database, new Map());
|
|
110
|
-
}
|
|
111
|
-
transaction(callback) {
|
|
112
|
-
return this.access.runAsync(async () => {
|
|
113
|
-
this.database.exec("BEGIN IMMEDIATE");
|
|
114
|
-
const scope = { active: true };
|
|
115
|
-
try {
|
|
116
|
-
const result = await callback(new NodeSqliteTransaction(this.database, this.statements, scope));
|
|
117
|
-
scope.active = false;
|
|
118
|
-
this.database.exec("COMMIT");
|
|
119
|
-
return result;
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
scope.active = false;
|
|
123
|
-
try {
|
|
124
|
-
this.database.exec("ROLLBACK");
|
|
125
|
-
}
|
|
126
|
-
catch (rollbackError) {
|
|
127
|
-
throw new AggregateError([error, rollbackError], "SQLite transaction failed and rollback failed");
|
|
128
|
-
}
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
close() {
|
|
134
|
-
return this.access.run(() => {
|
|
135
|
-
if (this.closed)
|
|
136
|
-
return;
|
|
137
|
-
this.closed = true;
|
|
138
|
-
this.statements.clear();
|
|
139
|
-
try {
|
|
140
|
-
this.database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
141
|
-
}
|
|
142
|
-
finally {
|
|
143
|
-
this.database.close();
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
runOperation(operation) {
|
|
148
|
-
return this.access.run(operation);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/** Open and configure a Node-backed SQLite database facade. */
|
|
152
|
-
export async function openNodeSqliteDatabase(path, options = {}) {
|
|
153
|
-
const checkpointPages = options.walAutoCheckpointPages ?? DEFAULT_WAL_AUTO_CHECKPOINT_PAGES;
|
|
154
|
-
const timeout = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;
|
|
155
|
-
if (path !== ":memory:")
|
|
156
|
-
await mkdir(dirname(path), { recursive: true });
|
|
157
|
-
const database = new DatabaseSync(path, { timeout });
|
|
158
|
-
const adapter = new NodeSqliteDatabase(database);
|
|
159
|
-
try {
|
|
160
|
-
await adapter.exec("PRAGMA journal_mode = WAL");
|
|
161
|
-
await adapter.exec("PRAGMA synchronous = NORMAL");
|
|
162
|
-
await adapter.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);
|
|
163
|
-
return adapter;
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
try {
|
|
167
|
-
await adapter.close();
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
// Preserve the configuration failure.
|
|
171
|
-
}
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/** Open or create file-backed durable storage using Node's built-in SQLite. */
|
|
4
|
+
export { NodeSqliteDatabase, openNodeSqliteDatabase, } from "@earendil-works/chord/sqlite/node";
|
|
5
|
+
/** Open or create file-backed durable storage using Node's built-in SQLite, optionally under a table prefix. */
|
|
176
6
|
export async function openNodeSqliteStorage(path, options = {}) {
|
|
177
|
-
|
|
7
|
+
const { prefix, ...databaseOptions } = options;
|
|
8
|
+
if (prefix !== undefined)
|
|
9
|
+
validateSqliteSchemaPrefix(prefix);
|
|
10
|
+
return SqliteStorage.open(await openNodeSqliteDatabase(path, databaseOptions), { prefix });
|
|
178
11
|
}
|
|
179
12
|
//# sourceMappingURL=node.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../../src/storage/sqlite/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { StatementSync } from \"node:sqlite\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport type { SqliteDatabase, SqliteExecutor, SqliteValue } from \"./database.ts\";\nimport { SqliteStorage } from \"./storage.ts\";\n\n/** Node SQLite connection settings for a durable storage file. */\nexport type NodeSqliteStorageOptions = {\n\t/** SQLite WAL auto-checkpoint threshold. SQLite and this adapter default to 1,000 pages; 0 disables it. */\n\treadonly walAutoCheckpointPages?: number;\n\t/** Time SQLite waits for a competing file lock. SQLite defaults to 0; this adapter defaults to 5,000 ms. */\n\treadonly busyTimeoutMs?: number;\n};\n\nconst DEFAULT_WAL_AUTO_CHECKPOINT_PAGES = 1_000;\nconst DEFAULT_BUSY_TIMEOUT_MS = 5_000;\n\ntype TransactionScope = { active: boolean };\n\nconst ignore = (): void => {};\n\n/**\n * Runs operations in call order. An operation starts immediately when nothing is running or waiting;\n * otherwise it waits for everything before it. An asynchronous operation holds the queue until it settles.\n */\nclass SerialOperationQueue {\n\tprivate tail: Promise<void> = Promise.resolve();\n\tprivate pending = 0;\n\n\trun<T>(operation: () => T): Promise<T> {\n\t\tif (this.pending > 0) return this.enqueue(operation);\n\t\ttry {\n\t\t\treturn Promise.resolve(operation());\n\t\t} catch (error) {\n\t\t\treturn Promise.reject(error);\n\t\t}\n\t}\n\n\trunAsync<T>(operation: () => Promise<T>): Promise<T> {\n\t\tif (this.pending > 0) return this.enqueue(operation);\n\t\tthis.pending++;\n\t\t// Publish the barrier before the operation starts, so calls it makes synchronously wait behind it.\n\t\tconst { promise: barrier, resolve: releaseBarrier } = Promise.withResolvers<void>();\n\t\tthis.tail = barrier;\n\t\tlet started: Promise<T>;\n\t\ttry {\n\t\t\tstarted = operation();\n\t\t} catch (error) {\n\t\t\tstarted = Promise.reject(error);\n\t\t}\n\t\treturn started.finally(() => {\n\t\t\tthis.pending--;\n\t\t\treleaseBarrier();\n\t\t});\n\t}\n\n\tprivate enqueue<T>(operation: () => T | Promise<T>): Promise<T> {\n\t\tthis.pending++;\n\t\treturn this.release(this.tail.then(operation));\n\t}\n\n\tprivate release<T>(operation: Promise<T>): Promise<T> {\n\t\tconst settled = operation.finally(() => {\n\t\t\tthis.pending--;\n\t\t});\n\t\tthis.tail = settled.then(ignore, ignore);\n\t\treturn settled;\n\t}\n}\n\n/**\n * Executes SQL on one connection. Prepared statements are cached per connection by SQL text, so the\n * database and its transaction handles share them across transactions.\n */\nabstract class NodeSqliteExecutor implements SqliteExecutor {\n\tprotected readonly database: DatabaseSync;\n\tprotected readonly statements: Map<string, StatementSync>;\n\n\tconstructor(database: DatabaseSync, statements: Map<string, StatementSync>) {\n\t\tthis.database = database;\n\t\tthis.statements = statements;\n\t}\n\n\texec(sql: string): Promise<void> {\n\t\treturn this.runOperation(() => {\n\t\t\tthis.database.exec(sql);\n\t\t});\n\t}\n\n\trun(sql: string, ...params: SqliteValue[]): Promise<void> {\n\t\treturn this.runOperation(() => {\n\t\t\tthis.statement(sql).run(...params);\n\t\t});\n\t}\n\n\tget<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined> {\n\t\treturn this.runOperation(() => this.statement(sql).get(...params) as T | undefined);\n\t}\n\n\tall<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]> {\n\t\treturn this.runOperation(() => this.statement(sql).all(...params) as T[]);\n\t}\n\n\tprotected abstract runOperation<T>(operation: () => T): Promise<T>;\n\n\tprivate statement(sql: string): StatementSync {\n\t\tlet statement = this.statements.get(sql);\n\t\tif (statement === undefined) {\n\t\t\tstatement = this.database.prepare(sql);\n\t\t\tthis.statements.set(sql, statement);\n\t\t}\n\t\treturn statement;\n\t}\n}\n\nclass NodeSqliteTransaction extends NodeSqliteExecutor {\n\tprivate readonly scope: TransactionScope;\n\n\tconstructor(database: DatabaseSync, statements: Map<string, StatementSync>, scope: TransactionScope) {\n\t\tsuper(database, statements);\n\t\tthis.scope = scope;\n\t}\n\n\tprotected async runOperation<T>(operation: () => T): Promise<T> {\n\t\tif (!this.scope.active) throw new Error(\"SQLite transaction handle is no longer active\");\n\t\treturn operation();\n\t}\n}\n\n/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */\nexport class NodeSqliteDatabase extends NodeSqliteExecutor implements SqliteDatabase {\n\tprivate readonly access = new SerialOperationQueue();\n\tprivate closed = false;\n\n\tconstructor(database: DatabaseSync) {\n\t\tsuper(database, new Map());\n\t}\n\n\ttransaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T> {\n\t\treturn this.access.runAsync(async () => {\n\t\t\tthis.database.exec(\"BEGIN IMMEDIATE\");\n\t\t\tconst scope = { active: true };\n\t\t\ttry {\n\t\t\t\tconst result = await callback(new NodeSqliteTransaction(this.database, this.statements, scope));\n\t\t\t\tscope.active = false;\n\t\t\t\tthis.database.exec(\"COMMIT\");\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tscope.active = false;\n\t\t\t\ttry {\n\t\t\t\t\tthis.database.exec(\"ROLLBACK\");\n\t\t\t\t} catch (rollbackError) {\n\t\t\t\t\tthrow new AggregateError([error, rollbackError], \"SQLite transaction failed and rollback failed\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t}\n\n\tclose(): Promise<void> {\n\t\treturn this.access.run(() => {\n\t\t\tif (this.closed) return;\n\t\t\tthis.closed = true;\n\t\t\tthis.statements.clear();\n\t\t\ttry {\n\t\t\t\tthis.database.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n\t\t\t} finally {\n\t\t\t\tthis.database.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tprotected runOperation<T>(operation: () => T): Promise<T> {\n\t\treturn this.access.run(operation);\n\t}\n}\n\n/** Open and configure a Node-backed SQLite database facade. */\nexport async function openNodeSqliteDatabase(\n\tpath: string,\n\toptions: NodeSqliteStorageOptions = {},\n): Promise<NodeSqliteDatabase> {\n\tconst checkpointPages = options.walAutoCheckpointPages ?? DEFAULT_WAL_AUTO_CHECKPOINT_PAGES;\n\tconst timeout = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;\n\tif (path !== \":memory:\") await mkdir(dirname(path), { recursive: true });\n\tconst database = new DatabaseSync(path, { timeout });\n\tconst adapter = new NodeSqliteDatabase(database);\n\ttry {\n\t\tawait adapter.exec(\"PRAGMA journal_mode = WAL\");\n\t\tawait adapter.exec(\"PRAGMA synchronous = NORMAL\");\n\t\tawait adapter.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);\n\t\treturn adapter;\n\t} catch (error) {\n\t\ttry {\n\t\t\tawait adapter.close();\n\t\t} catch {\n\t\t\t// Preserve the configuration failure.\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/** Open or create file-backed durable storage using Node's built-in SQLite. */\nexport async function openNodeSqliteStorage(\n\tpath: string,\n\toptions: NodeSqliteStorageOptions = {},\n): Promise<SqliteStorage> {\n\treturn SqliteStorage.open(await openNodeSqliteDatabase(path, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../../src/storage/sqlite/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAkC,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3G,OAAO,EAAE,aAAa,EAA6B,MAAM,cAAc,CAAC;AAExE,OAAO,EACN,kBAAkB,EAElB,sBAAsB,GACtB,MAAM,mCAAmC,CAAC;AAK3C,gHAAgH;AAChH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,IAAY,EACZ,OAAO,GAA6B,EAAE;IAEtC,MAAM,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC;IAC/C,IAAI,MAAM,KAAK,SAAS;QAAE,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAC7D,OAAO,aAAa,CAAC,IAAI,CAAC,MAAM,sBAAsB,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AAC5F,CAAC","sourcesContent":["import { validateSqliteSchemaPrefix } from \"@earendil-works/chord/sqlite\";\nimport { type NodeSqliteDatabaseOptions, openNodeSqliteDatabase } from \"@earendil-works/chord/sqlite/node\";\nimport { SqliteStorage, type SqliteStorageOptions } from \"./storage.ts\";\n\nexport {\n\tNodeSqliteDatabase,\n\ttype NodeSqliteDatabaseOptions,\n\topenNodeSqliteDatabase,\n} from \"@earendil-works/chord/sqlite/node\";\n\n/** Node SQLite connection settings plus durable storage options. */\nexport type NodeSqliteStorageOptions = NodeSqliteDatabaseOptions & SqliteStorageOptions;\n\n/** Open or create file-backed durable storage using Node's built-in SQLite, optionally under a table prefix. */\nexport async function openNodeSqliteStorage(\n\tpath: string,\n\toptions: NodeSqliteStorageOptions = {},\n): Promise<SqliteStorage> {\n\tconst { prefix, ...databaseOptions } = options;\n\tif (prefix !== undefined) validateSqliteSchemaPrefix(prefix);\n\treturn SqliteStorage.open(await openNodeSqliteDatabase(path, databaseOptions), { prefix });\n}\n"]}
|