@xnetjs/sqlite 0.0.2 → 0.0.3

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/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ isSQLiteCorruptionError
3
+ } from "./chunk-CBU263LI.js";
1
4
  import {
2
5
  SCHEMA_DDL,
3
6
  SCHEMA_DDL_CORE,
@@ -5,11 +8,15 @@ import {
5
8
  SCHEMA_MIGRATIONS,
6
9
  SCHEMA_VERSION,
7
10
  getMigrationSQL
8
- } from "./chunk-HIREU5S5.js";
11
+ } from "./chunk-NVD7G7DZ.js";
9
12
  import {
10
13
  checkBrowserSupport,
11
- showUnsupportedBrowserMessage
12
- } from "./chunk-ZRR5D2OD.js";
14
+ checkPersistentStorage,
15
+ isSilentPersistRequestSafe,
16
+ requestPersistentStorage,
17
+ showUnsupportedBrowserMessage,
18
+ watchPersistentStoragePermission
19
+ } from "./chunk-2OK46ZBU.js";
13
20
 
14
21
  // src/query-builder.ts
15
22
  function buildInsert(table, columns, options) {
@@ -54,6 +61,164 @@ function buildBatchInsert(table, columns, rowCount) {
54
61
  return `INSERT INTO ${table} (${columnList}) VALUES ${valuesList}`;
55
62
  }
56
63
 
64
+ // src/diagnostics.ts
65
+ async function getIndexInfo(db) {
66
+ const indexes = await db.query(
67
+ "SELECT name, tbl_name, sql FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%'"
68
+ );
69
+ const result = [];
70
+ for (const idx of indexes) {
71
+ const columns = await db.query(`PRAGMA index_info('${idx.name}')`);
72
+ result.push({
73
+ name: idx.name,
74
+ tableName: idx.tbl_name,
75
+ unique: idx.sql?.includes("UNIQUE") ?? false,
76
+ columns: columns.map((c) => c.name),
77
+ partial: idx.sql?.includes("WHERE") ?? false
78
+ });
79
+ }
80
+ return result;
81
+ }
82
+ async function analyzeQuery(db, sql, params) {
83
+ const plan = await db.query(`EXPLAIN QUERY PLAN ${sql}`, params);
84
+ const steps = plan.map((row) => ({
85
+ id: row.id,
86
+ parent: row.parent,
87
+ detail: row.detail
88
+ }));
89
+ const usedIndexes = [];
90
+ let fullTableScan = false;
91
+ for (const step of steps) {
92
+ const detail = step.detail.toUpperCase();
93
+ const indexMatch = step.detail.match(/USING (?:COVERING )?INDEX (\w+)/i);
94
+ if (indexMatch) {
95
+ usedIndexes.push(indexMatch[1]);
96
+ }
97
+ if (detail.includes("SCAN TABLE") && !detail.includes("USING")) {
98
+ fullTableScan = true;
99
+ }
100
+ }
101
+ return { plan: steps, usedIndexes, fullTableScan };
102
+ }
103
+ async function analyzeTable(db, tableName) {
104
+ const countResult = await db.queryOne(`SELECT COUNT(*) as count FROM ${tableName}`);
105
+ const rowCount = countResult?.count ?? 0;
106
+ let pageCount = 0;
107
+ let unusedBytes = 0;
108
+ try {
109
+ const pages = await db.query(`SELECT pageno, unused FROM dbstat WHERE name = ?`, [
110
+ tableName
111
+ ]);
112
+ pageCount = pages.length;
113
+ unusedBytes = pages.reduce((sum, p) => sum + p.unused, 0);
114
+ } catch {
115
+ }
116
+ return {
117
+ name: tableName,
118
+ rowCount,
119
+ pageCount,
120
+ unusedBytes
121
+ };
122
+ }
123
+ async function getAllTableStats(db) {
124
+ const tables = await db.query(
125
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
126
+ );
127
+ const stats = [];
128
+ for (const table of tables) {
129
+ stats.push(await analyzeTable(db, table.name));
130
+ }
131
+ return stats.sort((a, b) => b.rowCount - a.rowCount);
132
+ }
133
+ async function getDatabaseStats(db) {
134
+ const [pageSize, pageCount, freePageCount, schemaVersion, walMode, foreignKeys] = await Promise.all([
135
+ db.queryOne("PRAGMA page_size"),
136
+ db.queryOne("PRAGMA page_count"),
137
+ db.queryOne("PRAGMA freelist_count"),
138
+ db.queryOne("PRAGMA schema_version"),
139
+ db.queryOne("PRAGMA journal_mode"),
140
+ db.queryOne("PRAGMA foreign_keys")
141
+ ]);
142
+ return {
143
+ pageSize: Number(pageSize?.page_size ?? 4096),
144
+ pageCount: Number(pageCount?.page_count ?? 0),
145
+ freePageCount: Number(freePageCount?.freelist_count ?? 0),
146
+ schemaVersion: Number(schemaVersion?.schema_version ?? 0),
147
+ walMode: String(walMode?.journal_mode ?? "").toLowerCase() === "wal",
148
+ foreignKeys: Boolean(foreignKeys?.foreign_keys)
149
+ };
150
+ }
151
+ var sqliteRuntimeCapabilities = /* @__PURE__ */ new WeakMap();
152
+ async function detectSQLiteCapabilities(db) {
153
+ const cached = sqliteRuntimeCapabilities.get(db);
154
+ if (cached) {
155
+ return cached;
156
+ }
157
+ const capabilities = {
158
+ fts5: await canCreateVirtualTable(db, "temp.__xnet_capability_fts5_probe", "fts5(content)"),
159
+ rtree: await canCreateVirtualTable(
160
+ db,
161
+ "temp.__xnet_capability_rtree_probe",
162
+ "rtree(id, min_x, max_x, min_y, max_y)"
163
+ )
164
+ };
165
+ sqliteRuntimeCapabilities.set(db, capabilities);
166
+ return capabilities;
167
+ }
168
+ async function canCreateVirtualTable(db, tableName, moduleDefinition) {
169
+ await dropProbeTable(db, tableName);
170
+ try {
171
+ await db.exec(`CREATE VIRTUAL TABLE ${tableName} USING ${moduleDefinition}`);
172
+ return true;
173
+ } catch {
174
+ return false;
175
+ } finally {
176
+ await dropProbeTable(db, tableName);
177
+ }
178
+ }
179
+ async function dropProbeTable(db, tableName) {
180
+ try {
181
+ await db.exec(`DROP TABLE IF EXISTS ${tableName}`);
182
+ } catch {
183
+ }
184
+ }
185
+ async function runAnalyze(db, tableName) {
186
+ if (tableName) {
187
+ await db.exec(`ANALYZE ${tableName}`);
188
+ } else {
189
+ await db.exec("ANALYZE");
190
+ }
191
+ }
192
+ async function checkIntegrity(db) {
193
+ const results = await db.query("PRAGMA integrity_check(100)");
194
+ const errors = [];
195
+ for (const row of results) {
196
+ if (row.integrity_check !== "ok") {
197
+ errors.push(row.integrity_check);
198
+ }
199
+ }
200
+ return {
201
+ ok: errors.length === 0,
202
+ errors
203
+ };
204
+ }
205
+ async function explainQuery(db, sql, params) {
206
+ const rows = await db.query(`EXPLAIN ${sql}`, params);
207
+ return rows.map(
208
+ (row) => `${row.opcode.padEnd(15)} ${String(row.p1).padStart(4)} ${String(row.p2).padStart(4)} ${String(row.p3).padStart(4)} ${row.p4 || ""}`
209
+ );
210
+ }
211
+ async function timeQuery(db, sql, params) {
212
+ const start = performance.now();
213
+ const result = await db.query(sql, params);
214
+ const durationMs = performance.now() - start;
215
+ return {
216
+ result,
217
+ durationMs,
218
+ rowCount: result.length
219
+ };
220
+ }
221
+
57
222
  // src/fts.ts
58
223
  async function updateNodeFTS(db, nodeId, title, content) {
59
224
  const hasFTS = await checkFTSSupport(db);
@@ -168,6 +333,14 @@ function extractSearchableContent(properties) {
168
333
  if (typeof body === "string") {
169
334
  parts.push(body);
170
335
  }
336
+ const name = properties.name;
337
+ if (typeof name === "string") {
338
+ parts.push(name);
339
+ }
340
+ const note = properties.note;
341
+ if (typeof note === "string") {
342
+ parts.push(note);
343
+ }
171
344
  return parts.length > 0 ? parts.join(" ") : null;
172
345
  }
173
346
  var ftsSupport = /* @__PURE__ */ new WeakMap();
@@ -176,6 +349,11 @@ async function checkFTSSupport(db) {
176
349
  return ftsSupport.get(db);
177
350
  }
178
351
  try {
352
+ const capabilities = await detectSQLiteCapabilities(db);
353
+ if (!capabilities.fts5) {
354
+ ftsSupport.set(db, false);
355
+ return false;
356
+ }
179
357
  const result = await db.queryOne(
180
358
  "SELECT name FROM sqlite_master WHERE type='table' AND name='nodes_fts'"
181
359
  );
@@ -196,130 +374,6 @@ function escapeFTSQuery(query) {
196
374
  }
197
375
  return query;
198
376
  }
199
-
200
- // src/diagnostics.ts
201
- async function getIndexInfo(db) {
202
- const indexes = await db.query(
203
- "SELECT name, tbl_name, sql FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%'"
204
- );
205
- const result = [];
206
- for (const idx of indexes) {
207
- const columns = await db.query(`PRAGMA index_info('${idx.name}')`);
208
- result.push({
209
- name: idx.name,
210
- tableName: idx.tbl_name,
211
- unique: idx.sql?.includes("UNIQUE") ?? false,
212
- columns: columns.map((c) => c.name),
213
- partial: idx.sql?.includes("WHERE") ?? false
214
- });
215
- }
216
- return result;
217
- }
218
- async function analyzeQuery(db, sql, params) {
219
- const plan = await db.query(`EXPLAIN QUERY PLAN ${sql}`, params);
220
- const steps = plan.map((row) => ({
221
- id: row.id,
222
- parent: row.parent,
223
- detail: row.detail
224
- }));
225
- const usedIndexes = [];
226
- let fullTableScan = false;
227
- for (const step of steps) {
228
- const detail = step.detail.toUpperCase();
229
- const indexMatch = step.detail.match(/USING (?:COVERING )?INDEX (\w+)/i);
230
- if (indexMatch) {
231
- usedIndexes.push(indexMatch[1]);
232
- }
233
- if (detail.includes("SCAN TABLE") && !detail.includes("USING")) {
234
- fullTableScan = true;
235
- }
236
- }
237
- return { plan: steps, usedIndexes, fullTableScan };
238
- }
239
- async function analyzeTable(db, tableName) {
240
- const countResult = await db.queryOne(`SELECT COUNT(*) as count FROM ${tableName}`);
241
- const rowCount = countResult?.count ?? 0;
242
- let pageCount = 0;
243
- let unusedBytes = 0;
244
- try {
245
- const pages = await db.query(`SELECT pageno, unused FROM dbstat WHERE name = ?`, [
246
- tableName
247
- ]);
248
- pageCount = pages.length;
249
- unusedBytes = pages.reduce((sum, p) => sum + p.unused, 0);
250
- } catch {
251
- }
252
- return {
253
- name: tableName,
254
- rowCount,
255
- pageCount,
256
- unusedBytes
257
- };
258
- }
259
- async function getAllTableStats(db) {
260
- const tables = await db.query(
261
- "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
262
- );
263
- const stats = [];
264
- for (const table of tables) {
265
- stats.push(await analyzeTable(db, table.name));
266
- }
267
- return stats.sort((a, b) => b.rowCount - a.rowCount);
268
- }
269
- async function getDatabaseStats(db) {
270
- const [pageSize, pageCount, freePageCount, schemaVersion, walMode, foreignKeys] = await Promise.all([
271
- db.queryOne("PRAGMA page_size"),
272
- db.queryOne("PRAGMA page_count"),
273
- db.queryOne("PRAGMA freelist_count"),
274
- db.queryOne("PRAGMA schema_version"),
275
- db.queryOne("PRAGMA journal_mode"),
276
- db.queryOne("PRAGMA foreign_keys")
277
- ]);
278
- return {
279
- pageSize: Number(pageSize?.page_size ?? 4096),
280
- pageCount: Number(pageCount?.page_count ?? 0),
281
- freePageCount: Number(freePageCount?.freelist_count ?? 0),
282
- schemaVersion: Number(schemaVersion?.schema_version ?? 0),
283
- walMode: String(walMode?.journal_mode ?? "").toLowerCase() === "wal",
284
- foreignKeys: Boolean(foreignKeys?.foreign_keys)
285
- };
286
- }
287
- async function runAnalyze(db, tableName) {
288
- if (tableName) {
289
- await db.exec(`ANALYZE ${tableName}`);
290
- } else {
291
- await db.exec("ANALYZE");
292
- }
293
- }
294
- async function checkIntegrity(db) {
295
- const results = await db.query("PRAGMA integrity_check(100)");
296
- const errors = [];
297
- for (const row of results) {
298
- if (row.integrity_check !== "ok") {
299
- errors.push(row.integrity_check);
300
- }
301
- }
302
- return {
303
- ok: errors.length === 0,
304
- errors
305
- };
306
- }
307
- async function explainQuery(db, sql, params) {
308
- const rows = await db.query(`EXPLAIN ${sql}`, params);
309
- return rows.map(
310
- (row) => `${row.opcode.padEnd(15)} ${String(row.p1).padStart(4)} ${String(row.p2).padStart(4)} ${String(row.p3).padStart(4)} ${row.p4 || ""}`
311
- );
312
- }
313
- async function timeQuery(db, sql, params) {
314
- const start = performance.now();
315
- const result = await db.query(sql, params);
316
- const durationMs = performance.now() - start;
317
- return {
318
- result,
319
- durationMs,
320
- rowCount: result.length
321
- };
322
- }
323
377
  export {
324
378
  SCHEMA_DDL,
325
379
  SCHEMA_DDL_CORE,
@@ -334,7 +388,9 @@ export {
334
388
  buildUpdate,
335
389
  checkBrowserSupport,
336
390
  checkIntegrity,
391
+ checkPersistentStorage,
337
392
  deleteNodeFTS,
393
+ detectSQLiteCapabilities,
338
394
  escapeLike,
339
395
  explainQuery,
340
396
  extractSearchableContent,
@@ -343,11 +399,15 @@ export {
343
399
  getDatabaseStats,
344
400
  getIndexInfo,
345
401
  getMigrationSQL,
402
+ isSQLiteCorruptionError,
403
+ isSilentPersistRequestSafe,
346
404
  optimizeFTS,
347
405
  rebuildFTS,
406
+ requestPersistentStorage,
348
407
  runAnalyze,
349
408
  searchNodes,
350
409
  showUnsupportedBrowserMessage,
351
410
  timeQuery,
352
- updateNodeFTS
411
+ updateNodeFTS,
412
+ watchPersistentStoragePermission
353
413
  };
@@ -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 = 6;
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);\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,132 @@
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
+ * Result of a mutation query (INSERT, UPDATE, DELETE).
14
+ */
15
+ interface RunResult {
16
+ /** Number of rows affected by the query */
17
+ changes: number;
18
+ /** Last inserted row ID (for INSERT with AUTOINCREMENT) */
19
+ lastInsertRowid: bigint;
20
+ }
21
+ /**
22
+ * Configuration options for SQLite database.
23
+ */
24
+ interface SQLiteConfig {
25
+ /** Database file path or name */
26
+ path: string;
27
+ /** Enable WAL mode (default: true) */
28
+ walMode?: boolean;
29
+ /** Enable foreign keys (default: true) */
30
+ foreignKeys?: boolean;
31
+ /** Busy timeout in milliseconds (default: 5000) */
32
+ busyTimeout?: number;
33
+ }
34
+ /**
35
+ * Schema version information.
36
+ */
37
+ interface SchemaVersion {
38
+ version: number;
39
+ appliedAt: number;
40
+ }
41
+ /**
42
+ * Runtime operation counters for diagnosing SQLite import performance.
43
+ *
44
+ * Counts are cumulative until reset by the adapter. Proxy-backed adapters can
45
+ * use `workerRequestCount` to show serialization-boundary pressure separately
46
+ * from the SQL statements executed by SQLite.
47
+ */
48
+ interface SQLiteOperationStats {
49
+ /** SELECT statements returning many rows. */
50
+ queryCount: number;
51
+ /** SELECT statements returning at most one row. */
52
+ queryOneCount: number;
53
+ /** INSERT, UPDATE, DELETE, or other mutation statements. */
54
+ runCount: number;
55
+ /** Raw SQL executions, often schema or transaction control statements. */
56
+ execCount: number;
57
+ /** Callback transactions requested through this adapter. */
58
+ transactionCount: number;
59
+ /** Batch transactions requested through this adapter. */
60
+ transactionBatchCount: number;
61
+ /** SQL operations included in batch transactions. */
62
+ transactionBatchOperationCount: number;
63
+ /** Comlink or postMessage requests crossing into a worker. */
64
+ workerRequestCount: number;
65
+ }
66
+ type SQLiteNodeBatchIndexMode = 'eager' | 'touched' | 'defer-schema';
67
+ interface SQLiteNodeBatchNodeRow {
68
+ id: string;
69
+ schemaId: string;
70
+ createdAt: number;
71
+ updatedAt: number;
72
+ createdBy: string;
73
+ deletedAt: number | null;
74
+ propertyKeys: string[];
75
+ }
76
+ interface SQLiteNodeBatchPropertyRow {
77
+ nodeId: string;
78
+ propertyKey: string;
79
+ value: Uint8Array | null;
80
+ lamportTime: number;
81
+ updatedBy: string;
82
+ updatedAt: number;
83
+ }
84
+ interface SQLiteNodeBatchChangeRow {
85
+ hash: string;
86
+ nodeId: string;
87
+ payload: Uint8Array;
88
+ lamportTime: number;
89
+ lamportPeer: string;
90
+ wallTime: number;
91
+ author: string;
92
+ parentHash: string | null;
93
+ batchId: string | null;
94
+ signature: Uint8Array;
95
+ }
96
+ interface SQLiteNodeBatchScalarIndexRow {
97
+ nodeId: string;
98
+ schemaId: string;
99
+ propertyKey: string;
100
+ valueType: string;
101
+ valueText: string | null;
102
+ valueNumber: number | null;
103
+ valueBoolean: number | null;
104
+ valueHash: string | null;
105
+ updatedAt: number;
106
+ lamportTime: number;
107
+ }
108
+ interface SQLiteNodeBatchFtsRow {
109
+ nodeId: string;
110
+ title: string;
111
+ content: string;
112
+ }
113
+ interface SQLiteNodeBatchApplyInput {
114
+ nodes: SQLiteNodeBatchNodeRow[];
115
+ properties: SQLiteNodeBatchPropertyRow[];
116
+ changes: SQLiteNodeBatchChangeRow[];
117
+ scalarIndexRows: SQLiteNodeBatchScalarIndexRow[];
118
+ ftsNodeIds: string[];
119
+ ftsRows: SQLiteNodeBatchFtsRow[];
120
+ affectedSchemaIds: string[];
121
+ lastLamportTime: number;
122
+ indexMode: SQLiteNodeBatchIndexMode;
123
+ }
124
+ interface SQLiteNodeBatchApplyResult {
125
+ nodeRowsWritten: number;
126
+ propertyRowsWritten: number;
127
+ changeRowsWritten: number;
128
+ scalarRowsWritten: number;
129
+ ftsRowsWritten: number;
130
+ }
131
+
132
+ export type { RunResult as R, SQLValue as S, SQLRow as a, SQLiteConfig as b, SchemaVersion as c, SQLiteOperationStats as d, SQLiteNodeBatchIndexMode as e, SQLiteNodeBatchNodeRow as f, SQLiteNodeBatchPropertyRow as g, SQLiteNodeBatchChangeRow as h, SQLiteNodeBatchScalarIndexRow as i, SQLiteNodeBatchFtsRow as j, SQLiteNodeBatchApplyInput as k, SQLiteNodeBatchApplyResult as l };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnetjs/sqlite",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Unified SQLite adapter for xNet across all platforms",
5
5
  "license": "MIT",
6
6
  "repository": {