# Structs, Records, Arrays & Tuples

Most real schemas are objects. `Schema.Struct` describes an object with a fixed
set of known keys; `Schema.Record` describes a dictionary with a uniform key and
value type. Around them sit arrays, tuples, and the Effect collection types
(`Chunk`, `HashMap`, `ReadonlySet`, …) for ordered and keyed data.

```ts
import { Schema } from "effect"

// A struct with required, optional, and constrained fields.
const User = Schema.Struct({
  id: Schema.String,
  name: Schema.String.check(Schema.isMinLength(1)),
  // Exact optional: the key may be omitted entirely, but not set to undefined.
  email: Schema.optionalKey(Schema.String),
  // A nested array of literals.
  roles: Schema.Array(Schema.Literals(["admin", "member"]))
})

const user = Schema.decodeUnknownSync(User)({
  id: "u_1",
  name: "Alice",
  roles: ["admin"]
})

console.log(user)
// { id: "u_1", name: "Alice", roles: ["admin"] }
```

## Structs

A struct decodes an object key by key. By default every field is required and
the resulting type is `readonly`. The decoded `Type` and `Encoded` types are
derived from the field schemas:

```ts
import { Schema } from "effect"

const Point = Schema.Struct({
  x: Schema.Number,
  y: Schema.Number
})

type Point = typeof Point.Type
// { readonly x: number; readonly y: number }
```

### Optional fields

There are two distinct notions of "optional", and the difference matters:

- **`Schema.optionalKey(S)`** — *exact* optional. The key may be **absent**, but
  if present it must satisfy `S`. The type becomes `key?: T`.
- **`Schema.optional(S)`** — the key may be absent **or** explicitly
  `undefined`. The type becomes `key?: T | undefined`. (It is shorthand for
  `optionalKey(UndefinedOr(S))`.)

```ts
import { Schema } from "effect"

const Profile = Schema.Struct({
  name: Schema.String,
  // absent OK, undefined NOT OK
  nickname: Schema.optionalKey(Schema.String),
  // absent OK, undefined also OK
  bio: Schema.optional(Schema.String)
})

type Profile = typeof Profile.Type
// {
//   readonly name: string
//   readonly nickname?: string
//   readonly bio?: string | undefined
// }
```
**Caution:** Reach for `optionalKey` by default. Use `optional` only when an explicit
`undefined` in the input is meaningful and should be accepted.

### Defaults for missing keys

To supply a value when a key is absent during decoding, wrap the field with
`Schema.withDecodingDefaultKey`. The default is provided as an `Effect`, so it
can be computed (and can even use services).

```ts
import { Effect, Schema } from "effect"

const Settings = Schema.Struct({
  // When `theme` is missing from the input, decode it as "light".
  theme: Schema.String.pipe(
    Schema.withDecodingDefaultKey(Effect.succeed("light"))
  )
})

console.log(Schema.decodeUnknownSync(Settings)({}))
// { theme: "light" }
```

### Mutable fields

Struct fields are `readonly` by default. Use `Schema.mutableKey` to drop the
`readonly` modifier on a single field:

```ts
import { Schema } from "effect"

const Counter = Schema.Struct({
  label: Schema.String,
  // produces `count: number` instead of `readonly count: number`
  count: Schema.mutableKey(Schema.Number)
})
```

## Records

`Schema.Record` describes a dictionary: an object whose keys all share one
schema and whose values all share another. Use it when the keys are not known
ahead of time.

```ts
import { Schema } from "effect"

// { readonly [x: string]: number }
const Scores = Schema.Record(Schema.String, Schema.Number)

const scores = Schema.decodeUnknownSync(Scores)({ alice: 10, bob: 7 })

console.log(scores)
// { alice: 10, bob: 7 }
```

## Arrays

`Schema.Array` describes a `ReadonlyArray` of a single element schema.
`Schema.NonEmptyArray` additionally requires at least one element and narrows the
type to a non-empty tuple.

```ts
import { Schema } from "effect"

const Tags = Schema.Array(Schema.String)
type Tags = typeof Tags.Type // readonly string[]

const Coordinates = Schema.NonEmptyArray(Schema.Number)
type Coordinates = typeof Coordinates.Type // readonly [number, ...number[]]
```

## Tuples

`Schema.Tuple` describes a fixed-length, positionally-typed array. To allow
additional trailing elements of a uniform type, use `Schema.TupleWithRest`.

```ts
import { Schema } from "effect"

// Exactly [string, number]
const Pair = Schema.Tuple([Schema.String, Schema.Number])
type Pair = typeof Pair.Type // readonly [string, number]

// [string, ...number[]] — a label followed by any number of values
const Row = Schema.TupleWithRest(
  Schema.Tuple([Schema.String]),
  [Schema.Number]
)
```

## Discriminated unions

When you have several object shapes distinguished by a tag, `Schema.TaggedUnion`
builds the union and the matching helpers in one step. Each key becomes the
`_tag` literal of that variant.

```ts
import { Schema } from "effect"

const Shape = Schema.TaggedUnion({
  Circle: { radius: Schema.Number },
  Rectangle: { width: Schema.Number, height: Schema.Number }
})

// `match` is generated for you — exhaustive over the variants.
const area = Shape.match(
  { _tag: "Circle", radius: 5 },
  {
    Circle: (c) => Math.PI * c.radius ** 2,
    Rectangle: (r) => r.width * r.height
  }
)

console.log(area.toFixed(2)) // "78.54"
```

This pairs naturally with [pattern matching](https://effect.plants.sh/pattern-matching/) and is the
recommended way to model serializable variant data.

## Recursive schemas

A schema that refers to itself must defer the self-reference with
`Schema.suspend`, otherwise the definition would loop forever while being built.

```ts
import { Schema } from "effect"

interface Category {
  readonly name: string
  readonly subcategories: ReadonlyArray<Category>
}

const Category = Schema.Struct({
  name: Schema.String,
  // `suspend` delays evaluating `Category` until it is actually needed.
  subcategories: Schema.Array(
    Schema.suspend((): Schema.Codec<Category> => Category)
  )
})
```

---

# Reference

Everything below is a per-API reference. Each entry has a short description and a
runnable snippet. The first group covers schema **constructors**, the next the
struct **field modifiers**, and the last the `Struct` module helpers for
reshaping plain objects (and the matching schema-level combinators).

## Object schemas

### Struct

Defines an object with a fixed set of known keys. Each field value is a schema;
the decoded `Type` is a readonly object.

```ts
import { Schema } from "effect"

const Person = Schema.Struct({
  name: Schema.String,
  age: Schema.Number
})

console.log(Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }))
// => { name: "Alice", age: 30 }
```

### StructWithRest

Extends a struct with one or more `Record` index signatures. The decoded type is
the struct intersected with every rest record, so it has both the fixed keys and
the open-ended ones.

```ts
import { Schema } from "effect"

const Config = Schema.StructWithRest(
  Schema.Struct({ id: Schema.Number }),
  [Schema.Record(Schema.String, Schema.String)]
)

// { readonly id: number } & { readonly [x: string]: string }
type Config = typeof Config.Type

console.log(Schema.decodeUnknownSync(Config)({ id: 1, env: "prod" }))
// => { id: 1, env: "prod" }
```

### TaggedStruct

A `Struct` shorthand that prepends a literal `_tag` field. `Schema.TaggedStruct("A", fields)`
is exactly `Schema.Struct({ _tag: Schema.tag("A"), ...fields })` — useful for the
cases of a discriminated union.

```ts
import { Schema } from "effect"

const Move = Schema.TaggedStruct("Move", { dx: Schema.Number, dy: Schema.Number })

console.log(Schema.decodeUnknownSync(Move)({ _tag: "Move", dx: 1, dy: 2 }))
// => { _tag: "Move", dx: 1, dy: 2 }

// The `make` constructor fills in `_tag` for you.
console.log(Move.make({ dx: 1, dy: 2 }))
// => { _tag: "Move", dx: 1, dy: 2 }
```

### TaggedUnion

Builds a union of `TaggedStruct`s from a `{ Tag: fields }` map and attaches
`match` / guard helpers. See [Discriminated unions](#discriminated-unions) above.

```ts
import { Schema } from "effect"

const Event = Schema.TaggedUnion({
  Created: { id: Schema.String },
  Deleted: { id: Schema.String, reason: Schema.String }
})

console.log(Schema.decodeUnknownSync(Event)({ _tag: "Created", id: "e1" }))
// => { _tag: "Created", id: "e1" }
```

## Records & dictionaries

### Record

A dictionary schema with a uniform key schema and value schema. The key schema
may be `String`, `Number`, a literal union, a template literal, or a refined
string.

```ts
import { Schema } from "effect"

const ByName = Schema.Record(Schema.String, Schema.Number)
console.log(Schema.decodeUnknownSync(ByName)({ a: 1, b: 2 }))
// => { a: 1, b: 2 }

// Restrict the allowed keys with a literal union.
const Flags = Schema.Record(
  Schema.Literals(["read", "write"]),
  Schema.Boolean
)
console.log(Schema.decodeUnknownSync(Flags)({ read: true, write: false }))
// => { read: true, write: false }
```

### isPropertyNames

A filter (used with `.check`) that validates **every** own key of an object —
including symbol keys — against the encoded side of a key schema. Pair it with
`Record` or a `Struct` to constrain key shapes.

```ts
import { Schema } from "effect"

// Keys must match the literal union, values are numbers.
const Limits = Schema.Record(Schema.String, Schema.Number).check(
  Schema.isPropertyNames(Schema.Literals(["min", "max"]))
)

console.log(Schema.decodeUnknownSync(Limits)({ min: 0, max: 10 }))
// => { min: 0, max: 10 }

// Schema.decodeUnknownSync(Limits)({ min: 0, other: 1 }) // throws: "other" not allowed
```
**Tip:** To decode a `Record` from a JSON string, form data, or URL search params, see
the record codecs in [transformations](https://effect.plants.sh/schema/transformations/).

## Collections

### Array

A `ReadonlyArray` of a single element schema. (`Schema.Array` is exported from
the `Array` constructor; reference it as `Schema.Array`.)

```ts
import { Schema } from "effect"

const Names = Schema.Array(Schema.String)
console.log(Schema.decodeUnknownSync(Names)(["a", "b"]))
// => ["a", "b"]
```

### NonEmptyArray

Like `Array` but requires at least one element; the decoded type is
`readonly [T, ...T[]]`.

```ts
import { Schema } from "effect"

const Path = Schema.NonEmptyArray(Schema.String)
console.log(Schema.decodeUnknownSync(Path)(["root"]))
// => ["root"]

// Schema.decodeUnknownSync(Path)([]) // throws: array must have at least 1 element
```

### ArrayEnsure

Accepts **either** a single value or an array, and always decodes to an array.
On encoding, a one-element array is written back as the bare element.

```ts
import { Schema } from "effect"

const Tags = Schema.ArrayEnsure(Schema.String)

console.log(Schema.decodeUnknownSync(Tags)("solo")) // => ["solo"]
console.log(Schema.decodeUnknownSync(Tags)(["a", "b"])) // => ["a", "b"]

console.log(Schema.encodeUnknownSync(Tags)(["solo"])) // => "solo"
console.log(Schema.encodeUnknownSync(Tags)(["a", "b"])) // => ["a", "b"]
```

### UniqueArray

An array schema that additionally requires all elements to be unique, using the
element schema's derived equivalence.

```ts
import { Schema } from "effect"

const Ids = Schema.UniqueArray(Schema.Number)
console.log(Schema.decodeUnknownSync(Ids)([1, 2, 3]))
// => [1, 2, 3]

// Schema.decodeUnknownSync(Ids)([1, 1]) // throws: duplicate element
```

### Chunk

Decodes a `Chunk` (Effect's immutable sequence). The decoded `Type` is
`Chunk<T>`; the Iso/array form is a plain array of elements.

```ts
import { Chunk, Schema } from "effect"

const Numbers = Schema.Chunk(Schema.Number)
const chunk = Schema.decodeUnknownSync(Numbers)(Chunk.make(1, 2, 3))
console.log(Chunk.toReadonlyArray(chunk))
// => [1, 2, 3]
```

### ReadonlySet

Decodes a native `ReadonlySet` whose values conform to the element schema.

```ts
import { Schema } from "effect"

const Ids = Schema.ReadonlySet(Schema.Number)
const set = Schema.decodeUnknownSync(Ids)(new Set([1, 2, 2]))
console.log([...set])
// => [1, 2]
```

### HashSet

Decodes an Effect `HashSet` of the element schema (value-based equality).

```ts
import { HashSet, Schema } from "effect"

const Ids = Schema.HashSet(Schema.Number)
const set = Schema.decodeUnknownSync(Ids)(HashSet.make(1, 2, 3))
console.log(HashSet.size(set))
// => 3
```

### ReadonlyMap

Decodes a native `ReadonlyMap` with typed keys and values. The Iso form is an
array of `[key, value]` tuples.

```ts
import { Schema } from "effect"

const Scores = Schema.ReadonlyMap(Schema.String, Schema.Number)
const map = Schema.decodeUnknownSync(Scores)(new Map([["a", 1], ["b", 2]]))
console.log([...map])
// => [["a", 1], ["b", 2]]
```

### HashMap

Decodes an Effect `HashMap` with typed keys and values (value-based key
equality). The Iso form is an array of `[key, value]` tuples.

```ts
import { HashMap, Schema } from "effect"

const Scores = Schema.HashMap(Schema.String, Schema.Number)
const map = Schema.decodeUnknownSync(Scores)(HashMap.make(["a", 1], ["b", 2]))
console.log(HashMap.get(map, "a"))
// => { _tag: "Some", value: 1 }
```

## Tuples

### Tuple

A fixed-length, positionally-typed array.

```ts
import { Schema } from "effect"

const Pair = Schema.Tuple([Schema.String, Schema.Number])
console.log(Schema.decodeUnknownSync(Pair)(["hello", 42]))
// => ["hello", 42]
```

### TupleWithRest

A leading tuple followed by a uniform "rest" of trailing elements. The rest is an
array of schemas, supporting both a rest element and fixed trailing elements.

```ts
import { Schema } from "effect"

// [string, number, ...boolean[]]
const Row = Schema.TupleWithRest(
  Schema.Tuple([Schema.String, Schema.Number]),
  [Schema.Boolean]
)

console.log(Schema.decodeUnknownSync(Row)(["x", 1, true, false]))
// => ["x", 1, true, false]
```

### mutable

Makes an array or tuple schema mutable, removing the `readonly` modifier from the
decoded type.

```ts
import { Schema } from "effect"

const Mutable = Schema.mutable(Schema.Array(Schema.Number))
type Mutable = typeof Mutable.Type // number[]  (no readonly)
```

## Field modifiers

These wrap a single field inside a `Struct`. They control optionality and
mutability of the **decoded key**; `encodeKeys` is the only one that changes the
**encoded key name**.

### optionalKey

Exact optional: the key may be **absent**, but if present must satisfy the inner
schema. Produces `key?: T`.

```ts
import { Schema } from "effect"

const S = Schema.Struct({ name: Schema.optionalKey(Schema.String) })
type S = typeof S.Type // { readonly name?: string }

console.log(Schema.decodeUnknownSync(S)({})) // => {}
console.log(Schema.decodeUnknownSync(S)({ name: "a" })) // => { name: "a" }
```

### optional

The key may be absent **or** explicitly `undefined`. Shorthand for
`optionalKey(UndefinedOr(S))`; produces `key?: T | undefined`.

```ts
import { Schema } from "effect"

const S = Schema.Struct({ name: Schema.optional(Schema.String) })
type S = typeof S.Type // { readonly name?: string | undefined }

console.log(Schema.decodeUnknownSync(S)({ name: undefined }))
// => { name: undefined }
```

### requiredKey

Reverses `optionalKey`, returning the inner schema as a required field.

```ts
import { Schema } from "effect"

const opt = Schema.optionalKey(Schema.String)
const req = Schema.requiredKey(opt)

const S = Schema.Struct({ name: req })
type S = typeof S.Type // { readonly name: string }
```

### required

Reverses `optional`, unwrapping the `UndefinedOr` member that `optional` added.

```ts
import { Schema } from "effect"

const opt = Schema.optional(Schema.String)
const req = Schema.required(opt)

const S = Schema.Struct({ name: req })
type S = typeof S.Type // { readonly name: string }
```

### mutableKey

Removes the `readonly` modifier on a single field's decoded property. Reverse
with `readonlyKey`.

```ts
import { Schema } from "effect"

const S = Schema.Struct({ count: Schema.mutableKey(Schema.Number) })
type S = typeof S.Type // { count: number }  (not readonly)
```

### readonlyKey

Reverses `mutableKey`, restoring the `readonly` modifier.

```ts
import { Schema } from "effect"

const mut = Schema.mutableKey(Schema.Number)
const ro = Schema.readonlyKey(mut)

const S = Schema.Struct({ count: ro })
type S = typeof S.Type // { readonly count: number }
```

### encodeKeys

Renames keys in the **encoded** form without touching the decoded type. Takes a
partial `{ decodedKey: encodedKey }` mapping and produces a transformation in
both directions.

```ts
import { Schema } from "effect"

const Person = Schema.Struct({ name: Schema.String, age: Schema.Number })
const Wire = Person.pipe(Schema.encodeKeys({ name: "full_name" }))

// decode: encoded key -> decoded key
console.log(Schema.decodeUnknownSync(Wire)({ full_name: "Alice", age: 30 }))
// => { name: "Alice", age: 30 }

// encode: decoded key -> encoded key
console.log(Schema.encodeUnknownSync(Wire)({ name: "Alice", age: 30 }))
// => { full_name: "Alice", age: 30 }
```

## Reshaping a struct

A `Struct` exposes its field map so you can reuse and transform fields. `fields`
is the raw map (spread it); `mapFields` rebuilds a struct after running a
function over the whole map — typically one of the `Struct` module helpers below.

### Struct.fields

The struct's field definitions. Spread them into a new struct to reuse fields
across schemas.

```ts
import { Schema } from "effect"

const Timestamped = Schema.Struct({
  createdAt: Schema.Date,
  updatedAt: Schema.Date
})

const User = Schema.Struct({
  ...Timestamped.fields,
  name: Schema.String
})
// { readonly createdAt: Date; readonly updatedAt: Date; readonly name: string }
```

### Struct.mapFields

Returns a new struct after transforming the entire field map with a function.
This is the entry point for the `Struct` module reshaping helpers.

```ts
import { Schema, Struct } from "effect"

const Full = Schema.Struct({
  id: Schema.String,
  name: Schema.String,
  secret: Schema.String
})

// Drop a field by mapping the field map through Struct.omit.
const Public = Full.mapFields(Struct.omit(["secret"]))
type Public = typeof Public.Type // { readonly id: string; readonly name: string }
```
**Note:** The `Struct.*` helpers below operate on plain objects (and, via `mapFields`, on a
struct's field map). They are dual: `Struct.pick(obj, keys)` (data-first) and
`pipe(obj, Struct.pick(keys))` (data-last) both work.

### Struct.pick

Creates a new object containing only the listed keys. With `mapFields`, narrows a
schema's fields.

```ts
import { Struct } from "effect"

console.log(Struct.pick({ a: 1, b: 2, c: 3 }, ["a", "c"]))
// => { a: 1, c: 3 }
```

### Struct.omit

Creates a new object with the listed keys removed.

```ts
import { Struct } from "effect"

console.log(Struct.omit({ a: 1, b: 2, c: 3 }, ["b"]))
// => { a: 1, c: 3 }
```

### Struct.assign

Merges two objects; on overlapping keys the second wins (type-level `{ ...a, ...b }`).

```ts
import { Struct } from "effect"

console.log(Struct.assign({ theme: "light", lang: "en" }, { theme: "dark" }))
// => { theme: "dark", lang: "en" }
```

Use it with `mapFields` to add fields to a schema:

```ts
import { Schema, Struct } from "effect"

const Base = Schema.Struct({ id: Schema.String })
const WithName = Base.mapFields(Struct.assign({ name: Schema.String }))
// { readonly id: string; readonly name: string }
```

### Struct.evolve

Transforms selected **values** with per-key functions; untouched keys are copied
as-is. Return types may differ from the inputs.

```ts
import { Struct } from "effect"

console.log(
  Struct.evolve({ name: "alice", age: 30 }, {
    name: (s) => s.toUpperCase(),
    age: (n) => n + 1
  })
)
// => { name: "ALICE", age: 31 }
```

### Struct.evolveKeys

Transforms selected **keys** with per-key functions; values are unchanged.

```ts
import { Struct } from "effect"

console.log(
  Struct.evolveKeys({ name: "Alice", age: 30 }, {
    name: (k) => k.toUpperCase()
  })
)
// => { NAME: "Alice", age: 30 }
```

### Struct.evolveEntries

Transforms both key and value together; each function returns a `[newKey, newValue]`
tuple.

```ts
import { Struct } from "effect"

console.log(
  Struct.evolveEntries({ amount: 100, label: "total" }, {
    amount: (k, v) => [`${k}Cents`, v * 100],
    label: (k, v) => [k, v.toUpperCase()]
  })
)
// => { amountCents: 10000, label: "TOTAL" }
```

### Struct.renameKeys

Renames keys via a static `{ oldKey: newKey }` mapping; unmentioned keys are kept.

```ts
import { Struct } from "effect"

console.log(
  Struct.renameKeys({ firstName: "Alice", lastName: "Smith" }, {
    firstName: "first",
    lastName: "last"
  })
)
// => { first: "Alice", last: "Smith" }
```

### Struct.map

Applies one typed `Lambda` to **every** value. The lambda must be created with
`Struct.lambda` so the compiler can track output types.

```ts
import { pipe, Struct } from "effect"

interface AsArray extends Struct.Lambda {
  <A>(self: A): Array<A>
  readonly "~lambda.out": Array<this["~lambda.in"]>
}
const asArray = Struct.lambda<AsArray>((a) => [a])

console.log(pipe({ x: 1, y: 2 }, Struct.map(asArray)))
// => { x: [1], y: [2] }
```

### Struct.mapPick

Like `Struct.map`, but applies the lambda only to the listed keys.

```ts
import { pipe, Struct } from "effect"

interface AsArray extends Struct.Lambda {
  <A>(self: A): Array<A>
  readonly "~lambda.out": Array<this["~lambda.in"]>
}
const asArray = Struct.lambda<AsArray>((a) => [a])

console.log(pipe({ x: 1, y: 2, z: 3 }, Struct.mapPick(["x", "z"], asArray)))
// => { x: [1], y: 2, z: [3] }
```

### Struct.mapOmit

Like `Struct.map`, but applies the lambda to all keys **except** the listed ones.

```ts
import { pipe, Struct } from "effect"

interface AsArray extends Struct.Lambda {
  <A>(self: A): Array<A>
  readonly "~lambda.out": Array<this["~lambda.in"]>
}
const asArray = Struct.lambda<AsArray>((a) => [a])

console.log(pipe({ x: 1, y: 2, z: 3 }, Struct.mapOmit(["y"], asArray)))
// => { x: [1], y: 2, z: [3] }
```

### Schema.fieldsAssign

A struct-mapping lambda that adds fields. It is a shortcut for
`mapFields(Struct.assign(fields))`, and works on every struct member of a union
via `mapMembers`.

```ts
import { Schema, Tuple } from "effect"

// Add `c` to all members of a union of structs.
const Tagged = Schema.Union([
  Schema.Struct({ a: Schema.String }),
  Schema.Struct({ b: Schema.Number })
]).mapMembers(Tuple.map(Schema.fieldsAssign({ c: Schema.Number })))
```

### Schema.extendTo

Adds **computed** fields to a struct during decoding. Each derived field returns
an `Option`; on encoding the derived fields are stripped, so they live only in the
decoded type.

```ts
import { Option, Schema } from "effect"

const Person = Schema.Struct({ first: Schema.String, last: Schema.String })

const Extended = Person.pipe(
  Schema.extendTo(
    { fullName: Schema.String },
    { fullName: (p) => Option.some(`${p.first} ${p.last}`) }
  )
)

const alice = Schema.decodeUnknownSync(Extended)({ first: "Alice", last: "Smith" })
console.log(alice.fullName)
// => "Alice Smith"
```

## Recursive & self-referential

### suspend

Defers evaluation of a schema until it is needed, which is required for any
schema that references itself.

```ts
import { Schema } from "effect"

interface Tree {
  readonly value: number
  readonly children: ReadonlyArray<Tree>
}

const Tree = Schema.Struct({
  value: Schema.Number,
  children: Schema.Array(Schema.suspend((): Schema.Codec<Tree> => Tree))
})
```

---

For nominal object types backed by a class (with methods, `instanceof`, and a
constructor), reach for [classes](https://effect.plants.sh/schema/classes/). To convert structs and
records to and from wire formats — JSON strings, form data, URL search params —
see [transformations](https://effect.plants.sh/schema/transformations/). With shapes in hand, the next
step is constraining the values inside them with [filters](https://effect.plants.sh/schema/filters/).