@zhin.js/schema 1.1.4 → 1.1.6
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 +2 -2
- package/lib/json-schema.d.ts +9 -0
- package/lib/json-schema.js +2 -0
- package/lib/schema.d.ts +27 -5
- package/lib/schema.js +55 -12
- package/lib/validate.js +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -207,8 +207,8 @@ plugins:
|
|
|
207
207
|
| `.default(value)` | 设置默认值 |
|
|
208
208
|
| `.option(label, value)` | 添加可选项 |
|
|
209
209
|
| `.multiple()` | 允许多选 |
|
|
210
|
-
| `.min(value)` |
|
|
211
|
-
| `.max(value)` |
|
|
210
|
+
| `.min(value)` | 设置最小值(数字下限 / 字符串最小长度 / 列表最少项);写入导出的 JSON Schema,由 Runtime 配置校验执行,`schema(value)` 与 `safeParse` 只做结构校验 |
|
|
211
|
+
| `.max(value)` | 设置最大值,规则同 `.min` |
|
|
212
212
|
| `.step(value)` | 设置步长 |
|
|
213
213
|
| `.component(name)` | 指定渲染组件 |
|
|
214
214
|
| `.toJSON()` | 业务 Schema 的内部序列化 |
|
package/lib/json-schema.d.ts
CHANGED
|
@@ -12,6 +12,15 @@ export interface ConfigFieldMetadata {
|
|
|
12
12
|
readonly component?: string;
|
|
13
13
|
readonly options?: readonly Schema.Option[];
|
|
14
14
|
readonly effect?: 'reload';
|
|
15
|
+
/** Property-level narrowing of the Plugin's feature levels (ADR 0050 §2). */
|
|
16
|
+
readonly levels?: readonly ('global' | 'adapter' | 'endpoint' | 'conversation')[];
|
|
17
|
+
}
|
|
18
|
+
/** Root `x-zhin-config` of a Plugin schema: layered levels (ADR 0050 / 0055). */
|
|
19
|
+
export interface ConfigFeatureLevels {
|
|
20
|
+
readonly levels: readonly ('global' | 'adapter' | 'endpoint' | 'conversation')[];
|
|
21
|
+
readonly conversationFields?: readonly string[];
|
|
22
|
+
readonly selfServiceFields?: readonly string[];
|
|
23
|
+
readonly chatAdminFields?: readonly string[];
|
|
15
24
|
}
|
|
16
25
|
export type ConfigJsonSchema = Readonly<Record<string, unknown>>;
|
|
17
26
|
/** Export serializable configuration declarations; non-JSON business types are explicit errors. */
|
package/lib/json-schema.js
CHANGED
|
@@ -100,6 +100,8 @@ export function toJsonSchema(schema) {
|
|
|
100
100
|
...(meta.component !== undefined ? { component: meta.component } : {}),
|
|
101
101
|
...(meta.options !== undefined ? { options: structuredClone(meta.options) } : {}),
|
|
102
102
|
...(meta.effect !== undefined ? { effect: meta.effect } : {}),
|
|
103
|
+
...(meta.feature !== undefined ? structuredClone(meta.feature) : {}),
|
|
104
|
+
...(meta.fieldLevels !== undefined ? { levels: [...meta.fieldLevels] } : {}),
|
|
103
105
|
};
|
|
104
106
|
if (Object.keys(annotations).length)
|
|
105
107
|
result['x-zhin-config'] = annotations;
|
package/lib/schema.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { safeParseSchema, type SchemaSafeParseResult } from './validate.js';
|
|
2
|
-
import { type ConfigFieldMetadata, type ConfigFieldI18n } from './json-schema.js';
|
|
2
|
+
import { type ConfigFeatureLevels, type ConfigFieldMetadata, type ConfigFieldI18n } from './json-schema.js';
|
|
3
3
|
export { SchemaValidationError, type SchemaSafeParseResult, type SchemaValidationIssue } from './validate.js';
|
|
4
4
|
export { safeParseSchema };
|
|
5
5
|
declare const configPresent: unique symbol;
|
|
@@ -31,13 +31,31 @@ export declare class Schema<S = any, T = S> {
|
|
|
31
31
|
secret(): this;
|
|
32
32
|
/** Declare the effect shown in configuration forms. */
|
|
33
33
|
effect(effect: ConfigFieldMetadata['effect']): this;
|
|
34
|
+
/**
|
|
35
|
+
* Plugin root only: layered feature levels (ADR 0050 / 0055) and the fields editable at the
|
|
36
|
+
* conversation level, in chat by group admins, and in chat above the conversation level.
|
|
37
|
+
*/
|
|
38
|
+
featureLevels(levels: ConfigFeatureLevels['levels'], fields?: Omit<ConfigFeatureLevels, 'levels'>): this;
|
|
39
|
+
/**
|
|
40
|
+
* Plugin property only: narrow the feature levels that may override this field (ADR 0050 §2),
|
|
41
|
+
* e.g. `.fieldLevels(['global'])` for secrets and process-wide resources. It can never widen
|
|
42
|
+
* the root `featureLevels` declaration; the Runtime rejects a wider list at startup.
|
|
43
|
+
*/
|
|
44
|
+
fieldLevels(levels: ConfigFeatureLevels['levels']): this;
|
|
34
45
|
/** Export the standard JSON Schema consumed by Runtime and Console. */
|
|
35
46
|
toJsonSchema(): Readonly<Record<string, unknown>>;
|
|
36
47
|
/** 设置选项列表 */
|
|
37
48
|
option(list: (T | Schema.Option<T>)[]): this;
|
|
38
49
|
/** 设置是否允许多选 */
|
|
39
50
|
multiple(): this;
|
|
40
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* 设置最小值。
|
|
53
|
+
* `min` / `max` / `step` are range constraints of the exported JSON Schema
|
|
54
|
+
* (minimum/minLength/minItems, maximum/…, multipleOf) and are enforced where that
|
|
55
|
+
* schema is compiled (Runtime config validation). The callable schema and
|
|
56
|
+
* `safeParse` are structural only; `toJsonSchema` relies on that when it matches
|
|
57
|
+
* union branches (see assertPublicDefault).
|
|
58
|
+
*/
|
|
41
59
|
min(value: number): this;
|
|
42
60
|
/** 设置最大值 */
|
|
43
61
|
max(value: number): this;
|
|
@@ -57,7 +75,9 @@ export declare class Schema<S = any, T = S> {
|
|
|
57
75
|
static date(key?: string): Schema<number | Date, Date>;
|
|
58
76
|
/** 声明一个字典类型 */
|
|
59
77
|
static dict<X extends Schema>(input: X, key?: string): Schema<Record<string, Schema.Input<X>>, Record<string, Schema.Output<X>>>;
|
|
60
|
-
static object<X extends Record<string, Schema<any, any>>>(input: X, key?: string): Schema<Schema.InputRecord<X>, Schema.RecordTypes<X
|
|
78
|
+
static object<X extends Record<string, Schema<any, any>>>(input: X, key?: string): Schema<Schema.InputRecord<X>, Schema.RecordTypes<X>> & {
|
|
79
|
+
readonly output?: Schema.RecordTypes<X>;
|
|
80
|
+
};
|
|
61
81
|
/** 声明一个列表类型 */
|
|
62
82
|
static list<X extends Schema>(inner: X, key?: string): Schema<Schema.Input<X>[], Schema.Output<X>[]>;
|
|
63
83
|
/** 声明一个元组类型 */
|
|
@@ -123,6 +143,8 @@ export declare namespace Schema {
|
|
|
123
143
|
step?: number;
|
|
124
144
|
secret?: boolean;
|
|
125
145
|
effect?: ConfigFieldMetadata['effect'];
|
|
146
|
+
feature?: ConfigFeatureLevels;
|
|
147
|
+
fieldLevels?: ConfigFeatureLevels['levels'];
|
|
126
148
|
}
|
|
127
149
|
interface Options {
|
|
128
150
|
object?: Record<string, Schema>;
|
|
@@ -130,11 +152,11 @@ export declare namespace Schema {
|
|
|
130
152
|
list?: readonly Schema[];
|
|
131
153
|
}
|
|
132
154
|
type Types<T> = Output<T>;
|
|
133
|
-
type RecordTypes<T
|
|
155
|
+
type RecordTypes<T extends Record<string, any>> = {
|
|
134
156
|
[K in keyof T as T[K] extends Present ? K : never]-?: Output<T[K]>;
|
|
135
157
|
} & {
|
|
136
158
|
[K in keyof T as T[K] extends Present ? never : K]?: Output<T[K]>;
|
|
137
|
-
}
|
|
159
|
+
};
|
|
138
160
|
type Union<T extends readonly any[]> = T extends readonly [infer L, ...infer R] ? Types<L> | Union<R> : never;
|
|
139
161
|
type Intersect<T extends readonly any[]> = T extends readonly [infer L, ...infer R] ? Types<L> & Intersect<R> : unknown;
|
|
140
162
|
type Tuple<T extends readonly any[]> = T extends readonly [infer L, ...infer R] ? [Types<L>, ...Tuple<R>] : [];
|
package/lib/schema.js
CHANGED
|
@@ -24,6 +24,9 @@ export class Schema {
|
|
|
24
24
|
set(target, p, value, receiver) {
|
|
25
25
|
return Reflect.set(_this, p, value, receiver);
|
|
26
26
|
},
|
|
27
|
+
has(target, p) {
|
|
28
|
+
return Reflect.has(_this, p) || Reflect.has(target, p);
|
|
29
|
+
},
|
|
27
30
|
});
|
|
28
31
|
}
|
|
29
32
|
static fromJSON(json) {
|
|
@@ -99,6 +102,27 @@ export class Schema {
|
|
|
99
102
|
this.meta.effect = effect;
|
|
100
103
|
return this;
|
|
101
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Plugin root only: layered feature levels (ADR 0050 / 0055) and the fields editable at the
|
|
107
|
+
* conversation level, in chat by group admins, and in chat above the conversation level.
|
|
108
|
+
*/
|
|
109
|
+
featureLevels(levels, fields = {}) {
|
|
110
|
+
if (this.meta.type !== 'object')
|
|
111
|
+
throw new TypeError('featureLevels only supports the plugin root object');
|
|
112
|
+
this.meta.feature = structuredClone({ levels, ...fields });
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Plugin property only: narrow the feature levels that may override this field (ADR 0050 §2),
|
|
117
|
+
* e.g. `.fieldLevels(['global'])` for secrets and process-wide resources. It can never widen
|
|
118
|
+
* the root `featureLevels` declaration; the Runtime rejects a wider list at startup.
|
|
119
|
+
*/
|
|
120
|
+
fieldLevels(levels) {
|
|
121
|
+
if (!levels.length || !levels.includes('global'))
|
|
122
|
+
throw new TypeError('fieldLevels must include global');
|
|
123
|
+
this.meta.fieldLevels = [...levels];
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
102
126
|
/** Export the standard JSON Schema consumed by Runtime and Console. */
|
|
103
127
|
toJsonSchema() {
|
|
104
128
|
return toJsonSchema(this);
|
|
@@ -115,7 +139,14 @@ export class Schema {
|
|
|
115
139
|
this.meta.multiple = true;
|
|
116
140
|
return this;
|
|
117
141
|
}
|
|
118
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* 设置最小值。
|
|
144
|
+
* `min` / `max` / `step` are range constraints of the exported JSON Schema
|
|
145
|
+
* (minimum/minLength/minItems, maximum/…, multipleOf) and are enforced where that
|
|
146
|
+
* schema is compiled (Runtime config validation). The callable schema and
|
|
147
|
+
* `safeParse` are structural only; `toJsonSchema` relies on that when it matches
|
|
148
|
+
* union branches (see assertPublicDefault).
|
|
149
|
+
*/
|
|
119
150
|
min(value) {
|
|
120
151
|
this.meta.min = value;
|
|
121
152
|
return this;
|
|
@@ -221,7 +252,7 @@ export class Schema {
|
|
|
221
252
|
throw new TypeError(`${key} is not a string`);
|
|
222
253
|
break;
|
|
223
254
|
case 'number':
|
|
224
|
-
if (!['number', 'undefined'].includes(typeof value))
|
|
255
|
+
if (!['number', 'undefined'].includes(typeof value) || (typeof value === 'number' && Number.isNaN(value)))
|
|
225
256
|
throw new TypeError(`${key} is not a number`);
|
|
226
257
|
break;
|
|
227
258
|
case 'boolean':
|
|
@@ -239,11 +270,11 @@ export class Schema {
|
|
|
239
270
|
throw new TypeError(`${key} is not a valid Date`);
|
|
240
271
|
break;
|
|
241
272
|
case 'dict':
|
|
242
|
-
if (!['object', 'undefined'
|
|
273
|
+
if (!['object', 'undefined'].includes(typeof value) || value === null || Array.isArray(value))
|
|
243
274
|
throw new TypeError(`${key} is not a object`);
|
|
244
275
|
break;
|
|
245
276
|
case 'object':
|
|
246
|
-
if (!['object', 'undefined'
|
|
277
|
+
if (!['object', 'undefined'].includes(typeof value) || value === null || Array.isArray(value))
|
|
247
278
|
throw new TypeError(`${key} is not a object`);
|
|
248
279
|
break;
|
|
249
280
|
case 'list':
|
|
@@ -310,19 +341,27 @@ Schema.extend('dict', function (key, value) {
|
|
|
310
341
|
Schema.extend('object', function (key, value) {
|
|
311
342
|
const getDefault = (schema) => {
|
|
312
343
|
const result = Object.create(null);
|
|
313
|
-
for (const
|
|
314
|
-
const propSchema = schema.options.object[
|
|
315
|
-
result[
|
|
344
|
+
for (const propKey in schema.options.object) {
|
|
345
|
+
const propSchema = schema.options.object[propKey];
|
|
346
|
+
result[propKey] = propSchema.meta.default !== undefined ? propSchema.meta.default : undefined;
|
|
316
347
|
}
|
|
317
348
|
return result;
|
|
318
349
|
};
|
|
319
350
|
value = Schema.checkDefault(this, key, value, getDefault(this));
|
|
351
|
+
if (value === undefined)
|
|
352
|
+
return value;
|
|
320
353
|
const result = Object.create(null);
|
|
321
|
-
|
|
322
|
-
|
|
354
|
+
const shape = this.options.object || {};
|
|
355
|
+
const allKeys = new Set([...Object.keys(shape), ...Object.keys(value)]);
|
|
356
|
+
for (const k of allKeys) {
|
|
357
|
+
const val = value[k];
|
|
358
|
+
const propSchema = shape[k];
|
|
323
359
|
if (propSchema) {
|
|
324
360
|
// propSchema是一个可调用的Schema实例
|
|
325
|
-
|
|
361
|
+
const evaluated = propSchema(val, `${key}.${k}`);
|
|
362
|
+
if (evaluated !== undefined || k in value) {
|
|
363
|
+
result[k] = evaluated;
|
|
364
|
+
}
|
|
326
365
|
}
|
|
327
366
|
else {
|
|
328
367
|
result[k] = val;
|
|
@@ -355,8 +394,12 @@ Schema.extend('const', function (key, value) {
|
|
|
355
394
|
return value;
|
|
356
395
|
});
|
|
357
396
|
Schema.extend('tuple', function (key, value) {
|
|
358
|
-
|
|
359
|
-
|
|
397
|
+
// No `[]` fallback: an empty array is not a valid tuple (JSON Schema exports
|
|
398
|
+
// minItems = maxItems = length, and safeParse rejects it), so absent stays absent.
|
|
399
|
+
value = Schema.checkDefault(this, key, value);
|
|
400
|
+
if (value === undefined)
|
|
401
|
+
return value;
|
|
402
|
+
if (value.length !== this.options.list.length)
|
|
360
403
|
throw new TypeError(`${key} tuple length mismatch`);
|
|
361
404
|
return value.map((item, index) => this.options.list[index](item, `${key}[${index}]`));
|
|
362
405
|
});
|
package/lib/validate.js
CHANGED
|
@@ -29,7 +29,7 @@ function validateOnly(schema, path, value) {
|
|
|
29
29
|
}
|
|
30
30
|
return value;
|
|
31
31
|
case 'number':
|
|
32
|
-
if (value !== undefined && typeof value !== 'number') {
|
|
32
|
+
if (value !== undefined && (typeof value !== 'number' || Number.isNaN(value))) {
|
|
33
33
|
throw issue(path, `${path} is not a number`);
|
|
34
34
|
}
|
|
35
35
|
return value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/schema",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Configuration validation and schema system for Zhin.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"lib"
|
|
16
16
|
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.19.0"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
17
21
|
"devDependencies": {
|
|
18
22
|
"@types/node": "^26.1.2",
|
|
19
23
|
"typescript": "^6.0.3"
|