Skip to content
File

Blob: src/client/lib/storage.ts

typescript97 lines
1import { createJSONStorage, type StateStorage } from "zustand/middleware";
2 
3interface VersionedEnvelope<T> {
4 version: number;
5 value: T;
6}
7 
8function getLocalStorage(): Storage | null {
9 try {
10 return typeof localStorage === "undefined" ? null : localStorage;
11 } catch {
12 return null;
13 }
14}
15 
16function isVersionedEnvelope<T>(value: unknown, version: number): value is VersionedEnvelope<T> {
17 return !!value && typeof value === "object" && "version" in value && "value" in value && value.version === version;
18}
19 
20export function readStorageString(key: string): string | null {
21 const storage = getLocalStorage();
22 if (!storage) return null;
23 try {
24 return storage.getItem(key);
25 } catch {
26 return null;
27 }
28}
29 
30export function writeStorageString(key: string, value: string): boolean {
31 const storage = getLocalStorage();
32 if (!storage) return false;
33 try {
34 storage.setItem(key, value);
35 return true;
36 } catch {
37 return false;
38 }
39}
40 
41export function removeStorageItem(key: string): void {
42 const storage = getLocalStorage();
43 if (!storage) return;
44 try {
45 storage.removeItem(key);
46 } catch {
47 // Ignore storage failures.
48 }
49}
50 
51export function readStorageJson<T>(key: string, parseValue: (value: unknown) => T | null): T | null {
52 const raw = readStorageString(key);
53 if (!raw) return null;
54 try {
55 return parseValue(JSON.parse(raw) as unknown);
56 } catch {
57 return null;
58 }
59}
60 
61export function writeStorageJson<T>(key: string, value: T): boolean {
62 try {
63 return writeStorageString(key, JSON.stringify(value));
64 } catch {
65 return false;
66 }
67}
68 
69export function readVersionedStorageJson<T>(
70 key: string,
71 version: number,
72 parseValue: (value: unknown) => T | null,
73): T | null {
74 return readStorageJson(key, (value) => {
75 if (isVersionedEnvelope<T>(value, version)) {
76 return parseValue(value.value);
77 }
78 return parseValue(value);
79 });
80}
81 
82export function writeVersionedStorageJson<T>(key: string, version: number, value: T): boolean {
83 return writeStorageJson<VersionedEnvelope<T>>(key, { version, value });
84}
85 
86export const safeStateStorage: StateStorage = {
87 getItem: (name) => readStorageString(name),
88 setItem: (name, value) => {
89 writeStorageString(name, value);
90 },
91 removeItem: (name) => {
92 removeStorageItem(name);
93 },
94};
95 
96export const safeJsonStorage = createJSONStorage(() => safeStateStorage);