A lightweight TypeScript utility library that combines Zod schema validation with error handling using a Maybe monad pattern. This library provides a clean and type-safe way to validate data and handle errors without try-catch blocks.
- Type-safe validation using Zod schemas
- Synchronous and asynchronous validation support
- Clean error handling without try-catch blocks
- Zero dependencies (except for Zod)
- Full TypeScript support with type inference
- Comprehensive test coverage
- Support for complex validation scenarios
- Transformation of validated data
- Detailed error messages
# Using npm
npm install maybe-zod
# Using yarn
yarn add maybe-zod
# Using bun
bun add maybe-zodimport { z } from "zod";
import { Maybe } from "maybe-zod";
// Define your schema
const userSchema = z.object({
name: z.string().min(2).max(50),
age: z.number().int().positive(),
email: z.string().email(),
});
type User = z.infer<typeof userSchema>;
// Define your Output schema
type UserOutput = User && { displayName: string }
// Define your processing function
const processUser = (user: z.infer<typeof userSchema>) => ({
...user,
displayName: `${user.name} (${user.age})`
});
// Create a validated processor
const validateUser = Maybe<User, UserOutput>(processUser, userSchema);
// Use it with valid data
const [error, result] = validateUser({
name: "Alice",
age: 30,
email: "alice@example.com"
});
// error = null
// result = { name: "Alice", age: 30, email: "alice@example.com", displayName: "Alice (30)" }
// Use it with invalid data
const [error, result] = validateUser({
name: "A",
age: -5,
email: "invalid"
});
// error contains validation messages:
// - "String must contain at least 2 character(s)"
// - "Number must be greater than 0"
// - "Invalid email"
// result = nullconst advancedSchema = z.object({
id: z.string().uuid(),
metadata: z.object({
tags: z.array(z.string()).min(1),
createdAt: z.date()
})
});
type Advanced = z.infer<typeof advancedSchema>;
// Define your Output schema
type AdvancedOutput = { id: string }
const processData = (data: Advanced) => data.id;
const validate = Maybe<Advanced, AdvancedOutput>(processData, advancedSchema);
const [error, result] = validate({
id: '123e4567-e89b-12d3-a456-426614174000',
metadata: {
tags: ['important', 'urgent'],
createdAt: new Date()
}
});// Optional fields
const optionalSchema = z.object({
title: z.string(),
description: z.string().optional(),
tags: z.array(z.string()).optional()
});
// Discriminated unions
const resultSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('success'), value: z.number() }),
z.object({ type: z.literal('error'), message: z.string() })
]);
type OptionalS = z.infer<typeof optionalSchema>;
// Define your Output schema
type ResultS = z.infer<typeof resultSchema>;
const validateResult = Maybe<OptionalS, ResultS>(
(data) => data.type === 'success' ? data.value : 0,
resultSchema
);
const [error, result] = validateResult({ type: 'success', value: 42 });
// result = 42const numberArraySchema = z.array(z.number());
const sum = (numbers: number[]) => numbers.reduce((a, b) => a + b, 0);
const validateSum = Maybe<number[], number>(sum, numberArraySchema);
const [error, result] = validateSum([1, 2, 3, 4, 5]);
// result = 15import { AsyncMaybe } from "maybe-zod";
const validateUserAsync = AsyncMaybe(processUser, userSchema);
// Basic async usage
const [error, result] = await validateUserAsync(
Promise.resolve({
name: "Alice",
age: 30,
email: "alice@example.com"
})
);
// Error handling for rejected promises
try {
const [error, result] = await validateUserAsync(
Promise.reject(new Error('Network error'))
);
// error = 'Unknown error'
// result = null
} catch {
// Handle any unexpected errors
}Maybe<T, U>(
fn: (params: T) => U,
schema: z.ZodSchema<T>
) => (data: T) => [string | null, U | null]Creates a validation wrapper for synchronous data processing.
fn: A function that processes the validated dataschema: A Zod schema that defines the shape and validation rules for the data- Returns a function that takes input data and returns a tuple of [error, result]
AsyncMaybe<T, U>(
fn: (params: T) => U,
schema: z.ZodSchema<T>
) => (data: Promise<T>) => Promise<[string | null, U | null]>Creates a validation wrapper for asynchronous data processing.
fn: A function that processes the validated dataschema: A Zod schema that defines the shape and validation rules for the data- Returns a function that takes a Promise of input data and returns a Promise of [error, result]
The library returns errors in a structured format:
- For validation errors, the error message contains a JSON string of all validation failures
- For async operations, unknown errors are caught and returned as 'Unknown error'
- The result is always null when an error occurs
-
Type Safety
- Always define your schemas with proper types
- Use
z.infer<typeof schema>for type inference
-
Error Handling
- Always check the error value before using the result
- Handle both validation errors and unknown errors in async operations
-
Schema Design
- Keep schemas modular and reusable
- Use schema composition for complex validations
The library includes comprehensive tests. Run them using:
bun testTest coverage includes:
- Basic validations
- Complex object validations
- Array transformations
- Async operations
- Error cases
- Edge cases
This project was initiated and is maintained by ThonyMg. I am available for freelance work on Svelte and VueJs projects. Feel free to reach out through my Linkedin profile for collaboration opportunities.
This project is licensed under the MIT License. For more details, see the LICENSE file.