@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.
@@ -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: (_d, params) => { queryClient.invalidateQueries({ queryKey: [params.resource ?? resource] }); },
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: (_d, params) => { queryClient.invalidateQueries({ queryKey: [params.resource ?? resource] }); },
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: (_d, params) => { queryClient.invalidateQueries({ queryKey: [params.resource ?? resource] }); },
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({ queryKey: [params.resource] });
362
- else if ((scope === 'detail' || scope === 'one') && params.id) queryClient.invalidateQueries({ queryKey: [params.resource, 'one', params.id] });
363
- else if (scope === 'one') queryClient.invalidateQueries({ queryKey: [params.resource, 'one'] });
364
- else if (scope === 'list') queryClient.invalidateQueries({ queryKey: [params.resource, 'list'] });
365
- else if (scope === 'many') queryClient.invalidateQueries({ queryKey: [params.resource, 'many'] });
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
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- const locales: Record<string, Record<string, string>> = {
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'; // Safe default for SSR
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] ?? locales['en'];
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] = { ...locales[locale], ...translations };
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 = $derived.by(() => {
552
- // Read currentLocale so this $derived depends on it
553
- const loc = currentLocale;
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';
@@ -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 unsubscribe = liveProvider.subscribe({
35
- resource,
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({ queryKey: [resource] });
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 unsubscribe = liveProvider.subscribe({
63
- resource,
64
- callback: onLiveEvent,
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
  });