Skip to content

Workery 1.2 → Cerces 2.0

Previously known as Workery (supporting exclusively Cloudflare Workers runtime), Cerces 2.0 is a complete overhaul of the framework, focusing on type safety, developer experience, and runtime flexibility. To upgrade your project from Workery 1.2 to Cerces 2.0, please follow the steps below.

Assuming the project continues to run on Cloudflare Workers, create a new Cerces project using the CLI:

sh
bun create cerces@latest
sh
pnpm create cerces@latest
sh
npm create cerces@latest

Select the cf-workers template, and follow the prompts to set up your new project. Once the new project is created, you can begin migrating your existing Workery code to Cerces.

Breaking Type Changes

Several type changes have been made to improve type safety and developer experience. Please review the following changes and update your code accordingly:

App and Router level parameters.

The App and Router classes have been reworked to support app-level and router-level parameters, as well as app-router parameter inheritance. The generics associated to these classes have been reworked. However, these generics are not designed to be manually specified in most cases, and the framework will infer the types automatically. The previously provided Env type parameter has been removed, please refer to the Env section below for migration instructions.

You can now define parameters at the App or Router level, which will be inherited by all routes within that scope. For example:

ts
const app = new App({
    parameters: {
        apiKey: Header(z.string()), // All routes will require this header
    },
    // ...
})

Routers now "bases" off of a parent App or Router, carrying over its parameters typings:

ts
import { Base, Router } from "cerces"
import type app from "./index"
// ^ IMPORTANT: import the type only to avoid circular dependency

const router = new Router({
    base: Base<typeof app>(),
    // ^ Carry over parameters types from the parent app or router
    parameters: {
        userId: Path(z.string().uuid()),
        // ^ All routes in this router will require this path param
    },
    // ...
})

This removes the need to repeatedly specify common parameters for each route, and ensures consistent parameter requirements across your application.

Removed Env in favor of runtime.d.ts

The Env type parameter has been removed from the App, Router, Dependency, and Middleware classes. Instead, the types of runtime arguments (e.g. env, ctx) are now defined using runtime.d.ts file in your project. This is an effort to support multiple runtimes, without coupling the types to the framework generics.

The runtime.d.ts file is automatically generated when you create a new Cerces project using the CLI and is already configured to use the appropriate types for your selected runtime (Cloudflare.Env).

To migrate your existing env type definitions, migrate your wrangler.toml to the new wrangler.jsonc format, refer to this documentation, then run the following command to generate the new worker-configuration.d.ts file:

sh
bun cf-typegen
sh
pnpm cf-typegen
sh
npm run cf-typegen

Parameter Flattening

A new improvement is added to parameter types and route handlers, the nested parameters defined in dependencies are now flattened into the route handler's parameters. This means that if a route depends on multiple dependencies, the parameters from all dependencies will be brought to the top level for the route handler. This reduces the need for nested destructuring in route handlers.

For example, consider the following dependencies and route:

ts
import { Dependency, Query, Depends } from "cerces"
import { z } from "zod"

const userPrefs = new Dependency({ 
    parameters: { 
        theme: Query(z.enum(["light", "dark"])),
        lang: Query(z.string())
    },
    handle: ({ theme, lang }) => {
        return { theme, lang }
    }
})

const requireAuth = new Dependency({ 
    parameters: { 
        authorization: Header(z.string()),
        userPrefs: Depends(userPrefs) // nested dependency
    },
    handle: async ({ authorization, userPrefs }) => { 
        const user = /* authenticate user */
        return { ...user, preferences: userPrefs }
    }
})

app.get("/profile", {
    parameters: {
        user: Depends(requireAuth), // only declare the top-level dependency
    },
    handle: ({ user, theme, lang, authorization }) => { 
        // `theme`, `lang`, `authorization` are available
        // without declaring them in route parameters!
        return {
            user: user,
            theme: theme,
            lang: lang
        }
    },
})

Restriction on duplicate parameter names

To avoid ambiguity and potential conflicts, Cerces enforces a restriction on duplicate parameter names across apps, routers, routes, and dependencies. If a parameter name is already defined on a higher level (e.g., app or router), it cannot be redefined in a nested scope (e.g., route or dependency), a new parameter name must be used.

For example, if an app defines a parameter named apiKey, a route within that app cannot define another parameter with the same name. This ensures that each parameter name is unique within its scope, preventing confusion and potential errors during request handling.

Breaking API Changes

A detailed list of breaking changes is provided below. Please review each change and update your code accordingly.

Renamed workery/* to cerces/*

All workery/* packages have been renamed to cerces/*. Update your import statements accordingly:

ts
import { App } from "workery"
import { App } from "cerces"

Removed workery/applications

The workery/applications module has been renamed to cerces/core. Update your import statements accordingly:

ts
import { App } from "workery/applications"
import { App } from "cerces/core"

Removed workery/dependencies

The workery/dependencies module has been dropped, and the Dependency class has been moved to cerces/core, along with its associated functionality, it is still available at index import cerces. Update your import statements accordingly:

ts
import { Dependency } from "workery/dependencies"
import { Dependency } from "cerces/core" // or
import { Dependency } from "cerces"

Removed workery/middleware

The workery/middleware module has been dropped, and the Middleware class has been moved to cerces/core, along with its associated functionality, it is still available at index import cerces. Update your import statements accordingly:

ts
import { Middleware } from "workery/middleware"
import { Middleware } from "cerces/core" // or
import { Middleware } from "cerces"

The built-in middleware cors and compress has been moved to its own module, the factory functions has also been renamed with create prefix to createCorsMiddleware and createCompressMiddleware for action clarity. Update your import statements accordingly:

ts
import { corsMiddleware } from "workery/middleware"
import { createCorsMiddleware } from "cerces/cors"
ts
import { compressMiddleware } from "workery/middleware"
import { createCompressMiddleware } from "cerces/compress"

Parameter declarators now importable on index

To reduce import verbosity, all members of the module workery/parameters (now cerces/parameters) are now re-exported on the index import cerces. You can now import them directly from cerces:

ts
import { Query, Header, Path, Cookie, Body } from "workery/parameters"
import { Query, Header, Path, Cookie, Body } from "cerces"

Moved JSON coercion helpers

The JSON coercion helpers jsonCoerce and isJsonCoercible have been moved to cerces/parameters. Update your import statements accordingly:

ts
import { jsonCoerce, isJsonCoercible } from "workery/helpers"
import { jsonCoerce, isJsonCoercible } from "cerces/parameters"

Response classes now importable on index

To reduce import verbosity, all response classes from the module workery/responses (now cerces/responses) are now re-exported on the index import cerces. You can now import them directly from cerces:

ts
import { JSONResponse, PlainTextResponse } from "workery/responses"
import { JSONResponse, PlainTextResponse } from "cerces"

Renamed exceptionHandler to errorHandler

Exception is a more specific term than error, and is less commonly used in JavaScript/TypeScript ecosystem. To improve clarity, the exceptionHandler option has been renamed to errorHandler. Update your code accordingly:

ts
const app = new App({
    // ...
    exceptionHandler: (error) => { /* ... */ } 
    errorHandler: (error) => { /* ... */ } 
})

Removed helper baseExceptionHandler

The default error handler is now defined inline on the App class init, to replace it, simply provide a new error handler function to the errorHandler option. The baseExceptionHandler helper has been removed.

Renamed members of workery/renderers

The workery/renderers module has been renamed to cerces/renderers, and the members have been renamed for clarity using the create prefix. Update your import statements accordingly:

ts
import { renderSwagger, renderRedoc } from "workery/renderers"
import { createSwaggerHTML, createRedocHTML } from "cerces/renderers"