@svadmin/core 0.30.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.
- package/package.json +5 -5
- package/src/auth-hooks.svelte.ts +58 -24
- package/src/context.svelte.ts +185 -46
- package/src/data-transfer.svelte.ts +51 -41
- package/src/export-format.ts +115 -0
- package/src/form-hooks.svelte.ts +322 -58
- package/src/helpers.ts +11 -7
- package/src/hook-utils.svelte.ts +13 -12
- package/src/hooks.svelte.ts +29 -23
- package/src/http.ts +114 -33
- package/src/i18n.svelte.ts +423 -21
- package/src/index.ts +11 -8
- package/src/mutation-hooks.svelte.ts +27 -16
- package/src/permissions.svelte.ts +4 -3
- package/src/query-hooks.svelte.ts +35 -15
- package/src/router.ts +28 -8
- package/src/routing-hooks.svelte.ts +22 -18
- package/src/table-hooks.svelte.ts +5 -3
- package/src/table-state.svelte.ts +6 -3
- package/src/task-hooks.svelte.ts +27 -14
- package/src/theme.svelte.ts +248 -154
- package/src/toast.svelte.ts +1 -2
- package/src/types.ts +29 -4
- package/src/unsaved-changes.svelte.ts +4 -38
- package/src/url-sync.ts +47 -8
- package/src/useCan.ts +3 -2
- package/src/useParsed.svelte.ts +39 -15
- package/src/utility-hooks.svelte.ts +119 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"author": "zuohuadong",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"main": "src/index.ts",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@happy-dom/global-registrator": "^20.10.0",
|
|
13
|
-
"@sveltejs/vite-plugin-svelte": "^7.
|
|
13
|
+
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
|
14
14
|
"@testing-library/dom": "^10.4.1",
|
|
15
|
-
"@testing-library/svelte": "^5.
|
|
15
|
+
"@testing-library/svelte": "^5.4.2",
|
|
16
16
|
"@testing-library/user-event": "^14.6.1",
|
|
17
17
|
"@types/bun": "^1.3.14",
|
|
18
18
|
"@types/jsdom": "^28.0.3",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"default": "./src/i18n.svelte.ts"
|
|
39
39
|
},
|
|
40
40
|
"./permissions": {
|
|
41
|
-
"types": "./src/permissions.ts",
|
|
42
|
-
"default": "./src/permissions.ts"
|
|
41
|
+
"types": "./src/permissions.svelte.ts",
|
|
42
|
+
"default": "./src/permissions.svelte.ts"
|
|
43
43
|
},
|
|
44
44
|
"./toast": {
|
|
45
45
|
"types": "./src/toast.svelte.ts",
|
package/src/auth-hooks.svelte.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Each hook encapsulates the auth call + loading state + error handling + redirect
|
|
3
3
|
// Uses module-level $state — no component init-time constraints.
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { captureAdminContext } from './context.svelte';
|
|
6
|
+
import type { AdminContextAccessor } from './context.svelte';
|
|
7
7
|
import { notify } from './notification.svelte';
|
|
8
|
-
import { t } from './i18n.svelte';
|
|
8
|
+
import { t, useTranslation } from './i18n.svelte';
|
|
9
9
|
import type { AuthActionResult, CheckResult, Identity, AuthProvider } from './types';
|
|
10
10
|
import { useQueryClient } from '@tanstack/svelte-query';
|
|
11
11
|
|
|
@@ -19,14 +19,16 @@ interface CreateAuthMutationOptions {
|
|
|
19
19
|
method: keyof AuthProvider;
|
|
20
20
|
successMessage?: string | null;
|
|
21
21
|
errorMessage?: string | false;
|
|
22
|
-
onSuccess?: (result: AuthActionResult) => void
|
|
22
|
+
onSuccess?: (result: AuthActionResult, adminContext: AdminContextAccessor) => void | Promise<void>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function createAuthMutation(options: CreateAuthMutationOptions) {
|
|
26
|
-
const
|
|
26
|
+
const adminContext = captureAdminContext();
|
|
27
|
+
const i18n = useTranslation();
|
|
27
28
|
let isLoading = $state(false);
|
|
28
29
|
|
|
29
30
|
async function mutate(params?: Record<string, unknown>): Promise<AuthActionResult> {
|
|
31
|
+
const provider = adminContext.authProvider;
|
|
30
32
|
if (!provider) throw new Error('AuthProvider not configured');
|
|
31
33
|
const fn = provider[options.method] as ((params?: Record<string, unknown>) => Promise<AuthActionResult>) | undefined;
|
|
32
34
|
if (!fn) throw new Error(`AuthProvider.${options.method} not implemented`);
|
|
@@ -36,14 +38,14 @@ function createAuthMutation(options: CreateAuthMutationOptions) {
|
|
|
36
38
|
const result = await fn.call(provider, params);
|
|
37
39
|
if (result.success) {
|
|
38
40
|
if (options.successMessage) notify({ type: 'success', message: options.successMessage });
|
|
39
|
-
if (options.onSuccess) options.onSuccess(result);
|
|
41
|
+
if (options.onSuccess) await options.onSuccess(result, adminContext);
|
|
40
42
|
} else {
|
|
41
|
-
const msg = result.error?.message ?? (typeof options.errorMessage === 'string' ? options.errorMessage : t('common.operationFailed'));
|
|
43
|
+
const msg = result.error?.message ?? (typeof options.errorMessage === 'string' ? options.errorMessage : i18n.t('common.operationFailed'));
|
|
42
44
|
if (options.errorMessage !== false) notify({ type: 'error', message: msg });
|
|
43
45
|
}
|
|
44
46
|
return result;
|
|
45
47
|
} catch (err) {
|
|
46
|
-
const msg = err instanceof Error ? err.message : (typeof options.errorMessage === 'string' ? options.errorMessage : t('common.operationFailed'));
|
|
48
|
+
const msg = err instanceof Error ? err.message : (typeof options.errorMessage === 'string' ? options.errorMessage : i18n.t('common.operationFailed'));
|
|
47
49
|
if (options.errorMessage !== false) notify({ type: 'error', message: msg });
|
|
48
50
|
return { success: false, error: { message: msg } };
|
|
49
51
|
} finally {
|
|
@@ -64,23 +66,26 @@ export function useLogin(opts?: { errorMessage?: string | false }) {
|
|
|
64
66
|
method: 'login',
|
|
65
67
|
successMessage: t('common.operationSuccess'),
|
|
66
68
|
errorMessage: opts?.errorMessage ?? t('common.loginFailed'),
|
|
67
|
-
onSuccess: (result) => { navigate(result.redirectTo ?? '/'); }
|
|
69
|
+
onSuccess: async (result, adminContext) => { await adminContext.navigate(result.redirectTo ?? '/'); }
|
|
68
70
|
});
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
// ─── useLogout ────────────────────────────────────────────────
|
|
72
74
|
|
|
73
75
|
export function useLogout() {
|
|
76
|
+
let queryClient: ReturnType<typeof useQueryClient> | undefined;
|
|
77
|
+
try {
|
|
78
|
+
queryClient = useQueryClient();
|
|
79
|
+
} catch {
|
|
80
|
+
// Auth hooks remain usable without TanStack Query; cache clearing is best-effort.
|
|
81
|
+
}
|
|
74
82
|
return createAuthMutation({
|
|
75
83
|
method: 'logout',
|
|
76
84
|
successMessage: null,
|
|
77
|
-
onSuccess: (result) => {
|
|
85
|
+
onSuccess: async (result, adminContext) => {
|
|
78
86
|
_logoutVersion++;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
queryClient.clear();
|
|
82
|
-
} catch { /* intentional */}
|
|
83
|
-
navigate(result.redirectTo ?? '/login');
|
|
87
|
+
queryClient?.clear();
|
|
88
|
+
await adminContext.navigate(result.redirectTo ?? '/login');
|
|
84
89
|
}
|
|
85
90
|
});
|
|
86
91
|
}
|
|
@@ -91,7 +96,9 @@ export function useRegister() {
|
|
|
91
96
|
return createAuthMutation({
|
|
92
97
|
method: 'register',
|
|
93
98
|
successMessage: t('auth.registerSuccess'),
|
|
94
|
-
onSuccess: (result) => {
|
|
99
|
+
onSuccess: async (result, adminContext) => {
|
|
100
|
+
if (result.redirectTo) await adminContext.navigate(result.redirectTo);
|
|
101
|
+
}
|
|
95
102
|
});
|
|
96
103
|
}
|
|
97
104
|
|
|
@@ -110,19 +117,44 @@ export function useUpdatePassword() {
|
|
|
110
117
|
return createAuthMutation({
|
|
111
118
|
method: 'updatePassword',
|
|
112
119
|
successMessage: t('common.operationSuccess'),
|
|
113
|
-
onSuccess: (result) => {
|
|
120
|
+
onSuccess: async (result, adminContext) => {
|
|
121
|
+
if (result.redirectTo) await adminContext.navigate(result.redirectTo);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── useUpdateIdentity / useUpdateProfile ────────────────────
|
|
127
|
+
|
|
128
|
+
export function useUpdateIdentity() {
|
|
129
|
+
return createAuthMutation({
|
|
130
|
+
method: 'updateIdentity',
|
|
131
|
+
successMessage: t('common.operationSuccess'),
|
|
132
|
+
onSuccess: async (result, adminContext) => {
|
|
133
|
+
if (result.redirectTo) await adminContext.navigate(result.redirectTo);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function useUpdateProfile() {
|
|
139
|
+
return createAuthMutation({
|
|
140
|
+
method: 'updateProfile',
|
|
141
|
+
successMessage: t('common.operationSuccess'),
|
|
142
|
+
onSuccess: async (result, adminContext) => {
|
|
143
|
+
if (result.redirectTo) await adminContext.navigate(result.redirectTo);
|
|
144
|
+
}
|
|
114
145
|
});
|
|
115
146
|
}
|
|
116
147
|
|
|
117
148
|
// ─── useGetIdentity ──────────────────────────────────────────
|
|
118
149
|
|
|
119
150
|
export function useGetIdentity() {
|
|
120
|
-
const
|
|
151
|
+
const adminContext = captureAdminContext();
|
|
121
152
|
let data = $state<Identity | null>(null);
|
|
122
153
|
let isLoading = $state(true);
|
|
123
154
|
let error = $state<Error | null>(null);
|
|
124
155
|
|
|
125
156
|
function fetch() {
|
|
157
|
+
const provider = adminContext.authProvider;
|
|
126
158
|
if (!provider) { isLoading = false; return; }
|
|
127
159
|
isLoading = true;
|
|
128
160
|
provider.getIdentity().then(identity => {
|
|
@@ -154,12 +186,13 @@ export function useGetIdentity() {
|
|
|
154
186
|
// ─── useIsAuthenticated ──────────────────────────────────────
|
|
155
187
|
|
|
156
188
|
export function useIsAuthenticated() {
|
|
157
|
-
const
|
|
189
|
+
const adminContext = captureAdminContext();
|
|
158
190
|
let isAuthenticated = $state(false);
|
|
159
191
|
let isLoading = $state(true);
|
|
160
192
|
let checkResult = $state<CheckResult | null>(null);
|
|
161
193
|
|
|
162
194
|
function check() {
|
|
195
|
+
const provider = adminContext.authProvider;
|
|
163
196
|
if (!provider) {
|
|
164
197
|
isAuthenticated = true;
|
|
165
198
|
isLoading = false;
|
|
@@ -209,9 +242,10 @@ export function useIsAuthenticated() {
|
|
|
209
242
|
* If it returns { redirectTo }, navigates there.
|
|
210
243
|
*/
|
|
211
244
|
export function useOnError() {
|
|
212
|
-
const
|
|
245
|
+
const adminContext = captureAdminContext();
|
|
213
246
|
|
|
214
247
|
async function mutate(error: unknown) {
|
|
248
|
+
const provider = adminContext.authProvider;
|
|
215
249
|
if (!provider?.onError) {
|
|
216
250
|
console.warn('[svadmin] useOnError: authProvider.onError not implemented');
|
|
217
251
|
return;
|
|
@@ -220,9 +254,9 @@ export function useOnError() {
|
|
|
220
254
|
const result = await provider.onError(error);
|
|
221
255
|
if (result.logout) {
|
|
222
256
|
await provider.logout?.();
|
|
223
|
-
navigate(result.redirectTo ?? '/login');
|
|
257
|
+
await adminContext.navigate(result.redirectTo ?? '/login');
|
|
224
258
|
} else if (result.redirectTo) {
|
|
225
|
-
navigate(result.redirectTo);
|
|
259
|
+
await adminContext.navigate(result.redirectTo);
|
|
226
260
|
}
|
|
227
261
|
} catch (err) {
|
|
228
262
|
console.warn('[svadmin] useOnError failed:', err);
|
|
@@ -250,13 +284,14 @@ export function useOnError() {
|
|
|
250
284
|
* ```
|
|
251
285
|
*/
|
|
252
286
|
export function usePermissions<T = unknown>() {
|
|
253
|
-
const
|
|
287
|
+
const adminContext = captureAdminContext();
|
|
254
288
|
let permissions = $state<T | null>(null);
|
|
255
289
|
let isLoading = $state(true);
|
|
256
290
|
let error = $state<Error | null>(null);
|
|
257
291
|
let version = $state(0);
|
|
258
292
|
|
|
259
293
|
function fetch() {
|
|
294
|
+
const provider = adminContext.authProvider;
|
|
260
295
|
if (!provider?.getPermissions) { isLoading = false; return; }
|
|
261
296
|
isLoading = true;
|
|
262
297
|
error = null;
|
|
@@ -297,4 +332,3 @@ export function usePermissions<T = unknown>() {
|
|
|
297
332
|
can: (resource: string, action: string): boolean => hasFn(`${resource}:${action}`),
|
|
298
333
|
};
|
|
299
334
|
}
|
|
300
|
-
|
package/src/context.svelte.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
// Svelte 5 Context —
|
|
2
|
-
//
|
|
1
|
+
// Svelte 5 Context — application trees use scoped context while the module-level
|
|
2
|
+
// setters remain as a compatibility facade for non-component consumers.
|
|
3
3
|
|
|
4
|
+
import { createContext } from 'svelte';
|
|
4
5
|
import type { DataProvider, AuthProvider, ResourceDefinition, TaskProvider } from './types';
|
|
5
6
|
import type { RouterProvider } from './router-provider';
|
|
7
|
+
import type { LiveProvider } from './live.svelte';
|
|
6
8
|
import { resetNotificationProvider } from './notification.svelte';
|
|
7
9
|
import { resetAuditLogProvider } from './audit';
|
|
8
10
|
import { resetAccessControlProvider } from './permissions.svelte';
|
|
@@ -11,7 +13,15 @@ import { resetI18n } from './i18n.svelte';
|
|
|
11
13
|
import { resetChatProvider } from './chatProvider.svelte';
|
|
12
14
|
import { resetToast } from './toast.svelte';
|
|
13
15
|
import { resetTheme } from './theme.svelte';
|
|
14
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
currentPath as currentLegacyPath,
|
|
18
|
+
currentPathWithProvider,
|
|
19
|
+
formatLink as formatLegacyLink,
|
|
20
|
+
formatLinkWithProvider,
|
|
21
|
+
navigate as navigateLegacy,
|
|
22
|
+
navigateWithProvider,
|
|
23
|
+
resetRouter,
|
|
24
|
+
} from './router';
|
|
15
25
|
import { resetUnsavedChanges } from './unsaved-changes.svelte';
|
|
16
26
|
import { resetGlobalPath } from './useParsed.svelte';
|
|
17
27
|
import { resetLogoutVersion } from './auth-hooks.svelte';
|
|
@@ -26,46 +36,176 @@ import { resetSidebarCollapsed } from './hooks.svelte';
|
|
|
26
36
|
*/
|
|
27
37
|
export type DataProviderInput = DataProvider | Record<string, DataProvider>;
|
|
28
38
|
|
|
39
|
+
export interface AdminContextSource {
|
|
40
|
+
readonly dataProvider: DataProviderInput;
|
|
41
|
+
readonly authProvider?: AuthProvider | null;
|
|
42
|
+
readonly resources: ResourceDefinition[];
|
|
43
|
+
readonly routerProvider?: RouterProvider;
|
|
44
|
+
readonly liveProvider?: LiveProvider;
|
|
45
|
+
readonly taskProvider?: TaskProvider;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AdminContextValue {
|
|
49
|
+
readonly providers: Record<string, DataProvider>;
|
|
50
|
+
readonly authProvider: AuthProvider | null;
|
|
51
|
+
readonly resources: ResourceDefinition[];
|
|
52
|
+
readonly routerProvider: RouterProvider | undefined;
|
|
53
|
+
readonly liveProvider: LiveProvider | undefined;
|
|
54
|
+
readonly taskProvider: TaskProvider | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialization-time snapshot of the owning AdminContext tree.
|
|
59
|
+
*
|
|
60
|
+
* The accessor keeps the scoped context object (whose fields are reactive
|
|
61
|
+
* getters) instead of calling Svelte's getContext from later DOM/query
|
|
62
|
+
* callbacks. When no scoped context exists, getters intentionally continue to
|
|
63
|
+
* read the module-level compatibility setters dynamically.
|
|
64
|
+
*/
|
|
65
|
+
export interface AdminContextAccessor {
|
|
66
|
+
readonly providers: Record<string, DataProvider> | null;
|
|
67
|
+
readonly authProvider: AuthProvider | null;
|
|
68
|
+
readonly resources: ResourceDefinition[];
|
|
69
|
+
readonly routerProvider: RouterProvider | undefined;
|
|
70
|
+
readonly liveProvider: LiveProvider | undefined;
|
|
71
|
+
readonly taskProvider: TaskProvider | undefined;
|
|
72
|
+
getDataProvider(name?: string): DataProvider;
|
|
73
|
+
getDataProviderNames(): string[];
|
|
74
|
+
getDataProviderForResource(resourceName: string, overrideName?: string): DataProvider;
|
|
75
|
+
getResource(nameOrIdentifier: string): ResourceDefinition;
|
|
76
|
+
currentPath(): string;
|
|
77
|
+
formatLink(path: string): string;
|
|
78
|
+
navigate(path: string, options?: { replaceState?: boolean }): Promise<void>;
|
|
79
|
+
back(): void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const [getRequiredAdminContext, setAdminContext] = createContext<AdminContextValue>();
|
|
83
|
+
|
|
84
|
+
function normalizeDataProviders(provider: DataProviderInput): Record<string, DataProvider> {
|
|
85
|
+
return isDataProvider(provider)
|
|
86
|
+
? { default: provider }
|
|
87
|
+
: provider;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createAdminContext(source: AdminContextSource): AdminContextValue {
|
|
91
|
+
return {
|
|
92
|
+
get providers() { return normalizeDataProviders(source.dataProvider); },
|
|
93
|
+
get authProvider() { return source.authProvider ?? null; },
|
|
94
|
+
get resources() { return source.resources; },
|
|
95
|
+
get routerProvider() { return source.routerProvider; },
|
|
96
|
+
get liveProvider() { return source.liveProvider; },
|
|
97
|
+
get taskProvider() { return source.taskProvider; },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Provide request/tree-scoped admin state to all descendants. */
|
|
102
|
+
export function provideAdminContext(source: AdminContextSource): AdminContextValue {
|
|
103
|
+
const context = createAdminContext(source);
|
|
104
|
+
setAdminContext(context);
|
|
105
|
+
return context;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Return the current tree context when called during component initialization/render. */
|
|
109
|
+
export function getAdminContext(): AdminContextValue | undefined {
|
|
110
|
+
try {
|
|
111
|
+
return getRequiredAdminContext();
|
|
112
|
+
} catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
29
117
|
let providers = $state<Record<string, DataProvider> | null>(null);
|
|
30
118
|
|
|
119
|
+
/** Capture the current tree once so delayed callbacks never re-enter getContext. */
|
|
120
|
+
export function captureAdminContext(): AdminContextAccessor {
|
|
121
|
+
const scopedContext = getAdminContext();
|
|
122
|
+
|
|
123
|
+
const accessor: AdminContextAccessor = {
|
|
124
|
+
get providers() { return scopedContext ? scopedContext.providers : providers; },
|
|
125
|
+
get authProvider() { return scopedContext ? scopedContext.authProvider : authProvider; },
|
|
126
|
+
get resources() { return scopedContext ? scopedContext.resources : resources ?? []; },
|
|
127
|
+
get routerProvider() { return scopedContext ? scopedContext.routerProvider : routerProvider; },
|
|
128
|
+
get liveProvider() { return scopedContext ? scopedContext.liveProvider : liveProviderState; },
|
|
129
|
+
get taskProvider() { return scopedContext ? scopedContext.taskProvider : taskProviderState; },
|
|
130
|
+
getDataProvider(name) {
|
|
131
|
+
const activeProviders = accessor.providers;
|
|
132
|
+
if (!activeProviders) throw new Error('DataProvider not found. Did you call setDataProvider in App.svelte?');
|
|
133
|
+
const key = name ?? 'default';
|
|
134
|
+
const provider = activeProviders[key];
|
|
135
|
+
if (!provider) {
|
|
136
|
+
throw new Error(`DataProvider "${key}" not found. Available: ${Object.keys(activeProviders).join(', ')}`);
|
|
137
|
+
}
|
|
138
|
+
return provider;
|
|
139
|
+
},
|
|
140
|
+
getDataProviderNames() {
|
|
141
|
+
return accessor.providers ? Object.keys(accessor.providers) : [];
|
|
142
|
+
},
|
|
143
|
+
getDataProviderForResource(resourceName, overrideName) {
|
|
144
|
+
if (overrideName) {
|
|
145
|
+
try {
|
|
146
|
+
return accessor.getDataProvider(overrideName);
|
|
147
|
+
} catch {
|
|
148
|
+
// Preserve the historical fallback to the resource/default provider.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const resource = accessor.getResource(resourceName);
|
|
153
|
+
const dataProviderName = resource.meta?.dataProviderName as string | undefined;
|
|
154
|
+
if (dataProviderName) return accessor.getDataProvider(dataProviderName);
|
|
155
|
+
} catch {
|
|
156
|
+
// Preserve the historical fallback when a resource is not registered.
|
|
157
|
+
}
|
|
158
|
+
return accessor.getDataProvider();
|
|
159
|
+
},
|
|
160
|
+
getResource(nameOrIdentifier) {
|
|
161
|
+
const resource = accessor.resources.find(
|
|
162
|
+
(candidate) => candidate.identifier === nameOrIdentifier || candidate.name === nameOrIdentifier,
|
|
163
|
+
);
|
|
164
|
+
if (!resource) throw new Error(`Resource "${nameOrIdentifier}" not found in resource definitions.`);
|
|
165
|
+
return resource;
|
|
166
|
+
},
|
|
167
|
+
currentPath() {
|
|
168
|
+
if (scopedContext) return currentPathWithProvider(scopedContext.routerProvider);
|
|
169
|
+
return currentLegacyPath();
|
|
170
|
+
},
|
|
171
|
+
formatLink(path) {
|
|
172
|
+
if (scopedContext) return formatLinkWithProvider(scopedContext.routerProvider, path);
|
|
173
|
+
return formatLegacyLink(path);
|
|
174
|
+
},
|
|
175
|
+
async navigate(path, options) {
|
|
176
|
+
if (scopedContext) {
|
|
177
|
+
await navigateWithProvider(scopedContext.routerProvider, path, options);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
await navigateLegacy(path, options);
|
|
181
|
+
},
|
|
182
|
+
back() {
|
|
183
|
+
const activeRouter = accessor.routerProvider;
|
|
184
|
+
if (activeRouter?.back) {
|
|
185
|
+
activeRouter.back();
|
|
186
|
+
} else if (typeof window !== 'undefined') {
|
|
187
|
+
window.history.back();
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return accessor;
|
|
193
|
+
}
|
|
194
|
+
|
|
31
195
|
export function setDataProvider(provider: DataProviderInput): void {
|
|
32
|
-
|
|
33
|
-
providers = { default: provider } as Record<string, DataProvider>;
|
|
34
|
-
} else {
|
|
35
|
-
providers = provider as Record<string, DataProvider>;
|
|
36
|
-
}
|
|
196
|
+
providers = normalizeDataProviders(provider);
|
|
37
197
|
}
|
|
38
198
|
|
|
39
199
|
export function getDataProvider(name?: string): DataProvider {
|
|
40
|
-
|
|
41
|
-
const key = name ?? 'default';
|
|
42
|
-
const provider = providers[key];
|
|
43
|
-
if (!provider) {
|
|
44
|
-
throw new Error(`DataProvider "${key}" not found. Available: ${Object.keys(providers).join(', ')}`);
|
|
45
|
-
}
|
|
46
|
-
return provider;
|
|
200
|
+
return captureAdminContext().getDataProvider(name);
|
|
47
201
|
}
|
|
48
202
|
|
|
49
203
|
export function getDataProviderNames(): string[] {
|
|
50
|
-
return
|
|
204
|
+
return captureAdminContext().getDataProviderNames();
|
|
51
205
|
}
|
|
52
206
|
|
|
53
207
|
export function getDataProviderForResource(resourceName: string, overrideName?: string): DataProvider {
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
return getDataProvider(overrideName);
|
|
57
|
-
} catch {
|
|
58
|
-
// ignore
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
const resource = getResource(resourceName);
|
|
63
|
-
const dpName = resource.meta?.dataProviderName as string | undefined;
|
|
64
|
-
if (dpName) return getDataProvider(dpName);
|
|
65
|
-
} catch {
|
|
66
|
-
// ignore
|
|
67
|
-
}
|
|
68
|
-
return getDataProvider();
|
|
208
|
+
return captureAdminContext().getDataProviderForResource(resourceName, overrideName);
|
|
69
209
|
}
|
|
70
210
|
|
|
71
211
|
function isDataProvider(value: unknown): value is DataProvider {
|
|
@@ -88,8 +228,9 @@ export function setAuthProvider(provider: AuthProvider | null | undefined): void
|
|
|
88
228
|
export function getAuthProvider(): AuthProvider;
|
|
89
229
|
export function getAuthProvider(options: { optional: true }): AuthProvider | null;
|
|
90
230
|
export function getAuthProvider(options?: { optional?: boolean }): AuthProvider | null {
|
|
91
|
-
|
|
92
|
-
|
|
231
|
+
const activeAuthProvider = captureAdminContext().authProvider;
|
|
232
|
+
if (!activeAuthProvider && !options?.optional) throw new Error('AuthProvider not found. Did you call setAuthProvider in App.svelte?');
|
|
233
|
+
return activeAuthProvider;
|
|
93
234
|
}
|
|
94
235
|
|
|
95
236
|
// ─── Resources ──────────────────────────────────────────────────
|
|
@@ -101,39 +242,36 @@ export function setResources(newResources: ResourceDefinition[]): void {
|
|
|
101
242
|
}
|
|
102
243
|
|
|
103
244
|
export function getResources(): ResourceDefinition[] {
|
|
104
|
-
return resources
|
|
245
|
+
return captureAdminContext().resources;
|
|
105
246
|
}
|
|
106
247
|
|
|
107
248
|
export function getResource(nameOrIdentifier: string): ResourceDefinition {
|
|
108
|
-
|
|
109
|
-
const resource = rList.find(r => r.identifier === nameOrIdentifier || r.name === nameOrIdentifier);
|
|
110
|
-
if (!resource) throw new Error(`Resource "${nameOrIdentifier}" not found in resource definitions.`);
|
|
111
|
-
return resource;
|
|
249
|
+
return captureAdminContext().getResource(nameOrIdentifier);
|
|
112
250
|
}
|
|
113
251
|
|
|
114
252
|
// ─── Router Provider ────────────────────────────────────────────
|
|
115
253
|
|
|
116
254
|
let routerProvider = $state<RouterProvider | undefined>(undefined);
|
|
117
255
|
|
|
118
|
-
export function setRouterProvider(provider: RouterProvider): void {
|
|
256
|
+
export function setRouterProvider(provider: RouterProvider | undefined): void {
|
|
119
257
|
routerProvider = provider;
|
|
120
258
|
}
|
|
121
259
|
|
|
122
260
|
export function getRouterProvider(): RouterProvider | undefined {
|
|
123
|
-
return routerProvider;
|
|
261
|
+
return captureAdminContext().routerProvider;
|
|
124
262
|
}
|
|
125
263
|
|
|
126
264
|
|
|
127
265
|
// ─── Live Provider ──────────────────────────────────────────────
|
|
128
266
|
|
|
129
|
-
let liveProviderState = $state<
|
|
267
|
+
let liveProviderState = $state<LiveProvider | undefined>(undefined);
|
|
130
268
|
|
|
131
|
-
export function setLiveProvider(provider:
|
|
269
|
+
export function setLiveProvider(provider: LiveProvider): void {
|
|
132
270
|
liveProviderState = provider;
|
|
133
271
|
}
|
|
134
272
|
|
|
135
|
-
export function getLiveProvider():
|
|
136
|
-
return
|
|
273
|
+
export function getLiveProvider(): LiveProvider | undefined {
|
|
274
|
+
return captureAdminContext().liveProvider;
|
|
137
275
|
}
|
|
138
276
|
|
|
139
277
|
// ─── Task Provider ──────────────────────────────────────────────
|
|
@@ -147,10 +285,11 @@ export function setTaskProvider(provider: TaskProvider | undefined): void {
|
|
|
147
285
|
export function getTaskProvider(): TaskProvider;
|
|
148
286
|
export function getTaskProvider(options: { optional: true }): TaskProvider | undefined;
|
|
149
287
|
export function getTaskProvider(options?: { optional?: boolean }): TaskProvider | undefined {
|
|
150
|
-
|
|
288
|
+
const activeTaskProvider = captureAdminContext().taskProvider;
|
|
289
|
+
if (!activeTaskProvider && !options?.optional) {
|
|
151
290
|
throw new Error('TaskProvider not found. Did you call setTaskProvider in App.svelte?');
|
|
152
291
|
}
|
|
153
|
-
return
|
|
292
|
+
return activeTaskProvider;
|
|
154
293
|
}
|
|
155
294
|
|
|
156
295
|
// ─── Reset — for testing / HMR ─────────────────────────────────
|