Skip to content

New: AI agent integration via Model Context Protocol (MCP).Learn more

Migration guide

The framework packages are no longer distributed as source code in a vendor/packages/ directory. They are published as @evolve-framework/* packages to the Evolve registry and consumed like any other dependency. This guide walks through migrating an implementation from the vendored, workspace:*-based setup to registry consumption, and through the module API changes that landed along the way.

You need read access to the Evolve registry (npm.registry.evolve-platform.com). Locally this means an NPM_TOKEN; in CI, authentication happens via OIDC (evolve-platform/registry-login).

Point the @evolve-framework scope at the Evolve registry in your project’s .npmrc:

@evolve-framework:registry=https://npm.registry.evolve-platform.com
//npm.registry.evolve-platform.com/:_authToken=${NPM_TOKEN}

Every other scope keeps resolving from the public npm registry.

Framework versions are pinned once, in the catalog: section of pnpm-workspace.yaml. Individual package.json files reference the catalog instead of a version or a workspace link:

pnpm-workspace.yaml
catalog:
"@evolve-framework/core": 0.1.0
"@evolve-framework/schemas": 0.1.0
"@evolve-framework/commercetools": 0.1.0
backend/services/account-commercetools/package.json
"dependencies": {
"@evolve-framework/core": "workspace:*",
"@evolve-framework/commercetools": "workspace:*",
"@evolve-framework/core": "catalog:",
"@evolve-framework/commercetools": "catalog:",
}

Upgrading the framework later is a one-line change: bump the version in the catalog and run pnpm install. Every package that references catalog: picks it up automatically.

Drop the vendor directory from your workspace configuration and delete it:

pnpm-workspace.yaml
packages:
- "frontend/*"
- "packages/*"
- "vendor/packages/*"

Run pnpm install afterwards. The lockfile should now resolve @evolve-framework/* from the registry instead of linking local directories, and your build no longer needs to compile the framework before your services.

Step 4: adopt the module-based service anatomy

Section titled “Step 4: adopt the module-based service anatomy”

The vendored era’s GraphQL-centric API — AbstractGraphQLModule, GraphQLCompositeModule, and passing typeDefs/resolvers into new DomainService() — was replaced by a general module system: Module / AbstractModule / CompositeModule. A module carries its own schema, resolvers, and HTTP routes; the service just composes modules and no longer manually wires GraphQL artifacts or process lifecycle.

Before:

src/server.ts (old)
import { DomainService, ProcessManager } from "@evolve-framework/core";
import {
CustomerGraphQLModule,
GraphQLCompositeModule,
} from "@evolve-framework/commercetools";
const module = new GraphQLCompositeModule([new CustomerGraphQLModule()]);
const app = new DomainService({
name: config.COMPONENT_NAME,
graphql: {
typeDefs: module.getTypedefs(),
resolvers: module.getResolvers(),
context: newContext,
},
http: {
address: { host: config.HTTP_HOST, port: config.HTTP_PORT },
},
});
const pm = new ProcessManager({
start: () => app.start(),
stop: () => app.stop(),
});
await pm.start();

After:

src/server.ts (new)
import { CompositeModule, createDomainService } from "@evolve-framework/core";
import {
AuthModule,
CustomerModule,
} from "@evolve-framework/commercetools";
const service = await createDomainService({
name: config.COMPONENT_NAME,
module: new CompositeModule([
new AuthModule({ config, clientFactory }),
new CustomerModule({ config }),
]),
graphql: {
context: createContext,
},
http: {
address: { host: config.HTTP_HOST, port: config.HTTP_PORT },
},
});
await service.start();

The key differences:

  • One module option replaces graphql.typeDefs and graphql.resolvers. The service extracts schema, resolvers, HTTP routes, and Fastify plugins from the module tree itself.
  • createDomainService is a convenience factory that constructs the service and awaits its init(). graphql.context and graphql.plugins still work as before.
  • ProcessManager is no longer public API. service.start() handles SIGTERM/SIGINT, graceful shutdown, and module cleanup internally.
  • Project-specific code (custom REST endpoints, event handlers) becomes a module too: implement Module or extend AbstractModule and add it to the CompositeModule. See module composition and module configuration.

Treat the migration as a sequence of small, verifiable steps rather than one big change. A phasing that has worked well:

  1. Inventory. Find everything that touches the old world: imports of @evolve-packages/*, vendor/packages/ paths, and uses of the retired API (see the mapping table below). This list is your work queue and your progress tracker.
  2. Mechanical swap first. Registry scope, catalog: pins, and the import renames are low-risk and cover most files. Land them as one reviewable change with pnpm check green.
  3. Then one service at a time. Adopt the module anatomy per service — each is a small, independently testable diff. Start with your simplest service to establish the pattern; save services with custom REST routes or event handlers for last.
  4. Verify each step the same way: pnpm install, pnpm codegen, pnpm check, pnpm test, then boot (pnpm dev) and hit the service’s /healthcheck and a representative GraphQL query.
Old (vendored era) New
AbstractGraphQLModule AbstractModule (@evolve-framework/core)
GraphQLCompositeModule CompositeModule (@evolve-framework/core)
module.getTypedefs() / module.getResolvers() module.getGraphQLConfig() (consumed by the service internally)
new DomainService({ graphql: { typeDefs, resolvers } }) createDomainService({ name, module, graphql: { context }, http })
ProcessManager gone — service.start() handles lifecycle
init.ts + context.ts service anatomy config.ts + module.ts + server.ts + index.ts
@evolve-packages/observability @evolve-framework/core/observability
@evolve-packages/cache @evolve-framework/core/cache
@evolve-packages/messaging @evolve-framework/core/messaging + @evolve-framework/cloud-adapter-{aws,azure,gcp}
@evolve-packages/mcp-core @evolve-framework/mcp-core
vendor/packages/commercetools/src/factories @evolve-framework/commercetools/factories

The migration is well-suited to AI coding agents: the transformation is pattern-based, the verification is scriptable, and this page plus the architecture page give an agent the ground truth it needs. Some example prompts, written to be pasted as-is (adjust paths to your project):

Inventory:

Scan this repository for consumption of the old vendored Evolve framework. Produce a table of: (1) every import from @evolve-packages/* or a vendor/packages/ path, (2) every use of AbstractGraphQLModule, GraphQLCompositeModule, new DomainService(, or ProcessManager, (3) every service still using the init.ts/context.ts anatomy. Group by package/service and estimate which services are simplest to migrate first. Do not change anything yet.

Migrate one service:

Migrate backend/services/<name> to the current Evolve service anatomy: src/config.ts (enviconf class), src/module.ts exporting createModule() returning a CompositeModule, src/server.ts using createDomainService({ name, module, graphql: { context }, http }), and a thin src/index.ts exporting and calling startServer. Use backend/services/quotes-commercetools as the reference. Replace retired imports per the mapping table in docs/architecture/core/framework/migration-guide.md. Preserve all existing behavior, including custom REST routes (move them into a module’s getHttpConfig()). Then run pnpm check and the service’s tests, and fix what fails. Show me the diff before committing.

Verify:

The migration of <name> is complete. Verify it end to end: boot the service, confirm /healthcheck responds, run one representative GraphQL query against it with the store-context headers set, and compare the service’s schema.generated.graphql before and after the migration — the schema must be unchanged. Report findings; do not fix anything without showing me first.

Two practical tips when delegating this: give the agent one service per task (reviewable diffs beat big-bang branches), and insist on the schema-diff check — an unchanged subgraph schema is the strongest signal that a migration preserved behavior.

Alongside stable releases, the registry receives snapshot versions of all packages on a continuous basis. Snapshots are versioned 0.0.0-<short-sha>-<timestamp> and published under the snapshot dist-tag.

To try an unreleased fix, pin the exact snapshot version in your catalog:

pnpm-workspace.yaml
catalog:
"@evolve-framework/commercetools": 0.1.0
"@evolve-framework/commercetools": 0.0.0-7ca0d25c3-20260703132118

Snapshots are immutable and safe to pin temporarily, but move back to a stable version once the change is released.

Nothing changes here compared to the vendored setup. Each service keeps its own codegen.ts and .graphql schema files; the framework’s types ship pre-generated inside the packages. Your codegen only needs to cover project-specific schema extensions.

The gateway is not affected by this migration. It is a standalone Rust Hive Router binary and does not consume the @evolve-framework/* npm packages — see the roadmap for how it shipped.