Sanity Library Reference Docs
    Preparing search index...

    Type Alias EffectHandler<Client>

    EffectHandler: {
        bivarianceHack(
            params: Record<string, unknown>,
            ctx: EffectHandlerContext<Client>,
        ): Promise<void | { ops?: FieldOp[]; outputs?: Record<string, unknown> }>;
    }["bivarianceHack"]

    External effect handler — invoked at drain time with resolved params and a context. Returning outputs records them on the run's effectHistory row, read downstream as $effects['<name>'].<output>. Returning ops applies the state half of the effect in the completion commit (field.* only, never status.set) — every returned op must explicitly set target.scope, since completion ops have no authoring location to infer one from. Throwing fails the attempt, with no ops. Without a EffectRetry policy on the node, or once one has spent its attempts or closed its window, that failure settles the effect as failed. A policy with attempts left inside its window dispatches again instead, unless the drain finds between two attempts that it no longer holds the claim: then it stops without settling and reports the entry as lost. That check happens between attempts only. The completion itself stays first-writer-wins, so a dispatch that runs to its end still reports its outcome and settles the entry if it gets there first.

    Delivery is at-least-once: a handler MAY run more than once for the same effect. Three causes. A dispatch can die after its side effect but before commit. A lease can expire mid-dispatch and be taken over. And every admitted attempt of a retry policy invokes the handler again. Completion is first-writer-wins, and the losing run's completion is reported as lost.

    Write handlers to tolerate repetition. Derive external identifiers from ctx.effectKey so the receiving system can dedupe: the key is stable across every repeat, including all attempts of one policy run. Checking effectHistory[] for a row keyed by ctx.effectKey before irreversible work catches a repeat of an already-completed run, but never a retry attempt, because a policy writes one history row when the whole run ends and nothing records the attempt that just failed.

    The bivarianceHack indirection keeps this readable from the non-generic Engine surface; only the typed drain invokes handlers.

    Type Parameters