-
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add a guide section with basics, constraints, scopes and generics
- Loading branch information
1 parent
1b395f3
commit 9d917b1
Showing
6 changed files
with
440 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
--- | ||
title: The basics | ||
sidebar: | ||
order: 1 | ||
--- | ||
|
||
## Primitives & validation | ||
|
||
The `type` object is the entrypoint for your validation schemas. | ||
Let's start with a very simple type : | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
const someString = type('string') | ||
``` | ||
|
||
`type` accepts a variety of values, including `string`, `number`, `symbol`, actual RegEx or literals like `"foobar"`. Maybe you have noticed that our literal string is wrapped into double quoted. That is because Arktype interpret strings the same way TypeScript interpret types. | ||
|
||
Let's validate some data ! | ||
|
||
```ts | ||
import { type } from "arktype" | ||
const someString = type('string') | ||
|
||
// ---cut-before--- | ||
const valid = someString('Hello 👋') // ✅ - Hello 👋 | ||
const invalid = someString(1) // ❌ - ArkErrors | ||
|
||
// The following will throw if the data is invalid, instead of returning ArkErrors. | ||
const str = someString.assert('kaboom?') | ||
``` | ||
|
||
When a value doesn't successfully pass the validation, an instance of ArkErrors is returned instead of the value. | ||
More on errors later. | ||
|
||
## Unions & intersections | ||
|
||
There are several ways to create a union or an intersection with Arktype. | ||
We'll show case unions, but you can use `&` and `.and()` to create intersections. | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
const schema = type('string'); | ||
const schema2 = type('number'); | ||
|
||
// ---cut-before--- | ||
const method1 = type('string | number') | ||
const method2 = type('string').or('number') | ||
const method3 = schema.or(schema2) | ||
``` | ||
|
||
Notice that you don't have to pass an instance of `type` to `.or()`, the argument is already interpreted as such. | ||
If you want to create a larger union, you can use nested tuples : | ||
|
||
```ts | ||
// @errors: 2304 | ||
import { type } from "arktype" | ||
|
||
const schema1 = type({ foo: 'string' }); | ||
const schema2 = type({ bar: 'string' }); | ||
const schema3 = type({ bazz: 'string' }); | ||
const schema4 = type({ yolo: '"Hell yeah!"'}); | ||
// ---cut-before--- | ||
|
||
const method4 = type([ | ||
schema1, '|', [ | ||
schema2, '|', [ | ||
schema3, '|', schema4 | ||
]]]) | ||
``` | ||
|
||
Because `type` accepts a tuple as argument, and because each element of the tuple is interpreted as an instance of `type`, you can use nested tuples to create more complex types in one go. | ||
|
||
## Objects | ||
|
||
Creating an object with Arktype is very simple : | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const userSchema = type({ | ||
username: 'string', | ||
'birthdate?': 'string', // We make the property as optional with "?" | ||
'connectionCount?': 'number = 1', // We set the default value to 1 | ||
}) | ||
``` | ||
|
||
Again, every value is interpreted as an instance of `type`, so you don't need to repeat yourself. | ||
|
||
:::note | ||
The optional marker only allow the property to be missing, not to be `undefined`. | ||
To allow both, use `undefined | string?`. | ||
::: | ||
|
||
### Records | ||
|
||
If you want to create records, `type` has a utility property that can help you with that : | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const teamSchema = type({ | ||
name: 'string', | ||
memberships: type.Record( | ||
/user__[a-z0-9]/, | ||
{ jointedAt: 'number', leftAt: 'number?' } | ||
) | ||
}) | ||
``` | ||
|
||
### Merging schemas | ||
|
||
Quite often, the data you expect will share some properties with other schemas. | ||
Instead of repeating yourself, you can re-use a schema in others. Hover `orderReceivedEventSchema` to discover the merged type. | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const baseEventSchema = type({ | ||
type: '"orderReceived" | "orderAccepted"', | ||
createdBy: 'string', | ||
createdAt: 'number', | ||
}) | ||
|
||
const orderReceivedEventSchema = baseEventSchema.merge({ | ||
type: '"orderReceived"', | ||
payload: { | ||
orderId: 'string', | ||
} | ||
}) | ||
``` | ||
|
||
As you can see by hovering `orderReceivedEventSchema`, the overlapping property defined in the merged object overwrites the one of the base object. | ||
|
||
## Arrays & tuples | ||
|
||
You can create an array in several ways : | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const schema = type('string[]') | ||
const schema2 = type('string').array() | ||
const schema3 = type.Array('string') | ||
``` | ||
|
||
Creating tuples is not more difficult and follow the same rules we've described before : | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
const userSchema = type({ | ||
username: 'string' | ||
}) | ||
|
||
// ---cut-before--- | ||
const schema = type(['string', 'number']) | ||
const schema3 = type(["string", userSchema]) | ||
const schema2 = type(["string", "...", "number[]"]) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
--- | ||
title: Adding constraints | ||
sidebar: | ||
order: 2 | ||
--- | ||
|
||
## String | ||
|
||
Let's dive right into it with some classics : | ||
|
||
### Length constraints | ||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const min4 = type('string >= 4') | ||
const max10 = type('string <= 10') | ||
const min4max10 = type('string >= 4 & string <= 10') | ||
``` | ||
|
||
### Shape constraints | ||
|
||
There are many constraints built-in, just type `string.` to discover them all. | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const regex = type(/[a-z0-9]{4,10}/) | ||
const email = type('string.email') | ||
const date = type('string.date.iso') | ||
``` | ||
|
||
### Custom constraints | ||
Arktype offer some transformation mechanisms. You can apply a constraint before or after the transformations. | ||
|
||
|
||
#### `satisfying` | ||
|
||
Use `satisfying` will be apply before any transformation logic. | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
type UserId = `user__${string}`; | ||
|
||
const userId = type('string').satisfying((v, ctx): v is UserId => { | ||
if (v.startsWith('user__')) { | ||
return true | ||
} | ||
|
||
return ctx.mustBe('a UserId') | ||
}); | ||
``` | ||
|
||
#### `narrow` | ||
|
||
Use `narrow` will be apply after all transformation logic. | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const stepNumber = type('string.numeric.parse').narrow((v, ctx) => { | ||
if (v > 0 && v < 4) { | ||
return true | ||
} | ||
|
||
return ctx.mustBe('a step number'); | ||
}); | ||
``` | ||
|
||
## Number | ||
|
||
### Range constraints | ||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const min4 = type('number >= 4') | ||
const max10 = type('number <= 10') | ||
const min4max10 = type('number >= 4 & number <= 10') | ||
``` | ||
|
||
### Other constraints | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
// ---cut-before--- | ||
const integer = type('number.integer') | ||
const safeInteger = type('number.safe') // number <= Number.MAX_SAFE_INTEGER | ||
const epoch = type('number.epoch') | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
--- | ||
title: Dynamic types | ||
sidebar: | ||
order: 4 | ||
--- | ||
|
||
Ever wanted to create a function that takes a schema and return another schema ? | ||
Arktype call them `generics`, and they are exactly what you think they are. | ||
|
||
## Single argument | ||
|
||
Let's start simple with a dynamic `payload` property : | ||
```ts | ||
import { type } from "arktype" | ||
|
||
const eventType = type('<Payload>', { | ||
type: 'string', | ||
createdAt: 'number.epoch', | ||
payload: 'Payload' | ||
}) | ||
``` | ||
|
||
Now you can use `eventType` to create a new type : | ||
|
||
```ts | ||
import { type } from "arktype" | ||
|
||
const eventType = type('<Payload>', { | ||
type: 'string', | ||
createdAt: 'number.epoch', | ||
payload: 'Payload' | ||
}) | ||
|
||
// ---cut-before--- | ||
const orderCreatedEvent = eventType({ | ||
orderId: 'string', | ||
numberOfItems: 'number > 0' | ||
}) | ||
``` | ||
|
||
You can also add a constraint, just you like you would do it in TypeScript with `<Payload extends Record<string, unknown>>`. | ||
|
||
## Multiple arguments | ||
If you want to have more parameters, you need a new syntax : | ||
|
||
```ts | ||
import { generic } from "arktype" | ||
|
||
const eventType = generic(['Type', 'string'], 'Payload')({ | ||
type: 'Type', | ||
createdAt: 'number.epoch', | ||
payload: 'Payload' | ||
}); | ||
``` | ||
|
||
First thing to notice, there is no bracket using this syntax. | ||
Second, if you want to add a constraint to your generic, you need to pass a tuple. | ||
Here, `Type` must extends `string`, and because `Payload` is given as is, there is no constraint on it this time. | ||
|
||
## Going further | ||
|
||
Perhaps you have noticed that the construction of the dynamic type is done in two steps. | ||
|
||
```ts | ||
import { generic } from "arktype" | ||
|
||
// We create a function that will accept a type that have access to the generic arguments. | ||
const genericParser = generic(['Type', 'string'], 'Payload'); | ||
|
||
// We create a function that will receive the arguments and return a type. | ||
const eventType = genericParser({ | ||
type: 'Type', | ||
createdAt: 'number.epoch', | ||
payload: 'Payload' | ||
}); | ||
``` | ||
|
||
The argument given to `genericParser` is, itself, a type definition. | ||
This means you can use all available syntaxes to create your dynamic type with generics. | ||
For example, if you want to merge types, you can do it : | ||
|
||
```ts | ||
import { generic, type } from "arktype" | ||
|
||
const genericParser = generic(['CreatedBy', 'string'], 'Schema'); | ||
|
||
// ---cut-before--- | ||
const audit = type({ | ||
createdBy: 'string', | ||
createdAt: 'number.epoch' | ||
}) | ||
|
||
const withAudit = type('<Schema>', [audit, '&', 'Schema']); | ||
|
||
const bookWithAudit = withAudit({ title: 'string' }); | ||
``` |
Oops, something went wrong.