logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·03.11.26
← Back to news

Show HN: Pure Effect – Reproduce production bugs on your laptop without a DB

Original article
pure-effect.org|38 points|8 comments|by tie-in|Jun 21, 2026

Show HN: Pure Effect – Debug Production Bugs Locally (No DB Required)

Pure Effect (v0.8.0) is a lightweight, zero-dependency effect library designed for JavaScript and TypeScript. It allows you to treat your business logic as plain data, enabling you to reproduce production failures on your laptop without needing a live database.

🚀 Quick Stats

  • Size: ≈1 KB\approx 1\text{ KB}≈1 KB (minified + gzipped)
  • Dependencies: 0
  • Core Primitives: 6

01 / The Problem: "It works on my machine"

Most bugs are hard to reproduce because business logic and I/O are tightly coupled.

"The cause is usually the same: business logic and I/O are tangled together."

When you use await db.findUser(email), the request is dispatched immediately. To test this, you are forced to use mocks, fakes, or heavy containers. When a request fails in production, a stack trace tells you where it crashed, but it doesn't capture the exact sequence of I/O results that led to the crash.

Comparison: Execution Models

The async / await approach: I/O is the logic.

async function registerUser(input) {
  const found = await db.findUser(input.email); // Fires immediately!
  if (found) throw new Error('Email in use');
  return db.saveUser(input);
}

The pure-effect approach: Logic returns I/O as data.

const flow = registerUserFlow(input);
// We can inspect the "intent" before executing it
assert.equal(flow.cmd.name, 'cmdFindUser'); 

// We can simulate production results without a DB
const next = flow.next(recordedUser); 
assert.equal(next.cmd.name, 'cmdSaveUser');

02 / Core Architecture

Pure Effect transforms your logic into a tree that an interpreter walks at the system's edge.

The Primitives

PrimitiveRoleDescription
Failure(error)ERRImmediately halts the pipeline and short-circuits.
Command(cmd, next)Deferred I/OA description of a side effect. cmd is the action; next processes the result.
Ask(next)Context/DIAccesses the environment (e.g., tenantId, config) without prop-drilling.
Retry(effect, opts)ResilienceWraps an effect to attempt recovery on failure.
Parallel(effects, next)ConcurrencyExecutes multiple effect trees via Promise.all.

The Composers

  • effectPipe(...fns): Chains functions sequentially.
  • runEffect(effect, ctx?): The interpreter that actually executes the I/O.
  • configureEffect(opts): Sets up global hooks.

Visualizing the Flow


03 / Complete Implementation Example

Here is how a user registration flow looks when decoupled from the database:

import { Success, Failure, Command, effectPipe, runEffect } from 'pure-effect';

// 1. Pure Logic: Instantly testable, no I/O
const validateRegistration = (input) => {
  if (!input.email.includes('@')) return Failure('Invalid email.');
  if (input.password?.length < 8) return Failure('Password too short.');
  return Success(input);
};

const ensureEmailAvailable = (found) => 
  found ? Failure('Email already in use.') : Success(true);

// 2. Commands: Side effects described as data
const findUserByEmail = (email) => {
  const cmdFindUser = () => db.findUserByEmail(email);
  return Command(cmdFindUser, (found) => Success(found));
};

const saveUser = (input) => {
  const cmdSaveUser = () => db.saveUser(input);
  return Command(cmdSaveUser, (saved) => Success(saved));
};

// 3. Pipeline: Composing the flow
const registerUserFlow = (input) => effectPipe(
  validateRegistration,
  () => findUserByEmail(input.email),
  ensureEmailAvailable,
  () => saveUser(input)
)(input);

// 4. Testing: Assert on the structure, not the result
const flow = registerUserFlow({ email: 'test@example.com', password: 'password123' });
assert.equal(flow.type, 'Command'); // No DB was touched!

// 5. Execution: Run at the boundary
const saved = await runEffect(registerUserFlow(input), { flowName: 'register' });

04 / Key Features

  • Tree Walking: Inspect every step of the logic without mocks or in-memory fakes.
  • Production Replay: Record I/O results in production and feed them into the local tree to retrace a failure path.
  • Data-Driven Retry: Test resilience without setTimeout.
    • Math: If ddd is delay and bbb is backoff, the sequence is d,d⋅b,d⋅b2…d, d \cdot b, d \cdot b^2 \dotsd,d⋅b,d⋅b2…
    • Retry(effect, { attempts: 3, delay: 200, backoff: 2 });
  • Concurrent Execution: Run branches in parallel with ordered results.
    • Parallel([getUser(id), getPerms(id)], ([user, perms]) => Success({ user, perms }));
  • Clean DI: Use Ask to resolve tenant or trace IDs from the framework layer.
  • Observability: Built-in hooks (onRun, onStep, onBeforeCommand) for OpenTelemetry spans.

05 / Positioning & Comparison

Pure Effect is designed for finite, request-shaped operations. It is not intended for long-running background processes.

LibraryDescriptionChoose pure-effect if...
Effect-TSA massive functional ecosystem (fibers, streaming, schema).You want testable pipelines and DI that can be learned in one afternoon.
fp-tsCategory-theory abstractions (Monads, Applicatives).You find that mocks drift from real drivers and tests feel like "theater."
async/awaitStandard JS imperative concurrency.You need to see exactly what your code would do before it does it.

Pure Effect Concept