Advanced Configuration
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.
Python
Node.js
Rust
Teardown in the Correct Order
Use the following order so active callbacks cannot outlive their implementation:
- 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. - Inspect
activation.report(or Rustactivation.report()) when retaining activation-time diagnostics for investigation. - Close the activation handle. Python and Node.js await
activation.close(); Rust checks theResultreturned byactivation.close(). - Deregister the plugin kind only after active behavior is gone. Deregistration affects future validation and initialization, not registrations already committed to the runtime.
- Confirm the kind no longer appears in
list_plugin_kinds,list_kinds, orlistKinds, 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:
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.