@svadmin/core 0.49.1 → 0.52.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/README.md +32 -0
- package/package.json +21 -4
- package/src/access-control-contract.ts +250 -0
- package/src/adapters/casbin.ts +20 -14
- package/src/adapters/casl.ts +22 -19
- package/src/admin-tool.ts +7 -7
- package/src/audit-contract.ts +105 -0
- package/src/audit.ts +88 -128
- package/src/auth-hooks.svelte.ts +593 -261
- package/src/auth-mutation-contract.ts +143 -0
- package/src/auth-query-contract.ts +97 -0
- package/src/captured-mutation.svelte.ts +45 -0
- package/src/chatProvider.svelte.ts +1 -1
- package/src/command-contract.ts +195 -0
- package/src/context.svelte.ts +217 -205
- package/src/contract-context.svelte.ts +37 -0
- package/src/contract-form.svelte.ts +566 -0
- package/src/create-contract.ts +56 -0
- package/src/create-hooks.svelte.ts +347 -0
- package/src/create-many-contract.ts +90 -0
- package/src/create-many-hooks.svelte.ts +396 -0
- package/src/data-transfer.svelte.ts +1 -215
- package/src/defined-options.ts +50 -0
- package/src/delete-contract.ts +50 -0
- package/src/delete-hooks.svelte.ts +403 -0
- package/src/delete-many-contract.ts +60 -0
- package/src/delete-many-hooks.svelte.ts +402 -0
- package/src/enterprise.ts +4 -4
- package/src/export-format.ts +16 -14
- package/src/export-hooks.svelte.ts +191 -0
- package/src/feature-gate-contract.ts +53 -0
- package/src/hook-utils.svelte.ts +53 -105
- package/src/hooks.svelte.ts +19 -774
- package/src/http.ts +88 -87
- package/src/i18n.svelte.ts +56 -2
- package/src/import-contract.ts +106 -0
- package/src/import-hooks.svelte.ts +310 -0
- package/src/index.ts +77 -28
- package/src/inferencer-graphql.ts +7 -6
- package/src/infinite-query.svelte.ts +228 -0
- package/src/invalidation-contract.ts +26 -0
- package/src/live-sse.ts +91 -127
- package/src/live-subscription.svelte.ts +130 -0
- package/src/live-transport.ts +127 -0
- package/src/live-websocket.ts +85 -118
- package/src/live.svelte.ts +66 -68
- package/src/notification.svelte.ts +17 -15
- package/src/permission-catalog.ts +121 -0
- package/src/permission-hints-contract.ts +48 -0
- package/src/permissions.svelte.ts +112 -59
- package/src/plain-data.ts +61 -0
- package/src/provider-bundle.ts +85 -79
- package/src/provider-response.ts +35 -0
- package/src/query-hooks.svelte.ts +241 -286
- package/src/query-invalidation.ts +172 -0
- package/src/query-keys.ts +221 -110
- package/src/query-session.svelte.ts +216 -0
- package/src/query-snapshot.ts +144 -0
- package/src/reactive-projection.ts +53 -0
- package/src/record-decoder.ts +63 -0
- package/src/resource-binding.svelte.ts +19 -0
- package/src/resource-contract.ts +595 -0
- package/src/resource-mutation-types.ts +30 -0
- package/src/resource-schemas.ts +371 -0
- package/src/resource-transport-config.ts +156 -0
- package/src/route-parsing.ts +11 -11
- package/src/router-provider.ts +64 -59
- package/src/router.ts +8 -3
- package/src/routing-hooks.svelte.ts +90 -62
- package/src/schema-validation.ts +92 -0
- package/src/schema.ts +13 -0
- package/src/select-options.ts +95 -0
- package/src/select-query.svelte.ts +191 -0
- package/src/session-query.svelte.ts +88 -0
- package/src/strict-command-hooks.svelte.ts +333 -0
- package/src/strict-hooks.svelte.ts +198 -0
- package/src/table-hooks.svelte.ts +180 -174
- package/src/table-state.svelte.ts +117 -117
- package/src/task-contract.ts +87 -0
- package/src/task-hooks.svelte.ts +193 -149
- package/src/task-provider.ts +115 -0
- package/src/task-subscription.ts +132 -0
- package/src/theme.svelte.ts +155 -154
- package/src/toast.svelte.ts +38 -37
- package/src/types.ts +55 -103
- package/src/unsafe.ts +7 -0
- package/src/update-contract.ts +65 -0
- package/src/update-hooks.svelte.ts +347 -0
- package/src/update-many-contract.ts +79 -0
- package/src/update-many-hooks.svelte.ts +373 -0
- package/src/url-sync.ts +11 -10
- package/src/useCan.ts +96 -31
- package/src/useParsed.svelte.ts +18 -14
- package/src/useStepsForm.svelte.ts +123 -63
- package/src/utility-hooks.svelte.ts +101 -158
- package/src/form-hooks.svelte.ts +0 -889
- package/src/mutation-hooks.svelte.ts +0 -561
package/src/audit.ts
CHANGED
|
@@ -1,149 +1,114 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
37
|
-
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
78
|
-
provider: AuditLogProvider | null | undefined,
|
|
83
|
+
entry: AuditDraft, provider: AuditLogTransport | null | undefined, current: () => boolean = () => true,
|
|
79
84
|
): void {
|
|
80
|
-
|
|
85
|
+
if (!current()) return;
|
|
86
|
+
let snapshot: AuditEntry;
|
|
87
|
+
let input: AuditCreateParams;
|
|
81
88
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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:
|
|
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:
|
|
114
|
-
provider: AuditLogProvider | null | undefined = auditLogProvider,
|
|
103
|
+
entry: AuditDraft, provider: AuditLogTransport | null | undefined = auditLogProvider,
|
|
115
104
|
): Promise<AuditEntry> {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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:
|
|
125
|
+
provider: AuditLogTransport | null | undefined = auditLogProvider,
|
|
161
126
|
): Promise<AuditEntry> {
|
|
162
127
|
return writeAuditEntry({
|
|
163
128
|
action: 'rollback',
|
|
164
129
|
resource: params.resource,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
}
|