Skip to content

Getting Started

Migration Guide

Upgrading between Surf versions — breaking changes, new features, and code examples

Migration Guide#

This guide covers breaking changes and upgrade paths between major Surf versions.


0.3.x / 0.4.x → 0.5.0#

Released: 2026-03-27

The 0.5.0 release unifies all package versions and adds Vue and Svelte support. If you're already on 0.3.x or 0.4.x, this is a smooth upgrade.

Unified Versioning

All packages are now versioned together at 0.5.0. Previously, @surfjs/react was on 0.4.x while other packages were on 0.3.x. Update all packages at once:

Bash
pnpm add @surfjs/core@latest @surfjs/client@latest @surfjs/react@latest

New Packages

SurfBadge Redesign

The SurfBadge visual style changed from rainbow/holographic to a consistent Surf blue. No API changes — purely cosmetic.

Error Codes

All @surfjs/client errors are now typed SurfClientError with a code property:

TypeScript
import { SurfClient, SurfClientError } from '@surfjs/client'
 
try {
await client.execute('search', { query: 'test' })
} catch (err) {
if (err instanceof SurfClientError) {
switch (err.code) {
case 'TIMEOUT': // handle timeout
case 'NETWORK_ERROR': // handle network failure
case 'NOT_CONNECTED': // handle disconnection
case 'RATE_LIMITED': // handle rate limiting (check err.retryAfter)
}
}
}

0.1.x → 0.2.x#

Released: 2026-03-21

The 0.2.x release introduced security hardening, basePath support, and the @surfjs/web browser runtime. This is the most significant upgrade path.

Breaking: createSurf() is Now Async

The most impactful change. createSurf() returns a Promise<SurfInstance> as of Phase 7 (framework adapters). All call sites must be awaited.

Before (0.1.x):

TypeScript
import { createSurf } from '@surfjs/core'
 
const surf = createSurf({
name: 'My API',
commands: { /* ... */ },
})
 
app.use(surf.middleware())

After (0.2.x+):

TypeScript
import { createSurf } from '@surfjs/core'
 
const surf = await createSurf({
name: 'My API',
commands: { /* ... */ },
})
 
app.use(surf.middleware())

If you're using Express at the top level, wrap in an async IIFE or use top-level await:

TypeScript
// Option 1: top-level await (ESM)
const surf = await createSurf({ /* ... */ })
const app = express()
app.use(surf.middleware())
app.listen(3000)
 
// Option 2: async IIFE
;(async () => {
const surf = await createSurf({ /* ... */ })
const app = express()
app.use(surf.middleware())
app.listen(3000)
})()

New: basePath Configuration

The client SDK now supports custom execute paths, useful for @surfjs/next or custom routing setups:

TypeScript
// Client
const client = await SurfClient.discover('https://myapi.com', {
basePath: '/api/surf/execute',
})

New: @surfjs/web Browser Runtime

The 0.2.x series introduced window.surf — a local command dispatcher for browser-based agents. This is now the foundation for all framework integrations:

TypeScript
import { initSurf, registerCommand } from '@surfjs/web'
 
initSurf({ endpoint: 'https://myapp.com' })
 
registerCommand('theme.toggle', {
mode: 'local',
run: () => {
document.body.classList.toggle('dark')
return { ok: true }
},
})

See @surfjs/web for the full API.

Security Hardening (0.2.x)

Several security fixes shipped across 0.2.10.2.3. No API changes, but worth noting:

  • Session tokens are now cryptographically random
  • Hidden commands are fully stripped from unauthenticated manifests
  • Error responses no longer leak stack traces in production
  • Auth comparison uses constant-time equality
  • Rate limiter state is cleaned up for expired sessions

New Framework Adapters

0.2.0 (Phase 7) added first-class adapters for:

  • FastifyfastifyPlugin(surf)
  • HonohonoApp(surf), honoMiddleware(surf)

These complement the existing Express surf.middleware() and the later Next.js adapter.

Scoped Auth

Phase 8 introduced requiredScopes on commands:

TypeScript
const surf = await createSurf({
authVerifier: async (token, command) => {
const user = await verifyToken(token)
if (!user) return { valid: false, reason: 'Invalid token' }
return { valid: true, claims: { userId: user.id }, scopes: user.scopes }
},
commands: {
'admin.stats': {
description: 'Server statistics',
auth: 'required',
requiredScopes: ['admin:read'],
run: async (_params, ctx) => getStats(),
},
},
})

See Scoped Auth for details.


Dependency Updates#

When upgrading, ensure your peer dependencies are compatible:

| Package | Minimum Node.js | TypeScript | Zod (if using @surfjs/zod) | |---------|-----------------|------------|---------------------------| | 0.5.x | 18+ | 5.0+ | 3.x | | 0.2.x–0.3.x | 18+ | 5.0+ | 3.x | | 0.1.x | 16+ | 4.9+ | 3.x |

TypeScript Configuration

For 0.2.x+ with top-level await, ensure your tsconfig.json targets ES2022+:

JSON
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true
}
}