1
0
forked from a/lifeto-shop
lifeto-shop/src/state/storage/index.ts
2025-06-24 00:37:47 -05:00

114 lines
3.0 KiB
TypeScript

import {
AsyncStorage,
AsyncStringStorage,
SyncStorage,
SyncStringStorage,
} from 'jotai/vanilla/utils/atomWithStorage'
import superjson from 'superjson'
const isPromiseLike = (x: unknown): x is PromiseLike<unknown> =>
typeof (x as any)?.then === 'function'
type Unsubscribe = () => void
type Subscribe<Value> = (
key: string,
callback: (value: Value) => void,
initialValue: Value,
) => Unsubscribe | undefined
type StringSubscribe = (
key: string,
callback: (value: string | null) => void,
) => Unsubscribe | undefined
export function createSuperjsonStorage<Value>(): SyncStorage<Value>
export function createSuperjsonStorage<Value>(
getStringStorage: () => AsyncStringStorage | SyncStringStorage | undefined = () => {
try {
return window.localStorage
} catch (_e) {
if (import.meta.env?.MODE !== 'production') {
if (typeof window !== 'undefined') {
}
}
return undefined
}
},
): AsyncStorage<Value> | SyncStorage<Value> {
let lastStr: string | undefined
let lastValue: Value
const storage: AsyncStorage<Value> | SyncStorage<Value> = {
getItem: (key, initialValue) => {
const parse = (str: string | null) => {
str = str || ''
if (lastStr !== str) {
try {
lastValue = superjson.parse(str)
} catch {
return initialValue
}
lastStr = str
}
return lastValue
}
const str = getStringStorage()?.getItem(key) ?? null
if (isPromiseLike(str)) {
return str.then(parse) as never
}
return parse(str) as never
},
setItem: (key, newValue) => getStringStorage()?.setItem(key, superjson.stringify(newValue)),
removeItem: key => getStringStorage()?.removeItem(key),
}
const createHandleSubscribe =
(subscriber: StringSubscribe): Subscribe<Value> =>
(key, callback, initialValue) =>
subscriber(key, v => {
let newValue: Value
try {
newValue = superjson.parse(v || '')
} catch {
newValue = initialValue
}
callback(newValue)
})
let subscriber: StringSubscribe | undefined
try {
subscriber = getStringStorage()?.subscribe
} catch {
// ignore
}
if (
!subscriber &&
typeof window !== 'undefined' &&
typeof window.addEventListener === 'function' &&
window.Storage
) {
subscriber = (key, callback) => {
if (!(getStringStorage() instanceof window.Storage)) {
return () => {}
}
const storageEventCallback = (e: StorageEvent) => {
if (e.storageArea === getStringStorage() && e.key === key) {
callback(e.newValue)
}
}
window.addEventListener('storage', storageEventCallback)
return () => {
window.removeEventListener('storage', storageEventCallback)
}
}
}
if (subscriber) {
storage.subscribe = createHandleSubscribe(subscriber)
}
return storage
}
export const superJsonStorage = createSuperjsonStorage()