@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,24 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <React/RCTBridge.h>
3
+ #import <React/RCTBridge+Private.h>
4
+ #import <jsi/jsi.h>
5
+
6
+ #import "OkintJSI.h"
7
+
8
+ /**
9
+ * Installs the okint C++/JSI engine into the bridge's JS runtime. Isolated in an
10
+ * Obj-C++ (.mm) file so the main Obj-C module stays free of C++.
11
+ */
12
+ BOOL OkintInstallJSIForBridge(RCTBridge *bridge) {
13
+ RCTCxxBridge *cxxBridge = (RCTCxxBridge *)bridge;
14
+ if (cxxBridge == nil || ![cxxBridge respondsToSelector:@selector(runtime)]) {
15
+ return NO;
16
+ }
17
+ void *runtimePtr = cxxBridge.runtime;
18
+ if (runtimePtr == NULL) {
19
+ return NO;
20
+ }
21
+ NSString *dir = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
22
+ okint::install(*reinterpret_cast<facebook::jsi::Runtime *>(runtimePtr), std::string([dir UTF8String]));
23
+ return YES;
24
+ }
@@ -0,0 +1,16 @@
1
+ import type { BackendKind, StorageBackend } from '../types';
2
+ /**
3
+ * Pure-JS, in-process backend. Zero native dependencies. Data lives only for the
4
+ * lifetime of the instance — ideal for tests, ephemeral caches, and as a safe
5
+ * default before the native module is available.
6
+ */
7
+ export declare class MemoryBackend implements StorageBackend {
8
+ readonly kind: BackendKind;
9
+ private readonly store;
10
+ constructor(kind?: BackendKind);
11
+ getString(key: string): Promise<string | null>;
12
+ setString(key: string, value: string): Promise<void>;
13
+ remove(key: string): Promise<void>;
14
+ clear(): Promise<void>;
15
+ keys(): Promise<string[]>;
16
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryBackend = void 0;
4
+ /**
5
+ * Pure-JS, in-process backend. Zero native dependencies. Data lives only for the
6
+ * lifetime of the instance — ideal for tests, ephemeral caches, and as a safe
7
+ * default before the native module is available.
8
+ */
9
+ class MemoryBackend {
10
+ kind;
11
+ store = new Map();
12
+ constructor(kind = 'memory') {
13
+ this.kind = kind;
14
+ }
15
+ async getString(key) {
16
+ return this.store.has(key) ? this.store.get(key) : null;
17
+ }
18
+ async setString(key, value) {
19
+ this.store.set(key, value);
20
+ }
21
+ async remove(key) {
22
+ this.store.delete(key);
23
+ }
24
+ async clear() {
25
+ this.store.clear();
26
+ }
27
+ async keys() {
28
+ return [...this.store.keys()];
29
+ }
30
+ }
31
+ exports.MemoryBackend = MemoryBackend;
@@ -0,0 +1,24 @@
1
+ import type { NativeOkintStorage, NativeStoreKind, StorageBackend } from '../types';
2
+ /**
3
+ * Backend that delegates to the native module. One class drives all four
4
+ * native-backed stores, selected by `kind`, which is forwarded to native as the
5
+ * `store` discriminator:
6
+ * - `secure` → hardware Keystore / Keychain
7
+ * - `async` → SharedPreferences / UserDefaults (plaintext)
8
+ * - `encrypted` → AES-encrypted blobs, key in Keystore/Keychain (large values)
9
+ * - `sqlite` → SQLite-backed key/value
10
+ *
11
+ * The native module is injected (not imported here) so this file stays free of
12
+ * `react-native` and is unit-testable under plain Node with a fake bridge.
13
+ */
14
+ export declare class NativeBackend implements StorageBackend {
15
+ private readonly native;
16
+ private readonly service;
17
+ readonly kind: NativeStoreKind;
18
+ constructor(native: NativeOkintStorage, service: string, kind: NativeStoreKind);
19
+ getString(key: string): Promise<string | null>;
20
+ setString(key: string, value: string): Promise<void>;
21
+ remove(key: string): Promise<void>;
22
+ clear(): Promise<void>;
23
+ keys(): Promise<string[]>;
24
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NativeBackend = void 0;
4
+ const errors_1 = require("../errors");
5
+ /**
6
+ * Backend that delegates to the native module. One class drives all four
7
+ * native-backed stores, selected by `kind`, which is forwarded to native as the
8
+ * `store` discriminator:
9
+ * - `secure` → hardware Keystore / Keychain
10
+ * - `async` → SharedPreferences / UserDefaults (plaintext)
11
+ * - `encrypted` → AES-encrypted blobs, key in Keystore/Keychain (large values)
12
+ * - `sqlite` → SQLite-backed key/value
13
+ *
14
+ * The native module is injected (not imported here) so this file stays free of
15
+ * `react-native` and is unit-testable under plain Node with a fake bridge.
16
+ */
17
+ class NativeBackend {
18
+ native;
19
+ service;
20
+ kind;
21
+ constructor(native, service, kind) {
22
+ this.native = native;
23
+ this.service = service;
24
+ this.kind = kind;
25
+ }
26
+ async getString(key) {
27
+ try {
28
+ const v = await this.native.getItem(this.service, key, this.kind);
29
+ return v ?? null;
30
+ }
31
+ catch (e) {
32
+ throw wrap(e, `get "${key}"`);
33
+ }
34
+ }
35
+ async setString(key, value) {
36
+ try {
37
+ await this.native.setItem(this.service, key, value, this.kind);
38
+ }
39
+ catch (e) {
40
+ throw wrap(e, `set "${key}"`);
41
+ }
42
+ }
43
+ async remove(key) {
44
+ try {
45
+ await this.native.removeItem(this.service, key, this.kind);
46
+ }
47
+ catch (e) {
48
+ throw wrap(e, `remove "${key}"`);
49
+ }
50
+ }
51
+ async clear() {
52
+ try {
53
+ await this.native.clear(this.service, this.kind);
54
+ }
55
+ catch (e) {
56
+ throw wrap(e, 'clear');
57
+ }
58
+ }
59
+ async keys() {
60
+ try {
61
+ const ks = await this.native.getAllKeys(this.service, this.kind);
62
+ return ks ?? [];
63
+ }
64
+ catch (e) {
65
+ throw wrap(e, 'keys');
66
+ }
67
+ }
68
+ }
69
+ exports.NativeBackend = NativeBackend;
70
+ function wrap(cause, op) {
71
+ const msg = cause instanceof Error ? cause.message : String(cause);
72
+ return new errors_1.OkintStorageError('NATIVE_ERROR', `Native storage failed during ${op}: ${msg}`, cause);
73
+ }
@@ -0,0 +1,9 @@
1
+ export type OkintStorageErrorCode = 'NATIVE_MODULE_MISSING' | 'BACKEND_NOT_IMPLEMENTED' | 'UNKNOWN_BACKEND' | 'PARSE_ERROR' | 'INVALID_VALUE' | 'INVALID_NAMESPACE' | 'INVALID_KEY' | 'NATIVE_ERROR';
2
+ /**
3
+ * Single error type for the whole package. Carries a stable `code` so callers
4
+ * can branch without string-matching messages.
5
+ */
6
+ export declare class OkintStorageError extends Error {
7
+ readonly code: OkintStorageErrorCode;
8
+ constructor(code: OkintStorageErrorCode, message: string, cause?: unknown);
9
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OkintStorageError = void 0;
4
+ /**
5
+ * Single error type for the whole package. Carries a stable `code` so callers
6
+ * can branch without string-matching messages.
7
+ */
8
+ class OkintStorageError extends Error {
9
+ code;
10
+ constructor(code, message, cause) {
11
+ // `cause` is carried by the native Error.cause (ES2022) — we don't redeclare it.
12
+ super(message, cause !== undefined ? { cause } : undefined);
13
+ this.name = 'OkintStorageError';
14
+ this.code = code;
15
+ // Restore prototype chain (TS targeting ES5/ES2015 class-extends-builtin caveat).
16
+ Object.setPrototypeOf(this, OkintStorageError.prototype);
17
+ }
18
+ }
19
+ exports.OkintStorageError = OkintStorageError;
@@ -0,0 +1,29 @@
1
+ import type { BackendKind, OkintStorage, StorageBackend } from './types';
2
+ /**
3
+ * Wraps a low-level StorageBackend with ergonomic, typed accessors. All values
4
+ * are persisted as strings; the facade handles JSON / number / boolean
5
+ * (de)serialization, key validation, and typed errors.
6
+ *
7
+ * Every method is async and REJECTS (never throws synchronously) on validation
8
+ * errors, so callers can rely on a single error channel (.catch / try-await).
9
+ */
10
+ export declare class StorageFacade implements OkintStorage {
11
+ private readonly impl;
12
+ constructor(impl: StorageBackend);
13
+ get backend(): BackendKind;
14
+ getString(key: string): Promise<string | null>;
15
+ setString(key: string, value: string): Promise<void>;
16
+ getItem<T>(key: string): Promise<T | null>;
17
+ setItem<T>(key: string, value: T): Promise<void>;
18
+ getNumber(key: string): Promise<number | null>;
19
+ setNumber(key: string, value: number): Promise<void>;
20
+ getBoolean(key: string): Promise<boolean | null>;
21
+ setBoolean(key: string, value: boolean): Promise<void>;
22
+ has(key: string): Promise<boolean>;
23
+ remove(key: string): Promise<void>;
24
+ clear(): Promise<void>;
25
+ keys(): Promise<string[]>;
26
+ multiGet(keys: string[]): Promise<Record<string, string | null>>;
27
+ multiSet(entries: Record<string, string>): Promise<void>;
28
+ multiRemove(keys: string[]): Promise<void>;
29
+ }
package/lib/facade.js ADDED
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorageFacade = void 0;
4
+ const validate_1 = require("./validate");
5
+ /**
6
+ * Wraps a low-level StorageBackend with ergonomic, typed accessors. All values
7
+ * are persisted as strings; the facade handles JSON / number / boolean
8
+ * (de)serialization, key validation, and typed errors.
9
+ *
10
+ * Every method is async and REJECTS (never throws synchronously) on validation
11
+ * errors, so callers can rely on a single error channel (.catch / try-await).
12
+ */
13
+ class StorageFacade {
14
+ impl;
15
+ constructor(impl) {
16
+ this.impl = impl;
17
+ }
18
+ get backend() {
19
+ return this.impl.kind;
20
+ }
21
+ async getString(key) {
22
+ (0, validate_1.assertKey)(key);
23
+ return this.impl.getString(key);
24
+ }
25
+ async setString(key, value) {
26
+ (0, validate_1.assertKey)(key);
27
+ (0, validate_1.assertStringValue)(value);
28
+ return this.impl.setString(key, value);
29
+ }
30
+ async getItem(key) {
31
+ (0, validate_1.assertKey)(key);
32
+ const raw = await this.impl.getString(key);
33
+ return raw == null ? null : (0, validate_1.fromJson)(key, raw);
34
+ }
35
+ async setItem(key, value) {
36
+ (0, validate_1.assertKey)(key);
37
+ return this.impl.setString(key, (0, validate_1.toJson)(key, value));
38
+ }
39
+ async getNumber(key) {
40
+ (0, validate_1.assertKey)(key);
41
+ const raw = await this.impl.getString(key);
42
+ return raw == null ? null : (0, validate_1.stringToNumber)(raw);
43
+ }
44
+ async setNumber(key, value) {
45
+ (0, validate_1.assertKey)(key);
46
+ return this.impl.setString(key, (0, validate_1.numberToString)(key, value));
47
+ }
48
+ async getBoolean(key) {
49
+ (0, validate_1.assertKey)(key);
50
+ const raw = await this.impl.getString(key);
51
+ return raw == null ? null : (0, validate_1.stringToBoolean)(raw);
52
+ }
53
+ async setBoolean(key, value) {
54
+ (0, validate_1.assertKey)(key);
55
+ return this.impl.setString(key, value ? 'true' : 'false');
56
+ }
57
+ async has(key) {
58
+ (0, validate_1.assertKey)(key);
59
+ return (await this.impl.getString(key)) !== null;
60
+ }
61
+ async remove(key) {
62
+ (0, validate_1.assertKey)(key);
63
+ return this.impl.remove(key);
64
+ }
65
+ clear() {
66
+ return this.impl.clear();
67
+ }
68
+ keys() {
69
+ return this.impl.keys();
70
+ }
71
+ async multiGet(keys) {
72
+ const out = {};
73
+ await Promise.all(keys.map(async (k) => {
74
+ (0, validate_1.assertKey)(k);
75
+ out[k] = await this.impl.getString(k);
76
+ }));
77
+ return out;
78
+ }
79
+ async multiSet(entries) {
80
+ await Promise.all(Object.entries(entries).map(([k, v]) => {
81
+ (0, validate_1.assertKey)(k);
82
+ (0, validate_1.assertStringValue)(v);
83
+ return this.impl.setString(k, v);
84
+ }));
85
+ }
86
+ async multiRemove(keys) {
87
+ await Promise.all(keys.map((k) => {
88
+ (0, validate_1.assertKey)(k);
89
+ return this.impl.remove(k);
90
+ }));
91
+ }
92
+ }
93
+ exports.StorageFacade = StorageFacade;
package/lib/index.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import type { OkintStorage, OkintStorageOptions, OkintSyncStorage, OkintSyncStorageOptions } from './types';
2
+ /**
3
+ * Create a storage instance bound to a backend + namespace.
4
+ *
5
+ * @example
6
+ * const secure = createStorage({ backend: 'secure', namespace: 'auth' });
7
+ * await secure.setString('refreshToken', token); // hardware-encrypted
8
+ *
9
+ * const cache = createStorage({ backend: 'memory' });
10
+ * await cache.setItem('campaigns', list);
11
+ */
12
+ export declare function createStorage(options: OkintStorageOptions): OkintStorage;
13
+ export declare function createSyncStorage(options: OkintSyncStorageOptions): Promise<OkintSyncStorage>;
14
+ /**
15
+ * Create a synchronous storage instance WITHOUT an async load step. Hydrates the
16
+ * snapshot in a single blocking native bulk-read, then all get/set are
17
+ * synchronous in-JS-memory ops (writes persist in the background). Use this when
18
+ * you need state available immediately at startup (e.g. before first render).
19
+ *
20
+ * @example
21
+ * const fast = createSyncStorageSync({ backend: 'fast', namespace: 'app' });
22
+ * const onboarded = fast.getBoolean('onboarded'); // sync, zero-load
23
+ */
24
+ export declare function createSyncStorageSync(options: OkintSyncStorageOptions): OkintSyncStorage;
25
+ /**
26
+ * Create a synchronous store backed by the C++/JSI engine — get/set go straight
27
+ * into C++ with no bridge serialization and no snapshot (maximum performance,
28
+ * zero JS memory overhead). Installs the native engine on first use; throws if
29
+ * the JSI runtime is unreachable (e.g. remote JS debugging) — fall back to
30
+ * `createSyncStorageSync` there.
31
+ *
32
+ * @example
33
+ * const kv = createJSIStorage({ namespace: 'app' });
34
+ * kv.setString('theme', 'dark'); // sync, in C++
35
+ * const theme = kv.getString('theme'); // sync, in C++
36
+ */
37
+ export declare function createJSIStorage(options?: {
38
+ namespace?: string;
39
+ }): OkintSyncStorage;
40
+ export { StorageFacade } from './facade';
41
+ export { MemoryBackend } from './backends/memory';
42
+ export { NativeBackend } from './backends/native-backend';
43
+ export { OkintSyncStore } from './sync/sync-store';
44
+ export { JSISyncStore } from './sync/jsi-store';
45
+ export { MemorySyncPersistence, BackendSyncPersistence } from './sync/persistence';
46
+ export { OkintStorageError } from './errors';
47
+ export type { OkintStorageErrorCode } from './errors';
48
+ export type { OkintStorage, OkintStorageOptions, StorageBackend, BackendKind, NativeOkintStorage, JSIStore, OkintSyncStorage, OkintSyncStorageOptions, SyncBackendKind, SyncPersistence, } from './types';
package/lib/index.js ADDED
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OkintStorageError = exports.BackendSyncPersistence = exports.MemorySyncPersistence = exports.JSISyncStore = exports.OkintSyncStore = exports.NativeBackend = exports.MemoryBackend = exports.StorageFacade = void 0;
4
+ exports.createStorage = createStorage;
5
+ exports.createSyncStorage = createSyncStorage;
6
+ exports.createSyncStorageSync = createSyncStorageSync;
7
+ exports.createJSIStorage = createJSIStorage;
8
+ const facade_1 = require("./facade");
9
+ const memory_1 = require("./backends/memory");
10
+ const native_backend_1 = require("./backends/native-backend");
11
+ const sync_store_1 = require("./sync/sync-store");
12
+ const jsi_store_1 = require("./sync/jsi-store");
13
+ const persistence_1 = require("./sync/persistence");
14
+ const bridge_1 = require("./native/bridge");
15
+ const jsi_1 = require("./native/jsi");
16
+ const errors_1 = require("./errors");
17
+ const validate_1 = require("./validate");
18
+ const DEFAULT_NAMESPACE = 'okint';
19
+ /**
20
+ * Create a storage instance bound to a backend + namespace.
21
+ *
22
+ * @example
23
+ * const secure = createStorage({ backend: 'secure', namespace: 'auth' });
24
+ * await secure.setString('refreshToken', token); // hardware-encrypted
25
+ *
26
+ * const cache = createStorage({ backend: 'memory' });
27
+ * await cache.setItem('campaigns', list);
28
+ */
29
+ function createStorage(options) {
30
+ const namespace = (0, validate_1.normalizeNamespace)(options.namespace, DEFAULT_NAMESPACE);
31
+ return new facade_1.StorageFacade(resolveBackend(options.backend, namespace));
32
+ }
33
+ function resolveBackend(kind, namespace) {
34
+ switch (kind) {
35
+ case 'memory':
36
+ return new memory_1.MemoryBackend('memory');
37
+ case 'secure':
38
+ case 'async':
39
+ case 'encrypted':
40
+ case 'sqlite':
41
+ return new native_backend_1.NativeBackend((0, bridge_1.getNativeModule)(), namespace, kind);
42
+ default:
43
+ throw new errors_1.OkintStorageError('UNKNOWN_BACKEND', `Unknown storage backend "${String(kind)}".`);
44
+ }
45
+ }
46
+ /**
47
+ * Create a SYNCHRONOUS storage instance. Resolves once the snapshot is loaded;
48
+ * thereafter all get/set are synchronous. This is the MMKV-style store — use it
49
+ * for state persistence/rehydration, feature flags, and hot-path UI state.
50
+ *
51
+ * @example
52
+ * const fast = await createSyncStorage({ backend: 'fast', namespace: 'app' });
53
+ * fast.setBoolean('onboarded', true); // sync write (persists in bg)
54
+ * const onboarded = fast.getBoolean('onboarded'); // sync read
55
+ * await fast.flush(); // ensure durability (e.g. on background)
56
+ */
57
+ // Sync stores are interned per (backend, namespace): two callers asking for the
58
+ // same fast store get the SAME instance, so their in-memory snapshots can't
59
+ // diverge and silently overwrite each other. The cached promise is evicted on
60
+ // load failure so a later call can retry.
61
+ const syncRegistry = new Map();
62
+ function createSyncStorage(options) {
63
+ // Promise-returning factory → surface validation as a rejection, not a sync throw.
64
+ let namespace;
65
+ try {
66
+ namespace = (0, validate_1.normalizeNamespace)(options.namespace, DEFAULT_NAMESPACE);
67
+ }
68
+ catch (e) {
69
+ return Promise.reject(e);
70
+ }
71
+ const registryKey = `${options.backend}:${namespace}`;
72
+ const existing = syncRegistry.get(registryKey);
73
+ if (existing)
74
+ return existing;
75
+ const built = buildSyncStore(options.backend, namespace);
76
+ syncRegistry.set(registryKey, built);
77
+ built.catch(() => syncRegistry.delete(registryKey));
78
+ return built;
79
+ }
80
+ // Synchronous, zero-load sync stores: hydrated in one blocking native call at
81
+ // construction, then pure in-JS reads (maximum read performance). Interned per
82
+ // (backend, namespace) like the async variant.
83
+ const syncRegistrySync = new Map();
84
+ /**
85
+ * Create a synchronous storage instance WITHOUT an async load step. Hydrates the
86
+ * snapshot in a single blocking native bulk-read, then all get/set are
87
+ * synchronous in-JS-memory ops (writes persist in the background). Use this when
88
+ * you need state available immediately at startup (e.g. before first render).
89
+ *
90
+ * @example
91
+ * const fast = createSyncStorageSync({ backend: 'fast', namespace: 'app' });
92
+ * const onboarded = fast.getBoolean('onboarded'); // sync, zero-load
93
+ */
94
+ function createSyncStorageSync(options) {
95
+ const namespace = (0, validate_1.normalizeNamespace)(options.namespace, DEFAULT_NAMESPACE);
96
+ const registryKey = `${options.backend}:${namespace}`;
97
+ const existing = syncRegistrySync.get(registryKey);
98
+ if (existing)
99
+ return existing;
100
+ let store;
101
+ switch (options.backend) {
102
+ case 'memory':
103
+ store = new sync_store_1.OkintSyncStore('memory', new persistence_1.MemorySyncPersistence());
104
+ store.loadSync({});
105
+ break;
106
+ case 'fast': {
107
+ const native = (0, bridge_1.getNativeModule)();
108
+ const entries = native.getEntriesSync(namespace, 'async');
109
+ store = new sync_store_1.OkintSyncStore('fast', new persistence_1.BackendSyncPersistence(new native_backend_1.NativeBackend(native, namespace, 'async')));
110
+ store.loadSync(entries);
111
+ break;
112
+ }
113
+ default:
114
+ throw new errors_1.OkintStorageError('UNKNOWN_BACKEND', `Unknown sync backend "${String(options.backend)}".`);
115
+ }
116
+ syncRegistrySync.set(registryKey, store);
117
+ return store;
118
+ }
119
+ // JSI stores are interned per namespace (one HostObject per logical store).
120
+ const jsiRegistry = new Map();
121
+ /**
122
+ * Create a synchronous store backed by the C++/JSI engine — get/set go straight
123
+ * into C++ with no bridge serialization and no snapshot (maximum performance,
124
+ * zero JS memory overhead). Installs the native engine on first use; throws if
125
+ * the JSI runtime is unreachable (e.g. remote JS debugging) — fall back to
126
+ * `createSyncStorageSync` there.
127
+ *
128
+ * @example
129
+ * const kv = createJSIStorage({ namespace: 'app' });
130
+ * kv.setString('theme', 'dark'); // sync, in C++
131
+ * const theme = kv.getString('theme'); // sync, in C++
132
+ */
133
+ function createJSIStorage(options = {}) {
134
+ const namespace = (0, validate_1.normalizeNamespace)(options.namespace, DEFAULT_NAMESPACE);
135
+ const existing = jsiRegistry.get(namespace);
136
+ if (existing)
137
+ return existing;
138
+ const store = new jsi_store_1.JSISyncStore((0, jsi_1.getJSIStore)((0, bridge_1.getNativeModule)(), namespace));
139
+ jsiRegistry.set(namespace, store);
140
+ return store;
141
+ }
142
+ async function buildSyncStore(backend, namespace) {
143
+ let persistence;
144
+ switch (backend) {
145
+ case 'memory':
146
+ persistence = new persistence_1.MemorySyncPersistence();
147
+ break;
148
+ case 'fast':
149
+ persistence = new persistence_1.BackendSyncPersistence(new native_backend_1.NativeBackend((0, bridge_1.getNativeModule)(), namespace, 'async'));
150
+ break;
151
+ default:
152
+ throw new errors_1.OkintStorageError('UNKNOWN_BACKEND', `Unknown sync backend "${String(backend)}".`);
153
+ }
154
+ const store = new sync_store_1.OkintSyncStore(backend, persistence);
155
+ await store.load();
156
+ return store;
157
+ }
158
+ var facade_2 = require("./facade");
159
+ Object.defineProperty(exports, "StorageFacade", { enumerable: true, get: function () { return facade_2.StorageFacade; } });
160
+ var memory_2 = require("./backends/memory");
161
+ Object.defineProperty(exports, "MemoryBackend", { enumerable: true, get: function () { return memory_2.MemoryBackend; } });
162
+ var native_backend_2 = require("./backends/native-backend");
163
+ Object.defineProperty(exports, "NativeBackend", { enumerable: true, get: function () { return native_backend_2.NativeBackend; } });
164
+ var sync_store_2 = require("./sync/sync-store");
165
+ Object.defineProperty(exports, "OkintSyncStore", { enumerable: true, get: function () { return sync_store_2.OkintSyncStore; } });
166
+ var jsi_store_2 = require("./sync/jsi-store");
167
+ Object.defineProperty(exports, "JSISyncStore", { enumerable: true, get: function () { return jsi_store_2.JSISyncStore; } });
168
+ var persistence_2 = require("./sync/persistence");
169
+ Object.defineProperty(exports, "MemorySyncPersistence", { enumerable: true, get: function () { return persistence_2.MemorySyncPersistence; } });
170
+ Object.defineProperty(exports, "BackendSyncPersistence", { enumerable: true, get: function () { return persistence_2.BackendSyncPersistence; } });
171
+ var errors_2 = require("./errors");
172
+ Object.defineProperty(exports, "OkintStorageError", { enumerable: true, get: function () { return errors_2.OkintStorageError; } });
@@ -0,0 +1,12 @@
1
+ import type { NativeOkintStorage } from '../types';
2
+ /**
3
+ * Resolves the native module. This is the ONLY file that imports `react-native`,
4
+ * so the rest of the package (facade, backends, types) stays runtime-agnostic
5
+ * and unit-testable under Node.
6
+ *
7
+ * Uses the classic NativeModules registry (works on both the legacy and the New
8
+ * Architecture via the interop layer). If the module isn't present — e.g. the
9
+ * app wasn't rebuilt after install — we throw a clear, actionable error rather
10
+ * than failing deep in a call.
11
+ */
12
+ export declare function getNativeModule(): NativeOkintStorage;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNativeModule = getNativeModule;
4
+ const react_native_1 = require("react-native");
5
+ const errors_1 = require("../errors");
6
+ /**
7
+ * Resolves the native module. This is the ONLY file that imports `react-native`,
8
+ * so the rest of the package (facade, backends, types) stays runtime-agnostic
9
+ * and unit-testable under Node.
10
+ *
11
+ * Uses the classic NativeModules registry (works on both the legacy and the New
12
+ * Architecture via the interop layer). If the module isn't present — e.g. the
13
+ * app wasn't rebuilt after install — we throw a clear, actionable error rather
14
+ * than failing deep in a call.
15
+ */
16
+ function getNativeModule() {
17
+ const mod = react_native_1.NativeModules['OkintRnStorage'];
18
+ if (!mod) {
19
+ throw new errors_1.OkintStorageError('NATIVE_MODULE_MISSING', "okint-rn-storage native module not found. Rebuild the app after install " +
20
+ "(pod install on iOS / gradle sync on Android), or use backend: 'memory'.");
21
+ }
22
+ return mod;
23
+ }
@@ -0,0 +1,7 @@
1
+ import type { JSIStore, NativeOkintStorage } from '../types';
2
+ /**
3
+ * Resolve a JSI HostObject store for `namespace`, installing the native C++
4
+ * engine on first use. Throws a clear error if the JSI runtime is unreachable
5
+ * (e.g. remote JS debugging) — callers should fall back to `createSyncStorageSync`.
6
+ */
7
+ export declare function getJSIStore(native: NativeOkintStorage, namespace: string): JSIStore;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getJSIStore = getJSIStore;
4
+ const errors_1 = require("../errors");
5
+ const GLOBAL_FACTORY = '__okintCreateJSI';
6
+ function factory() {
7
+ const g = globalThis;
8
+ const f = g[GLOBAL_FACTORY];
9
+ return typeof f === 'function' ? f : undefined;
10
+ }
11
+ let installed = false;
12
+ /**
13
+ * Resolve a JSI HostObject store for `namespace`, installing the native C++
14
+ * engine on first use. Throws a clear error if the JSI runtime is unreachable
15
+ * (e.g. remote JS debugging) — callers should fall back to `createSyncStorageSync`.
16
+ */
17
+ function getJSIStore(native, namespace) {
18
+ if (!installed && factory() === undefined) {
19
+ try {
20
+ native.installJSI();
21
+ }
22
+ catch {
23
+ // fall through to the missing-engine error below
24
+ }
25
+ }
26
+ const create = factory();
27
+ if (create === undefined) {
28
+ throw new errors_1.OkintStorageError('NATIVE_MODULE_MISSING', 'okint JSI engine is unavailable (remote JS debugging or unsupported runtime). ' +
29
+ 'Use createSyncStorageSync for synchronous access instead.');
30
+ }
31
+ installed = true;
32
+ return create(namespace);
33
+ }
@@ -0,0 +1,28 @@
1
+ import type { JSIStore, OkintSyncStorage, SyncBackendKind } from '../types';
2
+ /**
3
+ * OkintSyncStorage backed by the C++/JSI HostObject. Reads and writes go
4
+ * straight into C++ with no bridge serialization and no snapshot — the
5
+ * maximum-performance synchronous path. Writes are persisted synchronously by
6
+ * the native engine, so `flush()` is a no-op.
7
+ */
8
+ export declare class JSISyncStore implements OkintSyncStorage {
9
+ private readonly store;
10
+ readonly backend: SyncBackendKind;
11
+ constructor(store: JSIStore);
12
+ getString(key: string): string | null;
13
+ setString(key: string, value: string): void;
14
+ getItem<T>(key: string): T | null;
15
+ setItem<T>(key: string, value: T): void;
16
+ getNumber(key: string): number | null;
17
+ setNumber(key: string, value: number): void;
18
+ getBoolean(key: string): boolean | null;
19
+ setBoolean(key: string, value: boolean): void;
20
+ has(key: string): boolean;
21
+ remove(key: string): void;
22
+ clear(): void;
23
+ keys(): string[];
24
+ multiGet(keys: string[]): Record<string, string | null>;
25
+ multiSet(entries: Record<string, string>): void;
26
+ multiRemove(keys: string[]): void;
27
+ flush(): Promise<void>;
28
+ }