@svadmin/core 0.50.0 → 0.53.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.
Files changed (98) hide show
  1. package/package.json +21 -4
  2. package/src/access-control-contract.ts +250 -0
  3. package/src/adapters/casbin.ts +20 -14
  4. package/src/adapters/casl.ts +22 -19
  5. package/src/admin-tool.ts +7 -7
  6. package/src/audit-contract.ts +105 -0
  7. package/src/audit.ts +88 -128
  8. package/src/auth-hooks.svelte.ts +593 -261
  9. package/src/auth-mutation-contract.ts +143 -0
  10. package/src/auth-query-contract.ts +97 -0
  11. package/src/captured-mutation.svelte.ts +45 -0
  12. package/src/chatProvider.svelte.ts +1 -1
  13. package/src/command-contract.ts +195 -0
  14. package/src/context.svelte.ts +217 -205
  15. package/src/contract-context.svelte.ts +37 -0
  16. package/src/contract-form.svelte.ts +566 -0
  17. package/src/create-contract.ts +56 -0
  18. package/src/create-hooks.svelte.ts +347 -0
  19. package/src/create-many-contract.ts +90 -0
  20. package/src/create-many-hooks.svelte.ts +396 -0
  21. package/src/data-transfer.svelte.ts +1 -215
  22. package/src/defined-options.ts +50 -0
  23. package/src/delete-contract.ts +50 -0
  24. package/src/delete-hooks.svelte.ts +403 -0
  25. package/src/delete-many-contract.ts +60 -0
  26. package/src/delete-many-hooks.svelte.ts +402 -0
  27. package/src/enterprise.ts +4 -4
  28. package/src/export-format.ts +16 -14
  29. package/src/export-hooks.svelte.ts +191 -0
  30. package/src/feature-gate-contract.ts +53 -0
  31. package/src/hook-utils.svelte.ts +53 -105
  32. package/src/hooks.svelte.ts +19 -776
  33. package/src/http.ts +88 -87
  34. package/src/i18n.svelte.ts +64 -2
  35. package/src/import-contract.ts +106 -0
  36. package/src/import-hooks.svelte.ts +310 -0
  37. package/src/index.ts +77 -30
  38. package/src/inferencer-graphql.ts +7 -6
  39. package/src/inferencer.ts +9 -0
  40. package/src/infinite-query.svelte.ts +228 -0
  41. package/src/invalidation-contract.ts +26 -0
  42. package/src/live-sse.ts +91 -127
  43. package/src/live-subscription.svelte.ts +130 -0
  44. package/src/live-transport.ts +127 -0
  45. package/src/live-websocket.ts +85 -118
  46. package/src/live.svelte.ts +66 -68
  47. package/src/notification.svelte.ts +17 -15
  48. package/src/permission-catalog.ts +121 -0
  49. package/src/permission-hints-contract.ts +48 -0
  50. package/src/permissions.svelte.ts +112 -59
  51. package/src/plain-data.ts +61 -0
  52. package/src/provider-bundle.ts +85 -79
  53. package/src/provider-response.ts +35 -0
  54. package/src/query-hooks.svelte.ts +241 -288
  55. package/src/query-invalidation.ts +172 -0
  56. package/src/query-keys.ts +221 -110
  57. package/src/query-session.svelte.ts +220 -0
  58. package/src/query-snapshot.ts +144 -0
  59. package/src/reactive-projection.ts +53 -0
  60. package/src/record-decoder.ts +63 -0
  61. package/src/resource-binding.svelte.ts +19 -0
  62. package/src/resource-contract.ts +595 -0
  63. package/src/resource-mutation-types.ts +30 -0
  64. package/src/resource-schemas.ts +371 -0
  65. package/src/resource-transport-config.ts +156 -0
  66. package/src/route-parsing.ts +11 -11
  67. package/src/router-provider.ts +64 -59
  68. package/src/router.ts +8 -3
  69. package/src/routing-hooks.svelte.ts +90 -62
  70. package/src/schema-validation.ts +92 -0
  71. package/src/schema.ts +13 -0
  72. package/src/select-options.ts +95 -0
  73. package/src/select-query.svelte.ts +191 -0
  74. package/src/session-query.svelte.ts +103 -0
  75. package/src/strict-command-hooks.svelte.ts +333 -0
  76. package/src/strict-hooks.svelte.ts +198 -0
  77. package/src/table-hooks.svelte.ts +180 -174
  78. package/src/table-state.svelte.ts +117 -117
  79. package/src/task-contract.ts +87 -0
  80. package/src/task-hooks.svelte.ts +190 -180
  81. package/src/task-provider.ts +115 -0
  82. package/src/task-subscription.ts +132 -0
  83. package/src/theme.svelte.ts +155 -154
  84. package/src/toast.svelte.ts +38 -37
  85. package/src/types.ts +68 -110
  86. package/src/unsafe.ts +7 -0
  87. package/src/update-contract.ts +65 -0
  88. package/src/update-hooks.svelte.ts +347 -0
  89. package/src/update-many-contract.ts +79 -0
  90. package/src/update-many-hooks.svelte.ts +373 -0
  91. package/src/url-sync.ts +11 -10
  92. package/src/useCan.ts +96 -31
  93. package/src/useParsed.svelte.ts +18 -14
  94. package/src/useStepsForm.svelte.ts +123 -63
  95. package/src/utility-hooks.svelte.ts +101 -158
  96. package/src/form-hooks.svelte.ts +0 -889
  97. package/src/mutation-hooks.svelte.ts +0 -561
  98. package/src/schema-form.ts +0 -31
package/src/audit.ts CHANGED
@@ -1,149 +1,114 @@
1
- // Audit logging — record admin operations
2
- // Full AuditLogProvider interface
1
+ import { definedOptions } from './defined-options';
2
+ import { Value } from '@sinclair/typebox/value';
3
+ import {
4
+ AuditError, createAuditEntry, decodeAuditCreate, decodeAuditEntries, decodeAuditEntry, decodeAuditQuery,
5
+ type AuditCreateParams, type AuditDraft, type AuditEntry, type AuditQueryParams,
6
+ } from './audit-contract';
3
7
 
8
+ export type { AuditEntry, AuditDraft, AuditCreateParams, AuditQueryParams } from './audit-contract';
9
+ export { AuditError } from './audit-contract';
4
10
  export type AuditAction = 'create' | 'update' | 'delete' | 'login' | 'logout' | 'rollback' | 'undo' | (string & {});
11
+ export type AuditHandler = (entry: AuditEntry) => void | Promise<void>;
5
12
 
6
- export interface AuditEntry {
7
- id?: string | number;
8
- timestamp: string;
9
- action: AuditAction;
10
- mutationId?: string;
11
- resource?: string;
12
- recordId?: string | number;
13
- userId?: string;
14
- details?: Record<string, unknown>;
15
- data?: Record<string, unknown>;
16
- previousData?: Record<string, unknown>;
17
- meta?: Record<string, unknown>;
18
- outcome?: 'success' | 'failure';
19
- tenantId?: string | number;
20
- requestId?: string;
21
- traceId?: string;
22
- ipAddress?: string;
23
- userAgent?: string;
24
- error?: { message: string; code?: string };
13
+ export interface AuditLogTransport {
14
+ create(params: AuditCreateParams): Promise<unknown>;
15
+ get(params: AuditQueryParams): Promise<unknown>;
25
16
  }
26
17
 
27
- export type AuditHandler = (entry: AuditEntry) => void | Promise<void>;
28
-
29
- /**
30
- * Full AuditLogProvider interface.
31
- * - create: log a new entry
32
- * - get: retrieve entries for a resource
33
- * - update: update an existing entry (e.g. mark as reviewed)
34
- */
18
+ /** Audit records are append-only. Response types come from runtime validation. */
35
19
  export interface AuditLogProvider {
36
- create: (params: {
37
- resource: string;
38
- action: string;
39
- timestamp?: string;
40
- recordId?: string | number;
41
- userId?: string;
42
- outcome?: AuditEntry['outcome'];
43
- data?: Record<string, unknown>;
44
- previousData?: Record<string, unknown>;
45
- meta?: Record<string, unknown>;
46
- }) => Promise<AuditEntry>;
47
- get: (params: { resource: string; action?: string; meta?: Record<string, unknown>; author?: Record<string, unknown> }) => Promise<AuditEntry[]>;
48
- /** @deprecated 合规审计应保持追加写入;仅为旧实现兼容保留。 */
49
- update?: (params: { id: string | number; name: string; meta?: Record<string, unknown> }) => Promise<AuditEntry>;
20
+ create(params: AuditCreateParams): Promise<AuditEntry>;
21
+ get(params: AuditQueryParams): Promise<AuditEntry[]>;
50
22
  }
51
23
 
52
- let handler: AuditHandler = (entry) => {
24
+ export function withValidatedAuditProvider(transport: AuditLogTransport): AuditLogProvider {
25
+ return {
26
+ async create(params) {
27
+ return callProvider(transport, decodeAuditCreate(params));
28
+ },
29
+ async get(params) {
30
+ const input = decodeAuditQuery(params);
31
+ let result: unknown;
32
+ try { result = await transport.get(input); }
33
+ catch { throw new AuditError('AUDIT_PROVIDER_FAILED'); }
34
+ return decodeAuditEntries(result);
35
+ },
36
+ };
37
+ }
38
+
39
+ const defaultHandler: AuditHandler = entry => {
53
40
  console.info('[audit]', entry.action, entry.resource, entry.recordId);
54
41
  };
55
-
42
+ let handler = defaultHandler;
56
43
  let auditLogProvider: AuditLogProvider | null = null;
57
44
 
58
- export function setAuditHandler(fn: AuditHandler): void {
59
- handler = fn;
60
- }
45
+ export function setAuditHandler(fn: AuditHandler): void { handler = fn; }
61
46
 
62
- export function setAuditLogProvider(provider: AuditLogProvider): void {
63
- auditLogProvider = provider;
47
+ export function setAuditLogProvider(provider: AuditLogTransport): void {
48
+ auditLogProvider = withValidatedAuditProvider(provider);
64
49
  }
65
50
 
66
- export function getAuditLogProvider(): AuditLogProvider | null {
67
- return auditLogProvider;
68
- }
51
+ export function getAuditLogProvider(): AuditLogProvider | null { return auditLogProvider; }
69
52
 
70
53
  export function resetAuditLogProvider(): void {
71
54
  auditLogProvider = null;
72
- handler = (entry) => { console.info('[audit]', entry.action, entry.resource, entry.recordId); };
55
+ handler = defaultHandler;
56
+ }
57
+
58
+ function providerInput(entry: AuditEntry): AuditCreateParams {
59
+ const { id: _id, ...input } = entry;
60
+ return decodeAuditCreate(input);
73
61
  }
74
62
 
75
- /** Record an entry through an explicitly scoped provider and the compatibility handler. */
63
+ async function callHandler(target: AuditHandler, entry: AuditEntry): Promise<void> {
64
+ try { await target(decodeAuditEntry(entry)); }
65
+ catch { throw new AuditError('AUDIT_HANDLER_FAILED', true); }
66
+ }
67
+
68
+ async function callProvider(provider: AuditLogTransport, input: AuditCreateParams): Promise<AuditEntry> {
69
+ let result: unknown;
70
+ try { result = await provider.create(decodeAuditCreate(input)); }
71
+ catch { throw new AuditError('AUDIT_PROVIDER_FAILED', true); }
72
+ const receipt = decodeAuditEntry(result, 'response', true);
73
+ for (const [key, expected] of Object.entries(input)) {
74
+ const descriptor = Object.getOwnPropertyDescriptor(receipt, key);
75
+ const actual: unknown = descriptor && 'value' in descriptor ? descriptor.value : undefined;
76
+ if (!descriptor || !Value.Equal(expected, actual)) throw new AuditError('INVALID_AUDIT_RESPONSE', true);
77
+ }
78
+ return receipt;
79
+ }
80
+
81
+ /** Best-effort delivery still validates input and receipts and omits raw failure details. */
76
82
  export function auditWithProvider(
77
- entry: Omit<AuditEntry, 'timestamp'>,
78
- provider: AuditLogProvider | null | undefined,
83
+ entry: AuditDraft, provider: AuditLogTransport | null | undefined, current: () => boolean = () => true,
79
84
  ): void {
80
- const fullEntry: AuditEntry = { ...entry, timestamp: new Date().toISOString() };
85
+ if (!current()) return;
86
+ let snapshot: AuditEntry;
87
+ let input: AuditCreateParams;
81
88
  try {
82
- const result = handler(fullEntry);
83
- if (result && typeof result === 'object' && 'then' in result) {
84
- (result as Promise<void>).catch(e => console.error('[audit] handler error:', e));
85
- }
86
- } catch (e) {
87
- console.error('[audit] handler error:', e);
88
- }
89
- if (provider) {
90
- provider.create({
91
- resource: entry.resource ?? '',
92
- action: entry.action,
93
- timestamp: fullEntry.timestamp,
94
- recordId: entry.recordId,
95
- userId: entry.userId,
96
- outcome: entry.outcome,
97
- data: entry.data ?? entry.details,
98
- previousData: entry.previousData,
99
- meta: entry.meta,
100
- }).catch(e => console.error('[audit] provider create error:', e));
89
+ snapshot = createAuditEntry(entry);
90
+ input = providerInput(snapshot);
91
+ } catch {
92
+ console.error(new AuditError('INVALID_AUDIT_INPUT'));
93
+ return;
101
94
  }
95
+ void callHandler(handler, snapshot).catch(error => console.error(error));
96
+ if (provider && current()) void callProvider(provider, input).catch(error => console.error(error));
102
97
  }
103
98
 
104
- export function audit(entry: Omit<AuditEntry, 'timestamp'>): void {
105
- auditWithProvider(entry, auditLogProvider);
106
- }
99
+ export function audit(entry: AuditDraft): void { auditWithProvider(entry, auditLogProvider); }
107
100
 
108
- /**
109
- * 严格写入审计记录。与兼容的 best-effort `audit` 不同,此函数会等待所有
110
- * 写入并传播失败,适合权限、凭据、策略等必须和业务结果一起验证的流程。
111
- */
101
+ /** Wait for every sink and reject malformed receipts; earlier writes may already have committed. */
112
102
  export async function writeAuditEntry(
113
- entry: Omit<AuditEntry, 'timestamp'>,
114
- provider: AuditLogProvider | null | undefined = auditLogProvider,
103
+ entry: AuditDraft, provider: AuditLogTransport | null | undefined = auditLogProvider,
115
104
  ): Promise<AuditEntry> {
116
- const fullEntry: AuditEntry = { ...entry, timestamp: new Date().toISOString() };
117
- if (!provider) {
118
- throw new Error('Strict audit logging requires an AuditLogProvider.');
119
- }
120
- await handler(fullEntry);
121
- return provider.create({
122
- resource: entry.resource ?? '',
123
- action: entry.action,
124
- timestamp: fullEntry.timestamp,
125
- recordId: entry.recordId,
126
- userId: entry.userId,
127
- outcome: entry.outcome,
128
- data: entry.data ?? entry.details,
129
- previousData: entry.previousData,
130
- meta: {
131
- ...(entry.meta ?? {}),
132
- ...(entry.outcome === undefined ? {} : { outcome: entry.outcome }),
133
- ...(entry.tenantId === undefined ? {} : { tenantId: entry.tenantId }),
134
- ...(entry.requestId === undefined ? {} : { requestId: entry.requestId }),
135
- ...(entry.traceId === undefined ? {} : { traceId: entry.traceId }),
136
- ...(entry.ipAddress === undefined ? {} : { ipAddress: entry.ipAddress }),
137
- ...(entry.userAgent === undefined ? {} : { userAgent: entry.userAgent }),
138
- ...(entry.error === undefined ? {} : { error: entry.error }),
139
- },
140
- });
105
+ if (!provider) throw new AuditError('AUDIT_PROVIDER_REQUIRED');
106
+ const snapshot = createAuditEntry(entry);
107
+ const input = providerInput(snapshot);
108
+ await callHandler(handler, snapshot);
109
+ return callProvider(provider, input);
141
110
  }
142
111
 
143
- /**
144
- * 记录乐观变更回滚或撤回事件至审计流。
145
- * 将前序状态快照与回滚原因作为不可变事实写入审计。
146
- */
147
112
  export async function recordMutationRollback(
148
113
  params: {
149
114
  resource: string;
@@ -157,23 +122,18 @@ export async function recordMutationRollback(
157
122
  requestId?: string;
158
123
  traceId?: string;
159
124
  },
160
- provider: AuditLogProvider | null | undefined = auditLogProvider,
125
+ provider: AuditLogTransport | null | undefined = auditLogProvider,
161
126
  ): Promise<AuditEntry> {
162
127
  return writeAuditEntry({
163
128
  action: 'rollback',
164
129
  resource: params.resource,
165
- recordId: params.recordId,
166
- userId: params.userId,
167
- tenantId: params.tenantId,
168
- requestId: params.requestId,
169
- traceId: params.traceId,
130
+ ...definedOptions({
131
+ recordId: params.recordId, mutationId: params.mutationId,
132
+ userId: params.userId, tenantId: params.tenantId,
133
+ requestId: params.requestId, traceId: params.traceId,
134
+ previousData: params.previousData, data: params.currentData,
135
+ }),
170
136
  outcome: 'success',
171
- previousData: params.previousData,
172
- data: params.currentData,
173
- meta: {
174
- actionType: 'mutation_rollback',
175
- mutationId: params.mutationId,
176
- reason: params.reason,
177
- },
137
+ meta: { actionType: 'mutation_rollback', ...definedOptions({ reason: params.reason }) },
178
138
  }, provider);
179
139
  }