@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/adapters/opfs-capability.ts
|
|
2
|
+
function resolveScope(scope) {
|
|
3
|
+
if (scope) return scope;
|
|
4
|
+
const g = globalThis;
|
|
5
|
+
return g;
|
|
6
|
+
}
|
|
7
|
+
function supportsOpfs(scope) {
|
|
8
|
+
return typeof resolveScope(scope).navigator?.storage?.getDirectory === "function";
|
|
9
|
+
}
|
|
10
|
+
function supportsSyncAccessHandle(scope) {
|
|
11
|
+
const s = resolveScope(scope);
|
|
12
|
+
if (typeof s.FileSystemSyncAccessHandle !== "undefined") return true;
|
|
13
|
+
const proto = s.FileSystemFileHandle?.prototype;
|
|
14
|
+
return Boolean(proto && "createSyncAccessHandle" in proto);
|
|
15
|
+
}
|
|
16
|
+
function isCrossOriginIsolated(scope) {
|
|
17
|
+
const s = resolveScope(scope);
|
|
18
|
+
return typeof s.SharedArrayBuffer !== "undefined" && s.crossOriginIsolated === true;
|
|
19
|
+
}
|
|
20
|
+
function detectOpfsCapability(scope) {
|
|
21
|
+
const opfs = supportsOpfs(scope);
|
|
22
|
+
const syncAccessHandle = supportsSyncAccessHandle(scope);
|
|
23
|
+
const crossOriginIsolated = isCrossOriginIsolated(scope);
|
|
24
|
+
let mode;
|
|
25
|
+
let reason;
|
|
26
|
+
if (!opfs) {
|
|
27
|
+
mode = "memory";
|
|
28
|
+
reason = "OPFS is unavailable in this context (private browsing or an unsupported engine); local data will not persist across reloads.";
|
|
29
|
+
} else if (syncAccessHandle) {
|
|
30
|
+
mode = "sync-access-handle";
|
|
31
|
+
reason = "OPFS sync access handles available \u2014 using the durable opfs-sahpool fast path.";
|
|
32
|
+
} else {
|
|
33
|
+
mode = "async-opfs";
|
|
34
|
+
reason = "OPFS is available but sync access handles are not (iOS 15.2\u201316.3 or an older WebView); falling back to the slower async OPFS backend. Data still persists.";
|
|
35
|
+
}
|
|
36
|
+
return { opfs, syncAccessHandle, crossOriginIsolated, mode, reason };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
supportsOpfs,
|
|
41
|
+
supportsSyncAccessHandle,
|
|
42
|
+
isCrossOriginIsolated,
|
|
43
|
+
detectOpfsCapability
|
|
44
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/schema.ts
|
|
2
|
-
var SCHEMA_VERSION =
|
|
2
|
+
var SCHEMA_VERSION = 7;
|
|
3
3
|
var SCHEMA_DDL_CORE = `
|
|
4
4
|
-- ============================================
|
|
5
5
|
-- Schema Version Tracking
|
|
@@ -89,7 +89,12 @@ CREATE TABLE IF NOT EXISTS node_query_materializations (
|
|
|
89
89
|
descriptor_json TEXT NOT NULL,
|
|
90
90
|
generated_at INTEGER NOT NULL,
|
|
91
91
|
invalidated_at INTEGER,
|
|
92
|
-
row_count INTEGER NOT NULL
|
|
92
|
+
row_count INTEGER NOT NULL,
|
|
93
|
+
-- Authorization fingerprint the view was materialized under (exploration
|
|
94
|
+
-- 0226). NULL when authz is off; a mismatch forces an 'authz-changed'
|
|
95
|
+
-- refresh so a cached id list can never serve rows the viewer can no
|
|
96
|
+
-- longer read.
|
|
97
|
+
auth_fingerprint TEXT
|
|
93
98
|
);
|
|
94
99
|
|
|
95
100
|
CREATE TABLE IF NOT EXISTS node_query_materialized_ids (
|
|
@@ -383,6 +388,10 @@ CREATE INDEX IF NOT EXISTS idx_prop_scalars_node
|
|
|
383
388
|
ON node_property_scalars(node_id);
|
|
384
389
|
CREATE INDEX IF NOT EXISTS idx_changes_node_lamport
|
|
385
390
|
ON changes(node_id, lamport_time DESC, hash);
|
|
391
|
+
`,
|
|
392
|
+
7: `
|
|
393
|
+
ALTER TABLE node_query_materializations
|
|
394
|
+
ADD COLUMN auth_fingerprint TEXT;
|
|
386
395
|
`
|
|
387
396
|
};
|
|
388
397
|
function getMigrationSQL(fromVersion, toVersion) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { S as SQLValue } from './types-
|
|
2
|
-
export { R as RunResult, a as SQLRow, b as SQLiteConfig,
|
|
3
|
-
import { S as SQLiteAdapter } from './adapter-
|
|
4
|
-
export { P as PreparedStatement } from './adapter-
|
|
5
|
-
export { a as SCHEMA_DDL, b as SCHEMA_DDL_CORE, c as SCHEMA_DDL_FTS, d as SCHEMA_MIGRATIONS, S as SCHEMA_VERSION, g as getMigrationSQL } from './schema-
|
|
6
|
-
export { BrowserSupport, PersistentStorageRequestOptions, PersistentStorageStatus, checkBrowserSupport, checkPersistentStorage, isSilentPersistRequestSafe, requestPersistentStorage, showUnsupportedBrowserMessage, watchPersistentStoragePermission } from './browser-support.js';
|
|
1
|
+
import { S as SQLValue } from './types-BTabr_VP.js';
|
|
2
|
+
export { E as ElectronSQLiteDiagnostics, R as RunResult, c as SQLBatchRead, a as SQLRow, b as SQLiteConfig, l as SQLiteNodeBatchApplyInput, m as SQLiteNodeBatchApplyResult, i as SQLiteNodeBatchChangeRow, k as SQLiteNodeBatchFtsRow, f as SQLiteNodeBatchIndexMode, g as SQLiteNodeBatchNodeRow, h as SQLiteNodeBatchPropertyRow, j as SQLiteNodeBatchScalarIndexRow, e as SQLiteOperationStats, d as SchemaVersion } from './types-BTabr_VP.js';
|
|
3
|
+
import { S as SQLiteAdapter } from './adapter-imtxkmXZ.js';
|
|
4
|
+
export { P as PreparedStatement } from './adapter-imtxkmXZ.js';
|
|
5
|
+
export { a as SCHEMA_DDL, b as SCHEMA_DDL_CORE, c as SCHEMA_DDL_FTS, d as SCHEMA_MIGRATIONS, S as SCHEMA_VERSION, g as getMigrationSQL } from './schema-C4gufY3B.js';
|
|
6
|
+
export { BrowserSupport, PersistentStorageRequestOptions, PersistentStorageStatus, checkBrowserSupport, checkPersistentStorage, getMemoryFallbackSessionCount, isSilentPersistRequestSafe, recordMemoryFallbackSession, requestPersistentStorage, showUnsupportedBrowserMessage, watchPersistentStoragePermission } from './browser-support.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @xnetjs/sqlite - SQL query building helpers
|
|
@@ -161,7 +161,10 @@ interface SQLiteRuntimeCapabilities {
|
|
|
161
161
|
rtree: boolean;
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
|
-
* Get information about all indexes in the database.
|
|
164
|
+
* Get information about all indexes in the database. Cached per adapter and keyed
|
|
165
|
+
* on `PRAGMA schema_version`, so repeated calls between DDL changes pay one cheap
|
|
166
|
+
* version probe instead of `sqlite_master` + N `PRAGMA index_info` round-trips
|
|
167
|
+
* (exploration 0253).
|
|
165
168
|
*/
|
|
166
169
|
declare function getIndexInfo(db: SQLiteAdapter): Promise<IndexInfo[]>;
|
|
167
170
|
/**
|
|
@@ -223,4 +226,85 @@ declare function timeQuery<T>(db: SQLiteAdapter, sql: string, params?: SQLValue[
|
|
|
223
226
|
*/
|
|
224
227
|
declare function isSQLiteCorruptionError(error: unknown): boolean;
|
|
225
228
|
|
|
226
|
-
|
|
229
|
+
/**
|
|
230
|
+
* @xnetjs/sqlite — OPFS capability detection (exploration 0238)
|
|
231
|
+
*
|
|
232
|
+
* The web adapter persists through the Origin Private File System. Which OPFS
|
|
233
|
+
* backend is reachable depends on the runtime, and the difference matters most
|
|
234
|
+
* inside a **mobile webview**:
|
|
235
|
+
*
|
|
236
|
+
* - `opfs-sahpool` (the fast path) needs synchronous access handles —
|
|
237
|
+
* `FileSystemSyncAccessHandle` — which land in **iOS 16.4+** and Chromium 108+
|
|
238
|
+
* (Android System WebView). This is what {@link WebSQLiteAdapter} installs.
|
|
239
|
+
* - Below that (iOS 15.2–16.3, older WebViews) only the **async** OPFS VFS
|
|
240
|
+
* (`sqlite3.oo1.OpfsDb`) works — still durable, just slower I/O.
|
|
241
|
+
* - With no OPFS at all (private mode, ancient engines) the adapter falls back
|
|
242
|
+
* to a non-durable in-memory database.
|
|
243
|
+
*
|
|
244
|
+
* The web adapter already walks that fallback chain at `open()`; this module
|
|
245
|
+
* makes the decision *legible* — a pure, injectable predicate the adapter uses
|
|
246
|
+
* to emit an accurate "why are we on the slow/no-persistence path" diagnostic,
|
|
247
|
+
* and that the host app / tests can call directly to branch on capability.
|
|
248
|
+
*/
|
|
249
|
+
/** The durable OPFS backend a context can support, best first. */
|
|
250
|
+
type OpfsPersistenceMode = 'sync-access-handle' | 'async-opfs' | 'memory';
|
|
251
|
+
/** Structured capability report for the current (or an injected) scope. */
|
|
252
|
+
interface OpfsCapability {
|
|
253
|
+
/** OPFS root is reachable (`navigator.storage.getDirectory`). */
|
|
254
|
+
opfs: boolean;
|
|
255
|
+
/**
|
|
256
|
+
* Synchronous access handles exist → the `opfs-sahpool` fast path is usable
|
|
257
|
+
* (iOS 16.4+, Chromium 108+). When false on an OPFS-capable engine we are on
|
|
258
|
+
* an older iOS/WebView and must use the async OPFS VFS.
|
|
259
|
+
*/
|
|
260
|
+
syncAccessHandle: boolean;
|
|
261
|
+
/**
|
|
262
|
+
* `SharedArrayBuffer` is present *and* the context is cross-origin isolated.
|
|
263
|
+
* Required by the highest-performance sqlite-wasm mode; in a webview this is
|
|
264
|
+
* what `capacitor://localhost` + COOP/COEP unlocks (exploration 0238).
|
|
265
|
+
*/
|
|
266
|
+
crossOriginIsolated: boolean;
|
|
267
|
+
/** Best durable backend this context supports. */
|
|
268
|
+
mode: OpfsPersistenceMode;
|
|
269
|
+
/** Human-readable explanation, useful for the iOS <16.4 async-fallback case. */
|
|
270
|
+
reason: string;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* The slice of global APIs capability detection reads. Injectable so the
|
|
274
|
+
* shared (non-isolated) `unit` test pool can probe synthetic environments
|
|
275
|
+
* without mutating real globals.
|
|
276
|
+
*/
|
|
277
|
+
interface OpfsCapabilityScope {
|
|
278
|
+
navigator?: {
|
|
279
|
+
storage?: {
|
|
280
|
+
getDirectory?: unknown;
|
|
281
|
+
};
|
|
282
|
+
} | undefined;
|
|
283
|
+
FileSystemSyncAccessHandle?: unknown;
|
|
284
|
+
FileSystemFileHandle?: {
|
|
285
|
+
prototype?: Record<string, unknown>;
|
|
286
|
+
} | undefined;
|
|
287
|
+
SharedArrayBuffer?: unknown;
|
|
288
|
+
crossOriginIsolated?: unknown;
|
|
289
|
+
}
|
|
290
|
+
/** True when the context exposes OPFS via `navigator.storage.getDirectory`. */
|
|
291
|
+
declare function supportsOpfs(scope?: OpfsCapabilityScope): boolean;
|
|
292
|
+
/**
|
|
293
|
+
* True when synchronous access handles are available — the gate for the
|
|
294
|
+
* `opfs-sahpool` fast path (iOS 16.4+, Chromium 108+). Checks both the global
|
|
295
|
+
* constructor and the `createSyncAccessHandle` method on the file-handle
|
|
296
|
+
* prototype, since engines have shipped one or the other.
|
|
297
|
+
*/
|
|
298
|
+
declare function supportsSyncAccessHandle(scope?: OpfsCapabilityScope): boolean;
|
|
299
|
+
/**
|
|
300
|
+
* True when a `SharedArrayBuffer` can actually be used — present *and* the
|
|
301
|
+
* context is cross-origin isolated (the browser ungated SAB behind COI).
|
|
302
|
+
*/
|
|
303
|
+
declare function isCrossOriginIsolated(scope?: OpfsCapabilityScope): boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Report the best durable OPFS backend the given scope supports, with a reason.
|
|
306
|
+
* Pure and synchronous — safe to call before any sqlite-wasm import.
|
|
307
|
+
*/
|
|
308
|
+
declare function detectOpfsCapability(scope?: OpfsCapabilityScope): OpfsCapability;
|
|
309
|
+
|
|
310
|
+
export { type DatabaseStats, type FTSSearchOptions, type FTSSearchResult, type IndexInfo, type OpfsCapability, type OpfsCapabilityScope, type OpfsPersistenceMode, type QueryPlanStep, SQLValue, SQLiteAdapter, type SQLiteRuntimeCapabilities, type TableStats, analyzeQuery, analyzeTable, buildBatchInsert, buildInsert, buildSelect, buildUpdate, checkIntegrity, deleteNodeFTS, detectOpfsCapability, detectSQLiteCapabilities, escapeLike, explainQuery, extractSearchableContent, extractTextFromTipTap, getAllTableStats, getDatabaseStats, getIndexInfo, isCrossOriginIsolated, isSQLiteCorruptionError, optimizeFTS, rebuildFTS, runAnalyze, searchNodes, supportsOpfs, supportsSyncAccessHandle, timeQuery, updateNodeFTS };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkBrowserSupport,
|
|
3
|
+
checkPersistentStorage,
|
|
4
|
+
getMemoryFallbackSessionCount,
|
|
5
|
+
isSilentPersistRequestSafe,
|
|
6
|
+
recordMemoryFallbackSession,
|
|
7
|
+
requestPersistentStorage,
|
|
8
|
+
showUnsupportedBrowserMessage,
|
|
9
|
+
watchPersistentStoragePermission
|
|
10
|
+
} from "./chunk-S6MT6KCI.js";
|
|
11
|
+
import {
|
|
12
|
+
detectOpfsCapability,
|
|
13
|
+
isCrossOriginIsolated,
|
|
14
|
+
supportsOpfs,
|
|
15
|
+
supportsSyncAccessHandle
|
|
16
|
+
} from "./chunk-SV475UL5.js";
|
|
1
17
|
import {
|
|
2
18
|
isSQLiteCorruptionError
|
|
3
19
|
} from "./chunk-CBU263LI.js";
|
|
@@ -8,15 +24,7 @@ import {
|
|
|
8
24
|
SCHEMA_MIGRATIONS,
|
|
9
25
|
SCHEMA_VERSION,
|
|
10
26
|
getMigrationSQL
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import {
|
|
13
|
-
checkBrowserSupport,
|
|
14
|
-
checkPersistentStorage,
|
|
15
|
-
isSilentPersistRequestSafe,
|
|
16
|
-
requestPersistentStorage,
|
|
17
|
-
showUnsupportedBrowserMessage,
|
|
18
|
-
watchPersistentStoragePermission
|
|
19
|
-
} from "./chunk-2OK46ZBU.js";
|
|
27
|
+
} from "./chunk-W3L4FXU5.js";
|
|
20
28
|
|
|
21
29
|
// src/query-builder.ts
|
|
22
30
|
function buildInsert(table, columns, options) {
|
|
@@ -62,7 +70,21 @@ function buildBatchInsert(table, columns, rowCount) {
|
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
// src/diagnostics.ts
|
|
73
|
+
var indexInfoCache = /* @__PURE__ */ new WeakMap();
|
|
74
|
+
async function readSchemaVersion(db) {
|
|
75
|
+
try {
|
|
76
|
+
const row = await db.queryOne("PRAGMA schema_version");
|
|
77
|
+
return Number(row?.schema_version ?? 0);
|
|
78
|
+
} catch {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
65
82
|
async function getIndexInfo(db) {
|
|
83
|
+
const schemaVersion = await readSchemaVersion(db);
|
|
84
|
+
const cached = indexInfoCache.get(db);
|
|
85
|
+
if (cached && cached.schemaVersion === schemaVersion) {
|
|
86
|
+
return cached.indexes;
|
|
87
|
+
}
|
|
66
88
|
const indexes = await db.query(
|
|
67
89
|
"SELECT name, tbl_name, sql FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%'"
|
|
68
90
|
);
|
|
@@ -77,6 +99,7 @@ async function getIndexInfo(db) {
|
|
|
77
99
|
partial: idx.sql?.includes("WHERE") ?? false
|
|
78
100
|
});
|
|
79
101
|
}
|
|
102
|
+
indexInfoCache.set(db, { schemaVersion, indexes: result });
|
|
80
103
|
return result;
|
|
81
104
|
}
|
|
82
105
|
async function analyzeQuery(db, sql, params) {
|
|
@@ -390,6 +413,7 @@ export {
|
|
|
390
413
|
checkIntegrity,
|
|
391
414
|
checkPersistentStorage,
|
|
392
415
|
deleteNodeFTS,
|
|
416
|
+
detectOpfsCapability,
|
|
393
417
|
detectSQLiteCapabilities,
|
|
394
418
|
escapeLike,
|
|
395
419
|
explainQuery,
|
|
@@ -398,15 +422,20 @@ export {
|
|
|
398
422
|
getAllTableStats,
|
|
399
423
|
getDatabaseStats,
|
|
400
424
|
getIndexInfo,
|
|
425
|
+
getMemoryFallbackSessionCount,
|
|
401
426
|
getMigrationSQL,
|
|
427
|
+
isCrossOriginIsolated,
|
|
402
428
|
isSQLiteCorruptionError,
|
|
403
429
|
isSilentPersistRequestSafe,
|
|
404
430
|
optimizeFTS,
|
|
405
431
|
rebuildFTS,
|
|
432
|
+
recordMemoryFallbackSession,
|
|
406
433
|
requestPersistentStorage,
|
|
407
434
|
runAnalyze,
|
|
408
435
|
searchNodes,
|
|
409
436
|
showUnsupportedBrowserMessage,
|
|
437
|
+
supportsOpfs,
|
|
438
|
+
supportsSyncAccessHandle,
|
|
410
439
|
timeQuery,
|
|
411
440
|
updateNodeFTS,
|
|
412
441
|
watchPersistentStoragePermission
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xnetjs/sqlite - Unified SQLite schema for xNet
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Current schema version.
|
|
6
|
+
* Increment this when making schema changes.
|
|
7
|
+
*/
|
|
8
|
+
declare const SCHEMA_VERSION = 7;
|
|
9
|
+
/**
|
|
10
|
+
* Core SQLite schema for xNet (without FTS5).
|
|
11
|
+
* This schema works on all platforms including sql.js.
|
|
12
|
+
*/
|
|
13
|
+
declare const SCHEMA_DDL_CORE = "\n-- ============================================\n-- Schema Version Tracking\n-- ============================================\n\nCREATE TABLE IF NOT EXISTS _schema_version (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n);\n\n-- ============================================\n-- Core Tables\n-- ============================================\n\n-- All nodes (Pages, Databases, Rows, Comments, etc.)\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n schema_id TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n created_by TEXT NOT NULL,\n deleted_at INTEGER\n);\n\n-- Node properties (LWW per-property)\nCREATE TABLE IF NOT EXISTS node_properties (\n node_id TEXT NOT NULL,\n property_key TEXT NOT NULL,\n value BLOB,\n lamport_time INTEGER NOT NULL,\n updated_by TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n\n PRIMARY KEY (node_id, property_key),\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE\n);\n\n-- Rebuildable scalar property index for query planning.\nCREATE TABLE IF NOT EXISTS node_property_scalars (\n node_id TEXT NOT NULL,\n schema_id TEXT NOT NULL,\n property_key TEXT NOT NULL,\n value_type TEXT NOT NULL,\n value_text TEXT,\n value_number REAL,\n value_boolean INTEGER,\n value_hash TEXT,\n updated_at INTEGER NOT NULL,\n lamport_time INTEGER NOT NULL,\n\n PRIMARY KEY (schema_id, property_key, node_id),\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE\n);\n\n-- Query planner telemetry for adaptive read indexes.\nCREATE TABLE IF NOT EXISTS query_descriptor_stats (\n descriptor_hash TEXT PRIMARY KEY,\n schema_id TEXT NOT NULL,\n descriptor_json TEXT NOT NULL,\n hits INTEGER NOT NULL,\n total_duration_ms REAL NOT NULL,\n avg_duration_ms REAL NOT NULL,\n avg_candidates REAL NOT NULL,\n last_seen_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS query_index_candidates (\n index_name TEXT PRIMARY KEY,\n descriptor_hash TEXT NOT NULL,\n schema_id TEXT NOT NULL,\n property_key TEXT NOT NULL,\n value_type TEXT NOT NULL,\n ddl TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n last_used_at INTEGER NOT NULL,\n estimated_bytes INTEGER NOT NULL DEFAULT 0,\n estimated_rows INTEGER NOT NULL DEFAULT 0,\n\n FOREIGN KEY (descriptor_hash) REFERENCES query_descriptor_stats(descriptor_hash)\n ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS node_query_materializations (\n view_id TEXT PRIMARY KEY,\n descriptor_hash TEXT NOT NULL,\n schema_id TEXT NOT NULL,\n descriptor_json TEXT NOT NULL,\n generated_at INTEGER NOT NULL,\n invalidated_at INTEGER,\n row_count INTEGER NOT NULL,\n -- Authorization fingerprint the view was materialized under (exploration\n -- 0226). NULL when authz is off; a mismatch forces an 'authz-changed'\n -- refresh so a cached id list can never serve rows the viewer can no\n -- longer read.\n auth_fingerprint TEXT\n);\n\nCREATE TABLE IF NOT EXISTS node_query_materialized_ids (\n view_id TEXT NOT NULL,\n ordinal INTEGER NOT NULL,\n node_id TEXT NOT NULL,\n\n PRIMARY KEY (view_id, ordinal),\n UNIQUE (view_id, node_id),\n FOREIGN KEY (view_id) REFERENCES node_query_materializations(view_id)\n ON DELETE CASCADE,\n FOREIGN KEY (node_id) REFERENCES nodes(id)\n ON DELETE CASCADE\n);\n\n-- Change log (event sourcing)\nCREATE TABLE IF NOT EXISTS changes (\n hash TEXT PRIMARY KEY,\n node_id TEXT NOT NULL,\n payload BLOB NOT NULL,\n lamport_time INTEGER NOT NULL,\n lamport_peer TEXT NOT NULL,\n wall_time INTEGER NOT NULL,\n author TEXT NOT NULL,\n parent_hash TEXT,\n batch_id TEXT,\n signature BLOB NOT NULL,\n\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE\n);\n\n-- Y.Doc binary state (for nodes with collaborative content)\nCREATE TABLE IF NOT EXISTS yjs_state (\n node_id TEXT PRIMARY KEY,\n state BLOB NOT NULL,\n updated_at INTEGER NOT NULL,\n\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE\n);\n\n-- Y.Doc incremental updates (for sync)\nCREATE TABLE IF NOT EXISTS yjs_updates (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n node_id TEXT NOT NULL,\n update_data BLOB NOT NULL,\n timestamp INTEGER NOT NULL,\n origin TEXT,\n\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE\n);\n\n-- Yjs snapshots (for document time travel)\nCREATE TABLE IF NOT EXISTS yjs_snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n node_id TEXT NOT NULL,\n timestamp INTEGER NOT NULL,\n snapshot BLOB NOT NULL,\n doc_state BLOB NOT NULL,\n byte_size INTEGER NOT NULL,\n\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE\n);\n\n-- Blobs (content-addressed)\nCREATE TABLE IF NOT EXISTS blobs (\n cid TEXT PRIMARY KEY,\n data BLOB NOT NULL,\n mime_type TEXT,\n size INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n reference_count INTEGER DEFAULT 1\n);\n\n-- Documents (for @xnetjs/storage compatibility)\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n content BLOB NOT NULL,\n metadata TEXT NOT NULL,\n version INTEGER NOT NULL DEFAULT 1\n);\n\n-- Signed updates (for @xnetjs/storage compatibility)\nCREATE TABLE IF NOT EXISTS updates (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n doc_id TEXT NOT NULL,\n update_hash TEXT NOT NULL,\n update_data TEXT NOT NULL,\n created_at INTEGER DEFAULT (strftime('%s', 'now') * 1000),\n UNIQUE(doc_id, update_hash)\n);\n\n-- Snapshots (for @xnetjs/storage compatibility)\nCREATE TABLE IF NOT EXISTS snapshots (\n doc_id TEXT PRIMARY KEY,\n snapshot_data TEXT NOT NULL,\n created_at INTEGER DEFAULT (strftime('%s', 'now') * 1000)\n);\n\n-- Sync metadata\nCREATE TABLE IF NOT EXISTS sync_state (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- ============================================\n-- Indexes\n-- ============================================\n\nCREATE INDEX IF NOT EXISTS idx_nodes_schema ON nodes(schema_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_updated ON nodes(updated_at);\nCREATE INDEX IF NOT EXISTS idx_nodes_created_by ON nodes(created_by);\nCREATE INDEX IF NOT EXISTS idx_nodes_deleted ON nodes(deleted_at);\nCREATE INDEX IF NOT EXISTS idx_nodes_live_schema_updated\n ON nodes(schema_id, updated_at DESC, id)\n WHERE deleted_at IS NULL;\nCREATE INDEX IF NOT EXISTS idx_nodes_all_schema_updated\n ON nodes(schema_id, updated_at DESC, id);\nCREATE INDEX IF NOT EXISTS idx_nodes_live_schema_created\n ON nodes(schema_id, created_at DESC, id)\n WHERE deleted_at IS NULL;\n\nCREATE INDEX IF NOT EXISTS idx_properties_node ON node_properties(node_id);\nCREATE INDEX IF NOT EXISTS idx_properties_lamport ON node_properties(lamport_time);\n\nCREATE INDEX IF NOT EXISTS idx_prop_scalars_text\n ON node_property_scalars(schema_id, property_key, value_text, node_id)\n WHERE value_type = 'text';\nCREATE INDEX IF NOT EXISTS idx_prop_scalars_number\n ON node_property_scalars(schema_id, property_key, value_number, node_id)\n WHERE value_type = 'number';\nCREATE INDEX IF NOT EXISTS idx_prop_scalars_boolean\n ON node_property_scalars(schema_id, property_key, value_boolean, node_id)\n WHERE value_type = 'boolean';\nCREATE INDEX IF NOT EXISTS idx_prop_scalars_null\n ON node_property_scalars(schema_id, property_key, node_id)\n WHERE value_type = 'null';\nCREATE INDEX IF NOT EXISTS idx_prop_scalars_node\n ON node_property_scalars(node_id);\n\nCREATE INDEX IF NOT EXISTS idx_query_stats_schema_seen\n ON query_descriptor_stats(schema_id, last_seen_at DESC);\nCREATE INDEX IF NOT EXISTS idx_query_indexes_schema_property\n ON query_index_candidates(schema_id, property_key, value_type);\nCREATE INDEX IF NOT EXISTS idx_query_materializations_schema\n ON node_query_materializations(schema_id, invalidated_at);\nCREATE INDEX IF NOT EXISTS idx_query_materialized_ids_node\n ON node_query_materialized_ids(node_id);\n\nCREATE INDEX IF NOT EXISTS idx_changes_node ON changes(node_id);\nCREATE INDEX IF NOT EXISTS idx_changes_lamport ON changes(lamport_time);\nCREATE INDEX IF NOT EXISTS idx_changes_wall_time ON changes(wall_time);\nCREATE INDEX IF NOT EXISTS idx_changes_batch ON changes(batch_id);\nCREATE INDEX IF NOT EXISTS idx_changes_node_lamport\n ON changes(node_id, lamport_time DESC, hash);\n\nCREATE INDEX IF NOT EXISTS idx_yjs_state_updated ON yjs_state(updated_at);\nCREATE INDEX IF NOT EXISTS idx_yjs_updates_node ON yjs_updates(node_id);\nCREATE INDEX IF NOT EXISTS idx_yjs_snapshots_node ON yjs_snapshots(node_id);\nCREATE INDEX IF NOT EXISTS idx_yjs_snapshots_timestamp ON yjs_snapshots(node_id, timestamp);\n\nCREATE INDEX IF NOT EXISTS idx_updates_doc ON updates(doc_id);\nCREATE INDEX IF NOT EXISTS idx_updates_created ON updates(created_at);\n";
|
|
14
|
+
/**
|
|
15
|
+
* FTS5 schema for full-text search.
|
|
16
|
+
* This is applied separately because sql.js doesn't support FTS5.
|
|
17
|
+
*/
|
|
18
|
+
declare const SCHEMA_DDL_FTS = "\n-- ============================================\n-- Full-Text Search (FTS5)\n-- ============================================\n\n-- FTS index for searchable node content\nCREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(\n node_id,\n title,\n content,\n tokenize='porter unicode61'\n);\n\n-- Triggers to keep FTS in sync will be managed by application layer\n-- since the searchable content is derived from node properties\n";
|
|
19
|
+
/**
|
|
20
|
+
* Unified SQLite schema for xNet.
|
|
21
|
+
* This schema is shared across all platforms (with FTS5).
|
|
22
|
+
* Use SCHEMA_DDL_CORE for platforms without FTS5 support (sql.js).
|
|
23
|
+
*/
|
|
24
|
+
declare const SCHEMA_DDL: string;
|
|
25
|
+
/**
|
|
26
|
+
* Schema for future versions (migrations).
|
|
27
|
+
* Each key is the version number, value is the upgrade SQL.
|
|
28
|
+
*/
|
|
29
|
+
declare const SCHEMA_MIGRATIONS: Record<number, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the SQL to upgrade from one version to another.
|
|
32
|
+
*/
|
|
33
|
+
declare function getMigrationSQL(fromVersion: number, toVersion: number): string;
|
|
34
|
+
|
|
35
|
+
export { SCHEMA_VERSION as S, SCHEMA_DDL as a, SCHEMA_DDL_CORE as b, SCHEMA_DDL_FTS as c, SCHEMA_MIGRATIONS as d, getMigrationSQL as g };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xnetjs/sqlite - Type definitions for unified SQLite adapter
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* SQL parameter types that can be bound to statements.
|
|
6
|
+
*/
|
|
7
|
+
type SQLValue = string | number | bigint | Uint8Array | null;
|
|
8
|
+
/**
|
|
9
|
+
* Row type for query results.
|
|
10
|
+
*/
|
|
11
|
+
type SQLRow = Record<string, SQLValue>;
|
|
12
|
+
/**
|
|
13
|
+
* One read of a {@link SQLiteAdapter.queryBatch} call: a SELECT plus its
|
|
14
|
+
* bound parameters. Results come back positionally, one row array per read.
|
|
15
|
+
*/
|
|
16
|
+
interface SQLBatchRead {
|
|
17
|
+
sql: string;
|
|
18
|
+
params?: SQLValue[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Result of a mutation query (INSERT, UPDATE, DELETE).
|
|
22
|
+
*/
|
|
23
|
+
interface RunResult {
|
|
24
|
+
/** Number of rows affected by the query */
|
|
25
|
+
changes: number;
|
|
26
|
+
/** Last inserted row ID (for INSERT with AUTOINCREMENT) */
|
|
27
|
+
lastInsertRowid: bigint;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Configuration options for SQLite database.
|
|
31
|
+
*/
|
|
32
|
+
interface SQLiteConfig {
|
|
33
|
+
/** Database file path or name */
|
|
34
|
+
path: string;
|
|
35
|
+
/** Enable WAL mode (default: true) */
|
|
36
|
+
walMode?: boolean;
|
|
37
|
+
/** Enable foreign keys (default: true) */
|
|
38
|
+
foreignKeys?: boolean;
|
|
39
|
+
/** Busy timeout in milliseconds (default: 5000) */
|
|
40
|
+
busyTimeout?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Emit boot diagnostics from the worker: a per-operation queue/exec timing
|
|
43
|
+
* trace and a one-shot DB-stats line at open (exploration 0229). Set by the
|
|
44
|
+
* main thread, which can read the `xnet:boot:debug` flag — the worker can't
|
|
45
|
+
* (`localStorage` is unavailable in workers).
|
|
46
|
+
*/
|
|
47
|
+
bootDebug?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Per-attempt timeout (ms) for the web worker's `open()` before the worker is
|
|
50
|
+
* terminated and retried with a fresh one (default: 15000). A cold
|
|
51
|
+
* `installOpfsSAHPoolVfs()` on a large DB file can intermittently exceed this —
|
|
52
|
+
* usually because a prior boot leaked a worker still holding the file's
|
|
53
|
+
* exclusive OPFS handle; terminating + retrying clears that contention rather
|
|
54
|
+
* than hard-failing the boot (exploration 0253). Web adapter only.
|
|
55
|
+
*/
|
|
56
|
+
openTimeoutMs?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Multi-tab leadership routing (exploration 0263). When Web Locks and
|
|
59
|
+
* SharedWorker are available, tabs elect a leader that owns the SQLite
|
|
60
|
+
* worker; other tabs route their storage RPCs to it instead of losing the
|
|
61
|
+
* OPFS handle race and silently falling back to a non-durable `:memory:`
|
|
62
|
+
* database (exploration 0204). Default: `true` where supported — set `false`
|
|
63
|
+
* to force the previous per-tab behaviour. Web proxy only.
|
|
64
|
+
*/
|
|
65
|
+
multiTab?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Front every storage operation with the priority scheduler so a queued write
|
|
68
|
+
* burst can't head-of-line block an interactive read, and a manual transaction
|
|
69
|
+
* holds the connection exclusively (`BEGIN`…`COMMIT` can't be interleaved).
|
|
70
|
+
* Default: `true`. Has no effect on the WASM/web adapters, which schedule in
|
|
71
|
+
* their worker host instead.
|
|
72
|
+
*/
|
|
73
|
+
scheduler?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Open a second, **read-only** `better-sqlite3` connection so plain reads use
|
|
76
|
+
* a different connection than the writer (no contention with write locks).
|
|
77
|
+
* Ignored for `:memory:` databases (each connection would be a separate DB).
|
|
78
|
+
* Default: `false`.
|
|
79
|
+
*/
|
|
80
|
+
readonlyReadConnection?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Spawn a pool of read-only `better-sqlite3` reader threads so **heavy** reads
|
|
83
|
+
* (FTS, large aggregates, big scans) run in parallel on other cores instead of
|
|
84
|
+
* blocking the data-process thread. `'auto'` sizes the pool to the host's core
|
|
85
|
+
* count (capped). `0` / `undefined` disables it. Ignored for `:memory:`.
|
|
86
|
+
* Default: disabled.
|
|
87
|
+
*/
|
|
88
|
+
readerPoolSize?: number | 'auto';
|
|
89
|
+
/**
|
|
90
|
+
* Reads issued within this many milliseconds of the most recent commit route
|
|
91
|
+
* to the writer connection (read-your-writes safety) instead of the read-only
|
|
92
|
+
* connection / reader pool. WAL already makes committed writes visible across
|
|
93
|
+
* in-process connections, so the default `0` trusts WAL; raise it only if a
|
|
94
|
+
* platform shows stale cross-connection reads.
|
|
95
|
+
*/
|
|
96
|
+
readYourWritesWindowMs?: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Point-in-time diagnostics for the Electron SQLite adapter (exploration 0230):
|
|
100
|
+
* scheduler queue depth, reader-pool occupancy, and WAL growth. Surfaced to the
|
|
101
|
+
* desktop diagnostics seam.
|
|
102
|
+
*/
|
|
103
|
+
interface ElectronSQLiteDiagnostics {
|
|
104
|
+
/** Scheduler lane depths + in-flight flag, or null when the scheduler is off. */
|
|
105
|
+
scheduler: {
|
|
106
|
+
interactive: number;
|
|
107
|
+
bulk: number;
|
|
108
|
+
write: number;
|
|
109
|
+
inFlight: boolean;
|
|
110
|
+
} | null;
|
|
111
|
+
/** Reader-thread pool occupancy, or null when no pool is configured. */
|
|
112
|
+
readerPool: {
|
|
113
|
+
size: number;
|
|
114
|
+
healthy: number;
|
|
115
|
+
inFlight: number;
|
|
116
|
+
dispatched: number;
|
|
117
|
+
failures: number;
|
|
118
|
+
} | null;
|
|
119
|
+
/** WAL file growth, or null when unavailable / not in WAL mode. */
|
|
120
|
+
wal: {
|
|
121
|
+
walBytes: number;
|
|
122
|
+
pageCount: number;
|
|
123
|
+
} | null;
|
|
124
|
+
/** Whether a read-only secondary connection is open. */
|
|
125
|
+
readonlyConnection: boolean;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Schema version information.
|
|
129
|
+
*/
|
|
130
|
+
interface SchemaVersion {
|
|
131
|
+
version: number;
|
|
132
|
+
appliedAt: number;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Runtime operation counters for diagnosing SQLite import performance.
|
|
136
|
+
*
|
|
137
|
+
* Counts are cumulative until reset by the adapter. Proxy-backed adapters can
|
|
138
|
+
* use `workerRequestCount` to show serialization-boundary pressure separately
|
|
139
|
+
* from the SQL statements executed by SQLite.
|
|
140
|
+
*/
|
|
141
|
+
interface SQLiteOperationStats {
|
|
142
|
+
/** SELECT statements returning many rows. */
|
|
143
|
+
queryCount: number;
|
|
144
|
+
/** SELECT statements returning at most one row. */
|
|
145
|
+
queryOneCount: number;
|
|
146
|
+
/** INSERT, UPDATE, DELETE, or other mutation statements. */
|
|
147
|
+
runCount: number;
|
|
148
|
+
/** Raw SQL executions, often schema or transaction control statements. */
|
|
149
|
+
execCount: number;
|
|
150
|
+
/** Callback transactions requested through this adapter. */
|
|
151
|
+
transactionCount: number;
|
|
152
|
+
/** Batch transactions requested through this adapter. */
|
|
153
|
+
transactionBatchCount: number;
|
|
154
|
+
/** SQL operations included in batch transactions. */
|
|
155
|
+
transactionBatchOperationCount: number;
|
|
156
|
+
/** Comlink or postMessage requests crossing into a worker. */
|
|
157
|
+
workerRequestCount: number;
|
|
158
|
+
}
|
|
159
|
+
type SQLiteNodeBatchIndexMode = 'eager' | 'touched' | 'defer-schema';
|
|
160
|
+
interface SQLiteNodeBatchNodeRow {
|
|
161
|
+
id: string;
|
|
162
|
+
schemaId: string;
|
|
163
|
+
createdAt: number;
|
|
164
|
+
updatedAt: number;
|
|
165
|
+
createdBy: string;
|
|
166
|
+
deletedAt: number | null;
|
|
167
|
+
propertyKeys: string[];
|
|
168
|
+
}
|
|
169
|
+
interface SQLiteNodeBatchPropertyRow {
|
|
170
|
+
nodeId: string;
|
|
171
|
+
propertyKey: string;
|
|
172
|
+
value: Uint8Array | null;
|
|
173
|
+
lamportTime: number;
|
|
174
|
+
updatedBy: string;
|
|
175
|
+
updatedAt: number;
|
|
176
|
+
}
|
|
177
|
+
interface SQLiteNodeBatchChangeRow {
|
|
178
|
+
hash: string;
|
|
179
|
+
nodeId: string;
|
|
180
|
+
payload: Uint8Array;
|
|
181
|
+
lamportTime: number;
|
|
182
|
+
lamportPeer: string;
|
|
183
|
+
wallTime: number;
|
|
184
|
+
author: string;
|
|
185
|
+
parentHash: string | null;
|
|
186
|
+
batchId: string | null;
|
|
187
|
+
signature: Uint8Array;
|
|
188
|
+
}
|
|
189
|
+
interface SQLiteNodeBatchScalarIndexRow {
|
|
190
|
+
nodeId: string;
|
|
191
|
+
schemaId: string;
|
|
192
|
+
propertyKey: string;
|
|
193
|
+
valueType: string;
|
|
194
|
+
valueText: string | null;
|
|
195
|
+
valueNumber: number | null;
|
|
196
|
+
valueBoolean: number | null;
|
|
197
|
+
valueHash: string | null;
|
|
198
|
+
updatedAt: number;
|
|
199
|
+
lamportTime: number;
|
|
200
|
+
}
|
|
201
|
+
interface SQLiteNodeBatchFtsRow {
|
|
202
|
+
nodeId: string;
|
|
203
|
+
title: string;
|
|
204
|
+
content: string;
|
|
205
|
+
}
|
|
206
|
+
interface SQLiteNodeBatchApplyInput {
|
|
207
|
+
nodes: SQLiteNodeBatchNodeRow[];
|
|
208
|
+
properties: SQLiteNodeBatchPropertyRow[];
|
|
209
|
+
changes: SQLiteNodeBatchChangeRow[];
|
|
210
|
+
scalarIndexRows: SQLiteNodeBatchScalarIndexRow[];
|
|
211
|
+
ftsNodeIds: string[];
|
|
212
|
+
ftsRows: SQLiteNodeBatchFtsRow[];
|
|
213
|
+
affectedSchemaIds: string[];
|
|
214
|
+
lastLamportTime: number;
|
|
215
|
+
indexMode: SQLiteNodeBatchIndexMode;
|
|
216
|
+
}
|
|
217
|
+
interface SQLiteNodeBatchApplyResult {
|
|
218
|
+
nodeRowsWritten: number;
|
|
219
|
+
propertyRowsWritten: number;
|
|
220
|
+
changeRowsWritten: number;
|
|
221
|
+
scalarRowsWritten: number;
|
|
222
|
+
ftsRowsWritten: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export type { ElectronSQLiteDiagnostics as E, RunResult as R, SQLValue as S, SQLRow as a, SQLiteConfig as b, SQLBatchRead as c, SchemaVersion as d, SQLiteOperationStats as e, SQLiteNodeBatchIndexMode as f, SQLiteNodeBatchNodeRow as g, SQLiteNodeBatchPropertyRow as h, SQLiteNodeBatchChangeRow as i, SQLiteNodeBatchScalarIndexRow as j, SQLiteNodeBatchFtsRow as k, SQLiteNodeBatchApplyInput as l, SQLiteNodeBatchApplyResult as m };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xnetjs/sqlite - Priority scheduler for the single SQLite worker
|
|
3
|
+
*
|
|
4
|
+
* The web app funnels every storage operation — interactive reads, background
|
|
5
|
+
* writes, sync-apply batches — through ONE SQLite worker thread (exploration
|
|
6
|
+
* 0227). With no scheduling, the worker serves Comlink calls in arrival order,
|
|
7
|
+
* so an interactive read can be stuck behind a burst of queued writes. 0227
|
|
8
|
+
* fixed the one pathological 18s op; this scheduler generalises the fix so that
|
|
9
|
+
* *no* queued operation can starve an interactive read (exploration 0228).
|
|
10
|
+
*
|
|
11
|
+
* It does NOT add parallelism — a single OPFS connection is inherently serial
|
|
12
|
+
* (the `opfs-sahpool` VFS holds exclusive file handles, so multiple reader
|
|
13
|
+
* workers on the same DB are impossible). What it adds is **ordering**: queued
|
|
14
|
+
* work drains highest-priority-lane first, and identical concurrent reads are
|
|
15
|
+
* coalesced into a single execution.
|
|
16
|
+
*
|
|
17
|
+
* Safety: jobs run strictly one-at-a-time (no preemption of an in-flight op),
|
|
18
|
+
* which matches SQLite's single-connection serialization — operations on one
|
|
19
|
+
* connection are never truly concurrent anyway. A job already executing always
|
|
20
|
+
* completes before the next is dequeued.
|
|
21
|
+
*/
|
|
22
|
+
/** Priority lanes, drained in this order: interactive → bulk → write. */
|
|
23
|
+
type SchedulerLane = 'interactive' | 'bulk' | 'write';
|
|
24
|
+
/** Point-in-time view of scheduler depth (for diagnostics / the perf panel). */
|
|
25
|
+
interface SchedulerSnapshot {
|
|
26
|
+
interactive: number;
|
|
27
|
+
bulk: number;
|
|
28
|
+
write: number;
|
|
29
|
+
/** Whether a job is currently executing. */
|
|
30
|
+
inFlight: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Aggregated per-lane op latency (exploration 0263). All ms values rounded. */
|
|
33
|
+
interface SchedulerLaneOpStats {
|
|
34
|
+
/** Ops executed on this lane since open/reset. */
|
|
35
|
+
ops: number;
|
|
36
|
+
queueP50Ms: number;
|
|
37
|
+
queueP95Ms: number;
|
|
38
|
+
execP50Ms: number;
|
|
39
|
+
execP95Ms: number;
|
|
40
|
+
/** Worst single execution — the head-of-line-blocking amplitude. */
|
|
41
|
+
maxExecMs: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Cumulative scheduler statistics: how many ops ran, how many duplicate reads
|
|
45
|
+
* were served without executing (coalesced), and per-lane latency percentiles.
|
|
46
|
+
* This is the "p50/p95 per-query worker time" measurement of exploration 0263 —
|
|
47
|
+
* the number that says whether the statement cache / batch RPC actually moved
|
|
48
|
+
* anything, without wading through per-op boot-log lines.
|
|
49
|
+
*/
|
|
50
|
+
interface SchedulerOpStats {
|
|
51
|
+
ops: number;
|
|
52
|
+
coalescedHits: number;
|
|
53
|
+
lanes: Record<SchedulerLane, SchedulerLaneOpStats>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type { SchedulerSnapshot as S, SchedulerOpStats as a };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xnetjs/sqlite",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Unified SQLite adapter for xNet across all platforms",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"import": "./dist/adapters/web-worker.js",
|
|
32
32
|
"types": "./dist/adapters/web-worker.d.ts"
|
|
33
33
|
},
|
|
34
|
+
"./web-router-worker": {
|
|
35
|
+
"import": "./dist/adapters/web-router-worker.js",
|
|
36
|
+
"types": "./dist/adapters/web-router-worker.d.ts"
|
|
37
|
+
},
|
|
34
38
|
"./browser-support": {
|
|
35
39
|
"import": "./dist/browser-support.js",
|
|
36
40
|
"types": "./dist/browser-support.d.ts"
|