rhythmjs

Search documentation

Search guides, the tutorial and every package.

On this page

Create an instance#

Rhythm<TInput, TStartup> declares the input required by each run and, optionally, the shape of the startup values the app holds in context. Optional name and type labels make errors from registered modules easier to identify.

TypeScript
import { Rhythm } from "@rhythmjs/rhythm";

const app = new Rhythm<{ jobId: string }, { service: string }>({ name: "jobs" }).use((ctx) =>
  console.log(ctx.service, ctx.jobId),
);

app.context.service = "worker";

Run and reuse#

run(input) creates a shallow context from { ...app.context, ...input }, dispatches it through the chain, and returns the final context. Startup values are on the context from the start, and an input field with the same name wins. See Startup context.

callback() exposes the reusable async handler. Finish configuring the pipeline before obtaining a handler.

TypeScript
const handle = app.callback();
await handle({ jobId: "job-1" });
const result = await app.run({ jobId: "job-2" });
console.log(result.service); // worker

Understand state#

Every run gets a new top-level context object, but startup values are shared references. DeepReadonly is a TypeScript constraint, not a runtime freeze. Avoid mutable shared request state in context.

Choose a composition boundary#

Use register(child, exportValue) when a module needs a separate top-level context. Use use(child.middleware()) for flat composition and fallthrough into the parent.