Skip to content

Escape Hatches

The config-driven package/entity stack is the default path, not a cage.

When the managed surface stops being the right abstraction, these are the supported escape hatches.

Replace the executor behind a generated CRUD or named-operation route while keeping the route shell, OpenAPI spec, and middleware intact. Use defineEntityExecutor() with an overrides map.

Full examples: Override a generated route

Add routes that live alongside generated entity routes using extraRoutes and defineEntityRoute(). They share the same collision checking and specificity ordering as generated routes.

Full examples: Add an extra route

Use this when the route belongs to the app directly rather than to a package-owned entity/domain module. Author the route with createRoute(...), createRouter(), and router.openapi(...), then let routesDir auto-discover and mount the route module.

Full examples: File-based routes

Use raw Hono when:

  • you need a lower-level router shape
  • the route is middleware-heavy and does not fit the managed package/domain route shell
  • you are integrating third-party Hono middleware or request handling patterns directly

This is still a supported path. It is just lower-level than package/entity authoring.

Use entity({ wiring: { mode: 'factories', ... } }) when you want custom repo-backed behavior while keeping the entity shell.

This is the normal escape hatch for custom repos, store-specific adapter behavior, and persistence code that still maps cleanly to an entity adapter.

Full examples: Factories wiring mode

Use entity({ wiring: { mode: 'manual', buildAdapter(...) } }) when you need full control over adapter construction.

This is the stronger escape hatch for non-standard persistence composition, custom driver integration, and repo layers that do not fit the standard factories path.

Full examples: Manual wiring mode

Use createCompositeFactories(...) when a package owns several related entities that share a backing store and should be wired together.

Full examples: Composite factories

Switch the backing store for entities at the app root without changing entity definitions or adapter code. Supports memory, SQLite, Postgres, Redis, and Mongo.

Full examples: Backend swapping at the app root

Raw SQL belongs in the adapter or repo layer, not in the entity DSL itself.

Use it when you need hand-written joins, database-specific query tuning, or reporting queries that do not map to the operation DSL. The route shell or package layer can still sit above that repo code.

Drizzle is also an escape hatch, not the primary config-driven abstraction.

The right place for Drizzle code is usually a custom repo used by factories wiring, a manual adapter implementation, or a package-local service used by a package domain route. Keep the boundary explicit instead of pretending it is the entity DSL itself.

System fields, storage fields, and conventions

Section titled “System fields, storage fields, and conventions”

defineEntity(...) accepts systemFields, storageFields, and conventions to rename audit fields, storage column names, ID generation strategies, and Redis key formats without forking adapters.

Full examples: System fields, storage fields, and conventions

A package domain({ routes: [...] }) route is the first step out of pure entity CRUD/operation generation. Use it when the route spans multiple entities, handles orchestration, or has a package-level response model.

Full examples: Package domain routes

Use createEntityPlugin() when you need lower-level orchestration that entity(...) and definePackage(...) do not cover. This is the lower-level compatibility and escape-hatch surface beneath package-first authoring.

Full examples: Config-Driven Domain example

Use a raw SlingshotPlugin when you need full lifecycle control, custom bootstrapping that does not fit package/entity compilation, or direct router mounting in plugin phases.

This is the broadest escape hatch and should be treated as the lowest-level public authoring surface.

Full examples: Plugin Interface

Three levels, from managed to raw:

  1. Package middleware referenced by name from entity/domain routes
  2. Raw Hono middleware mounted through routers or plugin phases
  3. Full plugin lifecycle hooks for setup-time and post-assembly behavior

Use the lowest level that actually solves the problem.

Use:

  • package/entity authoring for the normal case
  • executor overrides to replace generated route behavior
  • extra routes for entity-adjacent endpoints
  • package domain routes for orchestration and custom package endpoints
  • factories/manual adapters for custom persistence
  • raw SQL or Drizzle inside repo/adapter code
  • routesDir or raw Hono routers for app-level file-based routes
  • raw plugins for lifecycle-level escape hatches