state.ts 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  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 entry of entries.values()) {
  42. if (!entry.dispose) continue
  43. const task = Promise.resolve(entry.state)
  44. .then((state) => entry.dispose!(state))
  45. .catch((error) => {
  46. log.error("Error while disposing state:", { error, key })
  47. })
  48. tasks.push(task)
  49. }
  50. entries.clear()
  51. recordsByKey.delete(key)
  52. await Promise.all(tasks)
  53. disposalFinished = true
  54. log.info("state disposal completed", { key })
  55. }
  56. }