@svadmin/core 0.34.2 → 0.36.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/package.json +1 -1
- package/src/audit.ts +11 -3
- package/src/auth-hooks.svelte.ts +83 -13
- package/src/context.svelte.ts +202 -29
- package/src/form-hooks.svelte.ts +70 -20
- package/src/hook-utils.svelte.ts +33 -21
- package/src/hooks.svelte.ts +180 -39
- package/src/index.ts +47 -4
- package/src/live.svelte.ts +7 -3
- package/src/mutation-hooks.svelte.ts +161 -52
- package/src/notification.svelte.ts +14 -4
- package/src/provider-bundle.ts +183 -0
- package/src/query-hooks.svelte.ts +55 -9
- package/src/query-keys.ts +124 -18
- package/src/task-hooks.svelte.ts +68 -19
- package/src/types.ts +25 -0
- package/src/useCan.ts +19 -4
- package/src/utility-hooks.svelte.ts +34 -6
package/src/form-hooks.svelte.ts
CHANGED
|
@@ -2,10 +2,13 @@ import { useQueryClient } from '@tanstack/svelte-query';
|
|
|
2
2
|
import { useParsed } from './useParsed.svelte';
|
|
3
3
|
import { getAdminOptions } from './options.svelte';
|
|
4
4
|
import { captureAdminContext } from './context.svelte';
|
|
5
|
+
import { queryKeyMatches } from './query-keys';
|
|
5
6
|
import { createQuery, createMutation } from '@tanstack/svelte-query';
|
|
6
|
-
import {
|
|
7
|
+
import { notifyWithProvider } from './notification.svelte';
|
|
8
|
+
import type { NotificationParams } from './notification.svelte';
|
|
7
9
|
import { useTranslation } from './i18n.svelte';
|
|
8
|
-
import {
|
|
10
|
+
import { auditWithProvider } from './audit';
|
|
11
|
+
import type { AuditEntry } from './audit';
|
|
9
12
|
import { HttpError, UndoError } from './types';
|
|
10
13
|
import type { BaseRecord, MutationMode, KnownResources } from './types';
|
|
11
14
|
import { checkError } from './hook-utils.svelte';
|
|
@@ -153,6 +156,10 @@ export function useForm<
|
|
|
153
156
|
const queryClient = useQueryClient();
|
|
154
157
|
const parsed = useParsed();
|
|
155
158
|
const adminOptions = getAdminOptions();
|
|
159
|
+
const sendNotification = (params: NotificationParams) =>
|
|
160
|
+
notifyWithProvider(params, adminContext.notificationProvider);
|
|
161
|
+
const recordAudit = (entry: Omit<AuditEntry, 'timestamp'>) =>
|
|
162
|
+
auditWithProvider(entry, adminContext.auditLogProvider);
|
|
156
163
|
|
|
157
164
|
const resource = $derived(options.resource ?? parsed.resource ?? '');
|
|
158
165
|
let action = $state<'create' | 'edit' | 'clone' | 'show'>(options.action ?? (parsed.action === 'list' ? 'create' : parsed.action as 'create' | 'edit' | 'clone' | 'show') ?? 'create');
|
|
@@ -441,9 +448,9 @@ export function useForm<
|
|
|
441
448
|
const msg = Array.isArray(messages) ? messages[0] : messages;
|
|
442
449
|
setFieldError(field, msg);
|
|
443
450
|
}
|
|
444
|
-
if (errorNotification !== false)
|
|
451
|
+
if (errorNotification !== false) sendNotification({ type: 'error', message: typeof errorNotification === 'string' ? errorNotification : (error.message || i18n.t('common.operationFailed')) });
|
|
445
452
|
} else {
|
|
446
|
-
if (errorNotification !== false)
|
|
453
|
+
if (errorNotification !== false) sendNotification({ type: 'error', message: typeof errorNotification === 'string' ? errorNotification : (i18n.t('common.operationFailed') + ': ' + error.message) });
|
|
447
454
|
}
|
|
448
455
|
}
|
|
449
456
|
|
|
@@ -451,7 +458,9 @@ export function useForm<
|
|
|
451
458
|
// Always create the query — use `enabled` to conditionally activate.
|
|
452
459
|
// This ensures setAction('edit') at runtime will trigger a fetch.
|
|
453
460
|
const query = createQuery(() => ({
|
|
454
|
-
queryKey:
|
|
461
|
+
queryKey: adminContext.queryKeys(resource, options.dataProviderName).data.one(resource, currentId ?? '', {
|
|
462
|
+
meta: queryMeta,
|
|
463
|
+
}),
|
|
455
464
|
queryFn: async () => {
|
|
456
465
|
const result = await provider.getOne<BaseRecord>({ resource, id: currentId as string, meta: queryMeta });
|
|
457
466
|
return result;
|
|
@@ -518,12 +527,23 @@ export function useForm<
|
|
|
518
527
|
onSuccess: (data: { data: TData }, _vars: unknown, context: unknown) => {
|
|
519
528
|
const ctx = context as { targetResource?: string } | undefined;
|
|
520
529
|
const res = ctx?.targetResource ?? resource;
|
|
521
|
-
invalidateByScopes(
|
|
530
|
+
invalidateByScopes({
|
|
531
|
+
queryClient,
|
|
532
|
+
resource: res,
|
|
533
|
+
scopes: invalidateScopes,
|
|
534
|
+
defaults: ['list', 'many'],
|
|
535
|
+
matcher: adminContext.queryKeyMatcher(res, options.dataProviderName),
|
|
536
|
+
});
|
|
522
537
|
if (!isManualSubmitIdentityCurrent()) return;
|
|
523
|
-
if (successNotification !== false)
|
|
538
|
+
if (successNotification !== false) sendNotification({ type: 'success', message: typeof successNotification === 'string' ? successNotification : i18n.t('common.createSuccess') });
|
|
524
539
|
const pk = adminContext.getResource(res).primaryKey ?? 'id';
|
|
525
540
|
const newId = (data.data as Record<string, unknown>)[pk];
|
|
526
|
-
|
|
541
|
+
recordAudit({
|
|
542
|
+
action: 'create',
|
|
543
|
+
resource: res,
|
|
544
|
+
recordId: String(newId),
|
|
545
|
+
meta: adminContext.getProviderMeta(res),
|
|
546
|
+
});
|
|
527
547
|
try {
|
|
528
548
|
const lp = adminContext.liveProvider;
|
|
529
549
|
if (lp?.publish) lp.publish({ type: 'INSERT', resource: res, payload: { ids: newId != null ? [newId as string | number] : [] } });
|
|
@@ -562,12 +582,17 @@ export function useForm<
|
|
|
562
582
|
if (mutationMode === 'pessimistic') return { targetId: currentId, targetResource: resource };
|
|
563
583
|
const targetId = currentId;
|
|
564
584
|
const targetResource = resource;
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
585
|
+
const matcher = adminContext.queryKeyMatcher(resource, options.dataProviderName);
|
|
586
|
+
const dataMatch = (queryKey: readonly unknown[], fields: { action?: string; id?: string | number } = {}) => queryKeyMatches(queryKey, {
|
|
587
|
+
...matcher,
|
|
588
|
+
kind: 'data',
|
|
589
|
+
resource,
|
|
590
|
+
...fields,
|
|
591
|
+
});
|
|
592
|
+
await queryClient.cancelQueries({ predicate: (q) => dataMatch(q.queryKey) });
|
|
593
|
+
const previousQueries = queryClient.getQueriesData({ predicate: (q) => dataMatch(q.queryKey) });
|
|
569
594
|
if (optimisticUpdateMap?.list !== false) {
|
|
570
|
-
queryClient.setQueriesData({ predicate: (q) =>
|
|
595
|
+
queryClient.setQueriesData({ predicate: (q) => dataMatch(q.queryKey, { action: 'list' }) }, (old: unknown) => {
|
|
571
596
|
if (!old || typeof old !== 'object' || !('data' in old)) return old;
|
|
572
597
|
const o = old as { data: Record<string, unknown>[] };
|
|
573
598
|
const pk = adminContext.getResource(resource).primaryKey ?? 'id';
|
|
@@ -575,7 +600,7 @@ export function useForm<
|
|
|
575
600
|
});
|
|
576
601
|
}
|
|
577
602
|
if (optimisticUpdateMap?.detail !== false && targetId != null) {
|
|
578
|
-
queryClient.setQueriesData({ predicate: (q) =>
|
|
603
|
+
queryClient.setQueriesData({ predicate: (q) => dataMatch(q.queryKey, { action: 'one', id: targetId }) }, (old: Record<string, unknown> | undefined) => old ? { ...old, data: deepMerge((old as Record<string, unknown>).data || {}, variables) } : old);
|
|
579
604
|
}
|
|
580
605
|
return { previousQueries, targetId, targetResource };
|
|
581
606
|
},
|
|
@@ -584,8 +609,13 @@ export function useForm<
|
|
|
584
609
|
const targetId = ctx?.targetId ?? currentId;
|
|
585
610
|
const res = ctx?.targetResource ?? resource;
|
|
586
611
|
if (!isManualSubmitIdentityCurrent()) return;
|
|
587
|
-
if (successNotification !== false)
|
|
588
|
-
|
|
612
|
+
if (successNotification !== false) sendNotification({ type: 'success', message: typeof successNotification === 'string' ? successNotification : i18n.t('common.updateSuccess') });
|
|
613
|
+
recordAudit({
|
|
614
|
+
action: 'update',
|
|
615
|
+
resource: res,
|
|
616
|
+
recordId: String(targetId),
|
|
617
|
+
meta: adminContext.getProviderMeta(res),
|
|
618
|
+
});
|
|
589
619
|
try {
|
|
590
620
|
const lp = adminContext.liveProvider;
|
|
591
621
|
if (lp?.publish) lp.publish({ type: 'UPDATE', resource: res, payload: { ids: targetId != null ? [targetId] : [] } });
|
|
@@ -598,7 +628,14 @@ export function useForm<
|
|
|
598
628
|
const ctx = context as { targetId?: string | number; targetResource?: string } | undefined;
|
|
599
629
|
const targetId = ctx?.targetId ?? currentId;
|
|
600
630
|
const res = ctx?.targetResource ?? resource;
|
|
601
|
-
invalidateByScopes(
|
|
631
|
+
invalidateByScopes({
|
|
632
|
+
queryClient,
|
|
633
|
+
resource: res,
|
|
634
|
+
scopes: invalidateScopes,
|
|
635
|
+
defaults: ['list', 'many', 'detail'],
|
|
636
|
+
id: targetId ?? undefined,
|
|
637
|
+
matcher: adminContext.queryKeyMatcher(res, options.dataProviderName),
|
|
638
|
+
});
|
|
602
639
|
},
|
|
603
640
|
onError: (error: unknown, _vars: unknown, context: unknown) => {
|
|
604
641
|
const ctx = context as { previousQueries?: [unknown, unknown][] } | undefined;
|
|
@@ -649,7 +686,7 @@ export function useForm<
|
|
|
649
686
|
|
|
650
687
|
if (!isRecordIdentityCurrent(submitIdentity)) return;
|
|
651
688
|
|
|
652
|
-
if (!runValidation()) {
|
|
689
|
+
if (!runValidation()) { sendNotification({ type: 'warning', message: i18n.t('validation.required') }); return; }
|
|
653
690
|
|
|
654
691
|
const submittedValues = createPersistenceSnapshot(values);
|
|
655
692
|
if (action === 'create' || action === 'clone') await createMut.mutateAsync(submittedValues);
|
|
@@ -740,7 +777,14 @@ export function useForm<
|
|
|
740
777
|
await provider.update<TData, TVariables>({ resource: safeResource, id: safeId as string | number, variables: submittedValues, meta: safeMeta });
|
|
741
778
|
if (formDestroyed) return;
|
|
742
779
|
const scopes = autoSaveOpts.invalidates ?? ['resourceAll'];
|
|
743
|
-
invalidateByScopes(
|
|
780
|
+
invalidateByScopes({
|
|
781
|
+
queryClient,
|
|
782
|
+
resource: safeResource,
|
|
783
|
+
scopes,
|
|
784
|
+
defaults: ['resourceAll'],
|
|
785
|
+
id: safeId ?? undefined,
|
|
786
|
+
matcher: adminContext.queryKeyMatcher(safeResource, options.dataProviderName),
|
|
787
|
+
});
|
|
744
788
|
if (!isRecordIdentityCurrent(scheduledIdentity)) return;
|
|
745
789
|
autoSaveStatus = 'saved';
|
|
746
790
|
lastAutoSaveData = submittedValues;
|
|
@@ -769,7 +813,13 @@ export function useForm<
|
|
|
769
813
|
|
|
770
814
|
if (autoSaveOpts?.invalidateOnUnmount) {
|
|
771
815
|
$effect(() => {
|
|
772
|
-
return () => queryClient.invalidateQueries({
|
|
816
|
+
return () => queryClient.invalidateQueries({
|
|
817
|
+
predicate: (q) => queryKeyMatches(q.queryKey, {
|
|
818
|
+
...adminContext.queryKeyMatcher(resource, options.dataProviderName),
|
|
819
|
+
kind: 'data',
|
|
820
|
+
resource,
|
|
821
|
+
}),
|
|
822
|
+
});
|
|
773
823
|
});
|
|
774
824
|
}
|
|
775
825
|
|
package/src/hook-utils.svelte.ts
CHANGED
|
@@ -5,7 +5,9 @@ import { useQueryClient } from '@tanstack/svelte-query';
|
|
|
5
5
|
import type { LiveProvider, LiveEvent, LiveMode } from './live.svelte';
|
|
6
6
|
import { captureAdminContext } from './context.svelte';
|
|
7
7
|
import type { AdminContextAccessor } from './context.svelte';
|
|
8
|
-
import {
|
|
8
|
+
import { dataQueryMatches } from './query-keys';
|
|
9
|
+
import { notifyWithProvider } from './notification.svelte';
|
|
10
|
+
import type { NotificationProvider } from './types';
|
|
9
11
|
|
|
10
12
|
// ─── Auth Error Delegate ────────────────────────────────────────
|
|
11
13
|
// Delegate auth errors to authProvider.onError() — refine pattern
|
|
@@ -88,6 +90,7 @@ export interface LiveSubscriptionParams {
|
|
|
88
90
|
|
|
89
91
|
export function createLiveSubscription(paramsFn: () => LiveSubscriptionParams): void {
|
|
90
92
|
const queryClient = useQueryClient();
|
|
93
|
+
const adminContext = captureAdminContext();
|
|
91
94
|
|
|
92
95
|
$effect(() => {
|
|
93
96
|
const params = paramsFn();
|
|
@@ -105,9 +108,10 @@ export function createLiveSubscription(paramsFn: () => LiveSubscriptionParams):
|
|
|
105
108
|
callback: (event: LiveEvent) => {
|
|
106
109
|
params.onLiveEvent?.(event);
|
|
107
110
|
if (liveMode === 'auto') {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
const matcher = adminContext.queryKeyMatcher(params.resource, params.dataProviderName);
|
|
112
|
+
queryClient.invalidateQueries({
|
|
113
|
+
predicate: (q) => dataQueryMatches(q.queryKey, { ...matcher, resource: params.resource }),
|
|
114
|
+
});
|
|
111
115
|
}
|
|
112
116
|
},
|
|
113
117
|
});
|
|
@@ -127,36 +131,44 @@ export type NotificationConfig =
|
|
|
127
131
|
| ((data?: unknown, values?: unknown, resource?: string) => { message: string; description?: string; type?: 'success' | 'error' })
|
|
128
132
|
| undefined;
|
|
129
133
|
|
|
130
|
-
export
|
|
131
|
-
config: NotificationConfig
|
|
132
|
-
defaultMessage: string
|
|
133
|
-
data?: unknown
|
|
134
|
-
values?: unknown
|
|
135
|
-
resource?: string
|
|
136
|
-
|
|
134
|
+
export interface SuccessNotificationRequest {
|
|
135
|
+
config: NotificationConfig;
|
|
136
|
+
defaultMessage: string;
|
|
137
|
+
data?: unknown;
|
|
138
|
+
values?: unknown;
|
|
139
|
+
resource?: string;
|
|
140
|
+
provider?: NotificationProvider | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface ErrorNotificationRequest {
|
|
144
|
+
config: NotificationConfig;
|
|
145
|
+
defaultMessage: string;
|
|
146
|
+
error?: unknown;
|
|
147
|
+
resource?: string;
|
|
148
|
+
provider?: NotificationProvider | null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function fireSuccessNotification(request: SuccessNotificationRequest): void {
|
|
152
|
+
const { config, defaultMessage, data, values, resource, provider } = request;
|
|
137
153
|
if (config === false) return;
|
|
138
154
|
if (!config && !defaultMessage) return;
|
|
139
155
|
if (typeof config === 'function') {
|
|
140
156
|
const result = config(data, values, resource);
|
|
141
|
-
|
|
157
|
+
notifyWithProvider({ type: 'success', message: result.message, description: result.description }, provider);
|
|
142
158
|
return;
|
|
143
159
|
}
|
|
144
|
-
|
|
160
|
+
notifyWithProvider({ type: 'success', message: config || defaultMessage }, provider);
|
|
145
161
|
}
|
|
146
162
|
|
|
147
|
-
export function fireErrorNotification(
|
|
148
|
-
config
|
|
149
|
-
defaultMessage: string,
|
|
150
|
-
error?: unknown,
|
|
151
|
-
resource?: string,
|
|
152
|
-
): void {
|
|
163
|
+
export function fireErrorNotification(request: ErrorNotificationRequest): void {
|
|
164
|
+
const { config, defaultMessage, error, resource, provider } = request;
|
|
153
165
|
if (config === false) return;
|
|
154
166
|
if (!config && !defaultMessage) return;
|
|
155
167
|
if (typeof config === 'function') {
|
|
156
168
|
const result = config(error, undefined, resource);
|
|
157
|
-
|
|
169
|
+
notifyWithProvider({ type: 'error', message: result.message, description: result.description }, provider);
|
|
158
170
|
return;
|
|
159
171
|
}
|
|
160
172
|
const errMsg = error instanceof Error ? error.message : String(error ?? '');
|
|
161
|
-
|
|
173
|
+
notifyWithProvider({ type: 'error', message: config || `${defaultMessage}${errMsg ? ': ' + errMsg : ''}` }, provider);
|
|
162
174
|
}
|
package/src/hooks.svelte.ts
CHANGED
|
@@ -17,20 +17,19 @@ export type { InvalidateScope, OvertimeConfig } from './options.svelte';
|
|
|
17
17
|
export { createOvertimeTracker, createLiveSubscription } from './hook-utils.svelte';
|
|
18
18
|
export type { OvertimeResult, OvertimeOptions, NotificationConfig } from './hook-utils.svelte';
|
|
19
19
|
|
|
20
|
-
// ───
|
|
21
|
-
// These hooks were in the original monolithic file. They are re-created
|
|
22
|
-
// here as thin wrappers or stubs until they get their own modular files.
|
|
20
|
+
// ─── Additional core hooks ─────────────────────────────────────────
|
|
23
21
|
|
|
24
22
|
import { createQuery, createInfiniteQuery, createMutation, useQueryClient } from '@tanstack/svelte-query';
|
|
25
23
|
import { getAdminOptions } from './options.svelte';
|
|
26
24
|
import { captureAdminContext } from './context.svelte';
|
|
25
|
+
import { queryKeyMatches } from './query-keys';
|
|
27
26
|
import { useParsed } from './useParsed.svelte';
|
|
28
27
|
import { createOvertimeTracker, createLiveSubscription, fireSuccessNotification, fireErrorNotification, checkError } from './hook-utils.svelte';
|
|
29
28
|
import { invalidateByScopes, publishLiveEvent } from './mutation-hooks.svelte';
|
|
30
29
|
import type { NotificationConfig, OvertimeOptions, LiveSubscriptionParams } from './hook-utils.svelte';
|
|
31
30
|
import type { BaseRecord, HttpError, Pagination, Sort, Filter, DataProvider, KnownResources } from './types';
|
|
32
31
|
import type { LiveMode, LiveEvent } from './live.svelte';
|
|
33
|
-
import {
|
|
32
|
+
import { auditWithProvider } from './audit';
|
|
34
33
|
|
|
35
34
|
// ─── useInfiniteList ────────────────────────────────────────────────
|
|
36
35
|
|
|
@@ -58,7 +57,12 @@ export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError =
|
|
|
58
57
|
const resource = options.resource ?? parsed.resource ?? '';
|
|
59
58
|
const provider = adminContext.getDataProviderForResource(resource, options.dataProviderName);
|
|
60
59
|
return {
|
|
61
|
-
queryKey:
|
|
60
|
+
queryKey: adminContext.queryKeys(resource, options.dataProviderName).data.infiniteList(resource, {
|
|
61
|
+
pageSize: options.pagination?.pageSize,
|
|
62
|
+
sorters: options.sorters,
|
|
63
|
+
filters: options.filters,
|
|
64
|
+
meta: options.meta,
|
|
65
|
+
}),
|
|
62
66
|
queryFn: async ({ pageParam = 1 }) => {
|
|
63
67
|
const result = await provider.getList<TData>({
|
|
64
68
|
resource,
|
|
@@ -99,11 +103,23 @@ export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError =
|
|
|
99
103
|
$effect(() => {
|
|
100
104
|
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
101
105
|
lastSuccessAt = query.dataUpdatedAt;
|
|
102
|
-
fireSuccessNotification(
|
|
106
|
+
fireSuccessNotification({
|
|
107
|
+
config: options.successNotification,
|
|
108
|
+
defaultMessage: '',
|
|
109
|
+
data: query.data,
|
|
110
|
+
resource: options.resource ?? parsed.resource ?? '',
|
|
111
|
+
provider: adminContext.notificationProvider,
|
|
112
|
+
});
|
|
103
113
|
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
104
114
|
lastErrorAt = query.errorUpdatedAt;
|
|
105
115
|
checkError(query.error, adminContext);
|
|
106
|
-
fireErrorNotification(
|
|
116
|
+
fireErrorNotification({
|
|
117
|
+
config: options.errorNotification,
|
|
118
|
+
defaultMessage: 'Fetch failed',
|
|
119
|
+
error: query.error,
|
|
120
|
+
resource: options.resource ?? parsed.resource ?? '',
|
|
121
|
+
provider: adminContext.notificationProvider,
|
|
122
|
+
});
|
|
107
123
|
}
|
|
108
124
|
});
|
|
109
125
|
|
|
@@ -152,7 +168,12 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
152
168
|
const query = createQuery<{ data: TData[]; total: number }>(() => {
|
|
153
169
|
const provider = adminContext.getDataProviderForResource(resource, dataProviderName);
|
|
154
170
|
return {
|
|
155
|
-
queryKey:
|
|
171
|
+
queryKey: adminContext.queryKeys(resource, dataProviderName).data.select(resource, {
|
|
172
|
+
filters: allFilters,
|
|
173
|
+
sorters,
|
|
174
|
+
pagination,
|
|
175
|
+
meta,
|
|
176
|
+
}),
|
|
156
177
|
queryFn: () => provider.getList<TData>({ resource, sorters, filters: allFilters, pagination: { current: 1, pageSize: effectivePageSize }, meta }),
|
|
157
178
|
enabled: options.queryOptions?.enabled ?? true,
|
|
158
179
|
staleTime: options.queryOptions?.staleTime ?? adminOptions.reactQuery?.staleTime,
|
|
@@ -165,7 +186,10 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
165
186
|
? createQuery<{ data: TData[] }>(() => {
|
|
166
187
|
const provider = adminContext.getDataProviderForResource(resource, dataProviderName);
|
|
167
188
|
return {
|
|
168
|
-
queryKey:
|
|
189
|
+
queryKey: adminContext.queryKeys(resource, dataProviderName).data.selectDefaults(resource, {
|
|
190
|
+
ids: defaultValueIds,
|
|
191
|
+
meta,
|
|
192
|
+
}),
|
|
169
193
|
queryFn: async () => {
|
|
170
194
|
if (provider.getMany) return provider.getMany<TData>({ resource, ids: defaultValueIds, meta });
|
|
171
195
|
const results = await Promise.all(defaultValueIds.map(id => provider.getOne<TData>({ resource, id, meta })));
|
|
@@ -203,11 +227,23 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
203
227
|
$effect(() => {
|
|
204
228
|
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
205
229
|
lastSuccessAt = query.dataUpdatedAt;
|
|
206
|
-
fireSuccessNotification(
|
|
230
|
+
fireSuccessNotification({
|
|
231
|
+
config: options.successNotification,
|
|
232
|
+
defaultMessage: '',
|
|
233
|
+
data: query.data,
|
|
234
|
+
resource,
|
|
235
|
+
provider: adminContext.notificationProvider,
|
|
236
|
+
});
|
|
207
237
|
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
208
238
|
lastErrorAt = query.errorUpdatedAt;
|
|
209
239
|
checkError(query.error, adminContext);
|
|
210
|
-
fireErrorNotification(
|
|
240
|
+
fireErrorNotification({
|
|
241
|
+
config: options.errorNotification,
|
|
242
|
+
defaultMessage: 'Fetch failed',
|
|
243
|
+
error: query.error,
|
|
244
|
+
resource,
|
|
245
|
+
provider: adminContext.notificationProvider,
|
|
246
|
+
});
|
|
211
247
|
}
|
|
212
248
|
});
|
|
213
249
|
|
|
@@ -250,7 +286,12 @@ export function useCustom<TData = unknown, TError = HttpError>(options: UseCusto
|
|
|
250
286
|
const query = createQuery<{ data: TData }, TError>(() => {
|
|
251
287
|
const provider = adminContext.getDataProvider(options.dataProviderName);
|
|
252
288
|
return {
|
|
253
|
-
queryKey:
|
|
289
|
+
queryKey: adminContext.queryKeys(undefined, options.dataProviderName).custom.call(
|
|
290
|
+
options.url,
|
|
291
|
+
options.url,
|
|
292
|
+
options.method,
|
|
293
|
+
{ config: options.config, meta: options.meta },
|
|
294
|
+
),
|
|
254
295
|
queryFn: async () => {
|
|
255
296
|
if (!provider.custom) throw new Error('DataProvider does not support custom method');
|
|
256
297
|
return provider.custom<TData>({
|
|
@@ -276,11 +317,22 @@ export function useCustom<TData = unknown, TError = HttpError>(options: UseCusto
|
|
|
276
317
|
$effect(() => {
|
|
277
318
|
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
278
319
|
lastSuccessAt = query.dataUpdatedAt;
|
|
279
|
-
fireSuccessNotification(
|
|
320
|
+
fireSuccessNotification({
|
|
321
|
+
config: options.successNotification,
|
|
322
|
+
defaultMessage: '',
|
|
323
|
+
data: query.data,
|
|
324
|
+
provider: adminContext.notificationProvider,
|
|
325
|
+
});
|
|
280
326
|
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
281
327
|
lastErrorAt = query.errorUpdatedAt;
|
|
282
328
|
checkError(query.error, adminContext);
|
|
283
|
-
fireErrorNotification(
|
|
329
|
+
fireErrorNotification({
|
|
330
|
+
config: options.errorNotification,
|
|
331
|
+
defaultMessage: 'Custom request failed',
|
|
332
|
+
error: query.error,
|
|
333
|
+
resource: '',
|
|
334
|
+
provider: adminContext.notificationProvider,
|
|
335
|
+
});
|
|
284
336
|
}
|
|
285
337
|
});
|
|
286
338
|
|
|
@@ -301,7 +353,13 @@ export function useCustomMutation<TData = unknown, TError = HttpError, TVariable
|
|
|
301
353
|
},
|
|
302
354
|
onSuccess: (data, params) => {
|
|
303
355
|
if (params.resource && params.invalidates !== false) {
|
|
304
|
-
invalidateByScopes(
|
|
356
|
+
invalidateByScopes({
|
|
357
|
+
queryClient,
|
|
358
|
+
resource: params.resource,
|
|
359
|
+
scopes: params.invalidates,
|
|
360
|
+
defaults: ['list', 'many'],
|
|
361
|
+
matcher: adminContext.queryKeyMatcher(params.resource, dataProviderName),
|
|
362
|
+
});
|
|
305
363
|
}
|
|
306
364
|
},
|
|
307
365
|
onError: (error) => {
|
|
@@ -331,17 +389,38 @@ export function useCreateMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
331
389
|
},
|
|
332
390
|
onSuccess: (data, params) => {
|
|
333
391
|
const resName = params.resource ?? resource;
|
|
334
|
-
fireSuccessNotification(
|
|
335
|
-
|
|
392
|
+
fireSuccessNotification({
|
|
393
|
+
config: undefined,
|
|
394
|
+
defaultMessage: 'Created successfully',
|
|
395
|
+
data: data.data,
|
|
396
|
+
values: params.variables,
|
|
397
|
+
resource: resName,
|
|
398
|
+
provider: adminContext.notificationProvider,
|
|
399
|
+
});
|
|
400
|
+
auditWithProvider(
|
|
401
|
+
{ action: 'create', resource: resName, meta: adminContext.getProviderMeta(resName) },
|
|
402
|
+
adminContext.auditLogProvider,
|
|
403
|
+
);
|
|
336
404
|
publishLiveEvent(resName, 'created', undefined, adminContext);
|
|
337
405
|
},
|
|
338
406
|
onError: (error, params) => {
|
|
339
407
|
checkError(error, adminContext);
|
|
340
|
-
fireErrorNotification(
|
|
408
|
+
fireErrorNotification({
|
|
409
|
+
config: undefined,
|
|
410
|
+
defaultMessage: 'Create many failed',
|
|
411
|
+
error,
|
|
412
|
+
resource: params.resource ?? resource,
|
|
413
|
+
provider: adminContext.notificationProvider,
|
|
414
|
+
});
|
|
341
415
|
},
|
|
342
416
|
onSettled: (_d, _e, params) => {
|
|
343
417
|
const resName = params.resource ?? resource;
|
|
344
|
-
invalidateByScopes(
|
|
418
|
+
invalidateByScopes({
|
|
419
|
+
queryClient,
|
|
420
|
+
resource: resName,
|
|
421
|
+
defaults: ['list', 'many'],
|
|
422
|
+
matcher: adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
423
|
+
});
|
|
345
424
|
},
|
|
346
425
|
}));
|
|
347
426
|
|
|
@@ -365,19 +444,48 @@ export function useUpdateMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
365
444
|
},
|
|
366
445
|
onSuccess: (data, params) => {
|
|
367
446
|
const resName = params.resource ?? resource;
|
|
368
|
-
fireSuccessNotification(
|
|
369
|
-
|
|
447
|
+
fireSuccessNotification({
|
|
448
|
+
config: undefined,
|
|
449
|
+
defaultMessage: 'Updated successfully',
|
|
450
|
+
data: data.data,
|
|
451
|
+
values: params.variables,
|
|
452
|
+
resource: resName,
|
|
453
|
+
provider: adminContext.notificationProvider,
|
|
454
|
+
});
|
|
455
|
+
auditWithProvider(
|
|
456
|
+
{ action: 'update', resource: resName, meta: adminContext.getProviderMeta(resName) },
|
|
457
|
+
adminContext.auditLogProvider,
|
|
458
|
+
);
|
|
370
459
|
publishLiveEvent(resName, 'updated', params.ids, adminContext);
|
|
371
460
|
},
|
|
372
461
|
onError: (error, params) => {
|
|
373
462
|
checkError(error, adminContext);
|
|
374
|
-
fireErrorNotification(
|
|
463
|
+
fireErrorNotification({
|
|
464
|
+
config: undefined,
|
|
465
|
+
defaultMessage: 'Update many failed',
|
|
466
|
+
error,
|
|
467
|
+
resource: params.resource ?? resource,
|
|
468
|
+
provider: adminContext.notificationProvider,
|
|
469
|
+
});
|
|
375
470
|
},
|
|
376
471
|
onSettled: (_d, _e, params) => {
|
|
377
472
|
const resName = params.resource ?? resource;
|
|
378
|
-
invalidateByScopes(
|
|
473
|
+
invalidateByScopes({
|
|
474
|
+
queryClient,
|
|
475
|
+
resource: resName,
|
|
476
|
+
defaults: ['list', 'many', 'detail'],
|
|
477
|
+
matcher: adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
478
|
+
});
|
|
379
479
|
for (const id of params.ids) {
|
|
380
|
-
queryClient.invalidateQueries({
|
|
480
|
+
queryClient.invalidateQueries({
|
|
481
|
+
predicate: (q) => queryKeyMatches(q.queryKey, {
|
|
482
|
+
...adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
483
|
+
kind: 'data',
|
|
484
|
+
resource: resName,
|
|
485
|
+
action: 'one',
|
|
486
|
+
id,
|
|
487
|
+
}),
|
|
488
|
+
});
|
|
381
489
|
}
|
|
382
490
|
},
|
|
383
491
|
}));
|
|
@@ -402,19 +510,47 @@ export function useDeleteMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
402
510
|
},
|
|
403
511
|
onSuccess: (data, params) => {
|
|
404
512
|
const resName = params.resource ?? resource;
|
|
405
|
-
fireSuccessNotification(
|
|
406
|
-
|
|
513
|
+
fireSuccessNotification({
|
|
514
|
+
config: undefined,
|
|
515
|
+
defaultMessage: 'Deleted successfully',
|
|
516
|
+
data: data.data,
|
|
517
|
+
resource: resName,
|
|
518
|
+
provider: adminContext.notificationProvider,
|
|
519
|
+
});
|
|
520
|
+
auditWithProvider(
|
|
521
|
+
{ action: 'delete', resource: resName, meta: adminContext.getProviderMeta(resName) },
|
|
522
|
+
adminContext.auditLogProvider,
|
|
523
|
+
);
|
|
407
524
|
publishLiveEvent(resName, 'deleted', params.ids, adminContext);
|
|
408
525
|
},
|
|
409
526
|
onError: (error, params) => {
|
|
410
527
|
checkError(error, adminContext);
|
|
411
|
-
fireErrorNotification(
|
|
528
|
+
fireErrorNotification({
|
|
529
|
+
config: undefined,
|
|
530
|
+
defaultMessage: 'Delete many failed',
|
|
531
|
+
error,
|
|
532
|
+
resource: params.resource ?? resource,
|
|
533
|
+
provider: adminContext.notificationProvider,
|
|
534
|
+
});
|
|
412
535
|
},
|
|
413
536
|
onSettled: (_d, _e, params) => {
|
|
414
537
|
const resName = params.resource ?? resource;
|
|
415
|
-
invalidateByScopes(
|
|
538
|
+
invalidateByScopes({
|
|
539
|
+
queryClient,
|
|
540
|
+
resource: resName,
|
|
541
|
+
defaults: ['list', 'many'],
|
|
542
|
+
matcher: adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
543
|
+
});
|
|
416
544
|
for (const id of params.ids) {
|
|
417
|
-
queryClient.removeQueries({
|
|
545
|
+
queryClient.removeQueries({
|
|
546
|
+
predicate: (q) => queryKeyMatches(q.queryKey, {
|
|
547
|
+
...adminContext.queryKeyMatcher(resName, params.dataProviderName),
|
|
548
|
+
kind: 'data',
|
|
549
|
+
resource: resName,
|
|
550
|
+
action: 'one',
|
|
551
|
+
id,
|
|
552
|
+
}),
|
|
553
|
+
});
|
|
418
554
|
}
|
|
419
555
|
},
|
|
420
556
|
}));
|
|
@@ -426,22 +562,27 @@ export function useDeleteMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
426
562
|
|
|
427
563
|
export function useInvalidate() {
|
|
428
564
|
const queryClient = useQueryClient();
|
|
565
|
+
const adminContext = captureAdminContext();
|
|
429
566
|
return (params: { resource?: string; invalidates?: string[] | 'all' | false; id?: string | number; dataProviderName?: string }) => {
|
|
430
567
|
if (params.invalidates === false) return;
|
|
431
568
|
if (params.invalidates === 'all' || !params.resource) {
|
|
432
|
-
queryClient.invalidateQueries({
|
|
569
|
+
queryClient.invalidateQueries({
|
|
570
|
+
predicate: (q) => queryKeyMatches(q.queryKey, {
|
|
571
|
+
...adminContext.queryKeyMatcher(undefined, params.dataProviderName),
|
|
572
|
+
kind: 'data',
|
|
573
|
+
}),
|
|
574
|
+
});
|
|
433
575
|
return;
|
|
434
576
|
}
|
|
435
|
-
const scopes = params.invalidates || ['resourceAll'];
|
|
436
577
|
const res = params.resource;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
578
|
+
invalidateByScopes({
|
|
579
|
+
queryClient,
|
|
580
|
+
resource: res,
|
|
581
|
+
scopes: params.invalidates || ['resourceAll'],
|
|
582
|
+
defaults: ['resourceAll'],
|
|
583
|
+
id: params.id,
|
|
584
|
+
matcher: adminContext.queryKeyMatcher(res, params.dataProviderName),
|
|
585
|
+
});
|
|
445
586
|
};
|
|
446
587
|
}
|
|
447
588
|
|