diff --git a/package-lock.json b/package-lock.json index 49319b749..613c25ef0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.31.20", + "version": "0.31.21", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.31.20", + "version": "0.31.21", "license": "MIT", "devDependencies": { "@sinclair/hammer": "^0.18.0", diff --git a/package.json b/package.json index 42fe99b9f..0b8deb405 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.31.20", + "version": "0.31.21", "description": "JSONSchema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/src/typebox.ts b/src/typebox.ts index 700e1c3ad..0f9d2e03c 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -1144,6 +1144,9 @@ export namespace TypeGuard { function IsOptionalSchema(value: unknown): value is boolean | undefined { return ValueGuard.IsUndefined(value) || TSchema(value) } + // ---------------------------------------------------------------- + // Types + // ---------------------------------------------------------------- /** Returns true if the given value is TAny */ export function TAny(schema: unknown): schema is TAny { // prettier-ignore @@ -1533,40 +1536,42 @@ export namespace TypeGuard { } /** Returns true if the given value is TSchema */ export function TSchema(schema: unknown): schema is TSchema { + // prettier-ignore return ( - ValueGuard.IsObject(schema) && - (TAny(schema) || - TArray(schema) || - TBoolean(schema) || - TBigInt(schema) || - TAsyncIterator(schema) || - TConstructor(schema) || - TDate(schema) || - TFunction(schema) || - TInteger(schema) || - TIntersect(schema) || - TIterator(schema) || - TLiteral(schema) || - TNever(schema) || - TNot(schema) || - TNull(schema) || - TNumber(schema) || - TObject(schema) || - TPromise(schema) || - TRecord(schema) || - TRef(schema) || - TString(schema) || - TSymbol(schema) || - TTemplateLiteral(schema) || - TThis(schema) || - TTuple(schema) || - TUndefined(schema) || - TUnion(schema) || - TUint8Array(schema) || - TUnknown(schema) || - TUnsafe(schema) || - TVoid(schema) || - (TKind(schema) && TypeRegistry.Has(schema[Kind] as any))) + ValueGuard.IsObject(schema) + ) && ( + TAny(schema) || + TArray(schema) || + TBoolean(schema) || + TBigInt(schema) || + TAsyncIterator(schema) || + TConstructor(schema) || + TDate(schema) || + TFunction(schema) || + TInteger(schema) || + TIntersect(schema) || + TIterator(schema) || + TLiteral(schema) || + TNever(schema) || + TNot(schema) || + TNull(schema) || + TNumber(schema) || + TObject(schema) || + TPromise(schema) || + TRecord(schema) || + TRef(schema) || + TString(schema) || + TSymbol(schema) || + TTemplateLiteral(schema) || + TThis(schema) || + TTuple(schema) || + TUndefined(schema) || + TUnion(schema) || + TUint8Array(schema) || + TUnknown(schema) || + TUnsafe(schema) || + TVoid(schema) || + (TKind(schema) && TypeRegistry.Has(schema[Kind] as any)) ) } } @@ -1986,7 +1991,12 @@ export namespace TypeExtends { !TypeGuard.TObject(right) ? TypeExtendsResult.False : (() => { for (const key of Object.getOwnPropertyNames(right.properties)) { - if (!(key in left.properties)) return TypeExtendsResult.False + if (!(key in left.properties) && !TypeGuard.TOptional(right.properties[key])) { + return TypeExtendsResult.False + } + if(TypeGuard.TOptional(right.properties[key])) { + return TypeExtendsResult.True + } if (Property(left.properties[key], right.properties[key]) === TypeExtendsResult.False) { return TypeExtendsResult.False } diff --git a/test/runtime/type/extends/object.ts b/test/runtime/type/extends/object.ts index 172eb46bf..8bc3a6e63 100644 --- a/test/runtime/type/extends/object.ts +++ b/test/runtime/type/extends/object.ts @@ -174,4 +174,55 @@ describe('type/extends/Object', () => { const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Date()) Assert.IsEqual(R, TypeExtendsResult.False) }) + // ---------------------------------------------------------------- + // Optional + // ---------------------------------------------------------------- + it('Should extend optional 1', () => { + const A = Type.Object({ a: Type.Number() }) + const B = Type.Object({ a: Type.Optional(Type.Number()) }) + const C = TypeExtends.Extends(A, B) + Assert.IsEqual(C, TypeExtendsResult.True) + }) + it('Should extend optional 2', () => { + const A = Type.Object({ a: Type.Number() }) + const B = Type.Object({ a: Type.Optional(Type.Number()) }) + const C = TypeExtends.Extends(B, A) + Assert.IsEqual(C, TypeExtendsResult.False) + }) + it('Should extend optional 3', () => { + const A = Type.Object({ + x: Type.Optional(Type.Number()), + y: Type.Number(), + }) + const B = Type.Object({ + y: Type.Number(), + z: Type.Number(), + }) + const R = TypeExtends.Extends(A, B) + Assert.IsEqual(R, TypeExtendsResult.False) + }) + it('Should extend optional 4', () => { + const A = Type.Object({ + x: Type.Number(), + y: Type.Number(), + }) + const B = Type.Object({ + y: Type.Number(), + z: Type.Number(), + }) + const R = TypeExtends.Extends(A, B) + Assert.IsEqual(R, TypeExtendsResult.False) + }) + it('Should extend optional 5', () => { + const A = Type.Object({ + x: Type.Number(), + y: Type.Number(), + }) + const B = Type.Object({ + y: Type.Number(), + z: Type.Optional(Type.Number()), + }) + const R = TypeExtends.Extends(A, B) + Assert.IsEqual(R, TypeExtendsResult.True) + }) })