@capgo/capacitor-fast-sql 8.1.1 → 8.2.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.
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@capacitor/core');
4
+ var sqliteWasm = require('@sqlite.org/sqlite-wasm');
4
5
 
5
6
  /**
6
7
  * Transaction isolation levels
@@ -1071,91 +1072,75 @@ FastSQLVectorStore.connectionRefs = new Map();
1071
1072
  FastSQLVectorStore.ownedConnections = new Set();
1072
1073
 
1073
1074
  /**
1074
- * Apply optional SQLite performance PRAGMAs after opening a database.
1075
- */
1076
- function applyPerformanceConfig(db, options) {
1077
- db.run('PRAGMA foreign_keys = ON');
1078
- if (options.walMode) {
1079
- db.run('PRAGMA journal_mode = WAL');
1080
- }
1081
- if (options.performancePresets) {
1082
- db.run('PRAGMA synchronous = NORMAL');
1083
- db.run('PRAGMA busy_timeout = 5000');
1084
- db.run('PRAGMA cache_size = -2000');
1085
- }
1086
- }
1087
-
1088
- const DEFAULT_SQLJS_URL = 'https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js';
1089
- const DEFAULT_WASM_URL = 'https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.wasm';
1090
- /**
1091
- * Web implementation using sql.js (SQLite compiled to WebAssembly)
1075
+ * Web implementation using the official SQLite Wasm build with OPFS persistence.
1092
1076
  *
1093
- * This provides a compatible API on the web platform, storing databases
1094
- * in IndexedDB for persistence.
1077
+ * Databases are stored in the Origin Private File System when available (requires
1078
+ * Cross-Origin Isolation: COOP/COEP). No full-database copy is kept in RAM or
1079
+ * IndexedDB — SQLite reads/writes the OPFS file directly from a worker.
1095
1080
  */
1096
1081
  class CapgoCapacitorFastSqlWeb extends core.WebPlugin {
1097
1082
  constructor() {
1098
- super();
1083
+ super(...arguments);
1099
1084
  this.databases = new Map();
1100
- this.sqlPromise = null;
1085
+ this.promiserPromise = null;
1101
1086
  this.nextPort = 9000;
1102
1087
  this.webConfig = {};
1103
1088
  }
1104
1089
  /**
1105
1090
  * Configure web-specific options (no-op on native platforms).
1106
- * Must be called before connect() when using locally bundled sql.js files.
1091
+ * Must be called before connect() when overriding the default worker.
1107
1092
  */
1108
1093
  async configureWeb(config) {
1109
1094
  this.webConfig = config;
1110
- // Reset any in-progress load so the next connect() uses the new config
1111
- this.sqlPromise = null;
1095
+ this.promiserPromise = null;
1112
1096
  }
1113
- /**
1114
- * Load sql.js library (lazy – called on first connect()).
1115
- * Returns the cached promise so callers can await it directly.
1116
- */
1117
- loadSqlJs() {
1118
- var _a, _b;
1119
- if (!this.sqlPromise) {
1120
- const jsUrl = (_a = this.webConfig.sqlJsUrl) !== null && _a !== void 0 ? _a : DEFAULT_SQLJS_URL;
1121
- const wasmUrl = (_b = this.webConfig.wasmUrl) !== null && _b !== void 0 ? _b : DEFAULT_WASM_URL;
1122
- this.sqlPromise = new Promise((resolve, reject) => {
1123
- try {
1124
- // Load sql.js from the configured URL (CDN or local bundle)
1125
- const script = document.createElement('script');
1126
- script.src = jsUrl;
1127
- script.onload = async () => {
1128
- const initSqlJs = window.initSqlJs;
1129
- const SQL = await initSqlJs({
1130
- locateFile: (file) => (file.endsWith('.wasm') ? wasmUrl : file),
1131
- });
1132
- resolve(SQL);
1133
- };
1134
- script.onerror = reject;
1135
- document.head.appendChild(script);
1136
- }
1137
- catch (error) {
1138
- reject(error);
1139
- }
1140
- });
1097
+ getPromiser() {
1098
+ if (!this.promiserPromise) {
1099
+ const config = this.webConfig.worker !== undefined ? { worker: this.webConfig.worker } : undefined;
1100
+ this.promiserPromise = sqliteWasm.sqlite3Worker1Promiser.v2(config);
1141
1101
  }
1142
- return this.sqlPromise;
1102
+ return this.promiserPromise;
1103
+ }
1104
+ dbPath(database) {
1105
+ return `/capgo-fast-sql/${database}.sqlite3`;
1143
1106
  }
1144
1107
  async connect(options) {
1145
- const SQL = await this.loadSqlJs();
1146
- // Check if database already exists in IndexedDB
1147
- const savedData = await this.loadFromIndexedDB(options.database);
1148
- let db;
1149
- if (savedData) {
1150
- db = new SQL.Database(savedData);
1108
+ if (this.databases.has(options.database)) {
1109
+ const existing = this.databases.get(options.database);
1110
+ return {
1111
+ port: existing.port,
1112
+ token: existing.token,
1113
+ database: options.database,
1114
+ };
1115
+ }
1116
+ const promiser = await this.getPromiser();
1117
+ const path = this.dbPath(options.database);
1118
+ const useOpfs = this.webConfig.useOpfs !== false;
1119
+ let openResult;
1120
+ if (useOpfs) {
1121
+ try {
1122
+ openResult = await promiser('open', {
1123
+ filename: `file:${path}?vfs=opfs`,
1124
+ });
1125
+ }
1126
+ catch (opfsError) {
1127
+ console.warn('[CapgoCapacitorFastSql] OPFS unavailable; web database will not persist across reloads. ' +
1128
+ 'Serve with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp.', opfsError);
1129
+ openResult = await promiser('open', { filename: path });
1130
+ }
1151
1131
  }
1152
1132
  else {
1153
- db = new SQL.Database();
1133
+ openResult = await promiser('open', { filename: path });
1134
+ }
1135
+ const dbId = openResult.result.dbId;
1136
+ const persistent = Boolean(openResult.result.persistent);
1137
+ if (!dbId) {
1138
+ throw new Error('Failed to open SQLite Wasm database: missing dbId');
1154
1139
  }
1155
- applyPerformanceConfig(db, options);
1140
+ await this.applyPerformanceConfig(promiser, dbId, options);
1156
1141
  const token = this.generateToken();
1157
1142
  const port = this.nextPort++;
1158
- this.databases.set(options.database, { db, token, port });
1143
+ this.databases.set(options.database, { dbId, token, port, persistent });
1159
1144
  return {
1160
1145
  port,
1161
1146
  token,
@@ -1167,10 +1152,9 @@ class CapgoCapacitorFastSqlWeb extends core.WebPlugin {
1167
1152
  if (!dbInfo) {
1168
1153
  throw new Error(`Database '${options.database}' is not connected`);
1169
1154
  }
1170
- // Save to IndexedDB before closing
1171
- const data = dbInfo.db.export();
1172
- await this.saveToIndexedDB(options.database, data);
1173
- dbInfo.db.close();
1155
+ const promiser = await this.getPromiser();
1156
+ // OPFS-backed DBs persist automatically; no export/IndexedDB write needed.
1157
+ await promiser({ type: 'close', dbId: dbInfo.dbId, args: {} });
1174
1158
  this.databases.delete(options.database);
1175
1159
  }
1176
1160
  async getServerInfo(options) {
@@ -1184,32 +1168,45 @@ class CapgoCapacitorFastSqlWeb extends core.WebPlugin {
1184
1168
  };
1185
1169
  }
1186
1170
  async execute(options) {
1171
+ var _a, _b, _c, _d;
1187
1172
  const dbInfo = this.databases.get(options.database);
1188
1173
  if (!dbInfo) {
1189
1174
  throw new Error(`Database '${options.database}' is not connected`);
1190
1175
  }
1191
1176
  try {
1192
- const stmt = dbInfo.db.prepare(options.statement);
1193
- if (options.params) {
1194
- stmt.bind(options.params);
1195
- }
1177
+ const promiser = await this.getPromiser();
1196
1178
  const rows = [];
1197
- while (stmt.step()) {
1198
- const row = stmt.getAsObject();
1199
- rows.push(row);
1200
- }
1201
- stmt.free();
1202
- // Get changes and last insert ID
1203
- const changes = dbInfo.db.getRowsModified();
1204
- const insertId = this.getLastInsertId(dbInfo.db);
1179
+ // countChanges / lastInsertRowId are supported by Worker1 at runtime but
1180
+ // omitted from the published Worker1ExecArgs typings.
1181
+ const result = await promiser({
1182
+ type: 'exec',
1183
+ dbId: dbInfo.dbId,
1184
+ args: {
1185
+ sql: options.statement,
1186
+ bind: options.params,
1187
+ rowMode: 'object',
1188
+ countChanges: true,
1189
+ lastInsertRowId: true,
1190
+ callback: (msg) => {
1191
+ if (msg.rowNumber != null && msg.row && typeof msg.row === 'object' && !Array.isArray(msg.row)) {
1192
+ rows.push(msg.row);
1193
+ }
1194
+ },
1195
+ },
1196
+ });
1197
+ const execResult = result.result;
1198
+ const rowsAffected = Number((_a = execResult.changeCount) !== null && _a !== void 0 ? _a : 0);
1199
+ const rawInsertId = execResult.lastInsertRowId;
1200
+ const insertId = rawInsertId !== undefined && rawInsertId !== null ? Number(rawInsertId) : undefined;
1205
1201
  return {
1206
1202
  rows,
1207
- rowsAffected: changes,
1208
- insertId: insertId > 0 ? insertId : undefined,
1203
+ rowsAffected,
1204
+ insertId: insertId !== undefined && insertId > 0 ? insertId : undefined,
1209
1205
  };
1210
1206
  }
1211
1207
  catch (error) {
1212
- throw new Error(`SQL execution failed: ${error.message}`);
1208
+ const message = (_d = (_c = (_b = error === null || error === void 0 ? void 0 : error.result) === null || _b === void 0 ? void 0 : _b.message) !== null && _c !== void 0 ? _c : error === null || error === void 0 ? void 0 : error.message) !== null && _d !== void 0 ? _d : String(error);
1209
+ throw new Error(`SQL execution failed: ${message}`);
1213
1210
  }
1214
1211
  }
1215
1212
  async beginTransaction(options) {
@@ -1230,87 +1227,22 @@ class CapgoCapacitorFastSqlWeb extends core.WebPlugin {
1230
1227
  statement: 'ROLLBACK',
1231
1228
  });
1232
1229
  }
1233
- /**
1234
- * Generate a random authentication token
1235
- */
1230
+ async applyPerformanceConfig(promiser, dbId, options) {
1231
+ await promiser({ type: 'exec', dbId, args: { sql: 'PRAGMA foreign_keys = ON' } });
1232
+ if (options.walMode) {
1233
+ await promiser({ type: 'exec', dbId, args: { sql: 'PRAGMA journal_mode = WAL' } });
1234
+ }
1235
+ if (options.performancePresets) {
1236
+ await promiser({ type: 'exec', dbId, args: { sql: 'PRAGMA synchronous = NORMAL' } });
1237
+ await promiser({ type: 'exec', dbId, args: { sql: 'PRAGMA busy_timeout = 5000' } });
1238
+ await promiser({ type: 'exec', dbId, args: { sql: 'PRAGMA cache_size = -2000' } });
1239
+ }
1240
+ }
1236
1241
  generateToken() {
1237
1242
  return Array.from(crypto.getRandomValues(new Uint8Array(32)))
1238
1243
  .map((b) => b.toString(16).padStart(2, '0'))
1239
1244
  .join('');
1240
1245
  }
1241
- /**
1242
- * Get last insert row ID
1243
- */
1244
- getLastInsertId(db) {
1245
- try {
1246
- const result = db.exec('SELECT last_insert_rowid()');
1247
- if (result.length > 0 && result[0].values.length > 0) {
1248
- return result[0].values[0][0];
1249
- }
1250
- }
1251
- catch (_a) {
1252
- // Ignore error
1253
- }
1254
- return -1;
1255
- }
1256
- /**
1257
- * Save database to IndexedDB
1258
- */
1259
- async saveToIndexedDB(dbName, data) {
1260
- return new Promise((resolve, reject) => {
1261
- const request = indexedDB.open('CapacitorNativeSQL', 1);
1262
- request.onerror = () => reject(request.error);
1263
- request.onupgradeneeded = (event) => {
1264
- const db = event.target.result;
1265
- if (!db.objectStoreNames.contains('databases')) {
1266
- db.createObjectStore('databases');
1267
- }
1268
- };
1269
- request.onsuccess = () => {
1270
- const db = request.result;
1271
- const transaction = db.transaction(['databases'], 'readwrite');
1272
- const store = transaction.objectStore('databases');
1273
- const putRequest = store.put(data, dbName);
1274
- putRequest.onsuccess = () => {
1275
- db.close();
1276
- resolve();
1277
- };
1278
- putRequest.onerror = () => {
1279
- db.close();
1280
- reject(putRequest.error);
1281
- };
1282
- };
1283
- });
1284
- }
1285
- /**
1286
- * Load database from IndexedDB
1287
- */
1288
- async loadFromIndexedDB(dbName) {
1289
- return new Promise((resolve, reject) => {
1290
- const request = indexedDB.open('CapacitorNativeSQL', 1);
1291
- request.onerror = () => reject(request.error);
1292
- request.onupgradeneeded = (event) => {
1293
- const db = event.target.result;
1294
- if (!db.objectStoreNames.contains('databases')) {
1295
- db.createObjectStore('databases');
1296
- }
1297
- };
1298
- request.onsuccess = () => {
1299
- const db = request.result;
1300
- const transaction = db.transaction(['databases'], 'readonly');
1301
- const store = transaction.objectStore('databases');
1302
- const getRequest = store.get(dbName);
1303
- getRequest.onsuccess = () => {
1304
- db.close();
1305
- resolve(getRequest.result || null);
1306
- };
1307
- getRequest.onerror = () => {
1308
- db.close();
1309
- reject(getRequest.error);
1310
- };
1311
- };
1312
- });
1313
- }
1314
1246
  async getPluginVersion() {
1315
1247
  return { version: 'web' };
1316
1248
  }