@sinclair/typebox 0.22.1 → 0.23.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/typebox.d.ts +25 -12
  3. package/typebox.js +98 -64
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sinclair/typebox",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "description": "JSONSchema Type Builder with Static Type Resolution for TypeScript",
5
5
  "keywords": [
6
6
  "json-schema",
package/typebox.d.ts CHANGED
@@ -28,6 +28,7 @@ export declare const BooleanKind: unique symbol;
28
28
  export declare const NullKind: unique symbol;
29
29
  export declare const UnknownKind: unique symbol;
30
30
  export declare const AnyKind: unique symbol;
31
+ export declare const RefKind: unique symbol;
31
32
  export interface CustomOptions {
32
33
  $id?: string;
33
34
  title?: string;
@@ -174,6 +175,11 @@ export interface TAny extends TSchema, CustomOptions {
174
175
  $static: any;
175
176
  kind: typeof AnyKind;
176
177
  }
178
+ export interface TRef<T extends TSchema> extends TSchema, CustomOptions {
179
+ $static: Static<T>;
180
+ kind: typeof RefKind;
181
+ $ref: string;
182
+ }
177
183
  export declare const ConstructorKind: unique symbol;
178
184
  export declare const FunctionKind: unique symbol;
179
185
  export declare const PromiseKind: unique symbol;
@@ -209,6 +215,9 @@ export interface TVoid extends TSchema, CustomOptions {
209
215
  kind: typeof VoidKind;
210
216
  type: 'void';
211
217
  }
218
+ export declare type Pickable = TObject<TProperties> | TRef<TObject<TProperties>>;
219
+ export declare type PickablePropertyKeys<T extends Pickable> = T extends TObject<infer U> ? keyof U : T extends TRef<TObject<infer U>> ? keyof U : never;
220
+ export declare type PickableProperties<T extends Pickable> = T extends TObject<infer U> ? U : T extends TRef<TObject<infer U>> ? U : never;
212
221
  export declare type UnionToIntersect<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
213
222
  export declare type StaticReadonlyOptionalPropertyKeys<T extends TProperties> = {
214
223
  [K in keyof T]: T[K] extends TReadonlyOptional<TSchema> ? K : never;
@@ -262,6 +271,7 @@ export declare type StaticFunction<T extends readonly TSchema[], U extends TSche
262
271
  export declare type StaticPromise<T extends TSchema> = Promise<Static<T>>;
263
272
  export declare type Static<T extends TSchema> = T['$static'];
264
273
  export declare class TypeBuilder {
274
+ private readonly schemas;
265
275
  /** `Standard` Modifies an object property to be both readonly and optional */
266
276
  ReadonlyOptional<T extends TSchema>(item: T): TReadonlyOptional<T>;
267
277
  /** `Standard` Modifies an object property to be readonly */
@@ -299,17 +309,17 @@ export declare class TypeBuilder {
299
309
  /** `Standard` Creates an any type */
300
310
  Any(options?: CustomOptions): TAny;
301
311
  /** `Standard` Creates a keyof type from the given object */
302
- KeyOf<T extends TObject<TProperties>>(schema: T, options?: CustomOptions): TKeyOf<(keyof T['properties'])[]>;
312
+ KeyOf<T extends TObject<TProperties>>(object: T, options?: CustomOptions): TKeyOf<(keyof T['properties'])[]>;
303
313
  /** `Standard` Creates a record type */
304
314
  Record<K extends TRecordKey, T extends TSchema>(key: K, value: T, options?: ObjectOptions): TRecord<K, T>;
305
315
  /** `Standard` Makes all properties in the given object type required */
306
- Required<T extends TObject<any>>(schema: T, options?: ObjectOptions): TObject<StaticRequired<T['properties']>>;
316
+ Required<T extends TObject<TProperties> | TRef<TObject<TProperties>>>(object: T, options?: ObjectOptions): TObject<StaticRequired<T['properties']>>;
307
317
  /** `Standard` Makes all properties in the given object type optional */
308
- Partial<T extends TObject<any>>(schema: T, options?: ObjectOptions): TObject<StaticPartial<T['properties']>>;
318
+ Partial<T extends TObject<TProperties> | TRef<TObject<TProperties>>>(object: T, options?: ObjectOptions): TObject<StaticPartial<T['properties']>>;
309
319
  /** `Standard` Picks property keys from the given object type */
310
- Pick<T extends TObject<TProperties>, K extends (keyof T['properties'])[]>(schema: T, keys: [...K], options?: ObjectOptions): TObject<Pick<T['properties'], K[number]>>;
320
+ Pick<T extends TObject<TProperties> | TRef<TObject<TProperties>>, K extends PickablePropertyKeys<T>[]>(object: T, keys: [...K], options?: ObjectOptions): TObject<Pick<PickableProperties<T>, K[number]>>;
311
321
  /** `Standard` Omits property keys from the given object type */
312
- Omit<T extends TObject<any>, K extends (keyof T['properties'])[]>(schema: T, keys: [...K], options?: ObjectOptions): TObject<Omit<T['properties'], K[number]>>;
322
+ Omit<T extends TObject<TProperties> | TRef<TObject<TProperties>>, K extends PickablePropertyKeys<T>[]>(object: T, keys: [...K], options?: ObjectOptions): TObject<Omit<PickableProperties<T>, K[number]>>;
313
323
  /** `Standard` Omits the `kind` and `modifier` properties from the underlying schema */
314
324
  Strict<T extends TSchema>(schema: T, options?: CustomOptions): T;
315
325
  /** `Extended` Creates a constructor type */
@@ -322,14 +332,17 @@ export declare class TypeBuilder {
322
332
  Undefined(options?: CustomOptions): TUndefined;
323
333
  /** `Extended` Creates a void type */
324
334
  Void(options?: CustomOptions): TVoid;
335
+ /** `Standard` Creates a namespace for a set of related types */
336
+ Namespace<T extends TDefinitions>($defs: T, options?: CustomOptions): TNamespace<T>;
337
+ /** `Standard` References a type within a namespace. The referenced namespace must specify an `$id` */
338
+ Ref<T extends TNamespace<TDefinitions>, K extends keyof T['$defs']>(box: T, key: K): TRef<T['$defs'][K]>;
339
+ /** `Standard` References type. The referenced type must specify an `$id` */
340
+ Ref<T extends TSchema>(schema: T): TRef<T>;
325
341
  /** `Experimental` Creates a recursive type */
326
342
  Rec<T extends TSchema>(callback: (self: TAny) => T, options?: CustomOptions): T;
327
- /** `Experimental` Creates a recursive type. Pending https://github.com/ajv-validator/ajv/issues/1709 */
328
- /** `Experimental` Creates a namespace for a set of related types */
329
- Namespace<T extends TDefinitions>($defs: T, options?: CustomOptions): TNamespace<T>;
330
- /** `Experimental` References a type within a namespace. The referenced namespace must specify an `$id` */
331
- Ref<T extends TNamespace<TDefinitions>, K extends keyof T['$defs']>(box: T, key: K): T['$defs'][K];
332
- /** `Experimental` References type. The referenced type must specify an `$id` */
333
- Ref<T extends TSchema>(schema: T): T;
343
+ /** Stores this schema if it contains an $id. This function is used for later referencing. */
344
+ private Store;
345
+ /** Resolves a schema by $id. May resolve recursively if the target is a TRef. */
346
+ private Resolve;
334
347
  }
335
348
  export declare const Type: TypeBuilder;
package/typebox.js CHANGED
@@ -27,7 +27,7 @@ THE SOFTWARE.
27
27
 
28
28
  ---------------------------------------------------------------------------*/
29
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.Type = exports.TypeBuilder = exports.VoidKind = exports.UndefinedKind = exports.PromiseKind = exports.FunctionKind = exports.ConstructorKind = exports.AnyKind = exports.UnknownKind = exports.NullKind = exports.BooleanKind = exports.IntegerKind = exports.NumberKind = exports.StringKind = exports.LiteralKind = exports.EnumKind = exports.ArrayKind = exports.RecordKind = exports.ObjectKind = exports.TupleKind = exports.UnionKind = exports.IntersectKind = exports.KeyOfKind = exports.BoxKind = exports.ReadonlyModifier = exports.OptionalModifier = exports.ReadonlyOptionalModifier = void 0;
30
+ exports.Type = exports.TypeBuilder = exports.VoidKind = exports.UndefinedKind = exports.PromiseKind = exports.FunctionKind = exports.ConstructorKind = exports.RefKind = exports.AnyKind = exports.UnknownKind = exports.NullKind = exports.BooleanKind = exports.IntegerKind = exports.NumberKind = exports.StringKind = exports.LiteralKind = exports.EnumKind = exports.ArrayKind = exports.RecordKind = exports.ObjectKind = exports.TupleKind = exports.UnionKind = exports.IntersectKind = exports.KeyOfKind = exports.BoxKind = exports.ReadonlyModifier = exports.OptionalModifier = exports.ReadonlyOptionalModifier = void 0;
31
31
  // --------------------------------------------------------------------------
32
32
  // Modifiers
33
33
  // --------------------------------------------------------------------------
@@ -54,6 +54,7 @@ exports.BooleanKind = Symbol('BooleanKind');
54
54
  exports.NullKind = Symbol('NullKind');
55
55
  exports.UnknownKind = Symbol('UnknownKind');
56
56
  exports.AnyKind = Symbol('AnyKind');
57
+ exports.RefKind = Symbol('RefKind');
57
58
  // --------------------------------------------------------------------------
58
59
  // Extended Schema Types
59
60
  // --------------------------------------------------------------------------
@@ -82,6 +83,7 @@ function clone(object) {
82
83
  // TypeBuilder
83
84
  // --------------------------------------------------------------------------
84
85
  class TypeBuilder {
86
+ schemas = new Map();
85
87
  /** `Standard` Modifies an object property to be both readonly and optional */
86
88
  ReadonlyOptional(item) {
87
89
  return { ...item, modifier: exports.ReadonlyOptionalModifier };
@@ -99,9 +101,10 @@ class TypeBuilder {
99
101
  const additionalItems = false;
100
102
  const minItems = items.length;
101
103
  const maxItems = items.length;
102
- return ((items.length > 0)
104
+ const schema = ((items.length > 0)
103
105
  ? { ...options, kind: exports.TupleKind, type: 'array', items, additionalItems, minItems, maxItems }
104
106
  : { ...options, kind: exports.TupleKind, type: 'array', minItems, maxItems });
107
+ return this.Store(schema);
105
108
  }
106
109
  /** `Standard` Creates an object type with the given properties */
107
110
  Object(properties, options = {}) {
@@ -114,35 +117,35 @@ class TypeBuilder {
114
117
  });
115
118
  const required_names = property_names.filter(name => !optional.includes(name));
116
119
  const required = (required_names.length > 0) ? required_names : undefined;
117
- return ((required)
120
+ return this.Store(((required)
118
121
  ? { ...options, kind: exports.ObjectKind, type: 'object', properties, required }
119
- : { ...options, kind: exports.ObjectKind, type: 'object', properties });
122
+ : { ...options, kind: exports.ObjectKind, type: 'object', properties }));
120
123
  }
121
124
  /** `Standard` Creates an intersect type. */
122
125
  Intersect(items, options = {}) {
123
- return { ...options, kind: exports.IntersectKind, type: 'object', allOf: items };
126
+ return this.Store({ ...options, kind: exports.IntersectKind, type: 'object', allOf: items });
124
127
  }
125
128
  /** `Standard` Creates a union type */
126
129
  Union(items, options = {}) {
127
- return { ...options, kind: exports.UnionKind, anyOf: items };
130
+ return this.Store({ ...options, kind: exports.UnionKind, anyOf: items });
128
131
  }
129
132
  /** `Standard` Creates an array type */
130
133
  Array(items, options = {}) {
131
- return { ...options, kind: exports.ArrayKind, type: 'array', items };
134
+ return this.Store({ ...options, kind: exports.ArrayKind, type: 'array', items });
132
135
  }
133
136
  /** `Standard` Creates an enum type from a TypeScript enum */
134
137
  Enum(item, options = {}) {
135
138
  const values = Object.keys(item).filter(key => isNaN(key)).map(key => item[key]);
136
139
  const anyOf = values.map(value => typeof value === 'string' ? { type: 'string', const: value } : { type: 'number', const: value });
137
- return { ...options, kind: exports.EnumKind, anyOf };
140
+ return this.Store({ ...options, kind: exports.EnumKind, anyOf });
138
141
  }
139
142
  /** `Standard` Creates a literal type. Supports string, number and boolean values only */
140
143
  Literal(value, options = {}) {
141
- return { ...options, kind: exports.LiteralKind, const: value, type: typeof value };
144
+ return this.Store({ ...options, kind: exports.LiteralKind, const: value, type: typeof value });
142
145
  }
143
146
  /** `Standard` Creates a string type */
144
147
  String(options = {}) {
145
- return { ...options, kind: exports.StringKind, type: 'string' };
148
+ return this.Store({ ...options, kind: exports.StringKind, type: 'string' });
146
149
  }
147
150
  /** `Standard` Creates a string type from a regular expression */
148
151
  RegEx(regex, options = {}) {
@@ -150,32 +153,32 @@ class TypeBuilder {
150
153
  }
151
154
  /** `Standard` Creates a number type */
152
155
  Number(options = {}) {
153
- return { ...options, kind: exports.NumberKind, type: 'number' };
156
+ return this.Store({ ...options, kind: exports.NumberKind, type: 'number' });
154
157
  }
155
158
  /** `Standard` Creates an integer type */
156
159
  Integer(options = {}) {
157
- return { ...options, kind: exports.IntegerKind, type: 'integer' };
160
+ return this.Store({ ...options, kind: exports.IntegerKind, type: 'integer' });
158
161
  }
159
162
  /** `Standard` Creates a boolean type */
160
163
  Boolean(options = {}) {
161
- return { ...options, kind: exports.BooleanKind, type: 'boolean' };
164
+ return this.Store({ ...options, kind: exports.BooleanKind, type: 'boolean' });
162
165
  }
163
166
  /** `Standard` Creates a null type */
164
167
  Null(options = {}) {
165
- return { ...options, kind: exports.NullKind, type: 'null' };
168
+ return this.Store({ ...options, kind: exports.NullKind, type: 'null' });
166
169
  }
167
170
  /** `Standard` Creates an unknown type */
168
171
  Unknown(options = {}) {
169
- return { ...options, kind: exports.UnknownKind };
172
+ return this.Store({ ...options, kind: exports.UnknownKind });
170
173
  }
171
174
  /** `Standard` Creates an any type */
172
175
  Any(options = {}) {
173
- return { ...options, kind: exports.AnyKind };
176
+ return this.Store({ ...options, kind: exports.AnyKind });
174
177
  }
175
178
  /** `Standard` Creates a keyof type from the given object */
176
- KeyOf(schema, options = {}) {
177
- const keys = Object.keys(schema.properties);
178
- return { ...options, kind: exports.KeyOfKind, type: 'string', enum: keys };
179
+ KeyOf(object, options = {}) {
180
+ const keys = Object.keys(object.properties);
181
+ return this.Store({ ...options, kind: exports.KeyOfKind, type: 'string', enum: keys });
179
182
  }
180
183
  /** `Standard` Creates a record type */
181
184
  Record(key, value, options = {}) {
@@ -188,14 +191,15 @@ class TypeBuilder {
188
191
  default: throw Error('Invalid Record Key');
189
192
  }
190
193
  })();
191
- return { ...options, kind: exports.RecordKind, type: 'object', patternProperties: { [pattern]: value } };
194
+ return this.Store({ ...options, kind: exports.RecordKind, type: 'object', patternProperties: { [pattern]: value } });
192
195
  }
193
196
  /** `Standard` Makes all properties in the given object type required */
194
- Required(schema, options = {}) {
195
- const next = { ...clone(schema), ...options };
196
- next.required = Object.keys(next.properties);
197
- for (const key of Object.keys(next.properties)) {
198
- const property = next.properties[key];
197
+ Required(object, options = {}) {
198
+ const source = this.Resolve(object);
199
+ const schema = { ...clone(source), ...options };
200
+ schema.required = Object.keys(schema.properties);
201
+ for (const key of Object.keys(schema.properties)) {
202
+ const property = schema.properties[key];
199
203
  switch (property.modifier) {
200
204
  case exports.ReadonlyOptionalModifier:
201
205
  property.modifier = exports.ReadonlyModifier;
@@ -211,14 +215,15 @@ class TypeBuilder {
211
215
  break;
212
216
  }
213
217
  }
214
- return next;
218
+ return this.Store(schema);
215
219
  }
216
220
  /** `Standard` Makes all properties in the given object type optional */
217
- Partial(schema, options = {}) {
218
- const next = { ...clone(schema), ...options };
219
- delete next.required;
220
- for (const key of Object.keys(next.properties)) {
221
- const property = next.properties[key];
221
+ Partial(object, options = {}) {
222
+ const source = this.Resolve(object);
223
+ const schema = { ...clone(source), ...options };
224
+ delete schema.required;
225
+ for (const key of Object.keys(schema.properties)) {
226
+ const property = schema.properties[key];
222
227
  switch (property.modifier) {
223
228
  case exports.ReadonlyOptionalModifier:
224
229
  property.modifier = exports.ReadonlyOptionalModifier;
@@ -234,27 +239,29 @@ class TypeBuilder {
234
239
  break;
235
240
  }
236
241
  }
237
- return next;
242
+ return this.Store(schema);
238
243
  }
239
244
  /** `Standard` Picks property keys from the given object type */
240
- Pick(schema, keys, options = {}) {
241
- const next = { ...clone(schema), ...options };
242
- next.required = next.required ? next.required.filter((key) => keys.includes(key)) : undefined;
243
- for (const key of Object.keys(next.properties)) {
245
+ Pick(object, keys, options = {}) {
246
+ const source = this.Resolve(object);
247
+ const schema = { ...clone(source), ...options };
248
+ schema.required = schema.required ? schema.required.filter((key) => keys.includes(key)) : undefined;
249
+ for (const key of Object.keys(schema.properties)) {
244
250
  if (!keys.includes(key))
245
- delete next.properties[key];
251
+ delete schema.properties[key];
246
252
  }
247
- return next;
253
+ return this.Store(schema);
248
254
  }
249
255
  /** `Standard` Omits property keys from the given object type */
250
- Omit(schema, keys, options = {}) {
251
- const next = { ...clone(schema), ...options };
252
- next.required = next.required ? next.required.filter((key) => !keys.includes(key)) : undefined;
253
- for (const key of Object.keys(next.properties)) {
256
+ Omit(object, keys, options = {}) {
257
+ const source = this.Resolve(object);
258
+ const schema = { ...clone(source), ...options };
259
+ schema.required = schema.required ? schema.required.filter((key) => !keys.includes(key)) : undefined;
260
+ for (const key of Object.keys(schema.properties)) {
254
261
  if (keys.includes(key))
255
- delete next.properties[key];
262
+ delete schema.properties[key];
256
263
  }
257
- return next;
264
+ return this.Store(schema);
258
265
  }
259
266
  /** `Standard` Omits the `kind` and `modifier` properties from the underlying schema */
260
267
  Strict(schema, options = {}) {
@@ -262,43 +269,70 @@ class TypeBuilder {
262
269
  }
263
270
  /** `Extended` Creates a constructor type */
264
271
  Constructor(args, returns, options = {}) {
265
- return { ...options, kind: exports.ConstructorKind, type: 'constructor', arguments: args, returns };
272
+ return this.Store({ ...options, kind: exports.ConstructorKind, type: 'constructor', arguments: args, returns });
266
273
  }
267
274
  /** `Extended` Creates a function type */
268
275
  Function(args, returns, options = {}) {
269
- return { ...options, kind: exports.FunctionKind, type: 'function', arguments: args, returns };
276
+ return this.Store({ ...options, kind: exports.FunctionKind, type: 'function', arguments: args, returns });
270
277
  }
271
278
  /** `Extended` Creates a promise type */
272
279
  Promise(item, options = {}) {
273
- return { ...options, type: 'promise', kind: exports.PromiseKind, item };
280
+ return this.Store({ ...options, type: 'promise', kind: exports.PromiseKind, item });
274
281
  }
275
282
  /** `Extended` Creates a undefined type */
276
283
  Undefined(options = {}) {
277
- return { ...options, type: 'undefined', kind: exports.UndefinedKind };
284
+ return this.Store({ ...options, type: 'undefined', kind: exports.UndefinedKind });
278
285
  }
279
286
  /** `Extended` Creates a void type */
280
287
  Void(options = {}) {
281
- return { ...options, type: 'void', kind: exports.VoidKind };
288
+ return this.Store({ ...options, type: 'void', kind: exports.VoidKind });
289
+ }
290
+ /** `Standard` Creates a namespace for a set of related types */
291
+ Namespace($defs, options = {}) {
292
+ return this.Store({ ...options, kind: exports.BoxKind, $defs });
293
+ }
294
+ Ref(...args) {
295
+ if (args.length === 2) {
296
+ const namespace = args[0];
297
+ const targetKey = args[1];
298
+ if (namespace.$id === undefined)
299
+ throw new Error(`Referenced namespace has no $id`);
300
+ if (!this.schemas.has(namespace.$id))
301
+ throw new Error(`Unable to locate namespace with $id '${namespace.$id}'`);
302
+ return this.Store({ kind: exports.RefKind, $ref: `${namespace.$id}#/$defs/${targetKey}` });
303
+ }
304
+ else if (args.length === 1) {
305
+ const target = args[0];
306
+ if (target.$id === undefined)
307
+ throw new Error(`Referenced schema has no $id`);
308
+ if (!this.schemas.has(target.$id))
309
+ throw new Error(`Unable to locate schema with $id '${target.$id}'`);
310
+ return this.Store({ kind: exports.RefKind, $ref: target.$id });
311
+ }
312
+ else {
313
+ throw new Error('Type.Ref: Invalid arguments');
314
+ }
282
315
  }
283
316
  /** `Experimental` Creates a recursive type */
284
317
  Rec(callback, options = {}) {
285
318
  const $id = options.$id || '';
286
319
  const self = callback({ $ref: `${$id}#/$defs/self` });
287
- return { ...options, $ref: `${$id}#/$defs/self`, $defs: { self } };
288
- }
289
- /** `Experimental` Creates a recursive type. Pending https://github.com/ajv-validator/ajv/issues/1709 */
290
- // public Rec<T extends TProperties>($id: string, callback: (self: TAny) => T, options: ObjectOptions = {}): TObject<T> {
291
- // const properties = callback({ $recursiveRef: `${$id}` } as any)
292
- // return { ...options, kind: ObjectKind, $id, $recursiveAnchor: true, type: 'object', properties }
293
- // }
294
- /** `Experimental` Creates a namespace for a set of related types */
295
- Namespace($defs, options = {}) {
296
- return { ...options, kind: exports.BoxKind, $defs };
297
- }
298
- Ref(...args) {
299
- const $id = args[0]['$id'] || '';
300
- const key = args[1];
301
- return (args.length === 2) ? { $ref: `${$id}#/$defs/${key}` } : { $ref: $id };
320
+ return this.Store({ ...options, $ref: `${$id}#/$defs/self`, $defs: { self } });
321
+ }
322
+ /** Stores this schema if it contains an $id. This function is used for later referencing. */
323
+ Store(schema) {
324
+ if (!schema.$id)
325
+ return schema;
326
+ this.schemas.set(schema.$id, schema);
327
+ return schema;
328
+ }
329
+ /** Resolves a schema by $id. May resolve recursively if the target is a TRef. */
330
+ Resolve(schema) {
331
+ if (schema.kind !== exports.RefKind)
332
+ return schema;
333
+ if (!this.schemas.has(schema.$ref))
334
+ throw Error(`Unable to locate schema with $id '${schema.$ref}'`);
335
+ return this.Resolve(this.schemas.get(schema.$ref));
302
336
  }
303
337
  }
304
338
  exports.TypeBuilder = TypeBuilder;