Skip to content

Lint rules ​

The workflow sandbox has authoring rules that a linter can see; this package ships them as an ESLint-compatible plugin, loadable by oxlint's jsPlugins.

The authoring rules ​

The whole Effect program runs inside the Temporal workflow sandbox. Activity.make is a typed seam, not a Temporal Activity — durability comes from the Temporal activity proxies, timers, and signals the effects call, each memoized in history. Hence:

  1. All I/O goes through a Temporal activity — a declared activity call (yield* Charge(payload)) or callRawActivity. Anything else is nondeterministic on replay. A raw Effect.promise(() => acts.foo()) works but is not cancelled on interrupt.
  2. Effect.promise callbacks must be zero-arity — non-zero arity makes Effect allocate an AbortController per call, which the sandbox does not provide.
  3. No module-level mutable state in workflow code — under the worker's default reuseV8Context, module-level variables are shared across every workflow instance on a thread. Keep run state inside the handler.
  4. Never mix the halves — a module must not import both the sandbox half (@temporalio/workflow, engine-sandbox) and the client half (@temporalio/client, engine-client): they can never share a process.
  5. Evaluate versions on the main fiber — version / versioned markers evaluated inside forks or races make marker order nondeterministic.
  6. Author with the definition module — the pre-0.4.0 surface (typed-activity, versioning, the primitive make constructors, the per-primitive engine-sandbox calls) was removed in 0.5.0; a stale import fails to resolve, and this rule tells you what replaced it.

Setup ​

Extend a shipped preset:

jsonc
// .oxlintrc.json
{
  "extends": ["./node_modules/@springbird/effect-temporal/oxlint-presets/recommended.json"]
}

Or configure the rules directly:

jsonc
{
  "jsPlugins": ["@springbird/effect-temporal/lint"],
  "rules": {
    "effect-temporal/zero-arity-effect-promise": "error",
    "effect-temporal/no-module-level-mutable": "error",
    "effect-temporal/no-mixed-halves": "error",
    "effect-temporal/prefer-call-temporal-activity": "warn",
    "effect-temporal/versioning-on-main-fiber": "error",
    "effect-temporal/prefer-definition": "error"
  }
}

Two presets ship: recommended (all six rules, prefer-call-temporal-activity as a warning) and correctness (only the hard-error sandbox rules — no migration rule).

Scope ​

A file counts as workflow code when it imports @temporalio/workflow, the bundle module, or the engine-sandbox module — the rules are inert elsewhere, so enabling them repo-wide is safe. no-mixed-halves applies everywhere by nature. versioning-on-main-fiber has one more trigger: importing version or versioned from the definition module marks the file for that rule (alias-aware), since definition-authored handler modules deliberately import nothing engine-shaped. The other sandbox rules cannot see such modules — a handler that needs them linted can live next to its bundle entry, which imports bundle.

prefer-definition applies everywhere: it keys off the import source alone (the package specifier or a relative path to one of this package's modules), and its message names the replacement — defineActivity for TypedActivity.make, versioned for Versioning.match, the declaration's .take for takeMailbox, codecsFor from wire, bundle for a workflowBundle import from engine-sandbox, and so on. Because it is an error in recommended, oxlint exits non-zero on any file still importing a removed symbol — a migration guide that runs as a lint.

The remaining footguns — drain mailboxes before continueAsNew, respond to updates before completion — are runtime-shaped and covered by runtime guards and the guide instead.

RuleCatches
zero-arity-effect-promiseEffect.promise((signal) => ...) in workflow code
no-module-level-mutablemodule-level let/var in workflow code
no-mixed-halvesone module importing both process halves
prefer-call-temporal-activityraw Effect.promise where a cancellable call belongs
versioning-on-main-fiberversion / versioned / Versioning.* inside fork / race / all
prefer-definitionany import of the pre-0.4.0 surface (removed in 0.5.0), with its replacement