@katlux/providers 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/build.config.ts +4 -0
- package/package.json +24 -0
- package/services/CacheProvider/ACacheProvider.ts +13 -0
- package/services/CacheProvider/CApplicationCache.ts +123 -0
- package/services/CacheProvider/CCookieCache.ts +212 -0
- package/services/CacheProvider/CIndexedDBCache.ts +312 -0
- package/services/CacheProvider/CLocalStorageCache.ts +232 -0
- package/services/CacheProvider/CMemoryCache.ts +185 -0
- package/services/CacheProvider/CSessionCache.ts +378 -0
- package/services/CacheProvider/CacheProviderFactory.ts +22 -0
- package/services/DataProvider/AAPIDataProvider.ts +24 -0
- package/services/DataProvider/ADataProvider.ts +126 -0
- package/services/DataProvider/CAPIFlatClientDataProvider.ts +199 -0
- package/services/DataProvider/CAPIFlatServerDataProvider.ts +77 -0
- package/services/DataProvider/CAPITreeClientDataProvider.ts +205 -0
- package/services/DataProvider/CAPITreeServerDataProvider.ts +98 -0
- package/services/DataProvider/CFlatClientDataProvider.ts +52 -0
- package/services/DataProvider/CFlatServerDataProvider.ts +104 -0
- package/services/DataProvider/CTreeClientDataProvider.ts +335 -0
- package/services/DataProvider/CTreeServerDataProvider.ts +207 -0
- package/services/RequestProvider/RequestProvider.ts +165 -0
- package/services/RequestProvider/serverCache.ts +24 -0
- package/services/index.ts +19 -0
- package/services/types.ts +172 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { ACacheProvider } from './ACacheProvider'
|
|
2
|
+
import type { ICacheEntry } from '../types'
|
|
3
|
+
|
|
4
|
+
export class CIndexedDBCache extends ACacheProvider {
|
|
5
|
+
private static instance: CIndexedDBCache | null = null
|
|
6
|
+
|
|
7
|
+
public static getInstance(): CIndexedDBCache {
|
|
8
|
+
if (!this.instance) {
|
|
9
|
+
this.instance = new CIndexedDBCache()
|
|
10
|
+
}
|
|
11
|
+
return this.instance
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private dbName = 'KatluxDB'
|
|
15
|
+
private storeName = 'cache'
|
|
16
|
+
private version = 2
|
|
17
|
+
private dbPromise: Promise<IDBDatabase> | null = null
|
|
18
|
+
|
|
19
|
+
public setStore(storeName: string = "cache") {
|
|
20
|
+
this.storeName = storeName || 'cache'
|
|
21
|
+
if (import.meta.client) {
|
|
22
|
+
this.dbPromise = this.openDB()
|
|
23
|
+
this._hydrateFromPayload()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private constructor() {
|
|
28
|
+
super()
|
|
29
|
+
this.setStore()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private _hydrateFromPayload() {
|
|
33
|
+
try {
|
|
34
|
+
const nuxtApp = typeof useNuxtApp === 'function' ? useNuxtApp() : null
|
|
35
|
+
const payloadData = nuxtApp?.payload?.data || (globalThis as any).__NUXT__?.payload?.data
|
|
36
|
+
if (!payloadData) return
|
|
37
|
+
|
|
38
|
+
const prefix = 'cache:'
|
|
39
|
+
let count = 0
|
|
40
|
+
for (const key in payloadData) {
|
|
41
|
+
if (key.startsWith(prefix)) {
|
|
42
|
+
const value = payloadData[key]
|
|
43
|
+
if (value && typeof value === 'object' && 'data' in value && 'timestamp' in value && 'lifetime' in value) {
|
|
44
|
+
this.dbPromise?.then(async (db) => {
|
|
45
|
+
try {
|
|
46
|
+
const tx = db.transaction(this.storeName, 'readwrite')
|
|
47
|
+
const store = tx.objectStore(this.storeName)
|
|
48
|
+
store.put(value, key)
|
|
49
|
+
tx.commit?.()
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// Silent fail
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
count++
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (count > 0) console.log(`[CIndexedDBCache] Hydrated ${count} entries from payload`)
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// Silent fail
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private openDB(): Promise<IDBDatabase> {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const request = indexedDB.open(this.dbName, this.version)
|
|
67
|
+
request.onerror = () => reject(request.error)
|
|
68
|
+
request.onsuccess = () => resolve(request.result)
|
|
69
|
+
request.onupgradeneeded = (event) => {
|
|
70
|
+
const db = (event.target as IDBOpenDBRequest).result
|
|
71
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
72
|
+
db.createObjectStore(this.storeName)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async getStore(mode: IDBTransactionMode): Promise<IDBObjectStore> {
|
|
79
|
+
if (!this.dbPromise) return Promise.reject("IndexedDB not initialized")
|
|
80
|
+
const db = await this.dbPromise
|
|
81
|
+
return db.transaction(this.storeName, mode).objectStore(this.storeName)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getKeyList(nuxtApp?: any): Promise<Array<string>> {
|
|
85
|
+
if (!import.meta.client || !this.dbPromise) return []
|
|
86
|
+
const db = await this.dbPromise
|
|
87
|
+
const store = db.transaction(this.storeName, 'readonly').objectStore(this.storeName)
|
|
88
|
+
const keys = await new Promise<string[]>((resolve, reject) => {
|
|
89
|
+
const req = store.getAllKeys()
|
|
90
|
+
req.onsuccess = () => resolve(req.result as string[])
|
|
91
|
+
req.onerror = () => reject(req.error)
|
|
92
|
+
})
|
|
93
|
+
const prefix = 'cache:'
|
|
94
|
+
return keys
|
|
95
|
+
.filter(key => key.startsWith(prefix))
|
|
96
|
+
.map(key => key.substring(prefix.length))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async get<T>(key: string, nuxtApp?: any): Promise<T | null> {
|
|
100
|
+
const prefixedKey = 'cache:' + key
|
|
101
|
+
|
|
102
|
+
if (import.meta.client) {
|
|
103
|
+
try {
|
|
104
|
+
const store = await this.getStore('readonly')
|
|
105
|
+
const data = await new Promise<ICacheEntry<T> | undefined>((resolve, reject) => {
|
|
106
|
+
const req = store.get(prefixedKey)
|
|
107
|
+
req.onsuccess = () => resolve(req.result)
|
|
108
|
+
req.onerror = () => reject(req.error)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
if (data) {
|
|
112
|
+
if (Date.now() - data.timestamp <= data.lifetime) {
|
|
113
|
+
console.log(`[CIndexedDBCache] GET SUCCESS: ${key}`)
|
|
114
|
+
return data.data
|
|
115
|
+
} else {
|
|
116
|
+
console.log(`[CIndexedDBCache] EXPIRED: ${key}`)
|
|
117
|
+
const writeStore = await this.getStore('readwrite')
|
|
118
|
+
writeStore.delete(prefixedKey)
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
console.log(`[CIndexedDBCache] NOT IN DB: ${key}`)
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
// Silent fail
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check SSR Payload
|
|
129
|
+
try {
|
|
130
|
+
let payloadData: any = null
|
|
131
|
+
if (import.meta.client) {
|
|
132
|
+
payloadData = (globalThis as any).__NUXT__?.payload?.data
|
|
133
|
+
} else if (nuxtApp) {
|
|
134
|
+
payloadData = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (payloadData && payloadData[prefixedKey]) {
|
|
138
|
+
const entry = payloadData[prefixedKey] as ICacheEntry<T>
|
|
139
|
+
if (Date.now() - entry.timestamp <= entry.lifetime) {
|
|
140
|
+
if (import.meta.client) {
|
|
141
|
+
this.set(key, entry.data, entry.lifetime, nuxtApp)
|
|
142
|
+
}
|
|
143
|
+
return entry.data
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// Silent fail
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async set<T>(key: string, data: T, lifetime: number, nuxtApp?: any): Promise<void> {
|
|
154
|
+
const prefixedKey = 'cache:' + key
|
|
155
|
+
const entry: ICacheEntry<T> = {
|
|
156
|
+
data,
|
|
157
|
+
timestamp: Date.now(),
|
|
158
|
+
lifetime
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (import.meta.server && nuxtApp) {
|
|
162
|
+
try {
|
|
163
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
164
|
+
if (payload) {
|
|
165
|
+
payload[prefixedKey] = entry
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
// Silent fail
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (import.meta.client) {
|
|
173
|
+
try {
|
|
174
|
+
const store = await this.getStore('readwrite')
|
|
175
|
+
await new Promise<void>((resolve, reject) => {
|
|
176
|
+
const req = store.put(entry, prefixedKey)
|
|
177
|
+
req.onsuccess = () => resolve()
|
|
178
|
+
req.onerror = () => reject(req.error)
|
|
179
|
+
})
|
|
180
|
+
} catch (e) {
|
|
181
|
+
// Silent fail
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async remove(key: string, nuxtApp?: any): Promise<void> {
|
|
187
|
+
const prefixedKey = 'cache:' + key
|
|
188
|
+
if (import.meta.client) {
|
|
189
|
+
try {
|
|
190
|
+
const store = await this.getStore('readwrite')
|
|
191
|
+
await new Promise<void>((resolve, reject) => {
|
|
192
|
+
const req = store.delete(prefixedKey)
|
|
193
|
+
req.onsuccess = () => resolve()
|
|
194
|
+
req.onerror = () => reject(req.error)
|
|
195
|
+
})
|
|
196
|
+
} catch (e) {
|
|
197
|
+
// Silent fail
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (import.meta.server && nuxtApp) {
|
|
201
|
+
try {
|
|
202
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
203
|
+
if (payload && payload[prefixedKey]) {
|
|
204
|
+
delete payload[prefixedKey]
|
|
205
|
+
}
|
|
206
|
+
} catch (e) {
|
|
207
|
+
// Silent fail
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async removeByPrefix(prefix: string, nuxtApp?: any): Promise<void> {
|
|
213
|
+
const fullPrefix = 'cache:' + prefix
|
|
214
|
+
if (import.meta.client) {
|
|
215
|
+
try {
|
|
216
|
+
const store = await this.getStore('readwrite')
|
|
217
|
+
const allKeys = await new Promise<string[]>((resolve, reject) => {
|
|
218
|
+
const req = store.getAllKeys()
|
|
219
|
+
req.onsuccess = () => resolve(req.result as string[])
|
|
220
|
+
req.onerror = () => reject(req.error)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const keysToDelete = allKeys.filter(k => k.startsWith(fullPrefix))
|
|
224
|
+
|
|
225
|
+
for (const key of keysToDelete) {
|
|
226
|
+
await new Promise<void>((resolve, reject) => {
|
|
227
|
+
const req = store.delete(key)
|
|
228
|
+
req.onsuccess = () => resolve()
|
|
229
|
+
req.onerror = () => reject(req.error)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
} catch (e) {
|
|
233
|
+
// Silent fail
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (import.meta.server && nuxtApp) {
|
|
237
|
+
try {
|
|
238
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
239
|
+
if (payload) {
|
|
240
|
+
for (const key in payload) {
|
|
241
|
+
if (key.startsWith(fullPrefix)) {
|
|
242
|
+
delete payload[key]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
// Silent fail
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async getRemainingTime(key: string, nuxtApp?: any): Promise<number | null> {
|
|
253
|
+
if (import.meta.client) {
|
|
254
|
+
try {
|
|
255
|
+
const prefixedKey = 'cache:' + key
|
|
256
|
+
const store = await this.getStore('readonly')
|
|
257
|
+
const entry = await new Promise<ICacheEntry<any> | undefined>((resolve, reject) => {
|
|
258
|
+
const req = store.get(prefixedKey)
|
|
259
|
+
req.onsuccess = () => resolve(req.result)
|
|
260
|
+
req.onerror = () => reject(req.error)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
if (!entry) return null
|
|
264
|
+
return Math.max(0, (entry.timestamp + entry.lifetime) - Date.now())
|
|
265
|
+
} catch (e) {
|
|
266
|
+
return null
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return null
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async cleanupExpired(): Promise<void> {
|
|
273
|
+
if (import.meta.client && this.dbPromise) {
|
|
274
|
+
try {
|
|
275
|
+
const prefix = 'cache:'
|
|
276
|
+
const now = Date.now()
|
|
277
|
+
const keysToDelete: string[] = []
|
|
278
|
+
|
|
279
|
+
const store = await this.getStore('readwrite')
|
|
280
|
+
const allKeys = await new Promise<string[]>((resolve, reject) => {
|
|
281
|
+
const req = store.getAllKeys()
|
|
282
|
+
req.onsuccess = () => resolve(req.result as string[])
|
|
283
|
+
req.onerror = () => reject(req.error)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
for (const key of allKeys) {
|
|
287
|
+
if (key.startsWith(prefix)) {
|
|
288
|
+
const entry = await new Promise<ICacheEntry<any> | undefined>((resolve, reject) => {
|
|
289
|
+
const req = store.get(key)
|
|
290
|
+
req.onsuccess = () => resolve(req.result)
|
|
291
|
+
req.onerror = () => reject(req.error)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
if (entry && now - entry.timestamp > entry.lifetime) {
|
|
295
|
+
keysToDelete.push(key)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
for (const key of keysToDelete) {
|
|
301
|
+
await new Promise<void>((resolve, reject) => {
|
|
302
|
+
const req = store.delete(key)
|
|
303
|
+
req.onsuccess = () => resolve()
|
|
304
|
+
req.onerror = () => reject(req.error)
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
} catch (e) {
|
|
308
|
+
// Silent fail
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { ACacheProvider } from './ACacheProvider'
|
|
2
|
+
import type { ICacheEntry } from '../types'
|
|
3
|
+
|
|
4
|
+
export class CLocalStorageCache extends ACacheProvider {
|
|
5
|
+
private static instance: CLocalStorageCache | null = null
|
|
6
|
+
|
|
7
|
+
public static getInstance(): CLocalStorageCache {
|
|
8
|
+
if (!this.instance) {
|
|
9
|
+
this.instance = new CLocalStorageCache()
|
|
10
|
+
}
|
|
11
|
+
return this.instance
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public type = 'LocalStorage'
|
|
15
|
+
|
|
16
|
+
private constructor() {
|
|
17
|
+
super()
|
|
18
|
+
if (import.meta.client) {
|
|
19
|
+
this._hydrateFromPayload()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private _hydrateFromPayload() {
|
|
24
|
+
try {
|
|
25
|
+
const nuxtApp = typeof useNuxtApp === 'function' ? useNuxtApp() : null
|
|
26
|
+
const payloadData = nuxtApp?.payload?.data || (window as any).__NUXT__?.payload?.data
|
|
27
|
+
if (!payloadData) return
|
|
28
|
+
|
|
29
|
+
const prefix = 'cache:'
|
|
30
|
+
let count = 0
|
|
31
|
+
for (const key in payloadData) {
|
|
32
|
+
if (key.startsWith(prefix)) {
|
|
33
|
+
const value = payloadData[key]
|
|
34
|
+
if (value && typeof value === 'object' && 'data' in value && 'timestamp' in value && 'lifetime' in value) {
|
|
35
|
+
try {
|
|
36
|
+
localStorage.setItem(key, JSON.stringify(value))
|
|
37
|
+
count++
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// Ignore quota errors
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (count > 0) console.log(`[CLocalStorageCache] Hydrated ${count} entries from payload`)
|
|
45
|
+
} catch (e) {
|
|
46
|
+
// Silent fail
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getKeyList(nuxtApp?: any): Promise<Array<string>> {
|
|
51
|
+
if (!import.meta.client) return []
|
|
52
|
+
const prefix = 'cache:'
|
|
53
|
+
return Object.keys(localStorage)
|
|
54
|
+
.filter(key => key.startsWith(prefix))
|
|
55
|
+
.map(key => key.substring(prefix.length))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async get<T>(key: string, nuxtApp?: any): Promise<T | null> {
|
|
59
|
+
const prefixedKey = 'cache:' + key
|
|
60
|
+
|
|
61
|
+
if (import.meta.client) {
|
|
62
|
+
try {
|
|
63
|
+
// 1. Check LocalStorage
|
|
64
|
+
const stored = localStorage.getItem(prefixedKey)
|
|
65
|
+
if (stored) {
|
|
66
|
+
const entry: ICacheEntry<T> = JSON.parse(stored)
|
|
67
|
+
if (Date.now() - entry.timestamp <= entry.lifetime) {
|
|
68
|
+
console.log(`[CLocalStorageCache] GET SUCCESS: ${key}`)
|
|
69
|
+
return entry.data
|
|
70
|
+
} else {
|
|
71
|
+
console.log(`[CLocalStorageCache] EXPIRED: ${key}`)
|
|
72
|
+
localStorage.removeItem(prefixedKey)
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
console.log(`[CLocalStorageCache] NOT IN STORAGE: ${key}`)
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// Serialisation error
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Check SSR Payload (Hydration/Request Scoped Reuse)
|
|
83
|
+
// On server, payload is in nuxtApp. On client, it's also in nuxtApp if passed, or window.__NUXT__
|
|
84
|
+
try {
|
|
85
|
+
let payloadData: any = null
|
|
86
|
+
if (import.meta.client) {
|
|
87
|
+
payloadData = (window as any).__NUXT__?.payload?.data
|
|
88
|
+
} else if (nuxtApp) {
|
|
89
|
+
payloadData = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (payloadData && payloadData[prefixedKey]) {
|
|
93
|
+
const entry = payloadData[prefixedKey] as ICacheEntry<T>
|
|
94
|
+
if (Date.now() - entry.timestamp <= entry.lifetime) {
|
|
95
|
+
if (import.meta.client) {
|
|
96
|
+
localStorage.setItem(prefixedKey, JSON.stringify(entry))
|
|
97
|
+
}
|
|
98
|
+
return entry.data
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// Silent fail
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async set<T>(key: string, data: T, lifetime: number, nuxtApp?: any): Promise<void> {
|
|
109
|
+
const prefixedKey = 'cache:' + key
|
|
110
|
+
const entry: ICacheEntry<T> = {
|
|
111
|
+
data,
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
lifetime
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (import.meta.server && nuxtApp) {
|
|
117
|
+
// Write to Payload for hydration
|
|
118
|
+
try {
|
|
119
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
120
|
+
if (payload) {
|
|
121
|
+
payload[prefixedKey] = entry
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
// Serialisation error
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (import.meta.client) {
|
|
129
|
+
try {
|
|
130
|
+
localStorage.setItem(prefixedKey, JSON.stringify(entry))
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// Quota exceeded
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async remove(key: string, nuxtApp?: any): Promise<void> {
|
|
138
|
+
const prefixedKey = 'cache:' + key
|
|
139
|
+
if (import.meta.client) {
|
|
140
|
+
localStorage.removeItem(prefixedKey)
|
|
141
|
+
}
|
|
142
|
+
if (import.meta.server && nuxtApp) {
|
|
143
|
+
try {
|
|
144
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
145
|
+
if (payload && payload[prefixedKey]) {
|
|
146
|
+
delete payload[prefixedKey]
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// Silent fail
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async removeByPrefix(prefix: string, nuxtApp?: any): Promise<void> {
|
|
155
|
+
const fullPrefix = 'cache:' + prefix
|
|
156
|
+
if (import.meta.client) {
|
|
157
|
+
const keysToDelete: string[] = []
|
|
158
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
159
|
+
const key = localStorage.key(i)
|
|
160
|
+
if (key && key.startsWith(fullPrefix)) {
|
|
161
|
+
keysToDelete.push(key)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const key of keysToDelete) {
|
|
165
|
+
localStorage.removeItem(key)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (import.meta.server && nuxtApp) {
|
|
169
|
+
try {
|
|
170
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
171
|
+
if (payload) {
|
|
172
|
+
for (const key in payload) {
|
|
173
|
+
if (key.startsWith(fullPrefix)) {
|
|
174
|
+
delete payload[key]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch (e) {
|
|
179
|
+
// Silent fail
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async getRemainingTime(key: string, nuxtApp?: any): Promise<number | null> {
|
|
185
|
+
if (import.meta.client) {
|
|
186
|
+
const prefixedKey = 'cache:' + key
|
|
187
|
+
const entryStr = localStorage.getItem(prefixedKey)
|
|
188
|
+
if (!entryStr) return null
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const entry: ICacheEntry<any> = JSON.parse(entryStr)
|
|
192
|
+
return Math.max(0, (entry.timestamp + entry.lifetime) - Date.now())
|
|
193
|
+
} catch (e) {
|
|
194
|
+
return null
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async cleanupExpired(): Promise<void> {
|
|
201
|
+
if (import.meta.client) {
|
|
202
|
+
try {
|
|
203
|
+
const prefix = 'cache:'
|
|
204
|
+
const now = Date.now()
|
|
205
|
+
const keysToDelete: string[] = []
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
208
|
+
const key = localStorage.key(i)
|
|
209
|
+
if (key && key.startsWith(prefix)) {
|
|
210
|
+
const stored = localStorage.getItem(key)
|
|
211
|
+
if (stored) {
|
|
212
|
+
try {
|
|
213
|
+
const entry: ICacheEntry<any> = JSON.parse(stored)
|
|
214
|
+
if (now - entry.timestamp > entry.lifetime) {
|
|
215
|
+
keysToDelete.push(key)
|
|
216
|
+
}
|
|
217
|
+
} catch (e) {
|
|
218
|
+
keysToDelete.push(key)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (const key of keysToDelete) {
|
|
225
|
+
localStorage.removeItem(key)
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
// Silent fail
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|