Skip to content
The Graft mark, a G whose crossbar is an arrow driven into its center

Horticultural Effect Systems

Graft grows your application as a tree of plugins on Effect. Load, replace and unload any branch while the process keeps running. Prune fearlessly.

Software is not built. It is grown.

Plugins arrive, take root and bear fruit. Some are pruned; some are replaced while the tree keeps standing. A good gardener never wonders where a branch came from, or what happens when it is cut.

Graft gives you that certainty. Every plugin is grafted onto a scope. Every fiber has an owner. Every failure is traced back to its root.

Hot swap

Change the variety without felling the tree. Hot reload swaps only the plugins your change affects, in one transaction, and keeps the old code if the new code fails. See hot reload.

Clean teardown

Each plugin runs in its own Scope. When it goes away, its dependents stop first, then its finalizers run. A failure keeps its full Cause. See lifecycle.

Checked dependencies

Services are Effect Context keys, provided by layers or by other plugins. A plugin lists what it needs in inject, and the compiler checks the list. See services.

import { Context, Effect, Option, Schema, Stream } from "effect"
import { Fork, Graft, Plugin } from "@graftland/graft"
class Greeter extends Context.Service<
Greeter,
{ readonly greet: (name: string) => Effect.Effect<void> }
>()("app/Greeter") {}
// Publishes a Greeter while it is loaded.
const greeter = Plugin.make({
name: "app/greeter",
config: Schema.Struct({ greeting: Schema.String }),
apply: ({ greeting }) =>
Graft.provide(Greeter, {
greet: (name) => Effect.log(`${greeting}, ${name}!`),
}),
})
// Starts once a Greeter exists, and stops before it goes away.
const hello = Plugin.make({
name: "app/hello",
inject: [Greeter],
apply: () => Effect.flatMap(Greeter, (greeter) => greeter.greet("world")),
})
const states = await Effect.runPromise(
Effect.gen(function* () {
const helloFork = yield* Graft.plugin(hello)
yield* helloFork.await
const waiting = yield* Stream.runHead(helloFork.state)
yield* Graft.plugin(greeter, { greeting: "Hello" })
const started = yield* helloFork.state.pipe(
Stream.filter(
(state) => state._tag !== "Pending" && state._tag !== "Loading",
),
Stream.runHead,
)
return [waiting, started]
}).pipe(Effect.scoped, Effect.provide(Graft.layer)),
)
states // => [Option.some(Fork.ForkState.Pending({ missing: ["app/Greeter"] })), Option.some(Fork.ForkState.Active())]

Run plugins elsewhere

Move one plugin into a child process, a worker or another machine with Remote. The plugins that use it do not change.

Section Pages Read it to…
Start here Quickstart, Core concepts, Coming from cordis Learn the model and run something.
Build plugins Plugins, Services, Events Write the code.
Structure Lifecycle, Realms, Intercepts, Replace plugins Control how plugins relate and change.
Applications Config file, Command line, Hot reload, Use Graft in your program Run and operate an app.
Remote Overview, Host entries, Runners, Replacement and stopping Run a plugin in another process or on another machine.
Reference Fork handle, Loader API, HMR API, Errors Look up exact behavior.
Project Releases Check and publish Graft.
Package What it does
@graftland/graft A plugin framework built on Effect: plugins, forks, services, isolation, events and logs.
@graftland/loader Loads plugins from a list of entries or a YAML config file.
@graftland/hmr Reloads plugin modules on Node.js without restarting the process.
@graftland/cli The graft command: start, create, add and remove.
@graftland/create Makes a new Graft project: npm create @graftland and graft create.
@graftland/package-manager Installs dependencies with npm, pnpm or Yarn. A failed install is undone by snapshot rollback.
@graftland/plugin-remote Runs a plugin in a child process, a worker or on another machine. See Remote.
@graftland/plugin-otlp Exports the spans of a subtree of plugins to OpenTelemetry. See Export spans.
@graftland/plugin-http Shared HTTP server; routes and streams follow plugin lifetimes.
@graftland/plugin-http-api Live HttpApi groups, typed clients and OpenAPI documentation.
@graftland/plugin-mcp-server Serves the tools of plugins to MCP clients. See MCP server.

All packages share one version number and are released together. They need effect 4.1.x.

The garden is only on this page. In the rest of the documentation and in the code, a Scope is still a Scope. Graft draws its plugin model from cordis. Where cordis composes across space and time, Graft keeps to the garden.