Skip to content

DougongCompose an application, don't stack frameworks

A small set of orthogonal atoms for capabilities, dependencies, change and resource ownership. No hidden magic.

What Dougong is

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:

  • Core — the capability composition and structured lifetime kernel. Six atoms: Service, ExtensionPoint, Event, Lifetime, Plugin, Host.
  • Platform — external plugin delivery on top of Core. Manifest validation, permissions, module loading, lazy activation, hot reload.

Plus an independent reactive package providing a signal value layer and an observe() combinator.

ts
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

When it fits

FitsDoes not fit
Capabilities install, update and roll back after startupYou just need a small DI container
Plugins have real dependencies on each otherPlugins are fully independent
A half-loaded state is unacceptableA long-running service where one broken module must not stop the rest
The application needs an observable execution modelOperational diagnostics do not matter
Desktop apps, editor kernels, build toolchainsA 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.

How to read these docs

Three layers, best read in order:

Layer 1 · Get running

  1. Getting started — install and run your first composition
  2. Core concepts — what each atom solves and why they cannot substitute for each other

Layer 2 · Go deeper

  1. Writing plugins — dependencies, provisions, config validation, failure
  2. Application code — read Services, observe ExtensionPoints and bridge Events from outside the graph
  3. Lifetime and resources — who owns what, and when it is released
  4. Transactions and change — ChangeSet, Group, rollback and fail-closed
  5. Reactive and observation — why a signal is not a fifth capability
  6. External plugin delivery — manifests, permissions, lazy activation, HMR

Layer 3 · Specification

  1. Core API specification — exact semantics and edge cases
  2. Architecture — layering, dependency direction and rationale
  3. Platform specification — the external plugin boundary
  4. Error codes — stable codes and what triggers them
  5. Mechanical guards — what each of the ten pnpm check steps protects

If 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.

Status

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.

Released under the MIT License.