@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,165 @@
|
|
|
1
|
+
import { ECacheStrategy, type IRequestOptions } from '../types'
|
|
2
|
+
import { CacheProviderFactory } from '../CacheProvider/CacheProviderFactory'
|
|
3
|
+
import { ACacheProvider } from '../CacheProvider/ACacheProvider'
|
|
4
|
+
|
|
5
|
+
interface IQueuedRequest {
|
|
6
|
+
url: string
|
|
7
|
+
options?: IRequestOptions
|
|
8
|
+
instance: RequestProvider
|
|
9
|
+
nuxtApp?: any
|
|
10
|
+
resolve?: (value: unknown) => void
|
|
11
|
+
reject?: (error: Error) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class RequestProvider {
|
|
15
|
+
cacheProvider: ACacheProvider | null = null
|
|
16
|
+
cacheLifetime: number | null = 60 * 60 * 1000
|
|
17
|
+
deduplicate: boolean = true
|
|
18
|
+
cacheKey: string = ""
|
|
19
|
+
private static requestQueue: IQueuedRequest[] = []
|
|
20
|
+
private static executing: boolean = false
|
|
21
|
+
private static requestsInFlight: Map<string, Promise<any>> = new Map()
|
|
22
|
+
|
|
23
|
+
constructor(provider?: ACacheProvider | ECacheStrategy, lifetime?: number, deduplicate?: boolean) {
|
|
24
|
+
if (provider) this.setCacheProvider(provider)
|
|
25
|
+
if (lifetime) this.setCacheLifetime(lifetime)
|
|
26
|
+
if (deduplicate !== undefined) this.setDeduplicate(deduplicate)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setCacheProvider(provider?: ACacheProvider | ECacheStrategy): void {
|
|
30
|
+
if (!provider) {
|
|
31
|
+
console.warn("RequestProvider: provider is required")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (provider instanceof ACacheProvider) this.cacheProvider = provider
|
|
35
|
+
else this.cacheProvider = CacheProviderFactory.getProvider(provider as ECacheStrategy)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setCacheLifetime(lifetime: number): void {
|
|
39
|
+
this.cacheLifetime = lifetime
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setDeduplicate(deduplicate: boolean): void {
|
|
43
|
+
this.deduplicate = deduplicate
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private getUniqueKey(url: string, options?: IRequestOptions): string {
|
|
47
|
+
if (options?.key) return options.key
|
|
48
|
+
const { query, body, headers, method } = options || {}
|
|
49
|
+
|
|
50
|
+
// Stable stringification: treats undefined as null for consistency
|
|
51
|
+
const stableStringify = (val: any) => JSON.stringify(val === undefined ? null : val)
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
url,
|
|
55
|
+
stableStringify(query || {}),
|
|
56
|
+
stableStringify(body || null),
|
|
57
|
+
stableStringify(headers || {}),
|
|
58
|
+
method || 'GET'
|
|
59
|
+
].join('|')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
registerRequest<T>(url: string, options?: IRequestOptions): Promise<T> {
|
|
63
|
+
const uniqueKey = this.getUniqueKey(url, options)
|
|
64
|
+
const shouldDeduplicate = options?.deduplicate ?? this.deduplicate
|
|
65
|
+
|
|
66
|
+
if (shouldDeduplicate && RequestProvider.requestsInFlight.has(uniqueKey)) {
|
|
67
|
+
return RequestProvider.requestsInFlight.get(uniqueKey) as Promise<T>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Context is optional here, we'll try to get it during execution if needed
|
|
71
|
+
const promise = new Promise<T>((resolve, reject) => {
|
|
72
|
+
RequestProvider.requestQueue.push({
|
|
73
|
+
url,
|
|
74
|
+
options,
|
|
75
|
+
instance: this,
|
|
76
|
+
resolve: resolve as (value: unknown) => void,
|
|
77
|
+
reject: reject as (error: Error) => void
|
|
78
|
+
})
|
|
79
|
+
RequestProvider.executeAll()
|
|
80
|
+
}).finally(() => {
|
|
81
|
+
if (shouldDeduplicate) {
|
|
82
|
+
RequestProvider.requestsInFlight.delete(uniqueKey)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if (shouldDeduplicate) {
|
|
87
|
+
RequestProvider.requestsInFlight.set(uniqueKey, promise)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return promise
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static executeAll(): void {
|
|
94
|
+
if (RequestProvider.executing) return
|
|
95
|
+
if (RequestProvider.requestQueue.length === 0) return
|
|
96
|
+
|
|
97
|
+
RequestProvider.executing = true
|
|
98
|
+
const queue = [...RequestProvider.requestQueue]
|
|
99
|
+
RequestProvider.requestQueue = []
|
|
100
|
+
|
|
101
|
+
queue.forEach((queuedRequest) => {
|
|
102
|
+
queuedRequest.instance.request(queuedRequest.url, queuedRequest.options)
|
|
103
|
+
.then((result) => queuedRequest.resolve?.(result))
|
|
104
|
+
.catch((error) => queuedRequest.reject?.(error instanceof Error ? error : new Error(String(error))))
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
Promise.resolve().then(() => {
|
|
108
|
+
RequestProvider.executing = false
|
|
109
|
+
if (RequestProvider.requestQueue.length > 0) {
|
|
110
|
+
RequestProvider.executeAll()
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async request<T>(url: string, options?: IRequestOptions): Promise<T> {
|
|
116
|
+
const { cacheStrategy, lifetime, query, body, headers, method } = options || {}
|
|
117
|
+
const uniqueKey = this.getUniqueKey(url, options)
|
|
118
|
+
|
|
119
|
+
// Get context if possible (environment agnostic)
|
|
120
|
+
const nuxtApp = options?.nuxtApp
|
|
121
|
+
let serverHeaders = {}
|
|
122
|
+
|
|
123
|
+
if (nuxtApp && import.meta.server) {
|
|
124
|
+
const req = (nuxtApp as any).node?.req || (nuxtApp as any).ssrContext?.event?.node?.req
|
|
125
|
+
if (req?.headers?.cookie) {
|
|
126
|
+
serverHeaders = { cookie: req.headers.cookie }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const provider = cacheStrategy ? CacheProviderFactory.getProvider(cacheStrategy) : this.cacheProvider
|
|
131
|
+
const cacheLifetime = lifetime || this.cacheLifetime
|
|
132
|
+
|
|
133
|
+
// Use 'GET' as default if method is undefined
|
|
134
|
+
const requestMethod = method || 'GET'
|
|
135
|
+
const isGetRequest = requestMethod === 'GET'
|
|
136
|
+
|
|
137
|
+
// 1. Check Cache (Only for GET requests and if NOT disabled)
|
|
138
|
+
if (provider && isGetRequest && !options?.disableCache) {
|
|
139
|
+
const cached = await provider.get<T>(uniqueKey, nuxtApp)
|
|
140
|
+
if (cached) {
|
|
141
|
+
console.log(`[RequestProvider] CACHE HIT: ${url}`)
|
|
142
|
+
return cached
|
|
143
|
+
}
|
|
144
|
+
console.log(`[RequestProvider] CACHE MISS: ${url}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
// 2. Perform Request
|
|
149
|
+
const fetchOptions: any = {
|
|
150
|
+
method: requestMethod,
|
|
151
|
+
body: body,
|
|
152
|
+
headers: { ...headers, ...serverHeaders },
|
|
153
|
+
query: query
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// $fetch is universal in Nuxt 3
|
|
157
|
+
const responseData = await ($fetch as any)(url, fetchOptions) as T
|
|
158
|
+
|
|
159
|
+
// 3. Save to Cache (Only for GET requests)
|
|
160
|
+
if (provider && cacheLifetime && responseData && isGetRequest) {
|
|
161
|
+
await provider.set(uniqueKey, responseData, cacheLifetime, nuxtApp)
|
|
162
|
+
}
|
|
163
|
+
return responseData
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ICacheEntry } from './types'
|
|
2
|
+
|
|
3
|
+
// Global cache storage for Application strategy (Server Side Only)
|
|
4
|
+
const applicationCache = new Map<string, ICacheEntry<any>>()
|
|
5
|
+
|
|
6
|
+
export const getServerCache = <T>(key: string): T | null => {
|
|
7
|
+
const entry = applicationCache.get(key)
|
|
8
|
+
if (!entry) return null
|
|
9
|
+
|
|
10
|
+
if (Date.now() - entry.timestamp > entry.lifetime) {
|
|
11
|
+
applicationCache.delete(key)
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return entry.data as T
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const setServerCache = <T>(key: string, data: T, lifetime: number) => {
|
|
19
|
+
applicationCache.set(key, {
|
|
20
|
+
data,
|
|
21
|
+
timestamp: Date.now(),
|
|
22
|
+
lifetime
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from './types'
|
|
2
|
+
export * from './DataProvider/CFlatClientDataProvider'
|
|
3
|
+
export * from './DataProvider/CAPIFlatClientDataProvider'
|
|
4
|
+
export * from './DataProvider/CAPIFlatServerDataProvider'
|
|
5
|
+
export * from './DataProvider/ADataProvider'
|
|
6
|
+
export * from './DataProvider/AAPIDataProvider'
|
|
7
|
+
export * from './DataProvider/CFlatServerDataProvider'
|
|
8
|
+
export * from './DataProvider/CTreeClientDataProvider'
|
|
9
|
+
export * from './DataProvider/CAPITreeClientDataProvider'
|
|
10
|
+
export * from './DataProvider/CTreeServerDataProvider'
|
|
11
|
+
export * from './DataProvider/CAPITreeServerDataProvider'
|
|
12
|
+
export * from './CacheProvider/CacheProviderFactory'
|
|
13
|
+
export * from './CacheProvider/CSessionCache'
|
|
14
|
+
export * from './CacheProvider/CCookieCache'
|
|
15
|
+
export * from './CacheProvider/CLocalStorageCache'
|
|
16
|
+
export * from './CacheProvider/CIndexedDBCache'
|
|
17
|
+
export * from './CacheProvider/CMemoryCache'
|
|
18
|
+
export * from './CacheProvider/CApplicationCache'
|
|
19
|
+
export * from './RequestProvider/RequestProvider'
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Cache-related Types
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
export enum ECacheStrategy {
|
|
8
|
+
Application = 'Application',
|
|
9
|
+
Session = 'Session',
|
|
10
|
+
Memory = 'Memory',
|
|
11
|
+
LocalStorage = 'LocalStorage',
|
|
12
|
+
IndexedDB = 'IndexedDB',
|
|
13
|
+
Cookie = 'Cookie'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ICacheEntry<T> {
|
|
17
|
+
data: T
|
|
18
|
+
timestamp: number
|
|
19
|
+
lifetime: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Request Provider Types
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
export interface IRequestOptions {
|
|
27
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
28
|
+
body?: any
|
|
29
|
+
headers?: Record<string, string>
|
|
30
|
+
cacheStrategy?: ECacheStrategy
|
|
31
|
+
lifetime?: number // milliseconds
|
|
32
|
+
key?: string // Unique key for caching, required if cacheStrategy is used
|
|
33
|
+
cacheKey?: string // context/namespace key for shared cache storage
|
|
34
|
+
query?: Record<string, any>
|
|
35
|
+
deduplicate?: boolean
|
|
36
|
+
nuxtApp?: any
|
|
37
|
+
disableCache?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Data Provider Types
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
export interface IKDatatableAction {
|
|
45
|
+
action: Function
|
|
46
|
+
label: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// DataRow is a map for a data row
|
|
50
|
+
export type TDataRow = { [key: string]: any }
|
|
51
|
+
|
|
52
|
+
export enum EDataFilterOperator {
|
|
53
|
+
Equal = "==",
|
|
54
|
+
NotEqual = "!=",
|
|
55
|
+
GreaterThan = ">",
|
|
56
|
+
LessThan = "<",
|
|
57
|
+
GreaterThanOrEqual = ">=",
|
|
58
|
+
LessThanOrEqual = "<=",
|
|
59
|
+
In = "in",
|
|
60
|
+
NotIn = "nin",
|
|
61
|
+
And = "and",
|
|
62
|
+
Or = "or",
|
|
63
|
+
Nand = "nand",
|
|
64
|
+
Nor = "nor"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// DataFilter interface for filtering data
|
|
68
|
+
export interface IDataFilter {
|
|
69
|
+
operator: EDataFilterOperator
|
|
70
|
+
field: TDataRow | IDataFilter[]
|
|
71
|
+
active: Boolean
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// DataSort interface for sorting data
|
|
75
|
+
export interface IDataSort {
|
|
76
|
+
field: string
|
|
77
|
+
direction: 'asc' | 'desc'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// DataResult interface for the result
|
|
81
|
+
export interface IDataResult {
|
|
82
|
+
rowCount: number
|
|
83
|
+
rows: TDataRow[]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Function type for getting a page of data
|
|
87
|
+
export type TPageDataHandler = (currentPage: number, pageSize: number, filter: IDataFilter, sortList: IDataSort[], options?: { disableCache?: boolean }) => IDataResult | Promise<IDataResult>;
|
|
88
|
+
|
|
89
|
+
export interface IDataProviderOptions {
|
|
90
|
+
filter?: IDataFilter
|
|
91
|
+
sortList?: IDataSort[]
|
|
92
|
+
pageSize?: number
|
|
93
|
+
currentPage?: number
|
|
94
|
+
SSR?: boolean
|
|
95
|
+
cacheStrategy?: ECacheStrategy
|
|
96
|
+
cacheLifetime?: number
|
|
97
|
+
deduplicate?: boolean
|
|
98
|
+
nuxtApp?: any
|
|
99
|
+
urlPageParam?: string // URL query param key for page sync (e.g., 'page', 'p')
|
|
100
|
+
refreshOnMutation?: boolean
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Tree Data Provider Types
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
export interface ITreeNode {
|
|
108
|
+
data: TDataRow
|
|
109
|
+
id: string | number
|
|
110
|
+
parentId: string | number | null
|
|
111
|
+
depth: number
|
|
112
|
+
hasChildren: boolean
|
|
113
|
+
isExpanded: boolean
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Internal node without expansion state (static structure)
|
|
117
|
+
export interface ITreeNodeStatic {
|
|
118
|
+
data: TDataRow
|
|
119
|
+
id: string | number
|
|
120
|
+
parentId: string | number | null
|
|
121
|
+
depth: number
|
|
122
|
+
hasChildren: boolean
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type TreePaginationMode = 'all' | 'root'
|
|
126
|
+
|
|
127
|
+
export interface ITreeDataProviderOptions extends IDataProviderOptions {
|
|
128
|
+
parentKey?: string
|
|
129
|
+
idKey?: string
|
|
130
|
+
expandedByDefault?: boolean
|
|
131
|
+
paginateBy?: TreePaginationMode
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Tree-specific page data handler that includes tree parameters
|
|
136
|
+
* Server handles tree building, visibility, and pagination
|
|
137
|
+
*/
|
|
138
|
+
export type TTreePageDataHandler = (
|
|
139
|
+
currentPage: number,
|
|
140
|
+
pageSize: number,
|
|
141
|
+
filter: IDataFilter,
|
|
142
|
+
sortList: IDataSort[],
|
|
143
|
+
treeParams: {
|
|
144
|
+
expandedNodes: (string | number)[],
|
|
145
|
+
parentKey: string,
|
|
146
|
+
idKey: string,
|
|
147
|
+
paginateBy: TreePaginationMode,
|
|
148
|
+
expandedByDefault: boolean
|
|
149
|
+
},
|
|
150
|
+
options?: { disableCache?: boolean }
|
|
151
|
+
) => IDataResult | Promise<IDataResult>;
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Product Types
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
export interface KProductItemData {
|
|
158
|
+
id: string | number
|
|
159
|
+
title: string
|
|
160
|
+
image?: string
|
|
161
|
+
price?: number
|
|
162
|
+
currency?: string
|
|
163
|
+
description?: string
|
|
164
|
+
[key: string]: any
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface KProductRowAction {
|
|
168
|
+
label: string
|
|
169
|
+
icon?: string
|
|
170
|
+
action: (item: KProductItemData) => void
|
|
171
|
+
color?: 'primary' | 'danger' | 'success' | 'warning' | 'info' | 'light' | 'dark' | 'default'
|
|
172
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../services/index';
|