Skip to content
app-compose

Lightweight IoC
for the front-end

Compose apps you can control and trust

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.

See it run

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.

Interactive — open on desktop to run and edit it live.
import { createTask, createWire, compose, tag } from "@grlt-hub/app-compose"
// 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()