strata-storage 2.4.2 → 2.5.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 +208 -0
- package/README.md +427 -181
- package/android/AGENTS.md +34 -0
- package/android/CLAUDE.md +51 -0
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +35 -0
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +191 -27
- package/dist/README.md +427 -181
- 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 +34 -0
- package/dist/android/CLAUDE.md +51 -0
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +35 -0
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +191 -27
- package/dist/capacitor.d.ts.map +1 -1
- package/dist/capacitor.js +2 -1
- package/dist/config/support.d.ts +10 -0
- package/dist/config/support.d.ts.map +1 -0
- package/dist/config/support.js +9 -0
- 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 +33 -0
- package/dist/ios/CLAUDE.md +49 -0
- package/dist/ios/Plugin/KeychainStorage.swift +139 -50
- package/dist/ios/Plugin/SQLiteStorage.swift +40 -0
- package/dist/ios/Plugin/StrataStoragePlugin.m +23 -0
- package/dist/ios/Plugin/StrataStoragePlugin.swift +201 -52
- 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 +33 -0
- package/ios/CLAUDE.md +49 -0
- package/ios/Plugin/KeychainStorage.swift +139 -50
- package/ios/Plugin/SQLiteStorage.swift +40 -0
- package/ios/Plugin/StrataStoragePlugin.m +23 -0
- package/ios/Plugin/StrataStoragePlugin.swift +201 -52
- package/package.json +35 -23
- package/scripts/build.js +16 -5
- package/scripts/configure.js +2 -6
- package/scripts/postinstall.js +2 -2
- package/Readme.md +0 -271
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,OAAO,CAAC,EAAE,OAAO,CAAC;gBAEtB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,OAAO,CAAC,EAAE,OAAO,CAAC;gBAEtB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAe7D;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;gBACrC,OAAO,SAA2B,EAAE,OAAO,CAAC,EAAE,OAAO;CAIlE;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,WAAW;gBAC3C,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAInD;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;gBACpC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAOvE;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,WAAW;gBAClC,OAAO,SAAgC,EAAE,OAAO,CAAC,EAAE,OAAO;CAIvE;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,WAAW;gBACnC,OAAO,SAAiC,EAAE,OAAO,CAAC,EAAE,OAAO;CAIxE;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;gBACrC,OAAO,SAAyB,EAAE,OAAO,CAAC,EAAE,OAAO;CAIhE;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,WAAW;gBACjC,OAAO,SAAgC,EAAE,OAAO,CAAC,EAAE,OAAO;CAIvE;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,WAAW;gBAClC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAI/C;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,WAAW;gBACnC,OAAO,SAAuB,EAAE,OAAO,CAAC,EAAE,OAAO;CAI9D;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,WAAW;gBACjC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAI/C;AAED;;GAEG;AACH,qBAAa,SAAU,SAAQ,WAAW;gBAC5B,OAAO,SAA0B,EAAE,OAAO,CAAC,EAAE,OAAO;CAIjE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,WAAW;gBAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAI/C;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,WAAW;gBAChC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAI3C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAepD"}
|
package/dist/utils/errors.js
CHANGED
|
@@ -12,9 +12,11 @@ export class StrataError extends Error {
|
|
|
12
12
|
this.name = 'StrataError';
|
|
13
13
|
this.code = code;
|
|
14
14
|
this.details = details;
|
|
15
|
-
// Maintains proper stack trace for where our error was thrown
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// Maintains proper stack trace for where our error was thrown (V8-only API,
|
|
16
|
+
// typed locally so the zero-dependency core needs no @types/node).
|
|
17
|
+
const ErrorCtor = Error;
|
|
18
|
+
if (ErrorCtor.captureStackTrace) {
|
|
19
|
+
ErrorCtor.captureStackTrace(this, this.constructor);
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
}
|
|
@@ -75,6 +77,16 @@ export class SerializationError extends StrataError {
|
|
|
75
77
|
this.name = 'SerializationError';
|
|
76
78
|
}
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Error thrown when a stored value fails its integrity checksum (data
|
|
82
|
+
* corruption), or when a snapshot fails validation during restore.
|
|
83
|
+
*/
|
|
84
|
+
export class IntegrityError extends StrataError {
|
|
85
|
+
constructor(message = 'Data integrity check failed', details) {
|
|
86
|
+
super(message, 'INTEGRITY_ERROR', details);
|
|
87
|
+
this.name = 'IntegrityError';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
78
90
|
/**
|
|
79
91
|
* Error thrown when validation fails
|
|
80
92
|
*/
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -23,7 +23,16 @@ export declare function isCapacitor(): boolean;
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function deepClone<T>(obj: T): T;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Check whether a key is safe to copy into a target object. Rejects the keys
|
|
27
|
+
* that prototype-pollution payloads use (`__proto__`, `constructor`,
|
|
28
|
+
* `prototype`). Use whenever merging/assigning keys from untrusted JSON.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isSafeKey(key: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Deep merge objects.
|
|
33
|
+
*
|
|
34
|
+
* Security: skips the prototype-pollution keys (`__proto__`, `constructor`,
|
|
35
|
+
* `prototype`) so a malicious source object cannot mutate `Object.prototype`.
|
|
27
36
|
*/
|
|
28
37
|
export declare function deepMerge<T extends Record<string, unknown>>(target: T, ...sources: Partial<T>[]): T;
|
|
29
38
|
/**
|
|
@@ -35,7 +44,39 @@ export declare function isObject(item: unknown): item is Record<string, unknown>
|
|
|
35
44
|
*/
|
|
36
45
|
export declare function generateId(): string;
|
|
37
46
|
/**
|
|
38
|
-
*
|
|
47
|
+
* Maximum length of a regex source string we are willing to compile from
|
|
48
|
+
* caller-supplied input (glob patterns, `$regex` query operands, deserialized
|
|
49
|
+
* RegExp). A hard cap is the simplest robust mitigation against catastrophic
|
|
50
|
+
* backtracking (ReDoS): an attacker cannot supply an arbitrarily long pattern
|
|
51
|
+
* that takes exponential time to match. 1000 chars comfortably fits every
|
|
52
|
+
* legitimate key glob / field regex while bounding worst-case work.
|
|
53
|
+
*
|
|
54
|
+
* NOTE: This caps pattern LENGTH, not matching complexity. A short pattern can
|
|
55
|
+
* still backtrack pathologically (e.g. `(a+)+$`). The JS RegExp engine has no
|
|
56
|
+
* timeout, so callers passing patterns from fully untrusted sources should
|
|
57
|
+
* validate them, prefer literal/prefix matching, or run matching off the main
|
|
58
|
+
* thread. Strata never builds a RegExp from a value it stores itself.
|
|
59
|
+
*/
|
|
60
|
+
export declare const SAFE_REGEX_MAX_LENGTH = 1000;
|
|
61
|
+
/**
|
|
62
|
+
* Construct a RegExp from a possibly-untrusted source with two safety guards:
|
|
63
|
+
* 1. The source length is capped at {@link SAFE_REGEX_MAX_LENGTH} — an
|
|
64
|
+
* over-long pattern is rejected up front instead of being compiled.
|
|
65
|
+
* 2. Compilation is wrapped so an invalid pattern throws a clear, typed error
|
|
66
|
+
* rather than leaking a raw `SyntaxError`.
|
|
67
|
+
*
|
|
68
|
+
* This does NOT alter matching semantics for valid, reasonably-sized patterns —
|
|
69
|
+
* the resulting RegExp behaves identically to `new RegExp(source, flags)`.
|
|
70
|
+
*
|
|
71
|
+
* @throws {ValidationError} if the source exceeds the length cap or is invalid.
|
|
72
|
+
*/
|
|
73
|
+
export declare function safeRegExp(source: string, flags?: string): RegExp;
|
|
74
|
+
/**
|
|
75
|
+
* Simple glob pattern matching.
|
|
76
|
+
*
|
|
77
|
+
* Security: glob metacharacters are escaped before compilation and the source
|
|
78
|
+
* is length-capped via {@link safeRegExp}, so a hostile glob cannot inject an
|
|
79
|
+
* arbitrary regex or supply an unbounded pattern.
|
|
39
80
|
*/
|
|
40
81
|
export declare function matchGlob(pattern: string, str: string): boolean;
|
|
41
82
|
/**
|
|
@@ -84,7 +125,13 @@ export declare function createDeferred<T>(): {
|
|
|
84
125
|
*/
|
|
85
126
|
export declare function serialize(value: unknown): string;
|
|
86
127
|
/**
|
|
87
|
-
* Deserialize JSON with support for special types
|
|
128
|
+
* Deserialize JSON with support for special types.
|
|
129
|
+
*
|
|
130
|
+
* Security: the reviver drops the prototype-pollution keys (`__proto__`,
|
|
131
|
+
* `constructor`, `prototype`) by returning `undefined` for them, so parsing
|
|
132
|
+
* untrusted JSON cannot smuggle a polluting key into the resulting object.
|
|
133
|
+
* The reconstructed RegExp is built from a length-capped source to avoid
|
|
134
|
+
* pathological patterns (see SAFE_REGEX_MAX_LENGTH).
|
|
88
135
|
*/
|
|
89
136
|
export declare function deserialize(json: string): unknown;
|
|
90
137
|
/**
|
|
@@ -103,11 +150,22 @@ export declare class EventEmitter {
|
|
|
103
150
|
removeAllListeners(event?: string): void;
|
|
104
151
|
}
|
|
105
152
|
/**
|
|
106
|
-
*
|
|
153
|
+
* Upper bound on storage-key length. Real keys are short; an unbounded key is a
|
|
154
|
+
* memory/DoS vector and offers no legitimate benefit. 1024 chars is generous.
|
|
155
|
+
*/
|
|
156
|
+
export declare const MAX_KEY_LENGTH = 1024;
|
|
157
|
+
/**
|
|
158
|
+
* Check if a value is a valid storage key.
|
|
159
|
+
*
|
|
160
|
+
* A valid key is a non-empty string within {@link MAX_KEY_LENGTH} characters
|
|
161
|
+
* that is not a prototype-pollution key (`__proto__`, `constructor`,
|
|
162
|
+
* `prototype`) — those must never be used as storage keys.
|
|
107
163
|
*/
|
|
108
164
|
export declare function isValidKey(key: unknown): key is string;
|
|
109
165
|
/**
|
|
110
|
-
* Check if a value can be stored
|
|
166
|
+
* Check if a value can be stored. Allows any value except `undefined`. Symbols
|
|
167
|
+
* and functions are rejected because they are not JSON-serializable and cannot
|
|
168
|
+
* round-trip through any storage backend.
|
|
111
169
|
*/
|
|
112
170
|
export declare function isValidValue(value: unknown): boolean;
|
|
113
171
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Zero dependencies - all utilities implemented from scratch
|
|
4
4
|
*/
|
|
5
5
|
import { ValidationError } from "./errors.js";
|
|
6
|
+
import { logger } from "./logger.js";
|
|
6
7
|
/**
|
|
7
8
|
* Check if code is running in a browser environment
|
|
8
9
|
*/
|
|
@@ -13,9 +14,10 @@ export function isBrowser() {
|
|
|
13
14
|
* Check if code is running in Node.js
|
|
14
15
|
*/
|
|
15
16
|
export function isNode() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// Access `process` through globalThis so the universal core compiles without
|
|
18
|
+
// Node ambient types and never throws in browsers where `process` is absent.
|
|
19
|
+
const proc = globalThis.process;
|
|
20
|
+
return proc?.versions?.node !== undefined;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Check if code is running in a Web Worker
|
|
@@ -51,7 +53,23 @@ export function deepClone(obj) {
|
|
|
51
53
|
return clonedObj;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
|
-
*
|
|
56
|
+
* Keys that must never be copied during a merge/assign of untrusted data —
|
|
57
|
+
* setting them can pollute `Object.prototype` (prototype pollution).
|
|
58
|
+
*/
|
|
59
|
+
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
60
|
+
/**
|
|
61
|
+
* Check whether a key is safe to copy into a target object. Rejects the keys
|
|
62
|
+
* that prototype-pollution payloads use (`__proto__`, `constructor`,
|
|
63
|
+
* `prototype`). Use whenever merging/assigning keys from untrusted JSON.
|
|
64
|
+
*/
|
|
65
|
+
export function isSafeKey(key) {
|
|
66
|
+
return !FORBIDDEN_KEYS.has(key);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Deep merge objects.
|
|
70
|
+
*
|
|
71
|
+
* Security: skips the prototype-pollution keys (`__proto__`, `constructor`,
|
|
72
|
+
* `prototype`) so a malicious source object cannot mutate `Object.prototype`.
|
|
55
73
|
*/
|
|
56
74
|
export function deepMerge(target, ...sources) {
|
|
57
75
|
if (!sources.length)
|
|
@@ -59,6 +77,9 @@ export function deepMerge(target, ...sources) {
|
|
|
59
77
|
const source = sources.shift();
|
|
60
78
|
if (isObject(target) && isObject(source)) {
|
|
61
79
|
for (const key in source) {
|
|
80
|
+
// Skip own dangerous keys; never let untrusted data reach the prototype.
|
|
81
|
+
if (!Object.prototype.hasOwnProperty.call(source, key) || !isSafeKey(key))
|
|
82
|
+
continue;
|
|
62
83
|
if (isObject(source[key])) {
|
|
63
84
|
if (!target[key])
|
|
64
85
|
Object.assign(target, { [key]: {} });
|
|
@@ -86,14 +107,62 @@ export function generateId() {
|
|
|
86
107
|
return `${timestamp}-${randomPart}`;
|
|
87
108
|
}
|
|
88
109
|
/**
|
|
89
|
-
*
|
|
110
|
+
* Maximum length of a regex source string we are willing to compile from
|
|
111
|
+
* caller-supplied input (glob patterns, `$regex` query operands, deserialized
|
|
112
|
+
* RegExp). A hard cap is the simplest robust mitigation against catastrophic
|
|
113
|
+
* backtracking (ReDoS): an attacker cannot supply an arbitrarily long pattern
|
|
114
|
+
* that takes exponential time to match. 1000 chars comfortably fits every
|
|
115
|
+
* legitimate key glob / field regex while bounding worst-case work.
|
|
116
|
+
*
|
|
117
|
+
* NOTE: This caps pattern LENGTH, not matching complexity. A short pattern can
|
|
118
|
+
* still backtrack pathologically (e.g. `(a+)+$`). The JS RegExp engine has no
|
|
119
|
+
* timeout, so callers passing patterns from fully untrusted sources should
|
|
120
|
+
* validate them, prefer literal/prefix matching, or run matching off the main
|
|
121
|
+
* thread. Strata never builds a RegExp from a value it stores itself.
|
|
122
|
+
*/
|
|
123
|
+
export const SAFE_REGEX_MAX_LENGTH = 1000;
|
|
124
|
+
/**
|
|
125
|
+
* Construct a RegExp from a possibly-untrusted source with two safety guards:
|
|
126
|
+
* 1. The source length is capped at {@link SAFE_REGEX_MAX_LENGTH} — an
|
|
127
|
+
* over-long pattern is rejected up front instead of being compiled.
|
|
128
|
+
* 2. Compilation is wrapped so an invalid pattern throws a clear, typed error
|
|
129
|
+
* rather than leaking a raw `SyntaxError`.
|
|
130
|
+
*
|
|
131
|
+
* This does NOT alter matching semantics for valid, reasonably-sized patterns —
|
|
132
|
+
* the resulting RegExp behaves identically to `new RegExp(source, flags)`.
|
|
133
|
+
*
|
|
134
|
+
* @throws {ValidationError} if the source exceeds the length cap or is invalid.
|
|
135
|
+
*/
|
|
136
|
+
export function safeRegExp(source, flags) {
|
|
137
|
+
if (source.length > SAFE_REGEX_MAX_LENGTH) {
|
|
138
|
+
throw new ValidationError('Regular expression pattern is too long', {
|
|
139
|
+
length: source.length,
|
|
140
|
+
max: SAFE_REGEX_MAX_LENGTH,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
return new RegExp(source, flags);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
throw new ValidationError('Invalid regular expression pattern', {
|
|
148
|
+
pattern: source,
|
|
149
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Simple glob pattern matching.
|
|
155
|
+
*
|
|
156
|
+
* Security: glob metacharacters are escaped before compilation and the source
|
|
157
|
+
* is length-capped via {@link safeRegExp}, so a hostile glob cannot inject an
|
|
158
|
+
* arbitrary regex or supply an unbounded pattern.
|
|
90
159
|
*/
|
|
91
160
|
export function matchGlob(pattern, str) {
|
|
92
161
|
const regexPattern = pattern
|
|
93
162
|
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
94
163
|
.replace(/\*/g, '.*')
|
|
95
164
|
.replace(/\?/g, '.');
|
|
96
|
-
return
|
|
165
|
+
return safeRegExp(`^${regexPattern}$`).test(str);
|
|
97
166
|
}
|
|
98
167
|
/**
|
|
99
168
|
* Format bytes to human readable
|
|
@@ -233,17 +302,26 @@ export function serialize(value) {
|
|
|
233
302
|
});
|
|
234
303
|
}
|
|
235
304
|
/**
|
|
236
|
-
* Deserialize JSON with support for special types
|
|
305
|
+
* Deserialize JSON with support for special types.
|
|
306
|
+
*
|
|
307
|
+
* Security: the reviver drops the prototype-pollution keys (`__proto__`,
|
|
308
|
+
* `constructor`, `prototype`) by returning `undefined` for them, so parsing
|
|
309
|
+
* untrusted JSON cannot smuggle a polluting key into the resulting object.
|
|
310
|
+
* The reconstructed RegExp is built from a length-capped source to avoid
|
|
311
|
+
* pathological patterns (see SAFE_REGEX_MAX_LENGTH).
|
|
237
312
|
*/
|
|
238
313
|
export function deserialize(json) {
|
|
239
|
-
return JSON.parse(json, (
|
|
314
|
+
return JSON.parse(json, (key, val) => {
|
|
315
|
+
// Returning undefined from a reviver removes the property from the result.
|
|
316
|
+
if (!isSafeKey(key))
|
|
317
|
+
return undefined;
|
|
240
318
|
if (val && typeof val === 'object' && '__type' in val) {
|
|
241
319
|
switch (val.__type) {
|
|
242
320
|
case 'Date':
|
|
243
321
|
return new Date(val.value);
|
|
244
322
|
case 'RegExp': {
|
|
245
|
-
const match = val.value.match(/^\/(.*)\/([gimuy]*)$/);
|
|
246
|
-
return match ?
|
|
323
|
+
const match = typeof val.value === 'string' ? val.value.match(/^\/(.*)\/([gimuy]*)$/) : null;
|
|
324
|
+
return match ? safeRegExp(match[1], match[2]) : safeRegExp(String(val.value));
|
|
247
325
|
}
|
|
248
326
|
case 'Map':
|
|
249
327
|
return new Map(val.value);
|
|
@@ -312,7 +390,7 @@ export class EventEmitter {
|
|
|
312
390
|
handler(...args);
|
|
313
391
|
}
|
|
314
392
|
catch (error) {
|
|
315
|
-
|
|
393
|
+
logger.error(`Error in event handler for ${event}:`, error);
|
|
316
394
|
}
|
|
317
395
|
});
|
|
318
396
|
}
|
|
@@ -333,17 +411,32 @@ export class EventEmitter {
|
|
|
333
411
|
}
|
|
334
412
|
}
|
|
335
413
|
/**
|
|
336
|
-
*
|
|
414
|
+
* Upper bound on storage-key length. Real keys are short; an unbounded key is a
|
|
415
|
+
* memory/DoS vector and offers no legitimate benefit. 1024 chars is generous.
|
|
416
|
+
*/
|
|
417
|
+
export const MAX_KEY_LENGTH = 1024;
|
|
418
|
+
/**
|
|
419
|
+
* Check if a value is a valid storage key.
|
|
420
|
+
*
|
|
421
|
+
* A valid key is a non-empty string within {@link MAX_KEY_LENGTH} characters
|
|
422
|
+
* that is not a prototype-pollution key (`__proto__`, `constructor`,
|
|
423
|
+
* `prototype`) — those must never be used as storage keys.
|
|
337
424
|
*/
|
|
338
425
|
export function isValidKey(key) {
|
|
339
|
-
return typeof key === 'string' && key.length > 0;
|
|
426
|
+
return (typeof key === 'string' && key.length > 0 && key.length <= MAX_KEY_LENGTH && isSafeKey(key));
|
|
340
427
|
}
|
|
341
428
|
/**
|
|
342
|
-
* Check if a value can be stored
|
|
429
|
+
* Check if a value can be stored. Allows any value except `undefined`. Symbols
|
|
430
|
+
* and functions are rejected because they are not JSON-serializable and cannot
|
|
431
|
+
* round-trip through any storage backend.
|
|
343
432
|
*/
|
|
344
433
|
export function isValidValue(value) {
|
|
345
|
-
|
|
346
|
-
|
|
434
|
+
if (value === undefined)
|
|
435
|
+
return false;
|
|
436
|
+
const t = typeof value;
|
|
437
|
+
if (t === 'function' || t === 'symbol')
|
|
438
|
+
return false;
|
|
439
|
+
return true;
|
|
347
440
|
}
|
|
348
441
|
/**
|
|
349
442
|
* Serialize a value for storage
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal logger for Strata Storage.
|
|
3
|
+
*
|
|
4
|
+
* A library must never spam the consumer's console, so every diagnostic message
|
|
5
|
+
* routes through this single, level-gated logger instead of calling `console.*`
|
|
6
|
+
* directly. It deliberately does NOT patch the global `console` (that would be
|
|
7
|
+
* hostile to consumers) — it only forwards to it when the level allows.
|
|
8
|
+
*
|
|
9
|
+
* Default level is `warn`: warnings and errors surface (they signal real
|
|
10
|
+
* problems), while `debug`/`info` stay quiet. Raise verbosity with
|
|
11
|
+
* `new Strata({ debug: true })`, `setLogLevel('debug')`, or — in a browser
|
|
12
|
+
* devtools session — `globalThis.__strataSetLogLevel('debug')`.
|
|
13
|
+
*/
|
|
14
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
15
|
+
/** Set the global log level for all Strata diagnostic output. */
|
|
16
|
+
export declare function setLogLevel(level: LogLevel): void;
|
|
17
|
+
/** Get the current global log level. */
|
|
18
|
+
export declare function getLogLevel(): LogLevel;
|
|
19
|
+
export declare const logger: {
|
|
20
|
+
debug(...args: unknown[]): void;
|
|
21
|
+
info(...args: unknown[]): void;
|
|
22
|
+
warn(...args: unknown[]): void;
|
|
23
|
+
error(...args: unknown[]): void;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Expose runtime log-level controls on the global scope for devtools use.
|
|
27
|
+
* Call once (e.g. from a Strata instance) — never auto-invoked at module load
|
|
28
|
+
* so the package stays free of import-time side effects (`sideEffects: false`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function exposeLogLevelControls(): void;
|
|
31
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AActE,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAED,wCAAwC;AACxC,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAOD,eAAO,MAAM,MAAM;mBACF,OAAO,EAAE,GAAG,IAAI;kBAGjB,OAAO,EAAE,GAAG,IAAI;kBAGhB,OAAO,EAAE,GAAG,IAAI;mBAGf,OAAO,EAAE,GAAG,IAAI;CAGhC,CAAC;AAGF;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAO7C"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal logger for Strata Storage.
|
|
3
|
+
*
|
|
4
|
+
* A library must never spam the consumer's console, so every diagnostic message
|
|
5
|
+
* routes through this single, level-gated logger instead of calling `console.*`
|
|
6
|
+
* directly. It deliberately does NOT patch the global `console` (that would be
|
|
7
|
+
* hostile to consumers) — it only forwards to it when the level allows.
|
|
8
|
+
*
|
|
9
|
+
* Default level is `warn`: warnings and errors surface (they signal real
|
|
10
|
+
* problems), while `debug`/`info` stay quiet. Raise verbosity with
|
|
11
|
+
* `new Strata({ debug: true })`, `setLogLevel('debug')`, or — in a browser
|
|
12
|
+
* devtools session — `globalThis.__strataSetLogLevel('debug')`.
|
|
13
|
+
*/
|
|
14
|
+
const LEVEL_ORDER = {
|
|
15
|
+
debug: 0,
|
|
16
|
+
info: 1,
|
|
17
|
+
warn: 2,
|
|
18
|
+
error: 3,
|
|
19
|
+
silent: 4,
|
|
20
|
+
};
|
|
21
|
+
const PREFIX = '[strata-storage]';
|
|
22
|
+
let currentLevel = 'warn';
|
|
23
|
+
/** Set the global log level for all Strata diagnostic output. */
|
|
24
|
+
export function setLogLevel(level) {
|
|
25
|
+
currentLevel = level;
|
|
26
|
+
}
|
|
27
|
+
/** Get the current global log level. */
|
|
28
|
+
export function getLogLevel() {
|
|
29
|
+
return currentLevel;
|
|
30
|
+
}
|
|
31
|
+
function isEnabled(level) {
|
|
32
|
+
return LEVEL_ORDER[level] >= LEVEL_ORDER[currentLevel];
|
|
33
|
+
}
|
|
34
|
+
/* eslint-disable no-console -- this module is the single sanctioned console boundary */
|
|
35
|
+
export const logger = {
|
|
36
|
+
debug(...args) {
|
|
37
|
+
if (isEnabled('debug'))
|
|
38
|
+
console.debug(PREFIX, ...args);
|
|
39
|
+
},
|
|
40
|
+
info(...args) {
|
|
41
|
+
if (isEnabled('info'))
|
|
42
|
+
console.info(PREFIX, ...args);
|
|
43
|
+
},
|
|
44
|
+
warn(...args) {
|
|
45
|
+
if (isEnabled('warn'))
|
|
46
|
+
console.warn(PREFIX, ...args);
|
|
47
|
+
},
|
|
48
|
+
error(...args) {
|
|
49
|
+
if (isEnabled('error'))
|
|
50
|
+
console.error(PREFIX, ...args);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
/* eslint-enable no-console */
|
|
54
|
+
/**
|
|
55
|
+
* Expose runtime log-level controls on the global scope for devtools use.
|
|
56
|
+
* Call once (e.g. from a Strata instance) — never auto-invoked at module load
|
|
57
|
+
* so the package stays free of import-time side effects (`sideEffects: false`).
|
|
58
|
+
*/
|
|
59
|
+
export function exposeLogLevelControls() {
|
|
60
|
+
const scope = globalThis;
|
|
61
|
+
scope.__strataSetLogLevel = setLogLevel;
|
|
62
|
+
scope.__strataGetLogLevel = getLogLevel;
|
|
63
|
+
}
|
package/ios/AGENTS.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# AGENTS.md — ios/
|
|
2
|
+
|
|
3
|
+
Last Updated: 2026-04-03
|
|
4
|
+
|
|
5
|
+
> Agent instructions for iOS native development.
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|------|---------|
|
|
11
|
+
| `Plugin/StrataStoragePlugin.swift` | Capacitor plugin entry |
|
|
12
|
+
| `Plugin/UserDefaultsStorage.swift` | General key-value storage |
|
|
13
|
+
| `Plugin/KeychainStorage.swift` | Secure storage |
|
|
14
|
+
| `Plugin/SQLiteStorage.swift` | Database storage (~11KB) |
|
|
15
|
+
|
|
16
|
+
## Agent Rules
|
|
17
|
+
|
|
18
|
+
### Security (IRON-SOLID)
|
|
19
|
+
- Sensitive data MUST use `KeychainStorage`
|
|
20
|
+
- NEVER store secrets in `UserDefaultsStorage`
|
|
21
|
+
|
|
22
|
+
### SQL Safety
|
|
23
|
+
- ALWAYS use parameterized queries in SQLiteStorage
|
|
24
|
+
- NEVER string-interpolate SQL values
|
|
25
|
+
|
|
26
|
+
### Plugin Architecture
|
|
27
|
+
- Methods exposed through `StrataStoragePlugin.swift`
|
|
28
|
+
- Each storage backend is a separate Swift file
|
|
29
|
+
- Bridges Capacitor JS calls to native Swift
|
|
30
|
+
|
|
31
|
+
### Before Modifying
|
|
32
|
+
- Understand Capacitor plugin protocol
|
|
33
|
+
- Test on iOS simulator after changes
|
package/ios/CLAUDE.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# CLAUDE.md — ios/
|
|
2
|
+
|
|
3
|
+
Last Updated: 2026-04-03
|
|
4
|
+
|
|
5
|
+
## iOS Native Plugin
|
|
6
|
+
|
|
7
|
+
Swift implementation of native storage backends for Capacitor.
|
|
8
|
+
|
|
9
|
+
### Files
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
|------|---------|
|
|
13
|
+
| `Plugin/StrataStoragePlugin.swift` | Main Capacitor plugin entry point |
|
|
14
|
+
| `Plugin/UserDefaultsStorage.swift` | UserDefaults-based general storage |
|
|
15
|
+
| `Plugin/KeychainStorage.swift` | Keychain-based secure storage |
|
|
16
|
+
| `Plugin/SQLiteStorage.swift` | SQLite database storage (~11KB) |
|
|
17
|
+
|
|
18
|
+
### Configuration
|
|
19
|
+
|
|
20
|
+
- Pod spec: `StrataStorage.podspec` (root)
|
|
21
|
+
- Minimum iOS version: defined in podspec
|
|
22
|
+
- Language: Swift
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
### Security (IRON-SOLID)
|
|
27
|
+
- Sensitive data MUST use `KeychainStorage`
|
|
28
|
+
- NEVER store credentials, tokens, or secrets in `UserDefaultsStorage`
|
|
29
|
+
- Keychain items should use appropriate access control flags
|
|
30
|
+
|
|
31
|
+
### Plugin Architecture
|
|
32
|
+
- All native methods are exposed through `StrataStoragePlugin.swift`
|
|
33
|
+
- Plugin bridges Capacitor JS calls to native Swift implementations
|
|
34
|
+
- Each storage backend is a separate Swift file
|
|
35
|
+
|
|
36
|
+
### SQLite
|
|
37
|
+
- `SQLiteStorage.swift` is the largest file (~11KB)
|
|
38
|
+
- Handles table creation, migrations, and CRUD operations
|
|
39
|
+
- Use parameterized queries — NEVER string-interpolate SQL
|
|
40
|
+
|
|
41
|
+
### Testing
|
|
42
|
+
- Native code is tested through the Capacitor bridge
|
|
43
|
+
- Test via the demo app on iOS simulator
|
|
44
|
+
- Verify all adapter methods work end-to-end
|
|
45
|
+
|
|
46
|
+
### Before Modifying
|
|
47
|
+
- Understand the Capacitor plugin protocol
|
|
48
|
+
- Test on iOS simulator after changes
|
|
49
|
+
- Verify podspec still resolves
|