Simplicity
A small API with zero dependencies: lightweight, no containers, providers, or decorators. Works with any UI or server framework.
Simplicity
A small API with zero dependencies: lightweight, no containers, providers, or decorators. Works with any UI or server framework.
Clarity
No magic, no globals. Context moves through clear, typed wiring. Your app runs exactly as you composed it.
Reusability
Same code, different context per compose. Reuse across apps, tests, and environments — no copy-paste.
Testability
Validate your composition in tests. Missing context, duplicates, and unused wires fail in CI — not at startup.
Observability
Inspect your app as plain JSON — log it, render it, diff it across environments. Hook into start, complete, and fail events for timing, logs, or traces.
A minimal example built on the three key pieces: Task, Tag, and Wire (how they connect).
Two features share data without knowing about each other.
import { createTask, createWire, compose, tag } from "@grlt-hub/app-compose"
// where the name to greet will liveconst whoToGreet = tag<string>("whoToGreet")
const greeting = createTask({ name: "greeting", run: { // greeting reads from it context: whoToGreet.value, fn: (name) => console.log(`Hello, ${name}!`), },})
const user = createTask({ name: "user", run: { fn: () => ({ name: "World" }) },})
compose() .step(user) // filled by the user task .step(createWire({ from: user.result.name, to: whoToGreet })) .step(greeting) .run()