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.
Before you start
Section titled “Before you start”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).
Step 1: configure the registry scope
Section titled “Step 1: configure the registry scope”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.
Step 2: pin versions in the catalog
Section titled “Step 2: pin versions in the catalog”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:
catalog: "@evolve-framework/core": 0.1.0 "@evolve-framework/schemas": 0.1.0 "@evolve-framework/commercetools": 0.1.0"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.
Step 3: remove the vendored packages
Section titled “Step 3: remove the vendored packages”Drop the vendor directory from your workspace configuration and delete it:
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:
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:
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
moduleoption replacesgraphql.typeDefsandgraphql.resolvers. The service extracts schema, resolvers, HTTP routes, and Fastify plugins from the module tree itself. createDomainServiceis a convenience factory that constructs the service and awaits itsinit().graphql.contextandgraphql.pluginsstill work as before.ProcessManageris 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
Moduleor extendAbstractModuleand add it to theCompositeModule. See module composition and module configuration.
A migration plan for teams
Section titled “A migration plan for teams”Treat the migration as a sequence of small, verifiable steps rather than one big change. A phasing that has worked well:
- 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. - Mechanical swap first. Registry scope,
catalog:pins, and the import renames are low-risk and cover most files. Land them as one reviewable change withpnpm checkgreen. - 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.
- Verify each step the same way:
pnpm install,pnpm codegen,pnpm check,pnpm test, then boot (pnpm dev) and hit the service’s/healthcheckand a representative GraphQL query.
API mapping
Section titled “API mapping”| 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 |
Using AI coding agents
Section titled “Using AI coding agents”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 avendor/packages/path, (2) every use ofAbstractGraphQLModule,GraphQLCompositeModule,new DomainService(, orProcessManager, (3) every service still using theinit.ts/context.tsanatomy. 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.tsexportingcreateModule()returning aCompositeModule,src/server.tsusingcreateDomainService({ name, module, graphql: { context }, http }), and a thinsrc/index.tsexporting and callingstartServer. Usebackend/services/quotes-commercetoolsas 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’sgetHttpConfig()). Then runpnpm checkand 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/healthcheckresponds, run one representative GraphQL query against it with the store-context headers set, and compare the service’sschema.generated.graphqlbefore 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.
Consuming snapshot releases
Section titled “Consuming snapshot releases”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:
catalog: "@evolve-framework/commercetools": 0.1.0 "@evolve-framework/commercetools": 0.0.0-7ca0d25c3-20260703132118Snapshots are immutable and safe to pin temporarily, but move back to a stable version once the change is released.
GraphQL codegen
Section titled “GraphQL codegen”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.
Gateway
Section titled “Gateway”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.

