Skip to main content
This guide covers upgrading between Runner versions, handling breaking changes, and migrating from other frameworks.

Current Version Support

VersionStatusSupport LevelEnd of Life
5.xCurrentFull support (new features, bug fixes, security)See releases
4.xMaintenanceCritical fixes and security onlySee releases
3.xEnd of lifeNo supportPast
Always review GitHub Releases before upgrading for detailed changelog and breaking changes.

Semantic Versioning

Runner follows strict semantic versioning:
  • Patch (5.0.1 → 5.0.2): Bug fixes, security patches, no breaking changes
  • Minor (5.0.0 → 5.1.0): New features, backward-compatible improvements
  • Major (5.0.0 → 6.0.0): Breaking changes, planned migrations

Upgrade Process

General Steps

1

Check current version

npm ls @bluelibs/runner
# or
cat package.json | grep @bluelibs/runner
2

Review release notes

Read the GitHub Releases for your target version. Look for:
  • Breaking changes
  • Deprecation warnings
  • Migration guides
  • New features
3

Update dependencies

npm install @bluelibs/runner@latest
# or specific version
npm install @bluelibs/[email protected]
4

Run type check

npx tsc --noEmit
TypeScript errors often reveal breaking changes that need attention.
5

Run tests

npm run test
# or full QA suite
npm run qa
6

Test in staging

Deploy to staging environment and run integration tests. Monitor for 24 hours before production deployment.
7

Production deployment

Use your standard deployment process (canary, blue-green, etc.) with rollback plan ready.

Deprecation Lifecycle

When a public API is deprecated, Runner follows this lifecycle:
StageTimelineWhat HappensCode Still Works?
AnnouncedMinor releaseRelease note + docs note with replacement path✅ Yes
WarnedNext minorDeprecated marker in docs/types + migration guide✅ Yes
RemovedNext majorRemoved with migration notes in release notes❌ No
Behavior changes without breaking types (e.g., default value changes) are documented in release notes and may occur in minor versions.

Version-Specific Migration

Migrating from 4.x to 5.x

This section will be updated with specific breaking changes when they occur. Check release notes for details.
Key changes in 5.x:
  1. Fluent builders become default The r.task(), r.resource() builder API is now the recommended approach. Classic task(), resource() functions still work but are considered legacy.
    // Old (still works)
    import { task, resource } from "@bluelibs/runner";
    const myTask = task({ id: "app.task", run: async (input) => {} });
    
    // New (recommended)
    import { r } from "@bluelibs/runner";
    const myTask = r.task("app.task").run(async (input) => {}).build();
    
  2. Platform detection improvements Edge worker detection is more robust. If you were using custom platform detection, review the new detectEnvironment() logic.
  3. Visibility boundaries (.exports()) Resources can now declare explicit exports. This is opt-in; existing code without .exports() continues to work with everything public.
    const billing = r
      .resource("billing")
      .register([internalTask, publicTask])
      .exports([publicTask]) // Only publicTask is visible outside
      .build();
    

Migrating from 3.x to 4.x

4.x is now in maintenance mode. If you’re on 3.x, we recommend upgrading directly to 5.x.

Breaking Changes by Category

Type System Changes

When type inference changes in breaking ways:
// Problem: Circular type inference fails
const resource1 = r.resource("a").dependencies({ resource2 }).build();
const resource2 = r.resource("b").dependencies({ resource1 }).build();
// TypeScript shows 'any' or fails to infer

// Solution: Explicit type annotation
import type { IResource } from "@bluelibs/runner";

const resource1 = r
  .resource("a")
  .dependencies({ resource2 })
  .build() as IResource<void, { value: string }>;
See Handling Circular Dependencies for full patterns.

Runtime Behavior Changes

When default behaviors change:
ChangeVersionMigration
Event cycle detection enabled by default5.0.0Disable with run(app, { runtimeEventCycleDetection: false }) if needed
Error boundary enabled by default5.0.0Disable with run(app, { errorBoundary: false }) if needed

API Removals

When APIs are removed in major versions:
// Removed in 5.0.0: Old task definition syntax
// This no longer works:
const oldTask = task({ 
  id: "app.task",
  runner: async (input) => {} // 'runner' renamed to 'run'
});

// New syntax:
const newTask = r
  .task("app.task")
  .run(async (input) => {})
  .build();

Migrating from Other Frameworks

From NestJS

NestJS services become Runner resources:
// NestJS
@Injectable()
export class DatabaseService {
  private client: Client;
  
  async onModuleInit() {
    this.client = await connect();
  }
  
  async onModuleDestroy() {
    await this.client.close();
  }
}

// Runner
const database = r
  .resource("app.db")
  .init(async () => {
    const client = await connect();
    return client;
  })
  .dispose(async (client) => {
    await client.close();
  })
  .build();
NestJS controllers become Runner tasks + Express/Fastify:
// NestJS
@Controller('users')
export class UsersController {
  constructor(private db: DatabaseService) {}
  
  @Post()
  async create(@Body() dto: CreateUserDto) {
    return this.db.users.insert(dto);
  }
}

// Runner
const createUser = r
  .task("users.create")
  .dependencies({ db: database })
  .inputSchema(CreateUserSchema)
  .run(async (input, { db }) => {
    return db.users.insert(input);
  })
  .build();

const server = r
  .resource("app.server")
  .dependencies({ createUser })
  .init(async (_, { createUser }) => {
    const app = express();
    app.post('/users', async (req, res) => {
      const user = await createUser.run(req.body, {});
      res.json(user);
    });
    return app.listen(3000);
  })
  .build();
NestJS guards and interceptors become Runner middleware:
// NestJS
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
async someMethod() {}

// Runner
const someTask = r
  .task("app.task")
  .middleware([authMiddleware, loggingMiddleware])
  .run(async (input) => {})
  .build();
NestJS event emitter becomes Runner events:
// NestJS
@Injectable()
export class OrdersService {
  constructor(private events: EventEmitter2) {}
  
  async create(order) {
    const created = await this.db.orders.insert(order);
    this.events.emit('order.created', created);
  }
}

@OnEvent('order.created')
handleOrderCreated(order) {}

// Runner
const orderCreated = r
  .event("orders.created")
  .payloadSchema<Order>({ parse: (v) => v })
  .build();

const createOrder = r
  .task("orders.create")
  .dependencies({ db, orderCreated })
  .run(async (input, { db, orderCreated }) => {
    const order = await db.orders.insert(input);
    await orderCreated(order);
    return order;
  })
  .build();

const handleOrderCreated = r
  .hook("orders.onCreated")
  .on(orderCreated)
  .run(async (event) => {
    // Handle event
  })
  .build();

From Express/Plain Node.js

// Before: Plain Express
const app = express();
const db = await connectDB();

app.post('/users', async (req, res) => {
  const user = await db.users.insert(req.body);
  res.json(user);
});

app.listen(3000);

// After: Runner + Express
const db = r
  .resource("app.db")
  .init(async () => await connectDB())
  .build();

const createUser = r
  .task("users.create")
  .dependencies({ db })
  .run(async (input, { db }) => {
    return db.users.insert(input);
  })
  .build();

const server = r
  .resource("app.server")
  .dependencies({ db, createUser })
  .init(async (_, { db, createUser }) => {
    const app = express();
    app.post('/users', async (req, res) => {
      const user = await createUser.run(req.body, {});
      res.json(user);
    });
    return app.listen(3000);
  })
  .build();

const app = r
  .resource("app")
  .register([db, createUser, server])
  .build();

await run(app);
// Before: Shared singleton
let dbConnection;
export async function getDB() {
  if (!dbConnection) {
    dbConnection = await connect();
  }
  return dbConnection;
}

// After: Resource
const db = r
  .resource("app.db")
  .init(async () => await connect())
  .dispose(async (conn) => await conn.close())
  .build();

Common Migration Issues

Problem:
const myTask = r.task("app.task").run(async () => {});
// TypeError: myTask is not a function
Solution:
const myTask = r.task("app.task").run(async () => {}).build();
Problem:
const task = r.task("app.task").dependencies({ db }).build();
const app = r.resource("app").register([task]).build();
// Runtime error: Resource "app.db" not found
Solution:
const app = r.resource("app").register([db, task]).build();
Problem:
// TypeScript complains about task input types after upgrade
Solution:
  • Run npm run test to identify failing tests
  • Check release notes for type system changes
  • Use explicit type annotations if needed
  • Update inputSchema and resultSchema if validation changed
Problem:
// TypeScript shows 'any' or circular reference errors
Solution: See Handling Circular Dependencies for patterns using explicit types and dependency injection.

Testing After Migration

1

Unit tests

Ensure all unit tests pass:
npm run test
2

Integration tests

Run full integration suite:
npm run test:integration
3

Type checking

Verify type safety:
npx tsc --noEmit
4

Lint

Check for deprecation warnings:
npm run lint
5

Manual testing

Test critical flows manually in staging:
  • User registration/login
  • Payment processing
  • Core business operations
6

Performance testing

Run load tests to ensure no performance regressions:
npm run bench

Rollback Plan

If the migration fails in production:
1

Immediate rollback

# Revert to previous version
npm install @bluelibs/[email protected]

# Or use git
git revert HEAD
git push
2

Redeploy

Use your deployment process to redeploy the previous version:
kubectl rollout undo deployment/my-app
# or
pm2 reload my-app
3

Investigate

  • Review logs for errors
  • Check metrics for anomalies
  • Identify root cause
  • Fix in staging before retrying

Support During Migration

Need help migrating? We’re here to help:

GitHub Issues

Ask questions and report migration issues

Enterprise Support

Get dedicated migration assistance

Release Notes

Read detailed changelogs

Examples

Reference working examples

Additional Resources

Build docs developers (and LLMs) love