@katlux/providers 0.1.0-beta.0 → 0.1.0-beta.11

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.
Files changed (30) hide show
  1. package/dist/index.cjs +5496 -0
  2. package/dist/index.d.cts +1861 -0
  3. package/dist/index.d.mts +1861 -0
  4. package/dist/index.d.ts +1861 -0
  5. package/dist/index.mjs +5475 -0
  6. package/package.json +7 -3
  7. package/build.config.ts +0 -4
  8. package/services/CacheProvider/ACacheProvider.ts +0 -13
  9. package/services/CacheProvider/CApplicationCache.ts +0 -123
  10. package/services/CacheProvider/CCookieCache.ts +0 -212
  11. package/services/CacheProvider/CIndexedDBCache.ts +0 -312
  12. package/services/CacheProvider/CLocalStorageCache.ts +0 -232
  13. package/services/CacheProvider/CMemoryCache.ts +0 -185
  14. package/services/CacheProvider/CSessionCache.ts +0 -378
  15. package/services/CacheProvider/CacheProviderFactory.ts +0 -22
  16. package/services/DataProvider/AAPIDataProvider.ts +0 -24
  17. package/services/DataProvider/ADataProvider.ts +0 -126
  18. package/services/DataProvider/CAPIFlatClientDataProvider.ts +0 -199
  19. package/services/DataProvider/CAPIFlatServerDataProvider.ts +0 -77
  20. package/services/DataProvider/CAPITreeClientDataProvider.ts +0 -205
  21. package/services/DataProvider/CAPITreeServerDataProvider.ts +0 -98
  22. package/services/DataProvider/CFlatClientDataProvider.ts +0 -52
  23. package/services/DataProvider/CFlatServerDataProvider.ts +0 -104
  24. package/services/DataProvider/CTreeClientDataProvider.ts +0 -335
  25. package/services/DataProvider/CTreeServerDataProvider.ts +0 -207
  26. package/services/RequestProvider/RequestProvider.ts +0 -165
  27. package/services/RequestProvider/serverCache.ts +0 -24
  28. package/services/index.ts +0 -19
  29. package/services/types.ts +0 -172
  30. package/src/index.ts +0 -1
@@ -1,378 +0,0 @@
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
- }
@@ -1,22 +0,0 @@
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
- }
@@ -1,24 +0,0 @@
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
-
@@ -1,126 +0,0 @@
1
- import { ref, type Ref } from 'vue'
2
- import type { TDataRow, IDataFilter, IDataSort, IDataResult, IKDatatableAction, IDataProviderOptions } from '../types'
3
- import { useDebounce } from '@katlux/toolkit/composables/useDebounce'
4
-
5
- // Note: In an isomorphic package, we must handle Nuxt auto-imports carefully.
6
- // Standard Vue ref and watch are explicitly imported.
7
- import { watch } from 'vue'
8
- const APP_MODULE = '#app';
9
- // DataProvider abstract class
10
- export abstract class ADataProvider {
11
- filter: Ref<IDataFilter | null> = ref(null)
12
- sortList: Ref<IDataSort[]> = ref([])
13
- currentPage: Ref<number> = ref(1)
14
- pageSize: Ref<number> = ref(10)
15
- pageData: Ref<TDataRow[]> = ref([])
16
- rowCount: Ref<number> = ref(0)
17
- loading: Ref<Boolean> = ref(false)
18
- SSR: Ref<Boolean> = ref(false)
19
- deduplicate: Ref<Boolean> = ref(true)
20
- nuxtApp: any = null
21
- urlPageParam: Ref<string> = ref('')
22
- selectedRows: Ref<any[]> = ref([])
23
- selectAll: Ref<boolean> = ref(false)
24
-
25
- constructor(options?: IDataProviderOptions) {
26
- if (options?.nuxtApp) {
27
- this.nuxtApp = options.nuxtApp
28
- } else if (import.meta.server) {
29
- try {
30
- import(/* @vite-ignore */ APP_MODULE).then(appModule => {
31
- this.nuxtApp = appModule.useNuxtApp()
32
- }).catch(() => { })
33
- } catch (e) {
34
- // Outside nuxt context
35
- }
36
- }
37
-
38
- if (options?.filter) {
39
- this.filter.value = options.filter
40
- }
41
- if (options?.sortList) {
42
- this.sortList.value = options.sortList
43
- }
44
- if (options?.pageSize) {
45
- this.pageSize.value = options.pageSize
46
- }
47
- if (options?.currentPage) {
48
- this.currentPage.value = options.currentPage
49
- }
50
- if (options?.SSR) {
51
- this.SSR.value = options.SSR
52
- }
53
- if (options?.deduplicate !== undefined) {
54
- this.deduplicate.value = options.deduplicate
55
- }
56
-
57
- // URL-based pagination: read initial page from URL
58
- if (options?.urlPageParam) {
59
- this.urlPageParam.value = options.urlPageParam
60
- try {
61
- import(/* @vite-ignore */ APP_MODULE).then(appModule => {
62
- const route = appModule.useRoute()
63
- const pageFromUrl = route.query[options.urlPageParam!]
64
- if (pageFromUrl) {
65
- const parsedPage = parseInt(pageFromUrl as string, 10)
66
- if (!isNaN(parsedPage) && parsedPage > 0) {
67
- this.currentPage.value = parsedPage
68
- }
69
- }
70
- }).catch(() => { })
71
- } catch (e) {
72
- // useRoute not available (outside Vue context)
73
- }
74
- }
75
-
76
- // Watch for changes in currentPage and pageSize to automatically reload data
77
- watch(this.currentPage, (newPage) => {
78
- this.loadPageData()
79
-
80
- // URL-based pagination: update URL when page changes (client-side only)
81
- if (import.meta.client && this.urlPageParam.value) {
82
- try {
83
- import(/* @vite-ignore */ APP_MODULE).then(appModule => {
84
- const router = appModule.useRouter()
85
- const currentQuery = { ...router.currentRoute.value.query }
86
- if (newPage === 1) {
87
- delete currentQuery[this.urlPageParam.value]
88
- } else {
89
- currentQuery[this.urlPageParam.value] = String(newPage)
90
- }
91
- router.replace({ query: currentQuery })
92
- }).catch(() => { })
93
- } catch (e) {
94
- // useRouter not available
95
- }
96
- }
97
- })
98
-
99
- watch(this.pageSize, () => {
100
- this.currentPage.value = 1
101
- this.loadPageData()
102
- })
103
-
104
- watch(this.filter, useDebounce(() => {
105
- this.currentPage.value = 1
106
- this.loadPageData()
107
- }, 300))
108
- watch(this.sortList, () => {
109
- this.currentPage.value = 1
110
- this.loadPageData()
111
- })
112
- }
113
- setFilter(filter: IDataFilter | null) {
114
- this.filter.value = filter
115
- }
116
- setSortList(sortList: IDataSort[]) {
117
- this.sortList.value = sortList
118
- }
119
- setCurrentPage(currentPage: number) {
120
- this.currentPage.value = currentPage
121
- }
122
- setPageSize(pageSize: number) {
123
- this.pageSize.value = pageSize
124
- }
125
- abstract loadPageData(): void
126
- }