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 checkIf 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-nextThis 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 effectseffect tracedfunctions with span names generated from the module and bindingservicedeclarations, emitted asContext.Serviceclassesresource { }blocks anddeferstatements for scoped cleanupandgroups that run concurrently and cancel remaining tasks on the first errorawaitwithin effect blocks, converting Promises using Effect’s promise constructorerrordeclarations emitted as tagged errors rather than standard classes- Explicit lifts from standard
OptionandResulttypes, and from Effect’s ownOptionandResult, 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.