@svadmin/core 0.31.0 → 0.32.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.
@@ -1,12 +1,10 @@
1
- /* eslint-disable svelte/prefer-svelte-reactivity */
2
- import { navigate } from './router';
3
- import { getResource, getResources, getRouterProvider } from './context.svelte';
1
+ import { captureAdminContext } from './context.svelte';
4
2
  import { useParsed } from './useParsed.svelte';
5
3
 
6
4
  // ─── routing-hooks.svelte.ts ───────────────────────────────────
7
5
 
8
6
  export function useGetToPath() {
9
- const routerProvider = getRouterProvider();
7
+ const adminContext = captureAdminContext();
10
8
  const parsed = useParsed();
11
9
 
12
10
  return (options: { resource?: string; action?: 'list' | 'create' | 'edit' | 'show' | 'clone'; id?: string | number; meta?: Record<string, unknown> }) => {
@@ -31,7 +29,7 @@ export function useGetToPath() {
31
29
  else if (action === 'clone' && id) path += `/clone/${id}`;
32
30
 
33
31
  // Apply router mapping if the router provider defines custom path transformers
34
- if (routerProvider?.go) {
32
+ if (adminContext.routerProvider?.go) {
35
33
  // SvelteKit path generation can be injected here.
36
34
  }
37
35
  return path;
@@ -39,6 +37,7 @@ export function useGetToPath() {
39
37
  }
40
38
 
41
39
  export function useGo() {
40
+ const adminContext = captureAdminContext();
42
41
  const getToPath = useGetToPath();
43
42
 
44
43
  return (options: { to?: string; query?: Record<string, unknown>; type?: 'push' | 'replace'; resource?: string; action?: 'list' | 'create' | 'edit' | 'show' | 'clone'; id?: string | number }) => {
@@ -55,19 +54,13 @@ export function useGo() {
55
54
  }
56
55
  }
57
56
 
58
- navigate(targetUrl, options.type === 'replace' ? { replaceState: true } : undefined);
57
+ void adminContext.navigate(targetUrl, options.type === 'replace' ? { replaceState: true } : undefined);
59
58
  };
60
59
  }
61
60
 
62
61
  export function useBack() {
63
- return () => {
64
- const routerProvider = getRouterProvider();
65
- if (routerProvider?.back) {
66
- routerProvider.back();
67
- } else if (typeof window !== 'undefined') {
68
- window.history.back();
69
- }
70
- };
62
+ const adminContext = captureAdminContext();
63
+ return () => adminContext.back();
71
64
  }
72
65
 
73
66
  /**
@@ -75,13 +68,15 @@ export function useBack() {
75
68
  * (to avoid full page reloads in SSR apps)
76
69
  */
77
70
  export function useLink() {
71
+ const adminContext = captureAdminContext();
78
72
  return () => {
79
- const routerProvider = getRouterProvider() as Record<string, unknown> | undefined;
73
+ const routerProvider = adminContext.routerProvider as unknown as Record<string, unknown> | undefined;
80
74
  return (routerProvider?.Link as string) ?? 'a';
81
75
  };
82
76
  }
83
77
 
84
78
  export function useResource(resourceName?: string) {
79
+ const adminContext = captureAdminContext();
85
80
  const parsed = useParsed();
86
81
 
87
82
  // Derived resource — re-evaluates when parsed.resource or resourceName changes
@@ -89,7 +84,7 @@ export function useResource(resourceName?: string) {
89
84
  const target = resourceName ?? parsed.resource;
90
85
  if (!target) return undefined;
91
86
  try {
92
- return getResource(target);
87
+ return adminContext.getResource(target);
93
88
  } catch {
94
89
  return undefined;
95
90
  }
@@ -97,7 +92,7 @@ export function useResource(resourceName?: string) {
97
92
 
98
93
  return {
99
94
  get resource() { return resolvedResource; },
100
- get resources() { return getResources(); },
95
+ get resources() { return adminContext.resources; },
101
96
  get identifier() { return resolvedResource?.identifier ?? resolvedResource?.name; },
102
97
  /** 当前 URL 解析出的 parent 资源信息(嵌套资源路由) */
103
98
  get parent() {
@@ -111,7 +106,7 @@ export function useResource(resourceName?: string) {
111
106
  /** Resolve a resource by name — useful for dynamic lookups inside callbacks */
112
107
  select: (name: string) => {
113
108
  try {
114
- const res = getResource(name);
109
+ const res = adminContext.getResource(name);
115
110
  return { resource: res, identifier: res.identifier ?? res.name };
116
111
  } catch {
117
112
  return { resource: undefined, identifier: undefined };
@@ -1,6 +1,7 @@
1
1
  import { useParsed } from './useParsed.svelte';
2
2
  import { useList } from './query-hooks.svelte';
3
3
  import { readURLState, writeURLState } from './url-sync';
4
+ import { captureAdminContext } from './context.svelte';
4
5
  import { getAdminOptions } from './options.svelte';
5
6
  import type { Pagination, Sort, Filter, BaseRecord, HttpError, KnownResources, GetListResult } from './types';
6
7
  import type { UseListOptions, MaybeGetter } from './query-hooks.svelte';
@@ -29,6 +30,7 @@ export function useTable<
29
30
  TError = HttpError,
30
31
  TSearchVariables = Record<string, unknown>
31
32
  >(optionsOrGetter: MaybeGetter<UseTableOptions<TQueryFnData, TError, TSearchVariables>> = {}) {
33
+ const adminContext = captureAdminContext();
32
34
  const getOptions = () => typeof optionsOrGetter === 'function' ? optionsOrGetter() : optionsOrGetter;
33
35
  const parsed = useParsed();
34
36
 
@@ -50,7 +52,7 @@ export function useTable<
50
52
  let initFilters = initialOpts.filters?.initial ?? [];
51
53
 
52
54
  if (syncWithLocation && typeof window !== 'undefined') {
53
- const urlState = readURLState();
55
+ const urlState = readURLState(adminContext);
54
56
  if (urlState.page || urlState.pageSize) {
55
57
  initPagination = {
56
58
  current: urlState.page ?? initPagination.current,
@@ -128,12 +130,12 @@ export function useTable<
128
130
  sortField: currentSorters[0]?.field,
129
131
  sortOrder: currentSorters[0]?.order,
130
132
  filters: currentFilters,
131
- });
133
+ }, adminContext);
132
134
  });
133
135
 
134
136
  $effect(() => {
135
137
  const handler = () => {
136
- const urlState = readURLState();
138
+ const urlState = readURLState(adminContext);
137
139
  const newPage = urlState.page ?? 1;
138
140
  if (newPage !== pagination.current) {
139
141
  pagination = { ...pagination, current: newPage };
@@ -1,10 +1,11 @@
1
- /* eslint-disable svelte/prefer-svelte-reactivity */
2
1
  // TableState — Svelte 5 Class State Pattern for table data management
3
2
  // Reactive class that encapsulates all table state with fine-grained $state/$derived
4
3
 
5
4
  import { useParsed } from './useParsed.svelte';
6
5
  import { useList } from './query-hooks.svelte';
7
6
  import { readURLState, writeURLState } from './url-sync';
7
+ import { captureAdminContext } from './context.svelte';
8
+ import type { AdminContextAccessor } from './context.svelte';
8
9
  import type { Pagination, Sort, Filter, BaseRecord, HttpError, KnownResources, GetListResult } from './types';
9
10
  import type { UseListOptions } from './query-hooks.svelte';
10
11
 
@@ -53,11 +54,13 @@ export class TableState<TData extends BaseRecord = BaseRecord, TError = HttpErro
53
54
  private filterDefaultBehavior: FilterSetMode;
54
55
  private syncWithLocation: boolean;
55
56
  private paginationMode: string;
57
+ private adminContext: AdminContextAccessor;
56
58
 
57
59
  // ─── Query result ─────────────────────────────────────────────
58
60
  private _queryResult;
59
61
 
60
62
  constructor(options: TableStateOptions<TData, TError> = {}) {
63
+ this.adminContext = captureAdminContext();
61
64
  const parsed = useParsed();
62
65
  const resource = options.resource ?? parsed.resource ?? '';
63
66
 
@@ -75,7 +78,7 @@ export class TableState<TData extends BaseRecord = BaseRecord, TError = HttpErro
75
78
  let initFilters = options.filters?.initial ?? [];
76
79
 
77
80
  if (this.syncWithLocation && typeof window !== 'undefined') {
78
- const urlState = readURLState();
81
+ const urlState = readURLState(this.adminContext);
79
82
  if (urlState.page || urlState.pageSize) {
80
83
  initPagination = {
81
84
  current: urlState.page ?? initPagination.current,
@@ -116,7 +119,7 @@ export class TableState<TData extends BaseRecord = BaseRecord, TError = HttpErro
116
119
  sortField: this.currentSorters[0]?.field,
117
120
  sortOrder: this.currentSorters[0]?.order,
118
121
  filters: this.currentFilters,
119
- });
122
+ }, this.adminContext);
120
123
  });
121
124
  }
122
125
  }
@@ -1,9 +1,10 @@
1
1
  import { createMutation, createQuery, useQueryClient } from '@tanstack/svelte-query';
2
2
  import { getAdminOptions } from './options.svelte';
3
- import { getTaskProvider } from './context.svelte';
3
+ import { captureAdminContext } from './context.svelte';
4
+ import type { AdminContextAccessor } from './context.svelte';
4
5
  import { checkError, createOvertimeTracker, fireErrorNotification, fireSuccessNotification } from './hook-utils.svelte';
5
6
  import type { NotificationConfig, OvertimeOptions } from './hook-utils.svelte';
6
- import { t } from './i18n.svelte';
7
+ import { useTranslation } from './i18n.svelte';
7
8
  import type {
8
9
  HttpError,
9
10
  SubmitTaskOptions,
@@ -15,8 +16,13 @@ import type {
15
16
 
16
17
  function normalizeTaskProvider<TTask extends TaskRecord = TaskRecord>(
17
18
  provider?: TaskProvider<TTask>,
19
+ adminContext: AdminContextAccessor = captureAdminContext(),
18
20
  ): TaskProvider<TTask> {
19
- return (provider ?? getTaskProvider({ optional: true }) ?? getTaskProvider()) as TaskProvider<TTask>;
21
+ const activeProvider = provider ?? adminContext.taskProvider;
22
+ if (!activeProvider) {
23
+ throw new Error('TaskProvider not found. Did you call setTaskProvider in App.svelte?');
24
+ }
25
+ return activeProvider as TaskProvider<TTask>;
20
26
  }
21
27
 
22
28
  export interface UseSubmitTaskOptions {
@@ -39,19 +45,21 @@ export function useSubmitTask<
39
45
  TTask extends TaskRecord = TaskRecord,
40
46
  TError = HttpError,
41
47
  >(options: UseSubmitTaskOptions = {}) {
48
+ const adminContext = captureAdminContext();
49
+ const i18n = useTranslation();
42
50
  const adminOptions = getAdminOptions();
43
51
  const queryClient = useQueryClient();
44
52
  const overtime = createOvertimeTracker(() => mutation.isPending, options.overtimeOptions ?? adminOptions.overtime);
45
53
 
46
54
  const mutation = createMutation<TaskHandle<TTask>, TError, UseSubmitTaskMutateParams<TTask>>(() => ({
47
55
  mutationFn: async (params) => {
48
- const provider = normalizeTaskProvider<TTask>(params.taskProvider);
56
+ const provider = normalizeTaskProvider<TTask>(params.taskProvider, adminContext);
49
57
  return provider.submit(params.taskName, params.options);
50
58
  },
51
59
  onSuccess: (data, params, context) => {
52
60
  fireSuccessNotification(
53
61
  params.successNotification,
54
- t('task.submitSuccess'),
62
+ i18n.t('task.submitSuccess'),
55
63
  data,
56
64
  params.options,
57
65
  params.taskName,
@@ -60,10 +68,10 @@ export function useSubmitTask<
60
68
  options.mutationOptions?.onSuccess?.(data, params, context);
61
69
  },
62
70
  onError: (error, params, context) => {
63
- checkError(error);
71
+ checkError(error, adminContext);
64
72
  fireErrorNotification(
65
73
  params.errorNotification,
66
- t('task.submitFailed'),
74
+ i18n.t('task.submitFailed'),
67
75
  error,
68
76
  params.taskName,
69
77
  );
@@ -93,10 +101,12 @@ export interface UseTaskOptions<TTask extends TaskRecord = TaskRecord, _TError =
93
101
  export function useTask<TTask extends TaskRecord = TaskRecord, TError = HttpError>(
94
102
  options: UseTaskOptions<TTask, TError> = {},
95
103
  ) {
104
+ const adminContext = captureAdminContext();
105
+ const i18n = useTranslation();
96
106
  const adminOptions = getAdminOptions();
97
107
 
98
108
  const query = createQuery<TTask, TError>(() => {
99
- const provider = normalizeTaskProvider<TTask>(options.taskProvider);
109
+ const provider = normalizeTaskProvider<TTask>(options.taskProvider, adminContext);
100
110
  const queryOptions = options.queryOptions;
101
111
  return {
102
112
  queryKey: ['task', options.taskId],
@@ -125,8 +135,8 @@ export function useTask<TTask extends TaskRecord = TaskRecord, TError = HttpErro
125
135
  }
126
136
  } else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
127
137
  lastErrorAt = query.errorUpdatedAt;
128
- checkError(query.error);
129
- fireErrorNotification(options.errorNotification, t('task.fetchFailed'), query.error, options.taskId);
138
+ checkError(query.error, adminContext);
139
+ fireErrorNotification(options.errorNotification, i18n.t('task.fetchFailed'), query.error, options.taskId);
130
140
  }
131
141
  });
132
142
 
@@ -158,10 +168,12 @@ export interface UseTaskListOptions<TTask extends TaskRecord = TaskRecord, _TErr
158
168
  export function useTaskList<TTask extends TaskRecord = TaskRecord, TError = HttpError>(
159
169
  options: UseTaskListOptions<TTask, TError> = {},
160
170
  ) {
171
+ const adminContext = captureAdminContext();
172
+ const i18n = useTranslation();
161
173
  const adminOptions = getAdminOptions();
162
174
 
163
175
  const query = createQuery<TaskListResult<TTask>, TError>(() => {
164
- const provider = normalizeTaskProvider<TTask>(options.taskProvider);
176
+ const provider = normalizeTaskProvider<TTask>(options.taskProvider, adminContext);
165
177
  const queryOptions = options.queryOptions;
166
178
  return {
167
179
  queryKey: ['taskList', options.dlq ? 'dlq' : 'default', options.params],
@@ -194,8 +206,8 @@ export function useTaskList<TTask extends TaskRecord = TaskRecord, TError = Http
194
206
  }
195
207
  } else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
196
208
  lastErrorAt = query.errorUpdatedAt;
197
- checkError(query.error);
198
- fireErrorNotification(options.errorNotification, t('task.fetchListFailed'), query.error, options.dlq ? 'taskDlq' : 'tasks');
209
+ checkError(query.error, adminContext);
210
+ fireErrorNotification(options.errorNotification, i18n.t('task.fetchListFailed'), query.error, options.dlq ? 'taskDlq' : 'tasks');
199
211
  }
200
212
  });
201
213
 
@@ -217,11 +229,12 @@ export interface UseTaskSubscriptionOptions<TTask extends TaskRecord = TaskRecor
217
229
  export function useTaskSubscription<TTask extends TaskRecord = TaskRecord>(
218
230
  options: UseTaskSubscriptionOptions<TTask>,
219
231
  ): void {
232
+ const adminContext = captureAdminContext();
220
233
  $effect(() => {
221
234
  const enabled = options.enabled ?? true;
222
235
  if (!enabled) return;
223
236
 
224
- const provider = normalizeTaskProvider<TTask>(options.taskProvider);
237
+ const provider = normalizeTaskProvider<TTask>(options.taskProvider, adminContext);
225
238
  if (!provider.subscribe) {
226
239
  throw new Error('TaskProvider does not implement subscribe');
227
240
  }