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.
- package/AI-INTEGRATION-GUIDE.md +129 -11
- package/README.md +42 -64
- package/android/AGENTS.md +18 -3
- package/android/CLAUDE.md +36 -3
- package/android/build.gradle +5 -1
- package/android/src/main/java/com/strata/storage/EncryptedStorage.java +31 -18
- package/android/src/main/java/com/strata/storage/FilesystemStorage.java +62 -10
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +65 -13
- package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +63 -51
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +80 -11
- package/dist/README.md +42 -64
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts +2 -2
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/FilesystemAdapter.js +6 -2
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts +2 -2
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/PreferencesAdapter.js +6 -2
- package/dist/adapters/capacitor/SecureAdapter.d.ts +3 -3
- package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SecureAdapter.js +6 -2
- package/dist/adapters/capacitor/SqliteAdapter.d.ts +24 -3
- package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SqliteAdapter.js +105 -23
- package/dist/adapters/capacitor/index.d.ts +4 -4
- package/dist/adapters/web/CacheAdapter.d.ts +2 -2
- package/dist/adapters/web/CacheAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CacheAdapter.js +20 -5
- package/dist/adapters/web/CookieAdapter.d.ts +2 -2
- package/dist/adapters/web/CookieAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CookieAdapter.js +27 -3
- package/dist/adapters/web/IndexedDBAdapter.d.ts +10 -3
- package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
- package/dist/adapters/web/IndexedDBAdapter.js +19 -4
- package/dist/adapters/web/LocalStorageAdapter.d.ts +2 -2
- package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/LocalStorageAdapter.js +15 -1
- package/dist/adapters/web/MemoryAdapter.d.ts +9 -2
- package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
- package/dist/adapters/web/MemoryAdapter.js +22 -5
- package/dist/adapters/web/SessionStorageAdapter.d.ts +10 -10
- package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/SessionStorageAdapter.js +17 -3
- package/dist/adapters/web/URLAdapter.d.ts +2 -2
- package/dist/adapters/web/index.d.ts +7 -7
- package/dist/capacitor.d.ts +6 -6
- package/dist/capacitor.d.ts.map +1 -1
- package/dist/capacitor.js +5 -4
- package/dist/core/AdapterRegistry.d.ts +1 -1
- package/dist/core/BaseAdapter.d.ts +21 -5
- package/dist/core/BaseAdapter.d.ts.map +1 -1
- package/dist/core/BaseAdapter.js +29 -1
- package/dist/core/Strata.d.ts +53 -7
- package/dist/core/Strata.d.ts.map +1 -1
- package/dist/core/Strata.js +581 -125
- package/dist/features/compression/index.d.ts +1 -1
- package/dist/features/compression.d.ts +28 -9
- package/dist/features/compression.d.ts.map +1 -1
- package/dist/features/compression.js +164 -77
- package/dist/features/encryption/index.d.ts +1 -1
- package/dist/features/encryption.d.ts +55 -2
- package/dist/features/encryption.d.ts.map +1 -1
- package/dist/features/encryption.js +139 -26
- package/dist/features/migration.d.ts +1 -1
- package/dist/features/migration.d.ts.map +1 -1
- package/dist/features/migration.js +7 -2
- package/dist/features/observer/index.d.ts +1 -1
- package/dist/features/observer.d.ts +1 -1
- package/dist/features/query/index.d.ts +1 -1
- package/dist/features/query.d.ts +2 -1
- package/dist/features/query.d.ts.map +1 -1
- package/dist/features/query.js +32 -7
- package/dist/features/sync/index.d.ts +1 -1
- package/dist/features/sync.d.ts +19 -3
- package/dist/features/sync.d.ts.map +1 -1
- package/dist/features/sync.js +75 -12
- package/dist/features/ttl/index.d.ts +1 -1
- package/dist/features/ttl.d.ts +2 -2
- package/dist/features/ttl.d.ts.map +1 -1
- package/dist/features/ttl.js +4 -2
- package/dist/firebase.d.ts +1 -1
- package/dist/firebase.d.ts.map +1 -1
- package/dist/firebase.js +72 -10
- package/dist/index.d.ts +24 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/integrations/angular/index.d.ts +2 -2
- package/dist/integrations/angular/index.d.ts.map +1 -1
- package/dist/integrations/angular/index.js +7 -8
- package/dist/integrations/react/index.d.ts +3 -3
- package/dist/integrations/react/index.d.ts.map +1 -1
- package/dist/integrations/react/index.js +16 -4
- package/dist/integrations/vue/index.d.ts +2 -2
- package/dist/integrations/vue/index.d.ts.map +1 -1
- package/dist/integrations/vue/index.js +5 -2
- package/dist/package.json +11 -2
- package/dist/plugin/definitions.d.ts +29 -3
- package/dist/plugin/definitions.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +2 -2
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +3 -0
- package/dist/plugin/web.d.ts +11 -2
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +23 -4
- package/dist/types/index.d.ts +50 -20
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -10
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +7 -0
- package/ios/AGENTS.md +14 -3
- package/ios/CLAUDE.md +25 -3
- package/ios/Plugin/FilesystemStorage.swift +19 -0
- package/ios/Plugin/SQLiteStorage.swift +59 -9
- package/ios/Plugin/StrataStoragePlugin.m +1 -0
- package/ios/Plugin/StrataStoragePlugin.swift +87 -34
- package/ios/Plugin/UserDefaultsStorage.swift +47 -46
- package/package.json +27 -24
- package/scripts/build.js +46 -27
- package/scripts/cli.js +1 -1
- package/scripts/configure.js +2 -2
- package/scripts/postinstall.js +3 -2
- package/dist/android/AGENTS.md +0 -51
- package/dist/android/CLAUDE.md +0 -89
- package/dist/android/build.gradle +0 -58
- package/dist/android/proguard-rules.pro +0 -11
- package/dist/android/settings.gradle +0 -2
- package/dist/android/src/main/AndroidManifest.xml +0 -2
- package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +0 -218
- package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +0 -287
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +0 -371
- package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +0 -209
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +0 -616
- package/dist/core/StorageStrategy.d.ts +0 -55
- package/dist/core/StorageStrategy.d.ts.map +0 -1
- package/dist/core/StorageStrategy.js +0 -200
- package/dist/ios/AGENTS.md +0 -48
- package/dist/ios/CLAUDE.md +0 -84
- package/dist/ios/Plugin/FilesystemStorage.swift +0 -218
- package/dist/ios/Plugin/KeychainStorage.swift +0 -233
- package/dist/ios/Plugin/SQLiteStorage.swift +0 -426
- package/dist/ios/Plugin/StrataStoragePlugin.m +0 -23
- package/dist/ios/Plugin/StrataStoragePlugin.swift +0 -411
- package/dist/ios/Plugin/UserDefaultsStorage.swift +0 -87
|
@@ -24,30 +24,44 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
24
24
|
private static let defaultDatabase = "strata_storage"
|
|
25
25
|
private static let defaultTable = "storage"
|
|
26
26
|
|
|
27
|
-
///
|
|
28
|
-
///
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
/// Allowed characters for a SQLite identifier body (the first character must
|
|
28
|
+
/// additionally not be a digit).
|
|
29
|
+
private static let sqliteIdentifierChars =
|
|
30
|
+
CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_")
|
|
31
|
+
|
|
32
|
+
/// Validates a `database`/`table` identifier against `^[A-Za-z_][A-Za-z0-9_]*$`.
|
|
33
|
+
/// These two cannot be bound as parameters (they are interpolated into SQL /
|
|
34
|
+
/// the `.db` filename), so they are the single injection surface; every
|
|
35
|
+
/// other value is bound. Non-conforming identifiers are REJECTED rather than
|
|
36
|
+
/// silently stripped — the TS adapter applies the same allow-list and rejects
|
|
37
|
+
/// first, so this is defense-in-depth (and also blocks path traversal).
|
|
38
|
+
private func isValidSqliteIdentifier(_ s: String) -> Bool {
|
|
39
|
+
guard let first = s.unicodeScalars.first else { return false }
|
|
40
|
+
if CharacterSet(charactersIn: "0123456789").contains(first) { return false }
|
|
41
|
+
return s.unicodeScalars.allSatisfy { StrataStoragePlugin.sqliteIdentifierChars.contains($0) }
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
///
|
|
39
|
-
///
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
/// Returns the validated table identifier for the call, or nil after
|
|
45
|
+
/// rejecting `call` with a clear error.
|
|
46
|
+
private func validatedTable(_ call: CAPPluginCall) -> String? {
|
|
47
|
+
let table = call.getString("table") ?? StrataStoragePlugin.defaultTable
|
|
48
|
+
guard isValidSqliteIdentifier(table) else {
|
|
49
|
+
call.reject("Invalid SQLite table name: \(table)")
|
|
50
|
+
return nil
|
|
51
|
+
}
|
|
52
|
+
return table
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
/// Returns the cached SQLite store for the call's `database
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
/// Returns the cached SQLite store for the call's validated `database`, or
|
|
56
|
+
/// nil after rejecting `call`. The filename cannot be a bound parameter, so
|
|
57
|
+
/// the name is validated before `.db` is appended.
|
|
58
|
+
private func validatedSqliteStore(for call: CAPPluginCall) -> SQLiteStorage? {
|
|
59
|
+
let database = call.getString("database") ?? StrataStoragePlugin.defaultDatabase
|
|
60
|
+
guard isValidSqliteIdentifier(database) else {
|
|
61
|
+
call.reject("Invalid SQLite database name: \(database)")
|
|
62
|
+
return nil
|
|
63
|
+
}
|
|
64
|
+
return SQLiteStorageManager.shared.store(forFile: "\(database).db")
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/**
|
|
@@ -56,8 +70,21 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
56
70
|
*/
|
|
57
71
|
@objc func isAvailable(_ call: CAPPluginCall) {
|
|
58
72
|
let storage = call.getString("storage") ?? "preferences"
|
|
73
|
+
let available: Bool
|
|
74
|
+
switch storage {
|
|
75
|
+
case "sqlite":
|
|
76
|
+
let database = call.getString("database") ?? StrataStoragePlugin.defaultDatabase
|
|
77
|
+
available = isValidSqliteIdentifier(database)
|
|
78
|
+
&& SQLiteStorageManager.shared.store(forFile: "\(database).db").isOpen
|
|
79
|
+
case "filesystem":
|
|
80
|
+
available = filesystemStorage.isAvailable()
|
|
81
|
+
case "preferences", "secure":
|
|
82
|
+
available = true
|
|
83
|
+
default:
|
|
84
|
+
available = false
|
|
85
|
+
}
|
|
59
86
|
call.resolve([
|
|
60
|
-
"available":
|
|
87
|
+
"available": available
|
|
61
88
|
])
|
|
62
89
|
}
|
|
63
90
|
|
|
@@ -81,7 +108,8 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
81
108
|
case "secure":
|
|
82
109
|
value = try keychainStorage.get(key: key)
|
|
83
110
|
case "sqlite":
|
|
84
|
-
|
|
111
|
+
guard let store = validatedSqliteStore(for: call), let table = validatedTable(call) else { return }
|
|
112
|
+
value = store.get(table: table, key: key)
|
|
85
113
|
case "filesystem":
|
|
86
114
|
value = filesystemStorage.get(key: key)
|
|
87
115
|
case "preferences":
|
|
@@ -117,6 +145,7 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
117
145
|
let value = call.getValue("value") ?? NSNull()
|
|
118
146
|
_ = try keychainStorage.set(key: key, value: value)
|
|
119
147
|
case "sqlite":
|
|
148
|
+
guard let store = validatedSqliteStore(for: call), let table = validatedTable(call) else { return }
|
|
120
149
|
// getObject returns JSObject ([String: JSValue]); upcast each
|
|
121
150
|
// value to Any (the canonical Capacitor pattern) so the bridged
|
|
122
151
|
// Foundation values are JSONSerialization-compatible.
|
|
@@ -124,8 +153,8 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
124
153
|
call.reject("SQLite value must be a StorageValue object")
|
|
125
154
|
return
|
|
126
155
|
}
|
|
127
|
-
let ok = try
|
|
128
|
-
table:
|
|
156
|
+
let ok = try store.set(
|
|
157
|
+
table: table,
|
|
129
158
|
key: key,
|
|
130
159
|
wrapper: jsObject as [String: Any]
|
|
131
160
|
)
|
|
@@ -172,7 +201,8 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
172
201
|
case "secure":
|
|
173
202
|
_ = try keychainStorage.remove(key: key)
|
|
174
203
|
case "sqlite":
|
|
175
|
-
|
|
204
|
+
guard let store = validatedSqliteStore(for: call), let table = validatedTable(call) else { return }
|
|
205
|
+
_ = store.remove(table: table, key: key)
|
|
176
206
|
case "filesystem":
|
|
177
207
|
_ = filesystemStorage.remove(key: key)
|
|
178
208
|
case "preferences":
|
|
@@ -202,7 +232,8 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
202
232
|
case "secure":
|
|
203
233
|
_ = try keychainStorage.clear(prefix: prefix)
|
|
204
234
|
case "sqlite":
|
|
205
|
-
|
|
235
|
+
guard let store = validatedSqliteStore(for: call), let table = validatedTable(call) else { return }
|
|
236
|
+
_ = store.clear(table: table, prefix: prefix)
|
|
206
237
|
case "filesystem":
|
|
207
238
|
_ = filesystemStorage.clear(prefix: prefix)
|
|
208
239
|
case "preferences":
|
|
@@ -231,7 +262,8 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
231
262
|
case "secure":
|
|
232
263
|
keys = try keychainStorage.keys(pattern: pattern)
|
|
233
264
|
case "sqlite":
|
|
234
|
-
|
|
265
|
+
guard let store = validatedSqliteStore(for: call), let table = validatedTable(call) else { return }
|
|
266
|
+
keys = store.keys(table: table, pattern: pattern)
|
|
235
267
|
case "filesystem":
|
|
236
268
|
keys = filesystemStorage.keys(pattern: pattern)
|
|
237
269
|
case "preferences":
|
|
@@ -266,7 +298,8 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
266
298
|
let info = try keychainStorage.size()
|
|
267
299
|
segments = ["total": info.total, "count": info.count, "keys": 0, "values": info.total, "metadata": 0]
|
|
268
300
|
case "sqlite":
|
|
269
|
-
|
|
301
|
+
guard let store = validatedSqliteStore(for: call), let table = validatedTable(call) else { return }
|
|
302
|
+
segments = try store.size(table: table)
|
|
270
303
|
case "filesystem":
|
|
271
304
|
segments = filesystemStorage.size()
|
|
272
305
|
case "preferences":
|
|
@@ -295,8 +328,10 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
295
328
|
|
|
296
329
|
/**
|
|
297
330
|
* Query SQLite-backed storage.
|
|
298
|
-
* Matches the optional `query` method in the JS contract:
|
|
299
|
-
*
|
|
331
|
+
* Matches the optional `query` method in the JS contract: resolves
|
|
332
|
+
* `{ results: [{ key, value }] }` where `value` is the full StorageValue
|
|
333
|
+
* wrapper for each non-expired row, so the JS adapter applies the condition
|
|
334
|
+
* in one round-trip without re-fetching each key.
|
|
300
335
|
*/
|
|
301
336
|
@objc func query(_ call: CAPPluginCall) {
|
|
302
337
|
let storage = call.getString("storage") ?? "sqlite"
|
|
@@ -306,16 +341,34 @@ public class StrataStoragePlugin: CAPPlugin {
|
|
|
306
341
|
return
|
|
307
342
|
}
|
|
308
343
|
|
|
344
|
+
guard let store = validatedSqliteStore(for: call), let table = validatedTable(call) else { return }
|
|
309
345
|
let condition = call.getObject("condition") ?? [:]
|
|
310
|
-
let results =
|
|
311
|
-
table: sqliteTableName(from: call.getString("table")),
|
|
312
|
-
condition: condition
|
|
313
|
-
)
|
|
346
|
+
let results = store.query(table: table, condition: condition)
|
|
314
347
|
call.resolve([
|
|
315
348
|
"results": results
|
|
316
349
|
])
|
|
317
350
|
}
|
|
318
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Delete every expired row for a SQLite store and resolve `{ removed }`.
|
|
354
|
+
* The JS adapter calls this on its TTL cleanup tick, since `keys`/`query`
|
|
355
|
+
* now exclude expired rows in SQL (no lazy per-key deletion on read).
|
|
356
|
+
*/
|
|
357
|
+
@objc func cleanupExpired(_ call: CAPPluginCall) {
|
|
358
|
+
let storage = call.getString("storage") ?? "sqlite"
|
|
359
|
+
|
|
360
|
+
guard storage == "sqlite" else {
|
|
361
|
+
call.reject("cleanupExpired is only supported for the 'sqlite' storage type")
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
guard let store = validatedSqliteStore(for: call), let table = validatedTable(call) else { return }
|
|
366
|
+
let removed = store.cleanupExpired(table: table)
|
|
367
|
+
call.resolve([
|
|
368
|
+
"removed": removed
|
|
369
|
+
])
|
|
370
|
+
}
|
|
371
|
+
|
|
319
372
|
/**
|
|
320
373
|
* iOS-specific: read a UserDefaults value.
|
|
321
374
|
* Resolves `{ value: unknown }`.
|
|
@@ -3,7 +3,13 @@ import Foundation
|
|
|
3
3
|
@objc public class UserDefaultsStorage: NSObject {
|
|
4
4
|
private let suiteName: String?
|
|
5
5
|
private var userDefaults: UserDefaults
|
|
6
|
-
|
|
6
|
+
/// All Strata keys are stored under this reserved prefix so that `keys()`,
|
|
7
|
+
/// `size()`, and `clear()` only ever touch Strata-owned entries — never the
|
|
8
|
+
/// rest of the app's `UserDefaults`. In particular `clear()` removes only
|
|
9
|
+
/// prefixed keys and NEVER calls `removePersistentDomain`, which would wipe
|
|
10
|
+
/// unrelated app/SDK settings in the shared domain.
|
|
11
|
+
private let keyPrefix = "strata_storage::"
|
|
12
|
+
|
|
7
13
|
@objc public init(suiteName: String? = nil) {
|
|
8
14
|
self.suiteName = suiteName
|
|
9
15
|
if let suiteName = suiteName, let customDefaults = UserDefaults(suiteName: suiteName) {
|
|
@@ -13,75 +19,70 @@ import Foundation
|
|
|
13
19
|
}
|
|
14
20
|
super.init()
|
|
15
21
|
}
|
|
16
|
-
|
|
22
|
+
|
|
23
|
+
private func physicalKey(_ key: String) -> String {
|
|
24
|
+
return keyPrefix + key
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// The physical keys in this domain that are owned by Strata.
|
|
28
|
+
private func ownedPhysicalKeys() -> [String] {
|
|
29
|
+
return userDefaults.dictionaryRepresentation().keys.filter { $0.hasPrefix(keyPrefix) }
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
@objc public func set(key: String, value: Any) -> Bool {
|
|
18
|
-
userDefaults.set(value, forKey: key)
|
|
33
|
+
userDefaults.set(value, forKey: physicalKey(key))
|
|
19
34
|
return userDefaults.synchronize()
|
|
20
35
|
}
|
|
21
|
-
|
|
36
|
+
|
|
22
37
|
@objc public func get(key: String) -> Any? {
|
|
23
|
-
return userDefaults.object(forKey: key)
|
|
38
|
+
return userDefaults.object(forKey: physicalKey(key))
|
|
24
39
|
}
|
|
25
|
-
|
|
40
|
+
|
|
26
41
|
@objc public func remove(key: String) -> Bool {
|
|
27
|
-
userDefaults.removeObject(forKey: key)
|
|
42
|
+
userDefaults.removeObject(forKey: physicalKey(key))
|
|
28
43
|
return userDefaults.synchronize()
|
|
29
44
|
}
|
|
30
|
-
|
|
45
|
+
|
|
31
46
|
@objc public func clear(prefix: String? = nil) -> Bool {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
} else {
|
|
39
|
-
// Clear all keys
|
|
40
|
-
if let suiteName = suiteName {
|
|
41
|
-
UserDefaults(suiteName: suiteName)?.removePersistentDomain(forName: suiteName)
|
|
42
|
-
} else {
|
|
43
|
-
guard let domain = Bundle.main.bundleIdentifier else {
|
|
44
|
-
// Fallback: manually clear all keys
|
|
45
|
-
let keys = Array(userDefaults.dictionaryRepresentation().keys)
|
|
46
|
-
for key in keys {
|
|
47
|
-
userDefaults.removeObject(forKey: key)
|
|
48
|
-
}
|
|
49
|
-
return userDefaults.synchronize()
|
|
50
|
-
}
|
|
51
|
-
userDefaults.removePersistentDomain(forName: domain)
|
|
52
|
-
}
|
|
47
|
+
// Only ever remove Strata-owned keys (optionally narrowed by `prefix`);
|
|
48
|
+
// never wipe the whole UserDefaults domain.
|
|
49
|
+
let targetPrefix = keyPrefix + (prefix ?? "")
|
|
50
|
+
for physical in userDefaults.dictionaryRepresentation().keys where physical.hasPrefix(targetPrefix) {
|
|
51
|
+
userDefaults.removeObject(forKey: physical)
|
|
53
52
|
}
|
|
54
53
|
return userDefaults.synchronize()
|
|
55
54
|
}
|
|
56
|
-
|
|
55
|
+
|
|
57
56
|
@objc public func keys(pattern: String? = nil) -> [String] {
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
// Present logical (un-prefixed) keys to callers.
|
|
58
|
+
let logical = ownedPhysicalKeys().map { String($0.dropFirst(keyPrefix.count)) }
|
|
59
|
+
|
|
60
60
|
guard let pattern = pattern else {
|
|
61
|
-
return
|
|
61
|
+
return logical
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return allKeys.filter { key in
|
|
63
|
+
|
|
64
|
+
return logical.filter { key in
|
|
66
65
|
key.hasPrefix(pattern) || key.contains(pattern)
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
|
-
|
|
68
|
+
|
|
70
69
|
@objc public func has(key: String) -> Bool {
|
|
71
|
-
return userDefaults.object(forKey: key) != nil
|
|
70
|
+
return userDefaults.object(forKey: physicalKey(key)) != nil
|
|
72
71
|
}
|
|
73
|
-
|
|
72
|
+
|
|
74
73
|
@objc public func size() -> (total: Int, count: Int) {
|
|
75
74
|
let all = userDefaults.dictionaryRepresentation()
|
|
76
75
|
var totalSize = 0
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
var count = 0
|
|
77
|
+
|
|
78
|
+
for (physical, value) in all where physical.hasPrefix(keyPrefix) {
|
|
79
|
+
count += 1
|
|
80
|
+
let logical = String(physical.dropFirst(keyPrefix.count))
|
|
81
|
+
// Estimate size (logical key + value in bytes)
|
|
82
|
+
totalSize += logical.data(using: .utf8)?.count ?? 0
|
|
82
83
|
totalSize += "\(value)".data(using: .utf8)?.count ?? 0
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
+
|
|
85
86
|
return (total: totalSize, count: count)
|
|
86
87
|
}
|
|
87
|
-
}
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strata-storage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
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
|
+
"packageManager": "yarn@4.17.0",
|
|
7
|
+
"sideEffects": false,
|
|
6
8
|
"main": "dist/index.js",
|
|
7
9
|
"types": "dist/index.d.ts",
|
|
8
10
|
"files": [
|
|
@@ -38,15 +40,16 @@
|
|
|
38
40
|
"./angular": {
|
|
39
41
|
"types": "./dist/integrations/angular/index.d.ts",
|
|
40
42
|
"default": "./dist/integrations/angular/index.js"
|
|
41
|
-
}
|
|
43
|
+
},
|
|
44
|
+
"./package.json": "./package.json"
|
|
42
45
|
},
|
|
43
46
|
"scripts": {
|
|
44
47
|
"build": "node scripts/build.js",
|
|
45
48
|
"clean": "rm -rf dist/",
|
|
46
|
-
"lint": "eslint 'src/**/*.{ts,tsx}'
|
|
49
|
+
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
50
|
+
"lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix",
|
|
47
51
|
"typecheck": "tsc --noEmit",
|
|
48
|
-
"
|
|
49
|
-
"test:coverage": "vitest --coverage",
|
|
52
|
+
"prepack": "yarn build && yarn lint && yarn typecheck",
|
|
50
53
|
"prepublishOnly": "yarn build && yarn lint && yarn typecheck",
|
|
51
54
|
"postinstall": "node scripts/postinstall.js || true"
|
|
52
55
|
},
|
|
@@ -75,13 +78,14 @@
|
|
|
75
78
|
"url": "https://github.com/aoneahsan/strata-storage.git"
|
|
76
79
|
},
|
|
77
80
|
"bugs": {
|
|
78
|
-
"url": "https://
|
|
81
|
+
"url": "https://stratastorage.aoneahsan.com/contact"
|
|
79
82
|
},
|
|
80
|
-
"homepage": "https://
|
|
83
|
+
"homepage": "https://stratastorage.aoneahsan.com",
|
|
81
84
|
"peerDependencies": {
|
|
82
85
|
"@angular/core": ">=21.0.6",
|
|
83
86
|
"@angular/forms": ">=21.0.6",
|
|
84
87
|
"@capacitor/core": ">=8.0.0",
|
|
88
|
+
"firebase": ">=10.0.0",
|
|
85
89
|
"react": ">=19.2.3",
|
|
86
90
|
"vue": ">=3.5.26"
|
|
87
91
|
},
|
|
@@ -95,6 +99,9 @@
|
|
|
95
99
|
"@capacitor/core": {
|
|
96
100
|
"optional": true
|
|
97
101
|
},
|
|
102
|
+
"firebase": {
|
|
103
|
+
"optional": true
|
|
104
|
+
},
|
|
98
105
|
"react": {
|
|
99
106
|
"optional": true
|
|
100
107
|
},
|
|
@@ -103,27 +110,24 @@
|
|
|
103
110
|
}
|
|
104
111
|
},
|
|
105
112
|
"devDependencies": {
|
|
106
|
-
"@angular/common": "^
|
|
107
|
-
"@angular/core": "^
|
|
108
|
-
"@angular/forms": "^
|
|
109
|
-
"@capacitor/core": "^8.
|
|
110
|
-
"@types/node": "^25.9.
|
|
111
|
-
"@types/react": "^19.2.
|
|
112
|
-
"@typescript-eslint/eslint-plugin": "^8.60.
|
|
113
|
-
"@typescript-eslint/parser": "^8.60.
|
|
114
|
-
"
|
|
115
|
-
"eslint": "^10.4.0",
|
|
113
|
+
"@angular/common": "^22.0.0",
|
|
114
|
+
"@angular/core": "^22.0.0",
|
|
115
|
+
"@angular/forms": "^22.0.0",
|
|
116
|
+
"@capacitor/core": "^8.4.0",
|
|
117
|
+
"@types/node": "^25.9.2",
|
|
118
|
+
"@types/react": "^19.2.17",
|
|
119
|
+
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
|
120
|
+
"@typescript-eslint/parser": "^8.60.1",
|
|
121
|
+
"eslint": "^10.4.1",
|
|
116
122
|
"eslint-config-prettier": "^10.1.8",
|
|
117
123
|
"eslint-plugin-prettier": "^5.5.6",
|
|
118
|
-
"jsdom": "^29.1.1",
|
|
119
124
|
"prettier": "^3.8.3",
|
|
120
|
-
"react": "^19.2.
|
|
125
|
+
"react": "^19.2.7",
|
|
121
126
|
"rxjs": "^7.8.2",
|
|
122
127
|
"typescript": "^6.0.3",
|
|
123
|
-
"typescript-eslint": "^8.60.
|
|
124
|
-
"vitest": "^4.1.7",
|
|
128
|
+
"typescript-eslint": "^8.60.1",
|
|
125
129
|
"vue": "^3.5.35",
|
|
126
|
-
"zone.js": "~0.16.
|
|
130
|
+
"zone.js": "~0.16.2"
|
|
127
131
|
},
|
|
128
132
|
"engines": {
|
|
129
133
|
"node": ">=24.13.0"
|
|
@@ -136,6 +140,5 @@
|
|
|
136
140
|
"src": "android",
|
|
137
141
|
"androidModule": "strata-storage"
|
|
138
142
|
}
|
|
139
|
-
}
|
|
140
|
-
"packageManager": "yarn@4.14.1"
|
|
143
|
+
}
|
|
141
144
|
}
|
package/scripts/build.js
CHANGED
|
@@ -43,7 +43,7 @@ const fixPathAliases = (dir) => {
|
|
|
43
43
|
|
|
44
44
|
if (stat.isDirectory()) {
|
|
45
45
|
fixPathAliases(filePath);
|
|
46
|
-
} else if (file.endsWith('.js')) {
|
|
46
|
+
} else if (file.endsWith('.js') || file.endsWith('.d.ts')) {
|
|
47
47
|
let content = fs.readFileSync(filePath, 'utf8');
|
|
48
48
|
|
|
49
49
|
// Calculate depth from dist root
|
|
@@ -61,6 +61,10 @@ const fixPathAliases = (dir) => {
|
|
|
61
61
|
.replace(/from\s+["']@\/types\//g, `from "${pathToRoot}types/`)
|
|
62
62
|
.replace(/from\s+["']@\/utils["']/g, `from "${pathToRoot}utils/index.js"`)
|
|
63
63
|
.replace(/from\s+["']@\/plugin["']/g, `from "${pathToRoot}plugin/index.js"`)
|
|
64
|
+
.replace(/from\s+["']@\/types["']/g, `from "${pathToRoot}types/index.js"`)
|
|
65
|
+
.replace(/from\s+["']@\/index["']/g, `from "${pathToRoot}index.js"`)
|
|
66
|
+
.replace(/from\s+["']@\/config["']/g, `from "${pathToRoot}config/index.js"`)
|
|
67
|
+
.replace(/from\s+["']@\/integrations["']/g, `from "${pathToRoot}integrations/index.js"`)
|
|
64
68
|
.replace(/import\(["']@\/core\//g, `import("${pathToRoot}core/`)
|
|
65
69
|
.replace(/import\(["']@\/adapters\//g, `import("${pathToRoot}adapters/`)
|
|
66
70
|
.replace(/import\(["']@\/features\//g, `import("${pathToRoot}features/`)
|
|
@@ -68,27 +72,48 @@ const fixPathAliases = (dir) => {
|
|
|
68
72
|
.replace(/import\(["']@\/plugin\//g, `import("${pathToRoot}plugin/`)
|
|
69
73
|
.replace(/import\(["']@\/types\//g, `import("${pathToRoot}types/`)
|
|
70
74
|
.replace(/import\(["']@\/utils["']\)/g, `import("${pathToRoot}utils/index.js")`)
|
|
71
|
-
.replace(/import\(["']@\/plugin["']\)/g, `import("${pathToRoot}plugin/index.js")`)
|
|
75
|
+
.replace(/import\(["']@\/plugin["']\)/g, `import("${pathToRoot}plugin/index.js")`)
|
|
76
|
+
.replace(/import\(["']@\/types["']\)/g, `import("${pathToRoot}types/index.js")`)
|
|
77
|
+
.replace(/import\(["']@\/index["']\)/g, `import("${pathToRoot}index.js")`)
|
|
78
|
+
.replace(/import\(["']@\/config["']\)/g, `import("${pathToRoot}config/index.js")`)
|
|
79
|
+
.replace(/import\(["']@\/integrations["']\)/g, `import("${pathToRoot}integrations/index.js")`);
|
|
72
80
|
|
|
73
81
|
// Also fix any remaining short imports that should point to index.js
|
|
74
82
|
content = content
|
|
75
83
|
.replace(/from\s+["']\.\/utils["']/g, `from "./utils/index.js"`)
|
|
76
84
|
.replace(/from\s+["']\.\/plugin["']/g, `from "./plugin/index.js"`);
|
|
77
85
|
|
|
78
|
-
// Add
|
|
86
|
+
// Add the correct extension to extensionless relative imports.
|
|
87
|
+
// Directory-aware: a barrel import like './types' must resolve to
|
|
88
|
+
// './types/index.js', a file import like './core/Strata' to
|
|
89
|
+
// './core/Strata.js'. tsc has already emitted every .js by now, so we
|
|
90
|
+
// resolve against the real dist files instead of blindly appending '.js'
|
|
91
|
+
// (the previous naive appender produced a dead './types.js' that broke
|
|
92
|
+
// type resolution for every TS consumer).
|
|
93
|
+
const fileDir = path.dirname(filePath);
|
|
94
|
+
const resolveRel = (importPath) => {
|
|
95
|
+
if (importPath.endsWith('.js') || importPath.endsWith('.json')) {
|
|
96
|
+
return importPath;
|
|
97
|
+
}
|
|
98
|
+
const abs = path.resolve(fileDir, importPath);
|
|
99
|
+
if (fs.existsSync(`${abs}.js`)) {
|
|
100
|
+
return `${importPath}.js`;
|
|
101
|
+
}
|
|
102
|
+
if (fs.existsSync(path.join(abs, 'index.js'))) {
|
|
103
|
+
return `${importPath.replace(/\/+$/, '')}/index.js`;
|
|
104
|
+
}
|
|
105
|
+
// Fall back to the legacy behavior if nothing matched on disk.
|
|
106
|
+
return `${importPath}.js`;
|
|
107
|
+
};
|
|
79
108
|
content = content
|
|
80
|
-
.replace(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return `import("${path}.js")`;
|
|
89
|
-
}
|
|
90
|
-
return match;
|
|
91
|
-
});
|
|
109
|
+
.replace(
|
|
110
|
+
/from\s+["'](\.[^"']+)["']/g,
|
|
111
|
+
(_match, p) => `from "${resolveRel(p)}"`,
|
|
112
|
+
)
|
|
113
|
+
.replace(
|
|
114
|
+
/import\(["'](\.[^"']+)["']\)/g,
|
|
115
|
+
(_match, p) => `import("${resolveRel(p)}")`,
|
|
116
|
+
);
|
|
92
117
|
|
|
93
118
|
fs.writeFileSync(filePath, content);
|
|
94
119
|
}
|
|
@@ -97,17 +122,6 @@ const fixPathAliases = (dir) => {
|
|
|
97
122
|
|
|
98
123
|
fixPathAliases(distDir);
|
|
99
124
|
|
|
100
|
-
// Copy native directories if they exist
|
|
101
|
-
console.log('📱 Copying native code...');
|
|
102
|
-
const nativeDirs = ['android', 'ios'];
|
|
103
|
-
nativeDirs.forEach(dir => {
|
|
104
|
-
const srcPath = path.join(rootDir, dir);
|
|
105
|
-
const destPath = path.join(distDir, dir);
|
|
106
|
-
if (fs.existsSync(srcPath)) {
|
|
107
|
-
fs.cpSync(srcPath, destPath, { recursive: true });
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
125
|
// Copy other files
|
|
112
126
|
console.log('📄 Copying additional files...');
|
|
113
127
|
const filesToCopy = ['README.md', 'LICENSE'];
|
|
@@ -153,11 +167,16 @@ const distPackageJson = {
|
|
|
153
167
|
'./angular': {
|
|
154
168
|
types: './integrations/angular/index.d.ts',
|
|
155
169
|
default: './integrations/angular/index.js'
|
|
156
|
-
}
|
|
170
|
+
},
|
|
171
|
+
'./package.json': './package.json'
|
|
157
172
|
},
|
|
158
173
|
author: packageJson.author,
|
|
159
174
|
license: packageJson.license,
|
|
160
175
|
repository: packageJson.repository,
|
|
176
|
+
// Carry metadata-only fields through so the dist manifest never drifts from
|
|
177
|
+
// the root manifest (bugs/homepage were previously dropped).
|
|
178
|
+
bugs: packageJson.bugs,
|
|
179
|
+
homepage: packageJson.homepage,
|
|
161
180
|
keywords: packageJson.keywords,
|
|
162
181
|
peerDependencies: packageJson.peerDependencies,
|
|
163
182
|
peerDependenciesMeta: packageJson.peerDependenciesMeta,
|
package/scripts/cli.js
CHANGED
package/scripts/configure.js
CHANGED
|
@@ -9,6 +9,7 @@ import fs from 'fs';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { execSync } from 'child_process';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
|
+
import readline from 'node:readline';
|
|
12
13
|
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
15
|
const __dirname = path.dirname(__filename);
|
|
@@ -110,7 +111,6 @@ function detectProject() {
|
|
|
110
111
|
|
|
111
112
|
// Get configuration through prompts
|
|
112
113
|
async function getConfiguration(projectInfo) {
|
|
113
|
-
const readline = require('readline');
|
|
114
114
|
const rl = readline.createInterface({
|
|
115
115
|
input: process.stdin,
|
|
116
116
|
output: process.stdout
|
|
@@ -433,7 +433,7 @@ function showCompletion(config) {
|
|
|
433
433
|
}
|
|
434
434
|
|
|
435
435
|
console.log('\nSee strata.example.* for a complete example.');
|
|
436
|
-
console.log('\nDocumentation: https://
|
|
436
|
+
console.log('\nDocumentation: https://stratastorage-docs.aoneahsan.com');
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
// Run configuration
|
package/scripts/postinstall.js
CHANGED
|
@@ -40,5 +40,6 @@ console.log(' • Optional Capacitor integration');
|
|
|
40
40
|
console.log(' • Built-in encryption & compression');
|
|
41
41
|
console.log(' • Cross-tab synchronization\n');
|
|
42
42
|
|
|
43
|
-
console.log('📖 Documentation: https://
|
|
44
|
-
console.log('
|
|
43
|
+
console.log('📖 Documentation: https://stratastorage-docs.aoneahsan.com');
|
|
44
|
+
console.log('🤖 For AI agents: https://stratastorage-docs.aoneahsan.com/ai');
|
|
45
|
+
console.log('🌐 Website: https://stratastorage.aoneahsan.com\n');
|