@svadmin/core 0.50.0 → 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/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 -776
- 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 -30
- 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 -288
- 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 -108
- 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/schema-form.ts +0 -31
package/src/task-hooks.svelte.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definedOptions } from './defined-options';
|
|
2
|
+
import { createMutation,createQuery,useQueryClient } from '@tanstack/svelte-query';
|
|
2
3
|
import { getAdminOptions } from './options.svelte';
|
|
3
4
|
import { captureAdminContext } from './context.svelte';
|
|
4
5
|
import type { AdminContextAccessor } from './context.svelte';
|
|
5
6
|
import {
|
|
6
7
|
resolveTenantProviderMeta,
|
|
7
8
|
} from './provider-bundle';
|
|
8
|
-
import { queryKeyMatches } from './query-keys';
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
9
|
+
import { queryKeyMatches, type QueryKey } from './query-keys';
|
|
10
|
+
import { replaceReactiveMembers } from './reactive-projection';
|
|
11
|
+
import { taskQueryParams, withValidatedTaskProvider } from './task-provider';
|
|
12
|
+
import type { TaskError } from './task-contract';
|
|
13
|
+
import { checkError,createOvertimeTracker,fireErrorNotification,fireSuccessNotification } from './hook-utils.svelte';
|
|
14
|
+
import type { NotificationConfig,OvertimeOptions } from './hook-utils.svelte';
|
|
11
15
|
import { useTranslation } from './i18n.svelte';
|
|
12
16
|
import type {
|
|
13
|
-
HttpError,
|
|
14
17
|
SubmitTaskOptions,
|
|
15
18
|
TaskHandle,
|
|
16
19
|
TaskListResult,
|
|
@@ -18,100 +21,128 @@ import type {
|
|
|
18
21
|
TaskRecord,
|
|
19
22
|
} from './types';
|
|
20
23
|
|
|
21
|
-
function normalizeTaskProvider
|
|
22
|
-
provider?: TaskProvider
|
|
23
|
-
adminContext: AdminContextAccessor
|
|
24
|
-
): TaskProvider
|
|
25
|
-
const activeProvider
|
|
26
|
-
if
|
|
24
|
+
function normalizeTaskProvider(
|
|
25
|
+
provider?: TaskProvider,
|
|
26
|
+
adminContext: AdminContextAccessor=captureAdminContext(),
|
|
27
|
+
): TaskProvider {
|
|
28
|
+
const activeProvider=provider??adminContext.taskProvider;
|
|
29
|
+
if(!activeProvider) {
|
|
27
30
|
throw new Error('TaskProvider not found. Did you call setTaskProvider in App.svelte?');
|
|
28
31
|
}
|
|
29
|
-
return activeProvider
|
|
32
|
+
return withValidatedTaskProvider(activeProvider);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const providerKeys = new WeakMap<TaskProvider, number>();
|
|
36
|
+
let nextProviderKey = 0;
|
|
37
|
+
function taskProviderKey(provider: TaskProvider | undefined): number {
|
|
38
|
+
if (!provider) return 0;
|
|
39
|
+
let providerKey = providerKeys.get(provider);
|
|
40
|
+
if (providerKey === undefined) {
|
|
41
|
+
providerKey = ++nextProviderKey;
|
|
42
|
+
providerKeys.set(provider, providerKey);
|
|
43
|
+
}
|
|
44
|
+
return providerKey;
|
|
45
|
+
}
|
|
46
|
+
function scopedTaskKey(key: QueryKey, provider: TaskProvider | undefined): QueryKey {
|
|
47
|
+
const providerKey = taskProviderKey(provider);
|
|
48
|
+
return [{ ...key[0], params: definedOptions({ taskProvider: providerKey, query: key[0].params }) }];
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
export interface UseSubmitTaskOptions {
|
|
33
52
|
mutationOptions?: {
|
|
34
|
-
onSuccess?: (data: unknown,
|
|
35
|
-
onError?: (error: unknown,
|
|
53
|
+
onSuccess?: (data: unknown,variables: unknown,context: unknown) => void;
|
|
54
|
+
onError?: (error: unknown,variables: unknown,context: unknown) => void;
|
|
36
55
|
};
|
|
37
56
|
overtimeOptions?: OvertimeOptions;
|
|
38
57
|
}
|
|
39
58
|
|
|
40
|
-
export interface UseSubmitTaskMutateParams
|
|
59
|
+
export interface UseSubmitTaskMutateParams {
|
|
41
60
|
taskName: string;
|
|
42
61
|
options?: SubmitTaskOptions;
|
|
43
|
-
taskProvider?: TaskProvider
|
|
62
|
+
taskProvider?: TaskProvider;
|
|
44
63
|
successNotification?: NotificationConfig;
|
|
45
64
|
errorNotification?: NotificationConfig;
|
|
46
65
|
}
|
|
47
66
|
|
|
48
|
-
export function useSubmitTask
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const queryClient = useQueryClient();
|
|
56
|
-
const overtime = createOvertimeTracker(() => mutation.isPending, options.overtimeOptions ?? adminOptions.overtime);
|
|
67
|
+
export function useSubmitTask(options: UseSubmitTaskOptions={}) {
|
|
68
|
+
const adminContext=captureAdminContext();
|
|
69
|
+
const i18n=useTranslation();
|
|
70
|
+
const adminOptions=getAdminOptions();
|
|
71
|
+
const queryClient=useQueryClient();
|
|
72
|
+
const overtime=createOvertimeTracker(() => mutation.isPending,options.overtimeOptions??adminOptions.overtime);
|
|
73
|
+
const submissionScopes = new WeakMap<TaskHandle, ReturnType<AdminContextAccessor['queryKeyMatcher']>>();
|
|
57
74
|
|
|
58
|
-
const mutation
|
|
75
|
+
const mutation=createMutation<TaskHandle,Error,UseSubmitTaskMutateParams>(() => ({
|
|
59
76
|
mutationFn: async (params) => {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
77
|
+
const scope = adminContext.queryKeyMatcher();
|
|
78
|
+
const provider=normalizeTaskProvider(params.taskProvider,adminContext);
|
|
79
|
+
const providerMeta=adminContext.tenant
|
|
62
80
|
? {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
:
|
|
67
|
-
const taskOptions
|
|
81
|
+
...(params.options?.meta??{}),
|
|
82
|
+
...resolveTenantProviderMeta(adminContext.tenant,adminContext.tenantAdapter),
|
|
83
|
+
}
|
|
84
|
+
:params.options?.meta;
|
|
85
|
+
const taskOptions=providerMeta===params.options?.meta
|
|
68
86
|
? params.options
|
|
69
|
-
:
|
|
70
|
-
|
|
87
|
+
:definedOptions({
|
|
88
|
+
...params.options,
|
|
89
|
+
meta: providerMeta,
|
|
90
|
+
});
|
|
91
|
+
const handle = await provider.submit(params.taskName,taskOptions);
|
|
92
|
+
submissionScopes.set(handle, scope);
|
|
93
|
+
return handle;
|
|
71
94
|
},
|
|
72
|
-
onSuccess: (data,
|
|
95
|
+
onSuccess: (data,params,context) => {
|
|
73
96
|
fireSuccessNotification({
|
|
74
97
|
config: params.successNotification,
|
|
75
98
|
defaultMessage: i18n.t('task.submitSuccess'),
|
|
76
99
|
data,
|
|
77
100
|
values: params.options,
|
|
78
101
|
resource: params.taskName,
|
|
79
|
-
|
|
102
|
+
...definedOptions({
|
|
103
|
+
provider: adminContext.notificationProvider
|
|
104
|
+
})
|
|
80
105
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
const scope = submissionScopes.get(data);
|
|
107
|
+
if (scope) {
|
|
108
|
+
submissionScopes.delete(data);
|
|
109
|
+
void queryClient.invalidateQueries({
|
|
110
|
+
predicate: (query) => queryKeyMatches(query.queryKey,{
|
|
111
|
+
...scope,
|
|
112
|
+
kind: 'task',
|
|
113
|
+
action: 'list',
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
options.mutationOptions?.onSuccess?.(data,params,context);
|
|
89
118
|
},
|
|
90
|
-
onError: (error,
|
|
91
|
-
checkError(error,
|
|
119
|
+
onError: (error,params,context) => {
|
|
120
|
+
checkError(error,adminContext);
|
|
92
121
|
fireErrorNotification({
|
|
93
122
|
config: params.errorNotification,
|
|
94
123
|
defaultMessage: i18n.t('task.submitFailed'),
|
|
95
124
|
error,
|
|
96
125
|
resource: params.taskName,
|
|
97
|
-
|
|
126
|
+
...definedOptions({
|
|
127
|
+
provider: adminContext.notificationProvider
|
|
128
|
+
})
|
|
98
129
|
});
|
|
99
|
-
options.mutationOptions?.onError?.(error,
|
|
130
|
+
options.mutationOptions?.onError?.(error,params,context);
|
|
100
131
|
},
|
|
101
132
|
}));
|
|
102
133
|
|
|
103
|
-
return { mutation,
|
|
134
|
+
return { mutation,get overtime() { return overtime; } };
|
|
104
135
|
}
|
|
105
136
|
|
|
106
|
-
export interface UseTaskOptions
|
|
137
|
+
export interface UseTaskOptions {
|
|
107
138
|
taskId?: string;
|
|
108
|
-
taskProvider?: TaskProvider
|
|
139
|
+
taskProvider?: TaskProvider;
|
|
109
140
|
queryOptions?: {
|
|
110
141
|
enabled?: boolean;
|
|
111
142
|
staleTime?: number;
|
|
112
143
|
gcTime?: number;
|
|
113
144
|
refetchOnWindowFocus?: boolean;
|
|
114
|
-
refetchInterval?: number
|
|
145
|
+
refetchInterval?: number|false;
|
|
115
146
|
refetchIntervalInBackground?: boolean;
|
|
116
147
|
};
|
|
117
148
|
successNotification?: NotificationConfig;
|
|
@@ -119,78 +150,84 @@ export interface UseTaskOptions<TTask extends TaskRecord = TaskRecord, _TError =
|
|
|
119
150
|
overtimeOptions?: OvertimeOptions;
|
|
120
151
|
}
|
|
121
152
|
|
|
122
|
-
export function useTask
|
|
123
|
-
options: UseTaskOptions
|
|
153
|
+
export function useTask(
|
|
154
|
+
options: UseTaskOptions={},
|
|
124
155
|
) {
|
|
125
|
-
const adminContext
|
|
126
|
-
const i18n
|
|
127
|
-
const adminOptions
|
|
156
|
+
const adminContext=captureAdminContext();
|
|
157
|
+
const i18n=useTranslation();
|
|
158
|
+
const adminOptions=getAdminOptions();
|
|
128
159
|
|
|
129
|
-
const query
|
|
130
|
-
const
|
|
131
|
-
const
|
|
160
|
+
const query=createQuery<TaskRecord,Error>(() => {
|
|
161
|
+
const activeProvider=options.taskProvider??adminContext.taskProvider;
|
|
162
|
+
const provider=activeProvider? withValidatedTaskProvider(activeProvider):undefined;
|
|
163
|
+
const taskId=options.taskId;
|
|
164
|
+
const queryOptions=options.queryOptions;
|
|
132
165
|
return {
|
|
133
|
-
queryKey: adminContext.queryKeys().task.one(
|
|
166
|
+
queryKey: scopedTaskKey(adminContext.queryKeys().task.one(taskId??''),provider),
|
|
134
167
|
queryFn: async () => {
|
|
135
|
-
if
|
|
136
|
-
|
|
168
|
+
if(!provider) throw new Error('TaskProvider not found.');
|
|
169
|
+
if(!taskId) throw new Error('useTask requires a taskId');
|
|
170
|
+
return provider.get(taskId);
|
|
137
171
|
},
|
|
138
|
-
enabled: (queryOptions?.enabled
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
172
|
+
enabled: (queryOptions?.enabled??true)&&!!taskId,
|
|
173
|
+
...definedOptions({
|
|
174
|
+
staleTime: queryOptions?.staleTime??adminOptions.reactQuery?.staleTime,
|
|
175
|
+
gcTime: queryOptions?.gcTime??adminOptions.reactQuery?.gcTime,
|
|
176
|
+
refetchOnWindowFocus: queryOptions?.refetchOnWindowFocus??adminOptions.reactQuery?.refetchOnWindowFocus,
|
|
177
|
+
}),
|
|
178
|
+
...definedOptions({
|
|
179
|
+
refetchInterval: queryOptions?.refetchInterval,
|
|
180
|
+
refetchIntervalInBackground: queryOptions?.refetchIntervalInBackground,
|
|
181
|
+
}),
|
|
144
182
|
};
|
|
145
183
|
});
|
|
146
184
|
|
|
147
|
-
const overtime
|
|
185
|
+
const overtime=createOvertimeTracker(() => query.isLoading,options.overtimeOptions??adminOptions.overtime);
|
|
148
186
|
|
|
149
|
-
let lastSuccessAt
|
|
150
|
-
let lastErrorAt
|
|
187
|
+
let lastSuccessAt=0;
|
|
188
|
+
let lastErrorAt=0;
|
|
151
189
|
$effect(() => {
|
|
152
|
-
if
|
|
153
|
-
lastSuccessAt
|
|
154
|
-
if
|
|
190
|
+
if(query.isSuccess&&query.dataUpdatedAt>lastSuccessAt) {
|
|
191
|
+
lastSuccessAt=query.dataUpdatedAt;
|
|
192
|
+
if(options.successNotification) {
|
|
155
193
|
fireSuccessNotification({
|
|
156
194
|
config: options.successNotification,
|
|
157
195
|
defaultMessage: '',
|
|
158
196
|
data: query.data,
|
|
159
|
-
resource: options.taskId,
|
|
160
|
-
|
|
197
|
+
...definedOptions({ resource: options.taskId }),
|
|
198
|
+
...definedOptions({
|
|
199
|
+
provider: adminContext.notificationProvider
|
|
200
|
+
})
|
|
161
201
|
});
|
|
162
202
|
}
|
|
163
|
-
} else if
|
|
164
|
-
lastErrorAt
|
|
165
|
-
checkError(query.error,
|
|
203
|
+
} else if(query.isError&&query.errorUpdatedAt>lastErrorAt) {
|
|
204
|
+
lastErrorAt=query.errorUpdatedAt;
|
|
205
|
+
checkError(query.error,adminContext);
|
|
166
206
|
fireErrorNotification({
|
|
167
207
|
config: options.errorNotification,
|
|
168
208
|
defaultMessage: i18n.t('task.fetchFailed'),
|
|
169
209
|
error: query.error,
|
|
170
|
-
resource: options.taskId,
|
|
171
|
-
|
|
210
|
+
...definedOptions({ resource: options.taskId }),
|
|
211
|
+
...definedOptions({
|
|
212
|
+
provider: adminContext.notificationProvider
|
|
213
|
+
})
|
|
172
214
|
});
|
|
173
215
|
}
|
|
174
216
|
});
|
|
175
217
|
|
|
176
|
-
return
|
|
177
|
-
get(target, prop) {
|
|
178
|
-
if (prop === 'overtime') return overtime;
|
|
179
|
-
return target[prop as keyof typeof target];
|
|
180
|
-
},
|
|
181
|
-
}) as typeof query & { overtime: typeof overtime };
|
|
218
|
+
return replaceReactiveMembers(query,{ get overtime() { return overtime; } });
|
|
182
219
|
}
|
|
183
220
|
|
|
184
|
-
export interface UseTaskListOptions
|
|
185
|
-
params?: Record<string,
|
|
221
|
+
export interface UseTaskListOptions {
|
|
222
|
+
params?: Record<string,unknown>;
|
|
186
223
|
dlq?: boolean;
|
|
187
|
-
taskProvider?: TaskProvider
|
|
224
|
+
taskProvider?: TaskProvider;
|
|
188
225
|
queryOptions?: {
|
|
189
226
|
enabled?: boolean;
|
|
190
227
|
staleTime?: number;
|
|
191
228
|
gcTime?: number;
|
|
192
229
|
refetchOnWindowFocus?: boolean;
|
|
193
|
-
refetchInterval?: number
|
|
230
|
+
refetchInterval?: number|false;
|
|
194
231
|
refetchIntervalInBackground?: boolean;
|
|
195
232
|
};
|
|
196
233
|
successNotification?: NotificationConfig;
|
|
@@ -198,104 +235,111 @@ export interface UseTaskListOptions<TTask extends TaskRecord = TaskRecord, _TErr
|
|
|
198
235
|
overtimeOptions?: OvertimeOptions;
|
|
199
236
|
}
|
|
200
237
|
|
|
201
|
-
export function useTaskList
|
|
202
|
-
options: UseTaskListOptions
|
|
238
|
+
export function useTaskList(
|
|
239
|
+
options: UseTaskListOptions={},
|
|
203
240
|
) {
|
|
204
|
-
const adminContext
|
|
205
|
-
const i18n
|
|
206
|
-
const adminOptions
|
|
241
|
+
const adminContext=captureAdminContext();
|
|
242
|
+
const i18n=useTranslation();
|
|
243
|
+
const adminOptions=getAdminOptions();
|
|
207
244
|
|
|
208
|
-
const query
|
|
209
|
-
const
|
|
210
|
-
const
|
|
245
|
+
const query=createQuery<TaskListResult,Error>(() => {
|
|
246
|
+
const activeProvider=options.taskProvider??adminContext.taskProvider;
|
|
247
|
+
const provider=activeProvider? withValidatedTaskProvider(activeProvider):undefined;
|
|
248
|
+
const params=options.params===undefined? undefined:taskQueryParams(options.params);
|
|
249
|
+
const dlq=options.dlq??false;
|
|
250
|
+
const queryOptions=options.queryOptions;
|
|
211
251
|
return {
|
|
212
|
-
queryKey: adminContext.queryKeys().task.list({
|
|
213
|
-
list:
|
|
214
|
-
params
|
|
215
|
-
}),
|
|
252
|
+
queryKey: scopedTaskKey(adminContext.queryKeys().task.list(definedOptions({
|
|
253
|
+
list: dlq? 'dlq':'default',
|
|
254
|
+
params,
|
|
255
|
+
})),provider),
|
|
216
256
|
queryFn: async () => {
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
257
|
+
if(!provider) throw new Error('TaskProvider not found.');
|
|
258
|
+
if(dlq) {
|
|
259
|
+
if(!provider.listDlq) throw new Error('TaskProvider does not implement listDlq');
|
|
260
|
+
return provider.listDlq(params);
|
|
220
261
|
}
|
|
221
|
-
if
|
|
222
|
-
return provider.list(
|
|
262
|
+
if(!provider.list) throw new Error('TaskProvider does not implement list');
|
|
263
|
+
return provider.list(params);
|
|
223
264
|
},
|
|
224
|
-
enabled: queryOptions?.enabled
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
enabled: queryOptions?.enabled??true,
|
|
266
|
+
...definedOptions({
|
|
267
|
+
staleTime: queryOptions?.staleTime??adminOptions.reactQuery?.staleTime,
|
|
268
|
+
gcTime: queryOptions?.gcTime??adminOptions.reactQuery?.gcTime,
|
|
269
|
+
refetchOnWindowFocus: queryOptions?.refetchOnWindowFocus??adminOptions.reactQuery?.refetchOnWindowFocus,
|
|
270
|
+
}),
|
|
271
|
+
...definedOptions({
|
|
272
|
+
refetchInterval: queryOptions?.refetchInterval,
|
|
273
|
+
refetchIntervalInBackground: queryOptions?.refetchIntervalInBackground,
|
|
274
|
+
}),
|
|
230
275
|
};
|
|
231
276
|
});
|
|
232
277
|
|
|
233
|
-
const overtime
|
|
278
|
+
const overtime=createOvertimeTracker(() => query.isLoading,options.overtimeOptions??adminOptions.overtime);
|
|
234
279
|
|
|
235
|
-
let lastSuccessAt
|
|
236
|
-
let lastErrorAt
|
|
280
|
+
let lastSuccessAt=0;
|
|
281
|
+
let lastErrorAt=0;
|
|
237
282
|
$effect(() => {
|
|
238
|
-
if
|
|
239
|
-
lastSuccessAt
|
|
240
|
-
if
|
|
283
|
+
if(query.isSuccess&&query.dataUpdatedAt>lastSuccessAt) {
|
|
284
|
+
lastSuccessAt=query.dataUpdatedAt;
|
|
285
|
+
if(options.successNotification) {
|
|
241
286
|
fireSuccessNotification({
|
|
242
287
|
config: options.successNotification,
|
|
243
288
|
defaultMessage: '',
|
|
244
289
|
data: query.data,
|
|
245
290
|
values: options.params,
|
|
246
|
-
resource: options.dlq
|
|
247
|
-
|
|
291
|
+
resource: options.dlq? 'taskDlq':'tasks',
|
|
292
|
+
...definedOptions({
|
|
293
|
+
provider: adminContext.notificationProvider
|
|
294
|
+
})
|
|
248
295
|
});
|
|
249
296
|
}
|
|
250
|
-
} else if
|
|
251
|
-
lastErrorAt
|
|
252
|
-
checkError(query.error,
|
|
297
|
+
} else if(query.isError&&query.errorUpdatedAt>lastErrorAt) {
|
|
298
|
+
lastErrorAt=query.errorUpdatedAt;
|
|
299
|
+
checkError(query.error,adminContext);
|
|
253
300
|
fireErrorNotification({
|
|
254
301
|
config: options.errorNotification,
|
|
255
302
|
defaultMessage: i18n.t('task.fetchListFailed'),
|
|
256
303
|
error: query.error,
|
|
257
|
-
resource: options.dlq
|
|
258
|
-
|
|
304
|
+
resource: options.dlq? 'taskDlq':'tasks',
|
|
305
|
+
...definedOptions({
|
|
306
|
+
provider: adminContext.notificationProvider
|
|
307
|
+
})
|
|
259
308
|
});
|
|
260
309
|
}
|
|
261
310
|
});
|
|
262
311
|
|
|
263
|
-
return
|
|
264
|
-
get(target, prop) {
|
|
265
|
-
if (prop === 'overtime') return overtime;
|
|
266
|
-
return target[prop as keyof typeof target];
|
|
267
|
-
},
|
|
268
|
-
}) as typeof query & { overtime: typeof overtime };
|
|
312
|
+
return replaceReactiveMembers(query,{ get overtime() { return overtime; } });
|
|
269
313
|
}
|
|
270
314
|
|
|
271
|
-
export interface UseTaskSubscriptionOptions
|
|
315
|
+
export interface UseTaskSubscriptionOptions {
|
|
272
316
|
taskId: string;
|
|
273
|
-
taskProvider?: TaskProvider
|
|
317
|
+
taskProvider?: TaskProvider;
|
|
274
318
|
enabled?: boolean;
|
|
275
|
-
onTask: (task:
|
|
319
|
+
onTask: (task: TaskRecord) => void;
|
|
320
|
+
onError?: (error: TaskError) => void;
|
|
276
321
|
}
|
|
277
322
|
|
|
278
|
-
export function useTaskSubscription
|
|
279
|
-
options: UseTaskSubscriptionOptions
|
|
323
|
+
export function useTaskSubscription(
|
|
324
|
+
options: UseTaskSubscriptionOptions,
|
|
280
325
|
): void {
|
|
281
|
-
const adminContext
|
|
326
|
+
const adminContext=captureAdminContext();
|
|
282
327
|
$effect(() => {
|
|
283
|
-
const enabled
|
|
284
|
-
if
|
|
328
|
+
const enabled=options.enabled??true;
|
|
329
|
+
if(!enabled) return;
|
|
285
330
|
|
|
286
|
-
const provider
|
|
287
|
-
if
|
|
331
|
+
const provider=normalizeTaskProvider(options.taskProvider,adminContext);
|
|
332
|
+
if(!provider.subscribe) {
|
|
288
333
|
throw new Error('TaskProvider does not implement subscribe');
|
|
289
334
|
}
|
|
290
335
|
|
|
291
|
-
const subscription
|
|
292
|
-
if (!subscription) return;
|
|
336
|
+
const subscription=provider.subscribe(options.taskId,options.onTask,options.onError);
|
|
293
337
|
|
|
294
|
-
if
|
|
338
|
+
if(typeof subscription==='function') {
|
|
295
339
|
return subscription;
|
|
296
340
|
}
|
|
297
341
|
|
|
298
|
-
if
|
|
342
|
+
if(typeof subscription.unsubscribe==='function') {
|
|
299
343
|
return () => subscription.unsubscribe();
|
|
300
344
|
}
|
|
301
345
|
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { checkExact } from './schema-validation';
|
|
3
|
+
import { snapshotPlainData } from './plain-data';
|
|
4
|
+
import { TaskError, decodeTaskList, decodeTaskRecord, decodeTaskSubmitOptions, type SubmitTaskOptions } from './task-contract';
|
|
5
|
+
import { requiredTaskClientMethod, taskClientMethod, taskHandleField, taskHandleMethod, validatedTaskSubscription } from './task-subscription';
|
|
6
|
+
import type { TaskHandle, TaskProvider } from './types';
|
|
7
|
+
|
|
8
|
+
export interface TaskTransport {
|
|
9
|
+
submit(name: string, options?: SubmitTaskOptions): Promise<unknown>;
|
|
10
|
+
get(id: string): Promise<unknown>;
|
|
11
|
+
list?(params?: Record<string, unknown>): Promise<unknown>;
|
|
12
|
+
listDlq?(params?: Record<string, unknown>): Promise<unknown>;
|
|
13
|
+
cancel?(id: string): Promise<unknown>;
|
|
14
|
+
retry?(id: string): Promise<unknown>;
|
|
15
|
+
subscribe?(id: string, callback: (task: unknown) => void, onError?: (error: unknown) => void): unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const providers = new WeakMap<TaskTransport, TaskProvider>();
|
|
19
|
+
const paramsSchema = Type.Record(Type.String(), Type.Unknown());
|
|
20
|
+
|
|
21
|
+
export function taskInputText(value: unknown): string {
|
|
22
|
+
if (typeof value === 'string' && value.trim().length > 0) return value;
|
|
23
|
+
throw new TaskError('INVALID_TASK_INPUT');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function taskQueryParams(value: unknown): Record<string, unknown> {
|
|
27
|
+
try {
|
|
28
|
+
const candidate = snapshotPlainData(value);
|
|
29
|
+
if (checkExact(paramsSchema, candidate)) return candidate;
|
|
30
|
+
} catch {
|
|
31
|
+
// Query inputs must be detached plain data.
|
|
32
|
+
}
|
|
33
|
+
throw new TaskError('INVALID_TASK_INPUT');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function request(invoke: () => unknown, write = false): Promise<unknown> {
|
|
37
|
+
try { return await invoke(); }
|
|
38
|
+
catch { throw new TaskError('TASK_PROVIDER_FAILED', write); }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handle(value: unknown): TaskHandle {
|
|
42
|
+
const rawId = taskHandleField(value, 'id', true);
|
|
43
|
+
if (typeof rawId !== 'string' || !rawId.trim()) throw new TaskError('INVALID_TASK_RESPONSE', true);
|
|
44
|
+
const id = rawId;
|
|
45
|
+
const wait = taskHandleMethod(value, 'wait', true);
|
|
46
|
+
const result: TaskHandle = {
|
|
47
|
+
id,
|
|
48
|
+
async wait() { return decodeTaskRecord(await request(() => wait()), id); },
|
|
49
|
+
};
|
|
50
|
+
if (taskHandleField(value, 'cancel', true) !== undefined) {
|
|
51
|
+
const cancel = taskHandleMethod(value, 'cancel', true);
|
|
52
|
+
result.cancel = async () => decodeTaskRecord(await request(() => cancel(), true), id, true);
|
|
53
|
+
}
|
|
54
|
+
if (taskHandleField(value, 'retry', true) !== undefined) {
|
|
55
|
+
const retry = taskHandleMethod(value, 'retry', true);
|
|
56
|
+
result.retry = async () => decodeTaskRecord(await request(() => retry(), true), id, true);
|
|
57
|
+
}
|
|
58
|
+
if (taskHandleField(value, 'subscribe', true) !== undefined) {
|
|
59
|
+
const subscribe = taskHandleMethod(value, 'subscribe', true);
|
|
60
|
+
result.subscribe = (callback, onError) => validatedTaskSubscription(
|
|
61
|
+
(onUpdate, onFailure) => subscribe(onUpdate, onFailure),
|
|
62
|
+
value => decodeTaskRecord(value, id), callback, onError,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function withValidatedTaskProvider(transport: TaskTransport): TaskProvider {
|
|
69
|
+
const existing = providers.get(transport);
|
|
70
|
+
if (existing) return existing;
|
|
71
|
+
const submit = requiredTaskClientMethod(transport, 'submit');
|
|
72
|
+
const get = requiredTaskClientMethod(transport, 'get');
|
|
73
|
+
const list = taskClientMethod(transport, 'list', true);
|
|
74
|
+
const listDlq = taskClientMethod(transport, 'listDlq', true);
|
|
75
|
+
const cancel = taskClientMethod(transport, 'cancel', true);
|
|
76
|
+
const retry = taskClientMethod(transport, 'retry', true);
|
|
77
|
+
const subscribe = taskClientMethod(transport, 'subscribe', true);
|
|
78
|
+
const provider: TaskProvider = {
|
|
79
|
+
async submit(name, options) {
|
|
80
|
+
const taskName = taskInputText(name);
|
|
81
|
+
const input = options === undefined ? undefined : decodeTaskSubmitOptions(options);
|
|
82
|
+
return handle(await request(() => submit(taskName, input), true));
|
|
83
|
+
},
|
|
84
|
+
async get(taskId) {
|
|
85
|
+
const id = taskInputText(taskId);
|
|
86
|
+
return decodeTaskRecord(await request(() => get(id)), id);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
if (list) provider.list = async params => {
|
|
90
|
+
const input = params === undefined ? undefined : taskQueryParams(params);
|
|
91
|
+
return decodeTaskList(await request(() => list(input)));
|
|
92
|
+
};
|
|
93
|
+
if (listDlq) provider.listDlq = async params => {
|
|
94
|
+
const input = params === undefined ? undefined : taskQueryParams(params);
|
|
95
|
+
return decodeTaskList(await request(() => listDlq(input)));
|
|
96
|
+
};
|
|
97
|
+
if (cancel) provider.cancel = async taskId => {
|
|
98
|
+
const id = taskInputText(taskId);
|
|
99
|
+
return decodeTaskRecord(await request(() => cancel(id), true), id, true);
|
|
100
|
+
};
|
|
101
|
+
if (retry) provider.retry = async taskId => {
|
|
102
|
+
const id = taskInputText(taskId);
|
|
103
|
+
return decodeTaskRecord(await request(() => retry(id), true), id, true);
|
|
104
|
+
};
|
|
105
|
+
if (subscribe) provider.subscribe = (taskId, callback, onError) => {
|
|
106
|
+
const id = taskInputText(taskId);
|
|
107
|
+
return validatedTaskSubscription(
|
|
108
|
+
(onUpdate, onFailure) => subscribe(id, onUpdate, onFailure),
|
|
109
|
+
value => decodeTaskRecord(value, id), callback, onError,
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
providers.set(transport, provider);
|
|
113
|
+
providers.set(provider, provider);
|
|
114
|
+
return provider;
|
|
115
|
+
}
|