strata-storage 2.4.3 → 2.6.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.
Files changed (114) hide show
  1. package/AI-INTEGRATION-GUIDE.md +115 -261
  2. package/README.md +426 -182
  3. package/android/AGENTS.md +51 -0
  4. package/android/CLAUDE.md +89 -0
  5. package/android/build.gradle +1 -1
  6. package/android/src/main/java/com/strata/storage/FilesystemStorage.java +287 -0
  7. package/android/src/main/java/com/strata/storage/SQLiteStorage.java +260 -203
  8. package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +357 -69
  9. package/dist/README.md +426 -182
  10. package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
  11. package/dist/adapters/capacitor/FilesystemAdapter.js +2 -1
  12. package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
  13. package/dist/adapters/capacitor/PreferencesAdapter.js +2 -1
  14. package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
  15. package/dist/adapters/capacitor/SecureAdapter.js +2 -1
  16. package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
  17. package/dist/adapters/capacitor/SqliteAdapter.js +2 -1
  18. package/dist/adapters/web/CacheAdapter.d.ts.map +1 -1
  19. package/dist/adapters/web/CacheAdapter.js +11 -3
  20. package/dist/adapters/web/CookieAdapter.d.ts +37 -1
  21. package/dist/adapters/web/CookieAdapter.d.ts.map +1 -1
  22. package/dist/adapters/web/CookieAdapter.js +89 -9
  23. package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
  24. package/dist/adapters/web/IndexedDBAdapter.js +10 -2
  25. package/dist/adapters/web/LocalStorageAdapter.d.ts +31 -0
  26. package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -1
  27. package/dist/adapters/web/LocalStorageAdapter.js +92 -19
  28. package/dist/adapters/web/MemoryAdapter.d.ts +24 -0
  29. package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
  30. package/dist/adapters/web/MemoryAdapter.js +69 -18
  31. package/dist/adapters/web/SessionStorageAdapter.d.ts +24 -0
  32. package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
  33. package/dist/adapters/web/SessionStorageAdapter.js +71 -9
  34. package/dist/adapters/web/URLAdapter.d.ts +59 -0
  35. package/dist/adapters/web/URLAdapter.d.ts.map +1 -0
  36. package/dist/adapters/web/URLAdapter.js +234 -0
  37. package/dist/adapters/web/index.d.ts +1 -0
  38. package/dist/adapters/web/index.d.ts.map +1 -1
  39. package/dist/adapters/web/index.js +1 -0
  40. package/dist/android/AGENTS.md +51 -0
  41. package/dist/android/CLAUDE.md +89 -0
  42. package/dist/android/build.gradle +1 -1
  43. package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +287 -0
  44. package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +260 -203
  45. package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +357 -69
  46. package/dist/capacitor.d.ts.map +1 -1
  47. package/dist/capacitor.js +2 -1
  48. package/dist/core/BaseAdapter.d.ts +8 -0
  49. package/dist/core/BaseAdapter.d.ts.map +1 -1
  50. package/dist/core/BaseAdapter.js +34 -14
  51. package/dist/core/Strata.d.ts +56 -2
  52. package/dist/core/Strata.d.ts.map +1 -1
  53. package/dist/core/Strata.js +501 -53
  54. package/dist/features/encryption.d.ts.map +1 -1
  55. package/dist/features/encryption.js +3 -2
  56. package/dist/features/integrity.d.ts +16 -0
  57. package/dist/features/integrity.d.ts.map +1 -0
  58. package/dist/features/integrity.js +28 -0
  59. package/dist/features/observer.d.ts.map +1 -1
  60. package/dist/features/observer.js +2 -1
  61. package/dist/features/query.d.ts +7 -1
  62. package/dist/features/query.d.ts.map +1 -1
  63. package/dist/features/query.js +9 -2
  64. package/dist/features/sync.d.ts.map +1 -1
  65. package/dist/features/sync.js +4 -3
  66. package/dist/index.d.ts +35 -2
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +55 -30
  69. package/dist/integrations/angular/index.d.ts +158 -0
  70. package/dist/integrations/angular/index.d.ts.map +1 -0
  71. package/dist/integrations/angular/index.js +395 -0
  72. package/dist/integrations/index.d.ts +15 -0
  73. package/dist/integrations/index.d.ts.map +1 -0
  74. package/dist/integrations/index.js +18 -0
  75. package/dist/integrations/react/index.d.ts +75 -0
  76. package/dist/integrations/react/index.d.ts.map +1 -0
  77. package/dist/integrations/react/index.js +191 -0
  78. package/dist/integrations/vue/index.d.ts +103 -0
  79. package/dist/integrations/vue/index.d.ts.map +1 -0
  80. package/dist/integrations/vue/index.js +274 -0
  81. package/dist/ios/AGENTS.md +48 -0
  82. package/dist/ios/CLAUDE.md +84 -0
  83. package/dist/ios/Plugin/FilesystemStorage.swift +218 -0
  84. package/dist/ios/Plugin/KeychainStorage.swift +139 -50
  85. package/dist/ios/Plugin/SQLiteStorage.swift +279 -147
  86. package/dist/ios/Plugin/StrataStoragePlugin.m +23 -0
  87. package/dist/ios/Plugin/StrataStoragePlugin.swift +272 -65
  88. package/dist/package.json +21 -5
  89. package/dist/plugin/index.d.ts.map +1 -1
  90. package/dist/plugin/index.js +2 -1
  91. package/dist/types/index.d.ts +58 -9
  92. package/dist/types/index.d.ts.map +1 -1
  93. package/dist/types/index.js +0 -13
  94. package/dist/utils/errors.d.ts +7 -0
  95. package/dist/utils/errors.d.ts.map +1 -1
  96. package/dist/utils/errors.js +15 -3
  97. package/dist/utils/index.d.ts +63 -5
  98. package/dist/utils/index.d.ts.map +1 -1
  99. package/dist/utils/index.js +109 -16
  100. package/dist/utils/logger.d.ts +31 -0
  101. package/dist/utils/logger.d.ts.map +1 -0
  102. package/dist/utils/logger.js +63 -0
  103. package/ios/AGENTS.md +48 -0
  104. package/ios/CLAUDE.md +84 -0
  105. package/ios/Plugin/FilesystemStorage.swift +218 -0
  106. package/ios/Plugin/KeychainStorage.swift +139 -50
  107. package/ios/Plugin/SQLiteStorage.swift +279 -147
  108. package/ios/Plugin/StrataStoragePlugin.m +23 -0
  109. package/ios/Plugin/StrataStoragePlugin.swift +272 -65
  110. package/package.json +31 -20
  111. package/scripts/build.js +16 -5
  112. package/scripts/configure.js +2 -6
  113. package/scripts/postinstall.js +2 -2
  114. package/Readme.md +0 -271
@@ -1,61 +1,132 @@
1
1
  package com.stratastorage;
2
2
 
3
3
  import com.getcapacitor.JSObject;
4
+ import com.getcapacitor.JSArray;
4
5
  import com.getcapacitor.Plugin;
5
6
  import com.getcapacitor.PluginCall;
6
7
  import com.getcapacitor.PluginMethod;
7
8
  import com.getcapacitor.annotation.CapacitorPlugin;
8
9
  import com.strata.storage.SharedPreferencesStorage;
9
10
  import com.strata.storage.EncryptedStorage;
11
+ import com.strata.storage.FilesystemStorage;
10
12
  import com.strata.storage.SQLiteStorage;
11
13
  import android.util.Log;
12
14
  import org.json.JSONArray;
13
- import org.json.JSONException;
15
+ import java.util.HashMap;
14
16
  import java.util.List;
17
+ import java.util.Map;
15
18
 
16
19
  /**
17
20
  * Main Capacitor plugin for Strata Storage
18
- * Coordinates between different storage types on Android
21
+ * Coordinates between different storage types on Android.
22
+ *
23
+ * Storage types with a real native backend: preferences, secure, sqlite,
24
+ * filesystem.
25
+ *
26
+ * SQLite supports multi-store: the `database` option selects the DB file and
27
+ * `table` selects the table within it. SQLite helper instances are cached per
28
+ * database name so the connection is not reopened per call.
19
29
  */
20
30
  @CapacitorPlugin(name = "StrataStorage")
21
31
  public class StrataStoragePlugin extends Plugin {
32
+ private static final String DEFAULT_DATABASE = "strata_storage";
33
+ private static final String DEFAULT_TABLE = "storage";
34
+
22
35
  private SharedPreferencesStorage sharedPrefsStorage;
23
36
  private EncryptedStorage encryptedStorage;
24
- private SQLiteStorage sqliteStorage;
37
+ private FilesystemStorage filesystemStorage;
38
+
39
+ /** SQLite helper cache keyed by resolved database name (without ".db"). */
40
+ private final Map<String, SQLiteStorage> sqliteStores = new HashMap<>();
25
41
 
26
42
  @Override
27
43
  public void load() {
28
44
  try {
29
45
  sharedPrefsStorage = new SharedPreferencesStorage(getContext());
46
+ } catch (Exception e) {
47
+ Log.e("StrataStorage", "Failed to initialize preferences storage", e);
48
+ }
49
+ try {
30
50
  encryptedStorage = new EncryptedStorage(getContext());
31
- sqliteStorage = new SQLiteStorage(getContext());
32
51
  } catch (Exception e) {
33
- // Log error but don't crash - some storage types may not be available
34
- Log.e("StrataStorage", "Failed to initialize storage", e);
52
+ // Secure storage may be unavailable on some devices/keystores
53
+ // keep the rest of the plugin functional.
54
+ Log.e("StrataStorage", "Failed to initialize encrypted storage", e);
55
+ }
56
+ try {
57
+ filesystemStorage = new FilesystemStorage(getContext());
58
+ } catch (Exception e) {
59
+ Log.e("StrataStorage", "Failed to initialize filesystem storage", e);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Sanitize a database name to a safe file stem ({@code ^[A-Za-z0-9_]+$})
65
+ * and return the resolved stem. The ".db" suffix is appended when opening.
66
+ */
67
+ private static String sanitizeDatabase(String database) {
68
+ if (database == null || database.isEmpty()) {
69
+ return DEFAULT_DATABASE;
70
+ }
71
+ String cleaned = database.replaceAll("[^A-Za-z0-9_]", "");
72
+ return cleaned.isEmpty() ? DEFAULT_DATABASE : cleaned;
73
+ }
74
+
75
+ /**
76
+ * Return (creating + caching on first use) the SQLite helper for the given
77
+ * database name. The cache is keyed by the sanitized DB stem so the
78
+ * connection is reused across bridge calls. May return {@code null} if the
79
+ * helper cannot be constructed.
80
+ */
81
+ private synchronized SQLiteStorage getSqliteStore(String database) {
82
+ String dbStem = sanitizeDatabase(database);
83
+ SQLiteStorage store = sqliteStores.get(dbStem);
84
+ if (store == null) {
85
+ try {
86
+ store = new SQLiteStorage(getContext(), dbStem + ".db");
87
+ sqliteStores.put(dbStem, store);
88
+ } catch (Exception e) {
89
+ Log.e("StrataStorage", "Failed to open SQLite database " + dbStem, e);
90
+ return null;
91
+ }
35
92
  }
93
+ return store;
36
94
  }
37
95
 
38
96
  /**
39
- * Check if storage is available
97
+ * Check if a specific storage type is available.
98
+ * Matches the JS contract: resolves { available: boolean }.
40
99
  */
41
100
  @PluginMethod
42
101
  public void isAvailable(PluginCall call) {
102
+ String storage = call.getString("storage", "preferences");
103
+ boolean available;
104
+ switch (storage) {
105
+ case "secure":
106
+ available = encryptedStorage != null;
107
+ break;
108
+ case "sqlite":
109
+ available = getSqliteStore(call.getString("database", DEFAULT_DATABASE)) != null;
110
+ break;
111
+ case "filesystem":
112
+ available = filesystemStorage != null;
113
+ break;
114
+ case "preferences":
115
+ default:
116
+ available = sharedPrefsStorage != null;
117
+ break;
118
+ }
119
+
43
120
  JSObject result = new JSObject();
44
- result.put("available", true);
45
- result.put("platform", "android");
46
-
47
- JSObject adapters = new JSObject();
48
- adapters.put("preferences", true);
49
- adapters.put("secure", true);
50
- adapters.put("sqlite", true);
51
- adapters.put("filesystem", true);
52
- result.put("adapters", adapters);
53
-
121
+ result.put("available", available);
54
122
  call.resolve(result);
55
123
  }
56
124
 
57
125
  /**
58
- * Get value from storage
126
+ * Get value from storage.
127
+ *
128
+ * For sqlite/filesystem the resolved `value` is the full wrapper object
129
+ * (parsed back from JSON). A miss resolves `value` = null.
59
130
  */
60
131
  @PluginMethod
61
132
  public void get(PluginCall call) {
@@ -66,45 +137,72 @@ public class StrataStoragePlugin extends Plugin {
66
137
  }
67
138
 
68
139
  String storage = call.getString("storage", "preferences");
69
-
70
- try {
71
- Object value = null;
72
140
 
141
+ try {
73
142
  switch (storage) {
74
- case "secure":
143
+ case "secure": {
75
144
  if (encryptedStorage == null) {
76
145
  call.reject("Encrypted storage not available");
77
146
  return;
78
147
  }
79
- value = encryptedStorage.get(key);
80
- break;
81
- case "sqlite":
82
- if (sqliteStorage == null) {
148
+ resolveValue(call, encryptedStorage.get(key));
149
+ return;
150
+ }
151
+ case "sqlite": {
152
+ SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
153
+ if (store == null) {
83
154
  call.reject("SQLite storage not available");
84
155
  return;
85
156
  }
86
- value = sqliteStorage.get(key);
87
- break;
157
+ String table = call.getString("table", DEFAULT_TABLE);
158
+ resolveValue(call, store.get(table, key));
159
+ return;
160
+ }
161
+ case "filesystem": {
162
+ if (filesystemStorage == null) {
163
+ call.reject("Filesystem storage not available");
164
+ return;
165
+ }
166
+ resolveValue(call, filesystemStorage.get(key));
167
+ return;
168
+ }
88
169
  case "preferences":
89
- default:
170
+ default: {
90
171
  if (sharedPrefsStorage == null) {
91
172
  call.reject("Preferences storage not available");
92
173
  return;
93
174
  }
94
- value = sharedPrefsStorage.get(key);
95
- break;
175
+ resolveValue(call, sharedPrefsStorage.get(key));
176
+ return;
177
+ }
96
178
  }
97
-
98
- JSObject result = new JSObject();
99
- result.put("value", value);
100
- call.resolve(result);
101
179
  } catch (Exception e) {
102
180
  call.reject("Failed to get value", e);
103
181
  }
104
182
  }
105
183
 
106
184
  /**
107
- * Set value in storage
185
+ * Resolve a get() call, mapping a Java {@code null} to a JS {@code null}
186
+ * value so the TS adapters treat it as a miss.
187
+ */
188
+ private void resolveValue(PluginCall call, Object value) {
189
+ JSObject result = new JSObject();
190
+ if (value == null) {
191
+ result.put("value", JSObject.NULL);
192
+ } else {
193
+ result.put("value", value);
194
+ }
195
+ call.resolve(result);
196
+ }
197
+
198
+ /**
199
+ * Set value in storage.
200
+ *
201
+ * For sqlite/filesystem the `value` is the FULL wrapper object
202
+ * ({ value, created, updated, expires?, tags?, metadata? }); it is read via
203
+ * call.getObject("value") and stored verbatim (with TTL/query columns
204
+ * extracted for sqlite). For preferences/secure the raw value is forwarded
205
+ * unchanged.
108
206
  */
109
207
  @PluginMethod
110
208
  public void set(PluginCall call) {
@@ -114,35 +212,59 @@ public class StrataStoragePlugin extends Plugin {
114
212
  return;
115
213
  }
116
214
 
117
- Object value = call.getData().opt("value");
118
215
  String storage = call.getString("storage", "preferences");
119
-
216
+
120
217
  try {
218
+ boolean ok;
121
219
  switch (storage) {
122
220
  case "secure":
123
221
  if (encryptedStorage == null) {
124
222
  call.reject("Encrypted storage not available");
125
223
  return;
126
224
  }
127
- encryptedStorage.set(key, value);
225
+ ok = encryptedStorage.set(key, call.getData().opt("value"));
128
226
  break;
129
- case "sqlite":
130
- if (sqliteStorage == null) {
227
+ case "sqlite": {
228
+ SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
229
+ if (store == null) {
131
230
  call.reject("SQLite storage not available");
132
231
  return;
133
232
  }
134
- sqliteStorage.set(key, value);
233
+ JSObject wrapper = call.getObject("value");
234
+ if (wrapper == null) {
235
+ call.reject("A wrapper object 'value' is required for sqlite storage");
236
+ return;
237
+ }
238
+ ok = store.set(call.getString("table", DEFAULT_TABLE), key, wrapper);
135
239
  break;
240
+ }
241
+ case "filesystem": {
242
+ if (filesystemStorage == null) {
243
+ call.reject("Filesystem storage not available");
244
+ return;
245
+ }
246
+ JSObject wrapper = call.getObject("value");
247
+ if (wrapper == null) {
248
+ call.reject("A wrapper object 'value' is required for filesystem storage");
249
+ return;
250
+ }
251
+ ok = filesystemStorage.set(key, wrapper);
252
+ break;
253
+ }
136
254
  case "preferences":
137
255
  default:
138
256
  if (sharedPrefsStorage == null) {
139
257
  call.reject("Preferences storage not available");
140
258
  return;
141
259
  }
142
- sharedPrefsStorage.set(key, value);
260
+ ok = sharedPrefsStorage.set(key, call.getData().opt("value"));
143
261
  break;
144
262
  }
145
263
 
264
+ if (!ok) {
265
+ call.reject("Failed to persist value to " + storage + " storage");
266
+ return;
267
+ }
146
268
  call.resolve();
147
269
  } catch (Exception e) {
148
270
  call.reject("Failed to set value", e);
@@ -161,7 +283,7 @@ public class StrataStoragePlugin extends Plugin {
161
283
  }
162
284
 
163
285
  String storage = call.getString("storage", "preferences");
164
-
286
+
165
287
  try {
166
288
  switch (storage) {
167
289
  case "secure":
@@ -171,12 +293,21 @@ public class StrataStoragePlugin extends Plugin {
171
293
  }
172
294
  encryptedStorage.remove(key);
173
295
  break;
174
- case "sqlite":
175
- if (sqliteStorage == null) {
296
+ case "sqlite": {
297
+ SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
298
+ if (store == null) {
176
299
  call.reject("SQLite storage not available");
177
300
  return;
178
301
  }
179
- sqliteStorage.remove(key);
302
+ store.remove(call.getString("table", DEFAULT_TABLE), key);
303
+ break;
304
+ }
305
+ case "filesystem":
306
+ if (filesystemStorage == null) {
307
+ call.reject("Filesystem storage not available");
308
+ return;
309
+ }
310
+ filesystemStorage.remove(key);
180
311
  break;
181
312
  case "preferences":
182
313
  default:
@@ -195,13 +326,17 @@ public class StrataStoragePlugin extends Plugin {
195
326
  }
196
327
 
197
328
  /**
198
- * Clear storage
329
+ * Clear storage.
330
+ * Accepts the JS `pattern` (used as a key prefix) and a legacy `prefix`.
199
331
  */
200
332
  @PluginMethod
201
333
  public void clear(PluginCall call) {
202
334
  String storage = call.getString("storage", "preferences");
203
- String prefix = call.getString("prefix");
204
-
335
+ String prefix = call.getString("pattern");
336
+ if (prefix == null) {
337
+ prefix = call.getString("prefix");
338
+ }
339
+
205
340
  try {
206
341
  switch (storage) {
207
342
  case "secure":
@@ -211,12 +346,21 @@ public class StrataStoragePlugin extends Plugin {
211
346
  }
212
347
  encryptedStorage.clear(prefix);
213
348
  break;
214
- case "sqlite":
215
- if (sqliteStorage == null) {
349
+ case "sqlite": {
350
+ SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
351
+ if (store == null) {
216
352
  call.reject("SQLite storage not available");
217
353
  return;
218
354
  }
219
- sqliteStorage.clear(prefix);
355
+ store.clear(call.getString("table", DEFAULT_TABLE), prefix);
356
+ break;
357
+ }
358
+ case "filesystem":
359
+ if (filesystemStorage == null) {
360
+ call.reject("Filesystem storage not available");
361
+ return;
362
+ }
363
+ filesystemStorage.clear(prefix);
220
364
  break;
221
365
  case "preferences":
222
366
  default:
@@ -241,9 +385,9 @@ public class StrataStoragePlugin extends Plugin {
241
385
  public void keys(PluginCall call) {
242
386
  String storage = call.getString("storage", "preferences");
243
387
  String pattern = call.getString("pattern");
244
-
388
+
245
389
  try {
246
- List<String> keys = null;
390
+ List<String> keys;
247
391
 
248
392
  switch (storage) {
249
393
  case "secure":
@@ -253,12 +397,21 @@ public class StrataStoragePlugin extends Plugin {
253
397
  }
254
398
  keys = encryptedStorage.keys(pattern);
255
399
  break;
256
- case "sqlite":
257
- if (sqliteStorage == null) {
400
+ case "sqlite": {
401
+ SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
402
+ if (store == null) {
258
403
  call.reject("SQLite storage not available");
259
404
  return;
260
405
  }
261
- keys = sqliteStorage.keys(pattern);
406
+ keys = store.keys(call.getString("table", DEFAULT_TABLE), pattern);
407
+ break;
408
+ }
409
+ case "filesystem":
410
+ if (filesystemStorage == null) {
411
+ call.reject("Filesystem storage not available");
412
+ return;
413
+ }
414
+ keys = filesystemStorage.keys(pattern);
262
415
  break;
263
416
  case "preferences":
264
417
  default:
@@ -279,17 +432,21 @@ public class StrataStoragePlugin extends Plugin {
279
432
  }
280
433
 
281
434
  /**
282
- * Get storage size information
435
+ * Get storage size information.
436
+ *
437
+ * When `detailed` is true the result also carries a byte breakdown
438
+ * { detailed: { keys, values, metadata } }.
283
439
  */
284
440
  @PluginMethod
285
441
  public void size(PluginCall call) {
286
442
  String storage = call.getString("storage", "preferences");
287
-
443
+ boolean detailed = Boolean.TRUE.equals(call.getBoolean("detailed", false));
444
+
288
445
  try {
289
446
  JSObject result = new JSObject();
290
447
 
291
448
  switch (storage) {
292
- case "secure":
449
+ case "secure": {
293
450
  if (encryptedStorage == null) {
294
451
  call.reject("Encrypted storage not available");
295
452
  return;
@@ -298,17 +455,29 @@ public class StrataStoragePlugin extends Plugin {
298
455
  result.put("total", encryptedSizeInfo.total);
299
456
  result.put("count", encryptedSizeInfo.count);
300
457
  break;
301
- case "sqlite":
302
- if (sqliteStorage == null) {
458
+ }
459
+ case "sqlite": {
460
+ SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
461
+ if (store == null) {
303
462
  call.reject("SQLite storage not available");
304
463
  return;
305
464
  }
306
- SQLiteStorage.SizeInfo sqliteSizeInfo = sqliteStorage.size();
307
- result.put("total", sqliteSizeInfo.total);
308
- result.put("count", sqliteSizeInfo.count);
465
+ SQLiteStorage.SizeInfo sqliteSizeInfo =
466
+ store.size(call.getString("table", DEFAULT_TABLE), detailed);
467
+ putSizeInfo(result, sqliteSizeInfo);
309
468
  break;
469
+ }
470
+ case "filesystem": {
471
+ if (filesystemStorage == null) {
472
+ call.reject("Filesystem storage not available");
473
+ return;
474
+ }
475
+ SQLiteStorage.SizeInfo fsSizeInfo = filesystemStorage.size(detailed);
476
+ putSizeInfo(result, fsSizeInfo);
477
+ break;
478
+ }
310
479
  case "preferences":
311
- default:
480
+ default: {
312
481
  if (sharedPrefsStorage == null) {
313
482
  call.reject("Preferences storage not available");
314
483
  return;
@@ -317,6 +486,7 @@ public class StrataStoragePlugin extends Plugin {
317
486
  result.put("total", prefsSizeInfo.total);
318
487
  result.put("count", prefsSizeInfo.count);
319
488
  break;
489
+ }
320
490
  }
321
491
 
322
492
  call.resolve(result);
@@ -324,5 +494,123 @@ public class StrataStoragePlugin extends Plugin {
324
494
  call.reject("Failed to get size", e);
325
495
  }
326
496
  }
327
-
328
- }
497
+
498
+ /**
499
+ * Populate a size result, including the detailed byte breakdown when the
500
+ * {@link SQLiteStorage.SizeInfo} carries one.
501
+ */
502
+ private void putSizeInfo(JSObject result, SQLiteStorage.SizeInfo info) {
503
+ result.put("total", info.total);
504
+ result.put("count", info.count);
505
+ if (info.detailed) {
506
+ JSObject breakdown = new JSObject();
507
+ breakdown.put("keys", info.keysBytes);
508
+ breakdown.put("values", info.valuesBytes);
509
+ breakdown.put("metadata", info.metadataBytes);
510
+ result.put("detailed", breakdown);
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Query SQLite-backed storage.
516
+ * Matches the optional `query` method in the JS contract:
517
+ * resolves { results: [{ key }] }. The JS SqliteAdapter applies the real
518
+ * condition by re-fetching/filtering, so this enumerates candidate keys.
519
+ */
520
+ @PluginMethod
521
+ public void query(PluginCall call) {
522
+ String storage = call.getString("storage", "sqlite");
523
+ if (!"sqlite".equals(storage)) {
524
+ call.reject("Query is only supported for the 'sqlite' storage type");
525
+ return;
526
+ }
527
+ SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
528
+ if (store == null) {
529
+ call.reject("SQLite storage not available");
530
+ return;
531
+ }
532
+
533
+ try {
534
+ List<Map<String, Object>> rows = store.query(call.getString("table", DEFAULT_TABLE));
535
+ JSArray results = new JSArray();
536
+ for (Map<String, Object> row : rows) {
537
+ JSObject obj = new JSObject();
538
+ obj.put("key", row.get("key"));
539
+ results.put(obj);
540
+ }
541
+ JSObject result = new JSObject();
542
+ result.put("results", results);
543
+ call.resolve(result);
544
+ } catch (Exception e) {
545
+ call.reject("Failed to query SQLite", e);
546
+ }
547
+ }
548
+
549
+ /**
550
+ * Android-specific: read an encrypted preference.
551
+ * Resolves { value: unknown }. Honors an optional `fileName`.
552
+ */
553
+ @PluginMethod
554
+ public void getEncryptedPreference(PluginCall call) {
555
+ String key = call.getString("key");
556
+ if (key == null) {
557
+ call.reject("Key is required");
558
+ return;
559
+ }
560
+ String fileName = call.getString("fileName");
561
+
562
+ try {
563
+ EncryptedStorage store = resolveEncryptedStore(fileName);
564
+ if (store == null) {
565
+ call.reject("Encrypted storage not available");
566
+ return;
567
+ }
568
+ JSObject result = new JSObject();
569
+ result.put("value", store.get(key));
570
+ call.resolve(result);
571
+ } catch (Exception e) {
572
+ call.reject("Failed to read encrypted preference", e);
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Android-specific: write an encrypted preference. Honors an optional
578
+ * `fileName`.
579
+ */
580
+ @PluginMethod
581
+ public void setEncryptedPreference(PluginCall call) {
582
+ String key = call.getString("key");
583
+ if (key == null) {
584
+ call.reject("Key is required");
585
+ return;
586
+ }
587
+ Object value = call.getData().opt("value");
588
+ String fileName = call.getString("fileName");
589
+
590
+ try {
591
+ EncryptedStorage store = resolveEncryptedStore(fileName);
592
+ if (store == null) {
593
+ call.reject("Encrypted storage not available");
594
+ return;
595
+ }
596
+ if (!store.set(key, value)) {
597
+ call.reject("Failed to persist encrypted preference");
598
+ return;
599
+ }
600
+ call.resolve();
601
+ } catch (Exception e) {
602
+ call.reject("Failed to write encrypted preference", e);
603
+ }
604
+ }
605
+
606
+ /**
607
+ * Returns the default encrypted store, or builds a file-specific one when a
608
+ * custom file name is supplied.
609
+ */
610
+ private EncryptedStorage resolveEncryptedStore(String fileName) throws Exception {
611
+ if (fileName == null || fileName.isEmpty()) {
612
+ return encryptedStorage;
613
+ }
614
+ return new EncryptedStorage(getContext(), fileName);
615
+ }
616
+ }