Use Effect Schema as the source of truth for domain models, DTOs, IDs, inputs, outputs, and typed errors.
This is guidance, not an inventory. Do not use this file to track which
schema modules are complete; verify current state with git grep before
starting a migration.
Use Schema.Class for exported data objects with a clear domain identity:
export class Info extends Schema.Class<Info>("Foo.Info")({
id: FooID,
name: Schema.String,
enabled: Schema.Boolean,
}) {}
Use Schema.Struct for local shapes and simple nested objects:
const Payload = Schema.Struct({
id: FooID,
value: Schema.String,
})
Use Schema.TaggedErrorClass for expected domain errors:
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("FooNotFoundError", {
id: FooID,
}) {}
Use branded schema-backed IDs for single-value domain identifiers.
Effect Schema should own the type. Boundaries should consume Effect Schema directly or use narrow boundary-specific helpers. Avoid reintroducing a generic Effect Schema -> Zod bridge.
Current intentional boundaries:
tool.schema = z.When Zod must stay temporarily, leave a short note explaining the boundary or compatibility reason.
Reuse named refinements instead of re-spelling constraints:
const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))
const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))
Prefer domain-named leaf schemas when the name improves callers or error messages. Avoid adding brands purely for novelty.
For a domain that still has mixed schemas:
Info, Input, Output, and event payload types.Keep public wire shapes stable unless the PR is explicitly a breaking API change.