Getting started
This page takes about ten minutes: install Dougong, write your first capability composition, and understand what separates it from an ordinary DI container.
Requirements
| Item | Requirement |
|---|---|
| Node.js | ≥ 22 |
| JavaScript runtime | Must provide ES2024 standard capabilities, including Promise.withResolvers() |
| TypeScript | ≥ 5.5 (if you use TypeScript) |
Browser and WebView runtimes
Promise.withResolvers() requires Safari 17.4+ (macOS 14.4+), Chrome 119+ or Firefox 121+. If you embed a system WebView through Electron, Tauri or Wails, check your minimum target OS first.
Explicit .dispose() does not require Explicit Resource Management support from the runtime. using / await using additionally require Symbol.dispose / Symbol.asyncDispose; when they are not native, the application must install a polyfill before importing Dougong. Dougong does not mutate globals.
Install
npm install dougongdougong is the facade package; it re-exports the three real packages. Install them individually if you only need part of the system:
npm install @dougongjs/core
npm install @dougongjs/reactive
npm install @dougongjs/platformTypeScript configuration
Dougong's types use Symbol.dispose and AbortSignal, so your tsconfig.json must include the matching libs:
{
"compilerOptions": {
"target": "ES2024",
"lib": ["ES2024", "DOM", "DOM.Iterable", "ESNext.Disposable"],
"moduleResolution": "Bundler",
"strict": true
}
}What happens without them
Writing only "lib": ["ES2024"] produces a wall of errors pointing into node_modules, which looks like a broken library:
Property 'dispose' does not exist on type 'SymbolConstructor'
Cannot find name 'AbortSignal'Adding DOM and ESNext.Disposable fixes it.
Your first composition
A complete, runnable example. The provider publishes a stable Service; the consumer declares its dependency through requires.
import { createHost, definePlugin, service } from "dougong"
interface Clock {
now(): Date
}
interface Greeter {
greet(name: string): string
}
const CLOCK = service<Clock>("example/clock")
const GREETER = service<Greeter>("example/greeter")
const clock = definePlugin({
name: "example.clock",
provides: { clock: CLOCK },
setup: () => ({ clock: { now: () => new Date() } }),
})
const greeter = definePlugin({
name: "example.greeter",
requires: { clock: CLOCK },
provides: { greeter: GREETER },
setup: (ctx) => ({
greeter: {
greet: (name) => `${ctx.clock.now().toISOString()} Hello, ${name}`,
},
}),
})
const host = createHost({ name: "hello" })
host.install(greeter)
host.install(clock)
await host.start()
console.log(host.get(GREETER).greet("Dougong"))
await host.stop()Output:
2026-08-15T00:00:00.000Z Hello, DougongWhat just happened
Install order is not start order
greeter is installed first but depends on clock. During host.start(), Dougong builds a dependency graph from the requires / provides declarations, topologically sorts it, starts each topological layer concurrently, and stops in reverse dependency order.
You never sort by hand, and there is no dependsOn: ["example.clock"] string array — the dependency is already in the types.
A plugin can only read what it declared
definePlugin({
name: "bad",
setup(ctx) {
ctx.clock.now()
},
})TypeScript rejects this with Property 'clock' does not exist on type 'PluginContext<{}>' because no clock dependency was declared.
The type of ctx is derived from requires. Undeclared means the property does not exist — a compile-time error, not an undefined discovered during execution.
This is the most practical difference from most plugin frameworks. Where dependencies resolve through strings or an ambient context, a forgotten declaration usually shows up as "works sometimes, undefined other times", depending on load order.
host.get() is for application code, not for Plugins
host.get(GREETER)Plugins have no ctx.get() method.
Plugins relate to each other through requires so that the dependency graph is complete. If a plugin could pull any capability from a service locator at will, the graph would stop reflecting real dependencies, and both topological ordering and transactional rollback would lose their meaning.
host.get() only works while status === "active"; otherwise it throws SERVICE_UNAVAILABLE.
Run the repository examples
To see fuller scenarios directly:
git clone https://github.com/Tangerg/dougong.git
cd dougong
pnpm install
pnpm examples
pnpm check
pnpm docs:devTwelve chapters climb three stages — atoms, composition, complete applications — from a minimal Service through the Planet / Lynx scenarios to declarative plans and module-graph HMR, all in CI. See runnable examples.
Next
- Understand the model → Core concepts: what each atom solves, and why an ExtensionPoint is not a Service
- Start writing code → Writing plugins: config validation, optional dependencies, failure handling
- Worried about leaks → Lifetime and resources
- Need exact semantics → Core API specification