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.
- package/AI-INTEGRATION-GUIDE.md +115 -261
- package/README.md +426 -182
- package/android/AGENTS.md +51 -0
- package/android/CLAUDE.md +89 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/strata/storage/FilesystemStorage.java +287 -0
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +260 -203
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +357 -69
- package/dist/README.md +426 -182
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/FilesystemAdapter.js +2 -1
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/PreferencesAdapter.js +2 -1
- package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SecureAdapter.js +2 -1
- package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SqliteAdapter.js +2 -1
- package/dist/adapters/web/CacheAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CacheAdapter.js +11 -3
- package/dist/adapters/web/CookieAdapter.d.ts +37 -1
- package/dist/adapters/web/CookieAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CookieAdapter.js +89 -9
- package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
- package/dist/adapters/web/IndexedDBAdapter.js +10 -2
- package/dist/adapters/web/LocalStorageAdapter.d.ts +31 -0
- package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/LocalStorageAdapter.js +92 -19
- package/dist/adapters/web/MemoryAdapter.d.ts +24 -0
- package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
- package/dist/adapters/web/MemoryAdapter.js +69 -18
- package/dist/adapters/web/SessionStorageAdapter.d.ts +24 -0
- package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/SessionStorageAdapter.js +71 -9
- package/dist/adapters/web/URLAdapter.d.ts +59 -0
- package/dist/adapters/web/URLAdapter.d.ts.map +1 -0
- package/dist/adapters/web/URLAdapter.js +234 -0
- package/dist/adapters/web/index.d.ts +1 -0
- package/dist/adapters/web/index.d.ts.map +1 -1
- package/dist/adapters/web/index.js +1 -0
- package/dist/android/AGENTS.md +51 -0
- package/dist/android/CLAUDE.md +89 -0
- package/dist/android/build.gradle +1 -1
- package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +287 -0
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +260 -203
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +357 -69
- package/dist/capacitor.d.ts.map +1 -1
- package/dist/capacitor.js +2 -1
- package/dist/core/BaseAdapter.d.ts +8 -0
- package/dist/core/BaseAdapter.d.ts.map +1 -1
- package/dist/core/BaseAdapter.js +34 -14
- package/dist/core/Strata.d.ts +56 -2
- package/dist/core/Strata.d.ts.map +1 -1
- package/dist/core/Strata.js +501 -53
- package/dist/features/encryption.d.ts.map +1 -1
- package/dist/features/encryption.js +3 -2
- package/dist/features/integrity.d.ts +16 -0
- package/dist/features/integrity.d.ts.map +1 -0
- package/dist/features/integrity.js +28 -0
- package/dist/features/observer.d.ts.map +1 -1
- package/dist/features/observer.js +2 -1
- package/dist/features/query.d.ts +7 -1
- package/dist/features/query.d.ts.map +1 -1
- package/dist/features/query.js +9 -2
- package/dist/features/sync.d.ts.map +1 -1
- package/dist/features/sync.js +4 -3
- package/dist/index.d.ts +35 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -30
- package/dist/integrations/angular/index.d.ts +158 -0
- package/dist/integrations/angular/index.d.ts.map +1 -0
- package/dist/integrations/angular/index.js +395 -0
- package/dist/integrations/index.d.ts +15 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +18 -0
- package/dist/integrations/react/index.d.ts +75 -0
- package/dist/integrations/react/index.d.ts.map +1 -0
- package/dist/integrations/react/index.js +191 -0
- package/dist/integrations/vue/index.d.ts +103 -0
- package/dist/integrations/vue/index.d.ts.map +1 -0
- package/dist/integrations/vue/index.js +274 -0
- package/dist/ios/AGENTS.md +48 -0
- package/dist/ios/CLAUDE.md +84 -0
- package/dist/ios/Plugin/FilesystemStorage.swift +218 -0
- package/dist/ios/Plugin/KeychainStorage.swift +139 -50
- package/dist/ios/Plugin/SQLiteStorage.swift +279 -147
- package/dist/ios/Plugin/StrataStoragePlugin.m +23 -0
- package/dist/ios/Plugin/StrataStoragePlugin.swift +272 -65
- package/dist/package.json +21 -5
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2 -1
- package/dist/types/index.d.ts +58 -9
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -13
- package/dist/utils/errors.d.ts +7 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +15 -3
- package/dist/utils/index.d.ts +63 -5
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +109 -16
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +63 -0
- package/ios/AGENTS.md +48 -0
- package/ios/CLAUDE.md +84 -0
- package/ios/Plugin/FilesystemStorage.swift +218 -0
- package/ios/Plugin/KeychainStorage.swift +139 -50
- package/ios/Plugin/SQLiteStorage.swift +279 -147
- package/ios/Plugin/StrataStoragePlugin.m +23 -0
- package/ios/Plugin/StrataStoragePlugin.swift +272 -65
- package/package.json +31 -20
- package/scripts/build.js +16 -5
- package/scripts/configure.js +2 -6
- package/scripts/postinstall.js +2 -2
- package/Readme.md +0 -271
|
@@ -3,93 +3,159 @@ import Capacitor
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Main Capacitor plugin for Strata Storage
|
|
6
|
-
* Coordinates between different storage types on iOS
|
|
6
|
+
* Coordinates between different storage types on iOS.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: Method registration with the Capacitor bridge lives in
|
|
9
|
+
* StrataStoragePlugin.m (CAP_PLUGIN / CAP_PLUGIN_METHOD). Keep the two in
|
|
10
|
+
* sync when adding or renaming methods.
|
|
7
11
|
*/
|
|
8
12
|
@objc(StrataStoragePlugin)
|
|
9
13
|
public class StrataStoragePlugin: CAPPlugin {
|
|
10
14
|
private let userDefaultsStorage = UserDefaultsStorage()
|
|
11
15
|
private let keychainStorage = KeychainStorage()
|
|
12
|
-
private let
|
|
13
|
-
|
|
16
|
+
private let filesystemStorage = FilesystemStorage()
|
|
17
|
+
|
|
18
|
+
/// Storage types that have a real native backend on iOS.
|
|
19
|
+
private let supportedStorageTypes: Set<String> = ["preferences", "secure", "sqlite", "filesystem"]
|
|
20
|
+
|
|
21
|
+
// MARK: - SQLite multi-store helpers
|
|
22
|
+
|
|
23
|
+
/// Default database name (→ file `strata_storage.db`) and table.
|
|
24
|
+
private static let defaultDatabase = "strata_storage"
|
|
25
|
+
private static let defaultTable = "storage"
|
|
26
|
+
|
|
27
|
+
/// Sanitizes a database name into a safe `.db` filename. Strips everything
|
|
28
|
+
/// outside `[A-Za-z0-9_]` (which also blocks path separators / traversal),
|
|
29
|
+
/// then appends `.db`. Empty input falls back to the default. The filename
|
|
30
|
+
/// CANNOT be a bound parameter, so it must be sanitized before use.
|
|
31
|
+
private func sqliteFileName(from database: String?) -> String {
|
|
32
|
+
let raw = database ?? StrataStoragePlugin.defaultDatabase
|
|
33
|
+
let cleaned = String(raw.unicodeScalars.filter { CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").contains($0) })
|
|
34
|
+
let safe = cleaned.isEmpty ? StrataStoragePlugin.defaultDatabase : cleaned
|
|
35
|
+
return "\(safe).db"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Sanitizes a table name to a safe SQL identifier matching `^[A-Za-z0-9_]+$`.
|
|
39
|
+
/// Disallowed characters are removed; empty input falls back to the default.
|
|
40
|
+
/// The table name is interpolated into SQL (it cannot be bound), so this is
|
|
41
|
+
/// the single point that guarantees injection-safety for the identifier.
|
|
42
|
+
private func sqliteTableName(from table: String?) -> String {
|
|
43
|
+
let raw = table ?? StrataStoragePlugin.defaultTable
|
|
44
|
+
let cleaned = String(raw.unicodeScalars.filter { CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").contains($0) })
|
|
45
|
+
return cleaned.isEmpty ? StrataStoragePlugin.defaultTable : cleaned
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Returns the cached SQLite store for the call's `database` option.
|
|
49
|
+
private func sqliteStore(for call: CAPPluginCall) -> SQLiteStorage {
|
|
50
|
+
return SQLiteStorageManager.shared.store(forFile: sqliteFileName(from: call.getString("database")))
|
|
51
|
+
}
|
|
52
|
+
|
|
14
53
|
/**
|
|
15
|
-
* Check if storage is available
|
|
54
|
+
* Check if a specific storage type is available.
|
|
55
|
+
* Matches the JS contract: resolves `{ available: boolean }`.
|
|
16
56
|
*/
|
|
17
57
|
@objc func isAvailable(_ call: CAPPluginCall) {
|
|
58
|
+
let storage = call.getString("storage") ?? "preferences"
|
|
18
59
|
call.resolve([
|
|
19
|
-
"available":
|
|
20
|
-
"platform": "ios",
|
|
21
|
-
"adapters": [
|
|
22
|
-
"preferences": true,
|
|
23
|
-
"secure": true,
|
|
24
|
-
"sqlite": true,
|
|
25
|
-
"filesystem": true
|
|
26
|
-
]
|
|
60
|
+
"available": supportedStorageTypes.contains(storage)
|
|
27
61
|
])
|
|
28
62
|
}
|
|
29
|
-
|
|
63
|
+
|
|
30
64
|
/**
|
|
31
|
-
* Get value from storage
|
|
65
|
+
* Get value from storage.
|
|
66
|
+
* For sqlite/filesystem the resolved `value` is the full StorageValue
|
|
67
|
+
* wrapper object (or NSNull on a miss).
|
|
32
68
|
*/
|
|
33
69
|
@objc func get(_ call: CAPPluginCall) {
|
|
34
70
|
guard let key = call.getString("key") else {
|
|
35
71
|
call.reject("Key is required")
|
|
36
72
|
return
|
|
37
73
|
}
|
|
38
|
-
|
|
74
|
+
|
|
39
75
|
let storage = call.getString("storage") ?? "preferences"
|
|
40
|
-
|
|
76
|
+
|
|
41
77
|
do {
|
|
42
78
|
let value: Any?
|
|
43
|
-
|
|
79
|
+
|
|
44
80
|
switch storage {
|
|
45
81
|
case "secure":
|
|
46
82
|
value = try keychainStorage.get(key: key)
|
|
47
83
|
case "sqlite":
|
|
48
|
-
value =
|
|
84
|
+
value = sqliteStore(for: call).get(table: sqliteTableName(from: call.getString("table")), key: key)
|
|
85
|
+
case "filesystem":
|
|
86
|
+
value = filesystemStorage.get(key: key)
|
|
49
87
|
case "preferences":
|
|
50
88
|
fallthrough
|
|
51
89
|
default:
|
|
52
90
|
value = userDefaultsStorage.get(key: key)
|
|
53
91
|
}
|
|
54
|
-
|
|
92
|
+
|
|
55
93
|
call.resolve([
|
|
56
94
|
"value": value ?? NSNull()
|
|
57
95
|
])
|
|
58
96
|
} catch {
|
|
59
|
-
call.reject("Failed to get value", error
|
|
97
|
+
call.reject("Failed to get value", nil, error)
|
|
60
98
|
}
|
|
61
99
|
}
|
|
62
|
-
|
|
100
|
+
|
|
63
101
|
/**
|
|
64
|
-
* Set value in storage
|
|
102
|
+
* Set value in storage.
|
|
103
|
+
* For sqlite/filesystem the `value` option is the full StorageValue
|
|
104
|
+
* wrapper object and is stored verbatim (JSON) for a perfect round-trip.
|
|
65
105
|
*/
|
|
66
106
|
@objc func set(_ call: CAPPluginCall) {
|
|
67
107
|
guard let key = call.getString("key") else {
|
|
68
108
|
call.reject("Key is required")
|
|
69
109
|
return
|
|
70
110
|
}
|
|
71
|
-
|
|
72
|
-
let value = call.getValue("value") ?? NSNull()
|
|
111
|
+
|
|
73
112
|
let storage = call.getString("storage") ?? "preferences"
|
|
74
|
-
|
|
113
|
+
|
|
75
114
|
do {
|
|
76
115
|
switch storage {
|
|
77
116
|
case "secure":
|
|
78
|
-
|
|
117
|
+
let value = call.getValue("value") ?? NSNull()
|
|
118
|
+
_ = try keychainStorage.set(key: key, value: value)
|
|
79
119
|
case "sqlite":
|
|
80
|
-
|
|
120
|
+
// getObject returns JSObject ([String: JSValue]); upcast each
|
|
121
|
+
// value to Any (the canonical Capacitor pattern) so the bridged
|
|
122
|
+
// Foundation values are JSONSerialization-compatible.
|
|
123
|
+
guard let jsObject = call.getObject("value") else {
|
|
124
|
+
call.reject("SQLite value must be a StorageValue object")
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
let ok = try sqliteStore(for: call).set(
|
|
128
|
+
table: sqliteTableName(from: call.getString("table")),
|
|
129
|
+
key: key,
|
|
130
|
+
wrapper: jsObject as [String: Any]
|
|
131
|
+
)
|
|
132
|
+
if !ok {
|
|
133
|
+
call.reject("Failed to write value to SQLite")
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
case "filesystem":
|
|
137
|
+
guard let jsObject = call.getObject("value") else {
|
|
138
|
+
call.reject("Filesystem value must be a StorageValue object")
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
let ok = try filesystemStorage.set(key: key, wrapper: jsObject as [String: Any])
|
|
142
|
+
if !ok {
|
|
143
|
+
call.reject("Failed to write value to filesystem")
|
|
144
|
+
return
|
|
145
|
+
}
|
|
81
146
|
case "preferences":
|
|
82
147
|
fallthrough
|
|
83
148
|
default:
|
|
149
|
+
let value = call.getValue("value") ?? NSNull()
|
|
84
150
|
userDefaultsStorage.set(key: key, value: value)
|
|
85
151
|
}
|
|
86
|
-
|
|
152
|
+
|
|
87
153
|
call.resolve()
|
|
88
154
|
} catch {
|
|
89
|
-
call.reject("Failed to set value", error
|
|
155
|
+
call.reject("Failed to set value", nil, error)
|
|
90
156
|
}
|
|
91
157
|
}
|
|
92
|
-
|
|
158
|
+
|
|
93
159
|
/**
|
|
94
160
|
* Remove value from storage
|
|
95
161
|
*/
|
|
@@ -98,107 +164,248 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
98
164
|
call.reject("Key is required")
|
|
99
165
|
return
|
|
100
166
|
}
|
|
101
|
-
|
|
167
|
+
|
|
102
168
|
let storage = call.getString("storage") ?? "preferences"
|
|
103
|
-
|
|
169
|
+
|
|
104
170
|
do {
|
|
105
171
|
switch storage {
|
|
106
172
|
case "secure":
|
|
107
|
-
try keychainStorage.remove(key: key)
|
|
173
|
+
_ = try keychainStorage.remove(key: key)
|
|
108
174
|
case "sqlite":
|
|
109
|
-
|
|
175
|
+
_ = sqliteStore(for: call).remove(table: sqliteTableName(from: call.getString("table")), key: key)
|
|
176
|
+
case "filesystem":
|
|
177
|
+
_ = filesystemStorage.remove(key: key)
|
|
110
178
|
case "preferences":
|
|
111
179
|
fallthrough
|
|
112
180
|
default:
|
|
113
181
|
userDefaultsStorage.remove(key: key)
|
|
114
182
|
}
|
|
115
|
-
|
|
183
|
+
|
|
116
184
|
call.resolve()
|
|
117
185
|
} catch {
|
|
118
|
-
call.reject("Failed to remove value", error
|
|
186
|
+
call.reject("Failed to remove value", nil, error)
|
|
119
187
|
}
|
|
120
188
|
}
|
|
121
|
-
|
|
189
|
+
|
|
122
190
|
/**
|
|
123
|
-
* Clear storage
|
|
191
|
+
* Clear storage.
|
|
192
|
+
* The JS contract sends an optional `pattern`. The native backends accept
|
|
193
|
+
* a prefix; we use `pattern` as that prefix (prefix/contains matching).
|
|
124
194
|
*/
|
|
125
195
|
@objc func clear(_ call: CAPPluginCall) {
|
|
126
196
|
let storage = call.getString("storage") ?? "preferences"
|
|
127
|
-
|
|
128
|
-
|
|
197
|
+
// Accept both `pattern` (JS contract) and legacy `prefix`.
|
|
198
|
+
let prefix = call.getString("pattern") ?? call.getString("prefix")
|
|
199
|
+
|
|
129
200
|
do {
|
|
130
201
|
switch storage {
|
|
131
202
|
case "secure":
|
|
132
|
-
try keychainStorage.clear(prefix: prefix)
|
|
203
|
+
_ = try keychainStorage.clear(prefix: prefix)
|
|
133
204
|
case "sqlite":
|
|
134
|
-
|
|
205
|
+
_ = sqliteStore(for: call).clear(table: sqliteTableName(from: call.getString("table")), prefix: prefix)
|
|
206
|
+
case "filesystem":
|
|
207
|
+
_ = filesystemStorage.clear(prefix: prefix)
|
|
135
208
|
case "preferences":
|
|
136
209
|
fallthrough
|
|
137
210
|
default:
|
|
138
211
|
userDefaultsStorage.clear(prefix: prefix)
|
|
139
212
|
}
|
|
140
|
-
|
|
213
|
+
|
|
141
214
|
call.resolve()
|
|
142
215
|
} catch {
|
|
143
|
-
call.reject("Failed to clear storage", error
|
|
216
|
+
call.reject("Failed to clear storage", nil, error)
|
|
144
217
|
}
|
|
145
218
|
}
|
|
146
|
-
|
|
219
|
+
|
|
147
220
|
/**
|
|
148
221
|
* Get all keys from storage
|
|
149
222
|
*/
|
|
150
223
|
@objc func keys(_ call: CAPPluginCall) {
|
|
151
224
|
let storage = call.getString("storage") ?? "preferences"
|
|
152
225
|
let pattern = call.getString("pattern")
|
|
153
|
-
|
|
226
|
+
|
|
154
227
|
do {
|
|
155
228
|
let keys: [String]
|
|
156
|
-
|
|
229
|
+
|
|
157
230
|
switch storage {
|
|
158
231
|
case "secure":
|
|
159
232
|
keys = try keychainStorage.keys(pattern: pattern)
|
|
160
233
|
case "sqlite":
|
|
161
|
-
keys =
|
|
234
|
+
keys = sqliteStore(for: call).keys(table: sqliteTableName(from: call.getString("table")), pattern: pattern)
|
|
235
|
+
case "filesystem":
|
|
236
|
+
keys = filesystemStorage.keys(pattern: pattern)
|
|
162
237
|
case "preferences":
|
|
163
238
|
fallthrough
|
|
164
239
|
default:
|
|
165
240
|
keys = userDefaultsStorage.keys(pattern: pattern)
|
|
166
241
|
}
|
|
167
|
-
|
|
242
|
+
|
|
168
243
|
call.resolve([
|
|
169
244
|
"keys": keys
|
|
170
245
|
])
|
|
171
246
|
} catch {
|
|
172
|
-
call.reject("Failed to get keys", error
|
|
247
|
+
call.reject("Failed to get keys", nil, error)
|
|
173
248
|
}
|
|
174
249
|
}
|
|
175
|
-
|
|
250
|
+
|
|
176
251
|
/**
|
|
177
|
-
* Get storage size information
|
|
252
|
+
* Get storage size information.
|
|
253
|
+
* When `detailed` is true, resolves `{ total, count, detailed: { keys,
|
|
254
|
+
* values, metadata } }`; otherwise `{ total, count }`.
|
|
178
255
|
*/
|
|
179
256
|
@objc func size(_ call: CAPPluginCall) {
|
|
180
257
|
let storage = call.getString("storage") ?? "preferences"
|
|
181
|
-
|
|
258
|
+
let detailed = call.getBool("detailed") ?? false
|
|
259
|
+
|
|
182
260
|
do {
|
|
183
|
-
|
|
184
|
-
|
|
261
|
+
// segments carries total/count/keys/values/metadata in bytes.
|
|
262
|
+
let segments: [String: Int]
|
|
263
|
+
|
|
185
264
|
switch storage {
|
|
186
265
|
case "secure":
|
|
187
|
-
|
|
266
|
+
let info = try keychainStorage.size()
|
|
267
|
+
segments = ["total": info.total, "count": info.count, "keys": 0, "values": info.total, "metadata": 0]
|
|
188
268
|
case "sqlite":
|
|
189
|
-
|
|
269
|
+
segments = try sqliteStore(for: call).size(table: sqliteTableName(from: call.getString("table")))
|
|
270
|
+
case "filesystem":
|
|
271
|
+
segments = filesystemStorage.size()
|
|
190
272
|
case "preferences":
|
|
191
273
|
fallthrough
|
|
192
274
|
default:
|
|
193
|
-
|
|
275
|
+
let info = userDefaultsStorage.size()
|
|
276
|
+
segments = ["total": info.total, "count": info.count, "keys": 0, "values": info.total, "metadata": 0]
|
|
194
277
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
"total":
|
|
198
|
-
"count":
|
|
199
|
-
]
|
|
278
|
+
|
|
279
|
+
var result: [String: Any] = [
|
|
280
|
+
"total": segments["total"] ?? 0,
|
|
281
|
+
"count": segments["count"] ?? 0
|
|
282
|
+
]
|
|
283
|
+
if detailed {
|
|
284
|
+
result["detailed"] = [
|
|
285
|
+
"keys": segments["keys"] ?? 0,
|
|
286
|
+
"values": segments["values"] ?? 0,
|
|
287
|
+
"metadata": segments["metadata"] ?? 0
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
call.resolve(result)
|
|
291
|
+
} catch {
|
|
292
|
+
call.reject("Failed to get size", nil, error)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Query SQLite-backed storage.
|
|
298
|
+
* Matches the optional `query` method in the JS contract:
|
|
299
|
+
* resolves `{ results: [{ key }] }` (JS re-fetches each value via get).
|
|
300
|
+
*/
|
|
301
|
+
@objc func query(_ call: CAPPluginCall) {
|
|
302
|
+
let storage = call.getString("storage") ?? "sqlite"
|
|
303
|
+
|
|
304
|
+
guard storage == "sqlite" else {
|
|
305
|
+
call.reject("Query is only supported for the 'sqlite' storage type")
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let condition = call.getObject("condition") ?? [:]
|
|
310
|
+
let results = sqliteStore(for: call).query(
|
|
311
|
+
table: sqliteTableName(from: call.getString("table")),
|
|
312
|
+
condition: condition
|
|
313
|
+
)
|
|
314
|
+
call.resolve([
|
|
315
|
+
"results": results
|
|
316
|
+
])
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* iOS-specific: read a UserDefaults value.
|
|
321
|
+
* Resolves `{ value: unknown }`.
|
|
322
|
+
*/
|
|
323
|
+
@objc func getUserDefaults(_ call: CAPPluginCall) {
|
|
324
|
+
guard let key = call.getString("key") else {
|
|
325
|
+
call.reject("Key is required")
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
let suiteName = call.getString("suiteName")
|
|
329
|
+
let store = suiteName != nil ? UserDefaultsStorage(suiteName: suiteName) : userDefaultsStorage
|
|
330
|
+
call.resolve([
|
|
331
|
+
"value": store.get(key: key) ?? NSNull()
|
|
332
|
+
])
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* iOS-specific: write a UserDefaults value.
|
|
337
|
+
*/
|
|
338
|
+
@objc func setUserDefaults(_ call: CAPPluginCall) {
|
|
339
|
+
guard let key = call.getString("key") else {
|
|
340
|
+
call.reject("Key is required")
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
let value = call.getValue("value") ?? NSNull()
|
|
344
|
+
let suiteName = call.getString("suiteName")
|
|
345
|
+
let store = suiteName != nil ? UserDefaultsStorage(suiteName: suiteName) : userDefaultsStorage
|
|
346
|
+
_ = store.set(key: key, value: value)
|
|
347
|
+
call.resolve()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* iOS-specific: read a Keychain value. Resolves `{ value: string | null }`.
|
|
352
|
+
*/
|
|
353
|
+
@objc func getKeychain(_ call: CAPPluginCall) {
|
|
354
|
+
guard let key = call.getString("key") else {
|
|
355
|
+
call.reject("Key is required")
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
let service = call.getString("service")
|
|
359
|
+
let accessGroup = call.getString("accessGroup")
|
|
360
|
+
let store = (service != nil || accessGroup != nil)
|
|
361
|
+
? KeychainStorage(service: service, accessGroup: accessGroup)
|
|
362
|
+
: keychainStorage
|
|
363
|
+
|
|
364
|
+
do {
|
|
365
|
+
let value = try store.get(key: key)
|
|
366
|
+
// The JS contract types this as `string | null`. Serialize
|
|
367
|
+
// non-string values to a JSON string so the bridge stays typed.
|
|
368
|
+
if let value = value {
|
|
369
|
+
if let str = value as? String {
|
|
370
|
+
call.resolve(["value": str])
|
|
371
|
+
} else if let data = try? JSONSerialization.data(withJSONObject: value, options: []),
|
|
372
|
+
let str = String(data: data, encoding: .utf8) {
|
|
373
|
+
call.resolve(["value": str])
|
|
374
|
+
} else {
|
|
375
|
+
call.resolve(["value": NSNull()])
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
call.resolve(["value": NSNull()])
|
|
379
|
+
}
|
|
380
|
+
} catch {
|
|
381
|
+
call.reject("Failed to read keychain item", nil, error)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* iOS-specific: write a Keychain value, honoring the `accessible` option.
|
|
387
|
+
*/
|
|
388
|
+
@objc func setKeychain(_ call: CAPPluginCall) {
|
|
389
|
+
guard let key = call.getString("key") else {
|
|
390
|
+
call.reject("Key is required")
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
guard let value = call.getString("value") else {
|
|
394
|
+
call.reject("Value is required and must be a string")
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
let service = call.getString("service")
|
|
398
|
+
let accessGroup = call.getString("accessGroup")
|
|
399
|
+
let accessible = call.getString("accessible")
|
|
400
|
+
let store = (service != nil || accessGroup != nil)
|
|
401
|
+
? KeychainStorage(service: service, accessGroup: accessGroup)
|
|
402
|
+
: keychainStorage
|
|
403
|
+
|
|
404
|
+
do {
|
|
405
|
+
_ = try store.set(key: key, value: value, accessible: accessible)
|
|
406
|
+
call.resolve()
|
|
200
407
|
} catch {
|
|
201
|
-
call.reject("Failed to
|
|
408
|
+
call.reject("Failed to write keychain item", nil, error)
|
|
202
409
|
}
|
|
203
410
|
}
|
|
204
|
-
}
|
|
411
|
+
}
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strata-storage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
"./firebase": {
|
|
18
18
|
"types": "./firebase.d.ts",
|
|
19
19
|
"default": "./firebase.js"
|
|
20
|
+
},
|
|
21
|
+
"./react": {
|
|
22
|
+
"types": "./integrations/react/index.d.ts",
|
|
23
|
+
"default": "./integrations/react/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./vue": {
|
|
26
|
+
"types": "./integrations/vue/index.d.ts",
|
|
27
|
+
"default": "./integrations/vue/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./angular": {
|
|
30
|
+
"types": "./integrations/angular/index.d.ts",
|
|
31
|
+
"default": "./integrations/angular/index.js"
|
|
20
32
|
}
|
|
21
33
|
},
|
|
22
34
|
"author": "Ahsan Mahmood",
|
|
@@ -45,11 +57,18 @@
|
|
|
45
57
|
],
|
|
46
58
|
"peerDependencies": {
|
|
47
59
|
"@angular/core": ">=21.0.6",
|
|
60
|
+
"@angular/forms": ">=21.0.6",
|
|
48
61
|
"@capacitor/core": ">=8.0.0",
|
|
49
62
|
"react": ">=19.2.3",
|
|
50
63
|
"vue": ">=3.5.26"
|
|
51
64
|
},
|
|
52
65
|
"peerDependenciesMeta": {
|
|
66
|
+
"@angular/core": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"@angular/forms": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
53
72
|
"@capacitor/core": {
|
|
54
73
|
"optional": true
|
|
55
74
|
},
|
|
@@ -58,9 +77,6 @@
|
|
|
58
77
|
},
|
|
59
78
|
"vue": {
|
|
60
79
|
"optional": true
|
|
61
|
-
},
|
|
62
|
-
"@angular/core": {
|
|
63
|
-
"optional": true
|
|
64
80
|
}
|
|
65
81
|
},
|
|
66
82
|
"capacitor": {
|
|
@@ -74,6 +90,6 @@
|
|
|
74
90
|
},
|
|
75
91
|
"sideEffects": false,
|
|
76
92
|
"engines": {
|
|
77
|
-
"node": ">=
|
|
93
|
+
"node": ">=24.13.0"
|
|
78
94
|
}
|
|
79
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,mBAAmB,EASpB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,mBAAmB,EASpB,MAAM,eAAe,CAAC;AA+IvB,eAAO,MAAM,aAAa,EAAE,mBAAmD,CAAC;AAEhF,cAAc,eAAe,CAAC"}
|
package/dist/plugin/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This is now optional and only loaded when Capacitor adapters are used
|
|
5
5
|
*/
|
|
6
6
|
import { NotSupportedError } from "../utils/errors.js";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
7
8
|
// Mock implementation for when Capacitor is not available
|
|
8
9
|
const mockPlugin = {
|
|
9
10
|
isAvailable: async () => ({ available: false }),
|
|
@@ -56,7 +57,7 @@ class LazyStrataStoragePlugin {
|
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
catch (error) {
|
|
59
|
-
|
|
60
|
+
logger.warn('Failed to register StrataStorage plugin:', error);
|
|
60
61
|
this.plugin = mockPlugin;
|
|
61
62
|
}
|
|
62
63
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Supported storage types across all platforms
|
|
7
7
|
*/
|
|
8
|
-
export type StorageType = 'memory' | 'localStorage' | 'sessionStorage' | 'indexedDB' | 'cookies' | 'cache' | 'preferences' | 'sqlite' | 'filesystem' | 'secure';
|
|
8
|
+
export type StorageType = 'memory' | 'localStorage' | 'sessionStorage' | 'indexedDB' | 'cookies' | 'cache' | 'url' | 'preferences' | 'sqlite' | 'filesystem' | 'secure';
|
|
9
9
|
/**
|
|
10
10
|
* Platform types
|
|
11
11
|
*/
|
|
@@ -75,6 +75,20 @@ export interface StorageOptions {
|
|
|
75
75
|
* Metadata to attach
|
|
76
76
|
*/
|
|
77
77
|
metadata?: Record<string, unknown>;
|
|
78
|
+
/**
|
|
79
|
+
* Compute + store an integrity checksum for this write and verify it on read.
|
|
80
|
+
*/
|
|
81
|
+
verify?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Durable write — read the value back and verify it after writing, retrying
|
|
84
|
+
* on mismatch. Guards against silent partial writes.
|
|
85
|
+
*/
|
|
86
|
+
durable?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* On a checksum mismatch during read, return null instead of throwing
|
|
89
|
+
* IntegrityError (after mirror read-repair has been attempted).
|
|
90
|
+
*/
|
|
91
|
+
ignoreCorruption?: boolean;
|
|
78
92
|
}
|
|
79
93
|
/**
|
|
80
94
|
* Storage value with metadata
|
|
@@ -112,6 +126,11 @@ export interface StorageValue<T = unknown> {
|
|
|
112
126
|
* Whether value is compressed
|
|
113
127
|
*/
|
|
114
128
|
compressed?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Integrity checksum (FNV-1a) over the stored value, present when integrity
|
|
131
|
+
* is enabled. Verified on read to detect corruption.
|
|
132
|
+
*/
|
|
133
|
+
checksum?: string;
|
|
115
134
|
}
|
|
116
135
|
/**
|
|
117
136
|
* Storage change event
|
|
@@ -390,6 +409,32 @@ export interface StrataConfig {
|
|
|
390
409
|
*/
|
|
391
410
|
onExpire?: (keys: string[]) => void;
|
|
392
411
|
};
|
|
412
|
+
/**
|
|
413
|
+
* Data integrity — compute + verify an FNV-1a checksum on every value so
|
|
414
|
+
* corruption is detected on read. Off by default.
|
|
415
|
+
*/
|
|
416
|
+
integrity?: boolean;
|
|
417
|
+
/**
|
|
418
|
+
* Durable writes — read back and verify every write, retrying on mismatch.
|
|
419
|
+
* Off by default (adds one read per write).
|
|
420
|
+
*/
|
|
421
|
+
durableWrites?: boolean;
|
|
422
|
+
/**
|
|
423
|
+
* Mirror every write/remove to these backup storage types. On a primary read
|
|
424
|
+
* miss or corruption, the value is recovered from a mirror (read-repair).
|
|
425
|
+
*/
|
|
426
|
+
mirror?: StorageType[];
|
|
427
|
+
/**
|
|
428
|
+
* Periodic automatic snapshot of all data to a durable storage type.
|
|
429
|
+
*/
|
|
430
|
+
autoBackup?: {
|
|
431
|
+
/** Interval in milliseconds between snapshots. */
|
|
432
|
+
interval: number;
|
|
433
|
+
/** Storage type to write the snapshot to. */
|
|
434
|
+
storage: StorageType;
|
|
435
|
+
/** Key to store the snapshot under (default `__strata_backup__`). */
|
|
436
|
+
key?: string;
|
|
437
|
+
};
|
|
393
438
|
/**
|
|
394
439
|
* Debug configuration
|
|
395
440
|
*/
|
|
@@ -460,6 +505,18 @@ export interface StorageAdapter {
|
|
|
460
505
|
* Get all keys
|
|
461
506
|
*/
|
|
462
507
|
keys(pattern?: string | RegExp): Promise<string[]>;
|
|
508
|
+
/** Synchronous get — sync-capable adapters only. */
|
|
509
|
+
getSync?<T = unknown>(key: string): StorageValue<T> | null;
|
|
510
|
+
/** Synchronous set — sync-capable adapters only. */
|
|
511
|
+
setSync?<T = unknown>(key: string, value: StorageValue<T>): void;
|
|
512
|
+
/** Synchronous remove — sync-capable adapters only. */
|
|
513
|
+
removeSync?(key: string): void;
|
|
514
|
+
/** Synchronous existence check — sync-capable adapters only. */
|
|
515
|
+
hasSync?(key: string): boolean;
|
|
516
|
+
/** Synchronous keys — sync-capable adapters only. */
|
|
517
|
+
keysSync?(pattern?: string | RegExp): string[];
|
|
518
|
+
/** Synchronous clear — sync-capable adapters only. */
|
|
519
|
+
clearSync?(options?: ClearOptions): void;
|
|
463
520
|
/**
|
|
464
521
|
* Get storage size
|
|
465
522
|
*/
|
|
@@ -695,14 +752,6 @@ export interface StorageEvent {
|
|
|
695
752
|
*/
|
|
696
753
|
timestamp: number;
|
|
697
754
|
}
|
|
698
|
-
/**
|
|
699
|
-
* Storage error class
|
|
700
|
-
*/
|
|
701
|
-
export declare class StorageError extends Error {
|
|
702
|
-
code?: string | undefined;
|
|
703
|
-
details?: unknown | undefined;
|
|
704
|
-
constructor(message: string, code?: string | undefined, details?: unknown | undefined);
|
|
705
|
-
}
|
|
706
755
|
/**
|
|
707
756
|
* Storage metadata
|
|
708
757
|
*/
|