Explicit over implicit
Dependencies live in requires, identity lives in Contracts, ownership lives in Lifetimes. No service locator, ambient scope, prototype-chain injection or Proxy — where ctx.foo comes from is always visible in the same file.
A small set of orthogonal atoms for capabilities, dependencies, change and resource ownership. No hidden magic.
Dougong (斗拱, the interlocking bracket set of Chinese timber architecture) solves one problem: when an application's capabilities must be split into independently installable units, how do their dependencies, lifetimes and changes remain easy to reason about?
It has two layers:
Plus an independent reactive package providing a signal value layer and an observe() combinator.
import { createHost, definePlugin, service } from "dougong"
const CLOCK = service<Clock>("app/clock")
const clock = definePlugin({
name: "app.clock",
provides: { clock: CLOCK },
setup: () => ({ clock: { now: () => new Date() } }),
})
const greeter = definePlugin({
name: "app.greeter",
requires: { clock: CLOCK }, // the dependency lives here
setup(ctx) {
console.log(ctx.clock.now()) // only declared dependencies exist, or it fails to compile
},
})
const host = createHost({ name: "hello" })
host.install(greeter) // install order does not decide start order
host.install(clock)
await host.start() // topology derived from declarations, layers start concurrently| Fits | Does not fit |
|---|---|
| Capabilities install, update and roll back after startup | You just need a small DI container |
| Plugins have real dependencies on each other | Plugins are fully independent |
| A half-loaded state is unacceptable | A long-running service where one broken module must not stop the rest |
| The application needs an observable execution model | Operational diagnostics do not matter |
| Desktop apps, editor kernels, build toolchains | A simple web page |
That last row deserves a note. Dougong's failure model is transactional — a plugin whose setup fails rolls the whole change back. If your scenario values isolation ("one plugin dying must not affect the others") more than consistency, something like cordis fits better. That is a product trade-off, not a quality ranking.
Three layers, best read in order:
Layer 1 · Get running
Layer 2 · Go deeper
Layer 3 · Specification
pnpm check steps protectsIf you would rather read code, the runnable examples are a twelve-chapter path from a minimal Service to Planet / Lynx / HMR — all of it runs in CI, and "each chapter adds exactly one rung" is itself a test.
Dougong is in early development (0.0.x) and makes no backward-compatibility promises yet. The current priority is a correct model, a consistent API and complete executable evidence.
Runtime baseline: Node.js ≥ 22; Chrome / Edge 119, Firefox 121 or Safari 17.4 for browsers and WebViews, with Promise.withResolvers(). Explicit .dispose() works throughout; using / await using additionally require the corresponding well-known symbols from the runtime or an application-supplied polyfill.