@okint-digital/okint-rn-storage 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +217 -0
  3. package/android/build.gradle +59 -0
  4. package/android/proguard-rules.pro +8 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/com/okint/rnstorage/OkintRnStorageModule.kt +354 -0
  7. package/android/src/main/java/com/okint/rnstorage/OkintRnStoragePackage.kt +14 -0
  8. package/android/src/main/jni/CMakeLists.txt +24 -0
  9. package/android/src/main/jni/OkintJNI.cpp +15 -0
  10. package/cpp/OkintJSI.cpp +170 -0
  11. package/cpp/OkintJSI.h +18 -0
  12. package/ios/OkintRnStorage.m +457 -0
  13. package/ios/OkintRnStorageJSI.mm +24 -0
  14. package/lib/backends/memory.d.ts +16 -0
  15. package/lib/backends/memory.js +31 -0
  16. package/lib/backends/native-backend.d.ts +24 -0
  17. package/lib/backends/native-backend.js +73 -0
  18. package/lib/errors.d.ts +9 -0
  19. package/lib/errors.js +19 -0
  20. package/lib/facade.d.ts +29 -0
  21. package/lib/facade.js +93 -0
  22. package/lib/index.d.ts +48 -0
  23. package/lib/index.js +172 -0
  24. package/lib/native/bridge.d.ts +12 -0
  25. package/lib/native/bridge.js +23 -0
  26. package/lib/native/jsi.d.ts +7 -0
  27. package/lib/native/jsi.js +33 -0
  28. package/lib/sync/jsi-store.d.ts +28 -0
  29. package/lib/sync/jsi-store.js +81 -0
  30. package/lib/sync/persistence.d.ts +19 -0
  31. package/lib/sync/persistence.js +49 -0
  32. package/lib/sync/sync-store.d.ts +49 -0
  33. package/lib/sync/sync-store.js +159 -0
  34. package/lib/types.d.ts +140 -0
  35. package/lib/types.js +10 -0
  36. package/lib/validate.d.ts +20 -0
  37. package/lib/validate.js +91 -0
  38. package/okint-rn-storage.podspec +27 -0
  39. package/package.json +74 -0
  40. package/react-native.config.js +15 -0
  41. package/src/backends/memory.ts +35 -0
  42. package/src/backends/native-backend.ts +69 -0
  43. package/src/errors.ts +26 -0
  44. package/src/facade.ts +118 -0
  45. package/src/index.ts +194 -0
  46. package/src/native/bridge.ts +28 -0
  47. package/src/native/jsi.ts +37 -0
  48. package/src/sync/jsi-store.ts +98 -0
  49. package/src/sync/persistence.ts +47 -0
  50. package/src/sync/sync-store.ts +186 -0
  51. package/src/types.ts +174 -0
  52. package/src/validate.ts +102 -0
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JSISyncStore = void 0;
4
+ const validate_1 = require("../validate");
5
+ /**
6
+ * OkintSyncStorage backed by the C++/JSI HostObject. Reads and writes go
7
+ * straight into C++ with no bridge serialization and no snapshot — the
8
+ * maximum-performance synchronous path. Writes are persisted synchronously by
9
+ * the native engine, so `flush()` is a no-op.
10
+ */
11
+ class JSISyncStore {
12
+ store;
13
+ backend = 'fast';
14
+ constructor(store) {
15
+ this.store = store;
16
+ }
17
+ getString(key) {
18
+ (0, validate_1.assertKey)(key);
19
+ return this.store.getString(key);
20
+ }
21
+ setString(key, value) {
22
+ (0, validate_1.assertKey)(key);
23
+ (0, validate_1.assertStringValue)(value);
24
+ this.store.setString(key, value);
25
+ }
26
+ getItem(key) {
27
+ const raw = this.getString(key);
28
+ return raw == null ? null : (0, validate_1.fromJson)(key, raw);
29
+ }
30
+ setItem(key, value) {
31
+ (0, validate_1.assertKey)(key);
32
+ this.store.setString(key, (0, validate_1.toJson)(key, value));
33
+ }
34
+ getNumber(key) {
35
+ const raw = this.getString(key);
36
+ return raw == null ? null : (0, validate_1.stringToNumber)(raw);
37
+ }
38
+ setNumber(key, value) {
39
+ (0, validate_1.assertKey)(key);
40
+ this.store.setString(key, (0, validate_1.numberToString)(key, value));
41
+ }
42
+ getBoolean(key) {
43
+ const raw = this.getString(key);
44
+ return raw == null ? null : (0, validate_1.stringToBoolean)(raw);
45
+ }
46
+ setBoolean(key, value) {
47
+ this.setString(key, value ? 'true' : 'false');
48
+ }
49
+ has(key) {
50
+ (0, validate_1.assertKey)(key);
51
+ return this.store.contains(key);
52
+ }
53
+ remove(key) {
54
+ (0, validate_1.assertKey)(key);
55
+ this.store.remove(key);
56
+ }
57
+ clear() {
58
+ this.store.clear();
59
+ }
60
+ keys() {
61
+ return this.store.getAllKeys();
62
+ }
63
+ multiGet(keys) {
64
+ const out = {};
65
+ for (const k of keys)
66
+ out[k] = this.getString(k);
67
+ return out;
68
+ }
69
+ multiSet(entries) {
70
+ for (const [k, v] of Object.entries(entries))
71
+ this.setString(k, v);
72
+ }
73
+ multiRemove(keys) {
74
+ for (const k of keys)
75
+ this.remove(k);
76
+ }
77
+ async flush() {
78
+ // The JSI engine persists synchronously on each write — nothing to flush.
79
+ }
80
+ }
81
+ exports.JSISyncStore = JSISyncStore;
@@ -0,0 +1,19 @@
1
+ import type { StorageBackend, SyncPersistence } from '../types';
2
+ /** No-op persistence for the ephemeral `memory` sync store. */
3
+ export declare class MemorySyncPersistence implements SyncPersistence {
4
+ loadAll(): Promise<Record<string, string>>;
5
+ persist(): Promise<void>;
6
+ clearAll(): Promise<void>;
7
+ }
8
+ /**
9
+ * Persists a sync store to any async StorageBackend (e.g. the native plain
10
+ * store). Pure TS — takes the backend by injection, so it's testable in Node
11
+ * with a MemoryBackend standing in for native.
12
+ */
13
+ export declare class BackendSyncPersistence implements SyncPersistence {
14
+ private readonly backend;
15
+ constructor(backend: StorageBackend);
16
+ loadAll(): Promise<Record<string, string>>;
17
+ persist(key: string, value: string | null): Promise<void>;
18
+ clearAll(): Promise<void>;
19
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BackendSyncPersistence = exports.MemorySyncPersistence = void 0;
4
+ /** No-op persistence for the ephemeral `memory` sync store. */
5
+ class MemorySyncPersistence {
6
+ async loadAll() {
7
+ return {};
8
+ }
9
+ async persist() {
10
+ /* nothing to persist */
11
+ }
12
+ async clearAll() {
13
+ /* nothing to clear */
14
+ }
15
+ }
16
+ exports.MemorySyncPersistence = MemorySyncPersistence;
17
+ /**
18
+ * Persists a sync store to any async StorageBackend (e.g. the native plain
19
+ * store). Pure TS — takes the backend by injection, so it's testable in Node
20
+ * with a MemoryBackend standing in for native.
21
+ */
22
+ class BackendSyncPersistence {
23
+ backend;
24
+ constructor(backend) {
25
+ this.backend = backend;
26
+ }
27
+ async loadAll() {
28
+ const keys = await this.backend.keys();
29
+ const out = {};
30
+ await Promise.all(keys.map(async (k) => {
31
+ const v = await this.backend.getString(k);
32
+ if (v !== null)
33
+ out[k] = v;
34
+ }));
35
+ return out;
36
+ }
37
+ async persist(key, value) {
38
+ if (value === null) {
39
+ await this.backend.remove(key);
40
+ }
41
+ else {
42
+ await this.backend.setString(key, value);
43
+ }
44
+ }
45
+ async clearAll() {
46
+ await this.backend.clear();
47
+ }
48
+ }
49
+ exports.BackendSyncPersistence = BackendSyncPersistence;
@@ -0,0 +1,49 @@
1
+ import type { OkintSyncStorage, SyncBackendKind, SyncPersistence } from '../types';
2
+ /**
3
+ * Synchronous storage over an in-memory snapshot. After `load()`, every accessor
4
+ * is synchronous; writes update the map immediately and are persisted to the
5
+ * backing `SyncPersistence` in the background.
6
+ *
7
+ * Persistence is **coalesced**: a burst of writes collapses to the latest value
8
+ * per key and is drained once per microtask (bounded by the number of distinct
9
+ * dirty keys, not the number of writes). `flush()` awaits durability and
10
+ * surfaces any background failure.
11
+ */
12
+ export declare class OkintSyncStore implements OkintSyncStorage {
13
+ private readonly persistence;
14
+ readonly backend: SyncBackendKind;
15
+ private map;
16
+ private loaded;
17
+ private pending;
18
+ private pendingClear;
19
+ private drainScheduled;
20
+ private chain;
21
+ private persistErrors;
22
+ constructor(backend: SyncBackendKind, persistence: SyncPersistence);
23
+ /** Load the snapshot. Idempotent — the factory calls it once; extra calls no-op. */
24
+ load(): Promise<void>;
25
+ /**
26
+ * Synchronous hydration (the zero-load path). Used by `createSyncStorageSync`
27
+ * after one blocking native bulk-read. Idempotent like `load()`.
28
+ */
29
+ loadSync(entries: Record<string, string>): void;
30
+ getString(key: string): string | null;
31
+ setString(key: string, value: string): void;
32
+ getItem<T>(key: string): T | null;
33
+ setItem<T>(key: string, value: T): void;
34
+ getNumber(key: string): number | null;
35
+ setNumber(key: string, value: number): void;
36
+ getBoolean(key: string): boolean | null;
37
+ setBoolean(key: string, value: boolean): void;
38
+ has(key: string): boolean;
39
+ remove(key: string): void;
40
+ clear(): void;
41
+ keys(): string[];
42
+ multiGet(keys: string[]): Record<string, string | null>;
43
+ multiSet(entries: Record<string, string>): void;
44
+ multiRemove(keys: string[]): void;
45
+ flush(): Promise<void>;
46
+ private queueWrite;
47
+ private schedule;
48
+ private drain;
49
+ }
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OkintSyncStore = void 0;
4
+ const errors_1 = require("../errors");
5
+ const validate_1 = require("../validate");
6
+ /**
7
+ * Synchronous storage over an in-memory snapshot. After `load()`, every accessor
8
+ * is synchronous; writes update the map immediately and are persisted to the
9
+ * backing `SyncPersistence` in the background.
10
+ *
11
+ * Persistence is **coalesced**: a burst of writes collapses to the latest value
12
+ * per key and is drained once per microtask (bounded by the number of distinct
13
+ * dirty keys, not the number of writes). `flush()` awaits durability and
14
+ * surfaces any background failure.
15
+ */
16
+ class OkintSyncStore {
17
+ persistence;
18
+ backend;
19
+ map = new Map();
20
+ loaded = false;
21
+ // Coalescing state.
22
+ pending = new Map(); // value === null → delete
23
+ pendingClear = false;
24
+ drainScheduled = false;
25
+ chain = Promise.resolve();
26
+ persistErrors = [];
27
+ constructor(backend, persistence) {
28
+ this.persistence = persistence;
29
+ this.backend = backend;
30
+ }
31
+ /** Load the snapshot. Idempotent — the factory calls it once; extra calls no-op. */
32
+ async load() {
33
+ if (this.loaded)
34
+ return;
35
+ const all = await this.persistence.loadAll();
36
+ this.map = new Map(Object.entries(all));
37
+ this.loaded = true;
38
+ }
39
+ /**
40
+ * Synchronous hydration (the zero-load path). Used by `createSyncStorageSync`
41
+ * after one blocking native bulk-read. Idempotent like `load()`.
42
+ */
43
+ loadSync(entries) {
44
+ if (this.loaded)
45
+ return;
46
+ this.map = new Map(Object.entries(entries));
47
+ this.loaded = true;
48
+ }
49
+ getString(key) {
50
+ (0, validate_1.assertKey)(key);
51
+ const v = this.map.get(key);
52
+ return v === undefined ? null : v;
53
+ }
54
+ setString(key, value) {
55
+ (0, validate_1.assertKey)(key);
56
+ (0, validate_1.assertStringValue)(value);
57
+ this.map.set(key, value);
58
+ this.queueWrite(key, value);
59
+ }
60
+ getItem(key) {
61
+ const raw = this.getString(key);
62
+ return raw == null ? null : (0, validate_1.fromJson)(key, raw);
63
+ }
64
+ setItem(key, value) {
65
+ (0, validate_1.assertKey)(key);
66
+ const json = (0, validate_1.toJson)(key, value);
67
+ this.map.set(key, json);
68
+ this.queueWrite(key, json);
69
+ }
70
+ getNumber(key) {
71
+ const raw = this.getString(key);
72
+ return raw == null ? null : (0, validate_1.stringToNumber)(raw);
73
+ }
74
+ setNumber(key, value) {
75
+ (0, validate_1.assertKey)(key);
76
+ const s = (0, validate_1.numberToString)(key, value);
77
+ this.map.set(key, s);
78
+ this.queueWrite(key, s);
79
+ }
80
+ getBoolean(key) {
81
+ const raw = this.getString(key);
82
+ return raw == null ? null : (0, validate_1.stringToBoolean)(raw);
83
+ }
84
+ setBoolean(key, value) {
85
+ this.setString(key, value ? 'true' : 'false');
86
+ }
87
+ has(key) {
88
+ (0, validate_1.assertKey)(key);
89
+ return this.map.has(key);
90
+ }
91
+ remove(key) {
92
+ (0, validate_1.assertKey)(key);
93
+ this.map.delete(key);
94
+ this.queueWrite(key, null);
95
+ }
96
+ clear() {
97
+ this.map.clear();
98
+ this.pending.clear(); // superseded by the clear
99
+ this.pendingClear = true;
100
+ this.schedule();
101
+ }
102
+ keys() {
103
+ return [...this.map.keys()];
104
+ }
105
+ multiGet(keys) {
106
+ const out = {};
107
+ for (const k of keys)
108
+ out[k] = this.getString(k);
109
+ return out;
110
+ }
111
+ multiSet(entries) {
112
+ for (const [k, v] of Object.entries(entries))
113
+ this.setString(k, v);
114
+ }
115
+ multiRemove(keys) {
116
+ for (const k of keys)
117
+ this.remove(k);
118
+ }
119
+ async flush() {
120
+ if ((this.pending.size > 0 || this.pendingClear) && !this.drainScheduled) {
121
+ this.schedule();
122
+ }
123
+ await this.chain;
124
+ if (this.persistErrors.length > 0) {
125
+ const first = this.persistErrors[0];
126
+ const count = this.persistErrors.length;
127
+ this.persistErrors = [];
128
+ throw new errors_1.OkintStorageError('NATIVE_ERROR', `${count} background persist operation(s) failed.`, first);
129
+ }
130
+ }
131
+ queueWrite(key, value) {
132
+ this.pending.set(key, value);
133
+ this.schedule();
134
+ }
135
+ schedule() {
136
+ if (this.drainScheduled)
137
+ return;
138
+ this.drainScheduled = true;
139
+ this.chain = this.chain.then(() => this.drain());
140
+ }
141
+ async drain() {
142
+ this.drainScheduled = false;
143
+ const doClear = this.pendingClear;
144
+ this.pendingClear = false;
145
+ const writes = this.pending;
146
+ this.pending = new Map();
147
+ try {
148
+ if (doClear)
149
+ await this.persistence.clearAll();
150
+ for (const [key, value] of writes) {
151
+ await this.persistence.persist(key, value);
152
+ }
153
+ }
154
+ catch (e) {
155
+ this.persistErrors.push(e);
156
+ }
157
+ }
158
+ }
159
+ exports.OkintSyncStore = OkintSyncStore;
package/lib/types.d.ts ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * okint-rn-storage — public types.
3
+ *
4
+ * One async API, several swappable backends. Pick the backend per the data's
5
+ * needs: secrets → `secure` (hardware Keystore / Keychain); large hardware-
6
+ * encrypted blobs → `encrypted`; structured/large data → `sqlite`; plain data →
7
+ * `async`; ephemeral → `memory`.
8
+ */
9
+ export type BackendKind = 'memory' | 'secure' | 'async' | 'encrypted' | 'sqlite';
10
+ /** The native-backed stores (everything except the pure-JS `memory`). */
11
+ export type NativeStoreKind = 'secure' | 'async' | 'encrypted' | 'sqlite';
12
+ export interface OkintStorageOptions {
13
+ /** Which storage backend to use. */
14
+ backend: BackendKind;
15
+ /**
16
+ * Logical store name. Partitions data so two stores never collide. Maps to:
17
+ * - secure → Keychain service (iOS) / EncryptedSharedPreferences file (Android)
18
+ * - async → UserDefaults suite (iOS) / SharedPreferences file (Android)
19
+ * - memory → a private in-process Map
20
+ * Defaults to `'okint'`.
21
+ */
22
+ namespace?: string;
23
+ }
24
+ /**
25
+ * Low-level backend contract. Everything is async (the secure/native backends
26
+ * cross the bridge). Keys reaching a backend are already partitioned by
27
+ * namespace at construction, so backends store keys verbatim.
28
+ */
29
+ export interface StorageBackend {
30
+ readonly kind: BackendKind;
31
+ getString(key: string): Promise<string | null>;
32
+ setString(key: string, value: string): Promise<void>;
33
+ remove(key: string): Promise<void>;
34
+ clear(): Promise<void>;
35
+ keys(): Promise<string[]>;
36
+ }
37
+ /**
38
+ * The storage instance returned by `createStorage`. Adds ergonomic typed
39
+ * accessors (JSON / number / boolean) on top of the raw string backend.
40
+ */
41
+ export interface OkintStorage {
42
+ /** The backend kind backing this instance. */
43
+ readonly backend: BackendKind;
44
+ getString(key: string): Promise<string | null>;
45
+ setString(key: string, value: string): Promise<void>;
46
+ /** JSON-parsed read. Throws OkintStorageError('PARSE_ERROR') on malformed data. */
47
+ getItem<T>(key: string): Promise<T | null>;
48
+ /** JSON-stringified write. */
49
+ setItem<T>(key: string, value: T): Promise<void>;
50
+ getNumber(key: string): Promise<number | null>;
51
+ setNumber(key: string, value: number): Promise<void>;
52
+ getBoolean(key: string): Promise<boolean | null>;
53
+ setBoolean(key: string, value: boolean): Promise<void>;
54
+ has(key: string): Promise<boolean>;
55
+ remove(key: string): Promise<void>;
56
+ clear(): Promise<void>;
57
+ keys(): Promise<string[]>;
58
+ /** Batched string reads/writes/removes. */
59
+ multiGet(keys: string[]): Promise<Record<string, string | null>>;
60
+ multiSet(entries: Record<string, string>): Promise<void>;
61
+ multiRemove(keys: string[]): Promise<void>;
62
+ }
63
+ export type SyncBackendKind = 'memory' | 'fast';
64
+ export interface OkintSyncStorageOptions {
65
+ backend: SyncBackendKind;
66
+ /** Partitions the store (see OkintStorageOptions.namespace). Defaults to 'okint'. */
67
+ namespace?: string;
68
+ }
69
+ /**
70
+ * A synchronous storage instance. Obtained via `await createSyncStorage(...)`,
71
+ * which resolves only after the initial snapshot has loaded — so every method
72
+ * below is safe to call synchronously thereafter.
73
+ */
74
+ export interface OkintSyncStorage {
75
+ readonly backend: SyncBackendKind;
76
+ getString(key: string): string | null;
77
+ setString(key: string, value: string): void;
78
+ getItem<T>(key: string): T | null;
79
+ setItem<T>(key: string, value: T): void;
80
+ getNumber(key: string): number | null;
81
+ setNumber(key: string, value: number): void;
82
+ getBoolean(key: string): boolean | null;
83
+ setBoolean(key: string, value: boolean): void;
84
+ has(key: string): boolean;
85
+ remove(key: string): void;
86
+ clear(): void;
87
+ keys(): string[];
88
+ /** Batched (synchronous) reads/writes/removes. */
89
+ multiGet(keys: string[]): Record<string, string | null>;
90
+ multiSet(entries: Record<string, string>): void;
91
+ multiRemove(keys: string[]): void;
92
+ /**
93
+ * Await pending background writes (the `fast` store persists asynchronously).
94
+ * Call on app background / before exit for guaranteed durability. Rejects if a
95
+ * background persist failed.
96
+ */
97
+ flush(): Promise<void>;
98
+ }
99
+ /** Persistence sink for sync stores. Snapshot load + per-key write-through. */
100
+ export interface SyncPersistence {
101
+ loadAll(): Promise<Record<string, string>>;
102
+ /** value === null → delete the key. */
103
+ persist(key: string, value: string | null): Promise<void>;
104
+ clearAll(): Promise<void>;
105
+ }
106
+ /**
107
+ * Shape of the native module (Android/iOS). The JS layer talks to this; tests
108
+ * inject a fake implementation. `store` selects which native store to target.
109
+ */
110
+ export interface NativeOkintStorage {
111
+ setItem(service: string, key: string, value: string, store: NativeStoreKind): Promise<void>;
112
+ getItem(service: string, key: string, store: NativeStoreKind): Promise<string | null>;
113
+ removeItem(service: string, key: string, store: NativeStoreKind): Promise<void>;
114
+ clear(service: string, store: NativeStoreKind): Promise<void>;
115
+ getAllKeys(service: string, store: NativeStoreKind): Promise<string[]>;
116
+ /**
117
+ * Synchronous bulk read (blocking bridge call) used by the zero-load sync
118
+ * store: it hydrates the in-memory snapshot in one shot at construction, so
119
+ * subsequent reads are pure in-JS Map lookups (no per-call bridge crossing).
120
+ */
121
+ getEntriesSync(service: string, store: NativeStoreKind): Record<string, string>;
122
+ /**
123
+ * Install the C++/JSI fast-path engine into the JS runtime (exposes
124
+ * `global.__okintCreateJSI`). Returns false if the runtime isn't reachable
125
+ * (e.g. remote JS debugging). Blocking-synchronous.
126
+ */
127
+ installJSI(): boolean;
128
+ }
129
+ /**
130
+ * The C++ JSI HostObject returned by `global.__okintCreateJSI(namespace)`.
131
+ * Every method is synchronous and crosses no bridge — the fastest path.
132
+ */
133
+ export interface JSIStore {
134
+ getString(key: string): string | null;
135
+ setString(key: string, value: string): void;
136
+ remove(key: string): void;
137
+ clear(): void;
138
+ contains(key: string): boolean;
139
+ getAllKeys(): string[];
140
+ }
package/lib/types.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * okint-rn-storage — public types.
4
+ *
5
+ * One async API, several swappable backends. Pick the backend per the data's
6
+ * needs: secrets → `secure` (hardware Keystore / Keychain); large hardware-
7
+ * encrypted blobs → `encrypted`; structured/large data → `sqlite`; plain data →
8
+ * `async`; ephemeral → `memory`.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,20 @@
1
+ /** Validate + default the namespace. Throws on anything unsafe. */
2
+ export declare function normalizeNamespace(ns: string | undefined, fallback: string): string;
3
+ /**
4
+ * Validate a key. Keys are stored verbatim (not used as filenames), so the rules
5
+ * are looser than namespaces — but must be a non-empty string free of control
6
+ * characters (NUL etc.), within a sane length.
7
+ */
8
+ export declare function assertKey(key: string): void;
9
+ /** Serialize a value to JSON, surfacing non-serializable inputs as a typed error. */
10
+ export declare function toJson(key: string, value: unknown): string;
11
+ /** Parse JSON, surfacing malformed data as a typed error. */
12
+ export declare function fromJson<T>(key: string, raw: string): T;
13
+ /** Serialize a number, rejecting NaN/+-Infinity (which don't round-trip). */
14
+ export declare function numberToString(key: string, value: number): string;
15
+ /** Parse a stored number; non-numeric -> null. */
16
+ export declare function stringToNumber(raw: string): number | null;
17
+ /** Strict boolean parse: only canonical 'true'/'false' map; anything else -> null. */
18
+ export declare function stringToBoolean(raw: string): boolean | null;
19
+ /** Assert a raw value is a string (for setString / multiSet). */
20
+ export declare function assertStringValue(value: unknown): asserts value is string;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeNamespace = normalizeNamespace;
4
+ exports.assertKey = assertKey;
5
+ exports.toJson = toJson;
6
+ exports.fromJson = fromJson;
7
+ exports.numberToString = numberToString;
8
+ exports.stringToNumber = stringToNumber;
9
+ exports.stringToBoolean = stringToBoolean;
10
+ exports.assertStringValue = assertStringValue;
11
+ const errors_1 = require("./errors");
12
+ /**
13
+ * Namespace becomes an Android SharedPreferences FILE NAME and an iOS Keychain
14
+ * service / UserDefaults suite. It must therefore be filename-safe — no path
15
+ * separators, `..`, NUL, or other characters that could traverse or collide.
16
+ * Restrict to a conservative charset.
17
+ */
18
+ const NAMESPACE_RE = /^[A-Za-z0-9._-]{1,200}$/;
19
+ const MAX_KEY_LENGTH = 1024;
20
+ /** Validate + default the namespace. Throws on anything unsafe. */
21
+ function normalizeNamespace(ns, fallback) {
22
+ const value = (typeof ns === 'string' ? ns.trim() : '') || fallback;
23
+ if (!NAMESPACE_RE.test(value)) {
24
+ throw new errors_1.OkintStorageError('INVALID_NAMESPACE', `Invalid namespace "${String(ns)}". Use only letters, digits, "." "-" "_" (1-200 chars).`);
25
+ }
26
+ return value;
27
+ }
28
+ /**
29
+ * Validate a key. Keys are stored verbatim (not used as filenames), so the rules
30
+ * are looser than namespaces — but must be a non-empty string free of control
31
+ * characters (NUL etc.), within a sane length.
32
+ */
33
+ function assertKey(key) {
34
+ if (typeof key !== 'string' || key.length === 0) {
35
+ throw new errors_1.OkintStorageError('INVALID_KEY', 'Key must be a non-empty string.');
36
+ }
37
+ if (key.length > MAX_KEY_LENGTH) {
38
+ throw new errors_1.OkintStorageError('INVALID_KEY', `Key exceeds the ${MAX_KEY_LENGTH}-character limit.`);
39
+ }
40
+ for (let i = 0; i < key.length; i++) {
41
+ if (key.charCodeAt(i) < 0x20) {
42
+ throw new errors_1.OkintStorageError('INVALID_KEY', 'Key must not contain control characters.');
43
+ }
44
+ }
45
+ }
46
+ /** Serialize a value to JSON, surfacing non-serializable inputs as a typed error. */
47
+ function toJson(key, value) {
48
+ let json;
49
+ try {
50
+ json = JSON.stringify(value);
51
+ }
52
+ catch (e) {
53
+ throw new errors_1.OkintStorageError('INVALID_VALUE', `Value for "${key}" is not JSON-serializable (circular reference or BigInt).`, e);
54
+ }
55
+ if (json === undefined) {
56
+ throw new errors_1.OkintStorageError('INVALID_VALUE', `Value for "${key}" is not JSON-serializable (undefined, function, or symbol).`);
57
+ }
58
+ return json;
59
+ }
60
+ /** Parse JSON, surfacing malformed data as a typed error. */
61
+ function fromJson(key, raw) {
62
+ try {
63
+ return JSON.parse(raw);
64
+ }
65
+ catch (e) {
66
+ throw new errors_1.OkintStorageError('PARSE_ERROR', `Stored value for "${key}" is not valid JSON.`, e);
67
+ }
68
+ }
69
+ /** Serialize a number, rejecting NaN/+-Infinity (which don't round-trip). */
70
+ function numberToString(key, value) {
71
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
72
+ throw new errors_1.OkintStorageError('INVALID_VALUE', `setNumber requires a finite number for "${key}" (got ${String(value)}). ` +
73
+ `For integers above 2^53 use setString to avoid precision loss.`);
74
+ }
75
+ return String(value);
76
+ }
77
+ /** Parse a stored number; non-numeric -> null. */
78
+ function stringToNumber(raw) {
79
+ const n = Number(raw);
80
+ return Number.isFinite(n) ? n : null;
81
+ }
82
+ /** Strict boolean parse: only canonical 'true'/'false' map; anything else -> null. */
83
+ function stringToBoolean(raw) {
84
+ return raw === 'true' ? true : raw === 'false' ? false : null;
85
+ }
86
+ /** Assert a raw value is a string (for setString / multiSet). */
87
+ function assertStringValue(value) {
88
+ if (typeof value !== 'string') {
89
+ throw new errors_1.OkintStorageError('INVALID_VALUE', 'Value must be a string.');
90
+ }
91
+ }
@@ -0,0 +1,27 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "okint-rn-storage"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+ s.platforms = { :ios => "15.1" }
13
+ s.source = { :git => "https://github.com/okint-digital/okint-rn-storage.git", :tag => "#{s.version}" }
14
+
15
+ s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}"
16
+ s.requires_arc = true
17
+ s.frameworks = "Security"
18
+ s.libraries = "sqlite3"
19
+
20
+ s.pod_target_xcconfig = {
21
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
22
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp\""
23
+ }
24
+
25
+ s.dependency "React-Core"
26
+ s.dependency "React-jsi"
27
+ end