strata-storage 2.6.1 → 2.7.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 (130) hide show
  1. package/AI-INTEGRATION-GUIDE.md +88 -10
  2. package/README.md +42 -64
  3. package/android/AGENTS.md +18 -3
  4. package/android/CLAUDE.md +36 -3
  5. package/android/build.gradle +5 -1
  6. package/android/src/main/java/com/strata/storage/EncryptedStorage.java +31 -18
  7. package/android/src/main/java/com/strata/storage/FilesystemStorage.java +62 -10
  8. package/android/src/main/java/com/strata/storage/SQLiteStorage.java +65 -13
  9. package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +63 -51
  10. package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +80 -11
  11. package/dist/README.md +42 -64
  12. package/dist/adapters/capacitor/FilesystemAdapter.d.ts +2 -2
  13. package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
  14. package/dist/adapters/capacitor/FilesystemAdapter.js +6 -2
  15. package/dist/adapters/capacitor/PreferencesAdapter.d.ts +2 -2
  16. package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
  17. package/dist/adapters/capacitor/PreferencesAdapter.js +6 -2
  18. package/dist/adapters/capacitor/SecureAdapter.d.ts +3 -3
  19. package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
  20. package/dist/adapters/capacitor/SecureAdapter.js +6 -2
  21. package/dist/adapters/capacitor/SqliteAdapter.d.ts +24 -3
  22. package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
  23. package/dist/adapters/capacitor/SqliteAdapter.js +105 -23
  24. package/dist/adapters/capacitor/index.d.ts +4 -4
  25. package/dist/adapters/web/CacheAdapter.d.ts +2 -2
  26. package/dist/adapters/web/CookieAdapter.d.ts +2 -2
  27. package/dist/adapters/web/IndexedDBAdapter.d.ts +10 -3
  28. package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
  29. package/dist/adapters/web/IndexedDBAdapter.js +14 -3
  30. package/dist/adapters/web/LocalStorageAdapter.d.ts +2 -2
  31. package/dist/adapters/web/MemoryAdapter.d.ts +2 -2
  32. package/dist/adapters/web/SessionStorageAdapter.d.ts +10 -10
  33. package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
  34. package/dist/adapters/web/SessionStorageAdapter.js +3 -2
  35. package/dist/adapters/web/URLAdapter.d.ts +2 -2
  36. package/dist/adapters/web/index.d.ts +7 -7
  37. package/dist/capacitor.d.ts +6 -6
  38. package/dist/capacitor.d.ts.map +1 -1
  39. package/dist/capacitor.js +5 -4
  40. package/dist/core/AdapterRegistry.d.ts +1 -1
  41. package/dist/core/BaseAdapter.d.ts +21 -5
  42. package/dist/core/BaseAdapter.d.ts.map +1 -1
  43. package/dist/core/BaseAdapter.js +29 -1
  44. package/dist/core/Strata.d.ts +41 -7
  45. package/dist/core/Strata.d.ts.map +1 -1
  46. package/dist/core/Strata.js +476 -118
  47. package/dist/features/compression/index.d.ts +1 -1
  48. package/dist/features/compression.d.ts +5 -1
  49. package/dist/features/compression.d.ts.map +1 -1
  50. package/dist/features/encryption/index.d.ts +1 -1
  51. package/dist/features/encryption.d.ts +55 -2
  52. package/dist/features/encryption.d.ts.map +1 -1
  53. package/dist/features/encryption.js +139 -26
  54. package/dist/features/migration.d.ts +1 -1
  55. package/dist/features/observer/index.d.ts +1 -1
  56. package/dist/features/observer.d.ts +1 -1
  57. package/dist/features/query/index.d.ts +1 -1
  58. package/dist/features/query.d.ts +1 -1
  59. package/dist/features/query.d.ts.map +1 -1
  60. package/dist/features/query.js +3 -1
  61. package/dist/features/sync/index.d.ts +1 -1
  62. package/dist/features/sync.d.ts +19 -3
  63. package/dist/features/sync.d.ts.map +1 -1
  64. package/dist/features/sync.js +57 -9
  65. package/dist/features/ttl/index.d.ts +1 -1
  66. package/dist/features/ttl.d.ts +2 -2
  67. package/dist/firebase.d.ts +1 -1
  68. package/dist/firebase.d.ts.map +1 -1
  69. package/dist/firebase.js +34 -9
  70. package/dist/index.d.ts +24 -22
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +6 -1
  73. package/dist/integrations/angular/index.d.ts +2 -2
  74. package/dist/integrations/angular/index.d.ts.map +1 -1
  75. package/dist/integrations/angular/index.js +7 -8
  76. package/dist/integrations/react/index.d.ts +3 -3
  77. package/dist/integrations/react/index.d.ts.map +1 -1
  78. package/dist/integrations/react/index.js +16 -4
  79. package/dist/integrations/vue/index.d.ts +2 -2
  80. package/dist/integrations/vue/index.d.ts.map +1 -1
  81. package/dist/integrations/vue/index.js +5 -2
  82. package/dist/package.json +11 -2
  83. package/dist/plugin/definitions.d.ts +29 -3
  84. package/dist/plugin/definitions.d.ts.map +1 -1
  85. package/dist/plugin/index.d.ts +2 -2
  86. package/dist/plugin/index.d.ts.map +1 -1
  87. package/dist/plugin/index.js +3 -0
  88. package/dist/plugin/web.d.ts +11 -2
  89. package/dist/plugin/web.d.ts.map +1 -1
  90. package/dist/plugin/web.js +23 -4
  91. package/dist/types/index.d.ts +50 -20
  92. package/dist/types/index.d.ts.map +1 -1
  93. package/dist/types/index.js +1 -10
  94. package/dist/utils/index.d.ts +5 -0
  95. package/dist/utils/index.d.ts.map +1 -1
  96. package/dist/utils/index.js +7 -0
  97. package/ios/AGENTS.md +14 -3
  98. package/ios/CLAUDE.md +25 -3
  99. package/ios/Plugin/FilesystemStorage.swift +19 -0
  100. package/ios/Plugin/SQLiteStorage.swift +59 -9
  101. package/ios/Plugin/StrataStoragePlugin.m +1 -0
  102. package/ios/Plugin/StrataStoragePlugin.swift +87 -34
  103. package/ios/Plugin/UserDefaultsStorage.swift +47 -46
  104. package/package.json +27 -24
  105. package/scripts/build.js +16 -14
  106. package/scripts/cli.js +1 -1
  107. package/scripts/configure.js +2 -2
  108. package/scripts/postinstall.js +3 -2
  109. package/dist/android/AGENTS.md +0 -51
  110. package/dist/android/CLAUDE.md +0 -89
  111. package/dist/android/build.gradle +0 -58
  112. package/dist/android/proguard-rules.pro +0 -11
  113. package/dist/android/settings.gradle +0 -2
  114. package/dist/android/src/main/AndroidManifest.xml +0 -2
  115. package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +0 -218
  116. package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +0 -287
  117. package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +0 -371
  118. package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +0 -209
  119. package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +0 -616
  120. package/dist/core/StorageStrategy.d.ts +0 -55
  121. package/dist/core/StorageStrategy.d.ts.map +0 -1
  122. package/dist/core/StorageStrategy.js +0 -200
  123. package/dist/ios/AGENTS.md +0 -48
  124. package/dist/ios/CLAUDE.md +0 -84
  125. package/dist/ios/Plugin/FilesystemStorage.swift +0 -218
  126. package/dist/ios/Plugin/KeychainStorage.swift +0 -233
  127. package/dist/ios/Plugin/SQLiteStorage.swift +0 -426
  128. package/dist/ios/Plugin/StrataStoragePlugin.m +0 -23
  129. package/dist/ios/Plugin/StrataStoragePlugin.swift +0 -411
  130. package/dist/ios/Plugin/UserDefaultsStorage.swift +0 -87
@@ -1,6 +1,6 @@
1
1
  # AI Integration Guide - strata-storage
2
2
 
3
- Quick reference for AI development agents (Claude Code, Cursor, Copilot, etc.) to integrate `strata-storage` into web and mobile projects. Current version: **2.6.1**.
3
+ Quick reference for AI development agents (Claude Code, Cursor, Copilot, etc.) to integrate `strata-storage` into web and mobile projects. Current version: **2.7.0**.
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,6 +18,36 @@ Zero runtime dependencies. React, Vue, Angular, and `@capacitor/core` are option
18
18
  - **One API, many backends** — `storage` types: `memory`, `localStorage`, `sessionStorage`, `indexedDB`, `cookies`, `cache`, `url`, plus native `preferences`, `secure`, `sqlite`, `filesystem` (Capacitor).
19
19
  - **Opt-in features** — encryption, compression, TTL, queries, cross-tab sync, integrity, durable writes, mirroring, snapshots. All off by default.
20
20
 
21
+ ## Entry points (import map)
22
+
23
+ | Import | Use for |
24
+ |--------|---------|
25
+ | `strata-storage` | Core API + all web adapters (`memory`, `localStorage`, `sessionStorage`, `indexedDB`, `cookies`, `cache`, `url`). |
26
+ | `strata-storage/capacitor` | Native mobile adapters: `PreferencesAdapter`, `SecureAdapter`, `SqliteAdapter`, `FilesystemAdapter`. |
27
+ | `strata-storage/firebase` | `enableFirebaseSync`, `isFirebaseAvailable` — cross-device Firestore / Realtime DB sync. `firebase` is an optional peer dep. |
28
+ | `strata-storage/react` | `createStrataHooks` (provider-free) + `<StrataProvider>`. |
29
+ | `strata-storage/vue` | `createStrataComposables` + `StrataPlugin`. |
30
+ | `strata-storage/angular` | `provideStrata` / `StrataService` (RxJS). |
31
+
32
+ ## Choosing a backend (decision guide)
33
+
34
+ Pick the `storage` per the host project's needs:
35
+
36
+ - **Web, small & persistent** → `localStorage` (sync-capable).
37
+ - **Web, large / structured / queryable** → `indexedDB`.
38
+ - **Web, offline assets / big blobs** → `cache`.
39
+ - **Shareable UI state in the URL** → `url`.
40
+ - **Server-readable small data** → `cookies`.
41
+ - **Temporary / tests** → `memory`.
42
+ - **Mobile (Capacitor), sensitive** → `secure` (Keychain/Keystore).
43
+ - **Mobile, structured / multi-store** → `sqlite` (isolated `(database, table)`).
44
+ - **Mobile, document-shaped values** → `filesystem`.
45
+ - **Mobile, simple settings** → `preferences`.
46
+ - **Cross-device / multi-user sync** → `firestore` or `realtime` (Firebase).
47
+
48
+ Layer features on top regardless of backend: `encrypt`, `compress`, `ttl`,
49
+ `tags`+`query`, cross-tab `sync`, `integrity`/`durable`/`mirror`.
50
+
21
51
  ## Quick Start
22
52
 
23
53
  ### Default instance (simplest)
@@ -162,14 +192,14 @@ const storage = defineStorage();
162
192
  import { defineStorage } from 'strata-storage';
163
193
  import {
164
194
  PreferencesAdapter,
165
- SecureStorageAdapter,
195
+ SecureAdapter,
166
196
  SqliteAdapter,
167
197
  FilesystemAdapter,
168
198
  } from 'strata-storage/capacitor';
169
199
 
170
200
  const storage = defineStorage();
171
201
  storage.registerAdapter(new PreferencesAdapter());
172
- storage.registerAdapter(new SecureStorageAdapter());
202
+ storage.registerAdapter(new SecureAdapter());
173
203
  storage.registerAdapter(new SqliteAdapter()); // 2.6.0 — multi-store (database + table)
174
204
  storage.registerAdapter(new FilesystemAdapter()); // 2.6.0 — file-per-key, atomic writes
175
205
  await storage.set('secret', token, { storage: 'secure' });
@@ -177,7 +207,51 @@ await storage.set('secret', token, { storage: 'secure' });
177
207
 
178
208
  **SQLite multi-store** (2.6.0): each `new SqliteAdapter({ database, table })` is an isolated store — distinct `(database, table)` pairs map to distinct physical `.db` files / tables natively and cannot collide. **Filesystem** (2.6.0): one JSON file per key under the app's documents/files directory; `isAvailable()` now returns `true` on iOS and Android.
179
209
 
180
- Native adapters depend on the downstream Capacitor project setup; follow `docs/guides/platforms/device-verification.md` to verify on a real iOS + Android device.
210
+ Native adapters depend on the downstream Capacitor project setup; follow the [device-verification guide](https://stratastorage-docs.aoneahsan.com/guides/platforms/device-verification) to verify on a real iOS + Android device.
211
+
212
+ ### Firebase cross-device sync (optional)
213
+
214
+ `firebase` is an optional peer dependency, loaded dynamically only when used.
215
+
216
+ ```typescript
217
+ import { enableFirebaseSync, isFirebaseAvailable } from 'strata-storage/firebase';
218
+
219
+ await enableFirebaseSync(storage, {
220
+ apiKey: '…', authDomain: '…', projectId: '…', appId: '…',
221
+ firestore: true, // registers the `firestore` adapter
222
+ realtimeDatabase: true, // registers the `realtime` adapter
223
+ collectionName: 'strata-storage', // optional (default)
224
+ });
225
+
226
+ await storage.set('profile', data, { storage: 'firestore' });
227
+ storage.subscribe((c) => { if (c.source === 'remote') apply(c); }, { storage: 'firestore' });
228
+ ```
229
+
230
+ `firestore` is observable + queryable; `realtime` is observable. **Neither is
231
+ encrypted at rest by Strata** — combine with `encrypt: true` and/or Firebase
232
+ Security Rules for confidentiality. `firestore.size()` returns `{ total: 0, count: 0 }`.
233
+ Note: `'firestore'`/`'realtime'` are runtime adapter names (not in the `StorageType`
234
+ union), so strict TS may need a cast on the per-call options object.
235
+
236
+ ## Encryption & security (2.7.0)
237
+
238
+ ```typescript
239
+ const storage = defineStorage({
240
+ encryption: {
241
+ enabled: true,
242
+ password: 'user-derived-password',
243
+ algorithm: 'AES-GCM', // default; or 'AES-CBC' (authenticated via Encrypt-then-MAC)
244
+ iterations: 600000, // PBKDF2 default (OWASP); stored per-record, used on decrypt
245
+ },
246
+ });
247
+ ```
248
+
249
+ - **`AES-GCM`** (default) is AEAD. **`AES-CBC`** is authenticated with
250
+ Encrypt-then-MAC (HMAC-SHA256 over `iv ‖ ciphertext`, verified before decrypt).
251
+ - **Breaking:** legacy AES-CBC ciphertexts written before authentication have no
252
+ MAC and now fail closed with a "re-encrypt" error — re-encrypt them or use GCM.
253
+ GCM data is unaffected.
254
+ - Encryption runs on the Web Crypto API on web, Capacitor webviews, and Node 20+ (SSR).
181
255
 
182
256
  ## Strata API (most-used methods)
183
257
 
@@ -209,9 +283,13 @@ Native adapters depend on the downstream Capacitor project setup; follow `docs/g
209
283
 
210
284
  ## Links
211
285
 
212
- - [README](./README.md)
213
- - [Changelog](./CHANGELOG.md)
214
- - [Core API](./docs/api/core/strata.md)
215
- - [URL Adapter](./docs/api/adapters/web/url.md)
216
- - [Recovery & Integrity](./docs/api/features/recovery.md)
217
- - [GitHub](https://github.com/aoneahsan/strata-storage)
286
+ Full documentation lives at **https://stratastorage-docs.aoneahsan.com**:
287
+
288
+ - [Documentation home](https://stratastorage-docs.aoneahsan.com/)
289
+ - [For AI Agents (this guide, expanded)](https://stratastorage-docs.aoneahsan.com/ai)
290
+ - [Core API](https://stratastorage-docs.aoneahsan.com/api/core/strata)
291
+ - [Firebase sync](https://stratastorage-docs.aoneahsan.com/guides/platforms/firebase)
292
+ - [URL Adapter](https://stratastorage-docs.aoneahsan.com/api/adapters/web/url)
293
+ - [Recovery & Integrity](https://stratastorage-docs.aoneahsan.com/api/features/recovery)
294
+ - [Changelog](https://stratastorage-docs.aoneahsan.com/reference/changelog)
295
+ - Machine-readable: [`/llms.txt`](https://stratastorage-docs.aoneahsan.com/llms.txt) · [`/llms-full.txt`](https://stratastorage-docs.aoneahsan.com/llms-full.txt)
package/README.md CHANGED
@@ -7,9 +7,9 @@
7
7
  [![npm version](https://img.shields.io/npm/v/strata-storage.svg)](https://www.npmjs.com/package/strata-storage)
8
8
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
9
9
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue.svg)](https://www.typescriptlang.org/)
10
- [![Platform](https://img.shields.io/badge/platform-Web%20%7C%20iOS%20%7C%20Android-lightgrey.svg)](https://github.com/aoneahsan/strata-storage)
10
+ [![Platform](https://img.shields.io/badge/platform-Web%20%7C%20iOS%20%7C%20Android-lightgrey.svg)](https://stratastorage.aoneahsan.com)
11
11
 
12
- - **Version:** `2.6.1`
12
+ - **Version:** `2.7.0`
13
13
  - **License:** Apache-2.0
14
14
  - **Node.js:** `>= 24.13.0`
15
15
  - **Module format:** ESM only
@@ -71,7 +71,7 @@ await storage.set('token', '...', { encrypt: true });
71
71
 
72
72
  ## Provider-Free Usage
73
73
 
74
- The recommended pattern across all frameworks: create one instance and bind to it. No Provider, plugin, or module is required. The Provider-based styles remain available and are documented in [docs/examples/frameworks](./docs/examples/frameworks).
74
+ The recommended pattern across all frameworks: create one instance and bind to it. No Provider, plugin, or module is required. The Provider-based styles remain available and are documented in the [examples](https://stratastorage-docs.aoneahsan.com/examples).
75
75
 
76
76
  ### Vanilla JavaScript / TypeScript
77
77
 
@@ -396,14 +396,14 @@ Register the native adapters you need when running under Capacitor. All four are
396
396
  import { defineStorage } from 'strata-storage';
397
397
  import {
398
398
  PreferencesAdapter,
399
- SecureStorageAdapter,
399
+ SecureAdapter,
400
400
  SqliteAdapter,
401
401
  FilesystemAdapter,
402
402
  } from 'strata-storage/capacitor';
403
403
 
404
404
  const storage = defineStorage();
405
405
  storage.registerAdapter(new PreferencesAdapter()); // UserDefaults / SharedPreferences
406
- storage.registerAdapter(new SecureStorageAdapter()); // Keychain / EncryptedSharedPreferences
406
+ storage.registerAdapter(new SecureAdapter()); // Keychain / EncryptedSharedPreferences
407
407
  storage.registerAdapter(new SqliteAdapter()); // native SQLite
408
408
  storage.registerAdapter(new FilesystemAdapter()); // native files
409
409
 
@@ -432,7 +432,7 @@ audit.registerAdapter(new SqliteAdapter({ database: 'audit', table: 'rows' }));
432
432
 
433
433
  `await storage.size(true)` aggregates `{ total, count, byStorage, ... }`; native SQLite and filesystem additionally report a per-column byte breakdown (keys / values / metadata) when called on those adapters directly.
434
434
 
435
- > **Honest note:** the native iOS/Android adapters depend on your downstream Capacitor project setup and platform configuration, and native behavior cannot be exercised by the web/Node test suite. Follow [`docs/guides/platforms/device-verification.md`](./docs/guides/platforms/device-verification.md) to verify on a real iOS and Android device after integrating.
435
+ > **Honest note:** the native iOS/Android adapters depend on your downstream Capacitor project setup and platform configuration, and native behavior cannot be exercised in a web/Node environment. Follow the [device-verification guide](https://stratastorage-docs.aoneahsan.com/guides/platforms/device-verification) to verify on a real iOS and Android device after integrating.
436
436
 
437
437
  ### Firebase (optional cloud sync)
438
438
 
@@ -441,7 +441,12 @@ import { defineStorage } from 'strata-storage';
441
441
  import { enableFirebaseSync } from 'strata-storage/firebase';
442
442
 
443
443
  const storage = defineStorage();
444
- await enableFirebaseSync(storage, { apiKey: '…', projectId: '…', firestore: true });
444
+ await enableFirebaseSync(storage, {
445
+ apiKey: '…', authDomain: '…', projectId: '…', appId: '…',
446
+ firestore: true,
447
+ });
448
+ // 'firestore' / 'realtime' are runtime adapter names (not in the StorageType
449
+ // union, so strict TS may need a cast on the options object).
445
450
  await storage.set('data', value, { storage: 'firestore' });
446
451
  ```
447
452
 
@@ -473,59 +478,24 @@ Optional peer dependencies (install only the ones you use): `react >= 19.2.3`, `
473
478
 
474
479
  ## Documentation
475
480
 
481
+ 📚 **Full documentation: [stratastorage-docs.aoneahsan.com](https://stratastorage-docs.aoneahsan.com)**
482
+ 🤖 **For AI agents: [stratastorage-docs.aoneahsan.com/ai](https://stratastorage-docs.aoneahsan.com/ai)** (plus [`/llms.txt`](https://stratastorage-docs.aoneahsan.com/llms.txt) and [`/llms-full.txt`](https://stratastorage-docs.aoneahsan.com/llms-full.txt))
483
+
476
484
  ### Getting Started
477
- - [Installation](./docs/getting-started/installation.md) install and set up
478
- - [Quick Start](./docs/getting-started/quick-start.md) — running in minutes
479
- - [Configuration](./docs/getting-started/configuration.md) — configuration options
480
-
481
- ### Core
482
- - [API Reference](./docs/api/README.md) — complete API
483
- - [Core API (`Strata`)](./docs/api/core/strata.md) — the main class
484
- - [Type Definitions](./docs/api/core/types.md) — TypeScript types
485
- - [Error Handling](./docs/api/core/errors.md) — error classes
486
-
487
- ### Storage Adapters
488
- - [Web Adapters](./docs/api/adapters/README.md#web-adapters)
489
- - [localStorage](./docs/api/adapters/web/localstorage.md)
490
- - [sessionStorage](./docs/api/adapters/web/sessionstorage.md)
491
- - [IndexedDB](./docs/api/adapters/web/indexeddb.md)
492
- - [Cookies](./docs/api/adapters/web/cookies.md)
493
- - [Cache API](./docs/api/adapters/web/cache.md)
494
- - [Memory](./docs/api/adapters/web/memory.md)
495
- - [URL](./docs/api/adapters/web/url.md)
496
- - [Capacitor Adapters](./docs/api/adapters/README.md#capacitor-adapters)
497
- - [Preferences](./docs/api/adapters/capacitor/preferences.md)
498
- - [Secure Storage](./docs/api/adapters/capacitor/secure.md)
499
- - [SQLite](./docs/api/adapters/capacitor/sqlite.md)
500
- - [Filesystem](./docs/api/adapters/capacitor/filesystem.md)
485
+ - [Installation](https://stratastorage-docs.aoneahsan.com/installation) · [Quick Start](https://stratastorage-docs.aoneahsan.com/quick-start) · [Configuration](https://stratastorage-docs.aoneahsan.com/configuration)
486
+
487
+ ### API
488
+ - [API Reference](https://stratastorage-docs.aoneahsan.com/api) · [Core (`Strata`)](https://stratastorage-docs.aoneahsan.com/api/core/strata) · [Types](https://stratastorage-docs.aoneahsan.com/api/core/types) · [Errors](https://stratastorage-docs.aoneahsan.com/api/core/errors)
489
+ - [All adapters](https://stratastorage-docs.aoneahsan.com/api/adapters) — web (localStorage, IndexedDB, cookies, Cache, URL, …) + Capacitor (Preferences, Secure, SQLite, Filesystem) + remote (Firebase)
501
490
 
502
491
  ### Features
503
- - [Encryption](./docs/guides/features/encryption.md)
504
- - [Compression](./docs/guides/features/compression.md)
505
- - [TTL Management](./docs/api/features/ttl.md)
506
- - [Cross-Tab Sync](./docs/guides/features/sync.md)
507
- - [Query Engine](./docs/guides/features/queries.md)
508
- - [Migrations](./docs/guides/features/migrations.md)
509
- - [Recovery & Integrity](./docs/api/features/recovery.md)
510
-
511
- ### Platform Guides
512
- - [Web](./docs/guides/platforms/web.md)
513
- - [iOS](./docs/guides/platforms/ios.md)
514
- - [Android](./docs/guides/platforms/android.md)
515
- - [Capacitor](./docs/guides/platforms/capacitor.md)
516
-
517
- ### Examples
518
- - [Basic Usage](./docs/examples/basic-usage.md)
519
- - [React Integration](./docs/examples/frameworks/react.md)
520
- - [Vue Integration](./docs/examples/frameworks/vue.md)
521
- - [Angular Integration](./docs/examples/frameworks/angular.md)
522
- - [All Examples](./docs/examples/README.md)
523
-
524
- ### Reference
525
- - [Changelog](./docs/reference/changelog.md)
526
- - [FAQ](./docs/reference/faq.md)
527
- - [Troubleshooting](./docs/reference/troubleshooting.md)
528
- - [Migration Guide](./docs/MIGRATION.md)
492
+ - [Encryption](https://stratastorage-docs.aoneahsan.com/guides/features/encryption) · [Compression](https://stratastorage-docs.aoneahsan.com/guides/features/compression) · [TTL](https://stratastorage-docs.aoneahsan.com/api/features/ttl) · [Sync](https://stratastorage-docs.aoneahsan.com/guides/features/sync) · [Queries](https://stratastorage-docs.aoneahsan.com/guides/features/queries) · [Migrations](https://stratastorage-docs.aoneahsan.com/guides/features/migrations) · [Recovery & Integrity](https://stratastorage-docs.aoneahsan.com/api/features/recovery)
493
+
494
+ ### Platforms
495
+ - [Web](https://stratastorage-docs.aoneahsan.com/guides/platforms/web) · [iOS](https://stratastorage-docs.aoneahsan.com/guides/platforms/ios) · [Android](https://stratastorage-docs.aoneahsan.com/guides/platforms/android) · [Capacitor](https://stratastorage-docs.aoneahsan.com/guides/platforms/capacitor) · [Firebase](https://stratastorage-docs.aoneahsan.com/guides/platforms/firebase)
496
+
497
+ ### Examples & Reference
498
+ - [Examples](https://stratastorage-docs.aoneahsan.com/examples) · [Changelog](https://stratastorage-docs.aoneahsan.com/reference/changelog) · [FAQ](https://stratastorage-docs.aoneahsan.com/reference/faq) · [Troubleshooting](https://stratastorage-docs.aoneahsan.com/reference/troubleshooting) · [Migration](https://stratastorage-docs.aoneahsan.com/migration)
529
499
 
530
500
  ## Contributing
531
501
 
@@ -547,17 +517,25 @@ Apache License 2.0 — see [LICENSE](LICENSE). Free for commercial use, modifica
547
517
 
548
518
  ## Links
549
519
 
550
- - **NPM Package:** [npmjs.com/package/strata-storage](https://www.npmjs.com/package/strata-storage)
551
- - **GitHub Repository:** [github.com/aoneahsan/strata-storage](https://github.com/aoneahsan/strata-storage)
552
- - **Issue Tracker:** [github.com/aoneahsan/strata-storage/issues](https://github.com/aoneahsan/strata-storage/issues)
553
- - **Documentation:** [github.com/aoneahsan/strata-storage/tree/main/docs](https://github.com/aoneahsan/strata-storage/tree/main/docs)
520
+ - **NPM Package:** https://www.npmjs.com/package/strata-storage
521
+ - **Documentation:** https://stratastorage-docs.aoneahsan.com
522
+ - **Website:** https://stratastorage.aoneahsan.com
554
523
 
555
524
  ## Support
556
525
 
557
- 1. Check the [FAQ](./docs/reference/faq.md)
558
- 2. Search [existing issues](https://github.com/aoneahsan/strata-storage/issues)
559
- 3. Open a [new issue](https://github.com/aoneahsan/strata-storage/issues/new)
526
+ 1. Check the [FAQ](https://stratastorage-docs.aoneahsan.com/reference/faq) and [Troubleshooting](https://stratastorage-docs.aoneahsan.com/reference/troubleshooting)
527
+ 2. Browse the [documentation](https://stratastorage-docs.aoneahsan.com)
528
+ 3. [Contact us / report an issue](https://stratastorage.aoneahsan.com/contact)
560
529
 
561
530
  ---
562
531
 
563
532
  Made by [Ahsan Mahmood](https://aoneahsan.com). **One API. Every Storage. Everywhere.**
533
+
534
+ <!-- project-links:start -->
535
+ ## Links
536
+
537
+ - Live: https://www.npmjs.com/package/strata-storage
538
+ - NPM: https://www.npmjs.com/package/strata-storage
539
+
540
+ _URL source of truth: `01-code/projects/project-live-urls.json` (auto-generated — do not hand-edit between these markers)._
541
+ <!-- project-links:end -->
package/android/AGENTS.md CHANGED
@@ -1,9 +1,24 @@
1
1
  # AGENTS.md — android/
2
2
 
3
- Last Updated: 2026-05-27
3
+ Last Updated: 2026-06-24
4
4
 
5
5
  > Agent instructions for Android native development.
6
6
 
7
+ > **v2.7.0:** Secure storage THROWS on API < 23 (no plaintext fallback) and
8
+ > `minSdkVersion` is raised to **23**. `FilesystemStorage` writes atomically
9
+ > (`Files.move` ATOMIC_MOVE on API 26+, backup-swap fallback below) — never
10
+ > deletes the old value before the new one commits. Preferences/secure `get()`
11
+ > parses the JSON wrapper back to an object so `StorageValue` (TTL/tags/metadata)
12
+ > round-trips. `EncryptedStorage.objectToJsonString()` serialises arrays via
13
+ > `java.lang.reflect.Array` so primitive arrays (`int[]`, `long[]`, …) round-trip
14
+ > (no lossy `toString()` fallback). **SQLite:** identifiers (`table`/`database`)
15
+ > are now REJECTED (throw → `call.reject`) when not strict SQL identifiers
16
+ > (`^[A-Za-z_][A-Za-z0-9_]*$`) instead of being silently stripped (`#5`);
17
+ > `keys()`/`query()` filter expired rows in SQL and `query()` returns the full
18
+ > `{key, value}` wrapper in one round-trip, plus a new `cleanupExpired`
19
+ > single-`DELETE` on the TTL tick (`#6` — no more per-key `get()` N+1).
20
+ > Device-verify pending.
21
+
7
22
  ## Files
8
23
 
9
24
  | File | Purpose |
@@ -19,7 +34,7 @@ Last Updated: 2026-05-27
19
34
  - **`androidx.security 1.1.0`:** upgraded from `1.1.0-alpha06`. Requires `compileSdkVersion 34`. If Gradle sync fails, bump `compileSdkVersion` and `targetSdkVersion` to 34 in `android/app/build.gradle`.
20
35
  - **SQLite multi-store:** `call.getString("database")` and `call.getString("table")` are now read and used. Each `(database, table)` pair → distinct `.db` file. Previously ignored.
21
36
  - **SQLite value shape:** native `get` returns full `StorageValue` wrapper. Corrupt rows → treated as miss (no throw).
22
- - **FilesystemStorage.java:** new class. Writes are atomic (staging temp → `renameTo`). `keys()` excludes `.staging/` artifacts. `size(detailed)` → `{ keys, values, metadata }`.
37
+ - **FilesystemStorage.java:** new class. Writes are atomic (staging temp → `renameTo`). `keys()` excludes `.strata-staging/` artifacts. `size(detailed)` → `{ keys, values, metadata }`.
23
38
  - **Pending on-device verification** — see `docs/guides/platforms/device-verification.md`.
24
39
 
25
40
  ## Agent Rules
@@ -34,7 +49,7 @@ Last Updated: 2026-05-27
34
49
  - NEVER concatenate user input into SQL strings
35
50
 
36
51
  ### Filesystem Safety
37
- - NEVER read from `strata_storage/.staging/` — staging files are transient
52
+ - NEVER read from `strata_storage/.strata-staging/` — staging files are transient
38
53
  - Key sanitisation must be consistent between `set`, `get`, `remove`, and `keys`
39
54
 
40
55
  ### Plugin Architecture
package/android/CLAUDE.md CHANGED
@@ -1,6 +1,39 @@
1
1
  # CLAUDE.md — android/
2
2
 
3
- Last Updated: 2026-05-27
3
+ Last Updated: 2026-06-24
4
+
5
+ ## v2.7.0 Changes
6
+
7
+ - **Secure storage no longer degrades to plaintext.** `EncryptedStorage` now
8
+ THROWS `GeneralSecurityException` on API < 23 instead of silently falling back
9
+ to unencrypted `SharedPreferences`, and `minSdkVersion` is raised to **23**
10
+ (EncryptedSharedPreferences requires API 23+). 23 is the floor for the secure
11
+ guarantees this plugin advertises.
12
+ - **Filesystem writes are atomic.** `FilesystemStorage.set()` commits via
13
+ `Files.move(ATOMIC_MOVE, REPLACE_EXISTING)` on API 26+, falling back to a
14
+ backup-swap on API 23–25 — the old value is never deleted before the new one
15
+ is committed (the prior delete-then-rename could lose data on a failed rename).
16
+ - **Preferences/secure `StorageValue` round-trip fixed.** `StrataStoragePlugin`
17
+ parses the stored JSON wrapper string back into a `JSObject` on `get()`, so
18
+ TTL/tags/metadata survive a write→read cycle (matching sqlite/filesystem).
19
+ - **Secure primitive-array serialization fixed.** `EncryptedStorage.objectToJsonString()`
20
+ now serialises arrays via `java.lang.reflect.Array` (`getLength`/`get`) instead
21
+ of the `(Object[]) obj` cast, so primitive arrays (`int[]`, `long[]`, …)
22
+ round-trip instead of falling back to lossy `value.toString()` (`[I@…`).
23
+ - **SQLite identifiers are rejected, not stripped (`#5`).** `SQLiteStorage.sanitizeTable`
24
+ and `StrataStoragePlugin.sanitizeDatabase` now throw `IllegalArgumentException`
25
+ on a non-empty name that is not a strict SQL identifier
26
+ (`^[A-Za-z_][A-Za-z0-9_]*$`) instead of silently removing illegal characters.
27
+ The throw propagates to the plugin's `try/catch` → `call.reject` (and
28
+ `getSqliteStore` maps an invalid database to `null` → reject). The TS adapter
29
+ applies the same allow-list and rejects first; this is defense-in-depth.
30
+ Values stay bound parameters — identifiers are the only interpolated input.
31
+ - **SQLite `keys()`/`query()` N+1 eliminated (`#6`).** `keys()` now excludes
32
+ expired rows in SQL (`expires IS NULL OR expires > ?`); `query()` returns the
33
+ full `StorageValue` wrapper `{key, value}` for each non-expired row in one
34
+ round-trip; new `cleanupExpired(table)` does a single `DELETE` of expired rows
35
+ (the TS adapter calls it on its TTL tick). The per-key `get()` loop is gone.
36
+ - All native changes remain pending on-device verification.
4
37
 
5
38
  ## Android Native Plugin
6
39
 
@@ -45,7 +78,7 @@ returns per-column byte breakdown.
45
78
 
46
79
  ### FilesystemStorage.java (new)
47
80
  Stores each key as `getFilesDir()/strata_storage/<sanitised-key>.json`. Writes
48
- are atomic: value is written to `strata_storage/.staging/<key>.tmp`, then
81
+ are atomic: value is written to `strata_storage/.strata-staging/<key>.tmp`, then
49
82
  renamed into place using `renameTo()`. Staging artifacts are excluded from
50
83
  `keys()`. No external filesystem permissions are needed (`getFilesDir()` is
51
84
  app-private). `isAvailable()` returns `true`. `size(detailed)` supported.
@@ -67,7 +100,7 @@ app-private). `isAvailable()` returns `true`. `size(detailed)` supported.
67
100
  - Use `SQLiteDatabase.query()` or prepared statements
68
101
 
69
102
  ### Filesystem Safety
70
- - NEVER read from `strata_storage/.staging/` — staging files are transient
103
+ - NEVER read from `strata_storage/.strata-staging/` — staging files are transient
71
104
  - Key sanitisation must be consistent between `set`, `get`, `remove`, and `keys`
72
105
  - `getFilesDir()` is app-private; no external storage permissions required
73
106
 
@@ -21,7 +21,11 @@ android {
21
21
  namespace "com.stratastorage"
22
22
  compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
23
23
  defaultConfig {
24
- minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
24
+ // minSdk 23 (Marshmallow): EncryptedSharedPreferences-backed secure
25
+ // storage requires API 23+. Below it, "secure" storage would degrade to
26
+ // plaintext, so 23 is the floor for the security guarantees this plugin
27
+ // advertises.
28
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
25
29
  targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
26
30
  versionCode 1
27
31
  versionName "1.0"
@@ -8,6 +8,7 @@ import androidx.security.crypto.EncryptedSharedPreferences;
8
8
  import androidx.security.crypto.MasterKey;
9
9
  import java.security.GeneralSecurityException;
10
10
  import java.io.IOException;
11
+ import java.lang.reflect.Array;
11
12
  import java.util.Set;
12
13
  import java.util.HashSet;
13
14
  import java.util.Map;
@@ -18,7 +19,6 @@ import org.json.JSONArray;
18
19
 
19
20
  public class EncryptedStorage {
20
21
  private SharedPreferences encryptedPrefs;
21
- private SharedPreferences.Editor editor;
22
22
  private static final String DEFAULT_NAME = "StrataSecureStorage";
23
23
 
24
24
  public EncryptedStorage(Context context) throws GeneralSecurityException, IOException {
@@ -42,20 +42,24 @@ public class EncryptedStorage {
42
42
  EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
43
43
  );
44
44
 
45
- editor = encryptedPrefs.edit();
46
45
  } catch (GeneralSecurityException | IOException e) {
47
46
  Log.e("StrataStorage", "Failed to initialize encrypted storage", e);
48
47
  throw e;
49
48
  }
50
49
  } else {
51
- Log.w("StrataStorage", "API < 23, using unencrypted SharedPreferences");
52
- // Fallback to regular SharedPreferences for older devices
53
- encryptedPrefs = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
54
- editor = encryptedPrefs.edit();
50
+ // SECURITY: EncryptedSharedPreferences requires API 23+. Refuse to
51
+ // silently fall back to UNENCRYPTED SharedPreferences for "secure"
52
+ // storage that would persist sensitive data in plaintext while the
53
+ // caller believes it is encrypted. Reject so the caller can decide.
54
+ // (minSdkVersion is also raised to 23, so this is defense in depth.)
55
+ Log.e("StrataStorage", "Secure storage unavailable on API " + Build.VERSION.SDK_INT + " (< 23)");
56
+ throw new GeneralSecurityException(
57
+ "Secure storage requires Android API 23 (Marshmallow) or higher; refusing to "
58
+ + "fall back to unencrypted storage on API " + Build.VERSION.SDK_INT);
55
59
  }
56
60
  }
57
61
 
58
- public boolean set(String key, Object value) {
62
+ public synchronized boolean set(String key, Object value) {
59
63
  try {
60
64
  String stringValue;
61
65
  if (value instanceof String) {
@@ -74,6 +78,7 @@ public class EncryptedStorage {
74
78
  }
75
79
  }
76
80
  }
81
+ SharedPreferences.Editor editor = encryptedPrefs.edit();
77
82
  editor.putString(key, stringValue);
78
83
  return editor.commit();
79
84
  } catch (Exception e) {
@@ -82,11 +87,12 @@ public class EncryptedStorage {
82
87
  }
83
88
  }
84
89
 
85
- public String get(String key) {
90
+ public synchronized String get(String key) {
86
91
  return encryptedPrefs.getString(key, null);
87
92
  }
88
-
89
- public boolean remove(String key) {
93
+
94
+ public synchronized boolean remove(String key) {
95
+ SharedPreferences.Editor editor = encryptedPrefs.edit();
90
96
  editor.remove(key);
91
97
  return editor.commit();
92
98
  }
@@ -95,7 +101,8 @@ public class EncryptedStorage {
95
101
  return clear(null);
96
102
  }
97
103
 
98
- public boolean clear(String prefix) {
104
+ public synchronized boolean clear(String prefix) {
105
+ SharedPreferences.Editor editor = encryptedPrefs.edit();
99
106
  if (prefix != null) {
100
107
  // Clear only keys with the given prefix
101
108
  Set<String> keysToRemove = new HashSet<>();
@@ -118,7 +125,7 @@ public class EncryptedStorage {
118
125
  return keys(null);
119
126
  }
120
127
 
121
- public List<String> keys(String pattern) {
128
+ public synchronized List<String> keys(String pattern) {
122
129
  Set<String> allKeys = encryptedPrefs.getAll().keySet();
123
130
 
124
131
  if (pattern == null) {
@@ -135,11 +142,11 @@ public class EncryptedStorage {
135
142
  return filteredKeys;
136
143
  }
137
144
 
138
- public boolean has(String key) {
145
+ public synchronized boolean has(String key) {
139
146
  return encryptedPrefs.contains(key);
140
147
  }
141
-
142
- public SizeInfo size() {
148
+
149
+ public synchronized SizeInfo size() {
143
150
  Map<String, ?> all = encryptedPrefs.getAll();
144
151
  long totalSize = 0;
145
152
  int count = all.size();
@@ -188,9 +195,15 @@ public class EncryptedStorage {
188
195
  jsonArray.put(item);
189
196
  }
190
197
  } else {
191
- Object[] array = (Object[]) obj;
192
- for (Object item : array) {
193
- jsonArray.put(item);
198
+ // Use java.lang.reflect.Array so EVERY array type round-trips,
199
+ // including primitive arrays (int[], long[], double[], boolean[],
200
+ // ...). A direct `(Object[]) obj` cast throws ClassCastException for
201
+ // primitive arrays, which the caller would swallow and replace with
202
+ // the lossy `value.toString()` (e.g. "[I@1a2b3c"). Array.get boxes
203
+ // each primitive element so JSONArray.put stores a real value.
204
+ int len = Array.getLength(obj);
205
+ for (int i = 0; i < len; i++) {
206
+ jsonArray.put(Array.get(obj, i));
194
207
  }
195
208
  }
196
209
  return jsonArray.toString();
@@ -1,6 +1,7 @@
1
1
  package com.strata.storage;
2
2
 
3
3
  import android.content.Context;
4
+ import android.os.Build;
4
5
  import android.util.Log;
5
6
  import com.getcapacitor.JSObject;
6
7
  import java.io.File;
@@ -124,16 +125,9 @@ public class FilesystemStorage {
124
125
  fos.close();
125
126
  fos = null;
126
127
 
127
- // Rename is atomic on the same filesystem. Remove a stale target
128
- // first since File.renameTo can fail when the destination exists.
129
- if (target.exists() && !target.delete()) {
130
- Log.w("StrataStorage", "Could not delete existing file before rename: " + key);
131
- }
132
- if (!tmp.renameTo(target)) {
133
- tmp.delete();
134
- return false;
135
- }
136
- return true;
128
+ // Commit the temp file over the target WITHOUT a delete-then-rename
129
+ // window (which could lose the existing value if the rename failed).
130
+ return commitAtomically(tmp, target, key);
137
131
  } catch (IOException e) {
138
132
  Log.e("StrataStorage", "Failed to set filesystem value for key " + key, e);
139
133
  if (tmp.exists()) {
@@ -151,6 +145,64 @@ public class FilesystemStorage {
151
145
  }
152
146
  }
153
147
 
148
+ /**
149
+ * Commit {@code tmp} over {@code target} without ever leaving the key with no
150
+ * value. The old delete-then-rename approach could lose the existing value
151
+ * if the rename failed after the delete succeeded.
152
+ *
153
+ * <p>Strategy: on API 26+ use an atomic NIO move (REPLACE_EXISTING +
154
+ * ATOMIC_MOVE). Otherwise try a direct {@code renameTo} (which atomically
155
+ * replaces on app-internal ext4/f2fs); only if that fails do we move the old
156
+ * value aside to a backup, swap in the new one, and restore the backup if the
157
+ * final rename fails — so the old value is never destroyed before the new one
158
+ * is committed.
159
+ */
160
+ private boolean commitAtomically(File tmp, File target, String key) {
161
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
162
+ try {
163
+ java.nio.file.Files.move(
164
+ tmp.toPath(),
165
+ target.toPath(),
166
+ java.nio.file.StandardCopyOption.REPLACE_EXISTING,
167
+ java.nio.file.StandardCopyOption.ATOMIC_MOVE);
168
+ return true;
169
+ } catch (Exception nioError) {
170
+ Log.w("StrataStorage", "Atomic NIO move failed, falling back for key " + key, nioError);
171
+ // fall through to the legacy strategy
172
+ }
173
+ }
174
+
175
+ // Direct rename — atomic replace on POSIX/ext4 internal storage.
176
+ if (tmp.renameTo(target)) {
177
+ return true;
178
+ }
179
+
180
+ // Rename-over-existing failed: move the old value aside first.
181
+ File backup = null;
182
+ if (target.exists()) {
183
+ backup = new File(stagingDir, UUID.randomUUID().toString() + ".bak");
184
+ if (!target.renameTo(backup)) {
185
+ // Could not preserve the old value — abort without data loss.
186
+ tmp.delete();
187
+ return false;
188
+ }
189
+ }
190
+
191
+ if (tmp.renameTo(target)) {
192
+ if (backup != null) {
193
+ backup.delete();
194
+ }
195
+ return true;
196
+ }
197
+
198
+ // New value did not land — restore the old value from backup.
199
+ if (backup != null && backup.renameTo(target)) {
200
+ Log.w("StrataStorage", "Restored previous value after failed write for key " + key);
201
+ }
202
+ tmp.delete();
203
+ return false;
204
+ }
205
+
154
206
  public synchronized boolean remove(String key) {
155
207
  File file = fileForKey(key);
156
208
  if (!file.exists()) {