Skip to content

Commit

Permalink
docs: add a guide section with basics, constraints, scopes and generics
Browse files Browse the repository at this point in the history
  • Loading branch information
JesusTheHun committed Nov 19, 2024
1 parent 1b395f3 commit 9d917b1
Show file tree
Hide file tree
Showing 6 changed files with 440 additions and 0 deletions.
6 changes: 6 additions & 0 deletions ark/docs/astro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export default defineConfig({
directory: "intro"
}
},
{
label: "Guides",
autogenerate: {
directory: "guides"
},
},
{
label: "Keywords",
collapsed: true,
Expand Down
1 change: 1 addition & 0 deletions ark/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@ark/fs": "workspace:*",
"@ark/util": "workspace:*",
"@ark/schema": "workspace:*",
"@astrojs/check": "0.9.4",
"@astrojs/react": "3.6.2",
"@astrojs/starlight": "0.28.3",
Expand Down
166 changes: 166 additions & 0 deletions ark/docs/src/content/docs/guides/basics.mdx
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[]"])
```
95 changes: 95 additions & 0 deletions ark/docs/src/content/docs/guides/constraints.mdx
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')
```
96 changes: 96 additions & 0 deletions ark/docs/src/content/docs/guides/generics.mdx
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' });
```
Loading

0 comments on commit 9d917b1

Please sign in to comment.