strata-storage 2.6.1 → 2.7.1

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 (143) hide show
  1. package/AI-INTEGRATION-GUIDE.md +129 -11
  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/CacheAdapter.d.ts.map +1 -1
  27. package/dist/adapters/web/CacheAdapter.js +20 -5
  28. package/dist/adapters/web/CookieAdapter.d.ts +2 -2
  29. package/dist/adapters/web/CookieAdapter.d.ts.map +1 -1
  30. package/dist/adapters/web/CookieAdapter.js +27 -3
  31. package/dist/adapters/web/IndexedDBAdapter.d.ts +10 -3
  32. package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
  33. package/dist/adapters/web/IndexedDBAdapter.js +19 -4
  34. package/dist/adapters/web/LocalStorageAdapter.d.ts +2 -2
  35. package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -1
  36. package/dist/adapters/web/LocalStorageAdapter.js +15 -1
  37. package/dist/adapters/web/MemoryAdapter.d.ts +9 -2
  38. package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
  39. package/dist/adapters/web/MemoryAdapter.js +22 -5
  40. package/dist/adapters/web/SessionStorageAdapter.d.ts +10 -10
  41. package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
  42. package/dist/adapters/web/SessionStorageAdapter.js +17 -3
  43. package/dist/adapters/web/URLAdapter.d.ts +2 -2
  44. package/dist/adapters/web/index.d.ts +7 -7
  45. package/dist/capacitor.d.ts +6 -6
  46. package/dist/capacitor.d.ts.map +1 -1
  47. package/dist/capacitor.js +5 -4
  48. package/dist/core/AdapterRegistry.d.ts +1 -1
  49. package/dist/core/BaseAdapter.d.ts +21 -5
  50. package/dist/core/BaseAdapter.d.ts.map +1 -1
  51. package/dist/core/BaseAdapter.js +29 -1
  52. package/dist/core/Strata.d.ts +53 -7
  53. package/dist/core/Strata.d.ts.map +1 -1
  54. package/dist/core/Strata.js +581 -125
  55. package/dist/features/compression/index.d.ts +1 -1
  56. package/dist/features/compression.d.ts +28 -9
  57. package/dist/features/compression.d.ts.map +1 -1
  58. package/dist/features/compression.js +164 -77
  59. package/dist/features/encryption/index.d.ts +1 -1
  60. package/dist/features/encryption.d.ts +55 -2
  61. package/dist/features/encryption.d.ts.map +1 -1
  62. package/dist/features/encryption.js +139 -26
  63. package/dist/features/migration.d.ts +1 -1
  64. package/dist/features/migration.d.ts.map +1 -1
  65. package/dist/features/migration.js +7 -2
  66. package/dist/features/observer/index.d.ts +1 -1
  67. package/dist/features/observer.d.ts +1 -1
  68. package/dist/features/query/index.d.ts +1 -1
  69. package/dist/features/query.d.ts +2 -1
  70. package/dist/features/query.d.ts.map +1 -1
  71. package/dist/features/query.js +32 -7
  72. package/dist/features/sync/index.d.ts +1 -1
  73. package/dist/features/sync.d.ts +19 -3
  74. package/dist/features/sync.d.ts.map +1 -1
  75. package/dist/features/sync.js +75 -12
  76. package/dist/features/ttl/index.d.ts +1 -1
  77. package/dist/features/ttl.d.ts +2 -2
  78. package/dist/features/ttl.d.ts.map +1 -1
  79. package/dist/features/ttl.js +4 -2
  80. package/dist/firebase.d.ts +1 -1
  81. package/dist/firebase.d.ts.map +1 -1
  82. package/dist/firebase.js +72 -10
  83. package/dist/index.d.ts +24 -22
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +6 -1
  86. package/dist/integrations/angular/index.d.ts +2 -2
  87. package/dist/integrations/angular/index.d.ts.map +1 -1
  88. package/dist/integrations/angular/index.js +7 -8
  89. package/dist/integrations/react/index.d.ts +3 -3
  90. package/dist/integrations/react/index.d.ts.map +1 -1
  91. package/dist/integrations/react/index.js +16 -4
  92. package/dist/integrations/vue/index.d.ts +2 -2
  93. package/dist/integrations/vue/index.d.ts.map +1 -1
  94. package/dist/integrations/vue/index.js +5 -2
  95. package/dist/package.json +11 -2
  96. package/dist/plugin/definitions.d.ts +29 -3
  97. package/dist/plugin/definitions.d.ts.map +1 -1
  98. package/dist/plugin/index.d.ts +2 -2
  99. package/dist/plugin/index.d.ts.map +1 -1
  100. package/dist/plugin/index.js +3 -0
  101. package/dist/plugin/web.d.ts +11 -2
  102. package/dist/plugin/web.d.ts.map +1 -1
  103. package/dist/plugin/web.js +23 -4
  104. package/dist/types/index.d.ts +50 -20
  105. package/dist/types/index.d.ts.map +1 -1
  106. package/dist/types/index.js +1 -10
  107. package/dist/utils/index.d.ts +5 -0
  108. package/dist/utils/index.d.ts.map +1 -1
  109. package/dist/utils/index.js +7 -0
  110. package/ios/AGENTS.md +14 -3
  111. package/ios/CLAUDE.md +25 -3
  112. package/ios/Plugin/FilesystemStorage.swift +19 -0
  113. package/ios/Plugin/SQLiteStorage.swift +59 -9
  114. package/ios/Plugin/StrataStoragePlugin.m +1 -0
  115. package/ios/Plugin/StrataStoragePlugin.swift +87 -34
  116. package/ios/Plugin/UserDefaultsStorage.swift +47 -46
  117. package/package.json +27 -24
  118. package/scripts/build.js +46 -27
  119. package/scripts/cli.js +1 -1
  120. package/scripts/configure.js +2 -2
  121. package/scripts/postinstall.js +3 -2
  122. package/dist/android/AGENTS.md +0 -51
  123. package/dist/android/CLAUDE.md +0 -89
  124. package/dist/android/build.gradle +0 -58
  125. package/dist/android/proguard-rules.pro +0 -11
  126. package/dist/android/settings.gradle +0 -2
  127. package/dist/android/src/main/AndroidManifest.xml +0 -2
  128. package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +0 -218
  129. package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +0 -287
  130. package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +0 -371
  131. package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +0 -209
  132. package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +0 -616
  133. package/dist/core/StorageStrategy.d.ts +0 -55
  134. package/dist/core/StorageStrategy.d.ts.map +0 -1
  135. package/dist/core/StorageStrategy.js +0 -200
  136. package/dist/ios/AGENTS.md +0 -48
  137. package/dist/ios/CLAUDE.md +0 -84
  138. package/dist/ios/Plugin/FilesystemStorage.swift +0 -218
  139. package/dist/ios/Plugin/KeychainStorage.swift +0 -233
  140. package/dist/ios/Plugin/SQLiteStorage.swift +0 -426
  141. package/dist/ios/Plugin/StrataStoragePlugin.m +0 -23
  142. package/dist/ios/Plugin/StrataStoragePlugin.swift +0 -411
  143. 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)
@@ -51,7 +81,7 @@ await storage.set('key', value, {
51
81
  ttl: 3_600_000, // expire in 1 hour (ms)
52
82
  encrypt: true, // async only — requires encryption config + password
53
83
  compress: true, // async only
54
- tags: ['user-data'], // for queries
84
+ tags: ['user-data'], // grouping; filter with clear({ tags })
55
85
  metadata: { version: 1 },
56
86
  verify: true, // store + check an integrity checksum (2.5.0)
57
87
  durable: true, // read back and verify the write (2.5.0)
@@ -60,6 +90,27 @@ await storage.set('key', value, {
60
90
  const value = await storage.get('key', { storage: 'indexedDB' });
61
91
  ```
62
92
 
93
+ ## Query data model
94
+
95
+ `query(condition, options?)` matches the condition against the **stored value's own
96
+ bare fields** (the decoded user value) using MongoDB-style operators
97
+ (`$eq $ne $gt $gte $lt $lte $in $nin $regex $exists $type $and $or $not`):
98
+
99
+ ```typescript
100
+ // matches stored values whose own `status` === 'active' AND `score` >= 10
101
+ const rows = await storage.query<{ status: string; score: number }>({
102
+ status: 'active',
103
+ score: { $gte: 10 },
104
+ }); // => Array<{ key: string; value: T }>
105
+
106
+ // sort/skip/limit/select use bare value field names (no `value.` prefix)
107
+ await storage.query({ status: 'active' }, { sort: { score: -1 }, limit: 20 });
108
+ ```
109
+
110
+ The wrapper fields `key`, `tags`, `created`, `updated`, `expires`, and `metadata`
111
+ are **NOT** queryable via `query()` — they return nothing. Filter by tag with
112
+ `clear({ tags })`, not `query()`.
113
+
63
114
  ## Synchronous API (2.5.0)
64
115
 
65
116
  No `await`. Works only on sync-capable adapters: `memory`, `localStorage`, `sessionStorage`, `cookies`, `url`.
@@ -162,14 +213,14 @@ const storage = defineStorage();
162
213
  import { defineStorage } from 'strata-storage';
163
214
  import {
164
215
  PreferencesAdapter,
165
- SecureStorageAdapter,
216
+ SecureAdapter,
166
217
  SqliteAdapter,
167
218
  FilesystemAdapter,
168
219
  } from 'strata-storage/capacitor';
169
220
 
170
221
  const storage = defineStorage();
171
222
  storage.registerAdapter(new PreferencesAdapter());
172
- storage.registerAdapter(new SecureStorageAdapter());
223
+ storage.registerAdapter(new SecureAdapter());
173
224
  storage.registerAdapter(new SqliteAdapter()); // 2.6.0 — multi-store (database + table)
174
225
  storage.registerAdapter(new FilesystemAdapter()); // 2.6.0 — file-per-key, atomic writes
175
226
  await storage.set('secret', token, { storage: 'secure' });
@@ -177,7 +228,51 @@ await storage.set('secret', token, { storage: 'secure' });
177
228
 
178
229
  **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
230
 
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.
231
+ 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.
232
+
233
+ ### Firebase cross-device sync (optional)
234
+
235
+ `firebase` is an optional peer dependency, loaded dynamically only when used.
236
+
237
+ ```typescript
238
+ import { enableFirebaseSync, isFirebaseAvailable } from 'strata-storage/firebase';
239
+
240
+ await enableFirebaseSync(storage, {
241
+ apiKey: '…', authDomain: '…', projectId: '…', appId: '…',
242
+ firestore: true, // registers the `firestore` adapter
243
+ realtimeDatabase: true, // registers the `realtime` adapter
244
+ collectionName: 'strata-storage', // optional (default)
245
+ });
246
+
247
+ await storage.set('profile', data, { storage: 'firestore' });
248
+ storage.subscribe((c) => { if (c.source === 'remote') apply(c); }, { storage: 'firestore' });
249
+ ```
250
+
251
+ `firestore` is observable + queryable; `realtime` is observable. **Neither is
252
+ encrypted at rest by Strata** — combine with `encrypt: true` and/or Firebase
253
+ Security Rules for confidentiality. `firestore.size()` returns `{ total: 0, count: 0 }`.
254
+ Note: `'firestore'`/`'realtime'` are runtime adapter names (not in the `StorageType`
255
+ union), so strict TS may need a cast on the per-call options object.
256
+
257
+ ## Encryption & security (2.7.0)
258
+
259
+ ```typescript
260
+ const storage = defineStorage({
261
+ encryption: {
262
+ enabled: true,
263
+ password: 'user-derived-password',
264
+ algorithm: 'AES-GCM', // default; or 'AES-CBC' (authenticated via Encrypt-then-MAC)
265
+ iterations: 600000, // PBKDF2 default (OWASP); stored per-record, used on decrypt
266
+ },
267
+ });
268
+ ```
269
+
270
+ - **`AES-GCM`** (default) is AEAD. **`AES-CBC`** is authenticated with
271
+ Encrypt-then-MAC (HMAC-SHA256 over `iv ‖ ciphertext`, verified before decrypt).
272
+ - **Breaking:** legacy AES-CBC ciphertexts written before authentication have no
273
+ MAC and now fail closed with a "re-encrypt" error — re-encrypt them or use GCM.
274
+ GCM data is unaffected.
275
+ - Encryption runs on the Web Crypto API on web, Capacitor webviews, and Node 20+ (SSR).
181
276
 
182
277
  ## Strata API (most-used methods)
183
278
 
@@ -196,6 +291,25 @@ Native adapters depend on the downstream Capacitor project setup; follow `docs/g
196
291
  | `getTTL`/`extendTTL`/`persist` | TTL management |
197
292
  | `registerAdapter(adapter)` | `void` |
198
293
 
294
+ ## Notes for AI agents
295
+
296
+ - **Error handling** — every library error class (`QuotaExceededError`,
297
+ `EncryptionError`, `ValidationError`, `NotFoundError`, `IntegrityError`, etc.)
298
+ extends `StrataError`. Catch any of them with `catch (e) { if (e instanceof StrataError) … }`.
299
+ - **Server / SSR durability** — the default `storage` singleton uses web adapters and
300
+ **falls back to non-persistent `memory` on Node/SSR** (no localStorage/IndexedDB there).
301
+ For durable server storage register `sqlite`/`filesystem` or a custom adapter and
302
+ target it with `{ storage: '…' }`.
303
+ - **Browser encryption** — `crypto.subtle` requires a **secure context**
304
+ (HTTPS or `localhost`); encryption throws on insecure-origin pages. Node 20+ is fine.
305
+ - **Sync API limits** — `getSync`/`setSync`/… throw on async-only adapters
306
+ (indexedDB, cache, sqlite, filesystem, secure, preferences); `setSync` throws if
307
+ `encrypt`/`compress` is requested, and `getSync` throws on an encrypted/compressed
308
+ stored value. Use the async API there.
309
+ - **`useStorage` value is `T | null`** — element `[0]` is `null` during initial load
310
+ and when the key is absent with no default, *even when a defaultValue is passed*.
311
+ Null-guard before use.
312
+
199
313
  ## Troubleshooting
200
314
 
201
315
  | Issue | Solution |
@@ -209,9 +323,13 @@ Native adapters depend on the downstream Capacitor project setup; follow `docs/g
209
323
 
210
324
  ## Links
211
325
 
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)
326
+ Full documentation lives at **https://stratastorage-docs.aoneahsan.com**:
327
+
328
+ - [Documentation home](https://stratastorage-docs.aoneahsan.com/)
329
+ - [For AI Agents (this guide, expanded)](https://stratastorage-docs.aoneahsan.com/ai)
330
+ - [Core API](https://stratastorage-docs.aoneahsan.com/api/core/strata)
331
+ - [Firebase sync](https://stratastorage-docs.aoneahsan.com/guides/platforms/firebase)
332
+ - [URL Adapter](https://stratastorage-docs.aoneahsan.com/api/adapters/web/url)
333
+ - [Recovery & Integrity](https://stratastorage-docs.aoneahsan.com/api/features/recovery)
334
+ - [Changelog](https://stratastorage-docs.aoneahsan.com/reference/changelog)
335
+ - 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.1`
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();