Skip to content

Instantly share code, notes, and snippets.

@danielberndt
Last active September 18, 2022 20:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielberndt/e2aa010c937849366df378df21c685ee to your computer and use it in GitHub Desktop.
Save danielberndt/e2aa010c937849366df378df21c685ee to your computer and use it in GitHub Desktop.
React Suspense Abstraction for PouchDb (or other async ressources)
import {Nominal} from "../types";
import PouchDB from "pouchdb-browser";
import LRU from "lru-cache";
export type GroupId = Nominal<string, "GroupId">;
export type GroupOverviewEntry = {
name: string;
createdAt: Date;
};
let _db: null | PouchDB.Database<GroupOverviewEntry> = null;
const getDb = () => {
if (!_db) _db = new PouchDB<GroupOverviewEntry>(`GroupOverview`);
return _db;
};
type SuspenseOperations = {clear: () => void};
type MyFn<Ret> = (...args: any) => PromiseLike<Ret>;
type Unwrap<Fn extends MyFn<unknown>> = ReturnType<Fn> extends PromiseLike<infer U>
? (...args: Parameters<Fn>) => U
: never;
const suspensifyFn = <Ret>(fn: MyFn<Ret>): Unwrap<MyFn<Ret> & SuspenseOperations> => {
type Entry =
| {
promise: PromiseLike<Ret>;
result: null;
resultSize: 1;
}
| {
promise: null;
result: Ret;
resultSize: number;
};
const cache = new LRU<string, Entry>({
maxSize: 1024 * 1024,
sizeCalculation: (e) => e.resultSize,
});
const transformedFn = (...args: any[]) => {
const key = args.map((a) => `${a}`).join(";");
const res = cache.get(key);
if (res) {
if (res.promise) throw res.promise;
return res.result;
}
const entry: Entry = {
result: null,
promise: fn(...args).then((retVal) => {
cache.set(key, {
promise: null,
result: retVal,
resultSize: JSON.stringify(retVal).length,
});
return retVal;
}),
resultSize: 1,
};
cache.set(key, entry);
throw entry.promise;
};
transformedFn.clear = () => {
cache.clear();
};
return transformedFn;
};
const suspensify = <T extends {[arg: string]: MyFn<unknown>}>(
obj: T
): {
[K in keyof T]: Unwrap<T[K]> & SuspenseOperations;
} => {
const trans = Object.fromEntries(Object.entries(obj).map(([key, fn]) => [key, suspensifyFn(fn)]));
return trans as {[K in keyof T]: Unwrap<T[K]> & SuspenseOperations};
};
const getters = {
getMyGroups: async () => {
const result = await getDb().allDocs({include_docs: true});
await new Promise((res) => setTimeout(res, 1000));
return result.rows.map((row) => row.doc!);
},
getGroup: async (id: GroupId) => {
return await getDb().get(id);
},
};
export const GroupOverviewRepo = {
...suspensify(getters),
rawGetters: getters,
mutate: {
addGroup: async (group: Omit<GroupOverviewEntry, "createdAt">) => {
const res = await getDb().put({...group, createdAt: new Date()});
console.log({res});
return res.id;
},
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment