state.ts 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import { Log } from "@/util/log"
  2. export namespace State {
  3. interface Entry {
  4. state: any
  5. dispose?: (state: any) => Promise<void>
  6. }
  7. const log = Log.create({ service: "state" })
  8. const recordsByKey = new Map<string, Map<any, Entry>>()
  9. export function create<S>(root: () => string, init: () => S, dispose?: (state: Awaited<S>) => Promise<void>) {
  10. return () => {
  11. const key = root()
  12. let entries = recordsByKey.get(key)
  13. if (!entries) {
  14. entries = new Map<string, Entry>()
  15. recordsByKey.set(key, entries)
  16. }
  17. const exists = entries.get(init)
  18. if (exists) return exists.state as S
  19. const state = init()
  20. entries.set(init, {
  21. state,
  22. dispose,
  23. })
  24. return state
  25. }
  26. }
  27. export async function dispose(key: string) {
  28. const entries = recordsByKey.get(key)
  29. if (!entries) return
  30. log.info("waiting for state disposal to complete", { key })
  31. let disposalFinished = false
  32. setTimeout(() => {
  33. if (!disposalFinished) {
  34. log.warn(
  35. "state disposal is taking an unusually long time - if it does not complete in a reasonable time, please report this as a bug",
  36. { key },
  37. )
  38. }
  39. }, 10000).unref()
  40. const tasks: Promise<void>[] = []
  41. for (const [init, entry] of entries) {
  42. if (!entry.dispose) continue
  43. const label = typeof init === "function" ? init.name : String(init)
  44. const task = Promise.resolve(entry.state)
  45. .then((state) => entry.dispose!(state))
  46. .catch((error) => {
  47. log.error("Error while disposing state:", { error, key, init: label })
  48. })
  49. tasks.push(task)
  50. }
  51. await Promise.all(tasks)
  52. entries.clear()
  53. recordsByKey.delete(key)
  54. disposalFinished = true
  55. log.info("state disposal completed", { key })
  56. }
  57. }