@noy-db/to-browser-idb 0.2.0-pre.3 → 0.2.0-pre.30
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/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -107,6 +107,10 @@ function createIndexedDBAdapter(prefix, obfuscate, obfKey) {
|
|
|
107
107
|
}
|
|
108
108
|
return {
|
|
109
109
|
name: "browser:indexedDB",
|
|
110
|
+
capabilities: {
|
|
111
|
+
casAtomic: true,
|
|
112
|
+
auth: { kind: "browser-origin", required: false, flow: "implicit" }
|
|
113
|
+
},
|
|
110
114
|
async get(vault, collection, id) {
|
|
111
115
|
const { store } = await tx("readonly");
|
|
112
116
|
const raw = await idbRequest(store.get(key(vault, collection, id)));
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/to-browser-idb** — IndexedDB-backed NOYDB store.\n *\n * The recommended browser store. Uses a single IndexedDB database\n * (`{prefix}_noydb`) with one object store (`records`). Composite string\n * keys (`{vault}:{collection}:{id}`) avoid the need for complex indexes.\n *\n * ## Why IndexedDB over localStorage\n *\n * - **Larger quota** — typically 50–80% of available disk vs. localStorage's\n * 5 MB hard cap.\n * - **True atomic CAS** — the compare-and-swap check and write happen inside\n * a single `readwrite` IDB transaction, so concurrent writes from two tabs\n * cannot interleave. (`casAtomic: true`.)\n * - **Async API** — doesn't block the main thread during large reads.\n *\n * ## Obfuscation mode\n *\n * Identical to `@noy-db/to-browser-local` — FNV-1a hashed keys + XOR-encoded\n * metadata. Envelope `_data` is always AES-GCM ciphertext regardless of this\n * setting.\n *\n * ## Capabilities\n *\n * | Capability | Value |\n * |---|---|\n * | `casAtomic` | `true` — single readwrite IDB transaction per put |\n * | `listPage` | ✓ — cursor-based pagination via `getAllKeys()` |\n * | `ping` | ✓ — `openDB()` round-trip |\n *\n * @packageDocumentation\n */\n\nimport type { NoydbStore, EncryptedEnvelope, VaultSnapshot } from '@noy-db/hub'\nimport { ConflictError } from '@noy-db/hub'\n\n/**\n * Options for `browserIdbStore()`.\n *\n * All NOYDB data is stored in a single IndexedDB database named\n * `{prefix}_noydb`, using composite string keys of the form\n * `{vault}:{collection}:{id}`. CAS checks and writes happen inside a\n * single `readwrite` IDB transaction, giving true atomic compare-and-swap\n * (`casAtomic: true`). Enable `obfuscate` to hash key components and\n * XOR-encode metadata — same obfuscation model as `browserLocalStore`.\n */\nexport interface BrowserIdbOptions {\n /** Storage key prefix. Default: 'noydb'. */\n prefix?: string\n /** Obfuscate storage keys so collection/record names are not readable. Default: false. */\n obfuscate?: boolean\n}\n\n/**\n * Create an IndexedDB-backed store adapter.\n *\n * Key scheme (normal): `{vault}:{collection}:{id}`\n * Key scheme (obfuscated): `{hash}:{hash}:{hash}`\n *\n * StoreCapabilities:\n * casAtomic: true — CAS check + write happen inside a single readwrite IDB transaction\n * auth: { kind: 'browser-origin', flow: 'implicit', required: false }\n */\nexport function browserIdbStore(options: BrowserIdbOptions = {}): NoydbStore {\n const prefix = options.prefix ?? 'noydb'\n const obfuscate = options.obfuscate ?? false\n const obfKey = obfuscate ? makeObfKey(prefix) : ''\n\n return createIndexedDBAdapter(prefix, obfuscate, obfKey)\n}\n\n// ─── Key Obfuscation ───────────────────────────────────────────────────\n\n/**\n * FNV-1a 32-bit hash → 8-char hex string.\n * Not cryptographic — just makes keys opaque to casual inspection.\n */\nfunction fnv1a(str: string): string {\n let hash = 0x811c9dc5\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n\nfunction hashComponent(value: string, obfuscate: boolean): string {\n return obfuscate ? fnv1a(value) : value\n}\n\n// ─── XOR Encode/Decode (makes metadata unreadable in storage) ──────────\n\n/** XOR-encode a string with a repeating key, return base64. */\nfunction xorEncode(plaintext: string, key: string): string {\n const bytes = new TextEncoder().encode(plaintext)\n const keyBytes = new TextEncoder().encode(key)\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = bytes[i]! ^ keyBytes[i % keyBytes.length]!\n }\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\n/** Decode a base64 XOR-encoded string. */\nfunction xorDecode(encoded: string, key: string): string {\n const binary = atob(encoded)\n const bytes = new Uint8Array(binary.length)\n const keyBytes = new TextEncoder().encode(key)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i) ^ keyBytes[i % keyBytes.length]!\n }\n return new TextDecoder().decode(bytes)\n}\n\n/** Stored value wraps envelope + encoded original key parts. */\ninterface StoredValue {\n /** Encoded original record ID. */\n _oi: string\n /** Encoded original collection name. */\n _oc: string\n /** The encrypted envelope. */\n _e: EncryptedEnvelope\n}\n\nfunction makeObfKey(prefix: string): string {\n return prefix + ':noydb-obf-key'\n}\n\n// ─── IndexedDB Backend ─────────────────────────────────────────────────\n\nfunction createIndexedDBAdapter(prefix: string, obfuscate: boolean, obfKey: string): NoydbStore {\n const DB_NAME = `${prefix}_noydb`\n const STORE_NAME = 'records'\n let dbPromise: Promise<IDBDatabase> | null = null\n\n function openDB(): Promise<IDBDatabase> {\n if (!dbPromise) {\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, 1)\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n }\n return dbPromise\n }\n\n function key(vault: string, collection: string, id: string): string {\n return `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:${hashComponent(id, obfuscate)}`\n }\n\n function tx(mode: IDBTransactionMode): Promise<{ store: IDBObjectStore; complete: Promise<void> }> {\n return openDB().then(db => {\n const transaction = db.transaction(STORE_NAME, mode)\n const store = transaction.objectStore(STORE_NAME)\n const complete = new Promise<void>((resolve, reject) => {\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => reject(transaction.error)\n })\n return { store, complete }\n })\n }\n\n function idbRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n }\n\n return {\n name: 'browser:indexedDB',\n\n async get(vault, collection, id) {\n const { store } = await tx('readonly')\n const raw = await idbRequest(store.get(key(vault, collection, id)))\n if (!raw) return null\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n return (raw as StoredValue)._e\n }\n return raw as EncryptedEnvelope\n },\n\n async put(vault, collection, id, envelope, expectedVersion) {\n const k = key(vault, collection, id)\n\n const { store, complete } = await tx('readwrite')\n if (expectedVersion !== undefined) {\n const existing = await idbRequest(store.get(k))\n if (existing) {\n const env = obfuscate && '_e' in (existing as StoredValue) ? (existing as StoredValue)._e : existing as EncryptedEnvelope\n if (env._v !== expectedVersion) {\n throw new ConflictError(env._v, `Version conflict: expected ${expectedVersion}, found ${env._v}`)\n }\n }\n }\n\n const value = obfuscate ? { _oi: xorEncode(id, obfKey), _oc: xorEncode(collection, obfKey), _e: envelope } : envelope\n store.put(value, k)\n await complete\n },\n\n async delete(vault, collection, id) {\n const { store, complete } = await tx('readwrite')\n store.delete(key(vault, collection, id))\n await complete\n },\n\n async list(vault, collection) {\n const pfx = `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n\n if (!obfuscate) {\n return allKeys\n .filter(k => typeof k === 'string' && k.startsWith(pfx))\n .map(k => k.slice(pfx.length))\n }\n\n // Obfuscated: need to read values for original IDs\n const ids: string[] = []\n for (const k of allKeys) {\n if (typeof k !== 'string' || !k.startsWith(pfx)) continue\n const raw = await idbRequest(store.get(k))\n if (raw && typeof raw === 'object' && '_oi' in (raw as StoredValue)) {\n ids.push(xorDecode((raw as StoredValue)._oi, obfKey))\n }\n }\n return ids\n },\n\n async loadAll(vault) {\n const pfx = `${hashComponent(vault, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n const snapshot: VaultSnapshot = {}\n\n for (const k of allKeys) {\n if (typeof k !== 'string' || !k.startsWith(pfx)) continue\n\n const raw = await idbRequest(store.get(k))\n if (!raw) continue\n\n let collection: string\n let id: string\n let envelope: EncryptedEnvelope\n\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n const stored = raw as StoredValue\n collection = xorDecode(stored._oc, obfKey)\n id = xorDecode(stored._oi, obfKey)\n envelope = stored._e\n } else {\n const rest = k.slice(pfx.length)\n const colonIdx = rest.indexOf(':')\n if (colonIdx < 0) continue\n collection = rest.slice(0, colonIdx)\n id = rest.slice(colonIdx + 1)\n envelope = raw as EncryptedEnvelope\n }\n\n if (collection.startsWith('_')) continue\n if (!snapshot[collection]) snapshot[collection] = {}\n snapshot[collection]![id] = envelope\n }\n\n return snapshot\n },\n\n async saveAll(vault, data) {\n const { store, complete } = await tx('readwrite')\n for (const [collection, records] of Object.entries(data)) {\n for (const [id, envelope] of Object.entries(records)) {\n const value = obfuscate ? { _oi: xorEncode(id, obfKey), _oc: xorEncode(collection, obfKey), _e: envelope } : envelope\n store.put(value, key(vault, collection, id))\n }\n }\n await complete\n },\n\n async ping() {\n try {\n await openDB()\n return true\n } catch {\n return false\n }\n },\n\n /**\n * Paginate over a collection backed by IndexedDB.\n *\n * Strategy: read every key in the prefix once (sorted), then slice\n * by cursor offset. IndexedDB's `getAllKeys()` returns sorted keys\n * efficiently for the modern browsers we target (Chrome 87+,\n * Firefox 78+, Safari 14+, Edge 88+ — same baseline as the rest of\n * the build target).\n */\n async listPage(vault, collection, cursor, limit = 100) {\n const pfx = `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n const matchedKeys = allKeys\n .filter(k => typeof k === 'string' && k.startsWith(pfx))\n .sort()\n\n const start = cursor ? parseInt(cursor, 10) : 0\n const end = Math.min(start + limit, matchedKeys.length)\n\n const items: Array<{ id: string; envelope: EncryptedEnvelope }> = []\n for (let i = start; i < end; i++) {\n const k = matchedKeys[i]!\n const raw = await idbRequest(store.get(k))\n if (!raw) continue\n\n let envelope: EncryptedEnvelope\n let id: string\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n const stored = raw as StoredValue\n envelope = stored._e\n id = xorDecode(stored._oi, obfKey)\n } else {\n envelope = raw as EncryptedEnvelope\n id = k.slice(pfx.length)\n }\n items.push({ id, envelope })\n }\n\n return {\n items,\n nextCursor: end < matchedKeys.length ? String(end) : null,\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAkCA,iBAA8B;AA6BvB,SAAS,gBAAgB,UAA6B,CAAC,GAAe;AAC3E,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,SAAS,YAAY,WAAW,MAAM,IAAI;AAEhD,SAAO,uBAAuB,QAAQ,WAAW,MAAM;AACzD;AAQA,SAAS,MAAM,KAAqB;AAClC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,YAAQ,IAAI,WAAW,CAAC;AACxB,WAAO,KAAK,KAAK,MAAM,QAAU;AAAA,EACnC;AACA,UAAQ,SAAS,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAClD;AAEA,SAAS,cAAc,OAAe,WAA4B;AAChE,SAAO,YAAY,MAAM,KAAK,IAAI;AACpC;AAKA,SAAS,UAAU,WAAmB,KAAqB;AACzD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,SAAS;AAChD,QAAM,WAAW,IAAI,YAAY,EAAE,OAAO,GAAG;AAC7C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,CAAC,IAAI,MAAM,CAAC,IAAK,SAAS,IAAI,SAAS,MAAM;AAAA,EACrD;AACA,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAE;AAAA,EACzC;AACA,SAAO,KAAK,MAAM;AACpB;AAGA,SAAS,UAAU,SAAiB,KAAqB;AACvD,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,QAAM,WAAW,IAAI,YAAY,EAAE,OAAO,GAAG;AAC7C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC,IAAI,SAAS,IAAI,SAAS,MAAM;AAAA,EAChE;AACA,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACvC;AAYA,SAAS,WAAW,QAAwB;AAC1C,SAAO,SAAS;AAClB;AAIA,SAAS,uBAAuB,QAAgB,WAAoB,QAA4B;AAC9F,QAAM,UAAU,GAAG,MAAM;AACzB,QAAM,aAAa;AACnB,MAAI,YAAyC;AAE7C,WAAS,SAA+B;AACtC,QAAI,CAAC,WAAW;AACd,kBAAY,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC3C,cAAM,UAAU,UAAU,KAAK,SAAS,CAAC;AACzC,gBAAQ,kBAAkB,MAAM;AAC9B,gBAAM,KAAK,QAAQ;AACnB,cAAI,CAAC,GAAG,iBAAiB,SAAS,UAAU,GAAG;AAC7C,eAAG,kBAAkB,UAAU;AAAA,UACjC;AAAA,QACF;AACA,gBAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,gBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,MAC9C,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,OAAe,YAAoB,IAAoB;AAClE,WAAO,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC,IAAI,cAAc,IAAI,SAAS,CAAC;AAAA,EACnH;AAEA,WAAS,GAAG,MAAuF;AACjG,WAAO,OAAO,EAAE,KAAK,QAAM;AACzB,YAAM,cAAc,GAAG,YAAY,YAAY,IAAI;AACnD,YAAM,QAAQ,YAAY,YAAY,UAAU;AAChD,YAAM,WAAW,IAAI,QAAc,CAAC,SAAS,WAAW;AACtD,oBAAY,aAAa,MAAM,QAAQ;AACvC,oBAAY,UAAU,MAAM,OAAO,YAAY,KAAK;AAAA,MACtD,CAAC;AACD,aAAO,EAAE,OAAO,SAAS;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,WAAS,WAAc,SAAoC;AACzD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,cAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,IAC9C,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,MAAM,IAAI,OAAO,YAAY,IAAI;AAC/B,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,MAAM,MAAM,WAAW,MAAM,IAAI,IAAI,OAAO,YAAY,EAAE,CAAC,CAAC;AAClE,UAAI,CAAC,IAAK,QAAO;AACjB,UAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,eAAQ,IAAoB;AAAA,MAC9B;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,IAAI,OAAO,YAAY,IAAI,UAAU,iBAAiB;AAC1D,YAAM,IAAI,IAAI,OAAO,YAAY,EAAE;AAEnC,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,UAAI,oBAAoB,QAAW;AACjC,cAAM,WAAW,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AAC9C,YAAI,UAAU;AACZ,gBAAM,MAAM,aAAa,QAAS,WAA4B,SAAyB,KAAK;AAC5F,cAAI,IAAI,OAAO,iBAAiB;AAC9B,kBAAM,IAAI,yBAAc,IAAI,IAAI,8BAA8B,eAAe,WAAW,IAAI,EAAE,EAAE;AAAA,UAClG;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,YAAY,EAAE,KAAK,UAAU,IAAI,MAAM,GAAG,KAAK,UAAU,YAAY,MAAM,GAAG,IAAI,SAAS,IAAI;AAC7G,YAAM,IAAI,OAAO,CAAC;AAClB,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,OAAO,OAAO,YAAY,IAAI;AAClC,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,YAAM,OAAO,IAAI,OAAO,YAAY,EAAE,CAAC;AACvC,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,KAAK,OAAO,YAAY;AAC5B,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC;AACtF,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AAEnD,UAAI,CAAC,WAAW;AACd,eAAO,QACJ,OAAO,OAAK,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG,CAAC,EACtD,IAAI,OAAK,EAAE,MAAM,IAAI,MAAM,CAAC;AAAA,MACjC;AAGA,YAAM,MAAgB,CAAC;AACvB,iBAAW,KAAK,SAAS;AACvB,YAAI,OAAO,MAAM,YAAY,CAAC,EAAE,WAAW,GAAG,EAAG;AACjD,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,OAAO,OAAO,QAAQ,YAAY,SAAU,KAAqB;AACnE,cAAI,KAAK,UAAW,IAAoB,KAAK,MAAM,CAAC;AAAA,QACtD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,QAAQ,OAAO;AACnB,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC;AAC9C,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AACnD,YAAM,WAA0B,CAAC;AAEjC,iBAAW,KAAK,SAAS;AACvB,YAAI,OAAO,MAAM,YAAY,CAAC,EAAE,WAAW,GAAG,EAAG;AAEjD,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,CAAC,IAAK;AAEV,YAAI;AACJ,YAAI;AACJ,YAAI;AAEJ,YAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,gBAAM,SAAS;AACf,uBAAa,UAAU,OAAO,KAAK,MAAM;AACzC,eAAK,UAAU,OAAO,KAAK,MAAM;AACjC,qBAAW,OAAO;AAAA,QACpB,OAAO;AACL,gBAAM,OAAO,EAAE,MAAM,IAAI,MAAM;AAC/B,gBAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,cAAI,WAAW,EAAG;AAClB,uBAAa,KAAK,MAAM,GAAG,QAAQ;AACnC,eAAK,KAAK,MAAM,WAAW,CAAC;AAC5B,qBAAW;AAAA,QACb;AAEA,YAAI,WAAW,WAAW,GAAG,EAAG;AAChC,YAAI,CAAC,SAAS,UAAU,EAAG,UAAS,UAAU,IAAI,CAAC;AACnD,iBAAS,UAAU,EAAG,EAAE,IAAI;AAAA,MAC9B;AAEA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,QAAQ,OAAO,MAAM;AACzB,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,iBAAW,CAAC,YAAY,OAAO,KAAK,OAAO,QAAQ,IAAI,GAAG;AACxD,mBAAW,CAAC,IAAI,QAAQ,KAAK,OAAO,QAAQ,OAAO,GAAG;AACpD,gBAAM,QAAQ,YAAY,EAAE,KAAK,UAAU,IAAI,MAAM,GAAG,KAAK,UAAU,YAAY,MAAM,GAAG,IAAI,SAAS,IAAI;AAC7G,gBAAM,IAAI,OAAO,IAAI,OAAO,YAAY,EAAE,CAAC;AAAA,QAC7C;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,OAAO;AACX,UAAI;AACF,cAAM,OAAO;AACb,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWA,MAAM,SAAS,OAAO,YAAY,QAAQ,QAAQ,KAAK;AACrD,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC;AACtF,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AACnD,YAAM,cAAc,QACjB,OAAO,OAAK,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG,CAAC,EACtD,KAAK;AAER,YAAM,QAAQ,SAAS,SAAS,QAAQ,EAAE,IAAI;AAC9C,YAAM,MAAM,KAAK,IAAI,QAAQ,OAAO,YAAY,MAAM;AAEtD,YAAM,QAA4D,CAAC;AACnE,eAAS,IAAI,OAAO,IAAI,KAAK,KAAK;AAChC,cAAM,IAAI,YAAY,CAAC;AACvB,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,CAAC,IAAK;AAEV,YAAI;AACJ,YAAI;AACJ,YAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,gBAAM,SAAS;AACf,qBAAW,OAAO;AAClB,eAAK,UAAU,OAAO,KAAK,MAAM;AAAA,QACnC,OAAO;AACL,qBAAW;AACX,eAAK,EAAE,MAAM,IAAI,MAAM;AAAA,QACzB;AACA,cAAM,KAAK,EAAE,IAAI,SAAS,CAAC;AAAA,MAC7B;AAEA,aAAO;AAAA,QACL;AAAA,QACA,YAAY,MAAM,YAAY,SAAS,OAAO,GAAG,IAAI;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/to-browser-idb** — IndexedDB-backed NOYDB store.\n *\n * The recommended browser store. Uses a single IndexedDB database\n * (`{prefix}_noydb`) with one object store (`records`). Composite string\n * keys (`{vault}:{collection}:{id}`) avoid the need for complex indexes.\n *\n * ## Why IndexedDB over localStorage\n *\n * - **Larger quota** — typically 50–80% of available disk vs. localStorage's\n * 5 MB hard cap.\n * - **True atomic CAS** — the compare-and-swap check and write happen inside\n * a single `readwrite` IDB transaction, so concurrent writes from two tabs\n * cannot interleave. (`casAtomic: true`.)\n * - **Async API** — doesn't block the main thread during large reads.\n *\n * ## Obfuscation mode\n *\n * Identical to `@noy-db/to-browser-local` — FNV-1a hashed keys + XOR-encoded\n * metadata. Envelope `_data` is always AES-GCM ciphertext regardless of this\n * setting.\n *\n * ## Capabilities\n *\n * | Capability | Value |\n * |---|---|\n * | `casAtomic` | `true` — single readwrite IDB transaction per put |\n * | `listPage` | ✓ — cursor-based pagination via `getAllKeys()` |\n * | `ping` | ✓ — `openDB()` round-trip |\n *\n * @packageDocumentation\n */\n\nimport type { NoydbStore, EncryptedEnvelope, VaultSnapshot } from '@noy-db/hub'\nimport { ConflictError } from '@noy-db/hub'\n\n/**\n * Options for `browserIdbStore()`.\n *\n * All NOYDB data is stored in a single IndexedDB database named\n * `{prefix}_noydb`, using composite string keys of the form\n * `{vault}:{collection}:{id}`. CAS checks and writes happen inside a\n * single `readwrite` IDB transaction, giving true atomic compare-and-swap\n * (`casAtomic: true`). Enable `obfuscate` to hash key components and\n * XOR-encode metadata — same obfuscation model as `browserLocalStore`.\n */\nexport interface BrowserIdbOptions {\n /** Storage key prefix. Default: 'noydb'. */\n prefix?: string\n /** Obfuscate storage keys so collection/record names are not readable. Default: false. */\n obfuscate?: boolean\n}\n\n/**\n * Create an IndexedDB-backed store adapter.\n *\n * Key scheme (normal): `{vault}:{collection}:{id}`\n * Key scheme (obfuscated): `{hash}:{hash}:{hash}`\n *\n * StoreCapabilities:\n * casAtomic: true — CAS check + write happen inside a single readwrite IDB transaction\n * auth: { kind: 'browser-origin', flow: 'implicit', required: false }\n */\nexport function browserIdbStore(options: BrowserIdbOptions = {}): NoydbStore {\n const prefix = options.prefix ?? 'noydb'\n const obfuscate = options.obfuscate ?? false\n const obfKey = obfuscate ? makeObfKey(prefix) : ''\n\n return createIndexedDBAdapter(prefix, obfuscate, obfKey)\n}\n\n// ─── Key Obfuscation ───────────────────────────────────────────────────\n\n/**\n * FNV-1a 32-bit hash → 8-char hex string.\n * Not cryptographic — just makes keys opaque to casual inspection.\n */\nfunction fnv1a(str: string): string {\n let hash = 0x811c9dc5\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n\nfunction hashComponent(value: string, obfuscate: boolean): string {\n return obfuscate ? fnv1a(value) : value\n}\n\n// ─── XOR Encode/Decode (makes metadata unreadable in storage) ──────────\n\n/** XOR-encode a string with a repeating key, return base64. */\nfunction xorEncode(plaintext: string, key: string): string {\n const bytes = new TextEncoder().encode(plaintext)\n const keyBytes = new TextEncoder().encode(key)\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = bytes[i]! ^ keyBytes[i % keyBytes.length]!\n }\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\n/** Decode a base64 XOR-encoded string. */\nfunction xorDecode(encoded: string, key: string): string {\n const binary = atob(encoded)\n const bytes = new Uint8Array(binary.length)\n const keyBytes = new TextEncoder().encode(key)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i) ^ keyBytes[i % keyBytes.length]!\n }\n return new TextDecoder().decode(bytes)\n}\n\n/** Stored value wraps envelope + encoded original key parts. */\ninterface StoredValue {\n /** Encoded original record ID. */\n _oi: string\n /** Encoded original collection name. */\n _oc: string\n /** The encrypted envelope. */\n _e: EncryptedEnvelope\n}\n\nfunction makeObfKey(prefix: string): string {\n return prefix + ':noydb-obf-key'\n}\n\n// ─── IndexedDB Backend ─────────────────────────────────────────────────\n\nfunction createIndexedDBAdapter(prefix: string, obfuscate: boolean, obfKey: string): NoydbStore {\n const DB_NAME = `${prefix}_noydb`\n const STORE_NAME = 'records'\n let dbPromise: Promise<IDBDatabase> | null = null\n\n function openDB(): Promise<IDBDatabase> {\n if (!dbPromise) {\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, 1)\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n }\n return dbPromise\n }\n\n function key(vault: string, collection: string, id: string): string {\n return `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:${hashComponent(id, obfuscate)}`\n }\n\n function tx(mode: IDBTransactionMode): Promise<{ store: IDBObjectStore; complete: Promise<void> }> {\n return openDB().then(db => {\n const transaction = db.transaction(STORE_NAME, mode)\n const store = transaction.objectStore(STORE_NAME)\n const complete = new Promise<void>((resolve, reject) => {\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => reject(transaction.error)\n })\n return { store, complete }\n })\n }\n\n function idbRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n }\n\n return {\n name: 'browser:indexedDB',\n\n capabilities: {\n casAtomic: true,\n auth: { kind: 'browser-origin' as const, required: false, flow: 'implicit' as const },\n },\n\n async get(vault, collection, id) {\n const { store } = await tx('readonly')\n const raw = await idbRequest(store.get(key(vault, collection, id)))\n if (!raw) return null\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n return (raw as StoredValue)._e\n }\n return raw as EncryptedEnvelope\n },\n\n async put(vault, collection, id, envelope, expectedVersion) {\n const k = key(vault, collection, id)\n\n const { store, complete } = await tx('readwrite')\n if (expectedVersion !== undefined) {\n const existing = await idbRequest(store.get(k))\n if (existing) {\n const env = obfuscate && '_e' in (existing as StoredValue) ? (existing as StoredValue)._e : existing as EncryptedEnvelope\n if (env._v !== expectedVersion) {\n throw new ConflictError(env._v, `Version conflict: expected ${expectedVersion}, found ${env._v}`)\n }\n }\n }\n\n const value = obfuscate ? { _oi: xorEncode(id, obfKey), _oc: xorEncode(collection, obfKey), _e: envelope } : envelope\n store.put(value, k)\n await complete\n },\n\n async delete(vault, collection, id) {\n const { store, complete } = await tx('readwrite')\n store.delete(key(vault, collection, id))\n await complete\n },\n\n async list(vault, collection) {\n const pfx = `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n\n if (!obfuscate) {\n return allKeys\n .filter(k => typeof k === 'string' && k.startsWith(pfx))\n .map(k => k.slice(pfx.length))\n }\n\n // Obfuscated: need to read values for original IDs\n const ids: string[] = []\n for (const k of allKeys) {\n if (typeof k !== 'string' || !k.startsWith(pfx)) continue\n const raw = await idbRequest(store.get(k))\n if (raw && typeof raw === 'object' && '_oi' in (raw as StoredValue)) {\n ids.push(xorDecode((raw as StoredValue)._oi, obfKey))\n }\n }\n return ids\n },\n\n async loadAll(vault) {\n const pfx = `${hashComponent(vault, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n const snapshot: VaultSnapshot = {}\n\n for (const k of allKeys) {\n if (typeof k !== 'string' || !k.startsWith(pfx)) continue\n\n const raw = await idbRequest(store.get(k))\n if (!raw) continue\n\n let collection: string\n let id: string\n let envelope: EncryptedEnvelope\n\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n const stored = raw as StoredValue\n collection = xorDecode(stored._oc, obfKey)\n id = xorDecode(stored._oi, obfKey)\n envelope = stored._e\n } else {\n const rest = k.slice(pfx.length)\n const colonIdx = rest.indexOf(':')\n if (colonIdx < 0) continue\n collection = rest.slice(0, colonIdx)\n id = rest.slice(colonIdx + 1)\n envelope = raw as EncryptedEnvelope\n }\n\n if (collection.startsWith('_')) continue\n if (!snapshot[collection]) snapshot[collection] = {}\n snapshot[collection]![id] = envelope\n }\n\n return snapshot\n },\n\n async saveAll(vault, data) {\n const { store, complete } = await tx('readwrite')\n for (const [collection, records] of Object.entries(data)) {\n for (const [id, envelope] of Object.entries(records)) {\n const value = obfuscate ? { _oi: xorEncode(id, obfKey), _oc: xorEncode(collection, obfKey), _e: envelope } : envelope\n store.put(value, key(vault, collection, id))\n }\n }\n await complete\n },\n\n async ping() {\n try {\n await openDB()\n return true\n } catch {\n return false\n }\n },\n\n /**\n * Paginate over a collection backed by IndexedDB.\n *\n * Strategy: read every key in the prefix once (sorted), then slice\n * by cursor offset. IndexedDB's `getAllKeys()` returns sorted keys\n * efficiently for the modern browsers we target (Chrome 87+,\n * Firefox 78+, Safari 14+, Edge 88+ — same baseline as the rest of\n * the build target).\n */\n async listPage(vault, collection, cursor, limit = 100) {\n const pfx = `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n const matchedKeys = allKeys\n .filter(k => typeof k === 'string' && k.startsWith(pfx))\n .sort()\n\n const start = cursor ? parseInt(cursor, 10) : 0\n const end = Math.min(start + limit, matchedKeys.length)\n\n const items: Array<{ id: string; envelope: EncryptedEnvelope }> = []\n for (let i = start; i < end; i++) {\n const k = matchedKeys[i]!\n const raw = await idbRequest(store.get(k))\n if (!raw) continue\n\n let envelope: EncryptedEnvelope\n let id: string\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n const stored = raw as StoredValue\n envelope = stored._e\n id = xorDecode(stored._oi, obfKey)\n } else {\n envelope = raw as EncryptedEnvelope\n id = k.slice(pfx.length)\n }\n items.push({ id, envelope })\n }\n\n return {\n items,\n nextCursor: end < matchedKeys.length ? String(end) : null,\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAkCA,iBAA8B;AA6BvB,SAAS,gBAAgB,UAA6B,CAAC,GAAe;AAC3E,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,SAAS,YAAY,WAAW,MAAM,IAAI;AAEhD,SAAO,uBAAuB,QAAQ,WAAW,MAAM;AACzD;AAQA,SAAS,MAAM,KAAqB;AAClC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,YAAQ,IAAI,WAAW,CAAC;AACxB,WAAO,KAAK,KAAK,MAAM,QAAU;AAAA,EACnC;AACA,UAAQ,SAAS,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAClD;AAEA,SAAS,cAAc,OAAe,WAA4B;AAChE,SAAO,YAAY,MAAM,KAAK,IAAI;AACpC;AAKA,SAAS,UAAU,WAAmB,KAAqB;AACzD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,SAAS;AAChD,QAAM,WAAW,IAAI,YAAY,EAAE,OAAO,GAAG;AAC7C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,CAAC,IAAI,MAAM,CAAC,IAAK,SAAS,IAAI,SAAS,MAAM;AAAA,EACrD;AACA,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAE;AAAA,EACzC;AACA,SAAO,KAAK,MAAM;AACpB;AAGA,SAAS,UAAU,SAAiB,KAAqB;AACvD,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,QAAM,WAAW,IAAI,YAAY,EAAE,OAAO,GAAG;AAC7C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC,IAAI,SAAS,IAAI,SAAS,MAAM;AAAA,EAChE;AACA,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACvC;AAYA,SAAS,WAAW,QAAwB;AAC1C,SAAO,SAAS;AAClB;AAIA,SAAS,uBAAuB,QAAgB,WAAoB,QAA4B;AAC9F,QAAM,UAAU,GAAG,MAAM;AACzB,QAAM,aAAa;AACnB,MAAI,YAAyC;AAE7C,WAAS,SAA+B;AACtC,QAAI,CAAC,WAAW;AACd,kBAAY,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC3C,cAAM,UAAU,UAAU,KAAK,SAAS,CAAC;AACzC,gBAAQ,kBAAkB,MAAM;AAC9B,gBAAM,KAAK,QAAQ;AACnB,cAAI,CAAC,GAAG,iBAAiB,SAAS,UAAU,GAAG;AAC7C,eAAG,kBAAkB,UAAU;AAAA,UACjC;AAAA,QACF;AACA,gBAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,gBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,MAC9C,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,OAAe,YAAoB,IAAoB;AAClE,WAAO,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC,IAAI,cAAc,IAAI,SAAS,CAAC;AAAA,EACnH;AAEA,WAAS,GAAG,MAAuF;AACjG,WAAO,OAAO,EAAE,KAAK,QAAM;AACzB,YAAM,cAAc,GAAG,YAAY,YAAY,IAAI;AACnD,YAAM,QAAQ,YAAY,YAAY,UAAU;AAChD,YAAM,WAAW,IAAI,QAAc,CAAC,SAAS,WAAW;AACtD,oBAAY,aAAa,MAAM,QAAQ;AACvC,oBAAY,UAAU,MAAM,OAAO,YAAY,KAAK;AAAA,MACtD,CAAC;AACD,aAAO,EAAE,OAAO,SAAS;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,WAAS,WAAc,SAAoC;AACzD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,cAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,IAC9C,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,cAAc;AAAA,MACZ,WAAW;AAAA,MACX,MAAM,EAAE,MAAM,kBAA2B,UAAU,OAAO,MAAM,WAAoB;AAAA,IACtF;AAAA,IAEA,MAAM,IAAI,OAAO,YAAY,IAAI;AAC/B,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,MAAM,MAAM,WAAW,MAAM,IAAI,IAAI,OAAO,YAAY,EAAE,CAAC,CAAC;AAClE,UAAI,CAAC,IAAK,QAAO;AACjB,UAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,eAAQ,IAAoB;AAAA,MAC9B;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,IAAI,OAAO,YAAY,IAAI,UAAU,iBAAiB;AAC1D,YAAM,IAAI,IAAI,OAAO,YAAY,EAAE;AAEnC,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,UAAI,oBAAoB,QAAW;AACjC,cAAM,WAAW,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AAC9C,YAAI,UAAU;AACZ,gBAAM,MAAM,aAAa,QAAS,WAA4B,SAAyB,KAAK;AAC5F,cAAI,IAAI,OAAO,iBAAiB;AAC9B,kBAAM,IAAI,yBAAc,IAAI,IAAI,8BAA8B,eAAe,WAAW,IAAI,EAAE,EAAE;AAAA,UAClG;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,YAAY,EAAE,KAAK,UAAU,IAAI,MAAM,GAAG,KAAK,UAAU,YAAY,MAAM,GAAG,IAAI,SAAS,IAAI;AAC7G,YAAM,IAAI,OAAO,CAAC;AAClB,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,OAAO,OAAO,YAAY,IAAI;AAClC,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,YAAM,OAAO,IAAI,OAAO,YAAY,EAAE,CAAC;AACvC,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,KAAK,OAAO,YAAY;AAC5B,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC;AACtF,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AAEnD,UAAI,CAAC,WAAW;AACd,eAAO,QACJ,OAAO,OAAK,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG,CAAC,EACtD,IAAI,OAAK,EAAE,MAAM,IAAI,MAAM,CAAC;AAAA,MACjC;AAGA,YAAM,MAAgB,CAAC;AACvB,iBAAW,KAAK,SAAS;AACvB,YAAI,OAAO,MAAM,YAAY,CAAC,EAAE,WAAW,GAAG,EAAG;AACjD,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,OAAO,OAAO,QAAQ,YAAY,SAAU,KAAqB;AACnE,cAAI,KAAK,UAAW,IAAoB,KAAK,MAAM,CAAC;AAAA,QACtD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,QAAQ,OAAO;AACnB,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC;AAC9C,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AACnD,YAAM,WAA0B,CAAC;AAEjC,iBAAW,KAAK,SAAS;AACvB,YAAI,OAAO,MAAM,YAAY,CAAC,EAAE,WAAW,GAAG,EAAG;AAEjD,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,CAAC,IAAK;AAEV,YAAI;AACJ,YAAI;AACJ,YAAI;AAEJ,YAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,gBAAM,SAAS;AACf,uBAAa,UAAU,OAAO,KAAK,MAAM;AACzC,eAAK,UAAU,OAAO,KAAK,MAAM;AACjC,qBAAW,OAAO;AAAA,QACpB,OAAO;AACL,gBAAM,OAAO,EAAE,MAAM,IAAI,MAAM;AAC/B,gBAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,cAAI,WAAW,EAAG;AAClB,uBAAa,KAAK,MAAM,GAAG,QAAQ;AACnC,eAAK,KAAK,MAAM,WAAW,CAAC;AAC5B,qBAAW;AAAA,QACb;AAEA,YAAI,WAAW,WAAW,GAAG,EAAG;AAChC,YAAI,CAAC,SAAS,UAAU,EAAG,UAAS,UAAU,IAAI,CAAC;AACnD,iBAAS,UAAU,EAAG,EAAE,IAAI;AAAA,MAC9B;AAEA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,QAAQ,OAAO,MAAM;AACzB,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,iBAAW,CAAC,YAAY,OAAO,KAAK,OAAO,QAAQ,IAAI,GAAG;AACxD,mBAAW,CAAC,IAAI,QAAQ,KAAK,OAAO,QAAQ,OAAO,GAAG;AACpD,gBAAM,QAAQ,YAAY,EAAE,KAAK,UAAU,IAAI,MAAM,GAAG,KAAK,UAAU,YAAY,MAAM,GAAG,IAAI,SAAS,IAAI;AAC7G,gBAAM,IAAI,OAAO,IAAI,OAAO,YAAY,EAAE,CAAC;AAAA,QAC7C;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,OAAO;AACX,UAAI;AACF,cAAM,OAAO;AACb,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWA,MAAM,SAAS,OAAO,YAAY,QAAQ,QAAQ,KAAK;AACrD,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC;AACtF,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AACnD,YAAM,cAAc,QACjB,OAAO,OAAK,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG,CAAC,EACtD,KAAK;AAER,YAAM,QAAQ,SAAS,SAAS,QAAQ,EAAE,IAAI;AAC9C,YAAM,MAAM,KAAK,IAAI,QAAQ,OAAO,YAAY,MAAM;AAEtD,YAAM,QAA4D,CAAC;AACnE,eAAS,IAAI,OAAO,IAAI,KAAK,KAAK;AAChC,cAAM,IAAI,YAAY,CAAC;AACvB,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,CAAC,IAAK;AAEV,YAAI;AACJ,YAAI;AACJ,YAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,gBAAM,SAAS;AACf,qBAAW,OAAO;AAClB,eAAK,UAAU,OAAO,KAAK,MAAM;AAAA,QACnC,OAAO;AACL,qBAAW;AACX,eAAK,EAAE,MAAM,IAAI,MAAM;AAAA,QACzB;AACA,cAAM,KAAK,EAAE,IAAI,SAAS,CAAC;AAAA,MAC7B;AAEA,aAAO;AAAA,QACL;AAAA,QACA,YAAY,MAAM,YAAY,SAAS,OAAO,GAAG,IAAI;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -83,6 +83,10 @@ function createIndexedDBAdapter(prefix, obfuscate, obfKey) {
|
|
|
83
83
|
}
|
|
84
84
|
return {
|
|
85
85
|
name: "browser:indexedDB",
|
|
86
|
+
capabilities: {
|
|
87
|
+
casAtomic: true,
|
|
88
|
+
auth: { kind: "browser-origin", required: false, flow: "implicit" }
|
|
89
|
+
},
|
|
86
90
|
async get(vault, collection, id) {
|
|
87
91
|
const { store } = await tx("readonly");
|
|
88
92
|
const raw = await idbRequest(store.get(key(vault, collection, id)));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/to-browser-idb** — IndexedDB-backed NOYDB store.\n *\n * The recommended browser store. Uses a single IndexedDB database\n * (`{prefix}_noydb`) with one object store (`records`). Composite string\n * keys (`{vault}:{collection}:{id}`) avoid the need for complex indexes.\n *\n * ## Why IndexedDB over localStorage\n *\n * - **Larger quota** — typically 50–80% of available disk vs. localStorage's\n * 5 MB hard cap.\n * - **True atomic CAS** — the compare-and-swap check and write happen inside\n * a single `readwrite` IDB transaction, so concurrent writes from two tabs\n * cannot interleave. (`casAtomic: true`.)\n * - **Async API** — doesn't block the main thread during large reads.\n *\n * ## Obfuscation mode\n *\n * Identical to `@noy-db/to-browser-local` — FNV-1a hashed keys + XOR-encoded\n * metadata. Envelope `_data` is always AES-GCM ciphertext regardless of this\n * setting.\n *\n * ## Capabilities\n *\n * | Capability | Value |\n * |---|---|\n * | `casAtomic` | `true` — single readwrite IDB transaction per put |\n * | `listPage` | ✓ — cursor-based pagination via `getAllKeys()` |\n * | `ping` | ✓ — `openDB()` round-trip |\n *\n * @packageDocumentation\n */\n\nimport type { NoydbStore, EncryptedEnvelope, VaultSnapshot } from '@noy-db/hub'\nimport { ConflictError } from '@noy-db/hub'\n\n/**\n * Options for `browserIdbStore()`.\n *\n * All NOYDB data is stored in a single IndexedDB database named\n * `{prefix}_noydb`, using composite string keys of the form\n * `{vault}:{collection}:{id}`. CAS checks and writes happen inside a\n * single `readwrite` IDB transaction, giving true atomic compare-and-swap\n * (`casAtomic: true`). Enable `obfuscate` to hash key components and\n * XOR-encode metadata — same obfuscation model as `browserLocalStore`.\n */\nexport interface BrowserIdbOptions {\n /** Storage key prefix. Default: 'noydb'. */\n prefix?: string\n /** Obfuscate storage keys so collection/record names are not readable. Default: false. */\n obfuscate?: boolean\n}\n\n/**\n * Create an IndexedDB-backed store adapter.\n *\n * Key scheme (normal): `{vault}:{collection}:{id}`\n * Key scheme (obfuscated): `{hash}:{hash}:{hash}`\n *\n * StoreCapabilities:\n * casAtomic: true — CAS check + write happen inside a single readwrite IDB transaction\n * auth: { kind: 'browser-origin', flow: 'implicit', required: false }\n */\nexport function browserIdbStore(options: BrowserIdbOptions = {}): NoydbStore {\n const prefix = options.prefix ?? 'noydb'\n const obfuscate = options.obfuscate ?? false\n const obfKey = obfuscate ? makeObfKey(prefix) : ''\n\n return createIndexedDBAdapter(prefix, obfuscate, obfKey)\n}\n\n// ─── Key Obfuscation ───────────────────────────────────────────────────\n\n/**\n * FNV-1a 32-bit hash → 8-char hex string.\n * Not cryptographic — just makes keys opaque to casual inspection.\n */\nfunction fnv1a(str: string): string {\n let hash = 0x811c9dc5\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n\nfunction hashComponent(value: string, obfuscate: boolean): string {\n return obfuscate ? fnv1a(value) : value\n}\n\n// ─── XOR Encode/Decode (makes metadata unreadable in storage) ──────────\n\n/** XOR-encode a string with a repeating key, return base64. */\nfunction xorEncode(plaintext: string, key: string): string {\n const bytes = new TextEncoder().encode(plaintext)\n const keyBytes = new TextEncoder().encode(key)\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = bytes[i]! ^ keyBytes[i % keyBytes.length]!\n }\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\n/** Decode a base64 XOR-encoded string. */\nfunction xorDecode(encoded: string, key: string): string {\n const binary = atob(encoded)\n const bytes = new Uint8Array(binary.length)\n const keyBytes = new TextEncoder().encode(key)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i) ^ keyBytes[i % keyBytes.length]!\n }\n return new TextDecoder().decode(bytes)\n}\n\n/** Stored value wraps envelope + encoded original key parts. */\ninterface StoredValue {\n /** Encoded original record ID. */\n _oi: string\n /** Encoded original collection name. */\n _oc: string\n /** The encrypted envelope. */\n _e: EncryptedEnvelope\n}\n\nfunction makeObfKey(prefix: string): string {\n return prefix + ':noydb-obf-key'\n}\n\n// ─── IndexedDB Backend ─────────────────────────────────────────────────\n\nfunction createIndexedDBAdapter(prefix: string, obfuscate: boolean, obfKey: string): NoydbStore {\n const DB_NAME = `${prefix}_noydb`\n const STORE_NAME = 'records'\n let dbPromise: Promise<IDBDatabase> | null = null\n\n function openDB(): Promise<IDBDatabase> {\n if (!dbPromise) {\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, 1)\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n }\n return dbPromise\n }\n\n function key(vault: string, collection: string, id: string): string {\n return `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:${hashComponent(id, obfuscate)}`\n }\n\n function tx(mode: IDBTransactionMode): Promise<{ store: IDBObjectStore; complete: Promise<void> }> {\n return openDB().then(db => {\n const transaction = db.transaction(STORE_NAME, mode)\n const store = transaction.objectStore(STORE_NAME)\n const complete = new Promise<void>((resolve, reject) => {\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => reject(transaction.error)\n })\n return { store, complete }\n })\n }\n\n function idbRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n }\n\n return {\n name: 'browser:indexedDB',\n\n async get(vault, collection, id) {\n const { store } = await tx('readonly')\n const raw = await idbRequest(store.get(key(vault, collection, id)))\n if (!raw) return null\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n return (raw as StoredValue)._e\n }\n return raw as EncryptedEnvelope\n },\n\n async put(vault, collection, id, envelope, expectedVersion) {\n const k = key(vault, collection, id)\n\n const { store, complete } = await tx('readwrite')\n if (expectedVersion !== undefined) {\n const existing = await idbRequest(store.get(k))\n if (existing) {\n const env = obfuscate && '_e' in (existing as StoredValue) ? (existing as StoredValue)._e : existing as EncryptedEnvelope\n if (env._v !== expectedVersion) {\n throw new ConflictError(env._v, `Version conflict: expected ${expectedVersion}, found ${env._v}`)\n }\n }\n }\n\n const value = obfuscate ? { _oi: xorEncode(id, obfKey), _oc: xorEncode(collection, obfKey), _e: envelope } : envelope\n store.put(value, k)\n await complete\n },\n\n async delete(vault, collection, id) {\n const { store, complete } = await tx('readwrite')\n store.delete(key(vault, collection, id))\n await complete\n },\n\n async list(vault, collection) {\n const pfx = `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n\n if (!obfuscate) {\n return allKeys\n .filter(k => typeof k === 'string' && k.startsWith(pfx))\n .map(k => k.slice(pfx.length))\n }\n\n // Obfuscated: need to read values for original IDs\n const ids: string[] = []\n for (const k of allKeys) {\n if (typeof k !== 'string' || !k.startsWith(pfx)) continue\n const raw = await idbRequest(store.get(k))\n if (raw && typeof raw === 'object' && '_oi' in (raw as StoredValue)) {\n ids.push(xorDecode((raw as StoredValue)._oi, obfKey))\n }\n }\n return ids\n },\n\n async loadAll(vault) {\n const pfx = `${hashComponent(vault, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n const snapshot: VaultSnapshot = {}\n\n for (const k of allKeys) {\n if (typeof k !== 'string' || !k.startsWith(pfx)) continue\n\n const raw = await idbRequest(store.get(k))\n if (!raw) continue\n\n let collection: string\n let id: string\n let envelope: EncryptedEnvelope\n\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n const stored = raw as StoredValue\n collection = xorDecode(stored._oc, obfKey)\n id = xorDecode(stored._oi, obfKey)\n envelope = stored._e\n } else {\n const rest = k.slice(pfx.length)\n const colonIdx = rest.indexOf(':')\n if (colonIdx < 0) continue\n collection = rest.slice(0, colonIdx)\n id = rest.slice(colonIdx + 1)\n envelope = raw as EncryptedEnvelope\n }\n\n if (collection.startsWith('_')) continue\n if (!snapshot[collection]) snapshot[collection] = {}\n snapshot[collection]![id] = envelope\n }\n\n return snapshot\n },\n\n async saveAll(vault, data) {\n const { store, complete } = await tx('readwrite')\n for (const [collection, records] of Object.entries(data)) {\n for (const [id, envelope] of Object.entries(records)) {\n const value = obfuscate ? { _oi: xorEncode(id, obfKey), _oc: xorEncode(collection, obfKey), _e: envelope } : envelope\n store.put(value, key(vault, collection, id))\n }\n }\n await complete\n },\n\n async ping() {\n try {\n await openDB()\n return true\n } catch {\n return false\n }\n },\n\n /**\n * Paginate over a collection backed by IndexedDB.\n *\n * Strategy: read every key in the prefix once (sorted), then slice\n * by cursor offset. IndexedDB's `getAllKeys()` returns sorted keys\n * efficiently for the modern browsers we target (Chrome 87+,\n * Firefox 78+, Safari 14+, Edge 88+ — same baseline as the rest of\n * the build target).\n */\n async listPage(vault, collection, cursor, limit = 100) {\n const pfx = `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n const matchedKeys = allKeys\n .filter(k => typeof k === 'string' && k.startsWith(pfx))\n .sort()\n\n const start = cursor ? parseInt(cursor, 10) : 0\n const end = Math.min(start + limit, matchedKeys.length)\n\n const items: Array<{ id: string; envelope: EncryptedEnvelope }> = []\n for (let i = start; i < end; i++) {\n const k = matchedKeys[i]!\n const raw = await idbRequest(store.get(k))\n if (!raw) continue\n\n let envelope: EncryptedEnvelope\n let id: string\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n const stored = raw as StoredValue\n envelope = stored._e\n id = xorDecode(stored._oi, obfKey)\n } else {\n envelope = raw as EncryptedEnvelope\n id = k.slice(pfx.length)\n }\n items.push({ id, envelope })\n }\n\n return {\n items,\n nextCursor: end < matchedKeys.length ? String(end) : null,\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/to-browser-idb** — IndexedDB-backed NOYDB store.\n *\n * The recommended browser store. Uses a single IndexedDB database\n * (`{prefix}_noydb`) with one object store (`records`). Composite string\n * keys (`{vault}:{collection}:{id}`) avoid the need for complex indexes.\n *\n * ## Why IndexedDB over localStorage\n *\n * - **Larger quota** — typically 50–80% of available disk vs. localStorage's\n * 5 MB hard cap.\n * - **True atomic CAS** — the compare-and-swap check and write happen inside\n * a single `readwrite` IDB transaction, so concurrent writes from two tabs\n * cannot interleave. (`casAtomic: true`.)\n * - **Async API** — doesn't block the main thread during large reads.\n *\n * ## Obfuscation mode\n *\n * Identical to `@noy-db/to-browser-local` — FNV-1a hashed keys + XOR-encoded\n * metadata. Envelope `_data` is always AES-GCM ciphertext regardless of this\n * setting.\n *\n * ## Capabilities\n *\n * | Capability | Value |\n * |---|---|\n * | `casAtomic` | `true` — single readwrite IDB transaction per put |\n * | `listPage` | ✓ — cursor-based pagination via `getAllKeys()` |\n * | `ping` | ✓ — `openDB()` round-trip |\n *\n * @packageDocumentation\n */\n\nimport type { NoydbStore, EncryptedEnvelope, VaultSnapshot } from '@noy-db/hub'\nimport { ConflictError } from '@noy-db/hub'\n\n/**\n * Options for `browserIdbStore()`.\n *\n * All NOYDB data is stored in a single IndexedDB database named\n * `{prefix}_noydb`, using composite string keys of the form\n * `{vault}:{collection}:{id}`. CAS checks and writes happen inside a\n * single `readwrite` IDB transaction, giving true atomic compare-and-swap\n * (`casAtomic: true`). Enable `obfuscate` to hash key components and\n * XOR-encode metadata — same obfuscation model as `browserLocalStore`.\n */\nexport interface BrowserIdbOptions {\n /** Storage key prefix. Default: 'noydb'. */\n prefix?: string\n /** Obfuscate storage keys so collection/record names are not readable. Default: false. */\n obfuscate?: boolean\n}\n\n/**\n * Create an IndexedDB-backed store adapter.\n *\n * Key scheme (normal): `{vault}:{collection}:{id}`\n * Key scheme (obfuscated): `{hash}:{hash}:{hash}`\n *\n * StoreCapabilities:\n * casAtomic: true — CAS check + write happen inside a single readwrite IDB transaction\n * auth: { kind: 'browser-origin', flow: 'implicit', required: false }\n */\nexport function browserIdbStore(options: BrowserIdbOptions = {}): NoydbStore {\n const prefix = options.prefix ?? 'noydb'\n const obfuscate = options.obfuscate ?? false\n const obfKey = obfuscate ? makeObfKey(prefix) : ''\n\n return createIndexedDBAdapter(prefix, obfuscate, obfKey)\n}\n\n// ─── Key Obfuscation ───────────────────────────────────────────────────\n\n/**\n * FNV-1a 32-bit hash → 8-char hex string.\n * Not cryptographic — just makes keys opaque to casual inspection.\n */\nfunction fnv1a(str: string): string {\n let hash = 0x811c9dc5\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n\nfunction hashComponent(value: string, obfuscate: boolean): string {\n return obfuscate ? fnv1a(value) : value\n}\n\n// ─── XOR Encode/Decode (makes metadata unreadable in storage) ──────────\n\n/** XOR-encode a string with a repeating key, return base64. */\nfunction xorEncode(plaintext: string, key: string): string {\n const bytes = new TextEncoder().encode(plaintext)\n const keyBytes = new TextEncoder().encode(key)\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = bytes[i]! ^ keyBytes[i % keyBytes.length]!\n }\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\n/** Decode a base64 XOR-encoded string. */\nfunction xorDecode(encoded: string, key: string): string {\n const binary = atob(encoded)\n const bytes = new Uint8Array(binary.length)\n const keyBytes = new TextEncoder().encode(key)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i) ^ keyBytes[i % keyBytes.length]!\n }\n return new TextDecoder().decode(bytes)\n}\n\n/** Stored value wraps envelope + encoded original key parts. */\ninterface StoredValue {\n /** Encoded original record ID. */\n _oi: string\n /** Encoded original collection name. */\n _oc: string\n /** The encrypted envelope. */\n _e: EncryptedEnvelope\n}\n\nfunction makeObfKey(prefix: string): string {\n return prefix + ':noydb-obf-key'\n}\n\n// ─── IndexedDB Backend ─────────────────────────────────────────────────\n\nfunction createIndexedDBAdapter(prefix: string, obfuscate: boolean, obfKey: string): NoydbStore {\n const DB_NAME = `${prefix}_noydb`\n const STORE_NAME = 'records'\n let dbPromise: Promise<IDBDatabase> | null = null\n\n function openDB(): Promise<IDBDatabase> {\n if (!dbPromise) {\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, 1)\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n }\n return dbPromise\n }\n\n function key(vault: string, collection: string, id: string): string {\n return `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:${hashComponent(id, obfuscate)}`\n }\n\n function tx(mode: IDBTransactionMode): Promise<{ store: IDBObjectStore; complete: Promise<void> }> {\n return openDB().then(db => {\n const transaction = db.transaction(STORE_NAME, mode)\n const store = transaction.objectStore(STORE_NAME)\n const complete = new Promise<void>((resolve, reject) => {\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => reject(transaction.error)\n })\n return { store, complete }\n })\n }\n\n function idbRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n }\n\n return {\n name: 'browser:indexedDB',\n\n capabilities: {\n casAtomic: true,\n auth: { kind: 'browser-origin' as const, required: false, flow: 'implicit' as const },\n },\n\n async get(vault, collection, id) {\n const { store } = await tx('readonly')\n const raw = await idbRequest(store.get(key(vault, collection, id)))\n if (!raw) return null\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n return (raw as StoredValue)._e\n }\n return raw as EncryptedEnvelope\n },\n\n async put(vault, collection, id, envelope, expectedVersion) {\n const k = key(vault, collection, id)\n\n const { store, complete } = await tx('readwrite')\n if (expectedVersion !== undefined) {\n const existing = await idbRequest(store.get(k))\n if (existing) {\n const env = obfuscate && '_e' in (existing as StoredValue) ? (existing as StoredValue)._e : existing as EncryptedEnvelope\n if (env._v !== expectedVersion) {\n throw new ConflictError(env._v, `Version conflict: expected ${expectedVersion}, found ${env._v}`)\n }\n }\n }\n\n const value = obfuscate ? { _oi: xorEncode(id, obfKey), _oc: xorEncode(collection, obfKey), _e: envelope } : envelope\n store.put(value, k)\n await complete\n },\n\n async delete(vault, collection, id) {\n const { store, complete } = await tx('readwrite')\n store.delete(key(vault, collection, id))\n await complete\n },\n\n async list(vault, collection) {\n const pfx = `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n\n if (!obfuscate) {\n return allKeys\n .filter(k => typeof k === 'string' && k.startsWith(pfx))\n .map(k => k.slice(pfx.length))\n }\n\n // Obfuscated: need to read values for original IDs\n const ids: string[] = []\n for (const k of allKeys) {\n if (typeof k !== 'string' || !k.startsWith(pfx)) continue\n const raw = await idbRequest(store.get(k))\n if (raw && typeof raw === 'object' && '_oi' in (raw as StoredValue)) {\n ids.push(xorDecode((raw as StoredValue)._oi, obfKey))\n }\n }\n return ids\n },\n\n async loadAll(vault) {\n const pfx = `${hashComponent(vault, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n const snapshot: VaultSnapshot = {}\n\n for (const k of allKeys) {\n if (typeof k !== 'string' || !k.startsWith(pfx)) continue\n\n const raw = await idbRequest(store.get(k))\n if (!raw) continue\n\n let collection: string\n let id: string\n let envelope: EncryptedEnvelope\n\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n const stored = raw as StoredValue\n collection = xorDecode(stored._oc, obfKey)\n id = xorDecode(stored._oi, obfKey)\n envelope = stored._e\n } else {\n const rest = k.slice(pfx.length)\n const colonIdx = rest.indexOf(':')\n if (colonIdx < 0) continue\n collection = rest.slice(0, colonIdx)\n id = rest.slice(colonIdx + 1)\n envelope = raw as EncryptedEnvelope\n }\n\n if (collection.startsWith('_')) continue\n if (!snapshot[collection]) snapshot[collection] = {}\n snapshot[collection]![id] = envelope\n }\n\n return snapshot\n },\n\n async saveAll(vault, data) {\n const { store, complete } = await tx('readwrite')\n for (const [collection, records] of Object.entries(data)) {\n for (const [id, envelope] of Object.entries(records)) {\n const value = obfuscate ? { _oi: xorEncode(id, obfKey), _oc: xorEncode(collection, obfKey), _e: envelope } : envelope\n store.put(value, key(vault, collection, id))\n }\n }\n await complete\n },\n\n async ping() {\n try {\n await openDB()\n return true\n } catch {\n return false\n }\n },\n\n /**\n * Paginate over a collection backed by IndexedDB.\n *\n * Strategy: read every key in the prefix once (sorted), then slice\n * by cursor offset. IndexedDB's `getAllKeys()` returns sorted keys\n * efficiently for the modern browsers we target (Chrome 87+,\n * Firefox 78+, Safari 14+, Edge 88+ — same baseline as the rest of\n * the build target).\n */\n async listPage(vault, collection, cursor, limit = 100) {\n const pfx = `${hashComponent(vault, obfuscate)}:${hashComponent(collection, obfuscate)}:`\n const { store } = await tx('readonly')\n const allKeys = await idbRequest(store.getAllKeys()) as string[]\n const matchedKeys = allKeys\n .filter(k => typeof k === 'string' && k.startsWith(pfx))\n .sort()\n\n const start = cursor ? parseInt(cursor, 10) : 0\n const end = Math.min(start + limit, matchedKeys.length)\n\n const items: Array<{ id: string; envelope: EncryptedEnvelope }> = []\n for (let i = start; i < end; i++) {\n const k = matchedKeys[i]!\n const raw = await idbRequest(store.get(k))\n if (!raw) continue\n\n let envelope: EncryptedEnvelope\n let id: string\n if (obfuscate && typeof raw === 'object' && '_e' in (raw as StoredValue)) {\n const stored = raw as StoredValue\n envelope = stored._e\n id = xorDecode(stored._oi, obfKey)\n } else {\n envelope = raw as EncryptedEnvelope\n id = k.slice(pfx.length)\n }\n items.push({ id, envelope })\n }\n\n return {\n items,\n nextCursor: end < matchedKeys.length ? String(end) : null,\n }\n },\n }\n}\n"],"mappings":";AAkCA,SAAS,qBAAqB;AA6BvB,SAAS,gBAAgB,UAA6B,CAAC,GAAe;AAC3E,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,SAAS,YAAY,WAAW,MAAM,IAAI;AAEhD,SAAO,uBAAuB,QAAQ,WAAW,MAAM;AACzD;AAQA,SAAS,MAAM,KAAqB;AAClC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,YAAQ,IAAI,WAAW,CAAC;AACxB,WAAO,KAAK,KAAK,MAAM,QAAU;AAAA,EACnC;AACA,UAAQ,SAAS,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAClD;AAEA,SAAS,cAAc,OAAe,WAA4B;AAChE,SAAO,YAAY,MAAM,KAAK,IAAI;AACpC;AAKA,SAAS,UAAU,WAAmB,KAAqB;AACzD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,SAAS;AAChD,QAAM,WAAW,IAAI,YAAY,EAAE,OAAO,GAAG;AAC7C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,CAAC,IAAI,MAAM,CAAC,IAAK,SAAS,IAAI,SAAS,MAAM;AAAA,EACrD;AACA,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAE;AAAA,EACzC;AACA,SAAO,KAAK,MAAM;AACpB;AAGA,SAAS,UAAU,SAAiB,KAAqB;AACvD,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,QAAM,WAAW,IAAI,YAAY,EAAE,OAAO,GAAG;AAC7C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC,IAAI,SAAS,IAAI,SAAS,MAAM;AAAA,EAChE;AACA,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACvC;AAYA,SAAS,WAAW,QAAwB;AAC1C,SAAO,SAAS;AAClB;AAIA,SAAS,uBAAuB,QAAgB,WAAoB,QAA4B;AAC9F,QAAM,UAAU,GAAG,MAAM;AACzB,QAAM,aAAa;AACnB,MAAI,YAAyC;AAE7C,WAAS,SAA+B;AACtC,QAAI,CAAC,WAAW;AACd,kBAAY,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC3C,cAAM,UAAU,UAAU,KAAK,SAAS,CAAC;AACzC,gBAAQ,kBAAkB,MAAM;AAC9B,gBAAM,KAAK,QAAQ;AACnB,cAAI,CAAC,GAAG,iBAAiB,SAAS,UAAU,GAAG;AAC7C,eAAG,kBAAkB,UAAU;AAAA,UACjC;AAAA,QACF;AACA,gBAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,gBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,MAC9C,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,OAAe,YAAoB,IAAoB;AAClE,WAAO,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC,IAAI,cAAc,IAAI,SAAS,CAAC;AAAA,EACnH;AAEA,WAAS,GAAG,MAAuF;AACjG,WAAO,OAAO,EAAE,KAAK,QAAM;AACzB,YAAM,cAAc,GAAG,YAAY,YAAY,IAAI;AACnD,YAAM,QAAQ,YAAY,YAAY,UAAU;AAChD,YAAM,WAAW,IAAI,QAAc,CAAC,SAAS,WAAW;AACtD,oBAAY,aAAa,MAAM,QAAQ;AACvC,oBAAY,UAAU,MAAM,OAAO,YAAY,KAAK;AAAA,MACtD,CAAC;AACD,aAAO,EAAE,OAAO,SAAS;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,WAAS,WAAc,SAAoC;AACzD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,cAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,IAC9C,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,cAAc;AAAA,MACZ,WAAW;AAAA,MACX,MAAM,EAAE,MAAM,kBAA2B,UAAU,OAAO,MAAM,WAAoB;AAAA,IACtF;AAAA,IAEA,MAAM,IAAI,OAAO,YAAY,IAAI;AAC/B,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,MAAM,MAAM,WAAW,MAAM,IAAI,IAAI,OAAO,YAAY,EAAE,CAAC,CAAC;AAClE,UAAI,CAAC,IAAK,QAAO;AACjB,UAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,eAAQ,IAAoB;AAAA,MAC9B;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,IAAI,OAAO,YAAY,IAAI,UAAU,iBAAiB;AAC1D,YAAM,IAAI,IAAI,OAAO,YAAY,EAAE;AAEnC,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,UAAI,oBAAoB,QAAW;AACjC,cAAM,WAAW,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AAC9C,YAAI,UAAU;AACZ,gBAAM,MAAM,aAAa,QAAS,WAA4B,SAAyB,KAAK;AAC5F,cAAI,IAAI,OAAO,iBAAiB;AAC9B,kBAAM,IAAI,cAAc,IAAI,IAAI,8BAA8B,eAAe,WAAW,IAAI,EAAE,EAAE;AAAA,UAClG;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,YAAY,EAAE,KAAK,UAAU,IAAI,MAAM,GAAG,KAAK,UAAU,YAAY,MAAM,GAAG,IAAI,SAAS,IAAI;AAC7G,YAAM,IAAI,OAAO,CAAC;AAClB,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,OAAO,OAAO,YAAY,IAAI;AAClC,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,YAAM,OAAO,IAAI,OAAO,YAAY,EAAE,CAAC;AACvC,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,KAAK,OAAO,YAAY;AAC5B,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC;AACtF,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AAEnD,UAAI,CAAC,WAAW;AACd,eAAO,QACJ,OAAO,OAAK,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG,CAAC,EACtD,IAAI,OAAK,EAAE,MAAM,IAAI,MAAM,CAAC;AAAA,MACjC;AAGA,YAAM,MAAgB,CAAC;AACvB,iBAAW,KAAK,SAAS;AACvB,YAAI,OAAO,MAAM,YAAY,CAAC,EAAE,WAAW,GAAG,EAAG;AACjD,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,OAAO,OAAO,QAAQ,YAAY,SAAU,KAAqB;AACnE,cAAI,KAAK,UAAW,IAAoB,KAAK,MAAM,CAAC;AAAA,QACtD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,QAAQ,OAAO;AACnB,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC;AAC9C,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AACnD,YAAM,WAA0B,CAAC;AAEjC,iBAAW,KAAK,SAAS;AACvB,YAAI,OAAO,MAAM,YAAY,CAAC,EAAE,WAAW,GAAG,EAAG;AAEjD,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,CAAC,IAAK;AAEV,YAAI;AACJ,YAAI;AACJ,YAAI;AAEJ,YAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,gBAAM,SAAS;AACf,uBAAa,UAAU,OAAO,KAAK,MAAM;AACzC,eAAK,UAAU,OAAO,KAAK,MAAM;AACjC,qBAAW,OAAO;AAAA,QACpB,OAAO;AACL,gBAAM,OAAO,EAAE,MAAM,IAAI,MAAM;AAC/B,gBAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,cAAI,WAAW,EAAG;AAClB,uBAAa,KAAK,MAAM,GAAG,QAAQ;AACnC,eAAK,KAAK,MAAM,WAAW,CAAC;AAC5B,qBAAW;AAAA,QACb;AAEA,YAAI,WAAW,WAAW,GAAG,EAAG;AAChC,YAAI,CAAC,SAAS,UAAU,EAAG,UAAS,UAAU,IAAI,CAAC;AACnD,iBAAS,UAAU,EAAG,EAAE,IAAI;AAAA,MAC9B;AAEA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,QAAQ,OAAO,MAAM;AACzB,YAAM,EAAE,OAAO,SAAS,IAAI,MAAM,GAAG,WAAW;AAChD,iBAAW,CAAC,YAAY,OAAO,KAAK,OAAO,QAAQ,IAAI,GAAG;AACxD,mBAAW,CAAC,IAAI,QAAQ,KAAK,OAAO,QAAQ,OAAO,GAAG;AACpD,gBAAM,QAAQ,YAAY,EAAE,KAAK,UAAU,IAAI,MAAM,GAAG,KAAK,UAAU,YAAY,MAAM,GAAG,IAAI,SAAS,IAAI;AAC7G,gBAAM,IAAI,OAAO,IAAI,OAAO,YAAY,EAAE,CAAC;AAAA,QAC7C;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,IAEA,MAAM,OAAO;AACX,UAAI;AACF,cAAM,OAAO;AACb,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWA,MAAM,SAAS,OAAO,YAAY,QAAQ,QAAQ,KAAK;AACrD,YAAM,MAAM,GAAG,cAAc,OAAO,SAAS,CAAC,IAAI,cAAc,YAAY,SAAS,CAAC;AACtF,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,UAAU;AACrC,YAAM,UAAU,MAAM,WAAW,MAAM,WAAW,CAAC;AACnD,YAAM,cAAc,QACjB,OAAO,OAAK,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG,CAAC,EACtD,KAAK;AAER,YAAM,QAAQ,SAAS,SAAS,QAAQ,EAAE,IAAI;AAC9C,YAAM,MAAM,KAAK,IAAI,QAAQ,OAAO,YAAY,MAAM;AAEtD,YAAM,QAA4D,CAAC;AACnE,eAAS,IAAI,OAAO,IAAI,KAAK,KAAK;AAChC,cAAM,IAAI,YAAY,CAAC;AACvB,cAAM,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC;AACzC,YAAI,CAAC,IAAK;AAEV,YAAI;AACJ,YAAI;AACJ,YAAI,aAAa,OAAO,QAAQ,YAAY,QAAS,KAAqB;AACxE,gBAAM,SAAS;AACf,qBAAW,OAAO;AAClB,eAAK,UAAU,OAAO,KAAK,MAAM;AAAA,QACnC,OAAO;AACL,qBAAW;AACX,eAAK,EAAE,MAAM,IAAI,MAAM;AAAA,QACzB;AACA,cAAM,KAAK,EAAE,IAAI,SAAS,CAAC;AAAA,MAC7B;AAEA,aAAO;AAAA,QACL;AAAA,QACA,YAAY,MAAM,YAAY,SAAS,OAAO,GAAG,IAAI;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noy-db/to-browser-idb",
|
|
3
|
-
"version": "0.2.0-pre.
|
|
3
|
+
"version": "0.2.0-pre.30",
|
|
4
4
|
"description": "IndexedDB adapter for noy-db with optional key obfuscation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vLannaAi <vicio@lanna.ai>",
|
|
@@ -39,13 +39,13 @@
|
|
|
39
39
|
"node": ">=18.0.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@noy-db/hub": "0.2.0-pre.
|
|
42
|
+
"@noy-db/hub": "0.2.0-pre.30"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"fake-indexeddb": "^6.2.5",
|
|
46
46
|
"happy-dom": "^18.0.0",
|
|
47
|
-
"@noy-db/
|
|
48
|
-
"@noy-db/
|
|
47
|
+
"@noy-db/hub": "0.2.0-pre.30",
|
|
48
|
+
"@noy-db/test-adapter-conformance": "0.0.0"
|
|
49
49
|
},
|
|
50
50
|
"keywords": [
|
|
51
51
|
"noy-db",
|