Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(deno/idb): implement IDBObjectStore.get #41

Merged
merged 3 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 4 additions & 22 deletions app/common/nostr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EventFilter, EventKind, NostrEvent } from "@lophus/nips";

export class Relay extends WithPool(_Relay) implements RelayLike {}

interface EventSource extends Pick<RelayLike, "config"> {
export interface EventSource extends Pick<RelayLike, "config"> {
list<K extends EventKind>(
filter: EventFilter<K>,
): AsyncIterable<NostrEvent<K>>;
Expand All @@ -13,7 +13,7 @@ interface EventSource extends Pick<RelayLike, "config"> {
): Promise<NostrEvent<K> | undefined>;
}

interface EventStore extends EventSource {
export interface EventStore extends EventSource {
put<K extends EventKind>(event: NostrEvent<K>): Promise<void>;
}

Expand All @@ -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<K extends EventKind>(filter: EventFilter<K>) {
const kv = await Deno.openKv();
if (filter.ids?.length) {
return Promise.any(
filter.ids.map((id) =>
new Promise<NostrEvent<K>>((resolve, reject) =>
kv.get<NostrEvent<K>>(["events", id]).then(({ value }) =>
value ? resolve(value) : reject()
)
)
),
).catch(() => undefined);
}
},
};

async function _get<K extends EventKind>(
source: RelayLike,
filter: EventFilter<K>,
Expand Down
32 changes: 4 additions & 28 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions deno/idb/databases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ type NormalTransactionMode = "readonly" | "readwrite";

export class _IDBDatabase extends EventTarget
implements IDBDatabaseInVersionChange {
readonly _key: string[];
_transaction: _IDBTransaction<"versionchange"> | null = null;

constructor(
Expand All @@ -57,7 +56,6 @@ export class _IDBDatabase extends EventTarget
this.onversionchange?.(event);
}
});
this._key = ["databases", name];
}

createObjectStore(
Expand Down
123 changes: 54 additions & 69 deletions deno/idb/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,83 +17,68 @@
export type { IDBObjectStore } from "./stores.ts";
export type { IDBTransaction } from "./transactions.ts";

interface IDBFactory {
export interface IDBFactory {
open(name: string, version?: number): IDBOpenDBRequest<IDBDatabase, null>;
cmp(a: unknown, b: unknown): -1 | 0 | 1;
databases(): Promise<IDBDatabaseInfo[]>;
deleteDatabase(name: string): IDBOpenDBRequest<undefined, null>;
}

const indexedDB: IDBFactory = {
open(
name: string,
version: number = 1,
): IDBOpenDBRequest<IDBDatabase, null> {
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<IDBDatabaseInfo>($.database(name));
if (existed.value?.version && existed.value.version >= version) {
const stores = new Map<string, IDBObjectStoreParameters>();
const iter = kv.list<IDBObjectStoreParameters>({
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<IDBDatabase, null> {
return new _IDBOpenDBRequest(async function () {
// Check if the database already exists and is up to date.
const existed = await kv.get<IDBDatabaseInfo>($.database(name));
if (existed.value?.version && existed.value.version >= version) {
const stores = new Map<string, IDBObjectStoreParameters>();
const iter = kv.list<IDBObjectStoreParameters>({
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);

Check warning on line 47 in deno/idb/mod.ts

View check run for this annotation

Codecov / codecov/patch

deno/idb/mod.ts#L39-L47

Added lines #L39 - L47 were not covered by tests
}
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<IDBDatabase, null>;
},

cmp(_a: unknown, _b: unknown): -1 | 0 | 1 {
console.warn("indexedDB.cmp is not implemented");
return 0;
},

async databases(): Promise<IDBDatabaseInfo[]> {
using kv = await Deno.openKv();
const iter = kv.list<IDBDatabaseInfo>({ prefix: $.database.prefix });
return (await Array.fromAsync(iter)).map((it) => it.value);
},
// 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(

Check warning on line 56 in deno/idb/mod.ts

View check run for this annotation

Codecov / codecov/patch

deno/idb/mod.ts#L56

Added line #L56 was not covered by tests
new _IDBVersionChangeEvent(existed.value?.version ?? 0, version),
);
await new Promise((resolve) => {
transaction.addEventListener("complete", resolve);
});
db._transaction = null;
return db;
}) as IDBOpenDBRequest<IDBDatabase, null>;
},

deleteDatabase(name: string): IDBOpenDBRequest<undefined, null> {
return new _IDBOpenDBRequest(async () => {
using kv = await Deno.openKv();
await kv.delete($.database(name));
return undefined;
}) as IDBOpenDBRequest<undefined, null>;
},
};
cmp(_a: unknown, _b: unknown): -1 | 0 | 1 {
console.warn("indexedDB.cmp is not implemented");
return 0;
},

Check warning on line 70 in deno/idb/mod.ts

View check run for this annotation

Codecov / codecov/patch

deno/idb/mod.ts#L67-L70

Added lines #L67 - L70 were not covered by tests

// FIXME: Can we avoid writing this twice?
declare global {
interface IDBFactory {
open(name: string, version?: number): IDBOpenDBRequest<IDBDatabase, null>;
cmp(a: unknown, b: unknown): -1 | 0 | 1;
databases(): Promise<IDBDatabaseInfo[]>;
deleteDatabase(name: string): IDBOpenDBRequest<undefined, null>;
}
}
async databases(): Promise<IDBDatabaseInfo[]> {
const iter = kv.list<IDBDatabaseInfo>({ prefix: $.database.prefix });
return (await Array.fromAsync(iter)).map((it) => it.value);
},

Check warning on line 75 in deno/idb/mod.ts

View check run for this annotation

Codecov / codecov/patch

deno/idb/mod.ts#L72-L75

Added lines #L72 - L75 were not covered by tests

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<undefined, null> {
return new _IDBOpenDBRequest(async () => {
await kv.delete($.database(name));
return undefined;
}) as IDBOpenDBRequest<undefined, null>;
},
};
}
13 changes: 3 additions & 10 deletions deno/idb/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,7 +21,7 @@ export interface IDBRequest<
onsuccess: EventHandler | null;
}

export class _IDBRequest<Result> extends EventTarget
export class _IDBRequest<Result = unknown> extends EventTarget
implements IDBRequest<Result> {
constructor(
readonly source: IDBRequestSource | null,
Expand Down Expand Up @@ -65,7 +65,7 @@ export class _IDBRequest<Result> 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,
Expand Down Expand Up @@ -104,10 +104,3 @@ export class _IDBOpenDBRequest<
onblocked: VersionChangeEventHandler | null = null;
onupgradeneeded: VersionChangeEventHandler | null = null;
}

export interface IDBFactory {
open(name: string, version?: number): IDBOpenDBRequest<IDBDatabase, null>;
cmp(a: unknown, b: unknown): -1 | 0 | 1;
databases(): Promise<IDBDatabaseInfo[]>;
deleteDatabase(name: string): IDBOpenDBRequest<undefined, null>;
}
Loading