Skip to content
app-compose

Lightweight IoC
for the front-end

Compose apps you can control and trust

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 live
const 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()