schema
This skill should be used when the user asks about "Effect Schema", "Schema.Struct", "Schema.decodeUnknown", "data validation", "parsing", "Schema.transform", "Schema filters", "Schema annotations", "JSON Schema", "Schema.Class", "Schema branded types", "encoding", "decoding", "Schema.parseJson", or needs to understand how Effect handles data validation and transformation.
What this skill does
# Schema in Effect
## Overview
Effect Schema provides:
- **Type-safe validation** - Runtime checks with TypeScript inference
- **Bidirectional transformation** - Decode from external, encode for output
- **Composable schemas** - Build complex types from primitives
- **Error messages** - Detailed, customizable validation errors
- **Interop** - JSON Schema, Pretty Printing, Arbitrary generation
## Schema Best Practices
### 1. Tagged Unions Over Optional Properties
**AVOID optional properties. USE tagged unions instead.** This makes states explicit and enables exhaustive pattern matching.
```typescript
// ❌ BAD: Optional properties hide state complexity
const User = Schema.Struct({
id: Schema.String,
name: Schema.String,
email: Schema.optional(Schema.String),
verifiedAt: Schema.optional(Schema.Date),
suspendedReason: Schema.optional(Schema.String),
});
// Unclear: Can a user be both verified AND suspended? What if email is missing?
// ✅ GOOD: Tagged union makes states explicit
const User = Schema.Union(
Schema.Struct({
_tag: Schema.Literal("Unverified"),
id: Schema.String,
name: Schema.String,
}),
Schema.Struct({
_tag: Schema.Literal("Active"),
id: Schema.String,
name: Schema.String,
email: Schema.String,
verifiedAt: Schema.Date,
}),
Schema.Struct({
_tag: Schema.Literal("Suspended"),
id: Schema.String,
name: Schema.String,
email: Schema.String,
suspendedReason: Schema.String,
}),
);
// Clear: Each state has exactly the fields it needs
```
**Why tagged unions:**
- No impossible states (suspended user always has a reason)
- Exhaustive matching catches missing cases
- Self-documenting state machine
- Works perfectly with Match.tag
### 2. Class-Based Schemas Over Struct Schemas
**PREFER Schema.Class over Schema.Struct.** Classes give you methods, Schema.is() type guards, and better ergonomics.
```typescript
// ❌ AVOID: Plain Struct (no methods, no Schema.is() support)
const UserStruct = Schema.Struct({
id: Schema.String,
firstName: Schema.String,
lastName: Schema.String,
email: Schema.String,
});
type User = Schema.Schema.Type<typeof UserStruct>;
// ✅ PREFER: Class-based Schema
class User extends Schema.Class<User>("User")({
id: Schema.String,
firstName: Schema.String,
lastName: Schema.String,
email: Schema.String,
}) {
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
get emailDomain() {
return this.email.split("@")[1];
}
withEmail(email: string) {
return new User({ ...this, email });
}
}
// Usage:
const user = Schema.decodeUnknownSync(User)(data);
console.log(user.fullName); // "John Doe"
console.log(Schema.is(User)(user)); // true - use Schema.is() for type checks
```
**For tagged unions with classes:**
```typescript
class Unverified extends Schema.TaggedClass<Unverified>()("Unverified", {
id: Schema.String,
name: Schema.String,
}) {}
class Active extends Schema.TaggedClass<Active>()("Active", {
id: Schema.String,
name: Schema.String,
email: Schema.String,
verifiedAt: Schema.Date,
}) {
get isRecent() {
return Date.now() - this.verifiedAt.getTime() < 86400000;
}
}
class Suspended extends Schema.TaggedClass<Suspended>()("Suspended", {
id: Schema.String,
name: Schema.String,
suspendedReason: Schema.String,
}) {}
const User = Schema.Union(Unverified, Active, Suspended);
type User = Schema.Schema.Type<typeof User>;
```
### 3. Schema.is() with Match Patterns
**USE Schema.is() as type guards in Match.when patterns.** This combines Schema validation with Match's exhaustive checking.
```typescript
import { Schema, Match } from "effect";
// Define schemas
class Circle extends Schema.TaggedClass<Circle>()("Circle", {
radius: Schema.Number,
}) {
get area() {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Schema.TaggedClass<Rectangle>()("Rectangle", {
width: Schema.Number,
height: Schema.Number,
}) {
get area() {
return this.width * this.height;
}
}
const Shape = Schema.Union(Circle, Rectangle);
type Shape = Schema.Schema.Type<typeof Shape>;
// Use Schema.is() in Match patterns
const describeShape = (shape: Shape) =>
Match.value(shape).pipe(
Match.when(Schema.is(Circle), (c) => `Circle with radius ${c.radius}`),
Match.when(Schema.is(Rectangle), (r) => `${r.width}x${r.height} rectangle`),
Match.exhaustive,
);
// Schema.is() also works for runtime type checking
const processUnknown = (input: unknown) => {
if (Schema.is(Circle)(input)) {
console.log(`Circle area: ${input.area}`);
}
};
```
**Schema.is() vs Match.tag:**
```typescript
// Match.tag - when you already know it's the union type
const handleUser = (user: User) =>
Match.value(user).pipe(
Match.tag("Active", (u) => sendEmail(u.email)),
Match.tag("Suspended", (u) => logSuspension(u.suspendedReason)),
Match.tag("Unverified", () => sendVerificationReminder()),
Match.exhaustive,
);
// Schema.is() - when validating unknown data or need class features
const handleUnknown = (input: unknown) =>
Match.value(input).pipe(
Match.when(Schema.is(Active), (u) => u.isRecent), // Can use class methods
Match.when(Schema.is(Suspended), () => false),
Match.orElse(() => false),
);
```
**NEVER access `._tag` directly:**
```typescript
// ❌ FORBIDDEN - direct ._tag access
if (user._tag === "Active") { ... }
const isActive = user._tag === "Active"
// ❌ FORBIDDEN - ._tag in type definitions
type UserTag = User["_tag"] // Never extract _tag as a type
// ❌ FORBIDDEN - ._tag in array predicates
const hasActive = users.some((u) => u._tag === "Active")
const activeUsers = users.filter((u) => u._tag === "Active")
const activeCount = users.filter((u) => u._tag === "Active").length
// ✅ REQUIRED - Schema.is() as array predicate
const hasActive = users.some(Schema.is(Active))
const activeUsers = users.filter(Schema.is(Active))
const activeCount = users.filter(Schema.is(Active)).length
// ✅ REQUIRED - use Match.tag or Schema.is()
const handleUser = Match.type<User>().pipe(
Match.tag("Active", (u) => ...),
Match.exhaustive
)
// ✅ REQUIRED - Schema.is() for type guards
const isActive = Schema.is(Active)
if (isActive(user)) { ... }
```
### 4. Never Use Schema.Any or Schema.Unknown for Type Weakening
**`Schema.Any` and `Schema.Unknown` are ONLY permitted when the value is genuinely unconstrained at the domain level.** Using them to avoid writing a proper schema is type weakening and defeats the purpose of Schema-first modeling.
**Semantically correct uses (ALLOWED):**
```typescript
// ✅ Error `cause` capturing arbitrary caught exceptions - the value is genuinely unknown
class NetworkError extends Schema.TaggedError<NetworkError>()("NetworkError", {
url: Schema.String,
cause: Schema.Unknown,
}) {}
// ✅ A generic container that truly accepts any value (e.g., a cache, event metadata)
class CacheEntry extends Schema.Class<CacheEntry>("CacheEntry")({
key: Schema.String,
value: Schema.Unknown, // Cache genuinely stores arbitrary data
ttl: Schema.Number,
}) {}
// ✅ Schema.parseJson without a target schema to get raw parsed JSON
const RawJson = Schema.parseJson(); // Produces Schema<unknown>
// This is fine as an intermediate step before further validation
```
**Type weakening (FORBIDDEN):**
```typescript
// ❌ FORBIDDEN: Lazy schema definition - write the actual shape
const UserResponse = Schema.Struct({
data: Schema.Unknown, // What is "data"? Define it!
});
// ❌ FORBIDDEN: Avoiding nested schema definition
const ApiResponse = Schema.Struct({
body: Schema.Any, // Write the actual body schema
});
// ❌ FORBIDDEN: Using Schema.Unknown for "I'll validate later"
const input: Schema.Schema<unknown> = Schema.Unknown;
// Define the correct schema upfront
// ❌ FORBIDDEN: Using Schema.Any to bypass type checking
const config = Schema.Struct({
settings: Schema.Any, // Define the settings shape!
});
```
**How to fix type-weakened schemas:**
```typeRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.