forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { createDefu } from 'defu'
2
3/**
4 * Abstraction for preferences storage
5 * Currently uses localStorage, designed for future user prefs API
6 */
7
8const STORAGE_KEY = 'npmx-list-prefs'
9
10interface StorageProvider<T> {
11 get: () => T | null
12 set: (value: T) => void
13 remove: () => void
14}
15
16const defu = createDefu((object, key, value) => {
17 if (Array.isArray(object[key]) && Array.isArray(value)) {
18 object[key] = value
19 return true
20 }
21})
22
23/**
24 * Creates a localStorage-based storage provider
25 */
26function createLocalStorageProvider<T>(key: string): StorageProvider<T> {
27 return {
28 get: () => {
29 if (import.meta.server) return null
30 try {
31 const stored = localStorage.getItem(key)
32 if (stored) {
33 return JSON.parse(stored) as T
34 }
35 } catch {
36 // Corrupted data, remove it
37 localStorage.removeItem(key)
38 }
39 return null
40 },
41 set: (value: T) => {
42 if (import.meta.server) return
43 try {
44 localStorage.setItem(key, JSON.stringify(value))
45 } catch {
46 // Storage full or other error, fail silently
47 }
48 },
49 remove: () => {
50 if (import.meta.server) return
51 localStorage.removeItem(key)
52 },
53 }
54}
55
56// Future: API-based provider would look like this:
57// function createApiStorageProvider<T>(endpoint: string): StorageProvider<T> {
58// return {
59// get: async () => { /* fetch from API */ },
60// set: async (value) => { /* POST to API */ },
61// remove: async () => { /* DELETE from API */ },
62// }
63// }
64
65/**
66 * Composable for managing preferences storage
67 * Abstracts the storage mechanism to allow future migration to API-based storage
68 *
69 */
70export function usePreferencesProvider<T extends object>(defaultValue: T) {
71 const provider = createLocalStorageProvider<T>(STORAGE_KEY)
72 const data = ref<T>(defaultValue)
73 const isHydrated = shallowRef(false)
74
75 // Load from storage on client
76 onMounted(() => {
77 const stored = provider.get()
78 if (stored) {
79 // Merge stored values with defaults to handle schema evolution
80 data.value = defu(stored, defaultValue)
81 }
82 isHydrated.value = true
83 })
84
85 // Persist changes
86 function save() {
87 provider.set(data.value)
88 }
89
90 // Reset to defaults
91 function reset() {
92 data.value = { ...defaultValue }
93 provider.remove()
94 }
95
96 // Update specific keys
97 function update<K extends keyof T>(key: K, value: T[K]) {
98 data.value[key] = value
99 save()
100 }
101
102 return {
103 data,
104 isHydrated,
105 save,
106 reset,
107 update,
108 }
109}