Effect-TS Architecture in the Land Editor
How we use Effect-TS for typed error handling across the editor core.
Effect-TS Architecture in the Land Editor
Code Editor Land uses Effect-TS as the foundational abstraction for typed error handling, structured concurrency, and dependency injection across the TypeScript layer of the editor. Cocoon, Wind, and the shared Vine IPC contracts are all built on Effect-TS from the ground up.
Why Effect-TS?
Traditional TypeScript error handling relies on try/catch blocks and Promise chains, which lose type information at every boundary. Effect-TS encodes errors and requirements directly in the function signature, making it impossible to accidentally swallow an error or forget a dependency.
Every service in the Wind UI layer returns an Effect<Result, AppError, Env>. The three type parameters represent:
- Result - the success value
- AppError - the union of all errors that can occur
- Env - the services this Effect depends on
This makes refactoring safe: the compiler tells you exactly what changed at every call site. When you rename a service tag or change its interface, every consuming Effect fails to compile.
The Effect Layer Pattern
Each service in Wind follows an atomic directory structure:
Effect/
ServiceName/
Tag.ts -- Context.Tag for dependency injection
Interface.ts -- TypeScript interface the service implements
Implementation.ts -- Concrete implementation using Effect primitives
Layer.ts -- Effect Layer that binds Interface to Implementation
Type.ts -- Shared types, error definitionsThese compose into TauriLiveLayer, ElectronLiveLayer, and TestLayer stacks. In production, Wind uses TauriLiveLayer, which routes service calls through Tauri commands into Mountain’s Rust handlers. For testing, TestLayer provides mock implementations. The consuming code never knows the difference.
How It Powers the Extension Host (Cocoon)
The Cocoon element runs VS Code extensions inside an Effect-managed runtime. Each extension lifecycle hook (activate, deactivate) is modeled as an Effect program with explicit error types and resource cleanup via Scope.
The core architecture:
Bootstrap (
Bootstrap/Implementation/Cocoon/Main.ts) composes all Effect-TS layers, establishes the gRPC connection toMountain, and starts the extension host services.Module Interception (
Effect/Module/Interceptor.ts) patches both CJSrequire()and ESMimportstatements so that calls to thevscodemodule are correctly sandboxed and routed to the appropriate, extension-specific API instance.API Factory (
Services/API/Factory/Service.ts) constructs thevscodeAPI object that extensions receive. Every method call is wired through the Effect-TS service stack, not through raw callbacks.Extension Host Service (
Services/Extension/Host/Service.ts) manages extension activation, lifecycle, and supervision. One extension crashing does not take down the rest.
All of this runs under Effect-TS’s structured concurrency model. Each extension runs in its own supervised fiber with Scope-based resource management — file handles, network connections, and spawned processes are all tracked and cleaned up on extension deactivation or failure.
gRPC Integration via Vine
Communication between Cocoon and Mountain happens over the Vine gRPC protocol defined in .proto files. Cocoon wraps the gRPC client in Effect-TS services:
// Services/Mountain/gRPC/Client.ts -- Effect-wrapped gRPC operations
interface MountainClient {
window: WindowOperations;
workspace: WorkspaceOperations;
commands: CommandOperations;
secrets: SecretStorageOperations;
}The Services/Mountain/gRPC/Client.ts module provides Effect-wrapped methods for window, workspace, command, and secret storage calls. The gRPC server (Services/gRPC/Server/Service.ts) implements the Vine protocol with bidirectional streaming for real-time event communication.
Process Hardening
Cocoon applies process-level hardening before any other code runs:
- Patches
process.exitto prevent extensions from killing the host - Handles uncaught exceptions and unhandled promise rejections
- Pipes logs to the host
Mountainprocess - Automatically terminates if the parent
Mountainprocess exits
This is the PatchProcess/ module, the first thing that runs on bootstrap.
Structured Concurrency
Effect-TS provides Effect.fork, Effect.race, and Effect.timeout primitives that integrate with the Echo work-stealing executor in the Rust layer, giving the editor predictable resource usage even under heavy extension load.
The Echo element in Land/Element/Echo provides a work-stealing task scheduler for bounded background work. In the TypeScript layer, Effect.fork creates fibers that the scheduler can preempt. This replaces the traditional Node.js event-loop model where one hung Promise blocks every other extension.
Typed IPC: Two Layers
The editor has two independent IPC paths, each typed differently:
Tauri IPC (
WindtoMountain): TypedTauricommands with Rust handlers inMountain’sIPC/module.Preload.tsshims the ElectronipcRendererso the VS Code workbench code communicates through Tauri’sinvokesystem instead.Vine gRPC (
CocoontoMountain): Strongly-typed protocol described by.protofiles, with generated Rust and TypeScript stubs. Bidirectional streaming for real-time events like document notifications, language provider requests, terminal I/O, and extension health.
These paths are independent. The website should not describe every service call as flowing through one universal bus. Each has its own job and its own error surface.
