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/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": "dist/index.js",
|
|
@@ -13,9 +13,7 @@
|
|
|
13
13
|
"StrataStorage.podspec",
|
|
14
14
|
"AI-INTEGRATION-GUIDE.md"
|
|
15
15
|
],
|
|
16
|
-
"bin":
|
|
17
|
-
"strata-storage": "./scripts/cli.js"
|
|
18
|
-
},
|
|
16
|
+
"bin": "./scripts/cli.js",
|
|
19
17
|
"exports": {
|
|
20
18
|
".": {
|
|
21
19
|
"types": "./dist/index.d.ts",
|
|
@@ -82,11 +80,18 @@
|
|
|
82
80
|
"homepage": "https://github.com/aoneahsan/strata-storage#readme",
|
|
83
81
|
"peerDependencies": {
|
|
84
82
|
"@angular/core": ">=21.0.6",
|
|
83
|
+
"@angular/forms": ">=21.0.6",
|
|
85
84
|
"@capacitor/core": ">=8.0.0",
|
|
86
85
|
"react": ">=19.2.3",
|
|
87
86
|
"vue": ">=3.5.26"
|
|
88
87
|
},
|
|
89
88
|
"peerDependenciesMeta": {
|
|
89
|
+
"@angular/core": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"@angular/forms": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
90
95
|
"@capacitor/core": {
|
|
91
96
|
"optional": true
|
|
92
97
|
},
|
|
@@ -95,28 +100,33 @@
|
|
|
95
100
|
},
|
|
96
101
|
"vue": {
|
|
97
102
|
"optional": true
|
|
98
|
-
},
|
|
99
|
-
"@angular/core": {
|
|
100
|
-
"optional": true
|
|
101
103
|
}
|
|
102
104
|
},
|
|
103
105
|
"devDependencies": {
|
|
104
|
-
"@
|
|
105
|
-
"@
|
|
106
|
-
"@
|
|
107
|
-
"@
|
|
108
|
-
"@
|
|
109
|
-
"
|
|
106
|
+
"@angular/common": "^21.2.14",
|
|
107
|
+
"@angular/core": "^21.2.14",
|
|
108
|
+
"@angular/forms": "^21.2.14",
|
|
109
|
+
"@capacitor/core": "^8.3.4",
|
|
110
|
+
"@types/node": "^25.9.1",
|
|
111
|
+
"@types/react": "^19.2.15",
|
|
112
|
+
"@typescript-eslint/eslint-plugin": "^8.60.0",
|
|
113
|
+
"@typescript-eslint/parser": "^8.60.0",
|
|
114
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
115
|
+
"eslint": "^10.4.0",
|
|
110
116
|
"eslint-config-prettier": "^10.1.8",
|
|
111
117
|
"eslint-plugin-prettier": "^5.5.5",
|
|
112
|
-
"jsdom": "^
|
|
113
|
-
"prettier": "^3.8.
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
118
|
+
"jsdom": "^29.1.1",
|
|
119
|
+
"prettier": "^3.8.3",
|
|
120
|
+
"react": "^19.2.6",
|
|
121
|
+
"rxjs": "^7.8.2",
|
|
122
|
+
"typescript": "^6.0.3",
|
|
123
|
+
"typescript-eslint": "^8.60.0",
|
|
124
|
+
"vitest": "^4.1.7",
|
|
125
|
+
"vue": "^3.5.34",
|
|
126
|
+
"zone.js": "~0.16.0"
|
|
117
127
|
},
|
|
118
128
|
"engines": {
|
|
119
|
-
"node": ">=
|
|
129
|
+
"node": ">=24.13.0"
|
|
120
130
|
},
|
|
121
131
|
"capacitor": {
|
|
122
132
|
"ios": {
|
|
@@ -126,5 +136,6 @@
|
|
|
126
136
|
"src": "android",
|
|
127
137
|
"androidModule": "strata-storage"
|
|
128
138
|
}
|
|
129
|
-
}
|
|
139
|
+
},
|
|
140
|
+
"packageManager": "yarn@4.14.1"
|
|
130
141
|
}
|
package/scripts/build.js
CHANGED
|
@@ -26,7 +26,7 @@ fs.mkdirSync(distDir, { recursive: true });
|
|
|
26
26
|
// Compile TypeScript to ESM
|
|
27
27
|
console.log('📦 Building ES Modules...');
|
|
28
28
|
try {
|
|
29
|
-
execSync('
|
|
29
|
+
execSync('yarn exec tsc', { stdio: 'inherit', cwd: rootDir });
|
|
30
30
|
} catch (error) {
|
|
31
31
|
console.error('❌ Build failed');
|
|
32
32
|
process.exit(1);
|
|
@@ -141,6 +141,18 @@ const distPackageJson = {
|
|
|
141
141
|
'./firebase': {
|
|
142
142
|
types: './firebase.d.ts',
|
|
143
143
|
default: './firebase.js'
|
|
144
|
+
},
|
|
145
|
+
'./react': {
|
|
146
|
+
types: './integrations/react/index.d.ts',
|
|
147
|
+
default: './integrations/react/index.js'
|
|
148
|
+
},
|
|
149
|
+
'./vue': {
|
|
150
|
+
types: './integrations/vue/index.d.ts',
|
|
151
|
+
default: './integrations/vue/index.js'
|
|
152
|
+
},
|
|
153
|
+
'./angular': {
|
|
154
|
+
types: './integrations/angular/index.d.ts',
|
|
155
|
+
default: './integrations/angular/index.js'
|
|
144
156
|
}
|
|
145
157
|
},
|
|
146
158
|
author: packageJson.author,
|
|
@@ -151,9 +163,8 @@ const distPackageJson = {
|
|
|
151
163
|
peerDependenciesMeta: packageJson.peerDependenciesMeta,
|
|
152
164
|
capacitor: packageJson.capacitor,
|
|
153
165
|
sideEffects: false,
|
|
154
|
-
engines
|
|
155
|
-
|
|
156
|
-
}
|
|
166
|
+
// Mirror the root engines so the published manifest never drifts from it.
|
|
167
|
+
engines: packageJson.engines || { node: '>=18.0.0' }
|
|
157
168
|
};
|
|
158
169
|
|
|
159
170
|
fs.writeFileSync(
|
|
@@ -162,4 +173,4 @@ fs.writeFileSync(
|
|
|
162
173
|
);
|
|
163
174
|
|
|
164
175
|
console.log('✅ Build completed successfully!');
|
|
165
|
-
console.log(`📂 Output: ${distDir}`);
|
|
176
|
+
console.log(`📂 Output: ${distDir}`);
|
package/scripts/configure.js
CHANGED
|
@@ -399,11 +399,7 @@ async function installDependencies(config) {
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
// Install command
|
|
402
|
-
const command =
|
|
403
|
-
? `yarn add ${deps.join(' ')}`
|
|
404
|
-
: config.packageManager === 'pnpm'
|
|
405
|
-
? `pnpm add ${deps.join(' ')}`
|
|
406
|
-
: `npm install ${deps.join(' ')}`;
|
|
402
|
+
const command = `yarn add ${deps.join(' ')}`;
|
|
407
403
|
|
|
408
404
|
try {
|
|
409
405
|
log.info(`Running: ${command}`);
|
|
@@ -445,4 +441,4 @@ configure().catch((error) => {
|
|
|
445
441
|
log.error('Configuration failed:');
|
|
446
442
|
console.error(error);
|
|
447
443
|
process.exit(1);
|
|
448
|
-
});
|
|
444
|
+
});
|
package/scripts/postinstall.js
CHANGED
|
@@ -30,7 +30,7 @@ if (isCapacitorProject) {
|
|
|
30
30
|
console.log('📱 Capacitor Support Available (Optional):');
|
|
31
31
|
console.log(' import { registerCapacitorAdapters } from "strata-storage/capacitor";');
|
|
32
32
|
console.log(' await registerCapacitorAdapters(storage);');
|
|
33
|
-
console.log(' Run "
|
|
33
|
+
console.log(' Run "yarn cap sync" to sync native code\n');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
console.log('✨ Features:');
|
|
@@ -41,4 +41,4 @@ console.log(' • Built-in encryption & compression');
|
|
|
41
41
|
console.log(' • Cross-tab synchronization\n');
|
|
42
42
|
|
|
43
43
|
console.log('📖 Documentation: https://github.com/aoneahsan/strata-storage');
|
|
44
|
-
console.log('⭐ Star us on GitHub: https://github.com/aoneahsan/strata-storage\n');
|
|
44
|
+
console.log('⭐ Star us on GitHub: https://github.com/aoneahsan/strata-storage\n');
|