Last active
September 18, 2022 20:09
-
-
Save danielberndt/e2aa010c937849366df378df21c685ee to your computer and use it in GitHub Desktop.
React Suspense Abstraction for PouchDb (or other async ressources)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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