Advanced Configuration

View as Markdown

After one component works, the difficult questions are ownership questions: whether the kind can appear more than once, how a replacement interacts with the active report, and which API removes active behavior and which removes the implementation from future lookup.

Multiple Component Instances

Rust language-binding plugins return true from allows_multiple_components by default. A singleton implementation should override it to return false. Python and Node.js custom plugin objects do not expose a separate multiplicity hook in their current public interfaces, so design and test their component behavior with the binding contract rather than copying a Rust-only method into them.

When multiple components are allowed, each receives a separate component config and registration context. Give an instance an operator-facing identity such as a region or tenant only when diagnostics and external resources need it. Registration names remain local; Relay performs qualification and cleanup.

Replacement and Reports

Initialization replaces active configuration as a transaction. Validation and new registration run before Relay commits the replacement. If a callback fails, Relay removes partial new registrations and attempts to retain or restore the last proven configuration. A cleanup failure is more serious: the process can retain a runtime diagnostic report and refuse later mutation because it can no longer prove that stale callbacks are gone.

An activation-owned conditional middleware guardrail participates in the same transaction. Relay removes a partially registered gate if later component registration fails. A successful replacement removes the previous component’s gate during teardown and installs the replacement gate with the new component.

The report accessor is a snapshot, not a live inspection of every registry. The kind-listing accessor answers a different question: which implementations can future component documents reference? An inactive or disabled kind can appear in that list, and an active component can keep running after its kind is deregistered until its activation is closed.

Own the Complete Host Lifecycle

The host, rather than the plugin implementation, owns kind registration and active configuration. These complete lifecycle skeletons show where validation, report inspection, activation close, and deregistration belong. Put activation.close() in a guaranteed cleanup path so an application exception does not leave middleware installed.

1implementation = DocumentationPlugin()
2plugin.register("documentation-plugin", implementation)
3assert "documentation-plugin" in plugin.list_kinds()
4
5preflight = plugin.validate(plugin_config)
6if any(item["level"] == "error" for item in preflight["config"]["diagnostics"]):
7 raise ValueError(preflight["config"]["diagnostics"])
8
9try:
10 async with plugin.activate(plugin_config) as activation:
11 print("active report:", activation.report)
12 assert activation.is_active
13 await run_application_work()
14finally:
15 assert plugin.deregister("documentation-plugin")

Teardown in the Correct Order

Use the following order so active callbacks cannot outlive their implementation:

  1. Stop sending new managed calls and let in-flight plugin callbacks settle. In Node.js, await relay.flushSubscribers() before closing so queued event and scope-end sanitizers finish while their component still owns their callbacks.
  2. Inspect activation.report (or Rust activation.report()) when retaining activation-time diagnostics for investigation.
  3. Close the activation handle. Python and Node.js await activation.close(); Rust checks the Result returned by activation.close().
  4. Deregister the plugin kind only after active behavior is gone. Deregistration affects future validation and initialization, not registrations already committed to the runtime.
  5. Confirm the kind no longer appears in list_plugin_kinds, list_kinds, or listKinds, then repeat a representative call and prove the plugin effect is absent.

Success means instance multiplicity is intentional, reports are interpreted separately from registry state, async teardown never blocks its event loop, and the application can prove both active registrations and future kind lookup have been removed.

Configure Registration Control

The complete language-binding examples define the following optional group:

FieldDefaultValidation
enabledfalseMust be a Boolean value.
kinds["subscriber"]Must contain at least one supported global registration kind.
registration_name"documentation-controlled-subscriber"Must be a nonempty effective target name.
reason"disabled by documentation plugin"Must be a nonempty operator-facing explanation.

Keep the feature disabled unless the host deliberately supplies a target from runtime-registration discovery. Do not copy an effective name into long-lived configuration or reuse it after a process restart or plugin configuration reload. A host that generates plugin configuration dynamically can discover the target, insert its effective name, and initialize the component in the same runtime activation.

The example uses a constant-result callback because the component owns the gate for its complete activation. Use the application-level API for a timer or health check that changes eligibility without replacing plugin configuration.