Repository: jquense/yup
Pull request #1543 (2021-12-29 17:15:02)

feat: simplify base class hierarchy
BREAKING CHANGE: `mixed` schema are no longer treated as the base class for other schema types. It hasn't been for a while, but we've done some nasty prototype slinging to make it behave like it was. Now typescript types should be 1 to 1 with the actual classes yup exposes. 

In general this should not affect anything unless you are extending (via `addMethod` or otherwise) `mixed` prototype. 

```diff
import {
-  mixed,
+  Schema,
} from 'yup';

- addMethod(mixed, 'method', impl)
+ addMethod(Schema, 'method', impl)
```


Patch:
diff --git a/README.md b/README.md
index f2367b7..3fd6e6f 100644
--- a/README.md
+++ b/README.md
@@ -99,41 +99,44 @@ import {
   - [localization and i18n](#localization-and-i18n)
 - [API](#api)
   - [`yup`](#yup)
-    - [`yup.reach(schema: Schema, path: string, value?: object, context?: object): Schema`](#yupreachschema-schema-path-string-value-object-context-object-schema)
-    - [`yup.addMethod(schemaType: Schema, name: string, method: ()=> Schema): void`](#yupaddmethodschematype-schema-name-string-method--schema-void)
-    - [`yup.ref(path: string, options: { contextPrefix: string }): Ref`](#yuprefpath-string-options--contextprefix-string--ref)
-    - [`yup.lazy((value: any) => Schema): Lazy`](#yuplazyvalue-any--schema-lazy)
+    - [`reach(schema: Schema, path: string, value?: object, context?: object): Schema`](#reachschema-schema-path-string-value-object-context-object-schema)
+    - [`addMethod(schemaType: Schema, name: string, method: ()=> Schema): void`](#addmethodschematype-schema-name-string-method--schema-void)
+    - [`ref(path: string, options: { contextPrefix: string }): Ref`](#refpath-string-options--contextprefix-string--ref)
+    - [`lazy((value: any) => Schema): Lazy`](#lazyvalue-any--schema-lazy)
     - [`ValidationError(errors: string | Array<string>, value: any, path: string)`](#validationerrorerrors-string--arraystring-value-any-path-string)
+  - [`Schema`](#schema)
+    - [`Schema.clone(): Schema`](#schemaclone-schema)
+    - [`Schema.label(label: string): Schema`](#schemalabellabel-string-schema)
+    - [`Schema.meta(metadata: object): Schema`](#schemametametadata-object-schema)
+    - [`Schema.describe(): SchemaDescription`](#schemadescribe-schemadescription)
+    - [`Schema.concat(schema: Schema): Schema`](#schemaconcatschema-schema-schema)
+    - [`Schema.validate(value: any, options?: object): Promise<InferType<Schema>, ValidationError>`](#schemavalidatevalue-any-options-object-promiseinfertypeschema-validationerror)
+    - [`Schema.validateSync(value: any, options?: object): InferType<Schema>`](#schemavalidatesyncvalue-any-options-object-infertypeschema)
+    - [`Schema.validateAt(path: string, value: any, options?: object): Promise<InferType<Schema>, ValidationError>`](#schemavalidateatpath-string-value-any-options-object-promiseinfertypeschema-validationerror)
+    - [`Schema.validateSyncAt(path: string, value: any, options?: object): InferType<Schema>`](#schemavalidatesyncatpath-string-value-any-options-object-infertypeschema)
+    - [`Schema.isValid(value: any, options?: object): Promise<boolean>`](#schemaisvalidvalue-any-options-object-promiseboolean)
+    - [`Schema.isValidSync(value: any, options?: object): boolean`](#schemaisvalidsyncvalue-any-options-object-boolean)
+    - [`Schema.cast(value: any, options = {}): InferType<Schema>`](#schemacastvalue-any-options---infertypeschema)
+    - [`Schema.isType(value: any): value is InferType<Schema>`](#schemaistypevalue-any-value-is-infertypeschema)
+    - [`Schema.strict(enabled: boolean = false): Schema`](#schemastrictenabled-boolean--false-schema)
+    - [`Schema.strip(enabled: boolean = true): Schema`](#schemastripenabled-boolean--true-schema)
+    - [`Schema.withMutation(builder: (current: Schema) => void): void`](#schemawithmutationbuilder-current-schema--void-void)
+    - [`Schema.default(value: any): Schema`](#schemadefaultvalue-any-schema)
+    - [`Schema.getDefault(options?: object): Any`](#schemagetdefaultoptions-object-any)
+    - [`Schema.nullable(): Schema`](#schemanullable-schema)
+    - [`Schema.nonNullable(): Schema`](#schemanonnullable-schema)
+    - [`Schema.defined(): Schema`](#schemadefined-schema)
+    - [`Schema.optional(): Schema`](#schemaoptional-schema)
+    - [`Schema.required(message?: string | function): Schema`](#schemarequiredmessage-string--function-schema)
+    - [`Schema.notRequired(): Schema` Alias: `optional()`](#schemanotrequired-schema-alias-optional)
+    - [`Schema.typeError(message: string): Schema`](#schematypeerrormessage-string-schema)
+    - [`Schema.oneOf(arrayOfValues: Array<any>, message?: string | function): Schema` Alias: `equals`](#schemaoneofarrayofvalues-arrayany-message-string--function-schema-alias-equals)
+    - [`Schema.notOneOf(arrayOfValues: Array<any>, message?: string | function)`](#schemanotoneofarrayofvalues-arrayany-message-string--function)
+    - [`Schema.when(keys: string | string[], builder: object | (values: any[], schema) => Schema): Schema`](#schemawhenkeys-string--string-builder-object--values-any-schema--schema-schema)
+    - [`Schema.test(name: string, message: string | function | any, test: function): Schema`](#schematestname-string-message-string--function--any-test-function-schema)
+    - [`Schema.test(options: object): Schema`](#schematestoptions-object-schema)
+    - [`Schema.transform((currentValue: any, originalValue: any) => any): Schema`](#schematransformcurrentvalue-any-originalvalue-any--any-schema)
   - [mixed](#mixed)
-    - [`mixed.clone(): Schema`](#mixedclone-schema)
-    - [`mixed.label(label: string): Schema`](#mixedlabellabel-string-schema)
-    - [`mixed.meta(metadata: object): Schema`](#mixedmetametadata-object-schema)
-    - [`mixed.describe(): SchemaDescription`](#mixeddescribe-schemadescription)
-    - [`mixed.concat(schema: Schema): Schema`](#mixedconcatschema-schema-schema)
-    - [`mixed.validate(value: any, options?: object): Promise<any, ValidationError>`](#mixedvalidatevalue-any-options-object-promiseany-validationerror)
-    - [`mixed.validateSync(value: any, options?: object): any`](#mixedvalidatesyncvalue-any-options-object-any)
-    - [`mixed.validateAt(path: string, value: any, options?: object): Promise<any, ValidationError>`](#mixedvalidateatpath-string-value-any-options-object-promiseany-validationerror)
-    - [`mixed.validateSyncAt(path: string, value: any, options?: object): any`](#mixedvalidatesyncatpath-string-value-any-options-object-any)
-    - [`mixed.isValid(value: any, options?: object): Promise<boolean>`](#mixedisvalidvalue-any-options-object-promiseboolean)
-    - [`mixed.isValidSync(value: any, options?: object): boolean`](#mixedisvalidsyncvalue-any-options-object-boolean)
-    - [`mixed.cast(value: any, options = {}): any`](#mixedcastvalue-any-options---any)
-    - [`mixed.isType(value: any): boolean`](#mixedistypevalue-any-boolean)
-    - [`mixed.strict(isStrict: boolean = false): Schema`](#mixedstrictisstrict-boolean--false-schema)
-    - [`mixed.strip(stripField: boolean = true): Schema`](#mixedstripstripfield-boolean--true-schema)
-    - [`mixed.withMutation(builder: (current: Schema) => void): void`](#mixedwithmutationbuilder-current-schema--void-void)
-    - [`mixed.default(value: any): Schema`](#mixeddefaultvalue-any-schema)
-    - [`mixed.getDefault(options?: object): Any`](#mixedgetdefaultoptions-object-any)
-    - [`mixed.nullable(isNullable: boolean = true): Schema`](#mixednullableisnullable-boolean--true-schema)
-    - [`mixed.required(message?: string | function): Schema`](#mixedrequiredmessage-string--function-schema)
-    - [`mixed.notRequired(): Schema` Alias: `optional()`](#mixednotrequired-schema-alias-optional)
-    - [`mixed.defined(): Schema`](#mixeddefined-schema)
-    - [`mixed.typeError(message: string): Schema`](#mixedtypeerrormessage-string-schema)
-    - [`mixed.oneOf(arrayOfValues: Array<any>, message?: string | function): Schema` Alias: `equals`](#mixedoneofarrayofvalues-arrayany-message-string--function-schema-alias-equals)
-    - [`mixed.notOneOf(arrayOfValues: Array<any>, message?: string | function)`](#mixednotoneofarrayofvalues-arrayany-message-string--function)
-    - [`mixed.when(keys: string | string[], builder: object | (values: any[], schema) => Schema): Schema`](#mixedwhenkeys-string--string-builder-object--values-any-schema--schema-schema)
-    - [`mixed.test(name: string, message: string | function, test: function): Schema`](#mixedtestname-string-message-string--function-test-function-schema)
-    - [`mixed.test(options: object): Schema`](#mixedtestoptions-object-schema)
-    - [`mixed.transform((currentValue: any, originalValue: any) => any): Schema`](#mixedtransformcurrentvalue-any-originalvalue-any--any-schema)
   - [string](#string)
     - [`string.required(message?: string | function): Schema`](#stringrequiredmessage-string--function-schema)
     - [`string.length(limit: number | Ref, message?: string | function): Schema`](#stringlengthlimit-number--ref-message-string--function-schema)
@@ -175,7 +178,6 @@ import {
     - [`object.concat(schemaB: ObjectSchema): ObjectSchema`](#objectconcatschemab-objectschema-objectschema)
     - [`object.pick(keys: string[]): Schema`](#objectpickkeys-string-schema)
     - [`object.omit(keys: string[]): Schema`](#objectomitkeys-string-schema)
-    - [`object.getDefaultFromShape(): Record<string, unknown>`](#objectgetdefaultfromshape-recordstring-unknown)
     - [`object.from(fromKey: string, toKey: string, alias: boolean = false): this`](#objectfromfromkey-string-tokey-string-alias-boolean--false-this)
     - [`object.noUnknown(onlyKnownKeys: boolean = true, message?: string | function): Schema`](#objectnounknownonlyknownkeys-boolean--true-message-string--function-schema)
     - [`object.camelCase(): Schema`](#objectcamelcase-schema)
@@ -359,7 +361,7 @@ string().append('~~~~').cast('hi'); // 'hi~~~~'
 
 ### TypeScript configuration
 
-You MUST have the `strictNullChecks` compiler option enabled.
+You **must** have the `strictNullChecks` compiler option enabled for type inference to work.
 
 We also recommend settings `strictFunctionTypes` to `false`, for functionally better types. Yes
 this reduces overall soundness, however TypeScript already disables this check
@@ -442,36 +444,50 @@ try {
 
 The module export.
 
-```js
-let yup = require('yup');
+```ts
+// core schema
+import {
+  mixed,
+  string,
+  number,
+  boolean,
+  bool,
+  date,
+  object,
+  array,
+  ref,
+  lazy,
+} from 'yup';
 
-yup.mixed;
-yup.string;
-yup.number;
-yup.boolean; // also aliased as yup.bool
-yup.date;
-yup.object;
-yup.array;
+// Classes
+import {
+  Schema,
+  MixedSchema,
+  StringSchema,
+  NumberSchema,
+  BooleanSchema,
+  DateSchema,
+  ArraySchema,
+  ObjectSchema,
+} from 'yup';
 
-yup.reach;
-yup.addMethod;
-yup.ref;
-yup.lazy;
-yup.setLocale;
-yup.ValidationError;
+// Types
+import type { InferType, ISchema, AnySchema, AnyObjectSchema } from 'yup';
 ```
 
-#### `yup.reach(schema: Schema, path: string, value?: object, context?: object): Schema`
+#### `reach(schema: Schema, path: string, value?: object, context?: object): Schema`
 
-For nested schemas `yup.reach` will retrieve a nested schema based on the provided path.
+For nested schemas, `reach` will retrieve an inner schema based on the provided path.
 
 For nested schemas that need to resolve dynamically, you can provide a `value` and optionally
 a `context` object.
 
 ```js
-let schema = object().shape({
-  nested: object().shape({
-    arr: array().of(object().shape({ num: number().max(4) })),
+import { reach } from 'yup';
+
+let schema = object({
+  nested: object({
+    arr: array(object({ num: number().max(4) })),
   }),
 });
 
@@ -481,14 +497,16 @@ reach(schema, 'nested.arr[1].num');
 reach(schema, 'nested["arr"][1].num');
 ```
 
-#### `yup.addMethod(schemaType: Schema, name: string, method: ()=> Schema): void`
+#### `addMethod(schemaType: Schema, name: string, method: ()=> Schema): void`
 
 Adds a new method to the core schema types. A friendlier convenience method for `schemaType.prototype[name] = method`.
 
-```js
-yup.addMethod(yup.date, 'format', function (formats, parseStrict) {
-  return this.transform(function (value, originalValue) {
-    if (this.isType(value)) return value;
+```ts
+import { addMethod, date } from 'yup';
+
+addMethod(date, 'format', function format(formats, parseStrict) {
+  return this.transform((value, originalValue, ctx) => {
+    if (ctx.isType(value)) return value;
 
     value = Moment(originalValue, formats, parseStrict);
 
@@ -497,13 +515,23 @@ yup.addMethod(yup.date, 'format', function (formats, parseStrict) {
 });
 ```
 
-#### `yup.ref(path: string, options: { contextPrefix: string }): Ref`
+If you want to add a method to ALL schema types, extend the abstract base class: `Schema`
+
+```ts
+import { addMethod, Schema } from 'yup';
+
+addMethod(Schema, 'myMethod', ...)
+```
+
+#### `ref(path: string, options: { contextPrefix: string }): Ref`
 
 Creates a reference to another sibling or sibling descendant field. Refs are resolved
 at _validation/cast time_ and supported where specified. Refs are evaluated in the proper order so that
 the ref value is resolved before the field using the ref (be careful of circular dependencies!).
 
 ```js
+import { ref, object, string } from 'yup';
+
 let schema = object({
   baz: ref('foo.bar'),
   foo: object({
@@ -516,7 +544,7 @@ schema.cast({ foo: { bar: 'boom' } }, { context: { x: 5 } });
 // => { baz: 'boom',  x: 5, foo: { bar: 'boom' } }
 ```
 
-#### `yup.lazy((value: any) => Schema): Lazy`
+#### `lazy((value: any) => Schema): Lazy`
 
 Creates a schema that is evaluated at validation/cast time. Useful for creating
 recursive schema like Trees, for polymorphic fields and arrays.
@@ -555,67 +583,75 @@ Thrown on failed validations, with the following properties
   validation chain. When the `abortEarly` option is `false` this is where you can inspect each error thrown,
   alternatively, `errors` will have all of the messages from each inner error.
 
-### mixed
-
-Creates a schema that matches all types. All types inherit from this base type.
-
-```ts
-import { mixed } from 'yup';
-
-let schema = mixed();
-
-schema.validateSync('string'); // 'string';
-
-schema.validateSync(1); // 1;
-
-schema.validateSync(new Date()); // Date;
-```
-
-Custom types can be implemented by passing a type check function:
-
-```ts
-import { mixed } from 'yup';
-
-let objectIdSchema = yup
-  .mixed((input): input is ObjectId => input instanceof ObjectId)
-  .transform((value: any, input, ctx) => {
-    if (ctx.typeCheck(value)) return value;
-    return new ObjectId(value);
-  });
+### `Schema`
 
-await objectIdSchema.validate(ObjectId('507f1f77bcf86cd799439011')); // ObjectId("507f1f77bcf86cd799439011")
+`Schema` is the abstract base class that all schema type inherit from. It provides a number of base methods and properties
+to all other schema types.
 
-await objectIdSchema.validate('507f1f77bcf86cd799439011'); // ObjectId("507f1f77bcf86cd799439011")
-```
+> Note: unless you are creating a custom schema type, Schema should never be used directly. For unknown/any types use [`mixed()`](#mixed)
 
-#### `mixed.clone(): Schema`
+#### `Schema.clone(): Schema`
 
 Creates a deep copy of the schema. Clone is used internally to return a new schema with every schema state change.
 
-#### `mixed.label(label: string): Schema`
+#### `Schema.label(label: string): Schema`
 
 Overrides the key name which is used in error messages.
 
-#### `mixed.meta(metadata: object): Schema`
+#### `Schema.meta(metadata: object): Schema`
 
 Adds to a metadata object, useful for storing data with a schema, that doesn't belong
 the cast object itself.
 
-#### `mixed.describe(): SchemaDescription`
+#### `Schema.describe(): SchemaDescription`
 
 Collects schema details (like meta, labels, and active tests) into a serializable
 description object.
 
+```ts
+const description = object({
+  name: string().required(),
+});
 ```
-SchemaDescription {
-  type: string,
-  label: string,
-  meta: object,
-  tests: Array<{ name: string, params: object }>
+
+And below is are the description types, which differ a bit depending on the schema type.
+
+```ts
+interface SchemaDescription {
+  type: string;
+  label?: string;
+  meta: object | undefined;
+  oneOf: unknown[];
+  notOneOf: unknown[];
+  nullable: boolean;
+  optional: boolean;
+  tests: Array<{ name?: string; params: ExtraParams | undefined }>;
+
+  // Present on object schema descriptions
+  fields: Record<string, SchemaFieldDescription>;
+
+  // Present on array schema descriptions
+  innerType?: SchemaFieldDescription;
+}
+
+type SchemaFieldDescription =
+  | SchemaDescription
+  | SchemaRefDescription
+  | SchemaLazyDescription;
+
+interface SchemaRefDescription {
+  type: 'ref';
+  key: string;
+}
+
+interface SchemaLazyDescription {
+  type: string;
+  label?: string;
+  meta: object | undefined;
 }
 ```
 
-#### `mixed.concat(schema: Schema): Schema`
+#### `Schema.concat(schema: Schema): Schema`
 
 Creates a new instance of the schema by combining two schemas. Only schemas of the same type can be concatenated.
 `concat` is not a "merge" function in the sense that all settings from the provided schema, override ones in the
@@ -629,45 +665,33 @@ mixed<string>().defined().concat(mixed<number>().nullable());
 mixed<number>().defined().nullable();
 ```
 
-#### `mixed.validate(value: any, options?: object): Promise<any, ValidationError>`
+#### `Schema.validate(value: any, options?: object): Promise<InferType<Schema>, ValidationError>`
 
-Returns the value (a cast value if `isStrict` is `false`) if the value is valid, and returns the errors otherwise.
-This method is **asynchronous** and returns a Promise object, that is fulfilled with the value, or rejected
+Returns the parses and validates an input value, returning the parsed value or throwing an error. This method is **asynchronous** and returns a Promise object, that is fulfilled with the value, or rejected
 with a `ValidationError`.
 
-The `options` argument is an object hash containing any schema options you may want to override
-(or specify for the first time).
+```js
+value = await schema.validate({ name: 'jimmy', age: 24 });
+```
+
+Provide `options` to more specifically control the behavior of `validate`.
 
 ```js
-Options = {
+interface Options {
+  // when true, parsing is skipped an the input is validated "as-is"
   strict: boolean = false;
+  // Throw on the first error or collect and return all
   abortEarly: boolean = true;
+  // Remove unspecified keys from objects
   stripUnknown: boolean = false;
+  // when `false` validations will be preformed shallowly
   recursive: boolean = true;
+  // External values that can be provided to validations and conditionals
   context?: object;
 }
 ```
 
-- `strict`: only validate the input, and skip any coercion or transformation
-- `abortEarly`: return from validation methods on the first error rather
-  than after all validations run.
-- `stripUnknown`: remove unspecified keys from objects.
-- `recursive`: when `false` validations will not descend into nested schema
-  (relevant for objects or arrays).
-- `context`: any context needed for validating schema conditions (see: [`when()`](#mixedwhenkeys-string--arraystring-builder-object--value-schema-schema-schema))
-
-```js
-schema.validate({ name: 'jimmy', age: 24 }).then(function (value) {
-  value; // => { name: 'jimmy',age: 24 }
-});
-
-schema.validate({ name: 'jimmy', age: 'hi' }).catch(function (err) {
-  err.name; // => 'ValidationError'
-  err.errors; // => ['age must be a number']
-});
-```
-
-#### `mixed.validateSync(value: any, options?: object): any`
+#### `Schema.validateSync(value: any, options?: object): InferType<Schema>`
 
 Runs validatations synchronously _if possible_ and returns the resulting value,
 or throws a ValidationError. Accepts all the same options as `validate`.
@@ -695,7 +719,7 @@ let schema = number().test('is-42', "this isn't the number i want", (value) =>
 schema.validateSync(42); // throws Error
 ```
 
-#### `mixed.validateAt(path: string, value: any, options?: object): Promise<any, ValidationError>`
+#### `Schema.validateAt(path: string, value: any, options?: object): Promise<InferType<Schema>, ValidationError>`
 
 Validate a deeply nested path within the schema. Similar to how `reach` works,
 but uses the resulting schema as the subject for validation.
@@ -724,43 +748,53 @@ await schema.validateAt('foo[0].bar', rootValue); // => ValidationError: must be
 await schema.validateAt('foo[1].bar', rootValue); // => '1'
 ```
 
-#### `mixed.validateSyncAt(path: string, value: any, options?: object): any`
+#### `Schema.validateSyncAt(path: string, value: any, options?: object): InferType<Schema>`
 
 Same as `validateAt` but synchronous.
 
-#### `mixed.isValid(value: any, options?: object): Promise<boolean>`
+#### `Schema.isValid(value: any, options?: object): Promise<boolean>`
 
 Returns `true` when the passed in value matches the schema. `isValid`
 is **asynchronous** and returns a Promise object.
 
 Takes the same options as `validate()`.
 
-#### `mixed.isValidSync(value: any, options?: object): boolean`
+#### `Schema.isValidSync(value: any, options?: object): boolean`
 
 Synchronously returns `true` when the passed in value matches the schema.
 
 Takes the same options as `validateSync()` and has the same caveats around async tests.
 
-#### `mixed.cast(value: any, options = {}): any`
+#### `Schema.cast(value: any, options = {}): InferType<Schema>`
 
 Attempts to coerce the passed in value to a value that matches the schema. For example: `'5'` will
 cast to `5` when using the `number()` type. Failed casts generally return `null`, but may also
 return results like `NaN` and unexpected strings.
 
-`options` parameter can be an object containing `context`. (For more info on `context` see `mixed.validate`)
+Provide `options` to more specifically control the behavior of `validate`.
 
-#### `mixed.isType(value: any): boolean`
+```js
+interface CastOptions<TContext extends {}> {
+  // Remove undefined properties from objects
+  stripUnknown: boolean = false;
+
+  // External values that used to resolve conditions and references
+  context?: TContext;
+}
+```
+
+#### `Schema.isType(value: any): value is InferType<Schema>`
 
 Runs a type check against the passed in `value`. It returns true if it matches,
 it does not cast the value. When `nullable()` is set `null` is considered a valid value of the type.
 You should use `isType` for all Schema type checks.
 
-#### `mixed.strict(isStrict: boolean = false): Schema`
+#### `Schema.strict(enabled: boolean = false): Schema`
 
 Sets the `strict` option to `true`. Strict schemas skip coercion and transformation attempts,
 validating the value "as is".
 
-#### `mixed.strip(stripField: boolean = true): Schema`
+#### `Schema.strip(enabled: boolean = true): Schema`
 
 Marks a schema to be removed from an output object. Only works as a nested schema.
 
@@ -773,7 +807,24 @@ let schema = object({
 schema.cast({ notThis: 'foo', useThis: 4 }); // => { useThis: 4 }
 ```
 
-#### `mixed.withMutation(builder: (current: Schema) => void): void`
+Schema with `strip` enabled have an inferred type of `never`, allowing them to be
+removed from the overall type:
+
+```ts
+let schema = object({
+  useThis: number(),
+  notThis: string().strip(),
+});
+
+InferType<typeof schema>; /*
+{
+   useThis?: number | undefined
+}
+*/
+
+```
+
+#### `Schema.withMutation(builder: (current: Schema) => void): void`
 
 First the legally required Rich Hickey quote:
 
@@ -781,10 +832,9 @@ First the legally required Rich Hickey quote:
 >
 > If a pure function mutates some local data in order to produce an immutable return value, is that ok?
 
-`withMutation` allows you to mutate the schema in place, instead of the default behavior which clones before each change.
-Generally this isn't necessary since the vast majority of schema changes happen during the initial
+`withMutation` allows you to mutate the schema in place, instead of the default behavior which clones before each change. Generally this isn't necessary since the vast majority of schema changes happen during the initial
 declaration, and only happen once over the lifetime of the schema, so performance isn't an issue.
-However certain mutations _do_ occur at cast/validation time, (such as conditional schema using [`when()`](#mixedwhenkeys-string--arraystring-builder-object--value-schema-schema-schema)), or
+However certain mutations _do_ occur at cast/validation time, (such as conditional schema using [`when()`](#Schemawhenkeys-string--arraystring-builder-object--value-schema-schema-schema)), or
 when instantiating a schema object.
 
 ```js
@@ -797,7 +847,7 @@ object()
   });
 ```
 
-#### `mixed.default(value: any): Schema`
+#### `Schema.default(value: any): Schema`
 
 Sets a default value to use when the value is `undefined`.
 Defaults are created after transformations are executed, but before validations, to help ensure that safe
@@ -815,37 +865,78 @@ yup.object.default(() => ({ number: 5 })); // this is cheaper
 yup.date.default(() => new Date()); // also helpful for defaults that change over time
 ```
 
-#### `mixed.getDefault(options?: object): Any`
+#### `Schema.getDefault(options?: object): Any`
 
-Retrieve a previously set default value. `getDefault` will resolve any conditions that may alter the default. Optionally pass `options` with `context` (for more info on `context` see `mixed.validate`).
+Retrieve a previously set default value. `getDefault` will resolve any conditions that may alter the default. Optionally pass `options` with `context` (for more info on `context` see `Schema.validate`).
 
-#### `mixed.nullable(isNullable: boolean = true): Schema`
+#### `Schema.nullable(): Schema`
 
 Indicates that `null` is a valid value for the schema. Without `nullable()`
-`null` is treated as a different type and will fail `isType()` checks.
+`null` is treated as a different type and will fail `Schema.isType()` checks.
 
-#### `mixed.required(message?: string | function): Schema`
+```ts
+const schema = number().nullable()
 
-Mark the schema as required, which will not allow `undefined` or `null` as a value.
-Note that unless a schema is marked as `nullable()` a `null` value is treated as a type error, not a missing value. Mark a schema as `mixed().nullable().required()` treat `null` as missing.
+schema.cast(null); // null
 
-> Watch out! [`string().required`](#stringrequiredmessage-string--function-schema)) works a little
-> different and additionally prevents empty string values (`''`) when required.
+InferType<typeof schema> // number | null
+```
 
-#### `mixed.notRequired(): Schema` Alias: `optional()`
+#### `Schema.nonNullable(): Schema`
 
-Mark the schema as not required. Passing `undefined` (or `null` for nullable schema) as value will not fail validation.
+The opposite of `nullable`, removes `null` from valid type values for the schema.
+**Schema are non nullable by default**.
 
-#### `mixed.defined(): Schema`
+```ts
+const schema = number().nonNullable()
+
+schema.cast(null); // TypeError
+
+InferType<typeof schema> // number
+```
+
+#### `Schema.defined(): Schema`
 
 Require a value for the schema. All field values apart from `undefined` meet this requirement.
 
-#### `mixed.typeError(message: string): Schema`
+```ts
+const schema = string().defined()
+
+schema.cast(undefined); // TypeError
+
+InferType<typeof schema> // string
+```
+
+#### `Schema.optional(): Schema`
+
+The opposite of `defined()` allows `undefined` values for the given type.
+
+```ts
+const schema = string().optional()
+
+schema.cast(undefined); // undefined
+
+InferType<typeof schema> // string | undefined
+```
+
+#### `Schema.required(message?: string | function): Schema`
+
+Mark the schema as required, which will not allow `undefined` or `null` as a value. `required`
+negates the effects of calling `optional()` and `nullable()`
+
+> Watch out! [`string().required`](#stringrequiredmessage-string--function-schema)) works a little
+> different and additionally prevents empty string values (`''`) when required.
+
+#### `Schema.notRequired(): Schema` Alias: `optional()`
+
+Mark the schema as not required. This is a shortcut for `schema.nonNullable().defined()`;
+
+#### `Schema.typeError(message: string): Schema`
 
 Define an error message for failed type checks. The `${value}` and `${type}` interpolation can
 be used in the `message` argument.
 
-#### `mixed.oneOf(arrayOfValues: Array<any>, message?: string | function): Schema` Alias: `equals`
+#### `Schema.oneOf(arrayOfValues: Array<any>, message?: string | function): Schema` Alias: `equals`
 
 Only allow values from set of values. Values added are removed from any `notOneOf` values if present.
 The `${values}` interpolation can be used in the `message` argument. If a ref or refs are provided,
@@ -853,7 +944,7 @@ the `${resolved}` interpolation can be used in the message argument to get the r
 at validation time.
 
 Note that `undefined` does not fail this validator, even when `undefined` is not included in `arrayOfValues`.
-If you don't want `undefined` to be a valid value, you can use `mixed.required`.
+If you don't want `undefined` to be a valid value, you can use `Schema.required`.
 
 ```js
 let schema = yup.mixed().oneOf(['jimmy', 42]);
@@ -863,7 +954,7 @@ await schema.isValid('jimmy'); // => true
 await schema.isValid(new Date()); // => false
 ```
 
-#### `mixed.notOneOf(arrayOfValues: Array<any>, message?: string | function)`
+#### `Schema.notOneOf(arrayOfValues: Array<any>, message?: string | function)`
 
 Disallow values from a set of values. Values added are removed from `oneOf` values if present.
 The `${values}` interpolation can be used in the `message` argument. If a ref or refs are provided,
@@ -877,7 +968,7 @@ await schema.isValid(42); // => false
 await schema.isValid(new Date()); // => true
 ```
 
-#### `mixed.when(keys: string | string[], builder: object | (values: any[], schema) => Schema): Schema`
+#### `Schema.when(keys: string | string[], builder: object | (values: any[], schema) => Schema): Schema`
 
 Adjust the schema based on a sibling or sibling children fields. You can provide an object
 literal where the key `is` is value or a matcher function, `then` provides the true schema and/or
@@ -886,8 +977,10 @@ literal where the key `is` is value or a matcher function, `then` provides the t
 `is` conditions are strictly compared (`===`) if you want to use a different form of equality you
 can provide a function like: `is: (value) => value == true`.
 
-Like joi you can also prefix properties with `$` to specify a property that is dependent
-on `context` passed in by `validate()` or `isValid`. `when` conditions are additive.
+You can also prefix properties with `$` to specify a property that is dependent
+on `context` passed in by `validate()` or `cast` instead of the input value.
+
+`when` conditions are additive.
 
 ```js
 let schema = object({
@@ -898,7 +991,7 @@ let schema = object({
       then: (schema) => schema..min(5),
       otherwise: (schema) => schema..min(0),
     })
-    .when('$other', (other, schema) => (other === 4 ? schema.max(6) : schema)),
+    .when('$other', ([other], schema) => (other === 4 ? schema.max(6) : schema)),
 });
 
 await schema.validate(value, { context: { other: 4 } });
@@ -924,13 +1017,12 @@ await schema.validate({
 });
 ```
 
-Alternatively you can provide a function that returns a schema
-(called with the value of the key and the current schema).
+Alternatively you can provide a function that returns a schema, called with an array of values for each provided key the current schema.
 
 ```js
 let schema = yup.object({
   isBig: yup.boolean(),
-  count: yup.number().when('isBig', (isBig, schema) => {
+  count: yup.number().when('isBig', ([isBig], schema) => {
     return isBig ? schema.min(5) : schema.min(0);
   }),
 });
@@ -938,7 +1030,7 @@ let schema = yup.object({
 await schema.validate({ isBig: false, count: 4 });
 ```
 
-#### `mixed.test(name: string, message: string | function, test: function): Schema`
+#### `Schema.test(name: string, message: string | function | any, test: function): Schema`
 
 Adds a test function to the validation chain. Tests are run after any object is cast.
 Many types have some tests built in, but you can create custom ones easily.
@@ -987,7 +1079,7 @@ it via `this` it won't work in an arrow function.
   validation error. Useful for dynamically setting the `path`, `params`, or more likely, the error `message`.
   If either option is omitted it will use the current path, or default message.
 
-#### `mixed.test(options: object): Schema`
+#### `Schema.test(options: object): Schema`
 
 Alternative `test(..)` signature. `options` is an object containing some of the following options:
 
@@ -1001,7 +1093,7 @@ Options = {
   message: string;
   // values passed to message for interpolation
   params: ?object;
-  // mark the test as exclusive, meaning only one of the same can be active at once
+  // mark the test as exclusive, meaning only one test of the same name can be active at once
   exclusive: boolean = false;
 }
 ```
@@ -1015,7 +1107,7 @@ the previous tests are removed and further tests of the same name will replace e
 
 ```js
 let max = 64;
-let schema = yup.mixed().test({
+let schema = yup.string().test({
   name: 'max',
   exclusive: true,
   params: { max },
@@ -1024,17 +1116,17 @@ let schema = yup.mixed().test({
 });
 ```
 
-#### `mixed.transform((currentValue: any, originalValue: any) => any): Schema`
+#### `Schema.transform((currentValue: any, originalValue: any) => any): Schema`
 
 Adds a transformation to the transform chain. Transformations are central to the casting process,
-default transforms for each type coerce values to the specific type (as verified by [`isType()`](#mixedistypevalue-any-boolean)). transforms are run before validations and only applied when the schema is not marked as `strict` (the default). Some types have built in transformations.
+default transforms for each type coerce values to the specific type (as verified by [`isType()`](#Schemaistypevalue-any-boolean)). transforms are run before validations and only applied when the schema is not marked as `strict` (the default). Some types have built in transformations.
 
 Transformations are useful for arbitrarily altering how the object is cast, **however, you should take care
 not to mutate the passed in value.** Transforms are run sequentially so each `value` represents the
 current state of the cast, you can use the `originalValue` param if you need to work on the raw initial value.
 
 ```js
-let schema = string().transform(function (value, originalvalue) {
+let schema = string().transform((value, originalvalue) => {
   return this.isType(value) && value !== null ? value.toUpperCase() : value;
 });
 
@@ -1047,9 +1139,9 @@ date parsing strategy than the default one you could do that with a transform.
 
 ```js
 module.exports = function (formats = 'MMM dd, yyyy') {
-  return date().transform(function (value, originalValue) {
+  return date().transform((value, originalValue, context) => {
     // check to see if the previous transform already parsed the date
-    if (this.isType(value)) return value;
+    if (context.isType(value)) return value;
 
     // the default coercion failed so let's try it with Moment.js instead
     value = Moment(originalValue, formats);
@@ -1060,9 +1152,47 @@ module.exports = function (formats = 'MMM dd, yyyy') {
 };
 ```
 
+### mixed
+
+Creates a schema that matches all types, or just the ones you configure. Inherits from [`Schema`](#Schema).
+
+```ts
+import { mixed, InferType } from 'yup';
+
+let schema = mixed();
+
+schema.validateSync('string'); // 'string';
+
+schema.validateSync(1); // 1;
+
+schema.validateSync(new Date()); // Date;
+
+InferType<typeof schema> // any
+```
+
+Custom types can be implemented by passing a type check function:
+
+```ts
+import { mixed, InferType } from 'yup';
+
+let objectIdSchema = yup
+  .mixed((input): input is ObjectId => input instanceof ObjectId)
+  .transform((value: any, input, ctx) => {
+    if (ctx.typeCheck(value)) return value;
+    return new ObjectId(value);
+  });
+
+await objectIdSchema.validate(ObjectId('507f1f77bcf86cd799439011')); // ObjectId("507f1f77bcf86cd799439011")
+
+await objectIdSchema.validate('507f1f77bcf86cd799439011'); // ObjectId("507f1f77bcf86cd799439011")
+
+
+InferType<typeof objectIdSchema> // ObjectId
+```
+
 ### string
 
-Define a string schema. Supports all the same methods as [`mixed`](#mixed).
+Define a string schema. Inherits from [`Schema`](#Schema).
 
 ```js
 let schema = yup.string();
@@ -1147,7 +1277,7 @@ will only validate that the value is uppercase.
 
 ### number
 
-Define a number schema. Supports all the same methods as [`mixed`](#mixed).
+Define a number schema. Inherits from [`Schema`](#Schema).
 
 ```js
 let schema = yup.number();
@@ -1202,7 +1332,7 @@ Adjusts the value via the specified method of `Math` (defaults to 'round').
 
 ### boolean
 
-Define a boolean schema. Supports all the same methods as [`mixed`](#mixed).
+Define a boolean schema. Inherits from [`Schema`](#Schema).
 
 ```js
 let schema = yup.boolean();
@@ -1214,7 +1344,7 @@ await schema.isValid(true); // => true
 
 Define a Date schema. By default ISO date strings will parse correctly,
 for more robust parsing options see the extending schema types at the end of the readme.
-Supports all the same methods as [`mixed`](#mixed).
+Inherits from [`Schema`](#Schema).
 
 ```js
 let schema = yup.date();
@@ -1241,7 +1371,7 @@ and use the result as the limit.
 
 Define an array schema. Arrays can be typed or not, When specifying the element type, `cast` and `isValid`
 will apply to the elements as well. Options passed into `isValid` are passed also passed to child schemas.
-Supports all the same methods as [`mixed`](#mixed).
+Inherits from [`Schema`](#Schema).
 
 ```js
 let schema = yup.array().of(yup.number().min(2));
@@ -1309,10 +1439,10 @@ array()
 ### object
 
 Define an object schema. Options passed into `isValid` are also passed to child schemas.
-Supports all the same methods as [`mixed`](#mixed).
+Inherits from [`Schema`](#Schema).
 
 ```js
-yup.object().shape({
+yup.object({
   name: string().required(),
   age: number().required().positive().integer(),
   email: string().email(),
@@ -1320,18 +1450,6 @@ yup.object().shape({
 });
 ```
 
-You can also pass a shape to the object constructor as a convenience.
-
-```js
-object().shape({
-  num: number(),
-});
-// or
-object({
-  num: number(),
-});
-```
-
 The default `cast` behavior for `object` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
 
 Failed casts return: `null`;
@@ -1349,7 +1467,7 @@ const schema = object({
 schema.default(); // -> { name: '' }
 ```
 
-This may be a bit suprising, but is generally very helpful since it allows large, nested
+This may be a bit suprising, but is usually helpful since it allows large, nested
 schema to create default values that fill out the whole shape and not just the root object. There is
 one gotcha! though. For nested object schema that are optional but include non optional fields
 may fail in unexpected ways:
@@ -1380,9 +1498,9 @@ If you wish to avoid this behavior do one of the following:
 
 Define the keys of the object and the schemas for said keys.
 
-Note that you can chain `shape` method, which acts like object extends, for example:
+Note that you can chain `shape` method, which acts like `Object.assign`.
 
-```js
+```ts
 object({
   a: string(),
   b: number(),
@@ -1394,7 +1512,7 @@ object({
 
 would be exactly the same as:
 
-```js
+```ts
 object({
   a: string(),
   b: string(),
@@ -1438,12 +1556,6 @@ const nameAndAge = person.omit(['color']);
 nameAndAge.getDefault(); // => { age: 30, name: 'pat'}
 ```
 
-#### `object.getDefaultFromShape(): Record<string, unknown>`
-
-Produces a default object value by walking the object shape and calling `default()`
-on each field. This is the default behavior of `getDefault()` but allows for
-building out an object skeleton regardless of the default().
-
 #### `object.from(fromKey: string, toKey: string, alias: boolean = false): this`
 
 Transforms the specified key to a new key. If `alias` is `true` then the old key will be left.
diff --git a/docs/extending.md b/docs/extending.md
index d7129e2..850594a 100644
--- a/docs/extending.md
+++ b/docs/extending.md
@@ -49,7 +49,7 @@ Note that `addMethod` isn't magic, it mutates the prototype of the passed in sch
 
 If you're use case calls for creating an entirely new type. inheriting from
 and existing schema class may be best: Generally you should not inheriting from
-the abstract `BaseSchema` unless you know what you are doing. The other types are fair game though.
+the abstract `Schema` unless you know what you are doing. The other types are fair game though.
 
 You should keep in mind some basic guidelines when extending schemas:
 
diff --git a/src/Lazy.ts b/src/Lazy.ts
index 6472f0b..4e79e14 100644
--- a/src/Lazy.ts
+++ b/src/Lazy.ts
@@ -8,7 +8,7 @@ import type {
   SchemaLazyDescription,
 } from './schema';
 import { Flags, ISchema } from './util/types';
-import { BaseSchema } from '.';
+import { Schema } from '.';
 
 export type LazyBuilder<
   T,
@@ -60,8 +60,8 @@ class Lazy<T, TContext = AnyObject, TDefault = any, TFlags extends Flags = any>
   private _resolve = (
     value: any,
     options: ResolveOptions<TContext> = {},
-  ): BaseSchema<T, TContext, TDefault, TFlags> => {
-    let schema = this.builder(value, options) as BaseSchema<
+  ): Schema<T, TContext, TDefault, TFlags> => {
+    let schema = this.builder(value, options) as Schema<
       T,
       TContext,
       TDefault,
diff --git a/src/array.ts b/src/array.ts
index e9afc96..45a545d 100644
--- a/src/array.ts
+++ b/src/array.ts
@@ -22,7 +22,7 @@ import {
   ISchema,
   UnsetFlag,
 } from './util/types';
-import BaseSchema, { SchemaInnerTypeDescription, SchemaSpec } from './schema';
+import Schema, { SchemaInnerTypeDescription, SchemaSpec } from './schema';
 import { ResolveOptions } from './Condition';
 
 export type RejectorFn = (
@@ -43,7 +43,7 @@ export default class ArraySchema<
   TDefault = undefined,
   TFlags extends Flags = '',
   TIn extends any[] | null | undefined = T[] | undefined,
-> extends BaseSchema<TIn, TContext, TDefault, TFlags> {
+> extends Schema<TIn, TContext, TDefault, TFlags> {
   innerType?: ISchema<T, TContext>;
 
   constructor(type?: ISchema<T, TContext>) {
@@ -289,7 +289,7 @@ export default interface ArraySchema<
   TDefault = undefined,
   TFlags extends Flags = '',
   TIn extends any[] | null | undefined = T[] | undefined,
-> extends BaseSchema<TIn, TContext, TDefault, TFlags> {
+> extends Schema<TIn, TContext, TDefault, TFlags> {
   default<D extends Maybe<TIn>>(
     def: Thunk<D>,
   ): ArraySchema<T, TContext, D, ToggleDefault<TFlags, D>, TIn>;
diff --git a/src/boolean.ts b/src/boolean.ts
index ab34484..87203a3 100644
--- a/src/boolean.ts
+++ b/src/boolean.ts
@@ -1,4 +1,4 @@
-import BaseSchema from './schema';
+import Schema from './schema';
 import type { AnyObject, Maybe, Message, Optionals } from './types';
 import type {
   Defined,
@@ -26,7 +26,7 @@ export default class BooleanSchema<
   TContext = AnyObject,
   TDefault = undefined,
   TFlags extends Flags = '',
-> extends BaseSchema<TType, TContext, TDefault, TFlags> {
+> extends Schema<TType, TContext, TDefault, TFlags> {
   constructor() {
     super({
       type: 'boolean',
diff --git a/src/date.ts b/src/date.ts
index e2192ea..bd4d17d 100644
--- a/src/date.ts
+++ b/src/date.ts
@@ -13,7 +13,7 @@ import type {
   ToggleDefault,
   UnsetFlag,
 } from './util/types';
-import BaseSchema from './schema';
+import Schema from './schema';
 
 let invalidDate = new Date('');
 
@@ -34,7 +34,7 @@ export default class DateSchema<
   TContext = AnyObject,
   TDefault = undefined,
   TFlags extends Flags = '',
-> extends BaseSchema<TType, TContext, TDefault, TFlags> {
+> extends Schema<TType, TContext, TDefault, TFlags> {
   static INVALID_DATE = invalidDate;
 
   constructor() {
@@ -113,7 +113,7 @@ export default interface DateSchema<
   TContext = AnyObject,
   TDefault = undefined,
   TFlags extends Flags = '',
-> extends BaseSchema<TType, TContext, TDefault, TFlags> {
+> extends Schema<TType, TContext, TDefault, TFlags> {
   default<D extends Maybe<TType>>(
     def: Thunk<D>,
   ): DateSchema<TType, TContext, D, ToggleDefault<TFlags, D>>;
diff --git a/src/index.ts b/src/index.ts
index 23ef75c..bad5ea9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,7 @@
-import Mixed, {
+import MixedSchema, {
   create as mixedCreate,
-  MixedSchema,
   MixedOptions,
+  TypeGuard,
 } from './mixed';
 import BooleanSchema, { create as boolCreate } from './boolean';
 import StringSchema, { create as stringCreate } from './string';
@@ -15,7 +15,7 @@ import ValidationError from './ValidationError';
 import reach from './util/reach';
 import isSchema from './util/isSchema';
 import setLocale from './setLocale';
-import BaseSchema, { AnySchema } from './schema';
+import Schema, { AnySchema } from './schema';
 import type { InferType } from './util/types';
 
 function addMethod<T extends AnySchema>(
@@ -48,6 +48,7 @@ export type {
   InferType as Asserts,
   AnySchema,
   MixedOptions,
+  TypeGuard,
 };
 
 export {
@@ -69,9 +70,8 @@ export {
 };
 
 export {
-  BaseSchema,
-  Mixed as MixedSchema,
-  MixedSchema as MixedSchemaClass,
+  Schema,
+  MixedSchema,
   BooleanSchema,
   StringSchema,
   NumberSchema,
diff --git a/src/mixed.ts b/src/mixed.ts
index b93f627..602a34c 100644
--- a/src/mixed.ts
+++ b/src/mixed.ts
@@ -8,14 +8,42 @@ import type {
   ToggleDefault,
   UnsetFlag,
 } from './util/types';
-import BaseSchema from './schema';
+import Schema from './schema';
 
-export declare class MixedSchema<
+const returnsTrue: any = () => true;
+
+export type TypeGuard<TType> = (value: any) => value is NonNullable<TType>;
+export interface MixedOptions<TType> {
+  type?: string;
+  check?: TypeGuard<TType>;
+}
+export function create<TType = any>(
+  spec?: MixedOptions<TType> | TypeGuard<TType>,
+) {
+  return new MixedSchema<TType | undefined>(spec);
+}
+
+export default class MixedSchema<
+  TType = any,
+  TContext = AnyObject,
+  TDefault = undefined,
+  TFlags extends Flags = '',
+> extends Schema<TType, TContext, TDefault, TFlags> {
+  constructor(spec?: MixedOptions<TType> | TypeGuard<TType>) {
+    super(
+      typeof spec === 'function'
+        ? { type: 'mixed', check: spec }
+        : { type: 'mixed', check: returnsTrue as TypeGuard<TType>, ...spec },
+    );
+  }
+}
+
+export default interface MixedSchema<
   TType = any,
   TContext = AnyObject,
   TDefault = undefined,
   TFlags extends Flags = '',
-> extends BaseSchema<TType, TContext, TDefault, TFlags> {
+> extends Schema<TType, TContext, TDefault, TFlags> {
   default<D extends Maybe<TType>>(
     def: Thunk<D>,
   ): MixedSchema<TType, TContext, D, ToggleDefault<TFlags, D>>;
@@ -24,7 +52,7 @@ export declare class MixedSchema<
     schema: MixedSchema<IT, IC, ID, IF>,
   ): MixedSchema<Concat<TType, IT>, TContext & IC, ID, TFlags | IF>;
   concat<IT, IC, ID, IF extends Flags>(
-    schema: BaseSchema<IT, IC, ID, IF>,
+    schema: Schema<IT, IC, ID, IF>,
   ): MixedSchema<Concat<TType, IT>, TContext & IC, ID, TFlags | IF>;
   concat(schema: this): this;
 
@@ -52,21 +80,4 @@ export declare class MixedSchema<
   ): MixedSchema<TType, TContext, TDefault, SetFlag<TFlags, 's'>>;
 }
 
-const Mixed: typeof MixedSchema = BaseSchema as any;
-
-export default Mixed;
-
-export type TypeGuard<TType> = (value: any) => value is NonNullable<TType>;
-export interface MixedOptions<TType> {
-  type?: string;
-  check?: TypeGuard<TType>;
-}
-export function create<TType = any>(
-  spec?: MixedOptions<TType> | TypeGuard<TType>,
-) {
-  return new Mixed<TType | undefined>(
-    typeof spec === 'function' ? { check: spec } : spec,
-  );
-}
-// XXX: this is using the Base schema so that `addMethod(mixed)` works as a base class
-create.prototype = Mixed.prototype;
+create.prototype = MixedSchema.prototype;
diff --git a/src/number.ts b/src/number.ts
index 7ef4597..71f14b8 100644
--- a/src/number.ts
+++ b/src/number.ts
@@ -12,7 +12,7 @@ import type {
   ToggleDefault,
   UnsetFlag,
 } from './util/types';
-import BaseSchema from './schema';
+import Schema from './schema';
 
 let isNaN = (value: Maybe<number>) => value != +value!;
 
@@ -30,7 +30,7 @@ export default class NumberSchema<
   TContext = AnyObject,
   TDefault = undefined,
   TFlags extends Flags = '',
-> extends BaseSchema<TType, TContext, TDefault, TFlags> {
+> extends Schema<TType, TContext, TDefault, TFlags> {
   constructor() {
     super({
       type: 'number',
@@ -156,7 +156,7 @@ export default interface NumberSchema<
   TContext = AnyObject,
   TDefault = undefined,
   TFlags extends Flags = '',
-> extends BaseSchema<TType, TContext, TDefault, TFlags> {
+> extends Schema<TType, TContext, TDefault, TFlags> {
   default<D extends Maybe<TType>>(
     def: Thunk<D>,
   ): NumberSchema<TType, TContext, D, ToggleDefault<TFlags, D>>;
diff --git a/src/object.ts b/src/object.ts
index 7065f82..c53baa9 100644
--- a/src/object.ts
+++ b/src/object.ts
@@ -17,7 +17,7 @@ import { InternalOptions, Callback, Maybe, Message } from './types';
 import ValidationError from './ValidationError';
 import type { Defined, Thunk, NotNull, _ } from './util/types';
 import type Reference from './Reference';
-import BaseSchema, { SchemaObjectDescription, SchemaSpec } from './schema';
+import Schema, { SchemaObjectDescription, SchemaSpec } from './schema';
 import { ResolveOptions } from './Condition';
 import type {
   AnyObject,
@@ -74,7 +74,7 @@ export default interface ObjectSchema<
   // will match object schema regardless of defaults
   TDefault = any,
   TFlags extends Flags = '',
-> extends BaseSchema<MakeKeysOptional<TIn>, TContext, TDefault, TFlags> {
+> extends Schema<MakeKeysOptional<TIn>, TContext, TDefault, TFlags> {
   default<D extends Maybe<AnyObject>>(
     def: Thunk<D>,
   ): ObjectSchema<TIn, TContext, D, ToggleDefault<TFlags, 'd'>>;
@@ -105,7 +105,7 @@ export default class ObjectSchema<
   TContext = AnyObject,
   TDefault = any,
   TFlags extends Flags = '',
-> extends BaseSchema<MakeKeysOptional<TIn>, TContext, TDefault, TFlags> {
+> extends Schema<MakeKeysOptional<TIn>, TContext, TDefault, TFlags> {
   fields: Shape<NonNullable<TIn>, TContext> = Object.create(null);
 
   declare spec: ObjectSchemaSpec;
@@ -184,7 +184,7 @@ export default class ObjectSchema<
           parent: intermediateValue,
         });
 
-        let fieldSpec = field instanceof BaseSchema ? field.spec : undefined;
+        let fieldSpec = field instanceof Schema ? field.spec : undefined;
         let strict = fieldSpec?.strict;
 
         if (fieldSpec?.strip) {
@@ -391,7 +391,7 @@ export default class ObjectSchema<
   partial() {
     const partial: any = {};
     for (const [key, schema] of Object.entries(this.fields)) {
-      partial[key] = schema instanceof BaseSchema ? schema.optional() : schema;
+      partial[key] = schema instanceof Schema ? schema.optional() : schema;
     }
 
     return this.setFields<Partial<TIn>, TDefault>(partial);
@@ -401,9 +401,7 @@ export default class ObjectSchema<
     const partial: any = {};
     for (const [key, schema] of Object.entries(this.fields)) {
       if (schema instanceof ObjectSchema) partial[key] = schema.deepPartial();
-      else
-        partial[key] =
-          schema instanceof BaseSchema ? schema.optional() : schema;
+      else partial[key] = schema instanceof Schema ? schema.optional() : schema;
     }
     return this.setFields<PartialDeep<TIn>, TDefault>(partial);
   }
diff --git a/src/schema.ts b/src/schema.ts
index ae65deb..ceef462 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -31,14 +31,7 @@ import ValidationError from './ValidationError';
 import ReferenceSet from './util/ReferenceSet';
 import Reference from './Reference';
 import isAbsent from './util/isAbsent';
-import type {
-  Defined,
-  Flags,
-  ISchema,
-  ResolveFlags,
-  Thunk,
-  _,
-} from './util/types';
+import type { Flags, ISchema, ResolveFlags, Thunk, _ } from './util/types';
 import toArray from './util/toArray';
 
 export type SchemaSpec<TDefault> = {
@@ -54,9 +47,9 @@ export type SchemaSpec<TDefault> = {
 };
 
 export type SchemaOptions<TType, TDefault> = {
-  type?: string;
+  type: string;
   spec?: SchemaSpec<TDefault>;
-  check?: (value: any) => value is NonNullable<TType>;
+  check: (value: any) => value is NonNullable<TType>;
 };
 
 export type AnySchema<
@@ -64,7 +57,7 @@ export type AnySchema<
   C = AnyObject,
   D = any,
   F extends Flags = Flags,
-> = BaseSchema<TType, C, D, F>;
+> = Schema<TType, C, D, F>;
 
 export interface CastOptions<C = {}> {
   parent?: any;
@@ -80,13 +73,6 @@ export interface SchemaRefDescription {
   key: string;
 }
 
-export type Cast<T, D> = T extends undefined
-  ? // if default is undefined then it won't affect T
-    D extends undefined
-    ? T
-    : Defined<T>
-  : T;
-
 export interface SchemaInnerTypeDescription extends SchemaDescription {
   innerType?: SchemaFieldDescription;
 }
@@ -119,7 +105,7 @@ export interface SchemaDescription {
   tests: Array<{ name?: string; params: ExtraParams | undefined }>;
 }
 
-export default abstract class BaseSchema<
+export default abstract class Schema<
   TType = any,
   TContext = AnyObject,
   TDefault = any,
@@ -153,7 +139,7 @@ export default abstract class BaseSchema<
 
   spec: SchemaSpec<any>;
 
-  constructor(options?: SchemaOptions<TType, any>) {
+  constructor(options: SchemaOptions<TType, any>) {
     this.tests = [];
     this.transforms = [];
 
@@ -161,9 +147,8 @@ export default abstract class BaseSchema<
       this.typeError(locale.notType);
     });
 
-    this.type = options?.type || ('mixed' as const);
-    this._typeCheck =
-      options?.check || ((v: any): v is NonNullable<TType> => true);
+    this.type = options.type;
+    this._typeCheck = options.check;
 
     this.spec = {
       strip: false,
@@ -800,7 +785,7 @@ export default abstract class BaseSchema<
     return next;
   }
 
-  //BaseSchema<TType, SetFlag<TFlags, 's'>>
+  //Schema<TType, SetFlag<TFlags, 's'>>
   strip(strip = true): any {
     let next = this.clone();
     next.spec.strip = strip;
@@ -834,7 +819,7 @@ export default abstract class BaseSchema<
   }
 }
 
-export default interface BaseSchema<
+export default interface Schema<
   /* eslint-disable @typescript-eslint/no-unused-vars */
   TType = any,
   TContext = AnyObject,
@@ -852,17 +837,17 @@ export default interface BaseSchema<
     value: any,
     options?: ValidateOptions<TContext>,
   ): any;
-  equals: BaseSchema['oneOf'];
-  is: BaseSchema['oneOf'];
-  not: BaseSchema['notOneOf'];
-  nope: BaseSchema['notOneOf'];
+  equals: Schema['oneOf'];
+  is: Schema['oneOf'];
+  not: Schema['notOneOf'];
+  nope: Schema['notOneOf'];
 }
 
 // @ts-expect-error
-BaseSchema.prototype.__isYupSchema__ = true;
+Schema.prototype.__isYupSchema__ = true;
 
 for (const method of ['validate', 'validateSync'])
-  BaseSchema.prototype[`${method}At` as 'validateAt' | 'validateSyncAt'] =
+  Schema.prototype[`${method}At` as 'validateAt' | 'validateSyncAt'] =
     function (path: string, value: any, options: ValidateOptions = {}) {
       const { parent, parentPath, schema } = getIn(
         this,
@@ -878,7 +863,7 @@ for (const method of ['validate', 'validateSync'])
     };
 
 for (const alias of ['equals', 'is'] as const)
-  BaseSchema.prototype[alias] = BaseSchema.prototype.oneOf;
+  Schema.prototype[alias] = Schema.prototype.oneOf;
 
 for (const alias of ['not', 'nope'] as const)
-  BaseSchema.prototype[alias] = BaseSchema.prototype.notOneOf;
+  Schema.prototype[alias] = Schema.prototype.notOneOf;
diff --git a/src/string.ts b/src/string.ts
index e8766fc..7d308e0 100644
--- a/src/string.ts
+++ b/src/string.ts
@@ -12,7 +12,7 @@ import type {
   ToggleDefault,
   UnsetFlag,
 } from './util/types';
-import BaseSchema from './schema';
+import Schema from './schema';
 
 let rEmail =
   // eslint-disable-next-line
@@ -53,7 +53,7 @@ export default class StringSchema<
   TContext = AnyObject,
   TDefault = undefined,
   TFlags extends Flags = '',
-> extends BaseSchema<TType, TContext, TDefault, TFlags> {
+> extends Schema<TType, TContext, TDefault, TFlags> {
   constructor() {
     super({
       type: 'string',
@@ -231,7 +231,7 @@ export default interface StringSchema<
   TContext = AnyObject,
   TDefault = undefined,
   TFlags extends Flags = '',
-> extends BaseSchema<TType, TContext, TDefault, TFlags> {
+> extends Schema<TType, TContext, TDefault, TFlags> {
   default<D extends Maybe<TType>>(
     def: Thunk<D>,
   ): StringSchema<TType, TContext, D, ToggleDefault<TFlags, D>>;


Test patch:
diff --git a/test-setup.js b/test-setup.js
index f13dd94..6907751 100644
--- a/test-setup.js
+++ b/test-setup.js
@@ -3,11 +3,11 @@ const { SynchronousPromise } = require('synchronous-promise');
 global.TestHelpers = require('./test/helpers');
 
 if (global.YUP_USE_SYNC) {
-  const { BaseSchema } = require('./src'); // eslint-disable-line global-require
+  const { Schema } = require('./src'); // eslint-disable-line global-require
 
-  const { validate } = BaseSchema.prototype;
+  const { validate } = Schema.prototype;
 
-  BaseSchema.prototype.validate = function (value, options = {}, maybeCb) {
+  Schema.prototype.validate = function (value, options = {}, maybeCb) {
     let run = false;
 
     options.sync = true;
diff --git a/test/yup.js b/test/yup.js
index 02624c6..483dbd3 100644
--- a/test/yup.js
+++ b/test/yup.js
@@ -9,6 +9,7 @@ import {
   number,
   boolean,
   date,
+  Schema,
   ObjectSchema,
   ArraySchema,
   StringSchema,
@@ -197,19 +198,14 @@ describe('Yup', function () {
   });
 
   describe('addMethod', () => {
-    it('extending mixed should make method accessible everywhere', () => {
-      addMethod(mixed, 'foo', () => 'here');
-
-      expect(string().foo()).toBe('here');
-    });
-
-    it('extending Mixed should make method accessible everywhere', () => {
-      addMethod(MixedSchema, 'foo', () => 'here');
+    it('extending Schema should make method accessible everywhere', () => {
+      addMethod(Schema, 'foo', () => 'here');
 
       expect(string().foo()).toBe('here');
     });
 
     test.each([
+      ['mixed', mixed],
       ['object', object],
       ['array', array],
       ['string', string],
@@ -223,6 +219,7 @@ describe('Yup', function () {
     });
 
     test.each([
+      ['mixed', MixedSchema],
       ['object', ObjectSchema],
       ['array', ArraySchema],
       ['string', StringSchema],