Skip to content

Instantly share code, notes, and snippets.

@NuroDev
Last active September 29, 2023 14:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NuroDev/15e1f8f9a5f8050c8717e872778ea79a to your computer and use it in GitHub Desktop.
Save NuroDev/15e1f8f9a5f8050c8717e872778ea79a to your computer and use it in GitHub Desktop.
πŸ’™ Create typed KV ─ A wrapper for a Deno KV instance to provide a more ORM style API
type Prettify<T> =
& { [K in keyof T]: T[K] }
& {};
type KvConsistencyOptions = Parameters<Deno.Kv['get']>[1];
interface MapTypedKv<K extends string, V extends unknown> {
delete(id: K): Promise<void>;
get(id: K, options?: KvConsistencyOptions): Promise<Deno.KvEntryMaybe<V>>;
getMany<T extends readonly unknown[]>(
keys: readonly [...{ [L in keyof T]: K }],
options?: KvConsistencyOptions,
): Promise<{ [L in keyof T]: Deno.KvEntryMaybe<V> }>;
list(options?: Deno.KvListOptions): Promise<Deno.KvListIterator<V>>;
set(id: K, value: Partial<V>): Promise<void>;
}
/**
* Creates a typed wrapper around a Deno.Kv instance.
*
* @example
* ```ts
* interface Post {
* title: string;
* slug: string;
* content: string;
* }
*
* interface User {
* name: string;
* email: string;
* }
*
* type Database = {
* posts: [`pst_${number}`, Post];
* users: [`usr_${string}`, User];
* };
*
* const db = createdTypedKv<Database>(kv);
*
* const user = await db.users.get('usr_abc123');
* // ^? Deno.KvEntryMaybe<User>
*
* const posts = await db.posts.getMany(['pst_1', 'pst_2', 'pst_3']);
* // ^? [Deno.KvEntryMaybe<Post>, Deno.KvEntryMaybe<Post>, Deno.KvEntryMaybe<Post>]
* ```
*
* @param kv - The Deno.Kv instance to wrap.
*
* @returns - A typed wrapper around the Deno.Kv instance.
*/
export function createdTypedKv<
TDatabase extends Record<string, [key: string, value: unknown]>,
>(kv: Deno.Kv) {
return new Proxy(kv, {
get: (_, prop: string) => ({
delete: (id: string) => kv.delete([prop, id]),
get: (id: string, options?: KvConsistencyOptions) =>
kv.get([prop, id], options),
getMany: <T extends readonly unknown[]>(
keys: readonly [...{ [K in keyof T]: string }],
options?: KvConsistencyOptions,
) => kv.getMany(keys.map((key) => [prop, key] as const), options),
list: (options?: Deno.KvListOptions) =>
kv.list({ prefix: [prop] }, options),
set: (id: string, value: Partial<unknown>) =>
kv.set([prop, id], value),
}),
}) as {
[K in keyof TDatabase]: Prettify<
MapTypedKv<TDatabase[K][0], TDatabase[K][1]>
>;
};
}
interface User {
name: string;
email: string;
}
interface Post {
title: string;
slug: string;
content: string;
}
type Database = {
posts: [`pst_${number}`, Post];
users: [`usr_${string}`, User];
};
const db = createdTypedKv<Database>(kv);
await db.users.set('usr_abc123', {
name: 'Tim Apple',
email: 'tim@apple.com',
});
const user = await db.users.get('usr_abc123');
// ^? Deno.KvEntryMaybe<User>
await db.users.delete('usr_abc123');
const posts = await db.posts.getMany(['pst_1', 'pst_2']);
// ^? [Deno.KvEntryMaybe<Post>, Deno.KvEntryMaybe<Post>]
const list = await db.posts.list();
// ^? Deno.KvListIterator<Post>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment