@rebasepro/server 0.22.0 → 0.23.0

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.
Files changed (114) hide show
  1. package/dist/{GCSStorageController-CLIJXwGS.js → GCSStorageController-CjrA4PMo.js} +37 -23
  2. package/dist/GCSStorageController-CjrA4PMo.js.map +1 -0
  3. package/dist/{S3StorageController-Dcuf8lMA.js → S3StorageController-B6pKDNVj.js} +2 -2
  4. package/dist/{S3StorageController-Dcuf8lMA.js.map → S3StorageController-B6pKDNVj.js.map} +1 -1
  5. package/dist/api/ast-schema-editor.d.ts +35 -0
  6. package/dist/api/live-schema-routes.d.ts +14 -0
  7. package/dist/api/rest/api-generator.d.ts +117 -35
  8. package/dist/api/rest/auth-collection-writes.d.ts +85 -0
  9. package/dist/api/rest/field-access-query.d.ts +6 -2
  10. package/dist/api/rest/idempotency.d.ts +7 -1
  11. package/dist/api/rest/nested-write-access.d.ts +46 -0
  12. package/dist/api/rest/write-validation.d.ts +32 -0
  13. package/dist/{ast-schema-editor-CslO8Oje.js → ast-schema-editor-Mvr50v_S.js} +104 -4
  14. package/dist/ast-schema-editor-Mvr50v_S.js.map +1 -0
  15. package/dist/auth/address-ownership.d.ts +53 -0
  16. package/dist/auth/admin-user-ops.d.ts +35 -2
  17. package/dist/auth/auth-hooks.d.ts +4 -0
  18. package/dist/auth/captcha.d.ts +5 -0
  19. package/dist/auth/interfaces.d.ts +36 -6
  20. package/dist/auth/jwt.d.ts +17 -0
  21. package/dist/auth/oauth-signin-policy.d.ts +25 -8
  22. package/dist/auth/rate-limiter.d.ts +31 -1
  23. package/dist/auth/session-routes.d.ts +7 -0
  24. package/dist/auth/token-revocation.d.ts +4 -1
  25. package/dist/{auth-CCDpk2rn.js → auth-B-GIMpDG.js} +639 -228
  26. package/dist/auth-B-GIMpDG.js.map +1 -0
  27. package/dist/backend-DTAOsLQc.js +30 -0
  28. package/dist/backend-DTAOsLQc.js.map +1 -0
  29. package/dist/backup/backup-common.d.ts +19 -0
  30. package/dist/{backup-DzI9jLwc.js → backup-D7YR94N3.js} +69 -8
  31. package/dist/backup-D7YR94N3.js.map +1 -0
  32. package/dist/boot/driver.d.ts +10 -0
  33. package/dist/boot/env.d.ts +2 -2
  34. package/dist/boot/fetch-bundle.d.ts +18 -1
  35. package/dist/boot/rls-audit-option.d.ts +26 -0
  36. package/dist/boot/sources.d.ts +1 -0
  37. package/dist/{contract-routes-eLxV0le1.js → contract-routes-CbFjuBwa.js} +2 -2
  38. package/dist/{contract-routes-eLxV0le1.js.map → contract-routes-CbFjuBwa.js.map} +1 -1
  39. package/dist/cron/cron-routes.d.ts +7 -2
  40. package/dist/cron/cron-scheduler.d.ts +128 -8
  41. package/dist/cron/cron-store.d.ts +72 -8
  42. package/dist/cron/index.d.ts +1 -1
  43. package/dist/{cron-loader-CQjvjpEw.js → cron-loader-DfTj2Hbi.js} +2 -2
  44. package/dist/{cron-loader-CQjvjpEw.js.map → cron-loader-DfTj2Hbi.js.map} +1 -1
  45. package/dist/{cron-routes-B7CRGfiq.js → cron-routes-eE8nif_b.js} +33 -12
  46. package/dist/cron-routes-eE8nif_b.js.map +1 -0
  47. package/dist/{cron-scheduler-COPQxlEq.js → cron-scheduler-B0pLfAix.js} +394 -68
  48. package/dist/cron-scheduler-B0pLfAix.js.map +1 -0
  49. package/dist/{cron-store-BYGZFNWk.js → cron-store-TcoGz-xS.js} +133 -12
  50. package/dist/cron-store-TcoGz-xS.js.map +1 -0
  51. package/dist/{ddl-bootstrap-CfNvxMuK.js → ddl-bootstrap-C6mo0Kmz.js} +2 -25
  52. package/dist/ddl-bootstrap-C6mo0Kmz.js.map +1 -0
  53. package/dist/email/link-base.d.ts +5 -4
  54. package/dist/email/smtp-email-service.d.ts +13 -1
  55. package/dist/email/templates.d.ts +9 -0
  56. package/dist/email/types.d.ts +3 -2
  57. package/dist/env.d.ts +24 -5
  58. package/dist/{history-recorder-BQmB0P_j.js → history-recorder-B4MpJfJK.js} +8 -6
  59. package/dist/history-recorder-B4MpJfJK.js.map +1 -0
  60. package/dist/{history-store-CetkrBBD.js → history-store-BhxWOuz9.js} +2 -2
  61. package/dist/{history-store-CetkrBBD.js.map → history-store-BhxWOuz9.js.map} +1 -1
  62. package/dist/index.d.ts +6 -2
  63. package/dist/index.es.js +2717 -866
  64. package/dist/index.es.js.map +1 -1
  65. package/dist/init/docs.d.ts +5 -2
  66. package/dist/init/shutdown.d.ts +8 -3
  67. package/dist/jobs/index.d.ts +2 -2
  68. package/dist/jobs/job-queue.d.ts +23 -2
  69. package/dist/jobs/job-store.d.ts +37 -5
  70. package/dist/jobs/types.d.ts +8 -6
  71. package/dist/{jobs-Bjr8DZAi.js → jobs-CazMYhyy.js} +305 -166
  72. package/dist/jobs-CazMYhyy.js.map +1 -0
  73. package/dist/{jwt-C4OW-DNq.js → jwt-DnQHNFCl.js} +77 -25
  74. package/dist/{jwt-C4OW-DNq.js.map → jwt-DnQHNFCl.js.map} +1 -1
  75. package/dist/{keys-Qfc4XieN.js → keys-CogCQpxG.js} +18 -3
  76. package/dist/{keys-Qfc4XieN.js.map → keys-CogCQpxG.js.map} +1 -1
  77. package/dist/{logs-routes-3EEzPjhl.js → logs-routes-Bj4TYYUl.js} +7 -4
  78. package/dist/{logs-routes-3EEzPjhl.js.map → logs-routes-Bj4TYYUl.js.map} +1 -1
  79. package/dist/mcp/mcp-routes.d.ts +38 -2
  80. package/dist/mcp/mcp-tools.d.ts +7 -1
  81. package/dist/mcp/oauth-routes.d.ts +27 -0
  82. package/dist/mcp/oauth-store.d.ts +29 -13
  83. package/dist/metrics/history-recorder.d.ts +1 -1
  84. package/dist/{openapi-generator-D8uFz-LW.js → openapi-generator-O_O24MAT.js} +34 -12
  85. package/dist/openapi-generator-O_O24MAT.js.map +1 -0
  86. package/dist/{query-parser-BleZmY18.js → query-parser-DGRVFNM3.js} +39 -30
  87. package/dist/query-parser-DGRVFNM3.js.map +1 -0
  88. package/dist/rls-audit/index.d.ts +4 -0
  89. package/dist/{schema-editor-routes-DdLihzp0.js → schema-editor-routes-C5-lh_jO.js} +2 -2
  90. package/dist/{schema-editor-routes-DdLihzp0.js.map → schema-editor-routes-C5-lh_jO.js.map} +1 -1
  91. package/dist/{src-1vL-I1Po.js → src-pmvW7BFx.js} +122 -14
  92. package/dist/src-pmvW7BFx.js.map +1 -0
  93. package/dist/{src-Br6ARbs6.js → src-vkcwKXbT.js} +2 -27
  94. package/dist/{src-Br6ARbs6.js.map → src-vkcwKXbT.js.map} +1 -1
  95. package/dist/storage/GCSStorageController.d.ts +11 -1
  96. package/dist/storage/keys.d.ts +12 -0
  97. package/dist/storage/rendition-cache.d.ts +11 -1
  98. package/dist/storage/request-keys.d.ts +67 -0
  99. package/dist/storage/types.d.ts +17 -1
  100. package/dist/types-BfKcm9do.js.map +1 -1
  101. package/package.json +9 -9
  102. package/dist/GCSStorageController-CLIJXwGS.js.map +0 -1
  103. package/dist/ast-schema-editor-CslO8Oje.js.map +0 -1
  104. package/dist/auth-CCDpk2rn.js.map +0 -1
  105. package/dist/backup-DzI9jLwc.js.map +0 -1
  106. package/dist/cron-routes-B7CRGfiq.js.map +0 -1
  107. package/dist/cron-scheduler-COPQxlEq.js.map +0 -1
  108. package/dist/cron-store-BYGZFNWk.js.map +0 -1
  109. package/dist/ddl-bootstrap-CfNvxMuK.js.map +0 -1
  110. package/dist/history-recorder-BQmB0P_j.js.map +0 -1
  111. package/dist/jobs-Bjr8DZAi.js.map +0 -1
  112. package/dist/openapi-generator-D8uFz-LW.js.map +0 -1
  113. package/dist/query-parser-BleZmY18.js.map +0 -1
  114. package/dist/src-1vL-I1Po.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"openapi-generator-D8uFz-LW.js","names":[],"sources":["../src/api/openapi-generator.ts"],"sourcesContent":["import { CollectionConfig, Property, StringProperty, NumberProperty, ArrayProperty, MapProperty, isToMany, ResolvedRelation, VectorProperty, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from \"@rebasepro/types\";\nimport { effectiveAccess, fieldKeyForColumn, findRelation, getTenantConfig, isRelationRequired, resolveCollectionRelations } from \"@rebasepro/common\";\n\n/**\n * OpenAPI 3.0.3 specification generator.\n *\n * Produces a spec that exactly mirrors the REST API consumed by the\n * Rebase SDK client (`@rebasepro/client`).\n *\n * Routes are mounted at `{basePath}/data/{slug}` by `initializeRebaseBackend`.\n */\n\nexport interface OpenApiGeneratorOptions {\n /** Base path for the API (e.g. \"/api\"). Defaults to \"/api\". */\n basePath?: string;\n /** Whether auth is enabled on data routes. Defaults to true. */\n requireAuth?: boolean;\n /**\n * The list-pagination bounds the REST layer applies, so the spec states the\n * ones a request will actually meet.\n *\n * These were hardcoded as `default: 20, maximum: 100` — neither of which\n * the server has ever used. The spec drives the API Explorer and is what a\n * generated client is built from, so an understated ceiling is a request\n * the client refuses to make, and an overstated one is a 400 nobody\n * predicted.\n */\n listLimits?: { defaultLimit?: number; maxLimit?: number };\n}\n\nexport function generateOpenApiSpec(\n collections: CollectionConfig[],\n options: OpenApiGeneratorOptions = {}\n): Record<string, unknown> {\n const basePath = options.basePath ?? \"/api\";\n const requireAuth = options.requireAuth ?? true;\n const defaultLimit = options.listLimits?.defaultLimit ?? DEFAULT_LIST_LIMIT;\n const maxLimit = options.listLimits?.maxLimit ?? MAX_LIST_LIMIT;\n\n /**\n * The query parameters every list endpoint honours.\n *\n * Written once because it was written twice: the root listing named eight\n * and the subcollection listing named four, though both go through the same\n * `parseQueryOptions` and the same fetch. Four capabilities were therefore\n * unreachable from a generated client on nested routes, and `or`/`and` were\n * undocumented on both.\n */\n const listQueryParameters = () => [\n { name: \"limit\", in: \"query\", schema: { type: \"integer\", default: defaultLimit, minimum: 1, maximum: maxLimit },\n description: `Maximum number of records to return. Must be a whole number between 1 and ${maxLimit}; a larger one is rejected with 400 INVALID_LIMIT rather than trimmed, so a short page always means a short collection. Page past the ceiling with \\`offset\\`.` },\n { name: \"offset\", in: \"query\", schema: { type: \"integer\", default: 0 },\n description: \"Number of records to skip\" },\n { name: \"page\", in: \"query\", schema: { type: \"integer\", minimum: 1 },\n description: \"Page number (alternative to offset). Calculates offset as (page-1)*limit\" },\n {\n name: \"after\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Keyset cursor: continue after the row the previous page ended on. Pass back \"\n + \"`meta.nextCursor` from that response, unchanged — it is opaque, and encodes both the \"\n + \"sort keys and the last row's values for them. Unlike `offset`, a row inserted or \"\n + \"deleted before the cursor cannot shift the window, so a walk neither repeats nor \"\n + \"skips rows. Cannot be combined with `offset`/`page` (400 CURSOR_WITH_OFFSET), and an \"\n + \"`orderBy` different from the one the cursor was issued under is refused \"\n + \"(400 CURSOR_ORDER_MISMATCH) rather than seeked in an order nobody asked for.\"\n },\n {\n name: \"orderBy\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Sort field and direction. Accepts `field:asc`, `field:desc`, or `field:desc:last` — \"\n + \"the third segment places NULLs (`first`/`last`), defaulting to Postgres's own \"\n + \"convention (last ascending, first descending). Also accepts a JSON array \"\n + \"`[{\\\"field\\\":\\\"name\\\",\\\"direction\\\":\\\"asc\\\",\\\"nulls\\\":\\\"last\\\"}]` — several entries sort by \"\n + \"each in turn, the second breaking ties on the first.\",\n example: \"created_at:desc:last\"\n },\n {\n name: \"where\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"JSON object filter, mapping each field to a `[operator, value]` tuple. \"\n + \"Combines with the per-field `?field=op.value` parameters below; on the same field, the per-field parameter wins.\",\n example: \"{\\\"status\\\":[\\\"==\\\",\\\"active\\\"]}\"\n },\n {\n name: \"or\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Disjunction of conditions, AND-ed with `where` and `searchString`.\",\n example: \"(status.eq.draft,status.eq.review)\"\n },\n {\n name: \"and\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Conjunction of conditions, AND-ed with `where` and `searchString`. Ignored when `or` is also present.\",\n example: \"(views.gte.10,status.eq.draft)\"\n },\n {\n name: \"not\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Negation, AND-ed with `where` and `searchString`. Negates the **conjunction** of its \"\n + \"conditions: `not(a)` is `NOT a`, `not(a,b)` is `NOT (a AND b)`. Groups nest, so \"\n + \"`not(or(a,b))` is the De Morgan case. Compiles to a real SQL `NOT (...)`, which — \"\n + \"three-valued logic — also excludes rows whose column is NULL. Ignored when `or` or \"\n + \"`and` is also present.\",\n example: \"(status.eq.draft,views.gte.10)\"\n },\n {\n name: \"include\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Relations to load, in either of two spellings. **Comma-separated names or dotted \"\n + \"paths** — `author,comments.author`, up to 3 hops deep; `*` loads every relation one \"\n + \"hop deep. **JSON**, when a relation needs narrowing — \"\n + \"`{\\\"comments\\\":{\\\"limit\\\":5,\\\"where\\\":{\\\"published\\\":[\\\"==\\\",true]},\"\n + \"\\\"orderBy\\\":\\\"created_at:desc\\\",\\\"fields\\\":\\\"id,body\\\",\\\"include\\\":{\\\"author\\\":true}}}`. \"\n + \"A value starting with `{` is read as the JSON form. A name that is not a relation of \"\n + \"the collection is a 400 UNKNOWN_RELATION, not a silently missing field.\",\n example: \"author,comments.author\"\n },\n {\n name: \"fields\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to return. A projection pushed into the SELECT, so a query \"\n + \"that needs two fields of a wide row reads two columns. The primary key is always \"\n + \"returned (a row that cannot be addressed cannot be updated, deleted, or paged past), \"\n + \"and `excludeFromApi` columns stay hidden whether or not they are named here. An \"\n + \"unknown column is a 400 UNKNOWN_FIELD.\",\n example: \"id,name,created_at\"\n },\n {\n name: \"distinct\",\n in: \"query\",\n schema: { type: \"boolean\" },\n description:\n \"`SELECT DISTINCT` over the returned columns. Only meaningful alongside `fields`: the \"\n + \"primary key is always in the projection, so without narrowing it every row is \"\n + \"already distinct. `meta.total` counts distinct rows too. Refused (400) alongside \"\n + \"`searchString` or a vector search, which attach a per-row score that makes every row \"\n + \"distinct by construction, and (400 DISTINCT_ORDER_BY_NOT_SELECTED) when `orderBy` \"\n + \"names a column `fields` does not return.\",\n example: \"true\"\n },\n {\n name: \"searchString\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Text search. By default a case-insensitive substring match OR-ed across the \" +\n \"collection's top-level string properties. A collection declaring a `search` block \" +\n \"gets ranked full-text matching over the fields it names, and rows carry a `_score`.\"\n },\n // Vector search has been served here since vectors landed and was\n // documented nowhere, so the only way to find it was to read the query\n // parser. All four are needed together; `vector_search` and `vector`\n // are ignored unless both are present.\n {\n name: \"vector_search\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Name of the `vector` property to run a nearest-neighbour search against. Requires `vector`.\",\n example: \"embedding\"\n },\n {\n name: \"vector\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"The query embedding, as a JSON array of numbers. Its length must match the property's declared `dimensions`.\",\n example: \"[0.12,-0.04,0.98]\"\n },\n {\n name: \"vector_distance\",\n in: \"query\",\n schema: { type: \"string\", enum: [\"cosine\", \"l2\", \"inner_product\"], default: \"cosine\" },\n description: \"Distance function used for ordering.\"\n },\n {\n name: \"vector_threshold\",\n in: \"query\",\n schema: { type: \"number\" },\n description: \"Drop rows farther than this distance. Rows are returned closest-first with a `_distance` field.\"\n }\n ];\n\n const spec: Record<string, unknown> = {\n openapi: \"3.0.3\",\n info: {\n title: \"Rebase API\",\n version: \"1.0.0\",\n description:\n \"Auto-generated REST API from Rebase collection definitions. \" +\n \"This is the same API consumed by the `@rebasepro/client` SDK.\"\n },\n servers: [\n {\n url: basePath,\n description: \"API Server\"\n }\n ],\n paths: {} as Record<string, unknown>,\n components: {\n schemas: {\n ErrorResponse: {\n type: \"object\",\n properties: {\n error: {\n type: \"object\",\n required: [\"message\", \"code\"],\n properties: {\n message: { type: \"string\" },\n code: { type: \"string\" },\n details: {}\n }\n }\n }\n },\n PaginationMeta: {\n type: \"object\",\n properties: {\n total: { type: \"integer\",\ndescription: \"Total number of matching records\" },\n limit: { type: \"integer\",\ndescription: \"Page size used for this query\" },\n offset: { type: \"integer\",\ndescription: \"Number of records skipped\" },\n hasMore: { type: \"boolean\",\ndescription: \"Whether more records exist beyond this page\" },\n nextCursor: {\n type: \"string\",\n description:\n \"Opaque keyset cursor continuing this listing — pass it back as `?after=`. \"\n + \"Present when `hasMore` is true and the page returned at least one row; \"\n + \"absent on the last page and on an ordering no cursor can describe \"\n + \"(relevance, whose scores are computed per query and not stored). Do not \"\n + \"parse it: the encoding exists to be changed.\"\n }\n }\n }\n } as Record<string, unknown>,\n securitySchemes: {} as Record<string, unknown>\n },\n tags: [] as Array<{ name: string; description?: string }>\n };\n\n // ── Security Schemes ─────────────────────────────────────────────────\n if (requireAuth) {\n (spec.components as Record<string, unknown>).securitySchemes = {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n bearerFormat: \"JWT\",\n description:\n \"JWT access token obtained from `POST /auth/login` or `POST /auth/register`. \" +\n \"Can also be a static service key for server-to-server authentication.\"\n }\n // No `?token=` scheme. It was declared here — globally, so on every\n // operation — and no data route has ever accepted one: both\n // `createAuthMiddleware` and `createAdapterAuthMiddleware` read the\n // `Authorization` header and nothing else, deliberately, because\n // URLs leak into access logs, proxies, Referer headers and browser\n // history (`auth/middleware.ts`). Following it cost a caller twice:\n // unauthenticated, *and* a 400, since `token` is not in the query\n // parser's `reservedQueryKeys` and so compiles as a filter on a\n // column named `token`. `queryTokenAuth` is real but is mounted\n // only on storage file serving, for `<img src>`; if those routes\n // are ever documented, the scheme belongs on them, per-operation.\n };\n (spec as Record<string, unknown>).security = [\n { bearerAuth: [] }\n ];\n }\n\n const paths = spec.paths as Record<string, unknown>;\n const schemas = (spec.components as Record<string, unknown>).schemas as Record<string, unknown>;\n const tags = spec.tags as Array<{ name: string; description?: string }>;\n\n // The names a listing has already spent. A collection is free to have a\n // `limit` or a `fields` column, and the query parser reads those names as\n // pagination and field selection before any filter is compiled — so the\n // per-field filter could never fire, and documenting it a second time put\n // two parameters with the same (`name`, `in`) pair on one operation, which\n // is invalid OpenAPI: Swagger UI renders a duplicate and several generators\n // abort. Taken from the parameter list itself so the two cannot drift.\n const reservedParameterNames = new Set(listQueryParameters().map(p => p.name));\n\n // Every component name this document will carry, known before the first\n // schema is built: a relation may point at a collection that appears later\n // in the list, or at one that is not documented here at all, and a `$ref`\n // at a component that does not exist is a document Swagger UI renders empty\n // and a strict generator refuses.\n const registeredSchemas = new Set((collections || []).map(schemaNameFor));\n\n /**\n * `Prefer: return=minimal`, on every route that would otherwise send a row\n * back. Documented rather than left implicit because a client generated\n * from this spec cannot send a header the spec does not mention.\n */\n const preferHeader = {\n name: \"Prefer\",\n in: \"header\",\n required: false,\n schema: { type: \"string\", enum: [\"return=minimal\"] },\n description:\n \"`return=minimal` asks the server not to send the written row back. Single writes then \" +\n \"answer `204 No Content`; bulk and batch writes answer `200` carrying the ids only. \" +\n \"The response repeats it in `Preference-Applied` when it was honoured.\"\n };\n\n const ifMatchHeader = {\n name: \"If-Match\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"The `ETag` this edit was made against, from the `GET` that read the row. The write is \" +\n \"refused with `412` if the row has changed since — which is the difference between \" +\n \"\\\"update the row I read\\\" and \\\"overwrite whatever is there now\\\". `*` means only that \" +\n \"the row must exist.\"\n };\n\n const preconditionFailed = {\n 412: {\n description:\n \"The row changed since the ETag in `If-Match` was issued. Nothing was written: \" +\n \"re-read the row, re-apply the change, and send the new ETag\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n }\n };\n\n const minimalResponse = {\n 204: {\n description: \"Written. `Prefer: return=minimal` was honoured, so there is no body\",\n headers: {\n \"Preference-Applied\": { schema: { type: \"string\" }, description: \"`return=minimal`\" }\n }\n }\n };\n\n // ── POST /data/_batch — writes across collections, one transaction ────\n //\n // Registered before the per-collection paths for the same reason the route\n // is: `_batch` is not a collection, and reading it as one would document a\n // table that does not exist.\n if ((collections || []).length > 0) {\n paths[\"/data/_batch\"] = {\n post: {\n tags: [\"Data\"],\n summary: \"Write across collections in one transaction\",\n description:\n \"All-or-nothing across collections — an order and its line items, a user and \" +\n \"their membership row. `/bulk` is one collection at a time, and sending the two \" +\n \"halves as separate requests is exactly the sequence that can half-succeed.\\n\\n\" +\n \"Operations run in order, each through the same pipeline its single-row route \" +\n \"uses: the same validation, callbacks and row-level security, as the same role. \" +\n \"An operation may name itself with `ref`, and a later one may stand \" +\n \"`{ \\\"$ref\\\": \\\"order.id\\\" }` wherever a value goes — in `values`, at any depth, \" +\n \"or as an `id`. Only backward references resolve.\\n\\n\" +\n \"Capped at the same number of entries as a bulk write, because one batch is one \" +\n \"transaction and holds its locks for the whole of it.\",\n operationId: \"batchWrite\",\n parameters: [\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this batch so a retry is recognised instead of repeated. Without it a \" +\n \"client that lost the response cannot tell a replay from a second batch, and \" +\n \"the whole batch is written twice.\"\n },\n preferHeader\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"operations\"],\n properties: {\n operations: {\n type: \"array\",\n items: { $ref: \"#/components/schemas/BatchOperation\" }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description:\n \"One entry per operation, in order: the written row for a create, update or \" +\n \"upsert, and `null` for a delete\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { type: \"object\", nullable: true } },\n meta: { type: \"object\", properties: { operations: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 400: {\n description:\n \"Malformed body, an unknown collection or field, an illegal field operation \" +\n \"or conflict target, a forward `$ref`, or more operations than the limit. \" +\n \"Nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 404: {\n description: \"An `update` or `delete` names a row that does not exist; nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n schemas.FieldOperation = {\n type: \"object\",\n description:\n \"A change to the column's current value rather than the value to store. Exactly one \" +\n \"operator per field. Only on an update: an operation over a value that does not exist \" +\n \"yet is refused with a 400.\",\n properties: {\n $inc: { type: \"number\", description: \"Add to a `number` column; negative to subtract.\" },\n $push: { description: \"Append a value, or each of an array of values, to an `array` column.\" },\n $pull: { description: \"Remove every occurrence of a value from an `array` column.\" },\n $merge: { type: \"object\", description: \"Shallow-merge an object into a `map` column.\" }\n }\n };\n\n schemas.BatchOperation = {\n type: \"object\",\n required: [\"op\", \"collection\"],\n properties: {\n op: { type: \"string\", enum: [\"create\", \"update\", \"upsert\", \"delete\"] },\n collection: { type: \"string\", description: \"The collection slug this operation writes to.\" },\n id: {\n description:\n \"Required for `update` and `delete`. May instead be a reference marker — an \" +\n \"object whose single key is `$ref` and whose value is `<ref name>.<field>`, \" +\n \"e.g. `{ \\\"$ref\\\": \\\"order.id\\\" }` — naming a column of the row an earlier \" +\n \"operation wrote. (Described rather than declared as a schema: `$ref` is a \" +\n \"reserved word to every OpenAPI reader, and a property named `$ref` is read \" +\n \"as a reference and mangled.)\",\n oneOf: [{ type: \"string\" }, { type: \"integer\" }, { type: \"object\" }]\n },\n values: {\n type: \"object\",\n description:\n \"The row's fields. Values may be `$ref` markers; an `update` may also carry \" +\n \"field operations.\",\n additionalProperties: true\n },\n onConflict: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"`upsert` only: the columns the conflict is matched on. Must carry a declared \" +\n \"uniqueness guarantee. Defaults to the primary key.\"\n },\n ref: {\n type: \"string\",\n description: \"Names this operation's result, so a later one can `$ref` its columns.\"\n }\n }\n };\n }\n\n // ── Collection routes ────────────────────────────────────────────────\n for (const collection of (collections || [])) {\n const schemaName = schemaNameFor(collection);\n const slug = collection.slug;\n\n tags.push({\n name: collection.name,\n description: collection.description || `CRUD operations for ${collection.name}`\n });\n\n // Build component schema for this collection\n schemas[schemaName] = buildCollectionSchema(collection, registeredSchemas);\n\n // Build an \"input\" schema (no read-only/auto fields like autoValue dates)\n schemas[`${schemaName}Input`] = buildCollectionInputSchema(collection);\n\n // The update body — same columns, no `required`. PATCH and PUT both\n // merge, so a field left out means \"unchanged\", not \"omitted by mistake\".\n schemas[`${schemaName}Update`] = buildCollectionUpdateSchema(collection);\n\n const dataPath = `/data/${slug}`;\n\n // ── GET /data/{slug}/count — How many rows match ──────────────\n //\n // Served for every collection since the route existed and described\n // here for the first time. A spec is what a generated client can see,\n // so an endpoint missing from it is an endpoint that client does not\n // have — and this is the one a paginating UI needs to know how many\n // pages there are.\n //\n // Registered before the list path so its literal segment cannot be read\n // as an `{id}`, which is the same ordering the router uses.\n paths[`${dataPath}/count`] = {\n get: {\n tags: [collection.name],\n summary: `Count ${collection.name}`,\n description:\n \"The number of rows the same filters would return, without returning them. \" +\n \"Takes the filter and search parameters of the list endpoint; `limit`, `offset` \" +\n \"and `orderBy` are not part of the question and are ignored.\",\n operationId: `count${schemaName}`,\n parameters: [\n ...listQueryParameters().filter(p => p.name === \"searchString\"),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"The number of matching rows\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"count\"],\n properties: {\n count: { type: \"integer\", description: \"Rows matching the filters\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── GET /data/{slug}/aggregate — count/sum/avg/min/max ────────\n //\n // Before the list path for the same reason `/count` is: a literal\n // segment that a generated client would otherwise be told is an `{id}`.\n paths[`${dataPath}/aggregate`] = {\n get: {\n tags: [collection.name],\n summary: `Aggregate ${collection.name}`,\n description:\n \"Aggregate values over the rows the same filters would return. Takes the filter and \" +\n \"search parameters of the list endpoint. Row-level security applies to the rows \" +\n \"being aggregated, so a caller who can read nothing counts nothing.\",\n operationId: `aggregate${schemaName}`,\n parameters: [\n {\n name: \"select\",\n in: \"query\",\n required: true,\n description:\n \"Comma-separated aggregates, e.g. `count()`, `sum(total)`, `avg(total),max(total)`. \" +\n \"Results are keyed `count`, `sum_total`, `avg_total` and so on.\",\n schema: { type: \"string\" },\n example: \"count(),sum(total)\"\n },\n {\n name: \"groupBy\",\n in: \"query\",\n required: false,\n description: \"Comma-separated fields to group by. Each grouped field is returned alongside the aggregates.\",\n schema: { type: \"string\" },\n example: \"status\"\n },\n ...listQueryParameters().filter(p => p.name === \"searchString\" || p.name === \"limit\"),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"One row per group, or a single row when `groupBy` is absent\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"data\"],\n properties: {\n data: {\n type: \"array\",\n items: { type: \"object\", additionalProperties: true }\n }\n }\n }\n }\n }\n },\n 501: { description: \"This backend's data driver does not implement aggregates\" },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── GET /data/{slug} — List entities ──────────────────────────\n paths[dataPath] = {\n get: {\n tags: [collection.name],\n summary: `List ${collection.name}`,\n operationId: `list${schemaName}`,\n parameters: [\n ...listQueryParameters(),\n ...buildFilterParameters(collection, reservedParameterNames)\n ],\n responses: {\n 200: {\n description: \"Paginated list of entities\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: {\n type: \"array\",\n items: { $ref: `#/components/schemas/${schemaName}` }\n },\n meta: { $ref: \"#/components/schemas/PaginationMeta\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n },\n post: {\n tags: [collection.name],\n summary: `Create ${collection.singularName || collection.name}`,\n operationId: `create${schemaName}`,\n parameters: [\n {\n name: \"on_conflict\",\n in: \"query\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to upsert on, turning the create into \" +\n \"INSERT ... ON CONFLICT DO UPDATE. They must carry a declared uniqueness \" +\n \"guarantee — `validation.unique`, a `unique` index, or the primary key — \" +\n \"or the request is refused with a 400 naming the targets that do exist. \" +\n \"Left off, this is a plain insert and a duplicate key still raises.\",\n example: \"email\"\n },\n preferHeader\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}Input` }\n }\n }\n },\n responses: {\n 201: {\n description: \"Created entity\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n ...minimalResponse,\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── Bulk: one transaction, all-or-nothing ─────────────────────\n //\n // These went undocumented while they existed, which is the same defect\n // the update verb had: an endpoint the server serves and the spec does\n // not mention cannot be reached by a generated client at all.\n const idempotencyHeader = {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this write so a retry is recognised instead of repeated. Without it a \" +\n \"client that lost the response cannot distinguish a replay from a second \" +\n \"genuine batch, and the whole batch is written twice. A key names one request: \" +\n \"re-send the identical request to replay its answer, and use a new key for a \" +\n \"different one.\"\n };\n\n const bulkErrors = {\n 400: {\n description: \"Malformed body, an unknown field, or more rows than the per-batch limit\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description:\n \"A request with the same Idempotency-Key is still in flight. Retry it: the \" +\n \"first attempt's result is replayed once it lands\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...errorResponses(requireAuth)\n };\n\n paths[`/data/${slug}/bulk`] = {\n post: {\n tags: [collection.name],\n summary: `Create many ${collection.name} in one transaction`,\n description:\n \"All-or-nothing: if any row is rejected none of them land, and the error names \" +\n \"the offending index. Every row still runs callbacks, relations and row-level \" +\n \"security. Capped server-side because one batch holds its locks for its whole \" +\n \"duration.\",\n operationId: `createMany${schemaName}`,\n parameters: [idempotencyHeader, preferHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"rows\"],\n properties: {\n rows: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}Input` } },\n upsert: {\n type: \"boolean\",\n description: \"Write each row as INSERT ... ON CONFLICT DO UPDATE.\"\n },\n onConflict: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"The columns the conflict is matched on, instead of the primary key. \" +\n \"They must carry a declared uniqueness guarantee. Naming them without \" +\n \"`upsert: true` is a 400 rather than a silently ignored field.\"\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"The written rows, in the order given\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}` } },\n meta: { type: \"object\", properties: { written: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n ...bulkErrors\n }\n },\n patch: {\n tags: [collection.name],\n summary: `Update many ${collection.name} in one transaction`,\n description:\n \"Each entry names its row and the fields to change. `{ id, data }` rather than \" +\n \"flat rows carrying their own key, because on a table keyed on something other \" +\n \"than `id` a flat row cannot say whether a column is the address or a value to \" +\n \"write. An id matching no row fails the batch.\",\n operationId: `updateMany${schemaName}`,\n parameters: [idempotencyHeader, preferHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"updates\"],\n properties: {\n updates: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"id\", \"data\"],\n properties: {\n id: { type: \"string\", description: \"The row to update\" },\n data: { $ref: `#/components/schemas/${schemaName}Update` }\n }\n }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"The updated rows, in the order given\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: { type: \"array\", items: { $ref: `#/components/schemas/${schemaName}` } },\n meta: { type: \"object\", properties: { written: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 404: {\n description: \"One of the ids matches no row; nothing was written\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...bulkErrors\n }\n }\n };\n\n paths[`/data/${slug}/bulk/delete`] = {\n post: {\n tags: [collection.name],\n summary: `Delete many ${collection.name} in one transaction`,\n description:\n \"A POST, not `DELETE /bulk` with a body. Bodies on DELETE are permitted but \" +\n \"widely dropped by proxies and CDNs, and several generators ignore \" +\n \"`requestBody` on a DELETE operation — a generated client would send the \" +\n \"request with no ids at all. Takes ids rather than a filter: a mistyped \" +\n \"condition that empties a table cannot be reviewed at the call site the way \" +\n \"an explicit list can. `beforeDelete`/`afterDelete` fire per row.\",\n operationId: `deleteMany${schemaName}`,\n parameters: [idempotencyHeader],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n required: [\"ids\"],\n properties: {\n ids: {\n type: \"array\",\n items: { oneOf: [{ type: \"string\" }, { type: \"integer\" }] }\n }\n }\n }\n }\n }\n },\n responses: {\n 200: {\n description: \"How many rows were deleted\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n meta: { type: \"object\", properties: { deleted: { type: \"integer\" } } }\n }\n }\n }\n }\n },\n 404: {\n description: \"One of the ids matches no row; nothing was deleted\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...bulkErrors\n }\n }\n };\n\n // ── GET/PUT/DELETE /data/{slug}/{id} ──────────────────────────\n const entityPath = `/data/${slug}/{id}`;\n paths[entityPath] = {\n get: {\n tags: [collection.name],\n summary: `Get ${collection.singularName || collection.name} by ID`,\n operationId: `get${schemaName}ById`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" },\n // The same two parameters the list route documents, and the\n // same code serves them — one row and a page of them go\n // through one pipeline, so anything true of `include` or\n // `fields` there is true here.\n {\n name: \"include\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Relations to load: comma-separated names or dotted paths \"\n + \"(`author,comments.author`, up to 3 hops), `*` for all one hop deep, or the \"\n + \"JSON form for per-relation `limit`/`where`/`orderBy`/`fields`. An unknown \"\n + \"name is a 400 UNKNOWN_RELATION.\",\n example: \"author,comments.author\"\n },\n {\n name: \"fields\",\n in: \"query\",\n schema: { type: \"string\" },\n description:\n \"Comma-separated columns to return, as a SELECT projection. The primary key \"\n + \"always survives and `excludeFromApi` columns stay hidden.\",\n example: \"id,title\"\n }\n ],\n responses: {\n 200: {\n description: \"Entity found\",\n headers: {\n ETag: {\n schema: { type: \"string\" },\n description:\n \"This row's version. Send it back as `If-Match` on a later PATCH or \" +\n \"DELETE to have the write refused if the row has changed in between.\"\n }\n },\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n ...errorResponses(requireAuth)\n }\n },\n patch: updateOperation(collection, schemaName, requireAuth, {\n ifMatchHeader,\n preferHeader,\n preconditionFailed,\n minimalResponse\n }),\n delete: {\n tags: [collection.name],\n summary: `Delete ${collection.singularName || collection.name}`,\n operationId: `delete${schemaName}`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" },\n ifMatchHeader,\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this delete so a retry replays its answer. A delete replayed after \" +\n \"the first attempt committed would otherwise answer 404 — which an offline \" +\n \"queue reads as a permanent failure for a delete that in fact succeeded.\"\n }\n ],\n responses: {\n 204: { description: \"Deleted successfully\" },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...preconditionFailed,\n ...errorResponses(requireAuth)\n }\n }\n };\n\n }\n\n // ── Subcollection routes ─────────────────────────────────────────────\n //\n // A second pass, after every collection's component schema exists. These\n // routes `$ref` the *target's* schema, and the first pass builds schemas in\n // array order — so doing this inline meant a subcollection whose target\n // appeared later in the list silently degraded to an untyped `object`.\n //\n // The names come from the *resolved* relations, not from the authored\n // `relations` array. `relationName` is optional at the authoring surface —\n // it defaults to the property key, or to the target's slug — so reading the\n // raw field skipped every relation that relied on the default, and missed\n // relations declared inline on a property entirely, since those are not in\n // the array. These are the same resolved names the nested-path router\n // matches, so the spec and the routes cannot drift apart.\n //\n // A to-one relation is left out. `posts/1/author` resolves, but it\n // addresses a single row, and documenting it as a paginated list would\n // describe a response shape the client never gets.\n for (const collection of (collections || [])) {\n const slug = collection.slug;\n const schemaName = schemaNameFor(collection);\n const relations = Object.values(resolveCollectionRelations(collection))\n .filter(isToMany);\n for (const relation of relations) {\n const relationName = relation.relationName;\n const targetCollection = relation.target();\n const targetSchema = schemaNameFor(targetCollection);\n\n const subPath = `/data/${slug}/{parentId}/${relationName}`;\n\n // Only add if the schema exists (target collection is also registered)\n paths[subPath] = {\n get: {\n tags: [collection.name],\n summary: `List ${relationName} for ${withIndefiniteArticle(collection.singularName || collection.name)}`,\n operationId: `list${schemaName}${toPascalCase(relationName)}`,\n parameters: [\n { name: \"parentId\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: `${collection.singularName || collection.name} ID` },\n // The nested list handler goes through the same\n // `parseQueryOptions` and the same fetch as the root\n // one, so it honours the same parameters. It documented\n // four of them.\n ...listQueryParameters()\n ],\n responses: {\n 200: {\n description: `List of related ${relationName}`,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: {\n type: \"array\",\n items: schemas[targetSchema]\n ? { $ref: `#/components/schemas/${targetSchema}` }\n : { type: \"object\" }\n },\n meta: { $ref: \"#/components/schemas/PaginationMeta\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n }\n }\n\n return spec;\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Is this property part of the shape the document may describe?\n *\n * One exclusion, and it is the document telling the truth about what the server\n * does: `excludeFromApi` is a server-side guarantee that the column \"is\n * stripped from every row the API serves, for every caller, including admins\n * and service keys\" — `stripExcluded` in the row pipeline enforces it. A schema\n * that lists such a column describes a field that is never present, and it\n * describes it to everyone: `/docs` is mounted on the app, not on the data\n * router, so it carries none of the auth middleware `{basePath}/data` does.\n * Every project scaffolded by `rebase init` published its `users` collection's\n * `passwordHash` and `emailVerificationToken` this way.\n *\n * A `relation` property used to be excluded here too, on the reasoning that the\n * property is virtual. It is — but the row is not empty where it stands: the\n * owning side carries a foreign key under a *wire* name (`authorId`), and a\n * read that includes the relation carries the target's row under the relation's\n * own name. Excluding both left `/api/docs` describing a strictly smaller\n * collection than `generated/sdk/database.types.ts` did for the same config.\n * Relations are now emitted by {@link emitRelationProperties}, under the same\n * keys and in the same order as the SDK's `Row`; the direct-property loops skip\n * them so the two passes cannot emit one key twice.\n *\n * Written as one predicate rather than a `continue` per loop because it kept\n * being fixed in one loop at a time: this is the same rule the SDK generator\n * applies to its `Row` type (`packages/codegen/src/generate-types.ts`).\n */\nfunction isDocumentedProperty(property: Property, direction: \"read\" | \"write\" = \"read\"): boolean {\n const access = effectiveAccess(property);\n return access?.[direction]?.length !== 0;\n}\n\n/**\n * The `x-rebase-access` annotation, or nothing for a field with no rule.\n *\n * A vendor extension rather than a schema keyword because OpenAPI has no way to\n * say \"this property is present for some callers\" — `readOnly` is about the\n * direction of a field, not about who. Generators ignore what they do not know,\n * so a client built from this spec still compiles; a *human* reading `/docs`, or\n * a gateway that wants to enforce the same rule at the edge, gets the role lists\n * verbatim. Emitted for the role case only: a field closed to everybody is\n * absent from the document entirely, which is a stronger statement.\n */\nfunction accessAnnotation(property: Property): Record<string, unknown> | undefined {\n const access = effectiveAccess(property);\n if (!access) return undefined;\n const annotation: Record<string, unknown> = {};\n if (access.read !== undefined) annotation.read = [...access.read];\n if (access.write !== undefined) annotation.write = [...access.write];\n return Object.keys(annotation).length > 0 ? annotation : undefined;\n}\n\n/** The sentence `x-rebase-access` deserves in prose, for a reader of `/docs`. */\nfunction accessDescription(property: Property): string | undefined {\n const access = effectiveAccess(property);\n if (!access) return undefined;\n const parts: string[] = [];\n const phrase = (roles: readonly string[]) =>\n roles.length === 0 ? \"nobody through the API\" : roles.map(r => `\\`${r}\\``).join(\", \") + \" (and `admin`)\";\n if (access.read !== undefined) parts.push(`readable by ${phrase(access.read)}`);\n if (access.write !== undefined) parts.push(`writable by ${phrase(access.write)}`);\n if (parts.length === 0) return undefined;\n return `Field access: ${parts.join(\"; \")}. A caller without the role does not receive the field at all — it is absent, not null.`;\n}\n\n/**\n * The keys `stripExcluded` deletes: the property name *and* its column name.\n *\n * Seeding the emitted set with both is what stops a foreign key derived from a\n * relation putting an `excludeFromApi` column back on the surface under its\n * other name. Same pair, same reason, as `excludedApiKeys` in the SDK\n * generator.\n */\nfunction excludedApiKeys(collection: CollectionConfig, direction: \"read\" | \"write\" = \"read\"): Set<string> {\n const excluded = new Set<string>();\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if (isDocumentedProperty(property as Property, direction)) continue;\n excluded.add(key);\n const columnName = (property as { columnName?: unknown }).columnName;\n if (typeof columnName === \"string\") excluded.add(columnName);\n }\n return excluded;\n}\n\n/**\n * The collection's primary key, as the key it is addressed by on the wire and\n * the property that declares it.\n *\n * `id` was a literal in three places — seeded before the read loop, assigned\n * after the input loop, inherited by the update schema from the input one — and\n * the two spellings disagreed: a declared `id: { type: \"number\" }` overwrote\n * the read seed and was overwritten by the input assignment, so the same field\n * was `integer` in `Post` and `string` in `PostInput`. One helper now answers\n * the question for all three.\n */\nfunction idPropertyEntry(collection: CollectionConfig | undefined): [string, Property] | undefined {\n for (const [key, property] of Object.entries(collection?.properties ?? {})) {\n if ((property as Record<string, unknown>)?.isId) return [key, property as Property];\n }\n return undefined;\n}\n\n/**\n * The schema of a primary key or of a foreign key pointing at one: the declared\n * property's own type, stripped of the field-level facts (description,\n * validation bounds) that belong to the column and not to a reference to it.\n *\n * Falls back to `string` for a collection that declares no primary key, which\n * is what every schema here assumed unconditionally before.\n */\nfunction idSchemaFor(collection: CollectionConfig | undefined): Record<string, unknown> {\n const declared = idPropertyEntry(collection);\n if (!declared) return { type: \"string\" };\n const converted = convertPropertyToSchema(declared[1]);\n const schema: Record<string, unknown> = { type: converted.type ?? \"string\" };\n if (converted.format) schema.format = converted.format;\n return schema;\n}\n\n/**\n * Relations resolve or they do not; a document is still owed for a collection\n * whose target thunk throws (a circular import, usually). The SDK generator\n * warns and carries on with no relation fields, and so does this — the\n * alternative is `/api/docs` 500ing for the whole project.\n */\nfunction resolveRelationsForDocument(collection: CollectionConfig): Record<string, ResolvedRelation> {\n try {\n return resolveCollectionRelations(collection);\n } catch {\n return {};\n }\n}\n\n/** Unwrap a target handed back as a module namespace — `() => import(\"./authors\")`. */\nfunction relationTarget(relation: ResolvedRelation): CollectionConfig | undefined {\n try {\n let target = relation.target() as CollectionConfig & { default?: CollectionConfig; __esModule?: boolean };\n if (target && (target.default || target.__esModule)) {\n target = (target.default ?? target) as typeof target;\n }\n return target;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The schema an *included* relation arrives as: the target's own row.\n *\n * `$ref` when the target is one of the collections this document describes, and\n * an open object when it is not — a dangling pointer makes Swagger UI render an\n * empty model and makes a strict generator abort, which is worse than a vague\n * one.\n */\nfunction includedRelationSchema(\n relation: ResolvedRelation,\n registeredSchemas: ReadonlySet<string>\n): Record<string, unknown> {\n const target = relationTarget(relation);\n const targetSchema = target ? schemaNameFor(target) : undefined;\n const item: Record<string, unknown> = targetSchema && registeredSchemas.has(targetSchema)\n ? { $ref: `#/components/schemas/${targetSchema}` }\n : { type: \"object\" };\n return relation.cardinality === \"many\" ? { type: \"array\", items: item } : item;\n}\n\n/**\n * Emit a collection's relations onto a read schema: the foreign keys first,\n * then the relations themselves.\n *\n * The order and the keys are the SDK `Row`'s, deliberately — the parity test\n * next door compares the two key sets, and the only way that stays true is for\n * both to be derived the same way rather than kept in step by hand.\n *\n * A `belongsTo` reaches the wire under the *field* name of its local column\n * (`author_id` → `authorId`), which is what `fieldKeyForColumn` answers; a\n * relation addressed by the same name as its own foreign key is served as\n * either the scalar or the nested row depending on `include`, so it is\n * documented as both.\n */\nfunction emitRelationProperties(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n required: string[],\n emitted: Set<string>,\n registeredSchemas: ReadonlySet<string>\n): void {\n const resolved = resolveRelationsForDocument(collection);\n\n for (const [relationKey, relation] of Object.entries(resolved)) {\n if (relation.kind !== \"belongsTo\" || !relation.localKey) continue;\n const fieldKey = fieldKeyForColumn(collection, relation.localKey);\n if (emitted.has(fieldKey)) continue;\n\n const foreignKey = idSchemaFor(relationTarget(relation));\n const shadowedByInclude = relationKey === fieldKey;\n properties[fieldKey] = shadowedByInclude\n ? { oneOf: [foreignKey, includedRelationSchema(relation, registeredSchemas)] }\n : { ...foreignKey, description: `Foreign key into \\`${relation.targetSlug}\\`` };\n emitted.add(fieldKey);\n\n if (isRelationRequired(collection, relation) && !shadowedByInclude) required.push(fieldKey);\n }\n\n for (const [key, relation] of Object.entries(resolved)) {\n if (emitted.has(key)) continue;\n properties[key] = includedRelationSchema(relation, registeredSchemas);\n emitted.add(key);\n }\n\n // A `relation` property whose relation did not resolve. Still a field of the\n // row, just not a precisely describable one.\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n if (emitted.has(key)) continue;\n properties[key] = { type: \"object\" };\n emitted.add(key);\n }\n}\n\n/**\n * Build the component schema for a collection (output / read shape).\n *\n * Every declared property except the ones {@link isDocumentedProperty} rules\n * out, plus the foreign keys and relations {@link emitRelationProperties} adds.\n */\nfunction buildCollectionSchema(\n collection: CollectionConfig,\n registeredSchemas: ReadonlySet<string>\n): Record<string, unknown> {\n const idKey = idPropertyEntry(collection)?.[0] ?? \"id\";\n const properties: Record<string, unknown> = {\n [idKey]: { ...idSchemaFor(collection), description: \"Unique identifier\" }\n };\n const required: string[] = [idKey];\n const excluded = excludedApiKeys(collection);\n const emitted = new Set<string>(excluded);\n emitted.add(idKey);\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (property.type === \"relation\") continue;\n if (!isDocumentedProperty(property)) continue;\n\n properties[key] = convertPropertyToSchema(property);\n emitted.add(key);\n\n if (property.validation?.required && key !== idKey) {\n required.push(key);\n }\n }\n\n emitRelationProperties(collection, properties, required, emitted, registeredSchemas);\n annotateTenantField(collection, properties, \"read\");\n\n return {\n type: \"object\",\n required: required.length > 0 ? required : undefined,\n properties\n };\n}\n\n/**\n * Mark the tenant field, on whichever schema is being built.\n *\n * A vendor extension for the same reason `x-rebase-access` is one: OpenAPI has\n * no keyword for \"the server fills this in from who you are, and refuses a\n * value that is not yours\". `readOnly` is the closest and it is wrong — the\n * field *is* writable, by a caller sending their own tenant, and a bypass role\n * may send any. Generators ignore what they do not know, so a client built from\n * this document still compiles; a human reading `/docs`, or a gateway wanting\n * to enforce the same boundary at the edge, learns the field is special and\n * why.\n *\n * Applied after the property loops rather than inside `convertPropertyToSchema`\n * because tenancy is a fact about the *collection*, and that function is handed\n * a property with no idea which collection it came from.\n */\nfunction annotateTenantField(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n direction: \"read\" | \"write\"\n): void {\n const tenant = getTenantConfig(collection);\n if (!tenant) return;\n const schema = properties[tenant.field];\n if (!schema || typeof schema !== \"object\") return;\n\n const existing = (schema as { description?: unknown }).description;\n const sentence = direction === \"write\"\n ? \"The tenant this row belongs to. Omit it and the server stamps the tenant you are calling as; \" +\n \"send another tenant's and the write is refused with `TENANT_MISMATCH`. It cannot be changed \" +\n \"on an update (`TENANT_IMMUTABLE`).\"\n : \"The tenant this row belongs to. Rows of other tenants are not returned at all.\";\n\n properties[tenant.field] = {\n ...(schema as Record<string, unknown>),\n description: typeof existing === \"string\" && existing ? `${existing} — ${sentence}` : sentence,\n \"x-rebase-tenant\": true\n };\n}\n\n/**\n * The PATCH/PUT operation for `/data/{slug}/{id}`.\n *\n * Split out because both verbs serve it and they must not drift: the update\n * body is a **partial**, and describing it with the create schema was the bug\n * this replaces. `<Name>Input` marks every `validation.required` property as\n * required — correct for POST, wrong for an update, where omitting a field\n * means \"leave it alone\" rather than \"I forgot it\". A client generated from\n * that spec demanded fields the server does not, and a spec-validating gateway\n * would have rejected partial updates the server accepts.\n */\nfunction updateOperation(\n collection: CollectionConfig,\n schemaName: string,\n requireAuth: boolean,\n shared: {\n ifMatchHeader: Record<string, unknown>;\n preferHeader: Record<string, unknown>;\n preconditionFailed: Record<string, unknown>;\n minimalResponse: Record<string, unknown>;\n }\n): Record<string, unknown> {\n return {\n tags: [collection.name],\n summary: `Update ${collection.singularName || collection.name}`,\n description:\n \"Partial update: only the properties present in the body are written; the rest are left \" +\n \"unchanged.\\n\\n\" +\n \"A property's value may instead be a field operation — `{ \\\"views\\\": { \\\"$inc\\\": 1 } }`, \" +\n \"`{ \\\"tags\\\": { \\\"$push\\\": \\\"new\\\" } }`, `{ \\\"tags\\\": { \\\"$pull\\\": \\\"old\\\" } }`, \" +\n \"`{ \\\"meta\\\": { \\\"$merge\\\": { \\\"seen\\\": true } } }` — which is applied inside the \" +\n \"statement holding the row lock. That is the difference between a counter that is correct \" +\n \"under concurrency and one that silently loses increments, because expressing the same \" +\n \"change as a value means reading it first. `$inc` needs a `number` property, `$push`/`$pull` \" +\n \"an `array`, `$merge` a `map`; anything else is a 400. See the `FieldOperation` schema.\",\n operationId: `update${schemaName}`,\n parameters: [\n { name: \"id\", in: \"path\", required: true, schema: { type: \"string\" }, description: \"Entity ID\" },\n shared.ifMatchHeader,\n shared.preferHeader,\n {\n name: \"Idempotency-Key\",\n in: \"header\",\n required: false,\n schema: { type: \"string\" },\n description:\n \"Names this update so a retry replays its answer instead of applying the edit \" +\n \"again. A PATCH is not naturally idempotent — a field operation emphatically is \" +\n \"not — so a retry after a lost response applies it twice.\"\n }\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}Update` }\n }\n }\n },\n responses: {\n 200: {\n description: \"Updated entity\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n 404: {\n description: \"Entity not found\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 409: {\n description: \"A request with the same Idempotency-Key is still in flight\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 422: {\n description: \"The Idempotency-Key was already used for a different request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n ...shared.preconditionFailed,\n ...shared.minimalResponse,\n ...errorResponses(requireAuth)\n }\n };\n}\n\n/**\n * The update body: the create schema with `required` dropped.\n *\n * Derived rather than rebuilt so the two cannot describe different columns —\n * the only difference between creating and updating is which fields you must\n * supply, and that is exactly the one thing removed here.\n */\nfunction buildCollectionUpdateSchema(collection: CollectionConfig): Record<string, unknown> {\n const { required: _required, ...rest } = buildCollectionInputSchema(collection);\n return rest;\n}\n\n/**\n * Build an input schema (for POST/PUT) — excludes auto-generated fields.\n *\n * `excludeFromApi` columns are left out too, and the server now agrees: a write\n * naming one is refused (`write-validation.ts`), which is what the flag's name\n * and the generated SDK's own documentation always said. This comment used to\n * record that such a write was \"still accepted\" — the document was right and\n * the server was the thing that had not caught up.\n */\nfunction buildCollectionInputSchema(collection: CollectionConfig): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n // The write half, not the read half: a field readable by nobody but\n // writable by an `admin` belongs in the input body and not in the row, and\n // the two schemas used to share one exclusion set and so got both wrong.\n const excluded = excludedApiKeys(collection, \"write\");\n const emitted = new Set<string>(excluded);\n const idKey = idPropertyEntry(collection)?.[0] ?? \"id\";\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (property.type === \"relation\") continue;\n if (!isDocumentedProperty(property, \"write\")) continue;\n\n // Skip auto-value date fields from the input schema\n if (property.type === \"date\" && property.autoValue) continue;\n\n // Skip auto-generated ID fields\n if (\"isId\" in property && property.isId && property.isId !== \"manual\" && property.isId !== true) continue;\n\n properties[key] = convertPropertyToSchema(property);\n emitted.add(key);\n\n if (property.validation?.required) {\n required.push(key);\n }\n }\n\n // Allow explicit ID for create (optional). Typed from the declared primary\n // key rather than as a `string` literal: a serial `id` was `integer` on the\n // read schema and `string` here, for the same column, in every document the\n // generator has ever produced.\n if (!emitted.has(idKey)) {\n properties[idKey] = {\n ...idSchemaFor(collection),\n description: \"Optional: client-assigned ID. If omitted, the server generates one.\"\n };\n emitted.add(idKey);\n }\n\n // The two ways a write may name a `belongsTo` target, both of which the\n // server accepts and the generated SDK's `Insert` already offered: the\n // foreign key under its own wire name (`authorId`), and the relation\n // property (`author`), which the write transformer maps onto that column.\n // Neither reached the document, so the spec described a create that could\n // not set a relation at all.\n emitWritableRelations(collection, properties, emitted);\n annotateTenantField(collection, properties, \"write\");\n\n // The tenant field is never required on input, whatever the property\n // declares: the column is NOT NULL, and the server is what fills it.\n // Listing it would tell every generated client to demand a value its caller\n // is not supposed to compute.\n const tenantField = getTenantConfig(collection)?.field;\n const requiredOnInput = tenantField ? required.filter(key => key !== tenantField) : required;\n\n return {\n type: \"object\",\n required: requiredOnInput.length > 0 ? requiredOnInput : undefined,\n properties\n };\n}\n\n/**\n * The writable half of a collection's relations: `belongsTo` only.\n *\n * A to-many is not writable through the body — the server links rows through\n * the nested routes — so offering `tags: [...]` on a create would describe a\n * write that does nothing. Same rule, same reason, as `emitWritableRelations`\n * in the SDK generator, whose `Insert` type this mirrors key for key.\n *\n * Neither spelling is listed in `required`, even for a relation the collection\n * declares `validation: { required: true }` on: the two keys are alternatives,\n * and a schema naming both would tell a spec-validating gateway to reject a\n * create that the server accepts. (The SDK's `Insert` marks both non-optional\n * for the same relation, which is the same fact stated less carefully; a\n * document is the half that a gateway enforces.)\n */\nfunction emitWritableRelations(\n collection: CollectionConfig,\n properties: Record<string, unknown>,\n emitted: Set<string>\n): void {\n const resolved = resolveRelationsForDocument(collection);\n\n const emit = (key: string, relation: ResolvedRelation): void => {\n if (emitted.has(key)) return;\n properties[key] = {\n ...idSchemaFor(relationTarget(relation)),\n description: `The \\`${relation.targetSlug}\\` row this belongs to.`\n };\n emitted.add(key);\n };\n\n for (const relation of Object.values(resolved)) {\n if (relation.kind === \"belongsTo\" && relation.localKey) {\n emit(fieldKeyForColumn(collection, relation.localKey), relation);\n }\n }\n\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n const relation = findRelation(resolved, key);\n if (relation?.kind === \"belongsTo\" && relation.localKey) emit(key, relation);\n }\n}\n\n/**\n * Convert a Rebase Property to an OpenAPI 3.0 schema object.\n */\nfunction convertPropertyToSchema(property: Property): Record<string, unknown> {\n const schema = convertPropertyTypeToSchema(property);\n const annotation = accessAnnotation(property);\n if (!annotation) return schema;\n\n const sentence = accessDescription(property);\n return {\n ...schema,\n ...(sentence\n ? { description: schema.description ? `${schema.description} — ${sentence}` : sentence }\n : {}),\n \"x-rebase-access\": annotation\n };\n}\n\n/** The JSON Schema a property's *type* compiles to, before any access annotation. */\nfunction convertPropertyTypeToSchema(property: Property): Record<string, unknown> {\n const base: Record<string, unknown> = {};\n\n if (property.name) {\n base.description = property.name;\n }\n\n switch (property.type) {\n case \"string\": {\n const sp = property as StringProperty;\n base.type = \"string\";\n\n if (sp.enum) {\n const enumValues = resolveEnumValues(sp.enum);\n if (enumValues.length > 0) {\n base.enum = enumValues;\n }\n }\n\n if (sp.validation) {\n if (sp.validation.min !== undefined) base.minLength = sp.validation.min;\n if (sp.validation.max !== undefined) base.maxLength = sp.validation.max;\n if (sp.validation.length !== undefined) {\n base.minLength = sp.validation.length;\n base.maxLength = sp.validation.length;\n }\n if (sp.validation.matches !== undefined) {\n base.pattern = String(sp.validation.matches);\n }\n }\n\n if (sp.email) base.format = \"email\";\n if (sp.url) base.format = \"uri\";\n if (sp.storage) base.format = \"uri\";\n\n return base;\n }\n\n case \"number\": {\n const np = property as NumberProperty;\n // `isId` is on this list because the DDL generator puts it there:\n // a numeric primary key is `INTEGER GENERATED BY DEFAULT AS\n // IDENTITY` for `\"increment\"` and `INTEGER` for every other form\n // (`generate-postgres-ddl-logic.ts`), so the column the scaffold's\n // `posts.id` creates is an integer and the document called it a\n // `number` — which lets a generated client send `1.5` for a row id.\n const isInteger = np.validation?.integer\n || Boolean(np.isId)\n || np.columnType === \"integer\" || np.columnType === \"serial\"\n || np.columnType === \"bigserial\" || np.columnType === \"bigint\";\n base.type = isInteger ? \"integer\" : \"number\";\n\n if (np.enum) {\n const enumValues = resolveEnumValues(np.enum);\n if (enumValues.length > 0) {\n base.enum = enumValues;\n }\n }\n\n if (np.validation) {\n if (np.validation.min !== undefined) base.minimum = np.validation.min;\n if (np.validation.max !== undefined) base.maximum = np.validation.max;\n if (np.validation.moreThan !== undefined) {\n base.minimum = np.validation.moreThan;\n base.exclusiveMinimum = true;\n }\n if (np.validation.lessThan !== undefined) {\n base.maximum = np.validation.lessThan;\n base.exclusiveMaximum = true;\n }\n }\n\n return base;\n }\n\n case \"boolean\":\n base.type = \"boolean\";\n return base;\n\n case \"date\": {\n base.type = \"string\";\n if (property.mode === \"date\") {\n base.format = \"date\";\n } else {\n base.format = \"date-time\";\n }\n if (property.autoValue) {\n base.readOnly = true;\n base.description = (base.description || \"\") +\n (property.autoValue === \"on_create\" ? \" (Auto-set on creation)\" : \" (Auto-updated)\");\n }\n return base;\n }\n\n case \"geopoint\":\n base.type = \"object\";\n base.properties = {\n latitude: { type: \"number\" },\n longitude: { type: \"number\" }\n };\n base.required = [\"latitude\", \"longitude\"];\n return base;\n\n case \"reference\":\n base.type = \"string\";\n base.description = (base.description || \"\") + \" (Reference ID)\";\n return base;\n\n case \"array\": {\n const ap = property as ArrayProperty;\n base.type = \"array\";\n\n if (ap.oneOf) {\n // Discriminated union (e.g., content blocks)\n const typeField = ap.oneOf.typeField || \"type\";\n const valueField = ap.oneOf.valueField || \"value\";\n const variants: Record<string, unknown>[] = [];\n\n for (const [variantKey, variantProp] of Object.entries(ap.oneOf.properties)) {\n variants.push({\n type: \"object\",\n properties: {\n [typeField]: { type: \"string\",\nenum: [variantKey] },\n [valueField]: convertPropertyToSchema(variantProp)\n },\n required: [typeField, valueField]\n });\n }\n\n base.items = { oneOf: variants };\n } else if (ap.of) {\n if (Array.isArray(ap.of)) {\n base.items = { oneOf: ap.of.map(p => convertPropertyToSchema(p)) };\n } else {\n base.items = convertPropertyToSchema(ap.of);\n }\n } else {\n base.items = {};\n }\n\n if (ap.validation) {\n if (ap.validation.min !== undefined) base.minItems = ap.validation.min;\n if (ap.validation.max !== undefined) base.maxItems = ap.validation.max;\n }\n\n return base;\n }\n\n case \"map\": {\n const mp = property as MapProperty;\n base.type = \"object\";\n\n if (mp.properties) {\n const props: Record<string, unknown> = {};\n const req: string[] = [];\n\n for (const [key, subProp] of Object.entries(mp.properties)) {\n props[key] = convertPropertyToSchema(subProp);\n if (subProp.validation?.required) {\n req.push(key);\n }\n }\n\n base.properties = props;\n if (req.length > 0) base.required = req;\n } else if (mp.keyValue) {\n base.additionalProperties = true;\n }\n\n return base;\n }\n\n case \"vector\": {\n const vp = property as VectorProperty;\n base.type = \"array\";\n base.items = { type: \"number\" };\n base.description = (base.description || \"\") + ` (Vector(${vp.dimensions}))`;\n return base;\n }\n case \"binary\": {\n base.type = \"string\";\n base.description = (base.description || \"\") + \" (Binary/Base64)\";\n return base;\n }\n default:\n base.type = \"string\";\n return base;\n }\n}\n\n/**\n * Resolve EnumValues (array or record) into a flat array of enum values.\n */\nfunction resolveEnumValues(enumDef: Record<string | number, unknown> | Array<{ id: string | number }>): Array<string | number> {\n if (Array.isArray(enumDef)) {\n return enumDef.map(e => (typeof e === \"object\" && e !== null && \"id\" in e) ? e.id : e as string | number);\n }\n return Object.keys(enumDef).map(k => {\n // Preserve numeric keys as numbers\n const num = Number(k);\n return isNaN(num) ? k : num;\n });\n}\n\n/**\n * Build PostgREST-style filter parameters for a collection.\n * These are additional query parameters like `?status=eq.active&price=gte.100`.\n *\n * `excludeFromApi` columns are not offered: the server does filter on them, and\n * that is exactly the problem — a filter on a column no response can contain\n * answers questions about the value one row at a time, which is a worse\n * disclosure than the column name alone.\n */\nfunction buildFilterParameters(\n collection: CollectionConfig,\n reservedNames: ReadonlySet<string> = new Set()\n): Array<Record<string, unknown>> {\n const params: Array<Record<string, unknown>> = [];\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (!isDocumentedProperty(property)) continue;\n // A `relation` property is not a column, so there is nothing to compare\n // against. The foreign key beside it is filterable and is still not\n // offered here — a separate gap from the schema one, and one the query\n // layer has to answer first.\n if (property.type === \"relation\") continue;\n if (property.type === \"map\" || property.type === \"array\" || property.type === \"geopoint\") {\n continue;\n }\n // A column whose name a list parameter already owns is unfilterable\n // over the wire — see `reservedParameterNames`.\n if (reservedNames.has(key)) continue;\n\n params.push({\n name: key,\n in: \"query\",\n required: false,\n schema: { type: \"string\" },\n description:\n `Filter by \\`${key}\\`. Supports PostgREST operators: ` +\n \"`eq.value`, `neq.value`, `gt.value`, `gte.value`, `lt.value`, `lte.value`, \" +\n \"`in.(a,b,c)`, `nin.(a,b,c)`, `cs.value` (array-contains), `csa.(a,b)` (array-contains-any). \" +\n \"Plain values imply equality.\",\n example: property.type === \"string\" ? \"eq.active\" : property.type === \"number\" ? \"gte.100\" : undefined\n });\n }\n\n return params;\n}\n\n/**\n * Standard error responses included on every endpoint.\n */\nfunction errorResponses(requireAuth: boolean): Record<string, unknown> {\n const responses: Record<string, unknown> = {\n 400: {\n description: \"Bad request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 500: {\n description: \"Internal server error\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n }\n };\n\n if (requireAuth) {\n responses[401] = {\n description: \"Authentication required or invalid token\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n };\n responses[403] = {\n description: \"Insufficient permissions\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n };\n }\n\n return responses;\n}\n\n/**\n * Prefix a noun with \"a\" or \"an\" based on its leading sound.\n */\nfunction withIndefiniteArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? \"an\" : \"a\"} ${noun}`;\n}\n\n/**\n * The component-schema name for a collection — and the stem of every\n * `operationId` and `$ref` that mentions it.\n *\n * `toPascalCase` keeps ASCII letters and digits and nothing else, so a name\n * written in a script that has none of them — a Cyrillic or Japanese\n * `singularName`, which the docs' six locales make ordinary rather than exotic\n * — reduced to the empty string. The schema was then stored under `\"\"` and\n * every reference to it read `#/components/schemas/`, an unresolvable pointer:\n * Swagger UI renders the model empty and a strict generator fails outright. So\n * fall through the names until one survives, and keep a constant as the floor.\n *\n * Two collections whose names PascalCase identically still share one component;\n * that needs a disambiguation rule, not a fallback.\n */\nfunction schemaNameFor(collection: CollectionConfig): string {\n return toPascalCase(collection.singularName || \"\")\n || toPascalCase(collection.name || \"\")\n || toPascalCase(collection.slug || \"\")\n || \"Collection\";\n}\n\n/**\n * Convert a string to PascalCase for schema names.\n */\nfunction toPascalCase(str: string): string {\n return str\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .split(\" \")\n .filter(Boolean)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"query-parser-BleZmY18.js","names":[],"sources":["../src/api/rest/soft-delete-params.ts","../src/api/rest/field-access-query.ts","../src/api/rest/query-parser.ts"],"sourcesContent":["import { ApiError } from \"../errors\";\n\n/**\n * The two query parameters soft delete adds to the REST surface.\n *\n * Kept in their own module so the call sites in `query-parser.ts` and the\n * delete routes are a single line each: the parsing rules belong to soft\n * delete, not to the parser, and a rule spread across the two files that read\n * it is a rule that drifts.\n */\n\n/** `?deleted=` — what to do about rows a soft delete has stamped. */\nexport const DELETED_QUERY_PARAM = \"deleted\";\n/** `?hard=` — ask for a real `DELETE` on a soft-delete collection. */\nexport const HARD_DELETE_QUERY_PARAM = \"hard\";\n\n/**\n * `?deleted=include|only` → the driver's `withDeleted`.\n *\n * Spelled `deleted` on the wire and `withDeleted` in the driver, deliberately:\n * the URL reads as a question about the rows (`?deleted=only` — \"only the\n * deleted ones\"), and the driver option reads as an instruction about the query.\n *\n * A value neither word is a 400 rather than a silent fallback to the default.\n * `?deleted=true` quietly hiding every deleted row is the worst of both: it\n * looks like it worked and answers the opposite question. Absent is the\n * default, which is \"hide them\".\n */\nexport function parseWithDeleted(raw: unknown): boolean | \"only\" | undefined {\n if (raw === undefined || raw === null || raw === \"\") return undefined;\n const value = String(raw).trim().toLowerCase();\n if (value === \"include\") return true;\n if (value === \"only\") return \"only\";\n throw ApiError.badRequest(\n `Invalid \\`?${DELETED_QUERY_PARAM}=${String(raw)}\\`. It takes 'include' (live rows and deleted ones) ` +\n \"or 'only' (deleted rows alone). Omit it to see only the live rows.\",\n \"INVALID_DELETED_PARAM\"\n );\n}\n\n/**\n * `?hard=true` → a real `DELETE` on a collection that soft-deletes.\n *\n * Needs no permission beyond the delete it replaces: it is the same verb, and a\n * second access-control surface for one operation is a second thing to get\n * wrong. What it changes is whether the row can be restored.\n *\n * Only the exact words `true` and `1` mean yes. Anything else is a 400, not a\n * \"no\" — a typo that silently soft-deletes when the caller asked to purge is a\n * caller who believes the data is gone.\n */\nexport function parseHardDelete(raw: unknown): boolean {\n if (raw === undefined || raw === null || raw === \"\") return false;\n const value = String(raw).trim().toLowerCase();\n if (value === \"true\" || value === \"1\") return true;\n if (value === \"false\" || value === \"0\") return false;\n throw ApiError.badRequest(\n `Invalid \\`?${HARD_DELETE_QUERY_PARAM}=${String(raw)}\\`. It takes 'true' or 'false'.`,\n \"INVALID_HARD_PARAM\"\n );\n}\n","import type { CollectionConfig } from \"@rebasepro/types\";\nimport type { FilterCondition, LogicalCondition } from \"@rebasepro/types\";\nimport { type FieldViewer, restrictedFieldNames } from \"@rebasepro/common\";\nimport { ApiError } from \"../errors\";\n\n/**\n * A read may not name a field the caller cannot read.\n *\n * The strip in the row pipeline is what keeps the *value* off the wire. This is\n * the other half, and without it the value is still readable one bit at a time:\n * `?salary=gt.100000` returns the rows whose withheld salary is above 100k, and\n * `?orderBy=salary` returns them in order of it. A column no response can carry\n * has to be a column no query can interrogate, or the read rule is decoration.\n *\n * The refusal names the field. That is deliberate and it is not a leak: the\n * published OpenAPI lists every property of every collection, including the ones\n * a given caller cannot read, because the document is one document and is served\n * off the app rather than off the authenticated data router. Hiding the name\n * here would protect nothing and would answer a caller's genuine typo with\n * \"unknown field\", sending them to look for a spelling mistake that is not\n * there. Field *names* are public; field *values* are not.\n *\n * @module\n */\n\n/**\n * The roles behind a request, as a viewer a field rule can judge.\n *\n * Never `undefined`, and that is the point. `undefined` means the trusted server\n * plane in {@link FieldViewer}, which satisfies every non-empty role list — so\n * returning it for a request that merely has no `user` on the context would\n * hand an unauthenticated caller every field in the database. The auth\n * middleware scopes such a request's driver as `roles: [\"anon\"]` but sets no\n * `user`, so the fallback here has to be the same list the driver was scoped\n * with, not nothing.\n *\n * @param c anything carrying the Hono context's `get` — the batch route passes a\n * shim rather than the context itself, exactly as the API-key\n * permission check does.\n */\nexport function requestViewer(c: { get: (key: never) => unknown }): FieldViewer {\n const user = c.get(\"user\" as never) as { roles?: readonly string[] } | undefined;\n return { roles: user?.roles ?? ANON_ROLES };\n}\n\n/** What the auth middleware scopes an unauthenticated request's driver with. */\nconst ANON_ROLES: readonly string[] = Object.freeze([\"anon\"]);\n\n/** Which query parameter a refused field arrived in, for the message. */\ntype Where = \"filter\" | \"orderBy\" | \"fields\" | \"select\" | \"groupBy\";\n\nconst WHERE_LABEL: Record<Where, string> = {\n filter: \"a filter\",\n orderBy: \"`orderBy`\",\n fields: \"`fields`\",\n select: \"`select`\",\n groupBy: \"`groupBy`\"\n};\n\n/** Every column a logical group compares, however deeply nested. */\nfunction logicalColumns(logical: LogicalCondition | undefined, into: string[]): void {\n if (!logical?.conditions) return;\n for (const condition of logical.conditions) {\n if (\"conditions\" in condition) logicalColumns(condition as LogicalCondition, into);\n else if ((condition as FilterCondition).column) into.push((condition as FilterCondition).column);\n }\n}\n\n/**\n * The bare column an `orderBy` key names, or `undefined` for one that is not a\n * column at all.\n *\n * A sort key may be a relation aggregate (`comments.count()`) or one of the\n * computed keys a search adds (`_score`, `_distance`). Neither is a property of\n * this collection, so neither is a field this rule has anything to say about;\n * the field it *would* have named is checked by the same walk one level down\n * when the driver resolves the relation.\n */\nfunction orderByColumn(field: string): string | undefined {\n if (field.startsWith(\"_\")) return undefined;\n if (field.includes(\"(\") || field.includes(\".\")) return undefined;\n return field;\n}\n\n/**\n * Refuse the request when any of `names` is a field this caller cannot read.\n *\n * Exported so the aggregate route — whose `select` and `groupBy` are parsed\n * outside `parseQueryOptions` — applies the identical rule. `count(*)` over a\n * withheld column is the same disclosure as reading it, one predicate at a time.\n */\nexport function assertReadableFields(\n names: readonly (string | undefined)[],\n collection: CollectionConfig,\n viewer: FieldViewer | undefined,\n where: Where\n): void {\n if (names.length === 0) return;\n const { refused } = restrictedFieldNames(collection, viewer, \"read\");\n if (refused.size === 0) return;\n\n const named = [...new Set(names.filter((n): n is string => n !== undefined && refused.has(n)))];\n if (named.length === 0) return;\n\n throw ApiError.badRequest(\n `${named.map(f => `'${f}'`).join(\", \")} ${named.length > 1 ? \"are\" : \"is\"} not readable ` +\n `on '${collection.slug}' with your roles, so ${named.length > 1 ? \"they\" : \"it\"} cannot be used in ` +\n `${WHERE_LABEL[where]}.`,\n \"FIELD_NOT_READABLE\",\n {\n collection: collection.slug,\n fields: named,\n violations: named.map(field => ({\n field,\n code: \"access\",\n message: `'${field}' is not readable with your roles.`\n }))\n }\n );\n}\n\n/**\n * The whole of a parsed read request, checked in one pass.\n *\n * One call rather than five, because five call sites is five chances to add a\n * sixth query parameter and forget it — which is exactly how `?or=` came to be\n * parsed and then dropped by the list route.\n */\nexport function assertQueryFieldsReadable(\n options: {\n where?: Record<string, unknown>;\n logical?: LogicalCondition;\n orderBy?: { field: string }[];\n fields?: string[];\n },\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n const filtered: string[] = [];\n if (options.where) filtered.push(...Object.keys(options.where));\n logicalColumns(options.logical, filtered);\n assertReadableFields(filtered, collection, viewer, \"filter\");\n\n assertReadableFields(\n (options.orderBy ?? []).map(entry => orderByColumn(entry.field)),\n collection, viewer, \"orderBy\"\n );\n\n assertReadableFields(options.fields ?? [], collection, viewer, \"fields\");\n}\n","import type { CollectionConfig, FilterValues, ListLimitBounds, LogicalCondition, NullsPlacement, OrderByTuple, VectorSearchParams } from \"@rebasepro/types\";\nimport { toCanonicalOp, resolveClientListLimit, ListLimitError, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from \"@rebasepro/types\";\nimport type { DecodedCursor } from \"@rebasepro/common\";\nimport {\n CursorError,\n CursorMismatchError,\n type FieldViewer,\n IncludeSpecError,\n OrderBySpecError,\n decodeCursor,\n deserializeFilter,\n deserializeInclude,\n deserializeLogicalCondition,\n normalizeInclude,\n reconcileCursorOrder,\n UnknownFilterOperatorError\n} from \"@rebasepro/common\";\nimport { QueryOptions } from \"../types\";\nimport { ApiError } from \"../errors\";\nimport { DELETED_QUERY_PARAM, HARD_DELETE_QUERY_PARAM, parseWithDeleted } from \"./soft-delete-params\";\nimport { assertQueryFieldsReadable } from \"./field-access-query\";\n\nexport const mapOperator = (op: string) => toCanonicalOp(op) ?? null;\n\n/**\n * A malformed query parameter, refused with a 400.\n *\n * Every rejection in this file is one of these, and every one of them is\n * `expected` — the flag `errorHandler` reads to log a routine outcome at debug\n * instead of warn. A client that mistypes an operator, a sort direction or a\n * limit is not an incident: nothing on the server is wrong, the request never\n * reached the database, and the caller has already been told what to fix in the\n * response body. Left at warn, a single frontend holding a stale field name\n * writes a `⚠️` line per request forever, and the warn level stops meaning\n * anything — which is why \"routine 4xx logs at WARN\" is a standing finding\n * against this API.\n *\n * Not a factory on `ApiError`: the class's members are part of the tracked\n * runtime surface (`api-surface/server.api.txt`), and this needs no addition to\n * it. `ApiError.unauthenticated` is the same idea one status code up.\n */\nfunction invalidParam(message: string, code: string, details?: unknown): ApiError {\n return new ApiError(400, code, message, details, true);\n}\n\n/**\n * Decode a filter, turning the shared codec's operator rejection into a 400.\n *\n * `deserializeFilter` lives in `@rebasepro/common`, which cannot throw an\n * `ApiError` — it does not depend on this package, and the browser SDK decodes\n * through the same function and has nothing to render one with. So it throws\n * `UnknownFilterOperatorError`, and the HTTP boundary is where that becomes a\n * status code. Same seam `parseLogicalGroup` uses for the nesting bound.\n *\n * Without this the operator string became a *value*: `?where={\"title\":\n * [\"!!\",\"Hello\"]}` compiled to `title IN ('!!','Hello')` and answered 200 with\n * the row the caller was filtering out, and `{\"id\":[\">>\",0]}` reached Postgres\n * and came back a 500 quoting `invalid input syntax for type integer`. Both are\n * malformed requests and now say so.\n */\nfunction decodeFilter(query: Record<string, unknown>): FilterValues<string> {\n try {\n return deserializeFilter(query);\n } catch (e) {\n if (e instanceof UnknownFilterOperatorError) {\n throw invalidParam(e.message, e.code, e.details);\n }\n throw e;\n }\n}\n\nfunction getLastValue(val: unknown): unknown {\n if (Array.isArray(val)) {\n return val[val.length - 1];\n }\n return val;\n}\n\n/**\n * Parse an `or(...)` / `and(...)` logical group from its wire form.\n *\n * The wire carries the inner conditions wrapped in parens (e.g.\n * `(status.eq.active,age.gte.18)`); we re-attach the `or`/`and` prefix and\n * delegate to the canonical filter dialect (`@rebasepro/common`). Values are\n * preserved as strings — type coercion is the schema-aware driver's job, so\n * this path stays byte-for-byte consistent with the SDK/admin path (which\n * also parses via the shared dialect).\n */\nfunction parseLogicalGroup(type: \"or\" | \"and\" | \"not\", raw: unknown): LogicalCondition | undefined {\n let inner = String(raw).trim();\n if (inner.startsWith(\"(\") && inner.endsWith(\")\")) {\n inner = inner.slice(1, -1);\n }\n inner = inner.trim();\n if (!inner) return undefined;\n let parsed;\n try {\n parsed = deserializeLogicalCondition(`${type}(${inner})`);\n } catch (e) {\n // The parser refuses a nesting depth no real filter reaches. That is a\n // request problem, and without this it surfaced as a 500 — the\n // unbounded version reached `RangeError: Maximum call stack size\n // exceeded`, which tells the caller nothing about their filter.\n throw invalidParam(\n `Invalid \\`${type}\\` parameter: ${e instanceof Error ? e.message : String(e)}`,\n \"INVALID_LOGICAL_GROUP\"\n );\n }\n return \"type\" in parsed ? parsed : undefined;\n}\n\n/**\n * Parse the `?where=` JSON filter object.\n *\n * This is the dialect the OpenAPI document publishes on every\n * `GET /api/data/{slug}` — `{\"status\":[\"==\",\"active\"]}`: field → canonical\n * `[WhereFilterOp, value]` tuple. It is normalized through the same\n * `deserializeFilter` as the `?field=op.value` params below, so a value that\n * arrives as a PostgREST dot-string (`{\"status\":\"eq.active\"}`) or as a bare\n * scalar (`{\"status\":\"active\"}`) compiles to the same condition. Unlike the\n * querystring dialect, JSON carries types — a number stays a number.\n *\n * A malformed value is a 400 rather than a silent drop: dropping the filter\n * would run the read unfiltered and return everything RLS happens to allow.\n */\nfunction parseWhereParam(raw: unknown): FilterValues<string> | undefined {\n const str = String(raw).trim();\n if (!str) return undefined;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(str);\n } catch {\n throw invalidParam(\n \"Invalid `where` parameter: expected a JSON object, e.g. {\\\"status\\\":[\\\"==\\\",\\\"active\\\"]}\",\n \"INVALID_WHERE\"\n );\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw invalidParam(\n \"Invalid `where` parameter: expected a JSON object mapping fields to conditions, \"\n + \"e.g. {\\\"status\\\":[\\\"==\\\",\\\"active\\\"]}\",\n \"INVALID_WHERE\"\n );\n }\n\n const filter = decodeFilter(parsed as Record<string, unknown>);\n return Object.keys(filter).length > 0 ? filter : undefined;\n}\n\ntype OrderByEntry = { field: string; direction: \"asc\" | \"desc\"; nulls?: NullsPlacement };\n\n/**\n * The parsed entries as the driver contract spells them: `[field, direction]`\n * tuples in order of significance.\n *\n * The REST layer used to hand the driver `orderBy[0].field` and drop the rest,\n * so `?orderBy=[{\"field\":\"roles\"},{\"field\":\"created_at\",\"direction\":\"desc\"}]`\n * — a shape this parser has always accepted and validated in full — sorted by\n * `roles` alone and returned the ties in whatever order Postgres pleased.\n */\nexport function orderByEntriesToTuples(entries?: OrderByEntry[]): OrderByTuple[] | undefined {\n if (!entries || entries.length === 0) return undefined;\n return entries.map(({ field, direction, nulls }) => (nulls\n ? [field, direction, nulls]\n : [field, direction]) as OrderByTuple);\n}\n\nfunction invalidOrderBy(detail: string): never {\n throw invalidParam(\n `Invalid \\`orderBy\\` parameter: ${detail}. Expected \\`field\\`, \\`field:desc\\`, `\n + \"`field:desc:last`, or a JSON array like \"\n + \"[{\\\"field\\\":\\\"created_at\\\",\\\"direction\\\":\\\"desc\\\",\\\"nulls\\\":\\\"last\\\"}]\",\n \"INVALID_ORDER_BY\"\n );\n}\n\n/**\n * The `nulls` slot: `first`/`last`, or a refusal naming the entry.\n *\n * Refused rather than defaulted, for the reason every other parameter here is:\n * a sort quietly ordered by a convention the caller did not ask for reads as\n * though it obeyed them. See {@link NullsPlacement} for what the default is\n * when the slot is simply absent.\n */\nfunction toNulls(raw: unknown, context: string): NullsPlacement | undefined {\n if (raw === undefined || raw === null || raw === \"\") return undefined;\n if (raw !== \"first\" && raw !== \"last\") {\n invalidOrderBy(`${context} has nulls '${String(raw)}'`);\n }\n return raw;\n}\n\n/** The aggregate functions `?select=` accepts. */\nconst AGGREGATE_FUNCTIONS = new Set([\"count\", \"sum\", \"avg\", \"min\", \"max\"]);\n\nexport interface ParsedAggregate {\n fn: \"count\" | \"sum\" | \"avg\" | \"min\" | \"max\";\n /** Absent only for `count()`, which counts rows rather than values. */\n field?: string;\n /** The key this appears under in the response. */\n alias: string;\n}\n\n/**\n * Parse `?select=count(),sum(total),avg(total)`.\n *\n * The spelling is SQL's, because whoever writes it is thinking in SQL and\n * because any other spelling has to be learned first. `count()` with no field\n * counts rows; every other function names a column.\n *\n * Aliases are derived rather than accepted: `sum(total)` returns as\n * `sum_total`, `count()` as `count`. Letting a caller choose would mean\n * checking their alias is not also a `groupBy` field — a rule nobody would\n * guess, and a silently overwritten value if it went unchecked.\n */\nexport function parseAggregateSelect(raw: unknown): ParsedAggregate[] | undefined {\n const value = getLastValue(raw);\n if (!value) return undefined;\n\n const entries = String(value).split(\",\").map(s => s.trim()).filter(Boolean);\n if (entries.length === 0) return undefined;\n\n return entries.map((entry) => {\n const match = /^([a-z]+)\\(\\s*([A-Za-z0-9_]*)\\s*\\)$/i.exec(entry);\n if (!match) {\n throw invalidParam(\n `Invalid \\`select\\` entry \"${entry}\". Expected \\`fn(field)\\`, e.g. \\`sum(total)\\` or \\`count()\\`.`,\n \"INVALID_AGGREGATE_SELECT\"\n );\n }\n\n const fn = match[1].toLowerCase();\n const field = match[2] || undefined;\n\n if (!AGGREGATE_FUNCTIONS.has(fn)) {\n throw invalidParam(\n `Unknown aggregate function \"${fn}\". Expected: ${[...AGGREGATE_FUNCTIONS].join(\", \")}.`,\n \"INVALID_AGGREGATE_FUNCTION\"\n );\n }\n if (fn !== \"count\" && !field) {\n // `sum()` has no sensible reading, and guessing one would be\n // inventing a column on the caller's behalf.\n throw invalidParam(\n `\\`${fn}()\\` needs a field, e.g. \\`${fn}(total)\\`. Only \\`count()\\` may be empty.`,\n \"INVALID_AGGREGATE_SELECT\"\n );\n }\n\n return {\n fn: fn as ParsedAggregate[\"fn\"],\n field,\n alias: field ? `${fn}_${field}` : fn\n };\n });\n}\n\n/** Parse `?groupBy=status,country`. */\nexport function parseGroupBy(raw: unknown): string[] | undefined {\n const value = getLastValue(raw);\n if (!value) return undefined;\n const fields = String(value).split(\",\").map(s => s.trim()).filter(Boolean);\n return fields.length > 0 ? fields : undefined;\n}\n\n/** `asc`/`desc`, in any case. Anything else is a request to sort in a way that does not exist. */\nfunction toDirection(raw: unknown, context: string): \"asc\" | \"desc\" {\n if (raw === undefined || raw === null) return \"asc\";\n if (typeof raw !== \"string\") invalidOrderBy(`${context} has a non-string \\`direction\\``);\n const lowered = raw.toLowerCase();\n if (lowered !== \"asc\" && lowered !== \"desc\") {\n invalidOrderBy(`${context} has direction '${raw}'`);\n }\n return lowered;\n}\n\n/** One entry: the canonical `{field, direction}`, or the `field:direction` shorthand as a string. */\nfunction toOrderByEntry(raw: unknown, index: number): OrderByEntry {\n const context = `entry ${index}`;\n if (typeof raw === \"string\") {\n // Split here rather than through `deserializeOrderBy`, which is the\n // *client* end of the codec and normalises anything that is not\n // literally \"desc\" to \"asc\". Routed through it, `?orderBy=x:DESC`\n // reached `toDirection` already collapsed to \"asc\" and answered 200\n // with the rows in the opposite order — a newest-first list showing\n // the oldest rows — and `x:sideways` did the same. The direction token\n // has to arrive here raw for `toDirection` to have anything to refuse.\n const idx = raw.indexOf(\":\");\n const field = (idx === -1 ? raw : raw.slice(0, idx)).trim();\n if (!field) invalidOrderBy(`${context} is an empty field name`);\n if (idx === -1) return { field, direction: \"asc\" };\n // `field:direction:nulls`. The third segment is optional, so every\n // `field:desc` written before it existed parses exactly as it did.\n const rest = raw.slice(idx + 1);\n const nullsIdx = rest.indexOf(\":\");\n const direction = toDirection(nullsIdx === -1 ? rest : rest.slice(0, nullsIdx), context);\n const nulls = nullsIdx === -1 ? undefined : toNulls(rest.slice(nullsIdx + 1), context);\n return nulls ? { field, direction, nulls } : { field, direction };\n }\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n invalidOrderBy(`${context} is not a field name or a {field, direction} object`);\n }\n const entry = raw as Record<string, unknown>;\n if (typeof entry.field !== \"string\" || entry.field.trim() === \"\") {\n invalidOrderBy(`${context} has no \\`field\\``);\n }\n const nulls = toNulls(entry.nulls, context);\n const direction = toDirection(entry.direction, context);\n return nulls ? { field: entry.field, direction, nulls } : { field: entry.field, direction };\n}\n\n/**\n * Parse the `orderBy` query parameter.\n *\n * The field *name* has been validated against the schema for a while — an\n * `?orderBy=titel` is a 400 rather than 200 with unsorted rows, on the grounds\n * that silently dropping the sort leaves the caller believing in an order that\n * is not there. The parameter's *shape* was never checked the same way, and it\n * failed in exactly the same silent manner one layer earlier: whatever\n * `JSON.parse` returned was assigned to an option declared as an array of\n * `{field, direction}`, and the REST layer reads only `orderBy[0].field`. So\n * `?orderBy={\"field\":\"name\"}` — an object rather than an array, and the most\n * natural thing for a client to try — read `undefined`, dropped the ORDER BY,\n * and answered 200. So did a number, a boolean, `null`, and `[\"name\"]`.\n *\n * This refuses those, the way `parseWhereParam` above already refuses a\n * malformed filter and for the same reason. What it keeps working is every\n * shape that worked before: the `field` and `field:desc` shorthands, and the\n * canonical JSON array.\n */\nfunction parseOrderByParam(raw: unknown): OrderByEntry[] | undefined {\n if (Array.isArray(raw)) {\n // A repeated query parameter arrives pre-split; treat it as the list.\n return raw.length === 0 ? undefined : raw.map(toOrderByEntry);\n }\n\n const str = String(raw).trim();\n if (!str) return undefined;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(str);\n } catch {\n // Not JSON at all, so it is the `field:direction` shorthand.\n return [toOrderByEntry(str, 0)];\n }\n\n // `JSON.parse` succeeding says nothing about the shape being usable.\n if (Array.isArray(parsed)) {\n if (parsed.length === 0) return undefined;\n return parsed.map(toOrderByEntry);\n }\n if (typeof parsed === \"string\") return [toOrderByEntry(parsed, 0)];\n if (typeof parsed === \"object\" && parsed !== null) {\n // A bare `{field, direction}` is a near miss rather than nonsense, but\n // accepting it would leave two spellings of one parameter. Name it.\n invalidOrderBy(\"a single object was given where a JSON array was expected\");\n }\n invalidOrderBy(`${typeof parsed} is not a field name or a list of them`);\n}\n\n// Re-exported for callers/tests that reference the REST list bounds. The\n// numbers and the rule live in `@rebasepro/types` so the REST parser and the\n// WebSocket ingress enforce ONE shared guarantee. See `resolveClientListLimit`.\nexport { DEFAULT_LIST_LIMIT, DEFAULT_VECTOR_LIST_LIMIT, MAX_LIST_LIMIT } from \"@rebasepro/types\";\n\n/**\n * Overridable list-pagination bounds for {@link parseQueryOptions}. Without\n * these, `GET /<collection>` with no `?limit` would buffer the ENTIRE table\n * into a JS array + JSON response (a trivial OOM/DoS), and `?limit=100000000`\n * would be honoured verbatim.\n */\nexport interface ListLimitOptions {\n /**\n * Page size used when the client sends no `?limit`. Applied to plain and\n * text-search reads — a vector search falls back to its own default (10).\n */\n defaultLimit?: number;\n /** Largest `?limit` a client may ask for. A larger one is a 400, not a clamp. */\n maxLimit?: number;\n}\n\n/**\n * {@link resolveClientListLimit} for an HTTP route: the same bounds, answered\n * with a 400 rather than a 500.\n *\n * The shared resolver throws a `ListLimitError`, which carries `status` — but\n * the Hono error handler discriminates on `statusCode`, so an unconverted one\n * reaches the client as `INTERNAL_ERROR` with its message stripped, telling the\n * caller nothing about the parameter it got wrong. Every REST list ingress\n * routes its `limit` through here so all of them name the ceiling the same way.\n */\nexport function resolveListLimitParam(\n rawLimit: number | string | null | undefined,\n opts: ListLimitBounds & { vectorSearch?: boolean } = {}\n): number {\n try {\n return resolveClientListLimit(rawLimit, opts);\n } catch (e) {\n if (e instanceof ListLimitError) {\n throw invalidParam(e.message, \"INVALID_LIMIT\");\n }\n throw e;\n }\n}\n\n/**\n * A whole number at or above `minimum`, or a 400 naming the parameter.\n *\n * `parseInt` was the whole of the validation, and it answers `NaN` for\n * `?offset=abc` and a negative for `?offset=-5`. Neither was checked:\n *\n * - `NaN` reached the driver, where `OFFSET NaN` is a 500 about a syntax error\n * in a query the caller never wrote;\n * - `?page=0` computed `offset = -limit`, a negative offset, which Postgres\n * also refuses — and `?page=-3` refused deeper;\n * - `?offset=1.5` truncated silently to `1`, so the caller paged a window they\n * had not asked for.\n *\n * Every one of those is the caller's parameter, so every one is a 400 named\n * after the parameter — the shape `INVALID_LIMIT` already had, and the reason a\n * limit is *rejected* rather than clamped: a window quietly different from the\n * one asked for cannot be told apart from having reached the end.\n *\n * `expected: true` on the error (via {@link invalidParam}): a mistyped query\n * parameter never reached the database and the response body already says what\n * to fix, so it logs at debug rather than putting a warning in production logs\n * on every request from a client holding a stale link.\n */\nfunction parseWindowParam(raw: unknown, name: string, minimum: number, code: string): number {\n const text = String(raw).trim();\n const value = Number(text);\n if (text === \"\" || !Number.isFinite(value) || !Number.isInteger(value) || value < minimum) {\n throw invalidParam(\n `Invalid \\`${name}\\` parameter: expected a whole number ${minimum === 0 ? \"of 0 or more\" : `of ${minimum} or more`}, got ${JSON.stringify(text)}.`,\n code\n );\n }\n return value;\n}\n\n/**\n * Parse query parameters into QueryOptions\n */\nexport function parseQueryOptions(\n query: Record<string, unknown>,\n limits: ListLimitOptions = {},\n /**\n * The collection being read and who is reading it. Optional so the parser\n * stays a pure parser for the callers that have neither (tests, the WS\n * ingress, anything parsing a query it is not about to run); when present,\n * a `where`, `orderBy` or `fields` naming a field the caller cannot read is\n * a 400 rather than a query the driver would happily answer. See\n * {@link assertQueryFieldsReadable}.\n */\n access?: { collection: CollectionConfig; viewer?: FieldViewer }\n): QueryOptions {\n const options: QueryOptions = {};\n const rawLimit = getLastValue(query.limit) as number | string | null | undefined;\n\n // `?deleted=include|only` — soft delete. See `soft-delete-params.ts`.\n const withDeleted = parseWithDeleted(getLastValue(query[DELETED_QUERY_PARAM]));\n if (withDeleted !== undefined) options.withDeleted = withDeleted;\n\n const offsetVal = getLastValue(query.offset);\n if (offsetVal) options.offset = parseWindowParam(offsetVal, \"offset\", 0, \"INVALID_OFFSET\");\n\n const pageVal = getLastValue(query.page);\n if (pageVal) {\n const page = parseWindowParam(pageVal, \"page\", 1, \"INVALID_PAGE\");\n // Page stride uses the same bounded page size the read will use, so\n // pages neither overlap nor gap. (Vector search never paginates by\n // page, so the plain/text default is correct here.)\n const limit = resolveListLimitParam(rawLimit, {\n defaultLimit: limits.defaultLimit,\n maxLimit: limits.maxLimit\n });\n options.offset = (page - 1) * limit;\n }\n\n // ── Logical conditions (or / and / not) ────────────────────────────\n //\n // `?not=(status.eq.draft,views.gte.10)` negates the **conjunction** of its\n // conditions: `not(a)` is `NOT a`, `not(a,b)` is `NOT (a AND b)`. The rule\n // lives on `LogicalCondition` and is applied identically by this parser,\n // the shared wire codec and every driver compiler — one negation, one\n // meaning. A group nests, so `?not=(or(a,b))` is the De Morgan case.\n //\n // Three parameters and one slot, so exactly one applies. `or` wins over\n // `and`, and both over `not` — the precedence `or`/`and` already had, with\n // the third added at the end rather than in the middle, where it would have\n // silently changed which of two existing parameters was honoured.\n const orVal = getLastValue(query.or);\n const andVal = getLastValue(query.and);\n const notVal = getLastValue(query.not);\n if (orVal) {\n const logical = parseLogicalGroup(\"or\", orVal);\n if (logical) options.logical = logical;\n } else if (andVal) {\n const logical = parseLogicalGroup(\"and\", andVal);\n if (logical) options.logical = logical;\n } else if (notVal) {\n const logical = parseLogicalGroup(\"not\", notVal);\n if (logical) options.logical = logical;\n }\n\n // ── PostgREST-style field filters: ?field=op.value ─────────────────\n // Delegate to the canonical filter dialect (the single source of truth\n // for the wire grammar: operator codes, list/escape handling, implicit\n // eq). Values stay strings; the schema-aware driver coerces them to\n // column types. This keeps the REST path byte-for-byte consistent with\n // the SDK/admin path, which parses through the same `deserializeFilter`.\n //\n // `where` is reserved: it is the JSON filter dialect (see\n // `parseWhereParam`), not a column named \"where\". Leaving it out of this\n // list made the documented `?where={...}` compile as a filter on a\n // nonexistent field — which used to be dropped, widening the read to the\n // whole table, and is now a 400 `UNKNOWN_FILTER_FIELD`.\n //\n // `select` and `groupBy` are reserved for the same reason: on\n // `/aggregate` they are the request, and left out of this list\n // `?select=sum(total)` compiles into the filter as a comparison on a\n // column named \"select\" — a 400 on the one endpoint that requires it.\n // `not`, `after` and `distinct` join the list for the reason the comment\n // above gives: a reserved key left out of it compiles as a filter on a\n // column of that name, which is a 400 `UNKNOWN_FILTER_FIELD` on the one\n // request that needs the parameter. So do `?deleted=` and `?hard=`, which\n // ask about the soft-delete stamp rather than name a column.\n const reservedQueryKeys = [\"limit\", \"offset\", \"page\", \"after\", \"orderBy\", \"include\", \"fields\", \"distinct\", \"searchString\", \"searchExplain\", \"vector_search\", \"vector\", \"vector_distance\", \"vector_threshold\", \"or\", \"and\", \"not\", \"where\", \"select\", \"groupBy\", DELETED_QUERY_PARAM, HARD_DELETE_QUERY_PARAM];\n const filterDict: Record<string, unknown> = {};\n for (const [key, rawValue] of Object.entries(query)) {\n if (reservedQueryKeys.includes(key)) continue;\n filterDict[key] = rawValue;\n }\n // Both dialects may be sent together; an explicit `?field=op.value` wins\n // over the same field inside `where`, being the more specific request.\n const whereVal = getLastValue(query.where);\n const where = {\n ...(whereVal !== undefined && whereVal !== null ? parseWhereParam(whereVal) : undefined),\n ...decodeFilter(filterDict)\n };\n if (Object.keys(where).length > 0) {\n options.where = where;\n }\n\n // Sorting\n const orderByVal = getLastValue(query.orderBy);\n if (orderByVal) {\n options.orderBy = parseOrderByParam(orderByVal);\n }\n\n // ── Relation includes ──────────────────────────────────────────────\n //\n // Two spellings on one parameter, told apart by a leading `{`:\n //\n // ?include=author,comments.author — names and dotted paths\n // ?include={\"comments\":{\"limit\":5,\"include\":{\"author\":true}}}\n //\n // The flat form is what a human types and what every existing client\n // sends; the JSON form exists because the flat one has nowhere to put a\n // per-relation `limit`/`where`/`orderBy`/`fields`, and inventing a\n // punctuation for those (`comments(limit:5)`) would be a third grammar to\n // learn beside the two this API already has. Both compile to the same\n // request — `deserializeInclude` in `@rebasepro/common` is the codec, and\n // the SDK serialises through its inverse.\n const includeVal = getLastValue(query.include);\n if (includeVal !== undefined && includeVal !== null) {\n try {\n const include = deserializeInclude(String(includeVal));\n // Normalized for its *checks* — the depth bound and the shape of a\n // per-relation options object — and then discarded: what travels on\n // is the caller's own spelling, which the driver normalizes again\n // (idempotently) when it reads it. Validating here is what makes a\n // malformed include a 400 at the boundary rather than an\n // `IncludeSpecError` escaping from the driver as a 500, which is\n // what `?include=a.b.c.d` used to answer.\n normalizeInclude(include);\n options.include = include;\n } catch (e) {\n if (e instanceof IncludeSpecError) throw invalidParam(e.message, e.code);\n if (e instanceof OrderBySpecError) {\n throw invalidParam(`Invalid \\`include\\`: ${e.message}`, \"INVALID_INCLUDE\");\n }\n throw e;\n }\n }\n\n // Field selection. A projection at the driver, not a trim of the response:\n // the columns named here are the columns read.\n const fieldsVal = getLastValue(query.fields);\n if (fieldsVal) {\n const fieldsStr = String(fieldsVal).trim();\n options.fields = fieldsStr.split(\",\").map(s => s.trim()).filter(Boolean);\n }\n\n // `?distinct=true` — `SELECT DISTINCT` over the projection. Only `true`\n // and `1` mean yes; anything else is refused rather than read as \"no\",\n // because a `?distinct=1&` typo'd into `?distinct=ture` would otherwise\n // return duplicate rows while looking exactly like it had worked.\n const distinctVal = getLastValue(query.distinct);\n if (distinctVal !== undefined && distinctVal !== null && String(distinctVal) !== \"\") {\n const text = String(distinctVal).trim().toLowerCase();\n if (text !== \"true\" && text !== \"1\" && text !== \"false\" && text !== \"0\") {\n throw invalidParam(\n `Invalid \\`distinct\\` parameter: expected \\`true\\` or \\`false\\`, got ${JSON.stringify(String(distinctVal))}.`,\n \"INVALID_DISTINCT\"\n );\n }\n options.distinct = text === \"true\" || text === \"1\";\n }\n\n // ── Keyset cursor ──────────────────────────────────────────────────\n //\n // `?after=<meta.nextCursor>`. Decoded here so a malformed cursor is one\n // 400 in one place, and so the sort a cursor implies is settled before any\n // route reads `orderBy`: a request that names no sort adopts the cursor's,\n // and one that names a different sort is refused rather than seeked in an\n // order nobody asked for.\n const afterVal = getLastValue(query.after);\n if (afterVal !== undefined && afterVal !== null && String(afterVal).trim() !== \"\") {\n let cursor: DecodedCursor;\n try {\n cursor = decodeCursor(String(afterVal));\n } catch (e) {\n if (e instanceof CursorError) throw invalidParam(e.message, e.code);\n throw e;\n }\n try {\n const reconciled = reconcileCursorOrder(cursor, orderByEntriesToTuples(options.orderBy));\n options.orderBy = reconciled.map(([field, direction, nulls]) =>\n (nulls ? { field, direction, nulls } : { field, direction }));\n } catch (e) {\n if (e instanceof CursorMismatchError) throw invalidParam(e.message, e.code);\n throw e;\n }\n options.cursor = cursor;\n // A cursor and an offset describe the same window two incompatible\n // ways, and honouring both would start the page `offset` rows past\n // where the cursor pointed — a gap the caller cannot see.\n if (options.offset !== undefined) {\n throw invalidParam(\n \"`after` and `offset`/`page` cannot be combined: a cursor already says where the page \"\n + \"starts, and an offset on top of it skips rows. Use one or the other.\",\n \"CURSOR_WITH_OFFSET\"\n );\n }\n }\n\n // ── Vector similarity search ───────────────────────────────────────\n // Every rejection here is a malformed *request*, so it must carry a 400.\n // A bare `Error` reaches the handler with no `statusCode` and no known\n // `code`, which makes it a 500 — logged with a full stack as an incident,\n // and answered with \"An unexpected error occurred\", because the handler\n // only forwards a message to the client below 500. The caller was told\n // nothing about what it got wrong.\n const vectorSearchVal = getLastValue(query.vector_search);\n const vectorVal = getLastValue(query.vector);\n if (vectorSearchVal && vectorVal) {\n const vectorStr = String(vectorVal);\n let decoded: unknown;\n try {\n decoded = JSON.parse(vectorStr);\n } catch {\n decoded = undefined;\n }\n // Validated outside the `try` on purpose: inside it, the thrown\n // ApiError would be caught by its own `catch` and re-thrown as\n // something else.\n if (!Array.isArray(decoded) || !decoded.every(v => typeof v === \"number\")) {\n throw invalidParam(\n \"Invalid `vector` format. Expected a JSON array of numbers, e.g. [0.1,0.2,0.3]\",\n \"INVALID_VECTOR\"\n );\n }\n const queryVector = decoded as number[];\n\n const distanceParamVal = getLastValue(query.vector_distance);\n const distanceParam = distanceParamVal ? String(distanceParamVal) : \"cosine\";\n if (distanceParam !== \"cosine\" && distanceParam !== \"l2\" && distanceParam !== \"inner_product\") {\n throw invalidParam(\n `Invalid \\`vector_distance\\`: ${distanceParam}. Expected: cosine, l2, or inner_product`,\n \"INVALID_VECTOR_DISTANCE\"\n );\n }\n\n const vectorSearch: VectorSearchParams = {\n property: String(vectorSearchVal),\n vector: queryVector,\n distance: distanceParam\n };\n\n const thresholdVal = getLastValue(query.vector_threshold);\n if (thresholdVal) {\n const threshold = parseFloat(String(thresholdVal));\n if (isNaN(threshold)) {\n throw invalidParam(\n \"Invalid `vector_threshold`. Expected a number.\",\n \"INVALID_VECTOR_THRESHOLD\"\n );\n }\n vectorSearch.threshold = threshold;\n }\n\n options.vectorSearch = vectorSearch;\n }\n\n // Resolve the limit LAST — once we know whether this is a vector search —\n // so a client-supplied limit above the ceiling is refused with a 400 and an\n // absent one falls back to the correct mode default (plain/text =\n // defaultLimit, vector = 10). Without this a bare `GET /<collection>` would\n // return the whole table. Shared with the WebSocket ingress via\n // `resolveClientListLimit`.\n options.limit = resolveListLimitParam(rawLimit, {\n vectorSearch: !!options.vectorSearch,\n defaultLimit: limits.defaultLimit,\n maxLimit: limits.maxLimit\n });\n\n // Every field the request named, against what this caller may read. Last,\n // so a malformed parameter is still answered as malformed rather than as a\n // permission problem.\n if (access) assertQueryFieldsReadable(options, access.collection, access.viewer);\n\n return options;\n}\n"],"mappings":"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"}