@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,185 @@
|
|
|
1
|
+
import { ACacheProvider } from './ACacheProvider'
|
|
2
|
+
import type { ICacheEntry } from '../types'
|
|
3
|
+
|
|
4
|
+
export class CMemoryCache extends ACacheProvider {
|
|
5
|
+
private static instance: CMemoryCache | null = null
|
|
6
|
+
private static _storage: Map<string, ICacheEntry<any>> | null = null
|
|
7
|
+
|
|
8
|
+
private static get storage(): Map<string, ICacheEntry<any>> {
|
|
9
|
+
if (!this._storage) this._storage = new Map()
|
|
10
|
+
return this._storage
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public static getInstance(): CMemoryCache {
|
|
14
|
+
if (!this.instance) {
|
|
15
|
+
this.instance = new CMemoryCache()
|
|
16
|
+
}
|
|
17
|
+
return this.instance
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private constructor() {
|
|
21
|
+
super()
|
|
22
|
+
if (import.meta.client && CMemoryCache.storage.size === 0) {
|
|
23
|
+
this._hydrateFromPayload()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private _hydrateFromPayload() {
|
|
28
|
+
try {
|
|
29
|
+
const nuxtApp = typeof useNuxtApp === 'function' ? useNuxtApp() : null
|
|
30
|
+
const payloadData = nuxtApp?.payload?.data || (globalThis as any).__NUXT__?.payload?.data
|
|
31
|
+
if (!payloadData) return
|
|
32
|
+
|
|
33
|
+
for (const key in payloadData) {
|
|
34
|
+
const value = payloadData[key]
|
|
35
|
+
if (value && typeof value === 'object' && 'data' in value && 'timestamp' in value && 'lifetime' in value) {
|
|
36
|
+
if (typeof (value as any).lifetime === 'number') {
|
|
37
|
+
CMemoryCache.storage.set(key, value as ICacheEntry<any>)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
// Silent fail
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getKeyList(nuxtApp?: any): Promise<Array<string>> {
|
|
47
|
+
const prefix = 'cache:'
|
|
48
|
+
return Array.from(CMemoryCache.storage.keys())
|
|
49
|
+
.filter(key => key.startsWith(prefix))
|
|
50
|
+
.map(key => key.substring(prefix.length))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async get<T>(key: string, nuxtApp?: any): Promise<T | null> {
|
|
54
|
+
const prefixedKey = 'cache:' + key
|
|
55
|
+
|
|
56
|
+
if (import.meta.client) {
|
|
57
|
+
const entry = CMemoryCache.storage.get(prefixedKey)
|
|
58
|
+
if (entry) {
|
|
59
|
+
if (Date.now() - entry.timestamp <= entry.lifetime) {
|
|
60
|
+
return entry.data as T
|
|
61
|
+
} else {
|
|
62
|
+
CMemoryCache.storage.delete(prefixedKey)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check SSR Payload
|
|
68
|
+
try {
|
|
69
|
+
let payloadData: any = null
|
|
70
|
+
if (import.meta.client) {
|
|
71
|
+
payloadData = (globalThis as any).__NUXT__?.payload?.data
|
|
72
|
+
} else if (nuxtApp) {
|
|
73
|
+
payloadData = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (payloadData && payloadData[prefixedKey]) {
|
|
77
|
+
const entry = payloadData[prefixedKey] as ICacheEntry<T>
|
|
78
|
+
if (Date.now() - entry.timestamp <= entry.lifetime) {
|
|
79
|
+
if (import.meta.client) {
|
|
80
|
+
CMemoryCache.storage.set(prefixedKey, entry)
|
|
81
|
+
}
|
|
82
|
+
return entry.data
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Silent fail
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async set<T>(key: string, data: T, lifetime: number, nuxtApp?: any): Promise<void> {
|
|
93
|
+
const prefixedKey = 'cache:' + key
|
|
94
|
+
const entry: ICacheEntry<T> = {
|
|
95
|
+
data,
|
|
96
|
+
timestamp: Date.now(),
|
|
97
|
+
lifetime
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (import.meta.server && nuxtApp) {
|
|
101
|
+
try {
|
|
102
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
103
|
+
if (payload) {
|
|
104
|
+
payload[prefixedKey] = entry
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
// Silent fail
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (import.meta.client) {
|
|
112
|
+
CMemoryCache.storage.set(prefixedKey, entry)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async remove(key: string, nuxtApp?: any): Promise<void> {
|
|
117
|
+
const prefixedKey = 'cache:' + key
|
|
118
|
+
CMemoryCache.storage.delete(prefixedKey)
|
|
119
|
+
|
|
120
|
+
if (import.meta.server && nuxtApp) {
|
|
121
|
+
try {
|
|
122
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
123
|
+
if (payload && payload[prefixedKey]) {
|
|
124
|
+
delete payload[prefixedKey]
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// Silent fail
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async removeByPrefix(prefix: string, nuxtApp?: any): Promise<void> {
|
|
133
|
+
const fullPrefix = 'cache:' + prefix
|
|
134
|
+
|
|
135
|
+
const keysToDelete: string[] = []
|
|
136
|
+
for (const key of CMemoryCache.storage.keys()) {
|
|
137
|
+
if (key.startsWith(fullPrefix)) {
|
|
138
|
+
keysToDelete.push(key)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const key of keysToDelete) {
|
|
142
|
+
CMemoryCache.storage.delete(key)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (import.meta.server && nuxtApp) {
|
|
146
|
+
try {
|
|
147
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
148
|
+
if (payload) {
|
|
149
|
+
for (const key in payload) {
|
|
150
|
+
if (key.startsWith(fullPrefix)) {
|
|
151
|
+
delete payload[key]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch (e) {
|
|
156
|
+
// Silent fail
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async getRemainingTime(key: string, nuxtApp?: any): Promise<number | null> {
|
|
162
|
+
const prefixedKey = 'cache:' + key
|
|
163
|
+
const entry = CMemoryCache.storage.get(prefixedKey)
|
|
164
|
+
if (!entry) return null
|
|
165
|
+
return Math.max(0, (entry.timestamp + entry.lifetime) - Date.now())
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async cleanupExpired(): Promise<void> {
|
|
169
|
+
const prefix = 'cache:'
|
|
170
|
+
const now = Date.now()
|
|
171
|
+
const keysToDelete: string[] = []
|
|
172
|
+
|
|
173
|
+
for (const [key, entry] of CMemoryCache.storage.entries()) {
|
|
174
|
+
if (key.startsWith(prefix)) {
|
|
175
|
+
if (now - entry.timestamp > entry.lifetime) {
|
|
176
|
+
keysToDelete.push(key)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const key of keysToDelete) {
|
|
182
|
+
CMemoryCache.storage.delete(key)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { ACacheProvider } from './ACacheProvider'
|
|
2
|
+
import type { ICacheEntry } from '../types'
|
|
3
|
+
import { CCookieCache } from './CCookieCache'
|
|
4
|
+
|
|
5
|
+
export class CSessionCache extends ACacheProvider {
|
|
6
|
+
private static instance: CSessionCache | null = null
|
|
7
|
+
|
|
8
|
+
public static getInstance(): CSessionCache {
|
|
9
|
+
if (!this.instance) {
|
|
10
|
+
this.instance = new CSessionCache()
|
|
11
|
+
}
|
|
12
|
+
return this.instance
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Use global symbol to share storage across module instances (API vs App)
|
|
16
|
+
private static get storage(): Map<string, Map<string, ICacheEntry<any>>> {
|
|
17
|
+
const globalKey = Symbol.for('Katlux_CSessionCache_Storage')
|
|
18
|
+
const _global = globalThis as any
|
|
19
|
+
if (!_global[globalKey]) {
|
|
20
|
+
_global[globalKey] = new Map<string, Map<string, ICacheEntry<any>>>()
|
|
21
|
+
}
|
|
22
|
+
return _global[globalKey]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static getSnapshot(): Map<string, Map<string, ICacheEntry<any>>> {
|
|
26
|
+
return this.storage
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private sessionIdCache: ACacheProvider | null = null
|
|
30
|
+
private sessionId: string | null = null
|
|
31
|
+
private readonly SESSION_ID_KEY = '_session_id'
|
|
32
|
+
|
|
33
|
+
private constructor() {
|
|
34
|
+
super()
|
|
35
|
+
// Use cookie cache for storing sessionId on client via Singleton
|
|
36
|
+
this.sessionIdCache = CCookieCache.getInstance()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate a UUID v4 sessionId
|
|
41
|
+
*/
|
|
42
|
+
private generateSessionId(): string {
|
|
43
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
44
|
+
const r = Math.random() * 16 | 0
|
|
45
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
|
46
|
+
return v.toString(16)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get or create sessionId
|
|
52
|
+
*/
|
|
53
|
+
private async getSessionId(nuxtApp?: any): Promise<string> {
|
|
54
|
+
if (this.sessionId) {
|
|
55
|
+
return this.sessionId
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!this.sessionIdCache) {
|
|
59
|
+
return this.generateSessionId()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Try to get existing sessionId from cookie
|
|
63
|
+
const existingId = await this.sessionIdCache.get<string>(this.SESSION_ID_KEY, nuxtApp)
|
|
64
|
+
|
|
65
|
+
if (existingId) {
|
|
66
|
+
this.sessionId = existingId
|
|
67
|
+
return existingId
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Generate new sessionId
|
|
71
|
+
const newId = this.generateSessionId()
|
|
72
|
+
this.sessionId = newId
|
|
73
|
+
|
|
74
|
+
// Store in cookie (1 year lifetime)
|
|
75
|
+
if (this.sessionIdCache) {
|
|
76
|
+
await this.sessionIdCache.set(this.SESSION_ID_KEY, newId, 365 * 24 * 60 * 60 * 1000, nuxtApp)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return newId
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getKeyList(nuxtApp?: any): Promise<Array<string>> {
|
|
83
|
+
const prefix = 'cache:'
|
|
84
|
+
|
|
85
|
+
// Client: Use browser sessionStorage
|
|
86
|
+
if (import.meta.client) {
|
|
87
|
+
return Object.keys(sessionStorage)
|
|
88
|
+
.filter(key => key.startsWith(prefix))
|
|
89
|
+
.map(key => key.substring(prefix.length))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Server: Use session storage (Map)
|
|
93
|
+
const sessionId = await this.getSessionId(nuxtApp)
|
|
94
|
+
const sessionData = CSessionCache.storage.get(sessionId)
|
|
95
|
+
if (!sessionData) {
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Array.from(sessionData.keys())
|
|
100
|
+
.filter(key => key.startsWith(prefix))
|
|
101
|
+
.map(key => key.substring(prefix.length))
|
|
102
|
+
}
|
|
103
|
+
async get<T>(key: string, nuxtApp?: any): Promise<T | null> {
|
|
104
|
+
const prefixedKey = 'cache:' + key
|
|
105
|
+
|
|
106
|
+
// Client: Use browser sessionStorage
|
|
107
|
+
if (import.meta.client) {
|
|
108
|
+
try {
|
|
109
|
+
const stored = sessionStorage.getItem(prefixedKey)
|
|
110
|
+
if (stored) {
|
|
111
|
+
const entry: ICacheEntry<T> = JSON.parse(stored)
|
|
112
|
+
if (Date.now() - entry.timestamp <= entry.lifetime) {
|
|
113
|
+
return entry.data as T
|
|
114
|
+
} else {
|
|
115
|
+
sessionStorage.removeItem(prefixedKey)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
// Serialisation error
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check SSR Payload as fallback/hydration
|
|
123
|
+
try {
|
|
124
|
+
const payloadData = (window as any).__NUXT__?.payload?.data
|
|
125
|
+
if (payloadData && payloadData[prefixedKey]) {
|
|
126
|
+
const entry = payloadData[prefixedKey] as ICacheEntry<T>
|
|
127
|
+
if (Date.now() - entry.timestamp <= entry.lifetime) {
|
|
128
|
+
// Hydrate into sessionStorage
|
|
129
|
+
window.sessionStorage.setItem(prefixedKey, JSON.stringify(entry))
|
|
130
|
+
return entry.data
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// Silent fail
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Server: Retrieve from session storage (Map)
|
|
141
|
+
if (import.meta.server) {
|
|
142
|
+
const sessionId = await this.getSessionId(nuxtApp)
|
|
143
|
+
const sessionData = CSessionCache.storage.get(sessionId)
|
|
144
|
+
if (!sessionData) {
|
|
145
|
+
return null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const entry = sessionData.get(prefixedKey)
|
|
149
|
+
if (!entry) {
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check expiration
|
|
154
|
+
if (Date.now() - entry.timestamp <= entry.lifetime) {
|
|
155
|
+
return entry.data as T
|
|
156
|
+
} else {
|
|
157
|
+
// Remove expired entry
|
|
158
|
+
sessionData.delete(prefixedKey)
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async set<T>(key: string, data: T, lifetime: number, nuxtApp?: any): Promise<void> {
|
|
167
|
+
const prefixedKey = 'cache:' + key
|
|
168
|
+
const entry: ICacheEntry<T> = {
|
|
169
|
+
data,
|
|
170
|
+
timestamp: Date.now(),
|
|
171
|
+
lifetime
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Client: Store in browser sessionStorage
|
|
175
|
+
if (import.meta.client) {
|
|
176
|
+
try {
|
|
177
|
+
sessionStorage.setItem(prefixedKey, JSON.stringify(entry))
|
|
178
|
+
} catch (e) {
|
|
179
|
+
// Quota exceeded
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Server: Store in session storage (Map) AND Payload for hydration
|
|
184
|
+
if (import.meta.server) {
|
|
185
|
+
try {
|
|
186
|
+
const sessionId = await this.getSessionId(nuxtApp)
|
|
187
|
+
|
|
188
|
+
// 1. Session Storage
|
|
189
|
+
let sessionData = CSessionCache.storage.get(sessionId)
|
|
190
|
+
if (!sessionData) {
|
|
191
|
+
sessionData = new Map<string, ICacheEntry<any>>()
|
|
192
|
+
CSessionCache.storage.set(sessionId, sessionData)
|
|
193
|
+
}
|
|
194
|
+
sessionData.set(prefixedKey, entry)
|
|
195
|
+
|
|
196
|
+
// 2. Nuxt Payload (for hydration into client's sessionStorage)
|
|
197
|
+
if (nuxtApp) {
|
|
198
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
199
|
+
if (payload) {
|
|
200
|
+
payload[prefixedKey] = entry
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch (e) {
|
|
204
|
+
// Silent fail
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async remove(key: string, nuxtApp?: any): Promise<void> {
|
|
210
|
+
const prefixedKey = 'cache:' + key
|
|
211
|
+
|
|
212
|
+
if (import.meta.client) {
|
|
213
|
+
sessionStorage.removeItem(prefixedKey)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (import.meta.server) {
|
|
217
|
+
try {
|
|
218
|
+
// 1. Session storage
|
|
219
|
+
const sessionId = await this.getSessionId(nuxtApp)
|
|
220
|
+
const sessionData = CSessionCache.storage.get(sessionId)
|
|
221
|
+
if (sessionData) {
|
|
222
|
+
sessionData.delete(prefixedKey)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 2. Payload cleanup
|
|
226
|
+
if (nuxtApp) {
|
|
227
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
228
|
+
if (payload && payload[prefixedKey]) {
|
|
229
|
+
delete payload[prefixedKey]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} catch (e) {
|
|
233
|
+
// Silent fail
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async removeByPrefix(prefix: string, nuxtApp?: any): Promise<void> {
|
|
239
|
+
const fullPrefix = 'cache:' + prefix
|
|
240
|
+
|
|
241
|
+
// Client: Iterate sessionStorage
|
|
242
|
+
if (import.meta.client) {
|
|
243
|
+
const keysToDelete: string[] = []
|
|
244
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
245
|
+
const key = sessionStorage.key(i)
|
|
246
|
+
if (key && key.startsWith(fullPrefix)) {
|
|
247
|
+
keysToDelete.push(key)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (const key of keysToDelete) {
|
|
251
|
+
sessionStorage.removeItem(key)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Server: Iterate session map
|
|
256
|
+
if (import.meta.server) {
|
|
257
|
+
try {
|
|
258
|
+
const sessionId = await this.getSessionId(nuxtApp)
|
|
259
|
+
const sessionData = CSessionCache.storage.get(sessionId)
|
|
260
|
+
|
|
261
|
+
if (sessionData) {
|
|
262
|
+
const keysToDelete: string[] = []
|
|
263
|
+
for (const key of sessionData.keys()) {
|
|
264
|
+
if (key.startsWith(fullPrefix)) {
|
|
265
|
+
keysToDelete.push(key)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
for (const key of keysToDelete) {
|
|
269
|
+
sessionData.delete(key)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Payload cleanup
|
|
274
|
+
if (nuxtApp) {
|
|
275
|
+
const payload = (nuxtApp as any).payload?.data || (nuxtApp as any).ssrContext?.payload?.data
|
|
276
|
+
if (payload) {
|
|
277
|
+
for (const key in payload) {
|
|
278
|
+
if (key.startsWith(fullPrefix)) {
|
|
279
|
+
delete payload[key]
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
} catch (e) {
|
|
285
|
+
// Silent fail
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async getRemainingTime(key: string, nuxtApp?: any): Promise<number | null> {
|
|
291
|
+
const prefixedKey = 'cache:' + key
|
|
292
|
+
|
|
293
|
+
if (import.meta.client) {
|
|
294
|
+
const entryStr = sessionStorage.getItem(prefixedKey)
|
|
295
|
+
if (!entryStr) return null
|
|
296
|
+
try {
|
|
297
|
+
const entry: ICacheEntry<any> = JSON.parse(entryStr)
|
|
298
|
+
return Math.max(0, (entry.timestamp + entry.lifetime) - Date.now())
|
|
299
|
+
} catch (e) {
|
|
300
|
+
return null
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (import.meta.server) {
|
|
305
|
+
const sessionId = await this.getSessionId(nuxtApp)
|
|
306
|
+
const sessionData = CSessionCache.storage.get(sessionId)
|
|
307
|
+
if (!sessionData) return null
|
|
308
|
+
|
|
309
|
+
const entry = sessionData.get(prefixedKey)
|
|
310
|
+
if (!entry) return null
|
|
311
|
+
|
|
312
|
+
return Math.max(0, (entry.timestamp + entry.lifetime) - Date.now())
|
|
313
|
+
}
|
|
314
|
+
return null
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async cleanupExpired(): Promise<void> {
|
|
318
|
+
const prefix = 'cache:'
|
|
319
|
+
const now = Date.now()
|
|
320
|
+
|
|
321
|
+
if (import.meta.client) {
|
|
322
|
+
const keysToDelete: string[] = []
|
|
323
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
324
|
+
const key = sessionStorage.key(i)
|
|
325
|
+
if (key && key.startsWith(prefix)) {
|
|
326
|
+
const stored = sessionStorage.getItem(key)
|
|
327
|
+
if (stored) {
|
|
328
|
+
try {
|
|
329
|
+
const entry: ICacheEntry<any> = JSON.parse(stored)
|
|
330
|
+
if (now - entry.timestamp > entry.lifetime) {
|
|
331
|
+
keysToDelete.push(key)
|
|
332
|
+
}
|
|
333
|
+
} catch (e) {
|
|
334
|
+
keysToDelete.push(key)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
for (const key of keysToDelete) {
|
|
340
|
+
sessionStorage.removeItem(key)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (import.meta.server) {
|
|
345
|
+
const sessionsToDelete: string[] = []
|
|
346
|
+
|
|
347
|
+
// Iterate through all sessions
|
|
348
|
+
for (const [sessionId, sessionData] of CSessionCache.storage.entries()) {
|
|
349
|
+
const keysToDelete: string[] = []
|
|
350
|
+
|
|
351
|
+
// Check each entry in the session
|
|
352
|
+
for (const [key, entry] of sessionData.entries()) {
|
|
353
|
+
// Only cleanup entries for this cache key
|
|
354
|
+
if (key.startsWith(prefix)) {
|
|
355
|
+
if (now - entry.timestamp > entry.lifetime) {
|
|
356
|
+
keysToDelete.push(key)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Remove expired entries
|
|
362
|
+
for (const key of keysToDelete) {
|
|
363
|
+
sessionData.delete(key)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// If session is empty, mark for deletion
|
|
367
|
+
if (sessionData.size === 0) {
|
|
368
|
+
sessionsToDelete.push(sessionId)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Remove empty sessions
|
|
373
|
+
for (const sessionId of sessionsToDelete) {
|
|
374
|
+
CSessionCache.storage.delete(sessionId)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ECacheStrategy } from '../types'
|
|
2
|
+
import { ACacheProvider } from './ACacheProvider'
|
|
3
|
+
import { CApplicationCache } from './CApplicationCache'
|
|
4
|
+
import { CMemoryCache } from './CMemoryCache'
|
|
5
|
+
import { CLocalStorageCache } from './CLocalStorageCache'
|
|
6
|
+
import { CIndexedDBCache } from './CIndexedDBCache'
|
|
7
|
+
import { CCookieCache } from './CCookieCache'
|
|
8
|
+
import { CSessionCache } from './CSessionCache'
|
|
9
|
+
|
|
10
|
+
export class CacheProviderFactory {
|
|
11
|
+
static getProvider(strategy: ECacheStrategy): ACacheProvider | null {
|
|
12
|
+
switch (strategy) {
|
|
13
|
+
case ECacheStrategy.Application: return CApplicationCache.getInstance()
|
|
14
|
+
case ECacheStrategy.Session: return CSessionCache.getInstance()
|
|
15
|
+
case ECacheStrategy.Memory: return CMemoryCache.getInstance()
|
|
16
|
+
case ECacheStrategy.LocalStorage: return CLocalStorageCache.getInstance()
|
|
17
|
+
case ECacheStrategy.IndexedDB: return CIndexedDBCache.getInstance()
|
|
18
|
+
case ECacheStrategy.Cookie: return CCookieCache.getInstance()
|
|
19
|
+
default: return null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ref, type Ref } from 'vue'
|
|
2
|
+
import { ECacheStrategy } from '../types';
|
|
3
|
+
import type { IDataProviderOptions } from '../types';
|
|
4
|
+
import { RequestProvider } from '../RequestProvider/RequestProvider';
|
|
5
|
+
|
|
6
|
+
export abstract class AAPIDataProvider {
|
|
7
|
+
|
|
8
|
+
apiUrl: Ref<string> = ref("")
|
|
9
|
+
cacheStrategy: Ref<ECacheStrategy | null> = ref(null)
|
|
10
|
+
cacheLifetime: Ref<number> = ref(60 * 1000)
|
|
11
|
+
requestProvider: RequestProvider = new RequestProvider()
|
|
12
|
+
refreshOnMutation: Ref<boolean | undefined> = ref(undefined)
|
|
13
|
+
|
|
14
|
+
constructor(options?: IDataProviderOptions) {
|
|
15
|
+
if (options?.cacheStrategy) this.cacheStrategy.value = options.cacheStrategy
|
|
16
|
+
if (options?.cacheLifetime) this.cacheLifetime.value = options.cacheLifetime
|
|
17
|
+
if (options?.refreshOnMutation !== undefined) this.refreshOnMutation.value = options.refreshOnMutation
|
|
18
|
+
}
|
|
19
|
+
abstract setAPIUrl(url: string): void
|
|
20
|
+
abstract create(item: any): Promise<void>
|
|
21
|
+
abstract update(item: any): Promise<void>
|
|
22
|
+
abstract delete(items: any[]): Promise<void>
|
|
23
|
+
}
|
|
24
|
+
|