@kardoe/quickback 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/content.js +7 -7
- package/dist/docs/content.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/skill/skill/SKILL.md +103 -3
- package/dist/skill/skill/agents/quickback-specialist/AGENT.md +29 -0
- package/package.json +1 -1
- package/src/skill/SKILL.md +103 -3
- package/src/skill/agents/quickback-specialist/AGENT.md +29 -0
package/dist/docs/content.js
CHANGED
|
@@ -147,11 +147,11 @@ export const DOCS = {
|
|
|
147
147
|
},
|
|
148
148
|
"compiler/config": {
|
|
149
149
|
"title": "Configuration",
|
|
150
|
-
"content": "The `quickback.config.ts` file configures your Quickback project. It defines which providers to use, which features to enable, and how the compiler generates code.\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: { organizations: true },\n providers: {\n runtime: defineRuntime(\"cloudflare\"),\n database: defineDatabase(\"cloudflare-d1\"),\n auth: defineAuth(\"better-auth\"),\n },\n});\n```\n\n## Required Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | `string` | Project name |\n| `template` | `\"hono\"` | Application template (`\"nextjs\"` is experimental) |\n| `providers` | `object` | Runtime, database, and auth provider configuration |\n\n## Optional Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `features` | `object` | Feature flags — see [Single-Tenant Mode](/compiler/config/single-tenant) |\n| `build` | `object` | Build options (`outputDir`, `packageManager`, `dependencies`) |\n| `compiler` | `object` | Compiler options (including migration rename hints for headless CI) |\n| `openapi` | `object` | OpenAPI spec generation (`generate`, `publish`) — see [OpenAPI](/compiler/using-the-api/openapi) |\n| `schemaRegistry` | `object` | Schema registry generation for the CMS (enabled by default, `{ generate: false }` to disable) — see [Schema Registry](/cms/schema-registry) |\n| `etag` | `object` | ETag caching for GET responses (enabled by default, `{ enabled: false }` to disable) — see [Caching & ETags](/compiler/using-the-api/caching) |\n| `cms` | `boolean \\| object` | Embed CMS in compiled Worker — see [CMS](/cms) |\n| `account` | `boolean \\| object` | Embed Account UI in compiled Worker — see [Account UI](/account-ui) |\n| `trustedOrigins` | `string[]` | CORS trusted origins for cross-domain auth |\n| `domain` | `string` | Primary API domain (auto-inferred from custom domains if not set) |\n| `email` | `object` | Email delivery config (`region`, `from`, `fromName`) |\n\n## Custom Dependencies\n\nAdd third-party npm packages to the generated `package.json` using `build.dependencies`. This is useful when your action handlers import external libraries.\n\n```typescript\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n build: {\n dependencies: {\n \"fast-xml-parser\": \"^4.5.0\",\n \"lodash-es\": \"^4.17.21\",\n },\n },\n providers: {\n runtime: defineRuntime(\"cloudflare\"),\n database: defineDatabase(\"cloudflare-d1\"),\n auth: defineAuth(\"better-auth\"),\n },\n});\n```\n\nThese are merged into the generated `package.json` alongside Quickback's own dependencies. Run `npm install` after compiling to install them.\n\n## Headless Migration Rename Hints\n\nWhen `drizzle-kit generate` detects a potential rename, it normally prompts for confirmation. Cloud/CI compiles are non-interactive, so you must provide explicit rename hints.\n\nAdd hints in `compiler.migrations.renames`:\n\n```typescript\nexport default defineConfig({\n // ...\n compiler: {\n migrations: {\n renames: {\n // Keys are NEW names, values are OLD names\n tables: {\n events_v2: \"events\",\n },\n columns: {\n events: {\n summary_text: \"summary\",\n },\n },\n },\n },\n },\n});\n```\n\nRules:\n- `tables`: `new_table_name -> old_table_name`\n- `columns.<table>`: `new_column_name -> old_column_name`\n- Keys must match the names in your current schema (the new names)\n- Validation fails fast for malformed rename config, unsupported keys, or conflicting legacy/new rename paths.\n\n## Security Contract Reports and Signing\n\nAfter generation, Quickback verifies machine-checkable security contracts for Hono routes and RLS SQL.\n\nIt also emits a report artifact and signature artifact by default:\n\n- `reports/security-contracts.report.json`\n- `reports/security-contracts.report.sig.json`\n\nYou can configure output paths and signing behavior:\n\n```typescript\nexport default defineConfig({\n // ...\n compiler: {\n securityContracts: {\n failMode: \"error\", // or \"warn\"\n report: {\n path: \"reports/security-contracts.report.json\",\n signature: {\n enabled: true,\n required: true,\n keyEnv: \"QUICKBACK_SECURITY_REPORT_SIGNING_KEY\",\n keyId: \"prod-k1\",\n path: \"reports/security-contracts.report.sig.json\",\n },\n },\n },\n },\n});\n```\n\nIf `signature.required: true` and no key is available, compilation fails with a clear error message (or warning in `failMode: \"warn\"`).\n\n## CMS Configuration\n\nEmbed the [Quickback CMS](/cms) in your compiled Worker. The CMS reads your schema registry and renders a complete admin interface — zero UI code per table.\n\n```typescript\n// Simple — embed CMS at root\ncms: true,\n\n// With custom domain\ncms: { domain: \"cms.example.com\" },\n\n// With access control (only admins see CMS link in Account UI)\ncms: { domain: \"cms.example.com\", access: \"admin\" },\n```\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `domain` | `string` | — | Custom domain for CMS. Adds a `custom_domain` route to `wrangler.toml`. |\n| `access` | `\"user\" \\| \"admin\"` | `\"user\"` | Who can see the \"Go to CMS\" button in Account UI. Set to `\"admin\"` to restrict the link to admin users only. |\n\n## Account UI Configuration\n\nEmbed the [Account UI](/account-ui) in your compiled Worker. Provides login, signup, profile management, organization management, and admin — all built from your config at compile time.\n\n```typescript\n// Simple — embed Account UI with defaults\naccount: true,\n\n// Full configuration\naccount: {\n domain: \"auth.example.com\",\n adminDomain: \"admin.example.com\",\n name: \"My App\",\n companyName: \"My Company\",\n auth: {\n password: true,\n emailOTP: false,\n passkey: true,\n organizations: true,\n admin: true,\n },\n},\n```\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `domain` | `string` | Custom domain for Account UI (e.g., `auth.example.com`) |\n| `adminDomain` | `string` | Custom domain for admin-only access (e.g., `admin.example.com`). Serves the same Account SPA — the client-side router handles admin routing. |\n| `name` | `string` | App display name shown on login, profile, and email templates |\n| `companyName` | `string` | Company name for branding |\n| `tagline` | `string` | App tagline shown on login page |\n| `description` | `string` | App description |\n\n### Auth Feature Flags\n\nControl which authentication features are included in the Account UI bundle. Disabled features are excluded at compile time — their routes are removed before the Vite build, keeping bundle sizes minimal.\n\n| Flag | Type | Default | Description |\n|------|------|---------|-------------|\n| `auth.password` | `boolean` | `true` | Email/password authentication |\n| `auth.emailOTP` | `boolean` | `false` | Email one-time password login |\n| `auth.passkey` | `boolean` | `false` | WebAuthn/FIDO2 passkey authentication |\n| `auth.signup` | `boolean` | `true` | Self-service account registration |\n| `auth.organizations` | `boolean` | `false` | Multi-tenant organization management (dashboard, invitations, roles) |\n| `auth.admin` | `boolean` | `false` | Admin panel for user management (requires Better Auth admin plugin) |\n| `auth.emailVerification` | `boolean` | `false` | Require email verification after signup |\n\n## Trusted Origins\n\nList of origins allowed to make cross-origin requests to your API. Required when your CMS, Account UI, or frontend app run on different domains.\n\n```typescript\ntrustedOrigins: [\n \"https://auth.example.com\",\n \"https://admin.example.com\",\n \"https://cms.example.com\",\n \"https://app.example.com\",\n],\n```\n\n## Email Configuration\n\nConfigure email delivery for authentication emails (verification, password reset, invitations). Requires AWS SES credentials set as secrets.\n\n```typescript\nemail: {\n region: \"us-east-2\",\n from: \"noreply@example.com\",\n fromName: \"My App\",\n},\n```\n\n## Multi-Domain Example\n\nA complete config with CMS, Account UI, and Admin on separate subdomains:\n\n```typescript\nexport default {\n name: \"my-app\",\n template: \"hono\",\n cms: { domain: \"cms.example.com\", access: \"admin\" },\n account: {\n domain: \"auth.example.com\",\n adminDomain: \"admin.example.com\",\n name: \"My App\",\n companyName: \"Example Inc.\",\n auth: {\n password: true,\n emailOTP: false,\n passkey: true,\n organizations: false,\n admin: true,\n },\n },\n email: {\n region: \"us-east-2\",\n from: \"noreply@example.com\",\n fromName: \"My App\",\n },\n trustedOrigins: [\n \"https://auth.example.com\",\n \"https://admin.example.com\",\n \"https://cms.example.com\",\n \"https://example.com\",\n ],\n providers: {\n runtime: { name: \"cloudflare\", config: {\n routes: [{ pattern: \"api.example.com\", custom_domain: true }],\n }},\n database: { name: \"cloudflare-d1\", config: { binding: \"DB\" } },\n auth: { name: \"better-auth\", config: { plugins: [\"admin\"] } },\n },\n};\n```\n\nThis produces a single Cloudflare Worker serving five domains. See [Multi-Domain Architecture](/compiler/config/domains) for details on hostname routing, cross-subdomain cookies, and the unified fallback domain.\n\n---\n\nSee the sub-pages for detailed reference on each section:\n- [Providers](/compiler/config/providers) — Runtime, database, and auth options\n- [Variables](/compiler/config/variables) — Environment variables and flags\n- [Output](/compiler/config/output) — Generated file structure\n- [Single-Tenant Mode](/compiler/config/single-tenant) — Admin-only and public-facing apps without organizations\n- [Multi-Domain Architecture](/compiler/config/domains) — Custom domains, hostname routing, and cross-subdomain auth"
|
|
150
|
+
"content": "The `quickback.config.ts` file configures your Quickback project. It defines which providers to use, which features to enable, and how the compiler generates code.\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: { organizations: true },\n providers: {\n runtime: defineRuntime(\"cloudflare\"),\n database: defineDatabase(\"cloudflare-d1\"),\n auth: defineAuth(\"better-auth\"),\n },\n});\n```\n\n## Required Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | `string` | Project name |\n| `template` | `\"hono\"` | Application template (`\"nextjs\"` is experimental) |\n| `providers` | `object` | Runtime, database, and auth provider configuration |\n\n## Optional Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `features` | `object` | Feature flags — see [Single-Tenant Mode](/compiler/config/single-tenant) |\n| `build` | `object` | Build options (`outputDir`, `packageManager`, `dependencies`) |\n| `compiler` | `object` | Compiler options (including migration rename hints for headless CI) |\n| `openapi` | `object` | OpenAPI spec generation (`generate`, `publish`) — see [OpenAPI](/compiler/using-the-api/openapi) |\n| `schemaRegistry` | `object` | Schema registry generation for the CMS (enabled by default, `{ generate: false }` to disable) — see [Schema Registry](/cms/schema-registry) |\n| `etag` | `object` | ETag caching for GET responses (enabled by default, `{ enabled: false }` to disable) — see [Caching & ETags](/compiler/using-the-api/caching) |\n| `cms` | `boolean \\| object` | Embed CMS in compiled Worker — see [CMS](/cms) |\n| `account` | `boolean \\| object` | Embed Account UI in compiled Worker — see [Account UI](/account-ui) |\n| `trustedOrigins` | `string[]` | CORS trusted origins for cross-domain auth |\n| `domain` | `string` | Primary API domain (auto-inferred from custom domains if not set) |\n| `email` | `object` | Email delivery config (`provider`, `from`, `fromName`) |\n\n## Custom Dependencies\n\nAdd third-party npm packages to the generated `package.json` using `build.dependencies`. This is useful when your action handlers import external libraries.\n\n```typescript\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n build: {\n dependencies: {\n \"fast-xml-parser\": \"^4.5.0\",\n \"lodash-es\": \"^4.17.21\",\n },\n },\n providers: {\n runtime: defineRuntime(\"cloudflare\"),\n database: defineDatabase(\"cloudflare-d1\"),\n auth: defineAuth(\"better-auth\"),\n },\n});\n```\n\nThese are merged into the generated `package.json` alongside Quickback's own dependencies. Run `npm install` after compiling to install them.\n\n## Headless Migration Rename Hints\n\nWhen `drizzle-kit generate` detects a potential rename, it normally prompts for confirmation. Cloud/CI compiles are non-interactive, so you must provide explicit rename hints.\n\nAdd hints in `compiler.migrations.renames`:\n\n```typescript\nexport default defineConfig({\n // ...\n compiler: {\n migrations: {\n renames: {\n // Keys are NEW names, values are OLD names\n tables: {\n events_v2: \"events\",\n },\n columns: {\n events: {\n summary_text: \"summary\",\n },\n },\n },\n },\n },\n});\n```\n\nRules:\n- `tables`: `new_table_name -> old_table_name`\n- `columns.<table>`: `new_column_name -> old_column_name`\n- Keys must match the names in your current schema (the new names)\n- Validation fails fast for malformed rename config, unsupported keys, or conflicting legacy/new rename paths.\n\n## Security Contract Reports and Signing\n\nAfter generation, Quickback verifies machine-checkable security contracts for Hono routes and RLS SQL.\n\nIt also emits a report artifact and signature artifact by default:\n\n- `reports/security-contracts.report.json`\n- `reports/security-contracts.report.sig.json`\n\nYou can configure output paths and signing behavior:\n\n```typescript\nexport default defineConfig({\n // ...\n compiler: {\n securityContracts: {\n failMode: \"error\", // or \"warn\"\n report: {\n path: \"reports/security-contracts.report.json\",\n signature: {\n enabled: true,\n required: true,\n keyEnv: \"QUICKBACK_SECURITY_REPORT_SIGNING_KEY\",\n keyId: \"prod-k1\",\n path: \"reports/security-contracts.report.sig.json\",\n },\n },\n },\n },\n});\n```\n\nIf `signature.required: true` and no key is available, compilation fails with a clear error message (or warning in `failMode: \"warn\"`).\n\n## CMS Configuration\n\nEmbed the [Quickback CMS](/cms) in your compiled Worker. The CMS reads your schema registry and renders a complete admin interface — zero UI code per table.\n\n```typescript\n// Simple — embed CMS at root\ncms: true,\n\n// With custom domain\ncms: { domain: \"cms.example.com\" },\n\n// With access control (only admins see CMS link in Account UI)\ncms: { domain: \"cms.example.com\", access: \"admin\" },\n```\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `domain` | `string` | — | Custom domain for CMS. Adds a `custom_domain` route to `wrangler.toml`. |\n| `access` | `\"user\" \\| \"admin\"` | `\"user\"` | Who can see the \"Go to CMS\" button in Account UI. Set to `\"admin\"` to restrict the link to admin users only. |\n\n## Account UI Configuration\n\nEmbed the [Account UI](/account-ui) in your compiled Worker. Provides login, signup, profile management, organization management, and admin — all built from your config at compile time.\n\n```typescript\n// Simple — embed Account UI with defaults\naccount: true,\n\n// Full configuration\naccount: {\n domain: \"auth.example.com\",\n adminDomain: \"admin.example.com\",\n name: \"My App\",\n companyName: \"My Company\",\n auth: {\n password: true,\n emailOTP: false,\n passkey: true,\n organizations: true,\n admin: true,\n },\n},\n```\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `domain` | `string` | Custom domain for Account UI (e.g., `auth.example.com`) |\n| `adminDomain` | `string` | Custom domain for admin-only access (e.g., `admin.example.com`). Serves the same Account SPA — the client-side router handles admin routing. |\n| `name` | `string` | App display name shown on login, profile, and email templates |\n| `companyName` | `string` | Company name for branding |\n| `tagline` | `string` | App tagline shown on login page |\n| `description` | `string` | App description |\n\n### Auth Feature Flags\n\nControl which authentication features are included in the Account UI bundle. Disabled features are excluded at compile time — their routes are removed before the Vite build, keeping bundle sizes minimal.\n\n| Flag | Type | Default | Description |\n|------|------|---------|-------------|\n| `auth.password` | `boolean` | `true` | Email/password authentication |\n| `auth.emailOTP` | `boolean` | `false` | Email one-time password login |\n| `auth.passkey` | `boolean` | `false` | WebAuthn/FIDO2 passkey authentication |\n| `auth.signup` | `boolean` | `true` | Self-service account registration |\n| `auth.organizations` | `boolean` | `false` | Multi-tenant organization management (dashboard, invitations, roles) |\n| `auth.admin` | `boolean` | `false` | Admin panel for user management (requires Better Auth admin plugin) |\n| `auth.emailVerification` | `boolean` | `false` | Require email verification after signup |\n\n## Trusted Origins\n\nList of origins allowed to make cross-origin requests to your API. Required when your CMS, Account UI, or frontend app run on different domains.\n\n```typescript\ntrustedOrigins: [\n \"https://auth.example.com\",\n \"https://admin.example.com\",\n \"https://cms.example.com\",\n \"https://app.example.com\",\n],\n```\n\n## Email Configuration\n\nConfigure email delivery for authentication emails (verification, password reset, invitations).\n\n```typescript\n// Cloudflare Email Service (default) — uses send_email binding, no API keys needed\nemail: {\n from: \"noreply@example.com\",\n fromName: \"My App\",\n},\n\n// AWS SES — requires AWS credentials set as Wrangler secrets\nemail: {\n provider: \"aws-ses\",\n region: \"us-east-2\",\n from: \"noreply@example.com\",\n fromName: \"My App\",\n},\n```\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `provider` | `\"cloudflare\" \\| \"aws-ses\"` | `\"cloudflare\"` | Email transport |\n| `from` | `string` | `\"noreply@example.com\"` | Sender email address |\n| `fromName` | `string` | `\"{name} \\| Account Services\"` | Sender display name |\n| `region` | `string` | `\"us-east-2\"` | AWS SES region (aws-ses only) |\n\n## Multi-Domain Example\n\nA complete config with CMS, Account UI, and Admin on separate subdomains:\n\n```typescript\nexport default {\n name: \"my-app\",\n template: \"hono\",\n cms: { domain: \"cms.example.com\", access: \"admin\" },\n account: {\n domain: \"auth.example.com\",\n adminDomain: \"admin.example.com\",\n name: \"My App\",\n companyName: \"Example Inc.\",\n auth: {\n password: true,\n emailOTP: false,\n passkey: true,\n organizations: false,\n admin: true,\n },\n },\n email: {\n from: \"noreply@example.com\",\n fromName: \"My App\",\n },\n trustedOrigins: [\n \"https://auth.example.com\",\n \"https://admin.example.com\",\n \"https://cms.example.com\",\n \"https://example.com\",\n ],\n providers: {\n runtime: { name: \"cloudflare\", config: {\n routes: [{ pattern: \"api.example.com\", custom_domain: true }],\n }},\n database: { name: \"cloudflare-d1\", config: { binding: \"DB\" } },\n auth: { name: \"better-auth\", config: { plugins: [\"admin\"] } },\n },\n};\n```\n\nThis produces a single Cloudflare Worker serving five domains. See [Multi-Domain Architecture](/compiler/config/domains) for details on hostname routing, cross-subdomain cookies, and the unified fallback domain.\n\n---\n\nSee the sub-pages for detailed reference on each section:\n- [Providers](/compiler/config/providers) — Runtime, database, and auth options\n- [Variables](/compiler/config/variables) — Environment variables and flags\n- [Output](/compiler/config/output) — Generated file structure\n- [Single-Tenant Mode](/compiler/config/single-tenant) — Admin-only and public-facing apps without organizations\n- [Multi-Domain Architecture](/compiler/config/domains) — Custom domains, hostname routing, and cross-subdomain auth"
|
|
151
151
|
},
|
|
152
152
|
"compiler/config/output": {
|
|
153
153
|
"title": "Output Structure",
|
|
154
|
-
"content": "The compiler generates a complete project structure based on your definitions and provider configuration. The output varies depending on your runtime (Cloudflare vs Bun) and enabled features.\n\n**Warning:** Never edit files in `src/` directly. They are overwritten on every compile. Make changes in your `quickback/` definitions instead.\n\n## Cloudflare Output\n\n```\nsrc/\n├── index.ts # Hono app entry point (Workers export)\n├── env.d.ts # Cloudflare bindings TypeScript types\n├── db/\n│ ├── index.ts # Database connection factory\n│ ├── auth-schema.ts # Auth table schemas (dual mode)\n│ └── features-schema.ts # Feature table schemas (dual mode)\n├── auth/\n│ └── index.ts # Better Auth instance & config\n├── features/\n│ └── {feature}/\n│ ├── schema.ts # Drizzle table definition\n│ ├── routes.ts # CRUD + action endpoints\n│ └── actions.ts # Action handlers (if defined)\n├── lib/\n│ ├── access.ts # Access control helpers\n│ ├── types.ts # Runtime type definitions\n│ ├── masks.ts # Field masking utilities\n│ ├── services.ts # Service layer\n│ ├── audit-wrapper.ts # Auto-inject audit fields\n│ └── security-audit.ts # Unsafe cross-tenant audit logger (when needed)\n└── middleware/\n ├── auth.ts # Auth context middleware\n ├── db.ts # Database instance middleware\n └── services.ts # Service injection middleware\n\nquickback/drizzle/\n├── auth/ # Auth migrations (dual mode)\n│ ├── meta/\n│ │ ├── _journal.json\n│ │ └── 0000_snapshot.json\n│ └── 0000_initial.sql\n├── features/ # Feature migrations (dual mode)\n│ ├── meta/\n│ │ ├── _journal.json\n│ │ └── 0000_snapshot.json\n│ └── 0000_initial.sql\n└── audit/ # Unsafe cross-tenant action audit migrations (when needed)\n ├── meta/\n └── 0000_initial.sql\n\n# Root config files\n├── package.json\n├── tsconfig.json\n├── wrangler.toml # Cloudflare Workers config\n├── drizzle.config.ts # Features DB drizzle config\n├── drizzle.auth.config.ts # Auth DB drizzle config (dual mode)\n└── drizzle.audit.config.ts # Audit DB drizzle config (when unsafe actions exist)\n```\n\n## Bun Output\n\n```\nsrc/\n├── index.ts # Hono app entry point (Bun server)\n├── db/\n│ ├── index.ts # SQLite connection (bun:sqlite)\n│ └── schema.ts # Combined schema (single DB)\n├── auth/\n│ └── index.ts # Better Auth instance\n├── features/\n│ └── {feature}/\n│ ├── schema.ts\n│ ├── routes.ts\n│ └── actions.ts\n├── lib/\n│ ├── access.ts\n│ ├── types.ts\n│ ├── masks.ts\n│ ├── services.ts\n│ └── audit-wrapper.ts\n└── middleware/\n ├── auth.ts\n ├── db.ts\n └── services.ts\n\nquickback/drizzle/ # Single migration directory\n├── meta/\n│ ├── _journal.json\n│ └── 0000_snapshot.json\n└── 0000_initial.sql\n\ndata/ # SQLite database files\n└── app.db\n\n├── package.json\n├── tsconfig.json\n└── drizzle.config.ts\n```\n\n## Key Differences by Runtime\n\n| Aspect | Cloudflare | Bun |\n|--------|-----------|-----|\n| Entry point | Workers `export default` | `Bun.serve()` with port |\n| Database | D1 bindings (dual mode) | SQLite file (single DB) |\n| Types | `env.d.ts` for bindings | No extra types needed |\n| Config | `wrangler.toml` | `.env` file |\n| Migrations | `quickback/drizzle/auth/` + `quickback/drizzle/features/` | `quickback/drizzle/` |\n| Drizzle configs | 2 configs (auth + features) | 1 config |\n\n## Optional Output Files\n\nThese files are generated only when the corresponding features are configured:\n\n### Embeddings\n\nWhen any feature has `embeddings` configured:\n\n```\nsrc/\n├── lib/\n│ └── embeddings.ts # Embedding helpers\n├── routes/\n│ └── embeddings.ts # POST /api/v1/embeddings endpoint\n└── queue-consumer.ts # Queue handler for async embedding jobs\n```\n\n### File Storage (R2)\n\nWhen `fileStorage` is configured:\n\n```\nsrc/\n└── routes/\n └── storage.ts # File upload/download endpoints\nquickback/drizzle/\n└── files/ # File metadata migrations\n ├── meta/\n └── 0000_*.sql\n```\n\n### Webhooks\n\nWhen webhooks are enabled:\n\n```\nsrc/\n└── lib/\n └── webhooks/\n ├── index.ts # Webhook module entry\n ├── sign.ts # Webhook payload signing\n ├── handlers.ts # Handler registry\n ├── emit.ts # Queue emission helpers\n ├── routes.ts # Inbound/outbound endpoints\n └── providers/\n ├── index.ts\n └── stripe.ts # Stripe webhook handler\nquickback/drizzle/\n└── webhooks/ # Webhook schema migrations\n ├── meta/\n └── 0000_*.sql\n```\n\n### Security Audit Database (Unsafe Actions)\n\nWhen any action enables unsafe cross-tenant mode (`unsafe.crossTenant: true`):\n\n```\nsrc/\n├── db/\n│ └── audit-schema.ts # audit_events table\n└── lib/\n └── security-audit.ts # mandatory audit writer\n\nquickback/drizzle/\n└── audit/\n ├── meta/\n └── 0000_*.sql\n\n# Root\n└── drizzle.audit.config.ts\n```\n\nCloudflare output also includes an `AUDIT_DB` D1 binding in `wrangler.toml` and migration scripts:\n\n- `db:migrate:audit:local`\n- `db:migrate:audit:remote`\n\n### Security Contract Report and Signature\n\nGenerated on every compile (unless disabled via `compiler.securityContracts.report.enabled: false`):\n\n```\nreports/\n├── security-contracts.report.json # Contract evaluation summary + violations\n└── security-contracts.report.sig.json # Signature / digest envelope for the report\n```\n\nThe signature file uses HMAC-SHA256 when a signing key is configured, otherwise it falls back to SHA-256 digest mode. \nSet `compiler.securityContracts.report.signature.required: true` to fail compilation when a signing key is missing.\n\n### Realtime\n\nWhen any feature has `realtime` configured:\n\n```\nsrc/\n└── lib/\n └── realtime.ts # Broadcast helpers\n\ncloudflare-workers/ # Separate Durable Objects worker\n└── broadcast/\n ├── Broadcaster.ts\n ├── index.ts\n └── wrangler.toml\n```\n\n### Device Authorization\n\nWhen the `deviceAuthorization` plugin is enabled:\n\n```\nsrc/\n└── routes/\n └── cli-auth.ts # Device auth flow endpoints\n```\n\n## Database Schemas\n\n### Dual Database Mode (Cloudflare Default)\n\nThe compiler separates schemas into two files:\n\n**`src/db/auth-schema.ts`** — Re-exports Better Auth table schemas:\n- `users`, `sessions`, `accounts`\n- `organizations`, `members`, `invitations` (if organizations enabled)\n- Plugin-specific tables (`apiKeys`, etc.)\n\n**`src/db/features-schema.ts`** — Re-exports your feature schemas:\n- All tables defined with `defineTable()`\n- Audit field columns added automatically\n\n### Single Database Mode (Bun Default)\n\n**`src/db/schema.ts`** — Combined re-export of all schemas (auth + features).\n\n## Generated Routes\n\nFor each feature, the compiler generates a routes file at `src/features/{name}/routes.ts` containing:\n\n| Route | Generated When |\n|-------|----------------|\n| `GET /` | `crud.list` configured |\n| `GET /:id` | `crud.get` configured |\n| `POST /` | `crud.create` configured |\n| `PATCH /:id` | `crud.update` configured |\n| `DELETE /:id` | `crud.delete` configured |\n| `PUT /:id` | `crud.put` configured |\n| `POST /batch` | `crud.create` exists (auto-enabled) |\n| `PATCH /batch` | `crud.update` exists (auto-enabled) |\n| `DELETE /batch` | `crud.delete` exists (auto-enabled) |\n| `PUT /batch` | `crud.put` exists (auto-enabled) |\n| `GET /views/{name}` | `views` configured |\n| `POST /:id/{action}` | Record-based actions defined |\n| `POST /{action}` | Standalone actions defined |\n\nAll routes are mounted under `/api/v1/{feature}` in the main app.\n\n## Migrations\n\nThe compiler runs `drizzle-kit generate` during compilation to produce SQL migration files. On subsequent compiles, it uses `existingFiles` (your current migration state) to generate only incremental changes.\n\nThe CLI loads migration meta JSON from:\n\n- `quickback/drizzle/auth/meta/`\n- `quickback/drizzle/features/meta/`\n- `quickback/drizzle/files/meta/`\n- `quickback/drizzle/webhooks/meta/`\n- `quickback/drizzle/meta/` (single database mode)\n- `drizzle/...` (legacy paths, checked as fallback)\n\nMigration and report artifacts are written to the `quickback/` state directory:\n\n- `quickback/drizzle/...` for Drizzle migration SQL/meta artifacts\n- `quickback/reports/...` for security contract report artifacts\n\nFor 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.\n\nMigration files follow the Drizzle Kit naming convention:\n```\n0000_initial.sql\n0001_add_status_column.sql\n0002_create_orders_table.sql\n```\n\n## CMS and Account UI Assets\n\nWhen `cms` and/or `account` are enabled, the compiler builds the SPAs from source at compile time and includes the assets in the output.\n\nEach SPA is placed in its own subdirectory under `src/apps/`:\n\n```\nsrc/apps/ # Root assets directory\n├── cms/ # CMS SPA (served at /cms/ on unified domain)\n│ ├── index.html\n│ └── assets/\n│ ├── app.abc123.js # Content-hashed filenames from Vite\n│ └── app.xyz456.css\n└── account/ # Account SPA (served at /account/ on unified domain)\n ├── index.html\n └── assets/\n ├── app.def789.js\n └── app.ghi012.css\n```\n\nOn 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/`.\n\n### Generated Wrangler Assets Config\n\nThe compiler always generates `run_worker_first = true` when SPAs are enabled — the Worker handles all SPA routing (per hostname and per path prefix):\n\n```toml\n[assets]\nbinding = \"ASSETS\"\ndirectory = \"src/apps\"\nnot_found_handling = \"none\"\nrun_worker_first = true\n```\n\nSee [Multi-Domain Architecture](/compiler/config/domains) for details on hostname routing.\n\n## See Also\n\n- [Providers](/compiler/config/providers) — Configure runtime, database, and auth providers\n- [Environment variables](/compiler/config/variables) — Required variables by runtime\n- [Multi-Domain Architecture](/compiler/config/domains) — Custom domains and hostname routing"
|
|
154
|
+
"content": "The compiler generates a complete project structure based on your definitions and provider configuration. The output varies depending on your runtime (Cloudflare vs Bun) and enabled features.\n\n**Warning:** Never edit files in `src/` directly. They are overwritten on every compile. Make changes in your `quickback/` definitions instead.\n\n## Cloudflare Output\n\n```\nsrc/\n├── index.ts # Hono app entry point (Workers export)\n├── env.d.ts # Cloudflare bindings TypeScript types\n├── db/\n│ ├── index.ts # Database connection factory\n│ ├── auth-schema.ts # Auth table schemas (dual mode)\n│ └── features-schema.ts # Feature table schemas (dual mode)\n├── auth/\n│ └── index.ts # Better Auth instance & config\n├── features/\n│ └── {feature}/\n│ ├── schema.ts # Drizzle table definition\n│ ├── routes.ts # CRUD + action endpoints\n│ └── actions.ts # Action handlers (if defined)\n├── lib/\n│ ├── access.ts # Access control helpers\n│ ├── types.ts # Runtime type definitions\n│ ├── masks.ts # Field masking utilities\n│ ├── services.ts # Service layer\n│ ├── audit-wrapper.ts # Auto-inject audit fields\n│ └── security-audit.ts # Unsafe cross-tenant audit logger (when needed)\n└── middleware/\n ├── auth.ts # Auth context middleware\n ├── db.ts # Database instance middleware\n └── services.ts # Service injection middleware\n\nquickback/drizzle/\n├── auth/ # Auth migrations (dual mode)\n│ ├── meta/\n│ │ ├── _journal.json\n│ │ └── 0000_snapshot.json\n│ └── 0000_initial.sql\n├── features/ # Feature migrations (dual mode)\n│ ├── meta/\n│ │ ├── _journal.json\n│ │ └── 0000_snapshot.json\n│ └── 0000_initial.sql\n└── audit/ # Unsafe cross-tenant action audit migrations (when needed)\n ├── meta/\n └── 0000_initial.sql\n\n# Root config files\n├── package.json\n├── tsconfig.json\n├── wrangler.toml # Cloudflare Workers config\n├── drizzle.config.ts # Features DB drizzle config\n├── drizzle.auth.config.ts # Auth DB drizzle config (dual mode)\n└── drizzle.audit.config.ts # Audit DB drizzle config (when unsafe actions exist)\n```\n\n## Bun Output\n\n```\nsrc/\n├── index.ts # Hono app entry point (Bun server)\n├── db/\n│ ├── index.ts # SQLite connection (bun:sqlite)\n│ └── schema.ts # Combined schema (single DB)\n├── auth/\n│ └── index.ts # Better Auth instance\n├── features/\n│ └── {feature}/\n│ ├── schema.ts\n│ ├── routes.ts\n│ └── actions.ts\n├── lib/\n│ ├── access.ts\n│ ├── types.ts\n│ ├── masks.ts\n│ ├── services.ts\n│ └── audit-wrapper.ts\n└── middleware/\n ├── auth.ts\n ├── db.ts\n └── services.ts\n\nquickback/drizzle/ # Single migration directory\n├── meta/\n│ ├── _journal.json\n│ └── 0000_snapshot.json\n└── 0000_initial.sql\n\ndata/ # SQLite database files\n└── app.db\n\n├── package.json\n├── tsconfig.json\n└── drizzle.config.ts\n```\n\n## Key Differences by Runtime\n\n| Aspect | Cloudflare | Bun |\n|--------|-----------|-----|\n| Entry point | Workers `export default` | `Bun.serve()` with port |\n| Database | D1 bindings (dual mode) | SQLite file (single DB) |\n| Types | `env.d.ts` for bindings | No extra types needed |\n| Config | `wrangler.toml` | `.env` file |\n| Migrations | `quickback/drizzle/auth/` + `quickback/drizzle/features/` | `quickback/drizzle/` |\n| Drizzle configs | 2 configs (auth + features) | 1 config |\n\n## Optional Output Files\n\nThese files are generated only when the corresponding features are configured:\n\n### Embeddings\n\nWhen any feature has `embeddings` configured:\n\n```\nsrc/\n├── lib/\n│ └── embeddings.ts # Embedding helpers\n├── routes/\n│ └── embeddings.ts # POST /api/v1/embeddings endpoint\n└── queue-consumer.ts # Queue handler for async embedding jobs\n```\n\n### File Storage (R2)\n\nWhen `fileStorage` is configured:\n\n```\nsrc/\n└── routes/\n └── storage.ts # File upload/download endpoints\nquickback/drizzle/\n└── files/ # File metadata migrations\n ├── meta/\n └── 0000_*.sql\n```\n\n### Webhooks\n\nWhen webhooks are enabled:\n\n```\nsrc/\n└── lib/\n └── webhooks/\n ├── index.ts # Webhook module entry\n ├── sign.ts # Webhook payload signing\n ├── handlers.ts # Handler registry\n ├── emit.ts # Queue emission helpers\n ├── routes.ts # Inbound/outbound endpoints\n └── providers/\n ├── index.ts\n └── stripe.ts # Stripe webhook handler\nquickback/drizzle/\n└── webhooks/ # Webhook schema migrations\n ├── meta/\n └── 0000_*.sql\n```\n\n### Security Audit Database (Unsafe Actions)\n\nWhen any action enables unsafe cross-tenant mode (`unsafe.crossTenant: true`):\n\n```\nsrc/\n├── db/\n│ └── audit-schema.ts # audit_events table\n└── lib/\n └── security-audit.ts # mandatory audit writer\n\nquickback/drizzle/\n└── audit/\n ├── meta/\n └── 0000_*.sql\n\n# Root\n└── drizzle.audit.config.ts\n```\n\nCloudflare output also includes an `AUDIT_DB` D1 binding in `wrangler.toml` and migration scripts:\n\n- `db:migrate:audit:local`\n- `db:migrate:audit:remote`\n\n### Security Contract Report and Signature\n\nGenerated on every compile (unless disabled via `compiler.securityContracts.report.enabled: false`):\n\n```\nreports/\n├── security-contracts.report.json # Contract evaluation summary + violations\n└── security-contracts.report.sig.json # Signature / digest envelope for the report\n```\n\nThe signature file uses HMAC-SHA256 when a signing key is configured, otherwise it falls back to SHA-256 digest mode. \nSet `compiler.securityContracts.report.signature.required: true` to fail compilation when a signing key is missing.\n\n### Realtime\n\nWhen any feature has `realtime` configured:\n\n```\nsrc/\n├── lib/\n│ ├── realtime.ts # Broadcast helpers\n│ ├── Broadcaster.ts # Durable Object class (inline in main worker)\n│ └── ws-ticket.ts # WebSocket ticket auth utility\n└── routes/\n └── ws-ticket.ts # POST /realtime/v1/ws-ticket endpoint\n```\n\nThe Broadcaster DO class is exported from your main worker entry point and runs inline — no separate `cloudflare-workers/broadcast/` deployment needed.\n\n### Device Authorization\n\nWhen the `deviceAuthorization` plugin is enabled:\n\n```\nsrc/\n└── routes/\n └── cli-auth.ts # Device auth flow endpoints\n```\n\n## Database Schemas\n\n### Dual Database Mode (Cloudflare Default)\n\nThe compiler separates schemas into two files:\n\n**`src/db/auth-schema.ts`** — Re-exports Better Auth table schemas:\n- `users`, `sessions`, `accounts`\n- `organizations`, `members`, `invitations` (if organizations enabled)\n- Plugin-specific tables (`apiKeys`, etc.)\n\n**`src/db/features-schema.ts`** — Re-exports your feature schemas:\n- All tables defined with `defineTable()`\n- Audit field columns added automatically\n\n### Single Database Mode (Bun Default)\n\n**`src/db/schema.ts`** — Combined re-export of all schemas (auth + features).\n\n## Generated Routes\n\nFor each feature, the compiler generates a routes file at `src/features/{name}/routes.ts` containing:\n\n| Route | Generated When |\n|-------|----------------|\n| `GET /` | `crud.list` configured |\n| `GET /:id` | `crud.get` configured |\n| `POST /` | `crud.create` configured |\n| `PATCH /:id` | `crud.update` configured |\n| `DELETE /:id` | `crud.delete` configured |\n| `PUT /:id` | `crud.put` configured |\n| `POST /batch` | `crud.create` exists (auto-enabled) |\n| `PATCH /batch` | `crud.update` exists (auto-enabled) |\n| `DELETE /batch` | `crud.delete` exists (auto-enabled) |\n| `PUT /batch` | `crud.put` exists (auto-enabled) |\n| `GET /views/{name}` | `views` configured |\n| `POST /:id/{action}` | Record-based actions defined |\n| `POST /{action}` | Standalone actions defined |\n\nAll routes are mounted under `/api/v1/{feature}` in the main app.\n\n## Migrations\n\nThe compiler runs `drizzle-kit generate` during compilation to produce SQL migration files. On subsequent compiles, it uses `existingFiles` (your current migration state) to generate only incremental changes.\n\nThe CLI loads migration meta JSON from:\n\n- `quickback/drizzle/auth/meta/`\n- `quickback/drizzle/features/meta/`\n- `quickback/drizzle/files/meta/`\n- `quickback/drizzle/webhooks/meta/`\n- `quickback/drizzle/meta/` (single database mode)\n- `drizzle/...` (legacy paths, checked as fallback)\n\nMigration and report artifacts are written to the `quickback/` state directory:\n\n- `quickback/drizzle/...` for Drizzle migration SQL/meta artifacts\n- `quickback/reports/...` for security contract report artifacts\n\nFor 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.\n\nMigration files follow the Drizzle Kit naming convention:\n```\n0000_initial.sql\n0001_add_status_column.sql\n0002_create_orders_table.sql\n```\n\n## CMS and Account UI Assets\n\nWhen `cms` and/or `account` are enabled, the compiler builds the SPAs from source at compile time and includes the assets in the output.\n\nEach SPA is placed in its own subdirectory under `src/apps/`:\n\n```\nsrc/apps/ # Root assets directory\n├── cms/ # CMS SPA (served at /cms/ on unified domain)\n│ ├── index.html\n│ └── assets/\n│ ├── app.abc123.js # Content-hashed filenames from Vite\n│ └── app.xyz456.css\n└── account/ # Account SPA (served at /account/ on unified domain)\n ├── index.html\n └── assets/\n ├── app.def789.js\n └── app.ghi012.css\n```\n\nOn 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/`.\n\n### Generated Wrangler Assets Config\n\nThe compiler always generates `run_worker_first = true` when SPAs are enabled — the Worker handles all SPA routing (per hostname and per path prefix):\n\n```toml\n[assets]\nbinding = \"ASSETS\"\ndirectory = \"src/apps\"\nnot_found_handling = \"none\"\nrun_worker_first = true\n```\n\nSee [Multi-Domain Architecture](/compiler/config/domains) for details on hostname routing.\n\n## See Also\n\n- [Providers](/compiler/config/providers) — Configure runtime, database, and auth providers\n- [Environment variables](/compiler/config/variables) — Required variables by runtime\n- [Multi-Domain Architecture](/compiler/config/domains) — Custom domains and hostname routing"
|
|
155
155
|
},
|
|
156
156
|
"compiler/config/providers": {
|
|
157
157
|
"title": "Providers",
|
|
@@ -163,7 +163,7 @@ export const DOCS = {
|
|
|
163
163
|
},
|
|
164
164
|
"compiler/config/variables": {
|
|
165
165
|
"title": "Environment Variables",
|
|
166
|
-
"content": "## CLI Environment Variables\n\nThese variables configure the Quickback CLI itself.\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `QUICKBACK_API_URL` | Compiler API endpoint | `https://compiler.quickback.dev` |\n| `QUICKBACK_API_KEY` | API key for headless authentication (CI/CD) | — |\n| `QUICKBACK_AUTH_URL` | Auth server URL (custom deployments) | — |\n\n### Authentication\n\nThe CLI authenticates via two methods:\n\n1. **Interactive login** — `quickback login` stores credentials in `~/.quickback/credentials.json`\n2. **API key** — Set `QUICKBACK_API_KEY` for CI/CD environments\n\n```bash\n# Use the cloud compiler (default)\nquickback compile\n\n# Use a local compiler instance\nQUICKBACK_API_URL=http://localhost:3000 quickback compile\n\n# CI/CD with API key\nQUICKBACK_API_KEY=qb_key_... quickback compile\n```\n\n## Compiler Service Variables\n\nThese variables are used by the compiler service/runtime itself.\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `QUICKBACK_SECURITY_REPORT_SIGNING_KEY` | HMAC key for signing `security-contracts.report.json` artifacts | — |\n\nWhen `compiler.securityContracts.report.signature.required` is `true`, this variable (or `signature.key`) must be set or compile fails.\n\n## Cloudflare Variables\n\n### Wrangler Bindings\n\nThese are configured as bindings in `wrangler.toml`, not environment variables. The compiler generates them automatically.\n\n| Binding | Type | Description |\n|---------|------|-------------|\n| `AUTH_DB` | D1 Database | Better Auth tables (dual mode) |\n| `DB` | D1 Database | Feature tables (dual mode) |\n| `DATABASE` | D1 Database | All tables (single DB mode) |\n| `KV` | KV Namespace | Key-value storage |\n| `R2_BUCKET` | R2 Bucket | File storage (if configured) |\n| `AI` | Workers AI | Embedding generation (if configured) |\n| `VECTORIZE` | Vectorize | Vector similarity search (if configured) |\n| `EMBEDDINGS_QUEUE` | Queue | Async embedding jobs (if configured) |\n| `WEBHOOKS_DB` | D1 Database | Webhook events (if configured) |\n| `WEBHOOKS_QUEUE` | Queue | Webhook delivery (if configured) |\n| `FILES_DB` | D1 Database | File metadata (if R2 configured) |\n| `BROADCASTER` |
|
|
166
|
+
"content": "## CLI Environment Variables\n\nThese variables configure the Quickback CLI itself.\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `QUICKBACK_API_URL` | Compiler API endpoint | `https://compiler.quickback.dev` |\n| `QUICKBACK_API_KEY` | API key for headless authentication (CI/CD) | — |\n| `QUICKBACK_AUTH_URL` | Auth server URL (custom deployments) | — |\n\n### Authentication\n\nThe CLI authenticates via two methods:\n\n1. **Interactive login** — `quickback login` stores credentials in `~/.quickback/credentials.json`\n2. **API key** — Set `QUICKBACK_API_KEY` for CI/CD environments\n\n```bash\n# Use the cloud compiler (default)\nquickback compile\n\n# Use a local compiler instance\nQUICKBACK_API_URL=http://localhost:3000 quickback compile\n\n# CI/CD with API key\nQUICKBACK_API_KEY=qb_key_... quickback compile\n```\n\n## Compiler Service Variables\n\nThese variables are used by the compiler service/runtime itself.\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `QUICKBACK_SECURITY_REPORT_SIGNING_KEY` | HMAC key for signing `security-contracts.report.json` artifacts | — |\n\nWhen `compiler.securityContracts.report.signature.required` is `true`, this variable (or `signature.key`) must be set or compile fails.\n\n## Cloudflare Variables\n\n### Wrangler Bindings\n\nThese are configured as bindings in `wrangler.toml`, not environment variables. The compiler generates them automatically.\n\n| Binding | Type | Description |\n|---------|------|-------------|\n| `AUTH_DB` | D1 Database | Better Auth tables (dual mode) |\n| `DB` | D1 Database | Feature tables (dual mode) |\n| `DATABASE` | D1 Database | All tables (single DB mode) |\n| `KV` | KV Namespace | Key-value storage |\n| `R2_BUCKET` | R2 Bucket | File storage (if configured) |\n| `AI` | Workers AI | Embedding generation (if configured) |\n| `VECTORIZE` | Vectorize | Vector similarity search (if configured) |\n| `EMBEDDINGS_QUEUE` | Queue | Async embedding jobs (if configured) |\n| `WEBHOOKS_DB` | D1 Database | Webhook events (if configured) |\n| `WEBHOOKS_QUEUE` | Queue | Webhook delivery (if configured) |\n| `FILES_DB` | D1 Database | File metadata (if R2 configured) |\n| `BROADCASTER` | Durable Object | Realtime Broadcaster DO (if configured) |\n| `EMAIL` | send_email | Cloudflare Email Service (if email configured) |\n\n### Worker Variables\n\nSet these in `wrangler.toml` under `[vars]` or in the Cloudflare dashboard:\n\n| Variable | Description | Required |\n|----------|-------------|----------|\n| `BETTER_AUTH_URL` | Public URL of your auth endpoint | Yes |\n| `APP_NAME` | Application name (used in emails) | No |\n\n### Email (Cloudflare Email Service — default)\n\nUses the Cloudflare `send_email` binding. No API keys needed — the binding is configured automatically in `wrangler.toml`.\n\n| Variable | Description | Where |\n|----------|-------------|-------|\n| `EMAIL_FROM` | Sender email address | `[vars]` |\n| `EMAIL_FROM_NAME` | Sender display name | `[vars]` |\n| `APP_NAME` | Application name (used in templates) | `[vars]` |\n\nThe `EMAIL` binding is added to `wrangler.toml` automatically when using the `emailOtp` plugin.\n\n### Email (AWS SES)\n\nRequired when using `email: { provider: 'aws-ses' }`:\n\n| Variable | Description |\n|----------|-------------|\n| `AWS_ACCESS_KEY_ID` | AWS access key |\n| `AWS_SECRET_ACCESS_KEY` | AWS secret key |\n| `AWS_SES_REGION` | SES region (e.g., `us-east-2`) |\n| `EMAIL_FROM` | Sender email address |\n| `EMAIL_FROM_NAME` | Sender display name |\n| `EMAIL_REPLY_TO` | Reply-to address |\n\n### Drizzle Kit (Migrations)\n\nFor running remote migrations with `drizzle-kit`, set these in `.env`:\n\n| Variable | Description |\n|----------|-------------|\n| `CLOUDFLARE_ACCOUNT_ID` | Your Cloudflare account ID |\n| `CLOUDFLARE_API_TOKEN` | API token with D1 permissions |\n| `CLOUDFLARE_AUTH_DATABASE_ID` | Auth D1 database ID (dual mode) |\n| `CLOUDFLARE_FEATURES_DATABASE_ID` | Features D1 database ID (dual mode) |\n| `CLOUDFLARE_AUDIT_DATABASE_ID` | Security audit D1 database ID (unsafe cross-tenant actions) |\n| `CLOUDFLARE_DATABASE_ID` | Database ID (single DB mode) |\n\n## Bun Variables\n\nSet these in a `.env` file in your project root:\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `NODE_ENV` | Runtime environment | `development` |\n| `PORT` | Server port | `3000` |\n| `BETTER_AUTH_SECRET` | Auth encryption secret | — (required) |\n| `BETTER_AUTH_URL` | Public URL of your server | `http://localhost:3000` |\n| `DATABASE_PATH` | Path to SQLite file | `./data/app.db` |\n\n## Turso (LibSQL) Variables\n\nIn addition to the Bun variables above:\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `DATABASE_URL` | LibSQL connection URL | `file:./data/app.db` |\n| `DATABASE_AUTH_TOKEN` | Turso auth token (required for remote) | — |\n\n```bash\n# Local development\nDATABASE_URL=file:./data/app.db\n\n# Production (Turso cloud)\nDATABASE_URL=libsql://your-db-slug.turso.io\nDATABASE_AUTH_TOKEN=eyJhbGciOi...\n```\n\n## Social Login Providers\n\nWhen social login is configured in your auth provider:\n\n| Variable | Description |\n|----------|-------------|\n| `GOOGLE_CLIENT_ID` | Google OAuth client ID |\n| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |\n| `GITHUB_CLIENT_ID` | GitHub OAuth client ID |\n| `GITHUB_CLIENT_SECRET` | GitHub OAuth client secret |\n| `DISCORD_CLIENT_ID` | Discord OAuth client ID |\n| `DISCORD_CLIENT_SECRET` | Discord OAuth client secret |\n\n## See Also\n\n- [Output Structure](/compiler/config/output) — Generated file structure\n- [Providers](/compiler/config/providers) — Provider configuration reference\n- [Cloudflare Template](/compiler/getting-started/template-cloudflare) — Cloudflare setup guide\n- [Bun Template](/compiler/getting-started/template-bun) — Bun setup guide"
|
|
167
167
|
},
|
|
168
168
|
"compiler/definitions/access": {
|
|
169
169
|
"title": "Access - Role & Condition-Based Access Control",
|
|
@@ -251,7 +251,7 @@ export const DOCS = {
|
|
|
251
251
|
},
|
|
252
252
|
"compiler/integrations/cloudflare": {
|
|
253
253
|
"title": "Cloudflare Workers",
|
|
254
|
-
"content": "The Cloudflare target generates a complete Hono-based API running on Cloudflare Workers with D1 as the database. This is the recommended target for production deployments.\n\n## Configuration\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: [\"organizations\"],\n providers: {\n runtime: defineRuntime(\"cloudflare\"),\n database: defineDatabase(\"cloudflare-d1\"),\n auth: defineAuth(\"better-auth\"),\n },\n});\n```\n\n## Generated Output\n\n```\nsrc/\n├── routes/ # Hono route handlers (one per resource)\n├── middleware/ # Auth, firewall, access middleware\n├── features/ # Feature-specific helpers (security, masking)\n├── types/ # TypeScript interfaces\n├── db/\n│ └── schema.ts # Drizzle schema\n└── index.ts # Hono app entry point\n\nquickback/drizzle/\n├── migrations/ # SQL migration files\n└── meta/ # Drizzle metadata\n```\n\n## Security Model\n\nAll four security layers run at the application level:\n\n1. **Firewall** — Drizzle WHERE clauses for data isolation\n2. **Access** — Role checks in middleware\n3. **Guards** — Field filtering in request handlers\n4. **Masking** — Response transformation before sending\n\nSince D1 is only accessible through your Worker (no external connection string), the application layer is the only entry point. See [D1 Security Architecture](/stack/database/d1#security-architecture) for details.\n\n## Features\n\n- Full CRUD with batch operations (create, update, delete, upsert)\n- Custom actions with inline or handler-based execution\n- Soft delete with cascading to child tables\n- Pagination, filtering, sorting, field selection, and full-text search\n- Views (column-level projections with per-view access control)\n- OpenAPI specification generation at `/openapi.json`\n\n## Deployment\n\n```bash\n# Local development\nnpm run dev\n\n# Apply migrations to remote D1\nnpm run db:migrate:remote\n\n# Deploy to Cloudflare Workers\nnpm run deploy\n```\n\n## Environment & Bindings\n\nYour `wrangler.toml` needs these bindings:\n\n| Binding | Type | Required | Purpose |\n|---------|------|----------|---------|\n| `DB` | D1 | Yes | Feature database |\n| `AUTH_DB` | D1 | Yes | Better Auth database |\n| `AUDIT_DB` | D1 | No* | Unsafe cross-tenant action audit logs |\n| `KV` | KV | Yes | Session storage |\n| `R2` | R2 | No | File storage (avatars, uploads) |\n| `AI` | Workers AI | No | Embeddings generation |\n| `VECTORIZE` | Vectorize | No | Vector search index |\n| `BROADCASTER` |
|
|
254
|
+
"content": "The Cloudflare target generates a complete Hono-based API running on Cloudflare Workers with D1 as the database. This is the recommended target for production deployments.\n\n## Configuration\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: [\"organizations\"],\n providers: {\n runtime: defineRuntime(\"cloudflare\"),\n database: defineDatabase(\"cloudflare-d1\"),\n auth: defineAuth(\"better-auth\"),\n },\n});\n```\n\n## Generated Output\n\n```\nsrc/\n├── routes/ # Hono route handlers (one per resource)\n├── middleware/ # Auth, firewall, access middleware\n├── features/ # Feature-specific helpers (security, masking)\n├── types/ # TypeScript interfaces\n├── db/\n│ └── schema.ts # Drizzle schema\n└── index.ts # Hono app entry point\n\nquickback/drizzle/\n├── migrations/ # SQL migration files\n└── meta/ # Drizzle metadata\n```\n\n## Security Model\n\nAll four security layers run at the application level:\n\n1. **Firewall** — Drizzle WHERE clauses for data isolation\n2. **Access** — Role checks in middleware\n3. **Guards** — Field filtering in request handlers\n4. **Masking** — Response transformation before sending\n\nSince D1 is only accessible through your Worker (no external connection string), the application layer is the only entry point. See [D1 Security Architecture](/stack/database/d1#security-architecture) for details.\n\n## Features\n\n- Full CRUD with batch operations (create, update, delete, upsert)\n- Custom actions with inline or handler-based execution\n- Soft delete with cascading to child tables\n- Pagination, filtering, sorting, field selection, and full-text search\n- Views (column-level projections with per-view access control)\n- OpenAPI specification generation at `/openapi.json`\n\n## Deployment\n\n```bash\n# Local development\nnpm run dev\n\n# Apply migrations to remote D1\nnpm run db:migrate:remote\n\n# Deploy to Cloudflare Workers\nnpm run deploy\n```\n\n## Environment & Bindings\n\nYour `wrangler.toml` needs these bindings:\n\n| Binding | Type | Required | Purpose |\n|---------|------|----------|---------|\n| `DB` | D1 | Yes | Feature database |\n| `AUTH_DB` | D1 | Yes | Better Auth database |\n| `AUDIT_DB` | D1 | No* | Unsafe cross-tenant action audit logs |\n| `KV` | KV | Yes | Session storage |\n| `R2` | R2 | No | File storage (avatars, uploads) |\n| `AI` | Workers AI | No | Embeddings generation |\n| `VECTORIZE` | Vectorize | No | Vector search index |\n| `BROADCASTER` | Durable Object | No | Realtime broadcasts (inline DO) |\n\n\\* Required when you define cross-tenant unsafe actions.\n\n## See Also\n\n- [Cloudflare Template](/compiler/getting-started/template-cloudflare) — Step-by-step setup guide with full wrangler.toml configuration\n- [D1 Database](/stack/database/d1) — D1 setup, multi-database pattern, and security architecture\n- [Neon Integration](/compiler/integrations/neon) — Alternative: PostgreSQL with RLS\n- [Supabase Integration](/compiler/integrations/supabase) — Alternative: Supabase with RLS"
|
|
255
255
|
},
|
|
256
256
|
"compiler/integrations": {
|
|
257
257
|
"title": "Compile Targets",
|
|
@@ -391,15 +391,15 @@ export const DOCS = {
|
|
|
391
391
|
},
|
|
392
392
|
"stack/realtime/durable-objects": {
|
|
393
393
|
"title": "Realtime",
|
|
394
|
-
"content": "Quickback can generate realtime notification helpers for broadcasting changes to connected clients. This enables live updates via WebSocket using Cloudflare Durable Objects.\n\n## Enabling Realtime\n\nAdd `realtime` configuration to individual table definitions:\n\n```typescript\n// features/applications/applications.ts\n\nexport const applications = sqliteTable(\"applications\", {\n id: text(\"id\").primaryKey(),\n candidateId: text(\"candidate_id\").notNull(),\n jobId: text(\"job_id\").notNull(),\n stage: text(\"stage\").notNull(),\n organizationId: text(\"organization_id\").notNull(),\n});\n\nexport default defineTable(applications, {\n firewall: { organization: {} },\n realtime: {\n enabled: true, // Enable realtime for this table\n onInsert: true, // Broadcast on INSERT (default: true)\n onUpdate: true, // Broadcast on UPDATE (default: true)\n onDelete: true, // Broadcast on DELETE (default: true)\n requiredRoles: [\"recruiter\", \"hiring-manager\"], // Who receives broadcasts\n fields: [\"id\", \"candidateId\", \"stage\"], // Fields to include (optional)\n },\n // ... guards, crud\n});\n```\n\nWhen any table has realtime enabled, the compiler generates `src/lib/realtime.ts` with helper functions for sending notifications.\n\n## Generated Helper\n\nThe `createRealtime()` factory provides convenient methods for both Postgres Changes (CRUD events) and custom broadcasts:\n\n```typescript\n\nexport const execute: ActionExecutor = async ({ db, ctx, input }) => {\n const realtime = createRealtime(ctx.env);\n\n // After creating a record\n await realtime.insert('applications', newApplication, ctx.activeOrgId!);\n\n // After updating a record\n await realtime.update('applications', newApplication, oldApplication, ctx.activeOrgId!);\n\n // After deleting a record\n await realtime.delete('applications', { id: applicationId }, ctx.activeOrgId!);\n\n return { success: true };\n};\n```\n\n## Event Format\n\nQuickback broadcasts events in a structured JSON format for easy client-side handling.\n\n### Insert Event\n\n```typescript\nawait realtime.insert('applications', {\n id: 'app_123',\n candidateId: 'cnd_456',\n stage: 'applied'\n}, ctx.activeOrgId!);\n```\n\nBroadcasts:\n```json\n{\n \"type\": \"postgres_changes\",\n \"table\": \"applications\",\n \"eventType\": \"INSERT\",\n \"schema\": \"public\",\n \"new\": {\n \"id\": \"app_123\",\n \"candidateId\": \"cnd_456\",\n \"stage\": \"applied\"\n },\n \"old\": null\n}\n```\n\n### Update Event\n\n```typescript\nawait realtime.update('applications',\n { id: 'app_123', stage: 'interview' }, // new\n { id: 'app_123', stage: 'screening' }, // old\n ctx.activeOrgId!\n);\n```\n\nBroadcasts:\n```json\n{\n \"type\": \"postgres_changes\",\n \"table\": \"applications\",\n \"eventType\": \"UPDATE\",\n \"schema\": \"public\",\n \"new\": { \"id\": \"app_123\", \"stage\": \"interview\" },\n \"old\": { \"id\": \"app_123\", \"stage\": \"screening\" }\n}\n```\n\n### Delete Event\n\n```typescript\nawait realtime.delete('applications',\n { id: 'app_123' },\n ctx.activeOrgId!\n);\n```\n\nBroadcasts:\n```json\n{\n \"type\": \"postgres_changes\",\n \"table\": \"applications\",\n \"eventType\": \"DELETE\",\n \"schema\": \"public\",\n \"new\": null,\n \"old\": { \"id\": \"app_123\" }\n}\n```\n\n## Custom Broadcasts\n\nFor arbitrary events that don't map to CRUD operations:\n\n```typescript\nawait realtime.broadcast('screening-complete', {\n applicationId: 'app_123',\n candidateId: 'cnd_456',\n stage: 'interview'\n}, ctx.activeOrgId!);\n```\n\nBroadcasts:\n```json\n{\n \"type\": \"broadcast\",\n \"event\": \"screening-complete\",\n \"payload\": {\n \"applicationId\": \"app_123\",\n \"candidateId\": \"cnd_456\",\n \"stage\": \"interview\"\n }\n}\n```\n\n## User-Specific Broadcasts\n\nTarget a specific user instead of the entire organization:\n\n```typescript\n// Only this user receives the notification\nawait realtime.insert('notifications', newNotification, ctx.activeOrgId!, {\n userId: ctx.userId\n});\n\n// Notify a hiring manager of a new application\nawait realtime.broadcast('application-received', {\n applicationId: 'app_123',\n jobId: 'job_789'\n}, ctx.activeOrgId!, {\n userId: hiringManagerId\n});\n```\n\n## Role-Based Filtering\n\nLimit which roles receive a broadcast using `targetRoles`:\n\n```typescript\n// Only hiring managers and recruiters receive this broadcast\nawait realtime.insert('applications', application, ctx.activeOrgId!, {\n targetRoles: ['hiring-manager', 'recruiter']\n});\n\n// Interviewers only see applications assigned to them\nawait realtime.update('applications', newRecord, oldRecord, ctx.activeOrgId!, {\n targetRoles: ['hiring-manager']\n});\n```\n\nIf `targetRoles` is not specified, all authenticated users in the organization receive the broadcast.\n\n## Per-Role Field Masking\n\nApply different field masking based on the subscriber's role using `maskingConfig`:\n\n```typescript\nawait realtime.insert('candidates', newCandidate, ctx.activeOrgId!, {\n maskingConfig: {\n phone: { type: 'phone', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n email: { type: 'email', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n resumeUrl: { type: 'redact', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n },\n});\n```\n\n**How masking works:**\n- Each subscriber receives a payload masked according to their role\n- Roles in the `show.roles` array see unmasked values\n- All other roles see the masked version\n- Masking is pre-computed per-role (O(roles) not O(subscribers))\n\n**Available mask types:**\n| Type | Example Output |\n|------|----------------|\n| `email` | `j***@y*********.com` |\n| `phone` | `******4567` |\n| `ssn` | `*****6789` |\n| `creditCard` | `**** **** **** 1111` |\n| `name` | `J***` |\n| `redact` | `[REDACTED]` |\n\n### Owner-Based Masking\n\nShow unmasked data to the record owner:\n\n```typescript\nmaskingConfig: {\n phone: {\n type: 'phone',\n show: { roles: ['hiring-manager'], or: 'owner' } // Hiring manager OR owner sees unmasked\n },\n}\n```\n\n## Client-Side Subscription\n\n### WebSocket Authentication\n\nQuickback uses **ticket-based authentication** for WebSocket connections. The client first obtains a short-lived ticket from the main API, then connects with it as a URL parameter. The Broadcaster verifies the ticket cryptographically at upgrade time — no HTTP round-trip needed.\n\n```typescript\n// 1. Get a ws-ticket from your API (requires session auth)\nconst response = await fetch('/realtime/v1/ws-ticket', {\n method: 'POST',\n headers: { Authorization: `Bearer ${sessionToken}` },\n});\nconst { wsTicket } = await response.json();\n\n// 2. Connect with ticket as URL param\nconst ws = new WebSocket(\n `wss://api.yourdomain.com/realtime/v1/websocket?ws_ticket=${wsTicket}&organizationId=${activeOrgId}`\n);\n\nws.onopen = () => {\n console.log('Connected and authenticated!');\n // No auth message needed — pre-authenticated at upgrade\n};\n```\n\nTickets expire after 60 seconds, so fetch a fresh one before each connection or reconnection.\n\n### Handling Messages\n\n```typescript\nws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n\n // Handle CRUD events\n if (msg.type === 'postgres_changes') {\n const { table, eventType, new: newRecord, old: oldRecord } = msg;\n\n if (eventType === 'INSERT') {\n addRecord(table, newRecord);\n } else if (eventType === 'UPDATE') {\n updateRecord(table, newRecord);\n } else if (eventType === 'DELETE') {\n removeRecord(table, oldRecord.id);\n }\n }\n\n // Handle custom broadcasts\n if (msg.type === 'broadcast') {\n const { event, payload } = msg;\n\n if (event === 'screening-complete') {\n refreshApplication(payload.applicationId);\n } else if (event === 'interview-scheduled') {\n showInterviewNotification(payload.applicationId);\n }\n }\n};\n```\n\n## Required Environment Variables\n\n| Variable | Where | Description |\n|----------|-------|-------------|\n| `REALTIME_URL` | Main API | URL of the broadcast/realtime worker |\n| `ACCESS_TOKEN` | Both | Shared secret for broadcast API auth and ws-ticket signing |\n\nThe `ACCESS_TOKEN` serves double duty: it authenticates internal broadcast API calls from the main worker, and it signs/verifies WebSocket tickets. Set it as a secret on the broadcast worker:\n\n```bash\ncd cloudflare-workers/broadcast && wrangler secret put ACCESS_TOKEN\n```\n\n## Architecture\n\n```\n 1. POST /ws-ticket\n┌──────────────┐ ◄──────────────────────────────── ┌─────────────────┐\n│ API Worker │ ────────────────────────────────► │ Browser Client │\n│ (Quickback) │ { wsTicket, expiresIn: 60 } │ │\n└──────┬───────┘ └───────┬─────────┘\n │ │\n │ POST /broadcast 2. WS ?ws_ticket=...│\n │ │\n ▼ ▼\n┌──────────────────┐ Verified at upgrade\n│ Realtime Worker │ ◄──────── WebSocket ────── (no HTTP call)\n│ (Durable Object) │\n└──────────────────┘\n```\n\n1. **Client gets a ticket** — `POST /realtime/v1/ws-ticket` on the main API (session-authenticated). Returns a 60-second HMAC-signed ticket.\n2. **Client connects** — Opens WebSocket with `?ws_ticket=<ticket>`. The Durable Object verifies cryptographically at upgrade.\n3. **API broadcasts** — After CRUD ops, the main API calls `POST /broadcast` on the realtime worker, which delivers to matching subscribers.\n\n## Best Practices\n\n### Broadcast After Commit\n\nAlways broadcast after the database operation succeeds:\n\n```typescript\n// Good - broadcast after successful insert\nconst [newApp] = await db.insert(applications).values(data).returning();\nawait realtime.insert('applications', newApp, ctx.activeOrgId!);\n\n// Bad - don't broadcast before confirming success\nawait realtime.insert('applications', data, ctx.activeOrgId!);\nawait db.insert(applications).values(data); // Could fail!\n```\n\n### Minimal Payloads\n\nOnly include necessary data in broadcasts:\n\n```typescript\n// Good - minimal payload\nawait realtime.update('applications',\n { id: record.id, stage: 'interview' },\n { id: record.id, stage: 'screening' },\n ctx.activeOrgId!\n);\n\n// Avoid - sending entire record with large content\nawait realtime.update('applications', fullRecord, oldRecord, ctx.activeOrgId!);\n```\n\n### Use Broadcasts for Complex Events\n\nFor events that don't map cleanly to CRUD:\n\n```typescript\n// Hiring pipeline stage completed\nawait realtime.broadcast('pipeline-stage-complete', {\n applicationId: application.id,\n stages: ['applied', 'screening', 'interview'],\n currentStage: 'offer',\n candidateId: application.candidateId\n}, ctx.activeOrgId!);\n```\n\n## Custom Event Namespaces\n\nFor complex applications with many custom events, you can define typed event namespaces using `defineRealtime`. This generates strongly-typed helper methods for your custom events.\n\n### Defining Event Namespaces\n\nCreate a file in `services/realtime/`:\n\n```typescript\n// services/realtime/screening.ts\n\nexport default defineRealtime({\n name: 'screening',\n events: ['started', 'progress', 'completed', 'failed'],\n description: 'Candidate screening pipeline events',\n});\n```\n\n### Generated Helper Methods\n\nAfter compilation, the `createRealtime()` helper includes your custom namespace:\n\n```typescript\n\nexport const execute: ActionExecutor = async ({ ctx, input }) => {\n const realtime = createRealtime(ctx.env);\n\n // Type-safe custom event methods\n await realtime.screening.started({\n applicationId: input.applicationId,\n }, ctx.activeOrgId!);\n\n // Progress updates\n await realtime.screening.progress({\n applicationId: input.applicationId,\n percent: 50,\n step: 'background-check',\n }, ctx.activeOrgId!);\n\n // Completion\n await realtime.screening.completed({\n applicationId: input.applicationId,\n result: 'passed',\n duration: 1234,\n }, ctx.activeOrgId!);\n\n return { success: true };\n};\n```\n\n### Event Format\n\nCustom namespace events use the `broadcast` type with namespaced event names:\n\n```json\n{\n \"type\": \"broadcast\",\n \"event\": \"screening:started\",\n \"payload\": {\n \"applicationId\": \"app_123\"\n }\n}\n```\n\n### Multiple Namespaces\n\nDefine multiple namespaces for different parts of your application:\n\n```typescript\n// services/realtime/notifications.ts\nexport default defineRealtime({\n name: 'notifications',\n events: ['interview-reminder', 'offer-update', 'application-received'],\n description: 'Recruitment notification events',\n});\n\n// services/realtime/presence.ts\nexport default defineRealtime({\n name: 'presence',\n events: ['joined', 'left', 'reviewing', 'idle'],\n description: 'Who is reviewing which candidate',\n});\n```\n\nUsage:\n```typescript\nconst realtime = createRealtime(ctx.env);\n\n// Notification events\nawait realtime.notifications['interview-reminder']({ ... }, ctx.activeOrgId!);\n\n// Presence events — who's reviewing which candidate\nawait realtime.presence.reviewing({ userId: ctx.userId, candidateId: 'cnd_456' }, ctx.activeOrgId!);\n```\n\n### Namespace vs Generic Broadcast\n\n| Use Case | Approach |\n|----------|----------|\n| One-off custom event | `realtime.broadcast('event-name', payload, orgId)` |\n| Repeated event patterns | `defineRealtime` namespace |\n| Type-safe events | `defineRealtime` namespace |\n| Event discovery | `defineRealtime` (appears in generated types) |"
|
|
394
|
+
"content": "Quickback can generate realtime notification helpers for broadcasting changes to connected clients. This enables live updates via WebSocket using Cloudflare Durable Objects.\n\n## Enabling Realtime\n\nAdd `realtime` configuration to individual table definitions:\n\n```typescript\n// features/applications/applications.ts\n\nexport const applications = sqliteTable(\"applications\", {\n id: text(\"id\").primaryKey(),\n candidateId: text(\"candidate_id\").notNull(),\n jobId: text(\"job_id\").notNull(),\n stage: text(\"stage\").notNull(),\n organizationId: text(\"organization_id\").notNull(),\n});\n\nexport default defineTable(applications, {\n firewall: { organization: {} },\n realtime: {\n enabled: true, // Enable realtime for this table\n onInsert: true, // Broadcast on INSERT (default: true)\n onUpdate: true, // Broadcast on UPDATE (default: true)\n onDelete: true, // Broadcast on DELETE (default: true)\n requiredRoles: [\"recruiter\", \"hiring-manager\"], // Who receives broadcasts\n fields: [\"id\", \"candidateId\", \"stage\"], // Fields to include (optional)\n },\n // ... guards, crud\n});\n```\n\nWhen any table has realtime enabled, the compiler generates `src/lib/realtime.ts` with helper functions for sending notifications.\n\n## Generated Helper\n\nThe `createRealtime()` factory provides convenient methods for both Postgres Changes (CRUD events) and custom broadcasts:\n\n```typescript\n\nexport const execute: ActionExecutor = async ({ db, ctx, input }) => {\n const realtime = createRealtime(ctx.env);\n\n // After creating a record\n await realtime.insert('applications', newApplication, ctx.activeOrgId!);\n\n // After updating a record\n await realtime.update('applications', newApplication, oldApplication, ctx.activeOrgId!);\n\n // After deleting a record\n await realtime.delete('applications', { id: applicationId }, ctx.activeOrgId!);\n\n return { success: true };\n};\n```\n\n## Event Format\n\nQuickback broadcasts events in a structured JSON format for easy client-side handling.\n\n### Insert Event\n\n```typescript\nawait realtime.insert('applications', {\n id: 'app_123',\n candidateId: 'cnd_456',\n stage: 'applied'\n}, ctx.activeOrgId!);\n```\n\nBroadcasts:\n```json\n{\n \"type\": \"postgres_changes\",\n \"table\": \"applications\",\n \"eventType\": \"INSERT\",\n \"schema\": \"public\",\n \"new\": {\n \"id\": \"app_123\",\n \"candidateId\": \"cnd_456\",\n \"stage\": \"applied\"\n },\n \"old\": null\n}\n```\n\n### Update Event\n\n```typescript\nawait realtime.update('applications',\n { id: 'app_123', stage: 'interview' }, // new\n { id: 'app_123', stage: 'screening' }, // old\n ctx.activeOrgId!\n);\n```\n\nBroadcasts:\n```json\n{\n \"type\": \"postgres_changes\",\n \"table\": \"applications\",\n \"eventType\": \"UPDATE\",\n \"schema\": \"public\",\n \"new\": { \"id\": \"app_123\", \"stage\": \"interview\" },\n \"old\": { \"id\": \"app_123\", \"stage\": \"screening\" }\n}\n```\n\n### Delete Event\n\n```typescript\nawait realtime.delete('applications',\n { id: 'app_123' },\n ctx.activeOrgId!\n);\n```\n\nBroadcasts:\n```json\n{\n \"type\": \"postgres_changes\",\n \"table\": \"applications\",\n \"eventType\": \"DELETE\",\n \"schema\": \"public\",\n \"new\": null,\n \"old\": { \"id\": \"app_123\" }\n}\n```\n\n## Custom Broadcasts\n\nFor arbitrary events that don't map to CRUD operations:\n\n```typescript\nawait realtime.broadcast('screening-complete', {\n applicationId: 'app_123',\n candidateId: 'cnd_456',\n stage: 'interview'\n}, ctx.activeOrgId!);\n```\n\nBroadcasts:\n```json\n{\n \"type\": \"broadcast\",\n \"event\": \"screening-complete\",\n \"payload\": {\n \"applicationId\": \"app_123\",\n \"candidateId\": \"cnd_456\",\n \"stage\": \"interview\"\n }\n}\n```\n\n## User-Specific Broadcasts\n\nTarget a specific user instead of the entire organization:\n\n```typescript\n// Only this user receives the notification\nawait realtime.insert('notifications', newNotification, ctx.activeOrgId!, {\n userId: ctx.userId\n});\n\n// Notify a hiring manager of a new application\nawait realtime.broadcast('application-received', {\n applicationId: 'app_123',\n jobId: 'job_789'\n}, ctx.activeOrgId!, {\n userId: hiringManagerId\n});\n```\n\n## Role-Based Filtering\n\nLimit which roles receive a broadcast using `targetRoles`:\n\n```typescript\n// Only hiring managers and recruiters receive this broadcast\nawait realtime.insert('applications', application, ctx.activeOrgId!, {\n targetRoles: ['hiring-manager', 'recruiter']\n});\n\n// Interviewers only see applications assigned to them\nawait realtime.update('applications', newRecord, oldRecord, ctx.activeOrgId!, {\n targetRoles: ['hiring-manager']\n});\n```\n\nIf `targetRoles` is not specified, all authenticated users in the organization receive the broadcast.\n\n## Per-Role Field Masking\n\nApply different field masking based on the subscriber's role using `maskingConfig`:\n\n```typescript\nawait realtime.insert('candidates', newCandidate, ctx.activeOrgId!, {\n maskingConfig: {\n phone: { type: 'phone', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n email: { type: 'email', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n resumeUrl: { type: 'redact', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n },\n});\n```\n\n**How masking works:**\n- Each subscriber receives a payload masked according to their role\n- Roles in the `show.roles` array see unmasked values\n- All other roles see the masked version\n- Masking is pre-computed per-role (O(roles) not O(subscribers))\n\n**Available mask types:**\n| Type | Example Output |\n|------|----------------|\n| `email` | `j***@y*********.com` |\n| `phone` | `******4567` |\n| `ssn` | `*****6789` |\n| `creditCard` | `**** **** **** 1111` |\n| `name` | `J***` |\n| `redact` | `[REDACTED]` |\n\n### Owner-Based Masking\n\nShow unmasked data to the record owner:\n\n```typescript\nmaskingConfig: {\n phone: {\n type: 'phone',\n show: { roles: ['hiring-manager'], or: 'owner' } // Hiring manager OR owner sees unmasked\n },\n}\n```\n\n## Client-Side Subscription\n\n### WebSocket Authentication\n\nQuickback uses **ticket-based authentication** for WebSocket connections. The client first obtains a short-lived ticket from the main API, then connects with it as a URL parameter. The Broadcaster verifies the ticket cryptographically at upgrade time — no HTTP round-trip needed.\n\n```typescript\n// 1. Get a ws-ticket from your API (requires session auth)\nconst response = await fetch('/realtime/v1/ws-ticket', {\n method: 'POST',\n headers: { Authorization: `Bearer ${sessionToken}` },\n});\nconst { wsTicket } = await response.json();\n\n// 2. Connect with ticket as URL param\nconst ws = new WebSocket(\n `wss://api.yourdomain.com/realtime/v1/websocket?ws_ticket=${wsTicket}&organizationId=${activeOrgId}`\n);\n\nws.onopen = () => {\n console.log('Connected and authenticated!');\n // No auth message needed — pre-authenticated at upgrade\n};\n```\n\nTickets expire after 60 seconds, so fetch a fresh one before each connection or reconnection.\n\n### Handling Messages\n\n```typescript\nws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n\n // Handle CRUD events\n if (msg.type === 'postgres_changes') {\n const { table, eventType, new: newRecord, old: oldRecord } = msg;\n\n if (eventType === 'INSERT') {\n addRecord(table, newRecord);\n } else if (eventType === 'UPDATE') {\n updateRecord(table, newRecord);\n } else if (eventType === 'DELETE') {\n removeRecord(table, oldRecord.id);\n }\n }\n\n // Handle custom broadcasts\n if (msg.type === 'broadcast') {\n const { event, payload } = msg;\n\n if (event === 'screening-complete') {\n refreshApplication(payload.applicationId);\n } else if (event === 'interview-scheduled') {\n showInterviewNotification(payload.applicationId);\n }\n }\n};\n```\n\n## Environment\n\nNo additional environment variables are needed for realtime. The Broadcaster DO runs inline in your main worker and uses `BETTER_AUTH_SECRET` (already required for auth) to sign and verify WebSocket tickets.\n\n| Variable | Description |\n|----------|-------------|\n| `BETTER_AUTH_SECRET` | Signs/verifies WebSocket tickets (already set for auth) |\n\nThe `BROADCASTER` binding is a `DurableObjectNamespace` — the compiler generates this in your `wrangler.toml` automatically:\n\n```toml\n[[durable_objects.bindings]]\nname = \"BROADCASTER\"\nclass_name = \"Broadcaster\"\n\n[[migrations]]\ntag = \"v1\"\nnew_classes = [\"Broadcaster\"]\n```\n\n## Architecture\n\n```\n 1. POST /ws-ticket\n┌───────────────────────────────────────────────┐ ┌─────────────────┐\n│ Quickback Worker │ ◄──│ Browser Client │\n│ │ ──►│ │\n│ ┌──────────┐ DO binding ┌────────────────┐ │ └───────┬─────────┘\n│ │ Hono API │ ───────────► │ Broadcaster DO │ │ │\n│ └──────────┘ (in-process)└───────┬────────┘ │ 2. WS ?ws_ticket=...\n│ │ │ │\n└────────────────────────────────────┼──────────┘ │\n │◄──────────────────────┘\n WebSocket (verified at upgrade)\n```\n\n1. **Client gets a ticket** — `POST /realtime/v1/ws-ticket` on the main API (session-authenticated). Returns a 60-second HMAC-signed ticket.\n2. **Client connects** — Opens WebSocket with `?ws_ticket=<ticket>`. The Durable Object verifies the ticket cryptographically at upgrade — no HTTP round-trip.\n3. **API broadcasts** — After CRUD ops, the API calls the Broadcaster DO directly via its binding (in-process, no network hop).\n\n## Best Practices\n\n### Broadcast After Commit\n\nAlways broadcast after the database operation succeeds:\n\n```typescript\n// Good - broadcast after successful insert\nconst [newApp] = await db.insert(applications).values(data).returning();\nawait realtime.insert('applications', newApp, ctx.activeOrgId!);\n\n// Bad - don't broadcast before confirming success\nawait realtime.insert('applications', data, ctx.activeOrgId!);\nawait db.insert(applications).values(data); // Could fail!\n```\n\n### Minimal Payloads\n\nOnly include necessary data in broadcasts:\n\n```typescript\n// Good - minimal payload\nawait realtime.update('applications',\n { id: record.id, stage: 'interview' },\n { id: record.id, stage: 'screening' },\n ctx.activeOrgId!\n);\n\n// Avoid - sending entire record with large content\nawait realtime.update('applications', fullRecord, oldRecord, ctx.activeOrgId!);\n```\n\n### Use Broadcasts for Complex Events\n\nFor events that don't map cleanly to CRUD:\n\n```typescript\n// Hiring pipeline stage completed\nawait realtime.broadcast('pipeline-stage-complete', {\n applicationId: application.id,\n stages: ['applied', 'screening', 'interview'],\n currentStage: 'offer',\n candidateId: application.candidateId\n}, ctx.activeOrgId!);\n```\n\n## Custom Event Namespaces\n\nFor complex applications with many custom events, you can define typed event namespaces using `defineRealtime`. This generates strongly-typed helper methods for your custom events.\n\n### Defining Event Namespaces\n\nCreate a file in `services/realtime/`:\n\n```typescript\n// services/realtime/screening.ts\n\nexport default defineRealtime({\n name: 'screening',\n events: ['started', 'progress', 'completed', 'failed'],\n description: 'Candidate screening pipeline events',\n});\n```\n\n### Generated Helper Methods\n\nAfter compilation, the `createRealtime()` helper includes your custom namespace:\n\n```typescript\n\nexport const execute: ActionExecutor = async ({ ctx, input }) => {\n const realtime = createRealtime(ctx.env);\n\n // Type-safe custom event methods\n await realtime.screening.started({\n applicationId: input.applicationId,\n }, ctx.activeOrgId!);\n\n // Progress updates\n await realtime.screening.progress({\n applicationId: input.applicationId,\n percent: 50,\n step: 'background-check',\n }, ctx.activeOrgId!);\n\n // Completion\n await realtime.screening.completed({\n applicationId: input.applicationId,\n result: 'passed',\n duration: 1234,\n }, ctx.activeOrgId!);\n\n return { success: true };\n};\n```\n\n### Event Format\n\nCustom namespace events use the `broadcast` type with namespaced event names:\n\n```json\n{\n \"type\": \"broadcast\",\n \"event\": \"screening:started\",\n \"payload\": {\n \"applicationId\": \"app_123\"\n }\n}\n```\n\n### Multiple Namespaces\n\nDefine multiple namespaces for different parts of your application:\n\n```typescript\n// services/realtime/notifications.ts\nexport default defineRealtime({\n name: 'notifications',\n events: ['interview-reminder', 'offer-update', 'application-received'],\n description: 'Recruitment notification events',\n});\n\n// services/realtime/presence.ts\nexport default defineRealtime({\n name: 'presence',\n events: ['joined', 'left', 'reviewing', 'idle'],\n description: 'Who is reviewing which candidate',\n});\n```\n\nUsage:\n```typescript\nconst realtime = createRealtime(ctx.env);\n\n// Notification events\nawait realtime.notifications['interview-reminder']({ ... }, ctx.activeOrgId!);\n\n// Presence events — who's reviewing which candidate\nawait realtime.presence.reviewing({ userId: ctx.userId, candidateId: 'cnd_456' }, ctx.activeOrgId!);\n```\n\n### Namespace vs Generic Broadcast\n\n| Use Case | Approach |\n|----------|----------|\n| One-off custom event | `realtime.broadcast('event-name', payload, orgId)` |\n| Repeated event patterns | `defineRealtime` namespace |\n| Type-safe events | `defineRealtime` namespace |\n| Event discovery | `defineRealtime` (appears in generated types) |"
|
|
395
395
|
},
|
|
396
396
|
"stack/realtime": {
|
|
397
397
|
"title": "Realtime",
|
|
398
|
-
"content": "The Quickback Stack uses Cloudflare Durable Objects to broadcast real-time updates over WebSockets. CRUD events and custom broadcasts are delivered to connected clients with the same security layers (firewall isolation, role-based filtering, field masking) applied.\n\n## Architecture\n\n```\n
|
|
398
|
+
"content": "The Quickback Stack uses Cloudflare Durable Objects to broadcast real-time updates over WebSockets. CRUD events and custom broadcasts are delivered to connected clients with the same security layers (firewall isolation, role-based filtering, field masking) applied.\n\n## Architecture\n\n```\n┌──────────────────────────────────────────┐\n│ Quickback Worker │\n│ │\n│ ┌──────────┐ DO binding ┌────────────────────┐\n│ │ Hono API │ ──────────────► │ Broadcaster (DO) │\n│ │ │ │ WebSocket manager │\n│ └──────────┘ └─────────┬──────────┘\n│ │\n└─────────────────────────────────────────┼┘\n │ WebSocket\n │\n ┌────────▼─────────┐\n │ Browser Clients │\n │ (CMS, Account, │\n │ Admin, Custom) │\n └──────────────────┘\n```\n\nThe Broadcaster Durable Object runs **inline** in your main Quickback worker — no separate worker deployment needed. The API calls the DO directly via its binding (in-process, no network hop).\n\n1. **Quickback Worker** — Your compiled API with the Broadcaster DO class exported from the same worker.\n2. **Broadcaster (Durable Object)** — Manages WebSocket connections per organization. One instance per org.\n3. **Browser Clients** — CMS, Account, Admin, and custom frontends connect via WebSocket on the same origin.\n\n## Key Features\n\n| Feature | Description |\n|---------|-------------|\n| Organization-scoped | Each org gets its own Durable Object instance |\n| Role-based filtering | Only send events to users with matching roles |\n| Per-role masking | Different users see different field values based on their role |\n| User-specific targeting | Send events to a specific user within an org |\n| Custom broadcasts | Arbitrary events beyond CRUD |\n| Custom namespaces | `defineRealtime()` for type-safe event helpers |\n| Ticket-based auth | HMAC-signed tickets verified at WebSocket upgrade — no HTTP round-trip |\n\n## Enabling Realtime\n\nAdd `realtime` to individual table definitions:\n\n```typescript\nexport default defineTable(applications, {\n firewall: { organization: {} },\n realtime: {\n enabled: true,\n onInsert: true,\n onUpdate: true,\n onDelete: true,\n requiredRoles: [\"recruiter\", \"hiring-manager\"],\n fields: [\"id\", \"candidateId\", \"stage\"],\n },\n});\n```\n\nAnd enable the realtime binding in your database config. The compiler generates the Durable Object class, helper functions, and wrangler bindings — all within your main worker.\n\n## Pages\n\n- **[Durable Objects Setup](/stack/realtime/durable-objects)** — Configuration, wrangler bindings, event formats, masking, and custom namespaces\n- **[Using Realtime](/stack/realtime/using-realtime)** — WebSocket connection, authentication, and client-side handling"
|
|
399
399
|
},
|
|
400
400
|
"stack/realtime/using-realtime": {
|
|
401
401
|
"title": "Using Realtime",
|
|
402
|
-
"content": "This page covers connecting to the Quickback realtime system from client applications — authentication, subscribing to events, and handling messages.\n\n## Connecting\n\nOpen a WebSocket connection to the realtime worker:\n\n```typescript\nconst ws = new WebSocket(\"wss://api.yourdomain.com/realtime/v1/websocket\");\n```\n\n## Authentication\n\nQuickback uses **ticket-based authentication** for WebSocket connections. This is a two-step process:\n\n1. **Get a ticket** — Call the ws-ticket endpoint with your session auth\n2. **Connect with ticket** — Pass the ticket as a URL parameter when opening the WebSocket\n\nThis approach is faster and more secure than in-band auth messages — the connection is authenticated at upgrade time with no HTTP round-trip from the Durable Object.\n\n### Step 1: Get a WebSocket Ticket\n\n```typescript\nconst response = await fetch(\"/realtime/v1/ws-ticket\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${sessionToken}`,\n \"Content-Type\": \"application/json\",\n },\n});\nconst { wsTicket, expiresIn } = await response.json();\n// expiresIn = 60 (seconds)\n```\n\nThe ticket is a short-lived (60-second) HMAC-signed token containing your userId, organizationId, and roles.\n\n### Step 2: Connect with Ticket\n\n```typescript\nconst ws = new WebSocket(\n `wss://api.yourdomain.com/realtime/v1/websocket?ws_ticket=${wsTicket}&organizationId=${activeOrgId}`\n);\n\nws.onopen = () => {\n console.log(\"Connected and authenticated!\");\n // No auth message needed — connection is pre-authenticated\n};\n```\n\nIf the ticket is invalid or expired, the WebSocket upgrade is rejected with a 401 status.\n\n## Handling Messages\n\n### CRUD Events\n\nCRUD events use the `postgres_changes` type:\n\n```typescript\nws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n\n if (msg.type === \"postgres_changes\") {\n const { table, eventType, new: newRecord, old: oldRecord } = msg;\n\n switch (eventType) {\n case \"INSERT\":\n addRecord(table, newRecord);\n break;\n case \"UPDATE\":\n updateRecord(table, newRecord);\n break;\n case \"DELETE\":\n removeRecord(table, oldRecord.id);\n break;\n }\n }\n};\n```\n\n**Event payload:**\n\n```json\n{\n \"type\": \"postgres_changes\",\n \"table\": \"applications\",\n \"schema\": \"public\",\n \"eventType\": \"INSERT\",\n \"new\": { \"id\": \"app_123\", \"candidateId\": \"cnd_456\", \"stage\": \"interview\" },\n \"old\": null\n}\n```\n\nFor UPDATE events, both `new` and `old` are populated. For DELETE events, only `old` is populated.\n\n### Custom Broadcasts\n\nCustom events use the `broadcast` type:\n\n```typescript\nif (msg.type === \"broadcast\") {\n const { event, payload } = msg;\n\n if (event === \"screening-complete\") {\n refreshApplication(payload.applicationId);\n } else if (event === \"screening:progress\") {\n updateProgressBar(payload.percent);\n }\n}\n```\n\n**Event payload:**\n\n```json\n{\n \"type\": \"broadcast\",\n \"event\": \"screening-complete\",\n \"payload\": {\n \"applicationId\": \"app_123\",\n \"candidateId\": \"cnd_456\",\n \"stage\": \"interview\"\n }\n}\n```\n\nCustom namespaces (from `defineRealtime()`) use the format `namespace:event` — e.g., `screening:started`, `screening:progress`.\n\n## Security\n\n### Role-Based Filtering\n\nEvents are only delivered to users whose role matches the broadcast's `targetRoles`. If a table's realtime config specifies `requiredRoles: [\"hiring-manager\", \"recruiter\"]`, only users with those roles receive the events.\n\n### Per-Role Masking\n\nField values are masked according to the subscriber's role. For example, with this masking config:\n\n```typescript\nmasking: {\n ssn: { type: \"ssn\", show: { roles: [\"owner\"] } },\n}\n```\n\n- **Owner sees:** `{ ssn: \"123-45-6789\" }`\n- **Recruiter sees:** `{ ssn: \"*****6789\" }`\n\nMasking is pre-computed per-role (O(roles), not O(subscribers)) for efficiency.\n\n### User-Specific Events\n\nEvents can target a specific user within an organization. Only that user receives the broadcast — all other connections in the org are skipped.\n\n### Organization Isolation\n\nEach organization has its own Durable Object instance. Users can only subscribe to events in organizations they belong to, enforced during authentication.\n\n## Reconnection\n\nWebSocket connections can drop due to network issues. Implement reconnection logic in your client — note that you need to fetch a fresh ticket on each reconnect since tickets expire after 60 seconds:\n\n```typescript\nasync function connect() {\n // Get a fresh ticket each time\n const res = await fetch(\"/realtime/v1/ws-ticket\", {\n method: \"POST\",\n headers: { Authorization: `Bearer ${sessionToken}` },\n });\n const { wsTicket } = await res.json();\n\n const ws = new WebSocket(\n `wss://api.yourdomain.com/realtime/v1/websocket?ws_ticket=${wsTicket}&organizationId=${activeOrgId}`\n );\n\n ws.onclose = () => {\n // Reconnect after delay\n setTimeout(connect, 2000);\n };\n\n ws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n handleMessage(msg);\n };\n\n return ws;\n}\n```\n\n##
|
|
402
|
+
"content": "This page covers connecting to the Quickback realtime system from client applications — authentication, subscribing to events, and handling messages.\n\n## Connecting\n\nOpen a WebSocket connection to the realtime worker:\n\n```typescript\nconst ws = new WebSocket(\"wss://api.yourdomain.com/realtime/v1/websocket\");\n```\n\n## Authentication\n\nQuickback uses **ticket-based authentication** for WebSocket connections. This is a two-step process:\n\n1. **Get a ticket** — Call the ws-ticket endpoint with your session auth\n2. **Connect with ticket** — Pass the ticket as a URL parameter when opening the WebSocket\n\nThis approach is faster and more secure than in-band auth messages — the connection is authenticated at upgrade time with no HTTP round-trip from the Durable Object.\n\n### Step 1: Get a WebSocket Ticket\n\n```typescript\nconst response = await fetch(\"/realtime/v1/ws-ticket\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${sessionToken}`,\n \"Content-Type\": \"application/json\",\n },\n});\nconst { wsTicket, expiresIn } = await response.json();\n// expiresIn = 60 (seconds)\n```\n\nThe ticket is a short-lived (60-second) HMAC-signed token containing your userId, organizationId, and roles.\n\n### Step 2: Connect with Ticket\n\n```typescript\nconst ws = new WebSocket(\n `wss://api.yourdomain.com/realtime/v1/websocket?ws_ticket=${wsTicket}&organizationId=${activeOrgId}`\n);\n\nws.onopen = () => {\n console.log(\"Connected and authenticated!\");\n // No auth message needed — connection is pre-authenticated\n};\n```\n\nIf the ticket is invalid or expired, the WebSocket upgrade is rejected with a 401 status.\n\n## Handling Messages\n\n### CRUD Events\n\nCRUD events use the `postgres_changes` type:\n\n```typescript\nws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n\n if (msg.type === \"postgres_changes\") {\n const { table, eventType, new: newRecord, old: oldRecord } = msg;\n\n switch (eventType) {\n case \"INSERT\":\n addRecord(table, newRecord);\n break;\n case \"UPDATE\":\n updateRecord(table, newRecord);\n break;\n case \"DELETE\":\n removeRecord(table, oldRecord.id);\n break;\n }\n }\n};\n```\n\n**Event payload:**\n\n```json\n{\n \"type\": \"postgres_changes\",\n \"table\": \"applications\",\n \"schema\": \"public\",\n \"eventType\": \"INSERT\",\n \"new\": { \"id\": \"app_123\", \"candidateId\": \"cnd_456\", \"stage\": \"interview\" },\n \"old\": null\n}\n```\n\nFor UPDATE events, both `new` and `old` are populated. For DELETE events, only `old` is populated.\n\n### Custom Broadcasts\n\nCustom events use the `broadcast` type:\n\n```typescript\nif (msg.type === \"broadcast\") {\n const { event, payload } = msg;\n\n if (event === \"screening-complete\") {\n refreshApplication(payload.applicationId);\n } else if (event === \"screening:progress\") {\n updateProgressBar(payload.percent);\n }\n}\n```\n\n**Event payload:**\n\n```json\n{\n \"type\": \"broadcast\",\n \"event\": \"screening-complete\",\n \"payload\": {\n \"applicationId\": \"app_123\",\n \"candidateId\": \"cnd_456\",\n \"stage\": \"interview\"\n }\n}\n```\n\nCustom namespaces (from `defineRealtime()`) use the format `namespace:event` — e.g., `screening:started`, `screening:progress`.\n\n## Security\n\n### Role-Based Filtering\n\nEvents are only delivered to users whose role matches the broadcast's `targetRoles`. If a table's realtime config specifies `requiredRoles: [\"hiring-manager\", \"recruiter\"]`, only users with those roles receive the events.\n\n### Per-Role Masking\n\nField values are masked according to the subscriber's role. For example, with this masking config:\n\n```typescript\nmasking: {\n ssn: { type: \"ssn\", show: { roles: [\"owner\"] } },\n}\n```\n\n- **Owner sees:** `{ ssn: \"123-45-6789\" }`\n- **Recruiter sees:** `{ ssn: \"*****6789\" }`\n\nMasking is pre-computed per-role (O(roles), not O(subscribers)) for efficiency.\n\n### User-Specific Events\n\nEvents can target a specific user within an organization. Only that user receives the broadcast — all other connections in the org are skipped.\n\n### Organization Isolation\n\nEach organization has its own Durable Object instance. Users can only subscribe to events in organizations they belong to, enforced during authentication.\n\n## Reconnection\n\nWebSocket connections can drop due to network issues. Implement reconnection logic in your client — note that you need to fetch a fresh ticket on each reconnect since tickets expire after 60 seconds:\n\n```typescript\nasync function connect() {\n // Get a fresh ticket each time\n const res = await fetch(\"/realtime/v1/ws-ticket\", {\n method: \"POST\",\n headers: { Authorization: `Bearer ${sessionToken}` },\n });\n const { wsTicket } = await res.json();\n\n const ws = new WebSocket(\n `wss://api.yourdomain.com/realtime/v1/websocket?ws_ticket=${wsTicket}&organizationId=${activeOrgId}`\n );\n\n ws.onclose = () => {\n // Reconnect after delay\n setTimeout(connect, 2000);\n };\n\n ws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n handleMessage(msg);\n };\n\n return ws;\n}\n```\n\n## Environment\n\nNo additional environment variables are needed for realtime. The Broadcaster Durable Object runs inline in your main worker and reuses `BETTER_AUTH_SECRET` for WebSocket ticket signing.\n\nThe compiler generates the required `wrangler.toml` bindings automatically:\n\n```toml\n[[durable_objects.bindings]]\nname = \"BROADCASTER\"\nclass_name = \"Broadcaster\"\n\n[[migrations]]\ntag = \"v1\"\nnew_classes = [\"Broadcaster\"]\n```\n\n## Cloudflare Only\n\nRealtime requires Cloudflare Durable Objects and is only available with the Cloudflare runtime.\n\n## See Also\n\n- [Durable Objects Setup](/stack/realtime/durable-objects) — Configuration, event formats, masking, and custom namespaces\n- [Masking](/compiler/definitions/masking) — Field masking configuration"
|
|
403
403
|
},
|
|
404
404
|
"stack/storage": {
|
|
405
405
|
"title": "Storage",
|
package/dist/docs/content.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/docs/content.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,0DAA0D;AAO1D,MAAM,CAAC,MAAM,IAAI,GAA6B;IAC5C,0BAA0B,EAAE;QAC1B,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,8nRAA8nR;KAC1oR;IACD,kCAAkC,EAAE;QAClC,OAAO,EAAE,uBAAuB;QAChC,SAAS,EAAE,omNAAomN;KAChnN;IACD,2BAA2B,EAAE;QAC3B,OAAO,EAAE,aAAa;QACtB,SAAS,EAAE,8sFAA8sF;KAC1tF;IACD,8BAA8B,EAAE;QAC9B,OAAO,EAAE,oBAAoB;QAC7B,SAAS,EAAE,kgBAAkgB;KAC9gB;IACD,6BAA6B,EAAE;QAC7B,OAAO,EAAE,SAAS;QAClB,SAAS,EAAE,slBAAslB;KAClmB;IACD,mCAAmC,EAAE;QACnC,OAAO,EAAE,mBAAmB;QAC5B,SAAS,EAAE,irBAAirB;KAC7rB;IACD,qBAAqB,EAAE;QACrB,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,+8LAA+8L;KAC39L;IACD,mCAAmC,EAAE;QACnC,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,60BAA60B;KACz1B;IACD,8BAA8B,EAAE;QAC9B,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE,qwBAAqwB;KACjxB;IACD,kCAAkC,EAAE;QAClC,OAAO,EAAE,6BAA6B;QACtC,SAAS,EAAE,+uBAA+uB;KAC3vB;IACD,8BAA8B,EAAE;QAC9B,OAAO,EAAE,oBAAoB;QAC7B,SAAS,EAAE,0hBAA0hB;KACtiB;IACD,YAAY,EAAE;QACZ,OAAO,EAAE,YAAY;QACrB,SAAS,EAAE,o5KAAo5K;KACh6K;IACD,0BAA0B,EAAE;QAC1B,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,o7LAAo7L;KACh8L;IACD,uBAAuB,EAAE;QACvB,OAAO,EAAE,kBAAkB;QAC3B,SAAS,EAAE,kmIAAkmI;KAC9mI;IACD,2BAA2B,EAAE;QAC3B,OAAO,EAAE,gBAAgB;QACzB,SAAS,EAAE,61IAA61I;KACz2I;IACD,mBAAmB,EAAE;QACnB,OAAO,EAAE,cAAc;QACvB,SAAS,EAAE,43NAA43N;KACx4N;IACD,WAAW,EAAE;QACX,OAAO,EAAE,WAAW;QACpB,SAAS,EAAE,85UAA85U;KAC16U;IACD,aAAa,EAAE;QACb,OAAO,EAAE,SAAS;QAClB,SAAS,EAAE,qnOAAqnO;KACjoO;IACD,gBAAgB,EAAE;QAChB,OAAO,EAAE,sBAAsB;QAC/B,SAAS,EAAE,2ybAA2yb;KACvzb;IACD,gBAAgB,EAAE;QAChB,OAAO,EAAE,YAAY;QACrB,SAAS,EAAE,kmKAAkmK;KAC9mK;IACD,eAAe,EAAE;QACf,OAAO,EAAE,WAAW;QACpB,SAAS,EAAE,iuDAAiuD;KAC7uD;IACD,KAAK,EAAE;QACL,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,2jNAA2jN;KACvkN;IACD,oBAAoB,EAAE;QACpB,OAAO,EAAE,gBAAgB;QACzB,SAAS,EAAE,6nMAA6nM;KACzoM;IACD,WAAW,EAAE;QACX,OAAO,EAAE,cAAc;QACvB,SAAS,EAAE,i9PAAi9P;KAC79P;IACD,oBAAoB,EAAE;QACpB,OAAO,EAAE,gBAAgB;QACzB,SAAS,EAAE,wwKAAwwK;KACpxK;IACD,mBAAmB,EAAE;QACnB,OAAO,EAAE,yBAAyB;QAClC,SAAS,EAAE,kvYAAkvY;KAC9vY;IACD,qBAAqB,EAAE;QACrB,OAAO,EAAE,iBAAiB;QAC1B,SAAS,EAAE,29OAA29O;KACv+O;IACD,cAAc,EAAE;QACd,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE,0iMAA0iM;KACtjM;IACD,iBAAiB,EAAE;QACjB,OAAO,EAAE,aAAa;QACtB,SAAS,EAAE,88KAA88K;KAC19K;IACD,wCAAwC,EAAE;QACxC,OAAO,EAAE,gBAAgB;QACzB,SAAS,EAAE,2nEAA2nE;KACvoE;IACD,6BAA6B,EAAE;QAC7B,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,y0NAAy0N;KACr1N;IACD,mCAAmC,EAAE;QACnC,OAAO,EAAE,WAAW;QACpB,SAAS,EAAE,ipCAAipC;KAC7pC;IACD,yBAAyB,EAAE;QACzB,OAAO,EAAE,gBAAgB;QACzB,SAAS,EAAE,myEAAmyE;KAC/yE;IACD,wCAAwC,EAAE;QACxC,OAAO,EAAE,gBAAgB;QACzB,SAAS,EAAE,2sFAA2sF;KACvtF;IACD,yCAAyC,EAAE;QACzC,OAAO,EAAE,iBAAiB;QAC1B,SAAS,EAAE,g/DAAg/D;KAC5/D;IACD,yBAAyB,EAAE;QACzB,OAAO,EAAE,2BAA2B;QACpC,SAAS,EAAE,s8LAAs8L;KACl9L;IACD,iBAAiB,EAAE;QACjB,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/docs/content.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -64,9 +64,13 @@ my-app/
|
|
|
64
64
|
│ └── {slug}.ts # definePage() call
|
|
65
65
|
├── src/ # ← compiled output
|
|
66
66
|
│ ├── index.ts # API entry (Hono)
|
|
67
|
-
│ └──
|
|
68
|
-
│ ├──
|
|
69
|
-
│
|
|
67
|
+
│ └── apps/
|
|
68
|
+
│ ├── cms/ # ← CMS static assets (when cms: true)
|
|
69
|
+
│ │ ├── index.html
|
|
70
|
+
│ │ └── assets/
|
|
71
|
+
│ └── account/ # ← Account UI assets (when account: true/object)
|
|
72
|
+
│ ├── index.html
|
|
73
|
+
│ └── assets/
|
|
70
74
|
└── ...
|
|
71
75
|
```
|
|
72
76
|
|
|
@@ -78,6 +82,23 @@ import { defineConfig, defineRuntime, defineDatabase, defineAuth } from '@quickb
|
|
|
78
82
|
export default defineConfig({
|
|
79
83
|
name: "my-saas-app",
|
|
80
84
|
cms: true, // Embed CMS admin UI in compiled Worker (or { domain: "cms.example.com" })
|
|
85
|
+
account: { // Embed Account UI (auth, profile, orgs, admin) in compiled Worker
|
|
86
|
+
domain: "auth.example.com", // Optional: custom domain (served at / instead of /account/)
|
|
87
|
+
adminDomain: "admin.example.com", // Optional: separate admin domain
|
|
88
|
+
name: "My App",
|
|
89
|
+
companyName: "My Company",
|
|
90
|
+
tagline: "Build faster, ship sooner",
|
|
91
|
+
build: true, // Set false to skip SPA rebuild on subsequent compiles
|
|
92
|
+
auth: {
|
|
93
|
+
password: true, // Email/password login (default: false)
|
|
94
|
+
emailOTP: true, // Email OTP login (default: true)
|
|
95
|
+
passkey: true, // WebAuthn passkeys (default: true)
|
|
96
|
+
signup: true, // User registration (default: true)
|
|
97
|
+
organizations: true, // Multi-tenant org management (default: true)
|
|
98
|
+
admin: true, // Admin panel (default: true)
|
|
99
|
+
emailVerification: true, // Require email verification (default: true)
|
|
100
|
+
},
|
|
101
|
+
},
|
|
81
102
|
providers: {
|
|
82
103
|
runtime: defineRuntime("cloudflare"),
|
|
83
104
|
database: defineDatabase("cloudflare-d1"),
|
|
@@ -92,9 +113,13 @@ export default defineConfig({
|
|
|
92
113
|
});
|
|
93
114
|
```
|
|
94
115
|
|
|
116
|
+
Minimal: `account: true` enables with all defaults. `account: { build: false }` skips rebuild on subsequent compiles.
|
|
117
|
+
|
|
95
118
|
## Compile Output and State Artifacts
|
|
96
119
|
|
|
97
120
|
- Runtime code stays in `build.outputDir` (e.g. `src/`, `wrangler.toml`, `package.json`)
|
|
121
|
+
- CMS assets → `src/apps/cms/` (served at `/cms/` or root on custom domain)
|
|
122
|
+
- Account UI assets → `src/apps/account/` (served at `/account/` or root on custom domain)
|
|
98
123
|
- Drizzle migration/state artifacts are written to `quickback/drizzle/...`
|
|
99
124
|
- Security contract reports are written to `quickback/reports/...`
|
|
100
125
|
|
|
@@ -349,6 +374,74 @@ Section options: `label` (string), `fields` (string[]), `columns` (1|2, default
|
|
|
349
374
|
|
|
350
375
|
---
|
|
351
376
|
|
|
377
|
+
## Account UI — Authentication & Account Management
|
|
378
|
+
|
|
379
|
+
A complete pre-built SPA for login, signup, profile, organizations, and admin. Built from source at compile time (inside Docker) with your config baked in.
|
|
380
|
+
|
|
381
|
+
### What's Included
|
|
382
|
+
|
|
383
|
+
| Section | Pages |
|
|
384
|
+
|---------|-------|
|
|
385
|
+
| **Auth** | Login, signup, email OTP, forgot password, email verification |
|
|
386
|
+
| **Account** | Profile, devices/sessions, passkeys, account deletion |
|
|
387
|
+
| **Organizations** | Dashboard, org creation, member management, invitations, API keys, org settings |
|
|
388
|
+
| **Admin** | User management (search, create, ban/unban, reset passwords, view sessions) |
|
|
389
|
+
| **CLI** | Device authorization flow for `quickback login` |
|
|
390
|
+
|
|
391
|
+
### Enabling
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// Minimal
|
|
395
|
+
account: true
|
|
396
|
+
|
|
397
|
+
// Full config
|
|
398
|
+
account: {
|
|
399
|
+
domain: "auth.example.com",
|
|
400
|
+
adminDomain: "admin.example.com",
|
|
401
|
+
name: "My App",
|
|
402
|
+
companyName: "My Company",
|
|
403
|
+
tagline: "Build faster, ship sooner",
|
|
404
|
+
auth: {
|
|
405
|
+
password: true, // Email/password (default: false)
|
|
406
|
+
emailOTP: true, // Email OTP (default: true)
|
|
407
|
+
passkey: true, // WebAuthn passkeys (default: true)
|
|
408
|
+
signup: true, // Registration (default: true)
|
|
409
|
+
organizations: true, // Multi-tenant orgs (default: true)
|
|
410
|
+
admin: true, // Admin panel (default: true)
|
|
411
|
+
emailVerification: true, // Require email verification (default: true)
|
|
412
|
+
},
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Compile-Time Feature Gating
|
|
417
|
+
|
|
418
|
+
Disabled features are **removed before the Vite build** — route files are excluded from source, so they never appear in the JavaScript bundle. Not runtime checks.
|
|
419
|
+
|
|
420
|
+
### Routing
|
|
421
|
+
|
|
422
|
+
- **Unified domain (default):** Served at `/account/` alongside CMS at `/cms/` and API at `/api/`
|
|
423
|
+
- **Custom domain:** Served at root `/` on `auth.example.com`
|
|
424
|
+
- **Admin domain:** Admin routes served at root `/` on `admin.example.com`
|
|
425
|
+
- Cross-subdomain auth cookies auto-configured when multiple custom domains detected
|
|
426
|
+
|
|
427
|
+
### Skip Rebuild
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
account: { build: false, /* ...other options */ }
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Skips the SPA build on subsequent compiles — useful when iterating on API features only.
|
|
434
|
+
|
|
435
|
+
### Standalone & Library Usage
|
|
436
|
+
|
|
437
|
+
Account UI also works outside the compiler:
|
|
438
|
+
- **Template:** Clone repo, configure `.env`, deploy as separate Worker
|
|
439
|
+
- **Library:** `npm install quickback-better-auth-account-ui` and import as React component
|
|
440
|
+
|
|
441
|
+
Docs: `quickback docs account-ui` | https://docs.quickback.dev/account-ui
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
352
445
|
## Actions — Custom Business Logic
|
|
353
446
|
|
|
354
447
|
Defined in a separate `actions.ts` file using `defineActions()`.
|
|
@@ -497,6 +590,7 @@ Built on Better Auth with multi-tenant organization support.
|
|
|
497
590
|
- Session storage in KV namespace
|
|
498
591
|
- All auth routes at `/auth/v1/*`
|
|
499
592
|
- Extensible via Better Auth plugins
|
|
593
|
+
- **Account UI** — pre-built SPA for login, signup, profile, orgs, admin (see Account UI section above)
|
|
500
594
|
|
|
501
595
|
```typescript
|
|
502
596
|
// Config
|
|
@@ -683,6 +777,12 @@ npm install && npm run dev # 3. Run
|
|
|
683
777
|
- [Queues](https://docs.quickback.dev/stack/queues)
|
|
684
778
|
- [Embeddings](https://docs.quickback.dev/stack/vector)
|
|
685
779
|
- [Webhooks](https://docs.quickback.dev/stack/webhooks)
|
|
780
|
+
- [Account UI Overview](https://docs.quickback.dev/account-ui)
|
|
781
|
+
- [Account UI with Quickback](https://docs.quickback.dev/account-ui/with-quickback)
|
|
782
|
+
- [Account UI Standalone](https://docs.quickback.dev/account-ui/standalone)
|
|
783
|
+
- [Account UI Features](https://docs.quickback.dev/account-ui/features)
|
|
784
|
+
- [Account UI Environment Variables](https://docs.quickback.dev/account-ui/environment-variables)
|
|
785
|
+
- [Account UI Customization](https://docs.quickback.dev/account-ui/customization)
|
|
686
786
|
- [CMS Overview](https://docs.quickback.dev/cms)
|
|
687
787
|
- [CMS Dashboard](https://docs.quickback.dev/cms/dashboard)
|
|
688
788
|
- [CMS Custom Pages](https://docs.quickback.dev/cms/pages)
|
|
@@ -20,6 +20,7 @@ You deeply understand:
|
|
|
20
20
|
- **Views**: Column-level security with named projections
|
|
21
21
|
- **Validation**: Field-level validation rules
|
|
22
22
|
- **Layouts**: CMS record page field grouping with sections, columns, and collapsed state
|
|
23
|
+
- **Account UI**: Pre-built auth/account/org/admin SPA with compile-time feature gating
|
|
23
24
|
|
|
24
25
|
## When Invoked
|
|
25
26
|
|
|
@@ -218,6 +219,33 @@ submit: {
|
|
|
218
219
|
```
|
|
219
220
|
The wildcard `"*"` is NOT supported and will throw a compile error. Use `"PUBLIC"` explicitly.
|
|
220
221
|
|
|
222
|
+
### Account UI configuration
|
|
223
|
+
```typescript
|
|
224
|
+
// quickback.config.ts
|
|
225
|
+
export default defineConfig({
|
|
226
|
+
name: "my-app",
|
|
227
|
+
account: {
|
|
228
|
+
domain: "auth.example.com", // Custom domain (optional)
|
|
229
|
+
adminDomain: "admin.example.com", // Separate admin domain (optional)
|
|
230
|
+
name: "My App",
|
|
231
|
+
companyName: "My Company",
|
|
232
|
+
auth: {
|
|
233
|
+
password: true, // Email/password (default: false)
|
|
234
|
+
emailOTP: true, // Email OTP (default: true)
|
|
235
|
+
passkey: true, // WebAuthn passkeys (default: true)
|
|
236
|
+
signup: true, // Registration (default: true)
|
|
237
|
+
organizations: true, // Multi-tenant orgs (default: true)
|
|
238
|
+
admin: true, // Admin panel (default: true)
|
|
239
|
+
emailVerification: true,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
// ...
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
Minimal: `account: true`. Skip rebuild: `account: { build: false }`.
|
|
246
|
+
Disabled features are excluded at build time (route files removed before Vite build).
|
|
247
|
+
Served at `/account/` (unified domain) or `/` (custom domain).
|
|
248
|
+
|
|
221
249
|
### Custom dependencies in generated package.json
|
|
222
250
|
```typescript
|
|
223
251
|
// quickback.config.ts
|
|
@@ -245,6 +273,7 @@ Before finishing, verify:
|
|
|
245
273
|
- [ ] No audit fields in schema (auto-injected)
|
|
246
274
|
- [ ] Using `defineTable()` (not separate schema.ts + resource.ts)
|
|
247
275
|
- [ ] Actions use `defineActions()` with Zod schemas (not JSON schema)
|
|
276
|
+
- [ ] Account UI configured if auth UI needed (`account: true` or `account: { ... }`)
|
|
248
277
|
|
|
249
278
|
## Accessing Documentation
|
|
250
279
|
|
package/package.json
CHANGED
package/src/skill/SKILL.md
CHANGED
|
@@ -64,9 +64,13 @@ my-app/
|
|
|
64
64
|
│ └── {slug}.ts # definePage() call
|
|
65
65
|
├── src/ # ← compiled output
|
|
66
66
|
│ ├── index.ts # API entry (Hono)
|
|
67
|
-
│ └──
|
|
68
|
-
│ ├──
|
|
69
|
-
│
|
|
67
|
+
│ └── apps/
|
|
68
|
+
│ ├── cms/ # ← CMS static assets (when cms: true)
|
|
69
|
+
│ │ ├── index.html
|
|
70
|
+
│ │ └── assets/
|
|
71
|
+
│ └── account/ # ← Account UI assets (when account: true/object)
|
|
72
|
+
│ ├── index.html
|
|
73
|
+
│ └── assets/
|
|
70
74
|
└── ...
|
|
71
75
|
```
|
|
72
76
|
|
|
@@ -78,6 +82,23 @@ import { defineConfig, defineRuntime, defineDatabase, defineAuth } from '@quickb
|
|
|
78
82
|
export default defineConfig({
|
|
79
83
|
name: "my-saas-app",
|
|
80
84
|
cms: true, // Embed CMS admin UI in compiled Worker (or { domain: "cms.example.com" })
|
|
85
|
+
account: { // Embed Account UI (auth, profile, orgs, admin) in compiled Worker
|
|
86
|
+
domain: "auth.example.com", // Optional: custom domain (served at / instead of /account/)
|
|
87
|
+
adminDomain: "admin.example.com", // Optional: separate admin domain
|
|
88
|
+
name: "My App",
|
|
89
|
+
companyName: "My Company",
|
|
90
|
+
tagline: "Build faster, ship sooner",
|
|
91
|
+
build: true, // Set false to skip SPA rebuild on subsequent compiles
|
|
92
|
+
auth: {
|
|
93
|
+
password: true, // Email/password login (default: false)
|
|
94
|
+
emailOTP: true, // Email OTP login (default: true)
|
|
95
|
+
passkey: true, // WebAuthn passkeys (default: true)
|
|
96
|
+
signup: true, // User registration (default: true)
|
|
97
|
+
organizations: true, // Multi-tenant org management (default: true)
|
|
98
|
+
admin: true, // Admin panel (default: true)
|
|
99
|
+
emailVerification: true, // Require email verification (default: true)
|
|
100
|
+
},
|
|
101
|
+
},
|
|
81
102
|
providers: {
|
|
82
103
|
runtime: defineRuntime("cloudflare"),
|
|
83
104
|
database: defineDatabase("cloudflare-d1"),
|
|
@@ -92,9 +113,13 @@ export default defineConfig({
|
|
|
92
113
|
});
|
|
93
114
|
```
|
|
94
115
|
|
|
116
|
+
Minimal: `account: true` enables with all defaults. `account: { build: false }` skips rebuild on subsequent compiles.
|
|
117
|
+
|
|
95
118
|
## Compile Output and State Artifacts
|
|
96
119
|
|
|
97
120
|
- Runtime code stays in `build.outputDir` (e.g. `src/`, `wrangler.toml`, `package.json`)
|
|
121
|
+
- CMS assets → `src/apps/cms/` (served at `/cms/` or root on custom domain)
|
|
122
|
+
- Account UI assets → `src/apps/account/` (served at `/account/` or root on custom domain)
|
|
98
123
|
- Drizzle migration/state artifacts are written to `quickback/drizzle/...`
|
|
99
124
|
- Security contract reports are written to `quickback/reports/...`
|
|
100
125
|
|
|
@@ -349,6 +374,74 @@ Section options: `label` (string), `fields` (string[]), `columns` (1|2, default
|
|
|
349
374
|
|
|
350
375
|
---
|
|
351
376
|
|
|
377
|
+
## Account UI — Authentication & Account Management
|
|
378
|
+
|
|
379
|
+
A complete pre-built SPA for login, signup, profile, organizations, and admin. Built from source at compile time (inside Docker) with your config baked in.
|
|
380
|
+
|
|
381
|
+
### What's Included
|
|
382
|
+
|
|
383
|
+
| Section | Pages |
|
|
384
|
+
|---------|-------|
|
|
385
|
+
| **Auth** | Login, signup, email OTP, forgot password, email verification |
|
|
386
|
+
| **Account** | Profile, devices/sessions, passkeys, account deletion |
|
|
387
|
+
| **Organizations** | Dashboard, org creation, member management, invitations, API keys, org settings |
|
|
388
|
+
| **Admin** | User management (search, create, ban/unban, reset passwords, view sessions) |
|
|
389
|
+
| **CLI** | Device authorization flow for `quickback login` |
|
|
390
|
+
|
|
391
|
+
### Enabling
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// Minimal
|
|
395
|
+
account: true
|
|
396
|
+
|
|
397
|
+
// Full config
|
|
398
|
+
account: {
|
|
399
|
+
domain: "auth.example.com",
|
|
400
|
+
adminDomain: "admin.example.com",
|
|
401
|
+
name: "My App",
|
|
402
|
+
companyName: "My Company",
|
|
403
|
+
tagline: "Build faster, ship sooner",
|
|
404
|
+
auth: {
|
|
405
|
+
password: true, // Email/password (default: false)
|
|
406
|
+
emailOTP: true, // Email OTP (default: true)
|
|
407
|
+
passkey: true, // WebAuthn passkeys (default: true)
|
|
408
|
+
signup: true, // Registration (default: true)
|
|
409
|
+
organizations: true, // Multi-tenant orgs (default: true)
|
|
410
|
+
admin: true, // Admin panel (default: true)
|
|
411
|
+
emailVerification: true, // Require email verification (default: true)
|
|
412
|
+
},
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Compile-Time Feature Gating
|
|
417
|
+
|
|
418
|
+
Disabled features are **removed before the Vite build** — route files are excluded from source, so they never appear in the JavaScript bundle. Not runtime checks.
|
|
419
|
+
|
|
420
|
+
### Routing
|
|
421
|
+
|
|
422
|
+
- **Unified domain (default):** Served at `/account/` alongside CMS at `/cms/` and API at `/api/`
|
|
423
|
+
- **Custom domain:** Served at root `/` on `auth.example.com`
|
|
424
|
+
- **Admin domain:** Admin routes served at root `/` on `admin.example.com`
|
|
425
|
+
- Cross-subdomain auth cookies auto-configured when multiple custom domains detected
|
|
426
|
+
|
|
427
|
+
### Skip Rebuild
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
account: { build: false, /* ...other options */ }
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Skips the SPA build on subsequent compiles — useful when iterating on API features only.
|
|
434
|
+
|
|
435
|
+
### Standalone & Library Usage
|
|
436
|
+
|
|
437
|
+
Account UI also works outside the compiler:
|
|
438
|
+
- **Template:** Clone repo, configure `.env`, deploy as separate Worker
|
|
439
|
+
- **Library:** `npm install quickback-better-auth-account-ui` and import as React component
|
|
440
|
+
|
|
441
|
+
Docs: `quickback docs account-ui` | https://docs.quickback.dev/account-ui
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
352
445
|
## Actions — Custom Business Logic
|
|
353
446
|
|
|
354
447
|
Defined in a separate `actions.ts` file using `defineActions()`.
|
|
@@ -497,6 +590,7 @@ Built on Better Auth with multi-tenant organization support.
|
|
|
497
590
|
- Session storage in KV namespace
|
|
498
591
|
- All auth routes at `/auth/v1/*`
|
|
499
592
|
- Extensible via Better Auth plugins
|
|
593
|
+
- **Account UI** — pre-built SPA for login, signup, profile, orgs, admin (see Account UI section above)
|
|
500
594
|
|
|
501
595
|
```typescript
|
|
502
596
|
// Config
|
|
@@ -683,6 +777,12 @@ npm install && npm run dev # 3. Run
|
|
|
683
777
|
- [Queues](https://docs.quickback.dev/stack/queues)
|
|
684
778
|
- [Embeddings](https://docs.quickback.dev/stack/vector)
|
|
685
779
|
- [Webhooks](https://docs.quickback.dev/stack/webhooks)
|
|
780
|
+
- [Account UI Overview](https://docs.quickback.dev/account-ui)
|
|
781
|
+
- [Account UI with Quickback](https://docs.quickback.dev/account-ui/with-quickback)
|
|
782
|
+
- [Account UI Standalone](https://docs.quickback.dev/account-ui/standalone)
|
|
783
|
+
- [Account UI Features](https://docs.quickback.dev/account-ui/features)
|
|
784
|
+
- [Account UI Environment Variables](https://docs.quickback.dev/account-ui/environment-variables)
|
|
785
|
+
- [Account UI Customization](https://docs.quickback.dev/account-ui/customization)
|
|
686
786
|
- [CMS Overview](https://docs.quickback.dev/cms)
|
|
687
787
|
- [CMS Dashboard](https://docs.quickback.dev/cms/dashboard)
|
|
688
788
|
- [CMS Custom Pages](https://docs.quickback.dev/cms/pages)
|
|
@@ -20,6 +20,7 @@ You deeply understand:
|
|
|
20
20
|
- **Views**: Column-level security with named projections
|
|
21
21
|
- **Validation**: Field-level validation rules
|
|
22
22
|
- **Layouts**: CMS record page field grouping with sections, columns, and collapsed state
|
|
23
|
+
- **Account UI**: Pre-built auth/account/org/admin SPA with compile-time feature gating
|
|
23
24
|
|
|
24
25
|
## When Invoked
|
|
25
26
|
|
|
@@ -218,6 +219,33 @@ submit: {
|
|
|
218
219
|
```
|
|
219
220
|
The wildcard `"*"` is NOT supported and will throw a compile error. Use `"PUBLIC"` explicitly.
|
|
220
221
|
|
|
222
|
+
### Account UI configuration
|
|
223
|
+
```typescript
|
|
224
|
+
// quickback.config.ts
|
|
225
|
+
export default defineConfig({
|
|
226
|
+
name: "my-app",
|
|
227
|
+
account: {
|
|
228
|
+
domain: "auth.example.com", // Custom domain (optional)
|
|
229
|
+
adminDomain: "admin.example.com", // Separate admin domain (optional)
|
|
230
|
+
name: "My App",
|
|
231
|
+
companyName: "My Company",
|
|
232
|
+
auth: {
|
|
233
|
+
password: true, // Email/password (default: false)
|
|
234
|
+
emailOTP: true, // Email OTP (default: true)
|
|
235
|
+
passkey: true, // WebAuthn passkeys (default: true)
|
|
236
|
+
signup: true, // Registration (default: true)
|
|
237
|
+
organizations: true, // Multi-tenant orgs (default: true)
|
|
238
|
+
admin: true, // Admin panel (default: true)
|
|
239
|
+
emailVerification: true,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
// ...
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
Minimal: `account: true`. Skip rebuild: `account: { build: false }`.
|
|
246
|
+
Disabled features are excluded at build time (route files removed before Vite build).
|
|
247
|
+
Served at `/account/` (unified domain) or `/` (custom domain).
|
|
248
|
+
|
|
221
249
|
### Custom dependencies in generated package.json
|
|
222
250
|
```typescript
|
|
223
251
|
// quickback.config.ts
|
|
@@ -245,6 +273,7 @@ Before finishing, verify:
|
|
|
245
273
|
- [ ] No audit fields in schema (auto-injected)
|
|
246
274
|
- [ ] Using `defineTable()` (not separate schema.ts + resource.ts)
|
|
247
275
|
- [ ] Actions use `defineActions()` with Zod schemas (not JSON schema)
|
|
276
|
+
- [ ] Account UI configured if auth UI needed (`account: true` or `account: { ... }`)
|
|
248
277
|
|
|
249
278
|
## Accessing Documentation
|
|
250
279
|
|