@kardoe/quickback 0.5.16 → 0.6.1

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.
@@ -7,7 +7,7 @@ export const DOCS = {
7
7
  },
8
8
  "account-ui/environment-variables": {
9
9
  "title": "Environment Variables",
10
- "content": "# Environment Variables\n\nConfigure the Account UI by setting environment variables in your `.env` file or deployment platform.\n\n## Required Variables\n\nThese variables are **required** for the Account UI to function:\n\n### API Configuration\n\n```bash\n# Quickback API URL\nVITE_API_URL=https://api.example.com\n\n# Account UI URL (where the Account UI is deployed)\nVITE_ACCOUNT_APP_URL=https://account.example.com\n```\n\n## App Identity\n\nConfigure your application's basic information:\n\n```bash\n# App name (displayed in UI)\nVITE_APP_NAME=My Application\n\n# Tagline (shown on landing pages)\nVITE_APP_TAGLINE=Build faster, ship sooner\n\n# Full description\nVITE_APP_DESCRIPTION=A complete platform for building modern web applications\n\n# Company information\nVITE_COMPANY_NAME=My Company Inc.\nVITE_COMPANY_ADDRESS=123 Main St, Suite 100, San Francisco, CA 94105\n```\n\n## URLs\n\n### Main Application\n\n```bash\n# Your main application URL\n# Users will be redirected here after authentication\nVITE_APP_URL=https://app.example.com\n\n# Organization/Tenant URL pattern (optional)\n# Use {slug} as placeholder for organization identifier\n# Examples:\n# /organizations/{slug} -> app.example.com/organizations/acme\n# /workspace/{slug} -> app.example.com/workspace/acme\n# https://{slug}.example.com -> acme.example.com\nVITE_TENANT_URL_PATTERN=/organizations/{slug}\n```\n\n### Support and Legal\n\n```bash\n# Support/help URL\nVITE_SUPPORT_URL=https://support.example.com\n\n# Privacy policy URL\nVITE_PRIVACY_URL=https://example.com/privacy\n\n# Terms of service URL\nVITE_TERMS_URL=https://example.com/terms\n```\n\n## Email Configuration\n\n```bash\n# From address for all emails\nVITE_EMAIL_FROM=noreply@example.com\n\n# Reply-to address\nVITE_EMAIL_REPLY_TO=support@example.com\n\n# Support email address\nVITE_SUPPORT_EMAIL=support@example.com\n\n# AWS Region for SES (if using AWS SES)\nVITE_EMAIL_REGION=us-east-1\n```\n\n## Branding\n\n```bash\n# Primary theme color (hex code)\nVITE_THEME_COLOR=#1e293b\n\n# SEO keywords (comma-separated)\nVITE_SEO_KEYWORDS=saas,authentication,account management\n```\n\n## Feature Flags\n\nEnable or disable features using boolean environment variables:\n\n### Authentication Features\n\n```bash\n# Enable user signup (default: true)\nENABLE_SIGNUP=true\n\n# Enable email verification (default: true)\nENABLE_EMAIL_VERIFICATION=true\n\n# Disable email deliverability checks (default: false)\n# Set to true to skip checking if email addresses are deliverable\nDISABLE_EMAIL_STATUS_CHECK=false\n\n# Enable passkey (WebAuthn) authentication (default: true)\nENABLE_PASSKEYS=true\n\n# Enable passkey signup create accounts with just a passkey (default: true)\nENABLE_PASSKEY_SIGNUP=true\n\n# Enable email OTP verification (default: true)\nENABLE_EMAIL_OTP=true\n\n# Enable magic link authentication (default: true)\nENABLE_MAGIC_LINK=true\n\n# Enable social authentication (default: false)\nENABLE_SOCIAL_AUTH=false\n```\n\n### Account Management Features\n\n```bash\n# Enable account deletion (default: true)\nENABLE_ACCOUNT_DELETION=true\n\n# Enable file uploads (avatar, etc.) (default: false)\nVITE_ENABLE_FILE_UPLOADS=false\n\n# Enable dark mode toggle (default: true)\nENABLE_THEME_TOGGLE=true\n```\n\n### Organization Features\n\n```bash\n# Enable multi-tenant organizations (default: true)\nENABLE_ORGANIZATIONS=true\n\n# Enable teams within organizations (default: true)\nENABLE_TEAMS=true\n```\n\n### Admin Features\n\n```bash\n# Enable admin panel (default: true)\nENABLE_ADMIN=true\n```\n\n## Complete Example\n\nHere's a complete `.env` file with all common configuration:\n\n```bash title=\".env\"\n# ===== REQUIRED =====\nVITE_API_URL=https://api.example.com\nVITE_ACCOUNT_APP_URL=https://account.example.com\n\n# ===== APP IDENTITY =====\nVITE_APP_NAME=Acme SaaS\nVITE_APP_TAGLINE=Build faster, ship sooner\nVITE_APP_DESCRIPTION=The complete platform for modern web applications\nVITE_COMPANY_NAME=Acme Corporation\nVITE_COMPANY_ADDRESS=123 Main St, Suite 100, San Francisco, CA 94105\n\n# ===== URLS =====\nVITE_APP_URL=https://app.acme.com\nVITE_TENANT_URL_PATTERN=/organizations/{slug}\nVITE_SUPPORT_URL=https://help.acme.com\nVITE_PRIVACY_URL=https://acme.com/privacy\nVITE_TERMS_URL=https://acme.com/terms\n\n# ===== EMAIL =====\nVITE_EMAIL_FROM=noreply@acme.com\nVITE_EMAIL_REPLY_TO=support@acme.com\nVITE_SUPPORT_EMAIL=support@acme.com\nVITE_EMAIL_REGION=us-east-1\n\n# ===== BRANDING =====\nVITE_THEME_COLOR=#3b82f6\n\n# ===== SEO =====\nVITE_SEO_KEYWORDS=saas,project management,team collaboration\n\n# ===== FEATURES =====\nENABLE_SIGNUP=true\nENABLE_EMAIL_VERIFICATION=true\nENABLE_PASSKEYS=true\nENABLE_PASSKEY_SIGNUP=true\nENABLE_EMAIL_OTP=true\nENABLE_MAGIC_LINK=true\nENABLE_ORGANIZATIONS=true\nENABLE_TEAMS=true\nENABLE_ADMIN=true\nENABLE_ACCOUNT_DELETION=true\nVITE_ENABLE_FILE_UPLOADS=true\nENABLE_THEME_TOGGLE=true\nDISABLE_EMAIL_STATUS_CHECK=false\nENABLE_SOCIAL_AUTH=false\n```\n\n## Environment-Specific Configuration\n\n### Development\n\n```bash title=\".env.development\"\nVITE_API_URL=http://localhost:8787\nVITE_ACCOUNT_APP_URL=http://localhost:5173\nVITE_APP_URL=http://localhost:3000\nDISABLE_EMAIL_STATUS_CHECK=true\n```\n\n### Staging\n\n```bash title=\".env.staging\"\nVITE_API_URL=https://api-staging.example.com\nVITE_ACCOUNT_APP_URL=https://account-staging.example.com\nVITE_APP_URL=https://app-staging.example.com\n```\n\n### Production\n\n```bash title=\".env.production\"\nVITE_API_URL=https://api.example.com\nVITE_ACCOUNT_APP_URL=https://account.example.com\nVITE_APP_URL=https://app.example.com\nENABLE_EMAIL_VERIFICATION=true\nDISABLE_EMAIL_STATUS_CHECK=false\n```\n\n## Cloudflare Pages Setup\n\nWhen deploying to Cloudflare Pages, set environment variables in the dashboard:\n\n1. Go to **Settings** → **Environment Variables**\n2. Add each `VITE_*` variable\n3. Separate values for **Production** and **Preview** branches\n\n## Wrangler Configuration\n\nFor `wrangler dev` and `wrangler deploy`, add variables to `wrangler.toml`:\n\n```toml title=\"wrangler.toml\"\n[env.production.vars]\nVITE_API_URL = \"https://api.example.com\"\nVITE_ACCOUNT_APP_URL = \"https://account.example.com\"\nVITE_APP_NAME = \"My App\"\n# ... other variables\n```\n\nOr use `.dev.vars` for local development (git-ignored):\n\n```bash title=\".dev.vars\"\nVITE_API_URL=http://localhost:8787\nVITE_ACCOUNT_APP_URL=http://localhost:5173\n```\n\n## Runtime Configuration\n\nSome values can be overridden at runtime using the config API:\n\n```ts\n\nsetAppConfig({\n name: 'My Custom App',\n branding: {\n primaryColor: '#ff6600',\n logoUrl: '/custom-logo.png',\n },\n urls: {\n base: 'https://account.custom.com',\n app: 'https://app.custom.com',\n },\n});\n```\n\n## Validation\n\nThe Account UI validates configuration on startup:\n\n- **Missing VITE_API_URL**: Throws error\n- **Invalid URLs**: Logs warning\n- **Missing optional fields**: Uses defaults\n\nCheck the browser console for configuration warnings.\n\n## Next Steps\n\n- **[Feature Flags](/account-ui/features)** - Detailed feature configuration\n- **[Customization](/account-ui/customization)** - Customize labels and messages\n- **[Worker Setup](/account-ui/worker)** - Deploy to Cloudflare"
10
+ "content": "# Environment Variables\n\nConfigure the Account UI by setting environment variables in your `.env` file or deployment platform.\n\n## Required Variables\n\nThese variables are **required** for the Account UI to function:\n\n### API Configuration\n\n```bash\n# Quickback API URL\nVITE_API_URL=https://api.example.com\n\n# Account UI URL (where the Account UI is deployed)\nVITE_ACCOUNT_APP_URL=https://account.example.com\n```\n\n## App Identity\n\nConfigure your application's basic information:\n\n```bash\n# App name (displayed in UI)\nVITE_APP_NAME=My Application\n\n# Tagline (shown on landing pages)\nVITE_APP_TAGLINE=Build faster, ship sooner\n\n# Full description\nVITE_APP_DESCRIPTION=A complete platform for building modern web applications\n\n# Company information\nVITE_COMPANY_NAME=My Company Inc.\nVITE_COMPANY_ADDRESS=123 Main St, Suite 100, San Francisco, CA 94105\n```\n\n## URLs\n\n### Main Application\n\n```bash\n# Your main application URL\n# Users will be redirected here after authentication\nVITE_APP_URL=https://app.example.com\n\n# Organization/Tenant URL pattern (optional)\n# Use {slug} as placeholder for organization identifier\n# Examples:\n# /organizations/{slug} -> app.example.com/organizations/acme\n# /workspace/{slug} -> app.example.com/workspace/acme\n# https://{slug}.example.com -> acme.example.com\nVITE_TENANT_URL_PATTERN=/organizations/{slug}\n```\n\n### Support and Legal\n\n```bash\n# Support/help URL\nVITE_SUPPORT_URL=https://support.example.com\n\n# Privacy policy URL\nVITE_PRIVACY_URL=https://example.com/privacy\n\n# Terms of service URL\nVITE_TERMS_URL=https://example.com/terms\n```\n\n## Email Configuration\n\n```bash\n# From address for all emails\nVITE_EMAIL_FROM=noreply@example.com\n\n# Reply-to address\nVITE_EMAIL_REPLY_TO=support@example.com\n\n# Support email address\nVITE_SUPPORT_EMAIL=support@example.com\n\n# AWS Region for SES (if using AWS SES)\nVITE_EMAIL_REGION=us-east-1\n```\n\n## Branding\n\n```bash\n# Primary theme color (hex code)\nVITE_THEME_COLOR=#1e293b\n\n# SEO keywords (comma-separated)\nVITE_SEO_KEYWORDS=saas,authentication,account management\n```\n\n## Feature Flags\n\nEnable or disable features using boolean environment variables:\n\n### Authentication Features\n\n```bash\n# Enable user signup (default: true)\nENABLE_SIGNUP=true\n\n# Enable email verification after signup (default: true)\nENABLE_EMAIL_VERIFICATION=true\n\n# Disable email deliverability checks (default: false)\n# Set to true to skip checking if email addresses are deliverable\nDISABLE_EMAIL_STATUS_CHECK=false\n\n# Enable passkey (WebAuthn) login (default: true)\nENABLE_PASSKEY=true\n\n# Enable email OTP login and signup (default: true)\nENABLE_EMAIL_OTP=true\n\n# Enable email+password login and signup (default: false)\nENABLE_PASSWORD=false\n```\n\n### Account Management Features\n\n```bash\n# Enable file uploads (avatar, etc.) (default: false)\nVITE_ENABLE_FILE_UPLOADS=false\n```\n\n### Organization Features\n\n```bash\n# Enable multi-tenant organizations (default: true)\nENABLE_ORGANIZATIONS=true\n```\n\n### Admin Features\n\n```bash\n# Enable admin panel (default: true)\nENABLE_ADMIN=true\n```\n\n## Complete Example\n\nHere's a complete `.env` file with all common configuration:\n\n```bash title=\".env\"\n# ===== REQUIRED =====\nVITE_API_URL=https://api.example.com\nVITE_ACCOUNT_APP_URL=https://account.example.com\n\n# ===== APP IDENTITY =====\nVITE_APP_NAME=Acme SaaS\nVITE_APP_TAGLINE=Build faster, ship sooner\nVITE_APP_DESCRIPTION=The complete platform for modern web applications\nVITE_COMPANY_NAME=Acme Corporation\nVITE_COMPANY_ADDRESS=123 Main St, Suite 100, San Francisco, CA 94105\n\n# ===== URLS =====\nVITE_APP_URL=https://app.acme.com\nVITE_TENANT_URL_PATTERN=/organizations/{slug}\nVITE_SUPPORT_URL=https://help.acme.com\nVITE_PRIVACY_URL=https://acme.com/privacy\nVITE_TERMS_URL=https://acme.com/terms\n\n# ===== EMAIL =====\nVITE_EMAIL_FROM=noreply@acme.com\nVITE_EMAIL_REPLY_TO=support@acme.com\nVITE_SUPPORT_EMAIL=support@acme.com\nVITE_EMAIL_REGION=us-east-1\n\n# ===== BRANDING =====\nVITE_THEME_COLOR=#3b82f6\n\n# ===== SEO =====\nVITE_SEO_KEYWORDS=saas,project management,team collaboration\n\n# ===== FEATURES =====\nENABLE_SIGNUP=true\nENABLE_EMAIL_VERIFICATION=true\nENABLE_PASSKEY=true\nENABLE_EMAIL_OTP=true\nENABLE_PASSWORD=false\nENABLE_ORGANIZATIONS=true\nENABLE_ADMIN=true\nVITE_ENABLE_FILE_UPLOADS=true\nDISABLE_EMAIL_STATUS_CHECK=false\n```\n\n## Environment-Specific Configuration\n\n### Development\n\n```bash title=\".env.development\"\nVITE_API_URL=http://localhost:8787\nVITE_ACCOUNT_APP_URL=http://localhost:5173\nVITE_APP_URL=http://localhost:3000\nDISABLE_EMAIL_STATUS_CHECK=true\n```\n\n### Staging\n\n```bash title=\".env.staging\"\nVITE_API_URL=https://api-staging.example.com\nVITE_ACCOUNT_APP_URL=https://account-staging.example.com\nVITE_APP_URL=https://app-staging.example.com\n```\n\n### Production\n\n```bash title=\".env.production\"\nVITE_API_URL=https://api.example.com\nVITE_ACCOUNT_APP_URL=https://account.example.com\nVITE_APP_URL=https://app.example.com\nDISABLE_EMAIL_STATUS_CHECK=false\n```\n\n## Cloudflare Pages Setup\n\nWhen deploying to Cloudflare Pages, set environment variables in the dashboard:\n\n1. Go to **Settings** → **Environment Variables**\n2. Add each `VITE_*` variable\n3. Separate values for **Production** and **Preview** branches\n\n## Wrangler Configuration\n\nFor `wrangler dev` and `wrangler deploy`, add variables to `wrangler.toml`:\n\n```toml title=\"wrangler.toml\"\n[env.production.vars]\nVITE_API_URL = \"https://api.example.com\"\nVITE_ACCOUNT_APP_URL = \"https://account.example.com\"\nVITE_APP_NAME = \"My App\"\n# ... other variables\n```\n\nOr use `.dev.vars` for local development (git-ignored):\n\n```bash title=\".dev.vars\"\nVITE_API_URL=http://localhost:8787\nVITE_ACCOUNT_APP_URL=http://localhost:5173\n```\n\n## Runtime Configuration\n\nSome values can be overridden at runtime using the config API:\n\n```ts\n\nsetAppConfig({\n name: 'My Custom App',\n branding: {\n primaryColor: '#ff6600',\n logoUrl: '/custom-logo.png',\n },\n urls: {\n base: 'https://account.custom.com',\n app: 'https://app.custom.com',\n },\n});\n```\n\n## Validation\n\nThe Account UI validates configuration on startup:\n\n- **Missing VITE_API_URL**: Throws error\n- **Invalid URLs**: Logs warning\n- **Missing optional fields**: Uses defaults\n\nCheck the browser console for configuration warnings.\n\n## Next Steps\n\n- **[Feature Flags](/account-ui/features)** - Detailed feature configuration\n- **[Customization](/account-ui/customization)** - Customize labels and messages\n- **[Worker Setup](/account-ui/worker)** - Deploy to Cloudflare"
11
11
  },
12
12
  "account-ui/features/admin": {
13
13
  "title": "Admin Panel",
@@ -27,7 +27,7 @@ export const DOCS = {
27
27
  },
28
28
  "account-ui/features": {
29
29
  "title": "Feature Flags",
30
- "content": "# Feature Flags\n\nControl which features are available in your Account UI deployment using feature flags. All features are configured via environment variables.\n\n## Authentication Features\n\n### User Signup\n\n```bash\nENABLE_SIGNUP=true # default: true\n```\n\n**When enabled:**\n- Shows \"Sign Up\" link on login page\n- `/signup` route is accessible\n- New users can create accounts\n\n**When disabled:**\n- Signup route returns 404\n- Only existing users can log in\n- Useful for invite-only applications\n\n### Email Verification\n\n```bash\nENABLE_EMAIL_VERIFICATION=true # default: true\n```\n\n**When enabled:**\n- Users must verify email before full access\n- Verification email sent on signup\n- \"Resend verification email\" option available\n- Unverified users see verification prompt\n\n**When disabled:**\n- Email addresses are trusted without verification\n- Users have immediate access after signup\n\n### Email Deliverability Check\n\n```bash\nDISABLE_EMAIL_STATUS_CHECK=false # default: false\n```\n\n**When `false` (checking enabled):**\n- System validates email addresses are deliverable\n- Rejects disposable/temporary email providers\n- Prevents typos in domain names\n\n**When `true` (checking disabled):**\n- Accepts all email formats\n- Useful for development/testing\n- Allows `@test.com`, `@localhost`, etc.\n\n### Passkeys (WebAuthn)\n\n```bash\nENABLE_PASSKEYS=true # default: true\n```\n\n**When enabled:**\n- Users can register passkeys (fingerprint, Face ID, hardware keys)\n- Passwordless login option\n- \"Manage Passkeys\" page available\n- Passkey setup wizard\n\n**When disabled:**\n- No passkey registration\n- Password-only authentication\n\n**Requirements:**\n- HTTPS (passkeys require secure context)\n- Modern browser with WebAuthn support\n\n### Passkey Signup\n\n```bash\nENABLE_PASSKEY_SIGNUP=true # default: true\n```\n\n**When enabled (and browser supports WebAuthn):**\n- \"Create Account with Passkey\" button on signup page\n- Creates an anonymous session, registers a passkey, then shows an email collection step\n- Users can optionally provide their name and email, or skip to go straight to dashboard\n- If email is provided and verification is required, user verifies via OTP then goes to dashboard\n- When email delivery is also configured, both passkey and email signup options are shown with an \"Or\" divider\n\n**When disabled:**\n- Passkey signup option hidden on signup page\n- Users must sign up with email (passkey can still be added later from account settings)\n\n**Behavior when email is not configured:**\n- If `ENABLE_PASSKEY_SIGNUP=true` and email delivery is not available, only passkey signup is shown\n- If both passkey signup and email are unavailable, a fallback message directs users to contact an administrator\n\n**Requirements:**\n- HTTPS (WebAuthn requires secure context)\n- `ENABLE_PASSKEYS=true` (passkeys must be enabled)\n- `anonymous` plugin enabled on the backend\n\n### Email OTP\n\n```bash\nENABLE_EMAIL_OTP=true # default: true\n```\n\n**When enabled:**\n- Users can receive one-time passwords via email\n- Alternative to password login\n- `/email-otp` route available\n\n**When disabled:**\n- No email OTP option\n- Password or passkey required\n\n### Magic Link\n\n```bash\nENABLE_MAGIC_LINK=true # default: true\n```\n\n**When enabled:**\n- Users can request email login links\n- Passwordless authentication via email\n- No password required\n\n**When disabled:**\n- Password or other auth method required\n\n### Social Authentication\n\n```bash\nENABLE_SOCIAL_AUTH=false # default: false\n```\n\n**When enabled:**\n- OAuth login with Google, GitHub, etc.\n- \"Sign in with...\" buttons\n- Social account linking\n\n**When disabled:**\n- Email-based authentication only\n\n**Additional Configuration:**\nRequires Better Auth social providers to be configured in your API.\n\n## Account Management Features\n\n### Account Deletion\n\n```bash\nENABLE_ACCOUNT_DELETION=true # default: true\n```\n\n**When enabled:**\n- \"Delete Account\" option in settings\n- Confirmation dialog with password check\n- Permanent account removal\n\n**When disabled:**\n- No delete account option\n- Users must contact support to delete\n\n### File Uploads\n\n```bash\nVITE_ENABLE_FILE_UPLOADS=false # default: false\n```\n\n**When enabled:**\n- Avatar/profile picture upload\n- Image cropping and editing\n- File upload to R2/S3\n\n**When disabled:**\n- No file upload functionality\n- Users can only use default avatars\n\n**Requirements:**\n- R2 bucket or S3 configured\n- Upload endpoints in your API\n\n### Theme Toggle\n\n```bash\nENABLE_THEME_TOGGLE=true # default: true\n```\n\n**When enabled:**\n- Light/dark mode switcher\n- User preference saved\n- System theme detection\n\n**When disabled:**\n- Single theme mode\n- No theme switcher in UI\n\n## Organization Features\n\n### Organizations (Multi-Tenancy)\n\n```bash\nENABLE_ORGANIZATIONS=true # default: true\n```\n\n**When enabled:**\n- Users can create organizations\n- Organization management pages\n- Member invitations and roles\n- `/organizations/*` routes\n\n**When disabled:**\n- Single-user mode only\n- No organization features\n- Simpler user experience\n\n**Includes:**\n- Organization creation and deletion\n- Member management (owner, admin, member roles)\n- Invitation system\n- Organization settings\n\n### Teams\n\n```bash\nENABLE_TEAMS=true # default: true\n```\n\n**When enabled (requires ENABLE_ORGANIZATIONS=true):**\n- Sub-teams within organizations\n- Team-based permissions\n- Team management UI\n\n**When disabled:**\n- Organization members only\n- No team structure\n\n## Admin Features\n\n### Admin Panel\n\n```bash\nENABLE_ADMIN=true # default: true\n```\n\n**When enabled:**\n- `/admin` route accessible to admin users\n- User management dashboard\n- Subscription management\n- Admin-only features:\n - Create users manually\n - Ban/unban users\n - Reset user passwords\n - View all sessions\n - Manage subscriptions\n\n**When disabled:**\n- No admin panel\n- Admin must use database directly\n\n**Requirements:**\n- User must have admin role in database\n\n## Feature Combinations\n\n### Minimal Configuration (Password-Only)\n\n```bash\nENABLE_SIGNUP=true\nENABLE_EMAIL_VERIFICATION=false\nENABLE_PASSKEYS=false\nENABLE_PASSKEY_SIGNUP=false\nENABLE_EMAIL_OTP=false\nENABLE_MAGIC_LINK=false\nENABLE_SOCIAL_AUTH=false\nENABLE_ORGANIZATIONS=false\nENABLE_ADMIN=false\n```\n\nSimple email/password authentication for single-tenant apps.\n\n### Maximum Security\n\n```bash\nENABLE_SIGNUP=true\nENABLE_EMAIL_VERIFICATION=true\nDISABLE_EMAIL_STATUS_CHECK=false\nENABLE_PASSKEYS=true\nENABLE_PASSKEY_SIGNUP=true\nENABLE_EMAIL_OTP=true\nENABLE_MAGIC_LINK=true\nENABLE_SOCIAL_AUTH=true\nENABLE_ACCOUNT_DELETION=true\n```\n\nAll authentication methods with email verification and deliverability checks.\n\n### Multi-Tenant SaaS\n\n```bash\nENABLE_SIGNUP=true\nENABLE_EMAIL_VERIFICATION=true\nENABLE_PASSKEYS=true\nENABLE_PASSKEY_SIGNUP=true\nENABLE_ORGANIZATIONS=true\nENABLE_TEAMS=true\nENABLE_ADMIN=true\nVITE_ENABLE_FILE_UPLOADS=true\n```\n\nFull-featured SaaS with organizations, teams, and admin panel.\n\n### Invite-Only Platform\n\n```bash\nENABLE_SIGNUP=false\nENABLE_EMAIL_VERIFICATION=true\nENABLE_PASSKEYS=true\nENABLE_ORGANIZATIONS=true\nENABLE_ADMIN=true\n```\n\nNo public signup - users must be created by admin or invited to organizations.\n\n## Feature Detection\n\nCheck if a feature is enabled in your code:\n\n```ts\n\nif (isFeatureEnabled('organizations')) {\n // Show organizations menu\n}\n\nif (isFeatureEnabled('passkeys')) {\n // Offer passkey setup\n}\n```\n\nGet all enabled features:\n\n```ts\n\nconst enabled = getEnabledFeatures();\n// ['organizations', 'admin', 'passkeys', ...]\n```\n\n## Dynamic Feature Configuration\n\nOverride features at runtime:\n\n```ts\n\nsetAppConfig({\n features: {\n organizations: false, // Disable organizations\n passkeys: true, // Enable passkeys\n },\n});\n```\n\n## Testing Features\n\nFor local development, create `.env.local`:\n\n```bash title=\".env.local\"\n# Test with all features enabled\nENABLE_SIGNUP=true\nENABLE_EMAIL_VERIFICATION=true\nENABLE_PASSKEYS=true\nENABLE_PASSKEY_SIGNUP=true\nENABLE_EMAIL_OTP=true\nENABLE_MAGIC_LINK=true\nENABLE_ORGANIZATIONS=true\nENABLE_TEAMS=true\nENABLE_ADMIN=true\nVITE_ENABLE_FILE_UPLOADS=true\nENABLE_THEME_TOGGLE=true\nDISABLE_EMAIL_STATUS_CHECK=true # Allow test emails\n```\n\n## Next Steps\n\n- **[Environment Variables](/account-ui/environment-variables)** - Complete variable reference\n- **[Customization](/account-ui/customization)** - Customize UI text and labels\n- **[Worker Setup](/account-ui/worker)** - Deploy your configuration"
30
+ "content": "# Feature Flags\n\nControl which features are available in your Account UI deployment using feature flags. All features are configured via environment variables.\n\n## Authentication Features\n\n### User Signup\n\n```bash\nENABLE_SIGNUP=true # default: true\n```\n\nControls whether new users can register. Wired to the `ENABLE_SIGNUP` env var.\n\n**When enabled:**\n- Shows \"Sign Up\" link on login page\n- `/signup` route is accessible\n- New users can create accounts\n\n**When disabled:**\n- Signup route returns 404\n- Only existing users can log in\n- Useful for invite-only applications\n\n### Email Verification\n\n```bash\nENABLE_EMAIL_VERIFICATION=true # default: true\n```\n\nControls the email verification flow. Wired to the frontend via the `ENABLE_EMAIL_VERIFICATION` env var.\n\n**When enabled:**\n- Users must verify email before full access\n- Verification email sent on signup\n- \"Resend verification email\" option available\n- Unverified users see verification prompt\n\n**When disabled:**\n- Email addresses are trusted without verification\n- Users have immediate access after signup\n\n### Email Deliverability Check\n\n```bash\nDISABLE_EMAIL_STATUS_CHECK=false # default: false\n```\n\n**When `false` (checking enabled):**\n- System validates email addresses are deliverable\n- Rejects disposable/temporary email providers\n- Prevents typos in domain names\n\n**When `true` (checking disabled):**\n- Accepts all email formats\n- Useful for development/testing\n- Allows `@test.com`, `@localhost`, etc.\n\n### Passkey Login\n\n```bash\nENABLE_PASSKEY=true # default: true\n```\n\nControls passkey LOGIN only (not signup). When enabled, users can authenticate using passkeys.\n\n**When enabled:**\n- Users can register passkeys (fingerprint, Face ID, hardware keys)\n- Passwordless login option\n- \"Manage Passkeys\" page available\n\n**When disabled:**\n- No passkey registration\n- No passkey login option\n\n**Requirements:**\n- HTTPS (passkeys require secure context)\n- Modern browser with WebAuthn support\n\n### Email OTP\n\n```bash\nENABLE_EMAIL_OTP=true # default: true\n```\n\n**When enabled:**\n- Users can receive one-time passwords via email\n- Alternative to password login\n- `/email-otp` route available\n\n**When disabled:**\n- No email OTP option\n- Password or passkey required\n\n### Password Authentication\n\n```bash\nENABLE_PASSWORD=false # default: false\n```\n\n**When enabled:**\n- Email + password fields on login page\n- Password field on signup page\n- Traditional username/password authentication\n\n**When disabled:**\n- No password fields shown\n- Users authenticate via email OTP or passkey\n\n## Account Management Features\n\n### File Uploads\n\n```bash\nVITE_ENABLE_FILE_UPLOADS=false # default: false\n```\n\n**When enabled:**\n- Avatar/profile picture upload\n- Image cropping and editing\n- File upload to R2/S3\n\n**When disabled:**\n- No file upload functionality\n- Users can only use default avatars\n\n**Requirements:**\n- R2 bucket or S3 configured\n- Upload endpoints in your API\n\n## Organization Features\n\n### Organizations (Multi-Tenancy)\n\n```bash\nENABLE_ORGANIZATIONS=true # default: true\n```\n\n**When enabled:**\n- Users can create organizations\n- Organization management pages\n- Member invitations and roles\n- `/organizations/*` routes\n\n**When disabled:**\n- Single-user mode only\n- No organization features\n- Simpler user experience\n\n**Includes:**\n- Organization creation and deletion\n- Member management (owner, admin, member roles)\n- Invitation system\n- Organization settings\n\n## Admin Features\n\n### Admin Panel\n\n```bash\nENABLE_ADMIN=true # default: true\n```\n\n**When enabled:**\n- `/admin` route accessible to admin users\n- User management dashboard\n- Subscription management\n- Admin-only features:\n - Create users manually\n - Ban/unban users\n - Reset user passwords\n - View all sessions\n - Manage subscriptions\n\n**When disabled:**\n- No admin panel\n- Admin must use database directly\n\n**Requirements:**\n- User must have admin role in database\n\n## Feature Combinations\n\n### Minimal Configuration (Password-Only)\n\n```bash\nENABLE_PASSKEY=false\nENABLE_EMAIL_OTP=false\nENABLE_PASSWORD=true\nENABLE_ORGANIZATIONS=false\nENABLE_ADMIN=false\n```\n\nSimple email/password authentication for single-tenant apps.\n\n### Maximum Security\n\n```bash\nENABLE_PASSKEY=true\nENABLE_EMAIL_OTP=true\nENABLE_PASSWORD=false\nENABLE_EMAIL_VERIFICATION=true\n```\n\nAll authentication methods with email verification and deliverability checks.\n\n### Multi-Tenant SaaS\n\n```bash\nENABLE_PASSKEY=true\nENABLE_EMAIL_OTP=true\nENABLE_ORGANIZATIONS=true\nENABLE_ADMIN=true\n```\n\nFull-featured SaaS with organizations, teams, and admin panel.\n\n### Invite-Only Platform\n\n```bash\nENABLE_SIGNUP=false\nENABLE_PASSKEY=true\nENABLE_ORGANIZATIONS=true\nENABLE_ADMIN=true\n```\n\nNo public signup - users must be created by admin or invited to organizations.\n\n## Feature Detection\n\nCheck if a feature is enabled in your code:\n\n```ts\n\nif (isFeatureEnabled('organizations')) {\n // Show organizations menu\n}\n\nif (isFeatureEnabled('passkey')) {\n // Offer passkey setup\n}\n```\n\n## Dynamic Feature Configuration\n\nOverride features at runtime:\n\n```ts\n\nsetAppConfig({\n features: {\n organizations: false, // Disable organizations\n passkey: true, // Enable passkey\n },\n});\n```\n\n## Testing Features\n\nFor local development, create `.env.local`:\n\n```bash title=\".env.local\"\n# Test with all features enabled\nENABLE_SIGNUP=true\nENABLE_EMAIL_VERIFICATION=true\nENABLE_PASSKEY=true\nENABLE_EMAIL_OTP=true\nENABLE_PASSWORD=true\nENABLE_ORGANIZATIONS=true\nENABLE_ADMIN=true\nVITE_ENABLE_FILE_UPLOADS=true\nDISABLE_EMAIL_STATUS_CHECK=true # Allow test emails\n```\n\n## Next Steps\n\n- **[Environment Variables](/account-ui/environment-variables)** - Complete variable reference\n- **[Customization](/account-ui/customization)** - Customize UI text and labels\n- **[Worker Setup](/account-ui/worker)** - Deploy your configuration"
31
31
  },
32
32
  "account-ui/features/organizations": {
33
33
  "title": "Organizations",
@@ -35,7 +35,7 @@ export const DOCS = {
35
35
  },
36
36
  "account-ui/features/passkeys": {
37
37
  "title": "Passkeys",
38
- "content": "When `ENABLE_PASSKEYS=true`, Account UI provides passkey registration and management.\n\n## Passkey Registration\n\n- Setup wizard guides users through passkey creation\n- Supports fingerprint, Face ID, and hardware security keys\n- Multiple passkeys can be registered per account\n\n## Passkey Signup\n\nWhen `ENABLE_PASSKEY_SIGNUP=true`, users can create an account using only a passkey — no email required.\n\n- \"Create Account with Passkey\" button on the signup page\n- Creates an anonymous session, registers a passkey, then shows an **email collection step**\n- Users can optionally provide their name and email address, or skip\n- If email is provided and email delivery is configured, users verify via OTP then go to dashboard\n- If email is skipped, users go straight to dashboard\n- When email delivery is configured, both passkey and email signup options are shown on the initial signup page\n- When email delivery is **not** configured, only passkey signup is shown — preventing users from getting stuck on a verification flow that can't send emails\n\nThe email collection step is always shown (even when email delivery isn't configured) because the email is still useful as an identifier for the account.\n\n## Passkey Login\n\n- \"Sign in with passkey\" button on login page\n- Browser's native WebAuthn dialog\n- No password required\n\n## Managing Passkeys\n\n- View registered passkeys\n- Remove individual passkeys\n- Register additional passkeys\n\n## Requirements\n\n- HTTPS (WebAuthn requires secure context)\n- Modern browser with WebAuthn support\n\n## Related\n\n- [Auth Plugins](/stack/auth/plugins) — Plugin configuration\n- [Environment Variables](/account-ui/environment-variables)"
38
+ "content": "When `ENABLE_PASSKEY=true`, Account UI provides passkey registration and management.\n\n## Passkey Registration\n\n- Setup wizard guides users through passkey creation\n- Supports fingerprint, Face ID, and hardware security keys\n- Multiple passkeys can be registered per account\n\n## Passkey Login\n\n- \"Sign in with passkey\" button on login page\n- Browser's native WebAuthn dialog\n- No password required\n\n## Managing Passkeys\n\n- View registered passkeys\n- Remove individual passkeys\n- Register additional passkeys\n\n## Requirements\n\n- HTTPS (WebAuthn requires secure context)\n- Modern browser with WebAuthn support\n\n## Related\n\n- [Auth Plugins](/stack/auth/plugins) — Plugin configuration\n- [Environment Variables](/account-ui/environment-variables)"
39
39
  },
40
40
  "account-ui/features/passwordless": {
41
41
  "title": "Passwordless Authentication",
@@ -55,11 +55,11 @@ export const DOCS = {
55
55
  },
56
56
  "account-ui/standalone": {
57
57
  "title": "Standalone Usage",
58
- "content": "Account UI can be used with **any Better Auth-powered backend**, not just Quickback-compiled projects. This makes it a drop-in authentication frontend for any app using Better Auth.\n\n## Requirements\n\n- A backend running [Better Auth](https://better-auth.com) with API endpoints\n- Node.js 18+ and npm/pnpm/bun\n- A Cloudflare account (for deployment)\n\n## Setup\n\n### 1. Clone or Install the Account UI\n\n```bash\nnpx degit Kardoe-com/quickback-better-auth-account-ui my-account-app\ncd my-account-app\nnpm install\n```\n\n### 2. Configure the Auth Route Mode\n\nThe key difference from Quickback mode is the `VITE_AUTH_ROUTE` variable. Set it to `better-auth` to use Better Auth's default API paths:\n\n```bash title=\".env\"\n# Auth route mode — use 'better-auth' for standalone\nVITE_AUTH_ROUTE=better-auth\n\n# Your Better Auth backend URL\nVITE_API_URL=https://api.example.com\n\n# Where this Account UI is hosted\nVITE_ACCOUNT_APP_URL=https://account.example.com\n```\n\nThis configures the auth client to use `/api/auth` as the base path (Better Auth's default), instead of Quickback's `/auth/v1`.\n\n### 3. Configure Your App\n\n```bash title=\".env\"\n# App identity\nVITE_APP_NAME=My App\nVITE_APP_TAGLINE=Welcome to My App\n\n# Redirect after login\nVITE_APP_URL=https://app.example.com\n\n# Feature flags\nENABLE_SIGNUP=true\nENABLE_ORGANIZATIONS=true\nENABLE_PASSKEYS=true\nENABLE_EMAIL_OTP=true\n```\n\nSee [Environment Variables](/account-ui/environment-variables) for the complete reference.\n\n### 4. Build and Deploy\n\n```bash\nnpm run build\nnpx wrangler deploy\n```\n\n## Auth Route Modes\n\nAccount UI supports three routing modes, controlled by `VITE_AUTH_ROUTE`:\n\n| Mode | Value | Auth Path | Data Path | Storage Path |\n|------|-------|-----------|-----------|--------------|\n| Better Auth | `better-auth` | `/api/auth` | — | — |\n| Quickback | `quickback` | `/auth/v1` | `/api/v1` | `/storage/v1` |\n| Custom | `custom` | Custom | Custom | Custom |\n\n### Better Auth Mode (Default)\n\nRoutes all auth requests to `/api/auth/*` — the standard Better Auth convention:\n\n```bash\nVITE_AUTH_ROUTE=better-auth\n```\n\n### Custom Mode\n\nFor non-standard setups, use custom mode with explicit paths:\n\n```bash\nVITE_AUTH_ROUTE=custom\nVITE_AUTH_BASE_PATH=/my-auth\nVITE_DATA_BASE_PATH=/my-api\nVITE_STORAGE_BASE_PATH=/my-storage\n```\n\n## Backend Requirements\n\nYour Better Auth backend must have the following plugins enabled for full Account UI functionality:\n\n| Feature | Required Plugin | Required For |\n|---------|----------------|--------------|\n| Organizations | `organization` | Multi-tenant org management |\n| Admin panel | `admin` | User management dashboard |\n| API keys | `apiKey` | API key generation UI |\n| Passkeys | `@better-auth/passkey` | WebAuthn authentication |\n| Email OTP | `emailOTP` | One-time password login |\n| CLI login | `deviceAuthorization` | `quickback login` flow |\n\nOnly enable the features that match your backend's plugin configuration. Disable features for plugins you haven't installed:\n\n```bash\n# If your backend doesn't have the organization plugin:\nENABLE_ORGANIZATIONS=false\n\n# If you haven't set up passkeys:\nENABLE_PASSKEYS=false\n```\n\n## CORS Configuration\n\nYour Better Auth backend must allow requests from the Account UI's domain. Configure CORS to include:\n\n```typescript\n// In your Better Auth config\ntrustedOrigins: [\n \"https://account.example.com\", // Account UI domain\n],\n```\n\n## Differences from Quickback Mode\n\n| Aspect | Standalone | With Quickback |\n|--------|-----------|----------------|\n| Auth routes | `/api/auth/*` | `/auth/v1/*` |\n| Data API | Not used | `/api/v1/*` |\n| Storage API | Not used | `/storage/v1/*` |\n| File uploads | Manual setup | Auto-configured |\n| Backend | Any Better Auth app | Quickback-compiled |\n\n## Next Steps\n\n- [Environment Variables](/account-ui/environment-variables) — Complete configuration reference\n- [Customization](/account-ui/customization) — Branding, labels, and theming\n- [Worker Setup](/account-ui/worker) — Cloudflare Workers deployment\n- [Feature Flags](/account-ui/features) — Enable and disable features"
58
+ "content": "Account UI can be used with **any Better Auth-powered backend**, not just Quickback-compiled projects. This makes it a drop-in authentication frontend for any app using Better Auth.\n\n## Requirements\n\n- A backend running [Better Auth](https://better-auth.com) with API endpoints\n- Node.js 18+ and npm/pnpm/bun\n- A Cloudflare account (for deployment)\n\n## Setup\n\n### 1. Clone or Install the Account UI\n\n```bash\nnpx degit Kardoe-com/quickback-better-auth-account-ui my-account-app\ncd my-account-app\nnpm install\n```\n\n### 2. Configure the Auth Route Mode\n\nThe key difference from Quickback mode is the `VITE_AUTH_ROUTE` variable. Set it to `better-auth` to use Better Auth's default API paths:\n\n```bash title=\".env\"\n# Auth route mode — use 'better-auth' for standalone\nVITE_AUTH_ROUTE=better-auth\n\n# Your Better Auth backend URL\nVITE_API_URL=https://api.example.com\n\n# Where this Account UI is hosted\nVITE_ACCOUNT_APP_URL=https://account.example.com\n```\n\nThis configures the auth client to use `/api/auth` as the base path (Better Auth's default), instead of Quickback's `/auth/v1`.\n\n### 3. Configure Your App\n\n```bash title=\".env\"\n# App identity\nVITE_APP_NAME=My App\nVITE_APP_TAGLINE=Welcome to My App\n\n# Redirect after login\nVITE_APP_URL=https://app.example.com\n\n# Feature flags\nENABLE_SIGNUP=true\nENABLE_ORGANIZATIONS=true\nENABLE_PASSKEY=true\nENABLE_EMAIL_OTP=true\n```\n\nSee [Environment Variables](/account-ui/environment-variables) for the complete reference.\n\n### 4. Build and Deploy\n\n```bash\nnpm run build\nnpx wrangler deploy\n```\n\n## Auth Route Modes\n\nAccount UI supports three routing modes, controlled by `VITE_AUTH_ROUTE`:\n\n| Mode | Value | Auth Path | Data Path | Storage Path |\n|------|-------|-----------|-----------|--------------|\n| Better Auth | `better-auth` | `/api/auth` | — | — |\n| Quickback | `quickback` | `/auth/v1` | `/api/v1` | `/storage/v1` |\n| Custom | `custom` | Custom | Custom | Custom |\n\n### Better Auth Mode (Default)\n\nRoutes all auth requests to `/api/auth/*` — the standard Better Auth convention:\n\n```bash\nVITE_AUTH_ROUTE=better-auth\n```\n\n### Custom Mode\n\nFor non-standard setups, use custom mode with explicit paths:\n\n```bash\nVITE_AUTH_ROUTE=custom\nVITE_AUTH_BASE_PATH=/my-auth\nVITE_DATA_BASE_PATH=/my-api\nVITE_STORAGE_BASE_PATH=/my-storage\n```\n\n## Backend Requirements\n\nYour Better Auth backend must have the following plugins enabled for full Account UI functionality:\n\n| Feature | Required Plugin | Required For |\n|---------|----------------|--------------|\n| Organizations | `organization` | Multi-tenant org management |\n| Admin panel | `admin` | User management dashboard |\n| API keys | `apiKey` | API key generation UI |\n| Passkeys | `@better-auth/passkey` | WebAuthn authentication |\n| Email OTP | `emailOTP` | One-time password login |\n| CLI login | `deviceAuthorization` | `quickback login` flow |\n\nOnly enable the features that match your backend's plugin configuration. Disable features for plugins you haven't installed:\n\n```bash\n# If your backend doesn't have the organization plugin:\nENABLE_ORGANIZATIONS=false\n\n# If you haven't set up passkeys:\nENABLE_PASSKEYS=false\n```\n\n## CORS Configuration\n\nYour Better Auth backend must allow requests from the Account UI's domain. Configure CORS to include:\n\n```typescript\n// In your Better Auth config\ntrustedOrigins: [\n \"https://account.example.com\", // Account UI domain\n],\n```\n\n## Differences from Quickback Mode\n\n| Aspect | Standalone | With Quickback |\n|--------|-----------|----------------|\n| Auth routes | `/api/auth/*` | `/auth/v1/*` |\n| Data API | Not used | `/api/v1/*` |\n| Storage API | Not used | `/storage/v1/*` |\n| File uploads | Manual setup | Auto-configured |\n| Backend | Any Better Auth app | Quickback-compiled |\n\n## Next Steps\n\n- [Environment Variables](/account-ui/environment-variables) — Complete configuration reference\n- [Customization](/account-ui/customization) — Branding, labels, and theming\n- [Worker Setup](/account-ui/worker) — Cloudflare Workers deployment\n- [Feature Flags](/account-ui/features) — Enable and disable features"
59
59
  },
60
60
  "account-ui/with-quickback": {
61
61
  "title": "With Quickback",
62
- "content": "When using Account UI with a Quickback-compiled backend, set the auth route mode to `quickback` to match Quickback's API structure. This is the recommended setup for Quickback projects.\n\n## Setup\n\nYou can use Account UI with Quickback as either a standalone template or an npm library.\n\n### Option A: Template (Clone the Source)\n\n#### 1. Clone the Template\n\n```bash\nnpx degit Kardoe-com/quickback-better-auth-account-ui my-account-app\ncd my-account-app\nnpm install\n```\n\n#### 2. Set the Auth Route Mode\n\nConfigure Account UI to use Quickback's route conventions:\n\n```bash title=\".env\"\n# Use Quickback route mode\nVITE_AUTH_ROUTE=quickback\n\n# Your Quickback API URL\nVITE_API_URL=https://api.example.com\n\n# Where this Account UI is hosted\nVITE_ACCOUNT_APP_URL=https://account.example.com\n\n# Your main application URL (redirect after login)\nVITE_APP_URL=https://app.example.com\n```\n\n#### 3. Configure Your App\n\n```bash title=\".env\"\n# App identity\nVITE_APP_NAME=My App\nVITE_APP_TAGLINE=Build faster, ship sooner\nVITE_COMPANY_NAME=My Company Inc.\n\n# Organization routing\nVITE_TENANT_URL_PATTERN=/organizations/{slug}\n\n# Features (match your quickback.config.ts)\nENABLE_SIGNUP=true\nENABLE_ORGANIZATIONS=true\nENABLE_PASSKEYS=true\nENABLE_EMAIL_OTP=true\nENABLE_ADMIN=true\n```\n\n#### 4. Deploy\n\n```bash\nnpm run build\nnpx wrangler deploy\n```\n\n### Option B: Library (npm install)\n\nInstall as a dependency in your existing React app:\n\n```bash\nnpm install quickback-better-auth-account-ui\n```\n\n```tsx title=\"src/main.tsx\"\n\nsetAppConfig({\n authRoute: 'quickback',\n name: 'My App',\n companyName: 'My Company Inc.',\n tagline: 'Build faster, ship sooner',\n});\n\n// Render AuthApp inside your router\n```\n\nFor a Cloudflare Worker, re-export the worker entry:\n\n```ts title=\"src/worker.ts\"\nexport { default } from 'quickback-better-auth-account-ui/worker';\n```\n\nSee [Library Usage](/account-ui/library-usage) for the complete guide.\n\n## How It Works\n\nIn Quickback mode, Account UI routes requests to three API paths:\n\n| Path | Purpose | Examples |\n|------|---------|---------|\n| `/auth/v1/*` | Authentication | Login, signup, sessions, passkeys |\n| `/api/v1/*` | Data API | Organizations, API keys |\n| `/storage/v1/*` | File storage | Avatar uploads, file management |\n\nThese paths match what the Quickback compiler generates in your backend's `src/index.ts`.\n\n## Architecture\n\nA typical Quickback deployment has three services:\n\n```\naccount.example.com → Account UI (this app)\napi.example.com → Quickback-compiled API\napp.example.com → Your main application\n```\n\nThe Account UI handles all authentication flows. After login, users are redirected to your main application at `VITE_APP_URL`. Organization-specific redirects use the `VITE_TENANT_URL_PATTERN`.\n\n## Matching Features to Config\n\nEnable Account UI features that match your `quickback.config.ts`:\n\n```typescript title=\"quickback/quickback.config.ts\"\nexport default defineConfig({\n features: [\"organizations\"], // → ENABLE_ORGANIZATIONS=true\n providers: {\n auth: {\n name: \"better-auth\",\n config: {\n plugins: [\n \"organization\", // → ENABLE_ORGANIZATIONS=true\n \"admin\", // → ENABLE_ADMIN=true\n \"apiKey\", // → API key UI available\n \"passkey\", // → ENABLE_PASSKEYS=true\n \"emailOtp\", // → ENABLE_EMAIL_OTP=true\n \"deviceAuthorization\", // → CLI login flow available\n ],\n },\n },\n },\n});\n```\n\nIf your config doesn't include a plugin, disable the corresponding feature flag in Account UI to hide that section of the UI.\n\n## File Uploads\n\nWhen your Quickback project includes R2 file storage, enable avatar uploads:\n\n```bash\nVITE_ENABLE_FILE_UPLOADS=true\n```\n\nThis allows users to upload profile avatars via the storage API at `/storage/v1/object/avatars/`.\n\n## CLI Authorization\n\nIf your Quickback config includes the `deviceAuthorization` plugin, Account UI provides the `/cli/authorize` page. When users run `quickback login`, they're directed to this page to approve the CLI session.\n\nNo additional configuration is needed — the device authorization endpoints are generated automatically by the compiler.\n\n## Environment-Specific Setup\n\n### Development\n\n```bash title=\".env.development\"\nVITE_AUTH_ROUTE=quickback\nVITE_API_URL=http://localhost:8787\nVITE_ACCOUNT_APP_URL=http://localhost:5173\nVITE_APP_URL=http://localhost:3000\nDISABLE_EMAIL_STATUS_CHECK=true\n```\n\n### Production\n\n```bash title=\".env.production\"\nVITE_AUTH_ROUTE=quickback\nVITE_API_URL=https://api.example.com\nVITE_ACCOUNT_APP_URL=https://account.example.com\nVITE_APP_URL=https://app.example.com\nENABLE_EMAIL_VERIFICATION=true\n```\n\n## Wrangler Configuration\n\nFor Cloudflare Workers deployment, set variables in `wrangler.toml`:\n\n```toml title=\"wrangler.toml\"\n[vars]\nVITE_AUTH_ROUTE = \"quickback\"\nVITE_API_URL = \"https://api.example.com\"\nVITE_ACCOUNT_APP_URL = \"https://account.example.com\"\nVITE_APP_URL = \"https://app.example.com\"\nVITE_APP_NAME = \"My App\"\nVITE_TENANT_URL_PATTERN = \"/organizations/{slug}\"\n```\n\n## Next Steps\n\n- [Environment Variables](/account-ui/environment-variables) — Complete variable reference\n- [Customization](/account-ui/customization) — Branding, labels, and theming\n- [Feature Flags](/account-ui/features) — Enable and disable features\n- [Worker Setup](/account-ui/worker) — Cloudflare Workers deployment details"
62
+ "content": "When using Account UI with a Quickback-compiled backend, set the auth route mode to `quickback` to match Quickback's API structure. This is the recommended setup for Quickback projects.\n\n## Setup\n\nYou can use Account UI with Quickback as either a standalone template or an npm library.\n\n### Option A: Template (Clone the Source)\n\n#### 1. Clone the Template\n\n```bash\nnpx degit Kardoe-com/quickback-better-auth-account-ui my-account-app\ncd my-account-app\nnpm install\n```\n\n#### 2. Set the Auth Route Mode\n\nConfigure Account UI to use Quickback's route conventions:\n\n```bash title=\".env\"\n# Use Quickback route mode\nVITE_AUTH_ROUTE=quickback\n\n# Your Quickback API URL\nVITE_API_URL=https://api.example.com\n\n# Where this Account UI is hosted\nVITE_ACCOUNT_APP_URL=https://account.example.com\n\n# Your main application URL (redirect after login)\nVITE_APP_URL=https://app.example.com\n```\n\n#### 3. Configure Your App\n\n```bash title=\".env\"\n# App identity\nVITE_APP_NAME=My App\nVITE_APP_TAGLINE=Build faster, ship sooner\nVITE_COMPANY_NAME=My Company Inc.\n\n# Organization routing\nVITE_TENANT_URL_PATTERN=/organizations/{slug}\n\n# Features (match your quickback.config.ts)\nENABLE_SIGNUP=true\nENABLE_ORGANIZATIONS=true\nENABLE_PASSKEY=true\nENABLE_EMAIL_OTP=true\nENABLE_ADMIN=true\n```\n\n#### 4. Deploy\n\n```bash\nnpm run build\nnpx wrangler deploy\n```\n\n### Option B: Library (npm install)\n\nInstall as a dependency in your existing React app:\n\n```bash\nnpm install quickback-better-auth-account-ui\n```\n\n```tsx title=\"src/main.tsx\"\n\nsetAppConfig({\n authRoute: 'quickback',\n name: 'My App',\n companyName: 'My Company Inc.',\n tagline: 'Build faster, ship sooner',\n});\n\n// Render AuthApp inside your router\n```\n\nFor a Cloudflare Worker, re-export the worker entry:\n\n```ts title=\"src/worker.ts\"\nexport { default } from 'quickback-better-auth-account-ui/worker';\n```\n\nSee [Library Usage](/account-ui/library-usage) for the complete guide.\n\n## How It Works\n\nIn Quickback mode, Account UI routes requests to three API paths:\n\n| Path | Purpose | Examples |\n|------|---------|---------|\n| `/auth/v1/*` | Authentication | Login, signup, sessions, passkeys |\n| `/api/v1/*` | Data API | Organizations, API keys |\n| `/storage/v1/*` | File storage | Avatar uploads, file management |\n\nThese paths match what the Quickback compiler generates in your backend's `src/index.ts`.\n\n## Architecture\n\nA typical Quickback deployment has three services:\n\n```\naccount.example.com → Account UI (this app)\napi.example.com → Quickback-compiled API\napp.example.com → Your main application\n```\n\nThe Account UI handles all authentication flows. After login, users are redirected to your main application at `VITE_APP_URL`. Organization-specific redirects use the `VITE_TENANT_URL_PATTERN`.\n\n## Matching Features to Config\n\nEnable Account UI features that match your `quickback.config.ts`:\n\n```typescript title=\"quickback/quickback.config.ts\"\nexport default defineConfig({\n features: [\"organizations\"], // → ENABLE_ORGANIZATIONS=true\n providers: {\n auth: {\n name: \"better-auth\",\n config: {\n plugins: [\n \"organization\", // → ENABLE_ORGANIZATIONS=true\n \"admin\", // → ENABLE_ADMIN=true\n \"apiKey\", // → API key UI available\n \"passkey\", // → ENABLE_PASSKEY=true\n \"emailOtp\", // → ENABLE_EMAIL_OTP=true\n \"deviceAuthorization\", // → CLI login flow available\n ],\n },\n },\n },\n});\n```\n\nIf your config doesn't include a plugin, disable the corresponding feature flag in Account UI to hide that section of the UI.\n\n## File Uploads\n\nWhen your Quickback project includes R2 file storage, enable avatar uploads:\n\n```bash\nVITE_ENABLE_FILE_UPLOADS=true\n```\n\nThis allows users to upload profile avatars via the storage API at `/storage/v1/object/avatars/`.\n\n## CLI Authorization\n\nIf your Quickback config includes the `deviceAuthorization` plugin, Account UI provides the `/cli/authorize` page. When users run `quickback login`, they're directed to this page to approve the CLI session.\n\nNo additional configuration is needed — the device authorization endpoints are generated automatically by the compiler.\n\n## Environment-Specific Setup\n\n### Development\n\n```bash title=\".env.development\"\nVITE_AUTH_ROUTE=quickback\nVITE_API_URL=http://localhost:8787\nVITE_ACCOUNT_APP_URL=http://localhost:5173\nVITE_APP_URL=http://localhost:3000\nDISABLE_EMAIL_STATUS_CHECK=true\n```\n\n### Production\n\n```bash title=\".env.production\"\nVITE_AUTH_ROUTE=quickback\nVITE_API_URL=https://api.example.com\nVITE_ACCOUNT_APP_URL=https://account.example.com\nVITE_APP_URL=https://app.example.com\nENABLE_EMAIL_VERIFICATION=true\n```\n\n## Wrangler Configuration\n\nFor Cloudflare Workers deployment, set variables in `wrangler.toml`:\n\n```toml title=\"wrangler.toml\"\n[vars]\nVITE_AUTH_ROUTE = \"quickback\"\nVITE_API_URL = \"https://api.example.com\"\nVITE_ACCOUNT_APP_URL = \"https://account.example.com\"\nVITE_APP_URL = \"https://app.example.com\"\nVITE_APP_NAME = \"My App\"\nVITE_TENANT_URL_PATTERN = \"/organizations/{slug}\"\n```\n\n## Next Steps\n\n- [Environment Variables](/account-ui/environment-variables) — Complete variable reference\n- [Customization](/account-ui/customization) — Branding, labels, and theming\n- [Feature Flags](/account-ui/features) — Enable and disable features\n- [Worker Setup](/account-ui/worker) — Cloudflare Workers deployment details"
63
63
  },
64
64
  "account-ui/worker": {
65
65
  "title": "Worker Setup",
@@ -83,7 +83,7 @@ export const DOCS = {
83
83
  },
84
84
  "cms": {
85
85
  "title": "Quickback CMS",
86
- "content": "# Quickback CMS\n\nA schema-driven admin interface that reads `schema-registry.json` generated by the Quickback compiler. Every table, column, action, view, and security rule is rendered automatically. Zero UI code per table.\n\n## Overview\n\nThe CMS generates its entire UI from your Quickback definitions. Define a table with columns, guards, masking, views, and actions in your feature files. Run the compiler. The CMS reads the resulting schema registry and renders a complete admin interface — data tables, inline editing, action dialogs, role-based access, and field masking — all without writing a single line of UI code.\n\n## Key Features\n\n- **Schema-driven** — Zero UI code per table. Add a table, recompile, and it appears in the CMS.\n- **Dual view modes** — Table browse mode for navigation and Data Table mode for spreadsheet-style editing.\n- **Role-based access** — Owner, admin, and member roles with live switching. CRUD buttons hidden when unauthorized.\n- **Inline spreadsheet editing** — Excel/Google Sheets-like editing with keyboard navigation (arrows, Tab, Enter, Escape).\n- **FK typeahead** — Server-side search for foreign key fields with debounced queries and keyboard navigation.\n- **Field masking** — Email, phone, SSN, and redaction patterns applied per role. Masked fields show a lock icon.\n- **Custom actions** — Action dialogs with auto-generated input forms, access filtering, CMS metadata (icons, categories, confirmations), and side effects warnings.\n- **Views** — Named column-level projections per role. \"All Fields\" plus custom views in the toolbar.\n- **Auto-form generation** — Create and edit forms built from guards (createable/updatable fields).\n- **Display column auto-detection** — FK labels resolved automatically from `name`, `title`, `label`, `code`, and other common patterns.\n\n## Architecture\n\nThe CMS sits at the end of the Quickback compilation pipeline:\n\n```\nQuickback Definitions (feature files)\n |\n v\n Compiler\n |\n v\n schema-registry.json\n |\n v\n CMS reads it\n |\n v\n Renders admin UI\n```\n\nYour feature definitions are the single source of truth. The compiler extracts all metadata — columns, types, guards, masking rules, views, actions, validation, and firewall config — into a static JSON file. The CMS consumes that file and renders the appropriate UI for each table.\n\n## Quick Start\n\n### 1. Enable Schema Registry Generation\n\nAdd `schemaRegistry` to your `quickback.config.ts`:\n\n```typescript title=\"quickback/quickback.config.ts\"\nexport default defineConfig({\n schemaRegistry: { generate: true },\n // ... rest of your config\n});\n```\n\n### 2. Compile\n\n```bash\nquickback compile\n```\n\nThis generates `schema-registry.json` alongside your compiled API output.\n\n### 3. Point the CMS at Your Schema\n\nThe CMS reads the generated `schema-registry.json` to discover all tables, columns, security rules, and actions. In development, it can also run in demo mode with mock data and a role switcher for testing different access levels.\n\n## Next Steps\n\n- **[Schema Registry](/cms/schema-registry)** — Understand the JSON format the compiler generates\n- **[Connecting](/cms/connecting)** — Demo mode vs. live mode setup\n- **[Table Views](/cms/table-views)** — Browse and Data Table view modes\n- **[Inline Editing](/cms/inline-editing)** — Spreadsheet-style editing and FK typeahead\n- **[Security](/cms/security)** — How the CMS enforces all four security layers\n- **[Actions](/cms/actions)** — Custom actions with input forms, access filtering, and CMS metadata\n- **[Schema Format Reference](/cms/schema-format)** — Full TypeScript types for schema-registry.json\n- **[Components Reference](/cms/components)** — All CMS components and their props"
86
+ "content": "# Quickback CMS\n\nA schema-driven admin interface that reads `schema-registry.json` generated by the Quickback compiler. Every table, column, action, view, and security rule is rendered automatically. Zero UI code per table.\n\n## Overview\n\nThe CMS generates its entire UI from your Quickback definitions. Define a table with columns, guards, masking, views, and actions in your feature files. Run the compiler. The CMS reads the resulting schema registry and renders a complete admin interface — data tables, inline editing, action dialogs, role-based access, and field masking — all without writing a single line of UI code.\n\n## Key Features\n\n- **Schema-driven** — Zero UI code per table. Add a table, recompile, and it appears in the CMS.\n- **Dual view modes** — Table browse mode for navigation and Data Table mode for spreadsheet-style editing.\n- **Role-based access** — Owner, admin, and member roles with live switching. CRUD buttons hidden when unauthorized.\n- **Inline spreadsheet editing** — Excel/Google Sheets-like editing with keyboard navigation (arrows, Tab, Enter, Escape).\n- **FK typeahead** — Server-side search for foreign key fields with debounced queries and keyboard navigation.\n- **Field masking** — Email, phone, SSN, and redaction patterns applied per role. Masked fields show a lock icon.\n- **Custom actions** — Action dialogs with auto-generated input forms, access filtering, CMS metadata (icons, categories, confirmations), and side effects warnings.\n- **Views** — Named column-level projections per role. \"All Fields\" plus custom views in the toolbar.\n- **Auto-form generation** — Create and edit forms built from guards (createable/updatable fields).\n- **Display column auto-detection** — FK labels resolved automatically from `name`, `title`, `label`, `code`, and other common patterns.\n\n## Architecture\n\nThe CMS sits at the end of the Quickback compilation pipeline:\n\n```\nQuickback Definitions (feature files)\n |\n v\n Compiler\n |\n v\n schema-registry.json\n |\n v\n CMS reads it\n |\n v\n Renders admin UI\n```\n\nYour feature definitions are the single source of truth. The compiler extracts all metadata — columns, types, guards, masking rules, views, actions, validation, and firewall config — into a static JSON file. The CMS consumes that file and renders the appropriate UI for each table.\n\n## Quick Start\n\n### 1. Compile\n\n```bash\nquickback compile\n```\n\nThe compiler automatically generates `schema-registry.json` alongside your compiled API output.\n\n### 3. Point the CMS at Your Schema\n\nThe CMS reads the generated `schema-registry.json` to discover all tables, columns, security rules, and actions. In development, it can also run in demo mode with mock data and a role switcher for testing different access levels.\n\n## Next Steps\n\n- **[Schema Registry](/cms/schema-registry)** — Understand the JSON format the compiler generates\n- **[Connecting](/cms/connecting)** — Demo mode vs. live mode setup\n- **[Table Views](/cms/table-views)** — Browse and Data Table view modes\n- **[Inline Editing](/cms/inline-editing)** — Spreadsheet-style editing and FK typeahead\n- **[Security](/cms/security)** — How the CMS enforces all four security layers\n- **[Actions](/cms/actions)** — Custom actions with input forms, access filtering, and CMS metadata\n- **[Schema Format Reference](/cms/schema-format)** — Full TypeScript types for schema-registry.json\n- **[Components Reference](/cms/components)** — All CMS components and their props"
87
87
  },
88
88
  "cms/inline-editing": {
89
89
  "title": "Inline Editing",
@@ -99,7 +99,7 @@ export const DOCS = {
99
99
  },
100
100
  "cms/schema-registry": {
101
101
  "title": "Schema Registry",
102
- "content": "# Schema Registry\n\nThe schema registry is a static JSON file generated by the Quickback compiler. It contains full metadata about every table in your project — columns, types, guards, masking rules, views, actions, validation, and firewall config. The CMS reads this file to render its entire UI.\n\n## Enabling Generation\n\nAdd `schemaRegistry` to your config:\n\n```typescript title=\"quickback/quickback.config.ts\"\nexport default defineConfig({\n schemaRegistry: { generate: true },\n // ... rest of your config\n});\n```\n\nRun `quickback compile` and the compiler outputs `schema-registry.json` alongside your compiled API files.\n\n## Output Shape\n\nThe top-level structure of `schema-registry.json`:\n\n```json\n{\n \"generatedAt\": \"2026-02-14T06:26:53.554Z\",\n \"generatedBy\": \"quickback-compiler\",\n \"version\": \"1.0.0\",\n \"features\": { ... },\n \"tables\": { ... },\n \"tablesByFeature\": { ... }\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `generatedAt` | `string` | ISO timestamp of generation |\n| `generatedBy` | `string` | Always `\"quickback-compiler\"` |\n| `version` | `string` | Compiler version used |\n| `features` | `Record<string, string[]>` | Feature name to file list mapping |\n| `tables` | `Record<string, TableMeta>` | Table name to full metadata |\n| `tablesByFeature` | `Record<string, string[]>` | Feature name to table name list |\n\n## TableMeta\n\nEach table entry contains everything the CMS needs to render its UI:\n\n```typescript\ninterface TableMeta {\n name: string; // camelCase table name (e.g., \"accountCode\")\n dbName: string; // SQL table name (e.g., \"account_code\")\n feature: string; // Parent feature name\n columns: ColumnMeta[]; // All columns including audit fields\n firewall: Record<string, unknown>; // Tenant isolation config\n crud: Record<string, CrudConfig>; // Per-operation access rules\n guards: {\n createable: string[]; // Fields allowed on create\n updatable: string[]; // Fields allowed on update\n immutable: string[]; // Fields locked after creation\n protected: Record<string, string[]>; // Fields only updatable via actions\n };\n masking: Record<string, MaskingRule>; // Per-field masking rules\n views: Record<string, ViewConfig>; // Named column projections\n validation: Record<string, ValidationRule>; // Per-field validation\n actions: ActionMeta[]; // Available actions for this table\n displayColumn?: string; // Human-readable label column\n internal?: boolean; // Hidden from CMS sidebar when true\n}\n```\n\n## ColumnMeta\n\nEach column in the `columns` array:\n\n```typescript\ninterface ColumnMeta {\n name: string; // Property name (camelCase)\n dbName: string; // SQL column name (snake_case)\n type: \"text\" | \"integer\" | \"real\" | \"blob\"; // SQLite type\n mode?: \"boolean\"; // When an integer represents a boolean\n primaryKey: boolean;\n notNull: boolean;\n defaultValue?: string | number | boolean;\n fkTarget?: string; // Target table name for FK columns\n}\n```\n\nThe compiler automatically includes audit fields (`id`, `organizationId`, `createdAt`, `createdBy`, `modifiedAt`, `modifiedBy`, `deletedAt`) at the beginning of every table's column list.\n\n### fkTarget — FK Resolution\n\nColumns ending in `Id` may have a `fkTarget` property indicating which table they reference. This is resolved in priority order:\n\n1. **Explicit `references`** — from your `defineTable()` config (highest priority)\n2. **Drizzle `.references()`** — parsed from schema source code\n3. **Convention** — strip `Id` suffix, match table name directly\n\n```json\n{\n \"name\": \"vendorId\",\n \"type\": \"text\",\n \"fkTarget\": \"contact\"\n}\n```\n\nThe CMS uses `fkTarget` to render typeahead/lookup inputs that search the correct table instead of showing raw IDs.\n\n## Input Hints\n\nTables with `inputHints` configured in `defineTable()` include an `inputHints` map in their metadata:\n\n```json\n{\n \"name\": \"invoice\",\n \"inputHints\": {\n \"status\": \"select\",\n \"sortOrder\": \"radio\",\n \"isPartialPaymentDisabled\": \"checkbox\",\n \"headerMessage\": \"textarea\"\n }\n}\n```\n\nThe CMS reads these hints to render the appropriate form control for each field. See [Input Hints](/compiler/definitions/schema#input-hints) for the full list of supported values.\n\n## Display Column\n\nThe `displayColumn` field tells the CMS which column to use as a human-readable label for a record. This is used in:\n\n- FK typeahead dropdowns (showing names instead of IDs)\n- Record titles in detail views\n- Breadcrumb labels\n\n### Auto-Detection\n\nIf you don't explicitly set `displayColumn` in your resource config, the compiler auto-detects it by scanning column names in priority order:\n\n1. `name`\n2. `title`\n3. `label`\n4. `headline`\n5. `subject`\n6. `code`\n7. `displayName`\n8. `fullName`\n9. `description`\n\nThe first match wins. If no candidate matches, the table has no display column and the CMS falls back to showing IDs.\n\n### Explicit Config\n\nSet it explicitly in your table definition:\n\n```typescript\nexport default defineTable(contacts, {\n displayColumn: \"companyName\",\n // ...\n});\n```\n\n## FK Label Resolution\n\nWhen a table has foreign key columns (ending in `Id`), the API enriches list responses with `_label` fields. For example, a `roomTypeId` column gets a corresponding `roomType_label` field containing the display column value from the referenced table.\n\nThe CMS uses these `_label` fields to show human-readable names in table cells and FK typeahead dropdowns instead of raw UUIDs.\n\n```\nroomTypeId: \"rt_abc123\" → displayed as \"Master Bedroom\"\naccountCodeId: \"ac_xyz789\" → displayed as \"4100 - Revenue\"\n```\n\nThe FK target table is resolved from the `fkTarget` property on each column. For simple cases like `roomTypeId` → `roomType`, the compiler auto-detects it. For non-obvious mappings (e.g., `vendorId` → `contact`), use explicit [`references`](/compiler/definitions/schema#references) in your `defineTable()` config.\n\n## Next Steps\n\n- **[Connecting](/cms/connecting)** — Demo mode vs. live mode setup\n- **[Schema Format Reference](/cms/schema-format)** — Full TypeScript types"
102
+ "content": "# Schema Registry\n\nThe schema registry is a static JSON file generated by the Quickback compiler. It contains full metadata about every table in your project — columns, types, guards, masking rules, views, actions, validation, and firewall config. The CMS reads this file to render its entire UI.\n\n## Generation\n\nSchema registry generation is **enabled by default**. Run `quickback compile` and the compiler outputs `schema-registry.json` alongside your compiled API files.\n\nTo disable generation:\n\n```typescript title=\"quickback/quickback.config.ts\"\nexport default defineConfig({\n schemaRegistry: { generate: false },\n // ... rest of your config\n});\n```\n\n## Output Shape\n\nThe top-level structure of `schema-registry.json`:\n\n```json\n{\n \"generatedAt\": \"2026-02-14T06:26:53.554Z\",\n \"generatedBy\": \"quickback-compiler\",\n \"version\": \"1.0.0\",\n \"features\": { ... },\n \"tables\": { ... },\n \"tablesByFeature\": { ... }\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `generatedAt` | `string` | ISO timestamp of generation |\n| `generatedBy` | `string` | Always `\"quickback-compiler\"` |\n| `version` | `string` | Compiler version used |\n| `features` | `Record<string, string[]>` | Feature name to file list mapping |\n| `tables` | `Record<string, TableMeta>` | Table name to full metadata |\n| `tablesByFeature` | `Record<string, string[]>` | Feature name to table name list |\n\n## TableMeta\n\nEach table entry contains everything the CMS needs to render its UI:\n\n```typescript\ninterface TableMeta {\n name: string; // camelCase table name (e.g., \"accountCode\")\n dbName: string; // SQL table name (e.g., \"account_code\")\n feature: string; // Parent feature name\n columns: ColumnMeta[]; // All columns including audit fields\n firewall: Record<string, unknown>; // Tenant isolation config\n crud: Record<string, CrudConfig>; // Per-operation access rules\n guards: {\n createable: string[]; // Fields allowed on create\n updatable: string[]; // Fields allowed on update\n immutable: string[]; // Fields locked after creation\n protected: Record<string, string[]>; // Fields only updatable via actions\n };\n masking: Record<string, MaskingRule>; // Per-field masking rules\n views: Record<string, ViewConfig>; // Named column projections\n validation: Record<string, ValidationRule>; // Per-field validation\n actions: ActionMeta[]; // Available actions for this table\n displayColumn?: string; // Human-readable label column\n internal?: boolean; // Hidden from CMS sidebar when true\n}\n```\n\n## ColumnMeta\n\nEach column in the `columns` array:\n\n```typescript\ninterface ColumnMeta {\n name: string; // Property name (camelCase)\n dbName: string; // SQL column name (snake_case)\n type: \"text\" | \"integer\" | \"real\" | \"blob\"; // SQLite type\n mode?: \"boolean\"; // When an integer represents a boolean\n primaryKey: boolean;\n notNull: boolean;\n defaultValue?: string | number | boolean;\n fkTarget?: string; // Target table name for FK columns\n}\n```\n\nThe compiler automatically includes audit fields (`id`, `organizationId`, `createdAt`, `createdBy`, `modifiedAt`, `modifiedBy`, `deletedAt`) at the beginning of every table's column list.\n\n### fkTarget — FK Resolution\n\nColumns ending in `Id` may have a `fkTarget` property indicating which table they reference. This is resolved in priority order:\n\n1. **Explicit `references`** — from your `defineTable()` config (highest priority)\n2. **Drizzle `.references()`** — parsed from schema source code\n3. **Convention** — strip `Id` suffix, match table name directly\n\n```json\n{\n \"name\": \"vendorId\",\n \"type\": \"text\",\n \"fkTarget\": \"contact\"\n}\n```\n\nThe CMS uses `fkTarget` to render typeahead/lookup inputs that search the correct table instead of showing raw IDs.\n\n## Input Hints\n\nTables with `inputHints` configured in `defineTable()` include an `inputHints` map in their metadata:\n\n```json\n{\n \"name\": \"invoice\",\n \"inputHints\": {\n \"status\": \"select\",\n \"sortOrder\": \"radio\",\n \"isPartialPaymentDisabled\": \"checkbox\",\n \"headerMessage\": \"textarea\"\n }\n}\n```\n\nThe CMS reads these hints to render the appropriate form control for each field. See [Input Hints](/compiler/definitions/schema#input-hints) for the full list of supported values.\n\n## Display Column\n\nThe `displayColumn` field tells the CMS which column to use as a human-readable label for a record. This is used in:\n\n- FK typeahead dropdowns (showing names instead of IDs)\n- Record titles in detail views\n- Breadcrumb labels\n\n### Auto-Detection\n\nIf you don't explicitly set `displayColumn` in your resource config, the compiler auto-detects it by scanning column names in priority order:\n\n1. `name`\n2. `title`\n3. `label`\n4. `headline`\n5. `subject`\n6. `code`\n7. `displayName`\n8. `fullName`\n9. `description`\n\nThe first match wins. If no candidate matches, the table has no display column and the CMS falls back to showing IDs.\n\n### Explicit Config\n\nSet it explicitly in your table definition:\n\n```typescript\nexport default defineTable(contacts, {\n displayColumn: \"companyName\",\n // ...\n});\n```\n\n## FK Label Resolution\n\nWhen a table has foreign key columns (ending in `Id`), the API enriches list responses with `_label` fields. For example, a `roomTypeId` column gets a corresponding `roomType_label` field containing the display column value from the referenced table.\n\nThe CMS uses these `_label` fields to show human-readable names in table cells and FK typeahead dropdowns instead of raw UUIDs.\n\n```\nroomTypeId: \"rt_abc123\" → displayed as \"Master Bedroom\"\naccountCodeId: \"ac_xyz789\" → displayed as \"4100 - Revenue\"\n```\n\nThe FK target table is resolved from the `fkTarget` property on each column. For simple cases like `roomTypeId` → `roomType`, the compiler auto-detects it. For non-obvious mappings (e.g., `vendorId` → `contact`), use explicit [`references`](/compiler/definitions/schema#references) in your `defineTable()` config.\n\n## Next Steps\n\n- **[Connecting](/cms/connecting)** — Demo mode vs. live mode setup\n- **[Schema Format Reference](/cms/schema-format)** — Full TypeScript types"
103
103
  },
104
104
  "cms/security": {
105
105
  "title": "Security",
@@ -123,7 +123,7 @@ export const DOCS = {
123
123
  },
124
124
  "compiler/cloud-compiler": {
125
125
  "title": "Cloud Compiler",
126
- "content": "Quickback offers a hosted compiler at `https://compiler.quickback.dev`. The CLI uses it to turn your definitions into a complete backend.\n\n## How It Works\n\n```\n┌──────────────┐ ┌──────────────────────┐\n│ quickback │ POST │ compiler.quickback │\n│ CLI │────────▶│ .dev/compile │\n│ │ │ │\n│ Sends: │ │ Returns: │\n│ - config │ │ - Hono routes │\n│ - features │◀────────│ - Drizzle migrations │\n│ - meta │ │ - TypeScript SDK │\n└──────────────┘ └──────────────────────┘\n```\n\n1. The CLI reads your `quickback.config.ts` and feature definitions.\n2. It sends them to the cloud compiler as a JSON payload.\n3. The compiler generates all backend files (routes, migrations, SDK, OpenAPI spec).\n4. The CLI writes the generated files to your project.\n\n## Quick Start\n\n```bash\n# 1. Install the CLI\nnpm install -g @kardoe/quickback\n\n# 2. Create a project\nquickback create cloudflare my-app\ncd my-app\n\n# 3. Log in\nquickback login\n\n# 4. Compile\nquickback compile\n```\n\n## Existing Databases\n\nWhen recompiling an existing project, the CLI automatically sends your Drizzle meta files so the compiler generates **incremental** migrations instead of fresh `CREATE TABLE` statements.\n\nMeta files are loaded from:\n- `drizzle/auth/meta/`\n- `drizzle/features/meta/`\n- `drizzle/files/meta/`\n- `drizzle/webhooks/meta/`\n- `drizzle/meta/` (legacy single database mode)\n- `quickback/drizzle/...` mirrors (same paths under `quickback/`)\n\nWhen a `quickback/` folder exists and compile output is project root, the CLI writes state artifacts directly into `quickback/`:\n- `quickback/drizzle/...` for Drizzle migration SQL/meta artifacts\n- `quickback/reports/...` for security contract report artifacts\n\nThe CLI also syncs Drizzle meta JSON into `quickback/drizzle/...` as a compatibility fallback for tool-generated meta files.\n\nNo extra configuration needed — the CLI handles this automatically.\n\n## Next Steps\n\n- [CLI Reference](/compiler/cloud-compiler/cli) — All CLI commands\n- [Authentication](/compiler/cloud-compiler/authentication) — Login flow and API keys\n- [Endpoints](/compiler/cloud-compiler/endpoints) — Compiler API reference\n- [Troubleshooting](/compiler/cloud-compiler/troubleshooting) — Common issues\n- [Local Compiler](/compiler/cloud-compiler/local-compiler) — Run the compiler locally"
126
+ "content": "Quickback offers a hosted compiler at `https://compiler.quickback.dev`. The CLI uses it to turn your definitions into a complete backend.\n\n## How It Works\n\n```\n┌──────────────┐ ┌──────────────────────┐\n│ quickback │ POST │ compiler.quickback │\n│ CLI │────────▶│ .dev/compile │\n│ │ │ │\n│ Sends: │ │ Returns: │\n│ - config │ │ - Hono routes │\n│ - features │◀────────│ - Drizzle migrations │\n│ - meta │ │ - TypeScript SDK │\n└──────────────┘ └──────────────────────┘\n```\n\n1. The CLI reads your `quickback.config.ts` and feature definitions.\n2. It sends them to the cloud compiler as a JSON payload.\n3. The compiler generates all backend files (routes, migrations, SDK, OpenAPI spec).\n4. The CLI writes the generated files to your project.\n\n## Quick Start\n\n```bash\n# 1. Install the CLI\nnpm install -g @kardoe/quickback\n\n# 2. Create a project\nquickback create cloudflare my-app\ncd my-app\n\n# 3. Log in\nquickback login\n\n# 4. Compile\nquickback compile\n```\n\n## Existing Databases\n\nWhen recompiling an existing project, the CLI automatically sends your Drizzle meta files so the compiler generates **incremental** migrations instead of fresh `CREATE TABLE` statements.\n\nMeta files are loaded from:\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- `quickback/drizzle/...` for Drizzle migration SQL/meta artifacts\n- `quickback/reports/...` for security contract report artifacts\n\nNo extra configuration needed — the CLI handles this automatically.\n\n## Next Steps\n\n- [CLI Reference](/compiler/cloud-compiler/cli) — All CLI commands\n- [Authentication](/compiler/cloud-compiler/authentication) — Login flow and API keys\n- [Endpoints](/compiler/cloud-compiler/endpoints) — Compiler API reference\n- [Troubleshooting](/compiler/cloud-compiler/troubleshooting) — Common issues\n- [Local Compiler](/compiler/cloud-compiler/local-compiler) — Run the compiler locally"
127
127
  },
128
128
  "compiler/cloud-compiler/local-compiler": {
129
129
  "title": "Local Compiler",
@@ -135,11 +135,11 @@ export const DOCS = {
135
135
  },
136
136
  "compiler/config": {
137
137
  "title": "Configuration",
138
- "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\"],\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` | `string[]` | Feature flags, e.g. `[\"organizations\"]` |\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 (`{ generate: true }`) — see [Schema Registry](/cms/schema-registry) |\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\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"
138
+ "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\"],\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` | `string[]` | Feature flags, e.g. `[\"organizations\"]` |\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\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\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"
139
139
  },
140
140
  "compiler/config/output": {
141
141
  "title": "Output Structure",
142
- "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\ndrizzle/\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\ndrizzle/ # 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 | `drizzle/auth/` + `drizzle/features/` | `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\ndrizzle/\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\ndrizzle/\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\ndrizzle/\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- `drizzle/auth/meta/`\n- `drizzle/features/meta/`\n- `drizzle/files/meta/`\n- `drizzle/webhooks/meta/`\n- `drizzle/meta/` (legacy single database mode)\n- `quickback/drizzle/...` mirrors (same paths under `quickback/`)\n\nWhen a `quickback/` folder exists and compile output goes to project root, the CLI writes state artifacts directly into `quickback/`:\n\n- `quickback/drizzle/...` for Drizzle migration SQL/meta artifacts\n- `quickback/reports/...` for security contract report artifacts\n\nThe CLI also syncs Drizzle meta JSON from output into `quickback/drizzle/...` as a compatibility fallback for tool-generated meta files.\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## See Also\n\n- [Providers](/compiler/config/providers) — Configure runtime, database, and auth providers\n- [Environment variables](/compiler/config/variables) — Required variables by runtime"
142
+ "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## See Also\n\n- [Providers](/compiler/config/providers) — Configure runtime, database, and auth providers\n- [Environment variables](/compiler/config/variables) — Required variables by runtime"
143
143
  },
144
144
  "compiler/config/providers": {
145
145
  "title": "Providers",
@@ -155,7 +155,7 @@ export const DOCS = {
155
155
  },
156
156
  "compiler/definitions/actions": {
157
157
  "title": "Actions",
158
- "content": "Actions are custom API endpoints for business logic beyond CRUD operations. They enable workflows, integrations, and complex operations.\n\n## Overview\n\nQuickback supports two types of actions:\n\n| Aspect | Record-Based | Standalone |\n|--------|--------------|------------|\n| Route | `{METHOD} /:id/{actionName}` | Custom `path` or `/{actionName}` |\n| Record fetching | Automatic | None (`record` is `undefined`) |\n| Firewall applied | Yes | No |\n| Preconditions | Supported via `access.record` | Not applicable |\n| Response types | JSON only | JSON, stream, file |\n| Use case | Advance application, reject candidate | AI chat, bulk import from job board, webhooks |\n\n## Defining Actions\n\nActions are defined in a separate `actions.ts` file that references your table:\n\n```typescript\n// features/applications/actions.ts\n\nexport default defineActions(applications, {\n 'advance-stage': {\n description: \"Move application to the next pipeline stage\",\n input: z.object({ stage: z.enum([\"screening\", \"interview\", \"offer\", \"hired\"]), notes: z.string().optional() }),\n access: { roles: [\"owner\", \"hiring-manager\"] },\n execute: async ({ db, record, ctx }) => {\n // Business logic\n return record;\n },\n },\n});\n```\n\n### Configuration Options\n\n| Option | Required | Description |\n|--------|----------|-------------|\n| `description` | Yes | Human-readable description of the action |\n| `input` | Yes | Zod schema for request validation |\n| `access` | Yes | Access control (roles, record conditions, or function) |\n| `execute` | Yes* | Inline execution function |\n| `handler` | Yes* | File path for complex logic (alternative to `execute`) |\n| `standalone` | No | Set `true` for non-record actions |\n| `path` | No | Custom route path (standalone only) |\n| `method` | No | HTTP method: GET, POST, PUT, PATCH, DELETE (default: POST) |\n| `responseType` | No | Response format: json, stream, file (default: json) |\n| `sideEffects` | No | Hint for AI tools: 'sync', 'async', or 'fire-and-forget' |\n| `allowRawSql` | No | Explicit compile-time opt-in for raw SQL in execute/handler code |\n| `unsafe` | No | Unsafe raw DB mode. Use `true` (legacy) or object config (`reason`, `adminOnly`, `crossTenant`, `targetScope`) |\n\n*Either `execute` or `handler` is required, not both.\n\n## Record-Based Actions\n\nRecord-based actions operate on an existing record. The record is automatically loaded and validated before your action executes.\n\n**Route pattern:** `{METHOD} /:id/{actionName}`\n\n```\nPOST /applications/:id/advance-stage\nPOST /applications/:id/reject\nPOST /applications/:id/schedule-interview\n```\n\n### Runtime Flow\n\n1. **Authentication** - User token is validated\n2. **Record Loading** - The record is fetched by ID\n3. **Firewall Check** - Ensures user can access this record\n4. **Access Check** - Validates roles and preconditions\n5. **Input Validation** - Request body validated against Zod schema\n6. **Execution** - Your action handler runs\n7. **Response** - Result is returned to client\n\n### Example: Advance Application Stage\n\n```typescript\n// features/applications/actions.ts\n\nexport default defineActions(applications, {\n 'advance-stage': {\n description: \"Move application to the next pipeline stage\",\n input: z.object({\n stage: z.enum([\"screening\", \"interview\", \"offer\", \"hired\"]),\n notes: z.string().optional(),\n }),\n access: {\n roles: [\"owner\", \"hiring-manager\"],\n record: { stage: { notEquals: \"rejected\" } }, // Precondition\n },\n execute: async ({ db, ctx, record, input }) => {\n const [updated] = await db\n .update(applications)\n .set({\n stage: input.stage,\n notes: input.notes ?? record.notes,\n })\n .where(eq(applications.id, record.id))\n .returning();\n\n return updated;\n },\n },\n});\n```\n\n### Request Example\n\n```\nPOST /applications/app_123/advance-stage\nContent-Type: application/json\n\n{\n \"stage\": \"interview\",\n \"notes\": \"Strong technical background, moving to interview\"\n}\n```\n\n### Response Example\n\n```json\n{\n \"data\": {\n \"id\": \"app_123\",\n \"stage\": \"interview\",\n \"notes\": \"Strong technical background, moving to interview\"\n }\n}\n```\n\n## Standalone Actions\n\nStandalone actions are independent endpoints that don't require a record context. Use `standalone: true` and optionally specify a custom `path`.\n\n**Route pattern:** Custom `path` or `/{actionName}`\n\n```\nPOST /chat\nGET /reports/summary\nPOST /webhooks/stripe\n```\n\n### Example: AI Chat with Streaming\n\n```typescript\nexport default defineActions(sessions, {\n chat: {\n description: \"Send a message to AI\",\n standalone: true,\n path: \"/chat\",\n method: \"POST\",\n responseType: \"stream\",\n input: z.object({\n message: z.string().min(1).max(2000),\n }),\n access: {\n roles: [\"recruiter\", \"hiring-manager\"],\n },\n handler: \"./handlers/chat\",\n },\n});\n```\n\n### Streaming Response Example\n\nFor actions with `responseType: 'stream'`:\n\n```\nContent-Type: text/event-stream\n\ndata: {\"type\": \"start\"}\ndata: {\"type\": \"chunk\", \"content\": \"Hello\"}\ndata: {\"type\": \"chunk\", \"content\": \"! I'm\"}\ndata: {\"type\": \"chunk\", \"content\": \" here to help.\"}\ndata: {\"type\": \"done\"}\n```\n\n### Example: Report Generation\n\n```typescript\nexport default defineActions(applications, {\n generateReport: {\n description: \"Generate hiring pipeline report\",\n standalone: true,\n path: \"/applications/report\",\n method: \"GET\",\n responseType: \"file\",\n input: z.object({\n startDate: z.string().datetime(),\n endDate: z.string().datetime(),\n }),\n access: { roles: [\"owner\", \"hiring-manager\"] },\n handler: \"./handlers/generate-report\",\n },\n});\n```\n\n## Access Configuration\n\nAccess controls who can execute an action and under what conditions.\n\n### Role-Based Access\n\n```typescript\naccess: {\n roles: [\"hiring-manager\", \"recruiter\"] // OR logic: user needs any of these roles\n}\n```\n\n### Record Conditions\n\nFor record-based actions, you can require the record to be in a specific state:\n\n```typescript\naccess: {\n roles: [\"hiring-manager\"],\n record: {\n stage: { equals: \"screening\" } // Precondition\n }\n}\n```\n\n**Supported operators:**\n\n| Operator | Description |\n|----------|-------------|\n| `equals` | Field must equal value |\n| `notEquals` | Field must not equal value |\n| `in` | Field must be one of the values |\n| `notIn` | Field must not be one of the values |\n\n### Context Substitution\n\nUse `$ctx` to reference the current user's context:\n\n```typescript\naccess: {\n record: {\n ownerId: { equals: \"$ctx.userId\" },\n orgId: { equals: \"$ctx.orgId\" }\n }\n}\n```\n\n### OR/AND Combinations\n\n```typescript\naccess: {\n or: [\n { roles: [\"hiring-manager\"] },\n {\n roles: [\"recruiter\"],\n record: { ownerId: { equals: \"$ctx.userId\" } }\n }\n ]\n}\n```\n\n### Function Access\n\nFor complex logic, use an access function:\n\n```typescript\naccess: async (ctx, record) => {\n return ctx.roles.includes('admin') || record.ownerId === ctx.userId;\n}\n```\n\n## Scoped Database\n\nAll actions receive a **scoped `db`** instance that automatically enforces security:\n\n| Operation | Org Scoping | Owner Scoping | Soft Delete Filter | Auto-inject on INSERT |\n|-----------|-------------|---------------|--------------------|-----------------------|\n| `SELECT` | `WHERE organizationId = ?` | `WHERE ownerId = ?` | `WHERE deletedAt IS NULL` | n/a |\n| `INSERT` | n/a | n/a | n/a | `organizationId`, `ownerId` from ctx |\n| `UPDATE` | `WHERE organizationId = ?` | `WHERE ownerId = ?` | `WHERE deletedAt IS NULL` | n/a |\n| `DELETE` | `WHERE organizationId = ?` | `WHERE ownerId = ?` | `WHERE deletedAt IS NULL` | n/a |\n\nScoping is duck-typed at runtime — tables with an `organizationId` column get org scoping, tables with `ownerId` get owner scoping, tables with `deletedAt` get soft delete filtering.\n\n```typescript\nexecute: async ({ db, ctx, input }) => {\n // This query automatically includes WHERE organizationId = ? AND deletedAt IS NULL\n const items = await db.select().from(applications).where(eq(applications.stage, 'interview'));\n\n // Inserts automatically include organizationId\n await db.insert(applications).values({ candidateId: input.candidateId, jobId: input.jobId, stage: 'applied' });\n\n return items;\n}\n```\n\n**Not enforced** in scoped DB (by design):\n- **Guards** — actions ARE the authorized way to modify guarded fields\n- **Masking** — actions are backend code that may need raw data\n- **Access** — already checked before action execution\n\n### Unsafe Mode\n\nActions that need to bypass scoped DB filters (for example, platform-level support operations) can enable unsafe mode.\n\nUse object form for explicit policy and mandatory audit metadata:\n\n```typescript\nexport default defineActions(applications, {\n adminReport: {\n description: \"Generate cross-org hiring report\",\n unsafe: {\n reason: \"Support investigation for enterprise customer\",\n adminOnly: true, // default true\n crossTenant: true, // default true\n targetScope: \"all\", // \"all\" | \"organization\"\n },\n input: z.object({ startDate: z.string() }),\n access: { roles: [\"owner\"] },\n execute: async ({ db, rawDb, ctx, input }) => {\n // db is still scoped (safety net)\n // rawDb bypasses scoped filters\n const allOrgs = await rawDb.select().from(applications);\n return allOrgs;\n },\n },\n});\n```\n\nUnsafe cross-tenant actions are generated with:\n\n- Better Auth authentication required (no unauthenticated path)\n- platform admin gate (`ctx.userRole === \"admin\"`)\n- mandatory audit logging on deny/success/error\n\nWithout unsafe mode, `rawDb` is `undefined` in the executor params.\n\nLegacy `unsafe: true` is still supported, but object mode is recommended so audit logs include an explicit reason.\n\n### Raw SQL Policy\n\nBy default, the compiler rejects raw SQL in action code and handler files. Use Drizzle query-builder syntax whenever possible.\n\nIf a specific action must use raw SQL, opt in explicitly:\n\n```typescript\nreconcileLedger: {\n description: \"Run custom reconciliation query\",\n allowRawSql: true,\n input: z.object({}),\n access: { roles: [\"owner\"] },\n execute: async ({ db }) => {\n // Allowed because allowRawSql: true\n return db.execute(sql`select 1`);\n },\n}\n```\n\nWithout `allowRawSql: true`, compilation fails with a loud error pointing to the action and snippet.\n\n## Handler Files\n\nFor complex actions, separate the logic into handler files.\n\n### When to Use Handler Files\n\n- Complex business logic spanning multiple operations\n- External API integrations\n- File generation or processing\n- Logic reused across multiple actions\n\n### Handler Structure\n\n```typescript\n// handlers/generate-report.ts\n\nexport const execute: ActionExecutor = async ({ db, ctx, input, services }) => {\n const apps = await db\n .select()\n .from(applicationsTable)\n .where(between(applicationsTable.createdAt, input.startDate, input.endDate));\n\n const pdf = await services.pdf.generate(apps);\n\n return {\n file: pdf,\n filename: `hiring-report-${input.startDate}-${input.endDate}.pdf`,\n contentType: 'application/pdf',\n };\n};\n```\n\n### Importing Tables\n\nHandler files can import tables from their own feature or other features. The compiler generates alias files for each table, so you import by the table's file name:\n\n```typescript\n// handlers/advance-stage.ts\n\n// Same feature — import from parent directory using the table's file name\n\n// Cross-feature — go up to the features directory, then into the other feature\n\nexport const execute: ActionExecutor = async ({ db, ctx, input, c }) => {\n const [job] = await db\n .select()\n .from(jobs)\n .where(eq(jobs.id, input.jobId))\n .limit(1);\n\n if (!job) {\n return c.json({ error: 'Job not found', code: 'NOT_FOUND' }, 404);\n }\n\n // ... continue with business logic\n};\n```\n\n**Path pattern from `features/{name}/handlers/`:**\n- Same feature table: `../{table-file-name}` (e.g., `../applications`)\n- Other feature table: `../../{other-feature}/{table-file-name}` (e.g., `../../candidates/candidates`)\n- Generated lib files: `../../../lib/{module}` (e.g., `../../../lib/realtime`, `../../../lib/webhooks`)\n\n### Executor Parameters\n\n```typescript\ninterface ActionExecutorParams {\n db: DrizzleDB; // Scoped database (auto-applies org/owner/soft-delete filters)\n rawDb?: DrizzleDB; // Raw database (only available when unsafe mode is enabled)\n ctx: AppContext; // User context (userId, roles, orgId)\n record?: TRecord; // The record (record-based only, undefined for standalone)\n input: TInput; // Validated input from Zod schema\n services: TServices; // Configured integrations (billing, notifications, etc.)\n c: HonoContext; // Raw Hono context for advanced use\n auditFields: object; // { createdAt, modifiedAt } timestamps\n}\n```\n\n## HTTP API Reference\n\n### Request Format\n\n| Method | Input Source | Use Case |\n|--------|--------------|----------|\n| `GET` | Query parameters | Read-only operations, fetching data |\n| `POST` | JSON body | Default, state-changing operations |\n| `PUT` | JSON body | Full replacement operations |\n| `PATCH` | JSON body | Partial updates |\n| `DELETE` | JSON body | Deletion with optional payload |\n\n```typescript\n// GET action - input comes from query params\ngetStageHistory: {\n method: \"GET\",\n input: z.object({ format: z.string().optional() }),\n // Called as: GET /applications/:id/getStageHistory?format=detailed\n}\n\n// POST action (default) - input comes from JSON body\n'advance-stage': {\n // method: \"POST\" is implied\n input: z.object({ stage: z.enum([\"screening\", \"interview\", \"offer\", \"hired\"]) }),\n // Called as: POST /applications/:id/advance-stage with JSON body\n}\n```\n\n### Response Formats\n\n| Type | Content-Type | Use Case |\n|------|--------------|----------|\n| `json` | `application/json` | Standard API responses (default) |\n| `stream` | `text/event-stream` | Real-time streaming (AI chat, live updates) |\n| `file` | Varies | File downloads (reports, exports) |\n\n### Error Codes\n\n| Status | Description |\n|--------|-------------|\n| `400` | Invalid input / validation error |\n| `401` | Not authenticated |\n| `403` | Access check failed (role or precondition) |\n| `404` | Record not found (record-based actions) |\n| `500` | Handler execution error |\n\n### Validation Error Response\n\n```json\n{\n \"error\": \"Invalid request data\",\n \"layer\": \"validation\",\n \"code\": \"VALIDATION_FAILED\",\n \"details\": {\n \"fields\": {\n \"amount\": \"Expected positive number\"\n }\n },\n \"hint\": \"Check the input schema for this action\"\n}\n```\n\n### Error Handling\n\n**Option 1: Return a JSON error response** (recommended for most cases)\n\nSince action handlers receive the Hono context (`c`), you can return error responses directly:\n\n```typescript\nexecute: async ({ ctx, record, input, c }) => {\n if (record.stage === 'hired') {\n return c.json({\n error: 'Cannot modify a hired application',\n code: 'ALREADY_HIRED',\n details: { currentStage: record.stage },\n }, 400);\n }\n // ... continue\n}\n```\n\n**Option 2: Throw an ActionError**\n\n```typescript\n\nexecute: async ({ ctx, record, input }) => {\n if (record.stage === 'hired') {\n throw new ActionError('Cannot modify a hired application', 'ALREADY_HIRED', 400, {\n currentStage: record.stage,\n });\n }\n // ... continue\n}\n```\n\nThe `ActionError` constructor signature is `(message, code, statusCode, details?)`.\n\n## Protected Fields\n\nActions can modify fields that are protected from regular CRUD operations:\n\n```typescript\n// In resource.ts\nguards: {\n protected: {\n stage: [\"advance-stage\", \"reject\"], // Only these actions can modify stage\n }\n}\n```\n\nThis allows the `advance-stage` action to set `stage = \"interview\"` even though the field is protected from regular PATCH requests.\n\n## Examples\n\n### Application Stage Advance (Record-Based)\n\n```typescript\nexport default defineActions(applications, {\n 'advance-stage': {\n description: \"Move application to the next pipeline stage\",\n input: z.object({\n stage: z.enum([\"screening\", \"interview\", \"offer\", \"hired\"]),\n notes: z.string().optional(),\n }),\n access: {\n roles: [\"owner\", \"hiring-manager\"],\n record: { stage: { notEquals: \"rejected\" } },\n },\n execute: async ({ db, ctx, record, input }) => {\n const [updated] = await db\n .update(applications)\n .set({\n stage: input.stage,\n notes: input.notes ?? record.notes,\n })\n .where(eq(applications.id, record.id))\n .returning();\n return updated;\n },\n },\n});\n```\n\n### AI Chat (Standalone with Streaming)\n\n```typescript\nexport default defineActions(sessions, {\n chat: {\n description: \"Send a message to AI assistant\",\n standalone: true,\n path: \"/chat\",\n responseType: \"stream\",\n input: z.object({\n message: z.string().min(1).max(2000),\n }),\n access: { roles: [\"recruiter\", \"hiring-manager\"] },\n handler: \"./handlers/chat\",\n },\n});\n```\n\n### Bulk Import (Standalone, No Record)\n\n```typescript\nexport default defineActions(candidates, {\n bulkImport: {\n description: \"Import candidates from job board CSV\",\n standalone: true,\n path: \"/candidates/import\",\n input: z.object({\n data: z.array(z.object({\n email: z.string().email(),\n name: z.string(),\n })),\n }),\n access: { roles: [\"hiring-manager\", \"recruiter\"] },\n execute: async ({ db, input }) => {\n const inserted = await db\n .insert(candidates)\n .values(input.data)\n .returning();\n return { imported: inserted.length };\n },\n },\n});\n```"
158
+ "content": "Actions are custom API endpoints for business logic beyond CRUD operations. They enable workflows, integrations, and complex operations.\n\n## Overview\n\nQuickback supports two types of actions:\n\n| Aspect | Record-Based | Standalone |\n|--------|--------------|------------|\n| Route | `{METHOD} /:id/{actionName}` | Custom `path` or `/{actionName}` |\n| Record fetching | Automatic | None (`record` is `undefined`) |\n| Firewall applied | Yes | No |\n| Preconditions | Supported via `access.record` | Not applicable |\n| Response types | JSON only | JSON, stream, file |\n| Use case | Advance application, reject candidate | AI chat, bulk import from job board, webhooks |\n\n## Defining Actions\n\nActions are defined in a separate `actions.ts` file that references your table:\n\n```typescript\n// features/applications/actions.ts\n\nexport default defineActions(applications, {\n 'advance-stage': {\n description: \"Move application to the next pipeline stage\",\n input: z.object({ stage: z.enum([\"screening\", \"interview\", \"offer\", \"hired\"]), notes: z.string().optional() }),\n access: { roles: [\"owner\", \"hiring-manager\"] },\n execute: async ({ db, record, ctx }) => {\n // Business logic\n return record;\n },\n },\n});\n```\n\n### Configuration Options\n\n| Option | Required | Description |\n|--------|----------|-------------|\n| `description` | Yes | Human-readable description of the action |\n| `input` | Yes | Zod schema for request validation |\n| `access` | Yes | Access control (roles, record conditions, or function) |\n| `execute` | Yes* | Inline execution function |\n| `handler` | Yes* | File path for complex logic (alternative to `execute`) |\n| `standalone` | No | Set `true` for non-record actions |\n| `path` | No | Custom route path (standalone only) |\n| `method` | No | HTTP method: GET, POST, PUT, PATCH, DELETE (default: POST) |\n| `responseType` | No | Response format: json, stream, file (default: json) |\n| `sideEffects` | No | Hint for AI tools: 'sync', 'async', or 'fire-and-forget' |\n| `allowRawSql` | No | Explicit compile-time opt-in for raw SQL in execute/handler code |\n| `unsafe` | No | Unsafe raw DB mode. Use `true` (legacy) or object config (`reason`, `adminOnly`, `crossTenant`, `targetScope`) |\n\n*Either `execute` or `handler` is required, not both.\n\n## Record-Based Actions\n\nRecord-based actions operate on an existing record. The record is automatically loaded and validated before your action executes.\n\n**Route pattern:** `{METHOD} /:id/{actionName}`\n\n```\nPOST /applications/:id/advance-stage\nPOST /applications/:id/reject\nPOST /applications/:id/schedule-interview\n```\n\n### Runtime Flow\n\n1. **Authentication** - User token is validated\n2. **Record Loading** - The record is fetched by ID\n3. **Firewall Check** - Ensures user can access this record\n4. **Access Check** - Validates roles and preconditions\n5. **Input Validation** - Request body validated against Zod schema\n6. **Execution** - Your action handler runs\n7. **Response** - Result is returned to client\n\n### Example: Advance Application Stage\n\n```typescript\n// features/applications/actions.ts\n\nexport default defineActions(applications, {\n 'advance-stage': {\n description: \"Move application to the next pipeline stage\",\n input: z.object({\n stage: z.enum([\"screening\", \"interview\", \"offer\", \"hired\"]),\n notes: z.string().optional(),\n }),\n access: {\n roles: [\"owner\", \"hiring-manager\"],\n record: { stage: { notEquals: \"rejected\" } }, // Precondition\n },\n execute: async ({ db, ctx, record, input }) => {\n const [updated] = await db\n .update(applications)\n .set({\n stage: input.stage,\n notes: input.notes ?? record.notes,\n })\n .where(eq(applications.id, record.id))\n .returning();\n\n return updated;\n },\n },\n});\n```\n\n### Request Example\n\n```\nPOST /applications/app_123/advance-stage\nContent-Type: application/json\n\n{\n \"stage\": \"interview\",\n \"notes\": \"Strong technical background, moving to interview\"\n}\n```\n\n### Response Example\n\n```json\n{\n \"data\": {\n \"id\": \"app_123\",\n \"stage\": \"interview\",\n \"notes\": \"Strong technical background, moving to interview\"\n }\n}\n```\n\n## Standalone Actions\n\nStandalone actions are independent endpoints that don't require a record context. Use `standalone: true` and optionally specify a custom `path`.\n\n**Route pattern:** Custom `path` or `/{actionName}`\n\n```\nPOST /chat\nGET /reports/summary\nPOST /webhooks/stripe\n```\n\n### Example: AI Chat with Streaming\n\n```typescript\nexport default defineActions(sessions, {\n chat: {\n description: \"Send a message to AI\",\n standalone: true,\n path: \"/chat\",\n method: \"POST\",\n responseType: \"stream\",\n input: z.object({\n message: z.string().min(1).max(2000),\n }),\n access: {\n roles: [\"recruiter\", \"hiring-manager\"],\n },\n handler: \"./handlers/chat\",\n },\n});\n```\n\n### Streaming Response Example\n\nFor actions with `responseType: 'stream'`:\n\n```\nContent-Type: text/event-stream\n\ndata: {\"type\": \"start\"}\ndata: {\"type\": \"chunk\", \"content\": \"Hello\"}\ndata: {\"type\": \"chunk\", \"content\": \"! I'm\"}\ndata: {\"type\": \"chunk\", \"content\": \" here to help.\"}\ndata: {\"type\": \"done\"}\n```\n\n### Example: Report Generation\n\n```typescript\nexport default defineActions(applications, {\n generateReport: {\n description: \"Generate hiring pipeline report\",\n standalone: true,\n path: \"/applications/report\",\n method: \"GET\",\n responseType: \"file\",\n input: z.object({\n startDate: z.string().datetime(),\n endDate: z.string().datetime(),\n }),\n access: { roles: [\"owner\", \"hiring-manager\"] },\n handler: \"./handlers/generate-report\",\n },\n});\n```\n\n### Actions-Only Features\n\nYou can create feature directories that contain **only standalone actions** — no table definitions required. This is useful for utility endpoints like reports, integrations, or webhooks that don't map to a specific resource.\n\n```\nquickback/features/\n└── reports/\n ├── actions.ts # Standalone actions only (no table file)\n └── handlers/\n ├── trial-balance.ts\n └── profit-loss.ts\n```\n\n```typescript\n// features/reports/actions.ts\nexport default defineActions(null, {\n trialBalance: {\n description: \"Generate trial balance report\",\n standalone: true,\n path: \"/reports/trial-balance\",\n method: \"GET\",\n input: z.object({\n startDate: z.string(),\n endDate: z.string(),\n }),\n access: { roles: [\"admin\", \"accountant\"] },\n handler: \"./handlers/trial-balance\",\n },\n});\n```\n\nPass `null` as the schema argument since there is no table. All actions in a tableless feature **must** have `standalone: true` — record-based actions require a table to operate on.\n\n## Access Configuration\n\nAccess controls who can execute an action and under what conditions.\n\n### Role-Based Access\n\n```typescript\naccess: {\n roles: [\"hiring-manager\", \"recruiter\"] // OR logic: user needs any of these roles\n}\n```\n\n### Record Conditions\n\nFor record-based actions, you can require the record to be in a specific state:\n\n```typescript\naccess: {\n roles: [\"hiring-manager\"],\n record: {\n stage: { equals: \"screening\" } // Precondition\n }\n}\n```\n\n**Supported operators:**\n\n| Operator | Description |\n|----------|-------------|\n| `equals` | Field must equal value |\n| `notEquals` | Field must not equal value |\n| `in` | Field must be one of the values |\n| `notIn` | Field must not be one of the values |\n\n### Context Substitution\n\nUse `$ctx` to reference the current user's context:\n\n```typescript\naccess: {\n record: {\n ownerId: { equals: \"$ctx.userId\" },\n orgId: { equals: \"$ctx.orgId\" }\n }\n}\n```\n\n### OR/AND Combinations\n\n```typescript\naccess: {\n or: [\n { roles: [\"hiring-manager\"] },\n {\n roles: [\"recruiter\"],\n record: { ownerId: { equals: \"$ctx.userId\" } }\n }\n ]\n}\n```\n\n### Function Access\n\nFor complex logic, use an access function:\n\n```typescript\naccess: async (ctx, record) => {\n return ctx.roles.includes('admin') || record.ownerId === ctx.userId;\n}\n```\n\n## Scoped Database\n\nAll actions receive a **scoped `db`** instance that automatically enforces security:\n\n| Operation | Org Scoping | Owner Scoping | Soft Delete Filter | Auto-inject on INSERT |\n|-----------|-------------|---------------|--------------------|-----------------------|\n| `SELECT` | `WHERE organizationId = ?` | `WHERE ownerId = ?` | `WHERE deletedAt IS NULL` | n/a |\n| `INSERT` | n/a | n/a | n/a | `organizationId`, `ownerId` from ctx |\n| `UPDATE` | `WHERE organizationId = ?` | `WHERE ownerId = ?` | `WHERE deletedAt IS NULL` | n/a |\n| `DELETE` | `WHERE organizationId = ?` | `WHERE ownerId = ?` | `WHERE deletedAt IS NULL` | n/a |\n\nScoping is duck-typed at runtime — tables with an `organizationId` column get org scoping, tables with `ownerId` get owner scoping, tables with `deletedAt` get soft delete filtering.\n\n```typescript\nexecute: async ({ db, ctx, input }) => {\n // This query automatically includes WHERE organizationId = ? AND deletedAt IS NULL\n const items = await db.select().from(applications).where(eq(applications.stage, 'interview'));\n\n // Inserts automatically include organizationId\n await db.insert(applications).values({ candidateId: input.candidateId, jobId: input.jobId, stage: 'applied' });\n\n return items;\n}\n```\n\n**Not enforced** in scoped DB (by design):\n- **Guards** — actions ARE the authorized way to modify guarded fields\n- **Masking** — actions are backend code that may need raw data\n- **Access** — already checked before action execution\n\n### Unsafe Mode\n\nActions that need to bypass scoped DB filters (for example, platform-level support operations) can enable unsafe mode.\n\nUse object form for explicit policy and mandatory audit metadata:\n\n```typescript\nexport default defineActions(applications, {\n adminReport: {\n description: \"Generate cross-org hiring report\",\n unsafe: {\n reason: \"Support investigation for enterprise customer\",\n adminOnly: true, // default true\n crossTenant: true, // default true\n targetScope: \"all\", // \"all\" | \"organization\"\n },\n input: z.object({ startDate: z.string() }),\n access: { roles: [\"owner\"] },\n execute: async ({ db, rawDb, ctx, input }) => {\n // db is still scoped (safety net)\n // rawDb bypasses scoped filters\n const allOrgs = await rawDb.select().from(applications);\n return allOrgs;\n },\n },\n});\n```\n\nUnsafe cross-tenant actions are generated with:\n\n- Better Auth authentication required (no unauthenticated path)\n- platform admin gate (`ctx.userRole === \"admin\"`)\n- mandatory audit logging on deny/success/error\n\nWithout unsafe mode, `rawDb` is `undefined` in the executor params.\n\nLegacy `unsafe: true` is still supported, but object mode is recommended so audit logs include an explicit reason.\n\n### Raw SQL Policy\n\nBy default, the compiler rejects raw SQL in action code and handler files. Use Drizzle query-builder syntax whenever possible.\n\nIf a specific action must use raw SQL, opt in explicitly:\n\n```typescript\nreconcileLedger: {\n description: \"Run custom reconciliation query\",\n allowRawSql: true,\n input: z.object({}),\n access: { roles: [\"owner\"] },\n execute: async ({ db }) => {\n // Allowed because allowRawSql: true\n return db.execute(sql`select 1`);\n },\n}\n```\n\nWithout `allowRawSql: true`, compilation fails with a loud error pointing to the action and snippet.\n\n## Handler Files\n\nFor complex actions, separate the logic into handler files.\n\n### When to Use Handler Files\n\n- Complex business logic spanning multiple operations\n- External API integrations\n- File generation or processing\n- Logic reused across multiple actions\n\n### Handler Structure\n\n```typescript\n// handlers/generate-report.ts\n\nexport const execute: ActionExecutor = async ({ db, ctx, input, services }) => {\n const apps = await db\n .select()\n .from(applicationsTable)\n .where(between(applicationsTable.createdAt, input.startDate, input.endDate));\n\n const pdf = await services.pdf.generate(apps);\n\n return {\n file: pdf,\n filename: `hiring-report-${input.startDate}-${input.endDate}.pdf`,\n contentType: 'application/pdf',\n };\n};\n```\n\n### Importing Tables\n\nHandler files can import tables from their own feature or other features. The compiler generates alias files for each table, so you import by the table's file name:\n\n```typescript\n// handlers/advance-stage.ts\n\n// Same feature — import from parent directory using the table's file name\n\n// Cross-feature — go up to the features directory, then into the other feature\n\nexport const execute: ActionExecutor = async ({ db, ctx, input, c }) => {\n const [job] = await db\n .select()\n .from(jobs)\n .where(eq(jobs.id, input.jobId))\n .limit(1);\n\n if (!job) {\n return c.json({ error: 'Job not found', code: 'NOT_FOUND' }, 404);\n }\n\n // ... continue with business logic\n};\n```\n\n**Path pattern from `features/{name}/handlers/`:**\n- Same feature table: `../{table-file-name}` (e.g., `../applications`)\n- Other feature table: `../../{other-feature}/{table-file-name}` (e.g., `../../candidates/candidates`)\n- Generated lib files: `../../../lib/{module}` (e.g., `../../../lib/realtime`, `../../../lib/webhooks`)\n\n### Executor Parameters\n\n```typescript\ninterface ActionExecutorParams {\n db: DrizzleDB; // Scoped database (auto-applies org/owner/soft-delete filters)\n rawDb?: DrizzleDB; // Raw database (only available when unsafe mode is enabled)\n ctx: AppContext; // User context (userId, roles, orgId)\n record?: TRecord; // The record (record-based only, undefined for standalone)\n input: TInput; // Validated input from Zod schema\n services: TServices; // Configured integrations (billing, notifications, etc.)\n c: HonoContext; // Raw Hono context for advanced use\n auditFields: object; // { createdAt, modifiedAt } timestamps\n}\n```\n\n## HTTP API Reference\n\n### Request Format\n\n| Method | Input Source | Use Case |\n|--------|--------------|----------|\n| `GET` | Query parameters | Read-only operations, fetching data |\n| `POST` | JSON body | Default, state-changing operations |\n| `PUT` | JSON body | Full replacement operations |\n| `PATCH` | JSON body | Partial updates |\n| `DELETE` | JSON body | Deletion with optional payload |\n\n```typescript\n// GET action - input comes from query params\ngetStageHistory: {\n method: \"GET\",\n input: z.object({ format: z.string().optional() }),\n // Called as: GET /applications/:id/getStageHistory?format=detailed\n}\n\n// POST action (default) - input comes from JSON body\n'advance-stage': {\n // method: \"POST\" is implied\n input: z.object({ stage: z.enum([\"screening\", \"interview\", \"offer\", \"hired\"]) }),\n // Called as: POST /applications/:id/advance-stage with JSON body\n}\n```\n\n### Response Formats\n\n| Type | Content-Type | Use Case |\n|------|--------------|----------|\n| `json` | `application/json` | Standard API responses (default) |\n| `stream` | `text/event-stream` | Real-time streaming (AI chat, live updates) |\n| `file` | Varies | File downloads (reports, exports) |\n\n### Error Codes\n\n| Status | Description |\n|--------|-------------|\n| `400` | Invalid input / validation error |\n| `401` | Not authenticated |\n| `403` | Access check failed (role or precondition) |\n| `404` | Record not found (record-based actions) |\n| `500` | Handler execution error |\n\n### Validation Error Response\n\n```json\n{\n \"error\": \"Invalid request data\",\n \"layer\": \"validation\",\n \"code\": \"VALIDATION_FAILED\",\n \"details\": {\n \"fields\": {\n \"amount\": \"Expected positive number\"\n }\n },\n \"hint\": \"Check the input schema for this action\"\n}\n```\n\n### Error Handling\n\n**Option 1: Return a JSON error response** (recommended for most cases)\n\nSince action handlers receive the Hono context (`c`), you can return error responses directly:\n\n```typescript\nexecute: async ({ ctx, record, input, c }) => {\n if (record.stage === 'hired') {\n return c.json({\n error: 'Cannot modify a hired application',\n code: 'ALREADY_HIRED',\n details: { currentStage: record.stage },\n }, 400);\n }\n // ... continue\n}\n```\n\n**Option 2: Throw an ActionError**\n\n```typescript\n\nexecute: async ({ ctx, record, input }) => {\n if (record.stage === 'hired') {\n throw new ActionError('Cannot modify a hired application', 'ALREADY_HIRED', 400, {\n currentStage: record.stage,\n });\n }\n // ... continue\n}\n```\n\nThe `ActionError` constructor signature is `(message, code, statusCode, details?)`.\n\n## Protected Fields\n\nActions can modify fields that are protected from regular CRUD operations:\n\n```typescript\n// In resource.ts\nguards: {\n protected: {\n stage: [\"advance-stage\", \"reject\"], // Only these actions can modify stage\n }\n}\n```\n\nThis allows the `advance-stage` action to set `stage = \"interview\"` even though the field is protected from regular PATCH requests.\n\n## Examples\n\n### Application Stage Advance (Record-Based)\n\n```typescript\nexport default defineActions(applications, {\n 'advance-stage': {\n description: \"Move application to the next pipeline stage\",\n input: z.object({\n stage: z.enum([\"screening\", \"interview\", \"offer\", \"hired\"]),\n notes: z.string().optional(),\n }),\n access: {\n roles: [\"owner\", \"hiring-manager\"],\n record: { stage: { notEquals: \"rejected\" } },\n },\n execute: async ({ db, ctx, record, input }) => {\n const [updated] = await db\n .update(applications)\n .set({\n stage: input.stage,\n notes: input.notes ?? record.notes,\n })\n .where(eq(applications.id, record.id))\n .returning();\n return updated;\n },\n },\n});\n```\n\n### AI Chat (Standalone with Streaming)\n\n```typescript\nexport default defineActions(sessions, {\n chat: {\n description: \"Send a message to AI assistant\",\n standalone: true,\n path: \"/chat\",\n responseType: \"stream\",\n input: z.object({\n message: z.string().min(1).max(2000),\n }),\n access: { roles: [\"recruiter\", \"hiring-manager\"] },\n handler: \"./handlers/chat\",\n },\n});\n```\n\n### Bulk Import (Standalone, No Record)\n\n```typescript\nexport default defineActions(candidates, {\n bulkImport: {\n description: \"Import candidates from job board CSV\",\n standalone: true,\n path: \"/candidates/import\",\n input: z.object({\n data: z.array(z.object({\n email: z.string().email(),\n name: z.string(),\n })),\n }),\n access: { roles: [\"hiring-manager\", \"recruiter\"] },\n execute: async ({ db, input }) => {\n const inserted = await db\n .insert(candidates)\n .values(input.data)\n .returning();\n return { imported: inserted.length };\n },\n },\n});\n```"
159
159
  },
160
160
  "compiler/definitions/concepts": {
161
161
  "title": "Glossary",
@@ -171,7 +171,7 @@ export const DOCS = {
171
171
  },
172
172
  "compiler/definitions": {
173
173
  "title": "Definitions Overview",
174
- "content": "Before diving into specific features, let's understand how Quickback's pieces connect. This page gives you the mental model for everything that follows.\n\n## The Big Picture\n\nQuickback is a **backend compiler**. You write definition files, and Quickback compiles them into a production-ready API.\n\n1. **You write definitions** - Table files with schema and security config using `defineTable`\n2. **Quickback compiles them** - Analyzes your definitions at build time\n3. **You get a production API** - `GET /jobs`, `POST /jobs`, `PATCH /jobs/:id`, `DELETE /jobs/:id`, batch operations, plus custom actions\n\n## File Structure\n\nYour definitions live in a `quickback/features/` folder organized by feature:\n\n```\nmy-app/\n├── quickback/\n│ ├── quickback.config.ts # Compiler configuration\n│ └── features/\n│ └── {feature-name}/\n│ ├── candidates.ts # Table + config (defineTable)\n│ ├── applications.ts # Secondary table + config\n│ ├── interview-scores.ts # Internal table (no routes)\n│ ├── actions.ts # Custom actions (optional)\n│ └── handlers/ # Action handlers (optional)\n│ └── advance-stage.ts\n├── src/ # Generated code (output)\n├── drizzle/ # Generated migrations\n└── package.json\n```\n\n**Table files** use `defineTable` to combine schema and security config:\n\n```typescript\n// features/candidates/candidates.ts\n\nexport const candidates = sqliteTable(\"candidates\", {\n id: text(\"id\").primaryKey(),\n organizationId: text(\"organization_id\").notNull(),\n name: text(\"name\").notNull(),\n email: text(\"email\").notNull(),\n phone: text(\"phone\"),\n source: text(\"source\"),\n});\n\nexport default defineTable(candidates, {\n firewall: { organization: {} },\n guards: {\n createable: [\"name\", \"email\", \"phone\", \"source\"],\n updatable: [\"name\", \"phone\"],\n },\n masking: {\n email: { type: 'email', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n phone: { type: 'phone', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n },\n crud: {\n list: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\", \"interviewer\"] } },\n get: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\", \"interviewer\"] } },\n create: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\"] } },\n update: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\"] } },\n delete: { access: { roles: [\"owner\", \"hiring-manager\"] } },\n },\n});\n\nexport type Candidate = typeof candidates.$inferSelect;\n```\n\n**Key points:**\n- Tables with `export default defineTable(...)` → CRUD routes generated\n- Tables without default export → internal/junction tables (no routes)\n- Route path derived from filename: `applications.ts` → `/api/v1/applications`\n\n## The Four Security Layers\n\nEvery API request passes through four security layers, in order:\n\n```\nRequest → Firewall → Access → Guards → Masking → Response\n │ │ │ │\n │ │ │ └── Hide sensitive fields\n │ │ └── Block field modifications\n │ └── Check roles & conditions\n └── Isolate data by owner/org/team\n```\n\n### 1. Firewall (Data Isolation)\n\nThe firewall controls **which records** a user can see. It automatically adds WHERE clauses to every query based on your schema columns:\n\n| Column in Schema | What happens |\n|------------------|--------------|\n| `organization_id` | Data isolated by organization |\n| `user_id` | Data isolated by user (personal data) |\n\nNo manual configuration needed - Quickback applies smart rules based on your schema.\n\n[Learn more about Firewall →](/compiler/definitions/firewall)\n\n### 2. Access (CRUD Permissions)\n\nAccess controls **which operations** a user can perform. It checks roles and record conditions.\n\n```typescript\ncrud: {\n list: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\", \"interviewer\"] } },\n create: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\"] } },\n update: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\"] } },\n delete: { access: { roles: [\"owner\", \"hiring-manager\"] } },\n}\n```\n\n[Learn more about Access →](/compiler/definitions/access)\n\n### 3. Guards (Field Modification Rules)\n\nGuards control **which fields** can be modified in each operation.\n\n| Guard Type | What it means |\n|------------|---------------|\n| `createable` | Fields that can be set when creating |\n| `updatable` | Fields that can be changed when updating |\n| `protected` | Fields that can only be changed via specific actions |\n| `immutable` | Fields that can never be changed after creation |\n\n[Learn more about Guards →](/compiler/definitions/guards)\n\n### 4. Masking (Data Redaction)\n\nMasking hides sensitive fields from users who shouldn't see them.\n\n```typescript\nmasking: {\n email: { type: 'email' }, // Shows: j***@e******.com\n phone: { type: 'phone' }, // Shows: ******4567\n salary: { type: 'redact' }, // Shows: [REDACTED]\n}\n```\n\n[Learn more about Masking →](/compiler/definitions/masking)\n\n## How They Work Together\n\n**Scenario:** An interviewer requests `GET /candidates/cnd_123`\n\n1. **Firewall** checks: Is candidate `cnd_123` in the user's organization?\n - Yes → Continue\n - No → 404 Not Found (as if it doesn't exist)\n\n2. **Access** checks: Can interviewers perform GET?\n - Yes → Continue\n - No → 403 Forbidden\n\n3. **Guards** don't apply to GET (they're for writes)\n\n4. **Masking** applies: User is an `interviewer`, not `recruiter` or `hiring-manager`\n - Email: `jane@company.com` → `j***@c******.com`\n - Phone: `555-123-4567` → `******4567`\n\n5. **Response** sent with masked data\n\n## Locked Down by Default\n\nQuickback is **secure by default**. Nothing is accessible until you explicitly allow it.\n\n| Layer | Default | What you must do |\n|-------|---------|------------------|\n| Firewall | AUTO | Auto-detects from `organization_id`/`user_id` columns. Only configure for exceptions. |\n| Access | DENIED | Explicitly define `access` rules with roles |\n| Guards | LOCKED | Explicitly list `createable`, `updatable` fields |\n| Actions | BLOCKED | Explicitly define `access` for each action |\n\n**You must deliberately open each door.** This prevents accidental data exposure.\n\n## Next Steps\n\n1. [Database Schema](/compiler/definitions/schema) — Define your tables\n2. [Firewall](/compiler/definitions/firewall) — Set up data isolation\n3. [Access](/compiler/definitions/access) — Configure CRUD permissions\n4. [Guards](/compiler/definitions/guards) — Control field modifications\n5. [Masking](/compiler/definitions/masking) — Hide sensitive data\n6. [Views](/compiler/definitions/views) — Column-level security\n7. [Validation](/compiler/definitions/validation) — Field validation rules\n8. [Actions](/compiler/definitions/actions) — Add custom business logic"
174
+ "content": "Before diving into specific features, let's understand how Quickback's pieces connect. This page gives you the mental model for everything that follows.\n\n## The Big Picture\n\nQuickback is a **backend compiler**. You write definition files, and Quickback compiles them into a production-ready API.\n\n1. **You write definitions** - Table files with schema and security config using `defineTable`\n2. **Quickback compiles them** - Analyzes your definitions at build time\n3. **You get a production API** - `GET /jobs`, `POST /jobs`, `PATCH /jobs/:id`, `DELETE /jobs/:id`, batch operations, plus custom actions\n\n## File Structure\n\nYour definitions live in a `quickback/features/` folder organized by feature:\n\n```\nmy-app/\n├── quickback/\n│ ├── quickback.config.ts # Compiler configuration\n│ └── features/\n│ └── {feature-name}/\n│ ├── candidates.ts # Table + config (defineTable)\n│ ├── applications.ts # Secondary table + config\n│ ├── interview-scores.ts # Internal table (no routes)\n│ ├── actions.ts # Custom actions (optional)\n│ └── handlers/ # Action handlers (optional)\n│ └── advance-stage.ts\n├── src/ # Generated code (output)\n└── package.json\n```\n\n**Table files** use `defineTable` to combine schema and security config:\n\n```typescript\n// features/candidates/candidates.ts\n\nexport const candidates = sqliteTable(\"candidates\", {\n id: text(\"id\").primaryKey(),\n organizationId: text(\"organization_id\").notNull(),\n name: text(\"name\").notNull(),\n email: text(\"email\").notNull(),\n phone: text(\"phone\"),\n source: text(\"source\"),\n});\n\nexport default defineTable(candidates, {\n firewall: { organization: {} },\n guards: {\n createable: [\"name\", \"email\", \"phone\", \"source\"],\n updatable: [\"name\", \"phone\"],\n },\n masking: {\n email: { type: 'email', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n phone: { type: 'phone', show: { roles: ['owner', 'hiring-manager', 'recruiter'] } },\n },\n crud: {\n list: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\", \"interviewer\"] } },\n get: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\", \"interviewer\"] } },\n create: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\"] } },\n update: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\"] } },\n delete: { access: { roles: [\"owner\", \"hiring-manager\"] } },\n },\n});\n\nexport type Candidate = typeof candidates.$inferSelect;\n```\n\n**Key points:**\n- Tables with `export default defineTable(...)` → CRUD routes generated\n- Tables without default export → internal/junction tables (no routes)\n- Route path derived from filename: `applications.ts` → `/api/v1/applications`\n\n## The Four Security Layers\n\nEvery API request passes through four security layers, in order:\n\n```\nRequest → Firewall → Access → Guards → Masking → Response\n │ │ │ │\n │ │ │ └── Hide sensitive fields\n │ │ └── Block field modifications\n │ └── Check roles & conditions\n └── Isolate data by owner/org/team\n```\n\n### 1. Firewall (Data Isolation)\n\nThe firewall controls **which records** a user can see. It automatically adds WHERE clauses to every query based on your schema columns:\n\n| Column in Schema | What happens |\n|------------------|--------------|\n| `organization_id` | Data isolated by organization |\n| `user_id` | Data isolated by user (personal data) |\n\nNo manual configuration needed - Quickback applies smart rules based on your schema.\n\n[Learn more about Firewall →](/compiler/definitions/firewall)\n\n### 2. Access (CRUD Permissions)\n\nAccess controls **which operations** a user can perform. It checks roles and record conditions.\n\n```typescript\ncrud: {\n list: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\", \"interviewer\"] } },\n create: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\"] } },\n update: { access: { roles: [\"owner\", \"hiring-manager\", \"recruiter\"] } },\n delete: { access: { roles: [\"owner\", \"hiring-manager\"] } },\n}\n```\n\n[Learn more about Access →](/compiler/definitions/access)\n\n### 3. Guards (Field Modification Rules)\n\nGuards control **which fields** can be modified in each operation.\n\n| Guard Type | What it means |\n|------------|---------------|\n| `createable` | Fields that can be set when creating |\n| `updatable` | Fields that can be changed when updating |\n| `protected` | Fields that can only be changed via specific actions |\n| `immutable` | Fields that can never be changed after creation |\n\n[Learn more about Guards →](/compiler/definitions/guards)\n\n### 4. Masking (Data Redaction)\n\nMasking hides sensitive fields from users who shouldn't see them.\n\n```typescript\nmasking: {\n email: { type: 'email' }, // Shows: j***@e******.com\n phone: { type: 'phone' }, // Shows: ******4567\n salary: { type: 'redact' }, // Shows: [REDACTED]\n}\n```\n\n[Learn more about Masking →](/compiler/definitions/masking)\n\n## How They Work Together\n\n**Scenario:** An interviewer requests `GET /candidates/cnd_123`\n\n1. **Firewall** checks: Is candidate `cnd_123` in the user's organization?\n - Yes → Continue\n - No → 404 Not Found (as if it doesn't exist)\n\n2. **Access** checks: Can interviewers perform GET?\n - Yes → Continue\n - No → 403 Forbidden\n\n3. **Guards** don't apply to GET (they're for writes)\n\n4. **Masking** applies: User is an `interviewer`, not `recruiter` or `hiring-manager`\n - Email: `jane@company.com` → `j***@c******.com`\n - Phone: `555-123-4567` → `******4567`\n\n5. **Response** sent with masked data\n\n## Locked Down by Default\n\nQuickback is **secure by default**. Nothing is accessible until you explicitly allow it.\n\n| Layer | Default | What you must do |\n|-------|---------|------------------|\n| Firewall | AUTO | Auto-detects from `organization_id`/`user_id` columns. Only configure for exceptions. |\n| Access | DENIED | Explicitly define `access` rules with roles |\n| Guards | LOCKED | Explicitly list `createable`, `updatable` fields |\n| Actions | BLOCKED | Explicitly define `access` for each action |\n\n**You must deliberately open each door.** This prevents accidental data exposure.\n\n## Next Steps\n\n1. [Database Schema](/compiler/definitions/schema) — Define your tables\n2. [Firewall](/compiler/definitions/firewall) — Set up data isolation\n3. [Access](/compiler/definitions/access) — Configure CRUD permissions\n4. [Guards](/compiler/definitions/guards) — Control field modifications\n5. [Masking](/compiler/definitions/masking) — Hide sensitive data\n6. [Views](/compiler/definitions/views) — Column-level security\n7. [Validation](/compiler/definitions/validation) — Field validation rules\n8. [Actions](/compiler/definitions/actions) — Add custom business logic"
175
175
  },
176
176
  "compiler/definitions/masking": {
177
177
  "title": "Masking - Field Redaction",
@@ -190,8 +190,8 @@ export const DOCS = {
190
190
  "content": "Views implement **Column Level Security (CLS)** - controlling which columns users can access. This complements **Row Level Security (RLS)** provided by the [Firewall](/compiler/definitions/firewall), which controls which rows users can access.\n\n| Security Layer | Controls | Quickback Feature |\n|----------------|----------|-------------------|\n| Row Level Security | Which records | Firewall |\n| Column Level Security | Which fields | Views |\n\nViews provide named field projections with role-based access control. Use views to return different sets of columns to different users without duplicating CRUD endpoints.\n\n## When to Use Views\n\n| Concept | Purpose | Example |\n|---------|---------|---------|\n| CRUD list | Full records | Returns all columns |\n| Masking | Hide values | Email `j***@c******.com` |\n| Views | Exclude columns | Only `id`, `name`, `source` |\n\n- **CRUD list** returns all fields to authorized users\n- **Masking** transforms sensitive values but still includes the column\n- **Views** completely exclude columns from the response\n\n## Basic Usage\n\n```typescript\n// features/candidates/candidates.ts\n\nexport const candidates = sqliteTable('candidates', {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n email: text('email').notNull(),\n phone: text('phone'),\n source: text('source'),\n resumeUrl: text('resume_url'),\n internalNotes: text('internal_notes'),\n organizationId: text('organization_id').notNull(),\n});\n\nexport default defineTable(candidates, {\n masking: {\n email: { type: 'email', show: { roles: ['hiring-manager', 'recruiter'] } },\n phone: { type: 'phone', show: { roles: ['hiring-manager', 'recruiter'] } },\n },\n\n crud: {\n list: { access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] } },\n get: { access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] } },\n },\n\n // Views: Named field projections\n views: {\n pipeline: {\n fields: ['id', 'name', 'source'],\n access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] },\n },\n full: {\n fields: ['id', 'name', 'email', 'phone', 'source', 'resumeUrl', 'internalNotes'],\n access: { roles: ['hiring-manager', 'recruiter'] },\n },\n report: {\n fields: ['id', 'name', 'source', 'createdAt'],\n access: { roles: ['owner', 'hiring-manager'] },\n },\n },\n});\n```\n\n## Generated Endpoints\n\nViews generate GET endpoints at `/{resource}/views/{viewName}`:\n\n```\nGET /api/v1/candidates # CRUD list - all fields\nGET /api/v1/candidates/views/pipeline # View - only pipeline fields\nGET /api/v1/candidates/views/full # View - all fields (hiring-manager/recruiter only)\nGET /api/v1/candidates/views/report # View - report fields\n```\n\n## Query Parameters\n\nViews support the same query parameters as the list endpoint:\n\n### Pagination\n\n| Parameter | Description | Default | Max |\n|-----------|-------------|---------|-----|\n| `limit` | Number of records to return | 50 | 100 |\n| `offset` | Number of records to skip | 0 | - |\n\n```bash\n# Get first 10 records\nGET /api/v1/candidates/views/pipeline?limit=10\n\n# Get records 11-20\nGET /api/v1/candidates/views/pipeline?limit=10&offset=10\n```\n\n### Filtering\n\n```bash\n# Filter by exact value\nGET /api/v1/candidates/views/pipeline?source=linkedin\n\n# Filter with operators\nGET /api/v1/candidates/views/pipeline?createdAt.gt=2024-01-01\n\n# Multiple filters (AND logic)\nGET /api/v1/candidates/views/pipeline?source=linkedin&name.like=Smith\n```\n\n### Sorting\n\n| Parameter | Description | Default |\n|-----------|-------------|---------|\n| `sort` | Field to sort by | `createdAt` |\n| `order` | Sort direction (`asc` or `desc`) | `desc` |\n\n```bash\nGET /api/v1/candidates/views/pipeline?sort=name&order=asc\n```\n\n## Security\n\nAll four security pillars apply to views:\n\n| Pillar | Behavior |\n|--------|----------|\n| **Firewall** | WHERE clause applied (same as list) |\n| **Access** | Per-view access control |\n| **Guards** | N/A (read-only) |\n| **Masking** | Applied to returned fields |\n\n### Firewall\n\nViews automatically apply the same firewall conditions as the list endpoint. Users only see records within their organization scope.\n\n### Access Control\n\nEach view has its own access configuration:\n\n```typescript\nviews: {\n // Pipeline view - available to all roles\n pipeline: {\n fields: ['id', 'name', 'source'],\n access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] },\n },\n // Full view - recruiter and above\n full: {\n fields: ['id', 'name', 'source', 'email', 'phone', 'internalNotes'],\n access: { roles: ['hiring-manager', 'recruiter'] },\n },\n}\n```\n\n### Masking\n\nMasking rules are applied to the returned fields. If a view includes a masked field like `email`, the masking rules still apply:\n\n```typescript\nmasking: {\n email: { type: 'email', show: { roles: ['hiring-manager', 'recruiter'] } },\n},\n\nviews: {\n // Even if an interviewer accesses the 'full' view, email will be masked\n // because masking rules take precedence\n full: {\n fields: ['id', 'name', 'email'],\n access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] },\n },\n}\n```\n\n### How Views and Masking Work Together\n\nViews and masking are orthogonal concerns:\n- **Views** control field selection (which columns appear)\n- **Masking** controls field transformation (how values appear based on role)\n\nExample configuration:\n\n```typescript\nmasking: {\n email: { type: 'email', show: { roles: ['hiring-manager', 'recruiter'] } },\n},\n\nviews: {\n pipeline: {\n fields: ['id', 'name'], // email NOT included\n access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] },\n },\n full: {\n fields: ['id', 'name', 'email'], // email included\n access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] },\n },\n}\n```\n\n| Endpoint | Role | `email` in response? | `email` value |\n|----------|------|---------------------|---------------|\n| `/views/pipeline` | interviewer | No | N/A |\n| `/views/pipeline` | recruiter | No | N/A |\n| `/views/full` | interviewer | Yes | `j***@c******.com` |\n| `/views/full` | recruiter | Yes | `jane@company.com` |\n\n## Response Format\n\nView responses include metadata about the view:\n\n```json\n{\n \"data\": [\n { \"id\": \"cnd_123\", \"name\": \"Jane Doe\", \"source\": \"linkedin\" },\n { \"id\": \"cnd_456\", \"name\": \"John Smith\", \"source\": \"referral\" }\n ],\n \"view\": \"pipeline\",\n \"fields\": [\"id\", \"name\", \"source\"],\n \"pagination\": {\n \"limit\": 50,\n \"offset\": 0,\n \"count\": 2\n }\n}\n```\n\n## Complete Example\n\n```typescript\n// features/jobs/jobs.ts\nexport default defineTable(jobs, {\n guards: {\n createable: ['title', 'department', 'status', 'salaryMin', 'salaryMax'],\n updatable: ['title', 'department', 'status'],\n },\n\n masking: {\n salaryMin: { type: 'redact', show: { roles: ['owner', 'hiring-manager'] } },\n salaryMax: { type: 'redact', show: { roles: ['owner', 'hiring-manager'] } },\n },\n\n crud: {\n list: { access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] } },\n get: { access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] } },\n create: { access: { roles: ['owner', 'hiring-manager'] } },\n update: { access: { roles: ['owner', 'hiring-manager'] } },\n delete: { access: { roles: ['owner', 'hiring-manager'] } },\n },\n\n views: {\n // Public job board view\n board: {\n fields: ['id', 'title', 'department', 'status'],\n access: { roles: ['owner', 'hiring-manager', 'recruiter', 'interviewer'] },\n },\n // Internal view with salary info\n internal: {\n fields: ['id', 'title', 'department', 'status', 'salaryMin', 'salaryMax'],\n access: { roles: ['owner', 'hiring-manager'] },\n },\n // Compensation report\n compensation: {\n fields: ['id', 'title', 'department', 'salaryMin', 'salaryMax'],\n access: { roles: ['owner', 'hiring-manager'] },\n },\n },\n});\n```"
191
191
  },
192
192
  "compiler/getting-started/claude-code": {
193
- "title": "Claude Code Skill",
194
- "content": "The Quickback CLI includes a skill for [Claude Code](https://claude.com/claude-code) that gives Claude context about your project structure, definitions, and the Quickback compiler. This helps Claude write correct `defineTable()` definitions, actions, and configuration.\n\n## Installation\n\n### Install the Skill\n\n```bash\nquickback claude install\n```\n\nThis installs the Quickback skill files into your project so Claude Code can use them.\n\n**Options:**\n\n| Flag | Description |\n|------|-------------|\n| `--global` | Install to `~/.claude/` (available in all projects) |\n| `--local` | Install to `./quickback/.claude/` (project-specific, default) |\n\nAfter installing, start Claude Code in your project directory. The Quickback skill will be available automatically.\n\n## What the Skill Provides\n\nThe Quickback skill teaches Claude Code about:\n\n- **Project structure** Where definitions, features, and config files live\n- **`defineTable()` API** How to write schema + security definitions correctly\n- **`defineActions()` API** How to write custom actions with input validation\n- **Security pillars** — Firewall, Access, Guards, and Masking configuration\n- **Provider options** — Cloudflare, Bun, Turso runtime and database choices\n- **Compiler workflow** How to compile and deploy\n\n## Usage Examples\n\nWith the skill installed, you can ask Claude Code to:\n\n```\n\"Add a candidates feature with org-scoped firewall and email/phone masking for interviewers\"\n\n\"Create an action on applications called 'advance-stage' that requires hiring-manager role\"\n\n\"Add a pipeline view to the candidates feature showing only id, name, and source\"\n\n\"Switch my config from Bun to Cloudflare for production deployment\"\n```\n\nClaude will generate correct Quickback definitions that you can compile directly.\n\n## Updating\n\nTo update the skill to the latest version:\n\n```bash\nquickback claude install\n```\n\nThis overwrites the existing skill files with the latest version from the CLI.\n\n## See Also\n\n- [Claude Code Plugin](/plugins-tools/claude-code-skill) — Full plugin reference\n- [Getting Started](/compiler/getting-started) — Project setup guide"
193
+ "title": "AI Tools",
194
+ "content": "The Quickback CLI ships with built-in support for AI developer tools. One install gets you a Claude Code skill, Cursor IDE rules, and an MCP server that works with any AI tool.\n\n## MCP Server\n\nThe MCP server exposes Quickback documentation, project config, and feature definitions to any MCP-compatible AI tool — Claude Desktop, Cursor, VS Code Copilot, Windsurf, and more.\n\n### Setup\n\nAdd to your AI tool's MCP configuration:\n\n```json\n{\n \"mcpServers\": {\n \"quickback\": {\n \"command\": \"npx\",\n \"args\": [\"@kardoe/quickback\", \"mcp\"]\n }\n }\n}\n```\n\n**Where to put this:**\n\n| Tool | Config File |\n|------|-------------|\n| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) |\n| Cursor | `.cursor/mcp.json` in your project root |\n| VS Code (Copilot) | `.vscode/mcp.json` in your project root |\n\n### What the MCP Server Provides\n\n**Documentation tools:**\n\n| Tool | Description |\n|------|-------------|\n| `list_topics` | List all 100+ documentation topics |\n| `get_doc` | Get a specific doc by name (supports fuzzy matching) |\n| `search_docs` | Search documentation content by keyword |\n\n**Project-aware tools** (when running inside a Quickback project):\n\n| Tool | Description |\n|------|-------------|\n| `read_config` | Read your `quickback.config.ts` |\n| `list_features` | List all features and their table files |\n| `read_feature` | Read the source code of a specific feature |\n| `read_schema_registry` | Read the compiled `schema-registry.json` |\n\nThe MCP server also registers every documentation topic as an MCP resource at `quickback://docs/{topic}`.\n\n---\n\n## Claude Code Skill\n\nThe Claude Code skill gives Claude deep knowledge of Quickback's security layers, patterns, and APIs. It also activates a specialist agent for generating complete feature definitions.\n\n### Install\n\n```bash\nquickback claude install\n```\n\n**Options:**\n\n| Flag | Description |\n|------|-------------|\n| `--global` | Install to `~/.claude/` (available in all projects) |\n| `--local` | Install to `./quickback/.claude/` (project-specific) |\n\n### Manage\n\n```bash\nquickback claude status # Check installation\nquickback claude update # Update to latest version\nquickback claude remove # Remove the skill\n```\n\n### What You Get\n\n- **Quickback Skill** — Claude understands `defineTable()`, `defineActions()`, security pillars, provider options, and the compiler workflow\n- **Specialist Agent** — Automatically activates when creating resources, configuring security, or defining actions. Generates complete, working code in your `quickback/features/` directory.\n\n---\n\n## Cursor IDE Rules\n\nCursor rules provide Quickback context directly to Cursor's AI, activated automatically when editing `quickback/**/*.ts` files.\n\n### Install\n\n```bash\nquickback cursor install\n```\n\nThis copies `quickback.mdc` to `.cursor/rules/` in your project. Consider committing this file to your repo so your team gets the rules automatically.\n\n### Manage\n\n```bash\nquickback cursor status # Check installation\nquickback cursor update # Update to latest version\nquickback cursor remove # Remove the rules\n```\n\n---\n\n## Usage Examples\n\nWith any of these tools installed, you can ask your AI to:\n\n```\n\"Create a candidates feature with org-scoped firewall and email/phone masking\"\n\n\"Add an 'approve' action to expenses that sets status and records the approver\"\n\n\"Show me the firewall documentation\"\n\n\"What security layers does my current project have configured?\"\n```\n\n## See Also\n\n- [Claude Code Plugin](/plugins-tools/claude-code-skill) — Full plugin reference\n- [Getting Started](/compiler/getting-started) — Project setup guide"
195
195
  },
196
196
  "compiler/getting-started/full-example": {
197
197
  "title": "Complete Example",
@@ -211,11 +211,11 @@ export const DOCS = {
211
211
  },
212
212
  "compiler/getting-started/template-bun": {
213
213
  "title": "Bun Template",
214
- "content": "The Bun template creates a backend running on Bun with a local SQLite database and Better Auth. No cloud account needed — ideal for local development and prototyping.\n\n## Create the Project\n\n```bash\nquickback create bun my-app\ncd my-app\n```\n\nAliases: `quickback create local my-app`\n\nThis scaffolds a project with:\n\n```\nmy-app/\n├── quickback/\n│ ├── quickback.config.ts # Compiler configuration\n│ └── features/\n│ └── todos/\n│ ├── todos.ts # Schema + security (defineTable)\n│ └── actions.ts # Custom actions\n├── src/ # Compiled output (generated)\n├── drizzle/ # Migrations (generated)\n├── data/ # SQLite database files\n├── package.json\n├── tsconfig.json\n└── drizzle.config.ts\n```\n\n## Generated Configuration\n\n### quickback.config.ts\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: [\"organizations\"],\n providers: {\n runtime: { name: \"bun\", config: {} },\n database: {\n name: \"better-sqlite3\",\n config: { path: \"./data/app.db\" },\n },\n auth: { name: \"better-auth\", config: {} },\n },\n});\n```\n\nThe Bun template includes the same `todos` feature as the [Cloudflare template](/compiler/getting-started/template-cloudflare) with all four security pillars configured.\n\n## Setup Steps\n\n### 1. Install Dependencies\n\n```bash\nbun install\n```\n\n### 2. Log In to the Compiler\n\n```bash\nquickback login\n```\n\n### 3. Compile Your Definitions\n\n```bash\nquickback compile\n```\n\n### 4. Run Migrations\n\n```bash\nnpm run db:migrate\n```\n\nThis creates the SQLite database in `data/app.db` and applies all migrations.\n\n### 5. Create Environment File\n\nCreate a `.env` file in your project root:\n\n```bash\n# Runtime\nNODE_ENV=development\nPORT=3000\n\n# Auth\nBETTER_AUTH_SECRET=your-secret-key-change-in-production\nBETTER_AUTH_URL=http://localhost:3000\n```\n\n**Note:** Generate a strong random string for `BETTER_AUTH_SECRET` in production. You can use `openssl rand -hex 32`.\n\n### 6. Start Development Server\n\n```bash\nnpm run dev\n```\n\nYour API is running at `http://localhost:3000` with hot reload.\n\n## Database\n\nThe Bun template uses `better-sqlite3` with a local SQLite file stored in `data/app.db`. The generated database module automatically:\n\n- Creates the `data/` directory if it doesn't exist\n- Enables WAL mode for better concurrent performance\n- Enables foreign key constraints\n\n```\ndata/\n└── app.db # SQLite database file\n```\n\nUnlike the Cloudflare template, Bun uses a **single database** for both auth and feature tables. Migrations are in a single directory:\n\n```\ndrizzle/\n├── meta/\n│ ├── _journal.json\n│ └── 0000_snapshot.json\n└── 0000_initial.sql\n```\n\n## Available Scripts\n\n| Script | Command | Description |\n|--------|---------|-------------|\n| `dev` | `bun run --hot src/index.ts` | Start dev server with hot reload |\n| `start` | `bun run src/index.ts` | Start production server |\n| `build` | `bun build src/index.ts --outdir ./dist --target bun` | Build for production |\n| `db:migrate` | `npx drizzle-kit migrate` | Apply database migrations |\n| `type-check` | `tsc --noEmit` | Run TypeScript type checking |\n\n## Switching to Cloudflare\n\nWhen you're ready to deploy to production, you can switch to the Cloudflare template by updating your `quickback.config.ts`:\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: [\"organizations\"],\n providers: {\n runtime: { name: \"cloudflare\", config: {} },\n database: {\n name: \"cloudflare-d1\",\n config: { binding: \"DB\" },\n },\n auth: { name: \"better-auth\", config: {} },\n },\n});\n```\n\nThen recompile:\n\n```bash\nquickback compile\n```\n\nThe compiler regenerates the entire `src/` directory for the new runtime target. Your feature definitions stay the same.\n\n## Next Steps\n\n- [Add a new feature](/compiler/definitions/schema) — Define your own tables with security\n- [Configure access control](/compiler/definitions/access) — Set role-based permissions\n- [Add custom actions](/compiler/definitions/actions) — Business logic beyond CRUD\n- [Cloudflare template](/compiler/getting-started/template-cloudflare) — Deploy to production"
214
+ "content": "The Bun template creates a backend running on Bun with a local SQLite database and Better Auth. No cloud account needed — ideal for local development and prototyping.\n\n## Create the Project\n\n```bash\nquickback create bun my-app\ncd my-app\n```\n\nAliases: `quickback create local my-app`\n\nThis scaffolds a project with:\n\n```\nmy-app/\n├── quickback/\n│ ├── quickback.config.ts # Compiler configuration\n│ └── features/\n│ └── todos/\n│ ├── todos.ts # Schema + security (defineTable)\n│ └── actions.ts # Custom actions\n├── src/ # Compiled output (generated)\n├── data/ # SQLite database files\n├── package.json\n├── tsconfig.json\n└── drizzle.config.ts\n```\n\n## Generated Configuration\n\n### quickback.config.ts\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: [\"organizations\"],\n providers: {\n runtime: { name: \"bun\", config: {} },\n database: {\n name: \"better-sqlite3\",\n config: { path: \"./data/app.db\" },\n },\n auth: { name: \"better-auth\", config: {} },\n },\n});\n```\n\nThe Bun template includes the same `todos` feature as the [Cloudflare template](/compiler/getting-started/template-cloudflare) with all four security pillars configured.\n\n## Setup Steps\n\n### 1. Install Dependencies\n\n```bash\nbun install\n```\n\n### 2. Log In to the Compiler\n\n```bash\nquickback login\n```\n\n### 3. Compile Your Definitions\n\n```bash\nquickback compile\n```\n\n### 4. Run Migrations\n\n```bash\nnpm run db:migrate\n```\n\nThis creates the SQLite database in `data/app.db` and applies all migrations.\n\n### 5. Create Environment File\n\nCreate a `.env` file in your project root:\n\n```bash\n# Runtime\nNODE_ENV=development\nPORT=3000\n\n# Auth\nBETTER_AUTH_SECRET=your-secret-key-change-in-production\nBETTER_AUTH_URL=http://localhost:3000\n```\n\n**Note:** Generate a strong random string for `BETTER_AUTH_SECRET` in production. You can use `openssl rand -hex 32`.\n\n### 6. Start Development Server\n\n```bash\nnpm run dev\n```\n\nYour API is running at `http://localhost:3000` with hot reload.\n\n## Database\n\nThe Bun template uses `better-sqlite3` with a local SQLite file stored in `data/app.db`. The generated database module automatically:\n\n- Creates the `data/` directory if it doesn't exist\n- Enables WAL mode for better concurrent performance\n- Enables foreign key constraints\n\n```\ndata/\n└── app.db # SQLite database file\n```\n\nUnlike the Cloudflare template, Bun uses a **single database** for both auth and feature tables. Migrations are in a single directory:\n\n```\nquickback/drizzle/\n├── meta/\n│ ├── _journal.json\n│ └── 0000_snapshot.json\n└── 0000_initial.sql\n```\n\n## Available Scripts\n\n| Script | Command | Description |\n|--------|---------|-------------|\n| `dev` | `bun run --hot src/index.ts` | Start dev server with hot reload |\n| `start` | `bun run src/index.ts` | Start production server |\n| `build` | `bun build src/index.ts --outdir ./dist --target bun` | Build for production |\n| `db:migrate` | `npx drizzle-kit migrate` | Apply database migrations |\n| `type-check` | `tsc --noEmit` | Run TypeScript type checking |\n\n## Switching to Cloudflare\n\nWhen you're ready to deploy to production, you can switch to the Cloudflare template by updating your `quickback.config.ts`:\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: [\"organizations\"],\n providers: {\n runtime: { name: \"cloudflare\", config: {} },\n database: {\n name: \"cloudflare-d1\",\n config: { binding: \"DB\" },\n },\n auth: { name: \"better-auth\", config: {} },\n },\n});\n```\n\nThen recompile:\n\n```bash\nquickback compile\n```\n\nThe compiler regenerates the entire `src/` directory for the new runtime target. Your feature definitions stay the same.\n\n## Next Steps\n\n- [Add a new feature](/compiler/definitions/schema) — Define your own tables with security\n- [Configure access control](/compiler/definitions/access) — Set role-based permissions\n- [Add custom actions](/compiler/definitions/actions) — Business logic beyond CRUD\n- [Cloudflare template](/compiler/getting-started/template-cloudflare) — Deploy to production"
215
215
  },
216
216
  "compiler/getting-started/template-cloudflare": {
217
217
  "title": "Cloudflare Template",
218
- "content": "The Cloudflare template creates a production-ready backend running on Cloudflare Workers with D1 (SQLite at the edge), KV storage, and Better Auth.\n\n## Create the Project\n\n```bash\nquickback create cloudflare my-app\ncd my-app\n```\n\nAliases: `quickback create cf my-app`\n\nThis scaffolds a project with:\n\n```\nmy-app/\n├── quickback/\n│ ├── quickback.config.ts # Compiler configuration\n│ └── features/\n│ └── todos/\n│ ├── todos.ts # Schema + security (defineTable)\n│ └── actions.ts # Custom actions\n├── src/ # Compiled output (generated)\n├── drizzle/ # Migrations (generated)\n├── package.json\n├── tsconfig.json\n├── wrangler.toml\n└── drizzle.config.ts\n```\n\n## Generated Configuration\n\n### quickback.config.ts\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: [\"organizations\"],\n providers: {\n runtime: { name: \"cloudflare\", config: {} },\n database: {\n name: \"cloudflare-d1\",\n config: { binding: \"DB\" },\n },\n auth: { name: \"better-auth\", config: {} },\n },\n});\n```\n\n### Example Feature (todos.ts)\n\nThe template includes a `todos` feature with all four security pillars configured:\n\n```typescript\n\nexport const todos = sqliteTable(\"todos\", {\n id: text(\"id\").primaryKey(),\n title: text(\"title\").notNull(),\n description: text(\"description\"),\n completed: integer(\"completed\", { mode: \"boolean\" }).default(false),\n priority: text(\"priority\").default(\"medium\"),\n dueDate: integer(\"due_date\", { mode: \"timestamp\" }),\n organizationId: text(\"organization_id\").notNull(),\n ownerId: text(\"owner_id\").notNull(),\n});\n\nexport default defineTable(todos, {\n firewall: {\n organization: {},\n owner: { mode: \"optional\" },\n softDelete: {},\n },\n guards: {\n createable: [\"title\", \"description\", \"completed\", \"priority\", \"dueDate\"],\n updatable: [\"title\", \"description\", \"completed\", \"priority\", \"dueDate\"],\n immutable: [\"id\", \"organizationId\", \"ownerId\"],\n },\n masking: {\n description: {\n type: \"redact\",\n show: { roles: [\"owner\", \"admin\"] },\n },\n },\n crud: {\n list: { access: { roles: [\"owner\", \"admin\", \"member\"] } },\n get: { access: { roles: [\"owner\", \"admin\", \"member\"] } },\n create: { access: { roles: [\"owner\", \"admin\", \"member\"] } },\n update: { access: { roles: [\"owner\", \"admin\", \"member\"] } },\n delete: { access: { roles: [\"owner\", \"admin\"] }, mode: \"soft\" },\n },\n});\n```\n\n## Setup Steps\n\n### 1. Install Dependencies\n\n```bash\nnpm install\n```\n\n### 2. Log In to the Compiler\n\n```bash\nquickback login\n```\n\n### 3. Compile Your Definitions\n\n```bash\nquickback compile\n```\n\nThis sends your definitions to the Quickback compiler and generates the full `src/` directory, migrations, and configuration files.\n\n### 4. Create D1 Databases\n\nThe Cloudflare template uses **dual database mode** by default — separate databases for auth and application data.\n\n```bash\n# Create the auth database\nnpx wrangler d1 create my-app-auth\n\n# Create the features database\nnpx wrangler d1 create my-app-features\n```\n\nCopy the database IDs from the output and update your `wrangler.toml`:\n\n```toml\n[[d1_databases]]\nbinding = \"AUTH_DB\"\ndatabase_name = \"my-app-auth\"\ndatabase_id = \"paste-auth-id-here\"\n\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"my-app-features\"\ndatabase_id = \"paste-features-id-here\"\n```\n\n### 5. Run Migrations (Local)\n\n```bash\nnpm run db:migrate:local\n```\n\nThis applies migrations to both databases locally.\n\n### 6. Start Development Server\n\n```bash\nnpm run dev\n```\n\nYour API is running at `http://localhost:8787`.\n\n## Wrangler Configuration\n\nThe compiler generates a `wrangler.toml` with all required bindings:\n\n```toml\nname = \"my-app\"\nmain = \"src/index.ts\"\ncompatibility_date = \"2025-03-01\"\ncompatibility_flags = [\"nodejs_compat\"]\n\n[observability]\nenabled = true\n\n[placement]\nmode = \"smart\"\n\n# Auth database\n[[d1_databases]]\nbinding = \"AUTH_DB\"\ndatabase_name = \"my-app-auth\"\ndatabase_id = \"your-auth-db-id\"\n\n# Features database\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"my-app-features\"\ndatabase_id = \"your-features-db-id\"\n\n# Key-value storage\n[[kv_namespaces]]\nbinding = \"KV\"\nid = \"your-kv-id\"\n```\n\n### Optional Bindings\n\nDepending on your configuration, the compiler may also generate bindings for:\n\n| Binding | Type | When Generated |\n|---------|------|----------------|\n| `R2_BUCKET` | R2 Bucket | `fileStorage` configured |\n| `AI` | Workers AI | `embeddings` configured on any feature |\n| `VECTORIZE` | Vectorize Index | `embeddings` configured |\n| `EMBEDDINGS_QUEUE` | Queue | `embeddings` configured |\n| `WEBHOOKS_DB` | D1 | Webhooks enabled |\n| `WEBHOOKS_QUEUE` | Queue | Webhooks enabled |\n| `BROADCASTER` | Service | `realtime` configured |\n\n## Dual Database Mode\n\nBy default, the Cloudflare template separates auth tables from application tables:\n\n- **`AUTH_DB`** — Better Auth tables (`users`, `sessions`, `accounts`, `organizations`, `members`)\n- **`DB`** — Your feature tables (`todos`, etc.)\n\nThis keeps auth data isolated and allows independent scaling. Each database gets its own migration directory:\n\n```\ndrizzle/\n├── auth/ # Auth migrations\n│ ├── meta/\n│ └── 0000_*.sql\n└── features/ # Feature migrations\n ├── meta/\n └── 0000_*.sql\n```\n\n## Deploying to Production\n\n### 1. Apply Remote Migrations\n\n```bash\nnpm run db:migrate:remote\n```\n\n### 2. Deploy to Cloudflare\n\n```bash\nnpm run deploy\n```\n\nThis runs `wrangler deploy` to push your worker to Cloudflare's edge network.\n\n## Available Scripts\n\n| Script | Command | Description |\n|--------|---------|-------------|\n| `dev` | `wrangler dev` | Start local dev server |\n| `deploy` | `npm run db:migrate:remote && wrangler deploy` | Deploy to production |\n| `db:migrate:local` | Runs auth + features migrations locally | Apply migrations to local D1 |\n| `db:migrate:remote` | Runs auth + features migrations remotely | Apply migrations to production D1 |\n\n## Next Steps\n\n- [Add a new feature](/compiler/definitions/schema) — Define your own tables with security\n- [Configure access control](/compiler/definitions/access) — Set role-based permissions\n- [Add custom actions](/compiler/definitions/actions) — Business logic beyond CRUD\n- [Environment variables](/compiler/config/variables) — Configure secrets and bindings"
218
+ "content": "The Cloudflare template creates a production-ready backend running on Cloudflare Workers with D1 (SQLite at the edge), KV storage, and Better Auth.\n\n## Create the Project\n\n```bash\nquickback create cloudflare my-app\ncd my-app\n```\n\nAliases: `quickback create cf my-app`\n\nThis scaffolds a project with:\n\n```\nmy-app/\n├── quickback/\n│ ├── quickback.config.ts # Compiler configuration\n│ └── features/\n│ └── todos/\n│ ├── todos.ts # Schema + security (defineTable)\n│ └── actions.ts # Custom actions\n├── src/ # Compiled output (generated)\n├── package.json\n├── tsconfig.json\n├── wrangler.toml\n└── drizzle.config.ts\n```\n\n## Generated Configuration\n\n### quickback.config.ts\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n template: \"hono\",\n features: [\"organizations\"],\n providers: {\n runtime: { name: \"cloudflare\", config: {} },\n database: {\n name: \"cloudflare-d1\",\n config: { binding: \"DB\" },\n },\n auth: { name: \"better-auth\", config: {} },\n },\n});\n```\n\n### Example Feature (todos.ts)\n\nThe template includes a `todos` feature with all four security pillars configured:\n\n```typescript\n\nexport const todos = sqliteTable(\"todos\", {\n id: text(\"id\").primaryKey(),\n title: text(\"title\").notNull(),\n description: text(\"description\"),\n completed: integer(\"completed\", { mode: \"boolean\" }).default(false),\n priority: text(\"priority\").default(\"medium\"),\n dueDate: integer(\"due_date\", { mode: \"timestamp\" }),\n organizationId: text(\"organization_id\").notNull(),\n ownerId: text(\"owner_id\").notNull(),\n});\n\nexport default defineTable(todos, {\n firewall: {\n organization: {},\n owner: { mode: \"optional\" },\n softDelete: {},\n },\n guards: {\n createable: [\"title\", \"description\", \"completed\", \"priority\", \"dueDate\"],\n updatable: [\"title\", \"description\", \"completed\", \"priority\", \"dueDate\"],\n immutable: [\"id\", \"organizationId\", \"ownerId\"],\n },\n masking: {\n description: {\n type: \"redact\",\n show: { roles: [\"owner\", \"admin\"] },\n },\n },\n crud: {\n list: { access: { roles: [\"owner\", \"admin\", \"member\"] } },\n get: { access: { roles: [\"owner\", \"admin\", \"member\"] } },\n create: { access: { roles: [\"owner\", \"admin\", \"member\"] } },\n update: { access: { roles: [\"owner\", \"admin\", \"member\"] } },\n delete: { access: { roles: [\"owner\", \"admin\"] }, mode: \"soft\" },\n },\n});\n```\n\n## Setup Steps\n\n### 1. Install Dependencies\n\n```bash\nnpm install\n```\n\n### 2. Log In to the Compiler\n\n```bash\nquickback login\n```\n\n### 3. Compile Your Definitions\n\n```bash\nquickback compile\n```\n\nThis sends your definitions to the Quickback compiler and generates the full `src/` directory, migrations, and configuration files.\n\n### 4. Create D1 Databases\n\nThe Cloudflare template uses **dual database mode** by default — separate databases for auth and application data.\n\n```bash\n# Create the auth database\nnpx wrangler d1 create my-app-auth\n\n# Create the features database\nnpx wrangler d1 create my-app-features\n```\n\nCopy the database IDs from the output and update your `wrangler.toml`:\n\n```toml\n[[d1_databases]]\nbinding = \"AUTH_DB\"\ndatabase_name = \"my-app-auth\"\ndatabase_id = \"paste-auth-id-here\"\n\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"my-app-features\"\ndatabase_id = \"paste-features-id-here\"\n```\n\n### 5. Run Migrations (Local)\n\n```bash\nnpm run db:migrate:local\n```\n\nThis applies migrations to both databases locally.\n\n### 6. Start Development Server\n\n```bash\nnpm run dev\n```\n\nYour API is running at `http://localhost:8787`.\n\n## Wrangler Configuration\n\nThe compiler generates a `wrangler.toml` with all required bindings:\n\n```toml\nname = \"my-app\"\nmain = \"src/index.ts\"\ncompatibility_date = \"2025-03-01\"\ncompatibility_flags = [\"nodejs_compat\"]\n\n[observability]\nenabled = true\n\n[placement]\nmode = \"smart\"\n\n# Auth database\n[[d1_databases]]\nbinding = \"AUTH_DB\"\ndatabase_name = \"my-app-auth\"\ndatabase_id = \"your-auth-db-id\"\n\n# Features database\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"my-app-features\"\ndatabase_id = \"your-features-db-id\"\n\n# Key-value storage\n[[kv_namespaces]]\nbinding = \"KV\"\nid = \"your-kv-id\"\n```\n\n### Optional Bindings\n\nDepending on your configuration, the compiler may also generate bindings for:\n\n| Binding | Type | When Generated |\n|---------|------|----------------|\n| `R2_BUCKET` | R2 Bucket | `fileStorage` configured |\n| `AI` | Workers AI | `embeddings` configured on any feature |\n| `VECTORIZE` | Vectorize Index | `embeddings` configured |\n| `EMBEDDINGS_QUEUE` | Queue | `embeddings` configured |\n| `WEBHOOKS_DB` | D1 | Webhooks enabled |\n| `WEBHOOKS_QUEUE` | Queue | Webhooks enabled |\n| `BROADCASTER` | Service | `realtime` configured |\n\n## Dual Database Mode\n\nBy default, the Cloudflare template separates auth tables from application tables:\n\n- **`AUTH_DB`** — Better Auth tables (`users`, `sessions`, `accounts`, `organizations`, `members`)\n- **`DB`** — Your feature tables (`todos`, etc.)\n\nThis keeps auth data isolated and allows independent scaling. Each database gets its own migration directory:\n\n```\nquickback/drizzle/\n├── auth/ # Auth migrations\n│ ├── meta/\n│ └── 0000_*.sql\n└── features/ # Feature migrations\n ├── meta/\n └── 0000_*.sql\n```\n\n## Deploying to Production\n\n### 1. Apply Remote Migrations\n\n```bash\nnpm run db:migrate:remote\n```\n\n### 2. Deploy to Cloudflare\n\n```bash\nnpm run deploy\n```\n\nThis runs `wrangler deploy` to push your worker to Cloudflare's edge network.\n\n## Available Scripts\n\n| Script | Command | Description |\n|--------|---------|-------------|\n| `dev` | `wrangler dev` | Start local dev server |\n| `deploy` | `npm run db:migrate:remote && wrangler deploy` | Deploy to production |\n| `db:migrate:local` | Runs auth + features migrations locally | Apply migrations to local D1 |\n| `db:migrate:remote` | Runs auth + features migrations remotely | Apply migrations to production D1 |\n\n## Next Steps\n\n- [Add a new feature](/compiler/definitions/schema) — Define your own tables with security\n- [Configure access control](/compiler/definitions/access) — Set role-based permissions\n- [Add custom actions](/compiler/definitions/actions) — Business logic beyond CRUD\n- [Environment variables](/compiler/config/variables) — Configure secrets and bindings"
219
219
  },
220
220
  "compiler/getting-started/templates": {
221
221
  "title": "Templates",
@@ -223,11 +223,11 @@ export const DOCS = {
223
223
  },
224
224
  "compiler": {
225
225
  "title": "Quickback Compiler",
226
- "content": "The Quickback compiler transforms your declarative resource definitions into a complete, production-ready API. It analyzes your TypeScript definitions at build time, generates optimized code, validates your security configuration, and creates database migrations.\n\n## What the Compiler Does\n\nWhen you run `quickback compile`, the compiler:\n\n1. **Reads your definitions** - Analyzes all `defineTable()` configurations in your `quickback/features/` folder\n2. **Validates security** - Checks that firewall, guards, access, and masking are properly configured\n3. **Generates API routes** - Creates REST endpoints for each resource (GET, POST, PATCH, DELETE, plus batch operations)\n4. **Generates actions** - Creates custom endpoints from your `defineActions()` definitions\n5. **Creates middleware** - Generates authentication, authorization, and data validation logic\n6. **Generates TypeScript types** - Creates type-safe interfaces for your API\n7. **Generates migrations** - Automatically runs `drizzle-kit generate` to create database migration files\n\n**Input:**\n```\nquickback/\n├── quickback.config.ts # Compiler configuration\n└── features/\n └── jobs/\n ├── jobs.ts # defineTable(...)\n └── actions.ts # defineActions(...)\n```\n\n**Output:**\n```\nsrc/\n├── routes/\n│ └── jobs.ts # Generated API handlers\n├── middleware/\n│ ├── auth.ts # Authentication logic\n│ └── firewall.ts # Data isolation queries\n└── types/\n └── jobs.ts # TypeScript interfaces\n```\n\n## Basic Usage\n\n### Compile Your Project\n\n```bash\nquickback compile\n```\n\nThis command:\n- Reads your `quickback.config.ts`\n- Analyzes all resource definitions\n- Generates the complete API codebase\n- Reports any configuration errors\n\n### After Compilation\n\nOnce compilation succeeds, you need to:\n\n1. **Apply migrations:**\n\n Run the database migration for your provider. The generated `package.json` includes the appropriate migration script for your setup.\n\n > **Note:** The compiler automatically runs `drizzle-kit generate` during compilation, so you only need to apply the migrations.\n\n2. **Deploy your API:**\n ```bash\n npm run deploy # Cloudflare Workers\n # or\n npm start # Local development\n ```\n\n## The Compilation Process\n\n### 1. Configuration Loading\n\nThe compiler reads your `quickback.config.ts`:\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\nThis determines:\n- Which application template to use (`hono` or experimental `nextjs`)\n- Which database adapter to use (D1, SQLite, Turso, etc.)\n- Which authentication system to integrate\n- Which runtime to target (Cloudflare Workers, Bun, Node.js)\n- Which auth plugins to enable (based on `features`)\n\n### 2. Resource Discovery\n\nThe compiler scans `quickback/features/` and identifies:\n\n**Resources (with routes):**\n```typescript\n// quickback/features/jobs/jobs.ts\nexport default defineTable(jobs, { ... });\n// → Generates: GET/POST/PATCH/DELETE /api/v1/jobs\n// → Also generates: POST/PATCH/DELETE/PUT /api/v1/jobs/batch (auto-enabled)\n```\n\n**Internal tables (no routes):**\n```typescript\n// quickback/features/jobs/job-applications.ts\nexport const jobApplications = sqliteTable(...);\n// NO default export → No routes generated\n```\n\n**Actions:**\n```typescript\n// quickback/features/jobs/actions.ts\nexport default defineActions(jobs, {\n publish: { ... },\n close: { ... }\n});\n// → Generates: POST /api/v1/jobs/:id/publish, POST /api/v1/jobs/:id/close\n```\n\n### 3. Validation\n\nThe compiler validates your configuration at build time, catching errors before deployment. See [Definitions](/compiler/definitions) for details on what's validated.\n\n### 4. Code Generation\n\nThe compiler generates different files based on your provider:\n\n#### Cloudflare Workers (Hono)\n\n```typescript\n// Generated: src/routes/jobs.ts\n\nconst app = new Hono();\n\napp.get('/jobs', async (c) => {\n const ctx = c.get('ctx');\n let query = db.select().from(jobs);\n query = applyFirewall(query, ctx, 'jobs');\n await checkAccess(ctx, 'jobs', 'list');\n const results = await query;\n return c.json(results);\n});\n```\n\n### 5. Type Generation\n\nThe compiler generates TypeScript types for your API:\n\n```typescript\n// Generated: src/types/jobs.ts\nexport type Job = typeof jobs.$inferSelect;\nexport type JobInsert = typeof jobs.$inferInsert;\n```\n\n## Build-Time vs Runtime\n\n### Build Time (Compilation)\n\nThe compiler validates security configuration, checks for schema/firewall mismatches, generates API route handlers, creates TypeScript types, and prepares migration setup.\n\n### Runtime (API Requests)\n\nWhen your API receives a request: Authentication → Firewall → Access → Guards → Masking → Response. The compiled code handles all of this automatically.\n\n## Development Workflow\n\n1. **Define resources** in `quickback/features/`\n2. **Compile** with `quickback compile`\n3. **Review migrations** in `drizzle/`\n4. **Apply migrations** for your provider\n5. **Test locally** with `npm run dev`\n6. **Deploy** with `npm run deploy`\n\n## Next Steps\n\n- [Getting Started](/compiler/getting-started) — Create your first project\n- [Definitions](/compiler/definitions) — Schema, security layers, and actions\n- [Using the API](/compiler/using-the-api) — CRUD endpoints and filtering\n- [Cloud Compiler](/compiler/cloud-compiler) — CLI and authentication"
226
+ "content": "The Quickback compiler transforms your declarative resource definitions into a complete, production-ready API. It analyzes your TypeScript definitions at build time, generates optimized code, validates your security configuration, and creates database migrations.\n\n## What the Compiler Does\n\nWhen you run `quickback compile`, the compiler:\n\n1. **Reads your definitions** - Analyzes all `defineTable()` configurations in your `quickback/features/` folder\n2. **Validates security** - Checks that firewall, guards, access, and masking are properly configured\n3. **Generates API routes** - Creates REST endpoints for each resource (GET, POST, PATCH, DELETE, plus batch operations)\n4. **Generates actions** - Creates custom endpoints from your `defineActions()` definitions\n5. **Creates middleware** - Generates authentication, authorization, and data validation logic\n6. **Generates TypeScript types** - Creates type-safe interfaces for your API\n7. **Generates migrations** - Automatically runs `drizzle-kit generate` to create database migration files\n\n**Input:**\n```\nquickback/\n├── quickback.config.ts # Compiler configuration\n└── features/\n └── jobs/\n ├── jobs.ts # defineTable(...)\n └── actions.ts # defineActions(...)\n```\n\n**Output:**\n```\nsrc/\n├── routes/\n│ └── jobs.ts # Generated API handlers\n├── middleware/\n│ ├── auth.ts # Authentication logic\n│ └── firewall.ts # Data isolation queries\n└── types/\n └── jobs.ts # TypeScript interfaces\n```\n\n## Basic Usage\n\n### Compile Your Project\n\n```bash\nquickback compile\n```\n\nThis command:\n- Reads your `quickback.config.ts`\n- Analyzes all resource definitions\n- Generates the complete API codebase\n- Reports any configuration errors\n\n### After Compilation\n\nOnce compilation succeeds, you need to:\n\n1. **Apply migrations:**\n\n Run the database migration for your provider. The generated `package.json` includes the appropriate migration script for your setup.\n\n > **Note:** The compiler automatically runs `drizzle-kit generate` during compilation, so you only need to apply the migrations.\n\n2. **Deploy your API:**\n ```bash\n npm run deploy # Cloudflare Workers\n # or\n npm start # Local development\n ```\n\n## The Compilation Process\n\n### 1. Configuration Loading\n\nThe compiler reads your `quickback.config.ts`:\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\nThis determines:\n- Which application template to use (`hono` or experimental `nextjs`)\n- Which database adapter to use (D1, SQLite, Turso, etc.)\n- Which authentication system to integrate\n- Which runtime to target (Cloudflare Workers, Bun, Node.js)\n- Which auth plugins to enable (based on `features`)\n\n### 2. Resource Discovery\n\nThe compiler scans `quickback/features/` and identifies:\n\n**Resources (with routes):**\n```typescript\n// quickback/features/jobs/jobs.ts\nexport default defineTable(jobs, { ... });\n// → Generates: GET/POST/PATCH/DELETE /api/v1/jobs\n// → Also generates: POST/PATCH/DELETE/PUT /api/v1/jobs/batch (auto-enabled)\n```\n\n**Internal tables (no routes):**\n```typescript\n// quickback/features/jobs/job-applications.ts\nexport const jobApplications = sqliteTable(...);\n// NO default export → No routes generated\n```\n\n**Actions:**\n```typescript\n// quickback/features/jobs/actions.ts\nexport default defineActions(jobs, {\n publish: { ... },\n close: { ... }\n});\n// → Generates: POST /api/v1/jobs/:id/publish, POST /api/v1/jobs/:id/close\n```\n\n### 3. Validation\n\nThe compiler validates your configuration at build time, catching errors before deployment. See [Definitions](/compiler/definitions) for details on what's validated.\n\n### 4. Code Generation\n\nThe compiler generates different files based on your provider:\n\n#### Cloudflare Workers (Hono)\n\n```typescript\n// Generated: src/routes/jobs.ts\n\nconst app = new Hono();\n\napp.get('/jobs', async (c) => {\n const ctx = c.get('ctx');\n let query = db.select().from(jobs);\n query = applyFirewall(query, ctx, 'jobs');\n await checkAccess(ctx, 'jobs', 'list');\n const results = await query;\n return c.json(results);\n});\n```\n\n### 5. Type Generation\n\nThe compiler generates TypeScript types for your API:\n\n```typescript\n// Generated: src/types/jobs.ts\nexport type Job = typeof jobs.$inferSelect;\nexport type JobInsert = typeof jobs.$inferInsert;\n```\n\n## Build-Time vs Runtime\n\n### Build Time (Compilation)\n\nThe compiler validates security configuration, checks for schema/firewall mismatches, generates API route handlers, creates TypeScript types, and prepares migration setup.\n\n### Runtime (API Requests)\n\nWhen your API receives a request: Authentication → Firewall → Access → Guards → Masking → Response. The compiled code handles all of this automatically.\n\n## Development Workflow\n\n1. **Define resources** in `quickback/features/`\n2. **Compile** with `quickback compile`\n3. **Review migrations** in `quickback/drizzle/`\n4. **Apply migrations** for your provider\n5. **Test locally** with `npm run dev`\n6. **Deploy** with `npm run deploy`\n\n## Next Steps\n\n- [Getting Started](/compiler/getting-started) — Create your first project\n- [Definitions](/compiler/definitions) — Schema, security layers, and actions\n- [Using the API](/compiler/using-the-api) — CRUD endpoints and filtering\n- [Cloud Compiler](/compiler/cloud-compiler) — CLI and authentication"
227
227
  },
228
228
  "compiler/integrations/cloudflare": {
229
229
  "title": "Cloudflare Workers",
230
- "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\ndrizzle/\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` | Service | No | Realtime broadcasts |\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"
230
+ "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` | Service | No | Realtime broadcasts |\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"
231
231
  },
232
232
  "compiler/integrations": {
233
233
  "title": "Compile Targets",
@@ -235,7 +235,7 @@ export const DOCS = {
235
235
  },
236
236
  "compiler/integrations/neon": {
237
237
  "title": "Neon",
238
- "content": "Quickback supports Neon as a PostgreSQL database provider, generating schemas with Row Level Security (RLS) policies that enforce your firewall configuration at the database level.\n\n## Why Neon?\n\n- **Full PostgreSQL** — Advanced queries, joins, PostGIS, full-text search\n- **Database-Level Security** — RLS policies enforce access even if API is bypassed\n- **Serverless Architecture** — HTTP connection mode for Cloudflare Workers\n- **Neon Authorize** — JWT-based RLS with `auth.user_id()` function\n- **Better Auth Integration** — Works seamlessly with Better Auth\n\n## Configuration\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n providers: {\n runtime: defineRuntime(\"cloudflare\"),\n database: defineDatabase(\"neon\", {\n connectionMode: 'auto', // auto-detects based on runtime\n pooled: true,\n }),\n auth: defineAuth(\"better-auth\"),\n },\n});\n```\n\n### Environment Variables\n\n```bash\nDATABASE_URL=postgresql://user:pwd@ep-xxx.neon.tech/db?sslmode=require\n```\n\n## Row Level Security\n\nQuickback generates RLS policies from your firewall config:\n\n```typescript\nfirewall: {\n organization: {},\n owner: { mode: 'optional' }\n}\n```\n\n```sql\n-- Generated policy (uses auth.user_id() for Neon)\nCREATE POLICY \"documents_select\" ON documents FOR SELECT\nUSING (\n organization_id = get_active_org_id()\n AND (has_any_role(ARRAY['admin']) OR user_id = auth.user_id())\n);\n```\n\n### Firewall Patterns\n\n**Organization-Scoped:**\n```sql\nCREATE POLICY \"projects_select\" ON projects FOR SELECT\nUSING (organization_id = public.get_active_org_id());\n```\n\n**User-Scoped:**\n```sql\nCREATE POLICY \"preferences_select\" ON preferences FOR SELECT\nUSING (user_id = auth.user_id());\n```\n\n**Public Tables:**\n```sql\nCREATE POLICY \"categories_deny_anon\" ON categories FOR ALL TO anon USING (false);\nCREATE POLICY \"categories_all\" ON categories FOR ALL TO authenticated USING (true) WITH CHECK (true);\n```\n\n## Generated Helper Functions\n\n| Function | Purpose |\n|----------|---------|\n| `get_active_org_id()` | Returns user's active organization from `user_sessions` |\n| `has_any_role(roles[])` | Checks if user has any specified role |\n| `has_org_role(role)` | Checks for a specific role |\n| `is_org_member()` | Checks org membership |\n| `is_owner(owner_id)` | Checks record ownership |\n\n## Generated Files\n\n```\ndrizzle/\n├── migrations/\n│ ├── 0100_create_rls_helpers.sql\n│ ├── 0101_create_rls_policies.sql\n│ └── 0102_create_indexes.sql\nsrc/\n├── db/\n│ └── schema.ts\n├── auth/\n│ └── schema.ts\n└── lib/\n └── neon.ts\n```\n\n## Setting Up Neon Authorize\n\n1. Go to **Project Settings > Authorize** in Neon console\n2. Add your JWKS URL: `https://your-api.com/auth/v1/.well-known/jwks.json`\n3. The `auth.user_id()` function will return the authenticated user's ID from JWT\n\n## Getting Started\n\n1. Create a Neon project at [neon.tech](https://neon.tech)\n2. Configure your Quickback project for Neon\n3. Run `quickback compile`\n4. Apply migrations with `drizzle-kit migrate`"
238
+ "content": "Quickback supports Neon as a PostgreSQL database provider, generating schemas with Row Level Security (RLS) policies that enforce your firewall configuration at the database level.\n\n## Why Neon?\n\n- **Full PostgreSQL** — Advanced queries, joins, PostGIS, full-text search\n- **Database-Level Security** — RLS policies enforce access even if API is bypassed\n- **Serverless Architecture** — HTTP connection mode for Cloudflare Workers\n- **Neon Authorize** — JWT-based RLS with `auth.user_id()` function\n- **Better Auth Integration** — Works seamlessly with Better Auth\n\n## Configuration\n\n```typescript\n\nexport default defineConfig({\n name: \"my-app\",\n providers: {\n runtime: defineRuntime(\"cloudflare\"),\n database: defineDatabase(\"neon\", {\n connectionMode: 'auto', // auto-detects based on runtime\n pooled: true,\n }),\n auth: defineAuth(\"better-auth\"),\n },\n});\n```\n\n### Environment Variables\n\n```bash\nDATABASE_URL=postgresql://user:pwd@ep-xxx.neon.tech/db?sslmode=require\n```\n\n## Row Level Security\n\nQuickback generates RLS policies from your firewall config:\n\n```typescript\nfirewall: {\n organization: {},\n owner: { mode: 'optional' }\n}\n```\n\n```sql\n-- Generated policy (uses auth.user_id() for Neon)\nCREATE POLICY \"documents_select\" ON documents FOR SELECT\nUSING (\n organization_id = get_active_org_id()\n AND (has_any_role(ARRAY['admin']) OR user_id = auth.user_id())\n);\n```\n\n### Firewall Patterns\n\n**Organization-Scoped:**\n```sql\nCREATE POLICY \"projects_select\" ON projects FOR SELECT\nUSING (organization_id = public.get_active_org_id());\n```\n\n**User-Scoped:**\n```sql\nCREATE POLICY \"preferences_select\" ON preferences FOR SELECT\nUSING (user_id = auth.user_id());\n```\n\n**Public Tables:**\n```sql\nCREATE POLICY \"categories_deny_anon\" ON categories FOR ALL TO anon USING (false);\nCREATE POLICY \"categories_all\" ON categories FOR ALL TO authenticated USING (true) WITH CHECK (true);\n```\n\n## Generated Helper Functions\n\n| Function | Purpose |\n|----------|---------|\n| `get_active_org_id()` | Returns user's active organization from `user_sessions` |\n| `has_any_role(roles[])` | Checks if user has any specified role |\n| `has_org_role(role)` | Checks for a specific role |\n| `is_org_member()` | Checks org membership |\n| `is_owner(owner_id)` | Checks record ownership |\n\n## Generated Files\n\n```\nquickback/drizzle/\n├── migrations/\n│ ├── 0100_create_rls_helpers.sql\n│ ├── 0101_create_rls_policies.sql\n│ └── 0102_create_indexes.sql\nsrc/\n├── db/\n│ └── schema.ts\n├── auth/\n│ └── schema.ts\n└── lib/\n └── neon.ts\n```\n\n## Setting Up Neon Authorize\n\n1. Go to **Project Settings > Authorize** in Neon console\n2. Add your JWKS URL: `https://your-api.com/auth/v1/.well-known/jwks.json`\n3. The `auth.user_id()` function will return the authenticated user's ID from JWT\n\n## Getting Started\n\n1. Create a Neon project at [neon.tech](https://neon.tech)\n2. Configure your Quickback project for Neon\n3. Run `quickback compile`\n4. Apply migrations with `drizzle-kit migrate`"
239
239
  },
240
240
  "compiler/integrations/supabase": {
241
241
  "title": "Supabase",
@@ -279,7 +279,7 @@ export const DOCS = {
279
279
  },
280
280
  "index": {
281
281
  "title": "Quickback Documentation",
282
- "content": "# Quickback Documentation\n\nQuickback is a **backend compiler**. Define your database schema and security rules in TypeScript — Quickback compiles them into a complete, production-ready API.\n\n## Quick Start\n\n```bash\nnpm install -g @kardoe/quickback\nquickback create cloudflare my-app\ncd my-app\nquickback compile\n```\n\n## How It Works\n\nWrite ~50 lines of configuration. Get 500+ lines of production code.\n\n```\ndefineTable() → Compiler → Production API\n │ │\n ├── Schema ├── CRUD routes + batch operations\n ├── Firewall ├── Auth middleware\n ├── Access ├── Data isolation queries\n ├── Guards ├── Field validation\n └── Masking ├── PII redaction\n ├── TypeScript types\n └── Database migrations\n```\n\nEvery API request passes through four security layers:\n\n```\nRequest → Firewall → Access → Guards → Masking → Response\n │ │ │ │\n │ │ │ └── Hide sensitive fields\n │ │ └── Block field modifications\n │ └── Check roles & conditions\n └── Isolate data by owner/org\n```\n\n**Secure by default.** Nothing is accessible until you explicitly open it up. See [Definitions](/compiler/definitions) for the full security model.\n\n---\n\n## Products\n\n### [Quickback Compiler](/compiler)\n\nThe core product. Define your database schema and security rules in TypeScript, then compile them into a complete backend.\n\n- **[Getting Started](/compiler/getting-started)** — Create your first project\n- **[Definitions](/compiler/definitions)** — Schema, Firewall, Access, Guards, Masking, Views, Actions\n- **[Using the API](/compiler/using-the-api)** — CRUD, filtering, batch operations\n- **[Cloud Compiler](/compiler/cloud-compiler)** — CLI, authentication, endpoints\n\n---\n\n### [Quickback Stack](/stack)\n\nProduction-ready Cloudflare + Better Auth infrastructure that runs on YOUR Cloudflare account.\n\n- **[Auth](/stack/auth)** — Better Auth plugins, security, device auth, API keys\n- **[Database](/stack/database)** — D1, Neon\n- **[Storage](/stack/storage)** — KV, R2\n- **[Realtime](/stack/realtime)** — Durable Objects + WebSocket\n\n---\n\n### [Account UI](/account-ui)\n\nPre-built authentication and account management UI. Deploy to Cloudflare Workers.\n\n- **[Features](/account-ui)** — Sessions, organizations, passkeys, admin panel\n- **[Customization](/account-ui/customization)** — Branding, theming, feature flags\n\n---\n\n### [Plugins & Tools](/plugins-tools)\n\nOpen-source Better Auth plugins and developer tools.\n\n- **[Better Auth Plugins](/plugins-tools)** — AWS SES, combo auth, upgrade anonymous\n- **[Claude Code Skill](/plugins-tools/claude-code-skill)** — AI-powered Quickback assistance\n\n### [Changelog](/changelog)\n\nWhat's new in Quickback — release notes, new features, and improvements."
282
+ "content": "# Quickback Documentation\n\nQuickback is a **backend compiler**. Define your database schema and security rules in TypeScript — Quickback compiles them into a complete, production-ready API or Supabase RLS policies.\n\n## Quick Start\n\n```bash\nnpm install -g @kardoe/quickback\nquickback create cloudflare my-app\ncd my-app\nquickback compile\n```\n\n## How It Works\n\nWrite ~50 lines of configuration. Get 500+ lines of production code.\n\n```\ndefineTable() → Compiler → Production API\n │ │\n ├── Schema ├── CRUD routes + batch operations\n ├── Firewall ├── Auth middleware\n ├── Access ├── Data isolation queries\n ├── Guards ├── Field validation\n └── Masking ├── PII redaction\n ├── TypeScript types\n └── Database migrations\n```\n\nEvery API request passes through four security layers:\n\n```\nRequest → Firewall → Access → Guards → Masking → Response\n │ │ │ │\n │ │ │ └── Hide sensitive fields\n │ │ └── Block field modifications\n │ └── Check roles & conditions\n └── Isolate data by owner/org\n```\n\n**Secure by default.** Nothing is accessible until you explicitly open it up. See [Definitions](/compiler/definitions) for the full security model.\n\n---\n\n## Products\n\n### [Quickback API](/compiler)\n\nThe multi-tenant-first API. Define your schema and security rules in TypeScript the compiler generates a full Hono API with four built-in security layers.\n\n- **[Getting Started](/compiler/getting-started)** — Create your first project\n- **[Definitions](/compiler/definitions)** — Schema, Firewall, Access, Guards, Masking, Views, Actions\n- **[Using the API](/compiler/using-the-api)** — CRUD endpoints and filtering\n- **[Cloud Compiler](/compiler/cloud-compiler)** — CLI, MCP Server, authentication, endpoints\n\n**Compile targets:** Cloudflare D1 (recommended) · Neon\n\n---\n\n### Quickback for Supabase RLS\n\nAlready on Supabase? Quickback compiles your TypeScript definitions into Row Level Security policies. Keep Supabase Auth, Storage, and Realtime — Quickback just adds the security layer.\n\n- **[Getting Started](/compiler/getting-started)** — Same definitions, target `supabase`\n- **[Definitions](/compiler/definitions)** — Firewall and Access compile to `FOR ALL USING` and `FOR SELECT/INSERT/UPDATE/DELETE USING` policies\n\n**Compile target:** Supabase (RLS policies only)\n\n---\n\n### [Quickback Stack](/stack)\n\nThe complete Supabase alternative on Cloudflare. Includes Quickback API plus realtime, storage, vector search, queues, and email — all running on your own Cloudflare account.\n\n- **[Auth](/stack/auth)** — Better Auth plugins, security, device auth, API keys\n- **[Database](/stack/database)** — D1, Neon\n- **[Storage](/stack/storage)** — KV, R2\n- **[Realtime](/stack/realtime)** — Durable Objects + WebSocket\n- **[Queues](/stack/queues)** — Background jobs and webhooks\n- **[Vector & AI](/stack/vector)** — Embeddings, semantic search\n\n---\n\n### [Account UI](/account-ui)\n\nPre-built authentication and account management UI. Deploy to Cloudflare Workers.\n\n- **[Features](/account-ui)** — Sessions, organizations, passkeys, admin panel\n- **[Customization](/account-ui/customization)** — Branding, theming, feature flags\n\n---\n\n### [Plugins & Tools](/plugins-tools)\n\nOpen-source Better Auth plugins and developer tools.\n\n- **[Better Auth Plugins](/plugins-tools)** — AWS SES, combo auth, upgrade anonymous\n- **[Claude Code Skill](/plugins-tools/claude-code-skill)** — AI-powered Quickback assistance\n- **MCP Server** — Connect any MCP-compatible AI tool to Quickback\n\n### [Changelog](/changelog)\n\nWhat's new in Quickback — release notes, new features, and improvements."
283
283
  },
284
284
  "plugins-tools/better-auth-plugins/aws-ses": {
285
285
  "title": "AWS SES Plugin",
@@ -294,8 +294,8 @@ export const DOCS = {
294
294
  "content": "`@kardoe/better-auth-upgrade-anonymous` provides a single endpoint to convert anonymous users into full authenticated users. It flips the `isAnonymous` flag, optionally updates email and name, and refreshes the session — no re-authentication required.\n\n## Installation\n\n```bash\nnpm install @kardoe/better-auth-upgrade-anonymous\n```\n\n## Configuration\n\nEnable both the `anonymous` and `upgradeAnonymous` plugins:\n\n```typescript\nauth: defineAuth(\"better-auth\", {\n plugins: [\"anonymous\", \"upgradeAnonymous\"],\n})\n```\n\nThe plugin accepts optional configuration:\n\n```typescript\n\nupgradeAnonymous({\n emailConfigured: true, // Whether email delivery is available\n requireEmailVerification: true, // Whether to require email verification\n})\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `emailConfigured` | `false` | When `true` and an email is provided, `emailVerified` is set based on `requireEmailVerification` |\n| `requireEmailVerification` | `true` | When `true`, provided emails are marked unverified (requiring OTP). When `false`, emails are marked verified immediately |\n\n## Endpoint\n\n```\nPOST /auth/v1/upgrade-anonymous\n```\n\n### Flow\n\n1. Validates the user's session (returns `401` if not authenticated)\n2. Checks if the user is already upgraded (returns success immediately if so)\n3. If an email is provided, checks for duplicates (returns `400` if email is taken)\n4. Updates `isAnonymous` to `false`, plus optional `email`, `name`, and `emailVerified` fields\n5. Refreshes the session cookie with the updated user object\n6. Returns the updated user, session, and `verificationRequired` flag\n\n### Request\n\nThe body is optional. When provided, it accepts:\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `email` | `string` (email) | Optional. Real email to replace the anonymous placeholder |\n| `name` | `string` | Optional. User's display name |\n\n```bash\n# Minimal — just upgrade, no email/name\ncurl -X POST https://api.example.com/auth/v1/upgrade-anonymous \\\n -H \"Cookie: better-auth.session_token=...\"\n\n# With email and name\ncurl -X POST https://api.example.com/auth/v1/upgrade-anonymous \\\n -H \"Cookie: better-auth.session_token=...\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"email\": \"jane@example.com\", \"name\": \"Jane\"}'\n```\n\n### Response\n\n```json\n{\n \"success\": true,\n \"verificationRequired\": true,\n \"user\": {\n \"id\": \"usr_abc123\",\n \"email\": \"jane@example.com\",\n \"isAnonymous\": false\n },\n \"session\": {\n \"id\": \"sess_xyz\",\n \"userId\": \"usr_abc123\"\n }\n}\n```\n\nThe `verificationRequired` field is `true` when all three conditions are met:\n- An email was provided in the request body\n- `emailConfigured` is `true` in plugin config\n- `requireEmailVerification` is `true` in plugin config\n\nWhen `verificationRequired` is `true`, the frontend should send a verification OTP and redirect the user to verify their email.\n\n## How It Works\n\nThe plugin is designed to be flexible:\n\n- **Minimal by default** — With no body, only changes `isAnonymous` from `true` to `false`\n- **Optional email/name** — Can update email and name in the same request\n- **Duplicate protection** — Rejects emails already associated with another account\n- **Verification-aware** — Returns `verificationRequired` so the frontend knows whether to trigger OTP\n- **Session preserved** — Same session ID, no re-authentication needed\n- **Idempotent** — Calling on an already-upgraded user returns success immediately\n\n## Typical Flows\n\n### Passkey Signup (no email)\n\n1. User creates anonymous session\n2. User registers a passkey\n3. App calls `POST /auth/v1/upgrade-anonymous` with no body\n4. User is now a full user with passkey auth — no email on file\n\n### Passkey Signup (with email)\n\n1. User creates anonymous session\n2. User registers a passkey\n3. User optionally enters email/name on the email collection step\n4. App calls `POST /auth/v1/upgrade-anonymous` with `{ email, name }`\n5. If `verificationRequired`: app sends OTP, user verifies, then redirects to dashboard\n6. Otherwise: user goes straight to dashboard\n\n### Email Signup\n\n1. User starts as anonymous (created via Better Auth's `anonymous` plugin)\n2. User provides email/password through your UI (via combo auth or signup)\n3. Your app calls `POST /auth/v1/upgrade-anonymous`\n4. User is now a full user with the same ID and all their data intact\n\n### Client Plugin\n\n```typescript\n\nconst authClient = createAuthClient({\n plugins: [upgradeAnonymousClient()],\n});\n\n// Upgrade the current anonymous user\nawait authClient.upgradeAnonymous();\n```\n\n## See Also\n\n- [Combo Auth Plugin](/plugins-tools/better-auth-plugins/combo-auth) — Combined magic link + OTP for collecting credentials\n- [Auth Configuration](/compiler/config/providers) — Configuring auth plugins"
295
295
  },
296
296
  "plugins-tools/claude-code-skill": {
297
- "title": "Claude Code Integration",
298
- "content": "Build Quickback apps faster with Claude Code. The Quickback skill gives Claude deep knowledge of security layers, patterns, and best practices—so you can describe what you want and let Claude write the configuration.\n\n## Installation\n\n### Option 1: Install globally (Recommended)\n\nInstall the skill once and use it across all your projects:\n\n```bash\nnpm install -g @kardoe/quickback-skill\n```\n\nThis installs to `~/.claude/skills/quickback/` and is automatically available in Claude Code.\n\n### Option 2: New Quickback project\n\nCreate a new Quickback project—the skill is included automatically:\n\n```bash\nnpx @kardoe/quickback create cloudflare my-app\ncd my-app\n```\n\n### Option 3: Manual installation\n\nDownload the skill directly:\n\n```bash\nmkdir -p ~/.claude/skills/quickback\ncurl -o ~/.claude/skills/quickback/SKILL.md \\\n https://raw.githubusercontent.com/kardoe/quickback/main/.claude/skills/quickback/SKILL.md\n```\n\n## What You Get\n\nWhen you install the Quickback skill, you get:\n\n### Quickback Skill\n\nClaude understands Quickback concepts and can answer questions about:\n\n- **Security layers** - Firewall, Access, Guards, Masking, Actions\n- **Common patterns** - Multi-tenant, owner-scoped, hierarchical access\n- **Best practices** - Field protection, role-based access, PII handling\n- **Database dialects** - SQLite/D1, PostgreSQL, MySQL syntax\n\n### Quickback Specialist Agent\n\nClaude also gets a specialized agent that activates automatically when you're:\n\n- Creating new resources with schemas and security configurations\n- Configuring security layers (Firewall, Access, Guards, Masking)\n- Defining actions for business logic\n- Debugging configuration issues\n\nThe agent generates complete, working code in your `quickback/features/` directory.\n\n## Usage\n\n### Let Claude help automatically\n\nJust describe what you need. Claude will use Quickback knowledge when relevant:\n\n```\n\"Create a tasks resource where users can only see their own tasks,\nbut admins can see all tasks in the organization\"\n```\n\n### Invoke directly\n\nUse `/quickback` to explicitly activate the skill:\n\n```\n/quickback How do I configure soft delete?\n```\n\n## Example Conversations\n\n### Building a New Resource\n\n**You:** \"I need a resource for invoices. Users should only see invoices from their organization. The status field should only be changeable through approve/reject actions. Mask the customer email for non-admins.\"\n\n**Claude:** Creates complete configuration with:\n- Firewall scoped to organization\n- Protected status field with approve/reject actions\n- Email masking with admin bypass\n- Appropriate guards for createable/updatable fields\n\n### Understanding Existing Code\n\n**You:** \"Explain what this firewall configuration does\"\n\n**Claude:** Breaks down the WHERE clauses, explains the security implications, and identifies potential issues.\n\n### Debugging Configuration\n\n**You:** \"My users can see records from other organizations. What's wrong?\"\n\n**Claude:** Analyzes your firewall setup, checks for `exception: true` or missing organization scope, and suggests fixes.\n\n## What Claude Knows\n\n### Security Layers\n\n| Layer | What Claude Helps With |\n|-------|------------------------|\n| **Firewall** | Data isolation patterns, WHERE clause generation, soft delete |\n| **Access** | Role-based permissions, record-level conditions, combining rules |\n| **Guards** | Field protection, createable vs updatable, immutable fields |\n| **Masking** | PII redaction, role-based visibility, mask types |\n| **Actions** | Custom endpoints, protected field updates, input validation |\n\n### Common Patterns\n\nClaude recognizes and can implement these patterns:\n\n- **Multi-tenant SaaS** - Organization-scoped with role hierarchy\n- **Personal data apps** - Owner-scoped resources\n- **Hierarchical access** - Admins see all, users see own\n- **Public resources** - Reference data, system tables\n- **Workflow resources** - Status fields with action-based transitions\n\n### Database Support\n\nClaude generates correct syntax for your database:\n\n```typescript\n// SQLite / Cloudflare D1\ncreatedAt: integer('created_at', { mode: 'timestamp' })\n\n// PostgreSQL / Supabase\ncreatedAt: timestamp('created_at').defaultNow()\n\n// MySQL\ncreatedAt: timestamp('created_at')\n```\n\n## Tips for Best Results\n\n**Be specific about security requirements:**\n\n```\n// Good\n\"Users can only see their own tasks. Admins can see all tasks\nin the organization. The priority field can only be set by admins.\"\n\n// Less helpful\n\"Create a tasks resource\"\n```\n\n**Describe your user types:**\n\n```\n\"We have three roles: admin (full access), manager (can approve),\nand member (can only edit their own records)\"\n```\n\n**Mention sensitive fields:**\n\n```\n\"The ssn field should be masked for everyone except HR admins\"\n```\n\n## Common Tasks\n\n### Create a complete resource\n\n```\n\"Create an employees resource for a multi-tenant HR app. Include\nfields for name, email, department, salary. Mask salary for\nnon-admins. Only HR can create/delete employees.\"\n```\n\n### Add an action to existing resource\n\n```\n\"Add an 'approve' action to the expenses resource that sets\nstatus to 'approved' and records the approver\"\n```\n\n### Configure access control\n\n```\n\"Update the projects resource so managers can edit any project\nin their organization, but members can only edit projects they created\"\n```\n\n### Set up masking\n\n```\n\"Add masking to the customers resource: email partially masked,\nphone last 4 digits only, SSN fully redacted except for finance role\"\n```\n\n## Troubleshooting\n\n### Skill not found\n\nVerify the skill is installed:\n\n```bash\nls ~/.claude/skills/quickback/SKILL.md\n```\n\nIf missing, reinstall:\n\n```bash\nnpm install -g @kardoe/quickback-skill\n```\n\n### Claude doesn't understand Quickback\n\nMake sure the skill file exists and Claude Code is restarted. You can also invoke it directly with `/quickback` to force it to load.\n\n### Generated code has errors\n\nRun `quickback compile` to validate. Share the error messages with Claude for fixes.\n\n## Updating the Skill\n\nTo get the latest version:\n\n```bash\nnpm update -g @kardoe/quickback-skill\n```\n\n## Resources\n\n- [Getting Started](/compiler/getting-started) - Get your first Quickback project running\n- [Definitions Overview](/compiler/definitions) - Understand the security layer model\n- [npm package](https://www.npmjs.com/package/@kardoe/quickback-skill) - Skill package\n\n## Feedback\n\nFound an issue with the Claude Code integration?\n\n- [GitHub Issues](https://github.com/kardoe/quickback/issues)\n- [Quickback Documentation](https://docs.quickback.dev)"
297
+ "title": "AI Developer Tools",
298
+ "content": "Build Quickback apps faster with AI. The Quickback CLI ships with an MCP server, Claude Code skill, and Cursor IDE rules so any AI tool can help you write security configurations, define features, and understand your project.\n\n## Quick Install\n\n```bash\n# Install the CLI (includes all AI tools)\nnpm install -g @kardoe/quickback\n\n# Claude Code skill\nquickback claude install\n\n# Cursor IDE rules\nquickback cursor install\n\n# MCP server (configure in your AI tool, see below)\nquickback mcp\n```\n\n## MCP Server\n\nThe MCP server makes Quickback documentation and project context available to **any** MCP-compatible AI tool.\n\n### Configuration\n\nAdd to your AI tool's MCP config:\n\n```json\n{\n \"mcpServers\": {\n \"quickback\": {\n \"command\": \"npx\",\n \"args\": [\"@kardoe/quickback\", \"mcp\"]\n }\n }\n}\n```\n\n| Tool | Config File |\n|------|-------------|\n| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) |\n| Cursor | `.cursor/mcp.json` in your project |\n| VS Code | `.vscode/mcp.json` in your project |\n\n### Available Tools\n\n| Tool | Description |\n|------|-------------|\n| `list_topics` | List all 100+ documentation topics |\n| `get_doc` | Get a specific doc (supports fuzzy matching e.g., \"firewall\") |\n| `search_docs` | Search documentation by keyword |\n| `read_config` | Read your project's `quickback.config.ts` |\n| `list_features` | List features and their table files |\n| `read_feature` | Read a specific feature's source code |\n| `read_schema_registry` | Read the compiled `schema-registry.json` |\n\nAll documentation topics are also registered as MCP resources at `quickback://docs/{topic}`.\n\n## Claude Code Skill\n\n### Installation\n\nInstall via the CLI (recommended):\n\n```bash\nquickback claude install --global # All projects (~/.claude/)\nquickback claude install --local # This project only\n```\n\nOr install the standalone npm package:\n\n```bash\nnpm install -g @kardoe/quickback-skill\n```\n\nOr when creating a new project, the skill is included automatically:\n\n```bash\nnpx @kardoe/quickback create cloudflare my-app\n```\n\n## What You Get\n\nWhen you install the Quickback skill, you get:\n\n### Quickback Skill\n\nClaude understands Quickback concepts and can answer questions about:\n\n- **Security layers** - Firewall, Access, Guards, Masking, Actions\n- **Common patterns** - Multi-tenant, owner-scoped, hierarchical access\n- **Best practices** - Field protection, role-based access, PII handling\n- **Database dialects** - SQLite/D1, PostgreSQL, MySQL syntax\n\n### Quickback Specialist Agent\n\nClaude also gets a specialized agent that activates automatically when you're:\n\n- Creating new resources with schemas and security configurations\n- Configuring security layers (Firewall, Access, Guards, Masking)\n- Defining actions for business logic\n- Debugging configuration issues\n\nThe agent generates complete, working code in your `quickback/features/` directory.\n\n## Usage\n\n### Let Claude help automatically\n\nJust describe what you need. Claude will use Quickback knowledge when relevant:\n\n```\n\"Create a tasks resource where users can only see their own tasks,\nbut admins can see all tasks in the organization\"\n```\n\n### Invoke directly\n\nUse `/quickback` to explicitly activate the skill:\n\n```\n/quickback How do I configure soft delete?\n```\n\n## Example Conversations\n\n### Building a New Resource\n\n**You:** \"I need a resource for invoices. Users should only see invoices from their organization. The status field should only be changeable through approve/reject actions. Mask the customer email for non-admins.\"\n\n**Claude:** Creates complete configuration with:\n- Firewall scoped to organization\n- Protected status field with approve/reject actions\n- Email masking with admin bypass\n- Appropriate guards for createable/updatable fields\n\n### Understanding Existing Code\n\n**You:** \"Explain what this firewall configuration does\"\n\n**Claude:** Breaks down the WHERE clauses, explains the security implications, and identifies potential issues.\n\n### Debugging Configuration\n\n**You:** \"My users can see records from other organizations. What's wrong?\"\n\n**Claude:** Analyzes your firewall setup, checks for `exception: true` or missing organization scope, and suggests fixes.\n\n## What Claude Knows\n\n### Security Layers\n\n| Layer | What Claude Helps With |\n|-------|------------------------|\n| **Firewall** | Data isolation patterns, WHERE clause generation, soft delete |\n| **Access** | Role-based permissions, record-level conditions, combining rules |\n| **Guards** | Field protection, createable vs updatable, immutable fields |\n| **Masking** | PII redaction, role-based visibility, mask types |\n| **Actions** | Custom endpoints, protected field updates, input validation |\n\n### Common Patterns\n\nClaude recognizes and can implement these patterns:\n\n- **Multi-tenant SaaS** - Organization-scoped with role hierarchy\n- **Personal data apps** - Owner-scoped resources\n- **Hierarchical access** - Admins see all, users see own\n- **Public resources** - Reference data, system tables\n- **Workflow resources** - Status fields with action-based transitions\n\n### Database Support\n\nClaude generates correct syntax for your database:\n\n```typescript\n// SQLite / Cloudflare D1\ncreatedAt: integer('created_at', { mode: 'timestamp' })\n\n// PostgreSQL / Supabase\ncreatedAt: timestamp('created_at').defaultNow()\n\n// MySQL\ncreatedAt: timestamp('created_at')\n```\n\n## Tips for Best Results\n\n**Be specific about security requirements:**\n\n```\n// Good\n\"Users can only see their own tasks. Admins can see all tasks\nin the organization. The priority field can only be set by admins.\"\n\n// Less helpful\n\"Create a tasks resource\"\n```\n\n**Describe your user types:**\n\n```\n\"We have three roles: admin (full access), manager (can approve),\nand member (can only edit their own records)\"\n```\n\n**Mention sensitive fields:**\n\n```\n\"The ssn field should be masked for everyone except HR admins\"\n```\n\n## Common Tasks\n\n### Create a complete resource\n\n```\n\"Create an employees resource for a multi-tenant HR app. Include\nfields for name, email, department, salary. Mask salary for\nnon-admins. Only HR can create/delete employees.\"\n```\n\n### Add an action to existing resource\n\n```\n\"Add an 'approve' action to the expenses resource that sets\nstatus to 'approved' and records the approver\"\n```\n\n### Configure access control\n\n```\n\"Update the projects resource so managers can edit any project\nin their organization, but members can only edit projects they created\"\n```\n\n### Set up masking\n\n```\n\"Add masking to the customers resource: email partially masked,\nphone last 4 digits only, SSN fully redacted except for finance role\"\n```\n\n## Troubleshooting\n\n### Skill not found\n\nVerify the skill is installed:\n\n```bash\nls ~/.claude/skills/quickback/SKILL.md\n```\n\nIf missing, reinstall:\n\n```bash\nnpm install -g @kardoe/quickback-skill\n```\n\n### Claude doesn't understand Quickback\n\nMake sure the skill file exists and Claude Code is restarted. You can also invoke it directly with `/quickback` to force it to load.\n\n### Generated code has errors\n\nRun `quickback compile` to validate. Share the error messages with Claude for fixes.\n\n## Cursor IDE Rules\n\nCursor rules provide Quickback context when editing `quickback/**/*.ts` files.\n\n### Installation\n\n```bash\nquickback cursor install\n```\n\nThis installs `quickback.mdc` to `.cursor/rules/` in your project. Commit this file so your team gets the rules automatically.\n\n### Management\n\n```bash\nquickback cursor status # Check installation\nquickback cursor update # Update to latest version\nquickback cursor remove # Remove the rules\n```\n\n## Updating\n\nTo update all AI tools to the latest version:\n\n```bash\nnpm update -g @kardoe/quickback\nquickback claude update # Update Claude skill\nquickback cursor update # Update Cursor rules\n```\n\nThe MCP server always uses the latest installed version automatically.\n\n## Resources\n\n- [Getting Started with AI Tools](/compiler/getting-started/claude-code) - Setup guide\n- [Getting Started](/compiler/getting-started) - Get your first Quickback project running\n- [Definitions Overview](/compiler/definitions) - Understand the security layer model\n- [npm package](https://www.npmjs.com/package/@kardoe/quickback) - CLI package\n\n## Feedback\n\nFound an issue with the AI tools integration?\n\n- [GitHub Issues](https://github.com/kardoe/quickback/issues)\n- [Quickback Documentation](https://docs.quickback.dev)"
299
299
  },
300
300
  "plugins-tools": {
301
301
  "title": "Plugins & Tools",
@@ -315,7 +315,7 @@ export const DOCS = {
315
315
  },
316
316
  "stack/auth/jwt-optimization": {
317
317
  "title": "JWT Optimization",
318
- "content": "Quickback automatically optimizes authenticated API requests using JWT tokens. After the first request (which uses session cookies), subsequent requests skip the database entirely by sending a signed JWT that encodes the full auth context.\n\n## How It Works\n\n### Request Flow\n\n```\nFirst request (no JWT cached):\n Browser → Cookie auth → 2 DB queries → Response + set-auth-token header\n Browser caches JWT in localStorage\n\nSubsequent requests (JWT cached):\n Browser → Bearer JWT → Signature check only → Response (0 DB queries)\n```\n\n### What's in the JWT\n\nThe JWT encodes everything needed for auth middleware:\n\n| Claim | Description |\n|-------|-------------|\n| `sub` | User ID |\n| `orgId` | Active organization ID |\n| `role` | Organization membership role (`owner`, `admin`, `member`) |\n| `userRole` | Global user role (when admin panel is enabled) |\n| `email` | User email |\n| `name` | User display name |\n| `iat` | Issued-at timestamp |\n| `exp` | Expiry (15 minutes from issue) |\n\n### Signing\n\nJWTs are signed with HMAC-SHA256 using your `BETTER_AUTH_SECRET`. No additional configuration is needed — this uses the same secret you already set for Better Auth.\n\n---\n\n## Token Lifecycle\n\n### Automatic Minting\n\nWhen a request arrives without a JWT (or with an expired one), the auth middleware:\n\n1. Authenticates via session cookie (existing Better Auth flow)\n2. Signs a JWT with the full auth context\n3. Returns it in the `set-auth-token` response header\n\nThe Quickback API client (`quickback-client.ts`) automatically captures this header and stores the JWT in `localStorage`.\n\n### Re-minting on Org Switch\n\nWhen a user switches organizations, the JWT must be re-minted because `orgId` and `role` change. The auth UI handles this automatically:\n\n```typescript\n// This happens automatically in the org switcher\nawait authClient.organization.setActive({ organizationSlug: slug });\n\n// Fetch fresh JWT with new org context\nconst res = await fetch('/api/v1/token', {\n method: 'POST',\n credentials: 'include',\n});\nconst { token } = await res.json();\nlocalStorage.setItem('bearer_token', token);\n```\n\n### Invalidation on Role Changes\n\nWhen an admin changes a user's role or removes them from an organization, the server broadcasts an `auth:token-invalidated` event via WebSocket. The client automatically clears the cached JWT, forcing the next request to fall back to session auth and mint a fresh token.\n\n```\nAdmin changes user role\n → databaseHooks fires on member update\n → Broadcasts auth:token-invalidated via realtime\n → Client clears localStorage JWT\n → Next request uses session cookie → gets fresh JWT\n```\n\n### Expiry\n\nJWTs expire after 15 minutes. This is a safety net — in practice, JWTs are re-minted well before expiry through the `set-auth-token` response header on session-fallback requests.\n\nWhen a JWT expires or is invalid:\n1. The middleware silently falls back to session cookie auth\n2. A fresh JWT is minted and returned in the response header\n3. The client stores the new JWT for subsequent requests\n\n---\n\n## Token Endpoint\n\n`POST /api/v1/token` mints a fresh JWT from the current session. This endpoint goes through normal auth middleware, so the user must have a valid session cookie.\n\n**Request:**\n```bash\ncurl -X POST https://your-api.example.com/api/v1/token \\\n -H \"Content-Type: application/json\" \\\n --cookie \"better-auth.session_token=...\"\n```\n\n**Response:**\n```json\n{\n \"token\": \"eyJhbGciOiJIUzI1NiJ9...\"\n}\n```\n\nUse this endpoint after operations that change auth context (like switching organizations) to get a JWT that reflects the new state.\n\n---\n\n## Client Integration\n\n### Quickback API Client\n\nThe `quickback-client.ts` API client handles JWT auth automatically:\n\n- **Sends** the cached JWT as `Authorization: Bearer <token>` on every request\n- **Captures** refreshed JWTs from `set-auth-token` response headers\n- **Clears** the JWT on `401` responses (falls back to session cookie)\n\nNo additional client configuration is needed.\n\n### Custom API Calls\n\nIf you make direct `fetch()` calls to your API (outside the Quickback client), include the JWT:\n\n```typescript\nconst jwt = localStorage.getItem('bearer_token');\n\nconst res = await fetch('https://your-api.example.com/api/v1/things', {\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n },\n credentials: 'include', // Fallback to session cookie if no JWT\n});\n\n// Capture refreshed token\nconst newToken = res.headers.get('set-auth-token');\nif (newToken) {\n localStorage.setItem('bearer_token', newToken);\n}\n```\n\n### Sign Out\n\nClear the JWT on sign-out to prevent stale tokens:\n\n```typescript\nlocalStorage.removeItem('bearer_token');\nawait authClient.signOut();\n```\n\n---\n\n## Security\n\n### Threat Model\n\n| Threat | Mitigation |\n|--------|------------|\n| Token theft (XSS) | 15-minute expiry limits exposure window. `httpOnly` cookies protect the session. |\n| Token replay | Short expiry + `set-auth-token` rotation on session fallback |\n| Stale permissions | WebSocket `auth:token-invalidated` broadcast on role changes |\n| Token forgery | HMAC-SHA256 signature verified with `BETTER_AUTH_SECRET` |\n| Timing attacks | `crypto.subtle.verify()` provides constant-time comparison |\n\n### Key Points\n\n- JWTs are a **performance optimization**, not a replacement for session auth\n- Session cookies remain the source of truth — JWTs are derived from them\n- If a JWT is lost, stolen, or cleared, the system gracefully falls back to cookie auth\n- No additional secrets or configuration needed — uses your existing `BETTER_AUTH_SECRET`\n- The JWT contains no sensitive data beyond what's already in the auth context\n\n---\n\n## Files Worker\n\nThe files worker (for Cloudflare R2 file uploads) also supports JWT authentication. When a request includes a JWT `Authorization` header, the worker verifies it directly instead of calling back to the auth API — eliminating a network round-trip for file operations.\n\n---\n\n## Related\n\n- [Auth Overview](/docs/stack/auth) — Setup and configuration\n- [Auth Security](/docs/stack/auth/security) — Cookie security, rate limiting, CORS\n- [API Keys](/docs/stack/auth/api-keys) — Programmatic API access"
318
+ "content": "Quickback automatically optimizes authenticated API requests using JWT tokens. After the first request (which uses session cookies), subsequent requests skip the database entirely by sending a signed JWT that encodes the full auth context.\n\n## How It Works\n\n### Request Flow\n\n```\nFirst request (no JWT cached):\n Browser → Cookie auth → 2 DB queries → Response + set-auth-token header\n Browser caches JWT in localStorage\n\nSubsequent requests (JWT cached):\n Browser → Bearer JWT → Signature check only → Response (0 DB queries)\n```\n\n### What's in the JWT\n\nThe JWT encodes everything needed for auth middleware:\n\n| Claim | Description |\n|-------|-------------|\n| `sub` | User ID |\n| `orgId` | Active organization ID |\n| `role` | Organization membership role (`owner`, `admin`, `member`) |\n| `userRole` | Global user role (when admin panel is enabled) |\n| `email` | User email |\n| `name` | User display name |\n| `iat` | Issued-at timestamp |\n| `exp` | Expiry (15 minutes from issue) |\n\n### Signing\n\nJWTs are signed with HMAC-SHA256 using your `BETTER_AUTH_SECRET`. No additional configuration is needed — this uses the same secret you already set for Better Auth.\n\n---\n\n## Token Lifecycle\n\n### Automatic Minting\n\nWhen a request arrives without a JWT (or with an expired one), the auth middleware:\n\n1. Authenticates via session cookie (existing Better Auth flow)\n2. Signs a JWT with the full auth context\n3. Returns it in the `set-auth-token` response header\n\nThe Quickback API client (`quickback-client.ts`) automatically captures this header and stores the JWT in `localStorage`.\n\n### Re-minting on Org Switch\n\nWhen a user switches organizations, the JWT must be re-minted because `orgId` and `role` change. The auth UI handles this automatically:\n\n```typescript\n// This happens automatically in the org switcher\nawait authClient.organization.setActive({ organizationSlug: slug });\n\n// Fetch fresh JWT with new org context\nconst res = await fetch('/api/v1/token', {\n method: 'POST',\n credentials: 'include',\n});\nconst { token } = await res.json();\nlocalStorage.setItem('bearer_token', token);\n```\n\n### Invalidation on Role Changes\n\nWhen an admin changes a user's role or removes them from an organization, the server broadcasts an `auth:token-invalidated` event via WebSocket. The client automatically clears the cached JWT, forcing the next request to fall back to session auth and mint a fresh token.\n\n```\nAdmin changes user role\n → databaseHooks fires on member update\n → Broadcasts auth:token-invalidated via realtime\n → Client clears localStorage JWT\n → Next request uses session cookie → gets fresh JWT\n```\n\n### Expiry\n\nJWTs expire after 15 minutes. This is a safety net — in practice, JWTs are re-minted well before expiry through the `set-auth-token` response header on session-fallback requests.\n\nWhen a JWT expires or is invalid:\n1. The middleware silently falls back to session cookie auth\n2. A fresh JWT is minted and returned in the response header\n3. The client stores the new JWT for subsequent requests\n\n---\n\n## Token Endpoint\n\n`POST /api/v1/token` mints a fresh JWT from the current session. This endpoint goes through normal auth middleware, so the user must have a valid session cookie.\n\n**Request:**\n```bash\ncurl -X POST https://your-api.example.com/api/v1/token \\\n -H \"Content-Type: application/json\" \\\n --cookie \"better-auth.session_token=...\"\n```\n\n**Response:**\n```json\n{\n \"token\": \"eyJhbGciOiJIUzI1NiJ9...\"\n}\n```\n\nUse this endpoint after operations that change auth context (like switching organizations) to get a JWT that reflects the new state.\n\n---\n\n## Client Integration\n\n### Quickback API Client\n\nThe `quickback-client.ts` API client handles JWT auth automatically:\n\n- **Sends** the cached JWT as `Authorization: Bearer <token>` on every request\n- **Captures** refreshed JWTs from `set-auth-token` response headers\n- **Clears** the JWT on `401` responses (falls back to session cookie)\n\nNo additional client configuration is needed.\n\n### Custom API Calls\n\nIf you make direct `fetch()` calls to your API (outside the Quickback client), include the JWT:\n\n```typescript\nconst jwt = localStorage.getItem('bearer_token');\n\nconst res = await fetch('https://your-api.example.com/api/v1/things', {\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n },\n credentials: 'include', // Fallback to session cookie if no JWT\n});\n\n// Capture refreshed token\nconst newToken = res.headers.get('set-auth-token');\nif (newToken) {\n localStorage.setItem('bearer_token', newToken);\n}\n```\n\n### Sign Out\n\nClear the JWT on sign-out to prevent stale tokens:\n\n```typescript\nlocalStorage.removeItem('bearer_token');\nawait authClient.signOut();\n```\n\n---\n\n## Security\n\n### Threat Model\n\n| Threat | Mitigation |\n|--------|------------|\n| Token theft (XSS) | 15-minute expiry limits exposure window. `httpOnly` cookies protect the session. |\n| Token replay | Short expiry + `set-auth-token` rotation on session fallback |\n| Stale permissions | WebSocket `auth:token-invalidated` broadcast on role changes |\n| Token forgery | HMAC-SHA256 signature verified with `BETTER_AUTH_SECRET` |\n| Timing attacks | `crypto.subtle.verify()` provides constant-time comparison |\n\n### Key Points\n\n- JWTs are a **performance optimization**, not a replacement for session auth\n- Session cookies remain the source of truth — JWTs are derived from them\n- If a JWT is lost, stolen, or cleared, the system gracefully falls back to cookie auth\n- No additional secrets or configuration needed — uses your existing `BETTER_AUTH_SECRET`\n- The JWT contains no sensitive data beyond what's already in the auth context\n\n---\n\n## Files Worker\n\nThe files worker (for Cloudflare R2 file uploads) also supports JWT authentication. When a request includes a JWT `Authorization` header, the worker verifies it directly instead of calling back to the auth API — eliminating a network round-trip for file operations.\n\n---\n\n## Related\n\n- [Auth Overview](/stack/auth) — Setup and configuration\n- [Auth Security](/stack/auth/security) — Cookie security, rate limiting, CORS\n- [API Keys](/stack/auth/api-keys) — Programmatic API access"
319
319
  },
320
320
  "stack/auth/plugins": {
321
321
  "title": "Auth Plugins",
@@ -331,7 +331,7 @@ export const DOCS = {
331
331
  },
332
332
  "stack/database/d1": {
333
333
  "title": "D1 Database",
334
- "content": "Cloudflare D1 is SQLite at the edge. Quickback uses D1 as the primary database for Cloudflare deployments, with a multi-database pattern for separation of concerns.\n\n## What is D1?\n\nD1 is Cloudflare's serverless SQL database built on SQLite:\n\n- **Edge-native** - Runs in Cloudflare's global network\n- **SQLite compatible** - Use familiar SQL syntax\n- **Zero configuration** - No connection strings or pooling\n- **Automatic replication** - Read replicas at every edge location\n\n## Multi-Database Pattern\n\nQuickback generates separate D1 databases for different concerns:\n\n| Database | Binding | Purpose |\n|----------|---------|---------|\n| `AUTH_DB` | `AUTH_DB` | Better Auth tables (user, session, account) |\n| `DB` | `DB` | Your application data |\n| `FILES_DB` | `FILES_DB` | File metadata for R2 uploads |\n| `WEBHOOKS_DB` | `WEBHOOKS_DB` | Webhook delivery tracking |\n\nThis separation provides:\n- **Independent scaling** - Auth traffic doesn't affect app queries\n- **Isolation** - Auth schema changes don't touch your data\n- **Clarity** - Clear ownership of each database\n\n## Drizzle ORM Integration\n\nQuickback uses [Drizzle ORM](https://orm.drizzle.team/) for type-safe database access:\n\n```ts\n// schema/tables.ts - Your schema definition\n\nexport const posts = sqliteTable(\"posts\", {\n id: text(\"id\").primaryKey(),\n title: text(\"title\").notNull(),\n content: text(\"content\"),\n authorId: text(\"author_id\").notNull(),\n createdAt: integer(\"created_at\", { mode: \"timestamp\" }),\n});\n```\n\nThe compiler generates Drizzle queries based on your security rules:\n\n```ts\n// Generated query with firewall applied\nconst result = await db\n .select()\n .from(posts)\n .where(eq(posts.authorId, userId)); // Firewall injects ownership\n```\n\n## Migrations\n\nQuickback generates migrations automatically at compile time based on your schema changes:\n\n```bash\n# Compile your project (generates migrations)\nquickback compile\n\n# Apply migrations locally\nwrangler d1 migrations apply DB --local\n\n# Apply to production D1\nwrangler d1 migrations apply DB --remote\n```\n\nMigration files are generated in `drizzle/migrations/` and version-controlled with your code. You never need to manually generate migrations—just define your schema and compile.\n\n## wrangler.toml Bindings\n\nConfigure D1 bindings in `wrangler.toml`:\n\n```toml\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"my-app-db\"\ndatabase_id = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n\n[[d1_databases]]\nbinding = \"AUTH_DB\"\ndatabase_name = \"my-app-auth\"\ndatabase_id = \"yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy\"\n\n[[d1_databases]]\nbinding = \"FILES_DB\"\ndatabase_name = \"my-app-files\"\ndatabase_id = \"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz\"\n```\n\nCreate databases via Wrangler:\n\n```bash\nwrangler d1 create my-app-db\nwrangler d1 create my-app-auth\nwrangler d1 create my-app-files\n```\n\n## Accessing Data via API\n\nAll data access in Quickback goes through the generated API endpoints—never direct database queries. This ensures security rules (firewall, access, guards, masking) are always enforced.\n\n### CRUD Operations\n\n```bash\n# List posts (firewall automatically filters by ownership)\nGET /api/v1/posts\n\n# Get a single post\nGET /api/v1/posts/:id\n\n# Create a post (guards validate allowed fields)\nPOST /api/v1/posts\n{ \"title\": \"Hello World\", \"content\": \"...\" }\n\n# Update a post (guards validate updatable fields)\nPATCH /api/v1/posts/:id\n{ \"title\": \"Updated Title\" }\n\n# Delete a post\nDELETE /api/v1/posts/:id\n```\n\n### Filtering and Pagination\n\n```bash\n# Filter by field\nGET /api/v1/posts?status=published\n\n# Pagination\nGET /api/v1/posts?limit=10&offset=20\n\n# Sort\nGET /api/v1/posts?sort=createdAt&order=desc\n```\n\n### Why No Direct Database Access?\n\nDirect database queries bypass Quickback's security layers:\n- **Firewall** - Data isolation by user/org/team\n- **Access** - Role-based permissions\n- **Guards** - Field-level create/update restrictions\n- **Masking** - Sensitive data redaction\n\nAlways use the API endpoints. For custom business logic, use [Actions](/compiler/definitions/actions).\n\n## Local Development\n\nD1 works locally with Wrangler:\n\n```bash\n# Start local dev server with D1\nwrangler dev\n\n# D1 data persists in .wrangler/state/\n```\n\nLocal D1 uses SQLite files in `.wrangler/state/v3/d1/`, which you can inspect with any SQLite client.\n\n## Security Architecture\n\nFor all Quickback-generated code, D1's application-layer security provides equivalent protection to Supabase RLS. The key difference is where enforcement happens — and this matters if you write custom routes.\n\n### How Security Works\n\n| Component | Enforcement | Notes |\n|-----------|-------------|-------|\n| CRUD endpoints | ✅ Firewall auto-applied | All generated routes enforce security |\n| Actions | ✅ Firewall auto-applied | Both standalone and record-based |\n| Manual routes | ⚠️ Must apply firewall | Use `withFirewall` helper |\n\n### Why D1 is Secure\n\n1. **No external database access** - D1 can only be queried through your Worker. There's no connection string or external endpoint.\n2. **Generated code enforces rules** - All CRUD and Action endpoints automatically apply firewall, access, guards, and masking.\n3. **Single entry point** - Every request flows through your API where security is enforced.\n\nUnlike Supabase where PostgreSQL RLS provides database-level enforcement (protecting data even if application code has bugs), D1's security comes from architecture: the database is inaccessible except through your Worker, and all generated routes apply the four security pillars. The trade-off is that **custom routes you write outside Quickback must manually apply security** — there is no database-level safety net.\n\n### Comparison with Supabase RLS\n\n| Scenario | Supabase | D1 |\n|----------|----------|-----|\n| CRUD endpoints | ✅ Secure (RLS + App) | ✅ Secure (App) |\n| Actions | ✅ Secure (RLS + App) | ✅ Secure (App) |\n| Manual routes | ✅ RLS still protects | ⚠️ Must apply firewall |\n| External DB access | ⚠️ Possible with credentials | ✅ Not possible |\n| Dashboard queries | Via Supabase Studio | ⚠️ Admin only (audit logged) |\n\n### Writing Manual Routes\n\nIf you write custom routes outside of Quickback compilation (e.g., custom reports, integrations), use the generated `withFirewall` helper to ensure security:\n\n```ts\n\napp.get('/reports/monthly', async (c) => {\n return withFirewall(c, async (ctx, firewall) => {\n const results = await db.select()\n .from(invoices)\n .where(firewall);\n return c.json(results);\n });\n});\n```\n\nThe `withFirewall` helper:\n- Validates authentication\n- Builds the correct WHERE conditions for the current user/org\n- Returns 401 if not authenticated\n\n### Best Practices\n\n1. **Use generated endpoints** - Prefer CRUD and Actions over manual routes\n2. **Always apply firewall** - When writing manual routes, always use `withFirewall`\n3. **Avoid raw SQL** - Raw SQL bypasses application security; use Drizzle ORM\n4. **Review custom code** - Manual routes should be code-reviewed for security\n\n## Limitations\n\nD1 is built on SQLite, which means:\n\n- **No stored procedures** - Business logic lives in your Worker\n- **Single-writer** - One write connection at a time (reads scale horizontally)\n- **Size limits** - 10GB per database (free tier: 500MB)\n- **No PostgreSQL extensions** - Use Supabase if you need PostGIS, etc.\n\nFor most applications, these limits are non-issues. D1's edge distribution and zero-config setup outweigh the constraints."
334
+ "content": "Cloudflare D1 is SQLite at the edge. Quickback uses D1 as the primary database for Cloudflare deployments, with a multi-database pattern for separation of concerns.\n\n## What is D1?\n\nD1 is Cloudflare's serverless SQL database built on SQLite:\n\n- **Edge-native** - Runs in Cloudflare's global network\n- **SQLite compatible** - Use familiar SQL syntax\n- **Zero configuration** - No connection strings or pooling\n- **Automatic replication** - Read replicas at every edge location\n\n## Multi-Database Pattern\n\nQuickback generates separate D1 databases for different concerns:\n\n| Database | Binding | Purpose |\n|----------|---------|---------|\n| `AUTH_DB` | `AUTH_DB` | Better Auth tables (user, session, account) |\n| `DB` | `DB` | Your application data |\n| `FILES_DB` | `FILES_DB` | File metadata for R2 uploads |\n| `WEBHOOKS_DB` | `WEBHOOKS_DB` | Webhook delivery tracking |\n\nThis separation provides:\n- **Independent scaling** - Auth traffic doesn't affect app queries\n- **Isolation** - Auth schema changes don't touch your data\n- **Clarity** - Clear ownership of each database\n\n## Drizzle ORM Integration\n\nQuickback uses [Drizzle ORM](https://orm.drizzle.team/) for type-safe database access:\n\n```ts\n// schema/tables.ts - Your schema definition\n\nexport const posts = sqliteTable(\"posts\", {\n id: text(\"id\").primaryKey(),\n title: text(\"title\").notNull(),\n content: text(\"content\"),\n authorId: text(\"author_id\").notNull(),\n createdAt: integer(\"created_at\", { mode: \"timestamp\" }),\n});\n```\n\nThe compiler generates Drizzle queries based on your security rules:\n\n```ts\n// Generated query with firewall applied\nconst result = await db\n .select()\n .from(posts)\n .where(eq(posts.authorId, userId)); // Firewall injects ownership\n```\n\n## Migrations\n\nQuickback generates migrations automatically at compile time based on your schema changes:\n\n```bash\n# Compile your project (generates migrations)\nquickback compile\n\n# Apply migrations locally\nwrangler d1 migrations apply DB --local\n\n# Apply to production D1\nwrangler d1 migrations apply DB --remote\n```\n\nMigration files are generated in `quickback/drizzle/` and version-controlled with your code. You never need to manually generate migrations—just define your schema and compile.\n\n## wrangler.toml Bindings\n\nConfigure D1 bindings in `wrangler.toml`:\n\n```toml\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"my-app-db\"\ndatabase_id = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n\n[[d1_databases]]\nbinding = \"AUTH_DB\"\ndatabase_name = \"my-app-auth\"\ndatabase_id = \"yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy\"\n\n[[d1_databases]]\nbinding = \"FILES_DB\"\ndatabase_name = \"my-app-files\"\ndatabase_id = \"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz\"\n```\n\nCreate databases via Wrangler:\n\n```bash\nwrangler d1 create my-app-db\nwrangler d1 create my-app-auth\nwrangler d1 create my-app-files\n```\n\n## Accessing Data via API\n\nAll data access in Quickback goes through the generated API endpoints—never direct database queries. This ensures security rules (firewall, access, guards, masking) are always enforced.\n\n### CRUD Operations\n\n```bash\n# List posts (firewall automatically filters by ownership)\nGET /api/v1/posts\n\n# Get a single post\nGET /api/v1/posts/:id\n\n# Create a post (guards validate allowed fields)\nPOST /api/v1/posts\n{ \"title\": \"Hello World\", \"content\": \"...\" }\n\n# Update a post (guards validate updatable fields)\nPATCH /api/v1/posts/:id\n{ \"title\": \"Updated Title\" }\n\n# Delete a post\nDELETE /api/v1/posts/:id\n```\n\n### Filtering and Pagination\n\n```bash\n# Filter by field\nGET /api/v1/posts?status=published\n\n# Pagination\nGET /api/v1/posts?limit=10&offset=20\n\n# Sort\nGET /api/v1/posts?sort=createdAt&order=desc\n```\n\n### Why No Direct Database Access?\n\nDirect database queries bypass Quickback's security layers:\n- **Firewall** - Data isolation by user/org/team\n- **Access** - Role-based permissions\n- **Guards** - Field-level create/update restrictions\n- **Masking** - Sensitive data redaction\n\nAlways use the API endpoints. For custom business logic, use [Actions](/compiler/definitions/actions).\n\n## Local Development\n\nD1 works locally with Wrangler:\n\n```bash\n# Start local dev server with D1\nwrangler dev\n\n# D1 data persists in .wrangler/state/\n```\n\nLocal D1 uses SQLite files in `.wrangler/state/v3/d1/`, which you can inspect with any SQLite client.\n\n## Security Architecture\n\nFor all Quickback-generated code, D1's application-layer security provides equivalent protection to Supabase RLS. The key difference is where enforcement happens — and this matters if you write custom routes.\n\n### How Security Works\n\n| Component | Enforcement | Notes |\n|-----------|-------------|-------|\n| CRUD endpoints | ✅ Firewall auto-applied | All generated routes enforce security |\n| Actions | ✅ Firewall auto-applied | Both standalone and record-based |\n| Manual routes | ⚠️ Must apply firewall | Use `withFirewall` helper |\n\n### Why D1 is Secure\n\n1. **No external database access** - D1 can only be queried through your Worker. There's no connection string or external endpoint.\n2. **Generated code enforces rules** - All CRUD and Action endpoints automatically apply firewall, access, guards, and masking.\n3. **Single entry point** - Every request flows through your API where security is enforced.\n\nUnlike Supabase where PostgreSQL RLS provides database-level enforcement (protecting data even if application code has bugs), D1's security comes from architecture: the database is inaccessible except through your Worker, and all generated routes apply the four security pillars. The trade-off is that **custom routes you write outside Quickback must manually apply security** — there is no database-level safety net.\n\n### Comparison with Supabase RLS\n\n| Scenario | Supabase | D1 |\n|----------|----------|-----|\n| CRUD endpoints | ✅ Secure (RLS + App) | ✅ Secure (App) |\n| Actions | ✅ Secure (RLS + App) | ✅ Secure (App) |\n| Manual routes | ✅ RLS still protects | ⚠️ Must apply firewall |\n| External DB access | ⚠️ Possible with credentials | ✅ Not possible |\n| Dashboard queries | Via Supabase Studio | ⚠️ Admin only (audit logged) |\n\n### Writing Manual Routes\n\nIf you write custom routes outside of Quickback compilation (e.g., custom reports, integrations), use the generated `withFirewall` helper to ensure security:\n\n```ts\n\napp.get('/reports/monthly', async (c) => {\n return withFirewall(c, async (ctx, firewall) => {\n const results = await db.select()\n .from(invoices)\n .where(firewall);\n return c.json(results);\n });\n});\n```\n\nThe `withFirewall` helper:\n- Validates authentication\n- Builds the correct WHERE conditions for the current user/org\n- Returns 401 if not authenticated\n\n### Best Practices\n\n1. **Use generated endpoints** - Prefer CRUD and Actions over manual routes\n2. **Always apply firewall** - When writing manual routes, always use `withFirewall`\n3. **Avoid raw SQL** - Raw SQL bypasses application security; use Drizzle ORM\n4. **Review custom code** - Manual routes should be code-reviewed for security\n\n## Limitations\n\nD1 is built on SQLite, which means:\n\n- **No stored procedures** - Business logic lives in your Worker\n- **Single-writer** - One write connection at a time (reads scale horizontally)\n- **Size limits** - 10GB per database (free tier: 500MB)\n- **No PostgreSQL extensions** - Use Supabase if you need PostGIS, etc.\n\nFor most applications, these limits are non-issues. D1's edge distribution and zero-config setup outweigh the constraints."
335
335
  },
336
336
  "stack/database": {
337
337
  "title": "Database",