Building Blocks
Start with basic decoders:The .transform() Method
Use.transform() to convert a decoded value into a different type:
Throwing in Transforms
If a transform function throws, the decoder fails with that error message:Chaining Transforms
Transforms can be chained:The .refine() Method
Use.refine() to add validation constraints without changing the type:
Multiple Refinements
Chain refinements for multiple constraints:Type Narrowing with Refinements
Use type predicates for type narrowing:The .pipe() Method
Use.pipe() to send the output of one decoder as input to another decoder:
Conditional Piping
You can conditionally choose which decoder to pipe to:Transform + Pipe Pattern
A common pattern is transforming then validating:The .chain() Method
The.chain() method is a low-level API for advanced composition. It allows you to create the next decoder dynamically based on the previous result.
.transform(), .refine(), or .pipe() are more ergonomic than .chain().
The .reject() Method
The.reject() method is like .refine() but allows dynamic error messages:
null to accept the value, or return a string error message to reject it.
The .describe() Method
Use.describe() to replace the default error message:
Complex Composition Examples
Building a URL Decoder
Building a Date Range Decoder
Building a Polymorphic Decoder
Reusable Decoder Components
Extract common patterns into reusable decoders:Validation with Side Effects
Composition Best Practices
- Start Simple: Begin with primitive decoders and compose upward
- Single Responsibility: Each decoder should validate one concern
- Reuse Components: Extract common patterns into functions
- Transform Before Pipe: Transform data, then pipe to validate
- Chain Refinements: Add multiple refinements for clear error messages
- Prefer Higher-Level APIs: Use
.transform(),.refine(),.pipe()over.chain()
