You are an Effect TypeScript expert. This skill is a migration reference — it documents the key v4 Schema API changes so you don't accidentally use v3 patterns.
Effect Source Reference
The Effect v4 source is available at ~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/.
Browse and read files there directly to look up APIs, types, and implementations.
Reference this for:
- Full Schema API:
packages/effect/SCHEMA.md - Migration guide:
migration/schema.md - SchemaTransformation module:
packages/effect/src/SchemaTransformation.ts - SchemaGetter module:
packages/effect/src/SchemaGetter.ts - Schema representation model and reference policy:
packages/effect/src/SchemaRepresentation.ts
1. Key Renames (find-and-replace safe)
Current type model (rc.112)
Schema.Schema<T> describes only the decoded type. Preserve wire types and
services with Schema.Codec<T, E, RD, RE>: decoded Type, Encoded representation,
DecodingServices, and EncodingServices. A three-argument Schema.Schema<A, I, R>
is not a v4 type. Prefer inference or S extends Schema.Constraint in generic
schema helpers so concrete schema operations are retained.
| v3 | v4 | Notes |
|---|---|---|
annotations(ann) |
annotate(ann) |
|
compose(schemaB) |
decodeTo(schemaB) or decodeTo(schemaB, transformation) |
Transformation is optional; omitted uses passthrough composition |
typeSchema(schema) |
toType(schema) |
|
asSchema(schema) |
revealCodec(schema) |
|
equivalence() |
toEquivalence() |
|
arbitrary() |
toArbitrary() |
Returns a factory that accepts the fast-check module |
pretty() |
toFormatter() |
|
parseJson() |
fromJsonString(Schema.Unknown) |
UnknownFromJsonString is internal as of beta.103 |
parseJson(schema) |
fromJsonString(schema) |
With-schema version |
TaggedErrorClass |
TaggedError |
Renamed in beta.104 |
ErrorClass |
Error |
Renamed in beta.104 |
Error (instance schema) |
ErrorInstance |
Renamed in beta.104 |
BigIntFromSelf |
BigInt |
|
SymbolFromSelf |
Symbol |
|
URLFromSelf |
URL |
|
DateFromSelf |
Date |
All *FromSelf drop the suffix |
DurationFromSelf |
Duration |
|
OptionFromSelf |
Option |
|
EitherFromSelf |
Result |
Also renamed from Either to Result |
RedactedFromSelf |
Redacted |
Expects Redacted values; JSON encoding is allowed by default |
Redacted |
RedactedFromValue |
Raw value → Redacted; encoding is allowed by default |
ChunkFromSelf |
Chunk |
*FromSelf suffix removed |
ReadonlyMapFromSelf |
ReadonlyMap |
*FromSelf suffix removed |
ReadonlySetFromSelf |
ReadonlySet |
*FromSelf suffix removed |
HashMapFromSelf |
HashMap |
*FromSelf suffix removed |
HashSetFromSelf |
HashSet |
*FromSelf suffix removed |
BigDecimalFromSelf |
BigDecimal |
*FromSelf suffix removed |
CauseFromSelf |
Cause |
*FromSelf suffix removed |
ExitFromSelf |
Exit |
*FromSelf suffix removed |
RegExpFromSelf |
RegExp |
*FromSelf suffix removed |
encodedSchema(schema) |
toEncoded(schema) |
|
decodingFallback annotation |
catchDecoding(...) |
Annotation replaced by combinator |
Literal(null) |
Null |
Standalone schema for null |
standardSchemaV1 |
toStandardSchemaV1 |
|
nonEmptyString |
isNonEmpty() |
Now used with .check() |
disableValidation |
disableChecks |
In MakeOptions for Class constructors |
standalone SchemaError module |
Schema.SchemaError |
The root SchemaError namespace export was removed in rc.108; use Schema.isSchemaError to narrow |
Parser/Codec Function Renames
All parsing functions were renamed to clarify whether they return an Effect or an Exit:
| v3 | v4 |
|---|---|
decodeUnknown |
decodeUnknownEffect |
decode |
decodeEffect |
decodeUnknownEither |
decodeUnknownExit |
decodeEither |
decodeExit |
encodeUnknown |
encodeUnknownEffect |
encode |
encodeEffect |
encodeUnknownEither |
encodeUnknownExit |
encodeEither |
encodeExit |
Note: decodeUnknownSync and encodeSync are unchanged — they still exist on Schema.
Redacted encoding: Schema.Redacted and Schema.RedactedFromValue encode by default. Opt out with Schema.Redacted(schema, { disallowJsonEncode: true }) for JSON encoding or Schema.RedactedFromValue(schema, { disallowEncode: true }) for all encoding.
2. Variadic → Array Arguments
Several APIs that accepted variadic args now take arrays:
// v3
Schema.Literal('a', 'b');
Schema.Union(A, B);
Schema.Tuple(A, B);
Schema.TemplateLiteral(A, B);
// v4
Schema.Literals(['a', 'b']); // Note: single Literal("a") still exists
Schema.Union([A, B]); // Array form is the canonical v4 signature
Schema.Tuple([A, B]);
Schema.TemplateLiteral([A, B]);
Record: Object → Positional Args
// v3
Schema.Record({ key: Schema.String, value: Schema.Number });
// v4
Schema.Record(Schema.String, Schema.Number);
3. Filter → Check Migration
The .pipe(Schema.filter(...)) pattern is replaced by .check() with is* prefixed validators:
// v3
Schema.String.pipe(Schema.minLength(5));
Schema.Number.pipe(Schema.int());
Schema.Number.pipe(Schema.greaterThan(0));
// v4
Schema.String.check(Schema.isMinLength(5));
Schema.Number.check(Schema.isInt());
Schema.Number.check(Schema.isGreaterThan(0));
Complete Filter Rename Table
| v3 filter | v4 check |
|---|---|
greaterThan(n) |
isGreaterThan(n) |
greaterThanOrEqualTo(n) |
isGreaterThanOrEqualTo(n) |
lessThan(n) |
isLessThan(n) |
lessThanOrEqualTo(n) |
isLessThanOrEqualTo(n) |
between(min, max) |
isBetween({ minimum, maximum }) |
int() |
isInt() |
multipleOf(n) |
isMultipleOf(n) |
finite |
isFinite() |
minLength(n) |
isMinLength(n) |
maxLength(n) |
isMaxLength(n) |
length(n) |
isLengthBetween(n, n) |
pattern(regex) |
isPattern(regex) |
nonEmptyString |
isNonEmpty() |
Removed Filters (no v4 equivalent)
positive, negative, nonNegative, nonPositive — build these yourself:
// v4: replace removed convenience filters
const isPositive = Schema.isGreaterThan(0);
const isNonNegative = Schema.isGreaterThanOrEqualTo(0);
const isNegative = Schema.isLessThan(0);
const isNonPositive = Schema.isLessThanOrEqualTo(0);
For the common non-negative safe-integer domain, use the canonical Schema.Natural added in beta.102 instead of composing checks manually.
Custom Filters
// v3: inline predicate filter
Schema.String.pipe(Schema.filter((s) => s.length > 0));
// v4: use makeFilter
Schema.String.check(Schema.makeFilter((s) => s.length > 0));
// v3: refinement filter
Schema.Option(Schema.String).pipe(Schema.filter(Option.isSome));
// v4: use refine for type-narrowing predicates
Schema.Option(Schema.String).pipe(Schema.refine(Option.isSome));
String Transforms (not filters)
// v4: string transformations use SchemaTransformation + .decode()
import { Schema, SchemaTransformation } from 'effect';
Schema.String.pipe(Schema.decode(SchemaTransformation.trim()));
Schema.String.pipe(Schema.decode(SchemaTransformation.toLowerCase()));
Schema.String.pipe(Schema.decode(SchemaTransformation.toUpperCase()));
4. Transform Migration
Schema.transform and Schema.transformOrFail no longer exist as standalone functions. Use Schema.decodeTo with SchemaTransformation:
Pure Transform
// v3
const BoolFromString = Schema.transform(
Schema.Literal('on', 'off'),
Schema.Boolean,
{
strict: true,
decode: (literal) => literal === 'on',
encode: (bool) => (bool ? 'on' : 'off')
}
);
// v4
import { Schema, SchemaTransformation } from 'effect';
const BoolFromString = Schema.Literals(['on', 'off']).pipe(
Schema.decodeTo(
Schema.Boolean,
SchemaTransformation.transform({
decode: (literal) => literal === 'on',
encode: (bool) => (bool ? 'on' : 'off')
})
)
);
Fallible Transform
// v3
const NumberFromString = Schema.transformOrFail(Schema.String, Schema.Number, {
strict: true,
decode: (input, _, ast) => {
const parsed = parseFloat(input);
if (isNaN(parsed)) {
return ParseResult.fail(
new ParseResult.Type(ast, input, 'Not a number')
);
}
return ParseResult.succeed(parsed);
},
encode: (input) => ParseResult.succeed(input.toString())
});
// v4
import {
Effect,
Number,
Option,
Schema,
SchemaGetter,
SchemaIssue
} from 'effect';
const NumberFromString = Schema.String.pipe(
Schema.decodeTo(Schema.Number, {
decode: SchemaGetter.transformOrFail((s) =>
Option.match(Number.parse(s), {
onNone: () =>
Effect.fail(
new SchemaIssue.InvalidValue(Option.some(s))
),
onSome: (n) => Effect.succeed(n)
})
),
encode: SchemaGetter.String()
})
);
Literal Transforms
// v3
Schema.transformLiteral(0, 'a');
Schema.transformLiterals([0, 'a'], [1, 'b']);
// v4
Schema.Literal(0).transform('a');
Schema.Literals([0, 1]).transform(['a', 'b']);
5. Schema.Data Removal
Schema.Data is removed in v4. No replacement needed — Equal.equals now does deep structural comparison on plain objects by default.
// v3: needed Schema.Data for structural equality
const PersonData = Schema.Data(Schema.Struct({ name: Schema.String }));
// v4: just use the struct directly — equality works out of the box
const Person = Schema.Struct({ name: Schema.String });
6. Structural Operations via mapFields
pick, omit, partial, required, and extend are now expressed through mapFields:
import { Schema, Struct } from 'effect';
const base = Schema.Struct({
a: Schema.String,
b: Schema.Number,
c: Schema.Boolean
});
// pick
base.mapFields(Struct.pick(['a']));
// omit
base.mapFields(Struct.omit(['b']));
// partial (allows undefined)
base.mapFields(Struct.map(Schema.optional));
// partial exact (key can be absent, no undefined)
base.mapFields(Struct.map(Schema.optionalKey));
// partial subset
base.mapFields(Struct.mapPick(['a'], Schema.optional));
// required
base.mapFields(Struct.map(Schema.requiredKey));
// extend (add fields)
base.mapFields(Struct.assign({ d: Schema.Date }));
// or:
base.pipe(Schema.fieldsAssign({ d: Schema.Date }));
attachPropertySignature → mapFields + tagDefaultOmit
// v3
Circle.pipe(Schema.attachPropertySignature('kind', 'circle'));
// v4
Circle.mapFields((fields) => ({
...fields,
kind: Schema.tagDefaultOmit('circle')
}));
7. Optional Keys: optionalKey vs optional
v4 distinguishes between two kinds of optional struct fields:
| API | TypeScript type | Meaning |
|---|---|---|
Schema.optionalKey(S) |
readonly a?: T |
Key may be absent (exact optional) |
Schema.optional(S) |
readonly a?: T | undefined |
Key may be absent OR explicitly undefined |
Schema.mutableKey(S) |
a: T |
Writable (removes readonly) |
Use Schema.withDecodingDefaultKey / Schema.withDecodingDefault when the default is on the schema Encoded side. For transformed schemas (for example Schema.FiniteFromString), that means the default is the pre-decoded input such as '1'.
Use Schema.withDecodingDefaultTypeKey / Schema.withDecodingDefaultType when the default is on the decoded Type side, such as 1 for Schema.FiniteFromString.
Defaults are Effect values: they may require services and may fail with Schema.SchemaError.
import { Effect, Schema, SchemaGetter } from 'effect';
const User = Schema.Struct({
name: Schema.String.pipe(
Schema.withDecodingDefaultKey(Effect.succeed('anonymous'))
),
role: Schema.String.pipe(
Schema.withDecodingDefault(Effect.succeed('viewer'))
),
retriesEncoded: Schema.FiniteFromString.pipe(
Schema.withDecodingDefault(Effect.succeed('1'))
),
retriesType: Schema.FiniteFromString.pipe(
Schema.withDecodingDefaultType(Effect.succeed(1))
),
quotaTypeKey: Schema.FiniteFromString.pipe(
Schema.withDecodingDefaultTypeKey(Effect.succeed(10))
)
});
const fallback = SchemaGetter.withDefault(Effect.succeed('viewer'));
Later v4 Updates
schema.makeEffect(input, options?)on schemas and schema-backed classes returns anEffectthat fails directly withSchemaIssue.Issue, notSchema.SchemaError.Schema.resolveIntowas renamed toSchema.resolveAnnotations.Schema.resolveAnnotationsKey(schema)returns key-level annotations.Schema.annotateEncoded({...})annotates the encoded side of a transformed schema; useSchema.annotate({...})for the decoded Type side.- Schemas are directly extendable as classes;
Schema.asClasswas removed in beta.102. Schema.toArbitrary(schema)returns a factory that must be called with thefast-checkmodule;Schema.toArbitraryLazyand arbitrary derivation reports were removed in beta.106.- New built-in schemas:
Schema.DateFromStringSchema.BigIntFromStringSchema.BigDecimalFromStringSchema.TimeZoneNamedFromStringSchema.TimeZoneFromStringSchema.DateTimeZonedFromStringSchema.DurationFromStringSchema.StringFromBase64Schema.StringFromBase64UrlSchema.StringFromHexSchema.StringFromUriComponentSchema.JsonObject
import { Effect, Schema } from 'effect';
import * as FastCheck from 'fast-check';
class UserName extends Schema.NonEmptyString {
static readonly decodeUnknownSync = Schema.decodeUnknownSync(this);
}
const name = UserName.decodeUnknownSync('alice');
const resolved = Schema.resolveAnnotations(
Schema.String.annotate({ description: 'Display name' })
);
const resolvedKey = Schema.resolveAnnotationsKey(
Schema.String.annotateKey({ description: 'Primary user id' })
);
const annotatedEncoded = Schema.NumberFromString.pipe(
Schema.annotateEncoded({ description: 'Numeric string input' })
);
const duration = Schema.DurationFromString;
const parsed = Schema.String.makeEffect('alice');
const makeNameArbitrary = Schema.toArbitrary(Schema.NonEmptyString);
const nameArbitrary = makeNameArbitrary(FastCheck);
Graph Schemas
Schema.Graph(kind, node, edge) models immutable Effect graphs. Its canonical JSON codec encodes the active indexed snapshot, preserving sparse active node and edge indexes, isolated nodes, parallel edges, self-loops, and stored edge orientation:
import { Graph, Schema } from 'effect';
const DirectedGraphJson = Schema.toCodecJson(
Schema.Graph('directed', Schema.String, Schema.Number)
);
const graph = Graph.fromSnapshot({
type: 'directed',
nodes: [
{ index: 2, data: 'A' },
{ index: 5, data: 'B' }
],
edges: [{ index: 3, source: 2, target: 5, data: 1 }]
});
const snapshot = Schema.encodeSync(DirectedGraphJson)(graph);
const restored = Schema.decodeUnknownSync(DirectedGraphJson)(snapshot);
Encoding rejects mutable graphs and graphs of the wrong kind. Decoding requires strictly increasing non-negative safe-integer indexes and valid edge endpoints. Removed-ID allocator history is not encoded; future allocation resumes after the greatest active index. Do not use graph.toJSON() for persistence because it is only an inspection summary.
JSON Object Schema
Use Schema.JsonObject for a readonly string-keyed record containing JSON-compatible values. It is the canonical equivalent of Schema.Record(Schema.String, Schema.Json) and rejects arrays and primitive JSON values; use Schema.Json when any JSON value is allowed.
Schema.decodeUnknownOption(Schema.JsonObject)({ key: [1, true, null] });
Schema.decodeUnknownOption(Schema.JsonObject)([1, 2, 3]); // Option.none()
Representation Reference Policy
Schema.toRepresentation, SchemaRepresentation.toRepresentation, and SchemaRepresentation.toRepresentations accept { referencePolicy }. The callback runs once per encoded-side AST candidate after occurrences have been counted:
import { Schema, SchemaRepresentation } from 'effect';
const Item = Schema.Struct({ name: Schema.String });
const document = SchemaRepresentation.toRepresentations(
[Item.ast, Item.ast],
{
referencePolicy: ({ ast, identifier, occurrences }) =>
identifier ?? (occurrences > 1 ? `${ast._tag}_` : undefined)
}
);
The input is { ast, occurrences, identifier }; return a name to extract that candidate into references, or undefined to keep it inline. By default only candidates with resolved identifiers become references. Candidate identity is AST identity, not structural equality; recursive candidates always receive a reference, with a synthetic name when needed, and colliding requested names receive numeric suffixes.
Schema.toJsonSchemaDocument(schema, options) applies the policy after deriving the canonical JSON codec, so the callback observes canonical JSON-encoded ASTs. A policy passed to the lower-level SchemaRepresentation.toJsonSchemaDocument(document) cannot reallocate references already fixed in that document; pass it while creating the Document / MultiDocument instead.
SchemaError Location
The standalone SchemaError root module was removed. Parser adapters such as Schema.decodeUnknownEffect fail with Schema.SchemaError, which contains the structured issue; narrow unknown failures with Schema.isSchemaError. By contrast, schema/class makeEffect and constructor defaults fail directly with SchemaIssue.Issue.
In rc.112, SchemaError skips stack-frame capture for lower construction cost.
Use its issue and formatter/message for validation diagnostics; a captured
parser-error stack is not a diagnostic contract. Synchronous parser optimizations
do not change the choice between constructors, sync decoders, and Effect decoders.
Partial tagged-union matching (rc.112)
Schema.TaggedUnion(...) and Schema.Union([...]).pipe(Schema.toTaggedUnion(tag))
now expose matchOrElse(value, cases, fallback) and its curried
matchOrElse(cases, fallback)(value) form. Use exhaustive .match for closed
business decisions. Use .matchOrElse when all remaining cases genuinely share
one behavior. The toTaggedUnion helper narrows the fallback to omitted variants;
the direct TaggedUnion overload types its fallback as the full union.
Neither matcher decodes unknown input. See effect-pattern-matching for a checked example.
JSON Schema import, conversion, and Standard Schema (rc.112)
SchemaRepresentation.fromJsonSchemaDocumentrejects unsupported references, validation keywords, object/arrayconstorenumvalues, and intersections it cannot represent faithfully. Do not discard the failing constraint to make an import succeed.- A keyword such as
minLengthdoes not implytype: 'string'. Constraints besideconst,enum, and$refare applied. ImportedoneOfremainsoneOfon export, and tuple imports preserveminItemseven whenprefixItemsalone is insufficient. JsonSchemadialect conversion preserves custom keywords and representable conditionals, contains, dependencies, identifiers, and tuples; it relocates local references and throws for unsupported conversions. These synchronous schema-tooling failures need an explicitEffect.tryboundary when actionable.- Import vendored V1 interoperability types from
effect/StandardSchema, for exampleStandardSchemaV1andStandardJSONSchemaV1. Continue to adapt Effect schemas withSchema.toStandardSchemaV1; the new module is not a schema builder. - Binary encoding is available from
effect/unstable/encodingasSchemaBinary. Seeeffect-schema-compositionfor codecs andeffect-streamfor framing.
8. New Modules
SchemaTransformation
Bidirectional transformation pairs (decode + encode getters). Key exports:
transform({ decode, encode })— pure bidirectional transformpassthrough()— identity (no conversion)trim(),toLowerCase(),toUpperCase(),capitalize()— string transformsnumberFromString,bigintFromString— parsing transformsdurationFromString— string ↔Duration.DurationtransformoptionFromNullOr(),optionFromOptionalKey()— Option wrappingfromJsonString— JSON string codecMiddlewareclass — wraps the full parsing Effect pipeline (for fallbacks, retries)
import { Schema, SchemaTransformation } from 'effect';
// Basic usage: always pipe through Schema.decodeTo
const Cents = Schema.Number.pipe(
Schema.decodeTo(
Schema.Number,
SchemaTransformation.transform({
decode: (dollars) => dollars * 100,
encode: (cents) => cents / 100
})
)
);
const DurationFromString = Schema.String.pipe(
Schema.decodeTo(Schema.Duration, SchemaTransformation.durationFromString)
);
SchemaGetter
Single-direction transform primitives. A Getter<T, E, R> is Option<E> → Effect<Option<T>, Issue, R>. Key exports:
transform(fn)— pure map over present valuestransformOrFail(fn)— fallible map returningEffecttransformOptional(fn)— fullOption<E> → Option<T>control (for optional field transforms)passthrough()— identity getterwithDefault(effect)— provide a defaultEffectfor missing valuesrequired()— fail if value is missingcheckEffect(fn)— effectful validationString(),Number(),Boolean(),BigInt(),Date()— coercion getters
import { Schema, SchemaGetter } from 'effect';
// Used as decode/encode args in Schema.decodeTo
const NumberFromString = Schema.String.pipe(
Schema.decodeTo(Schema.Number, {
decode: SchemaGetter.transform((s) => Number(s)),
encode: SchemaGetter.transform((n) => String(n))
})
);
9. Class Schemas
Classes still use the same pattern — Schema.Class<Self>(tag)(fields):
import { Schema } from 'effect';
class User extends Schema.Class<User>('User')({
name: Schema.String,
email: Schema.String
}) {}
// With validation on the whole struct
class User2 extends Schema.Class<User2>('User2')(
Schema.Struct({
name: Schema.String,
age: Schema.Number
}).check(
Schema.makeFilter(({ age }) => age >= 0, { title: 'non-negative age' })
)
) {}
// Extending
class Admin extends User.extend<Admin>('Admin')({
role: Schema.Literal('admin')
}) {}
// Branded
class UserId extends Schema.Class<UserId, { readonly brand: unique symbol }>(
'UserId'
)({
id: Schema.String
}) {}
// Recursive
class Category extends Schema.Class<Category>('Category')(
Schema.Struct({
name: Schema.String,
children: Schema.Array(
Schema.suspend((): Schema.Codec<Category> => Category)
)
})
) {}
MakeOptions: disableChecks
Class constructors, make, makeEffect, and makeOption accept an optional second argument with MakeOptions:
interface MakeOptions {
readonly parseOptions?: AST.ParseOptions | undefined;
readonly disableChecks?: boolean | undefined;
}
When disableChecks: true, schema checks are skipped during construction. Constructor defaults (withConstructorDefault) are still applied even when checks are disabled. This was renamed from disableValidation in v3.
import { Effect, Schema } from 'effect';
class User extends Schema.Class<User>('User')({
name: Schema.String,
role: Schema.String.pipe(
Schema.withConstructorDefault(Effect.succeed('viewer'))
)
}) {}
// Normal construction — validates input
const user1 = new User({ name: 'Alice' });
// Skip checks — trusts the data, but still applies constructor defaults
const user2 = new User({ name: 'Alice' }, { disableChecks: true });
// Also available on make, makeEffect, and makeOption:
const user3 = User.make({ name: 'Alice' }, { disableChecks: true });
const user4 =
yield* User.makeEffect({ name: 'Alice' }, { disableChecks: true });
const user5 = User.makeOption({ name: 'Alice' }, { disableChecks: true });
When a subclass extends a parent, the parent constructor is automatically called with disableChecks: true after the child validates, avoiding double-validation.
TaggedClass (auto _tag field)
class Cat extends Schema.TaggedClass<Cat>()('Cat', {
lives: Schema.Number
}) {}
// new Cat({ lives: 9 }) → { _tag: "Cat", lives: 9 }
10. TaggedError
Schema.TaggedErrorClass was renamed to Schema.TaggedError in beta.104. The constructor pattern is unchanged:
import { Effect, Schema } from 'effect';
class HttpError extends Schema.TaggedError<HttpError>()('HttpError', {
status: Schema.Number,
message: Schema.String
}) {}
// Usage
const program = Effect.gen(function* () {
yield* new HttpError({ status: 404, message: 'Not found' });
});
const recovered = program.pipe(
Effect.catchTag('HttpError', (err) =>
Effect.succeed(`Caught: ${err.status} ${err.message}`)
)
);
11. Other Removed APIs
| v3 API | Status | Replacement |
|---|---|---|
validate* (validateSync, etc.) |
removed | Schema.decode* + Schema.toType |
keyof |
removed | — |
| non-empty array ensure helper | not present | Schema.NonEmptyArray(S) or Schema.Array(S).check(Schema.isMinLength(1)) |
withDefaults |
removed | — |
fromKey |
removed | — |
Data(schema) |
removed | Not needed (deep equality is default) |
12. Quick Decision Guide
"I need to..."
| Task | v4 Pattern |
|---|---|
| Validate a primitive | Schema.String.check(Schema.isMinLength(1)) |
| Transform between types | from.pipe(Schema.decodeTo(to, SchemaTransformation.transform({...}))) |
| Make fields optional | struct.mapFields(Struct.map(Schema.optionalKey)) |
| Pick/omit fields | struct.mapFields(Struct.pick(["a"])) |
| Extend a struct | struct.mapFields(Struct.assign({ newField: Schema.X })) |
| Parse JSON string | Schema.fromJsonString(Schema.Unknown) or Schema.fromJsonString(schema) |
| Add default value | Schema.withDecodingDefault(Effect.succeed(encoded)) or Schema.withDecodingDefaultType(Effect.succeed(type)) |
| Create tagged error | class E extends Schema.TaggedError<E>()("E", { ... }) {} |
| Rename fields | struct.pipe(Schema.encodeKeys({ oldName: "newName" })) |
| Discriminated union | Schema.Union([TaggedClassA, TaggedClassB]) |
| Decode unknown safely | Schema.decodeUnknownSync(schema)(input) (sync) or Schema.decodeUnknownEffect(schema)(input) (Effect) |
| Get Exit instead of Either | Schema.decodeUnknownExit(schema)(input) |