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

DRAFT: Improve onboarding with documentation guides #1206

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
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
169 changes: 169 additions & 0 deletions ark/docs/src/content/docs/guides/basics.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
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 will 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` also accepts a tuple as an 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 `{ 'birthdate?': '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[]"])
```

Have you noticed the `"..."` operator to create the variable number of elements in the tuple ?
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')
```

## 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')
```

## Custom constraints
Arktype offers some transformation mechanisms. You can apply constraints before or after the transformations.


### `satisfying`

Use `satisfying` and the constraints 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` and the constraints 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');
});
```
106 changes: 106 additions & 0 deletions ark/docs/src/content/docs/guides/generics.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: Dynamic types
sidebar:
order: 4
---

Ever wanted to create a function that takes a schema and return another schema ?
Arktype calls 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'
})
```
Hover `orderCreatedEvent` to discover your new type !

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 angle bracket with 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, we have set no constraint on it.

## 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 use the tuple syntax :

```ts
import { generic, type } from "arktype"

const genericParser = generic(['CreatedBy', 'string'], 'Schema');

// ---cut-before---
// We prepare our generic type
const audit = type({
createdBy: 'string',
createdAt: 'number.epoch',
})

const withAudit = type('<Schema>', [audit, '&', 'Schema']);

// Some app type
const book = type({
title: 'string',
authors: 'string[]',
})

// We use our generic type to create a new type 🪄
const bookWithAudit = withAudit(book);
```
Loading
Loading