effect-schema

Master type-safe data validation and transformation with @effect/schema. This skill covers schema definition, parsing, encoding, and advanced schema patterns for building robust data pipelines.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "effect-schema" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-effect-schema

Effect Schema

Master type-safe data validation and transformation with @effect/schema. This skill covers schema definition, parsing, encoding, and advanced schema patterns for building robust data pipelines.

Basic Schema Types

Primitive Schemas

import { Schema } from "@effect/schema"

// Primitive types const StringSchema = Schema.String const NumberSchema = Schema.Number const BooleanSchema = Schema.Boolean const BigIntSchema = Schema.BigInt const SymbolSchema = Schema.Symbol

// Special types const UndefinedSchema = Schema.Undefined const VoidSchema = Schema.Void const NullSchema = Schema.Null const UnknownSchema = Schema.Unknown const AnySchema = Schema.Any

Literal Values

import { Schema } from "@effect/schema"

// String literal const HelloSchema = Schema.Literal("hello")

// Number literal const FortyTwoSchema = Schema.Literal(42)

// Boolean literal const TrueSchema = Schema.Literal(true)

// Multiple literals (union) const StatusSchema = Schema.Literal("pending", "approved", "rejected")

Struct Schemas

Basic Struct

import { Schema } from "@effect/schema"

// Define user schema const UserSchema = Schema.Struct({ id: Schema.String, name: Schema.String, age: Schema.Number, email: Schema.String })

// Infer TypeScript type type User = Schema.Schema.Type<typeof UserSchema> // { id: string; name: string; age: number; email: string }

Optional Fields

import { Schema } from "@effect/schema"

const PersonSchema = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optional(Schema.String), phone: Schema.optional(Schema.String) })

type Person = Schema.Schema.Type<typeof PersonSchema> // { name: string; age: number; email?: string; phone?: string }

Nested Schemas

import { Schema } from "@effect/schema"

const AddressSchema = Schema.Struct({ street: Schema.String, city: Schema.String, zipCode: Schema.String })

const UserWithAddressSchema = Schema.Struct({ id: Schema.String, name: Schema.String, address: AddressSchema })

Arrays and Collections

Array Schemas

import { Schema } from "@effect/schema"

// Array of strings const StringArraySchema = Schema.Array(Schema.String)

// Array of numbers const NumberArraySchema = Schema.Array(Schema.Number)

// Array of complex objects const UsersSchema = Schema.Array(UserSchema)

// Non-empty array const NonEmptyStringArray = Schema.NonEmptyArray(Schema.String)

Tuple Schemas

import { Schema } from "@effect/schema"

// Fixed tuple const CoordinatesSchema = Schema.Tuple( Schema.Number, // latitude Schema.Number // longitude )

// Tuple with optional elements const ResponseSchema = Schema.Tuple( Schema.Number, // status code Schema.String, // message Schema.optional(Schema.Unknown) // optional data )

Record Schemas

import { Schema } from "@effect/schema"

// String keys, number values const ScoresSchema = Schema.Record({ key: Schema.String, value: Schema.Number })

// Using template literals for keys const ConfigSchema = Schema.Record({ key: Schema.TemplateLiteral("config.", Schema.String), value: Schema.String })

Union and Intersection

Union Types

import { Schema } from "@effect/schema"

// Simple union const StringOrNumberSchema = Schema.Union( Schema.String, Schema.Number )

// Tagged union (discriminated) const ShapeSchema = Schema.Union( Schema.Struct({ kind: Schema.Literal("circle"), radius: Schema.Number }), Schema.Struct({ kind: Schema.Literal("rectangle"), width: Schema.Number, height: Schema.Number }) )

type Shape = Schema.Schema.Type<typeof ShapeSchema> // { kind: "circle"; radius: number } | { kind: "rectangle"; width: number; height: number }

Intersection Types

import { Schema } from "@effect/schema"

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

const UserWithTimestampsSchema = Schema.extend( UserSchema, TimestampsSchema )

// Equivalent to intersection const ManualIntersection = Schema.Struct({ ...UserSchema.fields, ...TimestampsSchema.fields })

Parsing and Validation

Synchronous Parsing

import { Schema } from "@effect/schema"

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

// Parse unknown data const parseUser = Schema.decodeUnknownSync(UserSchema)

try { const user = parseUser({ name: "Alice", age: 30 }) console.log(user) // { name: "Alice", age: 30 } } catch (error) { console.error("Validation failed:", error) }

// Invalid data throws parseUser({ name: "Bob" }) // Throws: missing 'age' field

Effect-Based Parsing

import { Schema } from "@effect/schema" import { Effect } from "effect"

const parseUserEffect = Schema.decodeUnknown(UserSchema)

const program = Effect.gen(function* () { const user = yield* parseUserEffect({ name: "Alice", age: 30 }) return user })

// Handle parse errors const safeProgram = program.pipe( Effect.catchTag("ParseError", (error) => Effect.sync(() => { console.error("Parse error:", error.message) return null }) ) )

Encoding

import { Schema } from "@effect/schema"

// Encode typed data back to raw format const encodeUser = Schema.encodeSync(UserSchema)

const user: User = { name: "Alice", age: 30 } const encoded = encodeUser(user) // { name: "Alice", age: 30 }

Schema Transformations

Transform Schemas

import { Schema } from "@effect/schema"

// String to Date transformation const DateFromString = Schema.transform( Schema.String, Schema.Date, { decode: (s) => new Date(s), encode: (d) => d.toISOString() } )

// Parse ISO string to Date const parseDate = Schema.decodeUnknownSync(DateFromString) const date = parseDate("2024-01-01T00:00:00Z") // Date object

// Encode Date back to string const encodeDate = Schema.encodeSync(DateFromString) const isoString = encodeDate(new Date()) // "2024-01-01T00:00:00.000Z"

Validated Transformations

import { Schema } from "@effect/schema" import { ParseResult } from "@effect/schema/ParseResult"

// Email validation and transformation const EmailSchema = Schema.transformOrFail( Schema.String, Schema.String, { decode: (s) => { if (!/^[^\s@]+@[^\s@]+.[^\s@]+$/.test(s)) { return ParseResult.fail( ParseResult.type(Schema.String.ast, s, "Invalid email format") ) } return ParseResult.succeed(s.toLowerCase()) }, encode: (s) => ParseResult.succeed(s) } )

Refinements and Constraints

String Refinements

import { Schema } from "@effect/schema"

// Min/max length const UsernameSchema = Schema.String.pipe( Schema.minLength(3), Schema.maxLength(20) )

// Pattern matching const UUIDSchema = Schema.String.pipe( Schema.pattern(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i) )

// Email const EmailSchema = Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+.[^\s@]+$/))

// Starts with const HttpUrlSchema = Schema.String.pipe(Schema.startsWith("http"))

Number Refinements

import { Schema } from "@effect/schema"

// Positive numbers const PositiveSchema = Schema.Number.pipe(Schema.positive())

// Range const AgeSchema = Schema.Number.pipe( Schema.greaterThanOrEqualTo(0), Schema.lessThanOrEqualTo(120) )

// Integer const IntegerSchema = Schema.Number.pipe(Schema.int())

// Multiple of const EvenSchema = Schema.Number.pipe(Schema.multipleOf(2))

Custom Refinements

import { Schema } from "@effect/schema"

const PasswordSchema = Schema.String.pipe( Schema.minLength(8), Schema.filter((s) => ({ message: () => "Password must contain uppercase, lowercase, and number", test: () => /[A-Z]/.test(s) && /[a-z]/.test(s) && /[0-9]/.test(s) })) )

Common Schema Patterns

API Response Schema

import { Schema } from "@effect/schema"

const ApiResponseSchema = <A, I, R>(dataSchema: Schema.Schema<A, I, R>) => Schema.Struct({ success: Schema.Boolean, data: Schema.optional(dataSchema), error: Schema.optional(Schema.String) })

const UserResponseSchema = ApiResponseSchema(UserSchema)

type UserResponse = Schema.Schema.Type<typeof UserResponseSchema> // { success: boolean; data?: User; error?: string }

Paginated Response

import { Schema } from "@effect/schema"

const PaginatedSchema = <A, I, R>(itemSchema: Schema.Schema<A, I, R>) => Schema.Struct({ items: Schema.Array(itemSchema), total: Schema.Number, page: Schema.Number, pageSize: Schema.Number })

const PaginatedUsersSchema = PaginatedSchema(UserSchema)

Form Data Schema

import { Schema } from "@effect/schema"

const SignupFormSchema = Schema.Struct({ email: Schema.String.pipe( Schema.pattern(/^[^\s@]+@[^\s@]+.[^\s@]+$/) ), password: Schema.String.pipe( Schema.minLength(8) ), confirmPassword: Schema.String, acceptTerms: Schema.Literal(true) }).pipe( Schema.filter((data) => ({ message: () => "Passwords must match", test: () => data.password === data.confirmPassword })) )

Integration with Effect

Parsing in Effect Pipelines

import { Schema } from "@effect/schema" import { Effect } from "effect"

const processUserData = (rawData: unknown) => Effect.gen(function* () { // Parse data const user = yield* Schema.decodeUnknown(UserSchema)(rawData)

// Use parsed data
const saved = yield* saveUser(user)
const notified = yield* sendNotification(user.email)

return saved

})

Handling Parse Errors

import { Schema } from "@effect/schema" import { Effect } from "effect"

const safeParseUser = (data: unknown) => Schema.decodeUnknown(UserSchema)(data).pipe( Effect.catchTag("ParseError", (error) => Effect.fail({ _tag: "ValidationError", message: error.message, errors: error.errors }) ) )

Best Practices

Define Schemas Centrally: Keep schema definitions in a shared module.

Use Type Inference: Let TypeScript infer types from schemas with Schema.Schema.Type .

Compose Small Schemas: Build complex schemas from smaller, reusable pieces.

Validate at Boundaries: Parse external data at API boundaries.

Use Tagged Unions: Add discriminant fields for union types.

Document Schemas: Add JSDoc comments to schema definitions.

Test Schemas: Write tests for schema validation logic.

Use Transformations: Convert between internal and external representations.

Handle Errors Gracefully: Provide meaningful error messages.

Version Schemas: Consider versioning for API schemas.

Common Pitfalls

Over-Validation: Validating internal data unnecessarily.

Weak Constraints: Not adding sufficient refinements.

Missing Optional Fields: Forgetting to mark optional fields.

Wrong Union Order: Putting general schemas before specific ones.

Not Handling Parse Errors: Assuming parsing always succeeds.

Circular References: Creating schemas with circular dependencies.

Performance: Validating large datasets synchronously.

Type Mismatches: Schema and TypeScript type definitions diverging.

Missing Transformations: Not transforming between formats.

Exposing Internal Types: Returning internal types from APIs.

When to Use This Skill

Use effect-schema when you need to:

  • Validate API request/response data

  • Parse configuration files

  • Validate form inputs

  • Transform data between formats

  • Ensure data integrity at boundaries

  • Generate TypeScript types from schemas

  • Build type-safe data pipelines

  • Validate database queries and results

  • Parse environment variables

  • Build robust data validation layers

Resources

Official Documentation

  • Effect Schema Introduction

  • Basic Usage

  • Built-in Schemas

  • Transformations

  • API Reference

Related Skills

  • effect-core-patterns - Basic Effect operations

  • effect-error-handling - Handling parse errors

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Web3

cucumber-step-definitions

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

playwright-bdd-step-definitions

No summary provided by upstream source.

Repository SourceNeeds Review
General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review