forked from mxx1111/mdlook
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstorage.ts
More file actions
362 lines (316 loc) · 8.31 KB
/
Copy pathstorage.ts
File metadata and controls
362 lines (316 loc) · 8.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/**
* 现代化存储抽象层 - 完全异步化设计
* 支持本地存储和 RESTful API 存储,便于后续扩展
*/
import type { Ref } from 'vue'
import { customRef, ref, watch } from 'vue'
/**
* 存储引擎接口 - 完全异步化
*/
export interface StorageEngine {
get: (key: string) => Promise<string | null>
set: (key: string, value: string) => Promise<void>
remove: (key: string) => Promise<void>
has: (key: string) => Promise<boolean>
clear: () => Promise<void>
keys: () => Promise<string[]>
}
/**
* 本地存储引擎 - 使用 localStorage
*/
export class LocalStorageEngine implements StorageEngine {
async get(key: string): Promise<string | null> {
try {
return localStorage.getItem(key)
}
catch (error) {
console.error(`[Storage] Failed to get item:`, key, error)
return null
}
}
async set(key: string, value: string): Promise<void> {
try {
localStorage.setItem(key, value)
}
catch (error) {
console.error(`[Storage] Failed to set item:`, key, error)
throw error
}
}
async remove(key: string): Promise<void> {
try {
localStorage.removeItem(key)
}
catch (error) {
console.error(`[Storage] Failed to remove item:`, key, error)
}
}
async has(key: string): Promise<boolean> {
try {
return localStorage.getItem(key) !== null
}
catch {
return false
}
}
async clear(): Promise<void> {
try {
localStorage.clear()
}
catch (error) {
console.error(`[Storage] Failed to clear storage:`, error)
}
}
async keys(): Promise<string[]> {
try {
return Object.keys(localStorage)
}
catch {
return []
}
}
}
/**
* RESTful API 存储引擎 - 用于远程存储
*/
export class RestfulStorageEngine implements StorageEngine {
constructor(
private baseURL: string,
private getAuthToken?: () => string | null,
) {}
private async request(method: string, endpoint: string, data?: any): Promise<any> {
const headers: HeadersInit = {
'Content-Type': `application/json`,
}
const token = this.getAuthToken?.()
if (token) {
headers.Authorization = `Bearer ${token}`
}
const response = await fetch(`${this.baseURL}${endpoint}`, {
method,
headers,
body: data ? JSON.stringify(data) : undefined,
})
if (!response.ok) {
throw new Error(`Storage API error: ${response.statusText}`)
}
return response.json()
}
async get(key: string): Promise<string | null> {
try {
const result = await this.request(`GET`, `/storage/${encodeURIComponent(key)}`)
return result.value ?? null
}
catch {
return null
}
}
async set(key: string, value: string): Promise<void> {
await this.request(`PUT`, `/storage/${encodeURIComponent(key)}`, { value })
}
async remove(key: string): Promise<void> {
await this.request(`DELETE`, `/storage/${encodeURIComponent(key)}`)
}
async has(key: string): Promise<boolean> {
try {
await this.request(`HEAD`, `/storage/${encodeURIComponent(key)}`)
return true
}
catch {
return false
}
}
async clear(): Promise<void> {
await this.request(`DELETE`, `/storage`)
}
async keys(): Promise<string[]> {
const result = await this.request(`GET`, `/storage/keys`)
return result.keys ?? []
}
}
/**
* 统一存储管理器
*/
class StorageManager {
private engine: StorageEngine = new LocalStorageEngine()
/**
* 切换存储引擎
*/
setEngine(engine: StorageEngine): void {
this.engine = engine
}
/**
* 获取当前引擎
*/
getEngine(): StorageEngine {
return this.engine
}
/**
* 获取字符串值
*/
async get(key: string): Promise<string | null> {
return this.engine.get(key)
}
/**
* 设置字符串值
*/
async set(key: string, value: string): Promise<void> {
return this.engine.set(key, value)
}
/**
* 获取 JSON 值(带默认值重载)
*/
async getJSON<T>(key: string, defaultValue: T): Promise<T>
async getJSON<T>(key: string): Promise<T | null>
async getJSON<T>(key: string, defaultValue?: T): Promise<T | null> {
const value = await this.engine.get(key)
if (!value) {
return (defaultValue ?? null) as T | null
}
try {
return JSON.parse(value) as T
}
catch (error) {
console.error(`[Storage] Failed to parse JSON for key:`, key, error)
return (defaultValue ?? null) as T | null
}
}
/**
* 设置 JSON 值
*/
async setJSON<T>(key: string, value: T): Promise<void> {
try {
const jsonString = JSON.stringify(value)
return this.engine.set(key, jsonString)
}
catch (error) {
console.error(`[Storage] Failed to stringify JSON for key:`, key, error)
throw error
}
}
/**
* 删除值
*/
async remove(key: string): Promise<void> {
return this.engine.remove(key)
}
/**
* 检查键是否存在
*/
async has(key: string): Promise<boolean> {
return this.engine.has(key)
}
/**
* 清空所有存储
*/
async clear(): Promise<void> {
return this.engine.clear()
}
/**
* 获取所有键
*/
async keys(): Promise<string[]> {
return this.engine.keys()
}
/**
* 创建响应式存储引用
* - 对于 LocalStorageEngine:同步读取初始值,确保首次渲染正确
* - 对于其他引擎:异步加载,加载完成后更新
* - 自动监听变化并保存到存储
*/
reactive<T>(key: string, defaultValue: T): Ref<T> {
const isStringType = typeof defaultValue === `string`
let initialValue: T = defaultValue
// LocalStorageEngine 同步读取初始值
if (this.engine instanceof LocalStorageEngine) {
try {
const stored = localStorage.getItem(key)
if (stored !== null) {
initialValue = isStringType ? (stored as T) : this.parseJSON(stored, defaultValue)
}
}
catch (error) {
console.error(`[Storage] Failed to read initial value:`, key, error)
}
}
const data = ref<T>(initialValue) as Ref<T>
// 非 LocalStorageEngine 异步加载
if (!(this.engine instanceof LocalStorageEngine)) {
const loadAsync = isStringType
? this.get(key).then(value => value !== null ? (value as T) : null)
: this.getJSON<T>(key, defaultValue)
loadAsync.then((value) => {
if (value !== null) {
data.value = value
}
})
}
// 监听变化并自动保存
// 使用 Promise.resolve() 确保在下一个微任务中设置 watch,避免初始赋值触发保存
Promise.resolve().then(() => {
watch(
data,
(newValue) => {
const savePromise = isStringType
? this.set(key, newValue as string)
: this.setJSON(key, newValue)
savePromise.catch((error) => {
console.error(`[Storage] Failed to save reactive data:`, key, error)
})
},
{ deep: true },
)
})
return data
}
/**
* 创建自定义响应式存储引用
* 支持自定义 getter/setter 转换逻辑
*/
customReactive<T>(
key: string,
defaultValue: T,
options?: {
get?: (stored: T | null) => T
set?: (value: T) => T
},
): Ref<T> {
let cachedValue: T = defaultValue
// 异步加载初始值
this.getJSON<T>(key, defaultValue).then((value) => {
const stored = value ?? defaultValue
cachedValue = options?.get ? options.get(stored) : stored
})
return customRef<T>((track, trigger) => ({
get() {
track()
return cachedValue
},
set: (newValue: T) => {
const valueToStore = options?.set ? options.set(newValue) : newValue
cachedValue = valueToStore
trigger()
// 异步保存
this.setJSON(key, valueToStore).catch((error: any) => {
console.error(`[Storage] Failed to save custom reactive data:`, key, error)
})
},
}))
}
/**
* 解析 JSON 字符串的辅助方法
*/
private parseJSON<T>(value: string, fallback: T): T {
try {
return JSON.parse(value) as T
}
catch {
console.warn(`[Storage] Failed to parse JSON, using fallback`)
return fallback
}
}
}
/**
* 全局存储实例 - 统一通过 store.xxx 调用
*/
export const store = new StorageManager()