Skip to main content

Lifecycle

The four phases

Plugins run in dependency order through four ordered phases:

PhaseWhat belongs in it
initreading configuration; nothing that needs another plugin's output
buildproducing this plugin's output — a service, a store, a connection
registercontributing, now that every dependency has built
afterRegistrationwork that needs the whole registry to exist

register and afterRegistration may return a disposer, which is what disable, deactivate and stop run.

Turning plugins on and off at runtime

enable and disable are not restart-only switches. Disabling a plugin disposes everything it contributed, and any host reading through useContributions or ReactorSlot updates immediately — a view leaves the switcher, a command leaves the palette, without the application tracking anything.

reactor.disable('@app/notebook');
reactor.getContributions(ViewTypePoint); // the notebook view is gone
reactor.enable('@app/notebook');
reactor.getContributions(ViewTypePoint); // and back

This is what makes a plugin checkbox honest: the list of plugins comes from reactor.listPlugins(), the state from reactor.isEnabled(name), and the UI that follows is one useSyncExternalStore away.

Disabling takes dependants with it

A dependant left running against a disabled dependency is holding an output nobody maintains — and it will read getOutput and find nothing, which is a crash somewhere with no idea why. So disable stands dependants down first, transitively, exactly as deactivate does.

reactor.disable('@app/base'); // '@app/top', then '@app/middle', then '@app/base'

enable brings back only what that disabling took down. The manifest says which is which:

reactor.getManifest('@app/middle')?.disabledBy; // 'dependency'
reactor.getManifest('@app/base')?.disabledBy; // 'user'

The distinction is what keeps the cascade safe. A plugin somebody switched off by hand stays off when its dependency returns — otherwise a switch could be undone by an unrelated one three plugins away. It is the same three-state argument as deactivation, one level up.

A host drawing a switch should draw the two differently: a dependency-disabled row cannot usefully be switched on, and offering the control anyway invites somebody to try. The plugins manager greys it and says why.

The whole cascade is one revision bump, so a host re-renders once rather than painting a half-finished teardown.

function PluginToggles() {
const reactor = useReactorPlatform();
useSyncExternalStore(reactor.subscribe, reactor.getRevision);

return (
<ul>
{reactor.listPlugins().map(name => (
<li key={name}>
<label>
<input
type="checkbox"
checked={reactor.isEnabled(name)}
onChange={event =>
event.target.checked ? reactor.enable(name) : reactor.disable(name)
}
/>
{name}
</label>
</li>
))}
</ul>
);
}

You rarely have to write that: @datalayer/reactor-manager is exactly this list, as a plugin.

Plugins that own something say so

enable() re-runs init and build, which is right for a plugin that only contributes records — it comes back clean. It is wrong for one that owns a connection, a kernel or a cache: the fresh build returns a new instance while everything holding the previous one is quietly detached.

definePlugin({
name: '@app/sandbox',
preserveOutput: true, // keep what I built across disable/enable
build() {
return { sandbox: createSandboxService() }; // a live connection
},
});

With preserveOutput, enabling a plugin that has already built keeps its output and only re-runs register — so its contributions come back while the thing it owns stays where it was. A stateless plugin needs none of this and can be toggled freely.

Disposal, in one place

What happensWhat the reactor does
disable(name)runs the plugin's register / afterRegistration disposers, then drops every contribution it made
enable(name)re-runs init, build, register — a fresh build output
deactivate(name)the same, plus its dependants first — but it may activate again
stop()disposes every plugin in reverse dependency order
a disposer returned by ctx.contribute(...)removes that one contribution; idempotent

Every one of these bumps the revision exactly once, so a plugin contributing five views during register wakes subscribers once rather than five times.