@capgo/capacitor-fast-sql 8.2.11 โ†’ 8.2.12

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Run SQLite fast in your Capacitor app: queries go through a local HTTP channel instead of the bridge, so large result sets and sync jobs stay quick. A practical replacement for IndexedDB, with optional encryption.
4
4
 
5
- <a href="https://capgo.app/?ref=plugin_fast_sql"><img src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-fast-sql" alt="Capgo - Instant updates for Capacitor" /></a>
5
+ <a href="https://capgo.app/?ref=plugin_fast_sql"><img src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-fast-sql" alt="Capgo - Build Capacitor apps with AI, ship updates instantly" /></a>
6
6
 
7
7
  <div align="center">
8
8
  <p><b>Capgo</b>: push fixes to your Capacitor users in minutes, build signed iOS and Android apps without a Mac, and roll back in one click.</p>
@@ -10,6 +10,8 @@ Run SQLite fast in your Capacitor app: queries go through a local HTTP channel i
10
10
  <p>14-day unlimited free trial. No credit card required</p>
11
11
  <p><a href="https://capgo.app/consulting/?ref=plugin_fast_sql">Missing a feature? We'll build the plugin for you ๐Ÿ’ช</a></p>
12
12
  </div>
13
+
14
+ > ๐Ÿค– **Building with an AI coding agent?** Install [Capgo skills](https://capgo.app/skills/?ref=plugin_fast_sql) so Claude Code, Codex, Cursor, Copilot, or Gemini CLI know how to install and use `@capgo/capacitor-fast-sql` in your Capacitor app: `npx skills add Cap-go/capgo-skills`. Then ask your agent: *"Add @capgo/capacitor-fast-sql to my Capacitor app and wire it up."* [Agent setup guides](https://capgo.app/agents/?ref=plugin_fast_sql) ยท [Hosted MCP](https://capgo.app/docs/ai/mcp/?ref=plugin_fast_sql)
13
15
 
14
16
  <p align="center">
15
17
  <img src="https://raw.githubusercontent.com/Cap-go/capacitor-fast-sql/main/assets/github-social-preview.png" alt="@capgo/capacitor-fast-sql for Capacitor apps" width="300" />
@@ -40,6 +40,9 @@ android {
40
40
  sourceCompatibility JavaVersion.VERSION_21
41
41
  targetCompatibility JavaVersion.VERSION_21
42
42
  }
43
+ testOptions {
44
+ unitTests.includeAndroidResources = true
45
+ }
43
46
  }
44
47
 
45
48
  repositories {
@@ -52,10 +55,10 @@ dependencies {
52
55
  implementation project(':capacitor-android')
53
56
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
54
57
  implementation 'androidx.sqlite:sqlite:2.7.1'
55
- implementation 'com.google.code.gson:gson:2.14.0'
56
58
  implementation 'org.nanohttpd:nanohttpd:2.3.1'
57
59
  compileOnly 'net.zetetic:sqlcipher-android:4.19.1'
58
60
  testImplementation "junit:junit:$junitVersion"
61
+ testImplementation 'org.robolectric:robolectric:4.16'
59
62
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
60
63
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
61
64
  }
@@ -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.11";
22
+ private final String pluginVersion = "8.2.12";
23
23
 
24
24
  private Map<String, DatabaseConnection> databases = new HashMap<>();
25
25
  private SQLHTTPServer server;
@@ -0,0 +1,413 @@
1
+ package app.capgo.capacitor.fastsql;
2
+
3
+ import com.getcapacitor.JSArray;
4
+ import java.math.BigDecimal;
5
+ import java.util.ArrayList;
6
+ import java.util.Iterator;
7
+ import java.util.List;
8
+ import org.json.JSONArray;
9
+ import org.json.JSONException;
10
+ import org.json.JSONObject;
11
+ import org.json.JSONTokener;
12
+
13
+ /**
14
+ * Converts HTTP request JSON parameter arrays to {@link JSArray} values with the same
15
+ * semantics as the previous Gson-based parsing in {@link SQLHTTPServer}.
16
+ */
17
+ final class HttpRequestJsonParams {
18
+
19
+ private static final int MAX_NUMBER_STRING_LENGTH = 10_000;
20
+
21
+ private HttpRequestJsonParams() {}
22
+
23
+ static void populateJsArrayFromParamsJson(JSONArray paramsJson, JSArray params) throws JSONException {
24
+ populateJsArrayFromParamsJson(paramsJson, params, null);
25
+ }
26
+
27
+ static void populateJsArrayFromParamsJson(JSONArray paramsJson, JSArray params, String rawParamsArrayJson) throws JSONException {
28
+ List<String> rawElements = rawParamsArrayJson != null ? splitTopLevelJsonArrayElements(rawParamsArrayJson) : null;
29
+ for (int i = 0; i < paramsJson.length(); i++) {
30
+ String rawElement = rawElements != null && i < rawElements.size() ? rawElements.get(i) : null;
31
+ Object converted =
32
+ rawElement != null ? convertRequestParamFromRawJson(rawElement, paramsJson.get(i)) : convertRequestParam(paramsJson.get(i));
33
+ if (converted != null) {
34
+ params.put(converted);
35
+ }
36
+ }
37
+ }
38
+
39
+ static String extractJsonArrayAfterKey(String json, String key) throws JSONException {
40
+ int i = skipWhitespace(json, 0);
41
+ if (i >= json.length() || json.charAt(i) != '{') {
42
+ throw new JSONException("Expected JSON object");
43
+ }
44
+ i++;
45
+ String found = null;
46
+ boolean sawMatchingKey = false;
47
+ while (i < json.length()) {
48
+ i = skipWhitespace(json, i);
49
+ if (json.charAt(i) == '}') {
50
+ if (found != null) {
51
+ return found;
52
+ }
53
+ if (sawMatchingKey) {
54
+ throw new JSONException("No array for key: " + key);
55
+ }
56
+ throw new JSONException("No key: " + key);
57
+ }
58
+ int keyEnd = endOfJsonString(json, i);
59
+ String memberKey = parseQuotedStringToken(json, i, keyEnd);
60
+ i = skipWhitespace(json, keyEnd);
61
+ if (json.charAt(i) != ':') {
62
+ throw new JSONException("Expected ':' after object key");
63
+ }
64
+ i = skipWhitespace(json, i + 1);
65
+ if (memberKey.equals(key)) {
66
+ sawMatchingKey = true;
67
+ i = skipWhitespace(json, i);
68
+ if (i < json.length() && json.charAt(i) == '[') {
69
+ int end = indexOfMatchingBracket(json, i, '[', ']');
70
+ found = json.substring(i, end + 1);
71
+ i = end + 1;
72
+ } else {
73
+ found = null;
74
+ i = endOfJsonValue(json, i);
75
+ }
76
+ } else {
77
+ i = endOfJsonValue(json, i);
78
+ }
79
+ i = skipWhitespace(json, i);
80
+ if (i < json.length() && json.charAt(i) == ',') {
81
+ i++;
82
+ }
83
+ }
84
+ if (found != null) {
85
+ return found;
86
+ }
87
+ if (sawMatchingKey) {
88
+ throw new JSONException("No array for key: " + key);
89
+ }
90
+ throw new JSONException("No key: " + key);
91
+ }
92
+
93
+ /**
94
+ * Converts one JSON param value for {@link JSArray#put(Object)}.
95
+ *
96
+ * @return converted value, or {@code null} to skip unsupported values (e.g. nested arrays)
97
+ */
98
+ static Object convertRequestParam(Object param) {
99
+ if (param == JSONObject.NULL) {
100
+ return JSONObject.NULL;
101
+ }
102
+ if (param instanceof Boolean) {
103
+ return param;
104
+ }
105
+ if (param instanceof Number) {
106
+ return convertNumberLikeGson((Number) param);
107
+ }
108
+ if (param instanceof String) {
109
+ return param;
110
+ }
111
+ if (param instanceof JSONObject) {
112
+ JSONObject obj = (JSONObject) param;
113
+ JSONObject jsonObj = new JSONObject();
114
+ Iterator<String> keys = obj.keys();
115
+ while (keys.hasNext()) {
116
+ String key = keys.next();
117
+ try {
118
+ jsonObj.put(key, elementGetAsString(obj.get(key)));
119
+ } catch (JSONException e) {
120
+ throw new IllegalStateException(e);
121
+ }
122
+ }
123
+ return jsonObj;
124
+ }
125
+ return null;
126
+ }
127
+
128
+ static Object convertRequestParamFromRawJson(String rawElement, Object parsedFallback) {
129
+ String raw = rawElement.trim();
130
+ if (raw.startsWith("{")) {
131
+ return convertFlatObjectParamFromRawJson(raw);
132
+ }
133
+ if (raw.startsWith("[")) {
134
+ return null;
135
+ }
136
+ if (raw.equals("null")) {
137
+ return JSONObject.NULL;
138
+ }
139
+ if (raw.equals("true") || raw.equals("false")) {
140
+ return Boolean.parseBoolean(raw);
141
+ }
142
+ if (raw.startsWith("\"")) {
143
+ try {
144
+ return new JSONTokener(raw).nextValue();
145
+ } catch (JSONException e) {
146
+ throw new IllegalStateException(e);
147
+ }
148
+ }
149
+ return convertNumberLikeGsonFromLiteral(raw);
150
+ }
151
+
152
+ /** Mirrors Gson {@code JsonElement#getAsString()} for object field values. */
153
+ static String elementGetAsString(Object value) {
154
+ if (value == null || value == JSONObject.NULL) {
155
+ throw new IllegalStateException("Cannot get string from null");
156
+ }
157
+ if (value instanceof String) {
158
+ return (String) value;
159
+ }
160
+ if (value instanceof Number) {
161
+ try {
162
+ return JSONObject.numberToString((Number) value);
163
+ } catch (JSONException e) {
164
+ throw new IllegalStateException(e);
165
+ }
166
+ }
167
+ if (value instanceof Boolean) {
168
+ return value.toString();
169
+ }
170
+ if (value instanceof JSONObject || value instanceof JSONArray) {
171
+ throw new IllegalStateException("Cannot get string from non-primitive");
172
+ }
173
+ return String.valueOf(value);
174
+ }
175
+
176
+ static String elementGetAsStringFromRawToken(String token) {
177
+ String raw = token.trim();
178
+ if (raw.equals("null")) {
179
+ throw new IllegalStateException("Cannot get string from null");
180
+ }
181
+ if (raw.startsWith("{") || raw.startsWith("[")) {
182
+ throw new IllegalStateException("Cannot get string from non-primitive");
183
+ }
184
+ if (raw.startsWith("\"")) {
185
+ try {
186
+ Object value = new JSONTokener(raw).nextValue();
187
+ if (value instanceof String) {
188
+ return (String) value;
189
+ }
190
+ } catch (JSONException e) {
191
+ throw new IllegalStateException(e);
192
+ }
193
+ }
194
+ if (raw.equals("true") || raw.equals("false")) {
195
+ return raw;
196
+ }
197
+ return raw;
198
+ }
199
+
200
+ /**
201
+ * Mirrors Gson {@code JsonPrimitive#getAsLong()} with {@code getAsDouble()} fallback on
202
+ * {@link NumberFormatException}, using LazilyParsedNumber-style parsing on the number's text.
203
+ */
204
+ static Number convertNumberLikeGson(Number number) {
205
+ return convertNumberLikeGsonFromLiteral(numberToJsonNumberString(number));
206
+ }
207
+
208
+ static Number convertNumberLikeGsonFromLiteral(String literal) {
209
+ try {
210
+ return gsonStyleLongFromNumberString(literal);
211
+ } catch (NumberFormatException e) {
212
+ return gsonStyleDoubleFromNumberString(literal, Double.parseDouble(literal));
213
+ }
214
+ }
215
+
216
+ static long gsonStyleLongFromNumberString(String s) {
217
+ checkNumberStringLength(s);
218
+ try {
219
+ return Long.parseLong(s);
220
+ } catch (NumberFormatException e) {
221
+ return parseBigDecimalLikeGson(s).longValue();
222
+ }
223
+ }
224
+
225
+ static double gsonStyleDoubleFromNumberString(String s, Number number) {
226
+ try {
227
+ return Double.parseDouble(s);
228
+ } catch (NumberFormatException e) {
229
+ return number.doubleValue();
230
+ }
231
+ }
232
+
233
+ static String numberToJsonNumberString(Number number) {
234
+ if (number instanceof BigDecimal) {
235
+ return ((BigDecimal) number).toPlainString();
236
+ }
237
+ return number.toString();
238
+ }
239
+
240
+ static JSONObject convertFlatObjectParamFromRawJson(String raw) {
241
+ JSONObject out = new JSONObject();
242
+ int i = skipWhitespace(raw, 1);
243
+ while (true) {
244
+ i = skipWhitespace(raw, i);
245
+ if (raw.charAt(i) == '}') {
246
+ break;
247
+ }
248
+ int keyEnd = endOfJsonString(raw, i);
249
+ String key;
250
+ try {
251
+ key = (String) new JSONTokener(raw.substring(i, keyEnd)).nextValue();
252
+ } catch (JSONException e) {
253
+ throw new IllegalStateException(e);
254
+ }
255
+ i = skipWhitespace(raw, keyEnd);
256
+ if (raw.charAt(i) != ':') {
257
+ throw new IllegalStateException("Expected ':' after object key");
258
+ }
259
+ i = skipWhitespace(raw, i + 1);
260
+ int valueEnd = endOfJsonValue(raw, i);
261
+ String valueToken = raw.substring(i, valueEnd);
262
+ try {
263
+ out.put(key, elementGetAsStringFromRawToken(valueToken));
264
+ } catch (JSONException e) {
265
+ throw new IllegalStateException(e);
266
+ }
267
+ i = skipWhitespace(raw, valueEnd);
268
+ if (i < raw.length() && raw.charAt(i) == ',') {
269
+ i++;
270
+ }
271
+ }
272
+ return out;
273
+ }
274
+
275
+ static List<String> splitTopLevelJsonArrayElements(String arrayJson) {
276
+ String trimmed = arrayJson.trim();
277
+ if (trimmed.length() < 2 || trimmed.charAt(0) != '[') {
278
+ throw new IllegalStateException("Not a JSON array");
279
+ }
280
+ List<String> elements = new ArrayList<>();
281
+ int i = skipWhitespace(trimmed, 1);
282
+ while (true) {
283
+ i = skipWhitespace(trimmed, i);
284
+ if (trimmed.charAt(i) == ']') {
285
+ break;
286
+ }
287
+ int end = endOfJsonValue(trimmed, i);
288
+ elements.add(trimmed.substring(i, end));
289
+ i = skipWhitespace(trimmed, end);
290
+ if (trimmed.charAt(i) == ',') {
291
+ i++;
292
+ }
293
+ }
294
+ return elements;
295
+ }
296
+
297
+ static int endOfJsonValue(String s, int start) {
298
+ start = skipWhitespace(s, start);
299
+ char c = s.charAt(start);
300
+ if (c == '"') {
301
+ return endOfJsonString(s, start);
302
+ }
303
+ if (c == '{') {
304
+ return indexOfMatchingBracket(s, start, '{', '}') + 1;
305
+ }
306
+ if (c == '[') {
307
+ return indexOfMatchingBracket(s, start, '[', ']') + 1;
308
+ }
309
+ if (s.startsWith("null", start)) {
310
+ return start + 4;
311
+ }
312
+ if (s.startsWith("true", start)) {
313
+ return start + 4;
314
+ }
315
+ if (s.startsWith("false", start)) {
316
+ return start + 5;
317
+ }
318
+ int i = start;
319
+ while (i < s.length()) {
320
+ char ch = s.charAt(i);
321
+ if (ch == ',' || ch == ']' || ch == '}' || Character.isWhitespace(ch)) {
322
+ break;
323
+ }
324
+ i++;
325
+ }
326
+ return i;
327
+ }
328
+
329
+ static int endOfJsonString(String s, int start) {
330
+ if (s.charAt(start) != '"') {
331
+ throw new IllegalStateException("Expected string");
332
+ }
333
+ boolean escape = false;
334
+ for (int i = start + 1; i < s.length(); i++) {
335
+ char c = s.charAt(i);
336
+ if (escape) {
337
+ escape = false;
338
+ continue;
339
+ }
340
+ if (c == '\\') {
341
+ escape = true;
342
+ continue;
343
+ }
344
+ if (c == '"') {
345
+ return i + 1;
346
+ }
347
+ }
348
+ throw new IllegalStateException("Unterminated string");
349
+ }
350
+
351
+ static int indexOfMatchingBracket(String s, int openIdx, char open, char close) {
352
+ int depth = 0;
353
+ boolean inString = false;
354
+ boolean escape = false;
355
+ for (int i = openIdx; i < s.length(); i++) {
356
+ char c = s.charAt(i);
357
+ if (escape) {
358
+ escape = false;
359
+ continue;
360
+ }
361
+ if (inString && c == '\\') {
362
+ escape = true;
363
+ continue;
364
+ }
365
+ if (c == '"') {
366
+ inString = !inString;
367
+ continue;
368
+ }
369
+ if (inString) {
370
+ continue;
371
+ }
372
+ if (c == open) {
373
+ depth++;
374
+ } else if (c == close) {
375
+ depth--;
376
+ if (depth == 0) {
377
+ return i;
378
+ }
379
+ }
380
+ }
381
+ throw new IllegalStateException("Unbalanced brackets");
382
+ }
383
+
384
+ static int skipWhitespace(String s, int index) {
385
+ while (index < s.length() && Character.isWhitespace(s.charAt(index))) {
386
+ index++;
387
+ }
388
+ return index;
389
+ }
390
+
391
+ static String parseQuotedStringToken(String json, int start, int end) throws JSONException {
392
+ try {
393
+ return (String) new JSONTokener(json.substring(start, end)).nextValue();
394
+ } catch (JSONException e) {
395
+ throw new JSONException("Invalid object key", e);
396
+ }
397
+ }
398
+
399
+ private static void checkNumberStringLength(String s) {
400
+ if (s.length() > MAX_NUMBER_STRING_LENGTH) {
401
+ throw new NumberFormatException("Number string too large");
402
+ }
403
+ }
404
+
405
+ private static BigDecimal parseBigDecimalLikeGson(String s) {
406
+ checkNumberStringLength(s);
407
+ BigDecimal decimal = new BigDecimal(s);
408
+ if (Math.abs((long) decimal.scale()) >= 10_000) {
409
+ throw new NumberFormatException("Number has unsupported scale");
410
+ }
411
+ return decimal;
412
+ }
413
+ }
@@ -2,16 +2,12 @@ package app.capgo.capacitor.fastsql;
2
2
 
3
3
  import com.getcapacitor.JSArray;
4
4
  import com.getcapacitor.JSObject;
5
- import com.google.gson.Gson;
6
- import com.google.gson.JsonArray;
7
- import com.google.gson.JsonElement;
8
- import com.google.gson.JsonObject;
9
- import com.google.gson.JsonParser;
10
5
  import fi.iki.elonen.NanoHTTPD; // Note: org.nanohttpd:nanohttpd:2.3.1 still uses fi.iki.elonen package
11
6
  import java.io.DataInputStream;
12
7
  import java.io.IOException;
13
8
  import java.nio.charset.StandardCharsets;
14
9
  import java.security.SecureRandom;
10
+ import java.util.List;
15
11
  import java.util.Map;
16
12
  import java.util.concurrent.ExecutionException;
17
13
  import java.util.concurrent.ExecutorService;
@@ -29,7 +25,6 @@ public class SQLHTTPServer extends NanoHTTPD {
29
25
 
30
26
  private final String token;
31
27
  private final Map<String, DatabaseConnection> databases;
32
- private final Gson gson = new Gson();
33
28
  private final ExecutorService requestExecutor = Executors.newSingleThreadExecutor((runnable) -> {
34
29
  Thread thread = new Thread(runnable);
35
30
  thread.setName("FastSQL-DB");
@@ -120,37 +115,14 @@ public class SQLHTTPServer extends NanoHTTPD {
120
115
  private Response handleExecute(IHTTPSession session, DatabaseConnection db) throws Exception {
121
116
  // Read request body
122
117
  String body = readRequestBody(session);
123
- JsonObject request = JsonParser.parseString(body).getAsJsonObject();
118
+ JSONObject request = new JSONObject(body);
124
119
 
125
- String statement = request.get("statement").getAsString();
126
- JsonArray paramsJson = request.has("params") ? request.getAsJsonArray("params") : new JsonArray();
120
+ String statement = request.getString("statement");
121
+ JSONArray paramsJson = request.has("params") ? request.getJSONArray("params") : new JSONArray();
127
122
 
128
- // Convert to JSArray
129
123
  JSArray params = new JSArray();
130
- for (JsonElement param : paramsJson) {
131
- if (param.isJsonNull()) {
132
- params.put(JSONObject.NULL);
133
- } else if (param.isJsonPrimitive()) {
134
- if (param.getAsJsonPrimitive().isNumber()) {
135
- try {
136
- params.put(param.getAsLong());
137
- } catch (NumberFormatException e) {
138
- params.put(param.getAsDouble());
139
- }
140
- } else if (param.getAsJsonPrimitive().isBoolean()) {
141
- params.put(param.getAsBoolean());
142
- } else {
143
- params.put(param.getAsString());
144
- }
145
- } else if (param.isJsonObject()) {
146
- JsonObject obj = param.getAsJsonObject();
147
- JSONObject jsonObj = new JSONObject();
148
- for (String key : obj.keySet()) {
149
- jsonObj.put(key, obj.get(key).getAsString());
150
- }
151
- params.put(jsonObj);
152
- }
153
- }
124
+ String rawParamsArray = request.has("params") ? HttpRequestJsonParams.extractJsonArrayAfterKey(body, "params") : null;
125
+ HttpRequestJsonParams.populateJsArrayFromParamsJson(paramsJson, params, rawParamsArray);
154
126
 
155
127
  // Execute query
156
128
  JSObject result = db.execute(statement, params);
@@ -163,41 +135,21 @@ public class SQLHTTPServer extends NanoHTTPD {
163
135
  private Response handleBatch(IHTTPSession session, DatabaseConnection db) throws Exception {
164
136
  // Read request body
165
137
  String body = readRequestBody(session);
166
- JsonObject request = JsonParser.parseString(body).getAsJsonObject();
167
- JsonArray operations = request.getAsJsonArray("operations");
138
+ JSONObject request = new JSONObject(body);
139
+ JSONArray operations = request.getJSONArray("operations");
168
140
 
169
141
  JSONArray results = new JSONArray();
170
- for (JsonElement opElement : operations) {
171
- JsonObject operation = opElement.getAsJsonObject();
172
- String statement = operation.get("statement").getAsString();
173
- JsonArray paramsJson = operation.has("params") ? operation.getAsJsonArray("params") : new JsonArray();
142
+ String rawOperationsArray = HttpRequestJsonParams.extractJsonArrayAfterKey(body, "operations");
143
+ List<String> rawOperations = HttpRequestJsonParams.splitTopLevelJsonArrayElements(rawOperationsArray);
144
+ for (int i = 0; i < operations.length(); i++) {
145
+ JSONObject operation = operations.getJSONObject(i);
146
+ String statement = operation.getString("statement");
147
+ JSONArray paramsJson = operation.has("params") ? operation.getJSONArray("params") : new JSONArray();
174
148
 
175
- // Convert to JSArray
176
149
  JSArray params = new JSArray();
177
- for (JsonElement param : paramsJson) {
178
- if (param.isJsonNull()) {
179
- params.put(JSONObject.NULL);
180
- } else if (param.isJsonPrimitive()) {
181
- if (param.getAsJsonPrimitive().isNumber()) {
182
- try {
183
- params.put(param.getAsLong());
184
- } catch (NumberFormatException e) {
185
- params.put(param.getAsDouble());
186
- }
187
- } else if (param.getAsJsonPrimitive().isBoolean()) {
188
- params.put(param.getAsBoolean());
189
- } else {
190
- params.put(param.getAsString());
191
- }
192
- } else if (param.isJsonObject()) {
193
- JsonObject obj = param.getAsJsonObject();
194
- JSONObject jsonObj = new JSONObject();
195
- for (String key : obj.keySet()) {
196
- jsonObj.put(key, obj.get(key).getAsString());
197
- }
198
- params.put(jsonObj);
199
- }
200
- }
150
+ String rawOperation = rawOperations.get(i);
151
+ String rawParamsArray = operation.has("params") ? HttpRequestJsonParams.extractJsonArrayAfterKey(rawOperation, "params") : null;
152
+ HttpRequestJsonParams.populateJsArrayFromParamsJson(paramsJson, params, rawParamsArray);
201
153
 
202
154
  JSObject result = db.execute(statement, params);
203
155
  results.put(new JSONObject(result.toString()));
@@ -10,7 +10,7 @@ import SQLite3
10
10
  */
11
11
  @objc(CapgoCapacitorFastSqlPlugin)
12
12
  public class CapgoCapacitorFastSqlPlugin: CAPPlugin, CAPBridgedPlugin {
13
- private let pluginVersion: String = "8.2.11"
13
+ private let pluginVersion: String = "8.2.12"
14
14
  public let identifier = "CapgoCapacitorFastSqlPlugin"
15
15
  public let jsName = "CapgoCapacitorFastSql"
16
16
  public let pluginMethods: [CAPPluginMethod] = [
@@ -50,12 +50,11 @@ class SQLDatabase {
50
50
  throw SQLError.encryptionKeyMissing
51
51
  }
52
52
  #if canImport(SQLCipher)
53
- guard let keyData = key.data(using: .utf8) else {
54
- throw SQLError.encryptionFailed(message: "Invalid encryption key encoding")
55
- }
56
- let keyResult = keyData.withUnsafeBytes { bytes in
57
- sqlite3_key(db, bytes.baseAddress, Int32(keyData.count))
58
- }
53
+ // `PRAGMA key` is SQLCipher's SQL interface to sqlite3_key(). It avoids
54
+ // calling sqlite3_key() directly, which SQLCipher's headers only declare
55
+ // when SQLITE_HAS_CODEC is defined for the importing target.
56
+ let escapedKey = key.replacingOccurrences(of: "'", with: "''")
57
+ let keyResult = sqlite3_exec(db, "PRAGMA key = '\(escapedKey)'", nil, nil, nil)
59
58
  if keyResult != SQLITE_OK {
60
59
  let error = String(cString: sqlite3_errmsg(db))
61
60
  throw SQLError.encryptionFailed(message: error)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-fast-sql",
3
- "version": "8.2.11",
3
+ "version": "8.2.12",
4
4
  "description": "High-performance native SQLite plugin with custom protocol for efficient sync operations and IndexedDB replacement",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",