web-store.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import { invoke } from "@tauri-apps/api/core"
  2. export const APP_STATE_ATOMIC_WRITE_COMMAND = "write_app_state_atomic"
  3. export const APP_STATE_PERSIST_DEBOUNCE_MS = 100
  4. export interface AtomicPersistStore {
  5. get: <T>(key: string) => Promise<T | undefined>
  6. set: (key: string, value: unknown) => Promise<void>
  7. delete: (key: string) => Promise<boolean>
  8. entries: <T>() => Promise<Array<[string, T]>>
  9. }
  10. export interface AtomicAppStateStore {
  11. get: <T>(key: string) => Promise<T | undefined>
  12. set: (key: string, value: unknown) => Promise<void>
  13. delete: (key: string) => Promise<boolean>
  14. save: () => Promise<void>
  15. }
  16. export function wrapStoreForAtomicPersist(
  17. inner: AtomicPersistStore,
  18. persistEntries: (entries: Record<string, unknown>) => Promise<void>,
  19. debounceMs = APP_STATE_PERSIST_DEBOUNCE_MS,
  20. ): AtomicAppStateStore {
  21. let persistTimer: ReturnType<typeof setTimeout> | null = null
  22. let persistChain = Promise.resolve()
  23. const flushPersist = () => {
  24. if (persistTimer) {
  25. clearTimeout(persistTimer)
  26. persistTimer = null
  27. }
  28. persistChain = persistChain
  29. .catch(() => undefined)
  30. .then(async () => {
  31. const pairs = await inner.entries()
  32. await persistEntries(Object.fromEntries(pairs))
  33. })
  34. return persistChain
  35. }
  36. const schedulePersist = () => {
  37. if (persistTimer) clearTimeout(persistTimer)
  38. persistTimer = setTimeout(() => {
  39. persistTimer = null
  40. void flushPersist()
  41. }, debounceMs)
  42. }
  43. return {
  44. get: (key) => inner.get(key),
  45. set: async (key, value) => {
  46. await inner.set(key, value)
  47. schedulePersist()
  48. },
  49. delete: async (key) => {
  50. const deleted = await inner.delete(key)
  51. schedulePersist()
  52. return deleted
  53. },
  54. save: () => flushPersist(),
  55. }
  56. }
  57. let storePromise: Promise<AtomicAppStateStore> | null = null
  58. export async function getStore(): Promise<AtomicAppStateStore> {
  59. if (!storePromise) {
  60. storePromise = (async () => {
  61. const { load } = await import("@tauri-apps/plugin-store")
  62. const inner = await load("app-state.json", { autoSave: false })
  63. return wrapStoreForAtomicPersist(inner, async (entries) => {
  64. await invoke(APP_STATE_ATOMIC_WRITE_COMMAND, { entries })
  65. })
  66. })()
  67. }
  68. return storePromise
  69. }