@earendil-works/chord 1.3.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 +13 -0
- package/README.md +55 -1
- package/dist/context/index.d.ts +8 -0
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +15 -0
- package/dist/context/index.js.map +1 -1
- package/dist/output.d.ts +8 -2
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +17 -7
- package/dist/output.js.map +1 -1
- package/dist/sqlite/cloudflare.d.ts +52 -0
- package/dist/sqlite/cloudflare.d.ts.map +1 -0
- package/dist/sqlite/cloudflare.js +134 -0
- package/dist/sqlite/cloudflare.js.map +1 -0
- package/dist/sqlite/database.d.ts +37 -0
- package/dist/sqlite/database.d.ts.map +1 -0
- package/dist/sqlite/database.js +2 -0
- package/dist/sqlite/database.js.map +1 -0
- package/dist/sqlite/index.d.ts +4 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +3 -0
- package/dist/sqlite/index.js.map +1 -0
- package/dist/sqlite/migrations.d.ts +10 -0
- package/dist/sqlite/migrations.d.ts.map +1 -0
- package/dist/sqlite/migrations.js +33 -0
- package/dist/sqlite/migrations.js.map +1 -0
- package/dist/sqlite/node.d.ts +38 -0
- package/dist/sqlite/node.d.ts.map +1 -0
- package/dist/sqlite/node.js +174 -0
- package/dist/sqlite/node.js.map +1 -0
- package/dist/sqlite/schema.d.ts +3 -0
- package/dist/sqlite/schema.d.ts.map +1 -0
- package/dist/sqlite/schema.js +10 -0
- package/dist/sqlite/schema.js.map +1 -0
- package/dist/telemetry-sqlite/index.d.ts +24 -0
- package/dist/telemetry-sqlite/index.d.ts.map +1 -0
- package/dist/telemetry-sqlite/index.js +273 -0
- package/dist/telemetry-sqlite/index.js.map +1 -0
- package/dist/telemetry-sqlite/store.d.ts +9 -0
- package/dist/telemetry-sqlite/store.d.ts.map +1 -0
- package/dist/telemetry-sqlite/store.js +110 -0
- package/dist/telemetry-sqlite/store.js.map +1 -0
- package/package.json +22 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/** Runs synchronous operations immediately when idle and holds queued calls behind a transaction. */
|
|
2
|
+
class SerialQueue {
|
|
3
|
+
tail = Promise.resolve();
|
|
4
|
+
pending = 0;
|
|
5
|
+
run(operation) {
|
|
6
|
+
if (this.pending > 0)
|
|
7
|
+
return this.enqueue(operation);
|
|
8
|
+
try {
|
|
9
|
+
return Promise.resolve(operation());
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return Promise.reject(error);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
hold(operation) {
|
|
16
|
+
return this.enqueue(operation);
|
|
17
|
+
}
|
|
18
|
+
enqueue(operation) {
|
|
19
|
+
this.pending++;
|
|
20
|
+
const result = this.tail.then(operation).finally(() => {
|
|
21
|
+
this.pending--;
|
|
22
|
+
});
|
|
23
|
+
this.tail = result.then(() => { }, () => { });
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const storageQueues = new WeakMap();
|
|
28
|
+
function queueFor(storage) {
|
|
29
|
+
let queue = storageQueues.get(storage);
|
|
30
|
+
if (queue === undefined) {
|
|
31
|
+
queue = new SerialQueue();
|
|
32
|
+
storageQueues.set(storage, queue);
|
|
33
|
+
}
|
|
34
|
+
return queue;
|
|
35
|
+
}
|
|
36
|
+
/** Durable Object SQL binds numbers as doubles: a `bigint` outside the safe integer range would lose precision. */
|
|
37
|
+
function bindValue(value) {
|
|
38
|
+
if (value instanceof Uint8Array)
|
|
39
|
+
return value.slice().buffer;
|
|
40
|
+
if (typeof value !== "bigint")
|
|
41
|
+
return value;
|
|
42
|
+
if (value < BigInt(Number.MIN_SAFE_INTEGER) || value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
43
|
+
throw new RangeError(`Durable Object SQL cannot bind ${value} without losing precision`);
|
|
44
|
+
}
|
|
45
|
+
return Number(value);
|
|
46
|
+
}
|
|
47
|
+
const bind = (params) => params.map(bindValue);
|
|
48
|
+
/** A row as returned, with `BLOB` columns converted to `Uint8Array` in place; no other copy is made. */
|
|
49
|
+
function row(row) {
|
|
50
|
+
for (const key in row) {
|
|
51
|
+
const value = row[key];
|
|
52
|
+
if (value instanceof ArrayBuffer)
|
|
53
|
+
row[key] = new Uint8Array(value);
|
|
54
|
+
}
|
|
55
|
+
return row;
|
|
56
|
+
}
|
|
57
|
+
/** Executes SQL through `ctx.storage.sql`, which has no separate prepare step. */
|
|
58
|
+
class DurableObjectSqliteExecutor {
|
|
59
|
+
storage;
|
|
60
|
+
constructor(storage) {
|
|
61
|
+
this.storage = storage;
|
|
62
|
+
}
|
|
63
|
+
async exec(sql) {
|
|
64
|
+
this.check();
|
|
65
|
+
this.storage.sql.exec(sql);
|
|
66
|
+
}
|
|
67
|
+
async run(sql, ...params) {
|
|
68
|
+
this.check();
|
|
69
|
+
this.storage.sql.exec(sql, ...bind(params));
|
|
70
|
+
}
|
|
71
|
+
async get(sql, ...params) {
|
|
72
|
+
this.check();
|
|
73
|
+
const result = this.storage.sql.exec(sql, ...bind(params)).next();
|
|
74
|
+
return result.done ? undefined : row(result.value);
|
|
75
|
+
}
|
|
76
|
+
async all(sql, ...params) {
|
|
77
|
+
this.check();
|
|
78
|
+
return this.storage.sql
|
|
79
|
+
.exec(sql, ...bind(params))
|
|
80
|
+
.toArray()
|
|
81
|
+
.map((value) => row(value));
|
|
82
|
+
}
|
|
83
|
+
/** Throws when this handle may no longer run SQL. */
|
|
84
|
+
check() { }
|
|
85
|
+
}
|
|
86
|
+
/** The handle a transaction callback receives; invalid once the callback settles. */
|
|
87
|
+
class DurableObjectSqliteTransaction extends DurableObjectSqliteExecutor {
|
|
88
|
+
active = true;
|
|
89
|
+
check() {
|
|
90
|
+
if (!this.active)
|
|
91
|
+
throw new Error("SQLite transaction handle is no longer active");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `SqliteDatabase` adapter for the SQLite storage of a Cloudflare Durable Object. Adapters given the same storage
|
|
96
|
+
* object share a queue; direct calls to the underlying storage bypass it. Integers are JavaScript numbers; binding a
|
|
97
|
+
* `bigint` outside the safe integer range throws.
|
|
98
|
+
*/
|
|
99
|
+
export class DurableObjectSqliteDatabase extends DurableObjectSqliteExecutor {
|
|
100
|
+
queue;
|
|
101
|
+
constructor(storage) {
|
|
102
|
+
super(storage);
|
|
103
|
+
this.queue = queueFor(storage);
|
|
104
|
+
}
|
|
105
|
+
exec(sql) {
|
|
106
|
+
return this.queue.run(() => super.exec(sql));
|
|
107
|
+
}
|
|
108
|
+
run(sql, ...params) {
|
|
109
|
+
return this.queue.run(() => super.run(sql, ...params));
|
|
110
|
+
}
|
|
111
|
+
get(sql, ...params) {
|
|
112
|
+
return this.queue.run(() => super.get(sql, ...params));
|
|
113
|
+
}
|
|
114
|
+
all(sql, ...params) {
|
|
115
|
+
return this.queue.run(() => super.all(sql, ...params));
|
|
116
|
+
}
|
|
117
|
+
/** `ctx.storage.transaction()` commits when the callback resolves and rolls back when it rejects. */
|
|
118
|
+
transaction(callback) {
|
|
119
|
+
return this.queue.hold(() => this.storage.transaction(async () => {
|
|
120
|
+
const transaction = new DurableObjectSqliteTransaction(this.storage);
|
|
121
|
+
try {
|
|
122
|
+
return await callback(transaction);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
transaction.active = false;
|
|
126
|
+
}
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
/** Waits for queued work. The Durable Object owns its storage, so nothing else closes. */
|
|
130
|
+
close() {
|
|
131
|
+
return this.queue.run(() => { });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/sqlite/cloudflare.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SqliteDatabase, SqliteExecutor, SqliteValue } from \"./database.ts\";\n\n/** Values a Durable Object SQL binding accepts and returns. */\ntype DurableObjectSqlValue = ArrayBuffer | string | number | null;\n\n/**\n * The parts of a SQLite-backed Durable Object's `ctx.storage` this adapter uses, typed structurally so the package\n * needs no Workers type dependency.\n */\nexport type DurableObjectSqliteStorage = {\n\treadonly sql: {\n\t\texec(\n\t\t\tquery: string,\n\t\t\t...bindings: DurableObjectSqlValue[]\n\t\t): {\n\t\t\tnext():\n\t\t\t\t| { readonly done?: false; readonly value: Record<string, DurableObjectSqlValue> }\n\t\t\t\t| { readonly done: true; readonly value?: undefined };\n\t\t\ttoArray(): Record<string, DurableObjectSqlValue>[];\n\t\t};\n\t};\n\ttransaction<T>(closure: () => Promise<T>): Promise<T>;\n};\n\n/** Runs synchronous operations immediately when idle and holds queued calls behind a transaction. */\nclass SerialQueue {\n\tprivate tail: Promise<void> = Promise.resolve();\n\tprivate pending = 0;\n\n\trun<T>(operation: () => T | Promise<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\thold<T>(operation: () => Promise<T>): Promise<T> {\n\t\treturn this.enqueue(operation);\n\t}\n\n\tprivate enqueue<T>(operation: () => T | Promise<T>): Promise<T> {\n\t\tthis.pending++;\n\t\tconst result = this.tail.then(operation).finally(() => {\n\t\t\tthis.pending--;\n\t\t});\n\t\tthis.tail = result.then(\n\t\t\t() => {},\n\t\t\t() => {},\n\t\t);\n\t\treturn result;\n\t}\n}\n\nconst storageQueues = new WeakMap<DurableObjectSqliteStorage, SerialQueue>();\n\nfunction queueFor(storage: DurableObjectSqliteStorage): SerialQueue {\n\tlet queue = storageQueues.get(storage);\n\tif (queue === undefined) {\n\t\tqueue = new SerialQueue();\n\t\tstorageQueues.set(storage, queue);\n\t}\n\treturn queue;\n}\n\n/** Durable Object SQL binds numbers as doubles: a `bigint` outside the safe integer range would lose precision. */\nfunction bindValue(value: SqliteValue): DurableObjectSqlValue {\n\tif (value instanceof Uint8Array) return value.slice().buffer as ArrayBuffer;\n\tif (typeof value !== \"bigint\") return value;\n\tif (value < BigInt(Number.MIN_SAFE_INTEGER) || value > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\tthrow new RangeError(`Durable Object SQL cannot bind ${value} without losing precision`);\n\t}\n\treturn Number(value);\n}\n\nconst bind = (params: readonly SqliteValue[]): DurableObjectSqlValue[] => params.map(bindValue);\n\n/** A row as returned, with `BLOB` columns converted to `Uint8Array` in place; no other copy is made. */\nfunction row<T>(row: Record<string, DurableObjectSqlValue>): T {\n\tfor (const key in row) {\n\t\tconst value = row[key];\n\t\tif (value instanceof ArrayBuffer) (row as Record<string, unknown>)[key] = new Uint8Array(value);\n\t}\n\treturn row as T;\n}\n\n/** Executes SQL through `ctx.storage.sql`, which has no separate prepare step. */\nclass DurableObjectSqliteExecutor implements SqliteExecutor {\n\tprotected readonly storage: DurableObjectSqliteStorage;\n\n\tconstructor(storage: DurableObjectSqliteStorage) {\n\t\tthis.storage = storage;\n\t}\n\n\tasync exec(sql: string): Promise<void> {\n\t\tthis.check();\n\t\tthis.storage.sql.exec(sql);\n\t}\n\n\tasync run(sql: string, ...params: SqliteValue[]): Promise<void> {\n\t\tthis.check();\n\t\tthis.storage.sql.exec(sql, ...bind(params));\n\t}\n\n\tasync get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined> {\n\t\tthis.check();\n\t\tconst result = this.storage.sql.exec(sql, ...bind(params)).next();\n\t\treturn result.done ? undefined : row<T>(result.value);\n\t}\n\n\tasync all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]> {\n\t\tthis.check();\n\t\treturn this.storage.sql\n\t\t\t.exec(sql, ...bind(params))\n\t\t\t.toArray()\n\t\t\t.map((value) => row<T>(value));\n\t}\n\n\t/** Throws when this handle may no longer run SQL. */\n\tprotected check(): void {}\n}\n\n/** The handle a transaction callback receives; invalid once the callback settles. */\nclass DurableObjectSqliteTransaction extends DurableObjectSqliteExecutor {\n\tactive = true;\n\n\tprotected override check(): void {\n\t\tif (!this.active) throw new Error(\"SQLite transaction handle is no longer active\");\n\t}\n}\n\n/**\n * `SqliteDatabase` adapter for the SQLite storage of a Cloudflare Durable Object. Adapters given the same storage\n * object share a queue; direct calls to the underlying storage bypass it. Integers are JavaScript numbers; binding a\n * `bigint` outside the safe integer range throws.\n */\nexport class DurableObjectSqliteDatabase extends DurableObjectSqliteExecutor implements SqliteDatabase {\n\tprivate readonly queue: SerialQueue;\n\n\tconstructor(storage: DurableObjectSqliteStorage) {\n\t\tsuper(storage);\n\t\tthis.queue = queueFor(storage);\n\t}\n\n\toverride exec(sql: string): Promise<void> {\n\t\treturn this.queue.run(() => super.exec(sql));\n\t}\n\n\toverride run(sql: string, ...params: SqliteValue[]): Promise<void> {\n\t\treturn this.queue.run(() => super.run(sql, ...params));\n\t}\n\n\toverride get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined> {\n\t\treturn this.queue.run(() => super.get<T>(sql, ...params));\n\t}\n\n\toverride all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]> {\n\t\treturn this.queue.run(() => super.all<T>(sql, ...params));\n\t}\n\n\t/** `ctx.storage.transaction()` commits when the callback resolves and rolls back when it rejects. */\n\ttransaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T> {\n\t\treturn this.queue.hold(() =>\n\t\t\tthis.storage.transaction(async () => {\n\t\t\t\tconst transaction = new DurableObjectSqliteTransaction(this.storage);\n\t\t\t\ttry {\n\t\t\t\t\treturn await callback(transaction);\n\t\t\t\t} finally {\n\t\t\t\t\ttransaction.active = false;\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\t/** Waits for queued work. The Durable Object owns its storage, so nothing else closes. */\n\tclose(): Promise<void> {\n\t\treturn this.queue.run(() => {});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Values supported by the portable SQLite drivers. */
|
|
2
|
+
export type SqliteValue = null | number | bigint | string | Uint8Array;
|
|
3
|
+
/**
|
|
4
|
+
* Asynchronous SQL operations shared by a database and its transaction handles.
|
|
5
|
+
*
|
|
6
|
+
* `exec` runs SQL text without bindings and may contain several statements. `run`, `get`, and `all`
|
|
7
|
+
* execute one statement with positional bindings. Adapters may cache prepared statements by SQL text,
|
|
8
|
+
* so callers pass values as bindings instead of interpolating them.
|
|
9
|
+
*/
|
|
10
|
+
export interface SqliteExecutor {
|
|
11
|
+
exec(sql: string): Promise<void>;
|
|
12
|
+
run(sql: string, ...params: SqliteValue[]): Promise<void>;
|
|
13
|
+
get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined>;
|
|
14
|
+
all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Minimal portable SQLite database facade.
|
|
18
|
+
*
|
|
19
|
+
* All operations are asynchronous so adapters may execute outside the caller's runtime.
|
|
20
|
+
*
|
|
21
|
+
* `transaction` passes the callback a transaction handle. All work in the transaction
|
|
22
|
+
* must use that handle; the handle is invalid after the callback settles. Adapters must
|
|
23
|
+
* queue unrelated operations and other transactions until the transaction finishes. The
|
|
24
|
+
* returned promise settles after commit or rollback. Calling the database itself (including
|
|
25
|
+
* `transaction` or `close`) from inside a callback therefore waits for that transaction and
|
|
26
|
+
* never settles.
|
|
27
|
+
*
|
|
28
|
+
* When the callback rejects, the adapter must roll the transaction back before rejecting
|
|
29
|
+
* with that same error. If rollback fails, it must reject with a different error (for
|
|
30
|
+
* example an `AggregateError`) so callers cannot mistake the callback error for a
|
|
31
|
+
* guaranteed rollback.
|
|
32
|
+
*/
|
|
33
|
+
export interface SqliteDatabase extends SqliteExecutor {
|
|
34
|
+
transaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/sqlite/database.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEvE;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACrF,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,cAAe,SAAQ,cAAc;IACrD,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,WAAW,EAAE,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAClF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/sqlite/database.ts"],"names":[],"mappings":"","sourcesContent":["/** Values supported by the portable SQLite drivers. */\nexport type SqliteValue = null | number | bigint | string | Uint8Array;\n\n/**\n * Asynchronous SQL operations shared by a database and its transaction handles.\n *\n * `exec` runs SQL text without bindings and may contain several statements. `run`, `get`, and `all`\n * execute one statement with positional bindings. Adapters may cache prepared statements by SQL text,\n * so callers pass values as bindings instead of interpolating them.\n */\nexport interface SqliteExecutor {\n\texec(sql: string): Promise<void>;\n\trun(sql: string, ...params: SqliteValue[]): Promise<void>;\n\tget<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined>;\n\tall<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]>;\n}\n\n/**\n * Minimal portable SQLite database facade.\n *\n * All operations are asynchronous so adapters may execute outside the caller's runtime.\n *\n * `transaction` passes the callback a transaction handle. All work in the transaction\n * must use that handle; the handle is invalid after the callback settles. Adapters must\n * queue unrelated operations and other transactions until the transaction finishes. The\n * returned promise settles after commit or rollback. Calling the database itself (including\n * `transaction` or `close`) from inside a callback therefore waits for that transaction and\n * never settles.\n *\n * When the callback rejects, the adapter must roll the transaction back before rejecting\n * with that same error. If rollback fails, it must reject with a different error (for\n * example an `AggregateError`) so callers cannot mistake the callback error for a\n * guaranteed rollback.\n */\nexport interface SqliteDatabase extends SqliteExecutor {\n\ttransaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T>;\n\tclose(): Promise<void>;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sqlite/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sqlite/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAwB,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export type { SqliteDatabase, SqliteExecutor, SqliteValue } from \"./database.ts\";\nexport { applySqliteMigrations, type SqliteMigration } from \"./migrations.ts\";\nexport { validateSqliteSchemaPrefix } from \"./schema.ts\";\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "./database.ts";
|
|
2
|
+
export type SqliteMigration = {
|
|
3
|
+
readonly version: number;
|
|
4
|
+
readonly statements: readonly string[];
|
|
5
|
+
};
|
|
6
|
+
/** Apply all pending migrations atomically, tracking their version in the named metadata table. */
|
|
7
|
+
export declare function applySqliteMigrations(database: SqliteDatabase, migrations: readonly SqliteMigration[], options: {
|
|
8
|
+
readonly table: string;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/sqlite/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,MAAM,MAAM,eAAe,GAAG;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,CAAC;AAIF,mGAAmG;AACnG,wBAAsB,qBAAqB,CAC1C,QAAQ,EAAE,cAAc,EACxB,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { validateSqliteSchemaPrefix } from "./schema.js";
|
|
2
|
+
/** Apply all pending migrations atomically, tracking their version in the named metadata table. */
|
|
3
|
+
export async function applySqliteMigrations(database, migrations, options) {
|
|
4
|
+
const { table } = options;
|
|
5
|
+
validateSqliteSchemaPrefix(table);
|
|
6
|
+
for (let index = 0; index < migrations.length; index++) {
|
|
7
|
+
if (migrations[index]?.version !== index + 1) {
|
|
8
|
+
throw new Error("SQLite migrations must have contiguous versions starting at 1");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
await database.transaction(async (transaction) => {
|
|
12
|
+
await transaction.exec(`CREATE TABLE IF NOT EXISTS ${table} (
|
|
13
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
14
|
+
version INTEGER NOT NULL CHECK (version >= 0)
|
|
15
|
+
) STRICT`);
|
|
16
|
+
await transaction.run(`INSERT OR IGNORE INTO ${table} (singleton, version) VALUES (1, 0)`);
|
|
17
|
+
const row = await transaction.get(`SELECT version FROM ${table} WHERE singleton = 1`);
|
|
18
|
+
if (row === undefined)
|
|
19
|
+
throw new Error("SQLite schema metadata is missing");
|
|
20
|
+
const currentVersion = migrations.at(-1)?.version ?? 0;
|
|
21
|
+
if (row.version > currentVersion) {
|
|
22
|
+
throw new Error(`SQLite schema version ${row.version} is newer than supported version ${currentVersion}`);
|
|
23
|
+
}
|
|
24
|
+
for (const migration of migrations) {
|
|
25
|
+
if (migration.version <= row.version)
|
|
26
|
+
continue;
|
|
27
|
+
for (const statement of migration.statements)
|
|
28
|
+
await transaction.exec(statement);
|
|
29
|
+
await transaction.run(`UPDATE ${table} SET version = ? WHERE singleton = 1`, migration.version);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/sqlite/migrations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AASzD,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,QAAwB,EACxB,UAAsC,EACtC,OAAmC;IAEnC,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAC1B,0BAA0B,CAAC,KAAK,CAAC,CAAC;IAClC,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,+DAA+D,CAAC,CAAC;QAClF,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE;QAChD,MAAM,WAAW,CAAC,IAAI,CAAC,8BAA8B,KAAK;;;WAGjD,CAAC,CAAC;QACX,MAAM,WAAW,CAAC,GAAG,CAAC,yBAAyB,KAAK,qCAAqC,CAAC,CAAC;QAC3F,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,GAAG,CAAY,uBAAuB,KAAK,sBAAsB,CAAC,CAAC;QACjG,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAC5E,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,CAAC,yBAAyB,GAAG,CAAC,OAAO,oCAAoC,cAAc,EAAE,CAAC,CAAC;QAC3G,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,UAAU,KAAK,sCAAsC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;QACjG,CAAC;IACF,CAAC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { SqliteDatabase } from \"./database.ts\";\nimport { validateSqliteSchemaPrefix } from \"./schema.ts\";\n\nexport type SqliteMigration = {\n\treadonly version: number;\n\treadonly statements: readonly string[];\n};\n\ntype SchemaRow = { readonly version: number };\n\n/** Apply all pending migrations atomically, tracking their version in the named metadata table. */\nexport async function applySqliteMigrations(\n\tdatabase: SqliteDatabase,\n\tmigrations: readonly SqliteMigration[],\n\toptions: { readonly table: string },\n): Promise<void> {\n\tconst { table } = options;\n\tvalidateSqliteSchemaPrefix(table);\n\tfor (let index = 0; index < migrations.length; index++) {\n\t\tif (migrations[index]?.version !== index + 1) {\n\t\t\tthrow new Error(\"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 ${table} (\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 ${table} (singleton, version) VALUES (1, 0)`);\n\t\tconst row = await transaction.get<SchemaRow>(`SELECT version FROM ${table} WHERE singleton = 1`);\n\t\tif (row === undefined) throw new Error(\"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(`SQLite schema version ${row.version} is newer than supported version ${currentVersion}`);\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 ${table} SET version = ? WHERE singleton = 1`, migration.version);\n\t\t}\n\t});\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StatementSync } from "node:sqlite";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import type { SqliteDatabase, SqliteExecutor, SqliteValue } from "./database.ts";
|
|
4
|
+
/** Node SQLite connection settings for a database file. */
|
|
5
|
+
export type NodeSqliteDatabaseOptions = {
|
|
6
|
+
/** SQLite WAL auto-checkpoint threshold. SQLite and this adapter default to 1,000 pages; 0 disables it. */
|
|
7
|
+
readonly walAutoCheckpointPages?: number;
|
|
8
|
+
/** Time SQLite waits for a competing file lock. SQLite defaults to 0; this adapter defaults to 5,000 ms. */
|
|
9
|
+
readonly busyTimeoutMs?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Executes SQL on one connection. Prepared statements are cached per connection by SQL text, so the
|
|
13
|
+
* database and its transaction handles share them across transactions.
|
|
14
|
+
*/
|
|
15
|
+
declare abstract class NodeSqliteExecutor implements SqliteExecutor {
|
|
16
|
+
protected readonly database: DatabaseSync;
|
|
17
|
+
protected readonly statements: Map<string, StatementSync>;
|
|
18
|
+
constructor(database: DatabaseSync, statements: Map<string, StatementSync>);
|
|
19
|
+
exec(sql: string): Promise<void>;
|
|
20
|
+
run(sql: string, ...params: SqliteValue[]): Promise<void>;
|
|
21
|
+
get<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined>;
|
|
22
|
+
all<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]>;
|
|
23
|
+
protected abstract runOperation<T>(operation: () => T): Promise<T>;
|
|
24
|
+
private statement;
|
|
25
|
+
}
|
|
26
|
+
/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */
|
|
27
|
+
export declare class NodeSqliteDatabase extends NodeSqliteExecutor implements SqliteDatabase {
|
|
28
|
+
private readonly access;
|
|
29
|
+
private closed;
|
|
30
|
+
constructor(database: DatabaseSync);
|
|
31
|
+
transaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T>;
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
protected runOperation<T>(operation: () => T): Promise<T>;
|
|
34
|
+
}
|
|
35
|
+
/** Open and configure a Node-backed SQLite database facade. */
|
|
36
|
+
export declare function openNodeSqliteDatabase(path: string, options?: NodeSqliteDatabaseOptions): Promise<NodeSqliteDatabase>;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/sqlite/node.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjF,2DAA2D;AAC3D,MAAM,MAAM,yBAAyB,GAAG;IACvC,2GAA2G;IAC3G,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC,4GAA4G;IAC5G,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AA0DF;;;GAGG;AACH,uBAAe,kBAAmB,YAAW,cAAc;IAC1D,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAC1C,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAE1D,YAAY,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAGzE;IAED,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/B;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIxD;IAED,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAEnF;IAED,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAEzE;IAED,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnE,OAAO,CAAC,SAAS;CAQjB;AAgBD,wEAAwE;AACxE,qBAAa,kBAAmB,SAAQ,kBAAmB,YAAW,cAAc;IACnF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,QAAQ,EAAE,YAAY,EAEjC;IAED,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,WAAW,EAAE,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAmBhF;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAWrB;IAED,SAAS,CAAC,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAExD;CACD;AAED,+DAA+D;AAC/D,wBAAsB,sBAAsB,CAC3C,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,yBAA8B,GACrC,OAAO,CAAC,kBAAkB,CAAC,CAmB7B"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
const DEFAULT_WAL_AUTO_CHECKPOINT_PAGES = 1_000;
|
|
5
|
+
const DEFAULT_BUSY_TIMEOUT_MS = 5_000;
|
|
6
|
+
const ignore = () => { };
|
|
7
|
+
/**
|
|
8
|
+
* Runs operations in call order. An operation starts immediately when nothing is running or waiting;
|
|
9
|
+
* otherwise it waits for everything before it. An asynchronous operation holds the queue until it settles.
|
|
10
|
+
*/
|
|
11
|
+
class SerialOperationQueue {
|
|
12
|
+
tail = Promise.resolve();
|
|
13
|
+
pending = 0;
|
|
14
|
+
run(operation) {
|
|
15
|
+
if (this.pending > 0)
|
|
16
|
+
return this.enqueue(operation);
|
|
17
|
+
try {
|
|
18
|
+
return Promise.resolve(operation());
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return Promise.reject(error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
runAsync(operation) {
|
|
25
|
+
if (this.pending > 0)
|
|
26
|
+
return this.enqueue(operation);
|
|
27
|
+
this.pending++;
|
|
28
|
+
// Publish the barrier before the operation starts, so calls it makes synchronously wait behind it.
|
|
29
|
+
const { promise: barrier, resolve: releaseBarrier } = Promise.withResolvers();
|
|
30
|
+
this.tail = barrier;
|
|
31
|
+
let started;
|
|
32
|
+
try {
|
|
33
|
+
started = operation();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
started = Promise.reject(error);
|
|
37
|
+
}
|
|
38
|
+
return started.finally(() => {
|
|
39
|
+
this.pending--;
|
|
40
|
+
releaseBarrier();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
enqueue(operation) {
|
|
44
|
+
this.pending++;
|
|
45
|
+
return this.release(this.tail.then(operation));
|
|
46
|
+
}
|
|
47
|
+
release(operation) {
|
|
48
|
+
const settled = operation.finally(() => {
|
|
49
|
+
this.pending--;
|
|
50
|
+
});
|
|
51
|
+
this.tail = settled.then(ignore, ignore);
|
|
52
|
+
return settled;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Executes SQL on one connection. Prepared statements are cached per connection by SQL text, so the
|
|
57
|
+
* database and its transaction handles share them across transactions.
|
|
58
|
+
*/
|
|
59
|
+
class NodeSqliteExecutor {
|
|
60
|
+
database;
|
|
61
|
+
statements;
|
|
62
|
+
constructor(database, statements) {
|
|
63
|
+
this.database = database;
|
|
64
|
+
this.statements = statements;
|
|
65
|
+
}
|
|
66
|
+
exec(sql) {
|
|
67
|
+
return this.runOperation(() => {
|
|
68
|
+
this.database.exec(sql);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
run(sql, ...params) {
|
|
72
|
+
return this.runOperation(() => {
|
|
73
|
+
this.statement(sql).run(...params);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
get(sql, ...params) {
|
|
77
|
+
return this.runOperation(() => this.statement(sql).get(...params));
|
|
78
|
+
}
|
|
79
|
+
all(sql, ...params) {
|
|
80
|
+
return this.runOperation(() => this.statement(sql).all(...params));
|
|
81
|
+
}
|
|
82
|
+
statement(sql) {
|
|
83
|
+
let statement = this.statements.get(sql);
|
|
84
|
+
if (statement === undefined) {
|
|
85
|
+
statement = this.database.prepare(sql);
|
|
86
|
+
this.statements.set(sql, statement);
|
|
87
|
+
}
|
|
88
|
+
return statement;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
class NodeSqliteTransaction extends NodeSqliteExecutor {
|
|
92
|
+
scope;
|
|
93
|
+
constructor(database, statements, scope) {
|
|
94
|
+
super(database, statements);
|
|
95
|
+
this.scope = scope;
|
|
96
|
+
}
|
|
97
|
+
async runOperation(operation) {
|
|
98
|
+
if (!this.scope.active)
|
|
99
|
+
throw new Error("SQLite transaction handle is no longer active");
|
|
100
|
+
return operation();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */
|
|
104
|
+
export class NodeSqliteDatabase extends NodeSqliteExecutor {
|
|
105
|
+
access = new SerialOperationQueue();
|
|
106
|
+
closed = false;
|
|
107
|
+
constructor(database) {
|
|
108
|
+
super(database, new Map());
|
|
109
|
+
}
|
|
110
|
+
transaction(callback) {
|
|
111
|
+
return this.access.runAsync(async () => {
|
|
112
|
+
this.database.exec("BEGIN IMMEDIATE");
|
|
113
|
+
const scope = { active: true };
|
|
114
|
+
try {
|
|
115
|
+
const result = await callback(new NodeSqliteTransaction(this.database, this.statements, scope));
|
|
116
|
+
scope.active = false;
|
|
117
|
+
this.database.exec("COMMIT");
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
scope.active = false;
|
|
122
|
+
try {
|
|
123
|
+
this.database.exec("ROLLBACK");
|
|
124
|
+
}
|
|
125
|
+
catch (rollbackError) {
|
|
126
|
+
throw new AggregateError([error, rollbackError], "SQLite transaction failed and rollback failed");
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
close() {
|
|
133
|
+
return this.access.run(() => {
|
|
134
|
+
if (this.closed)
|
|
135
|
+
return;
|
|
136
|
+
this.closed = true;
|
|
137
|
+
this.statements.clear();
|
|
138
|
+
try {
|
|
139
|
+
this.database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
this.database.close();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
runOperation(operation) {
|
|
147
|
+
return this.access.run(operation);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Open and configure a Node-backed SQLite database facade. */
|
|
151
|
+
export async function openNodeSqliteDatabase(path, options = {}) {
|
|
152
|
+
const checkpointPages = options.walAutoCheckpointPages ?? DEFAULT_WAL_AUTO_CHECKPOINT_PAGES;
|
|
153
|
+
const timeout = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;
|
|
154
|
+
if (path !== ":memory:")
|
|
155
|
+
await mkdir(dirname(path), { recursive: true });
|
|
156
|
+
const database = new DatabaseSync(path, { timeout });
|
|
157
|
+
const adapter = new NodeSqliteDatabase(database);
|
|
158
|
+
try {
|
|
159
|
+
await adapter.exec("PRAGMA journal_mode = WAL");
|
|
160
|
+
await adapter.exec("PRAGMA synchronous = NORMAL");
|
|
161
|
+
await adapter.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);
|
|
162
|
+
return adapter;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
try {
|
|
166
|
+
await adapter.close();
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Preserve the configuration failure.
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/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\";\n\n/** Node SQLite connection settings for a database file. */\nexport type NodeSqliteDatabaseOptions = {\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: NodeSqliteDatabaseOptions = {},\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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/sqlite/schema.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAO/D"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Validate a prefix before using it as part of a SQLite identifier. */
|
|
2
|
+
export function validateSqliteSchemaPrefix(prefix) {
|
|
3
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {
|
|
4
|
+
throw new Error(`Invalid SQLite schema prefix ${JSON.stringify(prefix)}`);
|
|
5
|
+
}
|
|
6
|
+
if (prefix.toLowerCase().startsWith("sqlite_")) {
|
|
7
|
+
throw new Error(`SQLite schema prefix ${JSON.stringify(prefix)} is reserved`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/sqlite/schema.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,UAAU,0BAA0B,CAAC,MAAc;IACxD,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC/E,CAAC;AACF,CAAC","sourcesContent":["/** Validate a prefix before using it as part of a SQLite identifier. */\nexport function validateSqliteSchemaPrefix(prefix: string): void {\n\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {\n\t\tthrow new Error(`Invalid SQLite schema prefix ${JSON.stringify(prefix)}`);\n\t}\n\tif (prefix.toLowerCase().startsWith(\"sqlite_\")) {\n\t\tthrow new Error(`SQLite schema prefix ${JSON.stringify(prefix)} is reserved`);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TelemetryContext, type TelemetryMetrics, type TelemetryPropagation, type TelemetryStore } from "@earendil-works/pi-telemetry";
|
|
2
|
+
import { type SqliteDatabase } from "../sqlite/index.ts";
|
|
3
|
+
export interface SqliteTelemetryOptions {
|
|
4
|
+
/** Retain seven days by default. Cleanup runs in the flush transaction. */
|
|
5
|
+
readonly retentionMs?: number;
|
|
6
|
+
/** Settled spans awaiting commit, including the in-flight batch. Defaults to 10,000.
|
|
7
|
+
* Also bounds pending metric interval/series pairs independently. */
|
|
8
|
+
readonly maxQueuedSpans?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SqliteTelemetry extends TelemetryContext, TelemetryStore {
|
|
11
|
+
readonly metrics: TelemetryMetrics;
|
|
12
|
+
readonly propagation: TelemetryPropagation;
|
|
13
|
+
readonly droppedSpans: number;
|
|
14
|
+
readonly droppedMeasurements: number;
|
|
15
|
+
/** Persist settled spans and completed ten-second metric intervals. */
|
|
16
|
+
flush(): Promise<void>;
|
|
17
|
+
/** Stop recording and drain, including partial metric intervals. Does not close the database. */
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** Portable, buffered SQLite recorder. Recording never performs SQL; flush runs every second.
|
|
21
|
+
* Only settled spans are stored. Metrics aggregate count/sum/min/max per ten-second interval.
|
|
22
|
+
* A write failure permanently stops recording and is reported by flush and close. */
|
|
23
|
+
export declare function createSqliteTelemetry(db: SqliteDatabase, options?: SqliteTelemetryOptions): Promise<SqliteTelemetry>;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry-sqlite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAQN,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAEzB,KAAK,cAAc,EACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAyB,KAAK,cAAc,EAAoB,MAAM,oBAAoB,CAAC;AAGlG,MAAM,WAAW,sBAAsB;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;yEACqE;IACrE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB,EAAE,cAAc;IACxE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,uEAAuE;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,iGAAiG;IACjG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AA4SD;;qFAEqF;AACrF,wBAAsB,qBAAqB,CAC1C,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE,sBAA2B,GAClC,OAAO,CAAC,eAAe,CAAC,CAsB1B"}
|