Created
July 2, 2025 22:04
-
-
Save tanishqkancharla/b247a215ea2c3a4dfeef619a09e4c726 to your computer and use it in GitHub Desktop.
IndexedDb storage adapter for tuple-db
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Codec } from "@repo/utils/codec"; | |
import { deleteDB, IDBPDatabase, openDB } from "idb"; | |
import { KeyValuePair, ScanStorageArgs, WriteOps } from "tuple-database"; | |
import { decodeTuple, encodeTuple } from "tuple-database/helpers/codec"; | |
import { AnySchema, Json, StorageApi } from "../types"; | |
const version = 1; | |
const storeName = "tupledb"; | |
type AnyStorageSchema<Schema extends AnySchema> = { | |
[K in keyof Schema]?: Json; | |
}; | |
type IndexedDbTupleStorageArgs< | |
Schema extends AnySchema, | |
StorageSchema extends AnyStorageSchema<Schema> = AnyStorageSchema<Schema>, | |
> = { | |
dbName: string; | |
codecs?: { | |
[K in keyof Schema]?: Codec<Schema[K], StorageSchema[K]>; | |
}; | |
}; | |
export class IndexedDbTupleStorage< | |
Schema extends AnySchema, | |
StorageSchema extends { | |
[K in keyof Schema]?: Json; | |
} = Schema, | |
> implements StorageApi | |
{ | |
private db: Promise<IDBPDatabase<any>>; | |
private codecs?: { | |
[K in keyof Schema]?: Codec<Schema[K], StorageSchema[K]>; | |
}; | |
private dbName: string; | |
constructor({ | |
dbName, | |
codecs, | |
}: IndexedDbTupleStorageArgs<Schema, StorageSchema>) { | |
this.codecs = codecs; | |
this.dbName = dbName; | |
this.db = openDB(dbName, version, { | |
upgrade(db: IDBPDatabase) { | |
db.createObjectStore(storeName); | |
}, | |
}); | |
} | |
async scan(args?: ScanStorageArgs) { | |
console.log("scan", args); | |
const db = await this.db; | |
const tx = db.transaction(storeName, "readonly"); | |
const index = tx.store; // primary key | |
const lower = args?.gt || args?.gte; | |
const lowerEq = Boolean(args?.gte); | |
const upper = args?.lt || args?.lte; | |
const upperEq = Boolean(args?.lte); | |
let range: any; | |
if (upper) { | |
if (lower) { | |
range = (globalThis as any).IDBKeyRange.bound( | |
encodeTuple(lower), | |
encodeTuple(upper), | |
!lowerEq, | |
!upperEq, | |
); | |
} else { | |
range = (globalThis as any).IDBKeyRange.upperBound( | |
encodeTuple(upper), | |
!upperEq, | |
); | |
} | |
} else { | |
if (lower) { | |
range = (globalThis as any).IDBKeyRange.lowerBound( | |
encodeTuple(lower), | |
!lowerEq, | |
); | |
} else { | |
range = null; | |
} | |
} | |
const direction = args?.reverse ? "prev" : "next"; | |
const limit = args?.limit || Infinity; | |
let results: KeyValuePair[] = []; | |
for await (const cursor of index.iterate(range, direction)) { | |
const key = decodeTuple(cursor.key); | |
const recordType = key[1] as keyof Schema & string; | |
const codec = this.codecs?.[recordType]; | |
const value = codec ? codec.decode(cursor.value) : cursor.value; | |
results.push({ | |
key, | |
value, | |
}); | |
if (results.length >= limit) break; | |
} | |
await tx.done; | |
return results; | |
} | |
async commit(writes: WriteOps) { | |
const db = await this.db; | |
const tx = db.transaction(storeName, "readwrite"); | |
for (const { key, value } of writes.set || []) { | |
const recordType = key[1] as keyof Schema & string; | |
const codec = this.codecs?.[recordType]; | |
const encodedValue = codec ? codec.encode(value) : value; | |
await tx.store.put(encodedValue, encodeTuple(key)); | |
} | |
for (const key of writes.remove || []) { | |
await tx.store.delete(encodeTuple(key)); | |
} | |
await tx.done; | |
} | |
async close() { | |
const db = await this.db; | |
db.close(); | |
} | |
async clear() { | |
const db = await this.db; | |
db.close(); | |
// Delete the database using idb's deleteDB | |
await deleteDB(this.dbName); | |
// Recreate the database | |
this.db = openDB(this.dbName, version, { | |
upgrade(db: IDBPDatabase) { | |
db.createObjectStore(storeName); | |
}, | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment