# Output Structure

The compiler generates a complete project structure based on your definitions and provider configuration. Cloudflare Workers is the only supported runtime; what varies is which optional surfaces your config enables.

**Warning:** Never edit files in `src/` directly. They are overwritten on every compile. Make changes in your `quickback/` definitions instead.

## Output

```
src/
├── index.ts                    # Hono app entry point (Workers export)
├── env.d.ts                    # Cloudflare bindings TypeScript types
├── db/
│   ├── index.ts                # Database connection factory
│   ├── auth-schema.ts          # Auth table schemas (dual mode)
│   └── features-schema.ts     # Feature table schemas (dual mode)
├── auth/
│   └── schema.ts               # Better Auth table schemas (generated by the Better Auth CLI)
├── features/
│   └── {feature}/
│       ├── schema.ts                    # Drizzle table definition(s)
│       ├── {feature}.ts                 # Table alias — re-exports the table from schema.ts
│       ├── {feature}.resource.ts        # Firewall / access / masking helpers for this resource
│       ├── {feature}.routes.ts          # CRUD + record-bound action endpoints
│       ├── {feature}-actions.routes.ts  # Standalone action endpoints (when an action sets `path:`)
│       ├── actions.ts                   # Aggregator — imports every action's default export
│       ├── actions/
│       │   └── {action}.ts              # One file per authored action
│       └── .quickback/
│           └── define-action.ts         # Typed per-feature `defineAction` helper
├── routes/
│   └── {feature}.ts            # Mount re-export consumed by src/index.ts
├── lib/
│   ├── auth.ts                 # Better Auth instance & config
│   ├── access.ts               # Access control helpers
│   ├── types.ts                # Runtime type definitions
│   ├── masks.ts                # Field masking utilities
│   ├── services.ts             # Service layer
│   ├── audit-wrapper.ts        # Stamps the managed audit columns on write
│   └── security-audit.ts       # Unsafe cross-tenant audit logger (when needed)
└── middleware/
    ├── auth.ts                 # Auth context middleware
    ├── db.ts                   # Database instance middleware
    └── services.ts             # Service injection middleware

quickback/drizzle/
├── auth/                       # Auth migrations (dual mode)
│   ├── meta/
│   │   ├── _journal.json
│   │   └── 0000_snapshot.json
│   └── 0000_initial.sql
├── features/                   # Feature migrations (dual mode)
│   ├── meta/
│   │   ├── _journal.json
│   │   └── 0000_snapshot.json
│   └── 0000_initial.sql
└── audit/                      # Unsafe cross-tenant action audit migrations (when needed)
    ├── meta/
    └── 0000_initial.sql

# Root config files
├── package.json
├── tsconfig.json
├── wrangler.toml               # Cloudflare Workers config
├── drizzle.config.ts           # Features DB drizzle config
├── drizzle.auth.config.ts      # Auth DB drizzle config (dual mode)
└── drizzle.audit.config.ts     # Audit DB drizzle config (when unsafe actions exist)
```

Not every per-feature file is always emitted:

- `actions.ts`, `actions/`, and `.quickback/define-action.ts` appear only when the feature has at least one authored action under `quickback/features/{feature}/actions/`.
- `{feature}-actions.routes.ts` appears only when at least one of those actions declares `path:` (the marker that makes an action standalone).
- `schema.ts`, `{feature}.ts`, and `{feature}.resource.ts` are skipped for tableless features.
- A multi-table feature gets one alias file per table (`{feature}.ts`, `{other_table}.ts`) alongside the single `schema.ts`.

## Spec Blobs Are Assets, Not Bundle

`openapi.json`, `asyncapi.json` and `schema-registry.json` are **not** written
to your project root. They are emitted under the assets directory:

```
src/apps/__quickback/spec/
├── openapi.json
├── asyncapi.json           # only when the project has an events surface
└── schema-registry.json
```

This exists to protect cold-start time.

A Worker has a limited CPU budget for its **startup window** — the module
evaluation that runs before the first request is served — and everything in the
bundle counts against it, because the runtime has to parse the whole script. On
a large API the specs are the single biggest thing in that bundle, and the cost
is paid on every cold start to serve discovery routes most callers never
request.

Serving them from the [ASSETS
binding](https://developers.cloudflare.com/workers/static-assets/) takes them
out of the bundle entirely. The generated `src/index.ts` fetches on demand:

```ts
const __serveSpec = async (c: any, name: string) => {
  const res = await c.env.ASSETS.fetch(
    new Request(new URL(`/__quickback/spec/${name}`, c.req.url)),
  );
  // ...
};

app.get('/openapi.json', (c) => __serveSpec(c, 'openapi.json'));
```

The routes and payloads are unchanged — `GET /openapi.json` still returns the
same spec, under the same [`openapi.publish`](/api/openapi) gate.

Because of this, projects get an `[assets]` block even with no CMS, Account, or
custom app configured. `run_worker_first = true` in that block is
**security-load-bearing**: it routes every request through your Worker first, so
an asset is served only when the Worker fetches it. That is what keeps
`__quickback/spec/` reachable exclusively through the gated spec routes — a
`publish: 'admin'` spec is not a public URL. Do not relax it.

> **Reading the spec from disk.** Tooling that expected `openapi.json` at the
> project root — `npx openapi-typescript openapi.json`, CI steps, lint rules —
> must point at `src/apps/__quickback/spec/openapi.json`. `api-types.gen.ts` is
> unaffected: the compiler still generates it at the project root for you, so
> most projects never need to run `openapi-typescript` by hand.


## Retired Paths

The compiler's output is a manifest of files to **write**. It has no way to say
"and drop the thing you wrote last time at a path I no longer use."

For most moves that gap is invisible: the CLI wipes `src/` from the manifest
before each write, so relocating a file *within* `src/` cleans up after itself.
It bites the moment a path crosses that boundary — v0.60 moved the spec blobs
from the project root into `src/apps/__quickback/spec/`, and the root copies
survived every later compile as multi-MB orphans that tooling kept reading.

So the compiler now also returns **retired paths** — things it used to emit and
no longer does — and the CLI deletes them:

```
  ● Removed 3 retired file(s):
      openapi.json (spec blobs moved under the assets dir so they leave the Worker bundle)
      asyncapi.json (spec blobs moved under the assets dir so they leave the Worker bundle)
      schema-registry.json (spec blobs moved under the assets dir so they leave the Worker bundle)
```

Every filter errs toward keeping a file, because a wrong deletion is
unrecoverable while a missed one is only untidy:

- A retired path that was **moved** is only deleted once its replacement is
  actually in this compile's manifest. Disable the surface
  (`openapi.generate: false`) and neither path is emitted, so nothing is
  removed.
- A path the current manifest still writes is **never** deleted — that is what
  keeps an older compiler, which still writes to the old location, from having
  its live output pulled out from under it.
- The CLI re-checks both conditions itself and refuses any path that would
  escape the output directory.

Nothing under `src/` is ever listed: the wipe already covers it.

## Generated Package Dependency Security

For projects using Better Auth, the generated `package.json` keeps the stable
`auth@1.6.x` development dependency that provides the `better-auth` executable
used by `auth:schema`. It also emits this npm override:

```json
{
  "overrides": {
    "lodash": "4.18.1"
  }
}
```

The override lifts the CLI's dev-only Prisma schema-parser chain off Lodash
versions affected by the 2026 code-injection and prototype-pollution
advisories. It does not change the Better Auth runtime package or generated
authentication behavior. Quickback preserves unrelated overrides already in
your package manifest, but the compiler-owned Lodash security pin wins if an
existing override conflicts.

A project that declares an in-worker Durable Object also gets `hono-party`
plus a second entry in the same block:

```json
{
  "overrides": {
    "hono-party": {
      "@cloudflare/workers-types": "$@cloudflare/workers-types"
    }
  }
}
```

`hono-party` still declares a peer dependency on `@cloudflare/workers-types`
major 4, which cannot be satisfied alongside the major 5 the compiler pins, so
a plain `npm install` would fail with `ERESOLVE`. The override is scoped to
`hono-party` — nothing else in the tree is relaxed — and the `$` form resolves
to whatever your `package.json` already declares, so it tracks the pin instead
of drifting from it. Projects without an in-worker Durable Object do not get
this entry.

After recompiling, run your package manager's install command so its lockfile
records the patched transitive version. Use `npm audit --omit=dev` when
evaluating the production dependency tree; development-tool advisories are a
separate review surface.

For Better Auth projects, `quickback compile` owns auth-schema generation.
The generated `build` script runs TypeScript only and never rewrites
`src/auth/schema.ts`, so build and deployment preflight checks preserve a clean
generated worktree. `auth:schema` remains an explicit diagnostic command; run
`quickback compile` afterward to restore canonical generated bytes.

On recompilation, Quickback replaces commands for script names it generates,
including `build`, with the current compiler output. Scripts with other names
are treated as project customizations and preserved. This lets existing
projects acquire build and deployment fixes without losing unrelated tooling.

## Optional Output Files

These files are generated only when the corresponding features are configured:

### Embeddings

When any feature has `embeddings` configured:

```
src/
├── lib/
│   └── embeddings.ts           # Embedding helpers
├── routes/
│   └── embeddings.ts           # POST /api/{v1|v2}/embeddings endpoint
└── queue-consumer.ts           # Queue handler for async embedding jobs
```

### File Storage (R2)

When `fileStorage` is configured:

```
src/
└── routes/
    └── storage.ts              # File upload/download endpoints
quickback/drizzle/
└── files/                      # File metadata migrations
    ├── meta/
    └── 0000_*.sql
```

### Webhooks

When webhooks are enabled:

```
src/
└── lib/
    └── webhooks/
        ├── index.ts            # Webhook module entry
        ├── sign.ts             # Webhook payload signing
        ├── handlers.ts         # Handler registry
        ├── emit.ts             # Queue emission helpers
        ├── routes.ts           # Inbound/outbound endpoints
        └── providers/
            ├── index.ts
            └── stripe.ts       # Stripe webhook handler
quickback/drizzle/
└── webhooks/                   # Webhook schema migrations
    ├── meta/
    └── 0000_*.sql
```

### Security Audit Database (Unsafe Actions)

When any action enables unsafe cross-tenant mode (`unsafe.crossTenant: true`):

```
src/
├── db/
│   └── audit-schema.ts         # audit_events table
└── lib/
    └── security-audit.ts       # mandatory audit writer

quickback/drizzle/
└── audit/
    ├── meta/
    └── 0000_*.sql

# Root
└── drizzle.audit.config.ts
```

Cloudflare output also includes an `AUDIT_DB` D1 binding in `wrangler.toml` and migration scripts:

- `db:migrate:audit:local`
- `db:migrate:audit:remote`

### Security Contract Report and Signature

Generated on every compile (unless disabled via `compiler.securityContracts.report.enabled: false`):

```
reports/
├── security-contracts.report.json      # Contract evaluation summary + violations
└── security-contracts.report.sig.json  # Signature / digest envelope for the report
```

The signature file uses HMAC-SHA256 when a signing key is configured, otherwise it falls back to SHA-256 digest mode.  
Set `compiler.securityContracts.report.signature.required: true` to fail compilation when a signing key is missing.

### Realtime

When any feature has `realtime` configured:

```
src/
├── lib/
│   ├── realtime.ts             # Broadcast helpers
│   ├── Broadcaster.ts          # Durable Object class (inline in main worker)
│   └── ws-ticket.ts            # WebSocket ticket auth utility
└── routes/
    └── ws-ticket.ts            # POST /realtime/v1/ws-ticket endpoint
```

The Broadcaster DO class is exported from your main worker entry point and runs inline — no separate `cloudflare-workers/broadcast/` deployment needed.

### Device Authorization

When the `deviceAuthorization` plugin is enabled:

```
src/
└── routes/
    └── cli-auth.ts             # Device auth flow endpoints
```

## Database Schemas

### Dual Database Mode (D1 default)

The compiler separates schemas into two files:

**`src/db/auth-schema.ts`** — Re-exports Better Auth table schemas:
- `users`, `sessions`, `accounts`
- `organizations`, `members`, `invitations` (if organizations enabled)
- Plugin-specific tables (`apiKeys`, etc.)

**`src/db/features-schema.ts`** — Re-exports your feature schemas:
- Every table declared with `feature()` or `defineTable()`
- The managed audit / soft-delete columns you declared on each table — `...q.audit()` / `...q.softDelete()` on the q path, the canonical Drizzle lines on the raw-Drizzle path

### Single Database Mode (`splitDatabases: false`)

**`src/db/schema.ts`** — Combined re-export of all schemas (auth + features).

## Generated Routes

For each feature, the compiler generates a routes file at `src/features/{name}/routes.ts` containing:

| Route | Generated When |
|-------|----------------|
| `GET /` | `crud.list` configured |
| `GET /:id` | `crud.get` configured |
| `POST /` | `crud.create` configured |
| `PATCH /:id` | `crud.update` configured |
| `DELETE /:id` | `crud.delete` configured |
| `PUT /:id` | `crud.put` configured |
| `POST /batch` | `crud.create` exists (auto-enabled) |
| `PATCH /batch` | `crud.update` exists (auto-enabled) |
| `DELETE /batch` | `crud.delete` exists (auto-enabled) |
| `PUT /batch` | `crud.put` exists (auto-enabled) |
| `GET /views/{name}` | `views` configured |
| `POST /:id/{action}` | Record-based actions defined |
| `POST /{action}` | Standalone actions defined |

Routes are mounted under `/api/v1/{feature}` by default, or
`/api/v2/{feature}` when `contract: { routes: "v2" }` is set. The standard API shape
selects semantics and leaves these paths alone.

## Migrations

The CLI runs the project-local `drizzle-kit generate` binary during compile to
produce SQL migration files. Generation happens **on your machine**, against
migration state that never leaves it — the compiler emits the schema that
drizzle diffs against, but never sees your migration history.

What the compile request carries instead of that history is two derived facts
about your prior state: the tables already deployed on a primary key that isn't
`id` (so a generated migration never proposes rewriting one), and whether the
Postgres audit schema was ever emitted (so a shrinking feature surface can't
generate a migration that drops the append-only forensic sink). Both are a few
hundred bytes, and neither grows with the age of your project.

Generation runs in a staging workspace and is committed only after it succeeds,
so a failed compile leaves your migration tree untouched. Already-applied files
are never rewritten: the run aborts if anything pre-existing would be modified
or removed.

If a compiler response returns migration generation as a post-compile command,
the command matches `build.packageManager`: `npm exec`, `pnpm exec`, `yarn exec`,
or `bun run`. These forms resolve the binary from the generated project's
dependencies. The npm form runs offline and declines npm's missing package
install prompt, so Quickback never downloads an unpinned Drizzle CLI as a
fallback. If the local binary is missing, install the generated project's
dependencies and compile again.

The CLI loads migration meta JSON from one location only — `<project>/quickback/drizzle/`:

- `quickback/drizzle/auth/meta/`
- `quickback/drizzle/features/meta/`
- `quickback/drizzle/files/meta/`
- `quickback/drizzle/webhooks/meta/`
- `quickback/drizzle/audit/meta/`
- `quickback/drizzle/meta/` (single-database mode)

Quickback owns this state — nothing under a project-root `drizzle/` folder is ever read or written.

Migration and report artifacts are written to the `quickback/` state directory:

- `quickback/drizzle/...` for Drizzle migration SQL/meta artifacts
- `quickback/reports/...` for security contract report artifacts

### Migration history is never wiped

Unlike `src/`, the `quickback/drizzle/` tree is **not** hermetic — it is history,
and history is only ever appended to or superseded.

Because generation is local, the compile response contains no migration files at
all and therefore carries no instruction to remove one. New migrations are
appended to the tree; nothing already there is rewritten.

That guarantee is enforced rather than assumed. The generator runs against a
staged copy and compares a checksum inventory of the tree before and after: if
any pre-existing `.sql`, snapshot, or journal tag changed or disappeared, the
compile fails with `MIGRATION_STATE_CHANGED` instead of committing. This matters
most on D1, which records applied migrations **by path** — a rewritten filename
is drift on a live database with no clean fix-forward.

Only `meta/_journal.json` is expected to change, because that is where a new
entry lands.

> If `quickback migrations doctor` reports snapshot drift — more journal entries
> than snapshot files — that drift came from your project's history, not from
> compiling. Compile does not delete snapshots it was never given.


For non-interactive environments (cloud compile, CI), table/column renames must be declared with `compiler.migrations.renames` in `quickback.config.ts`. This avoids interactive rename prompts during migration generation.

Migration files follow the Drizzle Kit naming convention:
```
0000_initial.sql
0001_add_status_column.sql
0002_create_orders_table.sql
```

## CMS and Account UI Assets

When `cms` and/or `account` are enabled, the compiler stages the generic prebuilt SPA bundles (built once per compiler release) into the output; per-project configuration reaches them at serve time via the worker-injected `window.__QUICKBACK_RUNTIME` blob.

Each SPA is placed in its own subdirectory under `src/apps/`:

```
src/apps/                          # Root assets directory
├── cms/                           # CMS SPA (served at /cms/ on unified domain)
│   ├── index.html
│   └── assets/
│       ├── app.abc123.js         # Content-hashed filenames from Vite
│       └── app.xyz456.css
├── account/                      # Account SPA (served at /account/ on unified domain)
│   ├── index.html
│   └── assets/
│       ├── app.def789.js
│       └── app.ghi012.css
└── __quickback/                   # Compiler-owned — reserved, never yours to write
    └── spec/                      # Spec blobs, fetched via the ASSETS binding
        ├── openapi.json
        ├── asyncapi.json
        └── schema-registry.json
```

On custom domains, each SPA is served at root (`/`) via hostname-based routing. On the unified domain, CMS is at `/cms/` and Account is at `/account/`.

### Static Assets (`quickback/public/`)

Anything you drop under `quickback/public/` is copied verbatim into `src/apps/` on every compile. The compiler does not parse, template, or otherwise touch these files — they are bytes that ride past it into the Cloudflare ASSETS binding.

```
quickback/public/                  # Source — checked into your repo
├── favicon.ico
├── robots.txt
├── og/
│   └── og-image.png
└── pdfs/
    └── whitepaper.pdf
```

After compile:

```
src/apps/                          # Destination — written by the CLI
├── favicon.ico                    # Served at https://your-domain/favicon.ico
├── robots.txt                     # Served at /robots.txt
├── og/og-image.png                # Served at /og/og-image.png
├── pdfs/whitepaper.pdf
├── cms/                           # Compiler-emitted SPA (don't shadow)
└── account/                       # Compiler-emitted SPA (don't shadow)
```

Notes:

- The folder is optional. If it doesn't exist, this step is a no-op.
- Files removed from `quickback/public/` disappear from `src/apps/` on the next compile — `src/` is wiped and rewritten each run.
- Hidden entries (`.DS_Store`, `.git/`, dotfiles) are skipped.
- A file in `quickback/public/` that would overwrite a compiler-emitted path (e.g. `quickback/public/cms/index.html`) fails the compile with a clear error rather than silently shadowing the SPA shell.
- Binaries (images, fonts, PDFs) are copied byte-for-byte — there is no string round-trip.

### Source Apps (`quickback/apps/`)

For hand-authored TypeScript apps and prebuilt SPA bundles that live alongside the compiler-emitted SPA shells, drop them under `quickback/apps/<name>/`. The CLI copies the tree into `src/apps/<name>/` on every compile, byte-for-byte, after the compiler has finished writing. Bind a directory to a hostname via [`config.apps`](/configure/domains#app-domains) — the compiler emits the matching Worker middleware automatically.

```
quickback/apps/                    # Source — checked into your repo
├── m/                             # A hand-authored mobile companion app
│   ├── index.tsx
│   ├── components/Button.tsx
│   └── lib/utils.ts
└── admin/                         # A hand-authored admin tool
    └── index.tsx
```

After compile:

```
src/apps/
├── m/index.tsx                    # Hand-authored — preserved across compiles
├── m/components/Button.tsx
├── m/lib/utils.ts
├── admin/index.tsx
├── cms/                           # Compiler-emitted SPA (don't shadow)
└── account/                       # Compiler-emitted SPA (don't shadow)
```

When to choose `apps/` vs `public/`:

| | `quickback/public/` | `quickback/apps/` |
|---|---|---|
| **Use for** | Static binaries (logos, fonts, OG images, PDFs, robots.txt) | Hand-authored TS/TSX/JS app source |
| **Skips** | Hidden entries only (`.DS_Store`, `.git/`) | Hidden entries + `node_modules/`, `dist/`, `build/`, `.next/`, `.turbo/`, `.cache/` |
| **Folder layout** | Files at any depth — flat or nested | Apps under named subfolders (`m/`, `admin/`, …) |

Notes:

- The folder is optional. If it doesn't exist, this step is a no-op.
- Files removed from `quickback/apps/<name>/` disappear from `src/apps/<name>/` on the next compile — `src/` is wiped and rewritten each run.
- A file in `quickback/apps/` that would overwrite a compiler-emitted path (e.g. `quickback/apps/cms/index.tsx` while `cms: true`) fails the compile with a clear error.
- Migrating from hand-edited `src/apps/<name>/`: move the entire folder under `quickback/apps/<name>/` once, then trust the compile cycle. The hermetic `src/` contract holds again.

### Generated Wrangler Assets Config

The compiler always generates `run_worker_first = true` when SPAs are enabled — the Worker handles all SPA routing (per hostname and per path prefix):

```toml
[assets]
binding = "ASSETS"
directory = "src/apps"
not_found_handling = "none"
run_worker_first = true
```

See [Multi-Domain Architecture](/configure/domains) for details on hostname routing.

## See Also

- [Providers](/configure/providers) — Configure runtime, database, and auth providers
- [Environment variables](/configure/variables) — Required variables by runtime
- [Multi-Domain Architecture](/configure/domains) — Custom domains and hostname routing
