@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,126 @@
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
+ }
@@ -0,0 +1,199 @@
1
+ import { ref, type Ref } from 'vue'
2
+ import type { IDataResult, IDataProviderOptions, ECacheStrategy } from '../types'
3
+ import { CFlatClientDataProvider } from './CFlatClientDataProvider';
4
+ import type { AAPIDataProvider } from './AAPIDataProvider';
5
+ import { RequestProvider } from '../RequestProvider/RequestProvider';
6
+
7
+
8
+ export class CAPIFlatClientDataProvider extends CFlatClientDataProvider implements AAPIDataProvider {
9
+
10
+ apiUrl: Ref<string> = ref("")
11
+ contextKey: Ref<string> = ref("")
12
+ initialLoad: Ref<boolean> = ref(true)
13
+ requestProvider: RequestProvider = new RequestProvider()
14
+ cacheStrategy: Ref<ECacheStrategy | null> = ref(null)
15
+ cacheLifetime: Ref<number> = ref(0)
16
+ refreshOnMutation: Ref<boolean | undefined> = ref(undefined)
17
+
18
+ constructor(options?: IDataProviderOptions) {
19
+ super(options)
20
+ if (options?.cacheStrategy) this.cacheStrategy.value = options.cacheStrategy
21
+ if (options?.cacheLifetime) this.cacheLifetime.value = options.cacheLifetime
22
+ if (options?.refreshOnMutation !== undefined) this.refreshOnMutation.value = options.refreshOnMutation
23
+ }
24
+ setContextKey(key: string) {
25
+ this.contextKey.value = key
26
+ this.requestProvider.cacheKey = key
27
+ if (this.cacheStrategy.value) this.requestProvider.setCacheProvider(this.cacheStrategy.value)
28
+ if (this.cacheLifetime.value) this.requestProvider.setCacheLifetime(this.cacheLifetime.value)
29
+ }
30
+ override async refreshData(options?: { disableCache?: boolean }): Promise<void> {
31
+ if (!this.SSR.value && import.meta.server) return;
32
+
33
+ const fetcher = async (): Promise<IDataResult> => {
34
+ // Register request - execution happens automatically
35
+ const promise = this.requestProvider.registerRequest<IDataResult>(this.apiUrl.value, {
36
+ deduplicate: this.deduplicate.value as boolean,
37
+ nuxtApp: this.nuxtApp,
38
+ disableCache: options?.disableCache
39
+ })
40
+
41
+ // If SSR is enabled and we're on server, await to ensure content is ready for rendering
42
+ if (this.SSR.value && import.meta.server) {
43
+ return await promise
44
+ }
45
+
46
+ // For non-SSR, return promise - useAsyncData will handle it
47
+ return promise
48
+ }
49
+ const key = `${this.contextKey.value}`
50
+
51
+ if (this.contextKey.value && this.initialLoad.value) {
52
+ const { data } = useNuxtData(key)
53
+ if (data.value) {
54
+ const result = data.value as IDataResult
55
+ this.setData(result.rows)
56
+ this.initialLoad.value = false
57
+ this.loading.value = false
58
+ return
59
+ }
60
+ }
61
+ if (import.meta.server && this.contextKey.value) {
62
+ const { data } = await useAsyncData(key, fetcher)
63
+ if (data.value) {
64
+ const result = data.value as IDataResult
65
+ this.setData(result.rows)
66
+ this.initialLoad.value = false
67
+ this.loading.value = false
68
+ return
69
+ }
70
+ }
71
+
72
+ // For non-SSR client-side, register and handle asynchronously
73
+ if (!this.SSR.value) {
74
+ const promise = this.requestProvider.registerRequest<IDataResult>(this.apiUrl.value, {
75
+ deduplicate: this.deduplicate.value as boolean,
76
+ nuxtApp: this.nuxtApp,
77
+ disableCache: options?.disableCache
78
+ })
79
+ // Don't await - deliver when finished
80
+ promise.then((returnedData) => {
81
+ this.initialLoad.value = false
82
+ this.loading.value = false
83
+ this.setData(returnedData.rows)
84
+ }).catch(() => {
85
+ this.initialLoad.value = false
86
+ this.loading.value = false
87
+ })
88
+ return
89
+ }
90
+
91
+ // SSR client-side or fallback: await the request
92
+ const returnedData: IDataResult = await fetcher()
93
+ this.initialLoad.value = false
94
+ this.loading.value = false
95
+ this.setData(returnedData.rows)
96
+ }
97
+ async setAPIUrl(url: string) {
98
+ this.loading.value = true
99
+ this.apiUrl.value = url
100
+ this.setContextKey(url)
101
+ if (this.allData.value.length == 0)
102
+ await this.refreshData()
103
+ }
104
+
105
+ async create(item: any): Promise<void> {
106
+ if (!this.apiUrl.value) return
107
+
108
+ const result = await this.requestProvider.registerRequest(this.apiUrl.value, {
109
+ method: 'POST',
110
+ body: item,
111
+ nuxtApp: this.nuxtApp
112
+ })
113
+
114
+ if (this.refreshOnMutation.value) {
115
+ await this.refreshData({ disableCache: true })
116
+ } else {
117
+ if (result && (result as any).item) {
118
+ this.allData.value.push((result as any).item)
119
+ this.loadPageData() // Refresh view
120
+ } else {
121
+ await this.refreshData({ disableCache: true })
122
+ }
123
+
124
+ if (this.requestProvider.cacheProvider) {
125
+ await this.requestProvider.cacheProvider.removeByPrefix(this.apiUrl.value)
126
+ }
127
+ if (this.contextKey.value) {
128
+ clearNuxtData(this.contextKey.value)
129
+ }
130
+ }
131
+ }
132
+
133
+ async update(item: any): Promise<void> {
134
+ if (!this.apiUrl.value) return
135
+
136
+ const result = await this.requestProvider.registerRequest(this.apiUrl.value, {
137
+ method: 'PUT',
138
+ body: item,
139
+ nuxtApp: this.nuxtApp
140
+ })
141
+
142
+ if (this.refreshOnMutation.value) {
143
+ await this.refreshData({ disableCache: true })
144
+ } else {
145
+ // Local update
146
+ // We don't have idKey property on CFlatClientDataProvider explicitly defined like Tree,
147
+ // assuming 'id' or we need to look at implementation.
148
+ // CFlatClientDataProvider doesn't enforce idKey. Assuming 'id' for now as standard.
149
+ const index = this.allData.value.findIndex(row => row['id'] === item['id'])
150
+ if (index !== -1) {
151
+ if (result && (result as any).item) {
152
+ this.allData.value[index] = (result as any).item
153
+ } else {
154
+ this.allData.value[index] = { ...this.allData.value[index], ...item }
155
+ }
156
+ this.loadPageData()
157
+ }
158
+
159
+ if (this.requestProvider.cacheProvider) {
160
+ await this.requestProvider.cacheProvider.removeByPrefix(this.apiUrl.value)
161
+ }
162
+ if (this.contextKey.value) {
163
+ clearNuxtData(this.contextKey.value)
164
+ }
165
+ }
166
+ }
167
+
168
+ async delete(items: any[]): Promise<void> {
169
+ if (!this.apiUrl.value) return
170
+ if (!items || items.length === 0) return
171
+
172
+ const ids = items.map(i => i.id)
173
+
174
+ // Use RequestProvider for the delete request
175
+ await this.requestProvider.registerRequest(this.apiUrl.value, {
176
+ method: 'DELETE',
177
+ body: { id: ids },
178
+ nuxtApp: this.nuxtApp
179
+ })
180
+
181
+ if (this.refreshOnMutation.value) {
182
+ // Refresh data from server with cache disabled
183
+ await this.refreshData({ disableCache: true })
184
+ } else {
185
+ // Optimization: Remove deleted items locally instead of refetching
186
+ const idsToDelete = new Set(ids)
187
+ this.allData.value = this.allData.value.filter(row => !idsToDelete.has(row["id"]))
188
+
189
+ // Clear the cache for this provider to prevent stale data on reload
190
+ if (this.requestProvider.cacheProvider) {
191
+ await this.requestProvider.cacheProvider.removeByPrefix(this.apiUrl.value)
192
+ }
193
+ // Also clear Nuxt's internal cache for this key
194
+ if (this.contextKey.value) {
195
+ clearNuxtData(this.contextKey.value)
196
+ }
197
+ }
198
+ }
199
+ }
@@ -0,0 +1,77 @@
1
+ import { ref, type Ref } from 'vue'
2
+ import type { IDataResult, IDataFilter, IDataSort, IDataProviderOptions, ECacheStrategy } from '../types'
3
+ import { CFlatServerDataProvider } from './CFlatServerDataProvider';
4
+ import type { AAPIDataProvider } from './AAPIDataProvider';
5
+ import { RequestProvider } from '../RequestProvider/RequestProvider';
6
+
7
+
8
+ export class CAPIFlatServerDataProvider extends CFlatServerDataProvider implements AAPIDataProvider {
9
+
10
+ apiUrl: Ref<string> = ref("")
11
+ requestProvider: RequestProvider = new RequestProvider()
12
+ cacheStrategy: Ref<ECacheStrategy | null> = ref(null)
13
+ cacheLifetime: Ref<number> = ref(0)
14
+ refreshOnMutation: Ref<boolean | undefined> = ref(true) // Default true for server
15
+
16
+ constructor(options?: IDataProviderOptions) {
17
+ super(options)
18
+ if (options?.cacheStrategy) this.cacheStrategy.value = options.cacheStrategy
19
+ if (options?.cacheLifetime) this.cacheLifetime.value = options.cacheLifetime
20
+ if (options?.refreshOnMutation !== undefined) this.refreshOnMutation.value = options.refreshOnMutation
21
+
22
+ }
23
+ async setAPIUrl(url: string) {
24
+ this.apiUrl.value = url
25
+ this.setContextKey(url)
26
+ if (this.cacheStrategy.value) this.requestProvider.setCacheProvider(this.cacheStrategy.value)
27
+ if (this.cacheLifetime.value) this.requestProvider.setCacheLifetime(this.cacheLifetime.value)
28
+
29
+ await this.setPageDataHandler(
30
+ async (
31
+ currentPage: number,
32
+ pageSize: number,
33
+ filter: IDataFilter,
34
+ sortList: IDataSort[],
35
+ options?: { disableCache?: boolean }
36
+ ): Promise<IDataResult> => {
37
+ // Register request - execution happens automatically
38
+ // Always await the promise - loadPageData will decide whether to await the fetcher
39
+ return await this.requestProvider.registerRequest<IDataResult>(this.apiUrl.value, {
40
+ query: {
41
+ pageNumber: currentPage,
42
+ pageSize: pageSize,
43
+ filter: filter,
44
+ sortList: sortList,
45
+ },
46
+ deduplicate: this.deduplicate.value as boolean,
47
+ nuxtApp: this.nuxtApp,
48
+ disableCache: options?.disableCache
49
+ })
50
+ }
51
+ )
52
+ }
53
+
54
+ async delete(items: any[]): Promise<void> {
55
+ if (!this.apiUrl.value) return
56
+ if (!items || items.length === 0) return
57
+
58
+ const ids = items.map(i => i.id)
59
+
60
+ // Use RequestProvider for the delete request
61
+ await this.requestProvider.registerRequest(this.apiUrl.value, {
62
+ method: 'DELETE',
63
+ body: { id: ids },
64
+ nuxtApp: this.nuxtApp
65
+ })
66
+
67
+ // Refresh data (refetch from server)
68
+ if (this.refreshOnMutation.value !== false) {
69
+ // Clear client-side cache for this resource
70
+ if (this.requestProvider.cacheProvider) {
71
+ await this.requestProvider.cacheProvider.removeByPrefix(this.apiUrl.value)
72
+ }
73
+
74
+ this.loadPageData({ disableCache: true })
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,205 @@
1
+ import { ref, type Ref } from 'vue'
2
+ import type { IDataResult, ITreeDataProviderOptions, ECacheStrategy } from '../types'
3
+ import { CTreeClientDataProvider } from './CTreeClientDataProvider'
4
+ import type { AAPIDataProvider } from './AAPIDataProvider'
5
+ import { RequestProvider } from '../RequestProvider/RequestProvider'
6
+
7
+
8
+ export class CAPITreeClientDataProvider extends CTreeClientDataProvider implements AAPIDataProvider {
9
+
10
+ apiUrl: Ref<string> = ref("")
11
+ contextKey: Ref<string> = ref("")
12
+ initialLoad: Ref<boolean> = ref(true)
13
+ requestProvider: RequestProvider = new RequestProvider()
14
+ cacheStrategy: Ref<ECacheStrategy | null> = ref(null)
15
+ cacheLifetime: Ref<number> = ref(0)
16
+ refreshOnMutation: Ref<boolean | undefined> = ref(undefined)
17
+
18
+ constructor(options?: ITreeDataProviderOptions) {
19
+ super(options)
20
+ if (options?.cacheStrategy) this.cacheStrategy.value = options.cacheStrategy
21
+ if (options?.cacheLifetime) this.cacheLifetime.value = options.cacheLifetime
22
+ if (options?.refreshOnMutation !== undefined) this.refreshOnMutation.value = options.refreshOnMutation
23
+ }
24
+
25
+ setContextKey(key: string) {
26
+ this.contextKey.value = key
27
+ this.requestProvider.cacheKey = key
28
+ if (this.cacheStrategy.value) this.requestProvider.setCacheProvider(this.cacheStrategy.value)
29
+ if (this.cacheLifetime.value) this.requestProvider.setCacheLifetime(this.cacheLifetime.value)
30
+ }
31
+
32
+ override async refreshData(options?: { disableCache?: boolean }): Promise<void> {
33
+ if (!this.SSR.value && import.meta.server) return;
34
+
35
+ const fetcher = async (): Promise<IDataResult> => {
36
+ // Register request - execution happens automatically
37
+ const promise = this.requestProvider.registerRequest<IDataResult>(this.apiUrl.value, {
38
+ deduplicate: this.deduplicate.value as boolean,
39
+ nuxtApp: this.nuxtApp,
40
+ disableCache: options?.disableCache
41
+ })
42
+
43
+ // If SSR is enabled and we're on server, await to ensure content is ready for rendering
44
+ if (this.SSR.value && import.meta.server) {
45
+ return await promise
46
+ }
47
+
48
+ // For non-SSR, return promise - useAsyncData will handle it
49
+ return promise
50
+ }
51
+ const key = `${this.contextKey.value}`
52
+
53
+ if (this.contextKey.value && this.initialLoad.value) {
54
+ const { data } = useNuxtData(key)
55
+ if (data.value) {
56
+ const result = data.value as IDataResult
57
+ this.setData(result.rows)
58
+ this.initialLoad.value = false
59
+ this.loading.value = false
60
+ return
61
+ }
62
+ }
63
+ if (import.meta.server && this.contextKey.value) {
64
+ const { data } = await useAsyncData(key, fetcher)
65
+ if (data.value) {
66
+ const result = data.value as IDataResult
67
+ this.setData(result.rows)
68
+ this.initialLoad.value = false
69
+ this.loading.value = false
70
+ return
71
+ }
72
+ }
73
+
74
+ // For non-SSR client-side, register and handle asynchronously
75
+ if (!this.SSR.value) {
76
+ const promise = this.requestProvider.registerRequest<IDataResult>(this.apiUrl.value, {
77
+ deduplicate: this.deduplicate.value as boolean,
78
+ nuxtApp: this.nuxtApp,
79
+ disableCache: options?.disableCache
80
+ })
81
+ // Don't await - deliver when finished
82
+ promise.then((returnedData) => {
83
+ this.initialLoad.value = false
84
+ this.loading.value = false
85
+ this.setData(returnedData.rows)
86
+ }).catch(() => {
87
+ this.initialLoad.value = false
88
+ this.loading.value = false
89
+ })
90
+ return
91
+ }
92
+
93
+ // SSR client-side or fallback: await the request
94
+ const returnedData: IDataResult = await fetcher()
95
+ this.initialLoad.value = false
96
+ this.loading.value = false
97
+ this.setData(returnedData.rows)
98
+ }
99
+
100
+ async setAPIUrl(url: string) {
101
+ this.loading.value = true
102
+ this.apiUrl.value = url
103
+ this.setContextKey(url)
104
+ if (this.allData.value.length == 0)
105
+ await this.refreshData()
106
+ }
107
+
108
+ async create(item: any): Promise<void> {
109
+ if (!this.apiUrl.value) return
110
+
111
+ // Use RequestProvider for the create request
112
+ const result = await this.requestProvider.registerRequest(this.apiUrl.value, {
113
+ method: 'POST',
114
+ body: item,
115
+ nuxtApp: this.nuxtApp
116
+ })
117
+
118
+ if (this.refreshOnMutation.value) {
119
+ await this.refreshData({ disableCache: true })
120
+ } else {
121
+ // Optimistic UI update or fetch fresh data
122
+ if (result && (result as any).item) {
123
+ this.allData.value.push((result as any).item)
124
+ } else {
125
+ // Fallback if no item returned
126
+ await this.refreshData({ disableCache: true })
127
+ }
128
+
129
+ // Clear cache
130
+ if (this.requestProvider.cacheProvider) {
131
+ await this.requestProvider.cacheProvider.removeByPrefix(this.apiUrl.value)
132
+ }
133
+ if (this.contextKey.value) {
134
+ clearNuxtData(this.contextKey.value)
135
+ }
136
+ }
137
+ }
138
+
139
+ async update(item: any): Promise<void> {
140
+ if (!this.apiUrl.value) return
141
+
142
+ // Use RequestProvider for the update request
143
+ const result = await this.requestProvider.registerRequest(this.apiUrl.value, {
144
+ method: 'PUT',
145
+ body: item,
146
+ nuxtApp: this.nuxtApp
147
+ })
148
+
149
+ if (this.refreshOnMutation.value) {
150
+ await this.refreshData({ disableCache: true })
151
+ } else {
152
+ // Local update
153
+ const index = this.allData.value.findIndex(row => row[this.idKey] === item[this.idKey])
154
+ if (index !== -1) {
155
+ if (result && (result as any).item) {
156
+ this.allData.value[index] = (result as any).item
157
+ } else {
158
+ this.allData.value[index] = { ...this.allData.value[index], ...item }
159
+ }
160
+ // Trigger reactivity
161
+ this.allData.value = [...this.allData.value]
162
+ }
163
+
164
+ // Clear cache
165
+ if (this.requestProvider.cacheProvider) {
166
+ await this.requestProvider.cacheProvider.removeByPrefix(this.apiUrl.value)
167
+ }
168
+ if (this.contextKey.value) {
169
+ clearNuxtData(this.contextKey.value)
170
+ }
171
+ }
172
+ }
173
+
174
+ async delete(items: any[]): Promise<void> {
175
+ if (!this.apiUrl.value) return
176
+ if (!items || items.length === 0) return
177
+
178
+ const ids = items.map(i => i.id)
179
+
180
+ // Use RequestProvider for the delete request
181
+ await this.requestProvider.registerRequest(this.apiUrl.value, {
182
+ method: 'DELETE',
183
+ body: { id: ids },
184
+ nuxtApp: this.nuxtApp
185
+ })
186
+
187
+ if (this.refreshOnMutation.value) {
188
+ // Refresh data from server with cache disabled
189
+ await this.refreshData({ disableCache: true })
190
+ } else {
191
+ // Optimization: Remove deleted items locally instead of refetching
192
+ const idsToDelete = new Set(ids)
193
+ this.allData.value = this.allData.value.filter(row => !idsToDelete.has(row[this.idKey])) // Use this.idKey
194
+
195
+ // Clear the cache for this provider to prevent stale data on reload
196
+ if (this.requestProvider.cacheProvider) {
197
+ await this.requestProvider.cacheProvider.removeByPrefix(this.apiUrl.value)
198
+ }
199
+ // Also clear Nuxt's internal cache for this key
200
+ if (this.contextKey.value) {
201
+ clearNuxtData(this.contextKey.value)
202
+ }
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,98 @@
1
+ import { ref, type Ref } from 'vue'
2
+ import type { IDataResult, IDataFilter, IDataSort, ITreeDataProviderOptions, ECacheStrategy, TreePaginationMode } from '../types'
3
+ import { CTreeServerDataProvider } from './CTreeServerDataProvider';
4
+ import type { AAPIDataProvider } from './AAPIDataProvider';
5
+ import { RequestProvider } from '../RequestProvider/RequestProvider';
6
+
7
+
8
+ /**
9
+ * API-based Server-side Tree Data Provider
10
+ *
11
+ * Extends CTreeServerDataProvider with:
12
+ * - API URL configuration
13
+ * - Request management (caching, deduplication)
14
+ * - Automatic tree parameter serialization in query
15
+ */
16
+ export class CAPITreeServerDataProvider extends CTreeServerDataProvider implements AAPIDataProvider {
17
+
18
+ apiUrl: Ref<string> = ref("")
19
+ requestProvider: RequestProvider = new RequestProvider()
20
+ cacheStrategy: Ref<ECacheStrategy | null> = ref(null)
21
+ cacheLifetime: Ref<number> = ref(0)
22
+ refreshOnMutation: Ref<boolean | undefined> = ref(true) // Default true for server
23
+
24
+ constructor(options?: ITreeDataProviderOptions) {
25
+ super(options)
26
+ if (options?.cacheStrategy) this.cacheStrategy.value = options.cacheStrategy
27
+ if (options?.cacheLifetime) this.cacheLifetime.value = options.cacheLifetime
28
+ if (options?.refreshOnMutation !== undefined) this.refreshOnMutation.value = options.refreshOnMutation
29
+ }
30
+
31
+ async setAPIUrl(url: string) {
32
+ this.apiUrl.value = url
33
+ this.setContextKey(url)
34
+ if (this.cacheStrategy.value) this.requestProvider.setCacheProvider(this.cacheStrategy.value)
35
+ if (this.cacheLifetime.value) this.requestProvider.setCacheLifetime(this.cacheLifetime.value)
36
+
37
+ await this.setPageDataHandler(
38
+ async (
39
+ currentPage: number,
40
+ pageSize: number,
41
+ filter: IDataFilter,
42
+ sortList: IDataSort[],
43
+ treeParams: {
44
+ expandedNodes: (string | number)[],
45
+ parentKey: string,
46
+ idKey: string,
47
+ paginateBy: TreePaginationMode,
48
+ expandedByDefault: boolean
49
+ },
50
+ options?: { disableCache?: boolean }
51
+ ): Promise<IDataResult> => {
52
+ // Send all parameters to server for server-side tree pagination
53
+ return await this.requestProvider.registerRequest<IDataResult>(this.apiUrl.value, {
54
+ query: {
55
+ pageNumber: currentPage,
56
+ pageSize: pageSize,
57
+ filter: filter,
58
+ sortList: sortList,
59
+ // Tree-specific params for server-side processing
60
+ expandedNodes: JSON.stringify(treeParams.expandedNodes),
61
+ parentKey: treeParams.parentKey,
62
+ idKey: treeParams.idKey,
63
+ paginateBy: treeParams.paginateBy,
64
+ expandedByDefault: treeParams.expandedByDefault
65
+ },
66
+ deduplicate: this.deduplicate.value as boolean,
67
+ nuxtApp: this.nuxtApp,
68
+ disableCache: options?.disableCache
69
+ })
70
+ }
71
+ )
72
+ }
73
+
74
+ async delete(items: any[]): Promise<void> {
75
+ if (!this.apiUrl.value) return
76
+ if (!items || items.length === 0) return
77
+
78
+ const ids = items.map(i => i.id)
79
+
80
+ // Use RequestProvider for the delete request
81
+ await this.requestProvider.registerRequest(this.apiUrl.value, {
82
+ method: 'DELETE',
83
+ body: { id: ids },
84
+ nuxtApp: this.nuxtApp
85
+ })
86
+
87
+ if (this.refreshOnMutation.value !== false) {
88
+ // Clear client-side cache for this resource to ensure consistency (e.g. other pages/filters)
89
+ if (this.requestProvider.cacheProvider) {
90
+ await this.requestProvider.cacheProvider.removeByPrefix(this.apiUrl.value)
91
+ }
92
+
93
+ // Trigger reload which calls the setPageDataHandler defined above
94
+ // Disable cache to ensure fresh data
95
+ this.loadPageData({ disableCache: true })
96
+ }
97
+ }
98
+ }