Skip to content

Schema: Migration from v3

This document maps v3 Schema APIs to their v4 equivalents. Simple renames and argument changes are covered in the summary table below. More complex migrations have dedicated sections with code examples.

  • auto — mechanical find-and-replace, safe to auto-apply
  • semi-auto — follows a clear pattern but needs structural changes
  • manual — requires case-by-case decisions, flag for human review
  • removed — no v4 equivalent
v3 API v4 API Migration type
asSchema(schema) revealCodec(schema) rename
encodedSchema(schema) toEncoded(schema) rename
typeSchema(schema) toType(schema) rename
compose(schemaB) decodeTo(schemaB) rename
annotations(ann) annotate(ann) rename
decodingFallback annotation catchDecoding(...) rename
parseJson() UnknownFromJsonString rename
parseJson(schema) fromJsonString(schema) rename
pattern(regex) check(isPattern(regex)) rename
nonEmptyString isNonEmpty rename
BigIntFromSelf BigInt rename
SymbolFromSelf Symbol rename
URLFromSelf URL rename
RedactedFromSelf Redacted rename
Redacted RedactedFromValue rename
EitherFromSelf Result rename
TaggedError TaggedErrorClass rename
decodeUnknown decodeUnknownEffect rename
decode decodeEffect rename
decodeUnknownEither decodeUnknownExit rename
decodeEither decodeExit rename
encodeUnknown encodeUnknownEffect rename
encode encodeEffect rename
encodeUnknownEither encodeUnknownExit rename
encodeEither encodeExit rename
asserts(schema)(input) asserts(schema, input) semi-auto
Literal(null) Null restructure
Literal("a", "b") Literals(["a", "b"]) variadic-to-array
pickLiteral("a", "b") Literals(...).pick(["a", "b"]) restructure
Union(A, B) Union([A, B]) variadic-to-array
Tuple(A, B) Tuple([A, B]) variadic-to-array
TemplateLiteral(A, B) TemplateLiteral([A, B]) variadic-to-array
TemplateLiteralParser(A, B) TemplateLiteralParser(schema.parts) restructure
Record({ key, value }) Record(key, value) restructure
filter(predicate) check(makeFilter(predicate)) restructure
filter(refinement) refine(refinement) restructure
UUID String.check(isUUID()) restructure
ULID String.check(isULID()) restructure
pick("a") mapFields(Struct.pick(["a"])) restructure
omit("a") mapFields(Struct.omit(["a"])) restructure
partial mapFields(Struct.map(Schema.optional)) restructure
partialWith({ exact: true }) mapFields(Struct.map(Schema.optionalKey)) restructure
required(schema) schema.mapFields(Struct.map(Schema.requiredKey)) restructure
extend(structB) mapFields(Struct.assign(fieldsB)) or fieldsAssign(fieldsB) restructure
transform(from, to, { decode, encode }) from.pipe(decodeTo(to, SchemaTransformation.transform({ decode, encode }))) restructure
transformOrFail(from, to, { decode, encode }) from.pipe(decodeTo(to, { decode: SchemaGetter.transformOrFail(...), ... })) restructure
transformLiteral(from, to) Literal(from).transform(to) restructure
transformLiterals([0,"a"], [1,"b"]) Literals([0, 1]).transform(["a", "b"]) restructure
attachPropertySignature("k", "v") mapFields(f => ({...f, k: tagDefaultOmit("v")})) restructure
validate* removed (use decode* + toType) removed
keyof removed
NonEmptyArrayEnsure removed
withDefaults removed
Data(schema) removed
optionalWith(schema, opts) varies by options (see optionalWith) manual
optionalToOptional see optional field transformations manual
optionalToRequired see optional field transformations manual
requiredToOptional see optional field transformations manual
filterEffect see filterEffect manual
fromKey see rename manual
rename({ a: "c" }) see rename manual
format(schema) see format manual
ParseResult.ArrayFormatter.formatError(error) see ParseResult formatters manual
declare see declare manual

The following *FromSelf schemas have been renamed to drop the suffix:

DateFromSelfDate, DurationFromSelfDuration, ChunkFromSelfChunk, ReadonlyMapFromSelfReadonlyMap, ReadonlySetFromSelfReadonlySet, HashMapFromSelfHashMap, HashSetFromSelfHashSet, BigDecimalFromSelfBigDecimal, CauseFromSelfCause, ExitFromSelfExit, OptionFromSelfOption, RegExpFromSelfRegExp

All filters have been renamed with an is prefix and now use check(...) or pipe(Schema.check(...)):

greaterThanisGreaterThan, greaterThanOrEqualToisGreaterThanOrEqualTo, lessThanisLessThan, lessThanOrEqualToisLessThanOrEqualTo, betweenisBetween, intisInt, multipleOfisMultipleOf, finiteisFinite, minLengthisMinLength, maxLengthisMaxLength, lengthisLengthBetween

Note: positive, negative, nonNegative, nonPositive have been removed in v4.

equivalencetoEquivalence, arbitrarytoArbitrary, prettytoFormatter, standardSchemaV1toStandardSchemaV1

Migration: rename with behavior distinction

In v3, Schema.Redacted(value) decoded the raw encoded value and wrapped the decoded value in Redacted.

In v4, that behavior is named Schema.RedactedFromValue(value).

v3

import { Schema } from "effect"
const schema = Schema.Redacted(Schema.String)
const decode = Schema.decodeSync(schema)
decode("secret")

v4

import { Redacted, Schema } from "effect"
const schema = Schema.RedactedFromValue(Schema.String)
const decode = Schema.decodeSync(schema)
const redacted = decode("secret")
console.log(Redacted.value(redacted))
// secret

Schema.Redacted(value) in v4 is the replacement for v3 Schema.RedactedFromSelf(value): it expects the input to already be a Redacted value, so both Type and Encoded are Redacted<...>.

Migration: semi-auto

Schema.asserts now asserts an input directly instead of returning an assertion function.

v3

import { Schema } from "effect"
const assertString = Schema.asserts(Schema.String)
assertString(input)

v4

import { Schema } from "effect"
Schema.asserts(Schema.String, input)

Migration: removed

The validate, validateEither, validatePromise, validateSync, and validateOption APIs have been removed. Use Schema.decode* + Schema.toType instead.

import { Schema } from "effect"
// v3: Schema.validateSync(Schema.String)(input)
// v4:
const validateSync = Schema.decodeSync(Schema.toType(Schema.String))

Migration: removed

Schema.Data has no v4 equivalent. Remove it. Equal.equals performs deep structural comparison on objects by default in v4, so Schema.Data is unnecessary.

Migration: auto

v3

import { Schema } from "effect"
const schema = Schema.Literal("a", "b", "c").pipe(Schema.pickLiteral("a", "b"))

v4

import { Schema } from "effect"
const schema = Schema.Literals(["a", "b", "c"]).pick(["a", "b"])

Migration: semi-auto

v3

import { Schema } from "effect"
const schema = Schema.TemplateLiteral(Schema.String, ".", Schema.String)
const parser = Schema.TemplateLiteralParser(Schema.String, ".", Schema.String)

v4

import { Schema } from "effect"
const schema = Schema.TemplateLiteral([Schema.String, ".", Schema.String])
// use the `parts` property instead of repeating the template parts
const parser = Schema.TemplateLiteralParser(schema.parts)

Behavior note: TemplateLiteral and TemplateLiteralParser match parts semantically. Checks on string, number, and bigint schema parts are applied while matching each segment, so refined parts can reject strings that would match the broader primitive shape.

Migration: manual

New imports: SchemaRepresentation

v3

import { Schema } from "effect"
console.log(Schema.format(Schema.String))
// string

v4

import { Schema, SchemaRepresentation } from "effect"
const doc = SchemaRepresentation.fromAST(Schema.String.ast)
const multi = SchemaRepresentation.toMultiDocument(doc)
const codeDoc = SchemaRepresentation.toCodeDocument(multi)
console.log(codeDoc.codes[0].Type)
// string

Migration: manual

New imports: SchemaIssue

In v4, schema parsing fails with Schema.SchemaError, which contains a nested SchemaIssue in its issue field.

Use SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues for the v3 ParseResult.ArrayFormatter.formatError(error) equivalent.

v3

import { Either, ParseResult, Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number
})
const decode = Schema.decodeUnknownEither(Person)
const result = decode({})
if (Either.isLeft(result)) {
console.error("Decoding failed:")
console.error(ParseResult.ArrayFormatter.formatErrorSync(result.left))
}
/*
Decoding failed:
[ { _tag: 'Missing', path: [ 'name' ], message: 'is missing' } ]
*/

v4

import { Schema, SchemaIssue } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number
})
const decode = Schema.decodeUnknownSync(Person)
try {
decode({})
} catch (error) {
if (error instanceof Error) {
console.error("Decoding failed:")
if (SchemaIssue.isIssue(error.cause)) {
console.error(SchemaIssue.makeFormatterStandardSchemaV1()(error.cause).issues)
}
}
}
/*
Decoding failed:
[ { path: [ 'name' ], message: 'Missing key' } ]
*/

Migration: auto

v3

import { Schema } from "effect"
const schema = Schema.Record({ key: Schema.String, value: Schema.Number })

v4

import { Schema } from "effect"
const schema = Schema.Record(Schema.String, Schema.Number)

Behavior note: dynamic record key schemas select matching own properties before the value schema is applied. Refined key schemas such as Schema.String.check(...), Schema.Int, or checked template literals ignore properties that do not match the key schema; they do not validate the value at those ignored keys. For transformed key schemas, selection is based on encoded property names before selected keys are decoded.

Migration: semi-auto

New imports: Struct

v3

import { Schema } from "effect"
const picked = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(Schema.pick("a"))
const omitted = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(Schema.omit("b"))

v4

import { Schema, Struct } from "effect"
const picked = Schema.Struct({ a: Schema.String, b: Schema.Number }).mapFields(Struct.pick(["a"]))
const omitted = Schema.Struct({ a: Schema.String, b: Schema.Number }).mapFields(Struct.omit(["b"]))

Migration: semi-auto

New imports: Struct

  • Schema.partialmapFields(Struct.map(Schema.optional)) (allows undefined)
  • Schema.partialWith({ exact: true })mapFields(Struct.map(Schema.optionalKey)) (exact)
import { Schema, Struct } from "effect"
const struct = Schema.Struct({ a: Schema.String, b: Schema.Number })
// v3: struct.pipe(Schema.partial)
const withUndefined = struct.mapFields(Struct.map(Schema.optional))
// v3: struct.pipe(Schema.partialWith({ exact: true }))
const exact = struct.mapFields(Struct.map(Schema.optionalKey))

You can also make a subset of fields partial:

import { Schema, Struct } from "effect"
const schema = Schema.Struct({ a: Schema.String, b: Schema.Number })
.mapFields(Struct.mapPick(["a"], Schema.optional))

Migration: semi-auto

New imports: Struct

  • Schema.requiredKey: makes optionalKey fields required; makes optional fields required as T | undefined
  • Schema.required: makes optional fields required (removes undefined)
import { Schema, Struct } from "effect"
const original = Schema.Struct({
a: Schema.optionalKey(Schema.String),
b: Schema.optionalKey(Schema.Number)
})
// v3: Schema.required(original)
const schema = original.mapFields(Struct.map(Schema.requiredKey))
// { readonly a: string; readonly b: number; }

Migration: manual

New imports: SchemaGetter

optionalToOptional, optionalToRequired, and requiredToOptional are all replaced by Schema.decodeTo + SchemaGetter.transformOptional.

The pattern: start with the encoded optionality (optionalKey or required), pipe to decodeTo with the decoded optionality, and provide transformOptional functions for decode/encode.

Example (v3 optionalToRequired: setting null as default for missing field)

v3

import { Option, Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalToRequired(Schema.String, Schema.NullOr(Schema.String), {
decode: Option.getOrElse(() => null),
encode: Option.liftPredicate((value) => value !== null)
})
})

v4

import { Option, Schema, SchemaGetter } from "effect"
const schema = Schema.Struct({
a: Schema.optionalKey(Schema.String).pipe(
Schema.decodeTo(Schema.NullOr(Schema.String), {
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
encode: SchemaGetter.transformOptional(Option.filter((value) => value !== null))
})
)
})

Example (v3 requiredToOptional: empty string as missing value)

v3

import { Option, Schema } from "effect"
const schema = Schema.Struct({
a: Schema.requiredToOptional(Schema.String, Schema.String, {
decode: Option.liftPredicate((s) => s !== ""),
encode: Option.getOrElse(() => "")
})
})

v4

import { Option, Schema, SchemaGetter } from "effect"
const schema = Schema.Struct({
a: Schema.String.pipe(
Schema.decodeTo(Schema.optionalKey(Schema.String), {
decode: SchemaGetter.transformOptional(Option.filter((value) => value !== "")),
encode: SchemaGetter.transformOptional(Option.orElseSome(() => ""))
})
)
})

Migration: manual

New imports: SchemaGetter, Predicate (for nullable variants)

v3 options v4 pattern
{ exact: true } optionalKey(schema)
{ default } schema.pipe(withDecodingDefaultType(...))
{ exact: true, default } schema.pipe(withDecodingDefaultTypeKey(...))
{ nullable: true } optional(NullOr(schema)) + decodeTo + filter null
{ nullable: true, exact: true } optionalKey(NullOr(schema)) + decodeTo + filter null
{ nullable: true, default } optional(NullOr(schema)) + decodeTo + filter null + orElseSome
{ nullable: true, exact: true, default } optionalKey(NullOr(schema)) + decodeTo + filter null + orElseSome

Key rules:

  • exact: true → use optionalKey instead of optional
  • nullable: true → wrap inner schema in NullOr and filter nulls via Option.filter(Predicate.isNotNull)
  • default → use withDecodingDefaultType (or withDecodingDefaultTypeKey with exact: true)

v3

import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalWith(Schema.NumberFromString, { exact: true })
})

v4

import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalKey(Schema.NumberFromString)
})

v3

import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalWith(Schema.String, { default: () => "" })
})

v4

import { Effect, Schema } from "effect"
const schema = Schema.Struct({
a: Schema.String.pipe(Schema.withDecodingDefaultType(Effect.succeed("")))
})

v3

import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalWith(Schema.String, { exact: true, default: () => "" })
})

v4

import { Effect, Schema } from "effect"
const schema = Schema.Struct({
a: Schema.String.pipe(Schema.withDecodingDefaultTypeKey(Effect.succeed("")))
})

Example: { nullable: true, exact: true, default } (most complex case)

Section titled “Example: { nullable: true, exact: true, default } (most complex case)”

v3

import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalWith(Schema.NumberFromString, { nullable: true, default: () => -1, exact: true })
})

v4

import { Option, Predicate, Schema, SchemaGetter } from "effect"
const schema = Schema.Struct({
a: Schema.optionalKey(Schema.NullOr(Schema.NumberFromString)).pipe(
Schema.decodeTo(Schema.Number, {
decode: SchemaGetter.transformOptional((o) =>
o.pipe(Option.filter(Predicate.isNotNull), Option.orElseSome(() => -1))
),
encode: SchemaGetter.required()
})
)
})

Migration: manual

New imports: SchemaGetter, Struct

v3

import { Schema } from "effect"
const schema = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(Schema.pluck("a"))

v4

import { Schema, SchemaGetter, Struct } from "effect"
function pluck<P extends PropertyKey>(key: P) {
return <S extends Schema.Top>(
schema: Schema.Struct<{ [K in P]: S }>
): Schema.decodeTo<Schema.toType<S>, Schema.Struct<{ [K in P]: S }>> => {
return schema.mapFields(Struct.pick([key])).pipe(
Schema.decodeTo(Schema.toType(schema.fields[key]), {
decode: SchemaGetter.transform((whole: any) => whole[key]),
encode: SchemaGetter.transform((value) => ({ [key]: value } as any))
})
)
}
}
const schema = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(pluck("a"))

Migration: semi-auto

New imports: Struct (Struct case), Tuple (Union case)

v3

import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).pipe(Schema.extend(Schema.Struct({ c: Schema.Number })))

v4

import { Schema, Struct } from "effect"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).mapFields(Struct.assign({ c: Schema.Number }))
// or more succinctly
const schema2 = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).pipe(Schema.fieldsAssign({ c: Schema.Number }))

v3

import { Schema } from "effect"
const schema = Schema.Union(
Schema.Struct({ a: Schema.String }),
Schema.Struct({ b: Schema.Number })
).pipe(Schema.extend(Schema.Struct({ c: Schema.Boolean })))

v4

import { Schema, Tuple } from "effect"
const schema = Schema.Union([
Schema.Struct({ a: Schema.String }),
Schema.Struct({ b: Schema.Number })
]).mapMembers(Tuple.map(Schema.fieldsAssign({ c: Schema.Number })))

Migration: semi-auto

v3

import { Schema } from "effect"
// inline filter
const a = Schema.String.pipe(Schema.filter((s) => s.length > 0))
// refinement
const b = Schema.Option(Schema.String).pipe(Schema.filter(Option.isSome))

v4

import { Option, Schema } from "effect"
// inline filter
const a = Schema.String.check(Schema.makeFilter((s) => s.length > 0))
// refinement
const b = Schema.Option(Schema.String).pipe(Schema.refine(Option.isSome))

In v4, a makeFilter predicate can return any of the shapes described by Schema.FilterOutput:

  • undefined / true — success
  • false — generic failure
  • string — failure with that message
  • SchemaIssue.Issue — a fully-formed issue
  • { path, issue } — failure at a nested path (issue is a string or SchemaIssue.Issue)
  • ReadonlyArray<Schema.FilterIssue> — several failures reported together (empty array = success, single element is unwrapped, otherwise grouped into an Issue.Composite)

Example (Failure at a nested path)

import { Schema } from "effect"
const schema = Schema.Struct({ password: Schema.String, confirmPassword: Schema.String }).check(
Schema.makeFilter((o) =>
o.password === o.confirmPassword
? undefined
: { path: ["password"], issue: "password and confirmPassword must match" }
)
)
console.log(String(Schema.decodeUnknownExit(schema)({ password: "123456", confirmPassword: "1234567" })))
// Failure(Cause([Fail(SchemaError: password and confirmPassword must match
// at ["password"])]))

Example (Reporting multiple failures at once)

import { Schema } from "effect"
const schema = Schema.Struct({ a: Schema.Finite, b: Schema.Finite, c: Schema.Finite }).check(
Schema.makeFilter((o) => {
const issues: Array<Schema.FilterIssue> = []
if (o.a > 0) {
if (o.b <= 0) issues.push({ path: ["b"], issue: "b must be greater than 0" })
if (o.c <= 0) issues.push({ path: ["c"], issue: "c must be greater than 0" })
}
return issues
})
)
console.log(String(Schema.decodeUnknownExit(schema)({ a: 1, b: 0, c: 0 })))
// Failure(Cause([Fail(SchemaError: b must be greater than 0
// at ["b"]
// c must be greater than 0
// at ["c"])]))

Migration: manual

New imports: SchemaGetter, Result

v3

import { Effect, Schema } from "effect"
async function validateUsername(username: string) {
return Promise.resolve(username === "gcanti")
}
const ValidUsername = Schema.String.pipe(
Schema.filterEffect((username) =>
Effect.promise(() => validateUsername(username).then((valid) => valid || "Invalid username"))
)
)

v4

import { Effect, Result, Schema, SchemaGetter } from "effect"
async function validateUsername(username: string) {
return Promise.resolve(username === "gcanti")
}
const ValidUsername = Schema.String.pipe(
Schema.decode({
decode: SchemaGetter.checkEffect((username) =>
Effect.promise(() => validateUsername(username).then((valid) => valid || "Invalid username"))
),
encode: SchemaGetter.passthrough()
})
)

Migration: semi-auto

New imports: SchemaTransformation

v3

import { Schema } from "effect"
const BooleanFromString = 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 BooleanFromString = Schema.Literals(["on", "off"]).pipe(
Schema.decodeTo(
Schema.Boolean,
SchemaTransformation.transform({
decode: (literal) => literal === "on",
encode: (bool) => (bool ? "on" : "off")
})
)
)

Migration: semi-auto

New imports: SchemaGetter, SchemaIssue

v3

import { ParseResult, Schema } from "effect"
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, "Failed to convert string to 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) => {
const n = Number.parse(s)
if (n === undefined) {
return Effect.fail(new SchemaIssue.InvalidValue(Option.some(s)))
}
return Effect.succeed(n)
}),
encode: SchemaGetter.String()
})
)

Migration: auto

v3

import { Schema } from "effect"
const a = Schema.transformLiteral(0, "a")
const b = Schema.transformLiterals([0, "a"], [1, "b"], [2, "c"])

v4

import { Schema } from "effect"
const a = Schema.Literal(0).transform("a")
const b = Schema.Literals([0, 1, 2]).transform(["a", "b", "c"])

Migration: semi-auto

v3

import { Schema } from "effect"
const Circle = Schema.Struct({ radius: Schema.Number })
const Square = Schema.Struct({ sideLength: Schema.Number })
const DiscriminatedShape = Schema.Union(
Circle.pipe(Schema.attachPropertySignature("kind", "circle")),
Square.pipe(Schema.attachPropertySignature("kind", "square"))
)

v4

import { Schema } from "effect"
const Circle = Schema.Struct({ radius: Schema.Number })
const Square = Schema.Struct({ sideLength: Schema.Number })
const DiscriminatedShape = Schema.Union([
Circle.mapFields((fields) => ({ ...fields, kind: Schema.tagDefaultOmit("circle") })),
Square.mapFields((fields) => ({ ...fields, kind: Schema.tagDefaultOmit("square") }))
])

Migration: auto

v3

import { Effect, Schema } from "effect"
const schema = Schema.String.annotations({
decodingFallback: () => Effect.succeed("a")
})

v4

import { Effect, Schema } from "effect"
const schema = Schema.String.pipe(Schema.catchDecoding(() => Effect.succeedSome("a")))

Migration: manual

New imports: SchemaTransformation

v3

import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).pipe(Schema.rename({ a: "c" }))

v4

import { Schema } from "effect"
// experimental API
const schema = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).pipe(Schema.encodeKeys({ a: "c" }))

Capitalize / Lowercase / Uppercase / Uncapitalize

Section titled “Capitalize / Lowercase / Uppercase / Uncapitalize”

Migration: semi-auto

New imports: SchemaTransformation

v3

import { Schema } from "effect"
const schema = Schema.Capitalize

v4

import { Schema, SchemaTransformation } from "effect"
const schema = Schema.String.pipe(
Schema.decodeTo(Schema.String.check(Schema.isCapitalized()), SchemaTransformation.capitalize())
)

Migration: semi-auto

v3

import { Schema } from "effect"
const schema = Schema.NonEmptyTrimmedString

v4

import { Schema } from "effect"
const schema = Schema.Trimmed.check(Schema.isNonEmpty())

Migration: manual

New imports: SchemaTransformation

v3

import { Schema } from "effect"
const schema = Schema.split(",")

v4

import { Schema, SchemaTransformation } from "effect"
function split(separator: string) {
return Schema.String.pipe(
Schema.decodeTo(
Schema.Array(Schema.String),
SchemaTransformation.transform({
decode: (s) => s.split(separator) as ReadonlyArray<string>,
encode: (as) => as.join(separator)
})
)
)
}