Classes
Schema.Class turns a struct schema into a class. You get validated
construction, a named type you can attach methods to, and — because the class
extends Effect’s Data — value-based equality out of the box. It is the
idiomatic way to model a domain entity that is more than a bag of fields.
import { Schema } from "effect"
// `class Self extends Schema.Class<Self>("Identifier")({ fields }) {}`class Person extends Schema.Class<Person>("Person")({ firstName: Schema.String.check(Schema.isNonEmpty()), lastName: Schema.String.check(Schema.isNonEmpty()), age: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))}) { // Regular methods and getters work as you would expect. get fullName(): string { return `${this.firstName} ${this.lastName}` }}
// The constructor validates its input against the schema.const alice = new Person({ firstName: "Alice", lastName: "Ng", age: 30 })
console.log(alice.fullName) // "Alice Ng"console.log(`${alice}`) // "Person({ firstName: Alice, lastName: Ng, age: 30 })"Why a class instead of a struct
Section titled “Why a class instead of a struct”A plain Schema.Struct produces anonymous objects. A Schema.Class gives you:
- Validated construction —
new Person({...})runs the schema’s checks and throws on invalid input, so an instance is always valid. - Identity — the type has a name (
Person), useful in errors, logs, and pattern matching. - Behaviour — methods and getters live alongside the data.
- Value equality — two instances with equal fields are
Equal.equals.
The class is a schema, so you can decode and encode through it just like any other schema.
import { Schema } from "effect"
class Person extends Schema.Class<Person>("Person")({ name: Schema.String, age: Schema.Number}) {}
// Decode unknown input straight into a Person instance.const person = Schema.decodeUnknownSync(Person)({ name: "Bob", age: 25 })
console.log(person instanceof Person) // trueValue-based equality
Section titled “Value-based equality”Because the class extends Data.Class, instances compare by value rather than
by reference. This makes class instances safe to use as keys in
HashMap, members of a HashSet, or anywhere
Effect relies on structural equality.
import { Equal, Schema } from "effect"
class Point extends Schema.Class<Point>("Point")({ x: Schema.Number, y: Schema.Number}) {}
const a = new Point({ x: 1, y: 2 })const b = new Point({ x: 1, y: 2 })
console.log(Equal.equals(a, b)) // true — same field valuesconsole.log(a === b) // false — different referencesEffectful construction
Section titled “Effectful construction”new Person({...}) throws on invalid input, which is fine inside trusted code
where the inputs are statically known. When you would rather keep failures in
the error channel, use the static makeEffect — it
returns an Effect that fails with a SchemaError.
import { Effect, Schema } from "effect"
class Person extends Schema.Class<Person>("Person")({ name: Schema.String.check(Schema.isNonEmpty()), age: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))}) {}
const program = Effect.gen(function*() { // Validation failures surface as a typed SchemaError, not a thrown exception. const person = yield* Person.makeEffect({ name: "Carol", age: 41 }) yield* Effect.log(`Created ${person.name}`) return person})Tagged classes
Section titled “Tagged classes”Schema.TaggedClass adds a _tag literal field automatically, so instances
participate in tagged-union pattern matching. Note the extra () — the tag is
passed at the next call.
import { Schema } from "effect"
class Circle extends Schema.TaggedClass<Circle>()("Circle", { radius: Schema.Number}) {}
const c = new Circle({ radius: 5 })
console.log(c._tag) // "Circle"console.log(c.radius) // 5Extending a class
Section titled “Extending a class”Every Schema.Class exposes a static extend to derive a subclass with extra
fields, inheriting the parent’s fields, checks, and methods.
import { Schema } from "effect"
class Animal extends Schema.Class<Animal>("Animal")({ name: Schema.String}) {}
class Dog extends Animal.extend<Dog>("Dog")({ breed: Schema.String}) {}
const dog = new Dog({ name: "Rex", breed: "Labrador" })
console.log(dog.name) // "Rex"console.log(dog.breed) // "Labrador"