Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ts simpler 2 #1134

Merged
merged 34 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .babelrc.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
module.exports = (api) => ({
presets: [
[
'jason',
'babel-preset-jason/esm',
api.env() !== 'test'
? {
ignoreBrowserslistConfig: true,
modules: api.env() === 'modules' ? false : 'commonjs',
modules: api.env() === 'esm' ? false : 'commonjs',
}
: {
target: 'node',

// debug: true,
targets: { node: 'current' },
},
],
'@babel/preset-typescript',
],
plugins: [
'@babel/plugin-proposal-logical-assignment-operators',
api.env() === 'modules' && [
'transform-rename-import',
{
Expand Down
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.eslintrc
.eslintrc.js
139 changes: 69 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,14 @@ Yup's API is heavily inspired by [Joi](https://github.com/hapijs/joi), but leane
- [`object.shape(fields: object, noSortEdges?: Array<[string, string]>): Schema`](#objectshapefields-object-nosortedges-arraystring-string-schema)
- [`object.pick(keys: string[]): Schema`](#objectpickkeys-string-schema)
- [`object.omit(keys: string[]): Schema`](#objectomitkeys-string-schema)
- [`object.from(fromKey: string, toKey: string, alias: boolean = false): Schema`](#objectfromfromkey-string-tokey-string-alias-boolean--false-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)
- [`object.constantCase(): Schema`](#objectconstantcase-schema)
- [Extending Schema Types](#extending-schema-types)
- [TypeScript Support](#typescript-support)
- [TypeScript setting](#typescript-setting)
- [TypeScript settings](#typescript-settings)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -126,13 +127,7 @@ npm install -D @types/yup
You define and create schema objects. Schema objects are immutable, so each call of a method returns a _new_ schema object. When using es module syntax, yup exports everything as a named export

```js
import * as yup from 'yup'; // for everything
// or
import { string, object } from 'yup'; // for only what you need
```

```js
let yup = require('yup');
import * as yup from 'yup';

let schema = yup.object().shape({
name: yup.string().required(),
Expand Down Expand Up @@ -163,6 +158,21 @@ schema.cast({
// => { name: 'jimmy', age: 24, createdOn: Date }
```

The exported functions are factory methods for constructing schema instances, but without the `new` keyword.
If you need access to the actual schema classes, they are also exported:

```js
import {
BooleanSchema,
DateSchema,
MixedSchema,
NumberSchema,
ArraySchema,
ObjectSchema,
StringSchema,
} from 'yup';
```

> If you're looking for an easily serializable DSL for yup schema, check out [yup-ast](https://github.com/WASD-Team/yup-ast)

### Using a custom locale dictionary
Expand Down Expand Up @@ -1185,7 +1195,13 @@ const nameAndAge = person.omit('color']);
nameAndAge.getDefault(); // => { age: 30, name: 'pat'}
```

#### `object.from(fromKey: string, toKey: string, alias: boolean = false): Schema`
#### `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.

Expand Down Expand Up @@ -1270,10 +1286,14 @@ utility or pattern that works with that pattern. The below demonstrates using th
syntax since it's less verbose, but you absolutely aren't required to use it.

```js
let DateSchema = yup.date;
import { DateSchema } from 'yup';

let invalidDate = new Date(''); // our failed to coerce value

class MomentDateSchemaType extends DateSchema {
static create() {
return MomentDateSchemaType();
}
constructor() {
super();
this._validFormats = [];
Expand Down Expand Up @@ -1301,22 +1321,32 @@ class MomentDateSchemaType extends DateSchema {
}
}

let schema = new MomentDateSchemaType();
let schema = MomentDateSchemaType.create();

schema.format('YYYY-MM-DD').cast('It is 2012-05-25'); // => Fri May 25 2012 00:00:00 GMT-0400 (Eastern Daylight Time)
```

## TypeScript Support

If you are using TypeScript installing the Yup typings is recommended:
`yup` comes with robust typescript support! However, because of how dynamic `yup` is
not everything can be statically typed safely, but for most cases it's "Good Enough".

```sh
npm install -D @types/yup
Not that `yup` schema actually produce _two_ different types: the result of casting an input, and the value after validation.
Why are these types different? Because a schema can produce a value via casting that
would not pass validation!

```js
const schema = string().nullable().required();

schema.cast(null); // -> null
schema.validateSync(null); // ValidationError this is required!
```

You can now infer a TypeScript type alias using the exported `InferType`. Given the following Yup schema:
By itself this seems weird, but has it uses when handling user input. To get a
TypeScript type that matches all possible `cast()` values, use `yup.TypeOf<typeof schema>`.
To produce a type that matches a valid object for the schema use `yup.Asserts<typeof schema>>`

```TypeScript
```ts
import * as yup from 'yup';

const personSchema = yup.object({
Expand All @@ -1326,84 +1356,53 @@ const personSchema = yup.object({
// TypeScript. Both will have the same effect on the resulting type by
// excluding `undefined`, but `required` will also disallow empty strings.
.defined(),
nickName: yup
.string()
.defined()
.nullable(),
// defaults also affect the possible output type!
// schema with default values won't produce `undefined` values. Remember object schema
// have a default value built in.
nickName: yup.string().default('').nullable(),
gender: yup
.mixed()
// Note `as const`: this types the array as `["male", "female", "other"]`
// instead of `string[]`.
.oneOf(['male', 'female', 'other'] as const)
.defined(),
email: yup
.string()
.nullable()
.notRequired()
.email(),
birthDate: yup
.date()
.nullable()
.notRequired()
.min(new Date(1900, 0, 1)),
}).defined();
email: yup.string().nullable().notRequired().email(),
birthDate: yup.date().nullable().notRequired().min(new Date(1900, 0, 1)),
});
```

You can derive the TypeScript type as follows:

```TypeScript
type Person = yup.InferType<typeof personSchema>;
```
```ts
import type { Asserts, TypeOf } from 'yup'

Which is equivalent to the following TypeScript type alias:
type parsed: Typeof<typeof personSchema> = personSchema.cast(json);
jquense marked this conversation as resolved.
Show resolved Hide resolved

```TypeScript
type Person = {
firstName: string;
nickName: string | null;
gender: "male" | "female" | "other";
email?: string | null | undefined;
birthDate?: Date | null | undefined;
}
const validated: Asserts<typeof personSchema> = personSchema.validateSync(parsed);
```

Making the following objects valid both for TypeScript and Yup validation:

```TypeScript
const minimalPerson: Person = {
firstName: "Matt",
nickName: null,
gender: "male"
};

const fullPerson: Person = {
firstName: "Matt",
nickName: "The Hammer",
gender: "male",
email: "matt@the-hammer.com",
birthDate: new Date(1976, 9, 5)
};
```
You can also go the other direction, specifying an interface and ensuring that a schema would match it:

You can also go the other direction, specifying an interface and ensuring that a schema matches it:
```ts
import { string, object, number, SchemaOf } from 'yup';

```TypeScript
type Person = {
firstName: string;
}
};

// ✔️ compiles
const goodPersonSchema: yup.ObjectSchema<Person> = yup.object({
firstName: yup.string().defined()
const goodPersonSchema: SchemaOf<Person> = object({
firstName: string().defined(),
}).defined();

// ❌ errors:
// "Type 'number | undefined' is not assignable to type 'string'."
const badPersonSchema: yup.ObjectSchema<Person> = yup.object({
firstName: yup.number()
const badPersonSchema: SchemaOf<Person> = object({
firstName: number(),
});
```

### TypeScript setting
### TypeScript settings

For `yup.InferType<T>` to work correctly with required and nullable types you have to set `strict: true` or `strictNullChecks: true` in your tsconfig.json.
For type utilties to work correctly with required and nullable types you have
to set `strict: true` or `strictNullChecks: true` in your tsconfig.json.
4 changes: 2 additions & 2 deletions jest-sync.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"testEnvironment": "node",
"setupFilesAfterEnv": ["./test-setup.js"],
"roots": ["test"],
"testRegex": "\\.js",
"testPathIgnorePatterns": ["helpers\\.js"]
"testRegex": "\\.(t|j)s$",
"testPathIgnorePatterns": ["helpers\\.js", "\\.eslintrc\\.js", "types\\.ts"]
}
24 changes: 17 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
"precommit": "lint-staged",
"toc": "doctoc README.md --github",
"release": "rollout",
"build": "yarn build:commonjs && yarn build:modules && yarn toc",
"build:commonjs": "babel src --out-dir lib --delete-dir-on-start",
"build:modules": "babel src --out-dir es --delete-dir-on-start --env-name modules",
"build": "yarn 4c build && yarn toc",
"prepublishOnly": "yarn build"
},
"files": [
Expand Down Expand Up @@ -60,15 +58,22 @@
"roots": [
"test"
],
"testRegex": "\\.js",
"testRegex": "\\.(j|t)s$",
"testPathIgnorePatterns": [
"helpers\\.js"
"helpers\\.js",
"\\.eslintrc\\.js",
"types\\.ts"
]
},
"devDependencies": {
"@4c/rollout": "^2.1.10",
"@4c/cli": "^2.1.12",
"@4c/rollout": "^2.1.11",
"@4c/tsconfig": "^0.3.1",
"@babel/cli": "7.12.1",
"@babel/core": "7.12.3",
"@babel/plugin-proposal-logical-assignment-operators": "^7.12.1",
"@babel/preset-typescript": "^7.12.1",
"@typescript-eslint/parser": "^4.8.1",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.1",
Expand All @@ -86,6 +91,8 @@
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-ts-expect": "^1.0.1",
"eslint-plugin-typescript": "^0.14.0",
"husky": "^4.3.0",
"jest": "^26.6.1",
"lint-staged": "^10.4.2",
Expand All @@ -97,12 +104,15 @@
"rollup-plugin-size-snapshot": "^0.12.0",
"sinon": "^9.2.0",
"sinon-chai": "^3.5.0",
"synchronous-promise": "^2.0.15"
"synchronous-promise": "^2.0.15",
"typescript": "^4.0.5"
},
"dependencies": {
"@babel/runtime": "^7.10.5",
"@types/lodash": "^4.14.165",
"lodash": "^4.17.20",
"lodash-es": "^4.17.11",
"nanoclone": "^0.2.1",
"property-expr": "^2.0.4",
"toposort": "^2.0.2"
},
Expand Down
Loading