@svadmin/core 0.22.1 → 0.23.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 +5 -0
- package/src/auth-hooks.svelte.ts +34 -24
- package/src/chatProvider.svelte.ts +8 -21
- package/src/context.svelte.ts +20 -0
- package/src/data-transfer.svelte.ts +13 -10
- package/src/form-hooks.svelte.ts +103 -43
- package/src/helpers-pure.ts +159 -0
- package/src/helpers.test.ts +177 -0
- package/src/helpers.ts +9 -118
- package/src/hook-utils.svelte.ts +27 -6
- package/src/hooks.svelte.ts +100 -17
- package/src/i18n.svelte.ts +23 -17
- package/src/index.ts +10 -3
- package/src/live.svelte.ts +35 -19
- package/src/mutation-hooks.svelte.ts +162 -73
- package/src/notification.svelte.ts +9 -5
- package/src/options.svelte.ts +42 -19
- package/src/permissions.ts +4 -0
- package/src/query-hooks.svelte.ts +70 -20
- package/src/router-provider.ts +3 -1
- package/src/router.ts +23 -2
- package/src/routing-hooks.svelte.ts +37 -12
- package/src/table-hooks.svelte.ts +61 -15
- package/src/table-state.svelte.ts +2 -1
- package/src/theme.svelte.ts +26 -24
- package/src/toast.svelte.ts +22 -11
- package/src/types.ts +3 -1
- package/src/url-sync.ts +28 -16
- package/src/utility-hooks.svelte.ts +14 -4
package/src/hooks.svelte.ts
CHANGED
|
@@ -25,7 +25,8 @@ import { createQuery, createInfiniteQuery, createMutation, useQueryClient } from
|
|
|
25
25
|
import { getAdminOptions } from './options.svelte';
|
|
26
26
|
import { getDataProviderForResource, getDataProvider, getLiveProvider } from './context.svelte';
|
|
27
27
|
import { useParsed } from './useParsed.svelte';
|
|
28
|
-
import { createOvertimeTracker, createLiveSubscription, fireSuccessNotification, fireErrorNotification } from './hook-utils.svelte';
|
|
28
|
+
import { createOvertimeTracker, createLiveSubscription, fireSuccessNotification, fireErrorNotification, checkError } from './hook-utils.svelte';
|
|
29
|
+
import { invalidateByScopes } from './mutation-hooks.svelte';
|
|
29
30
|
import type { NotificationConfig, OvertimeOptions, LiveSubscriptionParams } from './hook-utils.svelte';
|
|
30
31
|
import type { BaseRecord, HttpError, Pagination, Sort, Filter, DataProvider, KnownResources } from './types';
|
|
31
32
|
import type { LiveMode, LiveEvent } from './live.svelte';
|
|
@@ -58,7 +59,7 @@ export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError =
|
|
|
58
59
|
const resource = options.resource ?? parsed.resource ?? '';
|
|
59
60
|
const provider = getDataProviderForResource(resource, options.dataProviderName);
|
|
60
61
|
return {
|
|
61
|
-
queryKey: [resource, 'infiniteList', options.pagination?.pageSize, options.sorters, options.filters, options.meta],
|
|
62
|
+
queryKey: [options.dataProviderName, resource, 'infiniteList', options.pagination?.pageSize, options.sorters, options.filters, options.meta],
|
|
62
63
|
queryFn: async ({ pageParam = 1 }) => {
|
|
63
64
|
const result = await provider.getList<TData>({
|
|
64
65
|
resource,
|
|
@@ -93,10 +94,15 @@ export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError =
|
|
|
93
94
|
enabled: options.queryOptions?.enabled ?? true,
|
|
94
95
|
}));
|
|
95
96
|
|
|
97
|
+
let lastSuccessAt = 0;
|
|
98
|
+
let lastErrorAt = 0;
|
|
96
99
|
$effect(() => {
|
|
97
|
-
if (query.isSuccess && options.successNotification) {
|
|
100
|
+
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
101
|
+
lastSuccessAt = query.dataUpdatedAt;
|
|
98
102
|
fireSuccessNotification(options.successNotification, '', query.data, undefined, options.resource ?? parsed.resource ?? '');
|
|
99
|
-
} else if (query.isError) {
|
|
103
|
+
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
104
|
+
lastErrorAt = query.errorUpdatedAt;
|
|
105
|
+
checkError(query.error);
|
|
100
106
|
fireErrorNotification(options.errorNotification, 'Fetch failed', query.error);
|
|
101
107
|
}
|
|
102
108
|
});
|
|
@@ -145,7 +151,7 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
145
151
|
const query = createQuery<{ data: TData[]; total: number }>(() => {
|
|
146
152
|
const provider = getDataProviderForResource(resource, dataProviderName);
|
|
147
153
|
return {
|
|
148
|
-
queryKey: [resource, 'select', allFilters, sorters, pagination, meta],
|
|
154
|
+
queryKey: [dataProviderName, resource, 'select', allFilters, sorters, pagination, meta],
|
|
149
155
|
queryFn: () => provider.getList<TData>({ resource, sorters, filters: allFilters, pagination: { current: 1, pageSize: effectivePageSize }, meta }),
|
|
150
156
|
enabled: options.queryOptions?.enabled ?? true,
|
|
151
157
|
staleTime: options.queryOptions?.staleTime ?? adminOptions.reactQuery?.staleTime,
|
|
@@ -158,7 +164,7 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
158
164
|
? createQuery<{ data: TData[] }>(() => {
|
|
159
165
|
const provider = getDataProviderForResource(resource, dataProviderName);
|
|
160
166
|
return {
|
|
161
|
-
queryKey: [resource, 'select-defaults', defaultValueIds],
|
|
167
|
+
queryKey: [dataProviderName, resource, 'select-defaults', defaultValueIds],
|
|
162
168
|
queryFn: async () => {
|
|
163
169
|
if (provider.getMany) return provider.getMany<TData>({ resource, ids: defaultValueIds, meta });
|
|
164
170
|
const results = await Promise.all(defaultValueIds.map(id => provider.getOne<TData>({ resource, id, meta })));
|
|
@@ -191,6 +197,19 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
|
|
|
191
197
|
|
|
192
198
|
const overtime = createOvertimeTracker(() => query.isLoading, options.overtimeOptions ?? adminOptions.overtime);
|
|
193
199
|
|
|
200
|
+
let lastSuccessAt = 0;
|
|
201
|
+
let lastErrorAt = 0;
|
|
202
|
+
$effect(() => {
|
|
203
|
+
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
204
|
+
lastSuccessAt = query.dataUpdatedAt;
|
|
205
|
+
fireSuccessNotification(options.successNotification, '', query.data, undefined, resource);
|
|
206
|
+
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
207
|
+
lastErrorAt = query.errorUpdatedAt;
|
|
208
|
+
checkError(query.error);
|
|
209
|
+
fireErrorNotification(options.errorNotification, 'Fetch failed', query.error);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
194
213
|
function onSearchChange(value: string) {
|
|
195
214
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
196
215
|
debounceTimer = setTimeout(() => { searchText = value; }, debounceMs);
|
|
@@ -229,7 +248,7 @@ export function useCustom<TData = unknown, TError = HttpError>(options: UseCusto
|
|
|
229
248
|
const query = createQuery<{ data: TData }, TError>(() => {
|
|
230
249
|
const provider = getDataProvider(options.dataProviderName);
|
|
231
250
|
return {
|
|
232
|
-
queryKey: ['custom', options.url, options.method, options.config, options.meta],
|
|
251
|
+
queryKey: [options.dataProviderName, 'custom', options.url, options.method, options.config, options.meta],
|
|
233
252
|
queryFn: async () => {
|
|
234
253
|
if (!provider.custom) throw new Error('DataProvider does not support custom method');
|
|
235
254
|
return provider.custom<TData>({
|
|
@@ -250,6 +269,19 @@ export function useCustom<TData = unknown, TError = HttpError>(options: UseCusto
|
|
|
250
269
|
|
|
251
270
|
const overtime = createOvertimeTracker(() => query.isLoading, options.overtimeOptions ?? adminOptions.overtime);
|
|
252
271
|
|
|
272
|
+
let lastSuccessAt = 0;
|
|
273
|
+
let lastErrorAt = 0;
|
|
274
|
+
$effect(() => {
|
|
275
|
+
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt && options.successNotification) {
|
|
276
|
+
lastSuccessAt = query.dataUpdatedAt;
|
|
277
|
+
fireSuccessNotification(options.successNotification, '', query.data);
|
|
278
|
+
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
279
|
+
lastErrorAt = query.errorUpdatedAt;
|
|
280
|
+
checkError(query.error);
|
|
281
|
+
fireErrorNotification(options.errorNotification, 'Custom request failed', query.error);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
253
285
|
return { query, get overtime() { return overtime; } };
|
|
254
286
|
}
|
|
255
287
|
|
|
@@ -258,11 +290,19 @@ export function useCustom<TData = unknown, TError = HttpError>(options: UseCusto
|
|
|
258
290
|
export function useCustomMutation<TData = unknown, TError = HttpError, TVariables = unknown>(dataProviderName?: string) {
|
|
259
291
|
const queryClient = useQueryClient();
|
|
260
292
|
|
|
261
|
-
const mutation = createMutation<{ data: TData }, TError, { url: string; method: 'get' | 'post' | 'put' | 'patch' | 'delete'; values?: TVariables; meta?: Record<string, unknown
|
|
293
|
+
const mutation = createMutation<{ data: TData }, TError, { url: string; method: 'get' | 'post' | 'put' | 'patch' | 'delete'; values?: TVariables; query?: Record<string, unknown>; headers?: Record<string, string>; sorters?: Sort[]; filters?: Filter[]; meta?: Record<string, unknown>; invalidates?: string[] | false; resource?: string }>(() => ({
|
|
262
294
|
mutationFn: async (params) => {
|
|
263
295
|
const provider = getDataProvider(dataProviderName);
|
|
264
296
|
if (!provider.custom) throw new Error('DataProvider does not support custom method');
|
|
265
|
-
return provider.custom<TData>({ url: params.url, method: params.method, payload: params.values, meta: params.meta });
|
|
297
|
+
return provider.custom<TData>({ url: params.url, method: params.method, payload: params.values, query: params.query, headers: params.headers, sorters: params.sorters, filters: params.filters, meta: params.meta });
|
|
298
|
+
},
|
|
299
|
+
onSuccess: (data, params) => {
|
|
300
|
+
if (params.resource && params.invalidates !== false) {
|
|
301
|
+
invalidateByScopes(queryClient, params.resource, params.invalidates, ['list', 'many']);
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
onError: (error) => {
|
|
305
|
+
checkError(error);
|
|
266
306
|
},
|
|
267
307
|
}));
|
|
268
308
|
|
|
@@ -290,7 +330,19 @@ export function useCreateMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
290
330
|
return { data: results.map(r => r.data) };
|
|
291
331
|
} finally { isMutating = false; }
|
|
292
332
|
},
|
|
293
|
-
onSuccess: (
|
|
333
|
+
onSuccess: (data, params) => {
|
|
334
|
+
const resName = params.resource ?? resource;
|
|
335
|
+
fireSuccessNotification(undefined, 'Created successfully', data.data, params.variables, resName);
|
|
336
|
+
audit({ action: 'create', resource: resName });
|
|
337
|
+
},
|
|
338
|
+
onError: (error, params) => {
|
|
339
|
+
checkError(error);
|
|
340
|
+
fireErrorNotification(undefined, 'Create many failed', error);
|
|
341
|
+
},
|
|
342
|
+
onSettled: (_d, _e, params) => {
|
|
343
|
+
const resName = params.resource ?? resource;
|
|
344
|
+
invalidateByScopes(queryClient, resName, undefined, ['list', 'many']);
|
|
345
|
+
},
|
|
294
346
|
}));
|
|
295
347
|
|
|
296
348
|
return { mutation, get overtime() { return overtime; } };
|
|
@@ -315,7 +367,22 @@ export function useUpdateMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
315
367
|
return { data: results.map(r => r.data) };
|
|
316
368
|
} finally { isMutating = false; }
|
|
317
369
|
},
|
|
318
|
-
onSuccess: (
|
|
370
|
+
onSuccess: (data, params) => {
|
|
371
|
+
const resName = params.resource ?? resource;
|
|
372
|
+
fireSuccessNotification(undefined, 'Updated successfully', data.data, params.variables, resName);
|
|
373
|
+
audit({ action: 'update', resource: resName });
|
|
374
|
+
},
|
|
375
|
+
onError: (error, params) => {
|
|
376
|
+
checkError(error);
|
|
377
|
+
fireErrorNotification(undefined, 'Update many failed', error);
|
|
378
|
+
},
|
|
379
|
+
onSettled: (_d, _e, params) => {
|
|
380
|
+
const resName = params.resource ?? resource;
|
|
381
|
+
invalidateByScopes(queryClient, resName, undefined, ['list', 'many', 'detail'], undefined);
|
|
382
|
+
for (const id of params.ids) {
|
|
383
|
+
queryClient.invalidateQueries({ predicate: (q) => q.queryKey[1] === resName && q.queryKey[2] === 'one' && q.queryKey[3] === id });
|
|
384
|
+
}
|
|
385
|
+
},
|
|
319
386
|
}));
|
|
320
387
|
|
|
321
388
|
return { mutation, get overtime() { return overtime; } };
|
|
@@ -340,7 +407,22 @@ export function useDeleteMany<TData extends BaseRecord = BaseRecord, TError = Ht
|
|
|
340
407
|
return { data: results.map(r => r.data) };
|
|
341
408
|
} finally { isMutating = false; }
|
|
342
409
|
},
|
|
343
|
-
onSuccess: (
|
|
410
|
+
onSuccess: (data, params) => {
|
|
411
|
+
const resName = params.resource ?? resource;
|
|
412
|
+
fireSuccessNotification(undefined, 'Deleted successfully', data.data, undefined, resName);
|
|
413
|
+
audit({ action: 'delete', resource: resName });
|
|
414
|
+
},
|
|
415
|
+
onError: (error, params) => {
|
|
416
|
+
checkError(error);
|
|
417
|
+
fireErrorNotification(undefined, 'Delete many failed', error);
|
|
418
|
+
},
|
|
419
|
+
onSettled: (_d, _e, params) => {
|
|
420
|
+
const resName = params.resource ?? resource;
|
|
421
|
+
invalidateByScopes(queryClient, resName, undefined, ['list', 'many']);
|
|
422
|
+
for (const id of params.ids) {
|
|
423
|
+
queryClient.removeQueries({ predicate: (q) => q.queryKey[1] === resName && q.queryKey[2] === 'one' && q.queryKey[3] === id });
|
|
424
|
+
}
|
|
425
|
+
},
|
|
344
426
|
}));
|
|
345
427
|
|
|
346
428
|
return { mutation, get overtime() { return overtime; } };
|
|
@@ -357,12 +439,13 @@ export function useInvalidate() {
|
|
|
357
439
|
return;
|
|
358
440
|
}
|
|
359
441
|
const scopes = params.invalidates || ['resourceAll'];
|
|
442
|
+
const res = params.resource;
|
|
360
443
|
for (const scope of scopes) {
|
|
361
|
-
if (scope === 'resourceAll') queryClient.invalidateQueries({
|
|
362
|
-
else if ((scope === 'detail' || scope === 'one') && params.id) queryClient.invalidateQueries({
|
|
363
|
-
else if (scope === 'one') queryClient.invalidateQueries({
|
|
364
|
-
else if (scope === 'list') queryClient.invalidateQueries({
|
|
365
|
-
else if (scope === 'many') queryClient.invalidateQueries({
|
|
444
|
+
if (scope === 'resourceAll') queryClient.invalidateQueries({ predicate: (q) => q.queryKey[1] === res });
|
|
445
|
+
else if ((scope === 'detail' || scope === 'one') && params.id) queryClient.invalidateQueries({ predicate: (q) => q.queryKey[1] === res && q.queryKey[2] === 'one' && q.queryKey[3] === params.id });
|
|
446
|
+
else if (scope === 'one') queryClient.invalidateQueries({ predicate: (q) => q.queryKey[1] === res && q.queryKey[2] === 'one' });
|
|
447
|
+
else if (scope === 'list') queryClient.invalidateQueries({ predicate: (q) => q.queryKey[1] === res && (q.queryKey[2] === 'list' || q.queryKey[2] === 'infiniteList' || q.queryKey[2] === 'select') });
|
|
448
|
+
else if (scope === 'many') queryClient.invalidateQueries({ predicate: (q) => q.queryKey[1] === res && q.queryKey[2] === 'many' });
|
|
366
449
|
}
|
|
367
450
|
};
|
|
368
451
|
}
|
package/src/i18n.svelte.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
let locales = $state<Record<string, Record<string, string>>>({
|
|
6
6
|
'zh-CN': {
|
|
7
7
|
'common.list': '列表',
|
|
8
8
|
'common.prev': '上一页',
|
|
@@ -19,6 +19,8 @@ const locales: Record<string, Record<string, string>> = {
|
|
|
19
19
|
'common.create': '新建',
|
|
20
20
|
'common.edit': '编辑',
|
|
21
21
|
'common.delete': '删除',
|
|
22
|
+
'common.batchDeletePartialFail': '批量删除部分失败:{failed}/{total} 条未成功',
|
|
23
|
+
'common.batchDeleteSuccess': '成功删除 {count} 条记录',
|
|
22
24
|
'common.search': '搜索...',
|
|
23
25
|
'common.confirm': '确定',
|
|
24
26
|
'common.loading': '加载中...',
|
|
@@ -151,6 +153,8 @@ const locales: Record<string, Record<string, string>> = {
|
|
|
151
153
|
'validation.minLength': '最少 {min} 个字符',
|
|
152
154
|
'validation.maxLength': '最多 {max} 个字符',
|
|
153
155
|
'validation.invalidEmail': '请输入有效的邮箱地址',
|
|
156
|
+
'validation.invalidUrl': '请输入有效的 URL 地址',
|
|
157
|
+
'validation.invalidNumber': '请输入有效的数字',
|
|
154
158
|
'validation.invalidFormat': '格式不正确',
|
|
155
159
|
// Settings
|
|
156
160
|
'settings.title': '设置',
|
|
@@ -266,6 +270,8 @@ const locales: Record<string, Record<string, string>> = {
|
|
|
266
270
|
'common.create': 'Create',
|
|
267
271
|
'common.edit': 'Edit',
|
|
268
272
|
'common.delete': 'Delete',
|
|
273
|
+
'common.batchDeletePartialFail': 'Batch delete partially failed: {failed}/{total} unsuccessful',
|
|
274
|
+
'common.batchDeleteSuccess': 'Successfully deleted {count} records',
|
|
269
275
|
'common.search': 'Search...',
|
|
270
276
|
'common.confirm': 'Confirm',
|
|
271
277
|
'common.loading': 'Loading...',
|
|
@@ -398,6 +404,8 @@ const locales: Record<string, Record<string, string>> = {
|
|
|
398
404
|
'validation.minLength': 'Minimum {min} characters',
|
|
399
405
|
'validation.maxLength': 'Maximum {max} characters',
|
|
400
406
|
'validation.invalidEmail': 'Please enter a valid email address',
|
|
407
|
+
'validation.invalidUrl': 'Please enter a valid URL',
|
|
408
|
+
'validation.invalidNumber': 'Please enter a valid number',
|
|
401
409
|
'validation.invalidFormat': 'Invalid format',
|
|
402
410
|
// Settings
|
|
403
411
|
'settings.title': 'Settings',
|
|
@@ -497,19 +505,18 @@ const locales: Record<string, Record<string, string>> = {
|
|
|
497
505
|
'profile.passwordChanged': 'Password updated successfully',
|
|
498
506
|
'profile.updatePassword': 'Update Password',
|
|
499
507
|
},
|
|
500
|
-
};
|
|
508
|
+
});
|
|
501
509
|
|
|
502
510
|
/** Detect best locale from browser language */
|
|
503
511
|
function detectLocale(): string {
|
|
504
|
-
if (typeof navigator === 'undefined') return 'en';
|
|
512
|
+
if (typeof navigator === 'undefined') return 'en';
|
|
505
513
|
const browserLang = navigator.language || navigator.languages?.[0] || 'zh-CN';
|
|
506
|
-
// Exact match
|
|
507
514
|
if (locales[browserLang]) return browserLang;
|
|
508
|
-
// Prefix match (e.g., 'zh' → 'zh-CN', 'en-US' → 'en')
|
|
509
515
|
const prefix = browserLang.split('-')[0];
|
|
510
516
|
for (const key of Object.keys(locales)) {
|
|
511
|
-
if (key.startsWith(prefix)) return key;
|
|
517
|
+
if (key === browserLang || key.startsWith(prefix + '-')) return key;
|
|
512
518
|
}
|
|
519
|
+
if (locales[prefix]) return prefix;
|
|
513
520
|
return 'en';
|
|
514
521
|
}
|
|
515
522
|
|
|
@@ -528,8 +535,8 @@ export function getAvailableLocales(): string[] {
|
|
|
528
535
|
}
|
|
529
536
|
|
|
530
537
|
export function t(key: string, params?: Record<string, string | number>): string {
|
|
531
|
-
const locale = locales[currentLocale]
|
|
532
|
-
let text = locale[key] ?? key;
|
|
538
|
+
const locale = locales[currentLocale];
|
|
539
|
+
let text = locale?.[key] ?? locales['en']?.[key] ?? key;
|
|
533
540
|
|
|
534
541
|
if (params) {
|
|
535
542
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -540,7 +547,11 @@ export function t(key: string, params?: Record<string, string | number>): string
|
|
|
540
547
|
}
|
|
541
548
|
|
|
542
549
|
export function addTranslations(locale: string, translations: Record<string, string>): void {
|
|
543
|
-
locales[locale]
|
|
550
|
+
locales = { ...locales, [locale]: { ...locales[locale], ...translations } };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export function resetI18n(): void {
|
|
554
|
+
currentLocale = detectLocale();
|
|
544
555
|
}
|
|
545
556
|
|
|
546
557
|
/**
|
|
@@ -548,14 +559,9 @@ export function addTranslations(locale: string, translations: Record<string, str
|
|
|
548
559
|
* Tracks locale changes natively.
|
|
549
560
|
*/
|
|
550
561
|
export function useTranslation() {
|
|
551
|
-
const translate =
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return (key: string, params?: Record<string, string | number>): string => {
|
|
555
|
-
// Intentionally passing the key to the main t() function which uses currentLocale
|
|
556
|
-
return t(key, params);
|
|
557
|
-
};
|
|
558
|
-
});
|
|
562
|
+
const translate = (key: string, params?: Record<string, string | number>): string => {
|
|
563
|
+
return t(key, params);
|
|
564
|
+
};
|
|
559
565
|
|
|
560
566
|
return {
|
|
561
567
|
get t() { return translate; },
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export {
|
|
|
6
6
|
setResources, getResources, getResource,
|
|
7
7
|
setRouterProvider, getRouterProvider,
|
|
8
8
|
setLiveProvider, getLiveProvider,
|
|
9
|
+
resetContext,
|
|
9
10
|
} from './context.svelte';
|
|
10
11
|
export type { DataProviderInput } from './context.svelte';
|
|
11
12
|
export {
|
|
@@ -26,7 +27,8 @@ export {
|
|
|
26
27
|
useNotification, useDataProvider,
|
|
27
28
|
useMenu, useBreadcrumb, useThemedLayoutContext,
|
|
28
29
|
} from './hooks.svelte';
|
|
29
|
-
export { matchRoute, navigate, currentPath, setActiveRouterProvider } from './router';
|
|
30
|
+
export { matchRoute, navigate, currentPath, setActiveRouterProvider, beforeEach, afterEach } from './router';
|
|
31
|
+
export type { RouteGuard } from './router';
|
|
30
32
|
export { readURLState, writeURLState } from './url-sync';
|
|
31
33
|
export { setAccessControlProvider, getAccessControlProvider, getAccessControlOptions, canAccessAsync } from './permissions';
|
|
32
34
|
export { useLive, useSubscription, usePublish } from './live.svelte';
|
|
@@ -42,12 +44,14 @@ export type { ThemeMode, ColorTheme, ThemeStrategy, ThemeConfig, ColorPreset } f
|
|
|
42
44
|
export { setUnsavedChanges, getUnsavedChanges, initUnsavedChangesNotifier } from './unsaved-changes.svelte';
|
|
43
45
|
export { setAdminOptions, getAdminOptions, getTextTransformers } from './options.svelte';
|
|
44
46
|
export type { AdminOptions, TextTransformers, OvertimeConfig } from './options.svelte';
|
|
47
|
+
export { checkError } from './hook-utils.svelte';
|
|
48
|
+
export type { NotificationConfig, OvertimeOptions, OvertimeResult, LiveSubscriptionParams } from './hook-utils.svelte';
|
|
45
49
|
|
|
46
50
|
export * from './query-keys';
|
|
47
|
-
export { HttpError } from './types';
|
|
51
|
+
export { HttpError, UndoError } from './types';
|
|
48
52
|
export type {
|
|
49
53
|
DataProvider, AuthProvider, NotificationProvider, MutationMode,
|
|
50
|
-
ValidationErrors, CrudOperator, LogicalFilter,
|
|
54
|
+
ValidationErrors, CrudOperator, LogicalFilter, FieldFilter,
|
|
51
55
|
CustomParams, CustomResult,
|
|
52
56
|
GetListParams, GetListResult,
|
|
53
57
|
GetOneParams, GetOneResult,
|
|
@@ -101,3 +105,6 @@ export {
|
|
|
101
105
|
} from './helpers';
|
|
102
106
|
export { TableState } from './table-state.svelte';
|
|
103
107
|
export type { TableStateOptions } from './table-state.svelte';
|
|
108
|
+
export type { UseInfiniteListOptions } from './hooks.svelte';
|
|
109
|
+
export type { UseSelectOptions } from './hooks.svelte';
|
|
110
|
+
export type { UseFormReturn } from './form-hooks.svelte';
|
package/src/live.svelte.ts
CHANGED
|
@@ -16,27 +16,39 @@ export interface LiveProvider {
|
|
|
16
16
|
subscribe(params: { resource: string; liveParams?: Record<string, unknown>; callback: (event: LiveEvent) => void }): () => void;
|
|
17
17
|
unsubscribe?(params: { resource: string; liveParams?: Record<string, unknown> }): void;
|
|
18
18
|
publish?(event: LiveEvent): void;
|
|
19
|
+
onConnected?: () => void;
|
|
20
|
+
onDisconnected?: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LiveProviderReconnectOptions {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
retryCount?: number;
|
|
26
|
+
retryInterval?: number;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
// ─── useLive — auto-invalidate queries on real-time events ──────
|
|
22
30
|
|
|
23
31
|
export function useLive(
|
|
24
|
-
liveProvider: LiveProvider,
|
|
25
|
-
resource: string,
|
|
26
|
-
options?: { liveMode?: LiveMode; onLiveEvent?: (event: LiveEvent) => void }
|
|
32
|
+
liveProvider: LiveProvider | (() => LiveProvider),
|
|
33
|
+
resource: string | (() => string),
|
|
34
|
+
options?: { liveMode?: LiveMode | (() => LiveMode); onLiveEvent?: (event: LiveEvent) => void; liveParams?: Record<string, unknown> | (() => Record<string, unknown>) }
|
|
27
35
|
): void {
|
|
28
36
|
const queryClient = useQueryClient();
|
|
29
|
-
const liveMode = options?.liveMode ?? 'auto';
|
|
30
|
-
|
|
31
|
-
if (liveMode === 'off') return;
|
|
32
37
|
|
|
33
38
|
$effect(() => {
|
|
34
|
-
const
|
|
35
|
-
|
|
39
|
+
const lp = typeof liveProvider === 'function' ? liveProvider() : liveProvider;
|
|
40
|
+
const res = typeof resource === 'function' ? resource() : resource;
|
|
41
|
+
const liveMode = typeof options?.liveMode === 'function' ? options.liveMode() : (options?.liveMode ?? 'auto');
|
|
42
|
+
const liveParams = typeof options?.liveParams === 'function' ? options.liveParams() : options?.liveParams;
|
|
43
|
+
if (liveMode === 'off') return;
|
|
44
|
+
|
|
45
|
+
const unsubscribe = lp.subscribe({
|
|
46
|
+
resource: res,
|
|
47
|
+
liveParams,
|
|
36
48
|
callback: (event) => {
|
|
37
49
|
options?.onLiveEvent?.(event);
|
|
38
50
|
if (liveMode === 'auto') {
|
|
39
|
-
queryClient.invalidateQueries({
|
|
51
|
+
queryClient.invalidateQueries({ predicate: (q) => q.queryKey[1] === res });
|
|
40
52
|
}
|
|
41
53
|
},
|
|
42
54
|
});
|
|
@@ -47,21 +59,25 @@ export function useLive(
|
|
|
47
59
|
// ─── useSubscription — manual channel subscription ──────────────
|
|
48
60
|
|
|
49
61
|
interface UseSubscriptionOptions {
|
|
50
|
-
resource: string;
|
|
51
|
-
liveProvider: LiveProvider;
|
|
62
|
+
resource: string | (() => string);
|
|
63
|
+
liveProvider: LiveProvider | (() => LiveProvider);
|
|
52
64
|
onLiveEvent: (event: LiveEvent) => void;
|
|
53
|
-
enabled?: boolean;
|
|
65
|
+
enabled?: boolean | (() => boolean);
|
|
66
|
+
liveParams?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
export function useSubscription(options: UseSubscriptionOptions): void {
|
|
57
|
-
const { resource, liveProvider, onLiveEvent, enabled = true } = options;
|
|
58
|
-
|
|
59
|
-
if (!enabled) return;
|
|
60
|
-
|
|
61
70
|
$effect(() => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
const res = typeof options.resource === 'function' ? options.resource() : options.resource;
|
|
72
|
+
const lp = typeof options.liveProvider === 'function' ? options.liveProvider() : options.liveProvider;
|
|
73
|
+
const enabled = typeof options.enabled === 'function' ? options.enabled() : (options.enabled ?? true);
|
|
74
|
+
const liveParams = typeof options.liveParams === 'function' ? options.liveParams() : options.liveParams;
|
|
75
|
+
if (!enabled) return;
|
|
76
|
+
|
|
77
|
+
const unsubscribe = lp.subscribe({
|
|
78
|
+
resource: res,
|
|
79
|
+
liveParams,
|
|
80
|
+
callback: options.onLiveEvent,
|
|
65
81
|
});
|
|
66
82
|
return unsubscribe;
|
|
67
83
|
});
|