Coming from cordis
Graft follows the design of cordis. An application is a tree of plugins that publish and use services. The tree can change while it runs. The difference is that Graft has no context object, effect system or lifecycle helpers of its own. It uses the ones in Effect, and adds only what Effect does not have.
Concepts
Section titled “Concepts”| cordis | Graft |
|---|---|
ctx, with services as properties |
The Effect environment. Read a service with yield* Key. |
ctx.plugin(plugin, config) |
Graft.plugin(plugin, input), which returns a fork. |
ctx.effect(), disposables |
Effect.acquireRelease, Effect.addFinalizer and the plugin’s Scope. |
inject |
inject, checked by the compiler, with optional entries. |
ctx.provide, ctx.set |
Graft.provide. |
| Fiber states | Fork states: Loading, Pending, Active, Failed, Disposed. |
fiber.update |
An update function returned from apply. |
fiber.restart |
fork.restart. |
ctx.isolate, labels |
isolate, for services and events alike. |
ctx.intercept |
intercept with Context.Reference. |
registry.replace |
Graft.replace. |
ctx.on, ctx.emit, serial, bail |
Events: on, emit, parallel, bail, waterfall. |
| Config with Standard Schema | Effect Schema, decoded inside the plugin’s activation. |
| Logger, timer and fs plugins | Effect’s Logger, Schedule, Clock and FileSystem. |
!!js tags with eval |
!env NAME only. Config files never run code. |
What Graft adds
Section titled “What Graft adds”- Checked dependencies. Reading a service that
injectdoes not list is a type error. - Optional dependencies.
Plugin.optional(Key)lets a plugin start without a service. The plugin restarts when the service comes or goes. - A fixed teardown order. First, the provider’s services are withdrawn. Then the plugins that use them stop. Only then are the provider’s resources released. See How withdrawal works.
- Interruptible startup. If a dependency goes away while a plugin is still starting, Graft interrupts the start. The plugin does not finish starting against a service that is gone.
- Typed failures with their causes. A failed plugin keeps its full
Cause. It can retry on aSchedule. - Config decoding with services. Decoding can be asynchronous, and it can use the plugin’s dependencies.
- Handlers that keep their context. An event handler runs with the services, realm and intercepts of its own plugin.
- Atomic replacement with rollback.
Graft.replacechanges every plugin in one transaction, and goes back to the old plugins if a new one fails to start. - Plugins in other processes. Remote runs one plugin in a child process, a worker or on another machine. The plugins that use its services do not change.
What Graft leaves out
Section titled “What Graft leaves out”- The context proxy, mixins and accessors. Effect’s environment and plain functions do the same job.
- A resource tree. cordis can list the effects that each plugin holds. Graft shows the fork tree. It leaves resource-level detail to Effect’s tracing.
Migrate with a coding agent
Section titled “Migrate with a coding agent”The repository ships
agent skills that walk an
agent through a port. migrate-cordis-plugin rewrites plugins, services and
their tests. migrate-cordis-app moves cordis.yml, the plugin list and the
host. The graft skill covers everything after the port: designing new plugins,
testing and running them. Install them all with:
npx skills add graftland/graft