This splits indexing into two phases: * preindex (where only pages, space lua, space styles are indexed) * index (where everything else is indexed) The goal is to get to more-or-less operational state quicker this way, because probably most functionality can already be enabled after the pre-index phase.
111 lines
3.0 KiB
TypeScript
111 lines
3.0 KiB
TypeScript
import type { KvPrimitives, KvQueryOptions } from "./kv_primitives.ts";
|
|
import { type IDBPDatabase, openDB } from "idb";
|
|
|
|
import type { KV, KvKey } from "../../plug-api/types/datastore.ts";
|
|
|
|
// Separator character to use for key serialization
|
|
const sep = "\0";
|
|
const objectStoreName = "data";
|
|
|
|
export class IndexedDBKvPrimitives implements KvPrimitives {
|
|
db!: IDBPDatabase<any>;
|
|
|
|
constructor(
|
|
private dbName: string,
|
|
) {
|
|
}
|
|
|
|
async init() {
|
|
this.db = await openDB(this.dbName, 1, {
|
|
upgrade: (db) => {
|
|
db.createObjectStore(objectStoreName);
|
|
},
|
|
});
|
|
}
|
|
|
|
async clear(): Promise<void> {
|
|
const objectStoreNames = this.db.objectStoreNames;
|
|
|
|
// Create a transaction that includes all object stores
|
|
const tx = this.db.transaction(objectStoreNames, "readwrite");
|
|
|
|
// Clear each object store in parallel
|
|
const clearPromises = Array.from(objectStoreNames).map((storeName) =>
|
|
tx.objectStore(storeName).clear()
|
|
);
|
|
|
|
// Wait for all clears to complete
|
|
await Promise.all(clearPromises);
|
|
|
|
// Complete the transaction
|
|
await tx.done;
|
|
}
|
|
|
|
batchGet(keys: KvKey[]): Promise<any[]> {
|
|
const tx = this.db.transaction(objectStoreName, "readonly");
|
|
return Promise.all(keys.map((key) => tx.store.get(this.buildKey(key))));
|
|
}
|
|
|
|
async batchSet(entries: KV[]): Promise<void> {
|
|
const tx = this.db.transaction(objectStoreName, "readwrite");
|
|
await Promise.all([
|
|
...entries.map(({ key, value }) =>
|
|
tx.store.put(value, this.buildKey(key))
|
|
),
|
|
tx.done,
|
|
]);
|
|
}
|
|
|
|
async batchDelete(keys: KvKey[]): Promise<void> {
|
|
const tx = this.db.transaction(objectStoreName, "readwrite");
|
|
await Promise.all([
|
|
...keys.map((key) => tx.store.delete(this.buildKey(key))),
|
|
tx.done,
|
|
]);
|
|
}
|
|
|
|
// Important IndexedDB limitation: no asynchronous processing can happen
|
|
// in the body of the for await: https://stackoverflow.com/a/51898463
|
|
async *query({ prefix }: KvQueryOptions): AsyncIterableIterator<KV> {
|
|
const tx = this.db.transaction(objectStoreName, "readonly");
|
|
prefix = prefix || [];
|
|
for await (
|
|
const entry of tx.store.iterate(IDBKeyRange.bound(
|
|
this.buildKey([...prefix, ""]),
|
|
this.buildKey([...prefix, "\uffff"]),
|
|
))
|
|
) {
|
|
yield { key: this.extractKey(entry.key), value: entry.value };
|
|
}
|
|
}
|
|
|
|
countQuery({ prefix }: KvQueryOptions): Promise<number> {
|
|
const tx = this.db.transaction(objectStoreName, "readonly");
|
|
prefix = prefix || [];
|
|
return tx.store.count(IDBKeyRange.bound(
|
|
this.buildKey([...prefix, ""]),
|
|
this.buildKey([...prefix, "\uffff"]),
|
|
));
|
|
}
|
|
|
|
close() {
|
|
this.db.close();
|
|
}
|
|
|
|
private buildKey(key: KvKey): string {
|
|
for (const k of key) {
|
|
if (typeof k !== "string") {
|
|
throw new Error(`Key needs to consists of strings: ${key}`);
|
|
}
|
|
if (k.includes(sep)) {
|
|
throw new Error(`Key cannot contain ${sep}`);
|
|
}
|
|
}
|
|
return key.join(sep);
|
|
}
|
|
|
|
private extractKey(key: string): KvKey {
|
|
return key.split(sep);
|
|
}
|
|
}
|