@svadmin/core 0.49.1 → 0.52.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/README.md +32 -0
- package/package.json +21 -4
- package/src/access-control-contract.ts +250 -0
- package/src/adapters/casbin.ts +20 -14
- package/src/adapters/casl.ts +22 -19
- package/src/admin-tool.ts +7 -7
- package/src/audit-contract.ts +105 -0
- package/src/audit.ts +88 -128
- package/src/auth-hooks.svelte.ts +593 -261
- package/src/auth-mutation-contract.ts +143 -0
- package/src/auth-query-contract.ts +97 -0
- package/src/captured-mutation.svelte.ts +45 -0
- package/src/chatProvider.svelte.ts +1 -1
- package/src/command-contract.ts +195 -0
- package/src/context.svelte.ts +217 -205
- package/src/contract-context.svelte.ts +37 -0
- package/src/contract-form.svelte.ts +566 -0
- package/src/create-contract.ts +56 -0
- package/src/create-hooks.svelte.ts +347 -0
- package/src/create-many-contract.ts +90 -0
- package/src/create-many-hooks.svelte.ts +396 -0
- package/src/data-transfer.svelte.ts +1 -215
- package/src/defined-options.ts +50 -0
- package/src/delete-contract.ts +50 -0
- package/src/delete-hooks.svelte.ts +403 -0
- package/src/delete-many-contract.ts +60 -0
- package/src/delete-many-hooks.svelte.ts +402 -0
- package/src/enterprise.ts +4 -4
- package/src/export-format.ts +16 -14
- package/src/export-hooks.svelte.ts +191 -0
- package/src/feature-gate-contract.ts +53 -0
- package/src/hook-utils.svelte.ts +53 -105
- package/src/hooks.svelte.ts +19 -774
- package/src/http.ts +88 -87
- package/src/i18n.svelte.ts +56 -2
- package/src/import-contract.ts +106 -0
- package/src/import-hooks.svelte.ts +310 -0
- package/src/index.ts +77 -28
- package/src/inferencer-graphql.ts +7 -6
- package/src/infinite-query.svelte.ts +228 -0
- package/src/invalidation-contract.ts +26 -0
- package/src/live-sse.ts +91 -127
- package/src/live-subscription.svelte.ts +130 -0
- package/src/live-transport.ts +127 -0
- package/src/live-websocket.ts +85 -118
- package/src/live.svelte.ts +66 -68
- package/src/notification.svelte.ts +17 -15
- package/src/permission-catalog.ts +121 -0
- package/src/permission-hints-contract.ts +48 -0
- package/src/permissions.svelte.ts +112 -59
- package/src/plain-data.ts +61 -0
- package/src/provider-bundle.ts +85 -79
- package/src/provider-response.ts +35 -0
- package/src/query-hooks.svelte.ts +241 -286
- package/src/query-invalidation.ts +172 -0
- package/src/query-keys.ts +221 -110
- package/src/query-session.svelte.ts +216 -0
- package/src/query-snapshot.ts +144 -0
- package/src/reactive-projection.ts +53 -0
- package/src/record-decoder.ts +63 -0
- package/src/resource-binding.svelte.ts +19 -0
- package/src/resource-contract.ts +595 -0
- package/src/resource-mutation-types.ts +30 -0
- package/src/resource-schemas.ts +371 -0
- package/src/resource-transport-config.ts +156 -0
- package/src/route-parsing.ts +11 -11
- package/src/router-provider.ts +64 -59
- package/src/router.ts +8 -3
- package/src/routing-hooks.svelte.ts +90 -62
- package/src/schema-validation.ts +92 -0
- package/src/schema.ts +13 -0
- package/src/select-options.ts +95 -0
- package/src/select-query.svelte.ts +191 -0
- package/src/session-query.svelte.ts +88 -0
- package/src/strict-command-hooks.svelte.ts +333 -0
- package/src/strict-hooks.svelte.ts +198 -0
- package/src/table-hooks.svelte.ts +180 -174
- package/src/table-state.svelte.ts +117 -117
- package/src/task-contract.ts +87 -0
- package/src/task-hooks.svelte.ts +193 -149
- package/src/task-provider.ts +115 -0
- package/src/task-subscription.ts +132 -0
- package/src/theme.svelte.ts +155 -154
- package/src/toast.svelte.ts +38 -37
- package/src/types.ts +55 -103
- package/src/unsafe.ts +7 -0
- package/src/update-contract.ts +65 -0
- package/src/update-hooks.svelte.ts +347 -0
- package/src/update-many-contract.ts +79 -0
- package/src/update-many-hooks.svelte.ts +373 -0
- package/src/url-sync.ts +11 -10
- package/src/useCan.ts +96 -31
- package/src/useParsed.svelte.ts +18 -14
- package/src/useStepsForm.svelte.ts +123 -63
- package/src/utility-hooks.svelte.ts +101 -158
- package/src/form-hooks.svelte.ts +0 -889
- package/src/mutation-hooks.svelte.ts +0 -561
package/src/hooks.svelte.ts
CHANGED
|
@@ -3,819 +3,64 @@
|
|
|
3
3
|
// ─── Re-exports from modular hook files ─────────────────────────────
|
|
4
4
|
|
|
5
5
|
export * from './query-hooks.svelte';
|
|
6
|
-
export * from './mutation-hooks.svelte';
|
|
7
|
-
export * from './form-hooks.svelte';
|
|
8
6
|
export * from './table-hooks.svelte';
|
|
9
7
|
export * from './routing-hooks.svelte';
|
|
10
8
|
export * from './utility-hooks.svelte';
|
|
11
9
|
|
|
12
10
|
// ─── Re-export types from options ─────────────────────────────────
|
|
13
|
-
export type { InvalidateScope,
|
|
11
|
+
export type { InvalidateScope,OvertimeConfig } from './options.svelte';
|
|
14
12
|
|
|
15
13
|
// ─── Export shared utilities ────────────────────────────────────────
|
|
16
14
|
|
|
17
|
-
export { createOvertimeTracker,
|
|
18
|
-
export type { OvertimeResult,
|
|
15
|
+
export { createOvertimeTracker,createLiveSubscription } from './hook-utils.svelte';
|
|
16
|
+
export type { OvertimeResult,OvertimeOptions,NotificationConfig } from './hook-utils.svelte';
|
|
19
17
|
|
|
20
18
|
// ─── Additional core hooks ─────────────────────────────────────────
|
|
21
19
|
|
|
22
|
-
import { createQuery, createInfiniteQuery, createMutation, useQueryClient } from '@tanstack/svelte-query';
|
|
23
|
-
import { getAdminOptions } from './options.svelte';
|
|
24
20
|
import { captureAdminContext } from './context.svelte';
|
|
25
|
-
import {
|
|
26
|
-
import type {
|
|
27
|
-
import {
|
|
28
|
-
import { createOvertimeTracker, createLiveSubscription, fireSuccessNotification, fireErrorNotification, checkError } from './hook-utils.svelte';
|
|
29
|
-
import { invalidateByScopes, publishLiveEvent } from './mutation-hooks.svelte';
|
|
30
|
-
import type { NotificationConfig, OvertimeOptions, LiveSubscriptionParams } from './hook-utils.svelte';
|
|
31
|
-
import { DeleteManyPartialError, UndoError } from './types';
|
|
32
|
-
import type { BaseRecord, HttpError, Pagination, Sort, Filter, DataProvider, KnownResources, MutationMode } from './types';
|
|
33
|
-
import type { LiveMode, LiveEvent } from './live.svelte';
|
|
34
|
-
import { auditWithProvider } from './audit';
|
|
35
|
-
import { useTranslation } from './i18n.svelte';
|
|
36
|
-
import { toast } from './toast.svelte';
|
|
37
|
-
import type { MutationCallbacks } from './mutation-hooks.svelte';
|
|
21
|
+
import { createOvertimeTracker } from './hook-utils.svelte';
|
|
22
|
+
import type { OvertimeOptions } from './hook-utils.svelte';
|
|
23
|
+
import type { DataProvider } from './types';
|
|
38
24
|
|
|
39
25
|
// ─── useInfiniteList ────────────────────────────────────────────────
|
|
40
26
|
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
pagination?: Pagination;
|
|
44
|
-
sorters?: Sort[];
|
|
45
|
-
filters?: Filter[];
|
|
46
|
-
meta?: Record<string, unknown>;
|
|
47
|
-
dataProviderName?: string;
|
|
48
|
-
queryOptions?: { staleTime?: number; enabled?: boolean };
|
|
49
|
-
successNotification?: NotificationConfig;
|
|
50
|
-
errorNotification?: NotificationConfig;
|
|
51
|
-
liveMode?: LiveMode;
|
|
52
|
-
onLiveEvent?: (event: LiveEvent) => void;
|
|
53
|
-
overtimeOptions?: OvertimeOptions;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError = HttpError>(options: UseInfiniteListOptions<TData, TError> = {}) {
|
|
57
|
-
const adminContext = captureAdminContext();
|
|
58
|
-
const parsed = useParsed();
|
|
59
|
-
const adminOptions = getAdminOptions();
|
|
60
|
-
|
|
61
|
-
const query = createInfiniteQuery<{ data: TData[]; total: number }, TError>(() => {
|
|
62
|
-
const resource = options.resource ?? parsed.resource ?? '';
|
|
63
|
-
const provider = adminContext.getDataProviderForResource(resource, options.dataProviderName);
|
|
64
|
-
return {
|
|
65
|
-
queryKey: adminContext.queryKeys(resource, options.dataProviderName).data.infiniteList(resource, {
|
|
66
|
-
pageSize: options.pagination?.pageSize,
|
|
67
|
-
sorters: options.sorters,
|
|
68
|
-
filters: options.filters,
|
|
69
|
-
meta: options.meta,
|
|
70
|
-
}),
|
|
71
|
-
queryFn: async ({ pageParam = 1 }) => {
|
|
72
|
-
const result = await provider.getList<TData>({
|
|
73
|
-
resource,
|
|
74
|
-
pagination: { current: pageParam as number, pageSize: options.pagination?.pageSize ?? 10 },
|
|
75
|
-
sorters: options.sorters,
|
|
76
|
-
filters: options.filters,
|
|
77
|
-
meta: options.meta,
|
|
78
|
-
});
|
|
79
|
-
return result;
|
|
80
|
-
},
|
|
81
|
-
initialPageParam: 1,
|
|
82
|
-
getNextPageParam: (lastPage: { data: TData[]; total: number }, allPages: { data: TData[]; total: number }[]) => {
|
|
83
|
-
const totalFetched = allPages.reduce((acc: number, p) => acc + (p.data?.length ?? 0), 0);
|
|
84
|
-
if (totalFetched >= (lastPage.total ?? 0)) return undefined;
|
|
85
|
-
return allPages.length + 1;
|
|
86
|
-
},
|
|
87
|
-
enabled: options.queryOptions?.enabled ?? true,
|
|
88
|
-
staleTime: options.queryOptions?.staleTime ?? adminOptions.reactQuery?.staleTime,
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const overtime = createOvertimeTracker(() => query.isLoading, options.overtimeOptions ?? adminOptions.overtime);
|
|
93
|
-
|
|
94
|
-
createLiveSubscription((): LiveSubscriptionParams => ({
|
|
95
|
-
resource: options.resource ?? parsed.resource ?? '',
|
|
96
|
-
liveProvider: adminContext.liveProvider,
|
|
97
|
-
liveMode: options.liveMode ?? adminOptions.liveMode,
|
|
98
|
-
onLiveEvent: (e: LiveEvent) => {
|
|
99
|
-
options.onLiveEvent?.(e);
|
|
100
|
-
adminOptions.onLiveEvent?.(e);
|
|
101
|
-
},
|
|
102
|
-
enabled: options.queryOptions?.enabled ?? true,
|
|
103
|
-
dataProviderName: options.dataProviderName,
|
|
104
|
-
}));
|
|
105
|
-
|
|
106
|
-
let lastSuccessAt = 0;
|
|
107
|
-
let lastErrorAt = 0;
|
|
108
|
-
$effect(() => {
|
|
109
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
110
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
111
|
-
fireSuccessNotification({
|
|
112
|
-
config: options.successNotification,
|
|
113
|
-
defaultMessage: '',
|
|
114
|
-
data: query.data,
|
|
115
|
-
resource: options.resource ?? parsed.resource ?? '',
|
|
116
|
-
provider: adminContext.notificationProvider,
|
|
117
|
-
});
|
|
118
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
119
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
120
|
-
checkError(query.error, adminContext);
|
|
121
|
-
fireErrorNotification({
|
|
122
|
-
config: options.errorNotification,
|
|
123
|
-
defaultMessage: 'Fetch failed',
|
|
124
|
-
error: query.error,
|
|
125
|
-
resource: options.resource ?? parsed.resource ?? '',
|
|
126
|
-
provider: adminContext.notificationProvider,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
return { query, get overtime() { return overtime; } };
|
|
132
|
-
}
|
|
27
|
+
export { useInfiniteList, createInfiniteListQuery } from './infinite-query.svelte';
|
|
28
|
+
export type { UseInfiniteListOptions } from './infinite-query.svelte';
|
|
133
29
|
|
|
134
30
|
// ─── useSelect ──────────────────────────────────────────────────────
|
|
135
31
|
|
|
136
|
-
export
|
|
137
|
-
|
|
138
|
-
optionLabel?: string | ((item: TData) => string);
|
|
139
|
-
optionValue?: string | ((item: TData) => string | number);
|
|
140
|
-
sorters?: Sort[];
|
|
141
|
-
filters?: Filter[];
|
|
142
|
-
defaultValue?: (string | number)[];
|
|
143
|
-
fetchSize?: number;
|
|
144
|
-
pagination?: Pagination;
|
|
145
|
-
debounce?: number;
|
|
146
|
-
queryOptions?: { staleTime?: number; enabled?: boolean };
|
|
147
|
-
defaultValueQueryOptions?: { staleTime?: number; enabled?: boolean };
|
|
148
|
-
meta?: Record<string, unknown>;
|
|
149
|
-
dataProviderName?: string;
|
|
150
|
-
onSearch?: (value: string) => Filter[];
|
|
151
|
-
successNotification?: NotificationConfig;
|
|
152
|
-
errorNotification?: NotificationConfig;
|
|
153
|
-
overtimeOptions?: OvertimeOptions;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { label: string; value: string | number }>(options: UseSelectOptions<TData, TOption>) {
|
|
157
|
-
const adminContext = captureAdminContext();
|
|
158
|
-
const { resource, optionLabel = 'title', optionValue = 'id', sorters, filters, pagination, meta, dataProviderName, onSearch, debounce: debounceMs = 300 } = options;
|
|
159
|
-
const adminOptions = getAdminOptions();
|
|
160
|
-
|
|
161
|
-
let searchText = $state('');
|
|
162
|
-
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
163
|
-
|
|
164
|
-
const searchFilters = $derived.by<Filter[]>(() => {
|
|
165
|
-
if (!onSearch || !searchText) return [];
|
|
166
|
-
return onSearch(searchText);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const allFilters = $derived([...(filters ?? []), ...searchFilters]);
|
|
170
|
-
|
|
171
|
-
const effectivePageSize = options.fetchSize ?? pagination?.pageSize ?? 999;
|
|
172
|
-
|
|
173
|
-
const query = createQuery<{ data: TData[]; total: number }>(() => {
|
|
174
|
-
const provider = adminContext.getDataProviderForResource(resource, dataProviderName);
|
|
175
|
-
return {
|
|
176
|
-
queryKey: adminContext.queryKeys(resource, dataProviderName).data.select(resource, {
|
|
177
|
-
filters: allFilters,
|
|
178
|
-
sorters,
|
|
179
|
-
pagination,
|
|
180
|
-
meta,
|
|
181
|
-
}),
|
|
182
|
-
queryFn: () => provider.getList<TData>({ resource, sorters, filters: allFilters, pagination: { current: 1, pageSize: effectivePageSize }, meta }),
|
|
183
|
-
enabled: options.queryOptions?.enabled ?? true,
|
|
184
|
-
staleTime: options.queryOptions?.staleTime ?? adminOptions.reactQuery?.staleTime,
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Parallel query to ensure default values are always available in options
|
|
189
|
-
const defaultValueIds = options.defaultValue ?? [];
|
|
190
|
-
const defaultValueQuery = defaultValueIds.length > 0
|
|
191
|
-
? createQuery<{ data: TData[] }>(() => {
|
|
192
|
-
const provider = adminContext.getDataProviderForResource(resource, dataProviderName);
|
|
193
|
-
return {
|
|
194
|
-
queryKey: adminContext.queryKeys(resource, dataProviderName).data.selectDefaults(resource, {
|
|
195
|
-
ids: defaultValueIds,
|
|
196
|
-
meta,
|
|
197
|
-
}),
|
|
198
|
-
queryFn: async () => {
|
|
199
|
-
if (provider.getMany) return provider.getMany<TData>({ resource, ids: defaultValueIds, meta });
|
|
200
|
-
const results = await Promise.all(defaultValueIds.map(id => provider.getOne<TData>({ resource, id, meta })));
|
|
201
|
-
return { data: results.map(r => r.data) };
|
|
202
|
-
},
|
|
203
|
-
enabled: (options.defaultValueQueryOptions?.enabled ?? true) && defaultValueIds.length > 0,
|
|
204
|
-
staleTime: options.defaultValueQueryOptions?.staleTime ?? Infinity,
|
|
205
|
-
};
|
|
206
|
-
})
|
|
207
|
-
: null;
|
|
208
|
-
|
|
209
|
-
const selectOptions = $derived.by(() => {
|
|
210
|
-
const queryResult = query.data as { data: TData[]; total: number } | undefined;
|
|
211
|
-
const data: TData[] = queryResult?.data ?? [];
|
|
212
|
-
// Merge default value items if not already in data
|
|
213
|
-
const defaultQueryResult = defaultValueQuery as { data: { data: TData[] } | undefined } | null;
|
|
214
|
-
const defaultData: TData[] = defaultQueryResult?.data?.data ?? [];
|
|
215
|
-
const allData: TData[] = [...data];
|
|
216
|
-
const resolveValue = (item: TData) => typeof optionValue === 'function' ? optionValue(item) : (item as Record<string, unknown>)[optionValue];
|
|
217
|
-
const existingIds = new Set(data.map((d: TData) => String(resolveValue(d))));
|
|
218
|
-
for (const item of defaultData) {
|
|
219
|
-
if (!existingIds.has(String(resolveValue(item)))) allData.push(item);
|
|
220
|
-
}
|
|
221
|
-
return allData.map((item: TData) => {
|
|
222
|
-
const label = typeof optionLabel === 'function' ? optionLabel(item) : String((item as Record<string, unknown>)[optionLabel] ?? '');
|
|
223
|
-
const value = resolveValue(item);
|
|
224
|
-
return { label, value } as unknown as TOption;
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const overtime = createOvertimeTracker(() => query.isLoading, options.overtimeOptions ?? adminOptions.overtime);
|
|
229
|
-
|
|
230
|
-
let lastSuccessAt = 0;
|
|
231
|
-
let lastErrorAt = 0;
|
|
232
|
-
$effect(() => {
|
|
233
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
234
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
235
|
-
fireSuccessNotification({
|
|
236
|
-
config: options.successNotification,
|
|
237
|
-
defaultMessage: '',
|
|
238
|
-
data: query.data,
|
|
239
|
-
resource,
|
|
240
|
-
provider: adminContext.notificationProvider,
|
|
241
|
-
});
|
|
242
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
243
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
244
|
-
checkError(query.error, adminContext);
|
|
245
|
-
fireErrorNotification({
|
|
246
|
-
config: options.errorNotification,
|
|
247
|
-
defaultMessage: 'Fetch failed',
|
|
248
|
-
error: query.error,
|
|
249
|
-
resource,
|
|
250
|
-
provider: adminContext.notificationProvider,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
function onSearchChange(value: string) {
|
|
256
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
257
|
-
debounceTimer = setTimeout(() => { searchText = value; }, debounceMs);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Cleanup debounce timer on unmount
|
|
261
|
-
$effect(() => {
|
|
262
|
-
return () => { if (debounceTimer) clearTimeout(debounceTimer); };
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
query,
|
|
267
|
-
get options() { return selectOptions; },
|
|
268
|
-
get overtime() { return overtime; },
|
|
269
|
-
onSearchChange,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// ─── useCustom ──────────────────────────────────────────────────────
|
|
274
|
-
|
|
275
|
-
export interface UseCustomOptions<_TData = unknown, _TError = HttpError> {
|
|
276
|
-
url: string;
|
|
277
|
-
method: 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
278
|
-
config?: { payload?: unknown; query?: Record<string, unknown>; headers?: Record<string, string>; sorters?: Sort[]; filters?: Filter[] };
|
|
279
|
-
meta?: Record<string, unknown>;
|
|
280
|
-
dataProviderName?: string;
|
|
281
|
-
queryOptions?: { staleTime?: number; enabled?: boolean };
|
|
282
|
-
successNotification?: NotificationConfig;
|
|
283
|
-
errorNotification?: NotificationConfig;
|
|
284
|
-
overtimeOptions?: OvertimeOptions;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export function useCustom<TData = unknown, TError = HttpError>(options: UseCustomOptions<TData, TError>) {
|
|
288
|
-
const adminContext = captureAdminContext();
|
|
289
|
-
const adminOptions = getAdminOptions();
|
|
290
|
-
|
|
291
|
-
const query = createQuery<{ data: TData }, TError>(() => {
|
|
292
|
-
const provider = adminContext.getDataProvider(options.dataProviderName);
|
|
293
|
-
return {
|
|
294
|
-
queryKey: adminContext.queryKeys(undefined, options.dataProviderName).custom.call(
|
|
295
|
-
options.url,
|
|
296
|
-
options.url,
|
|
297
|
-
options.method,
|
|
298
|
-
{ config: options.config, meta: options.meta },
|
|
299
|
-
),
|
|
300
|
-
queryFn: async () => {
|
|
301
|
-
if (!provider.custom) throw new Error('DataProvider does not support custom method');
|
|
302
|
-
return provider.custom<TData>({
|
|
303
|
-
url: options.url,
|
|
304
|
-
method: options.method,
|
|
305
|
-
payload: options.config?.payload,
|
|
306
|
-
query: options.config?.query,
|
|
307
|
-
headers: options.config?.headers,
|
|
308
|
-
sorters: options.config?.sorters,
|
|
309
|
-
filters: options.config?.filters,
|
|
310
|
-
meta: options.meta,
|
|
311
|
-
});
|
|
312
|
-
},
|
|
313
|
-
enabled: options.queryOptions?.enabled ?? true,
|
|
314
|
-
staleTime: options.queryOptions?.staleTime ?? adminOptions.reactQuery?.staleTime,
|
|
315
|
-
};
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const overtime = createOvertimeTracker(() => query.isLoading, options.overtimeOptions ?? adminOptions.overtime);
|
|
319
|
-
|
|
320
|
-
let lastSuccessAt = 0;
|
|
321
|
-
let lastErrorAt = 0;
|
|
322
|
-
$effect(() => {
|
|
323
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
324
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
325
|
-
fireSuccessNotification({
|
|
326
|
-
config: options.successNotification,
|
|
327
|
-
defaultMessage: '',
|
|
328
|
-
data: query.data,
|
|
329
|
-
provider: adminContext.notificationProvider,
|
|
330
|
-
});
|
|
331
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
332
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
333
|
-
checkError(query.error, adminContext);
|
|
334
|
-
fireErrorNotification({
|
|
335
|
-
config: options.errorNotification,
|
|
336
|
-
defaultMessage: 'Custom request failed',
|
|
337
|
-
error: query.error,
|
|
338
|
-
resource: '',
|
|
339
|
-
provider: adminContext.notificationProvider,
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
return { query, get overtime() { return overtime; } };
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// ─── useCustomMutation ──────────────────────────────────────────────
|
|
348
|
-
|
|
349
|
-
export function useCustomMutation<TData = unknown, TError = HttpError, TVariables = unknown>(dataProviderName?: string) {
|
|
350
|
-
const adminContext = captureAdminContext();
|
|
351
|
-
const queryClient = useQueryClient();
|
|
352
|
-
|
|
353
|
-
const mutation = createMutation<{ data: TData }, TError, { url: string; method: 'get' | 'post' | 'put' | 'patch' | 'delete'; values?: TVariables; query?: Record<string, unknown>; headers?: Record<string, string>; sorters?: Sort[]; filters?: Filter[]; meta?: Record<string, unknown>; invalidates?: string[] | false; resource?: string }>(() => ({
|
|
354
|
-
mutationFn: async (params) => {
|
|
355
|
-
const provider = adminContext.getDataProvider(dataProviderName);
|
|
356
|
-
if (!provider.custom) throw new Error('DataProvider does not support custom method');
|
|
357
|
-
return provider.custom<TData>({ url: params.url, method: params.method, payload: params.values, query: params.query, headers: params.headers, sorters: params.sorters, filters: params.filters, meta: params.meta });
|
|
358
|
-
},
|
|
359
|
-
onSuccess: (data, params) => {
|
|
360
|
-
if (params.resource && params.invalidates !== false) {
|
|
361
|
-
invalidateByScopes({
|
|
362
|
-
queryClient,
|
|
363
|
-
resource: params.resource,
|
|
364
|
-
scopes: params.invalidates,
|
|
365
|
-
defaults: ['list', 'many'],
|
|
366
|
-
matcher: adminContext.queryKeyMatcher(params.resource, dataProviderName),
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
},
|
|
370
|
-
onError: (error) => {
|
|
371
|
-
checkError(error, adminContext);
|
|
372
|
-
},
|
|
373
|
-
}));
|
|
374
|
-
|
|
375
|
-
return { mutation };
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// ─── useCreateMany / useUpdateMany / useDeleteMany ──────────────────
|
|
379
|
-
|
|
380
|
-
export function useCreateMany<TData extends BaseRecord = BaseRecord, TError = HttpError, TVariables = Record<string, unknown>>(options: { resource?: KnownResources; overtimeOptions?: OvertimeOptions } = {}) {
|
|
381
|
-
const adminContext = captureAdminContext();
|
|
382
|
-
const parsed = useParsed();
|
|
383
|
-
const resource = options.resource ?? parsed.resource ?? '';
|
|
384
|
-
const adminOptions = getAdminOptions();
|
|
385
|
-
const queryClient = useQueryClient();
|
|
386
|
-
|
|
387
|
-
const mutation = createMutation<{ data: TData[] }, TError, { resource?: KnownResources; variables: TVariables[]; meta?: Record<string, unknown>; dataProviderName?: string }>(() => ({
|
|
388
|
-
mutationFn: async (params) => {
|
|
389
|
-
const resName = params.resource ?? resource;
|
|
390
|
-
const provider = adminContext.getDataProviderForResource(resName, params.dataProviderName);
|
|
391
|
-
if (provider.createMany) return await provider.createMany<TData, TVariables>({ resource: resName, variables: params.variables, meta: params.meta });
|
|
392
|
-
const results = await Promise.all(params.variables.map(v => provider.create<TData, TVariables>({ resource: resName, variables: v, meta: params.meta })));
|
|
393
|
-
return { data: results.map(r => r.data) };
|
|
394
|
-
},
|
|
395
|
-
onSuccess: (data, params) => {
|
|
396
|
-
const resName = params.resource ?? resource;
|
|
397
|
-
fireSuccessNotification({
|
|
398
|
-
config: undefined,
|
|
399
|
-
defaultMessage: 'Created successfully',
|
|
400
|
-
data: data.data,
|
|
401
|
-
values: params.variables,
|
|
402
|
-
resource: resName,
|
|
403
|
-
provider: adminContext.notificationProvider,
|
|
404
|
-
});
|
|
405
|
-
auditWithProvider(
|
|
406
|
-
{ action: 'create', resource: resName, meta: adminContext.getProviderMeta(resName) },
|
|
407
|
-
adminContext.auditLogProvider,
|
|
408
|
-
);
|
|
409
|
-
publishLiveEvent(resName, 'created', undefined, adminContext);
|
|
410
|
-
},
|
|
411
|
-
onError: (error, params) => {
|
|
412
|
-
checkError(error, adminContext);
|
|
413
|
-
fireErrorNotification({
|
|
414
|
-
config: undefined,
|
|
415
|
-
defaultMessage: 'Create many failed',
|
|
416
|
-
error,
|
|
417
|
-
resource: params.resource ?? resource,
|
|
418
|
-
provider: adminContext.notificationProvider,
|
|
419
|
-
});
|
|
420
|
-
},
|
|
421
|
-
onSettled: (_d, _e, params) => {
|
|
422
|
-
const resName = params.resource ?? resource;
|
|
423
|
-
invalidateByScopes({
|
|
424
|
-
queryClient,
|
|
425
|
-
resource: resName,
|
|
426
|
-
defaults: ['list', 'many'],
|
|
427
|
-
matcher: adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
428
|
-
});
|
|
429
|
-
},
|
|
430
|
-
}));
|
|
431
|
-
|
|
432
|
-
return { mutation, get overtime() { return createOvertimeTracker(() => mutation.isPending, options.overtimeOptions ?? adminOptions.overtime); } };
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
export function useUpdateMany<TData extends BaseRecord = BaseRecord, TError = HttpError, TVariables = Record<string, unknown>>(options: { resource?: KnownResources; overtimeOptions?: OvertimeOptions } = {}) {
|
|
436
|
-
const adminContext = captureAdminContext();
|
|
437
|
-
const parsed = useParsed();
|
|
438
|
-
const resource = options.resource ?? parsed.resource ?? '';
|
|
439
|
-
const adminOptions = getAdminOptions();
|
|
440
|
-
const queryClient = useQueryClient();
|
|
441
|
-
|
|
442
|
-
const mutation = createMutation<{ data: TData[] }, TError, { resource?: KnownResources; ids: (string | number)[]; variables: TVariables; meta?: Record<string, unknown>; dataProviderName?: string }>(() => ({
|
|
443
|
-
mutationFn: async (params) => {
|
|
444
|
-
const resName = params.resource ?? resource;
|
|
445
|
-
const provider = adminContext.getDataProviderForResource(resName, params.dataProviderName);
|
|
446
|
-
if (provider.updateMany) return await provider.updateMany<TData, TVariables>({ resource: resName, ids: params.ids, variables: params.variables, meta: params.meta });
|
|
447
|
-
const results = await Promise.all(params.ids.map(id => provider.update<TData, TVariables>({ resource: resName, id, variables: params.variables, meta: params.meta })));
|
|
448
|
-
return { data: results.map(r => r.data) };
|
|
449
|
-
},
|
|
450
|
-
onSuccess: (data, params) => {
|
|
451
|
-
const resName = params.resource ?? resource;
|
|
452
|
-
fireSuccessNotification({
|
|
453
|
-
config: undefined,
|
|
454
|
-
defaultMessage: 'Updated successfully',
|
|
455
|
-
data: data.data,
|
|
456
|
-
values: params.variables,
|
|
457
|
-
resource: resName,
|
|
458
|
-
provider: adminContext.notificationProvider,
|
|
459
|
-
});
|
|
460
|
-
auditWithProvider(
|
|
461
|
-
{ action: 'update', resource: resName, meta: adminContext.getProviderMeta(resName) },
|
|
462
|
-
adminContext.auditLogProvider,
|
|
463
|
-
);
|
|
464
|
-
publishLiveEvent(resName, 'updated', params.ids, adminContext);
|
|
465
|
-
},
|
|
466
|
-
onError: (error, params) => {
|
|
467
|
-
checkError(error, adminContext);
|
|
468
|
-
fireErrorNotification({
|
|
469
|
-
config: undefined,
|
|
470
|
-
defaultMessage: 'Update many failed',
|
|
471
|
-
error,
|
|
472
|
-
resource: params.resource ?? resource,
|
|
473
|
-
provider: adminContext.notificationProvider,
|
|
474
|
-
});
|
|
475
|
-
},
|
|
476
|
-
onSettled: (_d, _e, params) => {
|
|
477
|
-
const resName = params.resource ?? resource;
|
|
478
|
-
invalidateByScopes({
|
|
479
|
-
queryClient,
|
|
480
|
-
resource: resName,
|
|
481
|
-
defaults: ['list', 'many', 'detail'],
|
|
482
|
-
matcher: adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
483
|
-
});
|
|
484
|
-
for (const id of params.ids) {
|
|
485
|
-
queryClient.invalidateQueries({
|
|
486
|
-
predicate: (q) => queryKeyMatches(q.queryKey, {
|
|
487
|
-
...adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
488
|
-
kind: 'data',
|
|
489
|
-
resource: resName,
|
|
490
|
-
action: 'one',
|
|
491
|
-
id,
|
|
492
|
-
}),
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
},
|
|
496
|
-
}));
|
|
497
|
-
|
|
498
|
-
return { mutation, get overtime() { return createOvertimeTracker(() => mutation.isPending, options.overtimeOptions ?? adminOptions.overtime); } };
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
export interface UseDeleteManyOptions {
|
|
502
|
-
resource?: KnownResources;
|
|
503
|
-
mutationMode?: MutationMode;
|
|
504
|
-
undoableTimeout?: number;
|
|
505
|
-
mutationOptions?: MutationCallbacks;
|
|
506
|
-
overtimeOptions?: OvertimeOptions;
|
|
507
|
-
successNotification?: NotificationConfig;
|
|
508
|
-
errorNotification?: NotificationConfig;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
export interface UseDeleteManyMutateParams {
|
|
512
|
-
resource?: KnownResources;
|
|
513
|
-
ids: (string | number)[];
|
|
514
|
-
mutationMode?: MutationMode;
|
|
515
|
-
undoableTimeout?: number;
|
|
516
|
-
meta?: Record<string, unknown>;
|
|
517
|
-
dataProviderName?: string;
|
|
518
|
-
invalidates?: string[] | false;
|
|
519
|
-
onCancel?: () => void;
|
|
520
|
-
successNotification?: NotificationConfig;
|
|
521
|
-
errorNotification?: NotificationConfig;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
interface DeleteManyMutationContext {
|
|
525
|
-
_svadmin_ctx?: boolean;
|
|
526
|
-
userContext?: unknown;
|
|
527
|
-
previousQueries?: [readonly unknown[], unknown][];
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
export function useDeleteMany<TData extends BaseRecord = BaseRecord, TError = HttpError>(options: UseDeleteManyOptions = {}) {
|
|
531
|
-
const adminContext = captureAdminContext();
|
|
532
|
-
const parsed = useParsed();
|
|
533
|
-
const resource = options.resource ?? parsed.resource ?? '';
|
|
534
|
-
const adminOptions = getAdminOptions();
|
|
535
|
-
const queryClient = useQueryClient();
|
|
536
|
-
const i18n = useTranslation();
|
|
537
|
-
const mutationMode = options.mutationMode ?? adminOptions.mutationMode ?? 'pessimistic';
|
|
538
|
-
const undoableTimeout = options.undoableTimeout ?? adminOptions.undoableTimeout ?? 5000;
|
|
539
|
-
|
|
540
|
-
const removeIdsFromCache = (
|
|
541
|
-
resName: string,
|
|
542
|
-
ids: (string | number)[],
|
|
543
|
-
dataProviderName?: string,
|
|
544
|
-
) => {
|
|
545
|
-
const matcher = adminContext.queryKeyMatcher(resName, dataProviderName);
|
|
546
|
-
const dataMatch = (queryKey: readonly unknown[], fields: QueryMatcher = {}) => dataQueryMatches(queryKey, {
|
|
547
|
-
...matcher,
|
|
548
|
-
resource: resName,
|
|
549
|
-
...fields,
|
|
550
|
-
});
|
|
551
|
-
const primaryKey = adminContext.getResource(resName).primaryKey ?? 'id';
|
|
552
|
-
const deletedIds = new Set(ids.map(String));
|
|
553
|
-
const removeFromCollection = (old: unknown): unknown => {
|
|
554
|
-
if (!old || typeof old !== 'object' || !('data' in old)) return old;
|
|
555
|
-
const current = old as { data: Record<string, unknown>[]; total?: number };
|
|
556
|
-
if (!Array.isArray(current.data)) return old;
|
|
557
|
-
const filtered = current.data.filter((item) => !deletedIds.has(String(item[primaryKey])));
|
|
558
|
-
const removedCount = current.data.length - filtered.length;
|
|
559
|
-
return {
|
|
560
|
-
...current,
|
|
561
|
-
data: filtered,
|
|
562
|
-
total: current.total == null ? current.total : Math.max(0, current.total - removedCount),
|
|
563
|
-
};
|
|
564
|
-
};
|
|
565
|
-
const removeFromInfiniteCollection = (old: unknown): unknown => {
|
|
566
|
-
if (!old || typeof old !== 'object' || !('pages' in old)) return old;
|
|
567
|
-
const current = old as {
|
|
568
|
-
pages: { data: Record<string, unknown>[]; total?: number }[];
|
|
569
|
-
pageParams?: unknown[];
|
|
570
|
-
};
|
|
571
|
-
if (!Array.isArray(current.pages)) return old;
|
|
572
|
-
|
|
573
|
-
const removedIds = new Set<string>();
|
|
574
|
-
for (const page of current.pages) {
|
|
575
|
-
if (!Array.isArray(page.data)) continue;
|
|
576
|
-
for (const item of page.data) {
|
|
577
|
-
const itemId = String(item[primaryKey]);
|
|
578
|
-
if (deletedIds.has(itemId)) removedIds.add(itemId);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
return {
|
|
583
|
-
...current,
|
|
584
|
-
pages: current.pages.map((page) => ({
|
|
585
|
-
...page,
|
|
586
|
-
data: Array.isArray(page.data)
|
|
587
|
-
? page.data.filter((item) => !deletedIds.has(String(item[primaryKey])))
|
|
588
|
-
: page.data,
|
|
589
|
-
total: page.total == null ? page.total : Math.max(0, page.total - removedIds.size),
|
|
590
|
-
})),
|
|
591
|
-
};
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
queryClient.setQueriesData({
|
|
595
|
-
predicate: (query) => ['list', 'select', 'selectDefaults'].some((action) => dataMatch(query.queryKey, { action })),
|
|
596
|
-
}, removeFromCollection);
|
|
597
|
-
queryClient.setQueriesData({
|
|
598
|
-
predicate: (query) => dataMatch(query.queryKey, { action: 'infiniteList' }),
|
|
599
|
-
}, removeFromInfiniteCollection);
|
|
600
|
-
queryClient.setQueriesData({
|
|
601
|
-
predicate: (query) => dataMatch(query.queryKey, { action: 'many' }),
|
|
602
|
-
}, removeFromCollection);
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
const mutation = createMutation<{ data: TData[] }, TError | DeleteManyPartialError, UseDeleteManyMutateParams, DeleteManyMutationContext>(() => ({
|
|
606
|
-
mutationFn: async (params) => {
|
|
607
|
-
const resName = params.resource ?? resource;
|
|
608
|
-
const effectiveMutationMode = params.mutationMode ?? mutationMode;
|
|
609
|
-
const effectiveUndoableTimeout = params.undoableTimeout ?? undoableTimeout;
|
|
610
|
-
if (effectiveMutationMode === 'undoable') {
|
|
611
|
-
await new Promise<void>((resolve, reject) => {
|
|
612
|
-
toast.undoable(i18n.t('common.actionCanBeUndone'), effectiveUndoableTimeout, () => {
|
|
613
|
-
params.onCancel?.();
|
|
614
|
-
reject(new UndoError());
|
|
615
|
-
}, resolve);
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
const provider = adminContext.getDataProviderForResource(resName, params.dataProviderName);
|
|
619
|
-
if (provider.deleteMany) return await provider.deleteMany<TData>({ resource: resName, ids: params.ids, meta: params.meta });
|
|
620
|
-
const settled = await Promise.allSettled(
|
|
621
|
-
params.ids.map(id => provider.deleteOne<TData>({ resource: resName, id, meta: params.meta })),
|
|
622
|
-
);
|
|
623
|
-
const succeededIds: (string | number)[] = [];
|
|
624
|
-
const failedIds: (string | number)[] = [];
|
|
625
|
-
const causes: unknown[] = [];
|
|
626
|
-
const data: TData[] = [];
|
|
627
|
-
settled.forEach((result, index) => {
|
|
628
|
-
const id = params.ids[index];
|
|
629
|
-
if (result.status === 'fulfilled') {
|
|
630
|
-
succeededIds.push(id);
|
|
631
|
-
data.push(result.value.data);
|
|
632
|
-
} else {
|
|
633
|
-
failedIds.push(id);
|
|
634
|
-
causes.push(result.reason);
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
if (failedIds.length > 0) {
|
|
638
|
-
if (succeededIds.length > 0) {
|
|
639
|
-
throw new DeleteManyPartialError(succeededIds, failedIds, causes);
|
|
640
|
-
}
|
|
641
|
-
throw causes[0] ?? new Error('Delete many failed');
|
|
642
|
-
}
|
|
643
|
-
return { data };
|
|
644
|
-
},
|
|
645
|
-
onMutate: async (params) => {
|
|
646
|
-
const userContext = typeof options.mutationOptions?.onMutate === 'function'
|
|
647
|
-
? await options.mutationOptions.onMutate(params)
|
|
648
|
-
: undefined;
|
|
649
|
-
const effectiveMutationMode = params.mutationMode ?? mutationMode;
|
|
650
|
-
if (effectiveMutationMode === 'pessimistic') return { _svadmin_ctx: true, userContext };
|
|
651
|
-
const resName = params.resource ?? resource;
|
|
652
|
-
const matcher = adminContext.queryKeyMatcher(resName, params.dataProviderName);
|
|
653
|
-
const dataMatch = (queryKey: readonly unknown[], fields: QueryMatcher = {}) => dataQueryMatches(queryKey, {
|
|
654
|
-
...matcher,
|
|
655
|
-
resource: resName,
|
|
656
|
-
...fields,
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
await queryClient.cancelQueries({ predicate: (query) => dataMatch(query.queryKey) });
|
|
660
|
-
const previousQueries = queryClient.getQueriesData({ predicate: (query) => dataMatch(query.queryKey) });
|
|
661
|
-
removeIdsFromCache(resName, params.ids, params.dataProviderName);
|
|
662
|
-
|
|
663
|
-
return { _svadmin_ctx: true, userContext, previousQueries };
|
|
664
|
-
},
|
|
665
|
-
onSuccess: (data, params, context) => {
|
|
666
|
-
const resName = params.resource ?? resource;
|
|
667
|
-
const extractedContext = context?._svadmin_ctx ? context.userContext : context;
|
|
668
|
-
fireSuccessNotification({
|
|
669
|
-
config: params.successNotification ?? options.successNotification,
|
|
670
|
-
defaultMessage: 'Deleted successfully',
|
|
671
|
-
data: data.data,
|
|
672
|
-
resource: resName,
|
|
673
|
-
provider: adminContext.notificationProvider,
|
|
674
|
-
});
|
|
675
|
-
auditWithProvider(
|
|
676
|
-
{ action: 'delete', resource: resName, meta: adminContext.getProviderMeta(resName) },
|
|
677
|
-
adminContext.auditLogProvider,
|
|
678
|
-
);
|
|
679
|
-
publishLiveEvent(resName, 'deleted', params.ids, adminContext);
|
|
680
|
-
for (const id of params.ids) {
|
|
681
|
-
queryClient.removeQueries({
|
|
682
|
-
predicate: (query) => queryKeyMatches(query.queryKey, {
|
|
683
|
-
...adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
684
|
-
kind: 'data',
|
|
685
|
-
resource: resName,
|
|
686
|
-
action: 'one',
|
|
687
|
-
id,
|
|
688
|
-
}),
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
if (typeof options.mutationOptions?.onSuccess === 'function') {
|
|
692
|
-
options.mutationOptions.onSuccess(data, params, extractedContext);
|
|
693
|
-
}
|
|
694
|
-
},
|
|
695
|
-
onError: (error, params, context) => {
|
|
696
|
-
if (context?.previousQueries) {
|
|
697
|
-
for (const [queryKey, data] of context.previousQueries) queryClient.setQueryData(queryKey, data);
|
|
698
|
-
}
|
|
699
|
-
if (error instanceof UndoError) return;
|
|
700
|
-
const resName = params.resource ?? resource;
|
|
701
|
-
if (error instanceof DeleteManyPartialError) {
|
|
702
|
-
removeIdsFromCache(resName, error.succeededIds, params.dataProviderName);
|
|
703
|
-
for (const id of error.succeededIds) {
|
|
704
|
-
queryClient.removeQueries({
|
|
705
|
-
predicate: (query) => queryKeyMatches(query.queryKey, {
|
|
706
|
-
...adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
707
|
-
kind: 'data',
|
|
708
|
-
resource: resName,
|
|
709
|
-
action: 'one',
|
|
710
|
-
id,
|
|
711
|
-
}),
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
auditWithProvider(
|
|
715
|
-
{
|
|
716
|
-
action: 'delete',
|
|
717
|
-
resource: resName,
|
|
718
|
-
outcome: 'failure',
|
|
719
|
-
details: { succeededIds: error.succeededIds, failedIds: error.failedIds },
|
|
720
|
-
meta: adminContext.getProviderMeta(resName),
|
|
721
|
-
},
|
|
722
|
-
adminContext.auditLogProvider,
|
|
723
|
-
);
|
|
724
|
-
publishLiveEvent(resName, 'deleted', error.succeededIds, adminContext);
|
|
725
|
-
}
|
|
726
|
-
const authError = error instanceof DeleteManyPartialError
|
|
727
|
-
? (error.causes[0] ?? error)
|
|
728
|
-
: error;
|
|
729
|
-
checkError(authError, adminContext);
|
|
730
|
-
fireErrorNotification({
|
|
731
|
-
config: params.errorNotification ?? options.errorNotification,
|
|
732
|
-
defaultMessage: 'Delete many failed',
|
|
733
|
-
error,
|
|
734
|
-
resource: resName,
|
|
735
|
-
provider: adminContext.notificationProvider,
|
|
736
|
-
});
|
|
737
|
-
const extractedContext = context?._svadmin_ctx ? context.userContext : context;
|
|
738
|
-
if (typeof options.mutationOptions?.onError === 'function') {
|
|
739
|
-
options.mutationOptions.onError(error, params, extractedContext);
|
|
740
|
-
}
|
|
741
|
-
},
|
|
742
|
-
onSettled: (_data, error, params, context) => {
|
|
743
|
-
if (error instanceof UndoError) return;
|
|
744
|
-
const resName = params.resource ?? resource;
|
|
745
|
-
invalidateByScopes({
|
|
746
|
-
queryClient,
|
|
747
|
-
resource: resName,
|
|
748
|
-
scopes: params.invalidates,
|
|
749
|
-
defaults: ['list', 'many'],
|
|
750
|
-
matcher: adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
751
|
-
});
|
|
752
|
-
const extractedContext = context?._svadmin_ctx ? context.userContext : context;
|
|
753
|
-
if (typeof options.mutationOptions?.onSettled === 'function') {
|
|
754
|
-
options.mutationOptions.onSettled(_data, error, params, extractedContext);
|
|
755
|
-
}
|
|
756
|
-
},
|
|
757
|
-
}));
|
|
758
|
-
|
|
759
|
-
return { mutation, get overtime() { return createOvertimeTracker(() => mutation.isPending, options.overtimeOptions ?? adminOptions.overtime); } };
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// ─── useInvalidate ──────────────────────────────────────────────────
|
|
763
|
-
|
|
764
|
-
export function useInvalidate() {
|
|
765
|
-
const queryClient = useQueryClient();
|
|
766
|
-
const adminContext = captureAdminContext();
|
|
767
|
-
return (params: { resource?: string; invalidates?: string[] | 'all' | false; id?: string | number; dataProviderName?: string }) => {
|
|
768
|
-
if (params.invalidates === false) return;
|
|
769
|
-
if (params.invalidates === 'all' || !params.resource) {
|
|
770
|
-
queryClient.invalidateQueries({
|
|
771
|
-
predicate: (q) => queryKeyMatches(q.queryKey, {
|
|
772
|
-
...adminContext.queryKeyMatcher(undefined, params.dataProviderName),
|
|
773
|
-
kind: 'data',
|
|
774
|
-
}),
|
|
775
|
-
});
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
const res = params.resource;
|
|
779
|
-
invalidateByScopes({
|
|
780
|
-
queryClient,
|
|
781
|
-
resource: res,
|
|
782
|
-
scopes: params.invalidates || ['resourceAll'],
|
|
783
|
-
defaults: ['resourceAll'],
|
|
784
|
-
id: params.id,
|
|
785
|
-
matcher: adminContext.queryKeyMatcher(res, params.dataProviderName),
|
|
786
|
-
});
|
|
787
|
-
};
|
|
788
|
-
}
|
|
32
|
+
export { createSelectQuery } from './select-query.svelte';
|
|
33
|
+
export type { UseSelectOptions } from './select-query.svelte';
|
|
789
34
|
|
|
790
35
|
// ─── useOvertime ────────────────────────────────────────────────────
|
|
791
36
|
|
|
792
37
|
export function useOvertime(options?: OvertimeOptions) {
|
|
793
|
-
let isLoading
|
|
794
|
-
const overtime
|
|
38
|
+
let isLoading=$state(false);
|
|
39
|
+
const overtime=createOvertimeTracker(() => isLoading,options);
|
|
795
40
|
return {
|
|
796
41
|
get elapsedTime() { return overtime.elapsedTime; },
|
|
797
|
-
start() { isLoading
|
|
798
|
-
stop() { isLoading
|
|
42
|
+
start() { isLoading=true; },
|
|
43
|
+
stop() { isLoading=false; },
|
|
799
44
|
};
|
|
800
45
|
}
|
|
801
46
|
|
|
802
47
|
// ─── useDataProvider ────────────────────────────────────────────────
|
|
803
48
|
|
|
804
49
|
export function useDataProvider(): (dataProviderName?: string) => DataProvider {
|
|
805
|
-
const adminContext
|
|
50
|
+
const adminContext=captureAdminContext();
|
|
806
51
|
return (name?: string) => adminContext.getDataProvider(name);
|
|
807
52
|
}
|
|
808
53
|
|
|
809
54
|
// ─── useThemedLayoutContext ─────────────────────────────────────────
|
|
810
55
|
|
|
811
|
-
let _sidebarCollapsed
|
|
56
|
+
let _sidebarCollapsed=$state(false);
|
|
812
57
|
|
|
813
|
-
export function resetSidebarCollapsed() { _sidebarCollapsed
|
|
58
|
+
export function resetSidebarCollapsed() { _sidebarCollapsed=false; }
|
|
814
59
|
|
|
815
60
|
export function useThemedLayoutContext() {
|
|
816
61
|
return {
|
|
817
62
|
get sidebarCollapsed() { return _sidebarCollapsed; },
|
|
818
|
-
setSidebarCollapsed(v: boolean) { _sidebarCollapsed
|
|
819
|
-
toggleSidebar() { _sidebarCollapsed
|
|
63
|
+
setSidebarCollapsed(v: boolean) { _sidebarCollapsed=v; },
|
|
64
|
+
toggleSidebar() { _sidebarCollapsed=!_sidebarCollapsed; },
|
|
820
65
|
};
|
|
821
66
|
}
|