@svadmin/core 0.39.0 → 0.40.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.39.0",
3
+ "version": "0.40.1",
4
4
  "author": "zuohuadong",
5
5
  "repository": {
6
6
  "type": "git",
package/src/enterprise.ts CHANGED
@@ -18,6 +18,68 @@ export interface EnterpriseRequestContext {
18
18
  meta?: Readonly<Record<string, unknown>>;
19
19
  }
20
20
 
21
+ /** Provider 调用使用的严格请求上下文;三项边界标识不可省略。 */
22
+ export interface StrictEnterpriseRequestContext {
23
+ tenantId: string | number;
24
+ requestId: string;
25
+ traceId: string;
26
+ meta?: Readonly<Record<string, unknown>>;
27
+ }
28
+
29
+ export interface EnterpriseRequestContextInput {
30
+ tenantId: string | number;
31
+ requestId?: string;
32
+ traceId?: string;
33
+ meta?: Readonly<Record<string, unknown>>;
34
+ }
35
+
36
+ function nonEmpty(value: string, field: string): string {
37
+ const normalized = value.trim();
38
+ if (!normalized) throw new Error(`${field} must not be empty.`);
39
+ return normalized;
40
+ }
41
+
42
+ function validatedTenantId(tenantId: string | number): string | number {
43
+ if (typeof tenantId === 'string') return nonEmpty(tenantId, 'tenantId');
44
+ if (!Number.isFinite(tenantId)) throw new Error('tenantId must be a finite number.');
45
+ return tenantId;
46
+ }
47
+
48
+ function createRequestId(): string {
49
+ const randomUUID = globalThis.crypto?.randomUUID;
50
+ if (!randomUUID) throw new Error('crypto.randomUUID is required to create an enterprise request context.');
51
+ return randomUUID.call(globalThis.crypto);
52
+ }
53
+
54
+ /** Create a validated provider context while preserving one trace across the request tree. */
55
+ export function createEnterpriseRequestContext(
56
+ input: EnterpriseRequestContextInput,
57
+ ): StrictEnterpriseRequestContext {
58
+ const requestId = input.requestId === undefined
59
+ ? createRequestId()
60
+ : nonEmpty(input.requestId, 'requestId');
61
+ return {
62
+ tenantId: validatedTenantId(input.tenantId),
63
+ requestId,
64
+ traceId: input.traceId === undefined ? requestId : nonEmpty(input.traceId, 'traceId'),
65
+ ...(input.meta ? { meta: input.meta } : {}),
66
+ };
67
+ }
68
+
69
+ /** Narrow a legacy optional context at the provider boundary and fail closed on missing identity. */
70
+ export function assertEnterpriseRequestContext(
71
+ context: EnterpriseRequestContext,
72
+ ): asserts context is StrictEnterpriseRequestContext {
73
+ if (context.tenantId === undefined || context.tenantId === null) {
74
+ throw new Error('tenantId must not be empty.');
75
+ }
76
+ validatedTenantId(context.tenantId);
77
+ nonEmpty(context.requestId ?? '', 'requestId');
78
+ nonEmpty(context.traceId ?? '', 'traceId');
79
+ }
80
+
81
+ export type EnterpriseProviderRequestContext = StrictEnterpriseRequestContext;
82
+
21
83
  export interface Organization {
22
84
  id: string;
23
85
  name: string;
package/src/index.ts CHANGED
@@ -93,6 +93,9 @@ export type {
93
93
  EnterpriseActionError,
94
94
  EnterpriseActionResult,
95
95
  EnterpriseRequestContext,
96
+ EnterpriseRequestContextInput,
97
+ EnterpriseProviderRequestContext,
98
+ StrictEnterpriseRequestContext,
96
99
  EnterpriseSecurityEvent,
97
100
  EnterpriseSecurityPolicy,
98
101
  IdentityGovernanceProvider,
@@ -106,6 +109,7 @@ export type {
106
109
  SessionProvider,
107
110
  WebhookSummary,
108
111
  } from './enterprise';
112
+ export { assertEnterpriseRequestContext, createEnterpriseRequestContext } from './enterprise';
109
113
  export { setChatProvider, getChatProvider, setChatContext, getChatContext, setAgentProvider, getAgentProvider, registerApproval, resolveApproval, hasPendingApprovals, resetChatProvider } from './chatProvider.svelte';
110
114
  export type { ChatProvider, ChatMessage, ChatContext, ChatAction, AgentProvider, AgentEvent, AgentOptions, AdminTool, AdminToolParameter, ToolResult } from './chatProvider.svelte';
111
115
  export { getTheme, setTheme, toggleTheme, getResolvedTheme, getColorTheme, setColorTheme, getColorThemes, configureTheme, getThemeConfig, clearCssOverrides, builtinPresets, registerColorPreset, getColorPresets, registerThemeOwner, updateThemeOwner, unregisterThemeOwner, resetTheme } from './theme.svelte';
@@ -0,0 +1,14 @@
1
+ export type ParsedResourceAction = {
2
+ action: 'list' | 'create' | 'edit' | 'show' | 'clone';
3
+ id?: string;
4
+ };
5
+
6
+ export function parseResourceActionSegments(restSegments: string[]): ParsedResourceAction {
7
+ if (restSegments.length === 0) return { action: 'list' };
8
+ if (restSegments[0] === 'create') return { action: 'create' };
9
+ if (restSegments[0] === 'edit' && restSegments[1]) return { action: 'edit', id: restSegments[1] };
10
+ if (restSegments[0] === 'show' && restSegments[1]) return { action: 'show', id: restSegments[1] };
11
+ if (restSegments[0] === 'clone' && restSegments[1]) return { action: 'clone', id: restSegments[1] };
12
+ if (restSegments[1] === 'edit') return { action: 'edit', id: restSegments[0] };
13
+ return { action: 'show', id: restSegments[0] };
14
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { registerRouterSync } from './router';
5
5
  import { captureAdminContext } from './context.svelte';
6
+ import { parseResourceActionSegments } from './route-parsing';
6
7
 
7
8
  interface ParsedRoute {
8
9
  resource?: string;
@@ -107,47 +108,13 @@ export function useParsed(): ParsedRoute {
107
108
  }
108
109
  }
109
110
 
110
- const restSegments = segments.slice(resourceIndex + 1);
111
- if (restSegments.length === 0) {
112
- result.action = 'list';
113
- } else if (restSegments[0] === 'create') {
114
- result.action = 'create';
115
- } else if (restSegments[0] === 'edit' && restSegments[1]) {
116
- result.action = 'edit';
117
- result.id = restSegments[1];
118
- } else if (restSegments[0] === 'show' && restSegments[1]) {
119
- result.action = 'show';
120
- result.id = restSegments[1];
121
- } else if (restSegments[0] === 'clone' && restSegments[1]) {
122
- result.action = 'clone';
123
- result.id = restSegments[1];
124
- } else if (restSegments[0]) {
125
- // Legacy: /:resource/:id
126
- result.action = 'show';
127
- result.id = restSegments[0];
128
- }
111
+ Object.assign(result, parseResourceActionSegments(segments.slice(resourceIndex + 1)));
129
112
  } else {
130
113
  // Fallback if resource definitions are not loaded yet
131
114
  result.resource = segments[0];
132
115
  result.resourcePath = segments[0];
133
116
 
134
- if (segments.length === 1) {
135
- result.action = 'list';
136
- } else if (segments[1] === 'create') {
137
- result.action = 'create';
138
- } else if (segments[1] === 'edit' && segments[2]) {
139
- result.action = 'edit';
140
- result.id = segments[2];
141
- } else if (segments[1] === 'show' && segments[2]) {
142
- result.action = 'show';
143
- result.id = segments[2];
144
- } else if (segments[1] === 'clone' && segments[2]) {
145
- result.action = 'clone';
146
- result.id = segments[2];
147
- } else if (segments[1]) {
148
- result.action = 'show';
149
- result.id = segments[1];
150
- }
117
+ Object.assign(result, parseResourceActionSegments(segments.slice(1)));
151
118
  }
152
119
 
153
120
  return result;