@svadmin/core 0.34.1 → 0.35.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 +6 -6
- package/src/audit.ts +11 -3
- package/src/auth-hooks.svelte.ts +83 -13
- package/src/context.svelte.ts +180 -23
- package/src/form-hooks.svelte.ts +63 -15
- package/src/hook-utils.svelte.ts +31 -19
- package/src/hooks.svelte.ts +175 -28
- package/src/index.ts +33 -3
- package/src/live.svelte.ts +5 -1
- package/src/mutation-hooks.svelte.ts +116 -19
- package/src/notification.svelte.ts +14 -4
- package/src/provider-bundle.ts +200 -0
- package/src/query-hooks.svelte.ts +54 -9
- package/src/task-hooks.svelte.ts +66 -19
- package/src/types.ts +25 -0
- package/src/useCan.ts +18 -4
- package/src/utility-hooks.svelte.ts +31 -2
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 { appendTenantCacheKey, queryKeyMatchesTenant } from './provider-bundle';
|
|
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: appendTenantCacheKey([
|
|
462
|
+
options.dataProviderName, resource, 'one', currentId, queryMeta,
|
|
463
|
+
], adminContext.tenantCacheKey),
|
|
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,25 @@ 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
|
+
res,
|
|
533
|
+
invalidateScopes,
|
|
534
|
+
['list', 'many'],
|
|
535
|
+
undefined,
|
|
536
|
+
options.dataProviderName,
|
|
537
|
+
(queryKey) => queryKeyMatchesTenant(queryKey, adminContext.tenantCacheKey),
|
|
538
|
+
);
|
|
522
539
|
if (!isManualSubmitIdentityCurrent()) return;
|
|
523
|
-
if (successNotification !== false)
|
|
540
|
+
if (successNotification !== false) sendNotification({ type: 'success', message: typeof successNotification === 'string' ? successNotification : i18n.t('common.createSuccess') });
|
|
524
541
|
const pk = adminContext.getResource(res).primaryKey ?? 'id';
|
|
525
542
|
const newId = (data.data as Record<string, unknown>)[pk];
|
|
526
|
-
|
|
543
|
+
recordAudit({
|
|
544
|
+
action: 'create',
|
|
545
|
+
resource: res,
|
|
546
|
+
recordId: String(newId),
|
|
547
|
+
meta: adminContext.getProviderMeta(res),
|
|
548
|
+
});
|
|
527
549
|
try {
|
|
528
550
|
const lp = adminContext.liveProvider;
|
|
529
551
|
if (lp?.publish) lp.publish({ type: 'INSERT', resource: res, payload: { ids: newId != null ? [newId as string | number] : [] } });
|
|
@@ -563,7 +585,8 @@ export function useForm<
|
|
|
563
585
|
const targetId = currentId;
|
|
564
586
|
const targetResource = resource;
|
|
565
587
|
const dpN = options.dataProviderName;
|
|
566
|
-
const dp = (q: { queryKey: readonly unknown[] }) =>
|
|
588
|
+
const dp = (q: { queryKey: readonly unknown[] }) =>
|
|
589
|
+
queryKeyMatchesTenant(q.queryKey, adminContext.tenantCacheKey) && q.queryKey[0] === dpN;
|
|
567
590
|
await queryClient.cancelQueries({ predicate: (q) => dp(q) && q.queryKey[1] === resource });
|
|
568
591
|
const previousQueries = queryClient.getQueriesData({ predicate: (q) => dp(q) && q.queryKey[1] === resource });
|
|
569
592
|
if (optimisticUpdateMap?.list !== false) {
|
|
@@ -584,8 +607,13 @@ export function useForm<
|
|
|
584
607
|
const targetId = ctx?.targetId ?? currentId;
|
|
585
608
|
const res = ctx?.targetResource ?? resource;
|
|
586
609
|
if (!isManualSubmitIdentityCurrent()) return;
|
|
587
|
-
if (successNotification !== false)
|
|
588
|
-
|
|
610
|
+
if (successNotification !== false) sendNotification({ type: 'success', message: typeof successNotification === 'string' ? successNotification : i18n.t('common.updateSuccess') });
|
|
611
|
+
recordAudit({
|
|
612
|
+
action: 'update',
|
|
613
|
+
resource: res,
|
|
614
|
+
recordId: String(targetId),
|
|
615
|
+
meta: adminContext.getProviderMeta(res),
|
|
616
|
+
});
|
|
589
617
|
try {
|
|
590
618
|
const lp = adminContext.liveProvider;
|
|
591
619
|
if (lp?.publish) lp.publish({ type: 'UPDATE', resource: res, payload: { ids: targetId != null ? [targetId] : [] } });
|
|
@@ -598,7 +626,15 @@ export function useForm<
|
|
|
598
626
|
const ctx = context as { targetId?: string | number; targetResource?: string } | undefined;
|
|
599
627
|
const targetId = ctx?.targetId ?? currentId;
|
|
600
628
|
const res = ctx?.targetResource ?? resource;
|
|
601
|
-
invalidateByScopes(
|
|
629
|
+
invalidateByScopes(
|
|
630
|
+
queryClient,
|
|
631
|
+
res,
|
|
632
|
+
invalidateScopes,
|
|
633
|
+
['list', 'many', 'detail'],
|
|
634
|
+
targetId ?? undefined,
|
|
635
|
+
options.dataProviderName,
|
|
636
|
+
(queryKey) => queryKeyMatchesTenant(queryKey, adminContext.tenantCacheKey),
|
|
637
|
+
);
|
|
602
638
|
},
|
|
603
639
|
onError: (error: unknown, _vars: unknown, context: unknown) => {
|
|
604
640
|
const ctx = context as { previousQueries?: [unknown, unknown][] } | undefined;
|
|
@@ -649,7 +685,7 @@ export function useForm<
|
|
|
649
685
|
|
|
650
686
|
if (!isRecordIdentityCurrent(submitIdentity)) return;
|
|
651
687
|
|
|
652
|
-
if (!runValidation()) {
|
|
688
|
+
if (!runValidation()) { sendNotification({ type: 'warning', message: i18n.t('validation.required') }); return; }
|
|
653
689
|
|
|
654
690
|
const submittedValues = createPersistenceSnapshot(values);
|
|
655
691
|
if (action === 'create' || action === 'clone') await createMut.mutateAsync(submittedValues);
|
|
@@ -740,7 +776,15 @@ export function useForm<
|
|
|
740
776
|
await provider.update<TData, TVariables>({ resource: safeResource, id: safeId as string | number, variables: submittedValues, meta: safeMeta });
|
|
741
777
|
if (formDestroyed) return;
|
|
742
778
|
const scopes = autoSaveOpts.invalidates ?? ['resourceAll'];
|
|
743
|
-
invalidateByScopes(
|
|
779
|
+
invalidateByScopes(
|
|
780
|
+
queryClient,
|
|
781
|
+
safeResource,
|
|
782
|
+
scopes,
|
|
783
|
+
['resourceAll'],
|
|
784
|
+
safeId ?? undefined,
|
|
785
|
+
options.dataProviderName,
|
|
786
|
+
(queryKey) => queryKeyMatchesTenant(queryKey, adminContext.tenantCacheKey),
|
|
787
|
+
);
|
|
744
788
|
if (!isRecordIdentityCurrent(scheduledIdentity)) return;
|
|
745
789
|
autoSaveStatus = 'saved';
|
|
746
790
|
lastAutoSaveData = submittedValues;
|
|
@@ -769,7 +813,11 @@ export function useForm<
|
|
|
769
813
|
|
|
770
814
|
if (autoSaveOpts?.invalidateOnUnmount) {
|
|
771
815
|
$effect(() => {
|
|
772
|
-
return () => queryClient.invalidateQueries({
|
|
816
|
+
return () => queryClient.invalidateQueries({
|
|
817
|
+
predicate: (q) => queryKeyMatchesTenant(q.queryKey, adminContext.tenantCacheKey)
|
|
818
|
+
&& q.queryKey[0] === options.dataProviderName
|
|
819
|
+
&& q.queryKey[1] === resource,
|
|
820
|
+
});
|
|
773
821
|
});
|
|
774
822
|
}
|
|
775
823
|
|
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 { queryKeyMatchesTenant } from './provider-bundle';
|
|
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();
|
|
@@ -106,7 +109,8 @@ export function createLiveSubscription(paramsFn: () => LiveSubscriptionParams):
|
|
|
106
109
|
params.onLiveEvent?.(event);
|
|
107
110
|
if (liveMode === 'auto') {
|
|
108
111
|
const dpN = params.dataProviderName;
|
|
109
|
-
const dpMatch = (q: { queryKey: readonly unknown[] }) =>
|
|
112
|
+
const dpMatch = (q: { queryKey: readonly unknown[] }) =>
|
|
113
|
+
queryKeyMatchesTenant(q.queryKey, adminContext.tenantCacheKey) && q.queryKey[0] === dpN;
|
|
110
114
|
queryClient.invalidateQueries({ predicate: (q) => dpMatch(q) && q.queryKey[1] === params.resource });
|
|
111
115
|
}
|
|
112
116
|
},
|
|
@@ -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
|
@@ -24,13 +24,14 @@ export type { OvertimeResult, OvertimeOptions, NotificationConfig } from './hook
|
|
|
24
24
|
import { createQuery, createInfiniteQuery, createMutation, useQueryClient } from '@tanstack/svelte-query';
|
|
25
25
|
import { getAdminOptions } from './options.svelte';
|
|
26
26
|
import { captureAdminContext } from './context.svelte';
|
|
27
|
+
import { appendTenantCacheKey, queryKeyMatchesTenant } from './provider-bundle';
|
|
27
28
|
import { useParsed } from './useParsed.svelte';
|
|
28
29
|
import { createOvertimeTracker, createLiveSubscription, fireSuccessNotification, fireErrorNotification, checkError } from './hook-utils.svelte';
|
|
29
30
|
import { invalidateByScopes, publishLiveEvent } from './mutation-hooks.svelte';
|
|
30
31
|
import type { NotificationConfig, OvertimeOptions, LiveSubscriptionParams } from './hook-utils.svelte';
|
|
31
32
|
import type { BaseRecord, HttpError, Pagination, Sort, Filter, DataProvider, KnownResources } from './types';
|
|
32
33
|
import type { LiveMode, LiveEvent } from './live.svelte';
|
|
33
|
-
import {
|
|
34
|
+
import { auditWithProvider } from './audit';
|
|
34
35
|
|
|
35
36
|
// ─── useInfiniteList ────────────────────────────────────────────────
|
|
36
37
|
|
|
@@ -58,7 +59,15 @@ export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError =
|
|
|
58
59
|
const resource = options.resource ?? parsed.resource ?? '';
|
|
59
60
|
const provider = adminContext.getDataProviderForResource(resource, options.dataProviderName);
|
|
60
61
|
return {
|
|
61
|
-
queryKey: [
|
|
62
|
+
queryKey: appendTenantCacheKey([
|
|
63
|
+
options.dataProviderName,
|
|
64
|
+
resource,
|
|
65
|
+
'infiniteList',
|
|
66
|
+
options.pagination?.pageSize,
|
|
67
|
+
options.sorters,
|
|
68
|
+
options.filters,
|
|
69
|
+
options.meta,
|
|
70
|
+
], adminContext.tenantCacheKey),
|
|
62
71
|
queryFn: async ({ pageParam = 1 }) => {
|
|
63
72
|
const result = await provider.getList<TData>({
|
|
64
73
|
resource,
|
|
@@ -99,11 +108,23 @@ export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError =
|
|
|
99
108
|
$effect(() => {
|
|
100
109
|
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
101
110
|
lastSuccessAt = query.dataUpdatedAt;
|
|
102
|
-
fireSuccessNotification(
|
|
111
|
+
fireSuccessNotification({
|
|
112
|
+
config: options.successNotification,
|
|
113
|
+
defaultMessage: '',
|
|
114
|
+
data: query.data,
|
|
115
|
+
resource: options.resource ?? parsed.resource ?? '',
|
|
116
|
+
provider: adminContext.notificationProvider,
|
|
117
|
+
});
|
|
103
118
|
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
104
119
|
lastErrorAt = query.errorUpdatedAt;
|
|
105
120
|
checkError(query.error, adminContext);
|
|
106
|
-
fireErrorNotification(
|
|
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
|
+
});
|
|
107
128
|
}
|
|
108
129
|
});
|
|
109
130
|
|
|
@@ -152,7 +173,9 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
152
173
|
const query = createQuery<{ data: TData[]; total: number }>(() => {
|
|
153
174
|
const provider = adminContext.getDataProviderForResource(resource, dataProviderName);
|
|
154
175
|
return {
|
|
155
|
-
queryKey: [
|
|
176
|
+
queryKey: appendTenantCacheKey([
|
|
177
|
+
dataProviderName, resource, 'select', allFilters, sorters, pagination, meta,
|
|
178
|
+
], adminContext.tenantCacheKey),
|
|
156
179
|
queryFn: () => provider.getList<TData>({ resource, sorters, filters: allFilters, pagination: { current: 1, pageSize: effectivePageSize }, meta }),
|
|
157
180
|
enabled: options.queryOptions?.enabled ?? true,
|
|
158
181
|
staleTime: options.queryOptions?.staleTime ?? adminOptions.reactQuery?.staleTime,
|
|
@@ -165,7 +188,9 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
165
188
|
? createQuery<{ data: TData[] }>(() => {
|
|
166
189
|
const provider = adminContext.getDataProviderForResource(resource, dataProviderName);
|
|
167
190
|
return {
|
|
168
|
-
queryKey: [
|
|
191
|
+
queryKey: appendTenantCacheKey([
|
|
192
|
+
dataProviderName, resource, 'select-defaults', defaultValueIds,
|
|
193
|
+
], adminContext.tenantCacheKey),
|
|
169
194
|
queryFn: async () => {
|
|
170
195
|
if (provider.getMany) return provider.getMany<TData>({ resource, ids: defaultValueIds, meta });
|
|
171
196
|
const results = await Promise.all(defaultValueIds.map(id => provider.getOne<TData>({ resource, id, meta })));
|
|
@@ -203,11 +228,23 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
203
228
|
$effect(() => {
|
|
204
229
|
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
205
230
|
lastSuccessAt = query.dataUpdatedAt;
|
|
206
|
-
fireSuccessNotification(
|
|
231
|
+
fireSuccessNotification({
|
|
232
|
+
config: options.successNotification,
|
|
233
|
+
defaultMessage: '',
|
|
234
|
+
data: query.data,
|
|
235
|
+
resource,
|
|
236
|
+
provider: adminContext.notificationProvider,
|
|
237
|
+
});
|
|
207
238
|
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
208
239
|
lastErrorAt = query.errorUpdatedAt;
|
|
209
240
|
checkError(query.error, adminContext);
|
|
210
|
-
fireErrorNotification(
|
|
241
|
+
fireErrorNotification({
|
|
242
|
+
config: options.errorNotification,
|
|
243
|
+
defaultMessage: 'Fetch failed',
|
|
244
|
+
error: query.error,
|
|
245
|
+
resource,
|
|
246
|
+
provider: adminContext.notificationProvider,
|
|
247
|
+
});
|
|
211
248
|
}
|
|
212
249
|
});
|
|
213
250
|
|
|
@@ -250,7 +287,9 @@ export function useCustom<TData = unknown, TError = HttpError>(options: UseCusto
|
|
|
250
287
|
const query = createQuery<{ data: TData }, TError>(() => {
|
|
251
288
|
const provider = adminContext.getDataProvider(options.dataProviderName);
|
|
252
289
|
return {
|
|
253
|
-
queryKey: [
|
|
290
|
+
queryKey: appendTenantCacheKey([
|
|
291
|
+
options.dataProviderName, 'custom', options.url, options.method, options.config, options.meta,
|
|
292
|
+
], adminContext.tenantCacheKey),
|
|
254
293
|
queryFn: async () => {
|
|
255
294
|
if (!provider.custom) throw new Error('DataProvider does not support custom method');
|
|
256
295
|
return provider.custom<TData>({
|
|
@@ -276,11 +315,22 @@ export function useCustom<TData = unknown, TError = HttpError>(options: UseCusto
|
|
|
276
315
|
$effect(() => {
|
|
277
316
|
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
278
317
|
lastSuccessAt = query.dataUpdatedAt;
|
|
279
|
-
fireSuccessNotification(
|
|
318
|
+
fireSuccessNotification({
|
|
319
|
+
config: options.successNotification,
|
|
320
|
+
defaultMessage: '',
|
|
321
|
+
data: query.data,
|
|
322
|
+
provider: adminContext.notificationProvider,
|
|
323
|
+
});
|
|
280
324
|
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
281
325
|
lastErrorAt = query.errorUpdatedAt;
|
|
282
326
|
checkError(query.error, adminContext);
|
|
283
|
-
fireErrorNotification(
|
|
327
|
+
fireErrorNotification({
|
|
328
|
+
config: options.errorNotification,
|
|
329
|
+
defaultMessage: 'Custom request failed',
|
|
330
|
+
error: query.error,
|
|
331
|
+
resource: '',
|
|
332
|
+
provider: adminContext.notificationProvider,
|
|
333
|
+
});
|
|
284
334
|
}
|
|
285
335
|
});
|
|
286
336
|
|
|
@@ -301,7 +351,15 @@ export function useCustomMutation<TData = unknown, TError = HttpError, TVariable
|
|
|
301
351
|
},
|
|
302
352
|
onSuccess: (data, params) => {
|
|
303
353
|
if (params.resource && params.invalidates !== false) {
|
|
304
|
-
invalidateByScopes(
|
|
354
|
+
invalidateByScopes(
|
|
355
|
+
queryClient,
|
|
356
|
+
params.resource,
|
|
357
|
+
params.invalidates,
|
|
358
|
+
['list', 'many'],
|
|
359
|
+
undefined,
|
|
360
|
+
dataProviderName,
|
|
361
|
+
(queryKey) => queryKeyMatchesTenant(queryKey, adminContext.tenantCacheKey),
|
|
362
|
+
);
|
|
305
363
|
}
|
|
306
364
|
},
|
|
307
365
|
onError: (error) => {
|
|
@@ -331,17 +389,41 @@ 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
|
+
resName,
|
|
421
|
+
undefined,
|
|
422
|
+
['list', 'many'],
|
|
423
|
+
undefined,
|
|
424
|
+
params.dataProviderName,
|
|
425
|
+
(queryKey) => queryKeyMatchesTenant(queryKey, adminContext.tenantCacheKey),
|
|
426
|
+
);
|
|
345
427
|
},
|
|
346
428
|
}));
|
|
347
429
|
|
|
@@ -365,19 +447,49 @@ export function useUpdateMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
365
447
|
},
|
|
366
448
|
onSuccess: (data, params) => {
|
|
367
449
|
const resName = params.resource ?? resource;
|
|
368
|
-
fireSuccessNotification(
|
|
369
|
-
|
|
450
|
+
fireSuccessNotification({
|
|
451
|
+
config: undefined,
|
|
452
|
+
defaultMessage: 'Updated successfully',
|
|
453
|
+
data: data.data,
|
|
454
|
+
values: params.variables,
|
|
455
|
+
resource: resName,
|
|
456
|
+
provider: adminContext.notificationProvider,
|
|
457
|
+
});
|
|
458
|
+
auditWithProvider(
|
|
459
|
+
{ action: 'update', resource: resName, meta: adminContext.getProviderMeta(resName) },
|
|
460
|
+
adminContext.auditLogProvider,
|
|
461
|
+
);
|
|
370
462
|
publishLiveEvent(resName, 'updated', params.ids, adminContext);
|
|
371
463
|
},
|
|
372
464
|
onError: (error, params) => {
|
|
373
465
|
checkError(error, adminContext);
|
|
374
|
-
fireErrorNotification(
|
|
466
|
+
fireErrorNotification({
|
|
467
|
+
config: undefined,
|
|
468
|
+
defaultMessage: 'Update many failed',
|
|
469
|
+
error,
|
|
470
|
+
resource: params.resource ?? resource,
|
|
471
|
+
provider: adminContext.notificationProvider,
|
|
472
|
+
});
|
|
375
473
|
},
|
|
376
474
|
onSettled: (_d, _e, params) => {
|
|
377
475
|
const resName = params.resource ?? resource;
|
|
378
|
-
invalidateByScopes(
|
|
476
|
+
invalidateByScopes(
|
|
477
|
+
queryClient,
|
|
478
|
+
resName,
|
|
479
|
+
undefined,
|
|
480
|
+
['list', 'many', 'detail'],
|
|
481
|
+
undefined,
|
|
482
|
+
params.dataProviderName,
|
|
483
|
+
(queryKey) => queryKeyMatchesTenant(queryKey, adminContext.tenantCacheKey),
|
|
484
|
+
);
|
|
379
485
|
for (const id of params.ids) {
|
|
380
|
-
queryClient.invalidateQueries({
|
|
486
|
+
queryClient.invalidateQueries({
|
|
487
|
+
predicate: (q) => queryKeyMatchesTenant(q.queryKey, adminContext.tenantCacheKey)
|
|
488
|
+
&& q.queryKey[0] === params.dataProviderName
|
|
489
|
+
&& q.queryKey[1] === resName
|
|
490
|
+
&& q.queryKey[2] === 'one'
|
|
491
|
+
&& q.queryKey[3] === id,
|
|
492
|
+
});
|
|
381
493
|
}
|
|
382
494
|
},
|
|
383
495
|
}));
|
|
@@ -402,19 +514,48 @@ export function useDeleteMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
402
514
|
},
|
|
403
515
|
onSuccess: (data, params) => {
|
|
404
516
|
const resName = params.resource ?? resource;
|
|
405
|
-
fireSuccessNotification(
|
|
406
|
-
|
|
517
|
+
fireSuccessNotification({
|
|
518
|
+
config: undefined,
|
|
519
|
+
defaultMessage: 'Deleted successfully',
|
|
520
|
+
data: data.data,
|
|
521
|
+
resource: resName,
|
|
522
|
+
provider: adminContext.notificationProvider,
|
|
523
|
+
});
|
|
524
|
+
auditWithProvider(
|
|
525
|
+
{ action: 'delete', resource: resName, meta: adminContext.getProviderMeta(resName) },
|
|
526
|
+
adminContext.auditLogProvider,
|
|
527
|
+
);
|
|
407
528
|
publishLiveEvent(resName, 'deleted', params.ids, adminContext);
|
|
408
529
|
},
|
|
409
530
|
onError: (error, params) => {
|
|
410
531
|
checkError(error, adminContext);
|
|
411
|
-
fireErrorNotification(
|
|
532
|
+
fireErrorNotification({
|
|
533
|
+
config: undefined,
|
|
534
|
+
defaultMessage: 'Delete many failed',
|
|
535
|
+
error,
|
|
536
|
+
resource: params.resource ?? resource,
|
|
537
|
+
provider: adminContext.notificationProvider,
|
|
538
|
+
});
|
|
412
539
|
},
|
|
413
540
|
onSettled: (_d, _e, params) => {
|
|
414
541
|
const resName = params.resource ?? resource;
|
|
415
|
-
invalidateByScopes(
|
|
542
|
+
invalidateByScopes(
|
|
543
|
+
queryClient,
|
|
544
|
+
resName,
|
|
545
|
+
undefined,
|
|
546
|
+
['list', 'many'],
|
|
547
|
+
undefined,
|
|
548
|
+
params.dataProviderName,
|
|
549
|
+
(queryKey) => queryKeyMatchesTenant(queryKey, adminContext.tenantCacheKey),
|
|
550
|
+
);
|
|
416
551
|
for (const id of params.ids) {
|
|
417
|
-
queryClient.removeQueries({
|
|
552
|
+
queryClient.removeQueries({
|
|
553
|
+
predicate: (q) => queryKeyMatchesTenant(q.queryKey, adminContext.tenantCacheKey)
|
|
554
|
+
&& q.queryKey[0] === params.dataProviderName
|
|
555
|
+
&& q.queryKey[1] === resName
|
|
556
|
+
&& q.queryKey[2] === 'one'
|
|
557
|
+
&& q.queryKey[3] === id,
|
|
558
|
+
});
|
|
418
559
|
}
|
|
419
560
|
},
|
|
420
561
|
}));
|
|
@@ -426,15 +567,21 @@ export function useDeleteMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
426
567
|
|
|
427
568
|
export function useInvalidate() {
|
|
428
569
|
const queryClient = useQueryClient();
|
|
570
|
+
const adminContext = captureAdminContext();
|
|
429
571
|
return (params: { resource?: string; invalidates?: string[] | 'all' | false; id?: string | number; dataProviderName?: string }) => {
|
|
430
572
|
if (params.invalidates === false) return;
|
|
431
573
|
if (params.invalidates === 'all' || !params.resource) {
|
|
432
|
-
queryClient.invalidateQueries({
|
|
574
|
+
queryClient.invalidateQueries({
|
|
575
|
+
predicate: (q) => queryKeyMatchesTenant(q.queryKey, adminContext.tenantCacheKey)
|
|
576
|
+
&& q.queryKey[0] === params.dataProviderName,
|
|
577
|
+
});
|
|
433
578
|
return;
|
|
434
579
|
}
|
|
435
580
|
const scopes = params.invalidates || ['resourceAll'];
|
|
436
581
|
const res = params.resource;
|
|
437
|
-
const dpMatch = (q: { queryKey: readonly unknown[] }) =>
|
|
582
|
+
const dpMatch = (q: { queryKey: readonly unknown[] }) =>
|
|
583
|
+
queryKeyMatchesTenant(q.queryKey, adminContext.tenantCacheKey)
|
|
584
|
+
&& q.queryKey[0] === params.dataProviderName;
|
|
438
585
|
for (const scope of scopes) {
|
|
439
586
|
if (scope === 'resourceAll') queryClient.invalidateQueries({ predicate: (q) => dpMatch(q) && q.queryKey[1] === res });
|
|
440
587
|
else if ((scope === 'detail' || scope === 'one') && params.id) queryClient.invalidateQueries({ predicate: (q) => dpMatch(q) && q.queryKey[1] === res && q.queryKey[2] === 'one' && q.queryKey[3] === params.id });
|
package/src/index.ts
CHANGED
|
@@ -8,9 +8,31 @@ export {
|
|
|
8
8
|
setLiveProvider, getLiveProvider,
|
|
9
9
|
setTaskProvider, getTaskProvider,
|
|
10
10
|
createAdminContext, provideAdminContext, getAdminContext, captureAdminContext,
|
|
11
|
+
provideTenantContext, getTenantContext, getProviderBundle,
|
|
11
12
|
resetContext,
|
|
12
13
|
} from './context.svelte';
|
|
13
14
|
export type { DataProviderInput, AdminContextSource, AdminContextValue, AdminContextAccessor } from './context.svelte';
|
|
15
|
+
export {
|
|
16
|
+
appendTenantCacheKey,
|
|
17
|
+
createProviderBundle,
|
|
18
|
+
createTenantCacheKey,
|
|
19
|
+
defaultTenantAdapter,
|
|
20
|
+
isTenantCacheKey,
|
|
21
|
+
queryKeyMatchesTenant,
|
|
22
|
+
resolveTenantProviderMeta,
|
|
23
|
+
withProviderMeta,
|
|
24
|
+
withTenantDataProvider,
|
|
25
|
+
} from './provider-bundle';
|
|
26
|
+
export type {
|
|
27
|
+
ProviderBundle,
|
|
28
|
+
ProviderMetaInput,
|
|
29
|
+
ProviderMetaResolver,
|
|
30
|
+
TenantAdapter,
|
|
31
|
+
TenantCacheIdentity,
|
|
32
|
+
TenantCacheKey,
|
|
33
|
+
TenantContext,
|
|
34
|
+
TenantId,
|
|
35
|
+
} from './provider-bundle';
|
|
14
36
|
export {
|
|
15
37
|
useList, useInfiniteList,
|
|
16
38
|
useOne, useShow,
|
|
@@ -36,10 +58,17 @@ export { readURLState, writeURLState } from './url-sync';
|
|
|
36
58
|
export { setAccessControlProvider, getAccessControlProvider, getAccessControlOptions, canAccessAsync, createFeatureGate } from './permissions.svelte';
|
|
37
59
|
export { useLive, useSubscription, usePublish } from './live.svelte';
|
|
38
60
|
export { toast, getToastQueue, consumeToastQueue, getPromiseQueue, consumePromiseQueue, resetToast } from './toast.svelte';
|
|
39
|
-
export {
|
|
61
|
+
export {
|
|
62
|
+
notify,
|
|
63
|
+
notifyWithProvider,
|
|
64
|
+
closeNotification,
|
|
65
|
+
setNotificationProvider,
|
|
66
|
+
getNotificationProvider,
|
|
67
|
+
} from './notification.svelte';
|
|
68
|
+
export type { NotificationParams } from './notification.svelte';
|
|
40
69
|
export { t, setLocale, getLocale, getAvailableLocales, addTranslations, useTranslation, setI18nProvider, getI18nProvider, createI18nScope, provideI18nScope, getI18nScope, resetI18n } from './i18n.svelte';
|
|
41
70
|
export type { I18nProvider, I18nScope, I18nScopeOptions } from './i18n.svelte';
|
|
42
|
-
export { audit, setAuditHandler, setAuditLogProvider, getAuditLogProvider } from './audit';
|
|
71
|
+
export { audit, auditWithProvider, setAuditHandler, setAuditLogProvider, getAuditLogProvider } from './audit';
|
|
43
72
|
export type { AuditLogProvider } from './audit';
|
|
44
73
|
export { setChatProvider, getChatProvider, setChatContext, getChatContext, setAgentProvider, getAgentProvider, registerApproval, resolveApproval, hasPendingApprovals, resetChatProvider } from './chatProvider.svelte';
|
|
45
74
|
export type { ChatProvider, ChatMessage, ChatContext, ChatAction, AgentProvider, AgentEvent, AgentOptions, AdminTool, AdminToolParameter, ToolResult } from './chatProvider.svelte';
|
|
@@ -67,7 +96,8 @@ export type {
|
|
|
67
96
|
DeleteParams, DeleteResult,
|
|
68
97
|
DeleteManyParams, DeleteManyResult,
|
|
69
98
|
Pagination, Sort, Filter, Identity,
|
|
70
|
-
ResourceDefinition,
|
|
99
|
+
ResourceDefinition, ResourceProviderConfig, ResourceTransportConfig, ResourceAdapterConfig,
|
|
100
|
+
FieldDefinition, MenuItem,
|
|
71
101
|
AuthActionResult, CheckResult,
|
|
72
102
|
ResourceTypeMap, KnownResources, InferData,
|
|
73
103
|
BaseRecord, Role, AuditLog
|