@xnetjs/sqlite 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-t2aqQ__n.d.ts → adapter-imtxkmXZ.d.ts} +22 -1
- package/dist/adapters/electron.d.ts +66 -6
- package/dist/adapters/electron.js +629 -47
- package/dist/adapters/expo.d.ts +6 -2
- package/dist/adapters/expo.js +8 -1
- package/dist/adapters/memory.d.ts +6 -2
- package/dist/adapters/memory.js +8 -1
- package/dist/adapters/reader-thread.d.ts +78 -0
- package/dist/adapters/reader-thread.js +57 -0
- package/dist/adapters/web-proxy.d.ts +54 -2
- package/dist/adapters/web-proxy.js +458 -55
- package/dist/adapters/web-router-worker.d.ts +76 -0
- package/dist/adapters/web-router-worker.js +91 -0
- package/dist/adapters/web-worker.d.ts +45 -1
- package/dist/adapters/web-worker.js +232 -17
- package/dist/adapters/web.d.ts +82 -3
- package/dist/adapters/web.js +7 -4
- package/dist/browser-support.d.ts +10 -1
- package/dist/browser-support.js +5 -1
- package/dist/chunk-5HC5V73T.js +191 -0
- package/dist/{chunk-DCY4LAUD.js → chunk-BBZDKLA3.js} +246 -13
- package/dist/chunk-CGI2YBZY.js +16 -0
- package/dist/{chunk-2OK46ZBU.js → chunk-S6MT6KCI.js} +22 -1
- package/dist/chunk-SV475UL5.js +44 -0
- package/dist/{chunk-NVD7G7DZ.js → chunk-W3L4FXU5.js} +11 -2
- package/dist/index.d.ts +92 -8
- package/dist/index.js +38 -9
- package/dist/schema-C4gufY3B.d.ts +35 -0
- package/dist/types-BTabr_VP.d.ts +225 -0
- package/dist/worker-scheduler-D04DqMmR.d.ts +56 -0
- package/package.json +5 -1
- package/dist/schema-BEgIA-rZ.d.ts +0 -35
- package/dist/types-DMrj3K4o.d.ts +0 -132
package/dist/adapters/expo.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SQLiteAdapter, P as PreparedStatement } from '../adapter-
|
|
2
|
-
import { b as SQLiteConfig, a as SQLRow, S as SQLValue, R as RunResult } from '../types-
|
|
1
|
+
import { S as SQLiteAdapter, P as PreparedStatement } from '../adapter-imtxkmXZ.js';
|
|
2
|
+
import { b as SQLiteConfig, a as SQLRow, S as SQLValue, R as RunResult } from '../types-BTabr_VP.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @xnetjs/sqlite - Expo SQLite adapter using expo-sqlite
|
|
@@ -31,6 +31,10 @@ declare class ExpoSQLiteAdapter implements SQLiteAdapter {
|
|
|
31
31
|
isOpen(): boolean;
|
|
32
32
|
query<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T[]>;
|
|
33
33
|
queryOne<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T | null>;
|
|
34
|
+
queryBatch(reads: Array<{
|
|
35
|
+
sql: string;
|
|
36
|
+
params?: SQLValue[];
|
|
37
|
+
}>): Promise<SQLRow[][]>;
|
|
34
38
|
run(sql: string, params?: SQLValue[]): Promise<RunResult>;
|
|
35
39
|
exec(sql: string): Promise<void>;
|
|
36
40
|
transaction<T>(fn: () => Promise<T>): Promise<T>;
|
package/dist/adapters/expo.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
SCHEMA_DDL,
|
|
6
6
|
SCHEMA_VERSION
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-W3L4FXU5.js";
|
|
8
8
|
|
|
9
9
|
// src/adapters/expo.ts
|
|
10
10
|
var ExpoSQLiteAdapter = class {
|
|
@@ -53,6 +53,13 @@ var ExpoSQLiteAdapter = class {
|
|
|
53
53
|
const result = await this.db.getFirstAsync(sql, params ?? []);
|
|
54
54
|
return result ?? null;
|
|
55
55
|
}
|
|
56
|
+
async queryBatch(reads) {
|
|
57
|
+
const results = [];
|
|
58
|
+
for (const read of reads) {
|
|
59
|
+
results.push(await this.query(read.sql, read.params));
|
|
60
|
+
}
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
56
63
|
async run(sql, params) {
|
|
57
64
|
this.ensureOpen();
|
|
58
65
|
const result = await this.db.runAsync(sql, params ?? []);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SQLiteAdapter, P as PreparedStatement } from '../adapter-
|
|
2
|
-
import { b as SQLiteConfig, a as SQLRow, S as SQLValue, R as RunResult } from '../types-
|
|
1
|
+
import { S as SQLiteAdapter, P as PreparedStatement } from '../adapter-imtxkmXZ.js';
|
|
2
|
+
import { b as SQLiteConfig, a as SQLRow, S as SQLValue, R as RunResult } from '../types-BTabr_VP.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @xnetjs/sqlite - In-memory SQLite adapter for testing
|
|
@@ -22,6 +22,10 @@ declare class MemorySQLiteAdapter implements SQLiteAdapter {
|
|
|
22
22
|
isOpen(): boolean;
|
|
23
23
|
query<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T[]>;
|
|
24
24
|
queryOne<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T | null>;
|
|
25
|
+
queryBatch(reads: Array<{
|
|
26
|
+
sql: string;
|
|
27
|
+
params?: SQLValue[];
|
|
28
|
+
}>): Promise<SQLRow[][]>;
|
|
25
29
|
run(sql: string, params?: SQLValue[]): Promise<RunResult>;
|
|
26
30
|
exec(sql: string): Promise<void>;
|
|
27
31
|
transaction<T>(fn: () => Promise<T>): Promise<T>;
|
package/dist/adapters/memory.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SCHEMA_DDL_CORE,
|
|
3
3
|
SCHEMA_VERSION
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-W3L4FXU5.js";
|
|
5
5
|
|
|
6
6
|
// src/adapters/memory.ts
|
|
7
7
|
var MemorySQLiteAdapter = class {
|
|
@@ -47,6 +47,13 @@ SQL: ${sql}`);
|
|
|
47
47
|
const rows = await this.query(sql, params);
|
|
48
48
|
return rows[0] ?? null;
|
|
49
49
|
}
|
|
50
|
+
async queryBatch(reads) {
|
|
51
|
+
const results = [];
|
|
52
|
+
for (const read of reads) {
|
|
53
|
+
results.push(await this.query(read.sql, read.params));
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
50
57
|
async run(sql, params) {
|
|
51
58
|
this.ensureOpen();
|
|
52
59
|
try {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { S as SQLValue, a as SQLRow } from '../types-BTabr_VP.js';
|
|
2
|
+
import DatabaseType from 'better-sqlite3';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @xnetjs/sqlite - Read-only reader thread for the Electron reader pool
|
|
6
|
+
*
|
|
7
|
+
* Runs in a Node `worker_threads` worker spawned by {@link ReaderPool}
|
|
8
|
+
* (exploration 0230). It opens its **own** `better-sqlite3` connection to the
|
|
9
|
+
* same database file in **read-only** mode and serves SELECTs against the WAL
|
|
10
|
+
* snapshot. Because native SQLite + WAL allows one writer concurrent with many
|
|
11
|
+
* readers — each on its own connection — these threads run genuinely in parallel
|
|
12
|
+
* on other cores, unlike the browser (where the `opfs-sahpool` VFS holds an
|
|
13
|
+
* exclusive handle and a second connection is impossible, exploration 0228).
|
|
14
|
+
*
|
|
15
|
+
* Each reader uses one-shot autocommit selects (no long-lived read transaction),
|
|
16
|
+
* so every query observes the latest committed snapshot and nothing holds back
|
|
17
|
+
* the writer's WAL checkpoint.
|
|
18
|
+
*
|
|
19
|
+
* The request handler is exported so it can be unit-tested against an in-process
|
|
20
|
+
* connection without spawning a worker.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** A read request sent from the pool to a reader thread. */
|
|
24
|
+
type ReaderRequest = {
|
|
25
|
+
id: number;
|
|
26
|
+
op: 'query';
|
|
27
|
+
sql: string;
|
|
28
|
+
params?: SQLValue[];
|
|
29
|
+
} | {
|
|
30
|
+
id: number;
|
|
31
|
+
op: 'queryOne';
|
|
32
|
+
sql: string;
|
|
33
|
+
params?: SQLValue[];
|
|
34
|
+
} | {
|
|
35
|
+
id: number;
|
|
36
|
+
op: 'ping';
|
|
37
|
+
};
|
|
38
|
+
/** A reader thread's response, correlated back to the request by `id`. */
|
|
39
|
+
type ReaderResponse = {
|
|
40
|
+
id: number;
|
|
41
|
+
ok: true;
|
|
42
|
+
rows: SQLRow[];
|
|
43
|
+
} | {
|
|
44
|
+
id: number;
|
|
45
|
+
ok: true;
|
|
46
|
+
row: SQLRow | null;
|
|
47
|
+
} | {
|
|
48
|
+
id: number;
|
|
49
|
+
ok: true;
|
|
50
|
+
pong: true;
|
|
51
|
+
} | {
|
|
52
|
+
id: number;
|
|
53
|
+
ok: false;
|
|
54
|
+
error: string;
|
|
55
|
+
};
|
|
56
|
+
/** Signal the pool the reader booted (or failed to). `id` is always 0. */
|
|
57
|
+
type ReaderReady = {
|
|
58
|
+
id: 0;
|
|
59
|
+
ready: true;
|
|
60
|
+
} | {
|
|
61
|
+
id: 0;
|
|
62
|
+
ready: false;
|
|
63
|
+
error: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Execute one reader request against `db`, caching prepared statements per
|
|
67
|
+
* connection. Pure and synchronous — `better-sqlite3` is synchronous, which is
|
|
68
|
+
* exactly why a reader thread fully occupies a core for the duration of a query.
|
|
69
|
+
*/
|
|
70
|
+
declare function handleReaderRequest(db: DatabaseType.Database, cache: Map<string, DatabaseType.Statement>, req: ReaderRequest): ReaderResponse;
|
|
71
|
+
/**
|
|
72
|
+
* Open a read-only connection for a reader thread. Applies connection-local
|
|
73
|
+
* pragmas only — `journal_mode` is owned by the writer and cannot be set on a
|
|
74
|
+
* read-only handle, but the reader transparently observes the file's WAL.
|
|
75
|
+
*/
|
|
76
|
+
declare function openReaderConnection(dbPath: string): Promise<DatabaseType.Database>;
|
|
77
|
+
|
|
78
|
+
export { type ReaderReady, type ReaderRequest, type ReaderResponse, handleReaderRequest, openReaderConnection };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/adapters/reader-thread.ts
|
|
2
|
+
function handleReaderRequest(db, cache, req) {
|
|
3
|
+
try {
|
|
4
|
+
if (req.op === "ping") {
|
|
5
|
+
return { id: req.id, ok: true, pong: true };
|
|
6
|
+
}
|
|
7
|
+
let stmt = cache.get(req.sql);
|
|
8
|
+
if (!stmt) {
|
|
9
|
+
stmt = db.prepare(req.sql);
|
|
10
|
+
cache.set(req.sql, stmt);
|
|
11
|
+
}
|
|
12
|
+
if (req.op === "query") {
|
|
13
|
+
const rows = req.params ? stmt.all(...req.params) : stmt.all();
|
|
14
|
+
return { id: req.id, ok: true, rows };
|
|
15
|
+
}
|
|
16
|
+
const row = req.params ? stmt.get(...req.params) : stmt.get();
|
|
17
|
+
return { id: req.id, ok: true, row: row ?? null };
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return { id: req.id, ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function openReaderConnection(dbPath) {
|
|
23
|
+
const { default: Database } = await import("better-sqlite3");
|
|
24
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
25
|
+
db.pragma("busy_timeout = 5000");
|
|
26
|
+
db.pragma("cache_size = -32000");
|
|
27
|
+
db.pragma("temp_store = MEMORY");
|
|
28
|
+
return db;
|
|
29
|
+
}
|
|
30
|
+
async function bootstrapReaderThread() {
|
|
31
|
+
const { parentPort, workerData, isMainThread } = await import("worker_threads");
|
|
32
|
+
if (isMainThread || !parentPort) return;
|
|
33
|
+
const dbPath = workerData?.dbPath;
|
|
34
|
+
if (!dbPath) {
|
|
35
|
+
parentPort.postMessage({ id: 0, ready: false, error: "reader thread: missing dbPath" });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const db = await openReaderConnection(dbPath);
|
|
40
|
+
const cache = /* @__PURE__ */ new Map();
|
|
41
|
+
parentPort.on("message", (req) => {
|
|
42
|
+
parentPort.postMessage(handleReaderRequest(db, cache, req));
|
|
43
|
+
});
|
|
44
|
+
parentPort.postMessage({ id: 0, ready: true });
|
|
45
|
+
} catch (err) {
|
|
46
|
+
parentPort.postMessage({
|
|
47
|
+
id: 0,
|
|
48
|
+
ready: false,
|
|
49
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
void bootstrapReaderThread();
|
|
54
|
+
export {
|
|
55
|
+
handleReaderRequest,
|
|
56
|
+
openReaderConnection
|
|
57
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { S as SQLiteAdapter, P as PreparedStatement } from '../adapter-
|
|
2
|
-
import { b as SQLiteConfig, a as SQLRow, S as SQLValue, R as RunResult,
|
|
1
|
+
import { S as SQLiteAdapter, P as PreparedStatement } from '../adapter-imtxkmXZ.js';
|
|
2
|
+
import { b as SQLiteConfig, a as SQLRow, S as SQLValue, c as SQLBatchRead, R as RunResult, l as SQLiteNodeBatchApplyInput, m as SQLiteNodeBatchApplyResult, e as SQLiteOperationStats } from '../types-BTabr_VP.js';
|
|
3
|
+
import { a as SchedulerOpStats } from '../worker-scheduler-D04DqMmR.js';
|
|
4
|
+
|
|
5
|
+
type TabRole = 'leader' | 'follower';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* @xnetjs/sqlite - Main thread proxy for SQLite Web Worker
|
|
@@ -26,13 +29,50 @@ declare class WebSQLiteProxy implements SQLiteAdapter {
|
|
|
26
29
|
private _config;
|
|
27
30
|
private inTransaction;
|
|
28
31
|
private operationStats;
|
|
32
|
+
/** 'single-tab' = the pre-0263 per-tab worker path (or multiTab: false). */
|
|
33
|
+
private role;
|
|
34
|
+
private roleHandle;
|
|
35
|
+
private router;
|
|
36
|
+
private unsubscribeLeaderServe;
|
|
37
|
+
private unsubscribeRouterEvents;
|
|
38
|
+
/** The Comlink port into the LEADER's SQLite worker (follower role only). */
|
|
39
|
+
private followerPort;
|
|
40
|
+
private readonly guard;
|
|
41
|
+
/** Resolvers waiting for the connection to come back after leader loss. */
|
|
42
|
+
private reconnectWaiters;
|
|
43
|
+
/** Serializes role transitions (promotion vs. reattach can race). */
|
|
44
|
+
private transition;
|
|
29
45
|
private createWorkerProxy;
|
|
30
46
|
open(config: SQLiteConfig): Promise<void>;
|
|
47
|
+
private openSingleTab;
|
|
48
|
+
private openMultiTab;
|
|
49
|
+
private becomeLeaderOnRouter;
|
|
50
|
+
private attachToLeader;
|
|
51
|
+
/** This tab won the leadership lock after the previous leader went away. */
|
|
52
|
+
private promoteToLeader;
|
|
53
|
+
/** Another tab became leader; drop the dead port and fetch a fresh one. */
|
|
54
|
+
private reattachFollower;
|
|
55
|
+
private enqueueTransition;
|
|
56
|
+
private notifyReconnected;
|
|
57
|
+
private whenReconnected;
|
|
58
|
+
private teardownMultiTab;
|
|
59
|
+
/**
|
|
60
|
+
* Run a worker RPC with follower protections: leader loss REJECTS in-flight
|
|
61
|
+
* calls immediately instead of hanging, and `retryable` (idempotent read)
|
|
62
|
+
* calls transparently re-issue once the connection is re-established —
|
|
63
|
+
* against this tab's own worker if it was promoted, or the new leader's.
|
|
64
|
+
*/
|
|
65
|
+
private rpc;
|
|
31
66
|
resetStorage(config: SQLiteConfig): Promise<void>;
|
|
32
67
|
close(): Promise<void>;
|
|
33
68
|
isOpen(): boolean;
|
|
34
69
|
query<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T[]>;
|
|
35
70
|
queryOne<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Execute several reads in ONE worker round-trip. N queries previously cost
|
|
73
|
+
* N postMessage round-trips; a batch costs one (exploration 0263).
|
|
74
|
+
*/
|
|
75
|
+
queryBatch(reads: SQLBatchRead[]): Promise<SQLRow[][]>;
|
|
36
76
|
run(sql: string, params?: SQLValue[]): Promise<RunResult>;
|
|
37
77
|
exec(sql: string): Promise<void>;
|
|
38
78
|
transaction<T>(_fn: () => Promise<T>): Promise<T>;
|
|
@@ -62,8 +102,11 @@ declare class WebSQLiteProxy implements SQLiteAdapter {
|
|
|
62
102
|
applySchema(version: number, sql: string): Promise<boolean>;
|
|
63
103
|
getDatabaseSize(): Promise<number>;
|
|
64
104
|
vacuum(): Promise<void>;
|
|
105
|
+
incrementalVacuum(maxPages?: number): Promise<number>;
|
|
65
106
|
checkpoint(): Promise<number>;
|
|
66
107
|
getStorageMode(): Promise<'opfs' | 'memory'>;
|
|
108
|
+
/** This tab's multi-tab role: 'leader', 'follower', or 'single-tab'. */
|
|
109
|
+
getTabRole(): TabRole | 'single-tab';
|
|
67
110
|
/**
|
|
68
111
|
* Create a MessagePort connected directly to the SQLite worker.
|
|
69
112
|
*
|
|
@@ -72,6 +115,15 @@ declare class WebSQLiteProxy implements SQLiteAdapter {
|
|
|
72
115
|
* (e.g. the data worker) so its storage calls skip the main thread.
|
|
73
116
|
*/
|
|
74
117
|
createMessagePort(): Promise<MessagePort>;
|
|
118
|
+
/**
|
|
119
|
+
* Worker-side scheduler stats: per-lane p50/p95 queue+exec latency and
|
|
120
|
+
* coalesce hits (exploration 0263). Complements getOperationStats(), which
|
|
121
|
+
* counts main-thread RPCs — together they give "how many round-trips" and
|
|
122
|
+
* "how long each op spent in the worker".
|
|
123
|
+
*/
|
|
124
|
+
getSchedulerOpStats(): Promise<SchedulerOpStats>;
|
|
125
|
+
/** Zero the worker-side scheduler stats for a focused measurement. */
|
|
126
|
+
resetSchedulerOpStats(): Promise<void>;
|
|
75
127
|
getOperationStats(): SQLiteOperationStats;
|
|
76
128
|
resetOperationStats(): void;
|
|
77
129
|
}
|