mini-effect
v0.0.9
Published
A mini-effect library for TypeScript.
Readme
mini-effect
Minimal Effect system for TypeScript. Lazy, composable, cancellable.
Installation
npm install mini-effectModules
mini-effect— core primitivesmini-effect/concurrency— parallel executionmini-effect/tag— tagged error handlingmini-effect/fetch— HTTP requestsmini-effect/schema— validation (Valibot)
Core
import {
fn,
succeed,
fail,
gen,
pipe,
run,
catchSome,
dependency,
retry,
} from "mini-effect";Create Effects
const delay = fn(
(signal) =>
new Promise<void>((resolve, reject) => {
const id = setTimeout(resolve, 1000);
signal.addEventListener("abort", () => {
clearTimeout(id);
reject(signal.reason);
});
}),
);
const success = succeed(42);
const failure = fail(new Error("failed"));Generators
const program = gen(function* (signal) {
const a = yield* fn(() => Promise.resolve(1));
const b = yield* succeed(2);
return a + b;
});Pipe
const result = await run(
pipe(
succeed(5),
(n: number) => succeed(n * 2),
(n: number) => succeed(n + 1),
),
);Error Recovery
pipe(
fn(() => {
throw new Error("x");
}),
catchSome((cause) =>
cause instanceof Error ? succeed("recovered") : undefined,
),
);Retry
// Retry up to 3 times before failing
const resilient = retry(
3,
fn(() => fetchUnreliableData()),
);
// Retry forever until success
const persistent = retry(
"forever",
fn(() => pollUntilReady()),
);When times is a number, the effect is retried up to that many times; if it still fails, the last error propagates. When times is "forever", retries are unlimited and the error type narrows to never.
Execution
const result = await run(effect);
const result = await run(effect, abortController.signal);Dependencies
import { dependency, gen, run } from "mini-effect";dependency creates a typed tag that represents a value your effects need at runtime. Use yield* to read a dependency and .provide() to supply it.
Define a Dependency
const UserName = dependency("UserName")<string>();
const Logger = dependency("Logger")<{ log: (msg: string) => void }>();Read Dependencies in Effects
const sayHello = gen(function* () {
const name = yield* UserName;
return `Hello, ${name}`;
});Provide Dependencies via Pipe
const result = await run(sayHello.pipe(UserName.provide("Alice")));
// => "Hello, Alice"Compose Across Effects
Dependencies propagate through yield* — provide them at any level:
const program = gen(function* () {
const message = yield* sayHello;
return message;
}).pipe(UserName.provide("World"));
await run(program);
// => "Hello, World"Missing Dependencies
If a dependency is not provided, the effect fails with an Error:
await run(sayHello);
// throws Error("Missing dependency: UserName")Concurrency
import { all, allSettled, any, race, fork } from "mini-effect/concurrency";| Function | Behavior |
| ------------ | -------------------------------------- |
| all | Parallel execution, fail-fast |
| allSettled | Parallel execution, collect all |
| any | First success wins |
| race | First completion wins |
| fork | Background execution, returns abort fn |
await run(all([effectA, effectB, effectC]));
const abort = await run(fork(backgroundEffect));Tagged Errors
import { failure, catchTags } from "mini-effect/tag";
const NotFound = failure("NotFound");
const Unauthorized = failure("Unauthorized");
pipe(
fn(() => {
throw new NotFound();
}),
catchTags({
NotFound: () => succeed("fallback"),
Unauthorized: () => succeed("login"),
}),
);Fetch
import { request, json, text, blob, bytes, formData } from "mini-effect/fetch";| Function | Returns | Error Tag |
| ---------- | ------------ | --------------- |
| request | Response | FailedToFetch |
| json | unknown | FailedToRead |
| text | string | FailedToRead |
| blob | Blob | FailedToRead |
| bytes | Uint8Array | FailedToRead |
| formData | FormData | FailedToRead |
pipe(request("https://api.example.com/data"), (res) => json(res));Schema
import { validate, object, string, number } from "mini-effect/schema";
const User = object({ name: string(), age: number() });
const validated = validate(User)({ name: "Alice", age: 30 });Error tag: FailedToValidate
Re-exports all of Valibot.
API Reference
mini-effect
| Export | Signature |
| ------------ | ---------------------------------------------------------------- |
| fn | (thunk: (signal: AbortSignal) => T \| Promise<T>) => Effect<T> |
| succeed | (value: T) => Effect<T, never> |
| fail | (error: E) => Effect<never, E> |
| gen | (generator: (signal) => Generator<Effect>) => Effect |
| pipe | (effect, ...fns) => Effect |
| run | (effect, signal?) => Promise<T> |
| catchSome | (handler: (cause) => Effect \| undefined) => WrapEffect |
| dependency | (tag: string) => <T>() => Dependency<tag, T> |
| retry | (times: number \| "forever", effect) => Effect |
Dependency<D, T>
| Member | Signature |
| --------- | ------------------------- |
| provide | (instance: T) => Effect |
mini-effect/concurrency
| Export | Signature |
| ------------ | ------------------------------------------------ |
| all | (effects: Effect[]) => Effect<T[]> |
| allSettled | (effects: Effect[]) => Effect<SettledResult[]> |
| any | (effects: Effect[]) => Effect<T> |
| race | (effects: Effect[]) => Effect<T> |
| fork | (effect: Effect) => Effect<() => void> |
mini-effect/tag
| Export | Signature |
| ----------- | ------------------------------------------------ |
| failure | (tag: string) => FailureConstructor |
| catchTags | (handlers: Record<Tag, Handler>) => WrapEffect |
mini-effect/fetch
| Export | Type |
| ---------- | -------- |
| request | Function |
| json | Function |
| text | Function |
| blob | Function |
| bytes | Function |
| formData | Function |
mini-effect/schema
| Export | Description |
| ---------- | ------------------------------- |
| validate | (schema) => (value) => Effect |
| * | All Valibot exports |
License
MIT
