Upgrading Beignet

Beignet packages version together. Upgrade every installed @beignet/* package, @beignet/cli, and create-beignet to the same release instead of mixing versions across the framework.

Upgrade workflow

  1. Read the target release notes and the migration section on this page.
  2. Update all Beignet packages to the same version and reinstall dependencies.
  3. Run bun beignet doctor --strict and bun beignet lint.
  4. Run the app's typecheck, test, and production build scripts.
  5. Exercise app-owned worker, cron, webhook, outbox, and task entrypoints that are not covered by the web build.

Generated files belong to the app after creation. Do not rerun create-beignet over an existing app or copy a fresh starter wholesale. Apply migration steps to the app's current structure and use doctor fixers only for findings explicitly marked safe.

Unreleased 1.0 stabilization changes

The current stabilization pass removes redundant pre-adoption APIs before the compatibility promise hardens.

CLI commands use one vocabulary

Provider setup now uses the singular provider command group and capability-first preset names. Replace beignet providers add redis-cache with beignet provider add cache-redis, and replace providers audit with provider audit. App creation now accepts the same catalog through --providers; the former --integrations flag is removed.

Workflow artifact names consistently use feature.name, including the value passed to make listener --event. The slash form is removed. make feature --with accepts singular addon names such as event, job, and upload.

Resource generation spells out --authorization and --tenant-scoped, and manual schedule runs use --run-id. Existing-app make, db, task, schedule, and outbox commands now accept --cwd <dir> consistently.

Runtime port wiring has an explicit name

The app's compile-time port shape remains in ports/index.ts. Rename the runtime wiring file and value so they cannot be confused with AppPorts:

mv infra/app-ports.ts infra/port-wiring.ts
// Before
import { appPorts } from "@/infra/app-ports";

// After
import { initialPorts } from "@/infra/port-wiring";

Pass initialPorts to the server's ports option and use it as the base in test context factories. Custom CLI config replaces paths.infrastructurePorts with paths.portWiring:

export default defineConfig({
  paths: {
    ports: "src/ports/index.ts",
    portWiring: "src/infra/port-wiring.ts",
  },
});

There is no compatibility alias for the former path, symbol, or config key.

Provider registration uses factories

Provider packages no longer export shared xProvider instances. Import and call the matching factory in server/providers.ts:

import { createRedisCacheProvider } from "@beignet/provider-cache-redis";

export const providers = [createRedisCacheProvider()] as const;

The same mechanical change applies across first-party providers, for example pinoLoggerProvider becomes createPinoLoggerProvider() and stripePaymentsProvider becomes createStripePaymentsProvider().

Provider factories expose stable named types

First-party provider factories now return named provider types such as RedisCacheProvider, PinoLoggerProvider, and DrizzlePostgresProvider<TSchema>. Their concrete Zod config schemas are private implementation details; exported config interfaces still describe the validated values when an app needs that shape. This does not change provider registration or InferProviderPorts results.

PostgresConfigSchema and MysqlConfigSchema are no longer exported from the Drizzle backend subpaths. Apps that imported either schema should own any app-level environment validation and use PostgresConfig or MysqlConfig for the corresponding validated shape.

Framework-neutral server imports come from core

Route declarations, route registries, hooks, and framework-neutral server types come from @beignet/core/server. Runtime adapter packages expose only their platform-specific APIs:

import {
  contractsFromRoutes,
  defineRouteGroup,
  defineRoutes,
} from "@beignet/core/server";
import { createNextServer, createNextServerLoader } from "@beignet/next";

Use @beignet/web for Web Fetch conversion and server adapters. In particular, replace toNextResponse(...) with toWebResponse(...) imported from @beignet/web.

Test helpers use one subpath

Move imports from @beignet/core/ports/testing to @beignet/core/testing. Fixtures, recording adapters, actors, policy helpers, assertions, factories, seeds, and provider test installation now share that single public boundary.

Schedule instrumentation uses shared provider targets

The schedule-specific ScheduleInstrumentation type was removed. createInlineScheduleRunner(...) now accepts a shared provider instrumentation target; existing record-only sinks remain structurally compatible. You can also pass the complete context ports object when running a schedule inside an application boundary:

const runner = createInlineScheduleRunner({
  ctx,
  instrumentation: ctx.ports,
});

The runner now follows the shared provider instrumentation contract, records events under the schedules watcher, and isolates instrumentation sink failures. onHookError now reports lifecycle hook failures only.

Route declarations use an app-bound builder

Create the route builders once and import them from feature route files:

// lib/routes.ts
import "@beignet/core/server-only";
import { createRoutes } from "@beignet/core/server";
import type { AppContext } from "@/app-context";

export const { defineRoute, defineRouteGroup } = createRoutes<AppContext>();

Replace defineRoute<AppContext>()(...) and defineRouteGroup<AppContext>()(...) calls with the corresponding app-bound builder. Add paths.routesBuilder when the file is not lib/routes.ts.

Listener identity is the first argument

Move listener names out of the options object and put the event in options:

defineListener("posts.enqueue-published-email", {
  event: PostPublished,
  async handle({ payload, ctx }) {},
});

Integration names match their role

The webhook packages are route-bound server integrations. They no longer carry provider audit metadata or appear in beignet provider audit.

Removed aliases

RemovedReplacement
contract.pathTemplatecontract.path
defineFlagRegistry(...)defineFlags(...)
ctx.ports.db.dbctx.ports.db.drizzle
defineRoute<AppContext>()(...)app-bound defineRoute(...) from lib/routes.ts
defineRouteGroup<AppContext>()(...)app-bound defineRouteGroup(...) from lib/routes.ts
createAuthBetterAuthProvider(...)createBetterAuthProvider(...)
@beignet/provider-webhooks-github@beignet/webhooks-github
@beignet/provider-webhooks-stripe@beignet/webhooks-stripe
toNextResponse(...)toWebResponse(...) from @beignet/web
createInMemoryEventBus(...)createMemoryEventBus(...)
createInMemoryEventBusProvider(...)createMemoryEventBusProvider(...)
InMemoryEventBus* typesMemoryEventBus* types
ScheduleInstrumentationProviderInstrumentationTarget from @beignet/core/providers

Getting help from the compiler

Most Beignet migrations are import or option-shape changes. TypeScript should identify every affected call site. When a removed symbol appears only in a generated registry or configuration string, beignet doctor --strict and beignet provider audit cover the registration and provider metadata that the compiler cannot inspect.

See Stability and releases for the compatibility policy.