Skip to content

Add AI friendly TL;DR to the docsΒ #1029

@taras

Description

@taras

This TL;DR was generated (by Claude Code) from the complete Effection documentation (22,242 tokens) with the following requirements:

  • Keep under 5,000 tokens for conciseness
  • Wrap all code examples in main or run for proper execution context
  • Use resources only when return values need to be tied to parent operation lifecycle
  • Always use try/finally blocks around provide in resource examples
  • Order content in learning sequence (getting started β†’ core concepts β†’ advanced patterns)
  • Simplify resource pool example to avoid explicit release management

The original documentation spans 15 MDX files covering installation, TypeScript usage, core concepts, operations, spawning, resources, collections/streams, events, error handling, context, actions, scope management, and process handling.

Effection TL;DR

Structured Concurrency and Effects for JavaScript - A library that makes async JavaScript code more predictable, composable, and cancellable.

Getting Started

Installation

# Node.js/Browser
npm install effection

# Deno  
import { main } from "jsr:@effection/effection@3";

Your First Program

import { main, sleep } from 'effection';

await main(function*() {
  console.log('Hello');
  yield* sleep(1000);
  console.log('World!');
});

Core Concepts

1. Operations vs Promises

  • Operations are stateless recipes that describe what to do
  • Promises are stateful and execute immediately
  • Operations only run when evaluated with yield* or run()
import { run, until } from 'effection';

// Promise - runs immediately
const promise = fetch('/api');

// Operation - only runs when evaluated
function* fetchData() {
  return yield* until(fetch('/api'));
}

await run(fetchData);

2. The Rosetta Stone

Async/Await Effection
await yield*
async function function*
Promise Operation
new Promise() action()
for await for yield* each
AsyncIterable Stream

3. Structured Concurrency

  • No operation runs longer than its parent
  • Every operation exits fully (guaranteed cleanup)
  • Child operations are automatically cancelled when parent completes
import { main, spawn, sleep } from 'effection';

await main(function*() {
  yield* spawn(function*() {
    // This will be cancelled after 5 seconds
    for (let i = 0; i < 100; i++) {
      yield* sleep(1000);
      console.log(i);
    }
  });
  
  yield* sleep(5000); // Parent exits after 5s
});

Basic Operations

Entry Points

import { main, run } from 'effection';

// For applications
await main(function*() {
  // Your code here
});

// For integration with existing async code
await run(function*() {
  // Your code here
});

Fundamental Operations

import { main, sleep, suspend, action } from 'effection';

await main(function*() {
  // Wait for duration
  yield* sleep(1000);

  // Wait forever (until cancelled)
  yield* suspend();

  // Create custom operation
  function myOperation() {
    return action(function*(resolve) {
      let timeoutId = setTimeout(resolve, 1000);
      return () => clearTimeout(timeoutId); // cleanup
    });
  }

  yield* myOperation();
});

Concurrency

Spawning Operations

import { main, spawn, all, race } from 'effection';

await main(function*() {
  // Spawn concurrent operation
  let task = yield* spawn(longRunningOperation);
  let result = yield* task;

  // Wait for all to complete
  let [a, b, c] = yield* all([op1(), op2(), op3()]);

  // Wait for first to complete
  let winner = yield* race([op1(), op2(), op3()]);
});

Error Handling

import { main, call, spawn, suspend } from 'effection';

await main(function*() {
  try {
    yield* riskyOperation();
  } catch (error) {
    console.log('Caught:', error);
  }

  // Error boundaries for background tasks
  try {
    yield* call(function*() {
      yield* spawn(riskyBackgroundTask); // Errors bubble up
      yield* suspend();
    });
  } catch (error) {
    console.log('Background task failed:', error);
  }
});

Events

import { main, on, once, each } from 'effection';

await main(function*() {
  // Single event
  let event = yield* once(button, 'click');

  // Stream of events
  for (let click of yield* each(on(button, 'click'))) {
    console.log('clicked!');
    yield* each.next();
  }
});

Streams & Channels

import { main, createChannel, each, spawn } from 'effection';

await main(function*() {
  // Channels for custom streams
  let channel = createChannel();
  yield* spawn(function*() {
    yield* channel.send('message 1');
    yield* channel.send('message 2');
  });

  for (let msg of yield* each(channel)) {
    console.log(msg);
    yield* each.next();
  }
});

Resources

Use resources when the return value needs to be tied to the lifecycle of the parent operation:

import { main, resource, once } from 'effection';

function useSocket(url) {
  return resource(function*(provide) {
    let socket = new WebSocket(url);
    
    // Setup
    yield* once(socket, 'open');
    
    try {
      yield* provide(socket); // Give socket to caller
    } finally {
      socket.close(); // Cleanup guaranteed
    }
  });
}

// Usage
await main(function*() {
  let socket = yield* useSocket('ws://localhost');
  socket.send('hello');
  // Socket automatically closed when scope exits
});

Context

Share values across operation tree without passing parameters:

import { main, createContext } from 'effection';

const DatabaseContext = createContext('db');

await main(function*() {
  yield* DatabaseContext.set(database);
  yield* processUsers();
});

function* processUsers() {
  let db = yield* DatabaseContext.expect();
  // Use db here
}

Common Patterns

Timeout Any Operation

import { main, race, sleep } from 'effection';

function withTimeout(operation, ms) {
  return race([
    operation,
    function*() {
      yield* sleep(ms);
      throw new Error('Timeout');
    }
  ]);
}

await main(function*() {
  yield* withTimeout(slowOperation(), 5000);
});

Retry with Backoff

import { main, sleep } from 'effection';

function* retry(operation, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return yield* operation();
    } catch (error) {
      if (attempt === maxAttempts) throw error;
      yield* sleep(1000 * attempt); // Exponential backoff
    }
  }
}

await main(function*() {
  let result = yield* retry(unreliableOperation);
});

Resource Pool

import { main, resource, sleep } from 'effection';

function usePool(createResource, size = 5) {
  return resource(function*(provide) {
    let pool = [];
    let inUse = new Set();
    
    // Pre-create resources
    for (let i = 0; i < size; i++) {
      pool.push(yield* createResource());
    }
    
    try {
      yield* provide({
        *acquire() {
          while (pool.length === 0) {
            yield* sleep(10);
          }
          let resource = pool.pop();
          inUse.add(resource);
          return resource;
        }
      });
    } finally {
      // Clean up all resources
      for (let resource of [...pool, ...inUse]) {
        resource.cleanup();
      }
    }
  });
}

await main(function*() {
  let pool = yield* usePool(createDatabaseConnection, 10);
  let conn = yield* pool.acquire();
  // Use connection - it's automatically cleaned up when scope exits
});

Key Benefits

  1. Automatic Cleanup - No leaked timeouts, requests, or resources
  2. Predictable Cancellation - Operations can always be interrupted
  3. Composability - Operations compose like UI components
  4. Familiar Syntax - Uses standard JavaScript constructs
  5. Type Safety - Full TypeScript support

That's Effection in a nutshell! It brings structured concurrency to JavaScript, making async code more reliable and easier to reason about.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions