Effects
A named body of work the host runs. Not invoked by a person, not called by the model.
import { function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): ModThe entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.defineMod, function effect(metadata: EffectMetadata, body: EffectBody): EffectDeclare background behavior that starts when the mod activates. The body runs once at activation, then again whenever any state or signal it read has changed. Dependencies are tracked automatically, so there is nothing to subscribe to and nothing to unsubscribe from. Every listener, interceptor, and subscription the body registers through `ctx` belongs to that one run and is torn down before the next one. Reach for an effect when behavior should stay live for as long as the mod is loaded, instead of firing when someone triggers it. A `command` runs when the user picks it. A `system.on` listener answers one host event source. An effect is the standing one, and it is also the only place `ctx.hooks.beforeAction` interceptors can be registered. The body must be synchronous. Returning a promise throws. Put asynchronous work inside an event handler or an action call. Return a cleanup function or a `Disposable` for anything the SDK does not already own.effect } from '@modular/sdk'; export defaultdefineMod<{ readonly metadata: { readonly displayName: "Hello"; readonly description: "A first mod"; }; readonly effects: readonly [Effect]; }>(definition: { readonly metadata: { readonly displayName: "Hello"; readonly description: "A first mod"; }; readonly effects: readonly [Effect]; }): ModThe entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.defineMod({metadata: { readonly displayName: "Hello"; readonly description: "A first mod"; }metadata: { displayName: "Hello"displayName: 'Hello', description: "A first mod"description: 'A first mod' }, effects: readonly [Effect]effects: [ function effect(metadata: EffectMetadata, body: EffectBody): EffectDeclare background behavior that starts when the mod activates. The body runs once at activation, then again whenever any state or signal it read has changed. Dependencies are tracked automatically, so there is nothing to subscribe to and nothing to unsubscribe from. Every listener, interceptor, and subscription the body registers through `ctx` belongs to that one run and is torn down before the next one. Reach for an effect when behavior should stay live for as long as the mod is loaded, instead of firing when someone triggers it. A `command` runs when the user picks it. A `system.on` listener answers one host event source. An effect is the standing one, and it is also the only place `ctx.hooks.beforeAction` interceptors can be registered. The body must be synchronous. Returning a promise throws. Put asynchronous work inside an event handler or an action call. Return a cleanup function or a `Disposable` for anything the SDK does not already own.effect({ EffectMetadata.id: stringid: 'hello.sync', EffectMetadata.title?: string | undefinedtitle: 'Sync' }, ctx: EffectContextctx => { ctx: EffectContextctx.ContextBase.logger: LoggerYour mod's log channel.logger.Logger.info(message: string, ...args: readonly unknown[]): voidinfo('the mod activated'); }), ], });
Members
Effect — id, title, body, kind.
Four members and no when, no owner, no stability. That is a small surface
compared to its neighbours in this section, and it is worth reading as a signal:
an effect is a unit of work, not a published contract.
Why this page is here, provisionally
Effects sit under Connections because the host schedules them — nobody in your
mod calls body directly. But unlike events and
actions, nothing in the SDK shows an effect being addressed by
another mod: no owner field, no listEffects(), no descriptor type.
If that stays true, this page belongs in Start beside State — both are internal machinery — rather than here among the things other mods can see. It is filed here for now because the trigger is external to your code, and flagged so the placement gets revisited rather than inherited.