595 lines
19 KiB
TypeScript
595 lines
19 KiB
TypeScript
import { Err, Result } from "../result";
|
|
import { Type, CustomCommutativeAndType, Either, DefaultIntersect, Comment } from "../type";
|
|
import { GetType } from "../get-type";
|
|
|
|
/*
|
|
* MissingKey is a marker type that indicates that the key in a struct that holds an MissingKey is
|
|
* optional. However, if the key is present, the value must typecheck T; it can't be left undefined.
|
|
* This can be useful for verification but it's not equivalent to TypeScript's optional fields,
|
|
* which can be left undefined even when present.
|
|
*/
|
|
export class MissingKey<T extends Type<any>> {
|
|
constructor(readonly type: T) {}
|
|
}
|
|
|
|
/*
|
|
* OptionalKey mimics TypeScript's ?: optional field syntax in types/interfaces: it allows the key
|
|
* to be missing, or set to undefined.
|
|
*/
|
|
export class OptionalKey<T extends Type<any>> {
|
|
constructor(readonly type: T) {}
|
|
}
|
|
|
|
type WrapperOrType<T> = T extends MissingKey<infer Inner> ? Inner :
|
|
T extends OptionalKey<infer Inner> ? Inner : T;
|
|
|
|
type RawDict<V> = {
|
|
[key: string]: V;
|
|
};
|
|
|
|
abstract class MergeableType<T> extends CustomCommutativeAndType<T> {
|
|
and<Incoming>(type: Type<Incoming>): Type<T & Incoming> {
|
|
if(type instanceof MergeableType) {
|
|
// @ts-ignore
|
|
return new MergeIntersect(this, type);
|
|
}
|
|
else if(type instanceof Either) {
|
|
return new Either(
|
|
this.and(type.l),
|
|
this.and(type.r),
|
|
);
|
|
}
|
|
else if(type instanceof DefaultIntersect) {
|
|
return new DefaultIntersect(
|
|
this.and(type.l),
|
|
this.and(type.r),
|
|
);
|
|
}
|
|
else if(type instanceof Comment) {
|
|
return new Comment(type.commentStr, this.and(type.wrapped));
|
|
}
|
|
return super.and(type);
|
|
}
|
|
}
|
|
|
|
// Dicts and structs merge together in TypeScript, so we put both in the struct file
|
|
export class Dict<V> extends MergeableType<RawDict<V>> {
|
|
readonly valueType: Type<V>;
|
|
constructor(v: Type<V>, readonly namedKey: string = "key") {
|
|
super();
|
|
this.valueType = v;
|
|
}
|
|
|
|
keyName(key: string): Dict<V> {
|
|
return new Dict(this.valueType, key);
|
|
}
|
|
|
|
check(val: any): Result<RawDict<V>> {
|
|
const err = basicDictErrs(val);
|
|
if(err) return err;
|
|
|
|
for(const prop in val) {
|
|
const result = this.valueType.check(val[prop]);
|
|
if(result instanceof Err) return new Err(`[${prop}]: ${result.message}`);
|
|
}
|
|
|
|
return val as Result<RawDict<V>>;
|
|
}
|
|
|
|
sliceResult(val: any): Result<RawDict<V>> {
|
|
const err = basicDictErrs(val);
|
|
if(err) return err;
|
|
|
|
const result: { [key: string]: any } = {};
|
|
for(const prop in val) {
|
|
const sliced = this.valueType.sliceResult(val[prop]);
|
|
if(sliced instanceof Err) return new Err(`[${prop}]: ${result.message}`);
|
|
result[prop] = sliced;
|
|
}
|
|
|
|
return result as Result<RawDict<V>>;
|
|
}
|
|
}
|
|
|
|
function basicDictErrs<V>(val: any): Err<V> | null {
|
|
if(typeof val !== 'object') return new Err(`${val} is not an object`);
|
|
if(Array.isArray(val)) return new Err(`${val} is an array`);
|
|
if(val === null) return new Err(`${val} is null`);
|
|
return null;
|
|
}
|
|
|
|
export function dict<V>(v: Type<V>): Dict<V> {
|
|
return new Dict(v);
|
|
}
|
|
|
|
export type FieldDef = Type<any> | MissingKey<any> | OptionalKey<any>;
|
|
|
|
export type TypeStruct = {
|
|
[key: string]: FieldDef
|
|
};
|
|
|
|
// see this blog post for an explanation of this type shenanigans
|
|
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html
|
|
|
|
// Returns a type like `"foo" | "bar"` for all the optional keys in a typestruct
|
|
type OptionalPropertyNames<T extends TypeStruct> = {
|
|
[K in keyof T]: T[K] extends MissingKey<any> ? K : T[K] extends OptionalKey<any> ? K : never;
|
|
}[keyof T];
|
|
|
|
// Unwraps Type<T> and OptionalKey<Type<T>> to T for all keys in a typestruct
|
|
type UnwrapTypes<T extends TypeStruct> = {
|
|
[K in keyof T]: GetType<WrapperOrType<T[K]>>;
|
|
};
|
|
|
|
export type UnwrappedTypeStruct<T extends TypeStruct> =
|
|
/* required props */ Pick<UnwrapTypes<T>, Exclude<keyof T, OptionalPropertyNames<T>>> &
|
|
/* optional props */ Partial<Pick<UnwrapTypes<T>, OptionalPropertyNames<T>>>;
|
|
|
|
export type TypeStructFor<T> = {
|
|
[K in keyof T]: Type<T[K]>;
|
|
};
|
|
|
|
export type StructFor<T> = Struct<TypeStructFor<T>>;
|
|
|
|
export function keyType<T>(box: MissingKey<Type<T>> | OptionalKey<Type<T>> | Type<T>): Type<T> {
|
|
if(box instanceof MissingKey || box instanceof OptionalKey) {
|
|
return box.type;
|
|
}
|
|
|
|
return box;
|
|
}
|
|
|
|
export function allowsMissing<T extends Type<any>>(box: MissingKey<T> | T): box is MissingKey<T> {
|
|
return (box instanceof MissingKey);
|
|
}
|
|
|
|
export function allowsOptional<T extends Type<any>>(box: MissingKey<T> | T): box is OptionalKey<T> {
|
|
return (box instanceof OptionalKey);
|
|
}
|
|
|
|
export class Struct<T extends TypeStruct> extends MergeableType<UnwrappedTypeStruct<T>> {
|
|
readonly definition: T;
|
|
readonly exact: boolean;
|
|
|
|
constructor(definition: T, exact: boolean) {
|
|
super();
|
|
this.definition = definition;
|
|
this.exact = exact;
|
|
}
|
|
|
|
check(val: any): Result<UnwrappedTypeStruct<T>> {
|
|
const typeErr = this.checkType(val);
|
|
if(typeErr) return typeErr;
|
|
|
|
const errs = this.checkFields(val, (t, val) => t.check(val));
|
|
|
|
if(errs.length === 0) return val as UnwrappedTypeStruct<T>;
|
|
return new Err(`${val} failed the following checks:\n${errs.join('\n')}`);
|
|
}
|
|
|
|
sliceResult(val: any): Result<UnwrappedTypeStruct<T>> {
|
|
const typeErr = this.checkType(val);
|
|
if(typeErr) return typeErr;
|
|
const sliced: { [key: string]: any } = {};
|
|
|
|
const errs = this.checkFields(val, (t, val) => t.sliceResult(val), (key, val) => {
|
|
sliced[key] = val;
|
|
});
|
|
|
|
if(errs.length === 0) return sliced as UnwrappedTypeStruct<T>;
|
|
return new Err(`${val} failed the following checks:\n${errs.join('\n')}`);
|
|
}
|
|
|
|
private checkType(val: any): Err<UnwrappedTypeStruct<T>> | undefined {
|
|
if(typeof val !== 'object') return new Err(`${val} is not an object`);
|
|
if(Array.isArray(val)) return new Err(`${val} is an array`);
|
|
if(val === null) return new Err(`${val} is null`);
|
|
return undefined;
|
|
}
|
|
|
|
private checkFields(val: any, checkFn: (t: Type<any>, val: any) => Result<any>, collect?: (key: string, val: any) => any): string[] {
|
|
const errs: string[] = [];
|
|
for(const prop in this.definition) {
|
|
const field = this.definition[prop]
|
|
if(!(prop in val)) {
|
|
if(allowsMissing(field)) continue;
|
|
if(allowsOptional(field)) continue;
|
|
|
|
errs.push(`missing key '${prop}'`);
|
|
continue;
|
|
}
|
|
|
|
const valField = val[prop];
|
|
if(valField === undefined && allowsOptional(field)) {
|
|
if(collect) collect(prop, undefined);
|
|
continue;
|
|
}
|
|
|
|
const result = checkFn(keyType(field), valField);
|
|
if(result instanceof Err) errs.push(result.message);
|
|
if(collect) collect(prop, result);
|
|
}
|
|
|
|
if(this.exact && typeof val === 'object') {
|
|
for(const prop in val) {
|
|
if(!(prop in this.definition)) {
|
|
errs.push(`unknown key ${prop}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return errs;
|
|
}
|
|
}
|
|
|
|
export function subtype<T extends TypeStruct>(def: T): Struct<T> {
|
|
return new Struct(def, false);
|
|
}
|
|
|
|
export function exact<T extends TypeStruct>(def: T): Struct<T> {
|
|
return new Struct(def, true);
|
|
}
|
|
|
|
export function optional<T extends Type<any>>(check: T): OptionalKey<T> {
|
|
return new OptionalKey(check);
|
|
}
|
|
|
|
export function allowMissing<T extends Type<any>>(check: T): MissingKey<T> {
|
|
return new MissingKey(check);
|
|
}
|
|
|
|
type MakeOptional<T extends FieldDef> = T extends Type<any> ? OptionalKey<T> :
|
|
T extends MissingKey<infer K> ? OptionalKey<K> : T;
|
|
|
|
type DeepPartialTypeStruct<T extends TypeStruct> = {
|
|
[K in keyof T]: T[K] extends Struct<infer T2> ? OptionalKey<Struct<DeepPartialTypeStruct<T2>>> :
|
|
MakeOptional<T[K]>
|
|
}
|
|
|
|
type PartialTypeStruct<T extends TypeStruct> = {
|
|
[K in keyof T]: MakeOptional<T[K]>
|
|
};
|
|
|
|
export class PartialStruct<T extends TypeStruct> extends MergeableType<UnwrappedTypeStruct<PartialTypeStruct<T>>> {
|
|
private readonly hiddenStruct: Struct<PartialTypeStruct<T>>;
|
|
private readonly hiddenTypeStruct: PartialTypeStruct<T>;
|
|
constructor(readonly struct: Struct<T>) {
|
|
super();
|
|
const partialDef: Partial<PartialTypeStruct<T>> = {};
|
|
for(const k in struct.definition) {
|
|
const v = struct.definition[k];
|
|
if(v instanceof MissingKey) {
|
|
//@ts-ignore
|
|
partialDef[k] = optional(v.type);
|
|
}
|
|
else if(v instanceof OptionalKey) {
|
|
//@ts-ignore
|
|
partialDef[k] = optional(v.type);
|
|
}
|
|
else {
|
|
//@ts-ignore
|
|
partialDef[k] = optional(v);
|
|
}
|
|
}
|
|
this.hiddenTypeStruct = partialDef as PartialTypeStruct<T>;
|
|
this.hiddenStruct = new Struct(this.hiddenTypeStruct, struct.exact);
|
|
}
|
|
check(val: any): Result<UnwrappedTypeStruct<PartialTypeStruct<T>>> {
|
|
return this.hiddenStruct.check(val);
|
|
}
|
|
sliceResult(val: any): Result<UnwrappedTypeStruct<PartialTypeStruct<T>>> {
|
|
return this.hiddenStruct.sliceResult(val);
|
|
}
|
|
|
|
reify(): Struct<PartialTypeStruct<T>> {
|
|
return new Struct(this.hiddenTypeStruct, this.hiddenStruct.exact);
|
|
}
|
|
}
|
|
|
|
|
|
export function deepPartial<T extends TypeStruct>(ogstruct: Struct<T>): PartialStruct<DeepPartialTypeStruct<T>> {
|
|
// If the original struct isn't nested, it's just an ordinary partial call. Don't modify the
|
|
// definition, or else when you convert to TypeScript it won't correctly ref out the struct
|
|
if(!hasNested(ogstruct)) {
|
|
// @ts-ignore
|
|
return partial(ogstruct);
|
|
}
|
|
|
|
// If we got this far, the struct has nesting, and therefore can't simply be ref-ed out when
|
|
// converting to TypeScript. We must modify it recursively.
|
|
const partialDef: Partial<DeepPartialTypeStruct<T>> = {};
|
|
for(const k in ogstruct.definition) {
|
|
const v = ogstruct.definition[k];
|
|
if(v instanceof MissingKey) {
|
|
//@ts-ignore
|
|
partialDef[k] = new MissingKey(v.type);
|
|
}
|
|
else if(v instanceof OptionalKey) {
|
|
//@ts-ignore
|
|
partialDef[k] = optional(v.type);
|
|
}
|
|
else {
|
|
const deepKind = deepPartialKind(v);
|
|
// @ts-ignore
|
|
partialDef[k] = deepKind;
|
|
}
|
|
}
|
|
const struct = new Struct(partialDef as DeepPartialTypeStruct<T>, ogstruct.exact);
|
|
return new PartialStruct(struct);
|
|
}
|
|
|
|
export class MergeIntersect<
|
|
LVal, RVal,
|
|
L extends MergeableType<LVal>,
|
|
R extends MergeableType<RVal>,
|
|
> extends MergeableType<LVal & RVal> {
|
|
|
|
// DANGER: NEVER EVER LEAK THIS OBJECT
|
|
// This is for internal use only. Do not use its .and function. Do not pass it to other .and or
|
|
// .or functions. Do not use it AT ALL except to call .check or .sliceResult on it. It MAY be a
|
|
// secret internal class called InternalDictStructMerge (defined below) that does not play well
|
|
// with other types or type algebra.
|
|
protected readonly merged: Type<LVal & RVal>;
|
|
|
|
constructor(readonly l: L, readonly r: R) {
|
|
super();
|
|
|
|
this.merged = merge(l, {
|
|
dict: l => this.mergeDictAndMergeable(l, r),
|
|
struct: l => this.mergeStructAndMergeable(l, r),
|
|
partial: l => this.mergeStructAndMergeable(l.reify(), r),
|
|
merge: l => this.mergeIntersectAndMergeable(l, r),
|
|
internal: () => {
|
|
throw `leaked internal dict/struct merge class; structural error`;
|
|
},
|
|
});
|
|
}
|
|
|
|
check(val: any) {
|
|
return this.merged.check(val);
|
|
}
|
|
|
|
sliceResult(val: any) {
|
|
return this.merged.sliceResult(val);
|
|
}
|
|
|
|
private mergeDictAndMergeable(
|
|
d: Dict<any>,
|
|
m: MergeableType<any> | InternalDictStructMerge<any, any, any, any>
|
|
): MergeableType<any> | InternalDictStructMerge<any, any, any, any> {
|
|
return merge(m, {
|
|
dict: (m) => this.mergeDicts(d, m),
|
|
partial: (m) => new InternalDictStructMerge(m.reify(), d),
|
|
struct: (m) => this.mergeDictAndStruct(d, m),
|
|
merge: (m) => this.mergeDictAndMergeable(d, m.merged),
|
|
internal: (m) => this.mergeInternalAndDict(m, d),
|
|
});
|
|
}
|
|
|
|
private mergeStructAndMergeable(
|
|
s: Struct<any>,
|
|
m: MergeableType<any> | InternalDictStructMerge<any, any, any, any>
|
|
): MergeableType<any> | InternalDictStructMerge<any, any, any, any> {
|
|
return merge(m, {
|
|
dict: m => this.mergeDictAndStruct(m, s),
|
|
partial: m => this.mergeStructs(s, m.reify()),
|
|
struct: m => this.mergeStructs(s, m),
|
|
merge: m => this.mergeStructAndMergeable(s, m.merged),
|
|
internal: m => this.mergeInternalAndStruct(m, s),
|
|
});
|
|
}
|
|
|
|
private mergePartialAndMergeable(
|
|
p: PartialStruct<any>,
|
|
m: MergeableType<any> | InternalDictStructMerge<any, any, any, any>,
|
|
): MergeableType<any> | InternalDictStructMerge<any, any, any, any> {
|
|
return merge(m, {
|
|
dict: m => new InternalDictStructMerge(p.reify(), m),
|
|
partial: m => this.mergeStructs(p.reify(), m.reify()),
|
|
struct: m => this.mergeStructs(p.reify(), m),
|
|
merge: m => this.mergePartialAndMergeable(p, m.merged),
|
|
internal: m => this.mergeInternalAndStruct(m, p.reify()),
|
|
});
|
|
}
|
|
|
|
private mergeIntersectAndMergeable(
|
|
i: MergeIntersect<any, any, any, any>,
|
|
m: MergeableType<any> | InternalDictStructMerge<any, any, any, any>
|
|
): MergeableType<any> | InternalDictStructMerge<any, any, any, any> {
|
|
return merge(m, {
|
|
dict: m => this.mergeDictAndMergeable(m, i.merged),
|
|
partial: m => this.mergePartialAndMergeable(m, i.merged),
|
|
struct: m => this.mergeStructAndMergeable(m, i.merged),
|
|
merge: m => this.mergeIntersectAndMergeable(m, i.merged),
|
|
internal: m => this.mergeInternalAndIntersect(m, i),
|
|
});
|
|
}
|
|
|
|
private mergeInternalAndDict(l: InternalDictStructMerge<any, any, any, any>, r: Dict<any>) {
|
|
return new InternalDictStructMerge(l.s, this.mergeDicts(l.d, r));
|
|
}
|
|
|
|
private mergeInternalAndStruct(l: InternalDictStructMerge<any, any, any, any>, r: Struct<any>) {
|
|
return new InternalDictStructMerge(this.mergeStructs(l.s, r), l.d);
|
|
}
|
|
|
|
private mergeInternalAndIntersect(
|
|
l: InternalDictStructMerge<any, any, any, any>,
|
|
r: MergeIntersect<any, any, any, any>
|
|
): MergeableType<any> | InternalDictStructMerge<any, any, any, any> {
|
|
return merge(r.merged, {
|
|
dict: merged => this.mergeInternalAndDict(l, merged),
|
|
partial: merged => this.mergeInternalAndStruct(l, merged.reify()),
|
|
struct: merged => this.mergeInternalAndStruct(l, merged),
|
|
merge: () => {
|
|
throw `MergeIntersect can't be a child of a MergeIntersect; structural internal error`
|
|
},
|
|
internal: merged => this.mergeInternalAndInternal(l, merged),
|
|
});
|
|
}
|
|
|
|
private mergeInternalAndInternal(
|
|
l: InternalDictStructMerge<any, any, any, any>,
|
|
r: InternalDictStructMerge<any, any, any, any>,
|
|
) {
|
|
return new InternalDictStructMerge(
|
|
this.mergeStructs(l.s, r.s),
|
|
this.mergeDicts(l.d, r.d),
|
|
)
|
|
}
|
|
|
|
private mergeDicts(l: Dict<L>, r: Dict<R>): Dict<L & R> {
|
|
return dict(l.valueType.and(r.valueType));
|
|
}
|
|
|
|
private mergeDictAndStruct(l: Dict<L>, r: StructFor<R>) {
|
|
return new InternalDictStructMerge(r, l);
|
|
}
|
|
|
|
private mergeStructs(l: Struct<any>, r: Struct<any>) {
|
|
const definition: { [key: string]: FieldDef } = {};
|
|
|
|
for(const prop in l.definition) {
|
|
definition[prop] = l.definition[prop];
|
|
}
|
|
for(const prop in r.definition) {
|
|
const existing = definition[prop];
|
|
const merging = r.definition[prop];
|
|
// If it's an additional key, slap it in
|
|
if(existing == null) definition[prop] = r.definition[prop];
|
|
// If it's a missing key, handle
|
|
else if(existing instanceof MissingKey) {
|
|
// Missing keys are stricter than optional, so both converge to missing key
|
|
if(merging instanceof MissingKey || merging instanceof OptionalKey) {
|
|
definition[prop] = new MissingKey(existing.type.and(merging.type));
|
|
}
|
|
// The strictest is just a raw type, so unwrap and merge
|
|
else {
|
|
definition[prop] = existing.type.and(merging);
|
|
}
|
|
}
|
|
// If it's an optional key, handle
|
|
else if(existing instanceof OptionalKey) {
|
|
// Missing key is stricter than optional, so it wins
|
|
if(merging instanceof MissingKey) {
|
|
definition[prop] = new MissingKey(existing.type.and(merging.type));
|
|
}
|
|
// Two optionals merge into an optional
|
|
else if(merging instanceof OptionalKey) {
|
|
definition[prop] = new OptionalKey(existing.type.and(merging.type));
|
|
}
|
|
// A raw type is stricter than optional, so it unwraps the optionality
|
|
else {
|
|
definition[prop] = existing.type.and(merging);
|
|
}
|
|
}
|
|
else {
|
|
// Raw types are stricter than missing or optional types
|
|
if(merging instanceof MissingKey || merging instanceof OptionalKey) {
|
|
definition[prop] = existing.and(merging.type);
|
|
}
|
|
// Finally, merging two raw types
|
|
else {
|
|
definition[prop] = existing.and(merging);
|
|
}
|
|
}
|
|
}
|
|
|
|
return new Struct(definition, l.exact && r.exact);
|
|
}
|
|
}
|
|
|
|
type MergeHandlers = {
|
|
dict: (d: Dict<any>) => MergeResult,
|
|
partial: (p: PartialStruct<any>) => MergeResult,
|
|
struct: (s: Struct<any>) => MergeResult,
|
|
merge: (m: MergeIntersect<any, any, any, any>) => MergeResult,
|
|
internal: (i: InternalDictStructMerge<any, any, any, any>) => MergeResult,
|
|
};
|
|
type MergeResult = MergeableType<any> | InternalDictStructMerge<any, any, any, any>;
|
|
function merge<Input extends MergeableType<any>>(
|
|
i: Input,
|
|
handlers: MergeHandlers,
|
|
): MergeResult {
|
|
if(i instanceof Dict) return handlers.dict(i);
|
|
if(i instanceof PartialStruct) return handlers.partial(i);
|
|
if(i instanceof Struct) return handlers.struct(i);
|
|
if(i instanceof MergeIntersect) return handlers.merge(i);
|
|
if(i instanceof InternalDictStructMerge) return handlers.internal(i);
|
|
throw `Unknown type ${i}`;
|
|
}
|
|
|
|
class InternalDictStructMerge<
|
|
SVal extends TypeStruct, DVal,
|
|
S extends Struct<SVal>,
|
|
D extends Dict<DVal>
|
|
> extends Type<GetType<S> & GetType<D>> {
|
|
readonly s: S;
|
|
constructor(s: S, readonly d: D) {
|
|
super();
|
|
this.s = new Struct(s.definition, false) as S;
|
|
}
|
|
|
|
check(val: any) {
|
|
const dResult = this.d.check(val);
|
|
if(dResult instanceof Err) return dResult;
|
|
const sResult = this.s.check(val);
|
|
if(sResult instanceof Err) return sResult;
|
|
return val;
|
|
}
|
|
|
|
sliceResult(val: any) {
|
|
const dResult = this.d.sliceResult(val);
|
|
if(dResult instanceof Err) return dResult;
|
|
const sResult = this.s.sliceResult(val);
|
|
return Object.assign({}, dResult, sResult) as GetType<S> & GetType<D>;
|
|
}
|
|
}
|
|
|
|
export const Nested = [
|
|
Struct,
|
|
PartialStruct,
|
|
Dict,
|
|
Either,
|
|
DefaultIntersect,
|
|
MergeIntersect,
|
|
Comment,
|
|
] as const;
|
|
export type NestedType = InstanceType<(typeof Nested)[number]>;
|
|
|
|
function deepPartialKind(kind: Type<any>): Type<any> {
|
|
if(isNested(kind)) return handleNested(kind);
|
|
return kind;
|
|
}
|
|
|
|
function handleNested(kind: NestedType): Type<any> {
|
|
if(kind instanceof Struct) {
|
|
if(hasNested(kind)) return deepPartial(kind);
|
|
return new PartialStruct(kind);
|
|
}
|
|
if(kind instanceof PartialStruct) return deepPartial(kind.struct);
|
|
if(kind instanceof Comment) return new Comment(kind.commentStr, deepPartialKind(kind.wrapped));
|
|
if(kind instanceof Dict) return new Dict(deepPartialKind(kind.valueType), kind.namedKey);
|
|
if(kind instanceof Either) return new Either(deepPartialKind(kind.l), deepPartialKind(kind.r));
|
|
if(kind instanceof MergeIntersect) return new DefaultIntersect(deepPartialKind(kind.l), deepPartialKind(kind.r));
|
|
return new DefaultIntersect(deepPartialKind(kind.l), deepPartialKind(kind.r));
|
|
}
|
|
|
|
function isNested(kind: Type<any> | NestedType): kind is NestedType {
|
|
for(const t of Nested) {
|
|
if(kind instanceof t) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function hasNested(struct: Struct<any>) {
|
|
for(const k in struct.definition) {
|
|
if(isNested(struct.definition[k])) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function partial<T extends TypeStruct>(struct: Struct<T>): PartialStruct<T> {
|
|
return new PartialStruct(struct);
|
|
}
|