@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.
@@ -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;
@@ -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> = T extends Record<string, Schema> ? {
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
- } : unknown;
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', 'null'].includes(typeof value))
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', 'null'].includes(typeof value))
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 key in schema.options.object) {
309
- const propSchema = schema.options.object[key];
310
- result[key] = propSchema.default !== undefined ? propSchema.default : undefined;
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
- for (const [k, val] of Object.entries(value)) {
317
- const propSchema = this.options.object[k];
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
- result[k] = propSchema(val, `${key}.${k}`);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/schema",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Configuration validation and schema system for Zhin.js",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",