diff --git a/app/common/nostr.ts b/app/common/nostr.ts index 24b7469..e655077 100644 --- a/app/common/nostr.ts +++ b/app/common/nostr.ts @@ -4,7 +4,7 @@ import { EventFilter, EventKind, NostrEvent } from "@lophus/nips"; export class Relay extends WithPool(_Relay) implements RelayLike {} -interface EventSource extends Pick { +export interface EventSource extends Pick { list( filter: EventFilter, ): AsyncIterable>; @@ -13,7 +13,7 @@ interface EventSource extends Pick { ): Promise | undefined>; } -interface EventStore extends EventSource { +export interface EventStore extends EventSource { put(event: NostrEvent): Promise; } @@ -26,32 +26,14 @@ const knowns = new RelayGroup([ */ export const nostr: EventSource = { config: { name: "nostr" }, - async get(filter) { - return await cache.get(filter) ?? _get(knowns, filter); + get(filter) { + return _get(knowns, filter); }, list(filter) { return knowns.subscribe(filter); }, }; -export const cache: EventStore = { - config: { name: "cache" }, - async get(filter: EventFilter) { - const kv = await Deno.openKv(); - if (filter.ids?.length) { - return Promise.any( - filter.ids.map((id) => - new Promise>((resolve, reject) => - kv.get>(["events", id]).then(({ value }) => - value ? resolve(value) : reject() - ) - ) - ), - ).catch(() => undefined); - } - }, -}; - async function _get( source: RelayLike, filter: EventFilter, diff --git a/deno.lock b/deno.lock index bab1595..8361445 100644 --- a/deno.lock +++ b/deno.lock @@ -6,20 +6,16 @@ "jsr:@std/assert@^0.219.1": "jsr:@std/assert@0.219.1", "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", "jsr:@std/bytes@^0.219.1": "jsr:@std/bytes@0.219.1", - "jsr:@std/bytes@^0.221.0": "jsr:@std/bytes@0.221.0", "jsr:@std/collections@^0.221.0": "jsr:@std/collections@0.221.0", - "jsr:@std/fmt@^0.219.1": "jsr:@std/fmt@0.219.1", "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", "jsr:@std/io@^0.219.1": "jsr:@std/io@0.219.1", - "jsr:@std/io@^0.221.0": "jsr:@std/io@0.221.0", "jsr:@std/streams@^0.219.1": "jsr:@std/streams@0.219.1", - "jsr:@std/streams@^0.221.0": "jsr:@std/streams@0.221.0", "jsr:@std/testing@^0.221.0": "jsr:@std/testing@0.221.0", "npm:@noble/curves@^1.3.0": "npm:@noble/curves@1.4.0", "npm:@noble/hashes@^1.3.3": "npm:@noble/hashes@1.4.0", "npm:esbuild@^0.20.2": "npm:esbuild@0.20.2", "npm:mini-van-plate@^0.5.6": "npm:mini-van-plate@0.5.6", - "npm:nostr-tools@^2.3.2": "npm:nostr-tools@2.3.2", + "npm:nostr-tools@^2.3.2": "npm:nostr-tools@2.5.0", "npm:sharp@^0.33.3": "npm:sharp@0.33.3", "npm:ts-toolbelt@9.6.0": "npm:ts-toolbelt@9.6.0" }, @@ -31,10 +27,7 @@ ] }, "@std/assert@0.219.1": { - "integrity": "e76c2a1799a78f0f4db7de04bdc9b908a7a4b821bb65eda0285885297d4fb8af", - "dependencies": [ - "jsr:@std/fmt@^0.219.1" - ] + "integrity": "e76c2a1799a78f0f4db7de04bdc9b908a7a4b821bb65eda0285885297d4fb8af" }, "@std/assert@0.221.0": { "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a", @@ -45,24 +38,15 @@ "@std/bytes@0.219.1": { "integrity": "693e5f3f7796b33a448fb16c448b75d7d6e62914b55e2f1f715f9a04f77853b4" }, - "@std/bytes@0.221.0": { - "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" - }, "@std/collections@0.221.0": { "integrity": "789a365bb79b06c4da79d2b93fe32ddfe03b93ab083e72c1de96303d12f84be5" }, - "@std/fmt@0.219.1": { - "integrity": "2432152e927df249a207177aa048a6d9465956ea0047653ee6abd4f514db504f" - }, "@std/fmt@0.221.0": { "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" }, "@std/io@0.219.1": { "integrity": "4f5073dd150fb7deff5c57449f4304edaf89a1137e0003602a22c133eb7cf93a" }, - "@std/io@0.221.0": { - "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da" - }, "@std/streams@0.219.1": { "integrity": "6f5dac5773a4fafdbe7ee612d0a0d5a2cbe465b9c9e2c85d371877dc8a52d1d3", "dependencies": [ @@ -71,14 +55,6 @@ "jsr:@std/io@^0.219.1" ] }, - "@std/streams@0.221.0": { - "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", - "dependencies": [ - "jsr:@std/assert@^0.221.0", - "jsr:@std/bytes@^0.221.0", - "jsr:@std/io@^0.221.0" - ] - }, "@std/testing@0.221.0": { "integrity": "d9b5ed7d5dc4e8d193734a594e7f80c04a5a6f34c2493e69c6713ae7fe6f880e" } @@ -399,8 +375,8 @@ "integrity": "sha512-gAJPXBihI8EFTutUc7oj9tmnOJKgLme5SQNAwj3gN0VU95Nzptiwo1oZy5SJRBwl5ZH7KmjQ6tsqxGHfSgdcoQ==", "dependencies": {} }, - "nostr-tools@2.3.2": { - "integrity": "sha512-8ceZ2ItkAGjR5b9+QOkkV9KWBOK0WPlpFrPPXmbWnNMcnlj9zB7rjdYPK2sV/OK4Ty9J3xL6+bvYKY77gup5EQ==", + "nostr-tools@2.5.0": { + "integrity": "sha512-G02O3JYNCfhx9NDjd3NOCw/5ck8PX5hiOIhHKpsXyu49ZtZbxGH3OLP9tf0fpUZ+EVWdjIYFR689sV0i7+TOng==", "dependencies": { "@noble/ciphers": "@noble/ciphers@0.5.2", "@noble/curves": "@noble/curves@1.2.0", diff --git a/deno/idb/mod.ts b/deno/idb/mod.ts index 6176a86..341345b 100644 --- a/deno/idb/mod.ts +++ b/deno/idb/mod.ts @@ -24,65 +24,61 @@ 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 () { - // 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) { - const stores = new Map(); - const iter = kv.list({ - prefix: $.store.prefix, - }); - for await (const { key, value } of iter) { - const name = key.toReversed()[0] as string; - stores.set(name, value); +export function createIDBFactory( + kv: Deno.Kv, +): IDBFactory { + return { + open( + name: string, + version: number = 1, + ): IDBOpenDBRequest { + return new _IDBOpenDBRequest(async function () { + // 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) { + const stores = new Map(); + const iter = kv.list({ + prefix: $.store.prefix, + }); + for await (const { key, value } of iter) { + const name = key.toReversed()[0] as string; + stores.set(name, value); + } + return new _IDBDatabase(name, version, kv, stores); } - return new _IDBDatabase(name, version, kv, stores); - } - // Create the new database. - await kv.set($.database(name), { name, version }); - const db = new _IDBDatabase(name, version, kv, new Map()); - const transaction = new _IDBTransaction(db, "versionchange"); - this.transaction = transaction as IDBTransaction<"versionchange">; - db._transaction = transaction; - this.result = db; - this.dispatchEvent( - new _IDBVersionChangeEvent(existed.value?.version ?? 0, version), - ); - await new Promise((resolve) => { - transaction.addEventListener("complete", resolve); - }); - db._transaction = null; - return db; - }) as IDBOpenDBRequest; - }, - - cmp(_a: unknown, _b: unknown): -1 | 0 | 1 { - console.warn("indexedDB.cmp is not implemented"); - return 0; - }, + // Create the new database. + await kv.set($.database(name), { name, version }); + const db = new _IDBDatabase(name, version, kv, new Map()); + const transaction = new _IDBTransaction(db, "versionchange"); + this.transaction = transaction as IDBTransaction<"versionchange">; + db._transaction = transaction; + this.result = db; + this.dispatchEvent( + new _IDBVersionChangeEvent(existed.value?.version ?? 0, version), + ); + await new Promise((resolve) => { + transaction.addEventListener("complete", resolve); + }); + db._transaction = null; + return db; + }) as IDBOpenDBRequest; + }, - async databases(): Promise { - const iter = kv.list({ prefix: $.database.prefix }); - return (await Array.fromAsync(iter)).map((it) => it.value); - }, + cmp(_a: unknown, _b: unknown): -1 | 0 | 1 { + console.warn("indexedDB.cmp is not implemented"); + return 0; + }, - deleteDatabase(name: string): IDBOpenDBRequest { - return new _IDBOpenDBRequest(async () => { - await kv.delete($.database(name)); - return undefined; - }) as IDBOpenDBRequest; - }, -}; + async databases(): Promise { + const iter = kv.list({ prefix: $.database.prefix }); + return (await Array.fromAsync(iter)).map((it) => it.value); + }, -if (self.indexedDB === undefined && self.Deno.Kv) { - console.warn("Using an experimental IndexedDB polyfill with Deno KV"); - // @ts-ignore We type IDBFactory better - self.indexedDB = indexedDB; + deleteDatabase(name: string): IDBOpenDBRequest { + return new _IDBOpenDBRequest(async () => { + await kv.delete($.database(name)); + return undefined; + }) as IDBOpenDBRequest; + }, + }; } diff --git a/deno/idb_test.ts b/deno/idb_test.ts index 7f13278..0314b49 100644 --- a/deno/idb_test.ts +++ b/deno/idb_test.ts @@ -14,25 +14,16 @@ import { } from "@std/testing/bdd"; import type { IDBDatabase, - IDBFactory, IDBIndex, IDBObjectStore, IDBOpenDBRequest, IDBTransaction, } from "./idb/mod.ts"; -import "./idb/mod.ts"; +import { createIDBFactory } from "./idb/mod.ts"; -declare global { - interface Window { - indexedDB: IDBFactory; - } -} - -function ensure(target: EventTarget, event: string): Promise { - return new Promise((resolve) => { - target.addEventListener(event, resolve, { once: true }); - }); -} +const indexedDB = createIDBFactory( + await Deno.openKv(), +); describe("IDBOpenRequest", () => { describe("onupgradeneeded", () => { @@ -41,11 +32,11 @@ describe("IDBOpenRequest", () => { beforeEach(() => { name = crypto.randomUUID(); - request = self.indexedDB.open(name); + request = indexedDB.open(name); }); afterEach(() => { - self.indexedDB.deleteDatabase(name); + indexedDB.deleteDatabase(name); }); it("should be called when the database is created", async () => { @@ -82,7 +73,7 @@ describe("IDBOpenRequest", () => { }); it("should be called when the database is opened", async () => { - const request = self.indexedDB.open(name); + const request = indexedDB.open(name); await ensure(request, "success"); assertEquals(request.result.name, name); }); @@ -94,7 +85,7 @@ describe("IDBDatabase", () => { let db: IDBDatabase; beforeAll(async () => { - const request = self.indexedDB.open(name); + const request = indexedDB.open(name); await new Promise((resolve) => { request.onupgradeneeded = (event) => resolve( @@ -138,7 +129,7 @@ describe("IDBObjectStore", () => { let store: IDBObjectStore; beforeAll(async () => { - const request = self.indexedDB.open(name); + const request = indexedDB.open(name); await new Promise((resolve) => { request.onupgradeneeded = (event) => resolve( @@ -150,7 +141,7 @@ describe("IDBObjectStore", () => { }); afterAll(async () => { - const request = self.indexedDB.deleteDatabase(name); + const request = indexedDB.deleteDatabase(name); await ensure(request, "success"); }); @@ -172,7 +163,7 @@ describe('IDBObjectStore<"readonly">', () => { let store: IDBObjectStore<"readonly">; beforeAll(async () => { - const request = self.indexedDB.open(name); + const request = indexedDB.open(name); await new Promise((resolve) => { request.onupgradeneeded = (event) => { const db = event.target.result; @@ -188,7 +179,7 @@ describe('IDBObjectStore<"readonly">', () => { }); afterAll(async () => { - const request = self.indexedDB.deleteDatabase(name); + const request = indexedDB.deleteDatabase(name); await ensure(request, "success"); }); @@ -230,7 +221,7 @@ describe('IDBObjectStore<"readwrite">', () => { let store: IDBObjectStore<"readwrite">; beforeAll(async () => { - const request = self.indexedDB.open(name); + const request = indexedDB.open(name); await new Promise((resolve) => { request.onupgradeneeded = (event) => resolve( @@ -244,7 +235,7 @@ describe('IDBObjectStore<"readwrite">', () => { }); afterAll(async () => { - const request = self.indexedDB.deleteDatabase(name); + const request = indexedDB.deleteDatabase(name); await ensure(request, "success"); }); @@ -289,7 +280,7 @@ describe('IDBObjectStore<"versionchange">', () => { let store: IDBObjectStore<"versionchange">; beforeAll(async () => { - const request = self.indexedDB.open(name); + const request = indexedDB.open(name); await new Promise((resolve) => { request.onupgradeneeded = (event) => { const db = event.target.result; @@ -301,7 +292,7 @@ describe('IDBObjectStore<"versionchange">', () => { }); afterAll(async () => { - const request = self.indexedDB.deleteDatabase(name); + const request = indexedDB.deleteDatabase(name); await ensure(request, "success"); }); @@ -337,7 +328,7 @@ describe("IDBTransaction", () => { let transaction: IDBTransaction; beforeAll(async () => { - const request = self.indexedDB.open(name); + const request = indexedDB.open(name); await new Promise((resolve) => { request.onupgradeneeded = (event) => resolve( @@ -349,7 +340,7 @@ describe("IDBTransaction", () => { }); afterAll(async () => { - const request = self.indexedDB.deleteDatabase(name); + const request = indexedDB.deleteDatabase(name); await ensure(request, "success"); }); @@ -391,7 +382,7 @@ describe("IDBIndex", () => { let index: IDBIndex; beforeAll(async () => { - const request = self.indexedDB.open(name); + const request = indexedDB.open(name); await new Promise((resolve) => { request.onupgradeneeded = (event) => { const db = event.target.result; @@ -420,3 +411,12 @@ describe("IDBIndex", () => { assertEquals(index.unique, false); }); }); + +/** + * A convenience function to wait for an event to be dispatched. + */ +function ensure(target: EventTarget, event: string): Promise { + return new Promise((resolve) => { + target.addEventListener(event, resolve, { once: true }); + }); +} diff --git a/std/stores.ts b/std/stores.ts index d862f72..83469e5 100644 --- a/std/stores.ts +++ b/std/stores.ts @@ -3,22 +3,33 @@ * @module */ -import "@lophus/deno/idb"; +import type { IDBFactory } from "@lophus/deno/idb"; import type { EventKind, NostrEvent } from "@lophus/core/protocol"; -export class EventStore { - #request: IDBOpenDBRequest; +let indexedDB: IDBFactory; + +if (self.indexedDB === undefined && Deno.Kv) { + const { createIDBFactory } = await import("@lophus/deno/idb"); + indexedDB = createIDBFactory( + await Deno.openKv(), + ); +} else { + // Use improved types from @lophus/deno/idb + indexedDB = self.indexedDB as unknown as IDBFactory; +} +export class EventStore { constructor(name: string, version?: number) { - this.#request = self.indexedDB.open(name, version); + const request = indexedDB.open(name, version); - this.#request.onupgradeneeded = () => { - const db = this.#request.result; + request.onupgradeneeded = (event) => { + const db = event.target.result; const events = db.createObjectStore("events", { keyPath: "id" }); events.createIndex("kind", "kind"); }; } - put(event: NostrEvent): Promise { + put(_event: NostrEvent) { + // TODO: Implement } } diff --git a/std/stores_test.ts b/std/stores_test.ts index ea7f91b..a10d65d 100644 --- a/std/stores_test.ts +++ b/std/stores_test.ts @@ -1,4 +1,4 @@ -import { assertExists, assertInstanceOf } from "@std/assert"; +import { assertInstanceOf } from "@std/assert"; import { describe, it } from "@std/testing/bdd"; import { EventStore } from "./stores.ts"; @@ -12,10 +12,6 @@ describe("EventStore", () => { describe("put", () => { it("should put an event into the store", async () => { - const store = new EventStore("test"); - await store.put({ id: "1", kind: 0, created_at: 0 }); - const event = await store.get({ ids: ["1"] }); - assertExists(event); }); }); });