@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.
@@ -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';