Quickstart
You need Node.js 22.19 or later. This is the version that the CLI’s Effect Node
adapter needs. It also has module.registerHooks, which hot reload uses.
-
Create a project. Press Enter to accept each default; the command installs the dependencies:
Terminal window npm create @graftland@latest my-appcd my-appThe files to start with:
File What it is graft.ymlThe config file: which plugins to load, and how. src/hello.tsA plugin that logs a message from its config. src/greeter.tsA plugin that publishes the Greeterservice.src/ticker.tsA plugin that injects Greeterand greets on a timer.tsconfig.jsonType checking for npm run typecheck.graft createlists the questions, the other templates and the options that skip the questions. -
Start the application and watch for changes:
Terminal window npm run devhellologsHello from Graft!, andtickerlogsHello, Graft!every 3 seconds through theGreeterthatgreeterpublishes. The process keeps running until you press Ctrl+C. Then Graft unloads every plugin. -
Change the config while the app runs. Open
graft.ymland change the greeting:- id: greetername: ./src/greeter.tsconfig:greeting: HiWhen you save, Graft stops
tickerfirst, because it uses theGreeter, then restartsgreeterwith the new greeting and startstickeragain. You seeTicker stopped,Greeter stopped, thenHi, Graft!.hellois not touched. -
Change the code while the app runs. In
src/ticker.ts, change"3 seconds"to"1 second"and save. Graft imports the new module and restarts onlyticker. If the new code fails to import or to start, the old version keeps running, and Graft logs the error. -
Take a dependency away. Add
disabled: trueto thegreeterentry and save.tickerstops and waits asPendinguntil aGreeterexists again. Remove the line, and it greets again.
The example plugins
Section titled “The example plugins”import { Context, Effect, Schema } from "effect"
import { Graft, Plugin } from "@graftland/graft"
/** Greets by name. Other plugins list it in `inject` to use it. */export class Greeter extends Context.Service< Greeter, { readonly greet: (name: string) => Effect.Effect<void> }>()("app/Greeter") {}
// Publishes a Greeter while it is loaded. Plugins that use it stop first.export default Plugin.make({ name: "app/greeter", config: Schema.Struct({ greeting: Schema.String }), apply: ({ greeting }) => Effect.gen(function* () { yield* Effect.addFinalizer(() => Effect.logInfo("Greeter stopped")) yield* Graft.provide(Greeter, { greet: (name) => Effect.logInfo(`${greeting}, ${name}!`), }) }),})import { Effect, Schedule, Schema } from "effect"
import { Plugin } from "@graftland/graft"
import { Greeter } from "./greeter.ts"
// Starts once a Greeter exists and greets on a schedule. The task belongs to// the plugin, so it stops when the plugin stops or reloads.export default Plugin.make({ name: "app/ticker", config: Schema.Struct({ name: Schema.String }), inject: [Greeter], apply: ({ name }) => Effect.gen(function* () { const greeter = yield* Greeter yield* Plugin.fork( greeter.greet(name).pipe(Effect.repeat(Schedule.spaced("3 seconds"))), ) yield* Effect.addFinalizer(() => Effect.logInfo("Ticker stopped")) }),})inject: [Greeter] is checked by the compiler: remove it, and
npm run typecheck reports that apply uses a service it did not declare. Node
runs the TypeScript files as they are, removing the types, so there is no build
step. A plugin module exports its plugin as the default export, or as an export
named plugin. Importing the module must not start anything. Open connections,
timers and servers inside apply, so that Graft can release them. See
Plugins and Services.
Add a plugin from npm
Section titled “Add a plugin from npm”npx graft add graft-plugin-reviewgraft add installs packages with the project’s package manager. If the install
fails, it puts package.json and the lockfile back as they were. While it
installs or recovers, do not edit project files or run another installer. A
rollback overwrites changes. It does not merge them. See
installation ownership and recovery. Then add an
entry for the plugin to graft.yml. Its name can be the short name review:
Graft looks for review itself, then for @graftland/plugin-review and
graft-plugin-review, and fails if both of those are installed. See
Config file.
Next steps
Section titled “Next steps”| You want to… | Read |
|---|---|
| Understand the parts | Core concepts |
| Write your own plugin | Plugins |
| Share state between plugins | Services |
| Configure more entries | Config file |
| Run Graft from your own program | Use Graft in your program |
| Run a plugin in another process | Remote |
| Build with a coding agent | The graft skill: npx skills add graftland/graft |