From 8cc87544c66df72f3cb831a3a2cf7dc0c315f244 Mon Sep 17 00:00:00 2001 From: hasundue Date: Mon, 22 Apr 2024 15:42:37 +0900 Subject: [PATCH] feat(deno/idb): implement `IDBObjectStore.get` --- deno/idb/mod.ts | 5 ++--- deno/idb/requests.ts | 6 +++--- deno/idb/stores.ts | 29 ++++++++++++++++++++++------- deno/idb/transactions.ts | 34 ++++++++++++++++++++++++++++++++++ deno/idb_test.ts | 35 ++++++++++++++++++++++++++--------- 5 files changed, 87 insertions(+), 22 deletions(-) diff --git a/deno/idb/mod.ts b/deno/idb/mod.ts index 3409814..6176a86 100644 --- a/deno/idb/mod.ts +++ b/deno/idb/mod.ts @@ -24,13 +24,14 @@ export interface IDBFactory { deleteDatabase(name: string): IDBOpenDBRequest; } +const kv = await Deno.openKv(); + export const indexedDB: IDBFactory = { open( name: string, version: number = 1, ): IDBOpenDBRequest { return new _IDBOpenDBRequest(async function () { - using kv = await Deno.openKv(); // Check if the database already exists and is up to date. const existed = await kv.get($.database(name)); if (existed.value?.version && existed.value.version >= version) { @@ -68,14 +69,12 @@ export const indexedDB: IDBFactory = { }, async databases(): Promise { - using kv = await Deno.openKv(); const iter = kv.list({ prefix: $.database.prefix }); return (await Array.fromAsync(iter)).map((it) => it.value); }, deleteDatabase(name: string): IDBOpenDBRequest { return new _IDBOpenDBRequest(async () => { - using kv = await Deno.openKv(); await kv.delete($.database(name)); return undefined; }) as IDBOpenDBRequest; diff --git a/deno/idb/requests.ts b/deno/idb/requests.ts index 619dc37..7d2f9c7 100644 --- a/deno/idb/requests.ts +++ b/deno/idb/requests.ts @@ -8,7 +8,7 @@ import { IDBObjectStore } from "./stores.ts"; import { AnyIDBTransaction, IDBTransaction } from "./transactions.ts"; export interface IDBRequest< - Result, + Result = unknown, Transaction extends AnyIDBTransaction | null = AnyIDBTransaction | null, > extends EventTarget { readonly error: DOMException | null; @@ -21,7 +21,7 @@ export interface IDBRequest< onsuccess: EventHandler | null; } -export class _IDBRequest extends EventTarget +export class _IDBRequest extends EventTarget implements IDBRequest { constructor( readonly source: IDBRequestSource | null, @@ -65,7 +65,7 @@ export class _IDBRequest extends EventTarget onsuccess: EventHandler | null = null; } -type IDBRequestSource = IDBObjectStore | IDBIndex | IDBCursor; +export type IDBRequestSource = IDBObjectStore | IDBIndex | IDBCursor; export interface IDBOpenDBRequest< Result extends IDBDatabase | undefined = IDBDatabase, diff --git a/deno/idb/stores.ts b/deno/idb/stores.ts index b89b07a..19b2032 100644 --- a/deno/idb/stores.ts +++ b/deno/idb/stores.ts @@ -1,14 +1,11 @@ import { match, placeholder as _, RegularPlaceholder } from "@core/match"; import { associateWith } from "@std/collections/associate-with"; +import { _IDBDatabase } from "./databases.ts"; import { _IDBIndex, IDBIndex } from "./indexes.ts"; import { IDBValidKey, isIDBKey, KvKeyFactoryRecord as $ } from "./keys.ts"; import { _IDBRequest, IDBRequest } from "./requests.ts"; -import { - _IDBTransaction, - AnyIDBTransaction, - IDBTransaction, -} from "./transactions.ts"; +import { _IDBTransaction, IDBTransaction } from "./transactions.ts"; export interface IDBObjectStore< M extends IDBTransactionMode = IDBTransactionMode, @@ -28,6 +25,8 @@ export interface IDBObjectStore< keyPath: string | string[], options?: IDBIndexParameters, ) => IDBIndex; + + get(key: IDBValidKey): IDBRequest; } /** @@ -47,6 +46,7 @@ export function _IDBObjectStore( transaction, autoIncrement, + // @ts-ignore: TS doesn't understand the conditional type add: transaction.mode === "readonly" ? undefined : ( value: unknown, key?: IDBValidKey, @@ -59,9 +59,8 @@ export function _IDBObjectStore( } const result = ensureKey(options, value, key); const parts = Array.isArray(result) ? result : [result]; - return new _IDBRequest( + return transaction._createRequest( store, - transaction as AnyIDBTransaction, () => { (transaction as _IDBTransaction)._atomic.set( $.value(transaction.db.name, name, ...parts), @@ -72,6 +71,7 @@ export function _IDBObjectStore( ); }, + // @ts-ignore: TS doesn't understand the conditional type createIndex: transaction.mode !== "versionchange" ? undefined : ( indexName: string, keyPath: string | string[], @@ -85,6 +85,21 @@ export function _IDBObjectStore( } return new _IDBIndex(indexName, keyPath, options); }, + + get(key: IDBValidKey): IDBRequest { + const keyArray = Array.isArray(key) ? key : [key]; + const request = transaction._createRequest( + store, + async () => { + const kv = (this.transaction.db as _IDBDatabase)._kv; + const result = await kv.get( + $.value(transaction.db.name, name, ...keyArray), + ); + return result.versionstamp ? result.value : undefined; + }, + ); + return request; + }, }; return store; } diff --git a/deno/idb/transactions.ts b/deno/idb/transactions.ts index 81b6f67..c4399e0 100644 --- a/deno/idb/transactions.ts +++ b/deno/idb/transactions.ts @@ -2,6 +2,7 @@ import { DOMStringList } from "@lophus/lib/legacy"; import { EventHandler } from "./events.ts"; import { _IDBDatabase, IDBDatabase } from "./databases.ts"; import { _IDBObjectStore, IDBObjectStore } from "./stores.ts"; +import { _IDBRequest, IDBRequestSource } from "./requests.ts"; export interface IDBTransaction< Mode extends IDBTransactionMode = IDBTransactionMode, @@ -62,4 +63,37 @@ export class _IDBTransaction } return _IDBObjectStore(this, name, options); } + + /** + * An internal method to add a request to the transaction. + * @internal + */ + _createRequest( + source: IDBRequestSource | null, + operation: () => Promise, + ): _IDBRequest { + if (this._completed) { + throw new DOMException( + "The transaction has already completed.", + "TransactionInactiveError", + ); + } + const request = new _IDBRequest( + source, + this as AnyIDBTransaction, + operation, + ); + this._requests.add(request); + request.addEventListener("success", async () => { + this._requests.delete(request); + if (this._requests.size === 0) { + await this._atomic.commit(); + this._completed = true; + this.dispatchEvent(new Event("complete")); + } + }); + return request; + } + readonly _requests: Set<_IDBRequest> = new Set(); + _completed: boolean = false; } diff --git a/deno/idb_test.ts b/deno/idb_test.ts index a7e727f..7f13278 100644 --- a/deno/idb_test.ts +++ b/deno/idb_test.ts @@ -13,8 +13,8 @@ import { it, } from "@std/testing/bdd"; import type { - IDBFactory, IDBDatabase, + IDBFactory, IDBIndex, IDBObjectStore, IDBOpenDBRequest, @@ -173,11 +173,13 @@ describe('IDBObjectStore<"readonly">', () => { beforeAll(async () => { const request = self.indexedDB.open(name); - await new Promise((resolve) => { - request.onupgradeneeded = (event) => - resolve( - event.target.result.createObjectStore("events", { keyPath: "id" }), - ); + await new Promise((resolve) => { + request.onupgradeneeded = (event) => { + const db = event.target.result; + const store = db.createObjectStore("events", { keyPath: "id" }); + store.add({ id: 1, pubkey: "pubkey" }); + resolve(); + }; }); await ensure(request, "success"); store = request.result.transaction("events", "readonly").objectStore( @@ -213,6 +215,14 @@ describe('IDBObjectStore<"readonly">', () => { ); }); }); + + describe("get", () => { + it("should get an object", async () => { + const request = store.get(1); + await ensure(store.transaction, "complete"); + assertEquals(request.result, { id: 1, pubkey: "pubkey" }); + }); + }); }); describe('IDBObjectStore<"readwrite">', () => { @@ -228,9 +238,9 @@ describe('IDBObjectStore<"readwrite">', () => { ); }); await ensure(request, "success"); - store = request.result.transaction("events", "readwrite").objectStore( - "events", - ); + store = request.result + .transaction("events", "readwrite") + .objectStore("events"); }); afterAll(async () => { @@ -265,6 +275,13 @@ describe('IDBObjectStore<"readwrite">', () => { ); }); }); + + describe("get", () => { + it("should throw a TransactionInactiveError once the transaction is completed", async () => { + await ensure(store.transaction, "complete"); + assertThrows(() => store.get(1)); + }); + }); }); describe('IDBObjectStore<"versionchange">', () => {