Skip to content

Runnable examples

@dougongjs/examples is not a folder of static snippets. It is an executable harness over Dougong's public API: twelve chapters run in order and pass through the repository's typecheck, test, coverage and build gates — so every semantic claimed in these docs has runnable evidence here.

sh
git clone https://github.com/Tangerg/dougong.git
cd dougong && pnpm install
pnpm examples

How the path is built

Three stages, twelve chapters, each adding exactly one rung:

StageChaptersWhat you are learning
1 · Atoms01–04One primitive per chapter, and the problem it exists to solve on its own
2 · Composition05–08The primitives together: what failure looks like, how identity is spelled out, how Host state is observed, how external code arrives
3 · Complete applications09–12The first eight chapters arranged into real application shapes, introducing no new primitive

"Strictly progressive" is a test

The concepts array in example.ts is both the syllabus and the reading order. Each chapter declares which concepts it is the first to use, and the test concatenates all twelve declarations and compares them to concepts for exact equality.

A repeated concept, an inverted order, or a chapter that adds nothing new — any one of them turns CI red. So this table cannot drift away from the code.

Stage 1 · Atoms

One primitive at a time. After these four chapters you know what four of the six atoms are responsible for, and why they cannot substitute for one another.

#ScenarioNew conceptsThe point
01Serviceservice provides requires host.getInstallation order is not startup order; the declared dependency edge is
02ExtensionPoint + Eventextension-point contribute contribution-view event contribution-disposeAn ExtensionPoint holds current contributions; an Event keeps nothing — it is not a query API
03Lifetimecleanup child-lifetime spawn abort-signalEverything hangs off one tree, released in reverse registration order; a subtree can be released on its own
04Reactivesignal computed batch observecomputed derives purely and owns nothing; observe() is the single seam between "a value changed" and "a resource is rebuilt"
What chapter 03 actually prints
- Setup acquired open:index → open:window → open:session.
- Disposing the child released only its own subtree: cancel:session-watcher → close:session.
- Stopping released the rest in reverse: cancel:editor-watcher → close:window → close:index.

The window is built on the index, so the window closes first. That order is what the test asserts — remove the .reverse() inside Core and this line turns red immediately.

Stage 2 · Composition

The primitives start meshing. These four chapters cover exactly what a small project can paper over and a large one cannot.

#ScenarioNew conceptsThe point
05Config and failureconfig-schema config-validation change-set setup-failure rollbackValidation happens before anything is stopped; rollback is work undone, not work skipped
06Contract families and Groupscontract-family group atomic-commit group-removalStatically selected variants of one capability use an explicit Contract family; a Group expresses installation ownership only
07Diagnosticsdiagnostics-view lifetime-snapshot terminal-detachment view-finalizationTerminal resources detach from their owner; after shutdown the view finalizes into data instead of retaining the Host
08Platformmanifest permissions placeholder activationRegistration ≠ activation; the placeholder-to-implementation swap is one committed step

Chapter 05 is the turning point of the path

The first four chapters live in a world where everything works. Chapter 05 is the first to ask: what if the declaration is wrong, and what if setup throws.

The answer is Dougong's most distinctive semantic, and its main trade-off: if any Installation in a ChangeSet fails to activate, the whole change rolls back. The audit Instance in the example genuinely did start, then was undone — started 1 time and was released 1 time.

If your situation needs "one plugin dies, the others keep running" instead, decide that here.

Stage 3 · Complete applications

No new API is introduced. These four chapters arrange the previous ones into shapes real applications take.

#ScenarioNew conceptsWhat it proves
09Planetcall-time-selection live-provider-swap group-bound-platformAdding and removing providers never restarts the player — an ExtensionPoint is not a dependency edge
10Lynxdomain-catalog workspace-ownership registration-updateCommand uniqueness is domain policy; a root consumer sees the Group's contribution, so a Group is not a scope
11Declarative plandesired-state content-revision platform-change-setDesired state diffed into one ChangeSet; identity from the manifest name, change from an explicit revision
12HMR module graphmodule-graph invalidation-closure multi-registration-hmrInvalidation propagates along importers; two Registrations update atomically and the observer sees exactly 1 commit

What 11 and 12 are for

These two chapters correspond to subsystems that mature plugin frameworks ship as thousands of built-in lines: a declarative config loader and a hot-reload engine.

Here each is roughly 200 lines, uses only the public API, and introduces no new primitive. That is the test of whether Core's abstractions are open enough to be composed on — if HMR invalidation required a framework-provided interception point, those 200 lines could not be written.

The shape of a chapter

Each chapter is an exported async function that creates and fully releases its own Host:

ts
import { diagnostics } from "@dougongjs/examples"

const result = await diagnostics()
console.log(result.facts)

The facts in the returned value record what the run actually observed, not a restatement of design intent. Tests assert the important semantics inside them, so a stale example turns CI red — they cannot quietly rot.

Startup-topology benchmark

The repository also includes a startup benchmark for the independent and chained topologies:

sh
pnpm examples:benchmark

It only prints measurements and never uses a wall-clock threshold as a CI condition. Concurrency semantics are guarded by deterministic behavioral tests, so jitter on a shared runner cannot manufacture a flaky failure.

Typical result (20 plugins each sleeping 20ms):

TopologyDescriptionOrder of magnitude
Independent20 plugins with no dependenciesClose to a single plugin — one layer, run concurrently
Chained20 plugins in a dependency chainClose to 20× — dependencies force serialization

That is exactly what layered concurrency should look like: concurrent where it can be, serial where it must be.

Next

Released under the MIT License.