Skip to content

Commit 53c555c

Browse files
Upgrade api (ianstormtaylor#363)
1 parent 2fc567d commit 53c555c

File tree

268 files changed

+3094
-4609
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

268 files changed

+3094
-4609
lines changed

Changelog.md

+149
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,155 @@
22

33
This document maintains a list of changes to the `superstruct` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes and fixes won't be detailed.
44

5+
### `0.10.0` — June 5, 2020
6+
7+
The `0.10` version is a complete overhaul with the goal of making Superstruct much simpler and easier to understand, and with complete support for runtime type signatures TypeScript.
8+
9+
This makes it much more powerful, however the core architecture has had to change to make it happen. It will still look very similar, but migrating between the versions _will be more work than usual_. There's no requirement to upgrade, although if you're using Superstruct in concert with TypeScript you will have a much better experience.
10+
11+
###### BREAKING
12+
13+
**All types are created from factories.** Previously depending on whether the type was a complex type or a scalar type they'd be defined different. Complex types used factories, whereas scalars used strings. Now all types are exposed as factories.
14+
15+
For example, previously:
16+
17+
```ts
18+
import { struct } from 'superstruct'
19+
20+
const User = struct.object({
21+
name: 'string',
22+
age: 'number',
23+
})
24+
```
25+
26+
Now becomes:
27+
28+
```ts
29+
import { object, string, number } from 'superstruct'
30+
31+
const User = object({
32+
name: string(),
33+
age: number(),
34+
})
35+
```
36+
37+
**Custom scalars are no longer pre-defined as strings.** Previously, you would define all of your "custom" types in a single place in your codebase and then refer to them in structs later on with a string value. This worked, but added a layer of unnecessary indirection, and made it impossible to accomodate runtime type signatures.
38+
39+
In the new version, custom types are defined extremely similarly to non-custom types. And this has the added benefit that you can easily trace the custom type definitions by just following `import` statements.
40+
41+
Here's how it used to work:
42+
43+
```ts
44+
import { superstruct } from 'superstruct'
45+
import isEmail from 'is-email'
46+
47+
const struct = superstruct({
48+
types: {
49+
email: isEmail,
50+
},
51+
})
52+
53+
const Email = struct('email')
54+
```
55+
56+
And here's what it would look like now:
57+
58+
```ts
59+
import { struct } from 'superstruct'
60+
import isEmail from 'is-email'
61+
62+
const Email = struct('email', isEmail)
63+
```
64+
65+
**Validation logic has been moved to helper functions.** Previously the `assert` and `is` helpers lived on the struct objects themselves. Now, these functions have been extracted into separate helpers. This was unfortunately necessary to work around limitations in TypeScript's `asserts` keyword.
66+
67+
For example, before:
68+
69+
```ts
70+
User.assert(data)
71+
```
72+
73+
Now would be:
74+
75+
```ts
76+
import { assert } from 'superstruct'
77+
78+
assert(data, User)
79+
```
80+
81+
**Coercion is now separate from validation.** Previously there was native logic for handling default values for structs when validating them. This has been abstracted into the ability to define _any_ custom coercion logic for structs, and it has been separate from validation to make it very clear when data can change and when it cannot.
82+
83+
For example, previously:
84+
85+
```ts
86+
const output = User.assert(input)
87+
```
88+
89+
Would now be:
90+
91+
```ts
92+
input = coerce(input, User)
93+
assert(input, User)
94+
```
95+
96+
With two clear steps. The `coerce` step is the only time that data will be transformed at all by coercion logic, and the `assert` step no longer needs to return any values. This makes it easy to do things like:
97+
98+
```ts
99+
if (is(input, User)) {
100+
// ...
101+
}
102+
```
103+
104+
**Validation context is now a dictionary of properties.** Previously when performing complex validation logic that was dependent on other properties on the root object, you could use the second `branch` argument to the validation function. This argument has been changed to be a `context` dictionary with more information. The same branch argument can now be accessed as `context.branch`, along with the new information.
105+
106+
**Unknown properties of objects now have a `'never'` type.** Previously unknown properties would throw errors with `type === null`, however the newly introduced `'never'` type is now used instead.
107+
108+
**The `function` struct was removed.** There's no longer any need for it, because you can define one-off validations directly with the custom `struct` factory.
109+
110+
**Defaults are now defined with a separate coercion helper.** Previously all structs took a second argument that defined the default value to use if an `undefined` value was present. This has been pulled out into a separate helper now to clearly distinguish coercion logic.
111+
112+
For example, previously you'd do:
113+
114+
```ts
115+
const Article = struct.object(
116+
{
117+
title: 'string',
118+
},
119+
{
120+
title: 'Untitled',
121+
}
122+
)
123+
```
124+
125+
Whereas now you'd do:
126+
127+
```ts
128+
const Article = defaulted(
129+
object({
130+
title: string(),
131+
}),
132+
{
133+
title: 'Untitled',
134+
}
135+
)
136+
```
137+
138+
**Optional arguments are now defined with a seperate factory.** Similarly to defaults, there is a new `optional` factory for defined values that can also be `undefined`.
139+
140+
Previously you'd do:
141+
142+
```ts
143+
const Flag = struct('string?')
144+
```
145+
146+
Now you'd do:
147+
148+
```ts
149+
const Flag = optional(string())
150+
```
151+
152+
- `interface` is now called `type`
153+
5154
### `0.8.0` — October 8, 2019
6155

7156
###### BREAKING

Readme.md

+111-41
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,18 @@ But Superstruct is designed for validating data at runtime, so it throws (or ret
4444

4545
### Usage
4646

47-
Superstruct exports a `struct` factory for creating structs that can validate data against a specific schema:
47+
Superstruct allows you to define the shape of data you want to validate:
4848

4949
```js
50-
import { struct } from 'superstruct'
51-
52-
const Article = struct({
53-
id: 'number',
54-
title: 'string',
55-
is_published: 'boolean?',
56-
tags: ['string'],
57-
author: {
58-
id: 'number',
59-
},
50+
import { assert, object, number, string, boolean, array } from 'superstruct'
51+
52+
const Article = object({
53+
id: number(),
54+
title: string(),
55+
tags: array(string()),
56+
author: object({
57+
id: number(),
58+
}),
6059
})
6160

6261
const data = {
@@ -68,41 +67,79 @@ const data = {
6867
},
6968
}
7069

71-
const article = Article(data)
72-
73-
// This will throw when the data is invalid, and return the data otherwise.
74-
// If you'd rather not throw, use `Struct.validate()` or `Struct.test()`.
70+
assert(data, Article)
71+
// This will throw an error when the data is invalid.
72+
// If you'd rather not throw, you can use `is()` or `validate()`.
7573
```
7674

77-
It recognizes all the native JavaScript types out of the box. But you can also define your own custom data types—specific to your application's requirements—by using the `superstruct` export:
75+
Superstruct comes with validators for all common JavaScript data types out of the box. And you can also define your own custom validations:
7876

7977
```js
80-
import { superstruct } from 'superstruct'
78+
import { is, struct, object, string } from 'superstruct'
8179
import isUuid from 'is-uuid'
8280
import isEmail from 'is-email'
8381

84-
const struct = superstruct({
85-
types: {
86-
uuid: value => isUuid.v4(value),
87-
email: value => isEmail(value) && value.length < 256,
88-
},
89-
})
82+
const Email = struct('Email', isEmail)
83+
const Uuid = struct('Uuid', isUuid.v4)
9084

91-
const User = struct({
92-
id: 'uuid',
93-
email: 'email',
94-
is_admin: 'boolean?',
85+
const User = object({
86+
id: Uuid,
87+
email: Email,
88+
name: string(),
9589
})
9690

9791
const data = {
9892
id: 'c8d63140-a1f7-45e0-bfc6-df72973fea86',
9993
94+
name: 'Jane',
95+
}
96+
97+
if (is(data, User)) {
98+
// Your data is guaranteed to be valid in this block.
99+
}
100+
```
101+
102+
Superstruct can also handle coercion of your data before validating it, for example to mix in default values (or trimming, parsing, sanitizing, etc.):
103+
104+
```ts
105+
import { assert, coerce, object, number, string, defaulted } from 'superstruct'
106+
107+
const User = object({
108+
id: defaulted(number(), () => i++),
109+
name: string(),
110+
})
111+
112+
const data = {
113+
name: 'Jane',
100114
}
101115

102-
const user = User(data)
116+
// You can apply the defaults to your data while validating.
117+
const user = coerce(data, User)
118+
// {
119+
// id: 1,
120+
// name: 'Jane',
121+
// }
103122
```
104123

105-
Superstruct supports more complex use cases too like defining list or scalar structs, applying default values, composing structs inside each other, returning errors instead of throwing them, etc. For more information read the full [Documentation](#documentation).
124+
And if you use TypeScript, Superstruct automatically ensures that your data has proper typings whenever you validate it:
125+
126+
```ts
127+
import { is, object, number, string } from 'superstruct'
128+
129+
const User = object({
130+
id: number(),
131+
name: string()
132+
})
133+
134+
const data: unknown = { ... }
135+
136+
if (is(data, User)) {
137+
// TypeScript knows the shape of `data` here, so it is safe to access
138+
// properties like `data.id` and `data.name`.
139+
}
140+
```
141+
142+
Superstruct supports more complex use cases too like defining arrays or nested objects, composing structs inside each other, returning errors instead of throwing them, and more! For more information read the full [Documentation](#documentation).
106143

107144
<br/>
108145

@@ -138,13 +175,9 @@ Which brings me to how Superstruct solves these issues...
138175

139176
3. **Composable interfaces.** Superstruct interfaces are composable, so you can break down commonly-repeated pieces of data into components, and compose them to build up the more complex objects.
140177

141-
4. **Terse schemas.** The schemas in Superstruct are designed to be extremely terse and expressive. This makes them very easy to read and write, encouraging you to have full data validation coverage.
142-
143-
5. **Compiled validators.** Superstruct does the work of compiling its schemas up front, so that it doesn't spend time performing expensive tasks for every call to the validation functions in your hot code paths.
144-
145-
6. **Useful errors.** The errors that Superstruct throws contain all the information you need to convert them into your own application-specific errors easy, which means more helpful errors for your end users!
178+
4. **Useful errors.** The errors that Superstruct throws contain all the information you need to convert them into your own application-specific errors easy, which means more helpful errors for your end users!
146179

147-
7. **Familiar API.** The Superstruct API was heavily inspired by [Typescript](https://www.typescriptlang.org/docs/handbook/basic-types.html), [Flow](https://flow.org/en/docs/types/), [Go](https://gobyexample.com/structs), and [GraphQL](http://graphql.org/learn/schema/). If you're familiar with any of those, then its schema definition API will feel very natural to use, so you can get started quickly.
180+
5. **Familiar API.** The Superstruct API was heavily inspired by [Typescript](https://www.typescriptlang.org/docs/handbook/basic-types.html), [Flow](https://flow.org/en/docs/types/), [Go](https://gobyexample.com/structs), and [GraphQL](http://graphql.org/learn/schema/). If you're familiar with any of those, then its schema definition API will feel very natural to use, so you can get started quickly.
148181

149182
<br/>
150183

@@ -167,6 +200,7 @@ Superstruct's API is very flexible, allowing it to be used for a variety of use
167200
- [Composing Structs](./examples/composing-structs.js)
168201
- [Throwing Errors](./examples/throwing-errors.js)
169202
- [Returning Errors](./examples/returning-errors.js)
203+
- [Testing Values](./examples/testing-values.js)
170204
- [Custom Errors](./examples/custom-errors.js)
171205

172206
<br/>
@@ -183,12 +217,48 @@ Read the getting started guide to familiarize yourself with how Superstruct work
183217
- [Throwing Customized Errors](./docs/guide.md#throwing-customized-errors)
184218
- [Validating Complex Shapes](./docs/guide.md#validating-complex-shapes)
185219
- [Composing Structs](./docs/guide.md#composing-structs)
186-
- [**Reference**](https://superstructjs.org)
187-
- [`Struct`](https://superstructjs.org/interfaces/struct)
188-
- [`Superstruct`](https://superstructjs.org/interfaces/superstruct)
189-
- [`Types`](https://superstructjs.org#types)
190-
- [`StructError`](https://superstructjs.org/classes/structerror)
191-
- [`isStruct`](https://superstructjs.org#isstruct)
220+
- [**Reference**](./docs/reference.md)
221+
- [Validation](./docs/reference.md#validation)
222+
- [`assert`](./docs/reference.md#assert)
223+
- [`coerce`](./docs/reference.md#coerce)
224+
- [`is`](./docs/reference.md#is)
225+
- [`validate`](./docs/reference.md#validate)
226+
- [Types](./docs/reference.md#types)
227+
- [`any`](./docs/reference.md#any)
228+
- [`array`](./docs/reference.md#array)
229+
- [`boolean`](./docs/reference.md#boolean)
230+
- [`date`](./docs/reference.md#date)
231+
- [`enums`](./docs/reference.md#enums)
232+
- [`instance`](./docs/reference.md#instance)
233+
- [`intersection`](./docs/reference.md#intersection)
234+
- [`literal`](./docs/reference.md#literal)
235+
- [`map`](./docs/reference.md#map)
236+
- [`never`](./docs/reference.md#never)
237+
- [`number`](./docs/reference.md#number)
238+
- [`object`](./docs/reference.md#object)
239+
- [`optional`](./docs/reference.md#optional)
240+
- [`partial`](./docs/reference.md#partial)
241+
- [`record`](./docs/reference.md#record)
242+
- [`set`](./docs/reference.md#set)
243+
- [`string`](./docs/reference.md#string)
244+
- [`tuple`](./docs/reference.md#tuple)
245+
- [`type`](./docs/reference.md#type)
246+
- [`union`](./docs/reference.md#union)
247+
- [Custom Types](./docs/reference.md#custom-types)
248+
- [Refinements](./docs/reference.md#refinements)
249+
- [`length`](./docs/reference.md#length)
250+
- [`pattern`](./docs/reference.md#pattern)
251+
- [Custom Refinements](./docs/reference.md#custom-refinements)
252+
- [Coercions](./docs/reference.md#coercions)
253+
- [`defaulted`](./docs/reference.md#defaulted)
254+
- [`masked`](./docs/reference.md#masked)
255+
- [Custom Coercions](./docs/reference.md#custom-coercions)
256+
- [Errors](./docs/reference.md#errors)
257+
- [`StructError`](./docs/reference.md#structerror)
258+
- [Error Properties](./docs/reference.md#error-properties)
259+
- [Multiple Errors](./docs/reference.md#multiple-errors)
260+
- [Utilities](./docs/reference.md#utilities)
261+
- [`StructType`](./docs/reference.md#structtype)
192262
- [**Resources**](/docs/resources.md)
193263

194264
<br/>

config/rollup.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export default {
2424
'@babel/preset-env',
2525
{
2626
targets: {
27-
browsers: ['last 2 versions'],
27+
// browsers: ['last 2 versions'],
28+
node: true,
2829
},
2930
modules: false,
3031
useBuiltIns: false,

0 commit comments

Comments
 (0)