Skip to main content
1

Install

npm install ts-chas
See the Installation page for yarn/pnpm commands and TypeScript requirements.
2

Your first Result

chas.fromPromise wraps any Promise and maps thrown errors into a typed value. The result is a ResultAsync<T, E> that’s awaitable, chainable, and explicit about what can go wrong.
import { chas } from 'ts-chas';

interface User {
  id: number;
  name: string;
}

function fetchUser(id: number): chas.ResultAsync<User, string> {
  return chas.fromPromise(
    fetch(`/api/users/${id}`).then(res => res.json()),
    (e) => `Fetch failed: ${e}`
  );
}

const result = await fetchUser(1)
  .map(user => user.name)          // transform the success value
  .mapErr(err => err.toUpperCase()); // transform the error

if (result.isOk()) {
  console.log(`Hello, ${result.value}`);
} else {
  console.error(result.error); // "FETCH FAILED: ..."
}
3

Validate input with Guard

is from ts-chas/guard gives you chainable type predicates you can use inline or combine into full schemas.
import { is, defineSchemas, type InferSchema } from 'ts-chas/guard';

// Inline type predicate — works as a standard TypeScript guard
const isValidEmail = is.string.trim().email.min(5);

if (isValidEmail(someInput)) {
  // someInput is narrowed to `string` here
  console.log('Valid email:', someInput);
}

// Schema parsing — returns Result<T, GuardErr[]> with no try/catch
const schemas = defineSchemas({
  UserPayload: {
    name: is.string.min(1),
    email: is.string.email,
    age: is.number.gte(18).error('Must be 18 or older'),
  },
});

type UserPayload = InferSchema<typeof schemas.UserPayload>;

const parsed = schemas.UserPayload.parse(incomingJson);

if (parsed.isOk()) {
  saveUser(parsed.value); // typed as UserPayload
} else {
  console.error(parsed.error.map(e => e.message).join('\n'));
}
4

A resilient Task

Task wraps an async operation lazily, which means it does not run until you call .execute(). You can attach retries, timeouts, and other resilience patterns before execution.
import { Task } from 'ts-chas/task';

const fetchData = Task.from(
  () => fetch('/api/data').then(res => res.json()),
  (e) => new Error(`Request failed: ${e}`)
);

const result = await fetchData
  .retry(3, { delay: 500, factor: 2 }) // up to 3 retries, exponential backoff
  .timeout(5000, () => new Error('Request timed out'))
  .execute();

if (result.isOk()) {
  console.log(result.value);
}
5

Putting it together

Here is a ~15-line end-to-end example that validates incoming request data with Guard, fetches a remote resource with Result, and wraps the whole thing in a resilient Task.
import { chas } from 'ts-chas';
import { is, defineSchemas } from 'ts-chas/guard';
import { Task } from 'ts-chas/task';

const schemas = defineSchemas({
  Request: { userId: is.string.uuid('v4') },
});

function handleRequest(body: unknown) {
  return Task.from(async () => {
    // Validate input — parse() returns a Result, unwrap throws on error
    const { userId } = schemas.Request.parse(body).unwrap();

    // Fetch with explicit error typing
    const user = await chas
      .fromPromise(
        fetch(`/api/users/${userId}`).then(r => r.json()),
        (e) => new Error(`Fetch failed: ${e}`)
      )
      .map(data => data as { id: string; name: string });

    return user;
  }, (e) => e instanceof Error ? e : new Error(String(e)))
    .retry(2)
    .timeout(8000, () => new Error('Request timed out'));
}

const result = await handleRequest(req.body).execute();

if (result.isOk()) {
  console.log('User:', result.value.name);
} else {
  console.error('Error:', result.error.message);
}
These examples only scratch the surface. Head to Core Concepts for a deeper dive into Result, Guard, Task, Tagged Errors, Option, and Pipe/Flow.