mirror of
https://github.com/zoriya/typebox.git
synced 2026-05-31 10:13:09 +00:00
Namespace and Ref Types (#128)
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
## [0.23.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- The types `Type.Namespace()` and `Type.Ref()` are promoted to `Standard`.
|
||||
- TypeBox adds a new type named `TRef<TSchema>` that is returned on calls to `Type.Ref(...)`. The `TRef` includes a `RefKind` symbol for introspection of the reference type.
|
||||
- TypeBox now maintains an internal dictionary of all schemas passed that contain an `$id` property. This dictionary is checked whenever a user attempts to reference a type and will throw if attempting to reference a target schema with no $id.
|
||||
- The types `Type.Partial(...)`, `Type.Required(...)`, `Type.Omit()` and `Type.Pick(...)` now support reference types. Note that when using these functions with references, TypeBox will replicate the source schema and apply the nessasary modifiers to the replication.
|
||||
|
||||
## [0.22.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.22.0)
|
||||
|
||||
Updates:
|
||||
|
||||
+9
-24
@@ -1,30 +1,15 @@
|
||||
import { TypeBuilder, TSchema, Static } from '@sinclair/typebox'
|
||||
import { Type, Static } from '@sinclair/typebox'
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Open API Extended Types
|
||||
// -----------------------------------------------------------
|
||||
const T = Type.Object({
|
||||
name: Type.Optional(Type.String()),
|
||||
order: Type.Number()
|
||||
}, { $id: 'T' })
|
||||
|
||||
export type TNullable<T extends TSchema> = TSchema & {
|
||||
['$static']: Static<T> | null
|
||||
} & { nullable: true }
|
||||
const R = Type.Ref(T)
|
||||
|
||||
export type TStringUnion<T extends string[]> = TSchema & {
|
||||
['$static']: {[K in keyof T]: T[K] }[number]
|
||||
enum: T
|
||||
}
|
||||
const P = Type.Omit(T, ['name'])
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Open API TypeBuilder
|
||||
// -----------------------------------------------------------
|
||||
console.log(P)
|
||||
|
||||
export class OpenApiTypeBuilder extends TypeBuilder {
|
||||
public Nullable<T extends TSchema>(schema: T): TNullable<T> {
|
||||
return { ...schema, nullable: true } as any
|
||||
}
|
||||
type T = Static<typeof P>
|
||||
|
||||
public StringUnion<T extends string[]>(values: [...T]): TStringUnion<T> {
|
||||
return { enum: values } as any
|
||||
}
|
||||
}
|
||||
|
||||
const Type = new OpenApiTypeBuilder()
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sinclair/typebox",
|
||||
"version": "0.22.1",
|
||||
"version": "0.23.0",
|
||||
"description": "JSONSchema Type Builder with Static Type Resolution for TypeScript",
|
||||
"keywords": [
|
||||
"json-schema",
|
||||
|
||||
@@ -33,4 +33,12 @@ describe('Omit', () => {
|
||||
strictEqual(A.additionalProperties, false)
|
||||
strictEqual(T.additionalProperties, false)
|
||||
})
|
||||
|
||||
it('Should construct new object when targetting reference', () => {
|
||||
const T = Type.Object({ a: Type.String(), b: Type.String() }, { $id: 'T' })
|
||||
const R = Type.Ref(T)
|
||||
const P = Type.Omit(R, [])
|
||||
strictEqual(P.properties.a.type, 'string')
|
||||
strictEqual(P.properties.b.type, 'string')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,4 +41,12 @@ describe('Partial', () => {
|
||||
strictEqual(A.additionalProperties, false)
|
||||
strictEqual(T.additionalProperties, false)
|
||||
})
|
||||
|
||||
it('Should construct new object when targetting reference', () => {
|
||||
const T = Type.Object({ a: Type.String(), b: Type.String() }, { $id: 'T' })
|
||||
const R = Type.Ref(T)
|
||||
const P = Type.Partial(R)
|
||||
strictEqual(P.properties.a.type, 'string')
|
||||
strictEqual(P.properties.b.type, 'string')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,4 +33,11 @@ describe('Pick', () => {
|
||||
strictEqual(A.additionalProperties, false)
|
||||
strictEqual(T.additionalProperties, false)
|
||||
})
|
||||
it('Should construct new object when targetting reference', () => {
|
||||
const T = Type.Object({ a: Type.String(), b: Type.String() }, { $id: 'T' })
|
||||
const R = Type.Ref(T)
|
||||
const P = Type.Pick(R, ['a', 'b'])
|
||||
strictEqual(P.properties.a.type, 'string')
|
||||
strictEqual(P.properties.b.type, 'string')
|
||||
})
|
||||
})
|
||||
|
||||
+12
-12
@@ -30,18 +30,18 @@ describe('Ref', () => {
|
||||
}, [T])
|
||||
})
|
||||
|
||||
it('Should should not validate when not specifying an $id', () => {
|
||||
const T = Type.Object({
|
||||
x: Type.Number(),
|
||||
y: Type.Number(),
|
||||
z: Type.Number()
|
||||
}, { })
|
||||
const R = Type.Ref(T)
|
||||
fail(R, {
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3
|
||||
}, [T])
|
||||
it('Should throw when not specifying an $id on target schema', () => {
|
||||
try {
|
||||
const T = Type.Object({
|
||||
x: Type.Number(),
|
||||
y: Type.Number(),
|
||||
z: Type.Number()
|
||||
}, { })
|
||||
const R = Type.Ref(T)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
throw Error('Expected throw')
|
||||
})
|
||||
|
||||
it('Should not validate when not adding additional schema', () => {
|
||||
|
||||
@@ -41,4 +41,12 @@ describe('Required', () => {
|
||||
strictEqual(A.additionalPropeties, false)
|
||||
strictEqual(T.additionalPropeties, false)
|
||||
})
|
||||
|
||||
it('Should construct new object when targetting reference', () => {
|
||||
const T = Type.Object({ a: Type.String(), b: Type.String() }, { $id: 'T' })
|
||||
const R = Type.Ref(T)
|
||||
const P = Type.Required(R)
|
||||
strictEqual(P.properties.a.type, 'string')
|
||||
strictEqual(P.properties.b.type, 'string')
|
||||
})
|
||||
})
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import * as Spec from './spec'
|
||||
import { Type } from './typebox'
|
||||
|
||||
{
|
||||
const T = Type.String()
|
||||
const T = Type.String({ $id: 'T' })
|
||||
|
||||
const R = Type.Ref(T)
|
||||
|
||||
|
||||
Vendored
+25
-12
@@ -28,6 +28,7 @@ export declare const BooleanKind: unique symbol;
|
||||
export declare const NullKind: unique symbol;
|
||||
export declare const UnknownKind: unique symbol;
|
||||
export declare const AnyKind: unique symbol;
|
||||
export declare const RefKind: unique symbol;
|
||||
export interface CustomOptions {
|
||||
$id?: string;
|
||||
title?: string;
|
||||
@@ -174,6 +175,11 @@ export interface TAny extends TSchema, CustomOptions {
|
||||
$static: any;
|
||||
kind: typeof AnyKind;
|
||||
}
|
||||
export interface TRef<T extends TSchema> extends TSchema, CustomOptions {
|
||||
$static: Static<T>;
|
||||
kind: typeof RefKind;
|
||||
$ref: string;
|
||||
}
|
||||
export declare const ConstructorKind: unique symbol;
|
||||
export declare const FunctionKind: unique symbol;
|
||||
export declare const PromiseKind: unique symbol;
|
||||
@@ -209,6 +215,9 @@ export interface TVoid extends TSchema, CustomOptions {
|
||||
kind: typeof VoidKind;
|
||||
type: 'void';
|
||||
}
|
||||
export declare type Pickable = TObject<TProperties> | TRef<TObject<TProperties>>;
|
||||
export declare type PickablePropertyKeys<T extends Pickable> = T extends TObject<infer U> ? keyof U : T extends TRef<TObject<infer U>> ? keyof U : never;
|
||||
export declare type PickableProperties<T extends Pickable> = T extends TObject<infer U> ? U : T extends TRef<TObject<infer U>> ? U : never;
|
||||
export declare type UnionToIntersect<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
||||
export declare type StaticReadonlyOptionalPropertyKeys<T extends TProperties> = {
|
||||
[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
|
||||
export declare type StaticPromise<T extends TSchema> = Promise<Static<T>>;
|
||||
export declare type Static<T extends TSchema> = T['$static'];
|
||||
export declare class TypeBuilder {
|
||||
private readonly schemas;
|
||||
/** `Standard` Modifies an object property to be both readonly and optional */
|
||||
ReadonlyOptional<T extends TSchema>(item: T): TReadonlyOptional<T>;
|
||||
/** `Standard` Modifies an object property to be readonly */
|
||||
@@ -299,17 +309,17 @@ export declare class TypeBuilder {
|
||||
/** `Standard` Creates an any type */
|
||||
Any(options?: CustomOptions): TAny;
|
||||
/** `Standard` Creates a keyof type from the given object */
|
||||
KeyOf<T extends TObject<TProperties>>(schema: T, options?: CustomOptions): TKeyOf<(keyof T['properties'])[]>;
|
||||
KeyOf<T extends TObject<TProperties>>(object: T, options?: CustomOptions): TKeyOf<(keyof T['properties'])[]>;
|
||||
/** `Standard` Creates a record type */
|
||||
Record<K extends TRecordKey, T extends TSchema>(key: K, value: T, options?: ObjectOptions): TRecord<K, T>;
|
||||
/** `Standard` Makes all properties in the given object type required */
|
||||
Required<T extends TObject<any>>(schema: T, options?: ObjectOptions): TObject<StaticRequired<T['properties']>>;
|
||||
Required<T extends TObject<TProperties> | TRef<TObject<TProperties>>>(object: T, options?: ObjectOptions): TObject<StaticRequired<T['properties']>>;
|
||||
/** `Standard` Makes all properties in the given object type optional */
|
||||
Partial<T extends TObject<any>>(schema: T, options?: ObjectOptions): TObject<StaticPartial<T['properties']>>;
|
||||
Partial<T extends TObject<TProperties> | TRef<TObject<TProperties>>>(object: T, options?: ObjectOptions): TObject<StaticPartial<T['properties']>>;
|
||||
/** `Standard` Picks property keys from the given object type */
|
||||
Pick<T extends TObject<TProperties>, K extends (keyof T['properties'])[]>(schema: T, keys: [...K], options?: ObjectOptions): TObject<Pick<T['properties'], K[number]>>;
|
||||
Pick<T extends TObject<TProperties> | TRef<TObject<TProperties>>, K extends PickablePropertyKeys<T>[]>(object: T, keys: [...K], options?: ObjectOptions): TObject<Pick<PickableProperties<T>, K[number]>>;
|
||||
/** `Standard` Omits property keys from the given object type */
|
||||
Omit<T extends TObject<any>, K extends (keyof T['properties'])[]>(schema: T, keys: [...K], options?: ObjectOptions): TObject<Omit<T['properties'], K[number]>>;
|
||||
Omit<T extends TObject<TProperties> | TRef<TObject<TProperties>>, K extends PickablePropertyKeys<T>[]>(object: T, keys: [...K], options?: ObjectOptions): TObject<Omit<PickableProperties<T>, K[number]>>;
|
||||
/** `Standard` Omits the `kind` and `modifier` properties from the underlying schema */
|
||||
Strict<T extends TSchema>(schema: T, options?: CustomOptions): T;
|
||||
/** `Extended` Creates a constructor type */
|
||||
@@ -322,14 +332,17 @@ export declare class TypeBuilder {
|
||||
Undefined(options?: CustomOptions): TUndefined;
|
||||
/** `Extended` Creates a void type */
|
||||
Void(options?: CustomOptions): TVoid;
|
||||
/** `Standard` Creates a namespace for a set of related types */
|
||||
Namespace<T extends TDefinitions>($defs: T, options?: CustomOptions): TNamespace<T>;
|
||||
/** `Standard` References a type within a namespace. The referenced namespace must specify an `$id` */
|
||||
Ref<T extends TNamespace<TDefinitions>, K extends keyof T['$defs']>(box: T, key: K): TRef<T['$defs'][K]>;
|
||||
/** `Standard` References type. The referenced type must specify an `$id` */
|
||||
Ref<T extends TSchema>(schema: T): TRef<T>;
|
||||
/** `Experimental` Creates a recursive type */
|
||||
Rec<T extends TSchema>(callback: (self: TAny) => T, options?: CustomOptions): T;
|
||||
/** `Experimental` Creates a recursive type. Pending https://github.com/ajv-validator/ajv/issues/1709 */
|
||||
/** `Experimental` Creates a namespace for a set of related types */
|
||||
Namespace<T extends TDefinitions>($defs: T, options?: CustomOptions): TNamespace<T>;
|
||||
/** `Experimental` References a type within a namespace. The referenced namespace must specify an `$id` */
|
||||
Ref<T extends TNamespace<TDefinitions>, K extends keyof T['$defs']>(box: T, key: K): T['$defs'][K];
|
||||
/** `Experimental` References type. The referenced type must specify an `$id` */
|
||||
Ref<T extends TSchema>(schema: T): T;
|
||||
/** Stores this schema if it contains an $id. This function is used for later referencing. */
|
||||
private Store;
|
||||
/** Resolves a schema by $id. May resolve recursively if the target is a TRef. */
|
||||
private Resolve;
|
||||
}
|
||||
export declare const Type: TypeBuilder;
|
||||
|
||||
+103
-72
@@ -60,6 +60,7 @@ export const BooleanKind = Symbol('BooleanKind')
|
||||
export const NullKind = Symbol('NullKind')
|
||||
export const UnknownKind = Symbol('UnknownKind')
|
||||
export const AnyKind = Symbol('AnyKind')
|
||||
export const RefKind = Symbol('RefKind')
|
||||
|
||||
export interface CustomOptions {
|
||||
$id?: string
|
||||
@@ -147,6 +148,7 @@ export interface TBoolean extends TS
|
||||
export interface TNull extends TSchema, CustomOptions { $static: null, kind: typeof NullKind, type: 'null' }
|
||||
export interface TUnknown extends TSchema, CustomOptions { $static: unknown, kind: typeof UnknownKind }
|
||||
export interface TAny extends TSchema, CustomOptions { $static: any, kind: typeof AnyKind }
|
||||
export interface TRef<T extends TSchema> extends TSchema, CustomOptions { $static: Static<T>, kind: typeof RefKind, $ref: string }
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Extended Schema Types
|
||||
@@ -166,6 +168,9 @@ export interface TVoid extends T
|
||||
// --------------------------------------------------------------------------
|
||||
// Utility Types
|
||||
// --------------------------------------------------------------------------
|
||||
export type Pickable = TObject<TProperties> | TRef<TObject<TProperties>>
|
||||
export type PickablePropertyKeys<T extends Pickable> = T extends TObject<infer U> ? keyof U : T extends TRef<TObject<infer U>> ? keyof U : never
|
||||
export type PickableProperties <T extends Pickable> = T extends TObject<infer U> ? U : T extends TRef<TObject<infer U>> ? U : never
|
||||
|
||||
export type UnionToIntersect<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
|
||||
export type StaticReadonlyOptionalPropertyKeys <T extends TProperties> = { [K in keyof T]: T[K] extends TReadonlyOptional<TSchema> ? K : never }[keyof T]
|
||||
@@ -241,6 +246,7 @@ function clone(object: any): any {
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
export class TypeBuilder {
|
||||
private readonly schemas = new Map<string, TSchema>()
|
||||
|
||||
/** `Standard` Modifies an object property to be both readonly and optional */
|
||||
public ReadonlyOptional<T extends TSchema>(item: T): TReadonlyOptional<T> {
|
||||
@@ -262,9 +268,10 @@ export class TypeBuilder {
|
||||
const additionalItems = false
|
||||
const minItems = items.length
|
||||
const maxItems = items.length
|
||||
return ((items.length > 0)
|
||||
const schema = ((items.length > 0)
|
||||
? { ...options, kind: TupleKind, type: 'array', items, additionalItems, minItems, maxItems }
|
||||
: { ...options, kind: TupleKind, type: 'array', minItems, maxItems }) as any
|
||||
return this.Store(schema)
|
||||
}
|
||||
|
||||
/** `Standard` Creates an object type with the given properties */
|
||||
@@ -278,82 +285,82 @@ export class TypeBuilder {
|
||||
})
|
||||
const required_names = property_names.filter(name => !optional.includes(name))
|
||||
const required = (required_names.length > 0) ? required_names : undefined
|
||||
return ((required)
|
||||
return this.Store(((required)
|
||||
? { ...options, kind: ObjectKind, type: 'object', properties, required }
|
||||
: { ...options, kind: ObjectKind, type: 'object', properties }) as any
|
||||
: { ...options, kind: ObjectKind, type: 'object', properties }))
|
||||
}
|
||||
|
||||
/** `Standard` Creates an intersect type. */
|
||||
public Intersect<T extends TSchema[]>(items: [...T], options: IntersectOptions = {}): TIntersect<T> {
|
||||
return { ...options, kind: IntersectKind, type: 'object', allOf: items } as any
|
||||
return this.Store({ ...options, kind: IntersectKind, type: 'object', allOf: items })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a union type */
|
||||
public Union<T extends TSchema[]>(items: [...T], options: CustomOptions = {}): TUnion<T> {
|
||||
return { ...options, kind: UnionKind, anyOf: items } as any
|
||||
return this.Store({ ...options, kind: UnionKind, anyOf: items })
|
||||
}
|
||||
|
||||
/** `Standard` Creates an array type */
|
||||
public Array<T extends TSchema>(items: T, options: ArrayOptions = {}): TArray<T> {
|
||||
return { ...options, kind: ArrayKind, type: 'array', items } as any
|
||||
return this.Store({ ...options, kind: ArrayKind, type: 'array', items })
|
||||
}
|
||||
|
||||
/** `Standard` Creates an enum type from a TypeScript enum */
|
||||
public Enum<T extends TEnumType>(item: T, options: CustomOptions = {}): TEnum<TEnumKey<T[keyof T]>[]> {
|
||||
const values = Object.keys(item).filter(key => isNaN(key as any)).map(key => item[key]) as T[keyof T][]
|
||||
const anyOf = values.map(value => typeof value === 'string' ? { type: 'string' as const, const: value } : { type: 'number' as const, const: value })
|
||||
return { ...options, kind: EnumKind, anyOf } as any
|
||||
return this.Store({ ...options, kind: EnumKind, anyOf })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a literal type. Supports string, number and boolean values only */
|
||||
public Literal<T extends TValue>(value: T, options: CustomOptions = {}): TLiteral<T> {
|
||||
return { ...options, kind: LiteralKind, const: value, type: typeof value as 'string' | 'number' | 'boolean' } as any
|
||||
return this.Store({ ...options, kind: LiteralKind, const: value, type: typeof value as 'string' | 'number' | 'boolean' })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a string type */
|
||||
public String<TCustomFormatOption extends string>(options: StringOptions<StringFormatOption | TCustomFormatOption> = {}): TString {
|
||||
return { ...options, kind: StringKind, type: 'string' } as any
|
||||
return this.Store({ ...options, kind: StringKind, type: 'string' })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a string type from a regular expression */
|
||||
public RegEx(regex: RegExp, options: CustomOptions = {}): TString {
|
||||
return this.String({ ...options, pattern: regex.source }) as any
|
||||
return this.String({ ...options, pattern: regex.source })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a number type */
|
||||
public Number(options: NumberOptions = {}): TNumber {
|
||||
return { ...options, kind: NumberKind, type: 'number' } as any
|
||||
return this.Store({ ...options, kind: NumberKind, type: 'number' })
|
||||
}
|
||||
|
||||
/** `Standard` Creates an integer type */
|
||||
public Integer(options: NumberOptions = {}): TInteger {
|
||||
return { ...options, kind: IntegerKind, type: 'integer' } as any
|
||||
return this.Store({ ...options, kind: IntegerKind, type: 'integer' })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a boolean type */
|
||||
public Boolean(options: CustomOptions = {}): TBoolean {
|
||||
return { ...options, kind: BooleanKind, type: 'boolean' } as any
|
||||
return this.Store({ ...options, kind: BooleanKind, type: 'boolean' })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a null type */
|
||||
public Null(options: CustomOptions = {}): TNull {
|
||||
return { ...options, kind: NullKind, type: 'null' } as any
|
||||
return this.Store({ ...options, kind: NullKind, type: 'null' })
|
||||
}
|
||||
|
||||
/** `Standard` Creates an unknown type */
|
||||
public Unknown(options: CustomOptions = {}): TUnknown {
|
||||
return { ...options, kind: UnknownKind } as any
|
||||
return this.Store({ ...options, kind: UnknownKind })
|
||||
}
|
||||
|
||||
/** `Standard` Creates an any type */
|
||||
public Any(options: CustomOptions = {}): TAny {
|
||||
return { ...options, kind: AnyKind } as any
|
||||
return this.Store({ ...options, kind: AnyKind })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a keyof type from the given object */
|
||||
public KeyOf<T extends TObject<TProperties>>(schema: T, options: CustomOptions = {}): TKeyOf<(keyof T['properties'])[]> {
|
||||
const keys = Object.keys(schema.properties)
|
||||
return {...options, kind: KeyOfKind, type: 'string', enum: keys } as any
|
||||
public KeyOf<T extends TObject<TProperties>>(object: T, options: CustomOptions = {}): TKeyOf<(keyof T['properties'])[]> {
|
||||
const keys = Object.keys(object.properties)
|
||||
return this.Store({...options, kind: KeyOfKind, type: 'string', enum: keys })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a record type */
|
||||
@@ -367,15 +374,16 @@ export class TypeBuilder {
|
||||
default: throw Error('Invalid Record Key')
|
||||
}
|
||||
})()
|
||||
return { ...options, kind: RecordKind, type: 'object', patternProperties: { [pattern]: value } } as any
|
||||
return this.Store({ ...options, kind: RecordKind, type: 'object', patternProperties: { [pattern]: value } })
|
||||
}
|
||||
|
||||
|
||||
/** `Standard` Makes all properties in the given object type required */
|
||||
public Required<T extends TObject<any>>(schema: T, options: ObjectOptions = {}): TObject<StaticRequired<T['properties']>> {
|
||||
const next = { ...clone(schema), ...options }
|
||||
next.required = Object.keys(next.properties)
|
||||
for(const key of Object.keys(next.properties)) {
|
||||
const property = next.properties[key]
|
||||
public Required<T extends TObject<TProperties> | TRef<TObject<TProperties>>>(object: T, options: ObjectOptions = {}): TObject<StaticRequired<T['properties']>> {
|
||||
const source = this.Resolve(object)
|
||||
const schema = { ...clone(source), ...options }
|
||||
schema.required = Object.keys(schema.properties)
|
||||
for(const key of Object.keys(schema.properties)) {
|
||||
const property = schema.properties[key]
|
||||
switch(property.modifier) {
|
||||
case ReadonlyOptionalModifier: property.modifier = ReadonlyModifier; break;
|
||||
case ReadonlyModifier: property.modifier = ReadonlyModifier; break;
|
||||
@@ -383,15 +391,16 @@ export class TypeBuilder {
|
||||
default: delete property.modifier; break;
|
||||
}
|
||||
}
|
||||
return next
|
||||
return this.Store(schema)
|
||||
}
|
||||
|
||||
/** `Standard` Makes all properties in the given object type optional */
|
||||
public Partial<T extends TObject<any>>(schema: T, options: ObjectOptions = {}): TObject<StaticPartial<T['properties']>> {
|
||||
const next = { ...clone(schema), ...options }
|
||||
delete next.required
|
||||
for(const key of Object.keys(next.properties)) {
|
||||
const property = next.properties[key]
|
||||
public Partial<T extends TObject<TProperties> | TRef<TObject<TProperties>>>(object: T, options: ObjectOptions = {}): TObject<StaticPartial<T['properties']>> {
|
||||
const source = this.Resolve(object)
|
||||
const schema = { ...clone(source), ...options }
|
||||
delete schema.required
|
||||
for(const key of Object.keys(schema.properties)) {
|
||||
const property = schema.properties[key]
|
||||
switch(property.modifier) {
|
||||
case ReadonlyOptionalModifier: property.modifier = ReadonlyOptionalModifier; break;
|
||||
case ReadonlyModifier: property.modifier = ReadonlyOptionalModifier; break;
|
||||
@@ -399,27 +408,29 @@ export class TypeBuilder {
|
||||
default: property.modifier = OptionalModifier; break;
|
||||
}
|
||||
}
|
||||
return next
|
||||
return this.Store(schema)
|
||||
}
|
||||
|
||||
|
||||
/** `Standard` Picks property keys from the given object type */
|
||||
public Pick<T extends TObject<TProperties>, K extends (keyof T['properties'])[]>(schema: T, keys: [...K], options: ObjectOptions = {}): TObject<Pick<T['properties'], K[number]>> {
|
||||
const next = { ...clone(schema), ...options }
|
||||
next.required = next.required ? next.required.filter((key: string) => keys.includes(key)) : undefined
|
||||
for(const key of Object.keys(next.properties)) {
|
||||
if(!keys.includes(key)) delete next.properties[key]
|
||||
public Pick<T extends TObject<TProperties> | TRef<TObject<TProperties>>, K extends PickablePropertyKeys<T>[]>(object: T, keys: [...K], options: ObjectOptions = {}): TObject<Pick<PickableProperties<T>, K[number]>> {
|
||||
const source = this.Resolve(object)
|
||||
const schema = { ...clone(source), ...options }
|
||||
schema.required = schema.required ? schema.required.filter((key: K) => keys.includes(key as any)) : undefined
|
||||
for(const key of Object.keys(schema.properties)) {
|
||||
if(!keys.includes(key as any)) delete schema.properties[key]
|
||||
}
|
||||
return next
|
||||
return this.Store(schema)
|
||||
}
|
||||
|
||||
/** `Standard` Omits property keys from the given object type */
|
||||
public Omit<T extends TObject<any>, K extends (keyof T['properties'])[]>(schema: T, keys: [...K], options: ObjectOptions = {}):TObject<Omit<T['properties'], K[number]>> {
|
||||
const next = { ...clone(schema), ...options }
|
||||
next.required = next.required ? next.required.filter((key: string) => !keys.includes(key)) : undefined
|
||||
for(const key of Object.keys(next.properties)) {
|
||||
if(keys.includes(key)) delete next.properties[key]
|
||||
public Omit<T extends TObject<TProperties> | TRef<TObject<TProperties>>, K extends PickablePropertyKeys<T>[]>(object: T, keys: [...K], options: ObjectOptions = {}):TObject<Omit<PickableProperties<T>, K[number]>> {
|
||||
const source = this.Resolve(object)
|
||||
const schema = { ...clone(source), ...options }
|
||||
schema.required = schema.required ? schema.required.filter((key: string) => !keys.includes(key as any)) : undefined
|
||||
for(const key of Object.keys(schema.properties)) {
|
||||
if(keys.includes(key as any)) delete schema.properties[key]
|
||||
}
|
||||
return next
|
||||
return this.Store(schema)
|
||||
}
|
||||
|
||||
/** `Standard` Omits the `kind` and `modifier` properties from the underlying schema */
|
||||
@@ -429,58 +440,78 @@ export class TypeBuilder {
|
||||
|
||||
/** `Extended` Creates a constructor type */
|
||||
public Constructor<T extends TSchema[], U extends TSchema>(args: [...T], returns: U, options: CustomOptions = {}): TConstructor<T, U> {
|
||||
return { ...options, kind: ConstructorKind, type: 'constructor', arguments: args, returns } as any
|
||||
return this.Store({ ...options, kind: ConstructorKind, type: 'constructor', arguments: args, returns })
|
||||
}
|
||||
|
||||
/** `Extended` Creates a function type */
|
||||
public Function<T extends TSchema[], U extends TSchema>(args: [...T], returns: U, options: CustomOptions = {}): TFunction<T, U> {
|
||||
return { ...options, kind: FunctionKind, type: 'function', arguments: args, returns } as any
|
||||
return this.Store({ ...options, kind: FunctionKind, type: 'function', arguments: args, returns })
|
||||
}
|
||||
|
||||
/** `Extended` Creates a promise type */
|
||||
public Promise<T extends TSchema>(item: T, options: CustomOptions = {}): TPromise<T> {
|
||||
return { ...options, type: 'promise', kind: PromiseKind, item } as any
|
||||
return this.Store({ ...options, type: 'promise', kind: PromiseKind, item })
|
||||
}
|
||||
|
||||
/** `Extended` Creates a undefined type */
|
||||
public Undefined(options: CustomOptions = {}): TUndefined {
|
||||
return { ...options, type: 'undefined', kind: UndefinedKind } as any
|
||||
return this.Store({ ...options, type: 'undefined', kind: UndefinedKind })
|
||||
}
|
||||
|
||||
/** `Extended` Creates a void type */
|
||||
public Void(options: CustomOptions = {}): TVoid {
|
||||
return { ...options, type: 'void', kind: VoidKind } as any
|
||||
return this.Store({ ...options, type: 'void', kind: VoidKind })
|
||||
}
|
||||
|
||||
/** `Standard` Creates a namespace for a set of related types */
|
||||
public Namespace<T extends TDefinitions>($defs: T, options: CustomOptions = {}): TNamespace<T> {
|
||||
return this.Store({ ...options, kind: BoxKind, $defs })
|
||||
}
|
||||
|
||||
/** `Standard` References a type within a namespace. The referenced namespace must specify an `$id` */
|
||||
public Ref<T extends TNamespace<TDefinitions>, K extends keyof T['$defs']>(box: T, key: K): TRef<T['$defs'][K]>
|
||||
|
||||
/** `Standard` References type. The referenced type must specify an `$id` */
|
||||
public Ref<T extends TSchema>(schema: T): TRef<T>
|
||||
|
||||
public Ref(...args: any[]): any {
|
||||
if(args.length === 2) {
|
||||
const namespace = args[0] as TNamespace<TDefinitions>
|
||||
const targetKey = args[1] as string
|
||||
if(namespace.$id === undefined) throw new Error(`Referenced namespace has no $id`)
|
||||
if(!this.schemas.has(namespace.$id)) throw new Error(`Unable to locate namespace with $id '${namespace.$id}'`)
|
||||
return this.Store({ kind: RefKind, $ref: `${namespace.$id}#/$defs/${targetKey}` })
|
||||
} else if(args.length === 1) {
|
||||
const target = args[0] as any
|
||||
if(target.$id === undefined) throw new Error(`Referenced schema has no $id`)
|
||||
if(!this.schemas.has(target.$id)) throw new Error(`Unable to locate schema with $id '${target.$id}'`)
|
||||
return this.Store({ kind: RefKind, $ref: target.$id })
|
||||
} else {
|
||||
throw new Error('Type.Ref: Invalid arguments')
|
||||
}
|
||||
}
|
||||
|
||||
/** `Experimental` Creates a recursive type */
|
||||
public Rec<T extends TSchema>(callback: (self: TAny) => T, options: CustomOptions = {}): T {
|
||||
const $id = options.$id || ''
|
||||
const self = callback({ $ref: `${$id}#/$defs/self` } as any)
|
||||
return { ...options, $ref: `${$id}#/$defs/self`, $defs: { self } } as unknown as T
|
||||
return this.Store({ ...options, $ref: `${$id}#/$defs/self`, $defs: { self } })
|
||||
}
|
||||
|
||||
/** `Experimental` Creates a recursive type. Pending https://github.com/ajv-validator/ajv/issues/1709 */
|
||||
// public Rec<T extends TProperties>($id: string, callback: (self: TAny) => T, options: ObjectOptions = {}): TObject<T> {
|
||||
// const properties = callback({ $recursiveRef: `${$id}` } as any)
|
||||
// return { ...options, kind: ObjectKind, $id, $recursiveAnchor: true, type: 'object', properties }
|
||||
// }
|
||||
|
||||
/** `Experimental` Creates a namespace for a set of related types */
|
||||
public Namespace<T extends TDefinitions>($defs: T, options: CustomOptions = {}): TNamespace<T> {
|
||||
return { ...options, kind: BoxKind, $defs } as any
|
||||
/** Stores this schema if it contains an $id. This function is used for later referencing. */
|
||||
private Store(schema: any): any {
|
||||
if(!schema.$id) return schema
|
||||
this.schemas.set(schema.$id, schema)
|
||||
return schema
|
||||
}
|
||||
|
||||
/** `Experimental` References a type within a namespace. The referenced namespace must specify an `$id` */
|
||||
public Ref<T extends TNamespace<TDefinitions>, K extends keyof T['$defs']>(box: T, key: K): T['$defs'][K]
|
||||
|
||||
/** `Experimental` References type. The referenced type must specify an `$id` */
|
||||
public Ref<T extends TSchema>(schema: T): T
|
||||
|
||||
public Ref(...args: any[]): any {
|
||||
const $id = args[0]['$id'] || '' as string
|
||||
const key = args[1] as string
|
||||
return (args.length === 2) ? { $ref: `${$id}#/$defs/${key}` } : { $ref: $id }
|
||||
|
||||
/** Resolves a schema by $id. May resolve recursively if the target is a TRef. */
|
||||
private Resolve(schema: any): any {
|
||||
if(schema.kind !== RefKind) return schema
|
||||
if(!this.schemas.has(schema.$ref)) throw Error(`Unable to locate schema with $id '${schema.$ref}'`)
|
||||
return this.Resolve(this.schemas.get(schema.$ref)!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const Type = new TypeBuilder()
|
||||
|
||||
Reference in New Issue
Block a user