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.
Files changed (111) hide show
  1. package/AI-INTEGRATION-GUIDE.md +208 -0
  2. package/README.md +427 -181
  3. package/android/AGENTS.md +34 -0
  4. package/android/CLAUDE.md +51 -0
  5. package/android/src/main/java/com/strata/storage/SQLiteStorage.java +35 -0
  6. package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +191 -27
  7. package/dist/README.md +427 -181
  8. package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
  9. package/dist/adapters/capacitor/FilesystemAdapter.js +2 -1
  10. package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
  11. package/dist/adapters/capacitor/PreferencesAdapter.js +2 -1
  12. package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
  13. package/dist/adapters/capacitor/SecureAdapter.js +2 -1
  14. package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
  15. package/dist/adapters/capacitor/SqliteAdapter.js +2 -1
  16. package/dist/adapters/web/CacheAdapter.d.ts.map +1 -1
  17. package/dist/adapters/web/CacheAdapter.js +11 -3
  18. package/dist/adapters/web/CookieAdapter.d.ts +37 -1
  19. package/dist/adapters/web/CookieAdapter.d.ts.map +1 -1
  20. package/dist/adapters/web/CookieAdapter.js +89 -9
  21. package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
  22. package/dist/adapters/web/IndexedDBAdapter.js +10 -2
  23. package/dist/adapters/web/LocalStorageAdapter.d.ts +31 -0
  24. package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -1
  25. package/dist/adapters/web/LocalStorageAdapter.js +92 -19
  26. package/dist/adapters/web/MemoryAdapter.d.ts +24 -0
  27. package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
  28. package/dist/adapters/web/MemoryAdapter.js +69 -18
  29. package/dist/adapters/web/SessionStorageAdapter.d.ts +24 -0
  30. package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
  31. package/dist/adapters/web/SessionStorageAdapter.js +71 -9
  32. package/dist/adapters/web/URLAdapter.d.ts +59 -0
  33. package/dist/adapters/web/URLAdapter.d.ts.map +1 -0
  34. package/dist/adapters/web/URLAdapter.js +234 -0
  35. package/dist/adapters/web/index.d.ts +1 -0
  36. package/dist/adapters/web/index.d.ts.map +1 -1
  37. package/dist/adapters/web/index.js +1 -0
  38. package/dist/android/AGENTS.md +34 -0
  39. package/dist/android/CLAUDE.md +51 -0
  40. package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +35 -0
  41. package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +191 -27
  42. package/dist/capacitor.d.ts.map +1 -1
  43. package/dist/capacitor.js +2 -1
  44. package/dist/config/support.d.ts +10 -0
  45. package/dist/config/support.d.ts.map +1 -0
  46. package/dist/config/support.js +9 -0
  47. package/dist/core/BaseAdapter.d.ts +8 -0
  48. package/dist/core/BaseAdapter.d.ts.map +1 -1
  49. package/dist/core/BaseAdapter.js +34 -14
  50. package/dist/core/Strata.d.ts +56 -2
  51. package/dist/core/Strata.d.ts.map +1 -1
  52. package/dist/core/Strata.js +501 -53
  53. package/dist/features/encryption.d.ts.map +1 -1
  54. package/dist/features/encryption.js +3 -2
  55. package/dist/features/integrity.d.ts +16 -0
  56. package/dist/features/integrity.d.ts.map +1 -0
  57. package/dist/features/integrity.js +28 -0
  58. package/dist/features/observer.d.ts.map +1 -1
  59. package/dist/features/observer.js +2 -1
  60. package/dist/features/query.d.ts +7 -1
  61. package/dist/features/query.d.ts.map +1 -1
  62. package/dist/features/query.js +9 -2
  63. package/dist/features/sync.d.ts.map +1 -1
  64. package/dist/features/sync.js +4 -3
  65. package/dist/index.d.ts +35 -2
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +55 -30
  68. package/dist/integrations/angular/index.d.ts +158 -0
  69. package/dist/integrations/angular/index.d.ts.map +1 -0
  70. package/dist/integrations/angular/index.js +395 -0
  71. package/dist/integrations/index.d.ts +15 -0
  72. package/dist/integrations/index.d.ts.map +1 -0
  73. package/dist/integrations/index.js +18 -0
  74. package/dist/integrations/react/index.d.ts +75 -0
  75. package/dist/integrations/react/index.d.ts.map +1 -0
  76. package/dist/integrations/react/index.js +191 -0
  77. package/dist/integrations/vue/index.d.ts +103 -0
  78. package/dist/integrations/vue/index.d.ts.map +1 -0
  79. package/dist/integrations/vue/index.js +274 -0
  80. package/dist/ios/AGENTS.md +33 -0
  81. package/dist/ios/CLAUDE.md +49 -0
  82. package/dist/ios/Plugin/KeychainStorage.swift +139 -50
  83. package/dist/ios/Plugin/SQLiteStorage.swift +40 -0
  84. package/dist/ios/Plugin/StrataStoragePlugin.m +23 -0
  85. package/dist/ios/Plugin/StrataStoragePlugin.swift +201 -52
  86. package/dist/package.json +21 -5
  87. package/dist/plugin/index.d.ts.map +1 -1
  88. package/dist/plugin/index.js +2 -1
  89. package/dist/types/index.d.ts +58 -9
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/types/index.js +0 -13
  92. package/dist/utils/errors.d.ts +7 -0
  93. package/dist/utils/errors.d.ts.map +1 -1
  94. package/dist/utils/errors.js +15 -3
  95. package/dist/utils/index.d.ts +63 -5
  96. package/dist/utils/index.d.ts.map +1 -1
  97. package/dist/utils/index.js +109 -16
  98. package/dist/utils/logger.d.ts +31 -0
  99. package/dist/utils/logger.d.ts.map +1 -0
  100. package/dist/utils/logger.js +63 -0
  101. package/ios/AGENTS.md +33 -0
  102. package/ios/CLAUDE.md +49 -0
  103. package/ios/Plugin/KeychainStorage.swift +139 -50
  104. package/ios/Plugin/SQLiteStorage.swift +40 -0
  105. package/ios/Plugin/StrataStoragePlugin.m +23 -0
  106. package/ios/Plugin/StrataStoragePlugin.swift +201 -52
  107. package/package.json +35 -23
  108. package/scripts/build.js +16 -5
  109. package/scripts/configure.js +2 -6
  110. package/scripts/postinstall.js +2 -2
  111. 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;CAW7D;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;;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"}
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"}
@@ -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
- if (Error.captureStackTrace) {
17
- Error.captureStackTrace(this, this.constructor);
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
  */
@@ -23,7 +23,16 @@ export declare function isCapacitor(): boolean;
23
23
  */
24
24
  export declare function deepClone<T>(obj: T): T;
25
25
  /**
26
- * Deep merge objects
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
- * Simple glob pattern matching
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
- * Check if a value is a valid storage key
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":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"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"}
@@ -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
- return (typeof process !== 'undefined' &&
17
- process.versions !== undefined &&
18
- process.versions.node !== undefined);
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
- * Deep merge objects
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
- * Simple glob pattern matching
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 new RegExp(`^${regexPattern}$`).test(str);
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, (_, val) => {
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 ? new RegExp(match[1], match[2]) : new RegExp(val.value);
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
- console.error(`Error in event handler for ${event}:`, error);
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
- * Check if a value is a valid storage key
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
- // Allow all values except undefined
346
- return value !== undefined;
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