@svadmin/core 0.26.0 → 0.27.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/core",
3
- "version": "0.26.0",
3
+ "version": "0.27.1",
4
4
  "author": "zuohuadong",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,16 +9,16 @@
9
9
  },
10
10
  "main": "src/index.ts",
11
11
  "devDependencies": {
12
- "@happy-dom/global-registrator": "^20.8.9",
13
- "@sveltejs/vite-plugin-svelte": "^7.0.0",
12
+ "@happy-dom/global-registrator": "^20.10.0",
13
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
14
14
  "@testing-library/dom": "^10.4.1",
15
15
  "@testing-library/svelte": "^5.3.1",
16
16
  "@testing-library/user-event": "^14.6.1",
17
- "@types/bun": "^1.3.11",
18
- "@types/jsdom": "^28.0.1",
19
- "happy-dom": "^20.8.9",
20
- "jsdom": "^29.0.2",
21
- "vitest": "^4.1.4"
17
+ "@types/bun": "^1.3.14",
18
+ "@types/jsdom": "^28.0.3",
19
+ "happy-dom": "^20.10.0",
20
+ "jsdom": "^29.1.1",
21
+ "vitest": "^4.1.8"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "svelte": "^5.0.0",
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  /**
2
3
  * CASL adapter for svadmin AccessControlProvider.
3
4
  *
@@ -79,7 +79,7 @@ export function useLogout() {
79
79
  try {
80
80
  const queryClient = useQueryClient();
81
81
  queryClient.clear();
82
- } catch {}
82
+ } catch { /* intentional */}
83
83
  navigate(result.redirectTo ?? '/login');
84
84
  }
85
85
  });
@@ -1,6 +1,6 @@
1
1
  import { getDataProviderForResource } from './context.svelte';
2
2
  import { useParsed } from './useParsed.svelte';
3
- import type { GetListResult, Sort, Filter, BaseRecord } from './types';
3
+ import type { Sort, Filter, BaseRecord } from './types';
4
4
 
5
5
  export interface UseExportOptions<TData extends BaseRecord = BaseRecord> {
6
6
  resource?: string;
@@ -1,3 +1,4 @@
1
+ /* eslint-disable svelte/prefer-svelte-reactivity */
1
2
  import { useQueryClient } from '@tanstack/svelte-query';
2
3
  import { useParsed } from './useParsed.svelte';
3
4
  import { getAdminOptions } from './options.svelte';
@@ -17,8 +18,8 @@ import { deepMerge, invalidateByScopes } from './mutation-hooks.svelte';
17
18
 
18
19
  export interface UseFormOptions<
19
20
  TVariables extends Record<string, unknown> = Record<string, unknown>,
20
- TData extends BaseRecord = BaseRecord,
21
- TError = HttpError,
21
+ _TData extends BaseRecord = BaseRecord,
22
+ _TError = HttpError,
22
23
  > {
23
24
  resource?: KnownResources;
24
25
  action?: 'create' | 'edit' | 'clone' | 'show';
@@ -44,7 +45,7 @@ export interface UseFormOptions<
44
45
  /** Called every time a field value changes. */
45
46
  onChange?: (event: { field: string; value: unknown; values: TVariables }) => void;
46
47
  /** Called before mutation. Return false or call cancel() to prevent submission. */
47
- onSubmit?: (ctx: { values: TVariables; action: 'create' | 'edit' | 'clone' | 'show'; cancel: () => void }) => void | boolean;
48
+ onSubmit?: (ctx: { values: TVariables; action: 'create' | 'edit' | 'clone' | 'show'; cancel: () => void }) => boolean | undefined;
48
49
 
49
50
  // ─── Meta ─────────────────────────────────────────────────────
50
51
  meta?: Record<string, unknown>;
@@ -85,7 +86,7 @@ export interface UseFormOptions<
85
86
 
86
87
  export interface UseFormReturn<
87
88
  TVariables extends Record<string, unknown> = Record<string, unknown>,
88
- TData extends BaseRecord = BaseRecord,
89
+ _TData extends BaseRecord = BaseRecord,
89
90
  > {
90
91
  // ─── Form values (single source of truth) ─────────────────────
91
92
  /** Reactive form values. Read/write directly. */
@@ -213,8 +214,7 @@ export function useForm<
213
214
  function setFieldError(field: string, message: string) { errors = { ...errors, [field]: message }; }
214
215
  function clearErrors() { errors = {}; }
215
216
  function clearFieldError(field: string) {
216
- const next = { ...errors };
217
- delete next[field];
217
+ const { [field]: _, ...next } = errors;
218
218
  errors = next;
219
219
  }
220
220
 
@@ -270,7 +270,7 @@ export function useForm<
270
270
  const query = createQuery(() => ({
271
271
  queryKey: [options.dataProviderName, resource, 'one', currentId, queryMeta],
272
272
  queryFn: async () => {
273
- const result = await provider.getOne<BaseRecord>({ resource, id: currentId!, meta: queryMeta });
273
+ const result = await provider.getOne<BaseRecord>({ resource, id: currentId as string, meta: queryMeta });
274
274
  return result;
275
275
  },
276
276
  enabled: (queryOptions?.enabled ?? true) && (action === 'edit' || action === 'clone' || action === 'show') && currentId != null,
@@ -345,7 +345,7 @@ export function useForm<
345
345
  const updateMut = createMutation<{ data: TData }, unknown, TVariables>(() => ({
346
346
  ...options.updateMutationOptions,
347
347
  mutationFn: async (variables: TVariables) => {
348
- const targetId = currentId!;
348
+ const targetId = currentId as string;
349
349
  const targetResource = resource;
350
350
  const targetMeta = mutationMeta;
351
351
  if (mutationMode === 'undoable') {
@@ -50,7 +50,7 @@ export function unionFilters(
50
50
  return false;
51
51
  };
52
52
 
53
- let result = [...prevFilters];
53
+ const result = [...prevFilters];
54
54
 
55
55
  for (const newFilter of newFilters) {
56
56
  const idx = result.findIndex((f) => compareFilters(f, newFilter));
package/src/helpers.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  // Refine v5 compatible utility helpers
2
2
 
3
- import type { Filter, Sort, CrudOperator } from './types';
4
3
  import { t } from './i18n.svelte';
5
4
  import { getTextTransformers } from './options.svelte';
6
5
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable svelte/prefer-svelte-reactivity */
1
2
  // @svadmin/core — Core Data and Hook APIs (Modularized for v0.2.29+)
2
3
 
3
4
  // ─── Re-exports from modular hook files ─────────────────────────────
@@ -30,13 +31,11 @@ import { invalidateByScopes, publishLiveEvent } from './mutation-hooks.svelte';
30
31
  import type { NotificationConfig, OvertimeOptions, LiveSubscriptionParams } from './hook-utils.svelte';
31
32
  import type { BaseRecord, HttpError, Pagination, Sort, Filter, DataProvider, KnownResources } from './types';
32
33
  import type { LiveMode, LiveEvent } from './live.svelte';
33
- import { useList } from './query-hooks.svelte';
34
- import type { UseListOptions } from './query-hooks.svelte';
35
34
  import { audit } from './audit';
36
35
 
37
36
  // ─── useInfiniteList ────────────────────────────────────────────────
38
37
 
39
- export interface UseInfiniteListOptions<TData extends BaseRecord = BaseRecord, TError = HttpError> {
38
+ export interface UseInfiniteListOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
40
39
  resource?: KnownResources;
41
40
  pagination?: Pagination;
42
41
  sorters?: Sort[];
@@ -113,7 +112,7 @@ export function useInfiniteList<TData extends BaseRecord = BaseRecord, TError =
113
112
 
114
113
  // ─── useSelect ──────────────────────────────────────────────────────
115
114
 
116
- export interface UseSelectOptions<TData extends BaseRecord = BaseRecord, TOption = { label: string; value: string | number }> {
115
+ export interface UseSelectOptions<TData extends BaseRecord = BaseRecord, _TOption = { label: string; value: string | number }> {
117
116
  resource: KnownResources;
118
117
  optionLabel?: string | ((item: TData) => string);
119
118
  optionValue?: string | ((item: TData) => string | number);
@@ -231,7 +230,7 @@ export function useSelect<TData extends BaseRecord = BaseRecord, TOption = { lab
231
230
 
232
231
  // ─── useCustom ──────────────────────────────────────────────────────
233
232
 
234
- export interface UseCustomOptions<TData = unknown, TError = HttpError> {
233
+ export interface UseCustomOptions<_TData = unknown, _TError = HttpError> {
235
234
  url: string;
236
235
  method: 'get' | 'post' | 'put' | 'patch' | 'delete';
237
236
  config?: { payload?: unknown; query?: Record<string, unknown>; headers?: Record<string, string>; sorters?: Sort[]; filters?: Filter[] };
package/src/live-sse.ts CHANGED
@@ -123,7 +123,7 @@ export function createSSELiveProvider(options: SSELiveProviderOptions): LiveProv
123
123
  subscribers.set(resource, new Set());
124
124
  isNewResource = true;
125
125
  }
126
- subscribers.get(resource)!.add(callback);
126
+ subscribers.get(resource)?.add(callback);
127
127
 
128
128
  if (!eventSource && typeof EventSource !== 'undefined') {
129
129
  connect();
@@ -121,7 +121,7 @@ export function createWebSocketLiveProvider(options: WebSocketLiveProviderOption
121
121
  if (!subscribers.has(resource)) {
122
122
  subscribers.set(resource, new Set());
123
123
  }
124
- subscribers.get(resource)!.add(callback);
124
+ subscribers.get(resource)?.add(callback);
125
125
  lastParams.set(resource, liveParams);
126
126
 
127
127
  // Ensure connection is open
@@ -1,10 +1,11 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import { createMutation, useQueryClient } from '@tanstack/svelte-query';
2
3
  import { getAdminOptions } from './options.svelte';
3
4
  import { getDataProviderForResource, getResource, getLiveProvider } from './context.svelte';
4
5
  import { useParsed } from './useParsed.svelte';
5
6
  import { audit } from './audit';
6
7
  import { UndoError } from './types';
7
- import type { BaseRecord, HttpError, CreateParams, UpdateParams, DeleteParams, KnownResources } from './types';
8
+ import type { BaseRecord, HttpError, KnownResources } from './types';
8
9
  import { checkError, createOvertimeTracker, fireSuccessNotification, fireErrorNotification } from './hook-utils.svelte';
9
10
  import type { NotificationConfig, OvertimeOptions } from './hook-utils.svelte';
10
11
  import { toast } from './toast.svelte';
@@ -132,14 +133,14 @@ export function useCreate<TData extends BaseRecord = BaseRecord, TError = HttpEr
132
133
  // Invalidation in onSuccess for create (refine pattern — no optimistic data to reconcile on error)
133
134
  invalidateByScopes(queryClient, resName, params.invalidates, ['list', 'many'], undefined, params.dataProviderName);
134
135
  if (typeof options.mutationOptions?.onSuccess === 'function') {
135
- (options.mutationOptions.onSuccess as Function)(data, params, context);
136
+ (options.mutationOptions.onSuccess as (...a: unknown[]) => unknown)(data, params, context);
136
137
  }
137
138
  },
138
139
  onError: (error, params, context) => {
139
140
  checkError(error);
140
141
  fireErrorNotification(params.errorNotification, 'Create failed', error, params.resource ?? defaultResource);
141
142
  if (typeof options.mutationOptions?.onError === 'function') {
142
- (options.mutationOptions.onError as Function)(error, params, context);
143
+ (options.mutationOptions.onError as (...a: unknown[]) => unknown)(error, params, context);
143
144
  }
144
145
  },
145
146
  }));
@@ -242,7 +243,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
242
243
 
243
244
  if (updater.list !== false) {
244
245
  queryClient.setQueriesData({ predicate: (q) => dp(q) && q.queryKey[1] === resName && q.queryKey[2] === 'list' }, (old: unknown) => {
245
- if (typeof updater.list === 'function') return updater.list!(old, params.variables, targetId);
246
+ if (typeof updater.list === 'function') return updater.list(old, params.variables, targetId);
246
247
  if (!old || typeof old !== 'object' || !('data' in old)) return old;
247
248
  const o = old as { data: Record<string, unknown>[] };
248
249
  return { ...o, data: o.data.map((item) => String(item[pk]) === String(targetId) ? deepMerge(item, params.variables) : item) };
@@ -251,7 +252,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
251
252
 
252
253
  if (updater.many !== false) {
253
254
  queryClient.setQueriesData({ predicate: (q) => dp(q) && q.queryKey[1] === resName && q.queryKey[2] === 'many' }, (old: unknown) => {
254
- if (typeof updater.many === 'function') return updater.many!(old, params.variables, targetId);
255
+ if (typeof updater.many === 'function') return updater.many(old, params.variables, targetId);
255
256
  if (!old || typeof old !== 'object' || !('data' in old)) return old;
256
257
  const o = old as { data: Record<string, unknown>[] };
257
258
  return { ...o, data: o.data.map((item) => String(item[pk]) === String(targetId) ? deepMerge(item, params.variables) : item) };
@@ -268,7 +269,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
268
269
  audit({ action: 'update', resource: resName, recordId: String(targetId) });
269
270
  publishLiveEvent(resName, 'updated', targetId != null ? [targetId] : undefined);
270
271
  if (typeof options.mutationOptions?.onSuccess === 'function') {
271
- (options.mutationOptions.onSuccess as Function)(data, params, extractedCtx);
272
+ (options.mutationOptions.onSuccess as (...a: unknown[]) => unknown)(data, params, extractedCtx);
272
273
  }
273
274
  },
274
275
  onSettled: (_data, error, params) => {
@@ -277,7 +278,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
277
278
  const targetId = params.id ?? defaultId;
278
279
  invalidateByScopes(queryClient, resName, params.invalidates, ['list', 'many', 'detail'], targetId != null ? targetId : undefined, params.dataProviderName);
279
280
  if (typeof options.mutationOptions?.onSettled === 'function') {
280
- (options.mutationOptions.onSettled as Function)(_data, error, params, undefined);
281
+ (options.mutationOptions.onSettled as (...a: unknown[]) => unknown)(_data, error, params, undefined);
281
282
  }
282
283
  },
283
284
  onError: (error, params, context: unknown) => {
@@ -292,7 +293,7 @@ export function useUpdate<TData extends BaseRecord = BaseRecord, TError = HttpEr
292
293
  const extractedCtx = ctx?._svadmin_ctx ? ctx.userContext : context;
293
294
  fireErrorNotification(params.errorNotification, 'Update failed', error, params.resource ?? defaultResource);
294
295
  if (typeof options.mutationOptions?.onError === 'function') {
295
- (options.mutationOptions.onError as Function)(error, params, extractedCtx);
296
+ (options.mutationOptions.onError as (...a: unknown[]) => unknown)(error, params, extractedCtx);
296
297
  }
297
298
  },
298
299
  }));
@@ -409,7 +410,7 @@ export function useDelete<TData extends BaseRecord = BaseRecord, TError = HttpEr
409
410
  audit({ action: 'delete', resource: resName, recordId: String(targetId) });
410
411
  publishLiveEvent(resName, 'deleted', targetId != null ? [targetId] : undefined);
411
412
  if (typeof options.mutationOptions?.onSuccess === 'function') {
412
- (options.mutationOptions.onSuccess as Function)(data, params, extractedCtx);
413
+ (options.mutationOptions.onSuccess as (...a: unknown[]) => unknown)(data, params, extractedCtx);
413
414
  }
414
415
  },
415
416
  onSettled: (_data, error, params) => {
@@ -418,7 +419,7 @@ export function useDelete<TData extends BaseRecord = BaseRecord, TError = HttpEr
418
419
  const targetId = params.id ?? defaultId;
419
420
  invalidateByScopes(queryClient, resName, params.invalidates, ['list', 'many'], targetId != null ? targetId : undefined, params.dataProviderName);
420
421
  if (typeof options.mutationOptions?.onSettled === 'function') {
421
- (options.mutationOptions.onSettled as Function)(_data, error, params, undefined);
422
+ (options.mutationOptions.onSettled as (...a: unknown[]) => unknown)(_data, error, params, undefined);
422
423
  }
423
424
  },
424
425
  onError: (error, params, context: unknown) => {
@@ -433,7 +434,7 @@ export function useDelete<TData extends BaseRecord = BaseRecord, TError = HttpEr
433
434
  const extractedCtx = ctx?._svadmin_ctx ? ctx.userContext : context;
434
435
  fireErrorNotification(params.errorNotification, 'Delete failed', error, params.resource ?? defaultResource);
435
436
  if (typeof options.mutationOptions?.onError === 'function') {
436
- (options.mutationOptions.onError as Function)(error, params, extractedCtx);
437
+ (options.mutationOptions.onError as (...a: unknown[]) => unknown)(error, params, extractedCtx);
437
438
  }
438
439
  },
439
440
  }));
@@ -93,21 +93,24 @@ export async function canAccessAsync(resourceOrBatch: string | CanParams[], acti
93
93
  return provider.can(resourceOrBatch);
94
94
  }
95
95
 
96
- return provider.can({ resource: resourceOrBatch, action: action!, params }) as Promise<CanResult>;
96
+ return provider.can({ resource: resourceOrBatch, action: action as string, params }) as Promise<CanResult>;
97
97
  }
98
98
 
99
99
  // ─── Feature Gate ─────────────────────────────────────────────
100
100
 
101
- /**
102
- * 角色层级定义 (高权 → 低权)
103
- */
104
- const ROLE_HIERARCHY = ["superadmin", "admin", "manager", "user", "viewer", "guest"];
105
-
106
101
  export interface FeatureGateConfig {
107
102
  /** 允许访问的角色列表 */
108
103
  roles?: string[];
109
- /** 需要的最低角色 (含以上所有角色) */
104
+ /**
105
+ * 需要的最低角色 (含以上所有角色)。
106
+ * 使用 minRole 时必须同时提供 roleHierarchy。
107
+ */
110
108
  minRole?: string;
109
+ /**
110
+ * 角色层级定义 (高权 → 低权),用于 minRole 比较。
111
+ * 由调用方按自身业务定义,框架不预设任何角色。
112
+ */
113
+ roleHierarchy?: string[];
111
114
  /** 需要的权限列表 (全部匹配) */
112
115
  permissions?: string[];
113
116
  }
@@ -119,34 +122,39 @@ export interface FeatureGateUser {
119
122
 
120
123
  /**
121
124
  * 创建功能门控函数 — 基于角色和权限判断用户是否可访问某功能。
125
+ * 不预设任何角色层级,由调用方完全定义。
122
126
  *
123
127
  * @example
124
128
  * ```ts
125
- * const canUseCode = createFeatureGate({
126
- * minRole: 'manager',
127
- * permissions: ['code:execute'],
129
+ * const HIERARCHY = ['admin', 'editor', 'viewer'];
130
+ * const canEdit = createFeatureGate({
131
+ * minRole: 'editor',
132
+ * roleHierarchy: HIERARCHY,
133
+ * permissions: ['content:write'],
128
134
  * });
129
135
  *
130
- * if (canUseCode(user)) { ... }
136
+ * if (canEdit(user)) { ... }
131
137
  * ```
132
138
  */
133
139
  export function createFeatureGate(config: FeatureGateConfig): (user: FeatureGateUser) => boolean {
134
140
  return (user: FeatureGateUser): boolean => {
135
- // 角色白名单
136
141
  if (config.roles && !config.roles.includes(user.role)) {
137
142
  return false;
138
143
  }
139
144
 
140
- // 最低角色层级检查
141
145
  if (config.minRole) {
142
- const userIndex = ROLE_HIERARCHY.indexOf(user.role);
143
- const minIndex = ROLE_HIERARCHY.indexOf(config.minRole);
144
- if (userIndex === -1 || minIndex === -1 || userIndex > minIndex) {
145
- return false;
146
+ const hierarchy = config.roleHierarchy;
147
+ if (!hierarchy || hierarchy.length === 0) {
148
+ if (user.role !== config.minRole) return false;
149
+ } else {
150
+ const userIndex = hierarchy.indexOf(user.role);
151
+ const minIndex = hierarchy.indexOf(config.minRole);
152
+ if (userIndex === -1 || minIndex === -1 || userIndex > minIndex) {
153
+ return false;
154
+ }
146
155
  }
147
156
  }
148
157
 
149
- // 权限检查 (全部匹配)
150
158
  if (config.permissions && config.permissions.length > 0) {
151
159
  const hasAll = config.permissions.every((permission) => {
152
160
  if (user.permissions.includes("*")) return true;
@@ -1,4 +1,4 @@
1
- import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query';
1
+ import { createQuery } from '@tanstack/svelte-query';
2
2
  import { getAdminOptions } from './options.svelte';
3
3
  import { getDataProviderForResource, getDataProvider, getLiveProvider } from './context.svelte';
4
4
  import { useParsed } from './useParsed.svelte';
@@ -57,7 +57,7 @@ function cloneQueryKeyPart<T>(value: T): T {
57
57
  return value;
58
58
  }
59
59
 
60
- export interface UseListOptions<TData extends BaseRecord = BaseRecord, TError = HttpError> {
60
+ export interface UseListOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
61
61
  resource?: KnownResources;
62
62
  pagination?: Pagination;
63
63
  sorters?: Sort[];
@@ -146,7 +146,7 @@ export function useList<TData extends BaseRecord = BaseRecord, TError = HttpErro
146
146
 
147
147
  // ─── useOne ────────────────────────────────────────────────────
148
148
 
149
- export interface UseOneOptions<TData extends BaseRecord = BaseRecord, TError = HttpError> {
149
+ export interface UseOneOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
150
150
  resource?: KnownResources;
151
151
  id?: string | number;
152
152
  meta?: Record<string, unknown>;
@@ -256,7 +256,7 @@ export function useShow<TData extends BaseRecord = BaseRecord, TError = HttpErro
256
256
 
257
257
  // ─── useMany ───────────────────────────────────────────────────
258
258
 
259
- export interface UseManyOptions<TData extends BaseRecord = BaseRecord, TError = HttpError> {
259
+ export interface UseManyOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
260
260
  resource: KnownResources;
261
261
  ids: (string | number)[];
262
262
  meta?: Record<string, unknown>;
@@ -1,4 +1,5 @@
1
- import { currentPath, navigate } from './router';
1
+ /* eslint-disable svelte/prefer-svelte-reactivity */
2
+ import { navigate } from './router';
2
3
  import { getResource, getResources, getRouterProvider } from './context.svelte';
3
4
  import { useParsed } from './useParsed.svelte';
4
5
 
@@ -7,7 +7,7 @@ import type { UseListOptions, MaybeGetter } from './query-hooks.svelte';
7
7
 
8
8
  export type FilterSetMode = 'merge' | 'replace';
9
9
 
10
- export interface UseTableOptions<TQueryFnData extends BaseRecord = BaseRecord, TError = HttpError, TSearchVariables = Record<string, unknown>> extends Omit<UseListOptions<TQueryFnData, TError>, 'pagination' | 'sorters' | 'filters'> {
10
+ export interface UseTableOptions<TQueryFnData extends BaseRecord = BaseRecord, TError = HttpError, _TSearchVariables = Record<string, unknown>> extends Omit<UseListOptions<TQueryFnData, TError>, 'pagination' | 'sorters' | 'filters'> {
11
11
  resource?: KnownResources;
12
12
  pagination?: Pagination;
13
13
  sorters?: {
@@ -80,7 +80,7 @@ export function useTable<
80
80
 
81
81
  const tableQueryInfo = useList<TQueryFnData, TError>(() => {
82
82
  const currentOpts = getOptions();
83
- const { meta, syncWithLocation, dataProviderName, pagination: _p, sorters: _s, filters: _f, ...restOptions } = currentOpts;
83
+ const { meta, syncWithLocation: _swl, dataProviderName, pagination: _p, sorters: _s, filters: _f, ...restOptions } = currentOpts;
84
84
  return {
85
85
  resource: currentOpts.resource ?? parsed.resource ?? '',
86
86
  pagination: queryPagination,
@@ -209,6 +209,7 @@ export function useTable<
209
209
  if (vb == null && va != null) return -1;
210
210
  if (va == null && vb == null) continue;
211
211
 
212
+ // eslint-disable-next-line no-useless-assignment
212
213
  let cmp = 0;
213
214
  if (typeof va === 'number' && typeof vb === 'number') cmp = va - vb;
214
215
  else if (va instanceof Date && vb instanceof Date) cmp = va.getTime() - vb.getTime();
@@ -1,3 +1,4 @@
1
+ /* eslint-disable svelte/prefer-svelte-reactivity */
1
2
  // TableState — Svelte 5 Class State Pattern for table data management
2
3
  // Reactive class that encapsulates all table state with fine-grained $state/$derived
3
4
 
@@ -94,7 +95,7 @@ export class TableState<TData extends BaseRecord = BaseRecord, TError = HttpErro
94
95
  this.currentFilters = initFilters;
95
96
 
96
97
  // Create query
97
- const _this = this;
98
+ const _this = this; // eslint-disable-line @typescript-eslint/no-this-alias
98
99
  const { meta, syncWithLocation: _s, dataProviderName, pagination: _p, sorters: _sr, filters: _f, ...restOptions } = options;
99
100
  this._queryResult = useList<TData, TError>({
100
101
  resource,
@@ -226,7 +227,7 @@ export class TableState<TData extends BaseRecord = BaseRecord, TError = HttpErro
226
227
  get clientData(): TData[] {
227
228
  if (this.paginationMode === 'server') return [];
228
229
  const allData = this.data;
229
- let sorted = [...allData];
230
+ const sorted = [...allData];
230
231
  const activeSorters = this.sortersMode === 'off' ? this.effectiveSorters : [];
231
232
  if (activeSorters.length > 0) {
232
233
  sorted.sort((a, b) => {
@@ -74,7 +74,7 @@ export function useSubmitTask<
74
74
  return { mutation, get overtime() { return overtime; } };
75
75
  }
76
76
 
77
- export interface UseTaskOptions<TTask extends TaskRecord = TaskRecord, TError = HttpError> {
77
+ export interface UseTaskOptions<TTask extends TaskRecord = TaskRecord, _TError = HttpError> {
78
78
  taskId?: string;
79
79
  taskProvider?: TaskProvider<TTask>;
80
80
  queryOptions?: {
@@ -138,7 +138,7 @@ export function useTask<TTask extends TaskRecord = TaskRecord, TError = HttpErro
138
138
  }) as typeof query & { overtime: typeof overtime };
139
139
  }
140
140
 
141
- export interface UseTaskListOptions<TTask extends TaskRecord = TaskRecord, TError = HttpError> {
141
+ export interface UseTaskListOptions<TTask extends TaskRecord = TaskRecord, _TError = HttpError> {
142
142
  params?: Record<string, unknown>;
143
143
  dlq?: boolean;
144
144
  taskProvider?: TaskProvider<TTask>;
@@ -255,7 +255,7 @@ export function configureTheme(config: ThemeConfig): void {
255
255
  if (preset) {
256
256
  colorTheme = preset.name as ColorTheme;
257
257
  if (typeof window !== 'undefined') {
258
- try { localStorage.setItem(COLOR_STORAGE_KEY, preset.name); } catch {}
258
+ try { localStorage.setItem(COLOR_STORAGE_KEY, preset.name); } catch { /* intentional */}
259
259
  }
260
260
  applyColorTheme(preset.name as ColorTheme);
261
261
  applyColorPreset(preset);
@@ -369,7 +369,7 @@ export function getTheme(): ThemeMode {
369
369
  export function setTheme(newMode: ThemeMode): void {
370
370
  mode = newMode;
371
371
  if (typeof window !== 'undefined') {
372
- try { localStorage.setItem(STORAGE_KEY, newMode); } catch {}
372
+ try { localStorage.setItem(STORAGE_KEY, newMode); } catch { /* intentional */}
373
373
  }
374
374
  applyTheme(newMode);
375
375
  // Re-apply color preset — light/dark CSS variable overrides differ
@@ -416,7 +416,7 @@ export function getColorTheme(): ColorTheme {
416
416
  export function setColorTheme(ct: ColorTheme): void {
417
417
  colorTheme = ct;
418
418
  if (typeof window !== 'undefined') {
419
- try { localStorage.setItem(COLOR_STORAGE_KEY, ct); } catch {}
419
+ try { localStorage.setItem(COLOR_STORAGE_KEY, ct); } catch { /* intentional */}
420
420
  }
421
421
  applyColorTheme(ct);
422
422
  const preset = builtinPresets[ct];
@@ -1,3 +1,4 @@
1
+ /* eslint-disable svelte/prefer-svelte-reactivity */
1
2
  export type ToastType = 'success' | 'error' | 'info' | 'warning' | 'undoable';
2
3
 
3
4
  export interface ToastItem {
package/src/types.ts CHANGED
@@ -256,7 +256,7 @@ export interface TaskSubscription {
256
256
  export interface TaskHandle<TTask extends TaskRecord = TaskRecord> {
257
257
  id?: string;
258
258
  wait(): Promise<TTask>;
259
- subscribe?(callback: (task: TTask) => void): TaskSubscription | (() => void) | void;
259
+ subscribe?(callback: (task: TTask) => void): TaskSubscription | (() => void) | undefined;
260
260
  cancel?(): Promise<unknown>;
261
261
  retry?(): Promise<unknown>;
262
262
  }
@@ -273,7 +273,7 @@ export interface TaskProvider<TTask extends TaskRecord = TaskRecord> {
273
273
  listDlq?(params?: Record<string, unknown>): Promise<TaskListResult<TTask>>;
274
274
  cancel?(taskId: string): Promise<unknown>;
275
275
  retry?(taskId: string): Promise<unknown>;
276
- subscribe?(taskId: string, callback: (task: TTask) => void): TaskSubscription | (() => void) | void;
276
+ subscribe?(taskId: string, callback: (task: TTask) => void): TaskSubscription | (() => void) | undefined;
277
277
  }
278
278
 
279
279
  // ─── AuthProvider ─────────────────────────────────────────────
@@ -448,6 +448,7 @@ export interface FieldDefinition {
448
448
  * // list.data → { id: string; name: string; email: string }[]
449
449
  * ```
450
450
  */
451
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
451
452
  export interface ResourceTypeMap {}
452
453
 
453
454
  /** When ResourceTypeMap is empty → string; otherwise → registered keys */
@@ -1,3 +1,4 @@
1
+ /* eslint-disable svelte/prefer-svelte-reactivity */
1
2
  // UnsavedChangesNotifier — guards against accidental navigation with dirty forms
2
3
  // Supports: browser tab close, hash-based SPA routing, and framework router interception
3
4
 
@@ -14,7 +15,7 @@ export function resetUnsavedChanges(): void {
14
15
  dirty = false;
15
16
  _unsavedChangesCleanup?.();
16
17
  _unsavedChangesCleanup = null;
17
- _navigationGuard = null;
18
+ __navigationGuard = null;
18
19
  }
19
20
 
20
21
  export function getUnsavedChanges(): boolean {
@@ -30,7 +31,7 @@ interface NavigationGuard {
30
31
  (opts: { cancel: () => void }): void;
31
32
  }
32
33
 
33
- let _navigationGuard: NavigationGuard | null = null;
34
+ let __navigationGuard: NavigationGuard | null = null;
34
35
 
35
36
  /**
36
37
  * Register a framework-level navigation guard.
@@ -51,7 +52,7 @@ let _navigationGuard: NavigationGuard | null = null;
51
52
  * and pass a `beforeNavigate` callback.
52
53
  */
53
54
  export function registerNavigationGuard(guard: NavigationGuard): void {
54
- _navigationGuard = guard;
55
+ __navigationGuard = guard;
55
56
  }
56
57
 
57
58
  /**
@@ -1,3 +1,5 @@
1
+ /* eslint-disable svelte/prefer-svelte-reactivity */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
3
  // useParsed — parse current URL hash into structured route info
2
4
 
3
5
  import { currentPath, registerRouterSync } from './router';
@@ -1,9 +1,9 @@
1
+ /* eslint-disable svelte/prefer-svelte-reactivity */
1
2
  import { useForm, type UseFormOptions, type UseFormReturn } from './form-hooks.svelte';
2
3
  import { useQueryClient } from '@tanstack/svelte-query';
3
4
  import { useParsed } from './useParsed.svelte';
4
5
  import { getResource, getResources } from './context.svelte';
5
6
  import { getAdminOptions } from './options.svelte';
6
- import { navigate } from './router';
7
7
  import { t } from './i18n.svelte';
8
8
  import type { BaseRecord, HttpError, Filter, KnownResources, ResourceDefinition } from './types';
9
9
  import { useList, type UseListOptions } from './query-hooks.svelte';
@@ -162,7 +162,7 @@ export function useBreadcrumb() {
162
162
 
163
163
  // ─── useRelation ───────────────────────────────────────────
164
164
 
165
- export interface UseRelationOptions<TData extends BaseRecord = BaseRecord, TError = HttpError> {
165
+ export interface UseRelationOptions<_TData extends BaseRecord = BaseRecord, _TError = HttpError> {
166
166
  resource: KnownResources;
167
167
  id?: string | number;
168
168
  pagination?: { current?: number; pageSize?: number };