Beignet is experimental alpha software. The 0.0.x package line is for early
evaluation, and APIs may change between releases while the framework settles.
Command-line tools for creating and maintaining Beignet apps.
Beignet requires Node.js 22.12 or newer. Bun is optional; npm, pnpm, Yarn, and Bun can run CLI commands.
The package ships a single beignet bin. Generated apps install
@beignet/cli as a dev dependency and add a beignet package script, so the
day-to-day form inside an app is:
bun beignet check
With npm use npm run beignet -- check, with pnpm pnpm beignet check, and
with yarn yarn beignet check.
For one-off runs outside an app, invoke the scoped package directly:
bunx @beignet/cli routes
Never run the unscoped beignet name through bunx or npx; that npm name
belongs to an unrelated package.
App scaffolding lives in the create-beignet package:
npm create beignet@latest my-app
# or
bun create beignet my-app
Both delegate to beignet create, so bunx @beignet/cli create my-app is
equivalent.
Running create in an interactive terminal without selection flags opens a
prompt-based setup that asks for the project directory, whether to scaffold
an API-only app, which database to use, and which providers to
add. Passing any selection flag (--api, --db, --providers, or
--package-manager) skips the prompts, and --yes forces the
non-interactive defaults (the full-stack SQLite starter with bun) even on a
terminal. Non-interactive runs in scripts and CI behave exactly as before.
By default the CLI creates an app using Next.js 16.3.3 or newer within v16, with:
--db postgres and --db mysql for the other backendsAGENTS.md and CLAUDE.md agent guides, plus a .mcp.json that registers
the beignet mcp server for MCP clients such as Claude CodeWorkflow-tier pieces such as events, jobs, listeners, schedules, seeds,
tasks, uploads, and the outbox are added by beignet make ... generators when
you opt into those capabilities.
Pass --api to scaffold an API-only app. It keeps the same contracts, auth,
persistence, and devtools, and swaps the UI shell for a minimal API landing
page and a reduced typed client.
The starter ships local production-shaped defaults: Better Auth, Drizzle persistence, pino logging, UOW, durable idempotency, audit logging, no-op error reporting, and devtools. Add workflow artifacts and external service providers only when you want those capabilities:
bunx @beignet/cli create my-app --providers jobs-inngest,mail-resend
Available starter-native providers:
jobs-inngest - Inngest-backed background jobs via @beignet/provider-jobs-inngestmail-resend - Resend-backed mail via @beignet/provider-mail-resendrate-limit-upstash - Upstash-backed rate limiting via
@beignet/provider-rate-limit-upstash--providers accepts every beignet provider add preset
(flags-openfeature, jobs-bullmq, jobs-inngest, mail-resend,
mail-smtp, payments-stripe, cache-redis, event-bus-redis,
locks-redis, storage-s3, search-meilisearch, error-reporting-sentry, and
storage-vercel-blob). Presets are
applied to the fresh scaffold with the same machinery as
beignet provider add; starter-native presets are rendered directly, and
selections that fill the same app port (for example mail-resend plus
mail-smtp) fail before any files are written.
Each selected provider adds its package dependencies, wires the
provider in server/providers.ts, extends .env.example, and writes setup
notes to docs/providers.md.
Drizzle persistence ships in every starter; pick the backend with --db
(default sqlite):
bunx @beignet/cli create my-app --db postgres
--db accepts sqlite (libSQL, local files plus hosted libSQL/Turso),
postgres (node-postgres, requires a running Postgres 14+ server), and
mysql (mysql2, requires MySQL 8.0+). Every backend scaffolds the same
structure: infra/db/schema/, infra/db/repositories.ts,
infra/db/test-database.ts, SQL-backed idempotency and outbox tables, a
Drizzle todo repository adapter, drizzle.config.ts with the matching
dialect, checked-in starter migrations, database scripts, and env examples
(SQLITE_*, POSTGRES_DB_URL, or MYSQL_DB_URL), so the generated todo
resource uses durable persistence instead of an in-memory repository.
Postgres starters run repository tests against in-process PGlite with no
server; MySQL persistence tests need a real server via MYSQL_TEST_URL.
Later make feature or make resource calls detect the app's backend from
infra/db/repositories.ts and generate a dialect-correct Drizzle
table/repository plus an in-memory test fake.
Better Auth also ships in every starter. The scaffold adds the Better Auth
Next.js route, Beignet auth provider wiring, typed UNAUTHORIZED and
FORBIDDEN errors, a request-bound authorization gate, and
requireUser(ctx). Use hooks for HTTP boundary authentication and use cases
or policies for business authorization.
Preview the planned files without writing anything:
bunx @beignet/cli create my-app --dry-run
bunx @beignet/cli create my-app --dry-run --json
When --providers includes post-create presets, the preview composes their
dependency, wiring, environment, and setup-note changes into the final file
list without creating the target directory.
The CLI writes files only. After creation:
cd my-app
bun install
cp .env.example .env.local
bun beignet db migrate
bun beignet db status
bun run dev
Open http://localhost:3000 for the starter UI. The generated homepage links
to the todo feature, health check, OpenAPI document, devtools, and Beignet
docs.
Generated apps also trust Beignet's package-shipped TanStack Intent skills via
package.json#intent.skills. Run bunx @tanstack/intent@latest install (or
the matching runner for your package manager) to add Intent's managed guidance
block, then load matching skills such as @beignet/core#app-architecture,
@beignet/next#routes-server, @beignet/web#fetch-server,
@beignet/devtools#runtime-safety, @beignet/provider-db-drizzle#database-provider,
@beignet/provider-auth-better-auth#auth-provider,
@beignet/react-query#client, @beignet/react-hook-form#forms, and
@beignet/cli#app-structure before substantial Beignet changes. The CLI skill
covers generators, full-slice recipes, db schema sync, lint, doctor, and
MCP tooling.
Inspect the app, then generate the next feature:
# in another terminal, from my-app
bun beignet routes
bun beignet map
bun run lint
bun beignet lint
bun beignet doctor
bun beignet make feature projects
bun beignet db generate
bun beignet db migrate
bun beignet db status
bun run test
bun run lint
bun run typecheck
bun beignet lint
bun beignet doctor
beignet create [directory] [options]
beignet task run <name> [options] [--cwd <dir>]
beignet outbox drain [options] [--cwd <dir>]
beignet outbox list [options] [--cwd <dir>]
beignet outbox show <id> [options] [--cwd <dir>]
beignet outbox requeue <id> [options] [--cwd <dir>]
beignet outbox purge [options] [--cwd <dir>]
beignet outbox prune [options] [--cwd <dir>]
beignet schedule run <name> [options] [--cwd <dir>]
beignet make task <feature.name> [options] [--cwd <dir>]
beignet make contract <name> [options] [--cwd <dir>]
beignet make event <feature.name> [options] [--cwd <dir>]
beignet make factory <feature.name> [options] [--cwd <dir>]
beignet make feature <name> [options] [--cwd <dir>]
beignet make job <feature.name> [options] [--cwd <dir>]
beignet make notification <feature.name> [options] [--cwd <dir>]
beignet make inbox [options] [--cwd <dir>]
beignet make outbox [options] [--cwd <dir>]
beignet make payments [options] [--cwd <dir>]
beignet make tenancy [options] [--cwd <dir>]
beignet make listener <feature.name> --event <feature.event> [options] [--cwd <dir>]
beignet make schedule <feature.name> [options] [--cwd <dir>]
beignet make upload <feature.name> [--ui] [options] [--cwd <dir>]
beignet make port <name> [options] [--cwd <dir>]
beignet make policy <name> [options] [--cwd <dir>]
beignet make adapter <name> [options] [--cwd <dir>]
beignet make resource <name> [options] [--cwd <dir>]
beignet make seed <feature.name> [options] [--cwd <dir>]
beignet make test <feature.action> [options] [--cwd <dir>]
beignet make use-case <feature.action> [options] [--cwd <dir>]
beignet db generate [--dry-run] [--json] [--cwd <dir>]
beignet db migrate [--dry-run] [--json] [--cwd <dir>]
beignet db status [--json] [--cwd <dir>]
beignet db seed [--dry-run] [--json] [--cwd <dir>]
beignet db reset [--dry-run] [--json] [--cwd <dir>]
beignet db schema sync [--dialect sqlite|postgres|mysql] [--tables audit,idempotency,outbox] [--output <path>] [--dry-run] [--json] [--cwd <dir>]
beignet routes [--json] [--cwd <dir>]
beignet map [--json] [--feature <name>] [--kind <kinds>] [--changed] [--base <ref>] [--cwd <dir>]
beignet explain <kind> <target> [--json] [--cwd <dir>]
beignet check [--json] [--fix] [--preflight] [--preflight-connect] [--connect-timeout-ms <ms>] [--cwd <dir>]
beignet preflight [--connect] [--connect-timeout-ms <ms>] [--json] [--cwd <dir>]
beignet lint [--json] [--cwd <dir>] [--format human|json|github]
beignet doctor [--json] [--strict] [--fix] [--dry-run] [--plan <id>] [--only <operation-ids>] [--cwd <dir>] [--format human|json|github]
beignet provider add <preset> [--dry-run] [--json] [--cwd <dir>]
beignet provider audit [--json] [--cwd <dir>]
beignet mcp
beignet completion install [--shell bash|zsh] [--json]
beignet completion uninstall [--shell bash|zsh] [--json]
beignet --version
Options:
--template next Template to use. Currently only `next`.
--api Scaffold an API-only app without the UI shell.
--db postgres Database backend: sqlite (default), postgres, or mysql.
--package-manager bun Package manager shown in next steps.
--providers jobs-inngest,mail-resend Add provider presets. Accepts one value or a comma-separated list.
--yes Skip interactive create prompts and use the defaults.
--force Write into a non-empty directory.
--dry-run Preview create/make writes without changing files.
--json Print machine-readable output.
--cwd path/to/app Run an existing-app command against another app directory.
--preflight-connect Add connected migration and dependency checks.
--connect-timeout-ms 5000 Override the connected preflight per-check timeout.
--format github Output format for lint and doctor: human, json, or github.
--input '{"dryRun":true}' JSON input for `beignet task run`.
--tenant acme Tenant id or slug passed to the app's createTaskContext by `beignet task run`, separate from task input.
--payload '{"date":"..."}' JSON payload for `beignet schedule run`.
--module server/tasks.ts Override the task, outbox, or schedule registry path.
--batch-size 100 Maximum outbox messages to claim in one drain pass.
--run-id run_123 Provider or app schedule run ID for schedule runs.
--scheduled-at 2026-01-01 Provider scheduled timestamp for schedule runs.
--triggered-at 2026-01-01 Trigger timestamp for schedule runs.
--attempt 1 One-based provider attempt number for schedule runs.
--source manual Provider or app source label for schedule runs.
--with policy,event Add optional artifacts to `make feature`; supports policy, factory, seed, task, event, listener, job, notification, schedule, ui, and upload.
--recipe full-slice Add the canonical full-slice feature recipe: policy, client UI, workflow artifacts, outbox-ready events/jobs, and listener registration.
make tenancy Add `features/workspaces` with membership-aware tenant resolution, workspace/member/invite routes, a demo seed, and workspace settings UI on shell apps.
make payments Add `features/billing` with free/pro plans, a `createPaymentWebhookRoute(...)` route, billing persistence, entitlement gating, a demo seed, and a plan settings UI on shell apps.
make inbox Add `features/inbox` with a per-user notification inbox, an in-app notification channel, cursor-paginated routes, and an inbox page on shell apps.
--authorization Add authorization metadata, policy wiring, policy tests, and use-case gate checks to `make resource`.
--tenant-scoped Add tenant-scoped schemas, `TenantScope` repository boundaries, and use-case tenant checks to `make resource`.
--events Add created, updated, and deleted domain events to `make resource`.
--soft-delete Archive resource rows with deletedAt instead of hard-deleting them.
--event posts.published Event for `make listener`.
--cron "0 9 * * *" Cron expression for `make schedule`.
--timezone America/Chicago Timezone for `make schedule`.
--route Add a Next.js cron route for `make schedule`.
--ui Add a typed upload client, React uploader, and component test to `make upload`.
provider add cache-redis Add dependencies, provider wiring, env examples, and setup notes for a supported provider preset.
--strict Include CI-oriented doctor warnings and fail on warnings.
--fix Apply low-risk doctor fixes before reporting.
--dry-run Preview an exact doctor fix plan without writing files.
--plan id Apply only when the current repair plan matches this id.
--only routes.register-missing Apply selected comma-separated repair operations. Requires --plan.
--shell zsh Shell for `completion install`/`uninstall`. Defaults to $SHELL.
-h, --help Show help.
-v, --version Print the CLI version.
Every command uses the same exit-code matrix:
| Code | Meaning |
|---|---|
| 0 | Success. |
| 1 | Findings: lint diagnostics, doctor errors, or strict-mode doctor warnings. |
| 2 | Usage or internal errors: unknown commands, bad flags, missing arguments, runtime failures, or running beignet with no arguments. |
--format never changes the exit code; lint --format github still exits 1
when it finds diagnostics.
lint and doctor support three output formats. --json stays the
canonical machine-readable flag; --format adds GitHub Actions annotations:
beignet lint --format github
beignet doctor --strict --format github
The github format emits one ::error, ::warning, or ::notice workflow
command per diagnostic, with file, line, and col properties when known.
When GITHUB_ACTIONS=true is set and no format is passed, lint and doctor
default to the github format; otherwise they default to human output.
Combining --json with a conflicting --format is an error. Human output
groups doctor diagnostics by severity, colors severity labels on TTYs, and
respects NO_COLOR.
All --json results begin with schemaVersion: 1 so other tools can detect
output-shape changes.
Install tab completions for the beignet bin into your shell rc file:
beignet completion install
beignet completion install --shell zsh
beignet completion uninstall
install writes a managed block to ~/.bashrc or ~/.zshrc (detected from
$SHELL when --shell is omitted) and uninstall removes it. Restart your
shell or source the rc file to activate completions. Zsh completions require
compinit to be loaded, which most zsh setups already do.
Completions cover commands, subcommands, flags, and enum flag values such as
--template and --providers. The shell scripts call the
beignet completion propose helper, which prints one proposal per line for a
partial command line; it exists for the shell integration and is safe to
ignore otherwise. Completions complete whatever beignet resolves to on your
PATH, such as the app-local bin installed by generated apps.
Beignet CLI commands are convention-aware. routes, map, lint, doctor, and
generators work best in the Next.js layout created by beignet create:
features/
app/api/
server/context.ts
server/index.ts
server/routes.ts
server/context.ts declares the app's context blueprint with
defineServerContext(...); server/index.ts passes it to the server and
generated route tests pass the same value to createTestApp(...).
Route inspection supports contract-group definitions and direct
defineContract({ method, path }) exports. It reads exported declarations and
fluent contract calls from TypeScript syntax, so nested objects, comments, and
multiline expressions do not truncate a contract before its method or path.
Local fluent-builder aliases and named local exports are followed. Factories
must be value imports from @beignet/core/contracts; named aliases and
namespace imports work, while same-named methods on unrelated objects are
ignored.
The standard app layout includes the files feature generators expect:
app-context.ts
infra/port-wiring.ts
ports/index.ts
lib/routes.ts
lib/use-case.ts
Workflow builders are created on demand. New apps do not ship lib/jobs.ts,
lib/listeners.ts, lib/notifications.ts, lib/schedules.ts, or
lib/tasks.ts; each beignet make ... workflow generator creates only the
app-bound builder it needs.
The CLI uses this shape to inspect routes, wire generated features, and report
drift safely. beignet doctor treats absent workflow-tier files as fine and
reports misplaced or stale workflow artifacts when they drift from the app
layout.
Use beignet.config.ts, beignet.config.js,
beignet.config.mjs, or beignet.config.json to select the server adapter
profile or when your app keeps the same architecture under different paths.
"next" is the default; use "web" when the server exposes
createFetchServer(...) from @beignet/web:
import { defineConfig } from "@beignet/cli/config";
export default defineConfig({
framework: "web",
});
The web profile derives route coverage from canonical feature route groups in
the central defineRoutes(...) registry passed to createFetchServer(...), so
routes and doctor do not require Next.js app/api/ files. Vite and Bun do
not need separate values: they are client tooling and a Fetch host,
respectively.
Path overrides remain available for either profile:
// beignet.config.ts
import { defineConfig } from "@beignet/cli/config";
export default defineConfig({
framework: "next",
paths: {
appContext: "src/app-context.ts",
contracts: "src/features",
features: "src/features",
ports: "src/ports/index.ts",
portWiring: "src/infra/port-wiring.ts",
routesBuilder: "src/lib/routes.ts",
routes: "src/app/api",
server: "src/core/server/index.ts",
listeners: "src/core/server/listeners.ts",
tasks: "src/core/server/tasks.ts",
outbox: "src/core/server/outbox.ts",
schedules: "src/core/server/schedules.ts",
},
});
Config values are optional overrides. Omitted paths fall back to the generated
defaults. routes, lint, doctor, and make generators all load the same resolved
config before inspecting or writing files. When appContext is moved under a
source root such as src/app-context.ts, client setup follows that root at
src/client/index.ts.
Framework-neutral generators work in both profiles. make payments,
make upload, make outbox, and make schedule --route currently generate
Next.js App Router handlers and therefore reject framework: "web" before
writing. The create command also remains a Next.js scaffold.
The generated lib/beignet-test-runner.ts also resolves all four supported
config formats and includes configured test, feature, route, server, port,
infra-port, route-builder, and use-case-builder roots in test discovery. The
generated test script invokes this runner with node --import tsx, so the
Node test suite uses the same runtime even when the package script is started
with Bun, npm, pnpm, or Yarn. beignet doctor --fix upgrades older generated
bun test and tsx lib/beignet-test-runner.ts scripts without replacing
custom test commands.
The same config can declare app-owned names for Beignet operational database tables when an app uses names other than the provider defaults:
// beignet.config.ts
import { defineConfig } from "@beignet/cli/config";
export default defineConfig({
database: {
tables: {
audit: "audit_events",
idempotency: "idempotency_records",
outbox: "outbox_messages",
},
schemaSources: ["@acme/db/schema"],
},
});
doctor uses these names when checking Drizzle-backed audit, idempotency, and
outbox wiring. Runtime code still needs the matching provider tableName
option wherever the Drizzle setup statements and ports are created.
Use database.schemaSources when operational table definitions live outside
the app's canonical infra/db/schema/, drizzle/, or infra/db/ files. Each
entry can be an app-relative file or directory, an @/ app path, or a package
specifier such as @acme/db/schema. doctor and provider audit also
follow schema-like imports from app database files, but explicit schema
sources make shared-package layouts predictable.
Provider metadata describes the standard environment requirements checked by
doctor --strict and provider audit. When an injected client gets its
configuration from a platform binding, IAM, or another non-env mechanism,
declare only the irrelevant static requirement as an explicit exception:
import { defineConfig } from "@beignet/cli/config";
export default defineConfig({
providerAudit: {
ignoreRequiredEnv: ["POSTGRES_DB_URL"],
},
});
This affects CLI provider auditing only. It does not skip createEnv(...) or
provider runtime configuration validation.
A web app and a worker can share Beignet application code and infrastructure through ordinary workspace packages. Each runtime owns startup and shutdown; shared packages export contracts, use cases, port types, and adapter factories. Sharing an adapter implementation does not share an in-memory instance across processes. Use the same persistent database or service when runtimes need shared state.
Opt into source analysis from the app's beignet.config.ts:
import { defineConfig } from "@beignet/cli/config";
export default defineConfig({
framework: "web",
workspace: {
packages: ["../../packages/application", "../../packages/infra"],
},
});
List every source package to inspect, including transitive dependencies. Paths
are relative to the app and may point to sibling packages. Each package needs a
unique package.json name and must be reachable through declared dependencies
from the app. Package roots cannot overlap. Beignet does not discover or inspect
all of node_modules; install workspace dependencies using your package manager.
Turborepo can schedule the package scripts but is not required by Beignet.
Each package uses its own beignet.config.* paths and tsconfig.json (or
jsconfig.json). An application package can keep the usual features/, ports/,
and lib/ layout. An infra package containing adapters under src/ can declare:
// packages/infra/beignet.config.ts
import { defineConfig } from "@beignet/cli/config";
export default defineConfig({
paths: { portWiring: "src/port-wiring.ts" },
});
This identifies src/ as the package's infrastructure layer. A package that
only exports adapter factories does not need a port-wiring file. The consuming
app can compose those factories in its own infra/port-wiring.ts.
Expose source entrypoints through package exports, for example:
{
"name": "@example/infra",
"private": true,
"type": "module",
"exports": { "./notes": "./src/notes.ts" },
"dependencies": { "@example/application": "workspace:*" }
}
The normal TypeScript resolver handles exports, subpaths, conditions, package
imports, and configured aliases. Configure the consuming build and runtime to
resolve the same source. Beignet does not rewrite imports or redirect built
exports to guessed source files. Missing exports and entries that resolve only
to excluded build output produce BEIGNET_WORKSPACE_IMPORT diagnostics.
beignet lint follows imports and reexports across included packages and applies
each owner's architecture rules. Moving an adapter to a package does not make
it accessible to use cases or browser code. Cross-package imports require a
dependency declaration in the importing package, including type-only imports.
Relative imports, TypeScript aliases, and package.json#imports aliases that
resolve outside the configured source roots also produce a workspace diagnostic;
include the owning source package in workspace.packages. Aliases to installed
external dependencies remain external imports.
App-owned provider checks recognize reexported AppPorts declarations and
provider metadata in hoisted node_modules installations. Inferred declarations
such as AppPorts = typeof initialPorts follow the underlying wiring declaration
through reexport aliases. Barrels may import a symbol and then export it, including
type-only exports of shared port interfaces.
beignet routes and doctor inspect shared contracts and explicit
defineRoutes(...) / defineRouteGroup(...) composition from the selected
server. Inspection follows the exported server or the server returned by an
exported getServer = createNextServerLoader(...). Unrelated server factories
do not register routes, and verified per-file Next.js handlers remain valid
without central registration. Contract matching follows imports and local aliases
to the declaration, so shared export names and loader-local variables remain
distinct. Computed or ambiguous workspace route registration produces
BEIGNET_WORKSPACE_ROUTES_UNRESOLVED. map and explain include shared source
and qualify shared feature names, for example @example/application:notes.
Evidence paths remain relative to the selected app and can contain ../.
map --changed includes changes to configured source packages.
Keep lifecycle provider registration, environment configuration, database
commands, and operational workflow registries app-owned. Provider and
operational doctor checks still use those app conventions; this source analysis
does not make arbitrary imported runtime factories statically verifiable.
Worker-only packages can use lint to check their dependencies; the HTTP-focused
routes and doctor commands run against the web app.
Generators and doctor repairs remain scoped to the selected app. paths.*
values must still stay inside that app; workspace.packages grants read-only
analysis, not permission to generate or repair files in another package. The
standard single-app examples remain the default architecture.
Use make contract when you want to start with the HTTP contract only:
beignet make contract projects
The command honors beignet.config.* path overrides and writes
features/projects/contracts.ts. It creates a self-contained contract group with a
starter list endpoint, schema, standard error response, and exported contract
list. It does not wire route handlers, use cases, ports, or OpenAPI; use
make feature when you want the full path from contract to tests.
Like make feature, make contract skips identical files and stops on
divergent files unless you pass --force.
Inside a Beignet app, scaffold the standard contract-first vertical slice for a product capability or workflow with:
beignet make feature projects
The command honors beignet.config.* path overrides. It writes:
features/projects/contracts.tsfeatures/projects/use-cases.tsfeatures/projects/ports.tsinfra/projects/drizzle-project-repository.tsfeatures/projects/routes.tsfeatures/projects/tests/projects.test.tsIt also wires the slice into the central route registry (server/routes.ts in
generated apps), ports/index.ts, infra/db/repositories.ts, and adds a
test script if the app does not already have one. The generated slice uses a small
name field by default so the app stays typecheckable immediately. Treat that
field as a placeholder and shape the schemas, use cases, repository, and tests
around the feature's real workflow.
Add common feature-owned artifacts with --with:
beignet make feature projects --with policy,event,job,notification,ui,upload
The optional artifacts use predictable starter names:
features/projects/policy.tsfeatures/projects/domain/events/created.tsfeatures/projects/jobs/process.tsfeatures/projects/notifications/created.tsfeatures/projects/components/projects-panel.tsxfeatures/projects/attachment-upload-manifest.tsfeatures/projects/client/attachment-upload.tsfeatures/projects/components/attachment-uploader.tsxfeatures/projects/uploads/attachment.tsEvent, job, notification, and upload registries are created or updated in the
matching folder index.ts files. The UI addon is opt-in:
beignet make feature projects --with ui
It writes a React component that imports the shared rq adapter and feature
contracts, then passes query/mutation options directly to TanStack Query. The
component binds the create contract through rhf(contract), reuses its body
schema for field validation, maps server failures with rootFormError(...),
resets after success, and refreshes the generated list query through
mutationOptions({ invalidates }). If the app does not have one yet, the
generator also creates the canonical client/forms.ts adapter and installs the
React Hook Form dependencies.
The addon targets React Hook Form. In an app that depends on
@beignet/react-form, both --with ui and --recipe full-slice stop with
guidance. Generate the feature without those options, then write its component
using the TanStack Form guide. You can still
select other addons with --with.
Use the named full-slice recipe when you want a richer reference slice:
beignet make feature projects --recipe full-slice
The recipe includes the standard contract, schema, use-case, port, route, test,
repository, and wiring files, then adds policy, factories, seeds, tasks,
events, listeners, jobs, notifications, schedules,
components, an upload definition with a connected uploader, outbox wiring, and
outbox_messages Drizzle schema. The
generated create use case publishes the generated created event so the event
artifact is connected, and the listener registry is registered in
server/listeners.ts and wired from server/providers.ts. Replace the starter names, listener/job bodies,
notification payloads, and task behavior with the app's real workflow. For
workflows that require durable audit records, record through the app audit port
inside the same Unit of Work transaction as the write.
Use make use-case inside a Beignet app when you want a focused
application workflow without generating HTTP contracts or ports:
beignet make use-case projects.archive-project
The name uses feature.action format. For a new feature, the command creates
features/projects/use-cases.ts. For an existing feature, it appends to that
module or adds use-cases/archive-project.ts and updates use-cases/index.ts.
Compact appends preserve handwritten code; declaration/import collisions stop
generation even with --force. Dry runs validate and report edits without writing.
Use --useCaseLayout split with make feature, make resource, or
make use-case to select separate files for a new feature. Existing layouts
are preserved; selecting a different layout requires moving the files manually.
Do not keep both use-cases.ts and use-cases/ in one feature. The inbox,
tenancy, and payments recipes use split modules.
Read-style actions that start with get, list,
find, search, or count generate .query(...); other actions generate
.command(...).
Use make test after generating a focused use case. Generated features and
resources already include their baseline contract, use-case, route, policy, and
repository-oriented tests; make test is for adding coverage around an
additional workflow that does not need a whole new resource slice.
beignet make use-case projects.archive-project
beignet make test projects.archive-project
The command writes features/projects/tests/archive-project.test.ts, builds the
app context through createTestPorts(...), createTestContextFactory(...), and
createUseCaseTester(...), and asserts the starter
{ ok: true } response. It also adds a test script when the app does not
already define one. Treat the output as a compiling starting point: replace the
input, context setup, and assertion with behavior-specific coverage as the use
case grows.
Use make port inside a Beignet app when a use case needs a new
application boundary:
beignet make port email
The command writes ports/email.ts, adds the port to AppPorts, creates a
small fake adapter for tests, and wires a throwing infra stub so the app still
typechecks until you replace it with a real port adapter. The generated port
starts with a generic execute method; rename it to the domain operation your
use case needs.
Use make policy when repeated authorization rules need a named home:
beignet make policy posts
The command writes features/posts/policy.ts with a definePolicy(...) starter.
Register the policy with createGate(...), install the gate as a port, and
bind it into request context so use cases can call ctx.gate.authorize(...).
For tenant, ownership, or role-heavy rules, add a matrix test with
createPolicyTester(...) from @beignet/core/testing.
Use make adapter after generating a port when you are ready to replace the
generated throwing stub with a concrete infra implementation.
beignet make port email
beignet make adapter email
The command writes infra/email/email-adapter.ts and replaces the generated
inline infra stub with email: createEmailAdapter(). The adapter still
throws by default; replace its implementation with real infrastructure code
while keeping use cases behind the port interface. If the infra wiring
was already customized, the command stops instead of guessing.
Use make resource when the feature you are building is CRUD-shaped and the
main concept is an entity with repository-backed persistence:
beignet make resource projects
beignet make resource projects --authorization --tenant-scoped --events --soft-delete
make resource generates a CRUD-shaped slice with list, create, get, update,
and delete contracts, use cases, route handlers, repository methods, a policy
starter, tests, and feature-specific not-found and conflict catalog errors. Use
make feature for workflows and capabilities that do not map cleanly to a REST
resource.
Generated list routes use cursor pagination with limit, cursor, name,
sortBy, and sortDirection query parameters. Their contract pairs the query
schema with an explicit transport so the client, server, and OpenAPI document
use the same URL representation. The generated use case
normalizes cursor pages, validates opaque base64url cursors against the selected
sort, and passes one repository query object into memory and Drizzle adapters.
Adapters filter name with case-insensitive contains matching, sort only by
createdAt or name plus id, and fetch limit + 1 records to derive
nextCursor without a count query.
Generated resources also include optimistic concurrency by default. Responses
include a numeric version; update request bodies must send that version back;
memory and Drizzle repositories include the version in the update check and
increment it on success. Stale updates become the generated
<Resource>Conflict catalog error.
Use --authorization to generate authorization metadata, policy wiring, and use-case
ctx.gate.authorize(...) calls. Use --tenant-scoped to scope repository reads and
writes with a branded TenantScope; generated use cases call
requireTenantScope(ctx), repository ports require scope: TenantScope, and
adapters unwrap the storage key with tenantScopeId(scope). doctor --strict
also warns when tenant-scoped generated repositories lose that boundary, when
hand-authored Drizzle ports expose raw tenantId or workspaceId app-facing
methods, or when scoped adapters stop using tenantScopeId(scope) predicates.
Use --events to generate created, updated, and deleted domain events and
publish them through ctx.ports.eventBus. The
starter ships no event bus, so --events also wires one: it adds
eventBus: EventBusPort to AppPorts, defers the key in
infra/port-wiring.ts, registers createMemoryEventBusProvider() in
server/providers.ts, and adds the @beignet/provider-event-bus-memory
dependency. The wiring is skipped when the ports file already mentions
eventBus, so apps that wired their own bus are left untouched. Use
--soft-delete when delete routes should archive rows with deletedAt while
list, get, and update operations continue to read only active records.
In standard apps, the command also creates
infra/db/schema/projects.ts,
infra/projects/drizzle-project-repository.ts, and registers the repository in
infra/db/repositories.ts.
Use feature artifact generators when a workflow grows beyond a single use case:
beignet make task posts.backfill-search
beignet make event posts.published
beignet make job posts.send-published-email
beignet make broadcast posts.changes
beignet make notification posts.published
beignet make listener posts.enqueue-published-email --event posts.published
beignet make schedule posts.daily-summary --cron "0 9 * * *" --timezone America/Chicago --route
beignet make upload posts.attachment
beignet make upload posts.attachment --ui
These commands use feature.name format and write colocated feature files:
features/posts/tasks/backfill-search.tsfeatures/posts/domain/events/published.tsfeatures/posts/jobs/send-published-email.tsfeatures/posts/notifications/published.tsfeatures/posts/listeners/enqueue-published-email.tsfeatures/posts/schedules/daily-summary.tsfeatures/posts/uploads/attachment.tsUploads always add a feature-root client-safe manifest so server constraints
and browser input hints share one source of truth. In full-stack apps, --ui
also writes features/posts/client/attachment-upload.ts,
features/posts/components/attachment-uploader.tsx, and
features/posts/tests/attachment-uploader.test.tsx, then installs
@beignet/react-uploads. The component handles progress, cancellation,
failures, reset, and completion. API-only apps reject --ui before writing;
omit it to generate the backend upload workflow alone.
Upload generation preflights central registration before writing any feature
or provider files. If an app has replaced the generated
uploadRegistry = defineUploads({ ... }) shape, the command fails with the
exact registry entry to add manually; it never reports success with an
unregistered upload.
Each command creates or updates the folder's index.ts with an exported
registry such as postTasks, postJobs, postNotifications,
postListeners, postSchedules, or postUploads. Task generators also
create or update server/tasks.ts, which is the registry and lifecycle
boundary used by
beignet task run <name> --tenant acme --input '{"dryRun":true}'.
New task registries include createTaskContext(...) and
stopTaskContext(...) placeholders so provider startup and teardown stay
out of task definitions. Both receive TaskRunContextArgs from
@beignet/core/tasks — the task, task name, parsed input, and the optional
--tenant value — so tenant scoping stays out of task input schemas.
make outbox creates server/outbox.ts and a bounded
app/api/cron/outbox/drain route. On Next.js 15.1 or newer it also wraps the
database Unit of Work from a server-local provider with
createObservedUnitOfWork(...) and schedules one push-assisted drain through
after(). Older Next.js versions retain valid cron-only wiring. make event
and make job create that outbox path on first use and register the feature's
postEvents and postJobs registries in defineOutboxRegistry({...});
beignet doctor reports events and jobs the registry cannot deliver. Keep the
route as the durable recovery sweep even when the deferred trigger is present.
make job also ensures jobs: JobDispatcherPort is declared and bound. When
the app has no job dispatcher yet, it defers the port and installs an app-owned
inline dispatcher provider; existing direct wiring and providers such as
Inngest are preserved. Registry membership alone is not execution wiring.
Adding the jobs-inngest or jobs-bullmq preset later replaces only that
marked generated fallback; an unmarked app-owned inline provider is reported
as a conflict instead of being removed.
beignet doctor warns when an outbox registry contains jobs but the configured
ports and port-wiring files do not declare and bind or defer jobs. Keep that
key and the registry's jobs entries explicit; doctor reports indeterminate
registry or port-wiring shapes when they prevent it from verifying required
wiring, and the generator stops rather than replacing custom spread- or
helper-based wiring it cannot verify.
Generators wire the provider-backed ports their output depends on. make event (and make resource --events) wires eventBus: EventBusPort with
createMemoryEventBusProvider() from
@beignet/provider-event-bus-memory, including the package dependency.
Listener generation also installs provider lifecycle wiring that registers the
central listener registry in start(), waits for its initial readiness with
the default 10-second registry deadline, and awaits cleanup in stop().
make notification wires mailer: MailerPort with
createMemoryMailerProvider() and notifications: NotificationPort with
createInlineNotificationsProvider(), both dev-default providers from
@beignet/core. Each port is wired independently — added to AppPorts,
deferred in infra/port-wiring.ts, and registered in server/providers.ts —
and skipped when the ports file already has the key, so providers such as
resend (which contributes mailer) and app-owned adapters are left
untouched. Swap the dev-default provider for a production adapter without
rerunning the generator; the port stays the same.
Task, job, listener, schedule, and notification generators define artifacts
through app-owned context-bound builders in lib/. Each generator creates the
builder file when it is missing:
lib/routes.ts exports defineRoute and defineRouteGroup from
createRoutes<AppContext>() (@beignet/core/server) and ships with every applib/tasks.ts exports defineTask from createTasks<AppContext>()
(@beignet/core/tasks)lib/jobs.ts exports defineJob from createJobs<AppContext>()
(@beignet/core/jobs)lib/listeners.ts exports defineListener from
createListeners<AppContext>() (@beignet/core/events)lib/schedules.ts exports defineSchedule from
createSchedules<AppContext>() (@beignet/core/schedules)lib/notifications.ts exports defineNotification from
createNotifications<AppContext>() (@beignet/core/notifications)Event generators use @beignet/core/events directly and upload generators use
@beignet/core/uploads.
--route also writes a Next.js cron
route under app/api/cron/<feature>/<name>/route.ts. Generated cron routes
require CRON_SECRET and record schedule start, completion, and failure events
in devtools. Generated uploads include starter metadata, constraints,
authorization, storage key, storage metadata, and completion hooks. Use
make upload <feature.name> --ui in a full-stack app to add the connected
React client and uploader.
Use beignet outbox drain when you need to drain durable events and jobs from
an app-owned CLI, CI, or worker entrypoint:
beignet outbox drain --batch-size 100 --concurrency 4
The runner loads server/outbox.ts or paths.outbox. That module should export
outboxRegistry, createOutboxDrainContext(...), and optionally
stopOutboxDrainContext(...). The context owns app ports and provider
lifecycle, so the CLI drain uses the same outbox, event bus, jobs,
instrumentation, actor, and tenant decisions as the scheduled drain route.
The drain validates all transports required by the registry before claiming a
batch, so missing eventBus or jobs wiring does not consume attempts.
Delivery is serial by default. --concurrency enables unordered parallel
delivery for handlers that can safely overlap.
The drain report includes abandonedDeadLettered, settlementFailed, and
leaseLost. When either uncertainty counter is nonzero, the CLI prints the
complete report and exits 1; the equivalent MCP operation returns the same
JSON as an error-bearing tool result. Investigate storage availability and
worker clock synchronization before retrying.
Inspect and recover failed deliveries through the same app-owned operational context:
beignet outbox list --status deadLettered
beignet outbox show <id>
beignet outbox requeue <id> --reset-attempts
beignet outbox purge --before 2026-06-01T00:00:00Z --dry-run
beignet outbox prune --before 2026-06-01T00:00:00Z --dry-run
purge only targets dead-lettered messages; prune only targets delivered
messages. Both support bounded --limit runs and --dry-run previews.
There is intentionally no separate beignet jobs drain command. Outbox-backed
jobs drain through the outbox beside durable events. Direct provider jobs should
use provider-owned worker entrypoints, such as a BullMQ worker built with
createBullMQJobWorker(...) or the generated Inngest server/inngest.ts
registry. Next apps receive app/api/inngest/route.ts; non-Next apps receive a
createAppInngestFunctions(...) factory for their runtime adapter. When the
Inngest provider is installed, beignet make job maintains the central
registry.
Use beignet schedule run when you need to run a schedule explicitly from a
local shell, CI job, or worker entrypoint:
beignet schedule run posts.log-daily-summary --scheduled-at 2026-01-01T09:00:00.000Z
The runner loads server/schedules.ts or paths.schedules. That module should
export a schedules array, createScheduleContext(...), and optionally
stopScheduleContext(...). Omit --payload when the schedule should build its
own payload from createPayload(...); pass --payload when a provider or manual
run supplies the payload.
Operational CLI commands are bounded entrypoints. Use them from a local shell, CI job, release job, scheduler, or worker host. Do not start outbox drains, queue consumers, or interval polling from provider lifecycle hooks in serverless apps.
The matching library functions and report types are exported from
@beignet/cli for orchestrators that do not use the bin:
import {
runAppTask,
runOutboxList,
} from "@beignet/cli";
const task = await runAppTask({
name: "reports.backfill",
input: { dryRun: true },
});
const failed = await runOutboxList({ status: "deadLettered", limit: 25 });
runAppSchedule, runOutboxDrain, runOutboxShow, runOutboxRequeue,
runOutboxPurge, and runOutboxPrune are exported from the same entrypoint.
Each function returns the same versioned report shape as its CLI --json
command and operational MCP tool. Programmatic task input and schedule
payloads accept any pre-parsed value supported by the registered Standard
Schema, including raw strings. The CLI --input and --payload flags decode
JSON before calling these APIs.
make listener expects the event file to exist at the canonical generated path.
Run make event <feature.event> first, then generate listeners for that event.
make feature and make resource are idempotent for unchanged generated files:
repeated runs skip identical files and avoid duplicate wiring. If a generated
file exists with different content, the command stops unless you pass --force.
beignet encryption key
beignet encryption key --json
Prints a new base64:-prefixed 256-bit key for
createEncryption(...) from @beignet/core/encryption. JSON output contains
schemaVersion: 1 and key. The command works outside an app and does not
read or update environment files. Put the key into your server secret store;
keep command output out of shared logs and never replace a key without retaining
the keys needed to read existing data. See Encryption
for wiring, rolling rotation, and backup recovery.
Use beignet db from an app root to run database workflow scripts through one
stable framework command surface:
beignet db generate
beignet db migrate
beignet db status
beignet db seed
beignet db reset
The CLI delegates to app-owned package scripts named db:generate,
db:migrate, db:status, db:seed, and db:reset. In standard Drizzle apps,
those scripts run Drizzle Kit and the generated status, seed, and reset
entrypoints. Status is read-only: exit 0 means current, exit 2 means pending,
and any other non-zero exit means inspection failed. This keeps database
behavior app-owned while making the lifecycle discoverable through Beignet.
When an app adopts Beignet's Drizzle-backed operational ports for audit,
idempotency, or outbox, run beignet db schema sync first. It idempotently
brings the app-owned schema file that re-exports Beignet provider tables
(infra/db/schema/beignet.ts by default) in sync with the installed providers
and updates the schema index. Run beignet db generate afterward so the app's
Drizzle Kit script creates the matching migration.
beignet db checks standard prerequisites before running those scripts. If the
script is missing, Drizzle Kit has no drizzle.config.*, or the standard
status/seed/reset entrypoint was removed, the error points to the exact file or
package script to restore.
beignet db <command> --json retains the final 64 KiB from each output stream
and sets outputTruncated: true when earlier output was discarded. Human
terminal output remains streamed directly by the app-owned script.
db reset is intentionally app-owned because destructive behavior depends on
the environment. The standard starter refuses to reset non-local database URLs
unless BEIGNET_ALLOW_DATABASE_RESET=true is set. Drizzle starters also include
infra/db/test-database.ts, which creates an isolated test database for
repository and persistence tests: an in-memory libSQL database for SQLite, an
in-process PGlite database for Postgres, or a real server via MYSQL_TEST_URL
for MySQL.
Coding agents can run the same lifecycle without leaving MCP. Call db with
{ command: "generate" | "migrate" | "seed" | "reset", dryRun?, timeoutMs? }
for the exact versioned CLI/library report. Output is tail-bounded, requests
cancel the active process tree, and commands default to a ten-minute timeout.
Call db_schema_sync with { dialect?, tables?, output?, dryRun? } for the
same report as beignet db schema sync --json. For lifecycle commands,
dryRun validates prerequisites and reports the app-owned script without
executing it; it does not simulate the script's SQL or data changes.
Call the separate read-only db_status tool with { timeoutMs? } for the same
report as beignet db status --json.
Generate feature-owned test factories and local/demo seeds with:
beignet make factory posts.post
beignet make seed posts.demo-posts
Factories are written under features/posts/tests/factories/ and persist
through app-owned repository ports. Seeds are written under
features/posts/seeds/; make seed creates the app-owned server/seed.ts
entrypoint and db:seed script when they are missing.
Generated feature and use-case tests use Beignet's test context helpers so
request IDs, actors, tenants, and ports follow the same pattern as the testing
docs. Generated route tests boot createTestApp(...) with the app's
server/context.ts blueprint, so HTTP coverage exercises the same identity
resolution as production.
Preview writes without changing files:
beignet make feature projects --dry-run
Use JSON output when another tool needs the exact planned changes:
beignet make feature projects --dry-run --json
Inside an app, list the contracts the CLI can match to Next.js route handlers:
beignet routes
Use JSON output when you want to feed the route catalog into another tool:
beignet routes --json
Build a deterministic, source-backed graph of the app:
beignet map
beignet map --feature issues
beignet map --json --kind route,use-case,event,listener
beignet map --changed
beignet map --changed --base origin/main --json
The human view summarizes contracts, use cases, workflows, ports, UI, and tests
per feature. JSON is a versioned schemaVersion: 1 payload with features,
contracts, route groups, use cases, policies and abilities, workflow
definitions and registries, agent capabilities, ports, app/package providers,
tables, OpenAPI exposure, tests, cross-feature dependencies, doctor/lint
findings, and unresolved references. Every node has a stable ID and source;
every edge carries source evidence and confidence.
Source analysis covers TypeScript (.ts, .tsx, .mts, and .cts) and
JavaScript (.js, .jsx, .mjs, and .cjs). Statically recognizable
Beignet declarations using ES module exports become the same stable concept
nodes as their TypeScript equivalents, while imports, re-exports, literal
dynamic imports, and CommonJS require() calls contribute dependency evidence
across mixed-language graphs.
Test nodes include JavaScript and TypeScript files named with .test or .spec
under Beignet's canonical app test roots and any relevant roots configured
through beignet.config.*.
Tests inside a feature retain feature ownership and a contains-test edge;
adjacent app, infra, server, and top-level tests remain app-level nodes. When
one of those tests imports changed source, map --changed can report it as an
exact reverse-import consumer. That is impact evidence, not proof that the test
covers every behavior affected by the change.
The JSON app map retains failed local module references as explicit
local_import_unresolved entries instead of silently omitting their edges. It
also reports contract_declaration_unresolved when an exported direct or
contract-group declaration has a method, path, or group prefix that cannot be
derived statically. Registration diagnostics identify their declaration
through a structured subject.file and subject.exportName; registration
status in the map uses that identity rather than matching human-readable
messages.
--feature keeps that feature plus its direct relationships, while --kind
accepts one or a comma-separated list of node kinds. These projections make
the same full graph practical for coding-agent context windows. The command is
report-only and does not fail because doctor or lint findings are present.
--changed maps the current Git change set to potentially affected concepts.
Without --base, it preserves staged index changes, unstaged worktree changes,
and untracked files instead of collapsing them into one net diff against
HEAD. With --base <ref>, it also includes committed branch changes from the
merge base of that already-local ref through HEAD; Beignet never fetches the
ref. The bounded report distinguishes direct source changes, reverse local
imports, one-hop semantic consumers, related members of directly changed
containers, and feature ownership context. Governing configuration and changed
direct local workspace dependencies are marked app-wide. Recognized guide
files such as README.md, AGENTS.md, and CLAUDE.md, plus Markdown or text
files under a root docs/ or documentation/ directory, remain non-impacting.
Other .mdx and .txt paths remain application changes. Deleted declarations,
unresolved imports, changed files with no mapped concept, and other
static-analysis blind spots remain visible as gaps.
Changed-file paths and gap paths are repository-relative; app-map node sources
and source evidence remain relative to the selected app root.
Changed mapping explains potential impact; it does not run checks or claim
that a change is correct. Use beignet check after inspecting the report.
--changed cannot be combined with --feature or --kind, and --base
requires --changed.
The same report is available to library callers:
import { mapAppChanges } from "@beignet/cli";
const impact = await mapAppChanges({ cwd: "apps/api", base: "origin/main" });
Resolve one app-map target into deterministic, source-backed context:
beignet explain feature issues
beignet explain route "POST /api/issues"
beignet explain use-case issues.create
beignet explain task issues.backfill-search
beignet explain port storage
beignet explain registry issueTasks
beignet explain table issues
beignet explain diagnostic BEIGNET_ROUTE_GROUP_UNREGISTERED
<kind> accepts every app-map node kind plus diagnostic: features,
contracts, routes and route groups, use cases, policies and abilities,
workflow artifacts, agent capabilities, registries, ports and providers,
tables, OpenAPI documents, entrypoints, and tests. Targets accept stable IDs,
runtime or declaration names, source selectors such as file#export, and
applicable aliases such as an HTTP method/path or provider port.
Human output summarizes relevant relationships, conventions, findings,
suggested files, and follow-up commands. --json returns the versioned
schemaVersion: 1 payload used by the MCP explain tool. Feature explanations
bound their relationship list and report full and returned counts; use
beignet map --feature <name> --json or explain a directly related concept for
the omitted detail. Provider explanations include the matching entry in the
configured provider registry when static provider audit proves it. Human
output names the app root, and command actions in JSON carry their own cwd,
so an explanation requested with --cwd stays copy-pasteable. Explain is
report-only and never changes the app.
Run check when you want the whole validation loop as one command:
beignet check
It runs beignet lint, beignet doctor --strict, and the app's own lint,
typecheck, and test package scripts through the detected package manager,
in that order. Every step runs even when an earlier one fails, so a single
run reports everything that needs fixing; the command exits non-zero when any
step fails. Missing package scripts are reported as skipped, never as
failures. Failed package-script output retains at most the last 64 KiB of each
stream. The target package must declare @beignet/core, @beignet/next, or
@beignet/web as a runtime dependency. The root must also contain
app-context.ts or beignet.config.*; a canonical app with both features/
and server/index.ts is accepted when app-context.ts is missing so doctor
can report the drift. Other package roots stop before lint, doctor, or app
scripts run.
When the app is inside a Git worktree, check captures the repository HEAD,
index, worktree, and non-ignored untracked files before and after validation.
If the ending source-state hash differs from the baseline, the result is
stale, ok is false, and the command exits non-zero even when every validation
step passed. Rerun check against the resulting source. Untracked build and
coverage output excluded by Git ignore rules does not affect the hash. Outside
Git, validation continues and reports source provenance as unavailable;
corrupt or unreadable Git state still fails loudly.
Use beignet check --fix to apply doctor's eligible low-risk fixes
before the provenance baseline and validation steps. beignet check --json
returns a versioned payload (schemaVersion: 1) with the step list, statuses,
captured failure output, applied fixes, and starting and ending provenance.
Add --preflight to run the disconnected runtime environment gate after
strict doctor. Use --preflight-connect when the same check should also
inspect migration status and dependency health; it implies --preflight.
The individual commands below still work when you need one check alone or its
--format github output.
beignet preflight
beignet preflight --connect
beignet check --preflight
beignet check --preflight-connect
preflight is the runtime production gate, distinct from the static doctor
checks: it reads the environment the process actually runs with. It verifies
every individually required provider env var and accepts mutually exclusive
credentials only when one complete requiredEnvAlternatives configuration is
present. Variant packages are scoped to the factories registered in the app,
so inactive variants do not demand credentials. Preflight also flags values
still matching .env.example (or common placeholder patterns) on secret-like
keys, validates the app env schema by importing lib/env.ts, folds in
doctor's production hardening diagnostics with promoted severities, and
warns when logging or error reporting is absent or inert. --connect first
runs the app-owned db:status script when available, failing before server
boot on pending migrations or inspection failure, then runs every port's
checkHealth(). Older apps without the script receive a compatibility note.
The flag is opt-in because it needs network access and real credentials.
Use --connect-timeout-ms <ms> to change the default 5000 ms limit for
migration status and each dependency health check. Timeout findings include
the configured duration, and bounded status output says when only the retained
tail is shown. --env-file merges a dotenv-style
file under the environment for local rehearsal, --env-module and
--server-module override the default lib/env.ts and server/index.ts
paths, and --json emits machine-readable output with schemaVersion: 1.
Run it in the deploy pipeline where production configuration is present; it
exits 1 on any error finding.
beignet task run, beignet schedule run, and beignet outbox drain use an
optional ctx.ports.errorReporter from their app-owned context. They report
terminal command failures, dead letters, lease failures, and settlement
failures, then perform a bounded flush before stopping context. Retryable
outbox deliveries remain instrumentation/log events rather than incident
reports.
Run lint when you want the CLI to enforce Beignet dependency direction:
beignet lint
The command analyzes .ts, .tsx, .mts, .cts, .js, .jsx, .mjs, and
.cjs files. It recognizes imports and re-exports, require(), import
assignments, and literal dynamic imports. It fails when app layers reach into
runtime or framework layers, including domain, use case, workflow, policy,
port, contract, route, schema, seed, agent capability, feature helper, infra,
and component files importing infra/, UI components, route handlers, server
modules, client modules, provider packages, Next.js, React, or database vendors
in the wrong direction. Computed require() and import() expressions inside
constrained layers fail because lint cannot verify their destination; use a
string-literal specifier or move runtime loading behind an allowed boundary.
Client-safe graphs reject Node built-ins whether they use node:fs or a bare
specifier such as fs/promises.
Local value-import checks follow feature-root and other local helpers.
For example, use-case -> history.ts -> infra reports the complete chain
instead of treating history.ts as an unknown layer. Every module inside the
configured feature root is classified; allowed feature-root helpers remain
helpers, while near-miss canonical names such as usecases/ report the
expected use-cases/ path. Feature-specific domain files still cannot import
another feature's domain unless the target is features/shared/domain.
Workflow folders include feature-owned jobs, listeners, notifications,
schedules, tasks, and uploads. Agent capabilities may adapt use cases but core
application layers cannot depend back on those adapters. Seeds may compose
factories and app ports, but not concrete infra, providers, UI, or runtime
composition.
Local imports are resolved with the app's complete tsconfig.json, or its
jsconfig.json when no TypeScript config exists, before dependency direction
is classified. That includes baseUrl, every paths alias such as @/*,
~/*, or #app/*, and aliases with multiple targets. The CLI uses
TypeScript's config parser and resolver, so JSON comments and extended configs
follow the same rules as the compiler. Beignet enables JavaScript resolution
for this read-only analysis without changing the app's compiler configuration.
Import diagnostics include 1-based line and column positions and preserve
separate occurrences of the same module specifier. Human output prints
clickable file.ts:12:3 locations; transitive and runtime-boundary diagnostics
report the chain root file and the full import chain in the message instead.
Use JSON output in CI or custom scripts, or GitHub annotations in workflows:
beignet lint --json
beignet lint --format github
Run doctor when you want a framework integrity report:
beignet doctor
Today it detects:
.query(...) transportserver/schedules.ts
or server/tasks.tsdefineOutboxRegistry({...}) when the app uses outbox deliveryjobs
application portserver/listeners.ts or otherwise not
referenced by registerListeners(...)start(), await initial readiness,
and await asynchronous cleanup in stop()contractsFromRoutes(routes) route registriesAppContext redeclarationsfeatures/shared/errors.tsappError(...) calls that are not declared on the feature's
contractsusecases/ instead of use-cases/ or events/ instead
of domain/events/; doctor and lint report the same expected path@beignet/* version ranges in package.json, and mixed installed
@beignet/* versions in node_modules; the packages release together, so one
app should use one version@beignet/core, reported
as an informational hint that suggests the app-local bun beignetserver/providers.ts, reported as an informational hint when the provider
declares registration as optional, such as @beignet/devtoolsdbinfra/db/repositories.tsinfra/db/schema.ts schemas that should move to the
infra/db/schema/ directory formfeatures/<feature>/ instead of
features/<feature>/tests/db:seed package scriptcreatePaymentWebhookRoute(...) for payment-port billingidempotency-key headerctx.ports.idempotency when
AppPorts does not declare an idempotency portAppPorts entitlements declarationCRON_SECRETaccount.issuer or the required
compound identity indexcreateSecurityHeadersHooks(...)ctx.ports.notificationsWhen production-readiness diagnostics are present, human doctor output also
prints a production hardening checklist covering secrets and provider
credentials, verified auth and tenant authority, exposed operational routes,
security headers, CORS and proxy trust, upload and storage limits, readiness
checks, worker shutdown, webhook secrets, and least-privilege provider
credentials.
Provider package diagnostics come from package-owned beignet.provider
metadata in installed package manifests. The CLI does not import provider
implementation modules to discover those facts, and it reports malformed
metadata before using provider-derived doctor rules.
Use beignet provider add <preset> to wire a stable provider recipe after
app creation. It updates package.json, server/providers.ts, app port
types, deferred port wiring, .env.example, and docs/providers.md.
Supported presets are flags-openfeature, jobs-bullmq, jobs-inngest,
mail-resend, mail-smtp, payments-stripe, search-meilisearch, error-reporting-sentry,
rate-limit-upstash, cache-redis, event-bus-redis, locks-redis,
storage-s3, and storage-vercel-blob; pass --dry-run --json to preview
the exact file changes. The same presets can be selected at create time with
--providers.
The storage presets declare StoragePort from @beignet/core/ports and reuse
that declaration if your app already has local storage. Before switching storage
providers, remove the previous provider entry, import, and unused provider
package; keep the app's storage port and deferred binding.
Use beignet provider audit when you want a report-only inventory of the
provider packages installed in an app. The human audit prints provider
metadata validity, registration status, required env, required tables, and
declared app ports. beignet provider audit --json returns a versioned
schemaVersion: 1 payload with active variants, watchers, and source locations
for matching provider-registry entries. Missing alternative credential paths
remain grouped in human diagnostics, while JSON preserves each active
variant's accepted configurations and status. The command does not fail CI for
missing setup; stable, actionable findings belong in doctor.
Use beignet doctor --strict for CI-oriented checks that may be noisy locally,
such as missing generated feature tests, canonical app conformance drift, and
feature-owned route errors that appear unused. Strict mode fails on warnings as
well as errors; informational hints never affect the exit code, so an
installed-but-unregistered optional provider such as @beignet/devtools still
passes doctor --strict. Use
beignet doctor --json in CI or custom scripts. Doctor JSON includes
schemaVersion: 1 plus targetDir, config, strict, convention,
contracts, routes, diagnostics, and fixes.
Use beignet doctor --fix for low-risk maintenance fixes. Today it can add
a missing test script, register existing feature route groups in the central
route list, register unregistered feature schedule, task, and outbox
event/job registries in their existing central registry files, wire fully
unregistered feature listener registries into server/listeners.ts and the
central listeners provider, register feature jobs in an existing
server/inngest.ts, align an opted-in runtime manifest with fully
unregistered workflow registries, sync missing default audit, idempotency, and
outbox table exports through infra/db/schema/beignet.ts, and repair
direct createOpenAPIHandler([...]) arrays when the missing contracts are
already imported in the OpenAPI route.
Registry and runtime-manifest fixes are append-only. When a central file is
missing, an anchor is customized beyond recognition, an import is ambiguous,
or some artifacts are already registered individually, the fix bails out and
the diagnostic stays. The listener fix additionally bails when the app has no
eventBus port or the providers array is customized, because apps own their
event-bus wiring. Database repair requires an existing schema index, default
Beignet table names, a missing beignet.ts or one that still matches
beignet db schema sync output, and a schema index where the generated
export * can be safely added or retained. It does not generate or apply a
migration. Run beignet db generate, beignet db migrate, and
beignet db status after accepting that source change. Compatible workflow
repairs that share files are combined
into the selectable workflows.register-missing operation so apply-all remains
atomic.
Preview the exact changes before applying them with:
beignet doctor --fix --dry-run
The plan contains stable repair operation IDs, SHA-256 before/after hashes,
unified patches, a planId, and the current doctor diagnostics. Human and
GitHub output render those diagnostics so every non-zero exit is explained.
Apply the complete guarded plan with
beignet doctor --fix --plan <planId>, or select operations with
--only routes.register-missing,outbox.register-missing. Guarded application
fails before writing when any fixable file or available repair changed after
planning. Plain doctor --fix and check --fix retain their apply-all
behavior for scripts that already use them.
Run an MCP (Model Context Protocol) server over stdio so coding agents can read app-local guidance and focused feature maps as resources, then call the CLI as tools:
beignet mcp
The server publishes read-only app guidance and per-feature app maps alongside its tools.
Resources:
beignet://app/guidance - bounded app-local AGENTS.md guidance, with a
concise fallback when the file is absentbeignet://app/features/{feature} - focused source-backed feature app maps,
with available features discoverable through MCP resource listing and
feature-name completionTools:
app_map - project the source-backed app graph by optional feature,
kinds, and includeDiagnostics inputs, or pass
{ changed: true, base?: "origin/main" } for the same bounded report as
beignet map --changed --json (read-only). Changed mode rejects projection
and diagnostic inputsexplain - explain any source-backed mapped concept or diagnostic with
{ kind, target }, returning relationships, conventions, findings,
suggested files, and follow-up commands (read-only)check - run the complete validation loop and return versioned step
results, bounded failure output, durations, starting and ending source
provenance, and overall ok. An ending fingerprint that differs from the
baseline makes the result stale and unsuccessful. The tool accepts
{ preflight?: boolean, preflightConnect?: boolean, connectTimeoutMs?: number, timeoutMs?: number },
cancels the active script with the MCP request, and does not apply Beignet
fixes. preflightConnect implies preflight and adds migration and dependency
checks. App scripts retain their normal side effects, so this is an
execution tool rather than a read-only tooldb - run generate, migrate, seed, or reset with
{ command, dryRun?, timeoutMs? }; returns the matching versioned CLI report
with bounded output, request cancellation, and a command timeoutdb_status - inspect the app-owned migration history without mutating the
database; returns current, pending, or faileddb_schema_sync - sync app-owned Beignet provider-table schema re-exports
with { dialect?, tables?, output?, dryRun? }; idempotent but writes source
unless dryRun is truetask_run - run a registered task with
{ name, input?, tenant?, module?, timeoutMs? } in an isolated processschedule_run - run a registered schedule with
{ name, payload?, runId?, attempt?, scheduledAt?, triggeredAt?, source?, module?, timeoutMs? }
in an isolated processoutbox_inspect - read outbox state with operation-specific list or show
inputs (read-only)outbox_run - run the state-changing drain, requeue, purge, or prune
operations; purge and prune accept dryRunroutes - list app routes and contracts (read-only)doctor - drift diagnostics as JSON, { strict?: boolean } defaults to
true (read-only)doctor_fix_plan - return stable repair operation IDs, exact patches, file
hashes, and a plan ID without changing filesdoctor_fix - apply doctor's low-risk fixes; accepts
{ planId?, fixIds?, strict? }. Omitting planId preserves apply-all;
fixIds requires the plan ID returned by doctor_fix_planlint - architecture dependency-direction diagnostics (read-only)make - run a generator with { artifact, name, ...options }, the same
artifact kinds as beignet makeprovider_add - add a provider setup preset with { preset, dryRun? }The db, db_status, db_schema_sync, task_run, schedule_run, outbox_inspect,
outbox_run, doctor_fix_plan, and doctor_fix tool outputs match the
corresponding CLI/library JSON payloads. Operational app code runs outside the
long-lived MCP server with bounded structured results, request cancellation,
and process-tree timeouts. Operational module overrides must be app-relative
and cannot leave the app root where the server started. Cancellation and
timeouts cannot roll back side effects that already completed. Inspect app
state before retrying an interrupted operation or one that reports a cleanup
failure. Generated apps ship a .mcp.json that runs
./node_modules/.bin/beignet mcp, so Claude Code picks up the app-local CLI
version with no configuration; Cursor and VS Code users add the same command
to .cursor/mcp.json or .vscode/mcp.json.
For one-off usage outside an installed app, run the scoped package, such as
bunx @beignet/cli mcp or npx @beignet/cli mcp. Generated apps also include
AGENTS.md and CLAUDE.md covering registration, generator, placement, and
validation conventions.
beignet make broadcast issues.changes adds a browser-safe
features/issues/channels.ts, a fail-closed authorization use case,
features/issues/broadcasts.ts, lib/broadcasting.ts, server/broadcasts.ts,
and a thin SSE adapter. It wires the memory broadcast port if missing and reuses
an existing conventional auth.required() hook when available. Implement
current identity, membership, and resource policy checks before enabling access;
then map events to query invalidation and reconcile after reconnect.
Run bun install after generation or a provider change to install newly added
dependencies.
Select beignet provider add broadcast-redis before the first generator when
publishers and subscribers run in different processes. Set REDIS_BROADCAST_URL
and an app/environment-specific REDIS_BROADCAST_PREFIX. The memory and Redis
presets are alternatives; replace the existing provider when switching.
beignet make inbox --broadcast composes a recipient-authorized channel,
list/count query mapping, and a publication job recorded in the inbox write's
transaction. make inbox selects this recipe automatically if a broadcast port
already exists. Rerun it after adding broadcasts to a standard generated inbox;
customized writes require explicit transaction edits. Configure the outbox drain
and mount the generated client helper with the signed-in user ID.
Configured paths are broadcasts (default server/broadcasts.ts),
broadcastingBuilder (lib/broadcasting.ts), and broadcastRoute
(app/api/broadcasts/route.ts for Next, server/broadcast-route.ts for web).
Web hosts pass their assembled server to the generated
createAppBroadcastRoute(server) factory and mount its returned GET handler.
Generated adapters use the default maxLifetimeMs: 60_000. Set a positive safe
integer up to 3_600_000 (one hour), such as 240_000 for four-minute streams,
when the host permits it. Leave setup/cleanup headroom below its request
deadline; a Next route can use maxDuration = 300 for a four-minute stream.
Longer streams reduce renewal/refetch frequency but increase the interval
between authorization checks. The browser honors the advertised lifetime and
reconciles after every renewal. Doctor accepts these bounded lifetimes.
Channel contracts are schema modules; server bindings and app-owned channel.ts notification
handlers are workflow modules. Browser imports of server bindings are rejected.
routes shows streaming endpoints separately from HTTP contracts. map and
explain recognize channel declarations and bindings. doctor follows bindings
through registry array literals, named arrays, and spreads, including readonly
arrays. It checks evident registration and lifetime drift; it cannot prove
access policies, transaction ordering, or host suitability. MCP's make tool
accepts artifact: "broadcast" and broadcast: true for artifact: "inbox". See the
broadcasting guide.
MIT