Routes and instances

Mount definitions on URI templates, create families, protect internal routes, and use typed handles.

On this page 4
Templates become typed instances
doc:/:idroute family
doc:/42instance
hub.of()typed handle
internalserver only

durableRoutes() maps URI templates to channel definitions. A literal segment matches itself; :name matches one non-empty segment. No scheme is special-cased.

Singleton or family

A template without a named parameter, such as counter://, is a singleton. The hub creates it from the initial state on first touch.

A template such as doc:/:id is a family. Create each concrete URI before subscribing or dispatching:

const routes = durableRoutes()
  .route("counter://", counter)
  .route("doc:/:id", document)
  .build();

await hub.create("doc:/42", { title: "Draft" });

Use a typed route handle

hub.of(template) binds the template once and types parameter objects, state, actions, and commands from it.

const docs = hub.of("doc:/:id");

docs.uri({ id: "42" });
await docs.create({ id: "42" }, { title: "Draft" });
await docs.dispatch({ id: "42" }, "doc/renamed", { title: "Final" });
await docs.get({ id: "42" });

Keep internal routes private

Mount a catalogue or coordination channel with { internal: true } when only server code should reach it. Connection-bound subscribe, dispatchFrom, and execFrom calls see RouteNotFoundError; trusted hub methods, commands, and effects still use the route normally.

durableRoutes()
  .route("doc:/:id", document)
  .route("x-catalog://", catalogue, { internal: true })
  .build();

Destroy deliberately

destroy(uri) removes persisted state, aborts background work, drops subscriptions, and removes the instance. The hub does not infer ownership between channels. A parent that owns children should destroy them explicitly in its own command.

Continue with Clients and reconnect to keep mirrors current across transport changes.