Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 141x 141x 517x 349x 344x 344x 344x 344x 344x 617x 617x 7x 7x 344x 274x 344x 344x 189x 141x 196x 194x 193x 193x 65x 19x 19x 46x 123x 122x 122x 114x 114x 114x 8x 8x 8x 8x 14x 14x 14x 14x 6x 6x 6x 8x 7x 7x 8x 7x 7x 24x | import { createStore } from "idb-keyval";
import { metadata, type StorageBackend, type Stored, scope, stored } from "./storage";
/** @internal */
export function persistentStorage(namespace: string, schemaVersion: number): StorageBackend {
const useStore = createStore("@oomol-lab/resource-cache", "records");
const prefix = `${scope(namespace)}:${schemaVersion}:`;
const identity = (key: string, part: string) => prefix + JSON.stringify([key, part]);
// All callbacks run synchronously within IDB events. Host code and network waits
// never run inside a transaction. Synchronous write failures explicitly abort it.
function transaction<R>(
mode: IDBTransactionMode,
work: (
store: IDBObjectStore,
read: <T>(request: IDBRequest<T>, fn: (value: T) => void) => void,
result: (value: R) => void,
) => void,
): Promise<R> {
return useStore(
mode,
(store) =>
new Promise<R>((resolve, reject) => {
const tx = store.transaction;
let value: R;
let failure: unknown;
tx.oncomplete = () => resolve(value);
tx.onabort = () => reject(failure ?? tx.error);
const guard = (fn: () => void) => {
try {
fn();
} catch (error) {
failure = error;
tx.abort();
}
};
const read = <T>(request: IDBRequest<T>, fn: (value: T) => void) => {
request.onsuccess = () => guard(() => fn(request.result));
};
guard(() =>
work(store, read, (result) => {
value = result;
}),
);
}),
);
}
return {
read(key, version) {
return transaction<Stored | undefined>("readonly", (store, read, result) => {
read(store.get(identity(key, "meta")), (raw) => {
const meta = metadata(raw);
if (!meta) return;
if (meta.version === version) {
result({ meta });
return;
}
read(store.get(identity(key, "body")), (body) => result(stored(meta, body)));
});
});
},
write(key, entry, modified) {
return transaction<boolean>("readwrite", (store, read, result) => {
const { data, ...meta } = entry;
if (modified) {
store.put(meta, identity(key, "meta"));
store.put({ version: meta.version, data }, identity(key, "body"));
result(true);
} else {
read(store.get(identity(key, "meta")), (raw) => {
const matches = metadata(raw)?.version === meta.version;
if (matches) store.put(meta, identity(key, "meta"));
result(matches);
});
}
});
},
invalidate(key) {
return transaction<Stored | undefined>("readwrite", (store, read, result) => {
read(store.get(identity(key, "meta")), (raw) => {
const meta = metadata(raw);
if (!meta) return;
meta.invalidated = true;
store.put(meta, identity(key, "meta"));
read(store.get(identity(key, "body")), (body) => result(stored(meta, body)));
});
});
},
remove(key) {
return transaction<void>("readwrite", (store) => {
store.delete(identity(key, "meta"));
store.delete(identity(key, "body"));
});
},
clear() {
return transaction<void>("readwrite", (store, read) => {
read(store.getAllKeys(), (keys) => {
for (const key of keys) {
if (typeof key === "string" && key.startsWith(prefix)) store.delete(key);
}
});
});
},
};
}
|