Skip to content

Activation

Activation is the plugin manager running a module’s body: once, when the module’s event has fired and every capability it requires has been contributed.

An activation event is a named moment in the app’s life, such as startup or the first time an article is shown. A module names the event it waits for in activatesOn, and can combine several: ActivationEvent.oneOf activates on the first of them, and ActivationEvent.allOf waits for all of them.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import ActivationEvent
ActivationEvent
from '@dxos/app-framework/ActivationEvent';
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
export const
const IndexModule: Capability.Module<void>
IndexModule
=
import Capability
Capability
.
const inlineModule: <readonly [Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">], readonly [], void, void>(name: string, spec: ModuleSpec<readonly [Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">], readonly [], void, void>, activate: (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">> | readonly Capability.Contribution<Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">>[], Error, Capability.Requirements<...>>) => Capability.Module<...>

Helper to define an eager module body with the same spec-carrying shape as

lazyModule

— for bodies that are plain values (translations, schema) or too small to justify a chunk, so plugin definitions stay a uniform chain of spec-carrying modules.

@param ― name The module name — used to auto-compute module IDs.

@param ― spec The requires/provides declaration.

@param ― activate The module body.

inlineModule
(
'Index',
{
provides: readonly [Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">]
provides
: [
const Index: Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">
Index
],
activatesOn?: ActivationEvent.Events | undefined

Activates when this runtime event fires instead of during the dependency pass.

activatesOn
:
import ActivationEvent
ActivationEvent
.
const oneOf: (...events: ActivationEvent.ActivationEvent[]) => {
type: "one-of";
events: ActivationEvent.ActivationEvent[];
}

Helper to create an activation event that triggers when any of the given events are activated.

oneOf
(
const IndexRequested: ActivationEvent.ActivationEvent
IndexRequested
,
const SearchStart: ActivationEvent.ActivationEvent
SearchStart
),
},
() =>
import Effect
Effect
.
const succeed: <Capability.Contribution<Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">>>(value: Capability.Contribution<Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">>) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">>, never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@stability ― stable

@category ― constructors

@since ― 2.0.0

succeed
(
import Capability
Capability
.
const contribute: <Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">>(capability: Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">, implementation: Map<string, string>) => Capability.Contribution<Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">> (+1 overload)
contribute
(
const Index: Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">
Index
, new
var Map: MapConstructor
new () => Map<any, any> (+3 overloads)
Map
())),
);

A module’s requires is the second gate. The plugin manager keeps a graph of which module provides each capability, and activates providers before the modules that require them, whichever event activated the provider. Registration order plays no part.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
// No `activatesOn`: this module waits for `Index`, so it activates whenever the
// module above does.
export const
const QueryModule: Capability.Module<void>
QueryModule
=
import Capability
Capability
.
const inlineModule: <readonly [], readonly [Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">], void, void>(name: string, spec: ModuleSpec<readonly [], readonly [Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">], void, void>, activate: (props: void) => Effect.Effect<void | readonly never[], Error, Capability.Requirements<readonly [Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">]>>) => Capability.Module<void>

Helper to define an eager module body with the same spec-carrying shape as

lazyModule

— for bodies that are plain values (translations, schema) or too small to justify a chunk, so plugin definitions stay a uniform chain of spec-carrying modules.

@param ― name The module name — used to auto-compute module IDs.

@param ― spec The requires/provides declaration.

@param ― activate The module body.

inlineModule
(
'Query',
{
requires?: readonly [Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">] | undefined
requires
: [
const Index: Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">
Index
],
provides: readonly []
provides
: [] },
() =>
import Effect
Effect
.
const gen: <Effect.Effect<Map<string, string>, never, Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">>, void>(f: () => Generator<Effect.Effect<Map<string, string>, never, Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">>, void, never>) => Effect.Effect<void, never, Capability.CapabilityIdentifier<"com.example.plugin.search.capability.index", "single">> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const index: Map<string, string>
index
= yield*
const Index: Capability.Tag<Map<string, string>, "com.example.plugin.search.capability.index">
Index
;
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
const index: Map<string, string>
index
.
Map<string, string>.size: number

@returns ― the number of elements in the Map.

size
);
}),
);
  • The app fires ActivationEvents.Startup while it boots and ActivationEvents.Idle once it has painted and the browser is idle. A module with no activatesOn belongs to the Idle wave, so only what the first screen needs runs at startup.
  • The framework fires further events when something first needs a module. ActivationEvents.SurfacesRequested(role) fires the first time a surface of that role renders; CommandsRequested fires when the command palette or CLI asks for commands. The module makers in AppCapability choose the event, so the surface module of a plugin loads with the first article, not at startup.
  • A module can fire an event itself with Plugin.activate, which is how one plugin loads another’s modules on demand.
import * as
import Plugin
Plugin
from '@dxos/app-framework/Plugin';
export const
const requestIndex: Effect.Effect<boolean, Error, Plugin.Service>
requestIndex
=
import Plugin
Plugin
.
const activate: (event: ActivationEvent.ActivationEvent) => Effect.Effect<boolean, Error, Plugin.Service>

Activates plugins based on the activation event. Accesses the PluginManager via the Effect layer system.

@param ― event The activation event.

@returns ― Whether the activation was successful.

activate
(
const IndexRequested: ActivationEvent.ActivationEvent
IndexRequested
);
  • An event stays fired. A module added after its event, for example by a plugin a person turns on later, activates at once.
  • A lazy module’s code is imported only when it is about to activate. A module whose event never fires is never loaded, which is what keeps a large app’s boot small.
  • Plugins come and go while the app runs. Enabling one registers its modules and activates every module whose event has fired; disabling one deactivates its modules in reverse order, runs their finalizers and removes their contributions. PluginManager shows the calls.
  • A module that fails, or does not finish within 30 seconds, is recorded on the manager’s failed list and its plugin is disabled, so one broken plugin does not stop the app from starting.
  • Two modules that require each other’s capabilities form a cycle, and the plugin manager fails them with DependencyCycleError at runtime.
  • A module that requires a capability no enabled plugin provides waits forever, without an error. The inspector shows such a module as waiting.
  • Activation order is a graph, not a sequence. Do not rely on two modules with no requirement between them activating in a particular order.