@stacksjs/server 0.70.78 → 0.70.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/server",
3
3
  "type": "module",
4
- "version": "0.70.78",
4
+ "version": "0.70.79",
5
5
  "description": "Local development and production-ready.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -52,11 +52,11 @@
52
52
  "prepublishOnly": "bun run build"
53
53
  },
54
54
  "devDependencies": {
55
- "@stacksjs/config": "0.70.78",
55
+ "@stacksjs/config": "0.70.79",
56
56
  "better-dx": "^0.2.16",
57
- "@stacksjs/path": "0.70.78",
58
- "@stacksjs/router": "0.70.78",
59
- "@stacksjs/validation": "0.70.78",
57
+ "@stacksjs/path": "0.70.79",
58
+ "@stacksjs/router": "0.70.79",
59
+ "@stacksjs/validation": "0.70.79",
60
60
  "bun-plugin-auto-imports": "^0.4.0"
61
61
  }
62
62
  }
package/dist/server DELETED
Binary file
package/dist/start.js.map DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../orm/src/auto-crud.ts", "../orm/routes.ts", "src/start.ts", "src/config-production.ts", "../../defaults/app/Routes.ts"],
4
- "sourcesContent": [
5
- "/**\n * Pure helpers for the auto-CRUD route generator (../routes.ts).\n *\n * Extracted so the write-path key mapping and middleware resolution can be\n * unit-tested without booting the router or a database. The canonical\n * generated routes file (storage/framework/orm/routes.ts) inlines copies of\n * these — it must stay importable when @stacksjs/orm is npm-installed — so\n * any behavioral change here must be mirrored there.\n */\n\ninterface UniqueViolation { code?: string, errno?: number, message?: string }\n\n/**\n * True when the error is a unique-constraint violation, across SQLite,\n * MySQL, and Postgres:\n *\n * - SQLite: `SQLITE_CONSTRAINT_UNIQUE` / `SQLITE_CONSTRAINT`\n * - MySQL: `errno: 1062` (ER_DUP_ENTRY)\n * - Postgres: `code: '23505'` (unique_violation)\n * - Generic fallback: message text match — covers wrapped errors from drivers\n * that lose the structured code.\n *\n * Lives here (cycle-free `@stacksjs/orm`) rather than in `@stacksjs/auth`\n * because every framework write path needs it: auto-CRUD routes, commerce/cms\n * write functions, and `@stacksjs/auth`'s `register()` (which re-exports this\n * via './rbac-store-bqb' for back-compat). `@stacksjs/database` is NOT a valid\n * home — its drivers statically import `@stacksjs/orm`, so orm routes importing\n * from database would create a package cycle.\n *\n * Exported for direct unit testing and for callers that map duplicates to\n * their own error (e.g. `register()`'s 409) instead of swallowing them.\n */\nexport function isUniqueViolation(err: unknown): boolean {\n const e = err as UniqueViolation\n return e?.code === 'SQLITE_CONSTRAINT_UNIQUE'\n || e?.code === 'SQLITE_CONSTRAINT'\n || e?.code === '23505'\n || e?.errno === 1062\n || /unique|duplicate/i.test(e?.message ?? '')\n}\n\n/**\n * Classify a write-path error into an HTTP status + JSON body for the\n * auto-CRUD store/update handlers. Three branches, in priority order:\n *\n * 1. HttpError-like (an Error carrying an integer `status` in 400-599) —\n * preserve its status, message and optional `details`. Duck-typed rather\n * than `instanceof HttpError` so this helper stays inline-copyable into the\n * canonical generated routes file without importing @stacksjs/error-handling.\n * Covers the 400/413/422 throws from getRequestBody / validation.\n * 2. Unique-constraint violation — 409 with a clean `${Model} already exists`\n * message (NO raw driver text, which would leak column names in prod).\n * 3. Anything else — the unchanged 500 contract, including `detail: String(err)`.\n */\nexport function mapWriteError(\n err: unknown,\n modelName: string,\n op: 'create' | 'update',\n): { status: number, body: Record<string, unknown> } {\n const e = err as { status?: unknown, message?: unknown, details?: unknown }\n if (\n err instanceof Error\n && typeof e.status === 'number'\n && Number.isInteger(e.status)\n && e.status >= 400\n && e.status < 600\n ) {\n const body: Record<string, unknown> = { error: err.message }\n if (e.details !== undefined) body.details = e.details\n return { status: e.status, body }\n }\n\n if (isUniqueViolation(err))\n return { status: 409, body: { error: `${modelName} already exists` } }\n\n return {\n status: 500,\n body: { error: `Failed to ${op} ${modelName}`, detail: String(err) },\n }\n}\n\n/**\n * Attribute names in model definitions may be camelCase; the migration\n * drivers (database/src/drivers/{sqlite,mysql,postgres}.ts) snake_case them\n * into column names. Write payload keys must be mapped the same way, LAST on\n * the write path — fillable filtering, validation, set-hooks and casts are\n * all keyed by attribute name. Output-identical to @stacksjs/strings\n * snakeCase for word-shaped attribute names (locked in by tests).\n */\nexport function toSnakeCase(s: string): string {\n return s.replace(/([a-z\\d])([A-Z])/g, '$1_$2').replace(/([A-Z])([A-Z][a-z])/g, '$1_$2').toLowerCase()\n}\n\n/** Map every key of a write payload to its snake_case column spelling. */\nexport function toSnakeCaseKeys(data: Record<string, any>): Record<string, any> {\n const out: Record<string, any> = {}\n for (const [k, v] of Object.entries(data)) out[toSnakeCase(k)] = v\n return out\n}\n\n/**\n * Filter a request body down to fillable fields. Accepts BOTH the\n * attribute-name spelling and its snake_case column spelling on input, so\n * read-modify-write round-trips work (GET responses expose snake_case\n * columns). The result stays keyed by attribute name — setters, casts and\n * validation rules all look fields up by that spelling.\n */\nexport function filterFillable(body: any, fillableFields: string[]): Record<string, any> {\n if (!body || fillableFields.length === 0) return {}\n const result: Record<string, any> = {}\n for (const field of fillableFields) {\n if (field in body) {\n result[field] = body[field]\n continue\n }\n const snake = toSnakeCase(field)\n if (snake !== field && snake in body) result[field] = body[snake]\n }\n return result\n}\n\n/**\n * Drop attribute keys flagged `hidden: true` from an incoming write body.\n * Must drop BOTH spellings — accepting the snake spelling in filterFillable\n * without this would let `payment_intent_id` sneak past a camelCase hidden\n * marker.\n */\nexport function dropHiddenInputs(data: Record<string, any>, hiddenFields: string[]): Record<string, any> {\n if (!hiddenFields.length) return data\n const out: Record<string, any> = { ...data }\n for (const f of hiddenFields) {\n delete out[f]\n delete out[toSnakeCase(f)]\n }\n return out\n}\n\n/**\n * Strip attribute keys flagged `hidden: true` from an outgoing response\n * record. Must drop BOTH spellings — DB rows come back keyed by snake_case\n * column names, so deleting only the attribute-name spelling lets a\n * camelCase hidden attribute (Transaction's `paymentDetails`) leak as\n * `payment_details` on public reads. Response-side mirror of\n * `dropHiddenInputs`.\n */\nexport function stripHidden(record: any, hiddenFields: string[]): any {\n if (!record || hiddenFields.length === 0) return record\n const result = { ...record }\n for (const field of hiddenFields) {\n delete result[field]\n delete result[toSnakeCase(field)]\n }\n return result\n}\n\n/**\n * Columns every auto-CRUD table carries regardless of declared attributes.\n * Members of the read allowlist (sort/filter) alongside the model's own\n * attribute names.\n */\nexport const SYSTEM_COLUMNS = ['id', 'uuid', 'created_at', 'updated_at', 'deleted_at']\n\n/**\n * Build the read-path column allowlist for a model: a map from BOTH the\n * attribute-name spelling and its snake_case column spelling to the real\n * snake_case column. One map serves `?sort=` and `?<column>=` filters.\n *\n * Why a map and not a set: attribute names may be camelCase\n * (`discountType`) while DB columns are always snake_case (the migration\n * drivers snake_case them — same contract as `toSnakeCaseKeys` on the\n * write path). A set keyed by attribute spelling let `?sort=discountType`\n * through to `orderBy('discountType')` (ghost column → 500) while\n * REJECTING the real column spelling `discount_type`. The map accepts\n * either spelling and always emits the column spelling.\n *\n * Hidden attributes are removed under BOTH spellings — sorting or\n * equality-filtering on a hidden column (`?two_factor_secret=x`) is a\n * blind-enumeration oracle even though the value never appears in the\n * response body.\n */\nexport function buildReadColumnMap(\n attributes: Record<string, unknown> | null | undefined,\n hiddenFields: string[],\n): Map<string, string> {\n const map = new Map<string, string>()\n for (const name of [...Object.keys(attributes ?? {}), ...SYSTEM_COLUMNS]) {\n const column = toSnakeCase(name)\n // bun-query-builder interpolates ORDER BY / WHERE columns raw and\n // unquoted — only word-shaped columns may enter the map.\n if (!/^\\w+$/.test(column)) continue\n map.set(name, column)\n map.set(column, column)\n }\n for (const f of hiddenFields) {\n map.delete(f)\n map.delete(toSnakeCase(f))\n }\n return map\n}\n\n/**\n * Apply a `?sort=` parameter to a query builder chain. Comma-separated\n * tokens, each optionally `-` prefixed for descending. Tokens are resolved\n * through the `columns` allowlist map (see `buildReadColumnMap`) so either\n * spelling of a declared, non-hidden attribute works and everything else —\n * unknown names, hidden attributes, non-word tokens — is silently skipped\n * (the existing contract, matching the filter loop).\n *\n * Examples:\n * ?sort=name → ORDER BY name ASC\n * ?sort=-rating → ORDER BY rating DESC\n * ?sort=discountType,name → ORDER BY discount_type ASC, name ASC\n */\nexport function applySorting(query: any, sortParam: string | null, columns: ReadonlyMap<string, string>): any {\n if (!sortParam) return query\n const tokens = String(sortParam).split(',').map(t => t.trim()).filter(Boolean)\n let q = query\n for (const tok of tokens) {\n const desc = tok.startsWith('-')\n const requested = desc ? tok.slice(1) : tok\n if (!/^\\w+$/.test(requested)) continue\n const column = columns.get(requested)\n if (!column) continue\n q = q.orderBy(column, desc ? 'desc' : 'asc')\n }\n return q\n}\n\n/**\n * Built-in cast resolvers — kept in sync with @stacksjs/orm/define-model.\n * A duplicate here is the simplest way to keep auto-CRUD parity with the\n * model-driven path without introducing a circular import.\n */\nexport const AUTO_CRUD_CASTERS: Record<string, { get: (v: unknown) => unknown, set: (v: unknown) => unknown }> = {\n string: { get: v => v != null ? String(v) : null, set: v => v != null ? String(v) : null },\n number: { get: v => v != null ? Number(v) : null, set: v => v != null ? Number(v) : null },\n integer: { get: v => v != null ? Math.trunc(Number(v)) : null, set: v => v != null ? Math.trunc(Number(v)) : null },\n float: { get: v => v != null ? Number.parseFloat(String(v)) : null, set: v => v != null ? Number.parseFloat(String(v)) : null },\n boolean: { get: v => v === 1 || v === '1' || v === true || v === 'true', set: v => (v === true || v === 1 || v === '1' || v === 'true') ? 1 : 0 },\n json: { get: v => v == null ? null : (typeof v === 'string' ? safeJSON(v) : v), set: v => v == null ? null : typeof v === 'string' ? v : JSON.stringify(v) },\n datetime: { get: v => v ? new Date(v as string) : null, set: v => v instanceof Date ? v.toISOString() : v },\n date: { get: v => v ? new Date(v as string) : null, set: v => v instanceof Date ? (v.toISOString().split('T')[0] as string) : v },\n array: { get: v => v == null ? [] : Array.isArray(v) ? v : (typeof v === 'string' ? safeJSONOrEmpty(v) : []), set: v => v == null ? null : Array.isArray(v) ? JSON.stringify(v) : v },\n}\n\nfunction safeJSON(s: string): unknown { try { return JSON.parse(s) } catch { return s } }\nfunction safeJSONOrEmpty(_s: string): unknown { try { return JSON.parse(_s) } catch { return [] } }\n\n/**\n * Apply a model's `casts` to a record, in either direction:\n * - `'get'` — DB shape → JS-typed values (read responses)\n * - `'set'` — input → DB shape (write payloads)\n *\n * Casts are declared keyed by attribute name (possibly camelCase:\n * `instantBook: 'boolean'`) but DB rows come back keyed by snake_case\n * column names (`instant_book`) — so each cast is applied under BOTH\n * spellings, whichever is present. A record keyed by attribute names\n * (the write path) behaves exactly as before; a snake-keyed DB row (the\n * read path) now gets its casts instead of leaking raw SQLite `\"1\"`s.\n */\nexport function applyCasts(\n record: Record<string, any> | null | undefined,\n casts: Record<string, string | { get: (v: unknown) => unknown, set: (v: unknown) => unknown }> | null | undefined,\n direction: 'get' | 'set',\n): any {\n if (!record || typeof record !== 'object' || !casts || Object.keys(casts).length === 0) return record\n const out: Record<string, any> = { ...record }\n for (const [attr, castDef] of Object.entries(casts)) {\n const caster = typeof castDef === 'string' ? AUTO_CRUD_CASTERS[castDef] : castDef\n if (!caster || typeof caster[direction] !== 'function') continue\n if (Object.prototype.hasOwnProperty.call(out, attr)) out[attr] = caster[direction](out[attr])\n const snake = toSnakeCase(attr)\n if (snake !== attr && Object.prototype.hasOwnProperty.call(out, snake)) out[snake] = caster[direction](out[snake])\n }\n return out\n}\n\n/**\n * Resolve middleware lists for a model's `useApi` trait value (which may be\n * `true` or `{ uri, routes, middleware }`).\n *\n * Secure-by-default: mutating routes (store/update/destroy) get `auth`\n * unless the model explicitly declares `useApi.middleware` — an explicit\n * `middleware: []` is a deliberate opt-out and is honored (with a startup\n * warning at the call site). Read routes stay public unless declared.\n */\nexport function resolveApiMiddleware(useApi: unknown): { read: string[], write: string[], declared: boolean } {\n const declared = typeof useApi === 'object' && useApi !== null && 'middleware' in (useApi as Record<string, unknown>)\n const raw = (useApi as any)?.middleware\n const list: string[] = Array.isArray(raw)\n ? raw.filter((m: unknown) => typeof m === 'string' && m.length > 0)\n : (typeof raw === 'string' && raw ? [raw] : [])\n return { read: list, write: declared ? list : ['auth'], declared }\n}\n\n// Default page size for the auto-CRUD index route. Matches the\n// request-aware Model.paginate() / resolvePageArgs default (15) so the\n// REST list endpoint and the in-process paginator agree out of the box.\nexport const INDEX_DEFAULT_PER_PAGE = 15\n// Upper bound on ?per_page= so a single request can't ask for an\n// unbounded page and exhaust memory.\nexport const INDEX_MAX_PER_PAGE = 100\n\n/**\n * Resolve `?page=` / `?per_page=` for the index route into a clamped,\n * NaN-safe `{ page, perPage, offset }`.\n *\n * - `page` is clamped to `>= 1` (a `?page=0` / negative would otherwise\n * produce a negative OFFSET), defaulting to 1 on missing/NaN.\n * - `perPage` defaults to {@link INDEX_DEFAULT_PER_PAGE}, is clamped to\n * `>= 1`, and capped at {@link INDEX_MAX_PER_PAGE}.\n */\nexport function resolveIndexPageArgs(params: URLSearchParams): { page: number, perPage: number, offset: number } {\n const pageRaw = Number.parseInt(params.get('page') || String(1), 10)\n const page = Number.isFinite(pageRaw) ? Math.max(1, pageRaw) : 1\n const perPageRaw = Number.parseInt(params.get('per_page') || String(INDEX_DEFAULT_PER_PAGE), 10)\n const perPage = Math.min(Number.isFinite(perPageRaw) ? Math.max(1, perPageRaw) : INDEX_DEFAULT_PER_PAGE, INDEX_MAX_PER_PAGE)\n return { page, perPage, offset: (page - 1) * perPage }\n}\n\n/**\n * Pagination `meta` for the auto-CRUD index envelope (`{ data, meta }`).\n *\n * Always carries `page` / `per_page` / `from` / `to` / `has_more_pages`\n * plus `prev_page_url` / `next_page_url`. `total` / `last_page` and the\n * `first_page_url` / `last_page_url` are added only when a total is known\n * (`?with_count=true`).\n */\nexport interface IndexPageMeta {\n page: number\n per_page: number\n from: number | null\n to: number | null\n has_more_pages: boolean\n prev_page_url: string | null\n next_page_url: string | null\n total?: number\n last_page?: number\n first_page_url?: string\n last_page_url?: string\n}\n\n// Build a URL string preserving every existing query param on `url`,\n// overriding only `page`. Returns `pathname + search` (relative) so the\n// caller doesn't leak the host. Standalone (not the request-context-coupled\n// buildUrl in paginator-request.ts) because the index route already holds\n// `new URL(req.url)` and the canonical routes.ts copy can't import ./src/*.\nfunction pageUrl(url: URL, page: number): string {\n const out = new URL(url.toString())\n out.searchParams.set('page', String(page))\n return `${out.pathname}${out.search}`\n}\n\n/**\n * Build the index pagination `meta`. `hasMore` is the source of truth for\n * \"is there a next page\" (derived by the route from a `LIMIT perPage + 1`\n * probe fetch), so `next_page_url` stays consistent whether or not a total\n * was counted. When `total` is known, `last_page` uses the\n * `Math.max(1, ceil(total / perPage))` floor from the Paginator interface.\n */\nexport function buildIndexMeta(\n url: URL,\n page: number,\n perPage: number,\n rowCount: number,\n hasMore: boolean,\n total?: number,\n): IndexPageMeta {\n const offset = (page - 1) * perPage\n const empty = rowCount === 0\n const meta: IndexPageMeta = {\n page,\n per_page: perPage,\n from: empty ? null : offset + 1,\n to: empty ? null : offset + rowCount,\n has_more_pages: hasMore,\n prev_page_url: page > 1 ? pageUrl(url, page - 1) : null,\n next_page_url: hasMore ? pageUrl(url, page + 1) : null,\n }\n if (total !== undefined && !Number.isNaN(total)) {\n const lastPage = Math.max(1, Math.ceil(total / perPage))\n meta.total = total\n meta.last_page = lastPage\n meta.first_page_url = pageUrl(url, 1)\n meta.last_page_url = pageUrl(url, lastPage)\n }\n return meta\n}\n\n/**\n * Flat Laravel paginator shape lifted to the index response top level.\n * Mirrors {@link IndexPageMeta} minus `data`/`path` (the route spreads this\n * alongside its own `data`), but keys the current page as `current_page`\n * instead of `page` so a generated-endpoint list response deep-equals a\n * `Model.paginate()` envelope. `total` / `last_page` / `first_page_url` /\n * `last_page_url` stay gated on `total` (`?with_count=true`), matching\n * {@link SimplePaginator} when absent.\n */\nexport interface IndexPaginator {\n current_page: number\n per_page: number\n from: number | null\n to: number | null\n has_more_pages: boolean\n prev_page_url: string | null\n next_page_url: string | null\n total?: number\n last_page?: number\n first_page_url?: string\n last_page_url?: string\n}\n\n/**\n * Flat Laravel paginator shape for the index response top level. Same values\n * as {@link buildIndexMeta} but keyed `current_page` (not `page`) so a\n * generated-endpoint list response deep-equals a `Model.paginate()` envelope.\n * The `page` -> `current_page` rename is the only delta; the value math lives\n * solely in `buildIndexMeta`.\n */\nexport function buildIndexPaginator(\n url: URL,\n page: number,\n perPage: number,\n rowCount: number,\n hasMore: boolean,\n total?: number,\n): IndexPaginator {\n const { page: currentPage, ...rest } = buildIndexMeta(url, page, perPage, rowCount, hasMore, total)\n return { current_page: currentPage, ...rest }\n}\n",
6
- "/**\n * ORM-generated routes\n *\n * Auto-generates CRUD REST API routes based on model `useApi` trait definitions.\n * User-defined routes in ./routes/ are loaded first and always take priority.\n */\n\nimport type { EnhancedRequest } from '@stacksjs/bun-router'\nimport { route } from '@stacksjs/router'\nimport { env } from '@stacksjs/env'\nimport { projectPath } from '@stacksjs/path'\nimport { createQueryBuilder, defaultConfig, setConfig } from '@stacksjs/query-builder'\nimport { HttpError } from '@stacksjs/error-handling'\nimport { log } from '@stacksjs/logging'\nimport { applyCasts, applySorting, buildIndexMeta, buildIndexPaginator, buildReadColumnMap, dropHiddenInputs, filterFillable, mapWriteError, resolveApiMiddleware, resolveIndexPageArgs, stripHidden, toSnakeCase, toSnakeCaseKeys } from './src/auto-crud'\n\n// Initialize the query builder config from the project's optional\n// `config/qb.ts` override (stacksjs/stacks#1930).\n//\n// This file is NOT scaffolded by the framework — it's a per-project\n// escape hatch. On a fresh clone / clean container build it's absent,\n// and a hard `await import(...)` here used to throw `Cannot find\n// module config/qb.ts` and abort the entire ORM-route bootstrap (so\n// every model-backed Action 404'd in production while `buddy dev`\n// masked it against stale local state).\n//\n// The fallback used to be bun-query-builder's own `defaultConfig`, whose\n// doc comment claims it's \"env-driven\" but is actually a hardcoded\n// `dialect: 'postgres'` literal — every project running the framework's\n// own zero-config SQLite default (any fresh `buddy new`, since no\n// config/qb.ts is ever scaffolded) got every useApi-generated REST route\n// silently pointed at Postgres, failing with \"role \\\"postgres\\\" does not\n// exist\" the moment anything hit GET /api/{resource}. Derive the fallback\n// from the same DB_CONNECTION / DB_DATABASE_PATH env vars every other\n// data-layer entry point (migrations, the ORM itself) already reads,\n// instead of a package-level default that has never matched this\n// framework's own default database.\nconst qbConfigPath = projectPath('config/qb.ts')\ntry {\n const projectQbConfig = (await import(qbConfigPath)).default\n setConfig(projectQbConfig ?? defaultConfig)\n}\ncatch {\n log.debug(`[orm] No config/qb.ts override found — deriving config from DB_CONNECTION`)\n const dialect = (env.DB_CONNECTION as 'sqlite' | 'mysql' | 'postgres' | undefined) || 'sqlite'\n setConfig({\n ...defaultConfig,\n dialect,\n database: dialect === 'sqlite'\n ? { database: env.DB_DATABASE_PATH || 'database/stacks.sqlite' }\n : {\n database: env.DB_DATABASE || 'stacks',\n host: env.DB_HOST || '127.0.0.1',\n port: env.DB_PORT || (dialect === 'postgres' ? 5432 : 3306),\n username: env.DB_USERNAME || (dialect === 'postgres' ? 'postgres' : 'root'),\n password: env.DB_PASSWORD || '',\n },\n } as Parameters<typeof setConfig>[0])\n}\n\n// Load all models from app/Models/ (individually, so one broken model doesn't block the rest)\nconst modelsDir = projectPath('app/Models')\nconst models: Record<string, any> = {}\n\ntry {\n const { readdirSync, statSync } = await import('node:fs')\n const { extname, basename } = await import('node:path')\n\n const entries = readdirSync(modelsDir)\n for (const entry of entries) {\n const full = `${modelsDir}/${entry}`\n const st = statSync(full)\n if (st.isDirectory()) continue\n const ext = extname(full)\n if (!['.ts', '.js'].includes(ext)) continue\n\n try {\n const mod = await import(`${full}?t=${Date.now()}`)\n const def = mod.default ?? mod\n const name = def.name ?? basename(entry, ext)\n models[name] = { ...def, name }\n }\n catch {\n // Skip models that fail to import (e.g., missing dependencies)\n }\n }\n}\ncatch {\n // Models directory may not exist yet\n}\n\n// Create a query builder instance (uses the config set above)\nconst db = createQueryBuilder()\n\n// Helper: check if a route is already registered (user-defined routes take priority)\nfunction routeExists(method: string, path: string): boolean {\n return route.routes.some(\n (r: any) => r.method === method && r.path === path,\n )\n}\n\n// Helper: get fillable attribute names from a model\nfunction getFillableFields(model: any): string[] {\n if (!model.attributes) return []\n return Object.entries(model.attributes)\n .filter(([_, attr]: [string, any]) => attr.fillable === true)\n .map(([name]: [string, any]) => name)\n}\n\n// Helper: get hidden attribute names from a model\nfunction getHiddenFields(model: any): string[] {\n if (!model.attributes) return []\n return Object.entries(model.attributes)\n .filter(([_, attr]: [string, any]) => attr.hidden === true)\n .map(([name]: [string, any]) => name)\n}\n\n// Run each declared `validation.rule` against an incoming write payload.\n// Returns { valid: true } or { valid: false, errors }. Per-attribute custom\n// messages from `validation.message` override the rule's default text.\n//\n// Skips fields the caller never sent on PATCH requests so a partial update\n// doesn't trip a \"required\" rule on a sibling field that wasn't touched.\n// eslint-disable-next-line pickier/no-unused-vars\nfunction validateWriteBody(\n _data: Record<string, any>,\n _model: any,\n _hook: 'creating' | 'updating',\n): { valid: true } | { valid: false, errors: Record<string, string[]> } {\n const data = _data\n const model = _model\n const hook = _hook\n const attrs = model?.attributes ?? {}\n const errors: Record<string, string[]> = {}\n for (const [field, def] of Object.entries(attrs as Record<string, any>)) {\n const rule: any = def?.validation?.rule\n if (!rule || typeof rule.validate !== 'function') continue\n const present = Object.prototype.hasOwnProperty.call(data, field)\n if (!present && hook === 'updating') continue\n const value = present ? data[field] : undefined\n const result = rule.validate(value)\n if (!result?.valid && Array.isArray(result?.errors) && result.errors.length > 0) {\n errors[field] = result.errors.map((e: any) =>\n def?.validation?.message?.[e?.code] ?? e?.message ?? 'invalid',\n )\n }\n }\n return Object.keys(errors).length === 0 ? { valid: true } : { valid: false, errors }\n}\n\n// Naive English pluralization for hasMany relation names. Matches\n// bun-query-builder's table-name convention (User → users, CarPhoto →\n// car_photos). Edge cases (mouse → mice, child → children) are not\n// covered — models that need them should override `useApi.uri`.\nfunction pluralize(s: string): string {\n if (s.endsWith('y') && !/[aeiou]y$/i.test(s)) return `${s.slice(0, -1)}ies`\n if (/(?:s|x|z|ch|sh)$/i.test(s)) return `${s}es`\n return `${s}s`\n}\n\n/**\n * Eager-load `belongsTo` + `hasMany` relations declared on a model and\n * attach them under snake_case keys on each row. Hidden fields are\n * recursively stripped from every loaded relation so a `?include=user`\n * on a Booking can never leak the user's password hash even if a future\n * model marks it fillable.\n *\n * Limited to one level of depth — `?include=user.host_profile` would\n * need a recursive walker that's beyond the scope of the v1 implementation.\n */\nasync function applyIncludes(\n rows: any[],\n model: any,\n includeParam: string,\n modelsRegistry: Record<string, any>,\n): Promise<any[]> {\n if (!rows.length) return rows\n const requested = includeParam.split(',').map(s => s.trim()).filter(Boolean)\n if (!requested.length) return rows\n\n const belongsTo: string[] = Array.isArray(model.belongsTo) ? model.belongsTo : []\n const hasMany: string[] = Array.isArray(model.hasMany) ? model.hasMany : []\n const hasOne: string[] = Array.isArray(model.hasOne) ? model.hasOne : []\n\n // Build a lookup: snake_case include key → { kind, modelName }\n const allowed = new Map<string, { kind: 'belongsTo' | 'hasOne' | 'hasMany', modelName: string }>()\n for (const m of belongsTo) allowed.set(toSnakeCase(m), { kind: 'belongsTo', modelName: m })\n for (const m of hasOne) allowed.set(toSnakeCase(m), { kind: 'hasOne', modelName: m })\n for (const m of hasMany) allowed.set(pluralize(toSnakeCase(m)), { kind: 'hasMany', modelName: m })\n\n for (const include of requested) {\n const meta = allowed.get(include)\n if (!meta) continue // silently drop unknown includes — same shape as filter handling\n const related = modelsRegistry[meta.modelName]\n if (!related) continue\n const relatedTable = related.table || pluralize(toSnakeCase(meta.modelName))\n const relatedHidden = getHiddenFields(related)\n const relatedAttrs = (id: any) => stripHidden(applyReadCasts(id, related), relatedHidden)\n\n if (meta.kind === 'belongsTo' || meta.kind === 'hasOne') {\n const fkOnParent = `${toSnakeCase(meta.modelName)}_id`\n const ids = [...new Set(rows.map(r => r[fkOnParent]).filter(v => v != null))]\n if (!ids.length) {\n for (const r of rows) r[include] = null\n continue\n }\n const childRows = await (db as any).selectFrom(relatedTable).whereIn('id', ids).get()\n const byId = new Map<string, any>()\n for (const cr of childRows ?? []) byId.set(String(cr.id), relatedAttrs(cr))\n for (const r of rows) r[include] = byId.get(String(r[fkOnParent])) ?? null\n }\n else {\n // hasMany: child rows have parent_id pointing back at us.\n const parentName = String(model.name ?? '')\n const fkOnChild = `${toSnakeCase(parentName)}_id`\n const parentIds = [...new Set(rows.map(r => r.id).filter(v => v != null))]\n if (!parentIds.length) {\n for (const r of rows) r[include] = []\n continue\n }\n const childRows = await (db as any).selectFrom(relatedTable).whereIn(fkOnChild, parentIds).get()\n const grouped = new Map<string, any[]>()\n for (const cr of childRows ?? []) {\n const key = String(cr[fkOnChild])\n const arr = grouped.get(key) ?? []\n arr.push(relatedAttrs(cr))\n grouped.set(key, arr)\n }\n for (const r of rows) r[include] = grouped.get(String(r.id)) ?? []\n }\n }\n return rows\n}\n\n// Apply user-defined `set:` hooks (e.g. User.set.password = bcrypt) before\n// raw DB writes so the auto-CRUD store/update endpoints don't end up storing\n// plaintext where the model declared a transformation. Mirrors the helper\n// in @stacksjs/orm/define-model — duplicated here to avoid a circular\n// import. Errors from individual setters are swallowed (logged in\n// define-model's version) so a single broken setter can't kill the whole\n// payload.\nasync function applyDefinedSetters(data: Record<string, any>, model: any): Promise<Record<string, any>> {\n const setters: Record<string, (attrs: Record<string, unknown>) => unknown> | undefined = model?.set\n if (!setters || typeof setters !== 'object') return data\n const out: Record<string, any> = { ...data }\n for (const [key, fn] of Object.entries(setters)) {\n if (typeof fn !== 'function' || !(key in out)) continue\n try {\n out[key] = await fn(out)\n }\n catch { /* surfaced via define-model's logger */ }\n }\n return out\n}\n\n// Apply set-side casts (input → DB shape) so PATCH /api/cars/{id} with\n// `{ instant_book: true }` writes `1`, matching what model.create() does.\n// applyCasts handles BOTH key spellings (attribute name + snake column).\nfunction applySetCasts(data: Record<string, any>, model: any): Record<string, any> {\n return applyCasts(data, model?.casts, 'set')\n}\n\n// Apply read-side casts (DB shape → JS-typed values) so the auto-CRUD\n// returns `instant_book: true` instead of the raw SQLite text `\"1\"`.\n// DB rows come back keyed by snake_case columns while casts are declared\n// by attribute name — applyCasts matches either spelling.\nfunction applyReadCasts(row: any, model: any): any {\n return applyCasts(row, model?.casts, 'get')\n}\n\n// Helper: create JSON response\nfunction jsonResponse(data: any, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\n// Same as jsonResponse but stamps `request_id` into the body for error\n// envelopes. Pairs with the X-Request-ID response header so SPAs + RUM\n// tooling can correlate a user-facing error to a specific log line\n// without relying on header capture.\nfunction errorResponse(req: any, body: Record<string, any>, status = 400): Response {\n const reqId = req?._requestId\n const enriched = reqId ? { ...body, request_id: reqId } : body\n return new Response(JSON.stringify(enriched), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\n// Helper: get request body (uses pre-parsed body from stacks-router middleware, falls back to clone).\n// Throws an HttpError on malformed JSON so the route handler can return 400 rather than silently\n// treating a corrupted payload as `{}` (which previously surfaced as \"No fillable fields\" 422s\n// and made debugging client bugs impossible).\n// Cap any single request body at this many bytes. 1 MB is generous for\n// JSON CRUD payloads (a 6-photo car listing is ~5 KB) but tight enough\n// that a hostile client can't park a 50 MB body and tie up the parser.\n// File uploads have their own multipart pipeline upstream and don't\n// route through this helper.\nconst MAX_BODY_BYTES = 1_048_576\n\nasync function getRequestBody(req: EnhancedRequest): Promise<Record<string, any>> {\n // Body-size check runs FIRST so even pre-parsed bodies (jsonBody attached\n // by upstream middleware) can't slip past the cap. We trust the\n // advertised content-length header here — clients that lie about it\n // get caught when the actual text is read below.\n const contentLength = Number(req.headers?.get?.('content-length') ?? 0)\n if (contentLength > MAX_BODY_BYTES)\n throw new HttpError(413, `Request body exceeds ${MAX_BODY_BYTES}-byte limit`)\n\n if ((req as any).jsonBody && typeof (req as any).jsonBody === 'object') {\n return (req as any).jsonBody\n }\n if ((req as any).formBody && typeof (req as any).formBody === 'object') {\n return (req as any).formBody\n }\n\n try {\n const text = await req.clone().text()\n if (text.length > MAX_BODY_BYTES)\n throw new HttpError(413, `Request body exceeds ${MAX_BODY_BYTES}-byte limit`)\n if (!text) return {}\n const parsed = JSON.parse(text)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}\n }\n catch (err) {\n if (err instanceof HttpError) throw err\n throw new HttpError(400, `Invalid JSON body: ${(err as Error).message}`)\n }\n}\n\n// Helper: coerce a primary-key-shaped value submitted by a client (bulk-delete IDs,\n// path params, etc.) into a number when the column looks numeric, or pass through\n// when it's a non-empty string (UUID, slug). Anything else becomes null and the\n// caller should reject with 422.\nfunction coerceId(raw: unknown): number | string | null {\n if (raw == null) return null\n if (typeof raw === 'number') return Number.isFinite(raw) && raw > 0 ? raw : null\n if (typeof raw === 'string') {\n const trimmed = raw.trim()\n if (!trimmed) return null\n if (/^\\d+$/.test(trimmed)) {\n const n = Number(trimmed)\n return Number.isFinite(n) && n > 0 ? n : null\n }\n return trimmed\n }\n return null\n}\n\n// Helper: extract bearer token from a request, falling back to the raw header\n// when the enhanced request hasn't attached a `bearerToken()` method.\nfunction bearerOf(req: EnhancedRequest): string | null {\n const fn = (req as any).bearerToken\n if (typeof fn === 'function') {\n const t = fn.call(req)\n if (t) return t\n }\n const auth = req.headers?.get?.('authorization') || req.headers?.get?.('Authorization') || ''\n if (typeof auth === 'string' && auth.startsWith('Bearer '))\n return auth.substring(7)\n return null\n}\n\n// Helper: resolve the authed user via the @stacksjs/auth façade — used by the\n// `authedFill` model option to derive ownership-stamping fields like\n// `host_profile_id` from the requesting user. Returns null when unauthed.\nasync function authedUserFromRequest(req: EnhancedRequest): Promise<any | null> {\n const stored = (req as any)._authenticatedUser\n if (stored) return stored\n const token = bearerOf(req)\n if (!token) return null\n try {\n const { Auth } = await import('@stacksjs/auth')\n const user = await (Auth as any).getUserFromToken(token)\n if (user) (req as any)._authenticatedUser = user\n return user || null\n }\n catch {\n return null\n }\n}\n\n// Helper: resolve the authed user's \"owner identity\" for a given model so\n// the auto-CRUD update/destroy/bulk-destroy paths can compare it against\n// the row's `field`.\n//\n// `ownership` model config shape:\n// ownership: {\n// field: 'host_profile_id', // column on this row\n// resolve: async (user) => number?, // what value of `field` does\n// // the authed user own?\n// bypass?: (user) => boolean, // optional admin escape hatch\n// }\n//\n// Returns the resolved owner value, or `null` if the model has no\n// ownership config (caller should treat as \"no per-row check\").\n// Helper: do row's ownership field value belong to the resolved owner?\n// Supports both scalar (single owner id) and array (set of allowed ids,\n// useful for two-hop ownership where a Photo's car_id must be ∈ the\n// authed host's owned car ids).\nfunction ownsRow(rowField: unknown, ownerValue: unknown): boolean {\n if (rowField == null || ownerValue == null) return false\n if (Array.isArray(ownerValue))\n return ownerValue.some(v => String(v) === String(rowField))\n return String(rowField) === String(ownerValue)\n}\n\n// Does this model carry a direct `team_id` column? Declared attributes are\n// keyed by attribute name (camelCase `teamId`) but the DB column is\n// snake_case `team_id` — accept either spelling, always return the column.\nfunction teamColumnOf(model: any): string | null {\n const attrs = model?.attributes || {}\n const has = (k: string) => Object.prototype.hasOwnProperty.call(attrs, k)\n return (has('teamId') || has('team_id')) ? 'team_id' : null\n}\n\n// Adapt a raw EnhancedRequest to the `{ bearerToken, cookies }` shape\n// @stacksjs/auth's team resolver expects. The handler can't rely on\n// `req.bearerToken()` being wired here (the auto-CRUD paths read the\n// Authorization header directly via bearerOf), so surface the credential\n// from the header and parse the Cookie header for the session/token cookie.\nfunction teamAuthRequest(req: EnhancedRequest): { bearerToken: () => string | null, cookies: { get: (name: string) => string | null } } {\n const token = bearerOf(req)\n const cookieHeader = (req.headers?.get?.('cookie') as string | null) || ''\n return {\n bearerToken: () => token,\n cookies: {\n get: (name: string) => {\n for (const part of cookieHeader.split(';')) {\n const eq = part.indexOf('=')\n if (eq === -1) continue\n if (part.slice(0, eq).trim() === name)\n return decodeURIComponent(part.slice(eq + 1).trim())\n }\n return null\n },\n },\n }\n}\n\n// The ownership config actually enforced for a model. An explicit\n// `model.ownership` always wins. Otherwise any model with a `team_id`\n// column is auto-scoped to the caller's active team — tenant tables are\n// row-isolated with zero per-model config, while a public catalog table\n// (no team_id, no ownership) resolves to `null` and stays un-scoped.\n//\n// The team is resolved from the request's REAL credential (bearer token or\n// session cookie) via @stacksjs/auth — never from a client-supplied field —\n// so a caller can't widen their own scope by POSTing or ?team_id=-ing another\n// team's id. Lazy import mirrors authedUserFromRequest: avoids a boot-time\n// cycle through @stacksjs/auth.\nfunction effectiveOwnershipConfig(model: any): any | null {\n if (model?.ownership) return model.ownership\n const teamCol = teamColumnOf(model)\n if (!teamCol) return null\n return {\n field: teamCol,\n resolve: async (_user: any, req: EnhancedRequest) => {\n const { resolveAuthenticatedTeamId } = await import('@stacksjs/auth')\n return resolveAuthenticatedTeamId(teamAuthRequest(req) as any)\n },\n }\n}\n\nasync function resolveOwnership(\n model: any,\n user: any,\n req: EnhancedRequest,\n): Promise<{ enforced: boolean, value: unknown, field: string, bypass: boolean }> {\n const cfg = effectiveOwnershipConfig(model)\n if (!cfg || !cfg.field || typeof cfg.resolve !== 'function')\n return { enforced: false, value: null, field: '', bypass: false }\n const bypass = typeof cfg.bypass === 'function' ? !!cfg.bypass(user, req) : false\n let value: unknown = null\n try {\n value = await cfg.resolve(user, req)\n }\n catch { value = null }\n return { enforced: true, value, field: String(cfg.field), bypass }\n}\n\n// Helper: apply a model's `authedFill` config for the given lifecycle hook.\n// Returns a partial object to merge into the insert/update payload.\nasync function resolveAuthedFill(\n model: any,\n hook: 'creating' | 'updating',\n user: any,\n req: EnhancedRequest,\n): Promise<Record<string, any>> {\n const cfg = model?.authedFill?.[hook]\n if (!cfg) return {}\n if (typeof cfg === 'function') {\n try {\n const result = await cfg(user, req)\n return result && typeof result === 'object' ? result : {}\n }\n catch {\n return {}\n }\n }\n if (typeof cfg === 'object') {\n const out: Record<string, any> = {}\n for (const [field, getter] of Object.entries(cfg)) {\n try {\n out[field] = typeof getter === 'function' ? await (getter as any)(user, req) : getter\n }\n catch { /* ignore individual getter errors */ }\n }\n return out\n }\n return {}\n}\n\n// Register CRUD routes for each model with useApi trait\nfor (const [modelName, model] of Object.entries(models)) {\n const useApi = model.traits?.useApi\n if (!useApi) continue\n\n // useApi can be `true` (auto-derive uri from table) or an object with { uri, routes }\n const apiConfig = typeof useApi === 'object' ? useApi : {}\n const uri = apiConfig.uri || model.table || modelName.toLowerCase() + 's'\n\n const enabledRoutes: string[] = apiConfig.routes || ['index', 'show', 'store', 'update', 'destroy']\n const table = model.table || uri\n const fillableFields = getFillableFields(model)\n const hiddenFields = getHiddenFields(model)\n const basePath = `/api/${uri}`\n\n // Read-path column allowlist: declared model attributes + system columns,\n // minus anything marked `hidden`, mapped from EITHER spelling (attribute\n // name or snake_case column) to the real snake_case column. Computed once\n // per model and shared by `?sort=` and the `?<column>=` filter loop so\n // neither can target a ghost column (camelCase attribute → 500) nor\n // enumerate sensitive/hidden columns.\n const readColumns = buildReadColumnMap(model.attributes, hiddenFields)\n\n // Per-model middleware, honoring the `useApi.middleware` field declared\n // on the model trait (e.g. `middleware: ['auth']` for resources that\n // should never be browsed anonymously). Read routes (index/show) stay\n // public by default — fine for catalog tables (products, posts). Mutating\n // routes (store/update/destroy) are secure-by-default: they get `auth`\n // unless the model explicitly declares `middleware` (an explicit `[]` is\n // a deliberate opt-out), because `enabledRoutes` defaults to all five and\n // a bare `useApi: true` used to expose anonymous POST/PUT/PATCH/DELETE.\n const { read: readMiddleware0, write: writeMiddleware, declared } = resolveApiMiddleware(useApi)\n const hasMutating = ['store', 'update', 'destroy'].some(r => enabledRoutes.includes(r))\n if (hasMutating && declared && writeMiddleware.length === 0)\n log.warn(`[orm] ${modelName}: registering UNAUTHENTICATED mutating routes at ${basePath} (explicit \\`middleware: []\\` opt-out)`)\n\n // Row-scoped resource? (explicit `ownership`, or a model with a team_id\n // column that's auto-team-scoped). If so its index/show handlers below\n // restrict every row to the caller's team — which requires knowing who is\n // calling, so force `auth` onto the read routes even though catalog tables\n // (no team_id, no ownership) stay anonymously browsable. Computed once and\n // reused by the handlers to gate the (DB-hitting) ownership resolution.\n const rowScoped = !!effectiveOwnershipConfig(model)\n const readMiddleware = (rowScoped && !readMiddleware0.includes('auth'))\n ? ['auth', ...readMiddleware0]\n : readMiddleware0\n\n // Local helper: chain `.middleware()` for every entry in `names`.\n // The chainable return value from `route.get/post/...` accepts one\n // name per call, so we fold the list into successive calls.\n const applyMiddleware = (chain: any, names: string[] = readMiddleware): any => {\n let r = chain\n for (const name of names) {\n if (r && typeof r.middleware === 'function') r = r.middleware(name)\n }\n return r\n }\n\n // GET /api/{uri} — list all records (paginated, sortable)\n if (enabledRoutes.includes('index') && !routeExists('GET', basePath)) {\n applyMiddleware(route.get(basePath, async (req: EnhancedRequest) => {\n try {\n const url = new URL(req.url)\n // Clamped, NaN-safe page/perPage (page >= 1, perPage in [1, 100],\n // default perPage 15 to match Model.paginate() / resolvePageArgs).\n const { page, perPage, offset } = resolveIndexPageArgs(url.searchParams)\n const sort = url.searchParams.get('sort')\n\n let query = (db as any).selectFrom(table)\n query = applySorting(query, sort, readColumns)\n\n // Apply query string filters: ?status=active&name=foo filters by column values.\n // Reserved query params (pagination, sort, etc.) are skipped. Filter keys are\n // resolved through the readColumns allowlist (either spelling of a declared,\n // non-hidden attribute → snake_case column) — an unknown key is ignored rather\n // than emitted as raw SQL (e.g. `WHERE limit = ?` would blow up because `limit`\n // is a SQL keyword), and hidden columns can't be equality-probed.\n const RESERVED = new Set(['page', 'per_page', 'sort', 'fields', 'search', 'include', 'limit', 'offset', 'with_count', 'withTrashed', 'onlyTrashed'])\n for (const [key, value] of url.searchParams.entries()) {\n if (RESERVED.has(key) || !value) continue\n if (!/^[a-z_][a-z0-9_]*$/i.test(key)) continue\n const col = readColumns.get(key)\n if (col) {\n query = query.where(col, '=', value)\n }\n }\n\n // Apply search across fillable text fields: ?search=keyword.\n // Fillable names are attribute spellings — map to the snake_case\n // column so a camelCase fillable doesn't hit a ghost column.\n const searchTerm = url.searchParams.get('search')\n if (searchTerm && fillableFields.length > 0) {\n const textFields = fillableFields.slice(0, 5) // Limit to first 5 fields for performance\n query = query.where((qb: any) => {\n for (const field of textFields) {\n qb = qb.orWhere(toSnakeCase(field), 'like', `%${searchTerm}%`)\n }\n return qb\n })\n }\n\n // Apply field selection: ?fields=id,name,email. Tokens are mapped\n // through toSnakeCase so attribute spellings select the real column;\n // no allowlist here — undeclared-but-real columns (FK `user_id`)\n // stay selectable, and hidden fields are stripped post-query anyway.\n const fieldsParam = url.searchParams.get('fields')\n if (fieldsParam) {\n const selectedFields = fieldsParam.split(',')\n .map(f => f.trim())\n .filter(f => /^[a-z_][a-z0-9_]*$/i.test(f))\n .map(f => toSnakeCase(f))\n if (selectedFields.length > 0) {\n query = query.select(selectedFields)\n }\n }\n\n // Soft-delete filtering. By default the index hides deleted_at IS NOT NULL\n // rows; ?withTrashed=true includes them, ?onlyTrashed=true returns only\n // them (admin/audit views). Skip silently if the trait isn't enabled.\n const usesSoftDeletes = !!model.traits?.useSoftDeletes\n const withTrashed = url.searchParams.get('withTrashed') === 'true'\n const onlyTrashed = url.searchParams.get('onlyTrashed') === 'true'\n if (usesSoftDeletes && !withTrashed) {\n query = onlyTrashed ? query.whereNotNull('deleted_at') : query.whereNull('deleted_at')\n }\n\n // Row-level ownership scoping. For owned/team tables, restrict the\n // listing to rows the caller owns — applied AFTER the ?<col>= filter\n // loop so a `?team_id=<other>` probe can only narrow, never widen,\n // the caller's own scope. Public tables (rowScoped === false) skip\n // this entirely and pay no auth-resolution cost.\n const own = rowScoped\n ? await resolveOwnership(model, await authedUserFromRequest(req), req)\n : { enforced: false, value: null as unknown, field: '', bypass: false }\n if (own.enforced && !own.bypass) {\n if (own.value == null || (Array.isArray(own.value) && own.value.length === 0)) {\n // Authed but owns nothing (e.g. no active team membership) — an\n // empty page, never another tenant's rows. `private, no-store`\n // so a shared cache can't hand this to a different caller.\n const emptyPaginator = buildIndexPaginator(url, page, perPage, 0, false, undefined)\n return new Response(JSON.stringify({\n data: [],\n ...emptyPaginator,\n meta: buildIndexMeta(url, page, perPage, 0, false, undefined),\n }), { status: 200, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'private, no-store', Vary: 'Authorization' } })\n }\n query = Array.isArray(own.value)\n ? query.where(own.field, 'in', own.value)\n : query.where(own.field, '=', own.value)\n }\n\n // Simple-paginate probe: fetch one extra row so `has_more_pages`\n // is known without a COUNT. Slice the probe row off BEFORE casts /\n // includes so applyIncludes doesn't fire N+1 relation queries for a\n // row that's about to be discarded.\n const rawResults = (await query.limit(perPage + 1).offset(offset).get()) || []\n const hasMore = rawResults.length > perPage\n const pageRows = hasMore ? rawResults.slice(0, perPage) : rawResults\n let records = pageRows.map((r: any) => stripHidden(applyReadCasts(r, model), hiddenFields))\n\n const includeParam = url.searchParams.get('include')\n if (includeParam)\n records = await applyIncludes(records, model, includeParam, models)\n\n // Total count is opt-in via ?with_count=true. Skipping it by default\n // turns the index from \"two queries every request\" into \"one\", which\n // is a meaningful win on big tables — the SPA usually paginates by\n // \"load more\" anyway and doesn't need a precise total.\n const wantCount = url.searchParams.get('with_count') === 'true'\n let total: number | undefined\n if (wantCount) {\n try {\n // bun-query-builder's count() resolves to a number directly —\n // not to a builder you call executeTakeFirst() on. The previous\n // shape silently swallowed a TypeError in this try/catch and\n // total stayed `undefined`, defeating the whole opt-in.\n //\n // Scope the count to the caller's rows too — an unscoped COUNT\n // would leak the cross-tenant total even though the page data is\n // team-filtered.\n let countQuery = (db as any).selectFrom(table)\n if (own.enforced && !own.bypass && own.value != null) {\n countQuery = Array.isArray(own.value)\n ? countQuery.where(own.field, 'in', own.value)\n : countQuery.where(own.field, '=', own.value)\n }\n const raw = await countQuery.count()\n const n = typeof raw === 'number' ? raw : Number(raw?.count ?? raw)\n if (Number.isFinite(n)) total = n\n } catch {\n // count() may not be supported by all query builder versions\n }\n }\n\n const respHeaders: Record<string, string> = { 'Content-Type': 'application/json' }\n if (total !== undefined && !Number.isNaN(total)) respHeaders['X-Total-Count'] = String(total)\n // Modest caching for unauthenticated browse — list views change\n // less frequently than detail views and SPAs naturally re-hit them\n // on navigation. Authed lists (which include user-specific filter\n // results) carry a `Vary: Authorization` so caches don't merge\n // them with the public response. Team-scoped listings are per-caller\n // and must NEVER land in a shared cache, so they go `private, no-store`.\n respHeaders['Cache-Control'] = (own.enforced && !own.bypass)\n ? 'private, no-store'\n : 'public, max-age=15, must-revalidate'\n respHeaders.Vary = 'Authorization'\n\n const paginator = buildIndexPaginator(url, page, perPage, records.length, hasMore, total)\n return new Response(JSON.stringify({\n data: records,\n ...paginator,\n // DEPRECATED: `meta` is kept for one transition release for backward\n // compat. Read the top-level fields instead (note: meta.page ===\n // current_page). Removed in a future release. (#1960)\n meta: buildIndexMeta(url, page, perPage, records.length, hasMore, total),\n }), { status: 200, headers: respHeaders })\n }\n catch (err) {\n if (err instanceof HttpError) {\n const body: Record<string, unknown> = { error: err.message }\n if (err.details !== undefined) body.details = err.details\n return jsonResponse(body, err.status || 400)\n }\n return jsonResponse({ error: `Failed to fetch ${uri}`, detail: String(err) }, 500)\n }\n }))\n }\n\n // GET /api/{uri}/{id} — show single record\n if (enabledRoutes.includes('show') && !routeExists('GET', `${basePath}/{id}`)) {\n applyMiddleware(route.get(`${basePath}/{id}`, async (req: EnhancedRequest) => {\n try {\n const id = coerceId((req as any).params?.id)\n\n // Validate ID parameter — coerceId returns null for negatives,\n // empty strings, NaN, etc., so this single check catches them all.\n if (id == null) {\n return jsonResponse({ error: 'Invalid ID parameter' }, 400)\n }\n\n const result = await (db as any).selectFrom(table).where({ id }).executeTakeFirst()\n\n if (!result) {\n return jsonResponse({ error: `${modelName} not found` }, 404)\n }\n\n // Mirror the soft-delete filter from the index handler — without\n // this, GET /api/cars/123 would happily return a row that was\n // soft-deleted, even though it doesn't appear in the listing.\n const url = new URL(req.url)\n if (model.traits?.useSoftDeletes && (result as any).deleted_at && url.searchParams.get('withTrashed') !== 'true') {\n return jsonResponse({ error: `${modelName} not found` }, 404)\n }\n\n // Row-level ownership enforcement for owned/team tables. A row that\n // belongs to another tenant is reported as 404 (not 403) so this\n // endpoint can't be used to probe which ids exist in other teams.\n const own = rowScoped\n ? await resolveOwnership(model, await authedUserFromRequest(req), req)\n : { enforced: false, value: null as unknown, field: '', bypass: false }\n if (own.enforced && !own.bypass) {\n if (own.value == null || !ownsRow((result as Record<string, unknown>)[own.field], own.value)) {\n return jsonResponse({ error: `${modelName} not found` }, 404)\n }\n }\n\n let payload: any = stripHidden(applyReadCasts(result, model), hiddenFields)\n\n // ?include=user,host_profile,car_photos hydrates declared relations.\n // Hidden fields are recursively stripped from each loaded relation.\n const includeParam = url.searchParams.get('include')\n if (includeParam) {\n const [withRel] = await applyIncludes([payload], model, includeParam, models)\n payload = withRel\n }\n\n // ETag derived from updated_at (or created_at) lets SPAs send\n // If-None-Match and short-circuit re-renders. The 304 response is\n // empty per spec — the client keeps its cached copy. Skip when the\n // request is authed because per-user variants would poison shared\n // caches.\n const lastWrite = (payload as any)?.updated_at || (payload as any)?.created_at\n const etag = lastWrite ? `W/\"${(payload as any).id}-${String(lastWrite)}\"` : undefined\n const ifNoneMatch = req.headers?.get?.('if-none-match')\n if (etag && ifNoneMatch && ifNoneMatch === etag) {\n return new Response(null, { status: 304, headers: { ETag: etag } })\n }\n\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n if (etag) headers.ETag = etag\n // Public caches default to a short TTL — long enough for SPA list/detail\n // hops to share, short enough that a stale row clears within a minute.\n // Team-scoped rows are per-caller and must never be shared-cached.\n headers['Cache-Control'] = (own.enforced && !own.bypass)\n ? 'private, no-store'\n : 'public, max-age=30, must-revalidate'\n headers.Vary = 'Authorization'\n return new Response(JSON.stringify({ data: payload }), { status: 200, headers })\n }\n catch (err) {\n if (err instanceof HttpError) {\n const body: Record<string, unknown> = { error: err.message }\n if (err.details !== undefined) body.details = err.details\n return jsonResponse(body, err.status || 400)\n }\n return jsonResponse({ error: `Failed to fetch ${modelName}`, detail: String(err) }, 500)\n }\n }))\n }\n\n // POST /api/{uri} — create record\n if (enabledRoutes.includes('store') && !routeExists('POST', basePath)) {\n applyMiddleware(route.post(basePath, async (req: EnhancedRequest) => {\n try {\n const body = await getRequestBody(req)\n // Belt-and-suspenders: drop hidden inputs FIRST so a curious client\n // can't sneak `payment_intent_id` etc. into a POST even if a future\n // change accidentally flips them to fillable.\n const safeBody = dropHiddenInputs(body, hiddenFields)\n const data = filterFillable(safeBody, fillableFields)\n\n // Stamp ownership / context-aware fields from the authed user before\n // the body fillable check, so models can declare e.g.\n // authedFill: { creating: { host_profile_id: async (u) => ... } }\n // and never have to ship a custom Store action just to attach FKs.\n const authedUser = await authedUserFromRequest(req)\n if (authedUser) {\n const stamped = await resolveAuthedFill(model, 'creating', authedUser, req)\n for (const [k, v] of Object.entries(stamped)) {\n if (v !== undefined && v !== null && data[k] === undefined)\n data[k] = v\n }\n }\n\n if (Object.keys(data).length === 0) {\n return jsonResponse({ error: 'No fillable fields provided' }, 422)\n }\n\n // Run declared validation rules. Models declare `validation: { rule:\n // schema.string().required().email() }` per attribute — without this\n // call, those declarations were dead documentation.\n const v = validateWriteBody(data, model, 'creating')\n if (!v.valid) return jsonResponse({ error: 'Validation failed', errors: v.errors }, 422)\n\n // Add timestamps if model uses them\n if (model.traits?.useTimestamps !== false) {\n const now = new Date().toISOString()\n data.created_at = now\n data.updated_at = now\n }\n\n // 1) User-defined `set:` hooks first (e.g. User.set.password = bcrypt)\n // so plaintext inputs never reach the DB on POST /api/users.\n // 2) Then the cast pass so booleans/JSON/dates are coerced to the\n // column shape (mirror of model.create() write path).\n // 3) LAST, map attribute-name keys to their snake_case column\n // spellings — migration drivers snake_case attribute names into\n // columns, so a camelCase fillable like `discountType` would\n // otherwise target a nonexistent column and 500 the INSERT.\n const hookedData = await applyDefinedSetters(data, model)\n const writeData = toSnakeCaseKeys(applySetCasts(hookedData, model))\n\n const result = await (db as any).insertInto(table).values(writeData).execute()\n\n // Try to return the full record with database-assigned ID\n let created = writeData\n try {\n const lastId = result?.lastInsertRowid ?? result?.insertId ?? result\n if (lastId) {\n const fetched = await (db as any).selectFrom(table).where({ id: lastId }).executeTakeFirst()\n if (fetched) created = fetched\n }\n } catch {\n // Fall back to returning the input data\n }\n\n return jsonResponse({ data: stripHidden(applyReadCasts(created, model), hiddenFields) }, 201)\n }\n catch (err) {\n // Maps HttpError-likes through, unique violations -> 409 (clean\n // message, no driver text leak), everything else -> 500. See #1957.\n const { status, body } = mapWriteError(err, modelName, 'create')\n return jsonResponse(body, status)\n }\n }), writeMiddleware)\n }\n\n // PUT/PATCH /api/{uri}/{id} — update record\n if (enabledRoutes.includes('update')) {\n const updateHandler = async (req: EnhancedRequest) => {\n try {\n const id = coerceId((req as any).params?.id)\n if (id == null) {\n return jsonResponse({ error: 'Invalid ID parameter' }, 400)\n }\n const body = await getRequestBody(req)\n const safeBody = dropHiddenInputs(body, hiddenFields)\n const data = filterFillable(safeBody, fillableFields)\n\n // Apply authedFill.updating stamps (mirror of the Store path).\n const authedUser = await authedUserFromRequest(req)\n if (authedUser) {\n const stamped = await resolveAuthedFill(model, 'updating', authedUser, req)\n for (const [k, v] of Object.entries(stamped)) {\n if (v !== undefined && v !== null && data[k] === undefined)\n data[k] = v\n }\n }\n\n if (Object.keys(data).length === 0) {\n return jsonResponse({ error: 'No fillable fields provided' }, 422)\n }\n\n // Run declared validation rules. Partial updates only validate fields\n // the caller actually sent — see validateWriteBody for the rule.\n const v = validateWriteBody(data, model, 'updating')\n if (!v.valid) return jsonResponse({ error: 'Validation failed', errors: v.errors }, 422)\n\n // 404 fast if the row doesn't exist — previously the UPDATE silently\n // matched zero rows and we returned the request body as if it had\n // succeeded, which masked legit \"deleted between read and write\" bugs.\n const existing = await (db as any).selectFrom(table).where({ id }).executeTakeFirst()\n if (!existing) {\n return jsonResponse({ error: `${modelName} not found` }, 404)\n }\n\n // Ownership check: when the model declares `ownership`, only the row's\n // owner (or an admin via `bypass`) can update it. Without this, any\n // authed user could PATCH /api/cars/{id} and re-parent another host's\n // car to themselves. `authedFill.updating` ALSO can't defend on its\n // own because it only fills missing fields, not replaces submitted ones.\n const own = await resolveOwnership(model, authedUser, req)\n if (own.enforced && !own.bypass) {\n if (!authedUser) return jsonResponse({ error: 'Auth required' }, 401)\n const rowOwner = (existing as Record<string, unknown>)[own.field]\n if (!ownsRow(rowOwner, own.value)) {\n return jsonResponse({ error: `Not your ${modelName}` }, 403)\n }\n // Also defend against the request trying to re-parent ownership.\n // Payload keys are attribute names (possibly camelCase) — compare\n // via the snake_case column spelling so `hostProfileId` can't\n // sneak past a `host_profile_id` ownership field.\n const submittedOwnerKey = Object.keys(data).find(k => toSnakeCase(k) === toSnakeCase(own.field))\n if (submittedOwnerKey !== undefined && !ownsRow(data[submittedOwnerKey], own.value)) {\n return jsonResponse({ error: `Cannot reassign ${modelName} ownership` }, 403)\n }\n }\n\n // Add updated_at timestamp\n if (model.traits?.useTimestamps !== false) {\n data.updated_at = new Date().toISOString()\n }\n\n // Same hook+cast pass as the create path — and crucially the set-hooks\n // must run too, otherwise `PATCH /api/users/{id}` with a password\n // field would store plaintext. Key mapping runs LAST (see store path).\n const hookedData = await applyDefinedSetters(data, model)\n const writeData = toSnakeCaseKeys(applySetCasts(hookedData, model))\n\n await (db as any).updateTable(table).set(writeData).where({ id }).execute()\n\n // Return the full updated record\n let updated: any = { ...existing, ...writeData }\n try {\n const fetched = await (db as any).selectFrom(table).where({ id }).executeTakeFirst()\n if (fetched) updated = fetched\n } catch {\n // Fall back to merging known existing + diff\n }\n\n return jsonResponse({ data: stripHidden(applyReadCasts(updated, model), hiddenFields) })\n }\n catch (err) {\n // See store handler — same classification, 'update' verb. #1957.\n const { status, body } = mapWriteError(err, modelName, 'update')\n return jsonResponse(body, status)\n }\n }\n\n if (!routeExists('PUT', `${basePath}/{id}`)) {\n applyMiddleware(route.put(`${basePath}/{id}`, updateHandler), writeMiddleware)\n }\n if (!routeExists('PATCH', `${basePath}/{id}`)) {\n applyMiddleware(route.patch(`${basePath}/{id}`, updateHandler), writeMiddleware)\n }\n }\n\n // DELETE /api/{uri}/{id} — delete record (or soft-delete if model has useSoftDeletes).\n const usesSoftDeletes = !!model.traits?.useSoftDeletes\n if (enabledRoutes.includes('destroy') && !routeExists('DELETE', `${basePath}/{id}`)) {\n applyMiddleware(route.delete(`${basePath}/{id}`, async (req: EnhancedRequest) => {\n try {\n const id = coerceId((req as any).params?.id)\n if (id == null) {\n return jsonResponse({ error: 'Invalid ID parameter' }, 400)\n }\n\n // Need the row both for the 404 fast-path and the ownership check.\n const existing = await (db as any).selectFrom(table).where({ id }).executeTakeFirst()\n if (!existing) {\n return jsonResponse({ error: `${modelName} not found` }, 404)\n }\n\n const authedUser = await authedUserFromRequest(req)\n const own = await resolveOwnership(model, authedUser, req)\n if (own.enforced && !own.bypass) {\n if (!authedUser) return jsonResponse({ error: 'Auth required' }, 401)\n const rowOwner = (existing as Record<string, unknown>)[own.field]\n if (!ownsRow(rowOwner, own.value)) {\n return jsonResponse({ error: `Not your ${modelName}` }, 403)\n }\n }\n\n if (usesSoftDeletes) {\n // Soft delete: stamp deleted_at + updated_at instead of dropping the row.\n // Reads in the index handler filter `WHERE deleted_at IS NULL` unless\n // the caller passes ?withTrashed=true.\n const now = new Date().toISOString()\n await (db as any).updateTable(table)\n .set({ deleted_at: now, updated_at: now })\n .where({ id })\n .execute()\n }\n else {\n await (db as any).deleteFrom(table).where({ id }).execute()\n }\n\n return new Response(null, { status: 204 })\n }\n catch (err) {\n if (err instanceof HttpError) {\n const body: Record<string, unknown> = { error: err.message }\n if (err.details !== undefined) body.details = err.details\n return jsonResponse(body, err.status || 400)\n }\n return jsonResponse({ error: `Failed to delete ${modelName}`, detail: String(err) }, 500)\n }\n }), writeMiddleware)\n }\n\n // POST /api/{uri}/bulk-delete — delete multiple records (also soft-aware,\n // also ownership-checked per row).\n if (enabledRoutes.includes('destroy') && !routeExists('POST', `${basePath}/bulk-delete`)) {\n applyMiddleware(route.post(`${basePath}/bulk-delete`, async (req: EnhancedRequest) => {\n try {\n const body = await getRequestBody(req)\n const ids = body?.ids\n\n if (!Array.isArray(ids) || ids.length === 0) {\n return jsonResponse({ error: 'An array of IDs is required' }, 422)\n }\n\n // Limit bulk operations to 100 records\n if (ids.length > 100) {\n return jsonResponse({ error: 'Cannot delete more than 100 records at once' }, 422)\n }\n\n // Coerce + validate each ID before opening any transactions.\n // A single bad ID inside a 100-element batch previously fell through\n // to a SQL syntax error after partial deletes had already committed.\n const validIds: Array<number | string> = []\n const invalidIds: unknown[] = []\n for (const raw of ids) {\n const coerced = coerceId(raw)\n if (coerced != null) validIds.push(coerced)\n else invalidIds.push(raw)\n }\n if (invalidIds.length > 0) {\n return jsonResponse({ error: 'Invalid IDs in batch', invalid: invalidIds }, 422)\n }\n\n const authedUser = await authedUserFromRequest(req)\n const own = await resolveOwnership(model, authedUser, req)\n\n // For ownership-enforced models, refuse the whole batch if any row\n // isn't owned by the caller. Partial deletes are worse than nothing.\n if (own.enforced && !own.bypass) {\n if (!authedUser) return jsonResponse({ error: 'Auth required' }, 401)\n if (own.value == null) return jsonResponse({ error: 'Caller has no ownership identity' }, 403)\n const rows = await (db as any).selectFrom(table).select(['id', own.field]).execute()\n const ownedIds = new Set(\n (rows as Array<Record<string, unknown>>)\n .filter(r => ownsRow(r[own.field], own.value))\n .map(r => String(r.id)),\n )\n const notOwned = validIds.filter(id => !ownedIds.has(String(id)))\n if (notOwned.length > 0)\n return jsonResponse({ error: `Cannot delete ${modelName} you don't own`, ids: notOwned }, 403)\n }\n\n const now = new Date().toISOString()\n for (const id of validIds) {\n if (usesSoftDeletes) {\n await (db as any).updateTable(table)\n .set({ deleted_at: now, updated_at: now })\n .where({ id })\n .execute()\n }\n else {\n await (db as any).deleteFrom(table).where({ id }).execute()\n }\n }\n\n return jsonResponse({ message: `Successfully deleted ${validIds.length} ${uri}` })\n }\n catch (err) {\n if (err instanceof HttpError) {\n const body: Record<string, unknown> = { error: err.message }\n if (err.details !== undefined) body.details = err.details\n return jsonResponse(body, err.status || 400)\n }\n return jsonResponse({ error: `Failed to bulk delete ${uri}`, detail: String(err) }, 500)\n }\n }), writeMiddleware)\n }\n}\n\nexport default route\n",
7
- "// Mark as binary mode to prevent auto-registration in routes/api.ts\n;(globalThis as any).__STACKS_BINARY_MODE__ = true\n\n// IMPORTANT: Import router package first to ensure it's initialized before routes\nimport { assertRouteMiddlewareResolvable, loadRoutes, serve } from '@stacksjs/router'\nimport { log, report } from '@stacksjs/logging'\nimport config from './config-production'\nimport routeRegistry from '../../../../../app/Routes'\n\n// Process-level safety net (stacksjs/stacks#1933). Without these, an\n// async throw that escapes a request try/catch — a floating promise in\n// middleware, a timer callback — would crash the HTTP server with\n// nothing in storage/logs/. The queue worker already had these; the\n// HTTP entry did not. Route both through the shared report() chokepoint.\nprocess.on('unhandledRejection', (reason) => {\n // Log and keep serving — a single rejected promise shouldn't take the\n // whole API down (Laravel-equivalent behavior).\n report(reason, { label: '[server] unhandledRejection' })\n})\n\nprocess.on('uncaughtException', (error) => {\n // An uncaught exception leaves the runtime in an undefined state — log\n // it, flush, then exit so a supervisor can restart cleanly.\n report(error, { label: '[server] uncaughtException' })\n void log.flush().finally(() => process.exit(1))\n})\n\nconsole.log('[START] Application starting...')\nconsole.log('[START] Node version:', process.version)\nconsole.log('[START] Working directory:', process.cwd())\nconsole.log('[START] Environment:', process.env.APP_ENV || 'not set')\n\n// Disable runtime config loading for compiled binary\nprocess.env.SKIP_CONFIG_LOADING = 'true'\n\nconsole.log('[START] Config loaded:', {\n port: config.server.port,\n host: config.server.host,\n appName: config.app.name,\n appUrl: config.app.url,\n})\n\n// Load routes from the registry, then ORM auto-routes, then start the server\nconsole.log('[START] Loading routes from registry...')\nloadRoutes(routeRegistry)\n .then(async () => {\n console.log('[START] Routes loaded successfully')\n\n // Load ORM auto-generated routes (model CRUD endpoints)\n // These run after manual routes so routeExists() correctly detects conflicts\n try {\n await import('../../orm/routes')\n console.log('[START] ORM routes loaded successfully')\n } catch (ormError) {\n console.warn('[START] ORM routes skipped:', ormError instanceof Error ? ormError.message : String(ormError))\n }\n\n // Fail closed at boot (stacksjs/stacks#1957): the compiled binary\n // bypasses route.importRoutes() (and its validation pass), so check\n // here that every middleware alias referenced by a registered route\n // resolves. A typo'd `auth` alias must abort startup — serving the\n // route unprotected is the one unacceptable outcome.\n try {\n await assertRouteMiddlewareResolvable()\n console.log('[START] Route middleware validated')\n } catch (middlewareError) {\n console.error('[START] FATAL: unresolvable route middleware — refusing to serve unprotected routes:', middlewareError instanceof Error ? middlewareError.message : String(middlewareError))\n process.exit(1)\n }\n\n console.log('[START] Calling serve()...')\n try {\n serve({\n port: config.server.port,\n host: config.server.host,\n } as any)\n console.log('[START] serve() called successfully')\n } catch (error) {\n console.error('[START] ERROR calling serve():', error)\n process.exit(1)\n }\n })\n .catch((error) => {\n console.error('[START] ERROR loading routes:', error)\n process.exit(1)\n })\n",
8
- "/**\n * Production server config - minimal, no runtime file loading\n * This config is inlined at build time for compiled binaries\n */\n\nexport const config = {\n app: {\n name: process.env.APP_NAME || 'Stacks',\n env: process.env.APP_ENV || 'production',\n debug: process.env.APP_DEBUG === 'true' || false,\n url: process.env.APP_URL || 'https://stacksjs.com',\n },\n server: {\n port: Number(process.env.PORT) || 3000,\n host: '0.0.0.0',\n },\n logging: {\n level: process.env.LOG_LEVEL || 'info',\n },\n}\n\nexport default config\n",
9
- "// Route registry types live in `@stacksjs/router` (the consumer) — see\n// stacksjs/stacks#1863. Re-exported here for backwards compatibility\n// with any project code that imports them via `app/Routes`.\nimport type { RouteDefinition, RouteRegistry } from '@stacksjs/router'\n\nexport type { RouteDefinition, RouteRegistry }\n\n/**\n * Application route registry.\n *\n * Define your route files here. The key becomes the URL prefix\n * automatically. The `'web'` key is the only one that loads at root\n * (`/`) with no prefix — see the route-loader's `NO_PREFIX_KEYS` for\n * the canonical list.\n *\n * `'api'` auto-prefixes with `/api` so user routes line up with the\n * rpx proxy forward path (stacksjs/stacks#1835). Writing\n * `route.get('/cart/add', ...)` in `routes/api.ts` registers as\n * `/api/cart/add` — exactly what `https://<domain>/api/cart/add`\n * resolves to via the dev proxy.\n *\n * @example\n * // Default API routes - routes/api.ts loaded at /api/*\n * 'api': 'api',\n *\n * // Auto prefix from key - routes/v1.ts loaded at /v1/*\n * 'v1': 'v1',\n *\n * // Explicit prefix - routes/api/v1.ts loaded at /api/v1/*\n * 'legacy': { path: 'api/v1', prefix: '/api/v1' },\n *\n * // No prefix override - routes/internal.ts loaded at /* (no prefix)\n * 'internal': { path: 'internal', prefix: '' },\n *\n * // With middleware - routes/admin.ts loaded at /admin/* with auth\n * 'admin': { path: 'admin', middleware: ['auth'] },\n */\nexport default {\n // Default API routes — auto-prefixed with /api by the route-loader\n // so `routes/api.ts` aligns with the proxy forward path.\n 'api': 'api',\n\n // Add versioned or prefixed routes here:\n 'v1': { path: 'v1', prefix: 'v1' }\n // 'v2': 'api/v2',\n // 'admin': { path: 'admin', middleware: ['auth'] },\n} satisfies RouteRegistry\n"
10
- ],
11
- "mappings": "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",
12
- "debugId": "6C513974EBA008C964756E2164756E21",
13
- "names": []
14
- }