You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: packages/effect/SCHEMA.md
+205Lines changed: 205 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1991,6 +1991,211 @@ console.log(matcher({ type: "B", b: 1 })) // This is a B: 1
1991
1991
console.log(matcher({ type: "C", c: true })) // This is a C: true
1992
1992
```
1993
1993
1994
+
# Declaring Custom Types
1995
+
1996
+
When none of the built-in schema combinators fit your data type, use `Schema.declare` or `Schema.declareConstructor`.
1997
+
1998
+
## `Schema.declare` (non-parametric types)
1999
+
2000
+
`Schema.declare` creates a schema from a **type guard** — a function that checks whether an unknown value is of a given type. This is useful when you have a type that doesn't fit the built-in combinators (like `Struct`, `Array`, etc.) and you need to teach Schema how to recognize it.
> **Tip**: For simple `instanceof` checks, prefer `Schema.instanceOf(URL)`, it wraps `Schema.declare` with an `instanceof` guard automatically.
2029
+
2030
+
### Customizing the error message with `expected`
2031
+
2032
+
The default error message `Expected <Declaration>` is not very descriptive. Use the `expected` annotation (second argument) to provide a human-readable name for your type.
// Now the error message shows "URL" instead of "<Declaration>"
2048
+
```
2049
+
2050
+
### Adding JSON support with `toCodecJson`
2051
+
2052
+
`Schema.toCodecJson` derives a codec that can convert your type **to and from JSON**. By default, declared schemas have no JSON representation — encoding produces `null`:
2053
+
2054
+
```ts
2055
+
import { Schema } from"effect"
2056
+
2057
+
const URLSchema =Schema.declare(
2058
+
(u):uisURL=>uinstanceofURL,
2059
+
{ expected: "URL" }
2060
+
)
2061
+
2062
+
// Derive a JSON codec from the schema
2063
+
const codec =Schema.toCodecJson(URLSchema)
2064
+
2065
+
// Encoding a URL produces null because Schema doesn't know
To fix this, provide a `toCodecJson` annotation. This annotation is a function that returns an `AST.Link`, a bridge that describes how to convert between your custom type and a JSON-friendly representation.
2072
+
2073
+
You build a `Link` using `Schema.link<T>()`, which takes two arguments:
2074
+
2075
+
1.**A JSON-side schema** — the shape of the JSON value (e.g. `Schema.String` for a URL string)
2076
+
2.**A transformation** — how to convert back and forth between your type and the JSON value
While `Schema.declare` works for fixed types like `URL` or `File`, some types are **generic** — they contain other types as parameters. Think of `Array<A>`, `Option<A>`, or a custom `Box<A>`. The schema for `Box<number>` is different from `Box<string>` because the inner value has a different type.
2121
+
2122
+
`Schema.declareConstructor` handles this by letting you define a **schema factory**: a function that takes schemas for the type parameters and returns a schema for the full type.
2123
+
2124
+
### How the two-step call works
2125
+
2126
+
`declareConstructor` uses a curried (two-step) call pattern:
2127
+
2128
+
```ts
2129
+
Schema.declareConstructor<Type, Encoded>()(
2130
+
typeParameters, // array of schemas, one per type parameter
2131
+
run, // factory that produces the parsing function
2132
+
annotations// optional metadata (same as Schema.declare)
2133
+
)
2134
+
```
2135
+
2136
+
1.**Outer call**`declareConstructor<Type, Encoded>()` — fixes the TypeScript types. `Type` is the decoded type, `Encoded` is the encoded type.
2137
+
2.**Inner call**`(typeParameters, run, annotations)` — provides the runtime behavior:
2138
+
-`typeParameters` — an array of schemas, one for each type variable (e.g. `[itemSchema]` for `Box<A>`)
2139
+
-`run` — a function that receives **resolved codecs** for those type parameters and returns a **parsing function**`(input, ast, options) => Effect<T, Issue>`
2140
+
-`annotations` — optional metadata like `expected`, `toCodecJson`, etc.
2141
+
2142
+
The parsing function you return from `run` is responsible for:
2143
+
2144
+
1. Checking that the input has the right shape (e.g. is an object with a `value` property)
2145
+
2. Recursively decoding inner values using the provided codecs
2146
+
3. Returning an `Effect` that succeeds with the decoded value or fails with an issue
console.log(String(Schema.decodeUnknownExit(schema)({ value: "a" })))
2193
+
// Failure(Cause([Fail(SchemaError(Expected a finite number, got NaN
2194
+
// at ["value"]))]))
2195
+
```
2196
+
2197
+
> `declareConstructor` accepts the same `annotations` as `declare` — including `expected` (for custom error messages) and `toCodecJson` (for JSON serialization). See the [`Schema.declare` section above](#schemadeclare-non-parametric-types) for details on how to use them.
2198
+
1994
2199
# Validation
1995
2200
1996
2201
After defining a schema's shape, you can add validation rules called _filters_. Filters check runtime values against constraints like minimum length, numeric range, or custom predicates. Validation happens at runtime — Schema checks the actual value against the rules you define and reports any violations.
0 commit comments