@rebasepro/server 0.25.0 → 0.26.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 (174) hide show
  1. package/dist/{GCSStorageController-C7ARrDAp.js → GCSStorageController-40FyFeSx.js} +8 -4
  2. package/dist/GCSStorageController-40FyFeSx.js.map +1 -0
  3. package/dist/{S3StorageController-qevJ0pVE.js → S3StorageController-Bl5shp94.js} +8 -4
  4. package/dist/S3StorageController-Bl5shp94.js.map +1 -0
  5. package/dist/access-BFAUvsSs.js +1897 -0
  6. package/dist/access-BFAUvsSs.js.map +1 -0
  7. package/dist/api/branch-routes.d.ts +25 -0
  8. package/dist/api/closed-database.d.ts +25 -0
  9. package/dist/api/live-schema-routes.d.ts +10 -0
  10. package/dist/api/rest/api-generator.d.ts +9 -1
  11. package/dist/api/rest/idempotency.d.ts +15 -1
  12. package/dist/api/root-error-handler.d.ts +6 -5
  13. package/dist/api/types.d.ts +12 -0
  14. package/dist/{ast-schema-editor-mCeSC03x.js → ast-schema-editor-madTa9Hh.js} +2 -2
  15. package/dist/{ast-schema-editor-mCeSC03x.js.map → ast-schema-editor-madTa9Hh.js.map} +1 -1
  16. package/dist/auth/access.d.ts +10 -0
  17. package/dist/auth/adapter-middleware.d.ts +2 -1
  18. package/dist/auth/adapter-service-key.d.ts +24 -0
  19. package/dist/auth/api-keys/api-key-middleware.d.ts +21 -1
  20. package/dist/auth/api-keys/api-key-store.d.ts +8 -1
  21. package/dist/auth/api-keys/key-grant.d.ts +18 -1
  22. package/dist/auth/expired-token-sweep.d.ts +5 -0
  23. package/dist/auth/impersonation.d.ts +3 -1
  24. package/dist/auth/jwt.d.ts +8 -2
  25. package/dist/auth/oauth-signin-policy.d.ts +21 -35
  26. package/dist/auth/rls-scope.d.ts +17 -7
  27. package/dist/auth/rls-simulator-routes.d.ts +51 -0
  28. package/dist/auth/simulation-read-only.d.ts +28 -0
  29. package/dist/auth/simulation.d.ts +141 -0
  30. package/dist/auth/sql-rate-limit-store.d.ts +15 -1
  31. package/dist/{auth-CteGWxsL.js → auth-izI7AEzC.js} +391 -1893
  32. package/dist/auth-izI7AEzC.js.map +1 -0
  33. package/dist/auth_adapter-B7RUMk6e.js +89 -0
  34. package/dist/auth_adapter-B7RUMk6e.js.map +1 -0
  35. package/dist/backend/boot-announcements.d.ts +13 -0
  36. package/dist/backend/create-backend.d.ts +10 -0
  37. package/dist/backend/options.d.ts +9 -4
  38. package/dist/{backend-DTAOsLQc.js → backend-CaG_Jyso.js} +9 -2
  39. package/dist/backend-CaG_Jyso.js.map +1 -0
  40. package/dist/{backup-Dq0lyJ_D.js → backup-BVbHoKo8.js} +8 -8
  41. package/dist/{backup-Dq0lyJ_D.js.map → backup-BVbHoKo8.js.map} +1 -1
  42. package/dist/boot/boot.d.ts +2 -15
  43. package/dist/boot/ddl-bootstrap.d.ts +34 -0
  44. package/dist/boot/env.d.ts +6 -0
  45. package/dist/boot/fetch-bundle.d.ts +5 -1
  46. package/dist/boot/provision-only.d.ts +23 -0
  47. package/dist/boot/provision.d.ts +53 -2
  48. package/dist/boot/role.d.ts +2 -0
  49. package/dist/boot/schema-change-recorder.d.ts +45 -0
  50. package/dist/boot/security-headers.d.ts +22 -0
  51. package/dist/branch-routes-B2vvEx53.js +84 -0
  52. package/dist/branch-routes-B2vvEx53.js.map +1 -0
  53. package/dist/{contract-routes-DiFwiLg2.js → contract-routes-CU2A3JnF.js} +3 -3
  54. package/dist/{contract-routes-DiFwiLg2.js.map → contract-routes-CU2A3JnF.js.map} +1 -1
  55. package/dist/cron/cron-store.d.ts +8 -1
  56. package/dist/{cron-loader-NHqRfKF0.js → cron-loader-mMhqbVsl.js} +3 -3
  57. package/dist/{cron-loader-NHqRfKF0.js.map → cron-loader-mMhqbVsl.js.map} +1 -1
  58. package/dist/{cron-routes-Dhf82HzT.js → cron-routes-Cs33j_Kr.js} +5 -5
  59. package/dist/{cron-routes-Dhf82HzT.js.map → cron-routes-Cs33j_Kr.js.map} +1 -1
  60. package/dist/{cron-scheduler-D1DT0nw5.js → cron-scheduler-Be7CNghL.js} +3 -3
  61. package/dist/{cron-scheduler-D1DT0nw5.js.map → cron-scheduler-Be7CNghL.js.map} +1 -1
  62. package/dist/{cron-store-DEpVstUU.js → cron-store-DLn11M8H.js} +11 -10
  63. package/dist/cron-store-DLn11M8H.js.map +1 -0
  64. package/dist/{strings.es-BeZWAodn.js → data_driver-D0mg1Y_x.js} +2 -90
  65. package/dist/data_driver-D0mg1Y_x.js.map +1 -0
  66. package/dist/{ddl-bootstrap-BNqi2NZe.js → ddl-bootstrap-CjRrn12l.js} +92 -3
  67. package/dist/ddl-bootstrap-CjRrn12l.js.map +1 -0
  68. package/dist/deploy/pod-contract.d.ts +1 -1
  69. package/dist/email/dev-sink.d.ts +6 -0
  70. package/dist/email/index.d.ts +1 -1
  71. package/dist/{errors-_ytoGi8A.js → errors-OMWkKSH-.js} +66 -5
  72. package/dist/errors-OMWkKSH-.js.map +1 -0
  73. package/dist/{soft-delete-params-BAgpmBvp.js → field-access-DYsvjI_P.js} +27 -57
  74. package/dist/field-access-DYsvjI_P.js.map +1 -0
  75. package/dist/{filter-dialect-O1HK6Nvr.js → filter-dialect-TrKFWLqT.js} +3 -3
  76. package/dist/{filter-dialect-O1HK6Nvr.js.map → filter-dialect-TrKFWLqT.js.map} +1 -1
  77. package/dist/{function-loader-DbCSoNJj.js → function-loader-B8YzXEsQ.js} +22 -15
  78. package/dist/function-loader-B8YzXEsQ.js.map +1 -0
  79. package/dist/{function-routes-DNV4RUF0.js → function-routes-RxrCHNuM.js} +2 -2
  80. package/dist/{function-routes-DNV4RUF0.js.map → function-routes-RxrCHNuM.js.map} +1 -1
  81. package/dist/functions/hono-like.d.ts +12 -0
  82. package/dist/functions/index.js +37 -2
  83. package/dist/functions/index.js.map +1 -1
  84. package/dist/functions/request-timeout.d.ts +3 -3
  85. package/dist/history/history-routes.d.ts +6 -0
  86. package/dist/history/retention-sweep.d.ts +5 -0
  87. package/dist/{history-recorder-RZ1Ow4YY.js → history-recorder-Bsau7h4X.js} +7 -7
  88. package/dist/history-recorder-Bsau7h4X.js.map +1 -0
  89. package/dist/{history-store-DPofGVDp.js → history-store-BQz5YCkW.js} +26 -2
  90. package/dist/history-store-BQz5YCkW.js.map +1 -0
  91. package/dist/index.d.ts +3 -1
  92. package/dist/index.es.js +3003 -681
  93. package/dist/index.es.js.map +1 -1
  94. package/dist/init/process-safety.d.ts +4 -3
  95. package/dist/init/shutdown.d.ts +8 -0
  96. package/dist/init.d.ts +6 -1
  97. package/dist/jobs/job-store.d.ts +8 -1
  98. package/dist/{jobs-8G-_agld.js → jobs-Co6Ni5HP.js} +7 -7
  99. package/dist/jobs-Co6Ni5HP.js.map +1 -0
  100. package/dist/{jwt-YhciR93H.js → jwt-rDS0884_.js} +5 -4
  101. package/dist/{jwt-YhciR93H.js.map → jwt-rDS0884_.js.map} +1 -1
  102. package/dist/{keys-COzQ2aE8.js → keys-BXcdRNhJ.js} +2 -2
  103. package/dist/{keys-COzQ2aE8.js.map → keys-BXcdRNhJ.js.map} +1 -1
  104. package/dist/{logger-Bt32sHFw.js → logger-CW8pmTS6.js} +35 -3
  105. package/dist/logger-CW8pmTS6.js.map +1 -0
  106. package/dist/{logs-routes-CelTiS7C.js → logs-routes-CBt7mMIL.js} +3 -3
  107. package/dist/{logs-routes-CelTiS7C.js.map → logs-routes-CBt7mMIL.js.map} +1 -1
  108. package/dist/mcp/consent-page.d.ts +38 -1
  109. package/dist/mcp/mcp-routes.d.ts +9 -0
  110. package/dist/mcp/mcp-tools.d.ts +7 -1
  111. package/dist/mcp/oauth-store.d.ts +12 -5
  112. package/dist/metrics/history-recorder.d.ts +4 -1
  113. package/dist/metrics/history-store.d.ts +8 -0
  114. package/dist/{offline-BrqCRgih-BtDB1ecy.js → offline-AvuEcg7u-DFHL98OT.js} +127 -40
  115. package/dist/offline-AvuEcg7u-DFHL98OT.js.map +1 -0
  116. package/dist/{offline-store-BhYXViha-BW5j1We_.js → offline-store-BhYXViha-YTmjBpo2.js} +5 -5
  117. package/dist/{offline-store-BhYXViha-BW5j1We_.js.map → offline-store-BhYXViha-YTmjBpo2.js.map} +1 -1
  118. package/dist/{openapi-generator-BamY6-ga.js → openapi-generator-CyxEtYEX.js} +61 -22
  119. package/dist/openapi-generator-CyxEtYEX.js.map +1 -0
  120. package/dist/{proxy-BEKbsil6.js → proxy-BYir8tWK.js} +5 -7
  121. package/dist/proxy-BYir8tWK.js.map +1 -0
  122. package/dist/{query-parser-CGohjyWI.js → query-parser-CDXWf2hH.js} +7 -6
  123. package/dist/{query-parser-CGohjyWI.js.map → query-parser-CDXWf2hH.js.map} +1 -1
  124. package/dist/{request-timeout-CRny5KD0.js → request-timeout-DVjo-c_8.js} +6 -6
  125. package/dist/request-timeout-DVjo-c_8.js.map +1 -0
  126. package/dist/rls-simulator-routes-bEY7-5-u.js +190 -0
  127. package/dist/rls-simulator-routes-bEY7-5-u.js.map +1 -0
  128. package/dist/schema-edit/deployed-schema-guard.d.ts +47 -0
  129. package/dist/{schema-editor-routes-CnNGCayU.js → schema-editor-routes-Dc3DU_jm.js} +4 -4
  130. package/dist/{schema-editor-routes-CnNGCayU.js.map → schema-editor-routes-Dc3DU_jm.js.map} +1 -1
  131. package/dist/soft-delete-params-BRONCOA2.js +59 -0
  132. package/dist/soft-delete-params-BRONCOA2.js.map +1 -0
  133. package/dist/{sql-export-routes-BgTJ7K3S.js → sql-export-routes-QycX8W8S.js} +40 -6
  134. package/dist/sql-export-routes-QycX8W8S.js.map +1 -0
  135. package/dist/{src-D5Y3N-S4.js → src-Bh9LRPb9.js} +21 -2
  136. package/dist/{src-D5Y3N-S4.js.map → src-Bh9LRPb9.js.map} +1 -1
  137. package/dist/storage/GCSStorageController.d.ts +2 -0
  138. package/dist/storage/LocalStorageController.d.ts +8 -3
  139. package/dist/storage/S3StorageController.d.ts +2 -0
  140. package/dist/storage/maintenance-keys.d.ts +17 -0
  141. package/dist/storage/maintenance-routes.d.ts +28 -5
  142. package/dist/storage/maintenance.d.ts +136 -4
  143. package/dist/storage/orphan-scans.d.ts +120 -0
  144. package/dist/storage/types.d.ts +11 -0
  145. package/dist/strings.es-B7QLnHh2.js +96 -0
  146. package/dist/strings.es-B7QLnHh2.js.map +1 -0
  147. package/dist/{upload-validation-I9cCUBIW.js → upload-validation-Bgo626uY.js} +2 -2
  148. package/dist/upload-validation-Bgo626uY.js.map +1 -0
  149. package/dist/utils/dev-dependency-command.d.ts +15 -0
  150. package/dist/utils/dev-port.d.ts +8 -0
  151. package/dist/utils/logger.d.ts +24 -0
  152. package/package.json +5 -5
  153. package/dist/GCSStorageController-C7ARrDAp.js.map +0 -1
  154. package/dist/S3StorageController-qevJ0pVE.js.map +0 -1
  155. package/dist/auth-CteGWxsL.js.map +0 -1
  156. package/dist/auth_adapter-CIItCTc8.js +0 -21
  157. package/dist/auth_adapter-CIItCTc8.js.map +0 -1
  158. package/dist/backend-DTAOsLQc.js.map +0 -1
  159. package/dist/cron-store-DEpVstUU.js.map +0 -1
  160. package/dist/ddl-bootstrap-BNqi2NZe.js.map +0 -1
  161. package/dist/errors-_ytoGi8A.js.map +0 -1
  162. package/dist/function-loader-DbCSoNJj.js.map +0 -1
  163. package/dist/history-recorder-RZ1Ow4YY.js.map +0 -1
  164. package/dist/history-store-DPofGVDp.js.map +0 -1
  165. package/dist/jobs-8G-_agld.js.map +0 -1
  166. package/dist/logger-Bt32sHFw.js.map +0 -1
  167. package/dist/offline-BrqCRgih-BtDB1ecy.js.map +0 -1
  168. package/dist/openapi-generator-BamY6-ga.js.map +0 -1
  169. package/dist/proxy-BEKbsil6.js.map +0 -1
  170. package/dist/request-timeout-CRny5KD0.js.map +0 -1
  171. package/dist/soft-delete-params-BAgpmBvp.js.map +0 -1
  172. package/dist/sql-export-routes-BgTJ7K3S.js.map +0 -1
  173. package/dist/strings.es-BeZWAodn.js.map +0 -1
  174. package/dist/upload-validation-I9cCUBIW.js.map +0 -1
@@ -3,7 +3,8 @@ import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
5
  import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
- import { r as logger } from "./logger-Bt32sHFw.js";
6
+ import { a as logger } from "./logger-CW8pmTS6.js";
7
+ import { i as errorHandler, t as ApiError } from "./errors-OMWkKSH-.js";
7
8
  import { Hono } from "hono";
8
9
  import { getConnInfo } from "@hono/node-server/conninfo";
9
10
  //#region src/functions/proxy.ts
@@ -108,10 +109,7 @@ function createFunctionsProxy(options) {
108
109
  path: incoming.pathname,
109
110
  error: err instanceof Error ? err : new Error(String(err))
110
111
  });
111
- return c.json({ error: {
112
- code: "FUNCTIONS_UPSTREAM_UNREACHABLE",
113
- message: "The process serving custom functions could not be reached from this one."
114
- } }, 502);
112
+ return errorHandler(new ApiError(502, "FUNCTIONS_UPSTREAM_UNREACHABLE", "The process serving custom functions could not be reached from this one."), c);
115
113
  }
116
114
  });
117
115
  return router;
@@ -125,7 +123,7 @@ function responseHeaders(headers) {
125
123
  for (const [name, value] of headers) {
126
124
  const lower = name.toLowerCase();
127
125
  if (HOP_BY_HOP.has(lower) || RESPONSE_ONLY_DROP.has(lower)) continue;
128
- out.set(name, value);
126
+ out.append(name, value);
129
127
  }
130
128
  return out;
131
129
  }
@@ -140,4 +138,4 @@ function socketAddress(c) {
140
138
  //#endregion
141
139
  export { proxy_exports as t };
142
140
 
143
- //# sourceMappingURL=proxy-BEKbsil6.js.map
141
+ //# sourceMappingURL=proxy-BYir8tWK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-BYir8tWK.js","names":[],"sources":["../src/functions/proxy.ts"],"sourcesContent":["/**\n * Forwarding `/api/functions/*` to the process that serves it.\n *\n * A split deployment normally puts a reverse proxy in front and routes the path\n * there. This exists so it does not have to: set `REBASE_FUNCTIONS_UPSTREAM` on\n * the API process and the two-container topology presents the identical URL\n * surface as the one-container one, with nothing new to install and nothing for\n * a client or an SDK to know about.\n *\n * It is a transport hop, not a trust boundary. Nothing is authenticated or\n * authorised here: the upstream runs the same auth middleware against the same\n * `JWT_SECRET` and must see the original `Authorization` header, so re-deciding\n * anything on the way through would put two answers where the system has one.\n */\nimport { Hono, type Context } from \"hono\";\nimport { getConnInfo } from \"@hono/node-server/conninfo\";\nimport type { HonoEnv } from \"../api/types\";\nimport { ApiError, errorHandler } from \"../api/errors\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Headers that describe *this* connection rather than the request, and so must\n * not be copied onto a new one.\n *\n * `host` is the important one: forwarded unchanged it names the API process, and\n * an upstream doing any virtual-host routing would answer for the wrong service.\n * `content-length` and `transfer-encoding` are recomputed by `fetch` from the\n * body it is actually given, and a stale value is a hang or a truncation.\n */\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n \"host\",\n \"content-length\"\n]);\n\n/**\n * Additionally dropped from the *response*, because `fetch` has already acted on\n * them by the time we see the body.\n *\n * `Content-Encoding` is the one that matters and the one that is easy to miss:\n * undici decompresses a `gzip` response transparently, but leaves the header on\n * the `Response` object. Copy it onto the body we hand back and the client is\n * told to gunzip bytes that are already plain — which does not fail cleanly. It\n * hangs, or it surfaces as a decode error several layers away from the proxy\n * that caused it. (This is not hypothetical: the runtime compresses its own\n * responses, so *every* forwarded response carries the header.)\n */\nconst RESPONSE_ONLY_DROP = new Set([\"content-encoding\", \"content-length\"]);\n\nexport interface FunctionsProxyOptions {\n /** Base URL of the process serving the functions, e.g. `http://functions:8080`. */\n upstream: string;\n /**\n * The mount path being forwarded, e.g. `/api/functions`. Omitted, it is the\n * `mountPath` the functions group recorded for the request — the path the\n * application mounted it at — so the upstream is asked for the same path.\n */\n basePath?: string;\n}\n\n/**\n * A router that forwards everything under its mount point to `upstream`.\n *\n * Method, path, query string, body and headers travel verbatim, with two\n * deliberate exceptions:\n *\n * 1. Hop-by-hop headers are dropped ({@link HOP_BY_HOP}).\n * 2. `X-Forwarded-For` gains the address this process saw the request come from.\n * This is not decoration — the upstream's rate limiters and the auth routes'\n * IP logging read that chain, and without the append every forwarded request\n * arrives from one address: the API pod's. Every caller would share a single\n * rate-limit bucket and every login would be logged from the same IP.\n *\n * **The upstream must therefore count one more proxy hop than the API process\n * does.** `TRUSTED_PROXY_HOPS` on the functions process is the API's value\n * plus one. Left unset it defaults to 0, and the upstream falls back to the\n * socket address — again, the API pod's — which is safe but useless.\n */\nexport function createFunctionsProxy(options: FunctionsProxyOptions): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n // Trailing slash trimmed once, here: joining it per request is how a proxy\n // ends up asking for `//functions/x`, which some servers route and some 404.\n const upstream = options.upstream.replace(/\\/+$/, \"\");\n\n router.all(\"/*\", async (c) => {\n const incoming = new URL(c.req.url);\n const basePath = options.basePath ?? c.get(\"mountPath\") ?? \"\";\n // The path *below* the mount point. Hono gives the full request path, so\n // taking the suffix is what keeps the upstream's own mount identical:\n // `/api/functions/send-invoice` here becomes `/api/functions/send-invoice`\n // there, not `/api/functions/api/functions/send-invoice`.\n const suffix = incoming.pathname.startsWith(basePath)\n ? incoming.pathname.slice(basePath.length)\n : incoming.pathname;\n const target = `${upstream}${basePath}${suffix}${incoming.search}`;\n\n const headers = new Headers();\n for (const [name, value] of c.req.raw.headers) {\n if (!HOP_BY_HOP.has(name.toLowerCase())) headers.set(name, value);\n }\n\n const seenFrom = socketAddress(c);\n if (seenFrom) {\n const chain = c.req.header(\"x-forwarded-for\");\n headers.set(\"x-forwarded-for\", chain ? `${chain}, ${seenFrom}` : seenFrom);\n }\n\n try {\n const response = await fetch(target, {\n method: c.req.method,\n headers,\n // GET and HEAD may not carry one; everything else streams\n // through rather than being buffered, so a large upload does not\n // have to fit in this process's memory on its way past.\n body: c.req.method === \"GET\" || c.req.method === \"HEAD\" ? undefined : c.req.raw.body,\n // Required by undici whenever the body is a stream. Without it\n // the fetch rejects with a message about duplex that says\n // nothing about what to do.\n duplex: \"half\",\n redirect: \"manual\"\n } as RequestInit);\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders(response.headers)\n });\n } catch (err) {\n // The upstream being down is an infrastructure fact, not a bug in\n // the caller's request, and 502 is the one status that says so.\n // Named explicitly in the body because the alternative — a generic\n // 500 from the API process — sends people reading the wrong logs.\n logger.error(\"Functions upstream unreachable\", {\n upstream,\n path: incoming.pathname,\n error: err instanceof Error ? err : new Error(String(err))\n });\n // Through the error handler, so it is the envelope every other\n // failure answers — with the request id that ties it to the line\n // above. Built by hand, it carried none.\n return errorHandler(new ApiError(\n 502,\n \"FUNCTIONS_UPSTREAM_UNREACHABLE\",\n \"The process serving custom functions could not be reached from this one.\"\n ), c);\n }\n });\n\n return router;\n}\n\n/**\n * Response headers, minus the ones that describe the upstream's connection and\n * the ones `fetch` has already consumed on our behalf.\n */\nfunction responseHeaders(headers: Headers): Headers {\n const out = new Headers();\n for (const [name, value] of headers) {\n const lower = name.toLowerCase();\n if (HOP_BY_HOP.has(lower) || RESPONSE_ONLY_DROP.has(lower)) continue;\n // `append`, not `set`: iterating a `Headers` yields each `Set-Cookie`\n // on its own, and `set` overwrote every one with the next — a sign-in\n // setting a session and a CSRF cookie reached the browser with the\n // CSRF cookie only. Every other header arrives folded, once.\n out.append(name, value);\n }\n return out;\n}\n\n/** The address this process saw, or undefined on a runtime that cannot say. */\nfunction socketAddress(c: Context<HonoEnv>): string | undefined {\n try {\n return getConnInfo(c).remote.address;\n } catch {\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAM,6BAAa,IAAI,IAAI;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;;;;;;;;;;;;;AAcD,IAAM,qCAAqB,IAAI,IAAI,CAAC,oBAAoB,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;AA+BzE,SAAgB,qBAAqB,SAA+C;CAChF,MAAM,SAAS,IAAI,KAAc;CAGjC,MAAM,WAAW,QAAQ,SAAS,QAAQ,QAAQ,EAAE;CAEpD,OAAO,IAAI,MAAM,OAAO,MAAM;EAC1B,MAAM,WAAW,IAAI,IAAI,EAAE,IAAI,GAAG;EAClC,MAAM,WAAW,QAAQ,YAAY,EAAE,IAAI,WAAW,KAAK;EAK3D,MAAM,SAAS,SAAS,SAAS,WAAW,QAAQ,IAC9C,SAAS,SAAS,MAAM,SAAS,MAAM,IACvC,SAAS;EACf,MAAM,SAAS,GAAG,WAAW,WAAW,SAAS,SAAS;EAE1D,MAAM,UAAU,IAAI,QAAQ;EAC5B,KAAK,MAAM,CAAC,MAAM,UAAU,EAAE,IAAI,IAAI,SAClC,IAAI,CAAC,WAAW,IAAI,KAAK,YAAY,CAAC,GAAG,QAAQ,IAAI,MAAM,KAAK;EAGpE,MAAM,WAAW,cAAc,CAAC;EAChC,IAAI,UAAU;GACV,MAAM,QAAQ,EAAE,IAAI,OAAO,iBAAiB;GAC5C,QAAQ,IAAI,mBAAmB,QAAQ,GAAG,MAAM,IAAI,aAAa,QAAQ;EAC7E;EAEA,IAAI;GACA,MAAM,WAAW,MAAM,MAAM,QAAQ;IACjC,QAAQ,EAAE,IAAI;IACd;IAIA,MAAM,EAAE,IAAI,WAAW,SAAS,EAAE,IAAI,WAAW,SAAS,KAAA,IAAY,EAAE,IAAI,IAAI;IAIhF,QAAQ;IACR,UAAU;GACd,CAAgB;GAEhB,OAAO,IAAI,SAAS,SAAS,MAAM;IAC/B,QAAQ,SAAS;IACjB,YAAY,SAAS;IACrB,SAAS,gBAAgB,SAAS,OAAO;GAC7C,CAAC;EACL,SAAS,KAAK;GAKV,OAAO,MAAM,kCAAkC;IAC3C;IACA,MAAM,SAAS;IACf,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;GAC7D,CAAC;GAID,OAAO,aAAa,IAAI,SACpB,KACA,kCACA,0EACJ,GAAG,CAAC;EACR;CACJ,CAAC;CAED,OAAO;AACX;;;;;AAMA,SAAS,gBAAgB,SAA2B;CAChD,MAAM,MAAM,IAAI,QAAQ;CACxB,KAAK,MAAM,CAAC,MAAM,UAAU,SAAS;EACjC,MAAM,QAAQ,KAAK,YAAY;EAC/B,IAAI,WAAW,IAAI,KAAK,KAAK,mBAAmB,IAAI,KAAK,GAAG;EAK5D,IAAI,OAAO,MAAM,KAAK;CAC1B;CACA,OAAO;AACX;;AAGA,SAAS,cAAc,GAAyC;CAC5D,IAAI;EACA,OAAO,YAAY,CAAC,CAAC,CAAC,OAAO;CACjC,QAAQ;EACJ;CACJ;AACJ"}
@@ -2,11 +2,12 @@ import { createRequire as __rebaseCreateRequire } from "module";
2
2
  import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
- import { i as ListLimitError, o as resolveClientListLimit } from "./strings.es-BeZWAodn.js";
6
- import { d as IncludeSpecError, f as deserializeInclude, g as OrderBySpecError, i as deserializeLogicalCondition, m as normalizeInclude, n as UnknownFilterOperatorError, r as deserializeFilter, s as PAGE_WITH_OFFSET_MESSAGE, t as RESERVED_QUERY_KEYS } from "./filter-dialect-O1HK6Nvr.js";
7
- import { $ as parseRelationAggregateSort } from "./src-D5Y3N-S4.js";
8
- import { a as apiHiddenFieldNames, c as fieldKeyForColumn, i as parseWithDeleted, l as findRelation, m as resolveCollectionRelations, s as restrictedFieldNames, t as DELETED_QUERY_PARAM } from "./soft-delete-params-BAgpmBvp.js";
9
- import { t as ApiError } from "./errors-_ytoGi8A.js";
5
+ import { r as resolveClientListLimit, t as ListLimitError } from "./data_driver-D0mg1Y_x.js";
6
+ import { d as IncludeSpecError, f as deserializeInclude, g as OrderBySpecError, i as deserializeLogicalCondition, m as normalizeInclude, n as UnknownFilterOperatorError, r as deserializeFilter, s as PAGE_WITH_OFFSET_MESSAGE, t as RESERVED_QUERY_KEYS } from "./filter-dialect-TrKFWLqT.js";
7
+ import { et as parseRelationAggregateSort } from "./src-Bh9LRPb9.js";
8
+ import { a as findRelation, i as fieldKeyForColumn, r as restrictedFieldNames, t as apiHiddenFieldNames, u as resolveCollectionRelations } from "./field-access-DYsvjI_P.js";
9
+ import { t as ApiError } from "./errors-OMWkKSH-.js";
10
+ import { i as parseWithDeleted, t as DELETED_QUERY_PARAM } from "./soft-delete-params-BRONCOA2.js";
10
11
  //#region ../common/src/collections/unknown-field.ts
11
12
  /**
12
13
  * The fields an unknown-field answer offers as valid, sorted: what a query on
@@ -1075,4 +1076,4 @@ function parseQueryOptions(query, limits = {}, access) {
1075
1076
  //#endregion
1076
1077
  export { resolveListLimitParam as a, assertReadableFields as c, decodeCursor as d, reconcileCursorOrder as f, parseQueryOptions as i, requestViewer as l, parseAggregateSelect as n, assertNoHiddenAggregateNames as o, parseGroupBy as r, assertQueryFieldsReadable as s, orderByEntriesToTuples as t, cursorToStartAfter as u };
1077
1078
 
1078
- //# sourceMappingURL=query-parser-CGohjyWI.js.map
1079
+ //# sourceMappingURL=query-parser-CDXWf2hH.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"query-parser-CGohjyWI.js","names":[],"sources":["../../common/src/collections/unknown-field.ts","../../common/src/data/cursor.ts","../src/api/rest/field-access-query.ts","../src/api/rest/query-parser.ts"],"sourcesContent":["import type { CollectionConfig, Property } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, findRelation, resolveCollectionRelations } from \"../util/relations\";\nimport { apiHiddenFieldNames } from \"./field-access\";\n\n/**\n * The answers a query gets for naming a field that does not exist — written\n * once, here, because two layers give them.\n *\n * The driver gives them for a typo. The REST check gives the same ones for a\n * field marked `excludeFromApi`: OpenAPI, `list_collections` and the generated\n * SDK all leave such a field out, so anything but the typo's answer — \"not\n * readable\", a different message, a list that names it — confirms it exists.\n * Both build the answer from these functions, so the two cannot drift apart by\n * a byte. Each returns the parts of the 400 (`message`, `code`, `details`) and\n * the caller wraps them in its own error type.\n *\n * @module\n */\n\nexport interface UnknownFieldAnswer {\n message: string;\n code: string;\n details: Record<string, unknown>;\n}\n\n/**\n * The fields an unknown-field answer offers as valid, sorted: what a query on\n * this collection can name as a column. A relation contributes its foreign key\n * (`authorId`) when the key is on this table, and nothing otherwise. An\n * `excludeFromApi` field is never offered.\n *\n * Read off the collection rather than the table, so the REST check — which\n * holds no table — lists exactly what the driver lists.\n */\nexport function queryableFieldNames(collection: CollectionConfig): string[] {\n const hidden = apiHiddenFieldNames(collection);\n const names = new Set<string>();\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property | undefined)?.type === \"relation\") {\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, key);\n if (relation?.kind === \"belongsTo\") names.add(fieldKeyForColumn(collection, relation.localKey));\n continue;\n }\n names.add(key);\n }\n return [...names].filter(name => !hidden.has(name)).sort();\n}\n\nconst validList = (validFields: string[]): string =>\n validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\";\n\n/** A filter key that resolves to nothing on `collectionPath`. */\nexport function unknownFilterField(field: string, collectionPath: string, validFields: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown filter field '${field}' on collection '${collectionPath}'${validList(validFields)}`,\n code: \"UNKNOWN_FILTER_FIELD\",\n details: { field, collection: collectionPath, ...(validFields.length > 0 && { validFields }) }\n };\n}\n\n/** `author.emial` — a relation path whose last segment names nothing on the relation's target. */\nexport function unknownRelationTargetField(\n path: { field: string; relationKey: string; fieldKey: string },\n target: { slug: string; validFields: string[] },\n collectionPath: string\n): UnknownFieldAnswer {\n return {\n message: `Unknown field '${path.fieldKey}' on '${target.slug}', the target of relation ` +\n `'${path.relationKey}' on collection '${collectionPath}'${validList(target.validFields)}`,\n code: \"UNKNOWN_FILTER_FIELD\",\n details: {\n field: path.field,\n collection: collectionPath,\n relation: path.relationKey,\n targetCollection: target.slug,\n ...(target.validFields.length > 0 && { validFields: target.validFields })\n }\n };\n}\n\n/** An `orderBy` key that resolves to nothing. `collectionName` is absent when the read carries no collection. */\nexport function unknownOrderByField(orderBy: string, collectionName: string | undefined, validFields: string[]): UnknownFieldAnswer {\n const onCollection = collectionName ? ` on collection '${collectionName}'` : \"\";\n return {\n message: `Unknown orderBy field '${orderBy}'${onCollection}${validList(validFields)}`,\n code: \"UNKNOWN_ORDER_BY_FIELD\",\n details: {\n field: orderBy,\n ...(collectionName && { collection: collectionName }),\n ...(validFields.length > 0 && { validFields })\n }\n };\n}\n\n/** A `fields` entry that names nothing. */\nexport function unknownProjectionField(name: string, collectionName: string, validFields: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown field '${name}' in \\`fields\\` on collection '${collectionName}'. Valid fields: ${validFields.join(\", \")}`,\n code: \"UNKNOWN_FIELD\",\n details: { field: name, collection: collectionName }\n };\n}\n\n/**\n * An `include` naming no relation of the collection. `validRelations` is what\n * the collection declares, never an `excludeFromApi` relation.\n */\nexport function unknownIncludeRelation(key: string, collectionName: string, validRelations: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown relation '${key}' on collection '${collectionName}'`\n + (validRelations.length > 0\n ? `. Its relations are: ${validRelations.join(\", \")}`\n : \". It declares no relations.\"),\n code: \"UNKNOWN_RELATION\",\n details: { relation: key, collection: collectionName, ...(validRelations.length > 0 && { validRelations }) }\n };\n}\n\n/** The relations an unknown-relation answer offers, sorted — never an `excludeFromApi` one. */\nexport function includableRelationNames(collection: CollectionConfig): string[] {\n const hidden = apiHiddenFieldNames(collection);\n return Object.keys(resolveCollectionRelations(collection)).filter(key => !hidden.has(key)).sort();\n}\n\n/**\n * An aggregate's `select` field or `groupBy` key that names no column.\n * `forWhat` is where it was named: `sum()`, `count()`, `groupBy`.\n * `validFields` is {@link queryableFieldNames}, never an `excludeFromApi` field.\n */\nexport function unknownAggregateField(field: string, forWhat: string, validFields: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown field '${field}' in ${forWhat}${validList(validFields)}`,\n code: \"UNKNOWN_AGGREGATE_FIELD\",\n details: { field, ...(validFields.length > 0 && { validFields }) }\n };\n}\n\n/**\n * The properties a `vector_search` can name, sorted: those declared\n * `{ type: \"vector\" }`, never an `excludeFromApi` one.\n */\nexport function vectorPropertyNames(collection: CollectionConfig): string[] {\n const hidden = apiHiddenFieldNames(collection);\n return Object.entries(collection.properties ?? {})\n .filter(([key, property]) => (property as Property | undefined)?.type === \"vector\" && !hidden.has(key))\n .map(([key]) => key)\n .sort();\n}\n\n/** A `vector_search` naming no vector property. `vectorProperties` is {@link vectorPropertyNames}. */\nexport function unknownVectorProperty(property: string, vectorProperties: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown vector property \"${property}\". ` +\n (vectorProperties.length > 0\n ? `This collection's vector properties are: ${vectorProperties.join(\", \")}.`\n : \"This collection declares no `vector` property to search.\"),\n code: \"UNKNOWN_VECTOR_PROPERTY\",\n details: { property, ...(vectorProperties.length > 0 && { vectorProperties }) }\n };\n}\n","import { parseRelationAggregateSort, type OrderByTuple } from \"@rebasepro/types\";\n\n/**\n * The keyset-cursor wire codec.\n *\n * ## Why this is one module\n *\n * Keyset pagination was implemented three times and reachable once. The driver\n * has a NULL-correct multi-key comparison (`FetchService.buildKeysetComparison`)\n * that only a WebSocket `startAfter` could reach; REST could not seek at all;\n * and the SDK's `iterate({cursor})` re-implemented a *single*-column keyset as a\n * `where` clause, which threw on any multi-key sort and silently dropped rows\n * whose sort value was NULL. Three implementations, three answers to \"what is\n * page two\".\n *\n * There is now one. The driver's comparison is the implementation; this module\n * is the only thing that says how a cursor is written down, and every transport\n * — the REST `?after=`, the WebSocket `startAfter`, the SDK's `iterate()` —\n * carries the string this produces and hands it back unread.\n *\n * ## What a cursor holds\n *\n * The sort keys the query was ordered by, the last served row's value for each\n * of them, and that row's id. The keys travel *with* the values because a\n * cursor that carried only values would be silently reinterpretable: paging a\n * `created_at DESC` listing and then asking for `title ASC` would seek on the\n * dates as though they were titles. Carrying the keys makes that a refusal\n * ({@link CursorMismatchError}) rather than a page of arbitrary rows.\n *\n * ## Opacity\n *\n * The encoding is base64url of JSON, and it is **not** API. It is opaque so it\n * can change — adding a key, changing how a value is tagged — without every\n * client that learned to read it breaking. Nothing outside this file parses it.\n *\n * @module\n */\n\n/** The decoded contents of a cursor. */\nexport interface DecodedCursor {\n /** The sort keys the cursor was produced under, in order of significance. */\n orderBy: OrderByTuple[];\n /** The last served row's value for each sort key, by field name. */\n values: Record<string, unknown>;\n /** The last served row's id, which breaks ties on the last key. */\n id: unknown;\n}\n\n/** A cursor that cannot be read at all — truncated, re-encoded, or invented. */\nexport class CursorError extends Error {\n readonly code = \"INVALID_CURSOR\";\n constructor(detail: string) {\n super(\n `Invalid \\`after\\` cursor: ${detail}. Pass back the \\`meta.nextCursor\\` ` +\n \"from the previous page unchanged — it is opaque and must not be built by hand.\"\n );\n this.name = \"CursorError\";\n Object.setPrototypeOf(this, CursorError.prototype);\n }\n}\n\n/**\n * A cursor that reads fine but describes a different query.\n *\n * Separate from {@link CursorError} because the fix is different: this one is\n * not a corrupt string, it is a correct cursor used against a sort it was not\n * produced under. Seeking anyway would return rows in an order nobody asked\n * for, and — worse — would look like it worked.\n */\nexport class CursorMismatchError extends Error {\n readonly code = \"CURSOR_ORDER_MISMATCH\";\n constructor(cursorKeys: string[], queryKeys: string[]) {\n super(\n `The \\`after\\` cursor was produced by a query ordered by ` +\n `${cursorKeys.map(k => `\"${k}\"`).join(\", \") || \"(nothing)\"}, but this query orders by ` +\n `${queryKeys.map(k => `\"${k}\"`).join(\", \") || \"(nothing)\"}. A cursor only continues the ` +\n \"listing it came from — keep `orderBy` identical across pages, or drop `after` to start over.\"\n );\n this.name = \"CursorMismatchError\";\n Object.setPrototypeOf(this, CursorMismatchError.prototype);\n }\n}\n\n/**\n * Tag for a value whose JSON round-trip would otherwise lose its type.\n *\n * A `timestamp` column comes back from the driver as a `Date`; JSON turns it\n * into a string, and the string would then be compared against the column by\n * whatever cast Postgres chose. Round-tripping it as a `Date` keeps the\n * comparison the one the ORDER BY made.\n */\nconst DATE_TAG = \"$date\";\n\nfunction encodeValue(value: unknown): unknown {\n if (value instanceof Date) return { [DATE_TAG]: value.toISOString() };\n return value;\n}\n\nfunction decodeValue(value: unknown): unknown {\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const tagged = (value as Record<string, unknown>)[DATE_TAG];\n if (typeof tagged === \"string\") {\n const date = new Date(tagged);\n return Number.isNaN(date.getTime()) ? tagged : date;\n }\n }\n return value;\n}\n\n/** base64url, without depending on Node's Buffer (this package runs in browsers). */\nfunction toBase64Url(text: string): string {\n const bytes = new TextEncoder().encode(text);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nfunction fromBase64Url(encoded: string): string {\n const padded = encoded.replace(/-/g, \"+\").replace(/_/g, \"/\")\n + \"=\".repeat((4 - (encoded.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Encode \"everything strictly after this row, in this order\".\n *\n * @param orderBy the sort keys the listing ran under, in order of significance\n * @param row the last row served, which the next page picks up after\n * @param id that row's id — the tiebreaker every keyset comparison ends on\n * @returns the opaque cursor, or `undefined` when no cursor can describe the\n * page. That is not a failure: a listing sorted by relevance has no stored\n * value to compare a later page against (scores are computed per query and\n * are not on the same scale between two of them), so it pages by offset and\n * `meta.nextCursor` is simply absent.\n */\nexport function encodeCursor(\n orderBy: OrderByTuple[] | undefined,\n row: Record<string, unknown>,\n id: unknown\n): string | undefined {\n if (id === undefined || id === null) return undefined;\n const keys = orderBy ?? [];\n // A key whose value is not on the row cannot be seeked past. Rather than\n // emit a cursor that the next request would refuse, emit none — the caller\n // falls back to offset paging, which is what it did before cursors existed.\n //\n // An aggregate over a relation (`count(orders)`) is the exception: it is\n // never on the row, and it needs no value here, because the keyset\n // comparison recomputes the cursor row's value in SQL from the id.\n // Refusing it withheld the cursor from every such listing.\n const values: Record<string, unknown> = {};\n for (const [field] of keys) {\n if (field in row) {\n values[field] = encodeValue(row[field]);\n continue;\n }\n if (parseRelationAggregateSort(field)) continue;\n return undefined;\n }\n return toBase64Url(JSON.stringify({ k: keys, v: values, i: encodeValue(id) }));\n}\n\n/**\n * Read a cursor produced by {@link encodeCursor}.\n *\n * @throws {CursorError} when the string is not a cursor this codec wrote.\n */\nexport function decodeCursor(raw: string): DecodedCursor {\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(raw.trim()));\n } catch {\n throw new CursorError(\"it is not a cursor this API issued\");\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new CursorError(\"it does not decode to a cursor\");\n }\n const body = parsed as { k?: unknown; v?: unknown; i?: unknown };\n if (!Array.isArray(body.k)) throw new CursorError(\"it carries no sort keys\");\n if (body.i === undefined) throw new CursorError(\"it carries no row id\");\n\n const orderBy: OrderByTuple[] = [];\n for (const entry of body.k) {\n if (!Array.isArray(entry) || typeof entry[0] !== \"string\") {\n throw new CursorError(\"one of its sort keys is malformed\");\n }\n const direction = entry[1] === \"desc\" ? \"desc\" : \"asc\";\n orderBy.push(entry[2] === \"first\" || entry[2] === \"last\"\n ? [entry[0], direction, entry[2]]\n : [entry[0], direction]);\n }\n\n const rawValues = (body.v && typeof body.v === \"object\" && !Array.isArray(body.v))\n ? body.v as Record<string, unknown>\n : {};\n const values: Record<string, unknown> = {};\n for (const [field, value] of Object.entries(rawValues)) values[field] = decodeValue(value);\n\n return { orderBy, values, id: decodeValue(body.i) };\n}\n\n/**\n * The `orderBy` a request should run under, given a cursor and whatever sort\n * the request itself named.\n *\n * A request that names no sort **adopts the cursor's** — that is what makes\n * `find({ after })` work without restating the `orderBy` from the previous\n * call, and it cannot be wrong, since the cursor is the only sort in play.\n * A request that names one must name the *same* one, key for key, direction for\n * direction, nulls for nulls; anything else is {@link CursorMismatchError}.\n *\n * @throws {CursorMismatchError}\n */\nexport function reconcileCursorOrder(\n cursor: DecodedCursor,\n requested: OrderByTuple[] | undefined\n): OrderByTuple[] {\n if (!requested || requested.length === 0) return cursor.orderBy;\n const spell = (keys: OrderByTuple[]) =>\n keys.map(([field, direction, nulls]) => `${field}:${direction}${nulls ? `:${nulls}` : \"\"}`);\n const cursorKeys = spell(cursor.orderBy);\n const queryKeys = spell(requested);\n if (cursorKeys.length !== queryKeys.length\n || cursorKeys.some((key, i) => key !== queryKeys[i])) {\n throw new CursorMismatchError(cursorKeys, queryKeys);\n }\n return requested;\n}\n\n/**\n * The `startAfter` shape the driver contract takes, built from a cursor.\n *\n * The driver has always accepted `{ id, values }`; this is the one place that\n * shape is produced, so the REST route and the WebSocket ingress cannot drift\n * into two spellings of the same seek.\n */\nexport function cursorToStartAfter(cursor: DecodedCursor): Record<string, unknown> {\n return { id: cursor.id, values: cursor.values };\n}\n","import type { CollectionConfig, IncludeSpec } from \"@rebasepro/types\";\nimport { type FilterCondition, type LogicalCondition, parseRelationAggregateSort } from \"@rebasepro/types\";\nimport {\n apiHiddenFieldNames,\n type FieldViewer,\n findRelation,\n includableRelationNames,\n type IncludeNode,\n normalizeInclude,\n queryableFieldNames,\n resolveCollectionRelations,\n restrictedFieldNames,\n type UnknownFieldAnswer,\n unknownAggregateField,\n unknownFilterField,\n unknownIncludeRelation,\n unknownOrderByField,\n unknownProjectionField,\n unknownRelationTargetField,\n unknownVectorProperty,\n vectorPropertyNames\n} 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 a field restricted by role. That is deliberate and it is\n * not a leak: the published OpenAPI lists every property of every collection,\n * including the ones a given caller cannot read, because the document is one\n * document and is served off the app rather than off the authenticated data\n * router. Hiding the name here would protect nothing and would answer a\n * caller's genuine typo with \"unknown field\", sending them to look for a\n * spelling mistake that is not there. Those field *names* are public; their\n * *values* are not.\n *\n * An `excludeFromApi` field is the exception, because its name is *not*\n * public: OpenAPI, `list_collections` and the generated SDK all leave it out.\n * \"`email` is not readable on `authors`\" would confirm the one thing those hide,\n * so a filter, sort, projection, include, aggregate or vector search naming one\n * is answered as one naming a field that does not exist — built by the functions the driver answers a\n * typo with (`@rebasepro/common`'s `unknown-field`), so the two answers are the\n * same bytes. See {@link assertNoHiddenNames}.\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\" | \"vector_search\";\n\nconst WHERE_LABEL: Record<Where, string> = {\n filter: \"a filter\",\n orderBy: \"`orderBy`\",\n fields: \"`fields`\",\n select: \"`select`\",\n groupBy: \"`groupBy`\",\n vector_search: \"`vector_search`\"\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 // An `excludeFromApi` name is answered as an unknown one, by\n // `assertNoHiddenNames` or `assertNoHiddenAggregateNames`, never here.\n const hidden = apiHiddenFieldNames(collection);\n\n const named = [...new Set(names.filter((n): n is string =>\n n !== undefined && refused.has(n) && !hidden.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 include?: IncludeSpec;\n vectorSearch?: { property: string };\n },\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n const filtered = assertRestrictedFieldsUnused(options, collection, viewer);\n\n // After every refusal, as the driver's unknown-field answers come after\n // them: a hidden name is a typo the driver has not reached yet.\n const include = options.include !== undefined ? normalizeInclude(options.include) : undefined;\n assertNoHiddenNames({ ...options, filtered, include: include?.tree }, collection);\n}\n\n/**\n * The aggregate route's `select` and `groupBy`, which it parses itself: an\n * `excludeFromApi` name there is answered exactly as the driver answers a\n * column that does not exist. Walked in the driver's order — each aggregate's\n * field, then each group key — and the first hidden *or* unknown name gets the\n * answer, so a typo before a hidden name is still the one named.\n */\nexport function assertNoHiddenAggregateNames(\n aggregates: readonly { fn: string; field?: string }[],\n groupBy: readonly string[],\n collection: CollectionConfig\n): void {\n const hidden = apiHiddenFieldNames(collection);\n if (hidden.size === 0) return;\n const asked = [\n ...aggregates.flatMap(({ fn, field }) => field === undefined ? [] : [{ name: field, forWhat: `${fn}()` }]),\n ...groupBy.map(name => ({ name, forWhat: \"groupBy\" }))\n ];\n if (!asked.some(({ name }) => hidden.has(name))) return;\n const valid = queryableFieldNames(collection);\n const known = (name: string) => !hidden.has(name) && (valid.includes(name) || name in (collection.properties ?? {}));\n for (const { name, forWhat } of asked) {\n if (known(name)) continue;\n const answer = unknownAggregateField(name, forWhat, valid);\n throw ApiError.badRequest(answer.message, answer.code, answer.details);\n }\n}\n\n/**\n * Refuse every field restricted by role that the read names, at every level of\n * its include tree. Returns the filter's column names, for the hidden pass.\n */\nfunction assertRestrictedFieldsUnused(\n options: Parameters<typeof assertQueryFieldsReadable>[0],\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): string[] {\n const filtered: string[] = [];\n if (options.where) filtered.push(...Object.keys(options.where));\n logicalColumns(options.logical, filtered);\n assertReadableFields(filtered.map(filterHead), collection, viewer, \"filter\");\n assertRelationPathsReadable(filtered, collection, viewer);\n\n assertReadableFields(\n (options.orderBy ?? []).map(entry => orderByColumn(entry.field)),\n collection, viewer, \"orderBy\"\n );\n assertAggregateSortsReadable(options.orderBy ?? [], collection, viewer);\n\n assertReadableFields(options.fields ?? [], collection, viewer, \"fields\");\n\n // A similarity search reads the vector it ranks by: `_distance` from a\n // point the caller chose is a measurement of it, enough of them locate it,\n // and `vector_threshold` alone answers \"is it within r of here\".\n assertReadableFields(\n options.vectorSearch ? [options.vectorSearch.property] : [],\n collection, viewer, \"vector_search\"\n );\n\n const include = options.include !== undefined ? normalizeInclude(options.include) : undefined;\n if (include) assertIncludeFieldsReadable(include.tree, collection, viewer);\n return filtered;\n}\n\n/**\n * The field a filter key reads on *this* collection: the column before a JSON\n * path's first arrow (`metadata->theme` reads `metadata`), the relation before\n * a relation path's dot (`author.email` reads `author`), the key itself\n * otherwise. A key that is a property outright wins, as the driver resolves it.\n */\nfunction filterHead(name: string): string {\n const arrow = name.indexOf(\"->\");\n if (arrow > 0) return name.slice(0, arrow);\n const dot = name.indexOf(\".\");\n if (dot > 0) return name.slice(0, dot);\n return name;\n}\n\n/**\n * `orderBy=max(staff.salary)` ranks the parents by the target's `salary` — the\n * same reading `orderBy=salary` on `staff` is — so the aggregated field is\n * judged by the target's rule. Whether the relation itself may be read is the\n * driver's check (`assertRelationReadable`), which runs on every door.\n */\nfunction assertAggregateSortsReadable(\n orderBy: readonly { field: string }[],\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n for (const { field } of orderBy) {\n const spec = parseRelationAggregateSort(field);\n if (!spec?.field) continue;\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, spec.relation);\n if (!relation) continue;\n assertReadableFields([spec.field], relation.target(), viewer, \"orderBy\");\n }\n}\n\n/**\n * `?author.salary=gt.100000` reads the target's `salary`, a row at a time, as\n * `?salary=gt.100000` on `authors` would — so it is judged by the target's\n * rule. Only a path whose first segment is a relation here; anything else is\n * the driver's to refuse.\n */\nfunction assertRelationPathsReadable(\n filtered: readonly string[],\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n for (const name of filtered) {\n const dot = name.indexOf(\".\");\n if (dot <= 0 || name.indexOf(\"->\") >= 0) continue;\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, name.slice(0, dot));\n if (!relation) continue;\n assertReadableFields([name.slice(dot + 1)], relation.target(), viewer, \"filter\");\n }\n}\n\n/**\n * Answer a read naming an `excludeFromApi` field — or relation — exactly as the\n * driver answers a read naming a field that does not exist.\n *\n * The names are walked in the order the driver resolves them — the `fields`\n * projection, then the filter, then the sort, then each include, parent before\n * child — and the first that is hidden *or* unknown gets the answer. Answering\n * only the hidden one would still tell the two apart: with `?aaa=x&email=x` the\n * driver names `aaa` when `email` does not exist, so this has to as well.\n *\n * A request naming nothing hidden is left alone entirely, so the driver stays\n * the only one answering ordinary typos.\n */\nfunction assertNoHiddenNames(\n options: {\n filtered: string[];\n orderBy?: { field: string }[];\n fields?: string[];\n include?: Record<string, IncludeNode>;\n vectorSearch?: { property: string };\n },\n collection: CollectionConfig\n): void {\n const hidden = apiHiddenFieldNames(collection);\n if (hidden.size > 0) {\n const asked: { kind: \"fields\" | \"filter\" | \"orderBy\"; name: string }[] = [\n ...(options.fields ?? []).map(name => ({ kind: \"fields\" as const, name })),\n ...options.filtered.map(name => ({ kind: \"filter\" as const, name })),\n ...(options.orderBy ?? []).flatMap(entry => {\n const name = orderByColumn(entry.field);\n return name === undefined ? [] : [{ kind: \"orderBy\" as const, name }];\n })\n ];\n const shapes = asked.map(({ kind, name }) => ({ kind, name, shape: nameShape(name, collection, hidden) }));\n const vectorHidden = options.vectorSearch !== undefined && hidden.has(options.vectorSearch.property);\n if (vectorHidden || shapes.some(entry => entry.shape.hidden)) {\n for (const { kind, name, shape } of shapes) {\n if (shape.known && !shape.hidden) continue;\n const answer = unknownAnswer(kind, name, shape, collection);\n throw ApiError.badRequest(answer.message, answer.code, answer.details);\n }\n }\n if (vectorHidden && options.vectorSearch) {\n const answer = unknownVectorProperty(options.vectorSearch.property, vectorPropertyNames(collection));\n throw ApiError.badRequest(answer.message, answer.code, answer.details);\n }\n }\n\n const relations = resolveCollectionRelations(collection);\n for (const [key, node] of Object.entries(options.include ?? {})) {\n if (hidden.has(key)) {\n const answer = unknownIncludeRelation(key, collection.slug ?? collection.name, includableRelationNames(collection));\n throw ApiError.badRequest(answer.message, answer.code, answer.details);\n }\n const relation = relations[key];\n if (!relation) continue;\n const filtered: string[] = [];\n if (node.where) filtered.push(...Object.keys(node.where));\n logicalColumns(node.logical, filtered);\n assertNoHiddenNames({\n filtered,\n orderBy: node.orderBy?.map(([field]) => ({ field })),\n fields: node.fields,\n include: node.children\n }, relation.target());\n }\n}\n\n/**\n * What a name in a read resolves to, as far as a collection can say without its\n * table: `hidden` when it reaches an `excludeFromApi` field or relation, `known`\n * when it names something the driver will resolve. A relation path\n * (`author.email`) carries its relation, to be answered about the target.\n */\ntype NameShape = {\n hidden: boolean;\n known: boolean;\n path?: { relationKey: string; fieldKey: string; target: CollectionConfig };\n};\n\nfunction nameShape(name: string, collection: CollectionConfig, hidden: ReadonlySet<string>): NameShape {\n if (hidden.has(name)) return { hidden: true, known: false };\n const columns = new Set(queryableFieldNames(collection));\n const relations = resolveCollectionRelations(collection);\n if (columns.has(name) || name in (collection.properties ?? {})) return { hidden: false, known: true };\n\n // `metadata->theme`: the column before the first arrow is what it names.\n const arrow = name.indexOf(\"->\");\n if (arrow > 0) {\n const head = name.slice(0, arrow);\n return { hidden: hidden.has(head), known: columns.has(head) };\n }\n\n // `author.email`: a field of the relation's target.\n const dot = name.indexOf(\".\");\n if (dot > 0) {\n const relationKey = name.slice(0, dot);\n const fieldKey = name.slice(dot + 1);\n if (hidden.has(relationKey)) return { hidden: true, known: false };\n const relation = findRelation(relations, relationKey);\n if (!relation) return { hidden: false, known: false };\n const target = relation.target();\n const targetHidden = apiHiddenFieldNames(target);\n const path = { relationKey, fieldKey, target };\n if (targetHidden.has(fieldKey)) return { hidden: true, known: false, path };\n const known = queryableFieldNames(target).includes(fieldKey) || fieldKey in (target.properties ?? {});\n return { hidden: false, known, path };\n }\n return { hidden: false, known: false };\n}\n\nfunction unknownAnswer(\n kind: \"fields\" | \"filter\" | \"orderBy\",\n name: string,\n shape: NameShape,\n collection: CollectionConfig\n): UnknownFieldAnswer {\n const slug = collection.slug ?? collection.name;\n if (kind === \"fields\") return unknownProjectionField(name, slug, queryableFieldNames(collection));\n if (kind === \"orderBy\") return unknownOrderByField(name, slug, queryableFieldNames(collection));\n if (shape.path) {\n return unknownRelationTargetField(\n { field: name, relationKey: shape.path.relationKey, fieldKey: shape.path.fieldKey },\n { slug: shape.path.target.slug, validFields: queryableFieldNames(shape.path.target) },\n slug\n );\n }\n return unknownFilterField(name, slug, queryableFieldNames(collection));\n}\n\n/**\n * Every relation an `include` loads, judged like the read it is part of.\n *\n * An include's own `where`, `logical`, `orderBy` and `fields` read the columns\n * of the relation's *target*: `?include={\"staff\":{\"where\":{\"salary\":[\">\",100000]}}}`\n * answers which departments employ someone paid over 100k, and `orderBy` with a\n * `limit` of one names who — while `?salary=gt.100000` on `staff` itself is\n * refused. So each node is checked against its target's rules, level by level.\n *\n * A name that is not a relation of the collection is skipped rather than\n * refused here: the driver refuses it with a 400 naming the relations there\n * are, and loads nothing for it. Looked up by exact key, as the driver does, so\n * both judge the same relation.\n */\nfunction assertIncludeFieldsReadable(\n tree: Record<string, IncludeNode>,\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n const relations = resolveCollectionRelations(collection);\n for (const [key, node] of Object.entries(tree)) {\n const relation = relations[key];\n if (!relation) continue;\n const target = relation.target();\n assertRestrictedFieldsUnused({\n where: node.where,\n logical: node.logical,\n orderBy: node.orderBy?.map(([field]) => ({ field })),\n fields: node.fields\n }, target, viewer);\n assertIncludeFieldsReadable(node.children, target, viewer);\n }\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 PAGE_WITH_OFFSET_MESSAGE,\n decodeCursor,\n deserializeFilter,\n deserializeInclude,\n deserializeLogicalCondition,\n normalizeInclude,\n reconcileCursorOrder,\n RESERVED_QUERY_KEYS,\n UnknownFilterOperatorError\n} from \"@rebasepro/common\";\nimport { QueryOptions } from \"../types\";\nimport { ApiError } from \"../errors\";\nimport { DELETED_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 // An operator the dialect does not have is the same refusal the JSON\n // `where` gives for it, by the same code.\n if (e instanceof UnknownFilterOperatorError) {\n throw invalidParam(e.message, e.code, e.details);\n }\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 // Two names for one window. `page` used to win silently, so a caller who\n // sent `offset=10` read whatever page two was and was told nothing — the\n // same quiet override `after` + `offset` is refused for below.\n if (pageVal && offsetVal) {\n throw invalidParam(PAGE_WITH_OFFSET_MESSAGE, \"PAGE_WITH_OFFSET\");\n }\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 //\n // The list is shared with the SDK, which sends a filter on a column with\n // one of these names inside `?where=` rather than as its own parameter.\n const filterDict: Record<string, unknown> = {};\n for (const [key, rawValue] of Object.entries(query)) {\n if (RESERVED_QUERY_KEYS.has(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 // `?count=exact|none` — whether a listing counts `meta.total`. Refused\n // rather than read as the default when it is neither: a `?count=nnone`\n // typo would otherwise pay for the count it was written to skip, and say\n // nothing.\n const countVal = getLastValue(query.count);\n if (countVal !== undefined && countVal !== null && String(countVal) !== \"\") {\n const text = String(countVal).trim().toLowerCase();\n if (text !== \"exact\" && text !== \"none\") {\n throw invalidParam(\n `Invalid \\`count\\` parameter: expected \\`exact\\` or \\`none\\`, got ${JSON.stringify(String(countVal))}.`,\n \"INVALID_COUNT\"\n );\n }\n options.count = text;\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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"}
1
+ {"version":3,"file":"query-parser-CDXWf2hH.js","names":[],"sources":["../../common/src/collections/unknown-field.ts","../../common/src/data/cursor.ts","../src/api/rest/field-access-query.ts","../src/api/rest/query-parser.ts"],"sourcesContent":["import type { CollectionConfig, Property } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, findRelation, resolveCollectionRelations } from \"../util/relations\";\nimport { apiHiddenFieldNames } from \"./field-access\";\n\n/**\n * The answers a query gets for naming a field that does not exist — written\n * once, here, because two layers give them.\n *\n * The driver gives them for a typo. The REST check gives the same ones for a\n * field marked `excludeFromApi`: OpenAPI, `list_collections` and the generated\n * SDK all leave such a field out, so anything but the typo's answer — \"not\n * readable\", a different message, a list that names it — confirms it exists.\n * Both build the answer from these functions, so the two cannot drift apart by\n * a byte. Each returns the parts of the 400 (`message`, `code`, `details`) and\n * the caller wraps them in its own error type.\n *\n * @module\n */\n\nexport interface UnknownFieldAnswer {\n message: string;\n code: string;\n details: Record<string, unknown>;\n}\n\n/**\n * The fields an unknown-field answer offers as valid, sorted: what a query on\n * this collection can name as a column. A relation contributes its foreign key\n * (`authorId`) when the key is on this table, and nothing otherwise. An\n * `excludeFromApi` field is never offered.\n *\n * Read off the collection rather than the table, so the REST check — which\n * holds no table — lists exactly what the driver lists.\n */\nexport function queryableFieldNames(collection: CollectionConfig): string[] {\n const hidden = apiHiddenFieldNames(collection);\n const names = new Set<string>();\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n for (const [key, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property | undefined)?.type === \"relation\") {\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, key);\n if (relation?.kind === \"belongsTo\") names.add(fieldKeyForColumn(collection, relation.localKey));\n continue;\n }\n names.add(key);\n }\n return [...names].filter(name => !hidden.has(name)).sort();\n}\n\nconst validList = (validFields: string[]): string =>\n validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\";\n\n/** A filter key that resolves to nothing on `collectionPath`. */\nexport function unknownFilterField(field: string, collectionPath: string, validFields: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown filter field '${field}' on collection '${collectionPath}'${validList(validFields)}`,\n code: \"UNKNOWN_FILTER_FIELD\",\n details: { field, collection: collectionPath, ...(validFields.length > 0 && { validFields }) }\n };\n}\n\n/** `author.emial` — a relation path whose last segment names nothing on the relation's target. */\nexport function unknownRelationTargetField(\n path: { field: string; relationKey: string; fieldKey: string },\n target: { slug: string; validFields: string[] },\n collectionPath: string\n): UnknownFieldAnswer {\n return {\n message: `Unknown field '${path.fieldKey}' on '${target.slug}', the target of relation ` +\n `'${path.relationKey}' on collection '${collectionPath}'${validList(target.validFields)}`,\n code: \"UNKNOWN_FILTER_FIELD\",\n details: {\n field: path.field,\n collection: collectionPath,\n relation: path.relationKey,\n targetCollection: target.slug,\n ...(target.validFields.length > 0 && { validFields: target.validFields })\n }\n };\n}\n\n/** An `orderBy` key that resolves to nothing. `collectionName` is absent when the read carries no collection. */\nexport function unknownOrderByField(orderBy: string, collectionName: string | undefined, validFields: string[]): UnknownFieldAnswer {\n const onCollection = collectionName ? ` on collection '${collectionName}'` : \"\";\n return {\n message: `Unknown orderBy field '${orderBy}'${onCollection}${validList(validFields)}`,\n code: \"UNKNOWN_ORDER_BY_FIELD\",\n details: {\n field: orderBy,\n ...(collectionName && { collection: collectionName }),\n ...(validFields.length > 0 && { validFields })\n }\n };\n}\n\n/** A `fields` entry that names nothing. */\nexport function unknownProjectionField(name: string, collectionName: string, validFields: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown field '${name}' in \\`fields\\` on collection '${collectionName}'. Valid fields: ${validFields.join(\", \")}`,\n code: \"UNKNOWN_FIELD\",\n details: { field: name, collection: collectionName }\n };\n}\n\n/**\n * An `include` naming no relation of the collection. `validRelations` is what\n * the collection declares, never an `excludeFromApi` relation.\n */\nexport function unknownIncludeRelation(key: string, collectionName: string, validRelations: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown relation '${key}' on collection '${collectionName}'`\n + (validRelations.length > 0\n ? `. Its relations are: ${validRelations.join(\", \")}`\n : \". It declares no relations.\"),\n code: \"UNKNOWN_RELATION\",\n details: { relation: key, collection: collectionName, ...(validRelations.length > 0 && { validRelations }) }\n };\n}\n\n/** The relations an unknown-relation answer offers, sorted — never an `excludeFromApi` one. */\nexport function includableRelationNames(collection: CollectionConfig): string[] {\n const hidden = apiHiddenFieldNames(collection);\n return Object.keys(resolveCollectionRelations(collection)).filter(key => !hidden.has(key)).sort();\n}\n\n/**\n * An aggregate's `select` field or `groupBy` key that names no column.\n * `forWhat` is where it was named: `sum()`, `count()`, `groupBy`.\n * `validFields` is {@link queryableFieldNames}, never an `excludeFromApi` field.\n */\nexport function unknownAggregateField(field: string, forWhat: string, validFields: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown field '${field}' in ${forWhat}${validList(validFields)}`,\n code: \"UNKNOWN_AGGREGATE_FIELD\",\n details: { field, ...(validFields.length > 0 && { validFields }) }\n };\n}\n\n/**\n * The properties a `vector_search` can name, sorted: those declared\n * `{ type: \"vector\" }`, never an `excludeFromApi` one.\n */\nexport function vectorPropertyNames(collection: CollectionConfig): string[] {\n const hidden = apiHiddenFieldNames(collection);\n return Object.entries(collection.properties ?? {})\n .filter(([key, property]) => (property as Property | undefined)?.type === \"vector\" && !hidden.has(key))\n .map(([key]) => key)\n .sort();\n}\n\n/** A `vector_search` naming no vector property. `vectorProperties` is {@link vectorPropertyNames}. */\nexport function unknownVectorProperty(property: string, vectorProperties: string[]): UnknownFieldAnswer {\n return {\n message: `Unknown vector property \"${property}\". ` +\n (vectorProperties.length > 0\n ? `This collection's vector properties are: ${vectorProperties.join(\", \")}.`\n : \"This collection declares no `vector` property to search.\"),\n code: \"UNKNOWN_VECTOR_PROPERTY\",\n details: { property, ...(vectorProperties.length > 0 && { vectorProperties }) }\n };\n}\n","import { parseRelationAggregateSort, type OrderByTuple } from \"@rebasepro/types\";\n\n/**\n * The keyset-cursor wire codec.\n *\n * ## Why this is one module\n *\n * Keyset pagination was implemented three times and reachable once. The driver\n * has a NULL-correct multi-key comparison (`FetchService.buildKeysetComparison`)\n * that only a WebSocket `startAfter` could reach; REST could not seek at all;\n * and the SDK's `iterate({cursor})` re-implemented a *single*-column keyset as a\n * `where` clause, which threw on any multi-key sort and silently dropped rows\n * whose sort value was NULL. Three implementations, three answers to \"what is\n * page two\".\n *\n * There is now one. The driver's comparison is the implementation; this module\n * is the only thing that says how a cursor is written down, and every transport\n * — the REST `?after=`, the WebSocket `startAfter`, the SDK's `iterate()` —\n * carries the string this produces and hands it back unread.\n *\n * ## What a cursor holds\n *\n * The sort keys the query was ordered by, the last served row's value for each\n * of them, and that row's id. The keys travel *with* the values because a\n * cursor that carried only values would be silently reinterpretable: paging a\n * `created_at DESC` listing and then asking for `title ASC` would seek on the\n * dates as though they were titles. Carrying the keys makes that a refusal\n * ({@link CursorMismatchError}) rather than a page of arbitrary rows.\n *\n * ## Opacity\n *\n * The encoding is base64url of JSON, and it is **not** API. It is opaque so it\n * can change — adding a key, changing how a value is tagged — without every\n * client that learned to read it breaking. Nothing outside this file parses it.\n *\n * @module\n */\n\n/** The decoded contents of a cursor. */\nexport interface DecodedCursor {\n /** The sort keys the cursor was produced under, in order of significance. */\n orderBy: OrderByTuple[];\n /** The last served row's value for each sort key, by field name. */\n values: Record<string, unknown>;\n /** The last served row's id, which breaks ties on the last key. */\n id: unknown;\n}\n\n/** A cursor that cannot be read at all — truncated, re-encoded, or invented. */\nexport class CursorError extends Error {\n readonly code = \"INVALID_CURSOR\";\n constructor(detail: string) {\n super(\n `Invalid \\`after\\` cursor: ${detail}. Pass back the \\`meta.nextCursor\\` ` +\n \"from the previous page unchanged — it is opaque and must not be built by hand.\"\n );\n this.name = \"CursorError\";\n Object.setPrototypeOf(this, CursorError.prototype);\n }\n}\n\n/**\n * A cursor that reads fine but describes a different query.\n *\n * Separate from {@link CursorError} because the fix is different: this one is\n * not a corrupt string, it is a correct cursor used against a sort it was not\n * produced under. Seeking anyway would return rows in an order nobody asked\n * for, and — worse — would look like it worked.\n */\nexport class CursorMismatchError extends Error {\n readonly code = \"CURSOR_ORDER_MISMATCH\";\n constructor(cursorKeys: string[], queryKeys: string[]) {\n super(\n `The \\`after\\` cursor was produced by a query ordered by ` +\n `${cursorKeys.map(k => `\"${k}\"`).join(\", \") || \"(nothing)\"}, but this query orders by ` +\n `${queryKeys.map(k => `\"${k}\"`).join(\", \") || \"(nothing)\"}. A cursor only continues the ` +\n \"listing it came from — keep `orderBy` identical across pages, or drop `after` to start over.\"\n );\n this.name = \"CursorMismatchError\";\n Object.setPrototypeOf(this, CursorMismatchError.prototype);\n }\n}\n\n/**\n * Tag for a value whose JSON round-trip would otherwise lose its type.\n *\n * A `timestamp` column comes back from the driver as a `Date`; JSON turns it\n * into a string, and the string would then be compared against the column by\n * whatever cast Postgres chose. Round-tripping it as a `Date` keeps the\n * comparison the one the ORDER BY made.\n */\nconst DATE_TAG = \"$date\";\n\nfunction encodeValue(value: unknown): unknown {\n if (value instanceof Date) return { [DATE_TAG]: value.toISOString() };\n return value;\n}\n\nfunction decodeValue(value: unknown): unknown {\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const tagged = (value as Record<string, unknown>)[DATE_TAG];\n if (typeof tagged === \"string\") {\n const date = new Date(tagged);\n return Number.isNaN(date.getTime()) ? tagged : date;\n }\n }\n return value;\n}\n\n/** base64url, without depending on Node's Buffer (this package runs in browsers). */\nfunction toBase64Url(text: string): string {\n const bytes = new TextEncoder().encode(text);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nfunction fromBase64Url(encoded: string): string {\n const padded = encoded.replace(/-/g, \"+\").replace(/_/g, \"/\")\n + \"=\".repeat((4 - (encoded.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Encode \"everything strictly after this row, in this order\".\n *\n * @param orderBy the sort keys the listing ran under, in order of significance\n * @param row the last row served, which the next page picks up after\n * @param id that row's id — the tiebreaker every keyset comparison ends on\n * @returns the opaque cursor, or `undefined` when no cursor can describe the\n * page. That is not a failure: a listing sorted by relevance has no stored\n * value to compare a later page against (scores are computed per query and\n * are not on the same scale between two of them), so it pages by offset and\n * `meta.nextCursor` is simply absent.\n */\nexport function encodeCursor(\n orderBy: OrderByTuple[] | undefined,\n row: Record<string, unknown>,\n id: unknown\n): string | undefined {\n if (id === undefined || id === null) return undefined;\n const keys = orderBy ?? [];\n // A key whose value is not on the row cannot be seeked past. Rather than\n // emit a cursor that the next request would refuse, emit none — the caller\n // falls back to offset paging, which is what it did before cursors existed.\n //\n // An aggregate over a relation (`count(orders)`) is the exception: it is\n // never on the row, and it needs no value here, because the keyset\n // comparison recomputes the cursor row's value in SQL from the id.\n // Refusing it withheld the cursor from every such listing.\n const values: Record<string, unknown> = {};\n for (const [field] of keys) {\n if (field in row) {\n values[field] = encodeValue(row[field]);\n continue;\n }\n if (parseRelationAggregateSort(field)) continue;\n return undefined;\n }\n return toBase64Url(JSON.stringify({ k: keys, v: values, i: encodeValue(id) }));\n}\n\n/**\n * Read a cursor produced by {@link encodeCursor}.\n *\n * @throws {CursorError} when the string is not a cursor this codec wrote.\n */\nexport function decodeCursor(raw: string): DecodedCursor {\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(raw.trim()));\n } catch {\n throw new CursorError(\"it is not a cursor this API issued\");\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new CursorError(\"it does not decode to a cursor\");\n }\n const body = parsed as { k?: unknown; v?: unknown; i?: unknown };\n if (!Array.isArray(body.k)) throw new CursorError(\"it carries no sort keys\");\n if (body.i === undefined) throw new CursorError(\"it carries no row id\");\n\n const orderBy: OrderByTuple[] = [];\n for (const entry of body.k) {\n if (!Array.isArray(entry) || typeof entry[0] !== \"string\") {\n throw new CursorError(\"one of its sort keys is malformed\");\n }\n const direction = entry[1] === \"desc\" ? \"desc\" : \"asc\";\n orderBy.push(entry[2] === \"first\" || entry[2] === \"last\"\n ? [entry[0], direction, entry[2]]\n : [entry[0], direction]);\n }\n\n const rawValues = (body.v && typeof body.v === \"object\" && !Array.isArray(body.v))\n ? body.v as Record<string, unknown>\n : {};\n const values: Record<string, unknown> = {};\n for (const [field, value] of Object.entries(rawValues)) values[field] = decodeValue(value);\n\n return { orderBy, values, id: decodeValue(body.i) };\n}\n\n/**\n * The `orderBy` a request should run under, given a cursor and whatever sort\n * the request itself named.\n *\n * A request that names no sort **adopts the cursor's** — that is what makes\n * `find({ after })` work without restating the `orderBy` from the previous\n * call, and it cannot be wrong, since the cursor is the only sort in play.\n * A request that names one must name the *same* one, key for key, direction for\n * direction, nulls for nulls; anything else is {@link CursorMismatchError}.\n *\n * @throws {CursorMismatchError}\n */\nexport function reconcileCursorOrder(\n cursor: DecodedCursor,\n requested: OrderByTuple[] | undefined\n): OrderByTuple[] {\n if (!requested || requested.length === 0) return cursor.orderBy;\n const spell = (keys: OrderByTuple[]) =>\n keys.map(([field, direction, nulls]) => `${field}:${direction}${nulls ? `:${nulls}` : \"\"}`);\n const cursorKeys = spell(cursor.orderBy);\n const queryKeys = spell(requested);\n if (cursorKeys.length !== queryKeys.length\n || cursorKeys.some((key, i) => key !== queryKeys[i])) {\n throw new CursorMismatchError(cursorKeys, queryKeys);\n }\n return requested;\n}\n\n/**\n * The `startAfter` shape the driver contract takes, built from a cursor.\n *\n * The driver has always accepted `{ id, values }`; this is the one place that\n * shape is produced, so the REST route and the WebSocket ingress cannot drift\n * into two spellings of the same seek.\n */\nexport function cursorToStartAfter(cursor: DecodedCursor): Record<string, unknown> {\n return { id: cursor.id, values: cursor.values };\n}\n","import type { CollectionConfig, IncludeSpec } from \"@rebasepro/types\";\nimport { type FilterCondition, type LogicalCondition, parseRelationAggregateSort } from \"@rebasepro/types\";\nimport {\n apiHiddenFieldNames,\n type FieldViewer,\n findRelation,\n includableRelationNames,\n type IncludeNode,\n normalizeInclude,\n queryableFieldNames,\n resolveCollectionRelations,\n restrictedFieldNames,\n type UnknownFieldAnswer,\n unknownAggregateField,\n unknownFilterField,\n unknownIncludeRelation,\n unknownOrderByField,\n unknownProjectionField,\n unknownRelationTargetField,\n unknownVectorProperty,\n vectorPropertyNames\n} 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 a field restricted by role. That is deliberate and it is\n * not a leak: the published OpenAPI lists every property of every collection,\n * including the ones a given caller cannot read, because the document is one\n * document and is served off the app rather than off the authenticated data\n * router. Hiding the name here would protect nothing and would answer a\n * caller's genuine typo with \"unknown field\", sending them to look for a\n * spelling mistake that is not there. Those field *names* are public; their\n * *values* are not.\n *\n * An `excludeFromApi` field is the exception, because its name is *not*\n * public: OpenAPI, `list_collections` and the generated SDK all leave it out.\n * \"`email` is not readable on `authors`\" would confirm the one thing those hide,\n * so a filter, sort, projection, include, aggregate or vector search naming one\n * is answered as one naming a field that does not exist — built by the functions the driver answers a\n * typo with (`@rebasepro/common`'s `unknown-field`), so the two answers are the\n * same bytes. See {@link assertNoHiddenNames}.\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\" | \"vector_search\";\n\nconst WHERE_LABEL: Record<Where, string> = {\n filter: \"a filter\",\n orderBy: \"`orderBy`\",\n fields: \"`fields`\",\n select: \"`select`\",\n groupBy: \"`groupBy`\",\n vector_search: \"`vector_search`\"\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 // An `excludeFromApi` name is answered as an unknown one, by\n // `assertNoHiddenNames` or `assertNoHiddenAggregateNames`, never here.\n const hidden = apiHiddenFieldNames(collection);\n\n const named = [...new Set(names.filter((n): n is string =>\n n !== undefined && refused.has(n) && !hidden.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 include?: IncludeSpec;\n vectorSearch?: { property: string };\n },\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n const filtered = assertRestrictedFieldsUnused(options, collection, viewer);\n\n // After every refusal, as the driver's unknown-field answers come after\n // them: a hidden name is a typo the driver has not reached yet.\n const include = options.include !== undefined ? normalizeInclude(options.include) : undefined;\n assertNoHiddenNames({ ...options, filtered, include: include?.tree }, collection);\n}\n\n/**\n * The aggregate route's `select` and `groupBy`, which it parses itself: an\n * `excludeFromApi` name there is answered exactly as the driver answers a\n * column that does not exist. Walked in the driver's order — each aggregate's\n * field, then each group key — and the first hidden *or* unknown name gets the\n * answer, so a typo before a hidden name is still the one named.\n */\nexport function assertNoHiddenAggregateNames(\n aggregates: readonly { fn: string; field?: string }[],\n groupBy: readonly string[],\n collection: CollectionConfig\n): void {\n const hidden = apiHiddenFieldNames(collection);\n if (hidden.size === 0) return;\n const asked = [\n ...aggregates.flatMap(({ fn, field }) => field === undefined ? [] : [{ name: field, forWhat: `${fn}()` }]),\n ...groupBy.map(name => ({ name, forWhat: \"groupBy\" }))\n ];\n if (!asked.some(({ name }) => hidden.has(name))) return;\n const valid = queryableFieldNames(collection);\n const known = (name: string) => !hidden.has(name) && (valid.includes(name) || name in (collection.properties ?? {}));\n for (const { name, forWhat } of asked) {\n if (known(name)) continue;\n const answer = unknownAggregateField(name, forWhat, valid);\n throw ApiError.badRequest(answer.message, answer.code, answer.details);\n }\n}\n\n/**\n * Refuse every field restricted by role that the read names, at every level of\n * its include tree. Returns the filter's column names, for the hidden pass.\n */\nfunction assertRestrictedFieldsUnused(\n options: Parameters<typeof assertQueryFieldsReadable>[0],\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): string[] {\n const filtered: string[] = [];\n if (options.where) filtered.push(...Object.keys(options.where));\n logicalColumns(options.logical, filtered);\n assertReadableFields(filtered.map(filterHead), collection, viewer, \"filter\");\n assertRelationPathsReadable(filtered, collection, viewer);\n\n assertReadableFields(\n (options.orderBy ?? []).map(entry => orderByColumn(entry.field)),\n collection, viewer, \"orderBy\"\n );\n assertAggregateSortsReadable(options.orderBy ?? [], collection, viewer);\n\n assertReadableFields(options.fields ?? [], collection, viewer, \"fields\");\n\n // A similarity search reads the vector it ranks by: `_distance` from a\n // point the caller chose is a measurement of it, enough of them locate it,\n // and `vector_threshold` alone answers \"is it within r of here\".\n assertReadableFields(\n options.vectorSearch ? [options.vectorSearch.property] : [],\n collection, viewer, \"vector_search\"\n );\n\n const include = options.include !== undefined ? normalizeInclude(options.include) : undefined;\n if (include) assertIncludeFieldsReadable(include.tree, collection, viewer);\n return filtered;\n}\n\n/**\n * The field a filter key reads on *this* collection: the column before a JSON\n * path's first arrow (`metadata->theme` reads `metadata`), the relation before\n * a relation path's dot (`author.email` reads `author`), the key itself\n * otherwise. A key that is a property outright wins, as the driver resolves it.\n */\nfunction filterHead(name: string): string {\n const arrow = name.indexOf(\"->\");\n if (arrow > 0) return name.slice(0, arrow);\n const dot = name.indexOf(\".\");\n if (dot > 0) return name.slice(0, dot);\n return name;\n}\n\n/**\n * `orderBy=max(staff.salary)` ranks the parents by the target's `salary` — the\n * same reading `orderBy=salary` on `staff` is — so the aggregated field is\n * judged by the target's rule. Whether the relation itself may be read is the\n * driver's check (`assertRelationReadable`), which runs on every door.\n */\nfunction assertAggregateSortsReadable(\n orderBy: readonly { field: string }[],\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n for (const { field } of orderBy) {\n const spec = parseRelationAggregateSort(field);\n if (!spec?.field) continue;\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, spec.relation);\n if (!relation) continue;\n assertReadableFields([spec.field], relation.target(), viewer, \"orderBy\");\n }\n}\n\n/**\n * `?author.salary=gt.100000` reads the target's `salary`, a row at a time, as\n * `?salary=gt.100000` on `authors` would — so it is judged by the target's\n * rule. Only a path whose first segment is a relation here; anything else is\n * the driver's to refuse.\n */\nfunction assertRelationPathsReadable(\n filtered: readonly string[],\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n for (const name of filtered) {\n const dot = name.indexOf(\".\");\n if (dot <= 0 || name.indexOf(\"->\") >= 0) continue;\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, name.slice(0, dot));\n if (!relation) continue;\n assertReadableFields([name.slice(dot + 1)], relation.target(), viewer, \"filter\");\n }\n}\n\n/**\n * Answer a read naming an `excludeFromApi` field — or relation — exactly as the\n * driver answers a read naming a field that does not exist.\n *\n * The names are walked in the order the driver resolves them — the `fields`\n * projection, then the filter, then the sort, then each include, parent before\n * child — and the first that is hidden *or* unknown gets the answer. Answering\n * only the hidden one would still tell the two apart: with `?aaa=x&email=x` the\n * driver names `aaa` when `email` does not exist, so this has to as well.\n *\n * A request naming nothing hidden is left alone entirely, so the driver stays\n * the only one answering ordinary typos.\n */\nfunction assertNoHiddenNames(\n options: {\n filtered: string[];\n orderBy?: { field: string }[];\n fields?: string[];\n include?: Record<string, IncludeNode>;\n vectorSearch?: { property: string };\n },\n collection: CollectionConfig\n): void {\n const hidden = apiHiddenFieldNames(collection);\n if (hidden.size > 0) {\n const asked: { kind: \"fields\" | \"filter\" | \"orderBy\"; name: string }[] = [\n ...(options.fields ?? []).map(name => ({ kind: \"fields\" as const, name })),\n ...options.filtered.map(name => ({ kind: \"filter\" as const, name })),\n ...(options.orderBy ?? []).flatMap(entry => {\n const name = orderByColumn(entry.field);\n return name === undefined ? [] : [{ kind: \"orderBy\" as const, name }];\n })\n ];\n const shapes = asked.map(({ kind, name }) => ({ kind, name, shape: nameShape(name, collection, hidden) }));\n const vectorHidden = options.vectorSearch !== undefined && hidden.has(options.vectorSearch.property);\n if (vectorHidden || shapes.some(entry => entry.shape.hidden)) {\n for (const { kind, name, shape } of shapes) {\n if (shape.known && !shape.hidden) continue;\n const answer = unknownAnswer(kind, name, shape, collection);\n throw ApiError.badRequest(answer.message, answer.code, answer.details);\n }\n }\n if (vectorHidden && options.vectorSearch) {\n const answer = unknownVectorProperty(options.vectorSearch.property, vectorPropertyNames(collection));\n throw ApiError.badRequest(answer.message, answer.code, answer.details);\n }\n }\n\n const relations = resolveCollectionRelations(collection);\n for (const [key, node] of Object.entries(options.include ?? {})) {\n if (hidden.has(key)) {\n const answer = unknownIncludeRelation(key, collection.slug ?? collection.name, includableRelationNames(collection));\n throw ApiError.badRequest(answer.message, answer.code, answer.details);\n }\n const relation = relations[key];\n if (!relation) continue;\n const filtered: string[] = [];\n if (node.where) filtered.push(...Object.keys(node.where));\n logicalColumns(node.logical, filtered);\n assertNoHiddenNames({\n filtered,\n orderBy: node.orderBy?.map(([field]) => ({ field })),\n fields: node.fields,\n include: node.children\n }, relation.target());\n }\n}\n\n/**\n * What a name in a read resolves to, as far as a collection can say without its\n * table: `hidden` when it reaches an `excludeFromApi` field or relation, `known`\n * when it names something the driver will resolve. A relation path\n * (`author.email`) carries its relation, to be answered about the target.\n */\ntype NameShape = {\n hidden: boolean;\n known: boolean;\n path?: { relationKey: string; fieldKey: string; target: CollectionConfig };\n};\n\nfunction nameShape(name: string, collection: CollectionConfig, hidden: ReadonlySet<string>): NameShape {\n if (hidden.has(name)) return { hidden: true, known: false };\n const columns = new Set(queryableFieldNames(collection));\n const relations = resolveCollectionRelations(collection);\n if (columns.has(name) || name in (collection.properties ?? {})) return { hidden: false, known: true };\n\n // `metadata->theme`: the column before the first arrow is what it names.\n const arrow = name.indexOf(\"->\");\n if (arrow > 0) {\n const head = name.slice(0, arrow);\n return { hidden: hidden.has(head), known: columns.has(head) };\n }\n\n // `author.email`: a field of the relation's target.\n const dot = name.indexOf(\".\");\n if (dot > 0) {\n const relationKey = name.slice(0, dot);\n const fieldKey = name.slice(dot + 1);\n if (hidden.has(relationKey)) return { hidden: true, known: false };\n const relation = findRelation(relations, relationKey);\n if (!relation) return { hidden: false, known: false };\n const target = relation.target();\n const targetHidden = apiHiddenFieldNames(target);\n const path = { relationKey, fieldKey, target };\n if (targetHidden.has(fieldKey)) return { hidden: true, known: false, path };\n const known = queryableFieldNames(target).includes(fieldKey) || fieldKey in (target.properties ?? {});\n return { hidden: false, known, path };\n }\n return { hidden: false, known: false };\n}\n\nfunction unknownAnswer(\n kind: \"fields\" | \"filter\" | \"orderBy\",\n name: string,\n shape: NameShape,\n collection: CollectionConfig\n): UnknownFieldAnswer {\n const slug = collection.slug ?? collection.name;\n if (kind === \"fields\") return unknownProjectionField(name, slug, queryableFieldNames(collection));\n if (kind === \"orderBy\") return unknownOrderByField(name, slug, queryableFieldNames(collection));\n if (shape.path) {\n return unknownRelationTargetField(\n { field: name, relationKey: shape.path.relationKey, fieldKey: shape.path.fieldKey },\n { slug: shape.path.target.slug, validFields: queryableFieldNames(shape.path.target) },\n slug\n );\n }\n return unknownFilterField(name, slug, queryableFieldNames(collection));\n}\n\n/**\n * Every relation an `include` loads, judged like the read it is part of.\n *\n * An include's own `where`, `logical`, `orderBy` and `fields` read the columns\n * of the relation's *target*: `?include={\"staff\":{\"where\":{\"salary\":[\">\",100000]}}}`\n * answers which departments employ someone paid over 100k, and `orderBy` with a\n * `limit` of one names who — while `?salary=gt.100000` on `staff` itself is\n * refused. So each node is checked against its target's rules, level by level.\n *\n * A name that is not a relation of the collection is skipped rather than\n * refused here: the driver refuses it with a 400 naming the relations there\n * are, and loads nothing for it. Looked up by exact key, as the driver does, so\n * both judge the same relation.\n */\nfunction assertIncludeFieldsReadable(\n tree: Record<string, IncludeNode>,\n collection: CollectionConfig,\n viewer: FieldViewer | undefined\n): void {\n const relations = resolveCollectionRelations(collection);\n for (const [key, node] of Object.entries(tree)) {\n const relation = relations[key];\n if (!relation) continue;\n const target = relation.target();\n assertRestrictedFieldsUnused({\n where: node.where,\n logical: node.logical,\n orderBy: node.orderBy?.map(([field]) => ({ field })),\n fields: node.fields\n }, target, viewer);\n assertIncludeFieldsReadable(node.children, target, viewer);\n }\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 PAGE_WITH_OFFSET_MESSAGE,\n decodeCursor,\n deserializeFilter,\n deserializeInclude,\n deserializeLogicalCondition,\n normalizeInclude,\n reconcileCursorOrder,\n RESERVED_QUERY_KEYS,\n UnknownFilterOperatorError\n} from \"@rebasepro/common\";\nimport { QueryOptions } from \"../types\";\nimport { ApiError } from \"../errors\";\nimport { DELETED_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 // An operator the dialect does not have is the same refusal the JSON\n // `where` gives for it, by the same code.\n if (e instanceof UnknownFilterOperatorError) {\n throw invalidParam(e.message, e.code, e.details);\n }\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 // Two names for one window. `page` used to win silently, so a caller who\n // sent `offset=10` read whatever page two was and was told nothing — the\n // same quiet override `after` + `offset` is refused for below.\n if (pageVal && offsetVal) {\n throw invalidParam(PAGE_WITH_OFFSET_MESSAGE, \"PAGE_WITH_OFFSET\");\n }\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 //\n // The list is shared with the SDK, which sends a filter on a column with\n // one of these names inside `?where=` rather than as its own parameter.\n const filterDict: Record<string, unknown> = {};\n for (const [key, rawValue] of Object.entries(query)) {\n if (RESERVED_QUERY_KEYS.has(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 // `?count=exact|none` — whether a listing counts `meta.total`. Refused\n // rather than read as the default when it is neither: a `?count=nnone`\n // typo would otherwise pay for the count it was written to skip, and say\n // nothing.\n const countVal = getLastValue(query.count);\n if (countVal !== undefined && countVal !== null && String(countVal) !== \"\") {\n const text = String(countVal).trim().toLowerCase();\n if (text !== \"exact\" && text !== \"none\") {\n throw invalidParam(\n `Invalid \\`count\\` parameter: expected \\`exact\\` or \\`none\\`, got ${JSON.stringify(String(countVal))}.`,\n \"INVALID_COUNT\"\n );\n }\n options.count = text;\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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"}
@@ -3,8 +3,8 @@ import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
5
  import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
- import { r as logger } from "./logger-Bt32sHFw.js";
7
- import { i as errorHandler, t as ApiError } from "./errors-_ytoGi8A.js";
6
+ import { a as logger } from "./logger-CW8pmTS6.js";
7
+ import { i as errorHandler, t as ApiError } from "./errors-OMWkKSH-.js";
8
8
  //#region src/functions/request-timeout.ts
9
9
  var request_timeout_exports = /* @__PURE__ */ __exportAll({
10
10
  DEFAULT_FUNCTIONS_TIMEOUT_MS: () => DEFAULT_FUNCTIONS_TIMEOUT_MS,
@@ -37,9 +37,9 @@ function resolveFunctionsTimeoutMs(configured) {
37
37
  * Node server is constructed without `requestTimeout`/`headersTimeout`, so a
38
38
  * handler awaiting a promise that never settles — a `fetch` to an unreachable
39
39
  * third party with no `AbortSignal`, a query on a wedged connection — holds its
40
- * socket and its request object until the client gives up. On the managed
41
- * runtime the process is shared between tenants, so that is not only the slow
42
- * caller's problem.
40
+ * socket and its request object until the client gives up. The process also
41
+ * serves the project's data API, auth and realtime, so that is not only the
42
+ * slow caller's problem.
43
43
  *
44
44
  * The handler is **not** killed — JavaScript cannot stop a function from the
45
45
  * outside. What is bounded is the client-visible request: after `ms` the caller
@@ -85,4 +85,4 @@ function createFunctionsRequestTimeout(ms) {
85
85
  //#endregion
86
86
  export { request_timeout_exports as t };
87
87
 
88
- //# sourceMappingURL=request-timeout-CRny5KD0.js.map
88
+ //# sourceMappingURL=request-timeout-DVjo-c_8.js.map