Skip to content

@pstdio/tiny-tasks

Composable, interrupt-friendly workflows for TypeScript/JavaScript. Turn async functions into checkpoint-able generators that can pause, persist, and resume.

Install

bash
npm i @pstdio/tiny-tasks

Quick Start

ts
import { createRuntime } from "@pstdio/tiny-tasks";

const { task, MemorySaver } = createRuntime();

const saver = new MemorySaver();
const opts = { runId: "demo", checkpointer: saver };

const workflow = task("workflow", async function* (name: string, ctx) {
  yield `processing ${name}`;
  const resume = ctx.interrupt({ message: "wait" });
  yield `done: ${resume.message}`;
});

// first run – yields once and interrupts
for await (const [msg, , intr] of workflow("job", opts)) {
  if (msg) console.log(msg);
  if (intr) console.log("interrupt →", intr);
}

// resume after external work is finished
for await (const [msg] of workflow.resume({ message: "ok" }, opts)) {
  if (msg) console.log(msg);
}

Examples

Nested streaming

ts
const step = task("step", async function* (q: string) {
  yield `processing ${q}`;
  yield `done: ${q}`;
});

const aggregate = task("aggregate", async function* (q: string) {
  const state: string[] = [];
  for await (const [msg] of step(q)) {
    if (msg) state.push(msg as string);
    yield state.slice();
  }
  return [...state, "aggregate done"];
});

for await (const [partial] of aggregate("task")) {
  console.log(partial);
}

Nested interrupts

ts
const inner = task("inner", async function* (_: void, ctx) {
  ctx.interrupt("stop");
  yield "resumed";
});

const outer = task("outer", async function* (_: void, ctx) {
  for await (const [msg, , intr] of inner()) {
    if (msg) yield msg;
    if (intr) ctx.interrupt(intr);
  }
  return "done";
});

for await (const [msg, , intr] of outer()) {
  console.log(msg, intr);
}

API Overview

  • task: Wrap an async generator. Use ctx.interrupt(value) inside to pause and persist.
  • createRuntime: Browser-friendly initializer returning { task, MemorySaver, ... }.
  • MemorySaver: In-memory checkpoint store for demos/tests.

Each task yields tuples [partial, snapshot, interrupt] during execution and supports:

  • .invoke(input, opts) – run to completion and resolve final result
  • .resume(value, opts) – resume after an interrupt

Notes & Caveats

  • Use a consistent runId when resuming to locate prior checkpoints.
  • Avoid mutating shared state between yields; earlier steps may re-run on resume.
  • The default MemorySaver is ephemeral; implement your own saver for persistence.