@xnetjs/sqlite 0.0.2

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.
@@ -0,0 +1,336 @@
1
+ import {
2
+ SCHEMA_DDL,
3
+ SCHEMA_VERSION
4
+ } from "../chunk-HIREU5S5.js";
5
+
6
+ // src/adapters/electron.ts
7
+ var DatabaseConstructor = null;
8
+ async function getBetterSqlite3() {
9
+ if (DatabaseConstructor) return DatabaseConstructor;
10
+ const module = await import("better-sqlite3");
11
+ DatabaseConstructor = module.default;
12
+ return DatabaseConstructor;
13
+ }
14
+ var ElectronSQLiteAdapter = class {
15
+ db = null;
16
+ config = null;
17
+ inTransaction = false;
18
+ // Cached prepared statements for performance
19
+ statementCache = /* @__PURE__ */ new Map();
20
+ async open(config) {
21
+ if (this.db) {
22
+ throw new Error("Database already open. Call close() first.");
23
+ }
24
+ const Database = await getBetterSqlite3();
25
+ this.db = new Database(config.path);
26
+ this.config = config;
27
+ if (config.walMode !== false) {
28
+ this.db.pragma("journal_mode = WAL");
29
+ }
30
+ if (config.foreignKeys !== false) {
31
+ this.db.pragma("foreign_keys = ON");
32
+ }
33
+ if (config.busyTimeout) {
34
+ this.db.pragma(`busy_timeout = ${config.busyTimeout}`);
35
+ } else {
36
+ this.db.pragma("busy_timeout = 5000");
37
+ }
38
+ this.db.pragma("synchronous = NORMAL");
39
+ this.db.pragma("cache_size = -64000");
40
+ this.db.pragma("temp_store = MEMORY");
41
+ }
42
+ async close() {
43
+ if (!this.db) return;
44
+ this.statementCache.clear();
45
+ if (this.config?.walMode !== false) {
46
+ try {
47
+ this.db.pragma("wal_checkpoint(TRUNCATE)");
48
+ } catch {
49
+ }
50
+ }
51
+ this.db.close();
52
+ this.db = null;
53
+ this.config = null;
54
+ }
55
+ isOpen() {
56
+ return this.db !== null;
57
+ }
58
+ async query(sql, params) {
59
+ this.ensureOpen();
60
+ try {
61
+ const stmt = this.getOrPrepare(sql);
62
+ const rows = params ? stmt.all(...params) : stmt.all();
63
+ return rows;
64
+ } catch (err) {
65
+ throw this.wrapError(err, sql);
66
+ }
67
+ }
68
+ async queryOne(sql, params) {
69
+ this.ensureOpen();
70
+ try {
71
+ const stmt = this.getOrPrepare(sql);
72
+ const row = params ? stmt.get(...params) : stmt.get();
73
+ return row ?? null;
74
+ } catch (err) {
75
+ throw this.wrapError(err, sql);
76
+ }
77
+ }
78
+ async run(sql, params) {
79
+ this.ensureOpen();
80
+ try {
81
+ const stmt = this.getOrPrepare(sql);
82
+ const result = params ? stmt.run(...params) : stmt.run();
83
+ return {
84
+ changes: result.changes,
85
+ lastInsertRowid: BigInt(result.lastInsertRowid)
86
+ };
87
+ } catch (err) {
88
+ throw this.wrapError(err, sql);
89
+ }
90
+ }
91
+ async exec(sql) {
92
+ this.ensureOpen();
93
+ try {
94
+ this.db.exec(sql);
95
+ } catch (err) {
96
+ throw this.wrapError(err, sql);
97
+ }
98
+ }
99
+ async transaction(fn) {
100
+ this.ensureOpen();
101
+ await this.beginTransaction();
102
+ try {
103
+ const result = await fn();
104
+ await this.commit();
105
+ return result;
106
+ } catch (err) {
107
+ await this.rollback();
108
+ throw err;
109
+ }
110
+ }
111
+ /**
112
+ * Synchronous transaction for performance-critical batch operations.
113
+ * Prefer this over async transaction when the body is synchronous.
114
+ */
115
+ transactionSync(fn) {
116
+ this.ensureOpen();
117
+ const txn = this.db.transaction(fn);
118
+ return txn();
119
+ }
120
+ async beginTransaction() {
121
+ if (this.inTransaction) {
122
+ throw new Error("Transaction already in progress");
123
+ }
124
+ this.db.exec("BEGIN IMMEDIATE");
125
+ this.inTransaction = true;
126
+ }
127
+ async commit() {
128
+ if (!this.inTransaction) {
129
+ throw new Error("No transaction in progress");
130
+ }
131
+ this.db.exec("COMMIT");
132
+ this.inTransaction = false;
133
+ }
134
+ async rollback() {
135
+ if (!this.inTransaction) {
136
+ return;
137
+ }
138
+ this.db.exec("ROLLBACK");
139
+ this.inTransaction = false;
140
+ }
141
+ async prepare(sql) {
142
+ this.ensureOpen();
143
+ const stmt = this.db.prepare(sql);
144
+ return {
145
+ query: async (params) => {
146
+ const rows = params ? stmt.all(...params) : stmt.all();
147
+ return rows;
148
+ },
149
+ queryOne: async (params) => {
150
+ const row = params ? stmt.get(...params) : stmt.get();
151
+ return row ?? null;
152
+ },
153
+ run: async (params) => {
154
+ const result = params ? stmt.run(...params) : stmt.run();
155
+ return {
156
+ changes: result.changes,
157
+ lastInsertRowid: BigInt(result.lastInsertRowid)
158
+ };
159
+ },
160
+ finalize: async () => {
161
+ }
162
+ };
163
+ }
164
+ async getSchemaVersion() {
165
+ try {
166
+ const row = await this.queryOne(
167
+ "SELECT version FROM _schema_version ORDER BY version DESC LIMIT 1"
168
+ );
169
+ return row?.version ?? 0;
170
+ } catch {
171
+ return 0;
172
+ }
173
+ }
174
+ async setSchemaVersion(version) {
175
+ await this.run("INSERT INTO _schema_version (version, applied_at) VALUES (?, ?)", [
176
+ version,
177
+ Date.now()
178
+ ]);
179
+ }
180
+ async applySchema(version, sql) {
181
+ const currentVersion = await this.getSchemaVersion();
182
+ if (currentVersion >= version) {
183
+ return false;
184
+ }
185
+ return this.transactionSync(() => {
186
+ this.db.exec(sql);
187
+ this.db.prepare("INSERT INTO _schema_version (version, applied_at) VALUES (?, ?)").run(
188
+ version,
189
+ Date.now()
190
+ );
191
+ return true;
192
+ });
193
+ }
194
+ async getDatabaseSize() {
195
+ try {
196
+ const row = await this.queryOne(
197
+ "SELECT page_count, page_size FROM pragma_page_count(), pragma_page_size()"
198
+ );
199
+ if (row) {
200
+ return row.page_count * row.page_size;
201
+ }
202
+ if (this.config?.path && this.config.path !== ":memory:") {
203
+ const { statSync } = await import("fs");
204
+ try {
205
+ const stats = statSync(this.config.path);
206
+ return stats.size;
207
+ } catch {
208
+ return 0;
209
+ }
210
+ }
211
+ return 0;
212
+ } catch {
213
+ return 0;
214
+ }
215
+ }
216
+ async vacuum() {
217
+ this.ensureOpen();
218
+ this.db.exec("VACUUM");
219
+ }
220
+ async checkpoint() {
221
+ return 0;
222
+ }
223
+ getStorageMode() {
224
+ return "opfs";
225
+ }
226
+ // ─── Helper Methods ─────────────────────────────────────────────────────
227
+ /**
228
+ * Get a statement from cache or prepare it.
229
+ * Statement caching significantly improves performance for repeated queries.
230
+ */
231
+ getOrPrepare(sql) {
232
+ let stmt = this.statementCache.get(sql);
233
+ if (!stmt) {
234
+ stmt = this.db.prepare(sql);
235
+ this.statementCache.set(sql, stmt);
236
+ }
237
+ return stmt;
238
+ }
239
+ ensureOpen() {
240
+ if (!this.db) {
241
+ throw new Error("Database not open. Call open() first.");
242
+ }
243
+ }
244
+ wrapError(err, sql) {
245
+ const message = err instanceof Error ? err.message : String(err);
246
+ return new Error(
247
+ `SQLite error: ${message}
248
+ SQL: ${sql.slice(0, 200)}${sql.length > 200 ? "..." : ""}`
249
+ );
250
+ }
251
+ // ─── Electron-Specific Methods ──────────────────────────────────────────
252
+ /**
253
+ * Get the underlying better-sqlite3 database instance.
254
+ * Use for advanced operations not covered by the interface.
255
+ */
256
+ getRawDatabase() {
257
+ this.ensureOpen();
258
+ return this.db;
259
+ }
260
+ /**
261
+ * Create a batch writer for efficient bulk inserts.
262
+ */
263
+ createBatchWriter(options) {
264
+ return new ElectronBatchWriter(this, options);
265
+ }
266
+ };
267
+ var ElectronBatchWriter = class {
268
+ adapter;
269
+ pendingOps = [];
270
+ maxBatchSize;
271
+ flushTimer = null;
272
+ flushPromise = null;
273
+ constructor(adapter, options) {
274
+ this.adapter = adapter;
275
+ this.maxBatchSize = options?.maxBatchSize ?? 100;
276
+ }
277
+ /**
278
+ * Queue an operation for batch execution.
279
+ */
280
+ queue(sql, params) {
281
+ this.pendingOps.push({ sql, params });
282
+ if (this.pendingOps.length >= this.maxBatchSize) {
283
+ this.flush();
284
+ } else if (!this.flushTimer) {
285
+ this.flushTimer = setTimeout(() => this.flush(), 50);
286
+ }
287
+ }
288
+ /**
289
+ * Flush all pending operations.
290
+ */
291
+ async flush() {
292
+ if (this.flushTimer) {
293
+ clearTimeout(this.flushTimer);
294
+ this.flushTimer = null;
295
+ }
296
+ if (this.pendingOps.length === 0) return;
297
+ if (this.flushPromise) {
298
+ await this.flushPromise;
299
+ return this.flush();
300
+ }
301
+ const ops = this.pendingOps;
302
+ this.pendingOps = [];
303
+ this.flushPromise = (async () => {
304
+ this.adapter.transactionSync(() => {
305
+ const db = this.adapter.getRawDatabase();
306
+ for (const { sql, params } of ops) {
307
+ db.prepare(sql).run(...params);
308
+ }
309
+ });
310
+ })();
311
+ try {
312
+ await this.flushPromise;
313
+ } finally {
314
+ this.flushPromise = null;
315
+ }
316
+ }
317
+ /**
318
+ * Close the batch writer, flushing any pending operations.
319
+ */
320
+ async close() {
321
+ await this.flush();
322
+ }
323
+ };
324
+ async function createElectronSQLiteAdapter(config) {
325
+ const adapter = new ElectronSQLiteAdapter();
326
+ await adapter.open(config);
327
+ await adapter.applySchema(SCHEMA_VERSION, SCHEMA_DDL);
328
+ return adapter;
329
+ }
330
+ export {
331
+ ElectronBatchWriter,
332
+ ElectronSQLiteAdapter,
333
+ SCHEMA_DDL,
334
+ SCHEMA_VERSION,
335
+ createElectronSQLiteAdapter
336
+ };
@@ -0,0 +1,65 @@
1
+ import { S as SQLiteAdapter, P as PreparedStatement } from '../adapter-I7yAV6iu.js';
2
+ import { b as SQLiteConfig, a as SQLRow, S as SQLValue, R as RunResult } from '../types-C_aHfRDF.js';
3
+
4
+ /**
5
+ * @xnetjs/sqlite - Expo SQLite adapter using expo-sqlite
6
+ *
7
+ * expo-sqlite runs SQLite on a native thread for performance.
8
+ * All methods are async to match the native API.
9
+ */
10
+
11
+ /**
12
+ * SQLite adapter for Expo/React Native using expo-sqlite.
13
+ *
14
+ * expo-sqlite runs SQLite on a native thread for performance.
15
+ * All methods are async to match the native API.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const adapter = new ExpoSQLiteAdapter()
20
+ * await adapter.open({ path: 'xnet.db' })
21
+ *
22
+ * const nodes = await adapter.query('SELECT * FROM nodes')
23
+ * ```
24
+ */
25
+ declare class ExpoSQLiteAdapter implements SQLiteAdapter {
26
+ private db;
27
+ private config;
28
+ private inTransaction;
29
+ open(config: SQLiteConfig): Promise<void>;
30
+ close(): Promise<void>;
31
+ isOpen(): boolean;
32
+ query<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T[]>;
33
+ queryOne<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T | null>;
34
+ run(sql: string, params?: SQLValue[]): Promise<RunResult>;
35
+ exec(sql: string): Promise<void>;
36
+ transaction<T>(fn: () => Promise<T>): Promise<T>;
37
+ beginTransaction(): Promise<void>;
38
+ commit(): Promise<void>;
39
+ rollback(): Promise<void>;
40
+ prepare(sql: string): Promise<PreparedStatement>;
41
+ getSchemaVersion(): Promise<number>;
42
+ setSchemaVersion(version: number): Promise<void>;
43
+ applySchema(version: number, sql: string): Promise<boolean>;
44
+ getDatabaseSize(): Promise<number>;
45
+ vacuum(): Promise<void>;
46
+ checkpoint(): Promise<number>;
47
+ getStorageMode(): 'opfs' | 'memory';
48
+ private ensureOpen;
49
+ /**
50
+ * Get storage statistics.
51
+ */
52
+ getStats(): Promise<{
53
+ documentCount: number;
54
+ updateCount: number;
55
+ snapshotCount: number;
56
+ blobCount: number;
57
+ totalBlobSize: number;
58
+ }>;
59
+ }
60
+ /**
61
+ * Create an ExpoSQLiteAdapter with schema applied.
62
+ */
63
+ declare function createExpoSQLiteAdapter(config: SQLiteConfig): Promise<ExpoSQLiteAdapter>;
64
+
65
+ export { ExpoSQLiteAdapter, createExpoSQLiteAdapter };
@@ -0,0 +1,217 @@
1
+ import {
2
+ SCHEMA_DDL,
3
+ SCHEMA_VERSION
4
+ } from "../chunk-HIREU5S5.js";
5
+
6
+ // src/adapters/expo.ts
7
+ var ExpoSQLiteAdapter = class {
8
+ db = null;
9
+ config = null;
10
+ inTransaction = false;
11
+ async open(config) {
12
+ if (this.db) {
13
+ throw new Error("Database already open. Call close() first.");
14
+ }
15
+ const SQLite = await import("expo-sqlite");
16
+ this.db = await SQLite.openDatabaseAsync(config.path);
17
+ this.config = config;
18
+ if (config.foreignKeys !== false) {
19
+ await this.exec("PRAGMA foreign_keys = ON");
20
+ }
21
+ if (config.busyTimeout) {
22
+ await this.exec(`PRAGMA busy_timeout = ${config.busyTimeout}`);
23
+ } else {
24
+ await this.exec("PRAGMA busy_timeout = 5000");
25
+ }
26
+ await this.exec("PRAGMA synchronous = NORMAL");
27
+ await this.exec("PRAGMA cache_size = -32000");
28
+ await this.exec("PRAGMA temp_store = MEMORY");
29
+ if (config.walMode !== false) {
30
+ await this.exec("PRAGMA journal_mode = WAL");
31
+ }
32
+ }
33
+ async close() {
34
+ if (this.db) {
35
+ await this.db.closeAsync();
36
+ this.db = null;
37
+ }
38
+ this.config = null;
39
+ }
40
+ isOpen() {
41
+ return this.db !== null;
42
+ }
43
+ async query(sql, params) {
44
+ this.ensureOpen();
45
+ const result = await this.db.getAllAsync(sql, params ?? []);
46
+ return result;
47
+ }
48
+ async queryOne(sql, params) {
49
+ this.ensureOpen();
50
+ const result = await this.db.getFirstAsync(sql, params ?? []);
51
+ return result ?? null;
52
+ }
53
+ async run(sql, params) {
54
+ this.ensureOpen();
55
+ const result = await this.db.runAsync(sql, params ?? []);
56
+ return {
57
+ changes: result.changes,
58
+ lastInsertRowid: BigInt(result.lastInsertRowId)
59
+ };
60
+ }
61
+ async exec(sql) {
62
+ this.ensureOpen();
63
+ await this.db.execAsync(sql);
64
+ }
65
+ async transaction(fn) {
66
+ await this.beginTransaction();
67
+ try {
68
+ const result = await fn();
69
+ await this.commit();
70
+ return result;
71
+ } catch (err) {
72
+ await this.rollback();
73
+ throw err;
74
+ }
75
+ }
76
+ async beginTransaction() {
77
+ if (this.inTransaction) {
78
+ throw new Error("Transaction already in progress");
79
+ }
80
+ await this.exec("BEGIN IMMEDIATE");
81
+ this.inTransaction = true;
82
+ }
83
+ async commit() {
84
+ if (!this.inTransaction) {
85
+ throw new Error("No transaction in progress");
86
+ }
87
+ await this.exec("COMMIT");
88
+ this.inTransaction = false;
89
+ }
90
+ async rollback() {
91
+ if (!this.inTransaction) {
92
+ return;
93
+ }
94
+ await this.exec("ROLLBACK");
95
+ this.inTransaction = false;
96
+ }
97
+ async prepare(sql) {
98
+ this.ensureOpen();
99
+ const stmt = await this.db.prepareAsync(sql);
100
+ return {
101
+ query: async (params) => {
102
+ const result = await stmt.executeAsync(params ?? []);
103
+ return await result.getAllAsync();
104
+ },
105
+ queryOne: async (params) => {
106
+ const result = await stmt.executeAsync(params ?? []);
107
+ const first = await result.getFirstAsync();
108
+ return first ?? null;
109
+ },
110
+ run: async (params) => {
111
+ await stmt.executeAsync(params ?? []);
112
+ const changesRow = await this.db.getFirstAsync(
113
+ "SELECT changes() as changes",
114
+ []
115
+ );
116
+ const lastIdRow = await this.db.getFirstAsync(
117
+ "SELECT last_insert_rowid() as id",
118
+ []
119
+ );
120
+ return {
121
+ changes: changesRow?.changes ?? 0,
122
+ lastInsertRowid: BigInt(lastIdRow?.id ?? 0)
123
+ };
124
+ },
125
+ finalize: async () => {
126
+ await stmt.finalizeAsync();
127
+ }
128
+ };
129
+ }
130
+ async getSchemaVersion() {
131
+ try {
132
+ const row = await this.queryOne(
133
+ "SELECT version FROM _schema_version ORDER BY version DESC LIMIT 1"
134
+ );
135
+ return row?.version ?? 0;
136
+ } catch {
137
+ return 0;
138
+ }
139
+ }
140
+ async setSchemaVersion(version) {
141
+ await this.run("INSERT INTO _schema_version (version, applied_at) VALUES (?, ?)", [
142
+ version,
143
+ Date.now()
144
+ ]);
145
+ }
146
+ async applySchema(version, sql) {
147
+ const currentVersion = await this.getSchemaVersion();
148
+ if (currentVersion >= version) {
149
+ return false;
150
+ }
151
+ await this.transaction(async () => {
152
+ await this.exec(sql);
153
+ await this.setSchemaVersion(version);
154
+ });
155
+ return true;
156
+ }
157
+ async getDatabaseSize() {
158
+ try {
159
+ const row = await this.queryOne(
160
+ "SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()"
161
+ );
162
+ return row?.size ?? 0;
163
+ } catch {
164
+ return 0;
165
+ }
166
+ }
167
+ async vacuum() {
168
+ await this.exec("VACUUM");
169
+ }
170
+ async checkpoint() {
171
+ try {
172
+ const result = await this.queryOne("PRAGMA wal_checkpoint(PASSIVE)");
173
+ return result?.checkpointed ?? 0;
174
+ } catch {
175
+ return 0;
176
+ }
177
+ }
178
+ getStorageMode() {
179
+ return "opfs";
180
+ }
181
+ ensureOpen() {
182
+ if (!this.db) {
183
+ throw new Error("Database not open. Call open() first.");
184
+ }
185
+ }
186
+ // ─── Expo-Specific Methods ──────────────────────────────────────────────
187
+ /**
188
+ * Get storage statistics.
189
+ */
190
+ async getStats() {
191
+ const [nodes, changes, blobs] = await Promise.all([
192
+ this.queryOne("SELECT COUNT(*) as count FROM nodes"),
193
+ this.queryOne("SELECT COUNT(*) as count FROM changes"),
194
+ this.queryOne(
195
+ "SELECT COUNT(*) as count, COALESCE(SUM(size), 0) as total_size FROM blobs"
196
+ )
197
+ ]);
198
+ return {
199
+ documentCount: nodes?.count ?? 0,
200
+ updateCount: changes?.count ?? 0,
201
+ snapshotCount: 0,
202
+ // Snapshots are derived, not stored separately
203
+ blobCount: blobs?.count ?? 0,
204
+ totalBlobSize: blobs?.total_size ?? 0
205
+ };
206
+ }
207
+ };
208
+ async function createExpoSQLiteAdapter(config) {
209
+ const adapter = new ExpoSQLiteAdapter();
210
+ await adapter.open(config);
211
+ await adapter.applySchema(SCHEMA_VERSION, SCHEMA_DDL);
212
+ return adapter;
213
+ }
214
+ export {
215
+ ExpoSQLiteAdapter,
216
+ createExpoSQLiteAdapter
217
+ };
@@ -0,0 +1,46 @@
1
+ import { S as SQLiteAdapter, P as PreparedStatement } from '../adapter-I7yAV6iu.js';
2
+ import { b as SQLiteConfig, a as SQLRow, S as SQLValue, R as RunResult } from '../types-C_aHfRDF.js';
3
+
4
+ /**
5
+ * @xnetjs/sqlite - In-memory SQLite adapter for testing
6
+ *
7
+ * Uses sql.js (SQLite compiled to WASM) for Node.js testing.
8
+ */
9
+
10
+ /**
11
+ * In-memory SQLite adapter using sql.js for testing.
12
+ * This adapter is synchronous but exposes async interface for compatibility.
13
+ *
14
+ * Note: Requires sql.js as a dev dependency for tests.
15
+ */
16
+ declare class MemorySQLiteAdapter implements SQLiteAdapter {
17
+ private db;
18
+ private opened;
19
+ private inTransaction;
20
+ open(_config: SQLiteConfig): Promise<void>;
21
+ close(): Promise<void>;
22
+ isOpen(): boolean;
23
+ query<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T[]>;
24
+ queryOne<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T | null>;
25
+ run(sql: string, params?: SQLValue[]): Promise<RunResult>;
26
+ exec(sql: string): Promise<void>;
27
+ transaction<T>(fn: () => Promise<T>): Promise<T>;
28
+ beginTransaction(): Promise<void>;
29
+ commit(): Promise<void>;
30
+ rollback(): Promise<void>;
31
+ prepare(sql: string): Promise<PreparedStatement>;
32
+ getSchemaVersion(): Promise<number>;
33
+ setSchemaVersion(version: number): Promise<void>;
34
+ applySchema(version: number, sql: string): Promise<boolean>;
35
+ getDatabaseSize(): Promise<number>;
36
+ vacuum(): Promise<void>;
37
+ checkpoint(): Promise<number>;
38
+ getStorageMode(): 'opfs' | 'memory';
39
+ private ensureOpen;
40
+ }
41
+ /**
42
+ * Create a MemorySQLiteAdapter with schema applied.
43
+ */
44
+ declare function createMemorySQLiteAdapter(): Promise<MemorySQLiteAdapter>;
45
+
46
+ export { MemorySQLiteAdapter, createMemorySQLiteAdapter };