Simplicity
A small API with zero dependencies: lightweight, no containers, providers, or decorators. Framework-agnostic.
Features, services, and modules often know about each other directly. That makes them hard to test, reuse, and maintain. With App-Compose, each part declares what it needs, and you supply it — so you stay in control as your app grows.
Simplicity
A small API with zero dependencies: lightweight, no containers, providers, or decorators. Framework-agnostic.
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.
If you want to use App-Compose for a part of your existing app, you don’t have to rewrite the rest. Add it to your stack, and bring in more when you’re ready.
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 "@app-compose/core"
// 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()