Created
August 26, 2025 07:36
-
-
Save markusand/39281e9272ede66b72ca7fb1f657adc6 to your computer and use it in GitHub Desktop.
Type-safe wrapper for localStorage supporting primitives, Date, arrays, and nested objects. Includes set, get, remove, clear and a Vue 3 ref that auto-syncs with storage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export type Storable = | |
| | string | |
| | number | |
| | boolean | |
| | null | |
| | Date | |
| | Storable[] | |
| | undefined | |
| | { [key: string]: Storable }; | |
| type StoredData = { | |
| __type: string; | |
| value: unknown; | |
| }; | |
| const getType = (value: Storable): string => { | |
| if (value === null) return 'null'; | |
| if (value instanceof Date) return 'Date'; | |
| if (Array.isArray(value)) return 'Array'; | |
| if (typeof value === 'object') return 'Object'; | |
| return typeof value; | |
| }; | |
| const encode = (data: Storable): StoredData => { | |
| const serializers: Record<string, (value: Storable) => unknown> = { | |
| Date: value => (value as Date).toISOString(), | |
| Array: value => (value as Storable[]).map(encode), | |
| Object: value => Object.fromEntries( | |
| Object.entries(value as object).map(([k, v]) => [k, encode(v)]), | |
| ), | |
| primitive: value => value, | |
| }; | |
| const primitives = ['number', 'string', 'boolean', 'null']; | |
| const type = getType(data); | |
| const key = primitives.includes(type) ? 'primitive' : type; | |
| return { __type: type, value: serializers[key]?.(data) ?? data }; | |
| }; | |
| const decode = (data: StoredData): Storable => { | |
| const parsers: Record<string, (value: unknown) => Storable> = { | |
| Date: value => new Date(value as string), | |
| Array: value => (value as StoredData[]).map(decode), | |
| Object: value => Object.fromEntries( | |
| Object.entries(value as object).map(([k, v]) => [k, decode(v)]), | |
| ), | |
| primitive: value => value as Storable, | |
| }; | |
| const primitives = ['number', 'string', 'boolean', 'null']; | |
| const key = primitives.includes(data.__type) ? 'primitive' : data.__type; | |
| return parsers[key]?.(data.value); | |
| }; | |
| export default { | |
| set: <T extends Storable>(key: string, value: T): void => { | |
| const encoded = encode(value); | |
| localStorage.setItem(key, JSON.stringify(encoded)); | |
| }, | |
| get: <T extends Storable>(key: string): T | null => { | |
| const raw = localStorage.getItem(key); | |
| if (!raw) return null; | |
| try { | |
| const parsed = JSON.parse(raw) as StoredData; | |
| return decode(parsed) as T; | |
| } catch { | |
| return null; | |
| } | |
| }, | |
| remove: localStorage.removeItem, | |
| clear: localStorage.clear, | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { ref, watch } from 'vue'; | |
| import storage, { type Storable } from '/@/services/typed-storage'; | |
| export default <T extends Storable>(key: string, initial: T) => { | |
| const data = ref<T>(storage.get(key) ?? initial); | |
| watch(data, value => storage.set(key, value), { deep: true }); | |
| return data; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment