@zhin.js/schema 1.1.3 → 1.1.5
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/lib/json-schema.d.ts +6 -0
- package/lib/json-schema.js +2 -0
- package/lib/schema.d.ts +9 -4
- package/lib/schema.js +25 -9
- package/lib/validate.js +1 -1
- package/package.json +1 -1
package/lib/json-schema.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { Schema } from './schema.js';
|
|
2
|
+
/** Optional display metadata; translated labels never change stored values. */
|
|
3
|
+
export type ConfigFieldI18n = Readonly<Partial<Record<'zh-CN' | 'en', Readonly<{
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
enumLabels?: Readonly<Record<string, string>>;
|
|
7
|
+
}>>>>;
|
|
2
8
|
/** Form annotations on the same JSON Schema used for configuration validation. */
|
|
3
9
|
export interface ConfigFieldMetadata {
|
|
4
10
|
readonly secret?: boolean;
|
package/lib/json-schema.js
CHANGED
|
@@ -69,6 +69,8 @@ export function toJsonSchema(schema) {
|
|
|
69
69
|
}
|
|
70
70
|
if (meta.description !== undefined)
|
|
71
71
|
result.description = meta.description;
|
|
72
|
+
if (meta.i18n !== undefined)
|
|
73
|
+
result['x-i18n'] = structuredClone(meta.i18n);
|
|
72
74
|
if (meta.default !== undefined) {
|
|
73
75
|
assertJsonValue(meta.default);
|
|
74
76
|
assertPublicDefault(schema, meta.default);
|
package/lib/schema.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { safeParseSchema, type SchemaSafeParseResult } from './validate.js';
|
|
2
|
-
import { type ConfigFieldMetadata } from './json-schema.js';
|
|
2
|
+
import { 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;
|
|
@@ -23,6 +23,8 @@ export declare class Schema<S = any, T = S> {
|
|
|
23
23
|
hidden(): this;
|
|
24
24
|
/** 设置描述 */
|
|
25
25
|
description(description: string): this;
|
|
26
|
+
/** Translate form labels without changing validation, defaults or values. */
|
|
27
|
+
i18n(translations: ConfigFieldI18n): this;
|
|
26
28
|
/** 设置默认值 */
|
|
27
29
|
default(defaultValue: T): this & Schema.Present;
|
|
28
30
|
/** Secret values remain environment references in application configuration. */
|
|
@@ -55,7 +57,9 @@ export declare class Schema<S = any, T = S> {
|
|
|
55
57
|
static date(key?: string): Schema<number | Date, Date>;
|
|
56
58
|
/** 声明一个字典类型 */
|
|
57
59
|
static dict<X extends Schema>(input: X, key?: string): Schema<Record<string, Schema.Input<X>>, Record<string, Schema.Output<X>>>;
|
|
58
|
-
static object<X extends Record<string, Schema<any, any>>>(input: X, key?: string): Schema<Schema.InputRecord<X>, Schema.RecordTypes<X
|
|
60
|
+
static object<X extends Record<string, Schema<any, any>>>(input: X, key?: string): Schema<Schema.InputRecord<X>, Schema.RecordTypes<X>> & {
|
|
61
|
+
readonly output?: Schema.RecordTypes<X>;
|
|
62
|
+
};
|
|
59
63
|
/** 声明一个列表类型 */
|
|
60
64
|
static list<X extends Schema>(inner: X, key?: string): Schema<Schema.Input<X>[], Schema.Output<X>[]>;
|
|
61
65
|
/** 声明一个元组类型 */
|
|
@@ -114,6 +118,7 @@ export declare namespace Schema {
|
|
|
114
118
|
multiple?: boolean;
|
|
115
119
|
key?: string;
|
|
116
120
|
description?: string;
|
|
121
|
+
i18n?: ConfigFieldI18n;
|
|
117
122
|
component?: string;
|
|
118
123
|
min?: number;
|
|
119
124
|
max?: number;
|
|
@@ -127,11 +132,11 @@ export declare namespace Schema {
|
|
|
127
132
|
list?: readonly Schema[];
|
|
128
133
|
}
|
|
129
134
|
type Types<T> = Output<T>;
|
|
130
|
-
type RecordTypes<T
|
|
135
|
+
type RecordTypes<T extends Record<string, any>> = {
|
|
131
136
|
[K in keyof T as T[K] extends Present ? K : never]-?: Output<T[K]>;
|
|
132
137
|
} & {
|
|
133
138
|
[K in keyof T as T[K] extends Present ? never : K]?: Output<T[K]>;
|
|
134
|
-
}
|
|
139
|
+
};
|
|
135
140
|
type Union<T extends readonly any[]> = T extends readonly [infer L, ...infer R] ? Types<L> | Union<R> : never;
|
|
136
141
|
type Intersect<T extends readonly any[]> = T extends readonly [infer L, ...infer R] ? Types<L> & Intersect<R> : unknown;
|
|
137
142
|
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) {
|
|
@@ -75,6 +78,11 @@ export class Schema {
|
|
|
75
78
|
this.meta.description = description;
|
|
76
79
|
return this;
|
|
77
80
|
}
|
|
81
|
+
/** Translate form labels without changing validation, defaults or values. */
|
|
82
|
+
i18n(translations) {
|
|
83
|
+
this.meta.i18n = structuredClone(translations);
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
78
86
|
/** 设置默认值 */
|
|
79
87
|
default(defaultValue) {
|
|
80
88
|
this.meta.default = defaultValue;
|
|
@@ -216,7 +224,7 @@ export class Schema {
|
|
|
216
224
|
throw new TypeError(`${key} is not a string`);
|
|
217
225
|
break;
|
|
218
226
|
case 'number':
|
|
219
|
-
if (!['number', 'undefined'].includes(typeof value))
|
|
227
|
+
if (!['number', 'undefined'].includes(typeof value) || (typeof value === 'number' && Number.isNaN(value)))
|
|
220
228
|
throw new TypeError(`${key} is not a number`);
|
|
221
229
|
break;
|
|
222
230
|
case 'boolean':
|
|
@@ -234,11 +242,11 @@ export class Schema {
|
|
|
234
242
|
throw new TypeError(`${key} is not a valid Date`);
|
|
235
243
|
break;
|
|
236
244
|
case 'dict':
|
|
237
|
-
if (!['object', 'undefined'
|
|
245
|
+
if (!['object', 'undefined'].includes(typeof value) || value === null || Array.isArray(value))
|
|
238
246
|
throw new TypeError(`${key} is not a object`);
|
|
239
247
|
break;
|
|
240
248
|
case 'object':
|
|
241
|
-
if (!['object', 'undefined'
|
|
249
|
+
if (!['object', 'undefined'].includes(typeof value) || value === null || Array.isArray(value))
|
|
242
250
|
throw new TypeError(`${key} is not a object`);
|
|
243
251
|
break;
|
|
244
252
|
case 'list':
|
|
@@ -305,19 +313,27 @@ Schema.extend('dict', function (key, value) {
|
|
|
305
313
|
Schema.extend('object', function (key, value) {
|
|
306
314
|
const getDefault = (schema) => {
|
|
307
315
|
const result = Object.create(null);
|
|
308
|
-
for (const
|
|
309
|
-
const propSchema = schema.options.object[
|
|
310
|
-
result[
|
|
316
|
+
for (const propKey in schema.options.object) {
|
|
317
|
+
const propSchema = schema.options.object[propKey];
|
|
318
|
+
result[propKey] = propSchema.meta.default !== undefined ? propSchema.meta.default : undefined;
|
|
311
319
|
}
|
|
312
320
|
return result;
|
|
313
321
|
};
|
|
314
322
|
value = Schema.checkDefault(this, key, value, getDefault(this));
|
|
323
|
+
if (value === undefined)
|
|
324
|
+
return value;
|
|
315
325
|
const result = Object.create(null);
|
|
316
|
-
|
|
317
|
-
|
|
326
|
+
const shape = this.options.object || {};
|
|
327
|
+
const allKeys = new Set([...Object.keys(shape), ...Object.keys(value)]);
|
|
328
|
+
for (const k of allKeys) {
|
|
329
|
+
const val = value[k];
|
|
330
|
+
const propSchema = shape[k];
|
|
318
331
|
if (propSchema) {
|
|
319
332
|
// propSchema是一个可调用的Schema实例
|
|
320
|
-
|
|
333
|
+
const evaluated = propSchema(val, `${key}.${k}`);
|
|
334
|
+
if (evaluated !== undefined || k in value) {
|
|
335
|
+
result[k] = evaluated;
|
|
336
|
+
}
|
|
321
337
|
}
|
|
322
338
|
else {
|
|
323
339
|
result[k] = val;
|
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;
|