@delali/sirannon-db 0.1.6 → 0.1.7
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/README.md +134 -19
- package/dist/backup-scheduler/index.d.ts +18 -3
- package/dist/backup-scheduler/index.mjs +2 -2
- package/dist/{change-tracker-CFTQ9TSn.d.ts → change-tracker-CbmaMO-N.d.ts} +12 -2
- package/dist/chunk-4IGMIJQK.mjs +318 -0
- package/dist/chunk-BNUTBHHH.mjs +22 -0
- package/dist/chunk-CJLYFDP5.mjs +26 -0
- package/dist/chunk-CW6S3WL5.mjs +222 -0
- package/dist/chunk-DJLX6CAE.mjs +20 -0
- package/dist/chunk-DVWQD3GF.mjs +49 -0
- package/dist/chunk-GEZUUIKV.mjs +268 -0
- package/dist/{chunk-UVMVN3OT.mjs → chunk-H5AB6NIR.mjs} +1 -1
- package/dist/{chunk-UTO3ZAFS.mjs → chunk-HHRMRFFR.mjs} +148 -28
- package/dist/chunk-TKGHYWQ6.mjs +35 -0
- package/dist/chunk-VLTICJOD.mjs +470 -0
- package/dist/{chunk-O7BHI3CF.mjs → chunk-YPYVQJ4C.mjs} +15 -1
- package/dist/client/index.d.ts +107 -15
- package/dist/client/index.mjs +348 -37
- package/dist/core/index.d.ts +70 -13
- package/dist/core/index.mjs +635 -192
- package/dist/core/writer-worker.d.ts +2 -0
- package/dist/core/writer-worker.mjs +107 -0
- package/dist/{database-BVY1GqE7.d.ts → database-DuGp0Rtr.d.ts} +51 -19
- package/dist/driver/better-sqlite3.d.ts +1 -1
- package/dist/driver/better-sqlite3.mjs +44 -6
- package/dist/driver/bun.mjs +35 -6
- package/dist/driver/expo.mjs +2 -2
- package/dist/driver/node.d.ts +1 -1
- package/dist/driver/node.mjs +48 -5
- package/dist/driver/wa-sqlite.d.ts +109 -0
- package/dist/driver/wa-sqlite.mjs +30 -2
- package/dist/{errors-C00ed08Q.d.ts → errors-5Nf5ZAEC.d.ts} +19 -1
- package/dist/file-migrations/index.d.ts +2 -3
- package/dist/file-migrations/index.mjs +1 -1
- package/dist/replication/coordinator/etcd.mjs +2 -2
- package/dist/replication/index.d.ts +7 -8
- package/dist/replication/index.mjs +79 -78
- package/dist/server/index.d.ts +111 -28
- package/dist/server/index.mjs +1016 -384
- package/dist/{sirannon-Cd-lK6T0.d.ts → sirannon-4SspRvP5.d.ts} +3 -3
- package/dist/transport/grpc.d.ts +3 -4
- package/dist/transport/grpc.mjs +2 -2
- package/dist/{types-Lc7ywFx7.d.ts → types-BsjobKbl.d.ts} +2 -2
- package/dist/{types-BeozgNPr.d.ts → types-D4p4UyDK.d.ts} +1 -1
- package/dist/types-D_hQW1hr.d.ts +494 -0
- package/package.json +3 -23
- package/dist/chunk-3MCMONVP.mjs +0 -115
- package/dist/chunk-74UN4DIE.mjs +0 -14
- package/dist/chunk-FB2U2Q3Y.mjs +0 -21
- package/dist/chunk-GS7T5YMI.mjs +0 -51
- package/dist/chunk-PXKAKK2V.mjs +0 -124
- package/dist/index-CLdNrcPz.d.ts +0 -16
- package/dist/types-BFSsG77t.d.ts +0 -29
- package/dist/types-D-74JiXb.d.ts +0 -265
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { serializeError } from '../chunk-DJLX6CAE.mjs';
|
|
2
|
+
import { executeGroup } from '../chunk-CW6S3WL5.mjs';
|
|
3
|
+
import '../chunk-GEZUUIKV.mjs';
|
|
4
|
+
import { SirannonError } from '../chunk-YPYVQJ4C.mjs';
|
|
5
|
+
import { parentPort } from 'worker_threads';
|
|
6
|
+
|
|
7
|
+
var STATEMENT_CACHE_CAPACITY = 128;
|
|
8
|
+
if (!parentPort) {
|
|
9
|
+
throw new SirannonError("Writer worker started outside a worker thread", "WRITER_WORKER_NO_PORT");
|
|
10
|
+
}
|
|
11
|
+
var port = parentPort;
|
|
12
|
+
var connection = null;
|
|
13
|
+
var statements = /* @__PURE__ */ new Map();
|
|
14
|
+
function requireConnection() {
|
|
15
|
+
if (!connection) {
|
|
16
|
+
throw new SirannonError(
|
|
17
|
+
"Writer worker received a command before the connection was opened",
|
|
18
|
+
"WRITER_WORKER_NOT_OPEN"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return connection;
|
|
22
|
+
}
|
|
23
|
+
async function statementFor(sql) {
|
|
24
|
+
const conn = requireConnection();
|
|
25
|
+
const cached = statements.get(sql);
|
|
26
|
+
if (cached) {
|
|
27
|
+
statements.delete(sql);
|
|
28
|
+
statements.set(sql, cached);
|
|
29
|
+
return cached;
|
|
30
|
+
}
|
|
31
|
+
const stmt = await conn.prepare(sql);
|
|
32
|
+
statements.set(sql, stmt);
|
|
33
|
+
if (statements.size > STATEMENT_CACHE_CAPACITY) {
|
|
34
|
+
const oldest = statements.keys().next().value;
|
|
35
|
+
if (oldest !== void 0) statements.delete(oldest);
|
|
36
|
+
}
|
|
37
|
+
return stmt;
|
|
38
|
+
}
|
|
39
|
+
async function open(entry, path, options) {
|
|
40
|
+
const module = await import(entry.specifier);
|
|
41
|
+
const factory = module[entry.exportName ?? "default"];
|
|
42
|
+
if (typeof factory !== "function") {
|
|
43
|
+
throw new SirannonError(
|
|
44
|
+
`Driver worker entry '${entry.specifier}' has no callable export '${entry.exportName ?? "default"}'`,
|
|
45
|
+
"WRITER_WORKER_BAD_ENTRY"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const driver = factory(entry.config);
|
|
49
|
+
connection = await driver.open(path, options);
|
|
50
|
+
}
|
|
51
|
+
async function dispatch(req) {
|
|
52
|
+
switch (req.kind) {
|
|
53
|
+
case "open":
|
|
54
|
+
await open(req.entry, req.path, req.options);
|
|
55
|
+
return void 0;
|
|
56
|
+
case "exec":
|
|
57
|
+
await requireConnection().exec(req.sql);
|
|
58
|
+
return void 0;
|
|
59
|
+
case "run":
|
|
60
|
+
return (await statementFor(req.sql)).run(...req.params);
|
|
61
|
+
case "get":
|
|
62
|
+
return (await statementFor(req.sql)).get(...req.params);
|
|
63
|
+
case "all":
|
|
64
|
+
return (await statementFor(req.sql)).all(...req.params);
|
|
65
|
+
case "allRaw": {
|
|
66
|
+
const stmt = await statementFor(req.sql);
|
|
67
|
+
return stmt.allRaw ? stmt.allRaw(...req.params) : stmt.all(...req.params);
|
|
68
|
+
}
|
|
69
|
+
case "runBatch": {
|
|
70
|
+
const conn = requireConnection();
|
|
71
|
+
if (conn.runBatch) return conn.runBatch(req.sql, req.paramsBatch);
|
|
72
|
+
const stmt = await statementFor(req.sql);
|
|
73
|
+
return Promise.all(req.paramsBatch.map((params) => stmt.run(...params)));
|
|
74
|
+
}
|
|
75
|
+
case "runBatchSummary": {
|
|
76
|
+
const conn = requireConnection();
|
|
77
|
+
if (conn.runBatchSummary) return conn.runBatchSummary(req.sql, req.paramsBatch);
|
|
78
|
+
const stmt = await statementFor(req.sql);
|
|
79
|
+
let changes = 0;
|
|
80
|
+
for (const params of req.paramsBatch) changes += (await stmt.run(...params)).changes;
|
|
81
|
+
return { rowsLoaded: req.paramsBatch.length, changes };
|
|
82
|
+
}
|
|
83
|
+
case "runGroup": {
|
|
84
|
+
const outcomes = await executeGroup(requireConnection(), req.units);
|
|
85
|
+
return outcomes.map(
|
|
86
|
+
(outcome) => outcome.ok ? { ok: true, results: outcome.values } : { ok: false, error: serializeError(outcome.error) }
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
case "close":
|
|
90
|
+
if (connection) await connection.close();
|
|
91
|
+
connection = null;
|
|
92
|
+
statements.clear();
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function handle(req) {
|
|
97
|
+
try {
|
|
98
|
+
const value = await dispatch(req);
|
|
99
|
+
port.postMessage({ id: req.id, ok: true, value });
|
|
100
|
+
} catch (err) {
|
|
101
|
+
port.postMessage({ id: req.id, ok: false, error: serializeError(err) });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
var tail = Promise.resolve();
|
|
105
|
+
port.on("message", (req) => {
|
|
106
|
+
tail = tail.then(() => handle(req));
|
|
107
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { M as Migration, a as MigrationResult, R as RollbackResult } from './types-BeozgNPr.js';
|
|
1
|
+
import { Q as QueryHookContext, k as ConnectionHookContext, l as BeforeSubscribeHook, H as HookConfig, M as MetricsConfig, m as QueryMetrics, n as ConnectionMetrics, o as CDCMetrics, a as SQLiteDriver, D as DatabaseOptions, P as Params, p as QueryOptions, E as ExecuteResult, q as BulkLoadOptions, h as BulkLoadResult, T as Transaction, e as SQLiteConnection, r as SubscriptionBuilder, f as BackupScheduleOptions, B as BeforeQueryHook, A as AfterQueryHook } from './types-D_hQW1hr.js';
|
|
2
|
+
import { M as Migration, a as MigrationResult, R as RollbackResult } from './types-D4p4UyDK.js';
|
|
4
3
|
|
|
5
4
|
type HookEvent = 'beforeQuery' | 'afterQuery' | 'beforeConnect' | 'databaseOpen' | 'databaseClose' | 'beforeSubscribe';
|
|
6
5
|
type SubscribeHookContext = Parameters<BeforeSubscribeHook>[0];
|
|
@@ -49,28 +48,66 @@ declare class Database {
|
|
|
49
48
|
readonly readOnly: boolean;
|
|
50
49
|
private readonly pool;
|
|
51
50
|
private readonly driver;
|
|
51
|
+
private readonly synchronous;
|
|
52
|
+
private readonly walMode;
|
|
53
|
+
private readonly writerLock;
|
|
54
|
+
private readonly writeGate;
|
|
55
|
+
private readonly groupCommitter;
|
|
52
56
|
private readonly closeListeners;
|
|
53
57
|
private _closed;
|
|
54
|
-
private
|
|
55
|
-
private subscriptionManager;
|
|
56
|
-
private stopCdcPolling;
|
|
57
|
-
private readonly cdcPollInterval;
|
|
58
|
-
private readonly cdcRetention;
|
|
58
|
+
private readonly cdc;
|
|
59
59
|
private readonly hookRegistry;
|
|
60
|
-
private readonly
|
|
61
|
-
private readonly
|
|
62
|
-
private readonly backupManager;
|
|
63
|
-
private readonly backupScheduler;
|
|
64
|
-
private readonly scheduledBackupCancellers;
|
|
60
|
+
private readonly observer;
|
|
61
|
+
private readonly backups;
|
|
65
62
|
private constructor();
|
|
66
63
|
static create(id: string, path: string, driver: SQLiteDriver, options?: DatabaseOptions, internals?: DatabaseInternals): Promise<Database>;
|
|
67
64
|
query<T = Record<string, unknown>>(sql: string, params?: Params, options?: QueryOptions): Promise<T[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Returns query rows already encoded for the wire (safe-range integers as
|
|
67
|
+
* plain numbers, larger integers and BLOBs as tagged envelopes) in a single
|
|
68
|
+
* pass. The server response path uses this so a read walks its values once
|
|
69
|
+
* rather than narrowing on the driver and re-scanning to tag.
|
|
70
|
+
*/
|
|
71
|
+
queryForWire(sql: string, params?: Params, options?: QueryOptions): Promise<unknown[]>;
|
|
68
72
|
queryOne<T = Record<string, unknown>>(sql: string, params?: Params, options?: QueryOptions): Promise<T | undefined>;
|
|
73
|
+
/**
|
|
74
|
+
* On a single-connection driver the reader is the writer, so serialise the
|
|
75
|
+
* read through the writer lock to avoid a bulk load's uncommitted rows.
|
|
76
|
+
*/
|
|
77
|
+
private runRead;
|
|
69
78
|
execute(sql: string, params?: Params, options?: QueryOptions): Promise<ExecuteResult>;
|
|
70
79
|
executeBatch(sql: string, paramsBatch: Params[], options?: QueryOptions): Promise<ExecuteResult[]>;
|
|
80
|
+
private runInTransaction;
|
|
81
|
+
/**
|
|
82
|
+
* Load rows with relaxed writer durability. The load holds the writer lock
|
|
83
|
+
* for its whole duration, so no other write commits under the relaxed level
|
|
84
|
+
* and no two loads race on the shared `synchronous` setting; the configured
|
|
85
|
+
* level is restored before this resolves, whether the load succeeds or
|
|
86
|
+
* fails. The load runs in one transaction, so one commit and one durability
|
|
87
|
+
* barrier cover the whole batch. Rows are summed rather than returned
|
|
88
|
+
* per-row to bound memory on large loads. Like `execute` and `transaction`,
|
|
89
|
+
* this writes only to the local database; under replication the server routes
|
|
90
|
+
* loads through the engine, not through this method.
|
|
91
|
+
*/
|
|
92
|
+
bulkLoad(sql: string, paramsBatch: Params[], options?: BulkLoadOptions): Promise<BulkLoadResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Takes the statements up front rather than a callback, since a group cannot wait on an
|
|
95
|
+
* arbitrary caller-supplied callback without delaying every transaction beside it.
|
|
96
|
+
*/
|
|
97
|
+
executeTransaction(statements: readonly {
|
|
98
|
+
sql: string;
|
|
99
|
+
params?: Params;
|
|
100
|
+
}[]): Promise<ExecuteResult[]>;
|
|
101
|
+
private runStatementsAlone;
|
|
71
102
|
transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T>;
|
|
72
|
-
private maybeApplyDdlSideEffects;
|
|
73
103
|
watch(table: string): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Runs a CDC maintenance write (change-log pruning) on the shared writer
|
|
106
|
+
* under the writer lock. Serialising it with application writes keeps it
|
|
107
|
+
* from becoming a second writer that contends for SQLite's single write
|
|
108
|
+
* lock and stalls the event loop on `busy_timeout`.
|
|
109
|
+
*/
|
|
110
|
+
runCdcMaintenance(op: (writer: SQLiteConnection) => Promise<unknown>): Promise<void>;
|
|
74
111
|
unwatch(table: string): Promise<void>;
|
|
75
112
|
on(table: string): SubscriptionBuilder;
|
|
76
113
|
migrate(migrations: Migration[]): Promise<MigrationResult>;
|
|
@@ -85,11 +122,6 @@ declare class Database {
|
|
|
85
122
|
get closed(): boolean;
|
|
86
123
|
get readerCount(): number;
|
|
87
124
|
private ensureOpen;
|
|
88
|
-
private ensureCdc;
|
|
89
|
-
private ensureCdcPolling;
|
|
90
|
-
private stopCdcPollingLoop;
|
|
91
|
-
private fireBeforeQueryHooks;
|
|
92
|
-
private fireAfterQueryHooks;
|
|
93
125
|
}
|
|
94
126
|
|
|
95
127
|
export { Database as D, type HookDispose as H, MetricsCollector as M, type SubscribeHookContext as S, type HookEvent as a, type HookEventContextMap as b, type HookHandler as c, HookRegistry as d };
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import '../chunk-
|
|
1
|
+
import { nodeResolveExtensionPath, nodeBackupEngine, nodeWriterContext, WriterWorker, createStatementCache, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-4IGMIJQK.mjs';
|
|
2
|
+
import { defineDriver } from '../chunk-BNUTBHHH.mjs';
|
|
3
|
+
import '../chunk-DJLX6CAE.mjs';
|
|
4
|
+
import { synchronousPragmaValue } from '../chunk-CJLYFDP5.mjs';
|
|
5
|
+
import '../chunk-VLTICJOD.mjs';
|
|
6
|
+
import '../chunk-YPYVQJ4C.mjs';
|
|
3
7
|
|
|
4
8
|
// src/drivers/better-sqlite3/index.ts
|
|
5
9
|
function createConnection(db) {
|
|
10
|
+
const batchStatementFor = createStatementCache((sql) => db.prepare(sql));
|
|
6
11
|
const conn = {
|
|
7
12
|
async exec(sql) {
|
|
8
13
|
db.exec(sql);
|
|
@@ -11,20 +16,43 @@ function createConnection(db) {
|
|
|
11
16
|
const stmt = db.prepare(sql);
|
|
12
17
|
return {
|
|
13
18
|
async all(...params) {
|
|
19
|
+
return narrowRowsIntegers(stmt.all(...params));
|
|
20
|
+
},
|
|
21
|
+
async allRaw(...params) {
|
|
14
22
|
return stmt.all(...params);
|
|
15
23
|
},
|
|
16
24
|
async get(...params) {
|
|
17
|
-
return stmt.get(...params);
|
|
25
|
+
return narrowRowIntegers(stmt.get(...params));
|
|
18
26
|
},
|
|
19
27
|
async run(...params) {
|
|
20
28
|
const result = stmt.run(...params);
|
|
21
29
|
return {
|
|
22
|
-
changes: result.changes,
|
|
23
|
-
lastInsertRowId: result.lastInsertRowid
|
|
30
|
+
changes: Number(result.changes),
|
|
31
|
+
lastInsertRowId: narrowSafeBigInt(result.lastInsertRowid)
|
|
24
32
|
};
|
|
25
33
|
}
|
|
26
34
|
};
|
|
27
35
|
},
|
|
36
|
+
async runBatch(sql, paramsBatch) {
|
|
37
|
+
const stmt = batchStatementFor(sql);
|
|
38
|
+
const results = new Array(paramsBatch.length);
|
|
39
|
+
for (let i = 0; i < paramsBatch.length; i++) {
|
|
40
|
+
const result = stmt.run(...paramsBatch[i]);
|
|
41
|
+
results[i] = {
|
|
42
|
+
changes: Number(result.changes),
|
|
43
|
+
lastInsertRowId: narrowSafeBigInt(result.lastInsertRowid)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
},
|
|
48
|
+
async runBatchSummary(sql, paramsBatch) {
|
|
49
|
+
const stmt = batchStatementFor(sql);
|
|
50
|
+
let changes = 0;
|
|
51
|
+
for (let i = 0; i < paramsBatch.length; i++) {
|
|
52
|
+
changes += Number(stmt.run(...paramsBatch[i]).changes);
|
|
53
|
+
}
|
|
54
|
+
return { rowsLoaded: paramsBatch.length, changes };
|
|
55
|
+
},
|
|
28
56
|
async transaction(fn) {
|
|
29
57
|
await conn.exec("BEGIN");
|
|
30
58
|
try {
|
|
@@ -46,13 +74,23 @@ function createConnection(db) {
|
|
|
46
74
|
return conn;
|
|
47
75
|
}
|
|
48
76
|
function betterSqlite3(driverOptions) {
|
|
77
|
+
const workerEntry = { specifier: import.meta.url, exportName: "betterSqlite3", config: driverOptions };
|
|
49
78
|
return defineDriver({
|
|
50
79
|
capabilities: { multipleConnections: true, extensions: true },
|
|
80
|
+
worker: workerEntry,
|
|
81
|
+
startWriterHost: async (path, options, hostOptions) => {
|
|
82
|
+
const host = await WriterWorker.start(workerEntry, path, options, hostOptions);
|
|
83
|
+
return host.connection;
|
|
84
|
+
},
|
|
85
|
+
createWriterContext: nodeWriterContext,
|
|
86
|
+
createBackupEngine: nodeBackupEngine,
|
|
87
|
+
resolveExtensionPath: nodeResolveExtensionPath,
|
|
51
88
|
async open(path, options) {
|
|
52
89
|
const Database = (await import('better-sqlite3')).default;
|
|
53
90
|
const db = new Database(path, { readonly: options?.readonly ?? false });
|
|
91
|
+
db.defaultSafeIntegers(true);
|
|
54
92
|
if (options?.walMode !== false) db.pragma("journal_mode = WAL");
|
|
55
|
-
db.pragma(
|
|
93
|
+
db.pragma(`synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
|
|
56
94
|
db.pragma("foreign_keys = ON");
|
|
57
95
|
db.pragma(`busy_timeout = ${driverOptions?.busyTimeout ?? 5e3}`);
|
|
58
96
|
return createConnection(db);
|
package/dist/driver/bun.mjs
CHANGED
|
@@ -1,4 +1,30 @@
|
|
|
1
|
-
import { defineDriver } from '../chunk-
|
|
1
|
+
import { defineDriver, synchronousPragmaValue } from '../chunk-DVWQD3GF.mjs';
|
|
2
|
+
|
|
3
|
+
// src/core/driver/values.ts
|
|
4
|
+
var SAFE_INT_BOUND = 9007199254740991n;
|
|
5
|
+
function narrowSafeBigInt(value) {
|
|
6
|
+
if (typeof value === "bigint" && value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
|
|
7
|
+
return Number(value);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function narrowRowIntegers(row) {
|
|
12
|
+
if (row === null || typeof row !== "object") return row;
|
|
13
|
+
const obj = row;
|
|
14
|
+
for (const key of Object.keys(obj)) {
|
|
15
|
+
const value = obj[key];
|
|
16
|
+
if (typeof value === "bigint" && value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
|
|
17
|
+
obj[key] = Number(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return row;
|
|
21
|
+
}
|
|
22
|
+
function narrowRowsIntegers(rows) {
|
|
23
|
+
for (const row of rows) {
|
|
24
|
+
narrowRowIntegers(row);
|
|
25
|
+
}
|
|
26
|
+
return rows;
|
|
27
|
+
}
|
|
2
28
|
|
|
3
29
|
// src/drivers/bun/index.ts
|
|
4
30
|
function bunSqlite(driverOptions) {
|
|
@@ -6,9 +32,9 @@ function bunSqlite(driverOptions) {
|
|
|
6
32
|
capabilities: { multipleConnections: true, extensions: true },
|
|
7
33
|
async open(path, options) {
|
|
8
34
|
const { Database } = await import('bun:sqlite');
|
|
9
|
-
const db = new Database(path, { readonly: options?.readonly ?? false });
|
|
35
|
+
const db = new Database(path, { readonly: options?.readonly ?? false, safeIntegers: true });
|
|
10
36
|
if (options?.walMode !== false) db.run("PRAGMA journal_mode = WAL");
|
|
11
|
-
db.run(
|
|
37
|
+
db.run(`PRAGMA synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
|
|
12
38
|
db.run("PRAGMA foreign_keys = ON");
|
|
13
39
|
db.run(`PRAGMA busy_timeout = ${driverOptions?.busyTimeout ?? 5e3}`);
|
|
14
40
|
const conn = {
|
|
@@ -19,18 +45,21 @@ function bunSqlite(driverOptions) {
|
|
|
19
45
|
const stmt = db.query(sql);
|
|
20
46
|
return {
|
|
21
47
|
async all(...params) {
|
|
48
|
+
return narrowRowsIntegers(stmt.all(...params));
|
|
49
|
+
},
|
|
50
|
+
async allRaw(...params) {
|
|
22
51
|
return stmt.all(...params);
|
|
23
52
|
},
|
|
24
53
|
async get(...params) {
|
|
25
|
-
return stmt.get(...params) ?? void 0;
|
|
54
|
+
return narrowRowIntegers(stmt.get(...params) ?? void 0);
|
|
26
55
|
},
|
|
27
56
|
async run(...params) {
|
|
28
57
|
stmt.run(...params);
|
|
29
58
|
const changesStmt = db.query("SELECT changes() AS changes, last_insert_rowid() AS lastId");
|
|
30
59
|
const info = changesStmt.get();
|
|
31
60
|
return {
|
|
32
|
-
changes: info.changes,
|
|
33
|
-
lastInsertRowId: info.lastId
|
|
61
|
+
changes: Number(info.changes),
|
|
62
|
+
lastInsertRowId: narrowSafeBigInt(info.lastId)
|
|
34
63
|
};
|
|
35
64
|
}
|
|
36
65
|
};
|
package/dist/driver/expo.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineDriver } from '../chunk-
|
|
1
|
+
import { defineDriver, synchronousPragmaValue } from '../chunk-DVWQD3GF.mjs';
|
|
2
2
|
|
|
3
3
|
// src/drivers/expo/index.ts
|
|
4
4
|
function expoSqlite() {
|
|
@@ -10,7 +10,7 @@ function expoSqlite() {
|
|
|
10
10
|
readOnly: options?.readonly
|
|
11
11
|
});
|
|
12
12
|
if (options?.walMode !== false) await db.execAsync("PRAGMA journal_mode = WAL");
|
|
13
|
-
await db.execAsync(
|
|
13
|
+
await db.execAsync(`PRAGMA synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
|
|
14
14
|
await db.execAsync("PRAGMA foreign_keys = ON");
|
|
15
15
|
const buildConnectionFromHandle = (dbHandle) => ({
|
|
16
16
|
async exec(sql) {
|
package/dist/driver/node.d.ts
CHANGED
package/dist/driver/node.mjs
CHANGED
|
@@ -1,39 +1,82 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import '../chunk-
|
|
1
|
+
import { nodeResolveExtensionPath, nodeBackupEngine, nodeWriterContext, createStatementCache, WriterWorker, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-4IGMIJQK.mjs';
|
|
2
|
+
import { defineDriver } from '../chunk-BNUTBHHH.mjs';
|
|
3
|
+
import '../chunk-DJLX6CAE.mjs';
|
|
4
|
+
import { synchronousPragmaValue } from '../chunk-CJLYFDP5.mjs';
|
|
5
|
+
import '../chunk-VLTICJOD.mjs';
|
|
6
|
+
import '../chunk-YPYVQJ4C.mjs';
|
|
3
7
|
|
|
4
8
|
// src/drivers/node/index.ts
|
|
5
9
|
function nodeSqlite(driverOptions) {
|
|
10
|
+
const workerEntry = { specifier: import.meta.url, exportName: "nodeSqlite", config: driverOptions };
|
|
6
11
|
return defineDriver({
|
|
7
12
|
capabilities: { multipleConnections: true, extensions: true },
|
|
13
|
+
worker: workerEntry,
|
|
14
|
+
startWriterHost: async (path, options, hostOptions) => {
|
|
15
|
+
const host = await WriterWorker.start(workerEntry, path, options, hostOptions);
|
|
16
|
+
return host.connection;
|
|
17
|
+
},
|
|
18
|
+
createWriterContext: nodeWriterContext,
|
|
19
|
+
createBackupEngine: nodeBackupEngine,
|
|
20
|
+
resolveExtensionPath: nodeResolveExtensionPath,
|
|
8
21
|
async open(path, options) {
|
|
9
22
|
const { DatabaseSync } = await import('node:sqlite');
|
|
10
23
|
const db = new DatabaseSync(path, { readOnly: options?.readonly ?? false });
|
|
11
24
|
if (options?.walMode !== false) db.exec("PRAGMA journal_mode = WAL");
|
|
12
|
-
db.exec(
|
|
25
|
+
db.exec(`PRAGMA synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
|
|
13
26
|
db.exec("PRAGMA foreign_keys = ON");
|
|
14
27
|
db.exec(`PRAGMA busy_timeout = ${driverOptions?.busyTimeout ?? 5e3}`);
|
|
28
|
+
const batchStatementFor = createStatementCache((sql) => {
|
|
29
|
+
const stmt = db.prepare(sql);
|
|
30
|
+
stmt.setReadBigInts(true);
|
|
31
|
+
return stmt;
|
|
32
|
+
});
|
|
15
33
|
const conn = {
|
|
16
34
|
async exec(sql) {
|
|
17
35
|
db.exec(sql);
|
|
18
36
|
},
|
|
19
37
|
async prepare(sql) {
|
|
20
38
|
const stmt = db.prepare(sql);
|
|
39
|
+
stmt.setReadBigInts(true);
|
|
21
40
|
return {
|
|
22
41
|
async all(...params) {
|
|
42
|
+
return narrowRowsIntegers(stmt.all(...params));
|
|
43
|
+
},
|
|
44
|
+
async allRaw(...params) {
|
|
23
45
|
return stmt.all(...params);
|
|
24
46
|
},
|
|
25
47
|
async get(...params) {
|
|
26
|
-
return stmt.get(...params);
|
|
48
|
+
return narrowRowIntegers(stmt.get(...params));
|
|
27
49
|
},
|
|
28
50
|
async run(...params) {
|
|
29
51
|
const result = stmt.run(...params);
|
|
30
52
|
return {
|
|
31
53
|
changes: Number(result.changes),
|
|
32
|
-
lastInsertRowId: result.lastInsertRowid
|
|
54
|
+
lastInsertRowId: narrowSafeBigInt(result.lastInsertRowid)
|
|
33
55
|
};
|
|
34
56
|
}
|
|
35
57
|
};
|
|
36
58
|
},
|
|
59
|
+
async runBatch(sql, paramsBatch) {
|
|
60
|
+
const stmt = batchStatementFor(sql);
|
|
61
|
+
const results = new Array(paramsBatch.length);
|
|
62
|
+
for (let i = 0; i < paramsBatch.length; i++) {
|
|
63
|
+
const result = stmt.run(...paramsBatch[i]);
|
|
64
|
+
results[i] = {
|
|
65
|
+
changes: Number(result.changes),
|
|
66
|
+
lastInsertRowId: narrowSafeBigInt(result.lastInsertRowid)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
},
|
|
71
|
+
async runBatchSummary(sql, paramsBatch) {
|
|
72
|
+
const stmt = batchStatementFor(sql);
|
|
73
|
+
let changes = 0;
|
|
74
|
+
for (let i = 0; i < paramsBatch.length; i++) {
|
|
75
|
+
const result = stmt.run(...paramsBatch[i]);
|
|
76
|
+
changes += Number(result.changes);
|
|
77
|
+
}
|
|
78
|
+
return { rowsLoaded: paramsBatch.length, changes };
|
|
79
|
+
},
|
|
37
80
|
async transaction(fn) {
|
|
38
81
|
await conn.exec("BEGIN");
|
|
39
82
|
try {
|
|
@@ -1,29 +1,138 @@
|
|
|
1
|
+
/** Options for scheduled backups. */
|
|
2
|
+
interface BackupScheduleOptions {
|
|
3
|
+
/** Cron expression (e.g., '0 * * * *' for hourly). */
|
|
4
|
+
cron: string;
|
|
5
|
+
/** Directory to store backup files. */
|
|
6
|
+
destDir: string;
|
|
7
|
+
/** Maximum number of backup files to keep. Default: 5. */
|
|
8
|
+
maxFiles?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Sirannon evaluates the cron expression in this IANA time zone (e.g. 'America/New_York').
|
|
11
|
+
* When omitted, it uses the host's local time zone, which also sets the daylight saving rules that apply.
|
|
12
|
+
*/
|
|
13
|
+
timezone?: string;
|
|
14
|
+
/** Called when a scheduled backup fails. Without this, errors are silently discarded. */
|
|
15
|
+
onError?: (error: Error) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface WorkerHostOptions {
|
|
19
|
+
writeTimeoutMs?: number;
|
|
20
|
+
maxRestarts?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
1
23
|
interface RunResult {
|
|
2
24
|
changes: number;
|
|
3
25
|
lastInsertRowId: number | bigint;
|
|
4
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Tells a caller running inside the operation that holds the writer from one
|
|
29
|
+
* merely waiting on it. A runtime without async context tracking cannot answer
|
|
30
|
+
* this, and answering it wrongly runs one caller's writes inside another
|
|
31
|
+
* caller's transaction.
|
|
32
|
+
*/
|
|
33
|
+
interface WriterContext {
|
|
34
|
+
run<T>(operation: () => T): T;
|
|
35
|
+
isActive(): boolean;
|
|
36
|
+
exit<T>(operation: () => T): T;
|
|
37
|
+
}
|
|
38
|
+
interface BackupEngine {
|
|
39
|
+
backup(conn: SQLiteConnection, destPath: string): Promise<void>;
|
|
40
|
+
schedule(conn: SQLiteConnection, options: BackupScheduleOptions, runExclusive: (op: () => Promise<void>) => Promise<void>): () => void;
|
|
41
|
+
}
|
|
42
|
+
interface BatchSummary {
|
|
43
|
+
rowsLoaded: number;
|
|
44
|
+
changes: number;
|
|
45
|
+
}
|
|
5
46
|
interface SQLiteStatement {
|
|
6
47
|
all<T = unknown>(...params: unknown[]): Promise<T[]>;
|
|
7
48
|
get<T = unknown>(...params: unknown[]): Promise<T | undefined>;
|
|
8
49
|
run(...params: unknown[]): Promise<RunResult>;
|
|
50
|
+
/**
|
|
51
|
+
* Like {@link all} but skips the safe-range BigInt narrowing, leaving every
|
|
52
|
+
* integer as a BigInt. The server wire path narrows and tags in one pass, so
|
|
53
|
+
* feeding it raw rows avoids a second walk. Optional: a driver that omits it
|
|
54
|
+
* falls back to {@link all}, still correct but with the extra narrowing walk.
|
|
55
|
+
*/
|
|
56
|
+
allRaw?<T = unknown>(...params: unknown[]): Promise<T[]>;
|
|
9
57
|
}
|
|
58
|
+
interface GroupRunError {
|
|
59
|
+
message: string;
|
|
60
|
+
name?: string;
|
|
61
|
+
code?: string;
|
|
62
|
+
}
|
|
63
|
+
type GroupRunOutcome = {
|
|
64
|
+
ok: true;
|
|
65
|
+
results: RunResult[];
|
|
66
|
+
} | {
|
|
67
|
+
ok: false;
|
|
68
|
+
error: GroupRunError;
|
|
69
|
+
};
|
|
10
70
|
interface SQLiteConnection {
|
|
11
71
|
exec(sql: string): Promise<void>;
|
|
12
72
|
prepare(sql: string): Promise<SQLiteStatement>;
|
|
13
73
|
transaction<T>(fn: (conn: SQLiteConnection) => Promise<T>): Promise<T>;
|
|
14
74
|
close(): Promise<void>;
|
|
75
|
+
runBatch?(sql: string, paramsBatch: readonly unknown[][]): Promise<RunResult[]>;
|
|
76
|
+
runBatchSummary?(sql: string, paramsBatch: readonly unknown[][]): Promise<BatchSummary>;
|
|
77
|
+
/**
|
|
78
|
+
* Runs several independent units in one transaction, one outcome per unit in
|
|
79
|
+
* order. A unit is one write or one whole transaction, and a unit that fails
|
|
80
|
+
* must not disturb the others.
|
|
81
|
+
*/
|
|
82
|
+
runGroup?(units: readonly {
|
|
83
|
+
statements: readonly {
|
|
84
|
+
sql: string;
|
|
85
|
+
params?: readonly unknown[];
|
|
86
|
+
}[];
|
|
87
|
+
}[]): Promise<GroupRunOutcome[]>;
|
|
15
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* SQLite `PRAGMA synchronous` level applied to a connection. `normal` is safe
|
|
91
|
+
* from corruption in WAL mode but can lose the most recent commits on power
|
|
92
|
+
* loss; `full` fsyncs every commit; `extra` adds a directory sync after the
|
|
93
|
+
* rollback journal is unlinked in DELETE journal mode and equals `full` in
|
|
94
|
+
* WAL mode; `off` hands writes to the OS without syncing and is sanctioned
|
|
95
|
+
* only for re-runnable bulk loads.
|
|
96
|
+
*/
|
|
97
|
+
type SynchronousLevel = 'off' | 'normal' | 'full' | 'extra';
|
|
16
98
|
interface OpenOptions {
|
|
17
99
|
readonly?: boolean;
|
|
18
100
|
walMode?: boolean;
|
|
101
|
+
synchronous?: SynchronousLevel;
|
|
19
102
|
}
|
|
20
103
|
interface DriverCapabilities {
|
|
21
104
|
multipleConnections: boolean;
|
|
22
105
|
extensions: boolean;
|
|
23
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Lets a worker thread rebuild the driver, since the driver's `open` function
|
|
109
|
+
* cannot cross the thread boundary. `specifier` must be importable from the
|
|
110
|
+
* worker and `config` must survive a structured clone; the worker imports the
|
|
111
|
+
* module and calls its `exportName` factory (default export otherwise) with it.
|
|
112
|
+
*/
|
|
113
|
+
interface DriverWorkerEntry {
|
|
114
|
+
specifier: string;
|
|
115
|
+
exportName?: string;
|
|
116
|
+
config?: unknown;
|
|
117
|
+
}
|
|
24
118
|
interface SQLiteDriver {
|
|
25
119
|
readonly capabilities: DriverCapabilities;
|
|
26
120
|
open(path: string, options?: OpenOptions): Promise<SQLiteConnection>;
|
|
121
|
+
readonly worker?: DriverWorkerEntry;
|
|
122
|
+
/**
|
|
123
|
+
* Offloads writes to a worker thread. Only a driver whose runtime has
|
|
124
|
+
* threads implements this, which is what keeps the thread machinery out of
|
|
125
|
+
* bundles built for runtimes that do not.
|
|
126
|
+
*/
|
|
127
|
+
startWriterHost?(path: string, options: OpenOptions, hostOptions?: WorkerHostOptions): Promise<SQLiteConnection>;
|
|
128
|
+
createWriterContext?(): WriterContext;
|
|
129
|
+
createBackupEngine?(): BackupEngine;
|
|
130
|
+
/**
|
|
131
|
+
* Makes an extension path absolute. Passing a bare name to `load_extension`
|
|
132
|
+
* would let the dynamic linker search its own paths and open a different
|
|
133
|
+
* library than the operator named.
|
|
134
|
+
*/
|
|
135
|
+
resolveExtensionPath?(extensionPath: string): string;
|
|
27
136
|
}
|
|
28
137
|
|
|
29
138
|
interface WaSqliteOptions {
|
|
@@ -12,12 +12,40 @@ function defineDriver(config) {
|
|
|
12
12
|
if (!config.capabilities || typeof config.open !== "function") {
|
|
13
13
|
throw new SirannonError("Driver must define capabilities and open()", "INVALID_DRIVER");
|
|
14
14
|
}
|
|
15
|
+
if (config.worker !== void 0 && typeof config.worker.specifier !== "string") {
|
|
16
|
+
throw new SirannonError("Driver worker entry must define a string specifier", "INVALID_DRIVER");
|
|
17
|
+
}
|
|
15
18
|
return Object.freeze({
|
|
16
19
|
capabilities: Object.freeze({ ...config.capabilities }),
|
|
17
|
-
open: config.open
|
|
20
|
+
open: config.open,
|
|
21
|
+
...config.worker ? { worker: Object.freeze({ ...config.worker }) } : {},
|
|
22
|
+
...config.startWriterHost ? { startWriterHost: config.startWriterHost } : {},
|
|
23
|
+
...config.createWriterContext ? { createWriterContext: config.createWriterContext } : {},
|
|
24
|
+
...config.createBackupEngine ? { createBackupEngine: config.createBackupEngine } : {},
|
|
25
|
+
...config.resolveExtensionPath ? { resolveExtensionPath: config.resolveExtensionPath } : {}
|
|
18
26
|
});
|
|
19
27
|
}
|
|
20
28
|
|
|
29
|
+
// src/core/driver/synchronous.ts
|
|
30
|
+
var DEFAULT_SYNCHRONOUS = "normal";
|
|
31
|
+
var SYNCHRONOUS_PRAGMA_VALUES = {
|
|
32
|
+
off: "OFF",
|
|
33
|
+
normal: "NORMAL",
|
|
34
|
+
full: "FULL",
|
|
35
|
+
extra: "EXTRA"
|
|
36
|
+
};
|
|
37
|
+
function synchronousPragmaValue(level) {
|
|
38
|
+
const resolved = level ?? DEFAULT_SYNCHRONOUS;
|
|
39
|
+
const value = SYNCHRONOUS_PRAGMA_VALUES[resolved];
|
|
40
|
+
if (value === void 0) {
|
|
41
|
+
throw new SirannonError(
|
|
42
|
+
`Invalid synchronous level '${String(resolved)}': expected 'off', 'normal', 'full', or 'extra'`,
|
|
43
|
+
"INVALID_SYNCHRONOUS"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
21
49
|
// src/drivers/wa-sqlite/index.ts
|
|
22
50
|
function waSqlite(driverOptions) {
|
|
23
51
|
return defineDriver({
|
|
@@ -46,7 +74,7 @@ function waSqlite(driverOptions) {
|
|
|
46
74
|
} catch {
|
|
47
75
|
}
|
|
48
76
|
}
|
|
49
|
-
await sqlite3.exec(db,
|
|
77
|
+
await sqlite3.exec(db, `PRAGMA synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
|
|
50
78
|
await sqlite3.exec(db, "PRAGMA foreign_keys = ON");
|
|
51
79
|
async function execStatements(sql, params) {
|
|
52
80
|
const columns = [];
|