react-native-nitro-storage 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/android/src/main/cpp/cpp-adapter.cpp +3 -1
- package/lib/commonjs/index.js +57 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +51 -1
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +130 -0
- package/lib/commonjs/indexeddb-backend.js.map +1 -0
- package/lib/module/index.js +51 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +45 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +126 -0
- package/lib/module/indexeddb-backend.js.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +2 -0
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts +29 -0
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -0
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +22 -17
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +13 -4
- package/package.json +7 -3
- package/src/index.ts +56 -1
- package/src/index.web.ts +49 -1
- package/src/indexeddb-backend.ts +143 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { WebSecureStorageBackend } from "./index.web";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_DB_NAME = "nitro-storage-secure";
|
|
4
|
+
const DEFAULT_STORE_NAME = "keyvalue";
|
|
5
|
+
const DB_VERSION = 1;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Opens (or creates) an IndexedDB database and returns the underlying IDBDatabase.
|
|
9
|
+
* Rejects if IndexedDB is unavailable in the current environment.
|
|
10
|
+
*/
|
|
11
|
+
function openDB(dbName: string, storeName: string): Promise<IDBDatabase> {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
if (typeof indexedDB === "undefined") {
|
|
14
|
+
reject(new Error("IndexedDB is not available in this environment."));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const request = indexedDB.open(dbName, DB_VERSION);
|
|
19
|
+
|
|
20
|
+
request.onupgradeneeded = () => {
|
|
21
|
+
const db = request.result;
|
|
22
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
23
|
+
db.createObjectStore(storeName);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
request.onsuccess = () => resolve(request.result);
|
|
28
|
+
request.onerror = () =>
|
|
29
|
+
reject(request.error ?? new Error("Failed to open IndexedDB database."));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a `WebSecureStorageBackend` backed by IndexedDB.
|
|
35
|
+
*
|
|
36
|
+
* IndexedDB is async, but `WebSecureStorageBackend` requires a synchronous
|
|
37
|
+
* interface. This implementation bridges the gap with a write-through in-memory
|
|
38
|
+
* cache:
|
|
39
|
+
*
|
|
40
|
+
* - **Reads** are always served from the in-memory cache (synchronous, O(1)).
|
|
41
|
+
* - **Writes** update the cache synchronously, then persist to IndexedDB
|
|
42
|
+
* asynchronously in the background.
|
|
43
|
+
* - **Initialisation**: the returned backend pre-loads all persisted entries
|
|
44
|
+
* from IndexedDB into memory before resolving, so the first synchronous read
|
|
45
|
+
* after `await createIndexedDBBackend()` already returns the correct value.
|
|
46
|
+
*
|
|
47
|
+
* @param dbName Name of the IndexedDB database. Defaults to `"nitro-storage-secure"`.
|
|
48
|
+
* @param storeName Name of the object store inside the database. Defaults to `"keyvalue"`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { setWebSecureStorageBackend } from "react-native-nitro-storage";
|
|
53
|
+
* import { createIndexedDBBackend } from "react-native-nitro-storage/indexeddb-backend";
|
|
54
|
+
*
|
|
55
|
+
* const backend = await createIndexedDBBackend();
|
|
56
|
+
* setWebSecureStorageBackend(backend);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export async function createIndexedDBBackend(
|
|
60
|
+
dbName = DEFAULT_DB_NAME,
|
|
61
|
+
storeName = DEFAULT_STORE_NAME,
|
|
62
|
+
): Promise<WebSecureStorageBackend> {
|
|
63
|
+
const db = await openDB(dbName, storeName);
|
|
64
|
+
const cache = new Map<string, string>();
|
|
65
|
+
|
|
66
|
+
// Hydrate the in-memory cache from IndexedDB.
|
|
67
|
+
await new Promise<void>((resolve, reject) => {
|
|
68
|
+
const tx = db.transaction(storeName, "readonly");
|
|
69
|
+
const store = tx.objectStore(storeName);
|
|
70
|
+
const request = store.openCursor();
|
|
71
|
+
|
|
72
|
+
request.onsuccess = () => {
|
|
73
|
+
const cursor = request.result;
|
|
74
|
+
if (cursor) {
|
|
75
|
+
const value = cursor.value as unknown;
|
|
76
|
+
if (typeof value === "string") {
|
|
77
|
+
cache.set(String(cursor.key), value);
|
|
78
|
+
}
|
|
79
|
+
cursor.continue();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
tx.oncomplete = () => resolve();
|
|
84
|
+
tx.onerror = () =>
|
|
85
|
+
reject(tx.error ?? new Error("Failed to load IndexedDB entries."));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/** Fire-and-forget IndexedDB write. Errors are silently ignored to avoid
|
|
89
|
+
* breaking the synchronous caller — the in-memory cache is always authoritative. */
|
|
90
|
+
function persistSet(key: string, value: string): void {
|
|
91
|
+
try {
|
|
92
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
93
|
+
tx.objectStore(storeName).put(value, key);
|
|
94
|
+
} catch {
|
|
95
|
+
// Best-effort; cache is the source of truth.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function persistDelete(key: string): void {
|
|
100
|
+
try {
|
|
101
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
102
|
+
tx.objectStore(storeName).delete(key);
|
|
103
|
+
} catch {
|
|
104
|
+
// Best-effort.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function persistClear(): void {
|
|
109
|
+
try {
|
|
110
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
111
|
+
tx.objectStore(storeName).clear();
|
|
112
|
+
} catch {
|
|
113
|
+
// Best-effort.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const backend: WebSecureStorageBackend = {
|
|
118
|
+
getItem(key: string): string | null {
|
|
119
|
+
return cache.get(key) ?? null;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
setItem(key: string, value: string): void {
|
|
123
|
+
cache.set(key, value);
|
|
124
|
+
persistSet(key, value);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
removeItem(key: string): void {
|
|
128
|
+
cache.delete(key);
|
|
129
|
+
persistDelete(key);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
clear(): void {
|
|
133
|
+
cache.clear();
|
|
134
|
+
persistClear();
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
getAllKeys(): string[] {
|
|
138
|
+
return Array.from(cache.keys());
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return backend;
|
|
143
|
+
}
|