@capgo/capacitor-fast-sql 8.2.16 → 8.2.18

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.
Files changed (37) hide show
  1. package/README.md +17 -16
  2. package/android/build.gradle +1 -0
  3. package/android/consumer-rules.pro +14 -0
  4. package/android/src/main/java/app/capgo/capacitor/fastsql/CapgoCapacitorFastSqlPlugin.java +29 -6
  5. package/android/src/main/java/app/capgo/capacitor/fastsql/DatabaseConnection.java +2 -0
  6. package/android/src/main/java/app/capgo/capacitor/fastsql/EncryptedSQLDatabase.java +33 -46
  7. package/android/src/main/java/app/capgo/capacitor/fastsql/SQLDatabase.java +32 -50
  8. package/android/src/main/java/app/capgo/capacitor/fastsql/SQLHTTPServer.java +11 -23
  9. package/dist/esm/definitions.d.ts +17 -2
  10. package/dist/esm/definitions.d.ts.map +1 -1
  11. package/dist/esm/definitions.js +3 -1
  12. package/dist/esm/fast-sql.d.ts +4 -0
  13. package/dist/esm/fast-sql.d.ts.map +1 -1
  14. package/dist/esm/fast-sql.js +35 -3
  15. package/dist/esm/native-sql-connection.d.ts +1 -0
  16. package/dist/esm/native-sql-connection.d.ts.map +1 -1
  17. package/dist/esm/native-sql-connection.js +13 -6
  18. package/dist/esm/transaction-isolation.d.ts +3 -0
  19. package/dist/esm/transaction-isolation.d.ts.map +1 -0
  20. package/dist/esm/transaction-isolation.js +6 -0
  21. package/dist/esm/vector-store.d.ts.map +1 -1
  22. package/dist/esm/vector-store.js +5 -2
  23. package/dist/esm/web-sql-connection.d.ts.map +1 -1
  24. package/dist/esm/web-sql-connection.js +2 -0
  25. package/dist/esm/web.d.ts +2 -0
  26. package/dist/esm/web.d.ts.map +1 -1
  27. package/dist/esm/web.js +32 -4
  28. package/dist/plugin.cjs.js +93 -16
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +93 -16
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Sources/CapgoCapacitorFastSqlPlugin/CapgoCapacitorFastSqlPlugin.swift +25 -3
  33. package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLDatabase.swift +15 -5
  34. package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLHTTPServer.swift +6 -0
  35. package/ios/Tests/CapgoCapacitorFastSqlPluginTests/CapgoCapacitorFastSqlPluginTests.swift +13 -3
  36. package/ios/Tests/CapgoCapacitorFastSqlPluginTests/SQLResultMetadataTests.swift +15 -0
  37. package/package.json +4 -2
package/README.md CHANGED
@@ -383,7 +383,7 @@ bypassing Capacitor's standard bridge for better performance with sync operation
383
383
  ### connect(...)
384
384
 
385
385
  ```typescript
386
- connect(options: SQLConnectionOptions) => Promise<{ port: number; token: string; database: string; }>
386
+ connect(options: SQLConnectionOptions) => Promise<{ port: number; token: string; database: string; persistent?: boolean; }>
387
387
  ```
388
388
 
389
389
  Initialize the database connection and start the HTTP server.
@@ -392,7 +392,7 @@ Initialize the database connection and start the HTTP server.
392
392
  | ------------- | --------------------------------------------------------------------- | -------------------- |
393
393
  | **`options`** | <code><a href="#sqlconnectionoptions">SQLConnectionOptions</a></code> | - Connection options |
394
394
 
395
- **Returns:** <code>Promise&lt;{ port: number; token: string; database: string; }&gt;</code>
395
+ **Returns:** <code>Promise&lt;{ port: number; token: string; database: string; persistent?: boolean; }&gt;</code>
396
396
 
397
397
  **Since:** 0.0.1
398
398
 
@@ -419,7 +419,7 @@ Close database connection and stop the HTTP server.
419
419
  ### getServerInfo(...)
420
420
 
421
421
  ```typescript
422
- getServerInfo(options: { database: string; }) => Promise<{ port: number; token: string; }>
422
+ getServerInfo(options: { database: string; }) => Promise<{ port: number; token: string; persistent?: boolean; }>
423
423
  ```
424
424
 
425
425
  Get the HTTP server port and token for direct communication.
@@ -428,7 +428,7 @@ Get the HTTP server port and token for direct communication.
428
428
  | ------------- | ---------------------------------- | --------------- |
429
429
  | **`options`** | <code>{ database: string; }</code> | - Database name |
430
430
 
431
- **Returns:** <code>Promise&lt;{ port: number; token: string; }&gt;</code>
431
+ **Returns:** <code>Promise&lt;{ port: number; token: string; persistent?: boolean; }&gt;</code>
432
432
 
433
433
  **Since:** 0.0.1
434
434
 
@@ -548,14 +548,14 @@ or supply a custom Worker. This method is a no-op on iOS and Android.
548
548
 
549
549
  Database connection options
550
550
 
551
- | Prop | Type | Description | Since |
552
- | ------------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
553
- | **`database`** | <code>string</code> | Database name (file will be created in app data directory) | |
554
- | **`encrypted`** | <code>boolean</code> | Enable encryption (iOS/Android only) | |
555
- | **`encryptionKey`** | <code>string</code> | Encryption key (required if encrypted is true) | |
556
- | **`readOnly`** | <code>boolean</code> | Read-only mode | |
557
- | **`walMode`** | <code>boolean</code> | Enable WAL (Write-Ahead Logging) journal mode for better concurrency and performance. Executes `PRAGMA journal_mode = WAL` immediately after opening the database. | 8.0.49 |
558
- | **`performancePresets`** | <code>boolean</code> | Apply recommended SQLite performance PRAGMAs after opening the database: `synchronous = NORMAL`, `busy_timeout = 5000`, `cache_size = -2000`, and `foreign_keys = ON`. | 8.0.49 |
551
+ | Prop | Type | Description | Since |
552
+ | ------------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
553
+ | **`database`** | <code>string</code> | Database name (file will be created in app data directory) | |
554
+ | **`encrypted`** | <code>boolean</code> | Enable encryption (iOS/Android only) | |
555
+ | **`encryptionKey`** | <code>string</code> | Encryption key (required if encrypted is true) | |
556
+ | **`readOnly`** | <code>boolean</code> | Open an existing native database read-only. Writes and missing-file opens fail. WAL mode changes are skipped for read-only connections. The web Worker transport rejects this option instead of silently opening writable. | |
557
+ | **`walMode`** | <code>boolean</code> | Enable WAL (Write-Ahead Logging) journal mode for better concurrency and performance. Executes `PRAGMA journal_mode = WAL` immediately after opening the database. | 8.0.49 |
558
+ | **`performancePresets`** | <code>boolean</code> | Apply recommended SQLite performance PRAGMAs after opening the database: `synchronous = NORMAL`, `busy_timeout = 5000`, `cache_size = -2000`, and `foreign_keys = ON`. | 8.0.49 |
559
559
 
560
560
 
561
561
  #### SQLResult
@@ -658,10 +658,11 @@ OPFS persistence requires Cross-Origin Isolation headers on your web server:
658
658
  `Cross-Origin-Opener-Policy: same-origin` and
659
659
  `Cross-Origin-Embedder-Policy: require-corp`.
660
660
 
661
- | Prop | Type | Description | Default |
662
- | ------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- |
663
- | **`useOpfs`** | <code>boolean</code> | Prefer OPFS persistence via a Web Worker (default: `true`). When OPFS is unavailable, the plugin falls back to a non-persistent database and logs a warning. | <code>true</code> |
664
- | **`worker`** | <code><a href="#sqlitewebworker">SqliteWebWorker</a></code> | Custom Worker instance or factory for SQLite Wasm. Advanced: override the default worker from `@sqlite.org/sqlite-wasm`. | |
661
+ | Prop | Type | Description | Default |
662
+ | ------------------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
663
+ | **`useOpfs`** | <code>boolean</code> | Prefer OPFS persistence via a Web Worker (default: `true`). When OPFS is unavailable, the plugin falls back to a non-persistent database and logs a warning. | <code>true</code> |
664
+ | **`requirePersistence`** | <code>boolean</code> | Reject web connections unless the Worker confirms persistent storage. Defaults to false for compatibility. Consider requiring persistence by default in the next Capacitor major, rather than changing fallback behavior in a minor. | <code>false</code> |
665
+ | **`worker`** | <code><a href="#sqlitewebworker">SqliteWebWorker</a></code> | Custom Worker instance or factory for SQLite Wasm. Advanced: override the default worker from `@sqlite.org/sqlite-wasm`. | |
665
666
 
666
667
 
667
668
  ### Type Aliases
@@ -26,6 +26,7 @@ android {
26
26
  versionCode 1
27
27
  versionName "1.0"
28
28
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ consumerProguardFiles 'consumer-rules.pro'
29
30
  }
30
31
  buildTypes {
31
32
  release {
@@ -0,0 +1,14 @@
1
+ # Consumer ProGuard rules for @capgo/capacitor-fast-sql (merged into app R8 when this AAR is a dependency).
2
+
3
+ # CapgoCapacitorFastSqlPlugin uses Class.forName on this class when encrypted databases are enabled.
4
+ # R8 must keep the type so the optional SQLCipher dependency still resolves at runtime.
5
+ -keep class net.zetetic.database.sqlcipher.SQLiteDatabase { *; }
6
+
7
+ # SQLCipher is compileOnly and also exposes types such as DatabaseErrorHandler
8
+ # directly under net.zetetic.database. Plain-SQLite apps omit the entire artifact.
9
+ -dontwarn net.zetetic.database.**
10
+
11
+ # EncryptedSQLDatabase loads the SQLCipher JNI library by name; keep JNI method names linked from Java.
12
+ -keepclasseswithmembernames class * {
13
+ native <methods>;
14
+ }
@@ -19,7 +19,7 @@ import java.util.Map;
19
19
  @CapacitorPlugin(name = "CapgoCapacitorFastSql")
20
20
  public class CapgoCapacitorFastSqlPlugin extends Plugin {
21
21
 
22
- private final String pluginVersion = "8.2.16";
22
+ private final String pluginVersion = "8.2.18";
23
23
 
24
24
  private Map<String, DatabaseConnection> databases = new HashMap<>();
25
25
  private SQLHTTPServer server;
@@ -31,6 +31,7 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
31
31
  call.reject("Database name is required");
32
32
  return;
33
33
  }
34
+ boolean readOnly = call.getBoolean("readOnly", false);
34
35
  boolean encrypted = call.getBoolean("encrypted", false);
35
36
  String encryptionKey = call.getString("encryptionKey");
36
37
  boolean walMode = call.getBoolean("walMode", false);
@@ -42,6 +43,10 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
42
43
 
43
44
  // Check if already connected
44
45
  if (databases.containsKey(database)) {
46
+ if (databases.get(database).isReadOnly() != readOnly) {
47
+ call.reject("Database is already open with a different readOnly option");
48
+ return;
49
+ }
45
50
  if (server != null) {
46
51
  JSObject ret = new JSObject();
47
52
  ret.put("port", server.getPort());
@@ -69,9 +74,9 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
69
74
  );
70
75
  return;
71
76
  }
72
- db = new EncryptedSQLDatabase(dbFile.getAbsolutePath(), encryptionKey, walMode, performancePresets);
77
+ db = new EncryptedSQLDatabase(dbFile.getAbsolutePath(), encryptionKey, walMode, performancePresets, readOnly);
73
78
  } else {
74
- db = new SQLDatabase(dbFile.getAbsolutePath(), walMode, performancePresets);
79
+ db = new SQLDatabase(dbFile.getAbsolutePath(), walMode, performancePresets, readOnly);
75
80
  }
76
81
  databases.put(database, db);
77
82
 
@@ -173,6 +178,10 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
173
178
 
174
179
  @PluginMethod
175
180
  public void beginTransaction(PluginCall call) {
181
+ if (!"SERIALIZABLE".equals(call.getString("isolationLevel", "SERIALIZABLE"))) {
182
+ call.reject("Only SERIALIZABLE transaction isolation is supported");
183
+ return;
184
+ }
176
185
  String database = call.getString("database");
177
186
  if (database == null) {
178
187
  call.reject("Database name is required");
@@ -250,8 +259,22 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
250
259
  call.resolve(ret);
251
260
  }
252
261
 
253
- private File getDatabasePath(String database) {
254
- File dataDir = getContext().getFilesDir();
255
- return new File(dataDir, database + ".db");
262
+ private File getDatabasePath(String database) throws java.io.IOException {
263
+ if (
264
+ database.isEmpty() ||
265
+ database.equals(".") ||
266
+ database.equals("..") ||
267
+ database.indexOf('/') >= 0 ||
268
+ database.indexOf('\\') >= 0 ||
269
+ database.indexOf(0) >= 0
270
+ ) {
271
+ throw new java.io.IOException("Database must be a non-empty file name without path separators");
272
+ }
273
+ File dataDir = getContext().getFilesDir().getCanonicalFile();
274
+ File dbFile = new File(dataDir, database + ".db").getCanonicalFile();
275
+ if (!dataDir.equals(dbFile.getParentFile())) {
276
+ throw new java.io.IOException("Database path must remain inside app storage");
277
+ }
278
+ return dbFile;
256
279
  }
257
280
  }
@@ -12,5 +12,7 @@ public interface DatabaseConnection {
12
12
 
13
13
  void rollbackTransaction() throws Exception;
14
14
 
15
+ boolean isReadOnly();
16
+
15
17
  void close();
16
18
  }
@@ -5,9 +5,9 @@ import android.util.Base64;
5
5
  import com.getcapacitor.JSArray;
6
6
  import com.getcapacitor.JSObject;
7
7
  import java.nio.charset.StandardCharsets;
8
- import java.util.ArrayList;
9
- import java.util.List;
8
+ import net.zetetic.database.sqlcipher.SQLiteCursor;
10
9
  import net.zetetic.database.sqlcipher.SQLiteDatabase;
10
+ import net.zetetic.database.sqlcipher.SQLiteProgram;
11
11
  import net.zetetic.database.sqlcipher.SQLiteStatement;
12
12
  import org.json.JSONObject;
13
13
 
@@ -20,6 +20,11 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
20
20
  private volatile boolean inTransaction = false;
21
21
 
22
22
  public EncryptedSQLDatabase(String path, String encryptionKey, boolean walMode, boolean performancePresets) throws Exception {
23
+ this(path, encryptionKey, walMode, performancePresets, false);
24
+ }
25
+
26
+ public EncryptedSQLDatabase(String path, String encryptionKey, boolean walMode, boolean performancePresets, boolean readOnly)
27
+ throws Exception {
23
28
  if (encryptionKey == null || encryptionKey.isEmpty()) {
24
29
  throw new Exception("Encryption key is required when encrypted is true");
25
30
  }
@@ -34,8 +39,14 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
34
39
  );
35
40
  }
36
41
  byte[] keyBytes = encryptionKey.getBytes(StandardCharsets.UTF_8);
37
- this.db = SQLiteDatabase.openOrCreateDatabase(path, keyBytes, null, null);
38
- SQLPerformanceConfig.apply(db::execSQL, walMode, performancePresets);
42
+ this.db = readOnly
43
+ ? SQLiteDatabase.openDatabase(path, keyBytes, null, SQLiteDatabase.OPEN_READONLY, null)
44
+ : SQLiteDatabase.openOrCreateDatabase(path, keyBytes, null, null);
45
+ SQLPerformanceConfig.apply(db::execSQL, walMode && !readOnly, performancePresets);
46
+ }
47
+
48
+ public boolean isReadOnly() {
49
+ return db.isReadOnly();
39
50
  }
40
51
 
41
52
  public void close() {
@@ -49,23 +60,24 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
49
60
  throw new Exception("Database is not open");
50
61
  }
51
62
 
52
- String trimmedStatement = statement.trim().toUpperCase();
53
- boolean isQuery =
54
- trimmedStatement.startsWith("SELECT") || trimmedStatement.startsWith("PRAGMA") || trimmedStatement.startsWith("EXPLAIN");
55
-
56
- if (isQuery) {
57
- return executeQuery(statement, params);
58
- } else {
59
- return executeUpdate(statement, params);
60
- }
61
- }
62
-
63
- private JSObject executeQuery(String statement, JSArray params) throws Exception {
64
- Cursor cursor = null;
63
+ Cursor cursor = db.rawQueryWithFactory(
64
+ (database, driver, editTable, query) -> {
65
+ try {
66
+ bindParams(query, params);
67
+ } catch (Exception error) {
68
+ throw new IllegalArgumentException("Invalid query parameters", error);
69
+ }
70
+ return new SQLiteCursor(driver, editTable, query);
71
+ },
72
+ statement,
73
+ new String[0],
74
+ null
75
+ );
65
76
  try {
66
- String[] bindArgs = convertParamsToStringArray(params);
67
- cursor = db.rawQuery(statement, bindArgs);
68
-
77
+ if (cursor.getColumnCount() == 0) {
78
+ cursor.close();
79
+ return executeUpdate(statement, params);
80
+ }
69
81
  JSArray rows = new JSArray();
70
82
  while (cursor.moveToNext()) {
71
83
  JSObject row = new JSObject();
@@ -142,32 +154,7 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
142
154
  inTransaction = false;
143
155
  }
144
156
 
145
- private String[] convertParamsToStringArray(JSArray params) throws Exception {
146
- if (params == null || params.length() == 0) {
147
- return new String[0];
148
- }
149
-
150
- List<String> args = new ArrayList<>();
151
- for (int i = 0; i < params.length(); i++) {
152
- Object value = params.get(i);
153
-
154
- if (value == null || value == JSONObject.NULL) {
155
- args.add(null);
156
- } else if (value instanceof JSONObject) {
157
- JSONObject obj = (JSONObject) value;
158
- if (obj.has("_type") && "binary".equals(obj.getString("_type"))) {
159
- args.add(obj.getString("_data"));
160
- } else {
161
- args.add(value.toString());
162
- }
163
- } else {
164
- args.add(value.toString());
165
- }
166
- }
167
- return args.toArray(new String[0]);
168
- }
169
-
170
- private void bindParams(SQLiteStatement stmt, JSArray params) throws Exception {
157
+ private void bindParams(SQLiteProgram stmt, JSArray params) throws Exception {
171
158
  if (params == null || params.length() == 0) {
172
159
  return;
173
160
  }
@@ -1,15 +1,14 @@
1
1
  package app.capgo.capacitor.fastsql;
2
2
 
3
3
  import android.database.Cursor;
4
+ import android.database.sqlite.SQLiteCursor;
4
5
  import android.database.sqlite.SQLiteDatabase;
6
+ import android.database.sqlite.SQLiteProgram;
5
7
  import android.database.sqlite.SQLiteStatement;
6
8
  import android.util.Base64;
7
9
  import com.getcapacitor.JSArray;
8
10
  import com.getcapacitor.JSObject;
9
- import java.util.ArrayList;
10
- import java.util.List;
11
11
  import org.json.JSONArray;
12
- import org.json.JSONException;
13
12
  import org.json.JSONObject;
14
13
 
15
14
  /**
@@ -21,8 +20,18 @@ public class SQLDatabase implements DatabaseConnection {
21
20
  private volatile boolean inTransaction = false;
22
21
 
23
22
  public SQLDatabase(String path, boolean walMode, boolean performancePresets) throws Exception {
24
- this.db = SQLiteDatabase.openOrCreateDatabase(path, null);
25
- SQLPerformanceConfig.apply(db::execSQL, walMode, performancePresets);
23
+ this(path, walMode, performancePresets, false);
24
+ }
25
+
26
+ public SQLDatabase(String path, boolean walMode, boolean performancePresets, boolean readOnly) throws Exception {
27
+ this.db = readOnly
28
+ ? SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY)
29
+ : SQLiteDatabase.openOrCreateDatabase(path, null);
30
+ SQLPerformanceConfig.apply(db::execSQL, walMode && !readOnly, performancePresets);
31
+ }
32
+
33
+ public boolean isReadOnly() {
34
+ return db.isReadOnly();
26
35
  }
27
36
 
28
37
  public void close() {
@@ -36,25 +45,24 @@ public class SQLDatabase implements DatabaseConnection {
36
45
  throw new Exception("Database is not open");
37
46
  }
38
47
 
39
- // Check if this is a query (SELECT) or a modification (INSERT/UPDATE/DELETE)
40
- String trimmedStatement = statement.trim().toUpperCase();
41
- boolean isQuery =
42
- trimmedStatement.startsWith("SELECT") || trimmedStatement.startsWith("PRAGMA") || trimmedStatement.startsWith("EXPLAIN");
43
-
44
- if (isQuery) {
45
- return executeQuery(statement, params);
46
- } else {
47
- return executeUpdate(statement, params);
48
- }
49
- }
50
-
51
- private JSObject executeQuery(String statement, JSArray params) throws Exception {
52
- Cursor cursor = null;
48
+ Cursor cursor = db.rawQueryWithFactory(
49
+ (database, driver, editTable, query) -> {
50
+ try {
51
+ bindParams(query, params);
52
+ } catch (Exception error) {
53
+ throw new IllegalArgumentException("Invalid query parameters", error);
54
+ }
55
+ return new SQLiteCursor(driver, editTable, query);
56
+ },
57
+ statement,
58
+ new String[0],
59
+ null
60
+ );
53
61
  try {
54
- // Prepare statement with parameters
55
- String[] bindArgs = convertParamsToStringArray(params);
56
- cursor = db.rawQuery(statement, bindArgs);
57
-
62
+ if (cursor.getColumnCount() == 0) {
63
+ cursor.close();
64
+ return executeUpdate(statement, params);
65
+ }
58
66
  // Build result
59
67
  JSArray rows = new JSArray();
60
68
  while (cursor.moveToNext()) {
@@ -136,33 +144,7 @@ public class SQLDatabase implements DatabaseConnection {
136
144
  inTransaction = false;
137
145
  }
138
146
 
139
- private String[] convertParamsToStringArray(JSArray params) throws JSONException {
140
- if (params == null || params.length() == 0) {
141
- return null;
142
- }
143
-
144
- List<String> args = new ArrayList<>();
145
- for (int i = 0; i < params.length(); i++) {
146
- Object value = params.get(i);
147
- if (value == null || value == JSONObject.NULL) {
148
- args.add(null);
149
- } else if (value instanceof JSONObject) {
150
- JSONObject obj = (JSONObject) value;
151
- if (obj.has("_type") && "binary".equals(obj.getString("_type"))) {
152
- // For queries, we can't bind binary data as string
153
- // This is a limitation - for binary data, use executeUpdate
154
- args.add(obj.getString("_data"));
155
- } else {
156
- args.add(value.toString());
157
- }
158
- } else {
159
- args.add(value.toString());
160
- }
161
- }
162
- return args.toArray(new String[0]);
163
- }
164
-
165
- private void bindParams(SQLiteStatement stmt, JSArray params) throws Exception {
147
+ private void bindParams(SQLiteProgram stmt, JSArray params) throws Exception {
166
148
  if (params == null || params.length() == 0) {
167
149
  return;
168
150
  }
@@ -33,7 +33,7 @@ public class SQLHTTPServer extends NanoHTTPD {
33
33
  });
34
34
 
35
35
  public SQLHTTPServer(Map<String, DatabaseConnection> databases) throws IOException {
36
- super(findAvailablePort());
36
+ super("127.0.0.1", 0);
37
37
  this.databases = databases;
38
38
  this.token = generateToken();
39
39
  }
@@ -152,14 +152,22 @@ public class SQLHTTPServer extends NanoHTTPD {
152
152
  HttpRequestJsonParams.populateJsArrayFromParamsJson(paramsJson, params, rawParamsArray);
153
153
 
154
154
  JSObject result = db.execute(statement, params);
155
- results.put(new JSONObject(result.toString()));
155
+ results.put(result);
156
156
  }
157
157
 
158
158
  return newFixedLengthResponse(Response.Status.OK, "application/json", results.toString());
159
159
  }
160
160
 
161
161
  private Response handleBeginTransaction(IHTTPSession session, DatabaseConnection db) throws Exception {
162
- readRequestBody(session); // drain any body to keep the connection clean for keep-alive
162
+ String body = readRequestBody(session);
163
+ JSONObject request = body.isEmpty() ? new JSONObject() : new JSONObject(body);
164
+ if (!"SERIALIZABLE".equals(request.optString("isolationLevel", "SERIALIZABLE"))) {
165
+ return newFixedLengthResponse(
166
+ Response.Status.BAD_REQUEST,
167
+ "text/plain",
168
+ "Only SERIALIZABLE transaction isolation is supported"
169
+ );
170
+ }
163
171
  db.beginTransaction();
164
172
  return newFixedLengthResponse(Response.Status.OK, "application/json", "{}");
165
173
  }
@@ -193,24 +201,4 @@ public class SQLHTTPServer extends NanoHTTPD {
193
201
  }
194
202
  return sb.toString();
195
203
  }
196
-
197
- private static int findAvailablePort() {
198
- // Try ports in the range 9000-9100
199
- for (int port = 9000; port < 9100; port++) {
200
- try {
201
- NanoHTTPD testServer = new NanoHTTPD(port) {
202
- @Override
203
- public Response serve(IHTTPSession session) {
204
- return null;
205
- }
206
- };
207
- testServer.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
208
- testServer.stop();
209
- return port;
210
- } catch (IOException e) {
211
- // Port not available, try next one
212
- }
213
- }
214
- return 9000; // Fallback
215
- }
216
204
  }
@@ -55,7 +55,9 @@ export interface SQLConnectionOptions {
55
55
  */
56
56
  encryptionKey?: string;
57
57
  /**
58
- * Read-only mode
58
+ * Open an existing native database read-only. Writes and missing-file opens fail.
59
+ * WAL mode changes are skipped for read-only connections.
60
+ * The web Worker transport rejects this option instead of silently opening writable.
59
61
  */
60
62
  readOnly?: boolean;
61
63
  /**
@@ -94,6 +96,13 @@ export interface WebConfig {
94
96
  * @default true
95
97
  */
96
98
  useOpfs?: boolean;
99
+ /**
100
+ * Reject web connections unless the Worker confirms persistent storage.
101
+ * Defaults to false for compatibility. Consider requiring persistence by default
102
+ * in the next Capacitor major, rather than changing fallback behavior in a minor.
103
+ * @default false
104
+ */
105
+ requirePersistence?: boolean;
97
106
  /**
98
107
  * Custom Worker instance or factory for SQLite Wasm.
99
108
  * Advanced: override the default worker from `@sqlite.org/sqlite-wasm`.
@@ -101,7 +110,9 @@ export interface WebConfig {
101
110
  worker?: SqliteWebWorker;
102
111
  }
103
112
  /**
104
- * Transaction isolation levels
113
+ * Transaction isolation levels. Only Serializable is supported by the current
114
+ * SQLite backends. Other values are retained for source compatibility but reject
115
+ * before starting a transaction; they are not silently mapped to another level.
105
116
  */
106
117
  export declare enum IsolationLevel {
107
118
  ReadUncommitted = "READ UNCOMMITTED",
@@ -135,6 +146,8 @@ export interface CapgoCapacitorFastSqlPlugin {
135
146
  port: number;
136
147
  token: string;
137
148
  database: string;
149
+ /** Whether the web Worker confirms persistent storage. Absent on native. */
150
+ persistent?: boolean;
138
151
  }>;
139
152
  /**
140
153
  * Close database connection and stop the HTTP server.
@@ -169,6 +182,8 @@ export interface CapgoCapacitorFastSqlPlugin {
169
182
  }): Promise<{
170
183
  port: number;
171
184
  token: string;
185
+ /** Whether the web Worker confirms persistent storage. Absent on native. */
186
+ persistent?: boolean;
172
187
  }>;
173
188
  /**
174
189
  * Execute a SQL query via Capacitor bridge (for simple queries).
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Transaction isolation levels
2
+ * Transaction isolation levels. Only Serializable is supported by the current
3
+ * SQLite backends. Other values are retained for source compatibility but reject
4
+ * before starting a transaction; they are not silently mapped to another level.
3
5
  */
4
6
  export var IsolationLevel;
5
7
  (function (IsolationLevel) {
@@ -7,6 +7,8 @@ import type { SQLConnection } from './sql-connection';
7
7
  * and managing multiple databases simultaneously.
8
8
  */
9
9
  export declare class FastSQL {
10
+ private static opening;
11
+ private static openOptions;
10
12
  private static connections;
11
13
  private static sharedConnections;
12
14
  private static connectionRetainers;
@@ -18,6 +20,8 @@ export declare class FastSQL {
18
20
  * @returns SQLConnection instance for executing queries
19
21
  */
20
22
  static connect(options: SQLConnectionOptions): Promise<SQLConnection>;
23
+ private static optionsMatch;
24
+ private static open;
21
25
  /**
22
26
  * Close a database connection
23
27
  *
@@ -1 +1 @@
1
- {"version":3,"file":"fast-sql.d.ts","sourceRoot":"","sources":["../../src/fast-sql.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD;;;;;GAKG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAC,WAAW,CAAyC;IACnE,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAA0B;IAC1D,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAkC;IACpE,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAA0B;IAE3D;;;;;OAKG;WACU,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IA0B3E;;;;OAIG;WACU,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxD;;;;;OAKG;IACH,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAI5D;;OAEG;IACH,MAAM,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIpD;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAO/C;;OAEG;WACU,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBlE;;OAEG;WACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3C;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,IAAI,MAAM,EAAE;CAGpC"}
1
+ {"version":3,"file":"fast-sql.d.ts","sourceRoot":"","sources":["../../src/fast-sql.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD;;;;;GAKG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAC,OAAO,CAA6C;IACnE,OAAO,CAAC,MAAM,CAAC,WAAW,CAA2C;IACrE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAyC;IACnE,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAA0B;IAC1D,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAkC;IACpE,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAA0B;IAE3D;;;;;OAKG;WACU,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IA0B3E,OAAO,CAAC,MAAM,CAAC,YAAY;mBAUN,IAAI;IAmBzB;;;;OAIG;WACU,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBxD;;;;;OAKG;IACH,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAI5D;;OAEG;IACH,MAAM,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIpD;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAO/C;;OAEG;WACU,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBlE;;OAEG;WACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3C;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,IAAI,MAAM,EAAE;CAGpC"}