Skip to content

How to enable the Effect extension

Use the supported Effect 4 preview, understand its version contract, and configure your project.

Updated View as Markdown

The Effect extension is disabled until you configure a transport module. When disabled, Hem does not emit Effect imports, and extension syntax and diagnostics are unavailable. Inside Effect code, raise, catch, and use use Effect’s failure and resource semantics. Ordinary synchronous code keeps its core semantics.

Supported preview

Hem’s Effect extension targets the native effect package’s Effect 4 API:

Part Supported configuration
Runtime Exactly effect@4.0.0
Compiler transport The effect package itself; generated code imports nothing else
Project setting "effectImportSource": "effect"
Package identity One physical Effect installation shared by the whole application

Effect 3 and other Effect 4 versions are outside this support contract. A passing capability probe does not extend it.

Upgrading from the release candidate

The supported runtime is now the stable 4.0.0 release, replacing 4.0.0-rc.117. Update the exact effect dependency and lockfile together. If you use @effect/vitest, update it to 4.0.0 as well; it requires Vitest 5. Keep hem.effectImportSource set to "effect". Hem’s generated code already uses the native Effect 4 API, so this upgrade does not change Hem syntax.

Effect 4 moves imports such as effect/unstable/http to effect/http. These APIs can still be unstable: consult their @stability tags before upgrading. See the Effect 4 migration guide for package and API changes in your handwritten TypeScript and ecosystem imports. After updating, run hem check, hem build, and your application’s tests.

What the generated code looks like

Emitted TypeScript is the Effect 4 code you would write by hand: service becomes a Context.Service class, error becomes Data.TaggedError, blocks become Effect.gen, binding catch arms become Effect.catch, await inside a block becomes Effect.tryPromise with Cause.UnknownError as its rejection type, and Effect’s eager carriers are Option and Result. There is no Hem runtime, adapter, or helper module between the emitted code and the library.

Install the runtime dependency

Hem’s packages are not published yet. Inside the Hem repository, use the exact runtime pin in the application’s package.json:

{
  "dependencies": {
    "effect": "4.0.0"
  }
}

Install with the workspace’s package manager. A packed preview consumer installs the same pinned version.

Keep a lockfile and avoid installing a second copy of Effect. Equal version numbers alone do not establish one package identity; HEM4116 reports duplicate physical installations.

Repair HEM4116

The diagnostic names each package root and a representative import or reexport that reaches it, including the source file and line. When no incoming edge is available, it names a participating declaration. These references show which packages to inspect; they are not an exhaustive import graph.

For example, an application’s native tests may resolve Effect from a pnpm installation while a generated module still resolves a second copy left by Bun. Restore the installation with the project’s chosen package manager and existing lockfile. For this repository, the repair command is pnpm install --frozen-lockfile. Recheck the reported roots before changing versions or running a dedupe command; the supported preview requires the exact version above.

Keep native tests and consumers in the project. Run the full project’s Hem check, build and behavior tests after repair. A small fixture that imports only effect can pass while the application’s complete graph still contains two roots. Removing those tests or suppressing the identity check would conceal the problem.

Configure the transport

Set effectImportSource in your tsconfig.json so the CLI, language server, Vite plugin, and hem test share the same configuration:

{
  "hem": { "effectImportSource": "effect" }
}

This names the module Hem checks and uses for generated imports.

To override the transport in Vite for a single build, pass the plugin option:

hem({ effectImportSource: "effect" });

Use tsconfig.json as the primary configuration. If the Vite plugin setting differs from tsconfig.json, your editor and build system will use different settings.

Use library APIs explicitly

Import the namespaces you call directly from effect, as the examples do; Hem adds the imports its own generated code needs (Effect, Context, Data, Cause, Option, Result) and aliases them when your names collide. Other Effect modules and ecosystem packages remain ordinary imports. They must share the same Effect installation and satisfy the checked types.

Arbitrary ecosystem packages are not certified by this preview; check their version requirements and runtime behavior separately. When consulting Effect documentation, check it against the pinned version rather than assuming current examples or Effect 3 examples apply unchanged.

Check that the probe passes

Before compiling, Hem checks the configured module for the Effect 4 APIs its generated code uses in Effect, Layer, Data, Context, Cause, Option, and Result, and that Effect.fromOption and Effect.fromResult lift the module’s own carriers inside Effect.gen.

hem check

If an API is missing, the compiler reports error HEM3001 and halts the build. This indicates that the installed module does not support the required features.

Types are validated by declaration origin rather than name, so local types named Effect will not conflict with the library.

Hem’s compatibility tests exercise resource release, interruption, typed errors, carrier lifts, and Layer wiring. These tests support the named configuration; they are not a guarantee for every Effect API or application. Run your own application tests as well.

Imports from the effect barrel, public subpaths such as effect/Effect and effect/Result, and typed reexports share the same carrier checks. Hem follows the configured package’s type declarations, including type aliases; constructor names on unrelated local types do not acquire Effect behavior.

Testing an alternate transport

hem test --effect ./vendor/effect-next

This command runs your test suite against an alternate package without updating your configuration. It is a compatibility experiment, not a supported upgrade path. Passing the capability probe establishes that required API shapes exist; it does not certify runtime compatibility. Keep the pinned version for the supported preview.

What turns on

Enabling the extension adds these features to .hem files:

  • effect { } blocks and the postfix ! operator for binding effects
  • effect traced functions with span names generated from the module and binding
  • service declarations, emitted as Context.Service classes
  • resource { } blocks and defer statements for scoped cleanup
  • and groups that run concurrently and cancel remaining tasks on the first error
  • await within effect blocks, converting Promises using Effect’s promise constructor
  • error declarations emitted as tagged errors rather than standard classes
  • Explicit lifts from standard Option and Result types, and from Effect’s own Option and Result, into the Effect error channel

The error declaration works across both modes: core and Effect builds both extend Error and include a _tag property, allowing modules with and without the extension to share errors.

The compiler also enables the HEM41xx diagnostic checks, which flag unsafe patterns such as calling Effect runners inside an effect or returning unhandled Promises in a success channel. See the Effect boundaries reference for the complete list.

Turning it back off

Remove effectImportSource from your configuration. Any code using Effect-specific syntax will fail to compile. There is no partial-enablement mode.

Next

Part II of the tour covers each Effect construct and its generated TypeScript.

For a complete small example, compare the report application in Hem and native TypeScript + Effect. It combines input validation, two services, concurrent reads, selective recovery, and scoped cleanup. Both versions import the same effect package and run the same tests. Its data and resource lease are synthetic; the example uses the preview configuration described above.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close