@svadmin/surface 0.11.8 → 0.13.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 +18 -3
- package/compatibility.json +1 -1
- package/dist/agent-contract.d.ts +6 -6
- package/dist/agent-contract.js +10 -4
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +6 -6
- package/dist/builtin-definitions.js +1 -1
- package/dist/builtin-schemas.d.ts +51 -51
- package/dist/builtin-schemas.js +1 -1
- package/dist/business-definitions.d.ts +20 -20
- package/dist/business-definitions.js +2 -2
- package/dist/catalog.js +1 -1
- package/dist/components/ActivityFeedWidget.svelte +1 -1
- package/dist/components/BarChartWidget.svelte +1 -1
- package/dist/components/LineChartWidget.svelte +1 -1
- package/dist/components/MetricWidget.svelte +1 -1
- package/dist/components/ResourceDetailWidget.svelte +1 -1
- package/dist/components/ResourceFormBody.svelte +2 -2
- package/dist/components/ResourceTableWidget.svelte +1 -1
- package/dist/edits.js +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/json-render.d.ts +68 -0
- package/dist/json-render.js +117 -0
- package/dist/schema.d.ts +92 -91
- package/dist/schema.js +1 -1
- package/dist/svar-schema.d.ts +14 -14
- package/dist/svar-schema.js +2 -2
- package/dist/types.d.ts +1 -1
- package/dist/validation.d.ts +13 -0
- package/dist/validation.js +29 -8
- package/dist/workflows/action-contracts.js +12 -11
- package/dist/workflows/catalog.d.ts +4 -3
- package/dist/workflows/catalog.js +7 -6
- package/dist/workflows/types.d.ts +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ the `minimum-supported` strict install and consumer checks in `pack:check` to
|
|
|
13
13
|
pass against registry artifacts; workspace tarballs do not replace that check.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
bun add @svadmin/surface @svadmin/core @svadmin/ui @tanstack/svelte-query svelte
|
|
16
|
+
bun add @svadmin/surface @svadmin/core @svadmin/ui @tanstack/svelte-query svelte typebox
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Import protocol types and validation from the DOM-free root entry. Import rendering code from the Svelte subpath. Hosts do not need CSS compiler plugins to consume the package's static component styles.
|
|
@@ -157,8 +157,8 @@ Preview controls use local finite class helpers and maintained static CSS. `@sva
|
|
|
157
157
|
The catalog version is `svadmin/v1`. `catalogVersion` must match exactly. Custom registrations use strict TypeBox props schemas and trusted Svelte components. Item widgets reading record fields must expose them through `getReferencedFields` so runtime validation checks `SurfacePolicy.readFields`.
|
|
158
158
|
|
|
159
159
|
```ts
|
|
160
|
-
import { Type } from '
|
|
161
|
-
import { Value } from '
|
|
160
|
+
import { Type } from 'typebox';
|
|
161
|
+
import { Value } from 'typebox/value';
|
|
162
162
|
|
|
163
163
|
const statusProps = Type.Object({ statusField: Type.String() }, { additionalProperties: false });
|
|
164
164
|
const catalog = defineSurfaceCatalog({
|
|
@@ -189,3 +189,18 @@ Limits are eight data sources, 24 widgets, 100 rows per page, eight filters, thr
|
|
|
189
189
|
Actual OpenUI Lang parsing, nested interactive forms, registered business actions, server persistence, SSR/Lite rendering, arbitrary URLs, client aggregation, Canvas and iframe execution are not implemented. Aggregated metrics should come from a policy-authorized backend summary resource and bind through `resource-one`. The current UI contract remains read-only even though its definition can be edited.
|
|
190
190
|
|
|
191
191
|
See `docs/architecture/openui-surface-phase2.md` for the original feature scope and `docs/architecture/surface-integration-provenance.md` for historical integration evidence. 中文指南见文档站的“声明式 Surface”。
|
|
192
|
+
|
|
193
|
+
## json-render 适配
|
|
194
|
+
|
|
195
|
+
Surface 可以接收 Vercel `json-render` 使用的标准扁平 `Spec`(`root` + `elements`),同时继续由 Surface 的 TypeBox Schema、组件目录和资源策略负责最终校验。协议入口提供 `jsonRenderSpecToSurfaceSpec` 和 `surfaceSpecToJsonRenderSpec`:前者只接受以 `surface` 为根、一级业务组件为子节点的受控子集,后者用于把已校验的 SurfaceSpec 输出为标准扁平 Spec。
|
|
196
|
+
|
|
197
|
+
适配层不执行 json-render 的任意 action、脚本、URL 或嵌套组件树;业务数据查询仍经过 Surface DataProvider 和既有授权流程。项目业务契约继续使用 TypeBox,未引入 Zod 运行时依赖。本入口只提供格式互操作,不包含官方 json-render Catalog、流式编译器或渲染器集成。`binding` 和 `placement` 是适配器保留属性;导出包含这些业务属性的组件时会抛错,避免静默覆盖。
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { jsonRenderSpecToSurfaceSpec } from '@svadmin/surface';
|
|
201
|
+
|
|
202
|
+
const result = jsonRenderSpecToSurfaceSpec(jsonRenderSpec, catalog, policy);
|
|
203
|
+
if (result.ok) {
|
|
204
|
+
// 交给现有 SurfaceRenderer;此处仍是候选页面,不代表业务动作授权。
|
|
205
|
+
}
|
|
206
|
+
```
|
package/compatibility.json
CHANGED
package/dist/agent-contract.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TSchema } from '
|
|
1
|
+
import { Type, type TSchema } from 'typebox';
|
|
2
2
|
import { type JsonObject, type SurfaceCatalog, type SurfacePolicy } from './types.js';
|
|
3
3
|
export declare const SURFACE_CATALOG_SCHEMA_VERSION: "surface-catalog/v1";
|
|
4
4
|
export declare const SURFACE_AGENT_SCHEMA_VERSION: "surface-agent/v1";
|
|
@@ -29,9 +29,9 @@ export declare function createSurfaceCatalogManifest(catalog: SurfaceCatalog): S
|
|
|
29
29
|
/** 生成约束缩小候选空间,不能代替绑定/字段关联校验,更不能代替后端授权。 */
|
|
30
30
|
export declare function createSurfaceGenerationSpecSchema(catalog: SurfaceCatalog, policy: SurfacePolicy): TSchema;
|
|
31
31
|
export declare function createSurfaceAgentResponseSchema(catalog: SurfaceCatalog, policy: SurfacePolicy): JsonObject;
|
|
32
|
-
export declare const surfaceCannotFulfillSchema:
|
|
33
|
-
schemaVersion:
|
|
34
|
-
action:
|
|
35
|
-
reason:
|
|
36
|
-
message:
|
|
32
|
+
export declare const surfaceCannotFulfillSchema: Type.TObject<{
|
|
33
|
+
schemaVersion: Type.TLiteral<"surface-agent/v2">;
|
|
34
|
+
action: Type.TLiteral<"cannot-fulfill">;
|
|
35
|
+
reason: Type.TUnion<[Type.TLiteral<"unsupported_request">, Type.TLiteral<"insufficient_permissions">, Type.TLiteral<"insufficient_context">]>;
|
|
36
|
+
message: Type.TString;
|
|
37
37
|
}>;
|
package/dist/agent-contract.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Value } from '
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
3
|
import { escapedJsonPointerToken, jsonValueIssue } from './json.js';
|
|
4
4
|
import { surfaceFilterSchema, surfaceIdSchema, surfaceListSourceSchema, surfaceOneSourceSchema, surfaceSpecSchema, surfaceWidgetSchema, } from './schema.js';
|
|
5
5
|
import { SURFACE_LIMITS, SURFACE_SCHEMA_VERSION, } from './types.js';
|
|
@@ -11,6 +11,8 @@ export const SURFACE_AGENT_LIMITS = {
|
|
|
11
11
|
maxRequestCharacters: 16_384,
|
|
12
12
|
maxContractCharacters: 131_072,
|
|
13
13
|
};
|
|
14
|
+
/** Scalar TypeBox 1.x markers that are dropped when serializing a schema to JSON. */
|
|
15
|
+
const SURFACE_MARKERS = new Set(['~kind', '~optional', '~readonly', '~immutable']);
|
|
14
16
|
/** 不把 Transform、函数或引用 schema 静默降级为宽松的 JSON schema。 */
|
|
15
17
|
export function surfaceSchemaToJson(schema) {
|
|
16
18
|
const ancestors = new Set();
|
|
@@ -37,10 +39,14 @@ export function surfaceSchemaToJson(schema) {
|
|
|
37
39
|
if (Array.isArray(value) && key === 'length')
|
|
38
40
|
continue;
|
|
39
41
|
if (typeof key === 'symbol') {
|
|
40
|
-
if (key === Kind || key === OptionalKind || key === ReadonlyKind)
|
|
41
|
-
continue;
|
|
42
42
|
throw new Error('Surface schema contains unsupported symbol metadata or a transform');
|
|
43
43
|
}
|
|
44
|
+
if (key.startsWith('~')) {
|
|
45
|
+
if (!SURFACE_MARKERS.has(key)) {
|
|
46
|
+
throw new Error('Surface schema contains unsupported transform or metadata');
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
44
50
|
if (['__proto__', 'constructor', 'prototype', '$ref', '$dynamicRef', '$recursiveRef'].includes(key)) {
|
|
45
51
|
throw new Error(`Surface schema key "${key}" is not supported; use an inline schema`);
|
|
46
52
|
}
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Static } from '
|
|
1
|
+
import { type Static } from 'typebox';
|
|
2
2
|
import { SURFACE_AGENT_RESPONSE_SCHEMA_VERSION, SURFACE_AGENT_SCHEMA_VERSION, surfaceCannotFulfillSchema } from './agent-contract.js';
|
|
3
3
|
import type { SurfaceCatalog, SurfacePolicy, SurfaceSpec, SurfaceValidationIssue } from './types.js';
|
|
4
4
|
export { SURFACE_AGENT_SCHEMA_VERSION } from './agent-contract.js';
|
package/dist/agent.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Type } from '
|
|
2
|
-
import {
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
|
+
import { Compile } from 'typebox/compile';
|
|
3
3
|
import { SURFACE_AGENT_LIMITS, SURFACE_AGENT_RESPONSE_SCHEMA_VERSION, SURFACE_AGENT_SCHEMA_VERSION, createSurfaceAgentResponseSchema, createSurfaceCatalogManifest, createSurfaceGenerationSpecSchema, surfaceCannotFulfillSchema, surfaceSchemaToJson, } from './agent-contract.js';
|
|
4
|
-
import { validateSurfaceSpec } from './validation.js';
|
|
4
|
+
import { validateSurfaceSpec, surfaceSchemaIssues } from './validation.js';
|
|
5
5
|
import { parseSurfaceJson, surfaceWireError } from './wire.js';
|
|
6
6
|
export { SURFACE_AGENT_SCHEMA_VERSION } from './agent-contract.js';
|
|
7
7
|
const proposalFields = {
|
|
@@ -15,12 +15,12 @@ const responseProposalSchema = Type.Object({
|
|
|
15
15
|
...proposalFields,
|
|
16
16
|
schemaVersion: Type.Literal(SURFACE_AGENT_RESPONSE_SCHEMA_VERSION),
|
|
17
17
|
}, { additionalProperties: false });
|
|
18
|
-
const compiledProposalSchema =
|
|
19
|
-
const compiledResponseSchema =
|
|
18
|
+
const compiledProposalSchema = Compile(proposalSchema);
|
|
19
|
+
const compiledResponseSchema = Compile(Type.Union([
|
|
20
20
|
proposalSchema, responseProposalSchema, surfaceCannotFulfillSchema,
|
|
21
21
|
]));
|
|
22
22
|
function schemaIssues(errors) {
|
|
23
|
-
return
|
|
23
|
+
return surfaceSchemaIssues(errors).map((issue) => ({ code: 'invalid_json', path: issue.path || '', message: issue.message }));
|
|
24
24
|
}
|
|
25
25
|
/** 保留 v1 的函数签名、返回类型以及带说明文字的 fenced JSON 兼容行为。 */
|
|
26
26
|
export function parseSurfaceAgentProposal(input, catalog, policy) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Value } from '
|
|
1
|
+
import { Value } from 'typebox/value';
|
|
2
2
|
import { styledMetricPropsSchema, styledResourceTablePropsSchema, barChartPropsSchema, lineChartPropsSchema, metricPropsSchema, resourceTablePropsSchema, } from './builtin-schemas.js';
|
|
3
3
|
export const DEFAULT_SURFACE_CATALOG_VERSION = 'svadmin/v1';
|
|
4
4
|
export const STYLED_SURFACE_CATALOG_VERSION = 'svadmin/styled-v1';
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import { type Static } from "
|
|
2
|
-
export declare const metricPropsSchema:
|
|
3
|
-
label:
|
|
4
|
-
format:
|
|
5
|
-
currency:
|
|
6
|
-
description:
|
|
7
|
-
}>,
|
|
8
|
-
label:
|
|
9
|
-
format:
|
|
10
|
-
currency:
|
|
11
|
-
description:
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
export declare const metricPropsSchema: Type.TUnion<[Type.TObject<{
|
|
3
|
+
label: Type.TString;
|
|
4
|
+
format: Type.TLiteral<"currency">;
|
|
5
|
+
currency: Type.TString;
|
|
6
|
+
description: Type.TOptional<Type.TString>;
|
|
7
|
+
}>, Type.TObject<{
|
|
8
|
+
label: Type.TString;
|
|
9
|
+
format: Type.TUnion<[Type.TLiteral<"number">, Type.TLiteral<"percent">]>;
|
|
10
|
+
currency: Type.TOptional<Type.TNever>;
|
|
11
|
+
description: Type.TOptional<Type.TString>;
|
|
12
12
|
}>]>;
|
|
13
|
-
export declare const resourceTablePropsSchema:
|
|
14
|
-
title:
|
|
15
|
-
emptyLabel:
|
|
16
|
-
columns:
|
|
17
|
-
field:
|
|
18
|
-
label:
|
|
19
|
-
format:
|
|
13
|
+
export declare const resourceTablePropsSchema: Type.TObject<{
|
|
14
|
+
title: Type.TString;
|
|
15
|
+
emptyLabel: Type.TOptional<Type.TString>;
|
|
16
|
+
columns: Type.TArray<Type.TObject<{
|
|
17
|
+
field: Type.TString;
|
|
18
|
+
label: Type.TString;
|
|
19
|
+
format: Type.TOptional<Type.TUnion<[Type.TLiteral<"text">, Type.TLiteral<"number">, Type.TLiteral<"date">, Type.TLiteral<"boolean">]>>;
|
|
20
20
|
}>>;
|
|
21
21
|
}>;
|
|
22
|
-
export declare const barChartPropsSchema:
|
|
23
|
-
title:
|
|
24
|
-
labelField:
|
|
25
|
-
valueField:
|
|
26
|
-
showValues:
|
|
22
|
+
export declare const barChartPropsSchema: Type.TObject<{
|
|
23
|
+
title: Type.TString;
|
|
24
|
+
labelField: Type.TString;
|
|
25
|
+
valueField: Type.TString;
|
|
26
|
+
showValues: Type.TOptional<Type.TBoolean>;
|
|
27
27
|
}>;
|
|
28
|
-
export declare const lineChartPropsSchema:
|
|
29
|
-
title:
|
|
30
|
-
labelField:
|
|
31
|
-
valueField:
|
|
32
|
-
showDots:
|
|
33
|
-
fill:
|
|
28
|
+
export declare const lineChartPropsSchema: Type.TObject<{
|
|
29
|
+
title: Type.TString;
|
|
30
|
+
labelField: Type.TString;
|
|
31
|
+
valueField: Type.TString;
|
|
32
|
+
showDots: Type.TOptional<Type.TBoolean>;
|
|
33
|
+
fill: Type.TOptional<Type.TBoolean>;
|
|
34
34
|
}>;
|
|
35
35
|
export type MetricProps = Static<typeof metricPropsSchema>;
|
|
36
36
|
export type ResourceTableProps = Static<typeof resourceTablePropsSchema>;
|
|
37
37
|
export type BarChartProps = Static<typeof barChartPropsSchema>;
|
|
38
38
|
export type LineChartProps = Static<typeof lineChartPropsSchema>;
|
|
39
|
-
export declare const styledMetricPropsSchema:
|
|
40
|
-
tone:
|
|
41
|
-
density:
|
|
42
|
-
label:
|
|
43
|
-
format:
|
|
44
|
-
currency:
|
|
45
|
-
description:
|
|
46
|
-
}>,
|
|
47
|
-
tone:
|
|
48
|
-
density:
|
|
49
|
-
label:
|
|
50
|
-
format:
|
|
51
|
-
currency:
|
|
52
|
-
description:
|
|
39
|
+
export declare const styledMetricPropsSchema: Type.TUnion<[Type.TObject<{
|
|
40
|
+
tone: Type.TOptional<Type.TUnion<Type.TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
|
|
41
|
+
density: Type.TOptional<Type.TUnion<Type.TLiteral<"compact" | "comfortable">[]>>;
|
|
42
|
+
label: Type.TString;
|
|
43
|
+
format: Type.TLiteral<"currency">;
|
|
44
|
+
currency: Type.TString;
|
|
45
|
+
description: Type.TOptional<Type.TString>;
|
|
46
|
+
}>, Type.TObject<{
|
|
47
|
+
tone: Type.TOptional<Type.TUnion<Type.TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
|
|
48
|
+
density: Type.TOptional<Type.TUnion<Type.TLiteral<"compact" | "comfortable">[]>>;
|
|
49
|
+
label: Type.TString;
|
|
50
|
+
format: Type.TUnion<[Type.TLiteral<"number">, Type.TLiteral<"percent">]>;
|
|
51
|
+
currency: Type.TOptional<Type.TNever>;
|
|
52
|
+
description: Type.TOptional<Type.TString>;
|
|
53
53
|
}>]>;
|
|
54
|
-
export declare const styledResourceTablePropsSchema:
|
|
55
|
-
density:
|
|
56
|
-
title:
|
|
57
|
-
emptyLabel:
|
|
58
|
-
columns:
|
|
59
|
-
field:
|
|
60
|
-
label:
|
|
61
|
-
format:
|
|
54
|
+
export declare const styledResourceTablePropsSchema: Type.TObject<{
|
|
55
|
+
density: Type.TOptional<Type.TUnion<Type.TLiteral<"compact" | "comfortable">[]>>;
|
|
56
|
+
title: Type.TString;
|
|
57
|
+
emptyLabel: Type.TOptional<Type.TString>;
|
|
58
|
+
columns: Type.TArray<Type.TObject<{
|
|
59
|
+
field: Type.TString;
|
|
60
|
+
label: Type.TString;
|
|
61
|
+
format: Type.TOptional<Type.TUnion<[Type.TLiteral<"text">, Type.TLiteral<"number">, Type.TLiteral<"date">, Type.TLiteral<"boolean">]>>;
|
|
62
62
|
}>>;
|
|
63
63
|
}>;
|
package/dist/builtin-schemas.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { type Static } from '
|
|
1
|
+
import { Type, type Static } from 'typebox';
|
|
2
2
|
import type { SurfaceCatalog } from './types.js';
|
|
3
|
-
export declare const resourceDetailPropsSchema:
|
|
4
|
-
fields:
|
|
5
|
-
field:
|
|
6
|
-
label:
|
|
7
|
-
format:
|
|
3
|
+
export declare const resourceDetailPropsSchema: Type.TObject<{
|
|
4
|
+
fields: Type.TArray<Type.TObject<{
|
|
5
|
+
field: Type.TString;
|
|
6
|
+
label: Type.TString;
|
|
7
|
+
format: Type.TOptional<Type.TUnion<Type.TLiteral<string>[]>>;
|
|
8
8
|
}>>;
|
|
9
|
-
title:
|
|
10
|
-
tone:
|
|
11
|
-
density:
|
|
9
|
+
title: Type.TString;
|
|
10
|
+
tone: Type.TOptional<Type.TUnion<Type.TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
|
|
11
|
+
density: Type.TOptional<Type.TUnion<Type.TLiteral<"compact" | "comfortable">[]>>;
|
|
12
12
|
}>;
|
|
13
|
-
export declare const activityFeedPropsSchema:
|
|
14
|
-
idField:
|
|
15
|
-
actionField:
|
|
16
|
-
timestampField:
|
|
17
|
-
actorField:
|
|
18
|
-
targetField:
|
|
19
|
-
commentField:
|
|
20
|
-
statusField:
|
|
21
|
-
title:
|
|
22
|
-
tone:
|
|
23
|
-
density:
|
|
13
|
+
export declare const activityFeedPropsSchema: Type.TObject<{
|
|
14
|
+
idField: Type.TString;
|
|
15
|
+
actionField: Type.TString;
|
|
16
|
+
timestampField: Type.TString;
|
|
17
|
+
actorField: Type.TOptional<Type.TString>;
|
|
18
|
+
targetField: Type.TOptional<Type.TString>;
|
|
19
|
+
commentField: Type.TOptional<Type.TString>;
|
|
20
|
+
statusField: Type.TOptional<Type.TString>;
|
|
21
|
+
title: Type.TString;
|
|
22
|
+
tone: Type.TOptional<Type.TUnion<Type.TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
|
|
23
|
+
density: Type.TOptional<Type.TUnion<Type.TLiteral<"compact" | "comfortable">[]>>;
|
|
24
24
|
}>;
|
|
25
25
|
export type ResourceDetailProps = Static<typeof resourceDetailPropsSchema>;
|
|
26
26
|
export type ActivityFeedProps = Static<typeof activityFeedPropsSchema>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Type } from '
|
|
2
|
-
import { Value } from '
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
3
|
import { styledSurfaceDefinitions } from './builtin-definitions.js';
|
|
4
4
|
import { surfaceDesignContract } from './design-contract.js';
|
|
5
5
|
const field = Type.String({ minLength: 1, maxLength: 64, pattern: '^[A-Za-z][A-Za-z0-9_-]*$' });
|
package/dist/catalog.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Value } from '
|
|
1
|
+
import { Value } from 'typebox/value';
|
|
2
2
|
import BarChartWidget from './components/BarChartWidget.svelte';
|
|
3
3
|
import LineChartWidget from './components/LineChartWidget.svelte';
|
|
4
4
|
import MetricWidget from './components/MetricWidget.svelte';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Value } from '
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
3
|
import ActivityFeed from '@svadmin/ui/components/ActivityFeed.svelte';
|
|
4
4
|
import { activityFeedPropsSchema } from '../business-definitions.js';
|
|
5
5
|
import { asSurfaceActivities } from '../business-data.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Value } from '
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
3
|
import BarChart from '@svadmin/ui/components/charts/BarChart.svelte';
|
|
4
4
|
import CardContent from '@svadmin/ui/components/ui/card/card-content.svelte';
|
|
5
5
|
import CardHeader from '@svadmin/ui/components/ui/card/card-header.svelte';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Value } from '
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
3
|
import LineChart from '@svadmin/ui/components/charts/LineChart.svelte';
|
|
4
4
|
import CardContent from '@svadmin/ui/components/ui/card/card-content.svelte';
|
|
5
5
|
import CardHeader from '@svadmin/ui/components/ui/card/card-header.svelte';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Value } from '
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
3
|
import Card from '@svadmin/ui/components/ui/card/card.svelte';
|
|
4
4
|
import CardHeader from '@svadmin/ui/components/ui/card/card-header.svelte';
|
|
5
5
|
import CardTitle from '@svadmin/ui/components/ui/card/card-title.svelte';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy, untrack } from 'svelte';
|
|
3
|
-
import { Value } from '
|
|
3
|
+
import { Value } from 'typebox/value';
|
|
4
4
|
import JsonSchemaForm from '@svadmin/ui/components/JsonSchemaForm.svelte';
|
|
5
5
|
import { createSurfaceFormController, type SurfaceWorkflowClientState } from '../workflows/client.js';
|
|
6
6
|
import type { SurfaceWorkflowHost } from '../workflows/context.js';
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
// wire representation. The server repeats JSON and schema validation.
|
|
50
50
|
const args: unknown = JSON.parse(JSON.stringify(input));
|
|
51
51
|
if (!Value.Check(action.inputSchema, args)) {
|
|
52
|
-
errors =
|
|
52
|
+
errors = Value.Errors(action.inputSchema, args).slice(0, 12).map((error) => `${error.instancePath}: ${error.message}`);
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
errors = [];
|
package/dist/edits.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Type } from '
|
|
2
|
-
import {
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
|
+
import { Compile } from 'typebox/compile';
|
|
3
3
|
import { createSurfaceCatalogManifest, SURFACE_AGENT_LIMITS, surfaceSchemaToJson } from './agent-contract.js';
|
|
4
4
|
import { surfaceIdSchema, surfaceLayoutSchema, surfaceListSourceSchema, surfaceOneSourceSchema, surfaceSpecSchema, surfaceWidgetSchema, } from './schema.js';
|
|
5
5
|
import { SURFACE_LIMITS } from './types.js';
|
|
@@ -23,8 +23,8 @@ const editSchema = Type.Object({
|
|
|
23
23
|
Type.Object({ op: Type.Literal('reorder-widgets'), ids: Type.Array(surfaceIdSchema, { maxItems: SURFACE_LIMITS.maxWidgets, uniqueItems: true }) }, { additionalProperties: false }),
|
|
24
24
|
]), { minItems: 1, maxItems: SURFACE_EDIT_LIMITS.maxOperations }),
|
|
25
25
|
}, { additionalProperties: false });
|
|
26
|
-
const compiledEdit =
|
|
27
|
-
const compiledRevision =
|
|
26
|
+
const compiledEdit = Compile(editSchema);
|
|
27
|
+
const compiledRevision = Compile(Type.Object({ revision: revisionSchema, spec: surfaceSpecSchema }, { additionalProperties: false }));
|
|
28
28
|
function editError(code, path, message) {
|
|
29
29
|
return { ok: false, issues: [{ code, path, message }] };
|
|
30
30
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,3 +10,5 @@ export { SURFACE_EDIT_SCHEMA_VERSION, SURFACE_EDIT_LIMITS, createSurfaceRevision
|
|
|
10
10
|
export type { SurfaceEditOperation, SurfaceEditProposal, SurfaceEditIssue, SurfaceRevision, SurfaceRevisionResult, } from './edits.js';
|
|
11
11
|
export { svarGridPropsSchema, svarGridDefinition } from './svar-schema.js';
|
|
12
12
|
export type { SvarGridProps } from './svar-schema.js';
|
|
13
|
+
export { jsonRenderSurfaceRootPropsSchema, jsonRenderUiElementSchema, jsonRenderSpecSchema, jsonRenderSpecToSurfaceSpec, surfaceSpecToJsonRenderSpec, } from './json-render.js';
|
|
14
|
+
export type { JsonRenderSurfaceElement, JsonRenderSurfaceSpec, JsonRenderSurfaceResult, } from './json-render.js';
|
package/dist/index.js
CHANGED
|
@@ -5,3 +5,4 @@ export { SURFACE_AGENT_LIMITS, SURFACE_AGENT_RESPONSE_SCHEMA_VERSION, SURFACE_CA
|
|
|
5
5
|
export { SURFACE_EDIT_SCHEMA_VERSION, SURFACE_EDIT_LIMITS, createSurfaceRevision, applySurfaceEditProposal, createSurfaceEditSchema, buildSurfaceEditMessages, } from './edits.js';
|
|
6
6
|
// 只导出 JSON schema,不将可选 SVAR 渲染器加载到协议入口。
|
|
7
7
|
export { svarGridPropsSchema, svarGridDefinition } from './svar-schema.js';
|
|
8
|
+
export { jsonRenderSurfaceRootPropsSchema, jsonRenderUiElementSchema, jsonRenderSpecSchema, jsonRenderSpecToSurfaceSpec, surfaceSpecToJsonRenderSpec, } from './json-render.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Type, type Static } from 'typebox';
|
|
2
|
+
import type { SurfaceCatalog, SurfacePolicy, SurfaceSpec } from './types.js';
|
|
3
|
+
/** json-render 的标准扁平 Spec 子集;业务字段仍由 Surface 的 TypeBox 契约校验。 */
|
|
4
|
+
export declare const jsonRenderSurfaceRootPropsSchema: Type.TObject<{
|
|
5
|
+
layout: Type.TObject<{
|
|
6
|
+
type: Type.TLiteral<"grid">;
|
|
7
|
+
columns: Type.TLiteral<12>;
|
|
8
|
+
gap: Type.TOptional<Type.TUnion<[Type.TLiteral<"sm">, Type.TLiteral<"md">, Type.TLiteral<"lg">]>>;
|
|
9
|
+
}>;
|
|
10
|
+
title: Type.TString;
|
|
11
|
+
catalogVersion: Type.TString;
|
|
12
|
+
surfaceId: Type.TString;
|
|
13
|
+
dataSources: Type.TArray<Type.TUnion<[Type.TObject<{
|
|
14
|
+
id: Type.TString;
|
|
15
|
+
type: Type.TLiteral<"resource-list">;
|
|
16
|
+
resource: Type.TString;
|
|
17
|
+
pageSize: Type.TOptional<Type.TInteger>;
|
|
18
|
+
sorters: Type.TOptional<Type.TArray<Type.TObject<{
|
|
19
|
+
field: Type.TString;
|
|
20
|
+
order: Type.TUnion<[Type.TLiteral<"asc">, Type.TLiteral<"desc">]>;
|
|
21
|
+
}>>>;
|
|
22
|
+
filters: Type.TOptional<Type.TArray<Type.TUnion<[Type.TObject<{
|
|
23
|
+
field: Type.TString;
|
|
24
|
+
operator: Type.TUnion<[Type.TLiteral<"eq">, Type.TLiteral<"ne">, Type.TLiteral<"lt">, Type.TLiteral<"lte">, Type.TLiteral<"gt">, Type.TLiteral<"gte">, Type.TLiteral<"contains">, Type.TLiteral<"startswith">, Type.TLiteral<"endswith">]>;
|
|
25
|
+
value: Type.TUnion<[Type.TString, Type.TNumber, Type.TBoolean, Type.TNull]>;
|
|
26
|
+
}>, Type.TObject<{
|
|
27
|
+
field: Type.TString;
|
|
28
|
+
operator: Type.TUnion<[Type.TLiteral<"in">, Type.TLiteral<"nin">]>;
|
|
29
|
+
value: Type.TArray<Type.TUnion<[Type.TString, Type.TNumber, Type.TBoolean, Type.TNull]>>;
|
|
30
|
+
}>, Type.TObject<{
|
|
31
|
+
field: Type.TString;
|
|
32
|
+
operator: Type.TUnion<[Type.TLiteral<"null">, Type.TLiteral<"nnull">]>;
|
|
33
|
+
}>]>>>;
|
|
34
|
+
}>, Type.TObject<{
|
|
35
|
+
id: Type.TString;
|
|
36
|
+
type: Type.TLiteral<"resource-one">;
|
|
37
|
+
resource: Type.TString;
|
|
38
|
+
recordId: Type.TUnion<[Type.TString, Type.TNumber]>;
|
|
39
|
+
}>]>>;
|
|
40
|
+
}>;
|
|
41
|
+
export declare const jsonRenderUiElementSchema: Type.TObject<{
|
|
42
|
+
type: Type.TString;
|
|
43
|
+
props: Type.TRecord<"^.*$", Type.TUnknown>;
|
|
44
|
+
children: Type.TOptional<Type.TArray<Type.TString>>;
|
|
45
|
+
}>;
|
|
46
|
+
export declare const jsonRenderSpecSchema: Type.TObject<{
|
|
47
|
+
root: Type.TString;
|
|
48
|
+
elements: Type.TRecord<"^.*$", Type.TObject<{
|
|
49
|
+
type: Type.TString;
|
|
50
|
+
props: Type.TRecord<"^.*$", Type.TUnknown>;
|
|
51
|
+
children: Type.TOptional<Type.TArray<Type.TString>>;
|
|
52
|
+
}>>;
|
|
53
|
+
}>;
|
|
54
|
+
export type JsonRenderSurfaceElement = Static<typeof jsonRenderUiElementSchema>;
|
|
55
|
+
export type JsonRenderSurfaceSpec = Static<typeof jsonRenderSpecSchema>;
|
|
56
|
+
export type JsonRenderSurfaceResult = {
|
|
57
|
+
readonly ok: true;
|
|
58
|
+
readonly value: SurfaceSpec;
|
|
59
|
+
} | {
|
|
60
|
+
readonly ok: false;
|
|
61
|
+
readonly issues: readonly {
|
|
62
|
+
readonly path: string;
|
|
63
|
+
readonly message: string;
|
|
64
|
+
}[];
|
|
65
|
+
};
|
|
66
|
+
export declare function jsonRenderSpecToSurfaceSpec(input: unknown, catalog: SurfaceCatalog, policy: SurfacePolicy): JsonRenderSurfaceResult;
|
|
67
|
+
/** 将已校验的 SurfaceSpec 编码为 json-render 的标准扁平 Spec。 */
|
|
68
|
+
export declare function surfaceSpecToJsonRenderSpec(spec: SurfaceSpec): JsonRenderSurfaceSpec;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Type, ObjectOptions } from 'typebox';
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
|
+
import { validateSurfaceSpec } from './validation.js';
|
|
4
|
+
import { jsonPointer } from './json.js';
|
|
5
|
+
import { parseSurfaceJson } from './wire.js';
|
|
6
|
+
import { surfaceSpecSchema, surfaceWidgetSchema } from './schema.js';
|
|
7
|
+
/** json-render 的标准扁平 Spec 子集;业务字段仍由 Surface 的 TypeBox 契约校验。 */
|
|
8
|
+
export const jsonRenderSurfaceRootPropsSchema = Type.Omit(surfaceSpecSchema, ['schemaVersion', 'widgets'], ObjectOptions(surfaceSpecSchema));
|
|
9
|
+
export const jsonRenderUiElementSchema = Type.Object({
|
|
10
|
+
type: Type.String({ minLength: 1 }),
|
|
11
|
+
props: Type.Record(Type.String(), Type.Unknown()),
|
|
12
|
+
children: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
13
|
+
}, { additionalProperties: false });
|
|
14
|
+
export const jsonRenderSpecSchema = Type.Object({
|
|
15
|
+
root: Type.String({ minLength: 1 }),
|
|
16
|
+
elements: Type.Record(Type.String({ minLength: 1 }), jsonRenderUiElementSchema),
|
|
17
|
+
}, { additionalProperties: false });
|
|
18
|
+
function issue(path, message) {
|
|
19
|
+
return { path, message };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 将 json-render Spec 转换为受 Surface 权限、目录和 TypeBox 规则保护的 SurfaceSpec。
|
|
23
|
+
* 只接受 root 类型为 surface 的子集,避免把任意 json-render 组件树当作业务页面执行。
|
|
24
|
+
*/
|
|
25
|
+
function readSurfaceRoot(candidate) {
|
|
26
|
+
const root = candidate.elements[candidate.root];
|
|
27
|
+
if (!root || root.type !== 'surface')
|
|
28
|
+
return { error: 'json-render root must reference a surface element' };
|
|
29
|
+
if (!Value.Check(jsonRenderSurfaceRootPropsSchema, root.props))
|
|
30
|
+
return { error: 'Invalid surface root properties' };
|
|
31
|
+
return root;
|
|
32
|
+
}
|
|
33
|
+
function readWidgetElement(element, id) {
|
|
34
|
+
if (element.type === 'surface')
|
|
35
|
+
return { error: 'Nested surface elements are not supported' };
|
|
36
|
+
if (element.children?.length)
|
|
37
|
+
return { error: 'Widget children are not supported' };
|
|
38
|
+
const binding = element.props['binding'];
|
|
39
|
+
const placement = element.props['placement'];
|
|
40
|
+
if (binding !== undefined && !Value.Check(surfaceWidgetSchema.properties.binding, binding))
|
|
41
|
+
return { error: 'Invalid widget binding' };
|
|
42
|
+
if (placement !== undefined && !Value.Check(surfaceWidgetSchema.properties.placement, placement))
|
|
43
|
+
return { error: 'Invalid widget placement' };
|
|
44
|
+
const props = { ...element.props };
|
|
45
|
+
delete props['binding'];
|
|
46
|
+
delete props['placement'];
|
|
47
|
+
return { id, type: element.type, props,
|
|
48
|
+
...(binding === undefined ? {} : { binding }),
|
|
49
|
+
...(placement === undefined ? {} : { placement }),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function parseJsonRenderSpec(input) {
|
|
53
|
+
const parsed = parseSurfaceJson(input);
|
|
54
|
+
if (!parsed.ok)
|
|
55
|
+
return { error: parsed.issues[0]?.message ?? 'Invalid JSON', path: parsed.issues[0]?.path ?? '' };
|
|
56
|
+
if (!Value.Check(jsonRenderSpecSchema, parsed.value))
|
|
57
|
+
return { error: 'Invalid json-render spec' };
|
|
58
|
+
return parsed.value;
|
|
59
|
+
}
|
|
60
|
+
export function jsonRenderSpecToSurfaceSpec(input, catalog, policy) {
|
|
61
|
+
const parsed = parseJsonRenderSpec(input);
|
|
62
|
+
if ('error' in parsed)
|
|
63
|
+
return { ok: false, issues: [issue(parsed.path ?? '', parsed.error)] };
|
|
64
|
+
const root = readSurfaceRoot(parsed);
|
|
65
|
+
if ('error' in root)
|
|
66
|
+
return { ok: false, issues: [issue('/root', root.error)] };
|
|
67
|
+
const children = root.children ?? [];
|
|
68
|
+
const widgets = [];
|
|
69
|
+
for (const childId of children) {
|
|
70
|
+
const element = parsed.elements[childId];
|
|
71
|
+
if (!element)
|
|
72
|
+
return { ok: false, issues: [issue(jsonPointer(['elements', childId]), 'Missing child element')] };
|
|
73
|
+
const widget = readWidgetElement(element, childId);
|
|
74
|
+
if ('error' in widget)
|
|
75
|
+
return { ok: false, issues: [issue(jsonPointer(['elements', childId]), widget.error)] };
|
|
76
|
+
widgets.push(widget);
|
|
77
|
+
}
|
|
78
|
+
if (new Set(children).size !== children.length || Object.keys(parsed.elements).length !== children.length + 1)
|
|
79
|
+
return { ok: false, issues: [issue('/elements', 'Every element must occur exactly once in the surface tree')] };
|
|
80
|
+
const surface = { schemaVersion: 'surface/v1', ...root.props, widgets };
|
|
81
|
+
const validation = validateSurfaceSpec(surface, catalog, policy);
|
|
82
|
+
return validation.ok ? validation : { ok: false, issues: validation.issues.map(({ path, message }) => ({ path, message })) };
|
|
83
|
+
}
|
|
84
|
+
/** 将已校验的 SurfaceSpec 编码为 json-render 的标准扁平 Spec。 */
|
|
85
|
+
export function surfaceSpecToJsonRenderSpec(spec) {
|
|
86
|
+
const root = '__svadmin_surface_root';
|
|
87
|
+
if (spec.widgets.some(widget => widget.id === root)) {
|
|
88
|
+
throw new Error('Surface widget id is reserved by the json-render adapter');
|
|
89
|
+
}
|
|
90
|
+
const elements = {
|
|
91
|
+
[root]: {
|
|
92
|
+
type: 'surface',
|
|
93
|
+
props: {
|
|
94
|
+
catalogVersion: spec.catalogVersion,
|
|
95
|
+
surfaceId: spec.surfaceId,
|
|
96
|
+
title: spec.title,
|
|
97
|
+
layout: spec.layout,
|
|
98
|
+
dataSources: spec.dataSources,
|
|
99
|
+
},
|
|
100
|
+
children: spec.widgets.map(widget => widget.id),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
for (const widget of spec.widgets) {
|
|
104
|
+
if (Object.hasOwn(widget.props, 'binding') || Object.hasOwn(widget.props, 'placement')) {
|
|
105
|
+
throw new Error('Widget props binding and placement are reserved by the json-render adapter');
|
|
106
|
+
}
|
|
107
|
+
elements[widget.id] = {
|
|
108
|
+
type: widget.type,
|
|
109
|
+
props: {
|
|
110
|
+
...widget.props,
|
|
111
|
+
...(widget.binding ? { binding: widget.binding } : {}),
|
|
112
|
+
...(widget.placement ? { placement: widget.placement } : {}),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return { root, elements };
|
|
117
|
+
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,108 +1,109 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
|
+
export declare const surfaceIdSchema: Type.TString;
|
|
3
|
+
export declare const surfaceFilterSchema: Type.TUnion<[Type.TObject<{
|
|
4
|
+
field: Type.TString;
|
|
5
|
+
operator: Type.TUnion<[Type.TLiteral<"eq">, Type.TLiteral<"ne">, Type.TLiteral<"lt">, Type.TLiteral<"lte">, Type.TLiteral<"gt">, Type.TLiteral<"gte">, Type.TLiteral<"contains">, Type.TLiteral<"startswith">, Type.TLiteral<"endswith">]>;
|
|
6
|
+
value: Type.TUnion<[Type.TString, Type.TNumber, Type.TBoolean, Type.TNull]>;
|
|
7
|
+
}>, Type.TObject<{
|
|
8
|
+
field: Type.TString;
|
|
9
|
+
operator: Type.TUnion<[Type.TLiteral<"in">, Type.TLiteral<"nin">]>;
|
|
10
|
+
value: Type.TArray<Type.TUnion<[Type.TString, Type.TNumber, Type.TBoolean, Type.TNull]>>;
|
|
11
|
+
}>, Type.TObject<{
|
|
12
|
+
field: Type.TString;
|
|
13
|
+
operator: Type.TUnion<[Type.TLiteral<"null">, Type.TLiteral<"nnull">]>;
|
|
13
14
|
}>]>;
|
|
14
|
-
export declare const surfaceListSourceSchema:
|
|
15
|
-
id:
|
|
16
|
-
type:
|
|
17
|
-
resource:
|
|
18
|
-
pageSize:
|
|
19
|
-
sorters:
|
|
20
|
-
field:
|
|
21
|
-
order:
|
|
15
|
+
export declare const surfaceListSourceSchema: Type.TObject<{
|
|
16
|
+
id: Type.TString;
|
|
17
|
+
type: Type.TLiteral<"resource-list">;
|
|
18
|
+
resource: Type.TString;
|
|
19
|
+
pageSize: Type.TOptional<Type.TInteger>;
|
|
20
|
+
sorters: Type.TOptional<Type.TArray<Type.TObject<{
|
|
21
|
+
field: Type.TString;
|
|
22
|
+
order: Type.TUnion<[Type.TLiteral<"asc">, Type.TLiteral<"desc">]>;
|
|
22
23
|
}>>>;
|
|
23
|
-
filters:
|
|
24
|
-
field:
|
|
25
|
-
operator:
|
|
26
|
-
value:
|
|
27
|
-
}>,
|
|
28
|
-
field:
|
|
29
|
-
operator:
|
|
30
|
-
value:
|
|
31
|
-
}>,
|
|
32
|
-
field:
|
|
33
|
-
operator:
|
|
24
|
+
filters: Type.TOptional<Type.TArray<Type.TUnion<[Type.TObject<{
|
|
25
|
+
field: Type.TString;
|
|
26
|
+
operator: Type.TUnion<[Type.TLiteral<"eq">, Type.TLiteral<"ne">, Type.TLiteral<"lt">, Type.TLiteral<"lte">, Type.TLiteral<"gt">, Type.TLiteral<"gte">, Type.TLiteral<"contains">, Type.TLiteral<"startswith">, Type.TLiteral<"endswith">]>;
|
|
27
|
+
value: Type.TUnion<[Type.TString, Type.TNumber, Type.TBoolean, Type.TNull]>;
|
|
28
|
+
}>, Type.TObject<{
|
|
29
|
+
field: Type.TString;
|
|
30
|
+
operator: Type.TUnion<[Type.TLiteral<"in">, Type.TLiteral<"nin">]>;
|
|
31
|
+
value: Type.TArray<Type.TUnion<[Type.TString, Type.TNumber, Type.TBoolean, Type.TNull]>>;
|
|
32
|
+
}>, Type.TObject<{
|
|
33
|
+
field: Type.TString;
|
|
34
|
+
operator: Type.TUnion<[Type.TLiteral<"null">, Type.TLiteral<"nnull">]>;
|
|
34
35
|
}>]>>>;
|
|
35
36
|
}>;
|
|
36
|
-
export declare const surfaceOneSourceSchema:
|
|
37
|
-
id:
|
|
38
|
-
type:
|
|
39
|
-
resource:
|
|
40
|
-
recordId:
|
|
37
|
+
export declare const surfaceOneSourceSchema: Type.TObject<{
|
|
38
|
+
id: Type.TString;
|
|
39
|
+
type: Type.TLiteral<"resource-one">;
|
|
40
|
+
resource: Type.TString;
|
|
41
|
+
recordId: Type.TUnion<[Type.TString, Type.TNumber]>;
|
|
41
42
|
}>;
|
|
42
|
-
export declare const surfaceLayoutSchema:
|
|
43
|
-
type:
|
|
44
|
-
columns:
|
|
45
|
-
gap:
|
|
43
|
+
export declare const surfaceLayoutSchema: Type.TObject<{
|
|
44
|
+
type: Type.TLiteral<"grid">;
|
|
45
|
+
columns: Type.TLiteral<12>;
|
|
46
|
+
gap: Type.TOptional<Type.TUnion<[Type.TLiteral<"sm">, Type.TLiteral<"md">, Type.TLiteral<"lg">]>>;
|
|
46
47
|
}>;
|
|
47
|
-
export declare const surfaceWidgetSchema:
|
|
48
|
-
id:
|
|
49
|
-
type:
|
|
50
|
-
props:
|
|
51
|
-
binding:
|
|
52
|
-
sourceId:
|
|
53
|
-
pointer:
|
|
48
|
+
export declare const surfaceWidgetSchema: Type.TObject<{
|
|
49
|
+
id: Type.TString;
|
|
50
|
+
type: Type.TString;
|
|
51
|
+
props: Type.TRecord<"^.*$", Type.TUnknown>;
|
|
52
|
+
binding: Type.TOptional<Type.TObject<{
|
|
53
|
+
sourceId: Type.TString;
|
|
54
|
+
pointer: Type.TOptional<Type.TString>;
|
|
54
55
|
}>>;
|
|
55
|
-
placement:
|
|
56
|
-
columnSpan:
|
|
56
|
+
placement: Type.TOptional<Type.TObject<{
|
|
57
|
+
columnSpan: Type.TOptional<Type.TInteger>;
|
|
57
58
|
}>>;
|
|
58
59
|
}>;
|
|
59
|
-
export declare const surfaceSpecSchema:
|
|
60
|
-
schemaVersion:
|
|
61
|
-
catalogVersion:
|
|
62
|
-
surfaceId:
|
|
63
|
-
title:
|
|
64
|
-
layout:
|
|
65
|
-
type:
|
|
66
|
-
columns:
|
|
67
|
-
gap:
|
|
60
|
+
export declare const surfaceSpecSchema: Type.TObject<{
|
|
61
|
+
schemaVersion: Type.TString;
|
|
62
|
+
catalogVersion: Type.TString;
|
|
63
|
+
surfaceId: Type.TString;
|
|
64
|
+
title: Type.TString;
|
|
65
|
+
layout: Type.TObject<{
|
|
66
|
+
type: Type.TLiteral<"grid">;
|
|
67
|
+
columns: Type.TLiteral<12>;
|
|
68
|
+
gap: Type.TOptional<Type.TUnion<[Type.TLiteral<"sm">, Type.TLiteral<"md">, Type.TLiteral<"lg">]>>;
|
|
68
69
|
}>;
|
|
69
|
-
dataSources:
|
|
70
|
-
id:
|
|
71
|
-
type:
|
|
72
|
-
resource:
|
|
73
|
-
pageSize:
|
|
74
|
-
sorters:
|
|
75
|
-
field:
|
|
76
|
-
order:
|
|
70
|
+
dataSources: Type.TArray<Type.TUnion<[Type.TObject<{
|
|
71
|
+
id: Type.TString;
|
|
72
|
+
type: Type.TLiteral<"resource-list">;
|
|
73
|
+
resource: Type.TString;
|
|
74
|
+
pageSize: Type.TOptional<Type.TInteger>;
|
|
75
|
+
sorters: Type.TOptional<Type.TArray<Type.TObject<{
|
|
76
|
+
field: Type.TString;
|
|
77
|
+
order: Type.TUnion<[Type.TLiteral<"asc">, Type.TLiteral<"desc">]>;
|
|
77
78
|
}>>>;
|
|
78
|
-
filters:
|
|
79
|
-
field:
|
|
80
|
-
operator:
|
|
81
|
-
value:
|
|
82
|
-
}>,
|
|
83
|
-
field:
|
|
84
|
-
operator:
|
|
85
|
-
value:
|
|
86
|
-
}>,
|
|
87
|
-
field:
|
|
88
|
-
operator:
|
|
79
|
+
filters: Type.TOptional<Type.TArray<Type.TUnion<[Type.TObject<{
|
|
80
|
+
field: Type.TString;
|
|
81
|
+
operator: Type.TUnion<[Type.TLiteral<"eq">, Type.TLiteral<"ne">, Type.TLiteral<"lt">, Type.TLiteral<"lte">, Type.TLiteral<"gt">, Type.TLiteral<"gte">, Type.TLiteral<"contains">, Type.TLiteral<"startswith">, Type.TLiteral<"endswith">]>;
|
|
82
|
+
value: Type.TUnion<[Type.TString, Type.TNumber, Type.TBoolean, Type.TNull]>;
|
|
83
|
+
}>, Type.TObject<{
|
|
84
|
+
field: Type.TString;
|
|
85
|
+
operator: Type.TUnion<[Type.TLiteral<"in">, Type.TLiteral<"nin">]>;
|
|
86
|
+
value: Type.TArray<Type.TUnion<[Type.TString, Type.TNumber, Type.TBoolean, Type.TNull]>>;
|
|
87
|
+
}>, Type.TObject<{
|
|
88
|
+
field: Type.TString;
|
|
89
|
+
operator: Type.TUnion<[Type.TLiteral<"null">, Type.TLiteral<"nnull">]>;
|
|
89
90
|
}>]>>>;
|
|
90
|
-
}>,
|
|
91
|
-
id:
|
|
92
|
-
type:
|
|
93
|
-
resource:
|
|
94
|
-
recordId:
|
|
91
|
+
}>, Type.TObject<{
|
|
92
|
+
id: Type.TString;
|
|
93
|
+
type: Type.TLiteral<"resource-one">;
|
|
94
|
+
resource: Type.TString;
|
|
95
|
+
recordId: Type.TUnion<[Type.TString, Type.TNumber]>;
|
|
95
96
|
}>]>>;
|
|
96
|
-
widgets:
|
|
97
|
-
id:
|
|
98
|
-
type:
|
|
99
|
-
props:
|
|
100
|
-
binding:
|
|
101
|
-
sourceId:
|
|
102
|
-
pointer:
|
|
97
|
+
widgets: Type.TArray<Type.TObject<{
|
|
98
|
+
id: Type.TString;
|
|
99
|
+
type: Type.TString;
|
|
100
|
+
props: Type.TRecord<"^.*$", Type.TUnknown>;
|
|
101
|
+
binding: Type.TOptional<Type.TObject<{
|
|
102
|
+
sourceId: Type.TString;
|
|
103
|
+
pointer: Type.TOptional<Type.TString>;
|
|
103
104
|
}>>;
|
|
104
|
-
placement:
|
|
105
|
-
columnSpan:
|
|
105
|
+
placement: Type.TOptional<Type.TObject<{
|
|
106
|
+
columnSpan: Type.TOptional<Type.TInteger>;
|
|
106
107
|
}>>;
|
|
107
108
|
}>>;
|
|
108
109
|
}>;
|
package/dist/schema.js
CHANGED
package/dist/svar-schema.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { type Static } from '
|
|
1
|
+
import { Type, type Static } from 'typebox';
|
|
2
2
|
import type { JsonObject, SurfaceWidgetDefinition } from './types.js';
|
|
3
3
|
/** JSON-only 契约;宿主引擎、回调、URL 和写操作不属于 AI 可以生成的属性。 */
|
|
4
|
-
export declare const svarGridPropsSchema:
|
|
5
|
-
title:
|
|
6
|
-
rowKey:
|
|
7
|
-
height:
|
|
8
|
-
density:
|
|
9
|
-
freezeLeft:
|
|
10
|
-
freezeRight:
|
|
11
|
-
emptyLabel:
|
|
12
|
-
columns:
|
|
13
|
-
field:
|
|
14
|
-
label:
|
|
15
|
-
width:
|
|
16
|
-
format:
|
|
4
|
+
export declare const svarGridPropsSchema: Type.TObject<{
|
|
5
|
+
title: Type.TString;
|
|
6
|
+
rowKey: Type.TOptional<Type.TString>;
|
|
7
|
+
height: Type.TOptional<Type.TInteger>;
|
|
8
|
+
density: Type.TOptional<Type.TUnion<[Type.TLiteral<"compact">, Type.TLiteral<"comfortable">]>>;
|
|
9
|
+
freezeLeft: Type.TOptional<Type.TInteger>;
|
|
10
|
+
freezeRight: Type.TOptional<Type.TInteger>;
|
|
11
|
+
emptyLabel: Type.TOptional<Type.TString>;
|
|
12
|
+
columns: Type.TArray<Type.TObject<{
|
|
13
|
+
field: Type.TString;
|
|
14
|
+
label: Type.TString;
|
|
15
|
+
width: Type.TOptional<Type.TInteger>;
|
|
16
|
+
format: Type.TOptional<Type.TUnion<[Type.TLiteral<"plain">, Type.TLiteral<"number">, Type.TLiteral<"percent">, Type.TLiteral<"date">, Type.TLiteral<"datetime">, Type.TLiteral<"boolean">]>>;
|
|
17
17
|
}>>;
|
|
18
18
|
}>;
|
|
19
19
|
export type SvarGridProps = Static<typeof svarGridPropsSchema>;
|
package/dist/svar-schema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Type } from '
|
|
2
|
-
import { Value } from '
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
3
|
const field = Type.String({ minLength: 1, maxLength: 64, pattern: '^[A-Za-z][A-Za-z0-9_-]*$' });
|
|
4
4
|
/** JSON-only 契约;宿主引擎、回调、URL 和写操作不属于 AI 可以生成的属性。 */
|
|
5
5
|
export const svarGridPropsSchema = Type.Object({
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DataProvider } from '@svadmin/core';
|
|
2
|
-
import type { TSchema } from '
|
|
2
|
+
import type { TSchema } from 'typebox';
|
|
3
3
|
export declare const SURFACE_SCHEMA_VERSION: "surface/v1";
|
|
4
4
|
export declare const SURFACE_LIMITS: {
|
|
5
5
|
readonly maxDataSources: 8;
|
package/dist/validation.d.ts
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
import { type SurfaceCatalog, type SurfacePolicy, type SurfaceValidationResult } from "./types.js";
|
|
2
|
+
interface SchemaErrorLike {
|
|
3
|
+
instancePath?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
keyword?: string;
|
|
6
|
+
params?: Record<string, unknown>;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
/** TypeBox 1.x groups missing properties into one Ajv-style error; split them per field. */
|
|
10
|
+
export declare function surfaceSchemaIssues(errors: Iterable<SchemaErrorLike>): {
|
|
11
|
+
path: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}[];
|
|
2
14
|
export declare function validateSurfaceSpec(input: unknown, catalog: SurfaceCatalog, policy: SurfacePolicy): SurfaceValidationResult;
|
|
15
|
+
export {};
|
package/dist/validation.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Value } from "
|
|
1
|
+
import { Compile } from "typebox/compile";
|
|
2
|
+
import { Value } from "typebox/value";
|
|
3
3
|
import { decodedJsonPointerToken, jsonPointer, jsonValueIssue } from "./json.js";
|
|
4
4
|
import { surfaceSpecSchema } from './schema.js';
|
|
5
5
|
import { SURFACE_LIMITS, SURFACE_SCHEMA_VERSION, } from "./types.js";
|
|
6
|
-
const compiledSurfaceSpec =
|
|
6
|
+
const compiledSurfaceSpec = Compile(surfaceSpecSchema);
|
|
7
7
|
const forbiddenPropertyNames = new Set([
|
|
8
8
|
"class", "className", "color", "href", "html", "innerHTML", "src", "style", "url",
|
|
9
9
|
]);
|
|
@@ -11,17 +11,38 @@ const strictSchemaProbeKey = "__surface_unknown_property_probe__";
|
|
|
11
11
|
function invalidJsonIssue(pathSegments, message) {
|
|
12
12
|
return { code: "invalid_json", path: jsonPointer(pathSegments), message };
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function escapePointerSegment(segment) {
|
|
15
|
+
return segment.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
16
|
+
}
|
|
17
|
+
/** TypeBox 1.x groups missing properties into one Ajv-style error; split them per field. */
|
|
18
|
+
export function surfaceSchemaIssues(errors) {
|
|
15
19
|
const issues = [];
|
|
16
|
-
for (const
|
|
20
|
+
for (const error of errors) {
|
|
21
|
+
const base = error.instancePath ?? error.path ?? '';
|
|
22
|
+
const required = error.params?.['requiredProperties'];
|
|
23
|
+
if (error.keyword === 'required' && Array.isArray(required)) {
|
|
24
|
+
for (const property of required) {
|
|
25
|
+
if (typeof property !== 'string')
|
|
26
|
+
continue;
|
|
27
|
+
issues.push({ path: `${base}/${escapePointerSegment(property)}`, message: `must have required property '${property}'` });
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const additional = error.params?.['additionalProperty'];
|
|
17
32
|
issues.push({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
message: err.message,
|
|
33
|
+
path: typeof additional === 'string' ? `${base}/${escapePointerSegment(additional)}` : base,
|
|
34
|
+
message: error.message,
|
|
21
35
|
});
|
|
22
36
|
}
|
|
23
37
|
return issues;
|
|
24
38
|
}
|
|
39
|
+
function schemaErrors(errors) {
|
|
40
|
+
return surfaceSchemaIssues(errors).map((issue) => ({
|
|
41
|
+
code: "invalid_json",
|
|
42
|
+
path: issue.path,
|
|
43
|
+
message: issue.message,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
25
46
|
function catalogDefinitions(catalog) {
|
|
26
47
|
return new Map(catalog.widgets.map((widgetDefinition) => [widgetDefinition.type, widgetDefinition]));
|
|
27
48
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Value } from '
|
|
1
|
+
import { Value } from 'typebox/value';
|
|
2
2
|
import { surfaceSchemaToJson } from '../agent-contract.js';
|
|
3
3
|
/** Only the form subset actually rendered by JsonSchemaForm is advertised.
|
|
4
4
|
* No refs, transforms, secret inputs, arbitrary unions or computed fields.
|
|
@@ -10,29 +10,30 @@ export function validateSurfaceActionDescriptor(action) {
|
|
|
10
10
|
}
|
|
11
11
|
surfaceSchemaToJson(action.inputSchema);
|
|
12
12
|
let fields = 0;
|
|
13
|
-
function visit(
|
|
13
|
+
function visit(input, depth) {
|
|
14
|
+
const schema = input;
|
|
14
15
|
if (++fields > 128 || depth > 8)
|
|
15
16
|
throw new Error('Surface form schema exceeds supported limits');
|
|
16
|
-
if (schema
|
|
17
|
-
|| !['object', 'array', 'string', 'number', 'integer', 'boolean'].includes(schema
|
|
17
|
+
if (schema.anyOf || schema.oneOf || schema.allOf || schema.not || schema.if || schema.writeOnly
|
|
18
|
+
|| !['object', 'array', 'string', 'number', 'integer', 'boolean'].includes(schema.type ?? '')) {
|
|
18
19
|
throw new Error('Unsupported Surface form schema; use a registered custom widget instead');
|
|
19
20
|
}
|
|
20
|
-
if (schema
|
|
21
|
-
if (schema
|
|
21
|
+
if (schema.type === 'object') {
|
|
22
|
+
if (schema.additionalProperties !== false || !schema.properties)
|
|
22
23
|
throw new Error('Surface actions require closed object schemas');
|
|
23
|
-
for (const [name, child] of Object.entries(schema
|
|
24
|
+
for (const [name, child] of Object.entries(schema.properties)) {
|
|
24
25
|
if (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/u.test(name))
|
|
25
26
|
throw new Error('Invalid Surface form field');
|
|
26
27
|
visit(child, depth + 1);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
else if (schema
|
|
30
|
-
if (!schema
|
|
30
|
+
else if (schema.type === 'array') {
|
|
31
|
+
if (!schema.items || Array.isArray(schema.items))
|
|
31
32
|
throw new Error('Surface forms require homogeneous array items');
|
|
32
|
-
visit(schema
|
|
33
|
+
visit(schema.items, depth + 1);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
|
-
if (action.inputSchema
|
|
36
|
+
if (action.inputSchema.type !== 'object')
|
|
36
37
|
throw new Error('Surface actions require an object input schema');
|
|
37
38
|
visit(action.inputSchema, 0);
|
|
38
39
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Type } from 'typebox';
|
|
1
2
|
import type { SurfaceCatalog, SurfaceWidgetDefinition, JsonObject } from '../types.js';
|
|
2
3
|
import type { SurfaceActionDescriptor } from './types.js';
|
|
3
|
-
export declare const surfaceAppearanceSchema:
|
|
4
|
-
tone:
|
|
5
|
-
density:
|
|
4
|
+
export declare const surfaceAppearanceSchema: Type.TObject<{
|
|
5
|
+
tone: Type.TOptional<Type.TUnion<Type.TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
|
|
6
|
+
density: Type.TOptional<Type.TUnion<Type.TLiteral<"compact" | "comfortable">[]>>;
|
|
6
7
|
}>;
|
|
7
8
|
export declare function withoutSurfaceAppearance(props: JsonObject): JsonObject;
|
|
8
9
|
/** Adds frame-level semantic appearance to every registered widget without
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Type } from '
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
2
|
import { surfaceDesignContract } from '../design-contract.js';
|
|
3
3
|
import { validateSurfaceActionDescriptor } from './action-contracts.js';
|
|
4
4
|
export const surfaceAppearanceSchema = Type.Object({
|
|
@@ -8,13 +8,14 @@ export const surfaceAppearanceSchema = Type.Object({
|
|
|
8
8
|
export function withoutSurfaceAppearance(props) {
|
|
9
9
|
return Object.fromEntries(Object.entries(props).filter(([key]) => key !== 'appearance'));
|
|
10
10
|
}
|
|
11
|
-
function withAppearance(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
function withAppearance(input) {
|
|
12
|
+
const schema = input;
|
|
13
|
+
if (Array.isArray(schema.anyOf))
|
|
14
|
+
return { ...input, anyOf: schema.anyOf.map((part) => withAppearance(part)) };
|
|
15
|
+
if (schema.type !== 'object' || schema.additionalProperties !== false || !schema.properties || 'appearance' in schema.properties) {
|
|
15
16
|
throw new Error('Surface appearance requires closed object schemas without a reserved appearance property');
|
|
16
17
|
}
|
|
17
|
-
return { ...
|
|
18
|
+
return { ...input, properties: { ...schema.properties, appearance: Type.Optional(surfaceAppearanceSchema) } };
|
|
18
19
|
}
|
|
19
20
|
/** Adds frame-level semantic appearance to every registered widget without
|
|
20
21
|
* leaking presentation props into its existing component implementation.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TSchema } from '
|
|
1
|
+
import type { TSchema } from 'typebox';
|
|
2
2
|
import type { JsonObject, JsonValue, SurfaceCatalog, SurfacePolicy, SurfaceSpec } from '../types.js';
|
|
3
3
|
/** Identity comes from authenticated server middleware, never from model JSON. */
|
|
4
4
|
export interface SurfaceWorkflowContext {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/surface",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Validated declarative surfaces for svadmin dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -74,12 +74,12 @@
|
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@svadmin/core": ">=0.53.0 <0.
|
|
78
|
-
"@svadmin/ui": ">=0.73.0 <0.
|
|
77
|
+
"@svadmin/core": ">=0.53.0 <0.60.0",
|
|
78
|
+
"@svadmin/ui": ">=0.73.0 <0.83.0",
|
|
79
79
|
"svelte": "^5.56.10"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"
|
|
82
|
+
"typebox": "^1.3.34"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|