@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
@@ -2,7 +2,66 @@ 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 { l as hostEnv, r as logger } from "./logger-Bt32sHFw.js";
5
+ import { a as logger, d as hostEnv } from "./logger-CW8pmTS6.js";
6
+ //#region src/api/closed-database.ts
7
+ /**
8
+ * A database closed to new connections (`ALLOW_CONNECTIONS false`), read off
9
+ * the error a refused connection raises, and the operator's line that names it.
10
+ *
11
+ * Its own module rather than in `errors.ts`, which the package root re-exports
12
+ * whole: these are the server's to use, not part of its API.
13
+ *
14
+ * @module
15
+ */
16
+ /** How often the closed-database line is said: once a minute, as the other database lines in `errors.ts` are. */
17
+ var CLOSED_WARNING_INTERVAL_MS = 6e4;
18
+ /** When the closed-database line was last said. */
19
+ var lastClosedWarningAt = 0;
20
+ /**
21
+ * The database that refused the connection because it is closed to new ones
22
+ * (`ALLOW_CONNECTIONS false`), or null.
23
+ *
24
+ * Postgres says it as `database "x" is not currently accepting connections`,
25
+ * SQLSTATE 55000. That code also covers statements the database judged
26
+ * (`lastval is not yet defined in this session`), so the sentence is what is
27
+ * matched — through Drizzle's `cause` and a dual-stack `AggregateError`. The
28
+ * driver's CLI reads it the same way (`closedDatabaseName` in
29
+ * `@rebasepro/server-postgres`'s `cli-errors.ts`).
30
+ */
31
+ function closedDatabaseName(error, depth = 0) {
32
+ if (typeof error !== "object" || error === null || depth > 8) return null;
33
+ const message = Reflect.get(error, "message");
34
+ if (typeof message === "string") {
35
+ const named = /database "(.+)" is not currently accepting connections/.exec(message);
36
+ if (named) return named[1];
37
+ }
38
+ const inner = Reflect.get(error, "errors");
39
+ if (Array.isArray(inner)) for (const item of inner) {
40
+ const named = closedDatabaseName(item, depth + 1);
41
+ if (named) return named;
42
+ }
43
+ return closedDatabaseName(Reflect.get(error, "cause"), depth + 1);
44
+ }
45
+ /**
46
+ * The operator's line for a closed database, at most once a minute, when
47
+ * `error` is one: which database, that nothing is lost, and what opens it. `true`
48
+ * when it was one — said now or within the minute — so the caller says nothing
49
+ * more general about it.
50
+ *
51
+ * Nothing in a development database closes one but `rebase db branch create
52
+ * --force`, which closes its source for the copy; one stopped part-way leaves
53
+ * it closed, and every `rebase db branch` command opens it again first.
54
+ */
55
+ function warnIfDatabaseClosed(error) {
56
+ const database = closedDatabaseName(error);
57
+ if (database === null) return false;
58
+ const now = Date.now();
59
+ if (now - lastClosedWarningAt < CLOSED_WARNING_INTERVAL_MS) return true;
60
+ lastClosedWarningAt = now;
61
+ logger.error(`❌ Database "${database}" is closed to new connections (ALLOW_CONNECTIONS false), so requests that need it are answered 503 DATABASE_UNAVAILABLE. Nothing in it is lost: \`rebase db branch create --force\` closes its source for the copy, and one stopped part-way leaves it closed. Open it again with \`rebase db branch list\` (every \`rebase db branch\` command opens it first), or run ALTER DATABASE "${database.replace(/"/g, "\"\"")}" ALLOW_CONNECTIONS true; connected to the server's postgres database. Not repeated for a minute.`);
62
+ return true;
63
+ }
64
+ //#endregion
6
65
  //#region src/api/errors.ts
7
66
  /**
8
67
  * A stale caller's schema stamp, as the cause of the error it explains.
@@ -193,6 +252,7 @@ function isSocketFailure(error, depth = 0) {
193
252
  */
194
253
  function isDatabaseUnreachable(error) {
195
254
  if (typeof error !== "object" || error === null) return false;
255
+ if (closedDatabaseName(error) !== null) return true;
196
256
  const reported = databaseReport(error);
197
257
  if (reported !== void 0) return reported;
198
258
  const socketFailureOf = (candidate) => typeof candidate === "object" && candidate !== null && isSocketFailure(candidate) && Reflect.get(candidate, "$metadata") === void 0;
@@ -530,9 +590,10 @@ var errorHandler = (err, c) => {
530
590
  } else if (unreachable) {
531
591
  code = "DATABASE_UNAVAILABLE";
532
592
  statusCode = 503;
593
+ const closedDatabase = closedDatabaseName(error);
533
594
  const reason = describeConnectionFailure(error);
534
- logMessage = `The database could not be reached: ${reason}.`;
535
- warnDatabaseUnreachable(reason);
595
+ logMessage = closedDatabase !== null ? `Database "${closedDatabase}" is closed to new connections.` : `The database could not be reached: ${reason}.`;
596
+ if (!warnIfDatabaseClosed(error)) warnDatabaseUnreachable(reason);
536
597
  c.header("Retry-After", String(DATABASE_RETRY_AFTER_SECONDS));
537
598
  } else if (upstream) {
538
599
  code = "SERVICE_UNAVAILABLE";
@@ -669,6 +730,6 @@ function codeToStatus(code) {
669
730
  }[code];
670
731
  }
671
732
  //#endregion
672
- export { isDataException as a, errorHandler as i, DATABASE_UNAVAILABLE_MESSAGE as n, isDatabaseUnreachable as o, declaredErrorAnswer as r, schemaDriftRemedy as s, ApiError as t };
733
+ export { isDataException as a, warnIfDatabaseClosed as c, errorHandler as i, DATABASE_UNAVAILABLE_MESSAGE as n, isDatabaseUnreachable as o, declaredErrorAnswer as r, schemaDriftRemedy as s, ApiError as t };
673
734
 
674
- //# sourceMappingURL=errors-_ytoGi8A.js.map
735
+ //# sourceMappingURL=errors-OMWkKSH-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-OMWkKSH-.js","names":[],"sources":["../src/api/closed-database.ts","../src/api/errors.ts"],"sourcesContent":["/**\n * A database closed to new connections (`ALLOW_CONNECTIONS false`), read off\n * the error a refused connection raises, and the operator's line that names it.\n *\n * Its own module rather than in `errors.ts`, which the package root re-exports\n * whole: these are the server's to use, not part of its API.\n *\n * @module\n */\nimport { logger } from \"../utils/logger\";\n\n/** How often the closed-database line is said: once a minute, as the other database lines in `errors.ts` are. */\nconst CLOSED_WARNING_INTERVAL_MS = 60_000;\n\n/** When the closed-database line was last said. */\nlet lastClosedWarningAt = 0;\n\n/** Forget when the closed-database line was last said. @internal For tests only. */\nexport function _resetClosedDatabaseWarningForTests(): void {\n lastClosedWarningAt = 0;\n}\n\n/**\n * The database that refused the connection because it is closed to new ones\n * (`ALLOW_CONNECTIONS false`), or null.\n *\n * Postgres says it as `database \"x\" is not currently accepting connections`,\n * SQLSTATE 55000. That code also covers statements the database judged\n * (`lastval is not yet defined in this session`), so the sentence is what is\n * matched — through Drizzle's `cause` and a dual-stack `AggregateError`. The\n * driver's CLI reads it the same way (`closedDatabaseName` in\n * `@rebasepro/server-postgres`'s `cli-errors.ts`).\n */\nexport function closedDatabaseName(error: unknown, depth = 0): string | null {\n if (typeof error !== \"object\" || error === null || depth > 8) return null;\n const message = Reflect.get(error, \"message\");\n if (typeof message === \"string\") {\n const named = /database \"(.+)\" is not currently accepting connections/.exec(message);\n if (named) return named[1];\n }\n const inner = Reflect.get(error, \"errors\");\n if (Array.isArray(inner)) {\n for (const item of inner) {\n const named = closedDatabaseName(item, depth + 1);\n if (named) return named;\n }\n }\n return closedDatabaseName(Reflect.get(error, \"cause\"), depth + 1);\n}\n\n/**\n * The operator's line for a closed database, at most once a minute, when\n * `error` is one: which database, that nothing is lost, and what opens it. `true`\n * when it was one — said now or within the minute — so the caller says nothing\n * more general about it.\n *\n * Nothing in a development database closes one but `rebase db branch create\n * --force`, which closes its source for the copy; one stopped part-way leaves\n * it closed, and every `rebase db branch` command opens it again first.\n */\nexport function warnIfDatabaseClosed(error: unknown): boolean {\n const database = closedDatabaseName(error);\n if (database === null) return false;\n const now = Date.now();\n if (now - lastClosedWarningAt < CLOSED_WARNING_INTERVAL_MS) return true;\n lastClosedWarningAt = now;\n logger.error(\n `❌ Database \"${database}\" is closed to new connections (ALLOW_CONNECTIONS false), so requests that need it ` +\n \"are answered 503 DATABASE_UNAVAILABLE. Nothing in it is lost: `rebase db branch create --force` closes its \" +\n \"source for the copy, and one stopped part-way leaves it closed. Open it again with `rebase db branch list` \" +\n `(every \\`rebase db branch\\` command opens it first), or run ALTER DATABASE \"${database.replace(/\"/g, '\"\"')}\" ` +\n \"ALLOW_CONNECTIONS true; connected to the server's postgres database. Not repeated for a minute.\"\n );\n return true;\n}\n","import type { Context, ErrorHandler } from \"hono\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport type { HonoEnv } from \"./types\";\nimport { logger } from \"../utils/logger\";\nimport { hostEnv } from \"../utils/host\";\nimport { closedDatabaseName, warnIfDatabaseClosed } from \"./closed-database\";\n\n/**\n * A stale caller's schema stamp, as the cause of the error it explains.\n *\n * `createSchemaDriftDetector` puts the two stamps on the context when a request\n * carries an `x-rebase-schema` older than this backend's. It lives next door;\n * this half is here because `errors.ts` is in the graph of\n * `@rebasepro/server/functions` and may not reach `@rebasepro/types` at runtime.\n */\nfunction schemaDriftCause(drift: { client: string; server: string } | undefined): {\n code: string;\n message: string;\n clientSchema: string;\n serverSchema: string;\n} | undefined {\n if (!drift) return undefined;\n return {\n code: \"SCHEMA_DRIFT\",\n message:\n `This client was generated against schema ${drift.client}; this backend serves `\n + `${drift.server}. If the field named above was renamed or removed, regenerate the `\n + \"SDK (`rebase generate-sdk`) and rebuild.\",\n clientSchema: drift.client,\n serverSchema: drift.server\n };\n}\n\n/** Tracks whether we've already shown the doctor hint (once per process). */\nlet _schemaDriftHinted = false;\n\n/**\n * The schema-drift remedy, in the words that work on *this* database.\n *\n * On the managed development database boot applies additive changes, so\n * restarting `rebase dev` is the first fix there, and `rebase db push` — which\n * plans against a throwaway database of its own — applies what boot leaves\n * alone. Elsewhere the push or the migrations are the fix, and a deployed\n * runtime applies the schema at boot, so the remedy has to know which database\n * is under this run.\n *\n * `REBASE_DEV_DATABASE_KIND` is set by the CLI from the database it resolved,\n * the same variable `rebase schema generate`'s closing line already branches\n * on. Absent — a deployed backend, a container, anything not started by\n * `rebase dev` — the answer is the general one.\n */\nexport function schemaDriftRemedy(): { short: string; lines: string[] } {\n if (hostEnv().REBASE_DEV_DATABASE_KIND === \"managed\") {\n return {\n short: \"Restart `rebase dev` — boot applies additive schema changes to the managed database — \" +\n \"or run `rebase db push` for a change boot leaves alone.\",\n lines: [\n \" Quick fixes (managed development database):\",\n \" restart `rebase dev` boot applies additive changes\",\n \" rebase db push applies what boot leaves alone\",\n \" (a drop or a retype; it asks first)\",\n \" rebase doctor full 3-way drift report\"\n ]\n };\n }\n\n return {\n short: \"Run `rebase db push` to sync your schema, or `rebase db migrate` to apply pending migrations.\",\n lines: [\n \" Quick fixes (local dev, against DATABASE_URL):\",\n \" rebase db push sync schema to database (dev)\",\n \" rebase db migrate apply pending migrations (prod)\",\n \" rebase doctor full 3-way drift report\",\n \"\",\n \" Managed cloud: the runtime applies schema + RLS at boot\",\n \" (REBASE_MIGRATE_ON_BOOT); redeploy rather than db push,\",\n \" which cannot reach the tenant database.\"\n ]\n };\n}\n\n/** Shape of Postgres / network errors with diagnostic codes */\ninterface PgLikeError {\n code?: string;\n address?: string;\n port?: number;\n message?: string;\n table?: string;\n column?: string;\n schema?: string;\n detail?: string;\n hint?: string;\n constraint?: string;\n}\n\n/** 5-character SQLSTATE, e.g. `42501`, `23505`. */\nconst SQLSTATE_RE = /^[0-9A-Z]{5}$/;\n\n/**\n * What SQLSTATE 25006 means here, in the only terms that help the author fix it.\n *\n * Every request-scoped read runs `withTransaction(..., { accessMode: \"read\n * only\" })`, so a write attempted anywhere under it — including from a\n * `context.data` call inside an `afterRead` callback — is refused by Postgres\n * rather than by us. The callback name is in the message because that is the\n * file the reader has to open, and nothing else on a read path can raise this.\n */\nconst READ_ONLY_TRANSACTION_MESSAGE =\n \"An `afterRead` callback tried to write. Request-scoped reads run in a READ ONLY \" +\n \"transaction, so neither the callback nor anything it calls (context.data included) \" +\n \"may write. Move the write outside the read: enqueue a background job, or use \" +\n \"`rebase.dataAsAdmin` from a job or a custom function.\";\n\n/**\n * Walk the cause chain for the underlying database error, identified by a\n * 5-char SQLSTATE `code`. Drizzle wraps the pg error in `.cause`, and route\n * code sometimes wraps drizzle again, so the real error may sit several\n * levels down.\n */\nfunction extractDbError(error: unknown, depth = 0): PgLikeError | null {\n if (!error || typeof error !== \"object\" || depth > 8) return null;\n const e = error as PgLikeError & { cause?: unknown };\n if (typeof e.code === \"string\" && SQLSTATE_RE.test(e.code)) return e;\n if (e.cause && typeof e.cause === \"object\") return extractDbError(e.cause, depth + 1);\n return null;\n}\n\n/**\n * Did the database refuse a value as unreadable for its column's type —\n * SQLSTATE class 22, anywhere down the cause chain?\n *\n * For a lookup by an id the caller supplied, that is an answer rather than a\n * failure: `abc` against a UUID key names no row, exactly as a well-formed id\n * nobody has does.\n */\nexport function isDataException(error: unknown): boolean {\n return extractDbError(error)?.code?.startsWith(\"22\") === true;\n}\n\n/**\n * The two ways a request fails before it ever reaches the database, read off\n * the cause chain by pg-pool's own wording (it sets no code on either).\n *\n * - `timeout exceeded when trying to connect` — every pooled connection was\n * busy for the whole of `connectionTimeoutMillis`. pg-pool raises it only\n * from its wait queue, so it means the pool is exhausted: slow queries\n * holding connections, or `DB_POOL_MAX` too small for the load.\n * - `Connection terminated due to connection timeout` — a fresh connection\n * took longer than that to open: the database is slow or far away.\n *\n * Either is load, not a fault. Answered as a 500 they read as a crash and gave\n * a client nothing to back off on; as a 503 with `Retry-After` the SDK's\n * existing retry path takes them (`offline-connectivity.ts`).\n */\nconst POOL_FAILURES: ReadonlyArray<{ message: string; code: \"DB_POOL_EXHAUSTED\" | \"DB_CONNECT_TIMEOUT\" }> = [\n { message: \"timeout exceeded when trying to connect\", code: \"DB_POOL_EXHAUSTED\" },\n { message: \"Connection terminated due to connection timeout\", code: \"DB_CONNECT_TIMEOUT\" }\n];\n\nfunction poolFailureOf(error: unknown, depth = 0): \"DB_POOL_EXHAUSTED\" | \"DB_CONNECT_TIMEOUT\" | undefined {\n if (!error || typeof error !== \"object\" || depth > 8) return undefined;\n const message = Reflect.get(error, \"message\");\n if (typeof message === \"string\") {\n const match = POOL_FAILURES.find(failure => message === failure.message);\n if (match) return match.code;\n }\n return poolFailureOf(Reflect.get(error, \"cause\"), depth + 1);\n}\n\n/** Seconds a client is asked to wait after a pool failure — one pool timeout's worth is too long to make a person wait. */\nconst POOL_RETRY_AFTER_SECONDS = 1;\n\n/**\n * SQLSTATEs that mean the database dropped or refused the connection, not\n * that it judged the statement: class 08 (connection exception), and the\n * three shutdown codes — `57P01` a server shutting down or an administrator\n * ending the session, `57P02` a crash restart, `57P03` a server still\n * starting.\n */\nconst UNREACHABLE_SQLSTATES = new Set([\"57P01\", \"57P02\", \"57P03\"]);\n\n/**\n * What node-postgres says when the socket under a client closed, in the words\n * it sets no code on.\n */\nconst CONNECTION_LOST_MESSAGES = new Set([\n \"Connection terminated unexpectedly\",\n \"Client has encountered a connection error and is not queryable\"\n]);\n\n/** The socket failures Node raises when a host cannot be reached or drops the connection. */\nconst SOCKET_FAILURE_CODES = new Set([\n \"ECONNREFUSED\", \"ECONNRESET\", \"ENOTFOUND\", \"EAI_AGAIN\", \"EHOSTUNREACH\", \"ENETUNREACH\", \"ETIMEDOUT\", \"EPIPE\"\n]);\n\n/**\n * A Node socket failure: one of {@link SOCKET_FAILURE_CODES} on an error the\n * runtime raised for a system call, or the `AggregateError` `net` raises when\n * every address a host resolved to failed that way.\n */\nfunction isSocketFailure(error: object, depth = 0): boolean {\n const code = Reflect.get(error, \"code\");\n if (typeof code === \"string\" && SOCKET_FAILURE_CODES.has(code) && typeof Reflect.get(error, \"syscall\") === \"string\") {\n return true;\n }\n const inner = Reflect.get(error, \"errors\");\n return depth < 2 && Array.isArray(inner) && inner.length > 0 &&\n inner.every(item => typeof item === \"object\" && item !== null && isSocketFailure(item, depth + 1));\n}\n\n/**\n * Did the request fail because the database could not be reached at all — not\n * because it refused the statement, and not because some other service is down?\n *\n * Read off the error the database driver raised, wherever it sits in the cause\n * chain:\n *\n * - a SQLSTATE that means the connection was refused or dropped (class `08`,\n * `57P01`–`57P03`). The database's own word, so it settles the question\n * either way: a SQLSTATE of any other class is the database answering;\n * - node-postgres's own sentence for a socket that closed under a query;\n * - a socket failure (`ECONNREFUSED`, `ENOTFOUND`, …), but only where it can\n * only be the database's: thrown as it is, which is how node-postgres hands\n * back a pool connection it could not open, or as the cause of a failed\n * Drizzle query (the wrapper that carries the statement). Under any other\n * wrapper it belongs to another client: `fetch failed` is a `fetch()` to\n * some other host, and an S3 call that cannot connect carries the AWS SDK's\n * `$metadata`. Answering those \"the database is unreachable\" would send an\n * operator to a database that is fine.\n */\nexport function isDatabaseUnreachable(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n\n // Ahead of the SQLSTATE read below, which takes 55000 for the database\n // answering: closed to new connections, it cannot serve the request at all.\n if (closedDatabaseName(error) !== null) return true;\n\n const reported = databaseReport(error);\n if (reported !== undefined) return reported;\n\n const socketFailureOf = (candidate: unknown): boolean =>\n typeof candidate === \"object\" && candidate !== null &&\n isSocketFailure(candidate) && Reflect.get(candidate, \"$metadata\") === undefined;\n if (socketFailureOf(error)) return true;\n return typeof Reflect.get(error, \"query\") === \"string\" && socketFailureOf(Reflect.get(error, \"cause\"));\n}\n\n/**\n * What the database itself said about the connection, down the cause chain:\n * `true` for a lost or refused connection, `false` for a SQLSTATE that is the\n * database answering, `undefined` when nothing on the chain is the database's.\n */\nfunction databaseReport(error: object, depth = 0): boolean | undefined {\n const code = Reflect.get(error, \"code\");\n // `EPIPE` has a SQLSTATE's shape, and is a socket's.\n if (typeof code === \"string\" && SQLSTATE_RE.test(code) && !SOCKET_FAILURE_CODES.has(code)) {\n return code.startsWith(\"08\") || UNREACHABLE_SQLSTATES.has(code);\n }\n const message = Reflect.get(error, \"message\");\n if (typeof message === \"string\" && CONNECTION_LOST_MESSAGES.has(message)) return true;\n const cause = Reflect.get(error, \"cause\");\n return depth < 8 && typeof cause === \"object\" && cause !== null ? databaseReport(cause, depth + 1) : undefined;\n}\n\n/**\n * The operator's half of a connection that failed — to the database, or to a\n * service a request called — in a few words: the socket failure and the host\n * it names (`ECONNREFUSED at 127.0.0.1:5432`), or what the database or its\n * client said about the connection.\n */\nfunction describeConnectionFailure(error: unknown, depth = 0): string {\n if (typeof error !== \"object\" || error === null || depth > 8) return \"the connection failed\";\n const code = Reflect.get(error, \"code\");\n const message = Reflect.get(error, \"message\");\n if (typeof code === \"string\" && SOCKET_FAILURE_CODES.has(code)) {\n const address = Reflect.get(error, \"address\");\n const port = Reflect.get(error, \"port\");\n const hostname = Reflect.get(error, \"hostname\");\n if (address !== undefined) return `${code} at ${String(address)}${port !== undefined ? `:${String(port)}` : \"\"}`;\n if (typeof hostname === \"string\") return `${code} for ${hostname}`;\n }\n if (typeof message === \"string\" && (CONNECTION_LOST_MESSAGES.has(message) ||\n (typeof code === \"string\" && (code.startsWith(\"08\") || UNREACHABLE_SQLSTATES.has(code))))) {\n return typeof code === \"string\" ? `${message} (${code})` : message;\n }\n const inner = Reflect.get(error, \"errors\");\n if (Array.isArray(inner) && inner.length > 0) return describeConnectionFailure(inner[0], depth + 1);\n const cause = Reflect.get(error, \"cause\");\n if (typeof cause === \"object\" && cause !== null) return describeConnectionFailure(cause, depth + 1);\n return typeof code === \"string\" ? code : \"the connection failed\";\n}\n\n/**\n * Is there a Node socket failure under this error, anywhere down its cause\n * chain?\n *\n * Asked only once {@link isDatabaseUnreachable} has said no, so what it finds\n * belongs to another service: a `fetch()` to some other host (`fetch failed`,\n * its `cause` the socket's error), an S3 call that cannot connect (the AWS SDK\n * hands back the socket's own error with `$metadata` on it), or anything else\n * wrapping one. An error that only carries a socket code, with no system call\n * behind it, is not one.\n */\nfunction hasUpstreamSocketFailure(error: unknown, depth = 0): boolean {\n if (typeof error !== \"object\" || error === null || depth > 8) return false;\n return isSocketFailure(error) || hasUpstreamSocketFailure(Reflect.get(error, \"cause\"), depth + 1);\n}\n\n/** What a caller is told when a service the request needs cannot be reached. The host stays in the log. */\nconst UPSTREAM_UNAVAILABLE_MESSAGE =\n \"A service this request depends on could not be reached, so it was not served. Retry shortly.\";\n\n/** What a caller is told when the database cannot be reached. Retryable, and not about them. */\nexport const DATABASE_UNAVAILABLE_MESSAGE =\n \"The database could not be reached, so this request was not served. \" +\n \"Retry shortly; if it keeps happening, the server's database is down or unreachable from it.\";\n\n/** Seconds a client is asked to wait before retrying a request the database could not be reached for. */\nconst DATABASE_RETRY_AFTER_SECONDS = 5;\n\n/** The operator's line for an unreachable database, at most once a minute — see {@link warnPoolFailure}. */\nlet lastUnreachableWarningAt = 0;\n\nfunction warnDatabaseUnreachable(detail: string): void {\n const now = Date.now();\n if (now - lastUnreachableWarningAt < POOL_WARNING_INTERVAL_MS) return;\n lastUnreachableWarningAt = now;\n logger.error(\n `❌ The database is unreachable (${detail}), so requests that need it are answered 503 DATABASE_UNAVAILABLE. ` +\n \"Check that it is running and that this server can reach it; requests recover on their own once it is back. \" +\n \"Not repeated for a minute.\"\n );\n}\n\n/**\n * The operator's line, at most once a minute. A pool that is exhausted is\n * exhausted for every request at once, and a line per request buries the one\n * thing to do about it.\n */\nconst POOL_WARNING_INTERVAL_MS = 60_000;\nlet lastPoolWarningAt = 0;\n\nfunction warnPoolFailure(code: \"DB_POOL_EXHAUSTED\" | \"DB_CONNECT_TIMEOUT\"): void {\n const now = Date.now();\n if (now - lastPoolWarningAt < POOL_WARNING_INTERVAL_MS) return;\n lastPoolWarningAt = now;\n logger.warn(code === \"DB_POOL_EXHAUSTED\"\n ? \"⚠️ Database pool exhausted: every connection stayed busy for DB_POOL_CONNECT_TIMEOUT, so requests \" +\n \"are answered 503. Raise DB_POOL_MAX (default 20; DB_POOL_MAX__<KEY> for another data source), or \" +\n \"find the slow queries or long transactions holding connections. Not repeated for a minute.\"\n : \"⚠️ Opening a database connection took longer than DB_POOL_CONNECT_TIMEOUT, so requests are \" +\n \"answered 503. The database is slow to accept connections or far away. Not repeated for a minute.\");\n}\n\n/**\n * Extract the missing table or column name from a PG error.\n * PG 42P01 messages look like: 'relation \"my_table\" does not exist'\n * PG 42703 messages look like: 'column \"my_col\" does not exist' or 'column my_table.my_col does not exist'\n */\nfunction extractMissingIdentifier(pgMessage?: string): string | null {\n if (!pgMessage) return null;\n // Match quoted identifier: relation \"xxx\" / column \"xxx\"\n const quoted = pgMessage.match(/(?:relation|column|table)\\s+\"([^\"]+)\"/i);\n if (quoted) return quoted[1];\n // Match unquoted: column table.col does not exist\n const unquoted = pgMessage.match(/(?:relation|column|table)\\s+([\\w.]+)\\s+does not exist/i);\n if (unquoted) return unquoted[1];\n return null;\n}\n\n/**\n * The sentence a `INVALID_FILTER_VALUE` answer carries.\n *\n * Built from Postgres's own wording rather than passed through, because the\n * driver hands the whole failed statement over as `error.message` and this one\n * goes to the caller in production too. What is quoted back is the type name\n * and the literal the caller themselves sent — never a table, a column list or\n * a statement.\n *\n * The three shapes are the ones a filter actually produces: an unparseable\n * literal (22P02, `?id=eq.abc`), a value outside an enum's labels (22P02 with\n * different wording), and a number past the column type's range (22003). A\n * fourth SQLSTATE in class 22 lands on the general sentence, which still says\n * the useful thing: it is the value that is wrong, not the server.\n */\nfunction describeDataException(dbError?: PgLikeError): string {\n const message = dbError?.message ?? \"\";\n const column = dbError?.column ? ` for column \"${dbError.column}\"` : \"\";\n\n const syntax = message.match(/invalid input syntax for type ([\\w ]+): \"(.*)\"/);\n if (syntax) return `\"${syntax[2]}\" is not a valid ${syntax[1]}${column}.`;\n\n const enumValue = message.match(/invalid input value for enum ([\\w.\"]+): \"(.*)\"/);\n if (enumValue) return `\"${enumValue[2]}\" is not one of the values of ${enumValue[1]}${column}.`;\n\n const range = message.match(/value \"(.*)\" is out of range for type ([\\w ]+)/);\n if (range) return `\"${range[1]}\" is out of range for ${range[2]}${column}.`;\n\n return `A value in this request could not be read as the type of the column it was compared against${column}.`;\n}\n\n/**\n * Standardized API error class.\n * Throw this from any route handler — the errorHandler middleware\n * will format it into `{ error: { message, code, details? } }`.\n */\nexport class ApiError extends Error {\n public readonly statusCode: number;\n public readonly code: string;\n public readonly details?: unknown;\n /**\n * Whether this outcome is a routine part of normal operation rather than\n * something an operator should look at. Expected errors log at debug; every\n * other operational error logs at warn.\n *\n * The motivating case is `POST /auth/refresh` with no session: clients\n * refresh on page load before they know whether one exists, so every\n * anonymous page view is a 401 — correct, and not worth a warning line.\n *\n * The other class is a caller-caused 4xx that never reached the database: a\n * mistyped filter operator, sort direction or limit, a request for a\n * collection that does not exist. Nothing on this server is wrong, and the\n * response body has already told the caller what to fix — while one client\n * holding a stale name would otherwise write a warning per request, forever,\n * until the level means nothing. See `api/rest/query-parser.ts`.\n *\n * What stays at warn is anything that says something about the *server*:\n * a schema that has drifted from the code, a permission the database\n * refused, a dependency that failed. Those are 4xx too, and they are still\n * incidents.\n */\n public readonly expected: boolean;\n\n constructor(statusCode: number, code: string, message: string, details?: unknown, expected = false) {\n super(message);\n this.name = \"ApiError\";\n this.statusCode = statusCode;\n this.code = code;\n this.details = details;\n this.expected = expected;\n }\n\n // ── Factory methods ──────────────────────────────────────────────\n\n static badRequest(message: string, code = \"BAD_REQUEST\", details?: unknown): ApiError {\n return new ApiError(400, code, message, details);\n }\n\n static unauthorized(message: string, code = \"UNAUTHORIZED\"): ApiError {\n return new ApiError(401, code, message);\n }\n\n /**\n * A 401 that is a normal outcome, not an incident — logged at debug.\n * See {@link ApiError.expected}.\n */\n static unauthenticated(message: string, code = \"UNAUTHORIZED\"): ApiError {\n return new ApiError(401, code, message, undefined, true);\n }\n\n static forbidden(message: string, code = \"FORBIDDEN\"): ApiError {\n return new ApiError(403, code, message);\n }\n\n static notFound(message: string, code = \"NOT_FOUND\"): ApiError {\n return new ApiError(404, code, message);\n }\n\n /**\n * `details` because a 409 is usually a `23505`, and the one thing the\n * caller needs is *which field* collided. The column name goes in; the\n * value never does — see `pgFieldViolations`.\n */\n static conflict(message: string, code = \"CONFLICT\", details?: unknown): ApiError {\n return new ApiError(409, code, message, details);\n }\n\n static internal(message: string, code = \"INTERNAL_ERROR\"): ApiError {\n return new ApiError(500, code, message);\n }\n\n static serviceUnavailable(message: string, code = \"SERVICE_UNAVAILABLE\"): ApiError {\n return new ApiError(503, code, message);\n }\n\n /**\n * `503 DATABASE_UNAVAILABLE`: the database could not be reached. What\n * every door answers then — the data API, sign-in, the admin routes and\n * the realtime socket — so a client sees one retryable answer, sent with\n * `Retry-After`, rather than a permission failure or a crash.\n *\n * @see isDatabaseUnreachable\n */\n static databaseUnavailable(message = DATABASE_UNAVAILABLE_MESSAGE): ApiError {\n return new ApiError(503, \"DATABASE_UNAVAILABLE\", message);\n }\n}\n\n/**\n * Canonical error response shape:\n * `{ error: { message: string, code: string, details?: unknown } }`\n */\nexport interface ErrorResponse {\n error: {\n message: string;\n code: string;\n details?: unknown;\n /** Request correlation ID for tracing (echoes X-Request-ID). */\n requestId?: string;\n /**\n * Why this request was going to fail whatever it asked for.\n *\n * Only `SCHEMA_DRIFT` today: the caller's `x-rebase-schema` stamp is\n * older than this backend's, so a 400 naming an unknown field is very\n * likely a rename the client has not regenerated for. The error itself\n * is unchanged — this explains it, it does not cause it.\n */\n cause?: {\n code: string;\n message: string;\n clientSchema: string;\n serverSchema: string;\n };\n };\n}\n\n/**\n * General shape of errors that flow through the API error handler.\n * Extends Error with optional HTTP status, error code, and details.\n */\nexport interface RebaseApiError extends Error {\n statusCode?: number;\n code?: string;\n details?: unknown;\n}\n\n/**\n * The answer an error chose for itself, read the same way at every door.\n *\n * @see declaredErrorAnswer\n */\nexport interface DeclaredErrorAnswer {\n /** The HTTP status the error carries. A socket frame has no slot for it. */\n status: number;\n code: string;\n message: string;\n details?: unknown;\n /** See {@link ApiError.expected}: log it at debug, not warn. */\n expected: boolean;\n}\n\n/**\n * The status, code and message an error carries as its own answer — or\n * `undefined` for an error that carries none, which is a server fault and gets\n * masked.\n *\n * Two classes carry one. The server's `ApiError`, and `RebaseApiError` (or its\n * `RebaseClientError` subclass) from `@rebasepro/types` once it has a status.\n * The second is the browser-safe class: a `config/collections/*.ts` file is\n * bundled into the admin SPA and cannot import this package, so it is what a\n * collection callback throws, and what a callback refusal becomes —\n * `callbackRefusal` returns one, and `toCallbackError` wraps anything thrown\n * that does not already carry a status.\n *\n * One function because several doors turn an error into an answer: the REST\n * error handler, the two WebSocket servers, and the Postgres realtime\n * subscriptions. Each used to list the classes it recognised by hand. The\n * sockets listed only `ApiError`, so a `beforeDelete` veto that REST\n * answered as 400 `CALLBACK_REJECTED` with the author's message reached the\n * admin panel — which writes through the socket — as `INTERNAL_ERROR`, and in\n * production as \"An unexpected error occurred\".\n *\n * Matched by name as well as `instanceof`: a monorepo can resolve two copies of\n * a package, and `instanceof` is false across them. Name matching is also why\n * this file needs no runtime import of `@rebasepro/types`, which it may not\n * have — it is in the graph of `@rebasepro/server/functions`.\n */\nexport function declaredErrorAnswer(error: unknown): DeclaredErrorAnswer | undefined {\n if (error === null || typeof error !== \"object\") return undefined;\n const e = error as { name?: unknown; message?: unknown; code?: unknown; details?: unknown; statusCode?: unknown; status?: unknown };\n\n let status: number | undefined;\n if (error instanceof ApiError || e.name === \"ApiError\") {\n status = typeof e.statusCode === \"number\" ? e.statusCode : undefined;\n } else if (typeof e.name === \"string\" && /^Rebase(Api|Client)Error$/.test(e.name)) {\n // It spells its status `status`; `statusCode` wins when both are set.\n status = typeof e.statusCode === \"number\" ? e.statusCode\n : typeof e.status === \"number\" ? e.status\n : undefined;\n // Without a status it has not chosen an answer — `RebaseClientError`\n // is also raised for plain logic errors — so it is not one here.\n if (status === undefined) return undefined;\n } else {\n return undefined;\n }\n\n return {\n status: status || 500,\n code: typeof e.code === \"string\" && e.code ? e.code : \"INTERNAL_ERROR\",\n message: typeof e.message === \"string\" ? e.message : String(e.message ?? \"\"),\n ...(e.details !== undefined && { details: e.details }),\n expected: error instanceof ApiError && error.expected\n };\n}\n\n// `isRebaseApiError` was here. It read `return error instanceof Error`, so it\n// answered yes to every error while being named and used as though it\n// discriminated — the create and update handlers guarded a \"classify this as\n// BAD_REQUEST\" branch on it, and an unreachable database was therefore reported\n// to callers as a bad request. Deleted rather than repaired: the shape it\n// claimed to test is not decidable from an `Error`, and the layer that does\n// know — the driver, which holds the SQLSTATE — raises a real `ApiError`.\n\n/**\n * Leave the code and the message where the request log will find them.\n *\n * A failed request used to produce two lines, each holding half of it: this\n * handler had the code and the diagnosis, `requestLogger` had the user, the\n * collection, the status and the latency. Correlating them meant matching on\n * the request id — which only one of them printed reliably — and the pair cost\n * twice the volume for less than one line's worth of meaning.\n */\nfunction handOffToRequestLog(c: Context<HonoEnv>, code: string, message: string, expected = false): void {\n if (typeof c.set !== \"function\") return;\n c.set(\"errorSummary\", { code, message, expected });\n}\n\n/**\n * Is a request line coming for this request?\n *\n * `requestLogger` claims it before the handler runs, so by the time an error\n * reaches here the answer is already known. When nothing claimed it — a router\n * a project mounted onto its own Hono app, a test driving `app.fetch`\n * directly — this handler stays the only thing that would report the failure,\n * so it still writes its own line. Silence is the one outcome neither half may\n * produce.\n */\nfunction requestWillBeLogged(c: Context<HonoEnv>): boolean {\n return typeof c.get === \"function\" && c.get(\"requestLogged\") === true;\n}\n\n/**\n * Hono error-handling middleware (`app.onError`).\n * Converts any error into the canonical `{ error: { message, code } }` shape.\n */\nexport const errorHandler: ErrorHandler<HonoEnv> = (err, c) => {\n // Typecast custom error properties\n const error: RebaseApiError = err;\n const reqId = typeof c.get === \"function\" ? c.get(\"requestId\") : undefined;\n\n /* A stale SDK, named on the errors it explains.\n\n 400 and 404 only: those are what a renamed or removed field produces —\n an unknown filter field is a 400, a collection gone from under its slug\n is a 404 — and they are the two a caller can act on by regenerating.\n Attaching it to a 500 would be noise, since a server fault has nothing to\n do with how old the caller's schema is. */\n const driftFor = (status: number) =>\n (status === 400 || status === 404) && typeof c.get === \"function\"\n ? schemaDriftCause(c.get(\"schemaDrift\"))\n : undefined;\n\n // An error that chose its own answer — `ApiError`, or the browser-safe\n // `RebaseApiError` a collection callback throws. The same predicate the\n // WebSocket servers use; see `declaredErrorAnswer`.\n const answer = declaredErrorAnswer(error);\n if (answer) {\n // Operational errors — log at warn, unless the error declares itself a\n // routine outcome (see ApiError.expected), which would otherwise put a\n // warning in the log for every anonymous page view.\n handOffToRequestLog(c, answer.code, answer.message, answer.expected);\n if (answer.code === \"DATABASE_UNAVAILABLE\") c.header(\"Retry-After\", String(DATABASE_RETRY_AFTER_SECONDS));\n if (!requestWillBeLogged(c)) {\n const line = `[API] ${c.req.method} ${c.req.path} → ${answer.status} ${answer.code}: ${answer.message}` +\n (reqId ? ` [${reqId}]` : \"\");\n if (answer.expected) {\n logger.debug(line);\n } else {\n logger.warn(`⚠️ ${line}`);\n }\n }\n const apiErrorDrift = driftFor(answer.status);\n return c.json({\n error: {\n message: answer.message,\n code: answer.code,\n ...(answer.details !== undefined && { details: answer.details }),\n ...(reqId && { requestId: reqId }),\n ...(apiErrorDrift && { cause: apiErrorDrift })\n }\n } satisfies ErrorResponse, answer.status as ContentfulStatusCode);\n }\n\n // A body that is not JSON, read with `c.req.json()` — every auth and admin\n // route reads its body that way. It threw a bare `SyntaxError`, which\n // landed here as a 500 logged with a stack: a server fault for the\n // caller's typo, where the data API (which parses its own body) answered\n // 400. Mapped once, here, rather than at each of forty call sites.\n if (err instanceof SyntaxError && /JSON/i.test(err.message)) {\n handOffToRequestLog(c, \"INVALID_JSON\", \"Invalid JSON body\");\n return c.json({\n error: {\n message: \"Invalid JSON body\",\n code: \"INVALID_JSON\",\n ...(reqId && { requestId: reqId })\n }\n } satisfies ErrorResponse, 400);\n }\n\n let statusCode = error.statusCode || codeToStatus(error.code) || 500;\n let code = error.code || \"INTERNAL_ERROR\";\n\n // Handle DB connection and specific system errors for better logging\n let logMessage = error.message;\n\n // Resolve the actual cause — Node's net module wraps dual-stack failures\n // in an AggregateError whose inner errors carry the real address/port.\n let resolvedCause: PgLikeError | undefined;\n if (error.cause && typeof error.cause === \"object\" && error.cause !== null && \"code\" in error.cause) {\n const cause = error.cause as PgLikeError & { errors?: PgLikeError[] };\n if (cause.code === \"ECONNREFUSED\" && !cause.address && Array.isArray(cause.errors)) {\n // AggregateError — pick the first inner error that has address info\n resolvedCause = cause.errors.find(e => e.address) || cause;\n } else {\n resolvedCause = cause;\n }\n }\n\n // The real database error may sit several levels down the cause chain.\n // Losing it turns a precise failure (e.g. an RLS denial) into an opaque\n // \"Failed query: …\" 500 that is undiagnosable without direct DB access.\n const dbError = extractDbError(error);\n const poolFailure = dbError ? undefined : poolFailureOf(error);\n // Ahead of every SQLSTATE arm below: `57P01` is a SQLSTATE too, and it\n // says the connection is gone, not that the statement was wrong.\n const unreachable = !poolFailure && isDatabaseUnreachable(error);\n // A socket failure that is not the database's is another service's: a\n // `fetch()`, an S3 call. It answered `500` with the errno as the code\n // (`ECONNREFUSED`), or `INTERNAL_ERROR` — a server fault, for a service it\n // calls being down.\n const upstream = !poolFailure && !unreachable && !dbError && hasUpstreamSocketFailure(error);\n\n if (poolFailure) {\n code = poolFailure;\n statusCode = 503;\n logMessage = poolFailure === \"DB_POOL_EXHAUSTED\"\n ? \"Timed out waiting for a database connection: every pooled connection was busy (DB_POOL_MAX).\"\n : \"Timed out opening a database connection (DB_POOL_CONNECT_TIMEOUT).\";\n warnPoolFailure(poolFailure);\n c.header(\"Retry-After\", String(POOL_RETRY_AFTER_SECONDS));\n } else if (unreachable) {\n code = \"DATABASE_UNAVAILABLE\";\n statusCode = 503;\n const closedDatabase = closedDatabaseName(error);\n const reason = describeConnectionFailure(error);\n logMessage = closedDatabase !== null\n ? `Database \"${closedDatabase}\" is closed to new connections.`\n : `The database could not be reached: ${reason}.`;\n if (!warnIfDatabaseClosed(error)) warnDatabaseUnreachable(reason);\n c.header(\"Retry-After\", String(DATABASE_RETRY_AFTER_SECONDS));\n } else if (upstream) {\n code = \"SERVICE_UNAVAILABLE\";\n statusCode = 503;\n logMessage = `A service this request depends on could not be reached: ${describeConnectionFailure(error)}.`;\n } else if (resolvedCause && (resolvedCause.code === \"ENETUNREACH\" || resolvedCause.code === \"ECONNREFUSED\")) {\n // Some other service: a database that cannot be reached is the arm\n // above, so this one does not say \"database\".\n const cause = resolvedCause;\n logMessage = cause.code === \"ENETUNREACH\"\n ? `Network unreachable: cannot connect to ${cause.address}:${cause.port}.`\n : `Connection refused by ${cause.address}:${cause.port}.`;\n } else if (\"code\" in error && error.code === \"ENETUNREACH\") {\n const netErr = error as PgLikeError;\n logMessage = `Network unreachable. Cannot connect to service at ${netErr.address}:${netErr.port}.`;\n } else if (dbError && (dbError.code === \"42703\" || dbError.code === \"42P01\")) {\n code = \"SCHEMA_DRIFT\";\n const issue = dbError.code === \"42703\" ? \"column\" : \"table\";\n const identifier = dbError.table || dbError.column || extractMissingIdentifier(dbError.message) || \"unknown\";\n logMessage = `Schema drift: ${issue} \"${identifier}\" does not exist in the database. ${schemaDriftRemedy().short}`;\n } else if (dbError) {\n const parts = [`[PG ${dbError.code}] ${dbError.message}`];\n if (dbError.detail) parts.push(`Detail: ${dbError.detail}`);\n if (dbError.hint) parts.push(`Hint: ${dbError.hint}`);\n if (dbError.table) parts.push(`Table: ${dbError.table}`);\n if (dbError.column) parts.push(`Column: ${dbError.column}`);\n if (dbError.constraint) parts.push(`Constraint: ${dbError.constraint}`);\n if (dbError.code === \"42501\") {\n code = \"DB_PERMISSION_DENIED\";\n const ungranted = missingGrant(dbError.message);\n parts.push(ungranted\n ? `The role this request runs as was never granted ${ungranted.kind} \"${ungranted.name}\" — ` +\n \"a missing GRANT, not a row-level security policy.\"\n : \"The database rejected the statement for lack of privilege — usually a row-level \" +\n `security policy${dbError.table ? ` on \"${dbError.table}\"` : \"\"} denying this role, ` +\n \"or a stale FORCE ROW LEVEL SECURITY flag binding the owner connection.\"\n );\n }\n // 25006 read_only_sql_transaction. A request-scoped read opens its\n // transaction `READ ONLY`, so the only way to reach this is user code on\n // a read path attempting a write — which means an `afterRead` callback,\n // or something it called. Left in the generic branch it was a 500\n // \"Internal Server Error\", indistinguishable from the database being\n // down; it is the caller's own code, and it is not a server failure.\n if (dbError.code === \"25006\") {\n code = \"READ_ONLY_TRANSACTION\";\n statusCode = 409;\n parts.push(READ_ONLY_TRANSACTION_MESSAGE);\n }\n // SQLSTATE class 22 — data exception. The caller sent a value the\n // column's type cannot hold: `?id=eq.abc` on an integer key,\n // `?status=eq.nope` on an enum, a timestamp that is not one, a number\n // past the type's range. Every *other* bad query parameter already has\n // a precise 400 (`INVALID_LIMIT`, `UNKNOWN_FILTER_FIELD`,\n // `INVALID_LOGICAL_GROUP`); a bad *value* fell off the end of this\n // chain and answered 500 INTERNAL_ERROR — and in production `dbMessage`\n // is stripped, so the caller got a bare 500 naming nothing and had no\n // way to learn their own typo was the cause.\n if (dbError.code?.startsWith(\"22\")) {\n code = \"INVALID_FILTER_VALUE\";\n statusCode = 400;\n }\n logMessage = parts.join(\". \");\n }\n\n const isDbSchemaMismatch = code === \"SCHEMA_DRIFT\";\n\n // `logMessage`, not the sanitized client message: the request line is a\n // server log, and the whole point of this branch is the diagnosis it built.\n handOffToRequestLog(c, code, logMessage);\n\n if (isDbSchemaMismatch) {\n // Database schema mismatch is logged as a warning instead of a fatal error\n if (!requestWillBeLogged(c)) logger.warn(\n `⚠️ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}` +\n (reqId ? ` [${reqId}]` : \"\")\n );\n // In dev mode, show a one-time hint to run `rebase doctor`\n if (!_schemaDriftHinted && hostEnv().NODE_ENV !== \"production\") {\n _schemaDriftHinted = true;\n // Drawn rather than hand-aligned: the remedy inside it now varies\n // with the database, and a box whose rows were padded by hand\n // stayed straight only for the text it was written around.\n const WIDTH = 62;\n const row = (text: string) => `│${text.padEnd(WIDTH).slice(0, WIDTH)}│`;\n logger.warn([\n \"\",\n `┌${\"─\".repeat(WIDTH)}┐`,\n // One space short, deliberately: the emoji occupies two columns\n // in a terminal and one in `String.length`.\n `│${\" 💡 TIP: Run `rebase doctor` for full schema diagnostics\".padEnd(WIDTH - 1)}│`,\n row(\"\"),\n ...schemaDriftRemedy().lines.map(row),\n `└${\"─\".repeat(WIDTH)}┘`,\n \"\"\n ].join(\"\\n\"));\n }\n } else if (poolFailure || unreachable) {\n // Said once a minute by `warnPoolFailure` / `warnDatabaseUnreachable`;\n // the request line carries each occurrence.\n if (!requestWillBeLogged(c)) logger.warn(\n `⚠️ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}` +\n (reqId ? ` [${reqId}]` : \"\")\n );\n } else if (code === \"READ_ONLY_TRANSACTION\" || code === \"INVALID_FILTER_VALUE\") {\n // A 4xx: the application's own callback, refused — or a filter value\n // the caller's own request could not have worked with. Not a server\n // fault, so not an ❌ in the log either — and, like the drift arm above,\n // not a second line when the request log is already going to carry it.\n if (!requestWillBeLogged(c)) logger.warn(\n `⚠️ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}` +\n (reqId ? ` [${reqId}]` : \"\")\n );\n } else if (!requestWillBeLogged(c)) {\n // Unexpected errors — log at error level\n logger.error(\n `❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}` +\n (reqId ? ` [${reqId}]` : \"\")\n );\n }\n\n // Suppress the huge stack trace for known DB errors: it is noisy, and the\n // extracted [PG …] line above carries the signal. The SQL and the bound\n // params it used to leak are no longer this branch's problem — `logger`\n // strips Drizzle's `Failed query: … / params: …` wrapper out of every\n // message and stack it emits, so the fallbacks below (a connection dropped\n // mid-statement carries no SQLSTATE, so `dbError` is null and the stack is\n // logged) are covered too.\n const suppressStack = isDbSchemaMismatch || dbError !== null || poolFailure !== undefined || unreachable ||\n (statusCode < 500 && code === \"BAD_REQUEST\");\n if (!suppressStack) {\n // The error goes in as a value, not as `String(error.stack)`. A string\n // is a leaf to the logger: `serialiseError` — the `.cause`/\n // `AggregateError` walker the boot path relies on — never runs on one,\n // so the request path used to print the outer wrapper's stack and drop\n // the sentence that says what actually failed (`connect ECONNRESET`,\n // sitting two `.cause` links down). Structured, it walks the chain and\n // redacts each link on the way.\n logger.error(\"unhandled request error\", { error });\n }\n\n // Sanitize the message for the client to prevent leaking sensitive details\n // like SQL queries or internal IP addresses.\n let clientMessage = \"An unexpected error occurred\";\n if (code === \"READ_ONLY_TRANSACTION\") {\n // Ahead of the generic 4xx arm below, which would echo the raw driver\n // message (\"Failed query: insert into …\") back to the caller.\n clientMessage = READ_ONLY_TRANSACTION_MESSAGE;\n } else if (code === \"INVALID_FILTER_VALUE\") {\n // Also ahead of the 4xx arm: `error.message` here is the driver's\n // \"Failed query: select … / params: …\", which is both unhelpful and the\n // one thing this envelope must never carry.\n clientMessage = describeDataException(dbError || (error as PgLikeError));\n } else if (statusCode < 500 && error.message) {\n // If it's a 4xx error (e.g. from validation), it's generally safe to send the message\n clientMessage = error.message;\n } else if (error instanceof ApiError || error.name === \"ApiError\") {\n // We already handled ApiError above, but just in case\n clientMessage = error.message;\n } else if (code === \"SCHEMA_DRIFT\") {\n const pgErr = dbError || (error as PgLikeError);\n const issue = pgErr.code === \"42703\" ? \"column\" : \"table\";\n const identifier = pgErr.table || pgErr.column || extractMissingIdentifier(pgErr.message || error.message) || \"unknown\";\n clientMessage = `Schema drift: ${issue} \"${identifier}\" does not exist. ${schemaDriftRemedy().short}`;\n } else if (poolFailure) {\n clientMessage = \"The database is busy: no connection was free in time. Retry shortly.\";\n } else if (unreachable) {\n clientMessage = DATABASE_UNAVAILABLE_MESSAGE;\n } else if (upstream) {\n clientMessage = UPSTREAM_UNAVAILABLE_MESSAGE;\n } else if (code === \"DB_PERMISSION_DENIED\") {\n const ungranted = missingGrant(dbError?.message);\n clientMessage = ungranted\n ? `Permission denied by the database: the role this request runs as has no privilege on ${ungranted.kind} ` +\n `\"${ungranted.name}\". That is a missing GRANT to that role, not a row-level security policy.`\n : `Permission denied by the database${dbError?.table ? ` on \"${dbError.table}\"` : \"\"} (row-level security). Check the RLS policies for this table.`;\n } else if (code === \"INTERNAL_ERROR\") {\n clientMessage = \"Internal Server Error\";\n }\n\n // Database diagnostics for the envelope: the SQLSTATE is always safe to\n // return; message/detail/hint can reference schema internals, so only\n // outside production.\n const dbDetails = dbError ? {\n dbCode: dbError.code,\n ...(hostEnv().NODE_ENV !== \"production\" && {\n dbMessage: dbError.message,\n ...(dbError.detail && { detail: dbError.detail }),\n ...(dbError.hint && { hint: dbError.hint })\n })\n } : undefined;\n\n const drift = driftFor(statusCode);\n\n return c.json({\n error: {\n message: clientMessage,\n code,\n ...(error.details !== undefined\n ? { details: error.details }\n : dbDetails !== undefined ? { details: dbDetails } : {}),\n ...(reqId && { requestId: reqId }),\n ...(drift && { cause: drift })\n }\n } satisfies ErrorResponse, statusCode as ContentfulStatusCode);\n};\n\n/**\n * The object a `42501` names when no policy was involved.\n *\n * One SQLSTATE covers two different failures. A policy refusing a row says\n * \"… violates row-level security policy …\"; a role that was never granted an\n * object says \"permission denied for schema rebase\" (or table, function, …).\n * Answering the second with \"check the RLS policies\" sent people looking at\n * policies that were fine, for a grant that was missing.\n */\nfunction missingGrant(message: string | undefined): { kind: string; name: string } | undefined {\n const match = message?.match(/^permission denied for (schema|table|relation|view|materialized view|sequence|function|procedure|routine|type|column|database|foreign table|large object|tablespace|language) (.+)$/i);\n return match ? { kind: match[1].toLowerCase(), name: match[2].replace(/^\"|\"$/g, \"\") } : undefined;\n}\n\n/**\n * Map known error codes to HTTP status codes.\n */\nfunction codeToStatus(code?: string): number | undefined {\n if (!code) return undefined;\n const map: Record<string, number> = {\n BAD_REQUEST: 400,\n INVALID_INPUT: 400,\n WEAK_PASSWORD: 400,\n UNAUTHORIZED: 401,\n INVALID_CREDENTIALS: 401,\n INVALID_TOKEN: 401,\n FORBIDDEN: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n EMAIL_EXISTS: 409,\n ROLE_EXISTS: 409,\n READ_ONLY_TRANSACTION: 409,\n INVALID_FILTER_VALUE: 400,\n SCHEMA_DRIFT: 500,\n DB_PERMISSION_DENIED: 500,\n INTERNAL_ERROR: 500,\n NOT_CONFIGURED: 503,\n SERVICE_UNAVAILABLE: 503\n };\n return map[code];\n}\n\n\n"],"mappings":"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"}
@@ -2,9 +2,8 @@ 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 { r as toSnakeCase } from "./strings.es-BeZWAodn.js";
6
- import { W as isRelationalCollectionConfig } from "./src-D5Y3N-S4.js";
7
- import { t as ApiError } from "./errors-_ytoGi8A.js";
5
+ import { G as isRelationalCollectionConfig } from "./src-Bh9LRPb9.js";
6
+ import { r as toSnakeCase } from "./strings.es-B7QLnHh2.js";
8
7
  //#region ../utils/dist/plurals.es.js
9
8
  /**
10
9
  * Returns the singular of an English word.
@@ -257,13 +256,35 @@ function resolveRelation(relation, sourceCollection, propertyKey) {
257
256
  ...shared,
258
257
  kind: "via",
259
258
  cardinality: relation.cardinality,
260
- writable: false,
259
+ writable: isSingleHopVia(relation.cardinality, relation.joinPath),
261
260
  shared: true,
262
261
  joinPath: relation.joinPath
263
262
  };
264
263
  default: throw new Error(`Unknown relation kind: ${JSON.stringify(relation)}`);
265
264
  }
266
265
  }
266
+ /**
267
+ * Whether a `via` chain has a single link for a write to set — the chains
268
+ * Rebase writes through a create or update:
269
+ *
270
+ * - **to-one, one step** (`{ table: target, on: { from, to } }`): the target
271
+ * row holds the key, and the write points it at this row, as a `hasOne`
272
+ * write does;
273
+ * - **to-many, two steps through one table between the ends**: that table
274
+ * holds the links, and the write sets them as junction rows, as a
275
+ * `manyToMany` write does.
276
+ *
277
+ * Both shipped writable (0.24.0 fixed the one-to-one writer). A longer chain
278
+ * has no single link to maintain: a to-many one found no junction connecting
279
+ * both ends and failed as a 500, and a to-one one re-pointed the target at
280
+ * whatever column the first step read. So those are read-only, and every door
281
+ * reads this answer through `writable` rather than re-deriving it.
282
+ */
283
+ function isSingleHopVia(cardinality, joinPath) {
284
+ if (!Array.isArray(joinPath)) return false;
285
+ if (cardinality === "one") return joinPath.length === 1;
286
+ return joinPath.length === 2 && joinPath[0].table !== joinPath[1].table;
287
+ }
267
288
  /** How this relation is addressed in an error message, before it has a resolved name. */
268
289
  function describe(relation, sourceCollection, propertyKey) {
269
290
  const name = relation.relationName ?? propertyKey;
@@ -612,57 +633,6 @@ function namesWhere(collection, restricted) {
612
633
  };
613
634
  }
614
635
  //#endregion
615
- //#region src/api/rest/soft-delete-params.ts
616
- /**
617
- * The two query parameters soft delete adds to the REST surface.
618
- *
619
- * Kept in their own module so the call sites in `query-parser.ts` and the
620
- * delete routes are a single line each: the parsing rules belong to soft
621
- * delete, not to the parser, and a rule spread across the two files that read
622
- * it is a rule that drifts.
623
- */
624
- /** `?deleted=` — what to do about rows a soft delete has stamped. */
625
- var DELETED_QUERY_PARAM = "deleted";
626
- /** `?hard=` — ask for a real `DELETE` on a soft-delete collection. */
627
- var HARD_DELETE_QUERY_PARAM = "hard";
628
- /**
629
- * `?deleted=include|only` → the driver's `withDeleted`.
630
- *
631
- * Spelled `deleted` on the wire and `withDeleted` in the driver, deliberately:
632
- * the URL reads as a question about the rows (`?deleted=only` — "only the
633
- * deleted ones"), and the driver option reads as an instruction about the query.
634
- *
635
- * A value neither word is a 400 rather than a silent fallback to the default.
636
- * `?deleted=true` quietly hiding every deleted row is the worst of both: it
637
- * looks like it worked and answers the opposite question. Absent is the
638
- * default, which is "hide them".
639
- */
640
- function parseWithDeleted(raw) {
641
- if (raw === void 0 || raw === null || raw === "") return void 0;
642
- const value = String(raw).trim().toLowerCase();
643
- if (value === "include") return true;
644
- if (value === "only") return "only";
645
- throw ApiError.badRequest(`Invalid \`?${DELETED_QUERY_PARAM}=${String(raw)}\`. It takes 'include' (live rows and deleted ones) or 'only' (deleted rows alone). Omit it to see only the live rows.`, "INVALID_DELETED_PARAM");
646
- }
647
- /**
648
- * `?hard=true` → a real `DELETE` on a collection that soft-deletes.
649
- *
650
- * Needs no permission beyond the delete it replaces: it is the same verb, and a
651
- * second access-control surface for one operation is a second thing to get
652
- * wrong. What it changes is whether the row can be restored.
653
- *
654
- * Only the exact words `true` and `1` mean yes. Anything else is a 400, not a
655
- * "no" — a typo that silently soft-deletes when the caller asked to purge is a
656
- * caller who believes the data is gone.
657
- */
658
- function parseHardDelete(raw) {
659
- if (raw === void 0 || raw === null || raw === "") return false;
660
- const value = String(raw).trim().toLowerCase();
661
- if (value === "true" || value === "1") return true;
662
- if (value === "false" || value === "0") return false;
663
- throw ApiError.badRequest(`Invalid \`?${HARD_DELETE_QUERY_PARAM}=${String(raw)}\`. It takes 'true' or 'false'.`, "INVALID_HARD_PARAM");
664
- }
665
- //#endregion
666
- export { apiHiddenFieldNames as a, fieldKeyForColumn as c, isJunctionBackedRelation as d, isRelationRequired as f, toWireKey as g, resolveRelation as h, parseWithDeleted as i, findRelation as l, resolveCollectionRelations as m, HARD_DELETE_QUERY_PARAM as n, effectiveAccess as o, relationDeclaringProperty as p, parseHardDelete as r, restrictedFieldNames as s, DELETED_QUERY_PARAM as t, getTableName as u };
636
+ export { findRelation as a, isRelationRequired as c, resolveRelation as d, toWireKey as f, fieldKeyForColumn as i, relationDeclaringProperty as l, effectiveAccess as n, getTableName as o, restrictedFieldNames as r, isJunctionBackedRelation as s, apiHiddenFieldNames as t, resolveCollectionRelations as u };
667
637
 
668
- //# sourceMappingURL=soft-delete-params-BAgpmBvp.js.map
638
+ //# sourceMappingURL=field-access-DYsvjI_P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-access-DYsvjI_P.js","names":[],"sources":["../../utils/dist/plurals.es.js","../../utils/dist/names.es.js","../../common/src/util/resolve-relation.ts","../../common/src/util/relations.ts","../../common/src/collections/field-access.ts"],"sourcesContent":["//#region src/plurals.ts\n/**\n* Returns the plural of an English word.\n*\n* @param {string} word\n* @param {number} [amount]\n* @returns {string}\n*/\nfunction plural(word, amount) {\n\tif (amount !== void 0 && amount === 1) return word;\n\tconst plurals = {\n\t\t\"(quiz)$\": \"$1zes\",\n\t\t\"^(ox)$\": \"$1en\",\n\t\t\"([m|l])ouse$\": \"$1ice\",\n\t\t\"(matr|vert|ind)ix|ex$\": \"$1ices\",\n\t\t\"(x|ch|ss|sh)$\": \"$1es\",\n\t\t\"([^aeiouy]|qu)y$\": \"$1ies\",\n\t\t\"(hive)$\": \"$1s\",\n\t\t\"(?:([^f])fe|([lr])f)$\": \"$1$2ves\",\n\t\t\"(shea|lea|loa|thie)f$\": \"$1ves\",\n\t\tsis$: \"ses\",\n\t\t\"([ti])um$\": \"$1a\",\n\t\t\"(tomat|potat|ech|her|vet)o$\": \"$1oes\",\n\t\t\"(bu)s$\": \"$1ses\",\n\t\t\"(alias)$\": \"$1es\",\n\t\t\"(octop)us$\": \"$1i\",\n\t\t\"(ax|test)is$\": \"$1es\",\n\t\t\"(us)$\": \"$1es\",\n\t\t\"([^s]+)$\": \"$1s\"\n\t};\n\tconst irregular = {\n\t\tmove: \"moves\",\n\t\tfoot: \"feet\",\n\t\tgoose: \"geese\",\n\t\tsex: \"sexes\",\n\t\tchild: \"children\",\n\t\tman: \"men\",\n\t\ttooth: \"teeth\",\n\t\tperson: \"people\"\n\t};\n\tif ([\n\t\t\"sheep\",\n\t\t\"fish\",\n\t\t\"deer\",\n\t\t\"moose\",\n\t\t\"series\",\n\t\t\"species\",\n\t\t\"money\",\n\t\t\"rice\",\n\t\t\"information\",\n\t\t\"equipment\",\n\t\t\"bison\",\n\t\t\"cod\",\n\t\t\"offspring\",\n\t\t\"pike\",\n\t\t\"salmon\",\n\t\t\"shrimp\",\n\t\t\"swine\",\n\t\t\"trout\",\n\t\t\"aircraft\",\n\t\t\"hovercraft\",\n\t\t\"spacecraft\",\n\t\t\"sugar\",\n\t\t\"tuna\",\n\t\t\"you\",\n\t\t\"wood\"\n\t].indexOf(word.toLowerCase()) >= 0) return word;\n\tfor (const w in irregular) {\n\t\tconst pattern = new RegExp(`${w}$`, \"i\");\n\t\tconst replace = irregular[w];\n\t\tif (pattern.test(word)) return word.replace(pattern, replace);\n\t}\n\tfor (const reg in plurals) {\n\t\tconst pattern = new RegExp(reg, \"i\");\n\t\tif (pattern.test(word)) return word.replace(pattern, plurals[reg]);\n\t}\n\treturn word;\n}\n/**\n* Returns the singular of an English word.\n*\n* @param {string} word\n* @param {number} [amount]\n* @returns {string}\n*/\nfunction singular(word, amount) {\n\tif (amount !== void 0 && amount !== 1) return word;\n\tconst singulars = {\n\t\t\"(quiz)zes$\": \"$1\",\n\t\t\"(matr)ices$\": \"$1ix\",\n\t\t\"(vert|ind)ices$\": \"$1ex\",\n\t\t\"^(ox)en$\": \"$1\",\n\t\t\"(alias)es$\": \"$1\",\n\t\t\"(octop|vir)i$\": \"$1us\",\n\t\t\"(cris|ax|test)es$\": \"$1is\",\n\t\t\"(shoe)s$\": \"$1\",\n\t\t\"(o)es$\": \"$1\",\n\t\t\"(bus)es$\": \"$1\",\n\t\t\"([m|l])ice$\": \"$1ouse\",\n\t\t\"(x|ch|ss|sh)es$\": \"$1\",\n\t\t\"(m)ovies$\": \"$1ovie\",\n\t\t\"(s)eries$\": \"$1eries\",\n\t\t\"([^aeiouy]|qu)ies$\": \"$1y\",\n\t\t\"([lr])ves$\": \"$1f\",\n\t\t\"(tive)s$\": \"$1\",\n\t\t\"(hive)s$\": \"$1\",\n\t\t\"(li|wi|kni)ves$\": \"$1fe\",\n\t\t\"(shea|loa|lea|thie)ves$\": \"$1f\",\n\t\t\"(^analy)ses$\": \"$1sis\",\n\t\t\"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$\": \"$1$2sis\",\n\t\t\"([ti])a$\": \"$1um\",\n\t\t\"(n)ews$\": \"$1ews\",\n\t\t\"(h|bl)ouses$\": \"$1ouse\",\n\t\t\"(corpse)s$\": \"$1\",\n\t\t\"(us)es$\": \"$1\",\n\t\ts$: \"\"\n\t};\n\tconst irregular = {\n\t\tmove: \"moves\",\n\t\tfoot: \"feet\",\n\t\tgoose: \"geese\",\n\t\tsex: \"sexes\",\n\t\tchild: \"children\",\n\t\tman: \"men\",\n\t\ttooth: \"teeth\",\n\t\tperson: \"people\"\n\t};\n\tif ([\n\t\t\"sheep\",\n\t\t\"fish\",\n\t\t\"deer\",\n\t\t\"moose\",\n\t\t\"series\",\n\t\t\"species\",\n\t\t\"money\",\n\t\t\"rice\",\n\t\t\"information\",\n\t\t\"equipment\",\n\t\t\"bison\",\n\t\t\"cod\",\n\t\t\"offspring\",\n\t\t\"pike\",\n\t\t\"salmon\",\n\t\t\"shrimp\",\n\t\t\"swine\",\n\t\t\"trout\",\n\t\t\"aircraft\",\n\t\t\"hovercraft\",\n\t\t\"spacecraft\",\n\t\t\"sugar\",\n\t\t\"tuna\",\n\t\t\"you\",\n\t\t\"wood\"\n\t].indexOf(word.toLowerCase()) >= 0) return word;\n\tfor (const w in irregular) {\n\t\tconst pattern = new RegExp(`${irregular[w]}$`, \"i\");\n\t\tif (pattern.test(word)) return word.replace(pattern, w);\n\t}\n\tfor (const reg in singulars) {\n\t\tconst pattern = new RegExp(reg, \"i\");\n\t\tif (pattern.test(word)) return word.replace(pattern, singulars[reg]);\n\t}\n\treturn word;\n}\n//#endregion\nexport { plural, singular };\n\n//# sourceMappingURL=plurals.es.js.map","import { toSnakeCase } from \"./strings.es.js\";\nimport { singular } from \"./plurals.es.js\";\n//#region src/names.ts\n/**\n* Generates a foreign key column name from a given string, typically a collection slug or name.\n* It singularizes the name, converts it to snake_case and appends '_id'.\n*\n* Singularization runs *before* snake-casing so that acronyms survive: `toSnakeCase`\n* splits on every capital, which turned \"URLs\" into \"ur_ls\" and then \"ur_l_id\".\n*\n* @param name The base name to convert to a foreign key.\n* @returns A foreign key name in the format 'singular_name_id'.\n*\n* @example\n* // returns \"user_id\"\n* generateForeignKeyName(\"users\")\n*\n* @example\n* // returns \"category_id\"\n* generateForeignKeyName(\"categories\")\n*\n* @example\n* // returns \"product_id\"\n* generateForeignKeyName(\"Product\")\n*\n*/\nfunction generateForeignKeyName(name) {\n\treturn `${toSnakeCase(singularizeForKey(name))}_id`;\n}\n/**\n* `singular()` handles real English plurals, but its final catch-all rule strips\n* any trailing \"s\", which mangles words that only look plural. Guard the two\n* cases that produce a column name nobody would recognise:\n*\n* - a double \"s\" ending is never a plural marker (\"address\", \"class\", \"process\"),\n* so stripping it yields \"addres\";\n* - a name that singularizes to nothing (the literal \"s\") would yield \"_id\".\n*/\nfunction singularizeForKey(name) {\n\tif (/ss$/i.test(name)) return name;\n\tconst result = singular(name);\n\treturn result.length > 0 ? result : name;\n}\n/**\n* What `generateForeignKeyName` returned before it learned to singularize:\n* snake-case the name, then chop one trailing \"s\".\n*\n* This is here to be *detected*, never to be generated. A database provisioned\n* under the old rule carries `categorie_id`, `addresse_id`, `children_id` or\n* `ur_l_id` where the current rule expects `category_id`, `address_id`,\n* `child_id` and `url_id` — and the boot-time schema ensure is additive, so it\n* would create the new column empty beside the populated old one and leave the\n* relation reading nothing. No error, no missing table: the failure is silent,\n* which is the only reason this function still exists.\n*\n* `ensureCollectionSchema` calls it to recognise that shape and say so.\n* Returns the same string as `generateForeignKeyName` for every regular plural,\n* so a caller can compare the two and act only when they differ.\n*/\nfunction legacyForeignKeyName(name) {\n\tconst snake = toSnakeCase(name);\n\treturn `${snake.endsWith(\"s\") ? snake.slice(0, -1) : snake}_id`;\n}\n/**\n* Truncate an identifier to what Postgres will actually store.\n*\n* Postgres silently truncates identifiers at NAMEDATALEN-1 = 63 **bytes**, so a\n* name generated longer than that is not the name the database ends up holding.\n* Anything that later looks the object up by the name it generated then misses.\n*\n* Byte length, not string length: NAMEDATALEN is a byte bound, and a multi-byte\n* character straddling the boundary would be cut mid-sequence by `slice(0, 63)`.\n*\n* `TextEncoder` rather than `Buffer`, which is not a matter of taste: `Buffer`\n* is a Node global, and this package is imported by browser-facing ones. It\n* typechecked only where `@types/node` happened to be in scope, so\n* `packages/codegen` — whose tsconfig is `lib: [\"ESNext\", \"dom\"]` — could not\n* compile the file at all, and both of its suites failed to run. `TextEncoder`\n* and `TextDecoder` are standard in both runtimes and need no ambient types.\n*/\nfunction toPostgresIdentifier(name) {\n\treturn truncateToBytes(name, 63);\n}\n/**\n* {@link toPostgresIdentifier} with the bound lifted to a parameter.\n*\n* Exists for names that end in something load-bearing. Truncating at 63 keeps\n* the *head* of a name and discards the tail, which is right for a descriptive\n* identifier and wrong for a hashed one: the hash is the part that makes it\n* unique, and it is at the end. A caller that appends a fingerprint truncates\n* the readable head to `63 - <tail>` itself and then appends, so the bound is\n* still 63 and the hash always survives.\n*\n* `contracts/derived-names.txt` records what the alternative costs — a foreign\n* key frozen as `..._corres`, its `_fkey` suffix truncated away, so a second\n* foreign key on that table would derive a byte-identical name.\n*\n* One truncation rule, in one function, so the two cannot drift.\n*/\nfunction truncateToBytes(name, maxBytes) {\n\tconst bytes = new TextEncoder().encode(name);\n\tif (bytes.byteLength <= maxBytes) return name;\n\treturn new TextDecoder(\"utf-8\").decode(bytes.subarray(0, maxBytes)).replace(/�+$/, \"\");\n}\n/**\n* The API name a database column is served under.\n*\n* The wire name of a field is its property key, and Rebase's property keys are\n* camelCase — `displayName`, `createdAt`, `photoURL`. Columns are snake_case,\n* because an unquoted Postgres identifier folds to lower case and a camelCase\n* column is therefore reachable only as `\"authorId\"` forever: in hand-written\n* SQL, in psql, in an RLS policy body, in a dump, and in every third-party tool\n* that ever touches the database. So the two conventions are both right, and\n* this is the function that crosses between them.\n*\n* It exists because two sources of field names never crossed: a foreign key\n* derived from a relation (`author_id`) and a column read back by introspection\n* (`user_id`) both landed on the wire under their column name, while every\n* hand-authored collection next to them used camelCase. One API, two\n* conventions, and no rule a caller could infer from outside — those names are\n* also the `where` and `orderBy` keys, so it was not a matter of taste.\n*\n* Rules, in the order they matter:\n*\n* - **A name with no separator is returned unchanged.** `photoURL` stays\n* `photoURL` and `id` stays `id`. Lower-casing a single token is what makes\n* a \"camelCase\" helper destructive — `camelCase(\"photoURL\")` is `photourl` —\n* and this function is applied to names that are *already* keys.\n* - **Each following segment keeps its own casing** apart from an upper-cased\n* first letter, so `photo_URL` → `photoURL` rather than `photoUrl`.\n* - **The result may still not be a JavaScript identifier.** `2fa_enabled`\n* becomes `2faEnabled`, which is a perfectly good object key and still needs\n* quoting where one is written into generated source.\n*\n* Not the inverse of {@link toSnakeCase}: `toSnakeCase` tokenises on case\n* boundaries and would turn `photoURL` into `photo_url`. Round-tripping is not\n* a property either function promises, which is why a column name that a\n* property maps explicitly is always read off `columnName` rather than derived.\n*/\nfunction toWireKey(columnName) {\n\tif (!columnName) return columnName;\n\tconst segments = columnName.split(/[-_ ]+/).filter(Boolean);\n\tif (segments.length <= 1) return columnName;\n\treturn segments.map((segment, index) => index === 0 ? segment.charAt(0).toLowerCase() + segment.slice(1) : segment.charAt(0).toUpperCase() + segment.slice(1)).join(\"\");\n}\n/**\n* The first candidate key not already used, or a numbered fallback.\n*\n* Introspection turns a set of column names into a set of object keys, and the\n* mapping is not injective: `user_id` and `userId` are two columns and one\n* {@link toWireKey}, and two foreign keys can strip to the same relation name.\n* A duplicate key in a generated object literal is a TypeScript error, so the\n* whole collection stops compiling — and a duplicate key in a `Record` built at\n* runtime is worse, because it silently drops a column instead.\n*\n* The numbered tail is what makes this total: a function that returns a key it\n* cannot guarantee is free has only moved the duplicate one line down.\n*\n* Structurally typed on `has` so a `Map` of emitted blocks and a `Set` of taken\n* names both satisfy it. Lives here, in the package both introspection\n* producers and the admin's table import can reach, because they must resolve a\n* collision the same way or one database describes itself three ways.\n*/\nfunction firstFreeKey(candidates, taken) {\n\tfor (const candidate of candidates) if (!taken.has(candidate)) return candidate;\n\tconst base = candidates[candidates.length - 1];\n\tfor (let suffix = 2;; suffix++) {\n\t\tconst candidate = `${base}_${suffix}`;\n\t\tif (!taken.has(candidate)) return candidate;\n\t}\n}\n//#endregion\nexport { firstFreeKey, generateForeignKeyName, legacyForeignKeyName, toPostgresIdentifier, toWireKey, truncateToBytes };\n\n//# sourceMappingURL=names.es.js.map","import {\n CollectionConfig,\n Relation,\n ResolvedRelation\n} from \"@rebasepro/types\";\nimport { generateForeignKeyName, toSnakeCase } from \"@rebasepro/utils\";\n\nimport { getTableName } from \"./relations\";\n\n/**\n * Fill in a relation's defaults.\n *\n * This replaces `sanitizeRelation`, which had to work out *which kind of link\n * you meant* from whichever optional fields happened to be set — 194 lines of\n * it, including a pass that inspected the target collection's own relations to\n * decide whether a `many`/`inverse` pair was a one-to-many or the far side of a\n * many-to-many, wrapped in a `try/catch` that fell through to the wrong answer\n * when it could not tell. Two consumers running that logic at different moments\n * could reach different conclusions about the same relation.\n *\n * With the kind declared there is nothing to work out. What remains is\n * defaulting — a table name, a column name — which is deterministic, depends\n * only on the relation and its two endpoints, and cannot fail. That is why this\n * function returns rather than throws, and why it needs no cache to be\n * consistent.\n */\nexport function resolveRelation(\n relation: Relation,\n sourceCollection: CollectionConfig,\n propertyKey?: string\n): ResolvedRelation {\n const target = relation.target;\n if (typeof target !== \"function\") {\n throw new Error(\n `Relation${relation.relationName ? ` '${relation.relationName}'` : \"\"} on ` +\n `'${sourceCollection.slug}' has no \\`target\\`. Give it a thunk: \\`target: () => otherCollection\\`.`\n );\n }\n\n const targetCollection = callTarget(relation, sourceCollection, propertyKey, target);\n\n // The name is the address: the `include` key, the admin tab, and the\n // segment of a nested path. Declared name wins, then the declaring\n // property's key, then the target's slug.\n const relationName = relation.relationName ?? propertyKey ?? toSnakeCase(targetCollection.slug);\n\n const shared: Pick<ResolvedRelation, \"relationName\" | \"target\" | \"targetSlug\" | \"onUpdate\" | \"onDelete\" | \"overrides\"> = {\n relationName,\n // Normalised, not the thunk as written. Resolution reads the target once\n // and every later consumer calls it again — the driver building a join,\n // the DDL and policy generators, the admin's relation fields — so\n // handing back the raw thunk would give all of them the module namespace\n // `callTarget` just looked past, and the fix would hold only for the\n // fields resolution happens to read here. Still lazy: same call at the\n // same moment, one unwrap on the way out.\n target: () => unwrapModuleNamespace(target()) as CollectionConfig,\n targetSlug: targetCollection.slug,\n onUpdate: relation.onUpdate,\n onDelete: relation.onDelete,\n overrides: relation.overrides\n // No `validation`. Whether the link is required is a fact about the\n // *property*, and copying it onto the resolved relation gave the\n // question two answers that were free to disagree. Ask\n // `isRelationRequired(collection, relation)`, which reads the one.\n };\n\n const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);\n\n switch (relation.kind) {\n case \"belongsTo\":\n return {\n ...shared,\n kind: \"belongsTo\",\n cardinality: \"one\",\n writable: true,\n shared: false,\n localKey: relation.localKey ?? generateForeignKeyName(relationName),\n index: relation.index !== false\n };\n\n case \"hasOne\":\n return {\n ...shared,\n kind: \"hasOne\",\n cardinality: \"one\",\n writable: true,\n shared: false,\n foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),\n sourceKey: relation.sourceKey\n };\n\n case \"hasMany\":\n return {\n ...shared,\n kind: \"hasMany\",\n cardinality: \"many\",\n writable: true,\n shared: false,\n foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),\n // Not defaulted: the source's primary key needs the driver's\n // schema to resolve, which resolution does not have. `undefined`\n // means \"the primary key\" — see `ResolvedHasMany.sourceKey`.\n sourceKey: relation.sourceKey\n };\n\n case \"manyToMany\": {\n const sourceTable = getTableName(sourceCollection);\n const targetTable = getTableName(targetCollection);\n return {\n ...shared,\n kind: \"manyToMany\",\n cardinality: \"many\",\n writable: true,\n shared: true,\n through: {\n // Sorted so both sides of the same link derive the same\n // table without having to agree in advance.\n table: relation.through?.table ?? [sourceTable, targetTable].sort().join(\"_\"),\n sourceColumn: relation.through?.sourceColumn ?? generateForeignKeyName(sourceName),\n targetColumn: relation.through?.targetColumn ?? generateForeignKeyName(relationName),\n // `{}` rather than `undefined`, for the reason every other\n // field here is filled in: a consumer reads one shape and\n // does not have to decide what an absent payload means.\n properties: relation.through?.properties ?? {}\n }\n };\n }\n\n case \"via\":\n return {\n ...shared,\n kind: \"via\",\n cardinality: relation.cardinality,\n writable: isSingleHopVia(relation.cardinality, relation.joinPath),\n // A join chain reaches rows that other parents reach too, and\n // Rebase does not know which hop, if any, is a link it owns.\n shared: true,\n joinPath: relation.joinPath\n };\n\n default: {\n // Exhaustive: a new kind is a compile error here, not a silent\n // fall-through to whatever shape happened to match first.\n const exhaustive: never = relation;\n throw new Error(`Unknown relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n}\n\n/**\n * Whether a `via` chain has a single link for a write to set — the chains\n * Rebase writes through a create or update:\n *\n * - **to-one, one step** (`{ table: target, on: { from, to } }`): the target\n * row holds the key, and the write points it at this row, as a `hasOne`\n * write does;\n * - **to-many, two steps through one table between the ends**: that table\n * holds the links, and the write sets them as junction rows, as a\n * `manyToMany` write does.\n *\n * Both shipped writable (0.24.0 fixed the one-to-one writer). A longer chain\n * has no single link to maintain: a to-many one found no junction connecting\n * both ends and failed as a 500, and a to-one one re-pointed the target at\n * whatever column the first step read. So those are read-only, and every door\n * reads this answer through `writable` rather than re-deriving it.\n */\nfunction isSingleHopVia(cardinality: \"one\" | \"many\", joinPath: readonly { table: string }[] | undefined): boolean {\n // A chain declared without its steps resolves — resolution cannot fail;\n // the relation validator names the missing `joinPath` — and is not written.\n if (!Array.isArray(joinPath)) return false;\n if (cardinality === \"one\") return joinPath.length === 1;\n return joinPath.length === 2 && joinPath[0].table !== joinPath[1].table;\n}\n\n/** How this relation is addressed in an error message, before it has a resolved name. */\nfunction describe(relation: Relation, sourceCollection: CollectionConfig, propertyKey?: string): string {\n const name = relation.relationName ?? propertyKey;\n return `Relation${name ? ` '${name}'` : \"\"} on '${sourceCollection.slug}'`;\n}\n\n/**\n * A module namespace, unwrapped to the collection it exports.\n *\n * A cycle transpiled to CommonJS does not hand the importing module the\n * *default export* — it hands it the module object, `{ __esModule: true,\n * default: … }`, captured before the exporting module finished evaluating. The\n * `default` slot fills in later, so by the time a lazy `target` thunk runs the\n * collection is sitting right there, one level down. Returning the namespace is\n * never a thing a thunk means to do, and there is exactly one reading of it.\n *\n * Only unwrapped when the inner value is itself a collection: a `default` that\n * is not one is a genuinely wrong thunk, and it should reach the error below\n * rather than be quietly swapped in.\n */\nfunction unwrapModuleNamespace(value: unknown): unknown {\n if (!value || typeof value !== \"object\") return value;\n if ((value as { slug?: unknown }).slug) return value;\n const inner = (value as { default?: unknown }).default;\n return inner && typeof inner === \"object\" && (inner as { slug?: unknown }).slug ? inner : value;\n}\n\n/**\n * Call the `target` thunk, and translate the ways an import cycle breaks it into\n * an error that names the cause — or, where the value is recoverable, into the\n * collection the thunk meant.\n *\n * The thunk exists to defer the reference until every module has finished\n * evaluating, and for a cycle that closes at import time it does. Two cycles\n * leave the binding permanently unusable:\n *\n * - **ESM/TDZ.** `const` and `class` bindings in a not-yet-evaluated module are\n * in the temporal dead zone, so reading one throws `ReferenceError: x is not\n * defined`. The stack points at the thunk — a one-line arrow function that is\n * obviously fine — and says nothing about the cycle that made it throw.\n * - **CJS interop, unresolved.** The half-initialised module object has no\n * `default` yet, the import resolves to `undefined`, and the thunk returns it\n * without complaint. That one used to surface here as \"did not resolve to a\n * collection\", which is true and unhelpful.\n *\n * Both mean the same thing, and the fix for both is the same: break the cycle,\n * or move the relation into the collection that does not close it.\n *\n * A third shape is *not* an error, and used to be reported as one. A loader that\n * transpiles ESM to CJS — jiti, which is what `rebase generate-sdk` and\n * `rebase build` load collections with — gives the module entered second in a\n * cycle a namespace object rather than the default export, and never replaces it\n * with a live binding. The thunk then returns `{ __esModule: true, default: … }`\n * holding the fully-initialised collection. Native ESM resolves the same thunk\n * to the collection directly, so this was a loader artefact reported as an\n * authoring mistake, and the advice it gave — make the target a lazy thunk — was\n * already satisfied by the code it was rejecting. Bidirectional relations make\n * these cycles unavoidable, and the lazy thunk is this framework's own answer to\n * them, so {@link unwrapModuleNamespace} takes the collection and moves on.\n */\nfunction callTarget(\n relation: Relation,\n sourceCollection: CollectionConfig,\n propertyKey: string | undefined,\n target: Relation[\"target\"]\n): ReturnType<Relation[\"target\"]> {\n let targetCollection: ReturnType<Relation[\"target\"]> | undefined;\n try {\n targetCollection = unwrapModuleNamespace(target()) as ReturnType<Relation[\"target\"]>;\n } catch (error) {\n // A ReferenceError from inside the thunk is a binding that was never\n // initialised — nothing else in a one-expression arrow can raise one.\n if (error instanceof ReferenceError) {\n throw new Error(\n `${describe(relation, sourceCollection, propertyKey)} targets a collection that is not ` +\n `initialized yet — almost always an import cycle between the two collection files. ` +\n `Break the cycle (move the shared piece into a third module, or import the target ` +\n `lazily) so the target's module finishes evaluating before the registry is built.`,\n { cause: error }\n );\n }\n throw error;\n }\n\n if (!targetCollection?.slug) {\n throw new Error(\n `${describe(relation, sourceCollection, propertyKey)} has a \\`target\\` that resolved to ` +\n `${targetCollection === undefined ? \"`undefined`\" : \"something that is not a collection\"}. ` +\n (targetCollection === undefined\n ? \"Under CommonJS interop an import cycle resolves the default import to `undefined`, \" +\n \"so check whether this collection and its target import each other. Otherwise the thunk \" +\n \"is returning the wrong value — it must return the collection itself, not a promise or a module.\"\n : typeof (targetCollection as { then?: unknown }).then === \"function\"\n ? \"The thunk returned a promise — `target: () => import(\\\"./other\\\")` is asynchronous. \" +\n \"Import the collection at the top of the file and return the binding: \" +\n \"`target: () => otherCollection`.\"\n : \"The thunk must return a collection config with a `slug`.\")\n );\n }\n\n return targetCollection;\n}\n","import { CollectionConfig, isRelationalCollectionConfig, Property, ResolvedRelation, RelationProperty } from \"@rebasepro/types\";\nimport { toSnakeCase, toWireKey } from \"@rebasepro/utils\";\n\nimport { resolveRelation } from \"./resolve-relation\";\n\n/**\n * Whether the target rows are shared with other parents — a many-to-many, or a\n * multi-hop `via` chain.\n *\n * Decides what a write \"through\" the relation may touch: a shared target\n * belongs to every parent that links it, so the parent owns the *link* and not\n * the row. The backend enforces that (an unlink rather than a delete) and the\n * admin renders it (remove-from-parent rather than delete).\n *\n * Now a field on the resolved relation rather than a re-derivation, so both\n * sides read the same answer instead of each computing one.\n */\nexport function isJunctionBackedRelation(relation: ResolvedRelation): boolean {\n return relation.shared;\n}\n\n/** WeakMap cache — same collection instance always yields the same relation map. */\nconst _resolvedRelationsCache = new WeakMap<CollectionConfig, Record<string, ResolvedRelation>>();\n\n/**\n * Every relation a collection declares, keyed by the name it is addressed by.\n *\n * A relation reaches the map from either of two places — the collection's\n * `relations` array, or a `relation` property that declares one inline — and is\n * keyed by its resolved `relationName`, which is what a nested path segment,\n * an `include` key and an admin tab all match against.\n *\n * Resolution no longer swallows failures. It used to wrap each relation in a\n * `try/catch` that dropped anything it could not work out, so a\n * mis-declared relation silently vanished instead of being reported; with the\n * kind declared, the only remaining failure is a `target` that does not resolve,\n * which is worth hearing about.\n */\nexport function resolveCollectionRelations(\n collection: CollectionConfig\n): Record<string, ResolvedRelation> {\n const cached = _resolvedRelationsCache.get(collection);\n if (cached) return cached;\n\n if (!isRelationalCollectionConfig(collection)) return {};\n\n const relations: Record<string, ResolvedRelation> = {};\n\n for (const relation of collection.relations ?? []) {\n const resolved = resolveRelation(relation, collection);\n relations[resolved.relationName] = resolved;\n }\n\n // A property declaring a relation inline is registered under the property\n // key as well: the fetch layer hydrates the result back onto that key, and\n // it is the name the admin addresses the field by.\n for (const [propertyKey, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n const declared = (property as RelationProperty).relation;\n if (!declared || relations[propertyKey]) continue;\n\n relations[propertyKey] = resolveRelation(declared, collection, propertyKey);\n }\n\n _resolvedRelationsCache.set(collection, relations);\n return relations;\n}\n\n/**\n * The `type: \"relation\"` property that declares a link, or `undefined` for one\n * that only exists in the collection's `relations` array.\n *\n * Both declaration sites end up in {@link resolveCollectionRelations}, and only\n * one of them has a property to carry field-level facts — `name`, `admin`, and\n * the one this exists for, `validation.required`.\n */\nexport function relationDeclaringProperty(\n collection: CollectionConfig,\n relation: ResolvedRelation\n): RelationProperty | undefined {\n const resolved = resolveCollectionRelations(collection);\n for (const [key, raw] of Object.entries(collection.properties ?? {})) {\n const prop = raw as Property | undefined;\n if (prop?.type !== \"relation\") continue;\n // A relation declared inline is keyed by the property; one declared in\n // `relations` is keyed by its name, which the property addresses.\n if (resolved[key] === relation) return prop as RelationProperty;\n const addressed = (prop as RelationProperty).relation?.relationName;\n if (addressed && findRelation(resolved, addressed) === relation) return prop as RelationProperty;\n }\n return undefined;\n}\n\n/**\n * Must every row of this collection point at a target through this link?\n *\n * Read from the declaring property's `validation.required` — the same key every\n * other field uses, and the only place it lives.\n *\n * `RelationBase` carried its own `validation.required` until 0.18, which made\n * this two questions rather than one. They were answered by different readers:\n * the Postgres DDL generator asked the property (so the foreign-key column was\n * `NOT NULL`) and the SDK type generator asked the relation (so the generated\n * `Insert` type made the field optional). A `create()` that left the relation\n * out therefore typechecked and then failed at the database with a not-null\n * violation, and the two `required`s had to be written twice, identically, for\n * the pair to agree.\n *\n * A relation with no declaring property — an entry in `relations` nothing\n * points at — is not required. There is no field to fill in.\n */\nexport function isRelationRequired(collection: CollectionConfig, relation: ResolvedRelation): boolean {\n return Boolean(relationDeclaringProperty(collection, relation)?.validation?.required);\n}\n\n/**\n * The path of the collection a relation property points at, derived from the\n * property alone.\n *\n * A preview holds a property and a value and no collection, so it cannot call\n * `resolveRelationProperty`. It does not need to: both forms that carry a\n * target — the stamped `resolvedRelation` and the inline `relation` — name it\n * directly. Only the third form, a relation declared by name in the\n * collection's `relations` array, is out of reach, and that one has no target\n * to read without the collection anyway.\n *\n * This is what lets a preview render a relation column that arrived as a bare\n * foreign key: the id says *which* row, the declared target says *which\n * collection*, and `RelationPreview` fetches the rest. Without it a scalar id\n * is indistinguishable from a value of the wrong type.\n */\nexport function getRelationTargetPath(property: RelationProperty): string | undefined {\n const stamped = property.resolvedRelation?.targetSlug;\n if (stamped) return stamped;\n\n const target = property.relation?.target;\n if (typeof target !== \"function\") return undefined;\n try {\n return target()?.slug;\n } catch (_e) {\n // A thunk reaching into a module that has not finished initialising:\n // there is no target to name yet, and a preview is not worth throwing over.\n return undefined;\n }\n}\n\n/**\n * The table a collection reads and writes.\n *\n * `table` when it is set, otherwise `toSnakeCase(slug)` — which is what made it\n * safe to drop `table` from the required fields on the config type: the runtime\n * had always derived it, and the type was demanding a value it did not need.\n *\n * The `||` chain is load-bearing. `toSnakeCase(undefined)` returns `\"\"`, not\n * `undefined`, so the previous `??` chain short-circuited on the empty string\n * and the name fallback could never run — a safety net that read like one and\n * caught nothing. It was unreachable while `slug` was required; it stops being\n * unreachable the moment anything constructs a config without one.\n */\nexport function getTableName(collection: CollectionConfig): string {\n const declared = isRelationalCollectionConfig(collection) ? collection.table : undefined;\n return declared || toSnakeCase(collection.slug) || toSnakeCase(collection.name);\n}\n\n/**\n * A JavaScript identifier: what a generated `export const <name> =` needs.\n *\n * Deliberately the same shape the two schema generators already define\n * privately — this is the third place that needed it, and the first two guard\n * property keys and member accesses while nothing guarded the variable name\n * itself.\n */\nconst JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * The variable name a generated table is bound to.\n *\n * Camel-cases underscores, and then guarantees the result is a legal\n * identifier. It did only the first, so a table name that is legal in Postgres\n * and not in JavaScript produced a `schema.generated.ts` that does not parse:\n *\n * `2024_archive` → `export const 2024Archive = pgTable(…)`\n * \"An identifier or keyword cannot immediately follow\n * a numeric literal\"\n * `reporting.events` → `export const reporting.events = pgTable(…)`\n * \"',' expected\"\n *\n * That file is imported by the server, so the failure is not one broken\n * collection — `rebase build` and `db push` fail at tsc for the whole\n * directory. And it is reachable from a documented flow: `rebase init` against\n * a database holding a table called `2024_archive` writes a collection file\n * that parses and a schema file that does not.\n *\n * **A no-op for every name that already worked**, which is what makes changing\n * a derived name safe here: the only inputs whose output changes are the ones\n * that produced a syntax error, and nothing can be running against those.\n * Separators become camel case rather than disappearing, so `reporting.events`\n * and `reporting_events` do not collide into one variable.\n */\nexport function getTableVarName(tableName: string): string {\n const camel = tableName.replace(/_([a-z])/g, (_, char: string) => char.toUpperCase());\n if (JS_IDENTIFIER.test(camel)) return camel;\n\n const sanitised = camel\n // Any other separator gets the same treatment `_` did, so two tables\n // differing only by separator keep differing.\n .replace(/[^A-Za-z0-9_$]+([A-Za-z0-9])?/g, (_, char?: string) =>\n (char ? char.toUpperCase() : \"\"))\n // A leading digit is legal in Postgres and not in JavaScript. Prefixed\n // rather than stripped, so `2024_archive` and `archive` stay distinct.\n .replace(/^([0-9])/, \"t$1\");\n\n return JS_IDENTIFIER.test(sanitised) ? sanitised : `t${sanitised}`;\n}\n\nexport function getEnumVarName(tableName: string, propName: string): string {\n const tableVar = getTableVarName(tableName);\n const propVar = propName.charAt(0).toUpperCase() + propName.slice(1);\n return `${tableVar}${propVar}`;\n}\n\nexport function getColumnName(fullColumn: string): string {\n return fullColumn.includes(\".\") ? fullColumn.split(\".\").pop()! : fullColumn;\n}\n\n/**\n * The field key a database column is served and addressed under.\n *\n * A column has two names and they are not the same name. `author_id` is what\n * Postgres stores; `authorId` is the key on the JSON row, the key in the\n * generated Drizzle table, and the key a caller writes in `where` and\n * `orderBy`. Every place that starts from a column and has to reach a row, a\n * Drizzle table or a payload goes through here, so there is one answer rather\n * than one per call site — the two that disagreed put `displayName` and\n * `author_id` on the same API.\n *\n * A declared property is the authority when there is one, because its key *is*\n * the wire name and `columnName` is the only thing that ever renamed the\n * column:\n *\n * 1. an explicit `columnName` equal to this column;\n * 2. a property whose key is literally the column (an author who wrote\n * `author_id:` meant `author_id` on the wire, and gets it);\n * 3. a property whose key snake-cases to the column, which is the default\n * mapping — `authorId` → `author_id`.\n *\n * With no property in the way — a foreign key derived from a relation, which\n * usually has none — the name is derived: {@link toWireKey}.\n *\n * Note the fallback is *not* the column verbatim. That was the old behaviour\n * and it is precisely the defect: a derived foreign key reached the wire under\n * its column name while every hand-authored field beside it was camelCase.\n */\nexport function fieldKeyForColumn(collection: CollectionConfig | undefined, column: string): string {\n const properties = collection?.properties;\n if (properties) {\n for (const [key, prop] of Object.entries(properties)) {\n const columnName = (prop as { columnName?: unknown } | undefined)?.columnName;\n if (typeof columnName === \"string\" && columnName === column) return key;\n }\n for (const key of Object.keys(properties)) {\n if (key === column) return key;\n if (toSnakeCase(key) === column) return key;\n }\n }\n return toWireKey(column);\n}\n\n/**\n * Look up a relation by key with forgiving normalization.\n *\n * `resolveCollectionRelations` stores each relation under a single canonical\n * key (no aliases). This helper tries the given key as-is, then falls back to\n * slug form (underscores → hyphens) and snake_case form (hyphens → underscores)\n * so that callers that receive a key from external input (URL path segments,\n * user-provided config, etc.) can still find the right entry.\n */\nexport function findRelation(\n resolvedRelations: Record<string, ResolvedRelation>,\n key: string\n): ResolvedRelation | undefined {\n // Exact match first\n if (resolvedRelations[key]) return resolvedRelations[key];\n\n // Try slug form (e.g. \"company_id\" → \"company-id\")\n const slugKey = key.replace(/_/g, \"-\");\n if (slugKey !== key && resolvedRelations[slugKey]) return resolvedRelations[slugKey];\n\n // Try snake_case form (e.g. \"company-id\" → \"company_id\")\n const snakeKey = key.replace(/-/g, \"_\");\n if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];\n\n return undefined;\n}\n","import type { CollectionConfig, FieldAccess, Property } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, findRelation, resolveCollectionRelations } from \"../util/relations\";\n\n/**\n * Field-level access control: one mechanism, read by every enforcement point.\n *\n * A collection's `securityRules` decide which *rows* a caller reaches;\n * `property.access` decides which *fields* of a reached row they see and may\n * set. The two are independent — a field rule never widens row access, and a\n * row a caller cannot read has no fields to talk about.\n *\n * `excludeFromApi` is sugar for `access: { read: [], write: [] }` and is\n * normalised into it by {@link effectiveAccess}, which is the only place either\n * spelling is read. It used to be its own code path in five files — the read\n * strip, the write refusal, the SDK generator, the OpenAPI schema builder and\n * the filter-parameter builder — and the second rule would have made ten.\n * There is one predicate now, and the flag is a shorthand for it.\n *\n * @module\n */\n\n/**\n * The caller a field rule is judged against: whatever the call context carries\n * as the user's application roles.\n *\n * `undefined` is the trusted server plane — an in-process `rebase.data` call\n * with no request behind it, the auth adapter writing a password hash, a\n * migration. Every API boundary has a viewer: an unauthenticated REST request is\n * scoped as `{ uid: ANONYMOUS_USER_ID, roles: [\"anon\"] }` before it reaches a\n * driver, so \"no viewer\" cannot be reached from outside.\n */\nexport interface FieldViewer {\n roles?: readonly string[];\n}\n\n/**\n * The role that satisfies any non-empty list.\n *\n * The same arm every baseline policy carries: `security_rules` injects\n * `rolesOverlap(['admin'])` into the default read and write policies, and\n * `rebase.dataAsAdmin` is scoped with `{ uid: \"service\", roles: [\"admin\"] }`.\n * Without this an author could declare `access: { read: [\"hr\"] }` and lock the\n * administrator out of a column of their own database — and lock the Studio out\n * of rendering it.\n */\nexport const ADMIN_ROLE = \"admin\";\n\n/**\n * What a property's access rules actually are, with `excludeFromApi` expanded.\n *\n * Returns `undefined` when the property constrains nothing, so callers can skip\n * the whole check for the overwhelmingly common case.\n */\nexport function effectiveAccess(property: Property | undefined): FieldAccess | undefined {\n if (!property) return undefined;\n if (property.excludeFromApi) return EXCLUDED_ACCESS;\n const access = property.access;\n if (!access) return undefined;\n if (access.read === undefined && access.write === undefined) return undefined;\n return access;\n}\n\n/** The rule `excludeFromApi: true` expands to. Frozen: it is shared by every caller. */\nconst EXCLUDED_ACCESS: FieldAccess = Object.freeze({ read: Object.freeze([]), write: Object.freeze([]) });\n\n/**\n * Does a caller holding `roles` satisfy `allowed`?\n *\n * Three cases, and the middle one is the one worth stating out loud:\n *\n * - `allowed` omitted — the field carries no rule of its own, so the row's\n * policies have already answered. True.\n * - `allowed` empty — nobody, at any privilege, through any API. Not the admin,\n * not the service key, not the trusted plane reading on a caller's behalf.\n * This is what `excludeFromApi` has always meant on the read side, and\n * collapsing the two spellings means the empty list has to keep meaning it.\n * - `allowed` non-empty — one of the named roles, or `admin`, or no viewer at\n * all (the trusted server plane, which is not an API caller).\n */\nfunction satisfies(allowed: readonly string[] | undefined, viewer: FieldViewer | undefined): boolean {\n if (allowed === undefined) return true;\n if (allowed.length === 0) return false;\n if (!viewer) return true;\n const roles = viewer.roles;\n if (!roles || roles.length === 0) return false;\n return roles.includes(ADMIN_ROLE) || allowed.some(role => roles.includes(role));\n}\n\n/** May this caller receive this field's value? */\nexport function canReadField(property: Property | undefined, viewer: FieldViewer | undefined): boolean {\n const access = effectiveAccess(property);\n return access ? satisfies(access.read, viewer) : true;\n}\n\n/** May this caller set this field's value? */\nexport function canWriteField(property: Property | undefined, viewer: FieldViewer | undefined): boolean {\n const access = effectiveAccess(property);\n return access ? satisfies(access.write, viewer) : true;\n}\n\n/**\n * The names on this collection a caller may not touch, in the two spellings a\n * caller can write them in.\n *\n * `declared` is the property keys, which is what has to leave a *known-fields*\n * set. `refused` is those plus the physical column names behind them: a caller\n * who knows the table can send `password_hash` as readily as `passwordHash`, and\n * a rule that only knew the wire name would be one rename away from useless.\n *\n * A restricted to-one relation takes its foreign key with it. `bandId: 7` names\n * the band exactly as `band: { id: 7 }` does, and the row carries the column\n * beside the relation — so a rule on the relation that left the key alone\n * withheld the value from the row and still let `?bandId=7` ask for it.\n *\n * `kind` picks which half of the rule is read; nothing else differs.\n */\nexport function restrictedFieldNames(\n collection: CollectionConfig,\n viewer: FieldViewer | undefined,\n kind: \"read\" | \"write\"\n): { declared: string[]; refused: Set<string> } {\n const allowed = kind === \"read\" ? canReadField : canWriteField;\n return namesWhere(collection, property => !allowed(property, viewer));\n}\n\n/**\n * The names on this collection that the API does not have, in every spelling\n * a caller could write them in — the `excludeFromApi` properties, their column\n * names, and the foreign key of an excluded to-one relation.\n *\n * Not the same set as {@link restrictedFieldNames}. A field restricted by role\n * is public by name: OpenAPI and `list_collections` describe it, and refusing a\n * query on it with \"not readable\" tells the caller nothing they could not read\n * there. An `excludeFromApi` field is described nowhere, so a query naming one\n * has to be answered as a query naming a column that does not exist — anything\n * else confirms that it does.\n */\nexport function apiHiddenFieldNames(collection: CollectionConfig): Set<string> {\n return namesWhere(collection, property => Boolean(property?.excludeFromApi)).refused;\n}\n\nfunction namesWhere(\n collection: CollectionConfig,\n restricted: (property: Property) => boolean\n): { declared: string[]; refused: Set<string> } {\n const declared: string[] = [];\n const refused = new Set<string>();\n const properties = collection.properties ?? {};\n // Resolved only when a restricted property is a relation, which is rare:\n // the read strip runs this on every row.\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n\n const restrict = (name: string) => {\n if (name in properties && !declared.includes(name)) declared.push(name);\n refused.add(name);\n };\n\n for (const [name, property] of Object.entries(properties)) {\n if (!restricted(property as Property)) continue;\n restrict(name);\n const columnName = (property as Property).columnName;\n if (columnName) refused.add(columnName);\n if ((property as Property).type === \"relation\") {\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, name);\n if (relation?.kind === \"belongsTo\") {\n restrict(fieldKeyForColumn(collection, relation.localKey));\n refused.add(relation.localKey);\n }\n }\n }\n return { declared, refused };\n}\n\n/**\n * True when nothing on this collection restricts a field, for either direction.\n *\n * Every read of every row runs through the strip, so the collection that has no\n * rules — which is almost all of them — has to cost one property walk and no\n * allocation.\n */\nexport function hasFieldAccessRules(collection: CollectionConfig): boolean {\n for (const property of Object.values(collection.properties ?? {})) {\n if (effectiveAccess(property as Property)) return true;\n }\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;AAqFA,SAAS,SAAS,MAAM,QAAQ;CAC/B,IAAI,WAAW,KAAK,KAAK,WAAW,GAAG,OAAO;CAC9C,MAAM,YAAY;EACjB,cAAc;EACd,eAAe;EACf,mBAAmB;EACnB,YAAY;EACZ,cAAc;EACd,iBAAiB;EACjB,qBAAqB;EACrB,YAAY;EACZ,UAAU;EACV,YAAY;EACZ,eAAe;EACf,mBAAmB;EACnB,aAAa;EACb,aAAa;EACb,sBAAsB;EACtB,cAAc;EACd,YAAY;EACZ,YAAY;EACZ,mBAAmB;EACnB,2BAA2B;EAC3B,gBAAgB;EAChB,iEAAiE;EACjE,YAAY;EACZ,WAAW;EACX,gBAAgB;EAChB,cAAc;EACd,WAAW;EACX,IAAI;CACL;CACA,MAAM,YAAY;EACjB,MAAM;EACN,MAAM;EACN,OAAO;EACP,KAAK;EACL,OAAO;EACP,KAAK;EACL,OAAO;EACP,QAAQ;CACT;CACA,IAAI;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACD,CAAC,CAAC,QAAQ,KAAK,YAAY,CAAC,KAAK,GAAG,OAAO;CAC3C,KAAK,MAAM,KAAK,WAAW;EAC1B,MAAM,UAAU,IAAI,OAAO,GAAG,UAAU,GAAG,IAAI,GAAG;EAClD,IAAI,QAAQ,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,SAAS,CAAC;CACvD;CACA,KAAK,MAAM,OAAO,WAAW;EAC5B,MAAM,UAAU,IAAI,OAAO,KAAK,GAAG;EACnC,IAAI,QAAQ,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,SAAS,UAAU,IAAI;CACpE;CACA,OAAO;AACR;;;;;;;;;;;;;;;;;;;;;;;;;;ACzIA,SAAS,uBAAuB,MAAM;CACrC,OAAO,GAAG,YAAY,kBAAkB,IAAI,CAAC,EAAE;AAChD;;;;;;;;;;AAUA,SAAS,kBAAkB,MAAM;CAChC,IAAI,OAAO,KAAK,IAAI,GAAG,OAAO;CAC9B,MAAM,SAAS,SAAS,IAAI;CAC5B,OAAO,OAAO,SAAS,IAAI,SAAS;AACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiGA,SAAS,UAAU,YAAY;CAC9B,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,WAAW,WAAW,MAAM,QAAQ,CAAC,CAAC,OAAO,OAAO;CAC1D,IAAI,SAAS,UAAU,GAAG,OAAO;CACjC,OAAO,SAAS,KAAK,SAAS,UAAU,UAAU,IAAI,QAAQ,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,QAAQ,MAAM,CAAC,IAAI,QAAQ,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,QAAQ,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE;AACvK;;;;;;;;;;;;;;;;;;;;ACtHA,SAAgB,gBACZ,UACA,kBACA,aACgB;CAChB,MAAM,SAAS,SAAS;CACxB,IAAI,OAAO,WAAW,YAClB,MAAM,IAAI,MACN,WAAW,SAAS,eAAe,KAAK,SAAS,aAAa,KAAK,GAAG,OAClE,iBAAiB,KAAK,yEAC9B;CAGJ,MAAM,mBAAmB,WAAW,UAAU,kBAAkB,aAAa,MAAM;CAKnF,MAAM,eAAe,SAAS,gBAAgB,eAAe,YAAY,iBAAiB,IAAI;CAE9F,MAAM,SAAmH;EACrH;EAQA,cAAc,sBAAsB,OAAO,CAAC;EAC5C,YAAY,iBAAiB;EAC7B,UAAU,SAAS;EACnB,UAAU,SAAS;EACnB,WAAW,SAAS;CAKxB;CAEA,MAAM,aAAa,YAAY,iBAAiB,QAAQ,iBAAiB,IAAI;CAE7E,QAAQ,SAAS,MAAjB;EACI,KAAK,aACD,OAAO;GACH,GAAG;GACH,MAAM;GACN,aAAa;GACb,UAAU;GACV,QAAQ;GACR,UAAU,SAAS,YAAY,uBAAuB,YAAY;GAClE,OAAO,SAAS,UAAU;EAC9B;EAEJ,KAAK,UACD,OAAO;GACH,GAAG;GACH,MAAM;GACN,aAAa;GACb,UAAU;GACV,QAAQ;GACR,oBAAoB,SAAS,sBAAsB,uBAAuB,UAAU;GACpF,WAAW,SAAS;EACxB;EAEJ,KAAK,WACD,OAAO;GACH,GAAG;GACH,MAAM;GACN,aAAa;GACb,UAAU;GACV,QAAQ;GACR,oBAAoB,SAAS,sBAAsB,uBAAuB,UAAU;GAIpF,WAAW,SAAS;EACxB;EAEJ,KAAK,cAAc;GACf,MAAM,cAAc,aAAa,gBAAgB;GACjD,MAAM,cAAc,aAAa,gBAAgB;GACjD,OAAO;IACH,GAAG;IACH,MAAM;IACN,aAAa;IACb,UAAU;IACV,QAAQ;IACR,SAAS;KAGL,OAAO,SAAS,SAAS,SAAS,CAAC,aAAa,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG;KAC5E,cAAc,SAAS,SAAS,gBAAgB,uBAAuB,UAAU;KACjF,cAAc,SAAS,SAAS,gBAAgB,uBAAuB,YAAY;KAInF,YAAY,SAAS,SAAS,cAAc,CAAC;IACjD;GACJ;EACJ;EAEA,KAAK,OACD,OAAO;GACH,GAAG;GACH,MAAM;GACN,aAAa,SAAS;GACtB,UAAU,eAAe,SAAS,aAAa,SAAS,QAAQ;GAGhE,QAAQ;GACR,UAAU,SAAS;EACvB;EAEJ,SAII,MAAM,IAAI,MAAM,0BAA0B,KAAK,UAAU,QAAU,GAAG;CAE9E;AACJ;;;;;;;;;;;;;;;;;;AAmBA,SAAS,eAAe,aAA6B,UAA6D;CAG9G,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG,OAAO;CACrC,IAAI,gBAAgB,OAAO,OAAO,SAAS,WAAW;CACtD,OAAO,SAAS,WAAW,KAAK,SAAS,EAAE,CAAC,UAAU,SAAS,EAAE,CAAC;AACtE;;AAGA,SAAS,SAAS,UAAoB,kBAAoC,aAA8B;CACpG,MAAM,OAAO,SAAS,gBAAgB;CACtC,OAAO,WAAW,OAAO,KAAK,KAAK,KAAK,GAAG,OAAO,iBAAiB,KAAK;AAC5E;;;;;;;;;;;;;;;AAgBA,SAAS,sBAAsB,OAAyB;CACpD,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,IAAK,MAA6B,MAAM,OAAO;CAC/C,MAAM,QAAS,MAAgC;CAC/C,OAAO,SAAS,OAAO,UAAU,YAAa,MAA6B,OAAO,QAAQ;AAC9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAS,WACL,UACA,kBACA,aACA,QAC8B;CAC9B,IAAI;CACJ,IAAI;EACA,mBAAmB,sBAAsB,OAAO,CAAC;CACrD,SAAS,OAAO;EAGZ,IAAI,iBAAiB,gBACjB,MAAM,IAAI,MACN,GAAG,SAAS,UAAU,kBAAkB,WAAW,EAAE,wRAIrD,EAAE,OAAO,MAAM,CACnB;EAEJ,MAAM;CACV;CAEA,IAAI,CAAC,kBAAkB,MACnB,MAAM,IAAI,MACN,GAAG,SAAS,UAAU,kBAAkB,WAAW,EAAE,qCAClD,qBAAqB,KAAA,IAAY,gBAAgB,qCAAqC,OACxF,qBAAqB,KAAA,IAChB,8QAGA,OAAQ,iBAAwC,SAAS,aACrD,8LAGA,2DACd;CAGJ,OAAO;AACX;;;;;;;;;;;;;;;AClQA,SAAgB,yBAAyB,UAAqC;CAC1E,OAAO,SAAS;AACpB;;AAGA,IAAM,0CAA0B,IAAI,QAA4D;;;;;;;;;;;;;;;AAgBhG,SAAgB,2BACZ,YACgC;CAChC,MAAM,SAAS,wBAAwB,IAAI,UAAU;CACrD,IAAI,QAAQ,OAAO;CAEnB,IAAI,CAAC,6BAA6B,UAAU,GAAG,OAAO,CAAC;CAEvD,MAAM,YAA8C,CAAC;CAErD,KAAK,MAAM,YAAY,WAAW,aAAa,CAAC,GAAG;EAC/C,MAAM,WAAW,gBAAgB,UAAU,UAAU;EACrD,UAAU,SAAS,gBAAgB;CACvC;CAKA,KAAK,MAAM,CAAC,aAAa,aAAa,OAAO,QAAQ,WAAW,cAAc,CAAC,CAAC,GAAG;EAC/E,IAAK,UAAuB,SAAS,YAAY;EACjD,MAAM,WAAY,SAA8B;EAChD,IAAI,CAAC,YAAY,UAAU,cAAc;EAEzC,UAAU,eAAe,gBAAgB,UAAU,YAAY,WAAW;CAC9E;CAEA,wBAAwB,IAAI,YAAY,SAAS;CACjD,OAAO;AACX;;;;;;;;;AAUA,SAAgB,0BACZ,YACA,UAC4B;CAC5B,MAAM,WAAW,2BAA2B,UAAU;CACtD,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,WAAW,cAAc,CAAC,CAAC,GAAG;EAClE,MAAM,OAAO;EACb,IAAI,MAAM,SAAS,YAAY;EAG/B,IAAI,SAAS,SAAS,UAAU,OAAO;EACvC,MAAM,YAAa,KAA0B,UAAU;EACvD,IAAI,aAAa,aAAa,UAAU,SAAS,MAAM,UAAU,OAAO;CAC5E;AAEJ;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,mBAAmB,YAA8B,UAAqC;CAClG,OAAO,QAAQ,0BAA0B,YAAY,QAAQ,CAAC,EAAE,YAAY,QAAQ;AACxF;;;;;;;;;;;;;;AA8CA,SAAgB,aAAa,YAAsC;CAE/D,QADiB,6BAA6B,UAAU,IAAI,WAAW,QAAQ,KAAA,MAC5D,YAAY,WAAW,IAAI,KAAK,YAAY,WAAW,IAAI;AAClF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2FA,SAAgB,kBAAkB,YAA0C,QAAwB;CAChG,MAAM,aAAa,YAAY;CAC/B,IAAI,YAAY;EACZ,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,UAAU,GAAG;GAClD,MAAM,aAAc,MAA+C;GACnE,IAAI,OAAO,eAAe,YAAY,eAAe,QAAQ,OAAO;EACxE;EACA,KAAK,MAAM,OAAO,OAAO,KAAK,UAAU,GAAG;GACvC,IAAI,QAAQ,QAAQ,OAAO;GAC3B,IAAI,YAAY,GAAG,MAAM,QAAQ,OAAO;EAC5C;CACJ;CACA,OAAO,UAAU,MAAM;AAC3B;;;;;;;;;;AAWA,SAAgB,aACZ,mBACA,KAC4B;CAE5B,IAAI,kBAAkB,MAAM,OAAO,kBAAkB;CAGrD,MAAM,UAAU,IAAI,QAAQ,MAAM,GAAG;CACrC,IAAI,YAAY,OAAO,kBAAkB,UAAU,OAAO,kBAAkB;CAG5E,MAAM,WAAW,IAAI,QAAQ,MAAM,GAAG;CACtC,IAAI,aAAa,OAAO,kBAAkB,WAAW,OAAO,kBAAkB;AAGlF;;;;;;;AChPA,SAAgB,gBAAgB,UAAyD;CACrF,IAAI,CAAC,UAAU,OAAO,KAAA;CACtB,IAAI,SAAS,gBAAgB,OAAO;CACpC,MAAM,SAAS,SAAS;CACxB,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,IAAI,OAAO,SAAS,KAAA,KAAa,OAAO,UAAU,KAAA,GAAW,OAAO,KAAA;CACpE,OAAO;AACX;;AAGA,IAAM,kBAA+B,OAAO,OAAO;CAAE,MAAM,OAAO,OAAO,CAAC,CAAC;CAAG,OAAO,OAAO,OAAO,CAAC,CAAC;AAAE,CAAC;;;;;;;;;;;;;;;AAgBxG,SAAS,UAAU,SAAwC,QAA0C;CACjG,IAAI,YAAY,KAAA,GAAW,OAAO;CAClC,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,QAAQ,OAAO;CACrB,IAAI,CAAC,SAAS,MAAM,WAAW,GAAG,OAAO;CACzC,OAAO,MAAM,SAAA,OAAmB,KAAK,QAAQ,MAAK,SAAQ,MAAM,SAAS,IAAI,CAAC;AAClF;;AAGA,SAAgB,aAAa,UAAgC,QAA0C;CACnG,MAAM,SAAS,gBAAgB,QAAQ;CACvC,OAAO,SAAS,UAAU,OAAO,MAAM,MAAM,IAAI;AACrD;;AAGA,SAAgB,cAAc,UAAgC,QAA0C;CACpG,MAAM,SAAS,gBAAgB,QAAQ;CACvC,OAAO,SAAS,UAAU,OAAO,OAAO,MAAM,IAAI;AACtD;;;;;;;;;;;;;;;;;AAkBA,SAAgB,qBACZ,YACA,QACA,MAC4C;CAC5C,MAAM,UAAU,SAAS,SAAS,eAAe;CACjD,OAAO,WAAW,aAAY,aAAY,CAAC,QAAQ,UAAU,MAAM,CAAC;AACxE;;;;;;;;;;;;;AAcA,SAAgB,oBAAoB,YAA2C;CAC3E,OAAO,WAAW,aAAY,aAAY,QAAQ,UAAU,cAAc,CAAC,CAAC,CAAC;AACjF;AAEA,SAAS,WACL,YACA,YAC4C;CAC5C,MAAM,WAAqB,CAAC;CAC5B,MAAM,0BAAU,IAAI,IAAY;CAChC,MAAM,aAAa,WAAW,cAAc,CAAC;CAG7C,IAAI;CAEJ,MAAM,YAAY,SAAiB;EAC/B,IAAI,QAAQ,cAAc,CAAC,SAAS,SAAS,IAAI,GAAG,SAAS,KAAK,IAAI;EACtE,QAAQ,IAAI,IAAI;CACpB;CAEA,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,UAAU,GAAG;EACvD,IAAI,CAAC,WAAW,QAAoB,GAAG;EACvC,SAAS,IAAI;EACb,MAAM,aAAc,SAAsB;EAC1C,IAAI,YAAY,QAAQ,IAAI,UAAU;EACtC,IAAK,SAAsB,SAAS,YAAY;GAC5C,cAAc,2BAA2B,UAAU;GACnD,MAAM,WAAW,aAAa,WAAW,IAAI;GAC7C,IAAI,UAAU,SAAS,aAAa;IAChC,SAAS,kBAAkB,YAAY,SAAS,QAAQ,CAAC;IACzD,QAAQ,IAAI,SAAS,QAAQ;GACjC;EACJ;CACJ;CACA,OAAO;EAAE;EAAU;CAAQ;AAC/B"}
@@ -2,8 +2,8 @@ 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 { c as RebaseClientError } from "./strings.es-BeZWAodn.js";
6
- import { J as CANONICAL_TO_REST, Q as isRelationAggregateSort, X as NULL_OPS, Y as LIST_OPS, Z as REST_TO_CANONICAL, et as sortKeyToString, q as ALL_WHERE_FILTER_OPS, tt as toCanonicalOp } from "./src-D5Y3N-S4.js";
5
+ import { a as RebaseClientError } from "./data_driver-D0mg1Y_x.js";
6
+ import { $ as isRelationAggregateSort, J as ALL_WHERE_FILTER_OPS, Q as REST_TO_CANONICAL, X as LIST_OPS, Y as CANONICAL_TO_REST, Z as NULL_OPS, nt as toCanonicalOp, tt as sortKeyToString } from "./src-Bh9LRPb9.js";
7
7
  //#region ../types/src/types/entities.ts
8
8
  /**
9
9
  * Class used to create a reference to a entity in a different path.
@@ -1625,4 +1625,4 @@ function deserializeLogicalCondition(str, nesting = 0) {
1625
1625
  //#endregion
1626
1626
  export { isFieldOperation as C, Vector as D, GeoPoint as E, hasFieldOperation as S, EntityRelation as T, normalizeOrderBy as _, serializeFilter as a, BATCH_REF_KEY as b, collectAllPages as c, IncludeSpecError as d, deserializeInclude as f, OrderBySpecError as g, serializeInclude as h, deserializeLogicalCondition as i, paginateFind as l, normalizeInclude as m, UnknownFilterOperatorError as n, serializeLogicalCondition as o, mergeIncludeSpecs as p, deserializeFilter as r, PAGE_WITH_OFFSET_MESSAGE as s, RESERVED_QUERY_KEYS as t, resolveFindWindow as u, serializeOrderBy as v, EntityReference as w, FIELD_OPERATORS as x, withRelationReferencesOnly as y };
1627
1627
 
1628
- //# sourceMappingURL=filter-dialect-O1HK6Nvr.js.map
1628
+ //# sourceMappingURL=filter-dialect-TrKFWLqT.js.map