effx
Concepts

Emit modes and targets

EmitMode chooses which files are written. TargetProfile chooses which Effect import paths they use. Both are output policy and never enter the IR or the semantic hash.

Two settings change the generated files without changing the IR.

SettingQuestion it answersLiterals
EmitModeWhich projections does this pass write?contract, handlers, all
TargetProfileWhich Effect package paths and API names do the files use?effect-4.0

Source: packages/compiler/src/Collected.ts (EmitMode, TargetProfile, ProjectResolution, ProjectConfig), packages/compiler/src/Extension.ts (GenerationContext), docs/specs/0010-target-profiles-and-emit-modes.md.

Output policy never enters the IR

GenerationContext carries the policy to generators. Its doc comment says: "Output policy never enters ApplicationIR or its semantic hash."

FieldTypeMeaning
targetTargetProfilethe Effect API profile
emitEmitModethe projection choice
allowImportingTsExtensionsbooleanemit .ts instead of .js in relative imports
canonicalImportBasestring, optionalone output-independent base for local symbols
outputDirstring, optionalwhere this pass writes
resolveEffectModulefunction, optionalruntime-only resolver for the target project's installed Effect modules

defaultGenerationContext is target: "effect-4.0", emit: "all", allowImportingTsExtensions: false. It applies when the pipeline runs without project resolution, for example in tests (compileCollected).

Spec 0010 requires the same source to give a byte-identical canonical IR and the same semantic hash in every mode, target and output directory.

Emit modes

Pass --emit to effx build or effx check. The default is all.

ModeWritesSource
contract<group>-contract.ts for each HTTP group. No handlers, no http.ts, rpc.ts, cli.ts, client.ts, foldkit.ts or guards.ts.packages/compiler/src/generate/http.ts and the other generators, which return nothing unless emit is all
handlers<group>-handlers.ts for each declaration-only group. Nothing else.generate/http.ts
allThe contract and handlers files for declaration-only groups, plus the projections for locally implemented operations: http.ts, rpc.ts, cli.ts, client.ts, and when applicable foldkit.ts and guards.ts.generate/*.ts

For an external-only group named profile, spec 0010 gives the exact outputs:

ModeOutput
contractprofile-contract.ts only
handlersprofile-handlers.ts only
allboth files, plus the usual projections for any locally implemented operations

Rules from spec 0010:

RuleDetail
Equal group names under different rootsBoth file names are prefixed with the root identifier. Files are never overwritten.
PlaceholdersNo mode writes an empty placeholder file for a missing implementation.
Manifestmanifest.json records the selected mode in emit and the exact files in generated.
Mode changeFiles the previous manifest owned and this pass does not write are removed. See effx build.

Why two passes

A backend package and a shared contract package can build from the same source with different modes. The contract pass writes the group the shared package exports. The handlers pass writes the factory the backend binds. Both passes see one IR.

 profile.effx.ts ──effx build --project <contract tsconfig> --emit=contract──▶ <contract dir>/.effx/generated/profile-contract.ts
        │
        └────────effx build --project <backend tsconfig> --emit=handlers──▶ <backend dir>/.effx/generated/profile-handlers.ts
                         same IR, same semanticHash

The two projects set one shared source identity with effx.projectRoot in each tsconfig, a path relative to that tsconfig. ProjectConfig.projectRoot supplies the same value programmatically. Local symbols and StableIds use that root, not the output directory (spec 0010, packages/frontend-ts/src/project.ts).

The output directory is selected by --out-dir, config outDir, or tsconfig effx.outDir. The default is .effx/generated next to the tsconfig.

Stable Effect target

effect-4.0 is the only target. It requires a stable installed Effect release in >=4.0.0 <5. Prereleases are not supported. The compiler reads the target project's own effect/package.json, not the CLI's bundled Effect. An explicit target cannot override an unsupported installed version. Missing or unsupported packages produce EFFX2701 and prevent generation.

Every generator uses the same native module names:

ImportModule
coreeffect
httpeffect/http
http apieffect/http-api
neteffect/net
rpceffect/rpc
clieffect/cli
sqleffect/sql

Application module leaves are checked against the installed package exports. Unsupported subpaths produce a diagnostic; the compiler does not rename them. Source: packages/compiler/src/generate/target.ts, packages/frontend-ts/src/project.ts, spec 0010's stable-only amendment.

Relative import suffix

Relative imports in generated files end in .js, unless the target tsconfig sets allowImportingTsExtensions. With that option they keep .ts. This applies to generated-to-generated imports and to imports of your own schema and service symbols. The suffix never affects a StableId or the hash (spec 0010).

Status

ItemStatus
--emit contract, --emit handlers, --emit allimplemented (scripts/effx.ts). STATE.md: "Gate 3 compiler prerequisites (target/split emit)", build, DoD met locally.
--target effect-4.0stable Effect 4 only; isolated fixture: packages/frontend-ts/test/fixtures/stable-v4
effx.config.tsimplemented (spec 0015): emit, target, outDir, strictAccess and project are optional config fields; precedence is flag, then config, then the tsconfig effx block, then the default (CLI).

On this page