@svadmin/core 0.27.0 → 0.28.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/core",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
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[] };
@@ -144,6 +144,19 @@ const baseLocales: Record<string, Record<string, string>> = {
144
144
  'devtools.title': '开发者工具 (Ctrl+Shift+D)',
145
145
  // Validation
146
146
  // Chat
147
+ 'chat.title': 'AI 助手',
148
+ 'chat.typing': '输入中...',
149
+ 'chat.clear': '清空',
150
+ 'chat.welcome': '我可以帮你做什么?',
151
+ 'chat.welcomeDesc': '可以询问当前后台、资源和库存运营数据。',
152
+ 'chat.thinking': '思考中...',
153
+ 'chat.placeholder': '输入消息...',
154
+ 'chat.shortcutHint': '切换',
155
+ 'chat.error': '抱歉,处理时出现问题,请重试。',
156
+ 'chat.approvalRequired': '需要确认',
157
+ 'chat.suggestion1': '如何新建一条资源记录?',
158
+ 'chat.suggestion2': '解释当前数据模型',
159
+ 'chat.suggestion3': '帮我写一个筛选条件',
147
160
  'chat.contextInfo': '当前上下文:{resource}',
148
161
  'chat.actionButton': '执行',
149
162
  'chat.historyRestored': '已恢复聊天记录',
@@ -162,6 +175,62 @@ const baseLocales: Record<string, Record<string, string>> = {
162
175
  'settings.appearance': '外观设置',
163
176
  'settings.about': '关于',
164
177
  'settings.general': '通用',
178
+ 'settings.security': '安全设置',
179
+ 'settings.integrations': '第三方集成',
180
+ 'settings.notifications': '通知设置',
181
+ 'settings.api': 'API 设置',
182
+ 'settings.developer': '开发者',
183
+ 'settings.workspace': '工作区',
184
+ 'settings.securityDescription': '管理您的密码、活跃会话和双重身份验证偏好。',
185
+ 'settings.integrationsDescription': '连接和管理工作区的第三方服务扩展。',
186
+ 'settings.notificationsDescription': '配置系统警报的接收方式和时机。',
187
+ 'settings.apiDescription': '生成并管理安全 API 密钥,以便集成外部程序脚本。',
188
+ 'security.twoFactorAuth': '双重身份验证 (2FA)',
189
+ 'security.twoFactorDescription': '启用后,在输入密码后还需要输入手机验证码,为账户增加额外安全保障。',
190
+ 'security.enable2fa': '启用双重身份验证',
191
+ 'security.twoFactorActive': '双重身份验证已启用。新登录时将需要验证码。',
192
+ 'security.twoFactorInactive': '双重身份验证已禁用。建议开启以保护账户安全。',
193
+ 'security.passwordLastChanged': '上次修改:{time}',
194
+ 'security.activeSessions': '活跃会话',
195
+ 'security.sessionsDescription': '以下是已登录您账户的设备。您可以撤销任何不识别的设备。',
196
+ 'security.revokeOthers': '注销其他所有设备',
197
+ 'security.currentSession': '当前设备',
198
+ 'security.revokeSession': '注销此设备',
199
+ 'security.noSessions': '无活跃会话。',
200
+ 'integrations.statusConnected': '已连接',
201
+ 'integrations.statusDisconnected': '未连接',
202
+ 'integrations.enabled': '已启用',
203
+ 'integrations.disabled': '已禁用',
204
+ 'integrations.addMoreHint': '需要自定义集成?请联系管理员注册新的应用程序。',
205
+ 'notifications.emailTitle': '邮件通知',
206
+ 'notifications.emailDescription': '将系统自动发送的通知推送到您的电子邮箱。',
207
+ 'notifications.securityAlerts': '安全警报',
208
+ 'notifications.securityAlertsDesc': '在密码修改、新登录尝试或生成 API 密钥时收到通知。',
209
+ 'notifications.activityLogs': '活动报告',
210
+ 'notifications.activityLogsDesc': '每周获取关于系统审计、资源修改和后台任务的摘要。',
211
+ 'notifications.systemAlerts': '系统报告',
212
+ 'notifications.systemAlertsDesc': '在服务器实例或数据流水线发生故障时接收实时通知。',
213
+ 'notifications.pushTitle': '应用内与浏览器推送',
214
+ 'notifications.pushDescription': '在当前打开的浏览器标签页上接收推送通知。',
215
+ 'notifications.securityPushDesc': '在检测到未经授权的登录时立即在浏览器发出警报。',
216
+ 'notifications.activityPushDesc': '当同事编辑资源时,即时接收操作日志推送。',
217
+ 'notifications.systemPushDesc': '针对任务队列和内存异常的严重警报。',
218
+ 'notifications.smsTitle': '短信紧急警报',
219
+ 'notifications.smsDescription': '可能产生标准资费。仅在需要管理员手动干预的紧急事件时使用。',
220
+ 'notifications.criticalSecurity': '关键安全事件',
221
+ 'notifications.criticalSecurityDesc': '在发生 Root 密码重置或双重身份验证绕过时发送短信警报。',
222
+ 'api.generateTitle': '创建新 API 密钥',
223
+ 'api.generateDesc': '配置权限范围并生成访问令牌。',
224
+ 'api.keyName': '密钥名称',
225
+ 'api.permissions': '权限范围',
226
+ 'api.generateButton': '生成令牌',
227
+ 'api.activeKeysTitle': '活跃 API 密钥',
228
+ 'api.activeKeysDesc': '请妥善保管这些密钥。切勿在公共仓库中分享。',
229
+ 'api.successTitle': 'API 密钥已生成',
230
+ 'api.successHint': '请立即复制此令牌。关闭此提示后,您将无法再次查看它。',
231
+ 'api.noKeys': '尚未生成活跃的 API 密钥。',
232
+ 'error.pageNotFoundDescription': '抱歉,您访问的页面不存在或已被移除。',
233
+ 'error.internalServerErrorDescription': '系统发生了一些问题,请稍后再试或联系支持人员。',
165
234
  'settings.system': '系统',
166
235
  'settings.themeMode': '主题模式',
167
236
  'settings.light': '浅色',
@@ -316,6 +385,127 @@ const baseLocales: Record<string, Record<string, string>> = {
316
385
  'task.status.dead_lettered': '死信',
317
386
  'task.status.retry_scheduled': '等待重试',
318
387
  'task.status.leased': '已领取',
388
+ // Public Profile
389
+ 'publicProfile.title': '公开资料',
390
+ 'publicProfile.projects': '项目',
391
+ 'publicProfile.activity': '动态',
392
+ 'publicProfile.teams': '团队',
393
+ 'publicProfile.profile': '资料',
394
+ 'publicProfile.viewProfile': '查看资料',
395
+ 'publicProfile.followers': '关注者',
396
+ 'publicProfile.following': '关注中',
397
+ 'publicProfile.editProfile': '编辑资料',
398
+ 'publicProfile.sendMessage': '发消息',
399
+ 'publicProfile.addFriend': '添加好友',
400
+ 'publicProfile.projects2Col': '项目 (两列)',
401
+ 'publicProfile.projects3Col': '项目 (三列)',
402
+ 'publicProfile.noProjects': '暂无项目',
403
+ 'publicProfile.noActivity': '暂无动态',
404
+ 'publicProfile.noTeams': '暂无团队',
405
+ 'publicProfile.joinedDate': '加入于 {date}',
406
+ 'publicProfile.projectMembers': '{count} 名成员',
407
+ 'publicProfile.projectTasks': '{count} 个任务',
408
+ 'publicProfile.activityPosted': '发布了内容',
409
+ 'publicProfile.activityCommented': '评论了',
410
+ 'publicProfile.activityJoined': '加入了',
411
+ 'publicProfile.activityCreated': '创建了',
412
+ // Profile variants
413
+ 'profile.company': '公司资料',
414
+ 'profile.companyDescription': '公司简介和统计信息',
415
+ 'profile.gamer': '玩家资料',
416
+ 'profile.gamerDescription': '游戏成就和统计',
417
+ 'profile.default': '个人资料',
418
+ 'profile.defaultDescription': '个人简介和活动',
419
+ 'profile.companyName': '公司名称',
420
+ 'profile.industry': '行业',
421
+ 'profile.employees': '员工数',
422
+ 'profile.founded': '成立时间',
423
+ 'profile.website': '网站',
424
+ 'profile.gamerTag': '玩家标签',
425
+ 'profile.level': '等级',
426
+ 'profile.rank': '排名',
427
+ 'profile.gamesPlayed': '游戏场次',
428
+ 'profile.winRate': '胜率',
429
+ // Account extensions
430
+ 'account.getStarted': '入门指南',
431
+ 'account.getStartedDescription': '按照步骤完成初始设置,快速上手平台功能。',
432
+ 'account.companyProfile': '公司信息',
433
+ 'account.companyProfileDescription': '管理公司基本信息和品牌设置。',
434
+ 'account.settingsPlain': '基本设置',
435
+ 'account.settingsPlainDescription': '管理基本账户设置。',
436
+ 'account.settingsSidebar': '侧栏设置',
437
+ 'account.settingsSidebarDescription': '配置侧边栏布局和行为。',
438
+ 'account.settingsEnterprise': '企业设置',
439
+ 'account.settingsEnterpriseDescription': '管理企业级配置和策略。',
440
+ 'account.importMembers': '导入成员',
441
+ 'account.importMembersDescription': '从 CSV 文件批量导入团队成员。',
442
+ 'account.membersStarter': '成员概览',
443
+ 'account.membersStarterDescription': '查看和管理工作区成员。',
444
+ 'account.securityLog': '安全日志',
445
+ 'account.securityLogDescription': '查看账户安全相关的操作记录。',
446
+ 'account.step': '第 {current} 步(共 {total} 步)',
447
+ 'account.completeSetup': '完成设置',
448
+ 'account.uploadAvatar': '上传头像',
449
+ 'account.connectProvider': '连接服务',
450
+ 'account.setupProfile': '完善资料',
451
+ 'account.setupTeam': '邀请团队',
452
+ 'account.skipSetup': '跳过设置',
453
+ 'account.nextStep': '下一步',
454
+ 'account.prevStep': '上一步',
455
+ 'account.finishSetup': '完成设置',
456
+ 'account.dragOrClick': '拖拽文件或点击上传',
457
+ 'account.supportedFormats': '支持 CSV、XLSX 格式',
458
+ 'account.downloadTemplate': '下载模板',
459
+ 'account.startImport': '开始导入',
460
+ 'account.importProgress': '导入进度',
461
+ 'account.importComplete': '导入完成',
462
+ 'account.imported': '已导入 {count} 条记录',
463
+ 'account.member': '成员',
464
+ 'account.role': '角色',
465
+ 'account.status': '状态',
466
+ 'account.joined': '加入时间',
467
+ 'account.active': '活跃',
468
+ 'account.invited': '已邀请',
469
+ 'account.inactive': '未激活',
470
+ 'account.inviteMember': '邀请成员',
471
+ 'account.removeMember': '移除成员',
472
+ // Network
473
+ 'network.userCards': '用户卡片',
474
+ 'network.userCardsDescription': '以卡片形式浏览用户列表。',
475
+ 'network.teamCrew': '团队花名册',
476
+ 'network.teamCrewDescription': '以表格形式查看团队成员信息。',
477
+ 'network.connect': '关注',
478
+ 'network.connected': '已关注',
479
+ 'network.nftCollection': 'NFT 收藏',
480
+ 'network.nftCount': '{count} 件藏品',
481
+ 'network.floorPrice': '地板价',
482
+ 'network.volume': '交易量',
483
+ 'network.viewCollection': '查看收藏',
484
+ // Auth
485
+ 'auth.twoFactorSetup': '设置双重验证',
486
+ 'auth.twoFactorVerify': '验证身份',
487
+ 'auth.twoFactorRecovery': '恢复代码',
488
+ 'auth.twoFactorScanQR': '使用验证器应用扫描二维码',
489
+ 'auth.twoFactorEnterCode': '输入 6 位验证码',
490
+ 'auth.twoFactorVerifySuccess': '双重验证已成功启用',
491
+ 'auth.twoFactorSaveRecovery': '请妥善保存以下恢复代码',
492
+ 'auth.twoFactorRecoveryHint': '当您无法使用验证器时,可使用恢复代码登录。每个代码只能使用一次。',
493
+ 'auth.twoFactorDisable': '关闭双重验证',
494
+ 'auth.twoFactorDisableConfirm': '确认要关闭双重验证吗?这将降低账户安全性。',
495
+ // Security Log
496
+ 'securityLog.event': '事件',
497
+ 'securityLog.ip': 'IP 地址',
498
+ 'securityLog.device': '设备',
499
+ 'securityLog.location': '位置',
500
+ 'securityLog.time': '时间',
501
+ 'securityLog.loginSuccess': '登录成功',
502
+ 'securityLog.loginFailed': '登录失败',
503
+ 'securityLog.passwordChanged': '密码已修改',
504
+ 'securityLog.twoFactorEnabled': '已启用双重验证',
505
+ 'securityLog.twoFactorDisabled': '已关闭双重验证',
506
+ 'securityLog.apiKeyCreated': 'API 密钥已创建',
507
+ 'securityLog.apiKeyRevoked': 'API 密钥已吊销',
508
+ 'securityLog.sessionRevoked': '会话已注销',
319
509
  },
320
510
  'en': {
321
511
  'common.list': 'List',
@@ -458,6 +648,19 @@ const baseLocales: Record<string, Record<string, string>> = {
458
648
  'devtools.title': 'DevTools (Ctrl+Shift+D)',
459
649
  // Validation
460
650
  // Chat
651
+ 'chat.title': 'AI Assistant',
652
+ 'chat.typing': 'Typing...',
653
+ 'chat.clear': 'Clear',
654
+ 'chat.welcome': 'How can I help?',
655
+ 'chat.welcomeDesc': 'Ask me anything about your admin panel, resources, and inventory operations.',
656
+ 'chat.thinking': 'Thinking...',
657
+ 'chat.placeholder': 'Type a message...',
658
+ 'chat.shortcutHint': 'to toggle',
659
+ 'chat.error': 'Sorry, something went wrong. Please try again.',
660
+ 'chat.approvalRequired': 'Approval required',
661
+ 'chat.suggestion1': 'How do I create a new resource?',
662
+ 'chat.suggestion2': 'Explain the data model',
663
+ 'chat.suggestion3': 'Help me write a filter query',
461
664
  'chat.contextInfo': 'Current context: {resource}',
462
665
  'chat.actionButton': 'Execute',
463
666
  'chat.historyRestored': 'Chat history restored',
@@ -476,6 +679,62 @@ const baseLocales: Record<string, Record<string, string>> = {
476
679
  'settings.appearance': 'Appearance',
477
680
  'settings.about': 'About',
478
681
  'settings.general': 'General',
682
+ 'settings.security': 'Security',
683
+ 'settings.integrations': 'Integrations',
684
+ 'settings.notifications': 'Notifications',
685
+ 'settings.api': 'API Settings',
686
+ 'settings.developer': 'Developer',
687
+ 'settings.workspace': 'Workspace',
688
+ 'settings.securityDescription': 'Manage your password, active sessions, and multi-factor authentication preferences.',
689
+ 'settings.integrationsDescription': 'Connect and manage third-party service extensions for your workspace.',
690
+ 'settings.notificationsDescription': 'Configure how and when you receive system alerts.',
691
+ 'settings.apiDescription': 'Generate and manage secure API keys to integrate external programmatic scripts.',
692
+ 'security.twoFactorAuth': 'Two-Factor Authentication (2FA)',
693
+ 'security.twoFactorDescription': 'Add an extra layer of security to your account by requiring a verification code in addition to your password.',
694
+ 'security.enable2fa': 'Enable 2FA Verification',
695
+ 'security.twoFactorActive': '2FA is currently active. Verification codes will be required on new logins.',
696
+ 'security.twoFactorInactive': '2FA is currently disabled. Protect your account by turning it on.',
697
+ 'security.passwordLastChanged': 'Last changed: {time}',
698
+ 'security.activeSessions': 'Active Sessions',
699
+ 'security.sessionsDescription': 'These are the devices that have logged into your account. Revoke any you do not recognize.',
700
+ 'security.revokeOthers': 'Sign Out Other Devices',
701
+ 'security.currentSession': 'Current Device',
702
+ 'security.revokeSession': 'Sign out device',
703
+ 'security.noSessions': 'No active sessions.',
704
+ 'integrations.statusConnected': 'Connected',
705
+ 'integrations.statusDisconnected': 'Disconnected',
706
+ 'integrations.enabled': 'Enabled',
707
+ 'integrations.disabled': 'Disabled',
708
+ 'integrations.addMoreHint': 'Need custom integrations? Contact your administrator to register new application manifests.',
709
+ 'notifications.emailTitle': 'Email Notifications',
710
+ 'notifications.emailDescription': 'Get automated system messages delivered directly to your mailbox.',
711
+ 'notifications.securityAlerts': 'Security Alerts',
712
+ 'notifications.securityAlertsDesc': 'Get notified of password changes, login attempts, and key generations.',
713
+ 'notifications.activityLogs': 'Activity Reports',
714
+ 'notifications.activityLogsDesc': 'Weekly summaries of system audits, modifications, and background tasks.',
715
+ 'notifications.systemAlerts': 'System Reports',
716
+ 'notifications.systemAlertsDesc': 'Real-time alerts when server instances or data pipelines fail.',
717
+ 'notifications.pushTitle': 'In-App & Push Notifications',
718
+ 'notifications.pushDescription': 'Receive push notifications on your active browser tab.',
719
+ 'notifications.securityPushDesc': 'Immediate browser alerts on unauthorized login detection.',
720
+ 'notifications.activityPushDesc': 'Instant notifications on CRUD logs when colleagues edit resources.',
721
+ 'notifications.systemPushDesc': 'Critical alerts for task queues and memory anomalies.',
722
+ 'notifications.smsTitle': 'SMS Emergency Alerts',
723
+ 'notifications.smsDescription': 'Standard rates apply. Used only for urgent events requiring manual administrator intervention.',
724
+ 'notifications.criticalSecurity': 'Critical Security Events',
725
+ 'notifications.criticalSecurityDesc': 'SMS alert sent when root password resets or 2FA bypass occurs.',
726
+ 'api.generateTitle': 'Create New API Key',
727
+ 'api.generateDesc': 'Configure scopes and generate a token.',
728
+ 'api.keyName': 'Key Name',
729
+ 'api.permissions': 'Permissions',
730
+ 'api.generateButton': 'Generate Token',
731
+ 'api.activeKeysTitle': 'Active API Keys',
732
+ 'api.activeKeysDesc': 'Keep these keys secure. Do not share them in public repositories.',
733
+ 'api.successTitle': 'API Key Generated',
734
+ 'api.successHint': 'Copy this token now. It will not be shown again.',
735
+ 'api.noKeys': 'No active API keys generated yet.',
736
+ 'error.pageNotFoundDescription': 'Sorry, the page you are looking for does not exist or has been moved.',
737
+ 'error.internalServerErrorDescription': 'Something went wrong on our end. Please try again later or contact support.',
479
738
  'settings.system': 'System',
480
739
  'settings.themeMode': 'Theme Mode',
481
740
  'settings.light': 'Light',
@@ -630,6 +889,127 @@ const baseLocales: Record<string, Record<string, string>> = {
630
889
  'task.status.dead_lettered': 'Dead lettered',
631
890
  'task.status.retry_scheduled': 'Retry scheduled',
632
891
  'task.status.leased': 'Leased',
892
+ // Public Profile
893
+ 'publicProfile.title': 'Public Profile',
894
+ 'publicProfile.projects': 'Projects',
895
+ 'publicProfile.activity': 'Activity',
896
+ 'publicProfile.teams': 'Teams',
897
+ 'publicProfile.profile': 'Profile',
898
+ 'publicProfile.viewProfile': 'View Profile',
899
+ 'publicProfile.followers': 'Followers',
900
+ 'publicProfile.following': 'Following',
901
+ 'publicProfile.editProfile': 'Edit Profile',
902
+ 'publicProfile.sendMessage': 'Send Message',
903
+ 'publicProfile.addFriend': 'Add Friend',
904
+ 'publicProfile.projects2Col': 'Projects (2 Columns)',
905
+ 'publicProfile.projects3Col': 'Projects (3 Columns)',
906
+ 'publicProfile.noProjects': 'No projects yet',
907
+ 'publicProfile.noActivity': 'No activity yet',
908
+ 'publicProfile.noTeams': 'No teams yet',
909
+ 'publicProfile.joinedDate': 'Joined {date}',
910
+ 'publicProfile.projectMembers': '{count} members',
911
+ 'publicProfile.projectTasks': '{count} tasks',
912
+ 'publicProfile.activityPosted': 'posted',
913
+ 'publicProfile.activityCommented': 'commented on',
914
+ 'publicProfile.activityJoined': 'joined',
915
+ 'publicProfile.activityCreated': 'created',
916
+ // Profile variants
917
+ 'profile.company': 'Company Profile',
918
+ 'profile.companyDescription': 'Company overview and statistics',
919
+ 'profile.gamer': 'Gamer Profile',
920
+ 'profile.gamerDescription': 'Gaming achievements and stats',
921
+ 'profile.default': 'Default Profile',
922
+ 'profile.defaultDescription': 'Personal overview and activity',
923
+ 'profile.companyName': 'Company Name',
924
+ 'profile.industry': 'Industry',
925
+ 'profile.employees': 'Employees',
926
+ 'profile.founded': 'Founded',
927
+ 'profile.website': 'Website',
928
+ 'profile.gamerTag': 'Gamer Tag',
929
+ 'profile.level': 'Level',
930
+ 'profile.rank': 'Rank',
931
+ 'profile.gamesPlayed': 'Games Played',
932
+ 'profile.winRate': 'Win Rate',
933
+ // Account extensions
934
+ 'account.getStarted': 'Get Started',
935
+ 'account.getStartedDescription': 'Follow the steps to complete your initial setup and get up to speed quickly.',
936
+ 'account.companyProfile': 'Company Profile',
937
+ 'account.companyProfileDescription': 'Manage company information and branding.',
938
+ 'account.settingsPlain': 'Basic Settings',
939
+ 'account.settingsPlainDescription': 'Manage basic account settings.',
940
+ 'account.settingsSidebar': 'Sidebar Settings',
941
+ 'account.settingsSidebarDescription': 'Configure sidebar layout and behavior.',
942
+ 'account.settingsEnterprise': 'Enterprise Settings',
943
+ 'account.settingsEnterpriseDescription': 'Manage enterprise-level configuration and policies.',
944
+ 'account.importMembers': 'Import Members',
945
+ 'account.importMembersDescription': 'Bulk import team members from a CSV file.',
946
+ 'account.membersStarter': 'Members Overview',
947
+ 'account.membersStarterDescription': 'View and manage workspace members.',
948
+ 'account.securityLog': 'Security Log',
949
+ 'account.securityLogDescription': 'View security-related activity logs for your account.',
950
+ 'account.step': 'Step {current} of {total}',
951
+ 'account.completeSetup': 'Complete Setup',
952
+ 'account.uploadAvatar': 'Upload Avatar',
953
+ 'account.connectProvider': 'Connect Provider',
954
+ 'account.setupProfile': 'Set Up Profile',
955
+ 'account.setupTeam': 'Invite Team',
956
+ 'account.skipSetup': 'Skip Setup',
957
+ 'account.nextStep': 'Next Step',
958
+ 'account.prevStep': 'Previous Step',
959
+ 'account.finishSetup': 'Finish Setup',
960
+ 'account.dragOrClick': 'Drag file or click to upload',
961
+ 'account.supportedFormats': 'Supports CSV, XLSX formats',
962
+ 'account.downloadTemplate': 'Download Template',
963
+ 'account.startImport': 'Start Import',
964
+ 'account.importProgress': 'Import Progress',
965
+ 'account.importComplete': 'Import Complete',
966
+ 'account.imported': 'Imported {count} records',
967
+ 'account.member': 'Member',
968
+ 'account.role': 'Role',
969
+ 'account.status': 'Status',
970
+ 'account.joined': 'Joined',
971
+ 'account.active': 'Active',
972
+ 'account.invited': 'Invited',
973
+ 'account.inactive': 'Inactive',
974
+ 'account.inviteMember': 'Invite Member',
975
+ 'account.removeMember': 'Remove Member',
976
+ // Network
977
+ 'network.userCards': 'User Cards',
978
+ 'network.userCardsDescription': 'Browse users in card layout.',
979
+ 'network.teamCrew': 'Team Crew',
980
+ 'network.teamCrewDescription': 'View team members in table layout.',
981
+ 'network.connect': 'Follow',
982
+ 'network.connected': 'Following',
983
+ 'network.nftCollection': 'NFT Collection',
984
+ 'network.nftCount': '{count} items',
985
+ 'network.floorPrice': 'Floor Price',
986
+ 'network.volume': 'Volume',
987
+ 'network.viewCollection': 'View Collection',
988
+ // Auth
989
+ 'auth.twoFactorSetup': 'Set Up Two-Factor Auth',
990
+ 'auth.twoFactorVerify': 'Verify Identity',
991
+ 'auth.twoFactorRecovery': 'Recovery Codes',
992
+ 'auth.twoFactorScanQR': 'Scan the QR code with your authenticator app',
993
+ 'auth.twoFactorEnterCode': 'Enter 6-digit verification code',
994
+ 'auth.twoFactorVerifySuccess': 'Two-factor authentication enabled successfully',
995
+ 'auth.twoFactorSaveRecovery': 'Save these recovery codes in a safe place',
996
+ 'auth.twoFactorRecoveryHint': 'You can use recovery codes to log in when you cannot access your authenticator. Each code can only be used once.',
997
+ 'auth.twoFactorDisable': 'Disable Two-Factor Auth',
998
+ 'auth.twoFactorDisableConfirm': 'Are you sure you want to disable two-factor authentication? This will reduce your account security.',
999
+ // Security Log
1000
+ 'securityLog.event': 'Event',
1001
+ 'securityLog.ip': 'IP Address',
1002
+ 'securityLog.device': 'Device',
1003
+ 'securityLog.location': 'Location',
1004
+ 'securityLog.time': 'Time',
1005
+ 'securityLog.loginSuccess': 'Login Successful',
1006
+ 'securityLog.loginFailed': 'Login Failed',
1007
+ 'securityLog.passwordChanged': 'Password Changed',
1008
+ 'securityLog.twoFactorEnabled': '2FA Enabled',
1009
+ 'securityLog.twoFactorDisabled': '2FA Disabled',
1010
+ 'securityLog.apiKeyCreated': 'API Key Created',
1011
+ 'securityLog.apiKeyRevoked': 'API Key Revoked',
1012
+ 'securityLog.sessionRevoked': 'Session Revoked',
633
1013
  },
634
1014
  };
635
1015
 
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,7 +93,7 @@ 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 ─────────────────────────────────────────────
@@ -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>;
@@ -235,6 +235,8 @@ export interface ThemeConfig {
235
235
  disableColorScheme?: boolean;
236
236
  /** Built-in color preset name (e.g. 'indigo', 'blue') or a custom ColorPreset object */
237
237
  colorPreset?: ColorPreset | string;
238
+ /** Layout style option: 'default' or 'clean-flat' for high-contrast flat panel design */
239
+ layoutPreset?: 'default' | 'clean-flat';
238
240
  }
239
241
 
240
242
  const STORAGE_KEY = 'svadmin-theme';
@@ -255,7 +257,7 @@ export function configureTheme(config: ThemeConfig): void {
255
257
  if (preset) {
256
258
  colorTheme = preset.name as ColorTheme;
257
259
  if (typeof window !== 'undefined') {
258
- try { localStorage.setItem(COLOR_STORAGE_KEY, preset.name); } catch {}
260
+ try { localStorage.setItem(COLOR_STORAGE_KEY, preset.name); } catch { /* intentional */}
259
261
  }
260
262
  applyColorTheme(preset.name as ColorTheme);
261
263
  applyColorPreset(preset);
@@ -328,6 +330,13 @@ function applyTheme(m: ThemeMode): void {
328
330
 
329
331
  const strategy = themeConfig.strategy ?? 'standard';
330
332
  const el = document.documentElement;
333
+
334
+ // Apply clean-flat layout preset class to html
335
+ if (themeConfig.layoutPreset === 'clean-flat') {
336
+ el.classList.add('layout-clean-flat');
337
+ } else {
338
+ el.classList.remove('layout-clean-flat');
339
+ }
331
340
 
332
341
  if (strategy === 'dark-first') {
333
342
  // Dark-first: default is dark, add 'light' class for light mode
@@ -369,7 +378,7 @@ export function getTheme(): ThemeMode {
369
378
  export function setTheme(newMode: ThemeMode): void {
370
379
  mode = newMode;
371
380
  if (typeof window !== 'undefined') {
372
- try { localStorage.setItem(STORAGE_KEY, newMode); } catch {}
381
+ try { localStorage.setItem(STORAGE_KEY, newMode); } catch { /* intentional */}
373
382
  }
374
383
  applyTheme(newMode);
375
384
  // Re-apply color preset — light/dark CSS variable overrides differ
@@ -416,7 +425,7 @@ export function getColorTheme(): ColorTheme {
416
425
  export function setColorTheme(ct: ColorTheme): void {
417
426
  colorTheme = ct;
418
427
  if (typeof window !== 'undefined') {
419
- try { localStorage.setItem(COLOR_STORAGE_KEY, ct); } catch {}
428
+ try { localStorage.setItem(COLOR_STORAGE_KEY, ct); } catch { /* intentional */}
420
429
  }
421
430
  applyColorTheme(ct);
422
431
  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 };