@rebasepro/server 0.23.0 → 0.24.1-canary.g19596ef

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 (287) hide show
  1. package/README.md +28 -3
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/GCSStorageController-DEga1ULx.js +402 -0
  4. package/dist/GCSStorageController-DEga1ULx.js.map +1 -0
  5. package/dist/S3StorageController-Btx-6bJ5.js +480 -0
  6. package/dist/S3StorageController-Btx-6bJ5.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +92 -1
  8. package/dist/api/errors.d.ts +41 -0
  9. package/dist/api/live-schema-routes.d.ts +64 -10
  10. package/dist/api/logs-routes.d.ts +39 -1
  11. package/dist/api/mount-path.d.ts +30 -0
  12. package/dist/api/mount.d.ts +6 -13
  13. package/dist/api/openapi-generator.d.ts +17 -0
  14. package/dist/api/rest/api-generator.d.ts +97 -11
  15. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  16. package/dist/api/rest/field-access-query.d.ts +28 -8
  17. package/dist/api/rest/field-ops.d.ts +19 -7
  18. package/dist/api/rest/query-body.d.ts +10 -0
  19. package/dist/api/rest/write-validation.d.ts +9 -7
  20. package/dist/api/schema-editor-routes.d.ts +10 -1
  21. package/dist/api/sql-export-routes.d.ts +8 -0
  22. package/dist/api/types.d.ts +52 -1
  23. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-mCeSC03x.js} +310 -12
  24. package/dist/ast-schema-editor-mCeSC03x.js.map +1 -0
  25. package/dist/auth/access.d.ts +156 -0
  26. package/dist/auth/adapter-middleware.d.ts +2 -1
  27. package/dist/auth/address-ownership.d.ts +16 -1
  28. package/dist/auth/admin-roles-route.d.ts +4 -2
  29. package/dist/auth/admin-roles.d.ts +17 -20
  30. package/dist/auth/admin-user-ops.d.ts +24 -2
  31. package/dist/auth/admin-users-route.d.ts +2 -0
  32. package/dist/auth/api-keys/api-key-middleware.d.ts +64 -56
  33. package/dist/auth/api-keys/api-key-routes.d.ts +44 -11
  34. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  35. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  36. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  37. package/dist/auth/api-keys/index.d.ts +11 -11
  38. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  39. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  40. package/dist/auth/auth-hooks.d.ts +46 -7
  41. package/dist/auth/builtin-auth-adapter.d.ts +21 -0
  42. package/dist/auth/cookie-utils.d.ts +7 -0
  43. package/dist/auth/deliverable-address.d.ts +6 -0
  44. package/dist/auth/email-change-routes.d.ts +44 -0
  45. package/dist/auth/expired-token-sweep.d.ts +71 -0
  46. package/dist/auth/impersonation.d.ts +110 -0
  47. package/dist/auth/index.d.ts +4 -2
  48. package/dist/auth/interfaces.d.ts +120 -59
  49. package/dist/auth/jwks-routes.d.ts +5 -1
  50. package/dist/auth/jwt-crypto.d.ts +7 -0
  51. package/dist/auth/jwt-keys.d.ts +4 -0
  52. package/dist/auth/jwt.d.ts +319 -157
  53. package/dist/auth/magic-link-routes.d.ts +5 -6
  54. package/dist/auth/mfa-crypto.d.ts +9 -3
  55. package/dist/auth/mfa-gate.d.ts +4 -2
  56. package/dist/auth/mfa-routes.d.ts +8 -9
  57. package/dist/auth/middleware.d.ts +26 -7
  58. package/dist/auth/otp-routes.d.ts +5 -6
  59. package/dist/auth/passwordless-signup.d.ts +27 -0
  60. package/dist/auth/platform-token.d.ts +122 -0
  61. package/dist/auth/rate-limiter.d.ts +99 -31
  62. package/dist/auth/registration-policy.d.ts +17 -0
  63. package/dist/auth/require-auth.d.ts +1 -1
  64. package/dist/auth/routes.d.ts +61 -0
  65. package/dist/auth/scope-routes.d.ts +22 -0
  66. package/dist/auth/session-routes.d.ts +17 -6
  67. package/dist/auth/token-revocation.d.ts +56 -1
  68. package/dist/auth/user-picking.d.ts +37 -0
  69. package/dist/auth/verify-credential.d.ts +49 -0
  70. package/dist/{auth-B-GIMpDG.js → auth-CSBky1mt.js} +4564 -1929
  71. package/dist/auth-CSBky1mt.js.map +1 -0
  72. package/dist/auth_adapter-CIItCTc8.js +21 -0
  73. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  74. package/dist/backend/create-backend.d.ts +281 -0
  75. package/dist/backend/default-backend.d.ts +35 -0
  76. package/dist/backend/no-collections.d.ts +19 -0
  77. package/dist/backend/options.d.ts +912 -0
  78. package/dist/backend/scope.d.ts +23 -0
  79. package/dist/backend/services.d.ts +54 -0
  80. package/dist/backend-DTAOsLQc.js.map +1 -1
  81. package/dist/backup/backup-common.d.ts +24 -3
  82. package/dist/backup/backup-routes.d.ts +46 -5
  83. package/dist/backup/backup-schedule.d.ts +33 -0
  84. package/dist/backup/backup-storage.d.ts +16 -0
  85. package/dist/backup/index.d.ts +5 -1
  86. package/dist/backup-DBGRUVTS.js +585 -0
  87. package/dist/backup-DBGRUVTS.js.map +1 -0
  88. package/dist/boot/boot.d.ts +2 -2
  89. package/dist/boot/bundle.d.ts +19 -0
  90. package/dist/boot/driver.d.ts +3 -0
  91. package/dist/boot/env.d.ts +58 -6
  92. package/dist/boot/http-server.d.ts +22 -0
  93. package/dist/boot/options.d.ts +16 -1
  94. package/dist/boot/provision.d.ts +4 -2
  95. package/dist/boot/resource-adapters.d.ts +11 -14
  96. package/dist/boot/runtime-cors.d.ts +17 -0
  97. package/dist/boot/security-headers.d.ts +26 -0
  98. package/dist/boot/sources.d.ts +9 -0
  99. package/dist/boot/static-routing.d.ts +65 -0
  100. package/dist/collection_patch-BRu-BvDv.js +472 -0
  101. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  102. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-DiFwiLg2.js} +5 -8
  103. package/dist/contract-routes-DiFwiLg2.js.map +1 -0
  104. package/dist/cron/cron-loader.d.ts +77 -18
  105. package/dist/cron/cron-routes.d.ts +1 -1
  106. package/dist/cron/cron-scheduler.d.ts +94 -23
  107. package/dist/cron/cron-store.d.ts +37 -5
  108. package/dist/cron/define-cron.d.ts +6 -2
  109. package/dist/cron/index.d.ts +3 -3
  110. package/dist/cron-loader-NHqRfKF0.js +202 -0
  111. package/dist/cron-loader-NHqRfKF0.js.map +1 -0
  112. package/dist/{cron-routes-eE8nif_b.js → cron-routes-DsH8Jwn5.js} +34 -8
  113. package/dist/cron-routes-DsH8Jwn5.js.map +1 -0
  114. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-D1DT0nw5.js} +349 -65
  115. package/dist/cron-scheduler-D1DT0nw5.js.map +1 -0
  116. package/dist/cron-store-DEpVstUU.js +577 -0
  117. package/dist/cron-store-DEpVstUU.js.map +1 -0
  118. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BNqi2NZe.js} +2 -2
  119. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BNqi2NZe.js.map} +1 -1
  120. package/dist/email/index.d.ts +2 -2
  121. package/dist/email/smtp-email-service.d.ts +23 -0
  122. package/dist/email/templates.d.ts +22 -0
  123. package/dist/email/types.d.ts +44 -0
  124. package/dist/env.d.ts +1 -39
  125. package/dist/{errors-DWsX4yTd.js → errors-Cqp86KnA.js} +263 -10
  126. package/dist/errors-Cqp86KnA.js.map +1 -0
  127. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  128. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  129. package/dist/{function-loader-xnbDAPfa.js → function-loader-DbCSoNJj.js} +69 -39
  130. package/dist/function-loader-DbCSoNJj.js.map +1 -0
  131. package/dist/{function-routes-Chet4-lB.js → function-routes-BSTClQNa.js} +28 -15
  132. package/dist/function-routes-BSTClQNa.js.map +1 -0
  133. package/dist/functions/context.d.ts +26 -6
  134. package/dist/functions/define-function.d.ts +10 -6
  135. package/dist/functions/function-loader.d.ts +75 -18
  136. package/dist/functions/function-routes.d.ts +2 -1
  137. package/dist/functions/guards.d.ts +22 -5
  138. package/dist/functions/index.d.ts +6 -5
  139. package/dist/functions/index.js +148 -50
  140. package/dist/functions/index.js.map +1 -1
  141. package/dist/functions/internal.d.ts +2 -2
  142. package/dist/functions/proxy.d.ts +6 -2
  143. package/dist/functions/request-timeout.d.ts +7 -5
  144. package/dist/history/history-routes.d.ts +9 -0
  145. package/dist/history/retention-sweep.d.ts +81 -0
  146. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-RZ1Ow4YY.js} +4 -4
  147. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-RZ1Ow4YY.js.map} +1 -1
  148. package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
  149. package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
  150. package/dist/index.d.ts +32 -13
  151. package/dist/index.es.js +25329 -18393
  152. package/dist/index.es.js.map +1 -1
  153. package/dist/init/docs.d.ts +35 -10
  154. package/dist/init/health.d.ts +17 -2
  155. package/dist/init/process-safety.d.ts +7 -2
  156. package/dist/init/shutdown.d.ts +26 -1
  157. package/dist/init.d.ts +49 -811
  158. package/dist/internal-tables-L2t39RdT.js +136 -0
  159. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  160. package/dist/jobs/job-queue.d.ts +8 -0
  161. package/dist/jobs/job-store.d.ts +24 -0
  162. package/dist/jobs/types.d.ts +25 -4
  163. package/dist/{jobs-CazMYhyy.js → jobs-8G-_agld.js} +235 -25
  164. package/dist/jobs-8G-_agld.js.map +1 -0
  165. package/dist/{jwt-DnQHNFCl.js → jwt-CBaFc34Y.js} +546 -447
  166. package/dist/jwt-CBaFc34Y.js.map +1 -0
  167. package/dist/{keys-CogCQpxG.js → keys-COzQ2aE8.js} +3 -17
  168. package/dist/{keys-CogCQpxG.js.map → keys-COzQ2aE8.js.map} +1 -1
  169. package/dist/load-failure-L5hvfDoG.js +80 -0
  170. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  171. package/dist/{logger-DO2PZc4i.js → logger-Bt32sHFw.js} +41 -5
  172. package/dist/logger-Bt32sHFw.js.map +1 -0
  173. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DQJhrf-b.js} +48 -8
  174. package/dist/logs-routes-DQJhrf-b.js.map +1 -0
  175. package/dist/mcp/consent-page.d.ts +1 -1
  176. package/dist/mcp/mcp-routes.d.ts +13 -0
  177. package/dist/mcp/mcp-tools.d.ts +23 -9
  178. package/dist/mcp/oauth-metadata.d.ts +21 -16
  179. package/dist/mcp/oauth-routes.d.ts +14 -1
  180. package/dist/metrics/index.d.ts +18 -0
  181. package/dist/metrics/pool-wait.d.ts +50 -0
  182. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  183. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  184. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  185. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  186. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-CeGW2dLC.js} +455 -80
  187. package/dist/openapi-generator-CeGW2dLC.js.map +1 -0
  188. package/dist/policy-PdzX-Opd.js +121 -0
  189. package/dist/policy-PdzX-Opd.js.map +1 -0
  190. package/dist/project_manifest-R6y_KHms.js +59 -0
  191. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  192. package/dist/{proxy-Czngl3p9.js → proxy-BEKbsil6.js} +5 -4
  193. package/dist/proxy-BEKbsil6.js.map +1 -0
  194. package/dist/{query-parser-DGRVFNM3.js → query-parser-ECxP0nDw.js} +497 -52
  195. package/dist/query-parser-ECxP0nDw.js.map +1 -0
  196. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-BiRelY-U.js} +17 -10
  197. package/dist/request-timeout-BiRelY-U.js.map +1 -0
  198. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  199. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  200. package/dist/schema-edit/apply-schema-change.d.ts +73 -3
  201. package/dist/schema-edit/local-source.d.ts +29 -0
  202. package/dist/schema-edit/project-root.d.ts +3 -2
  203. package/dist/schema-edit/remote-source.d.ts +20 -5
  204. package/dist/schema-edit/source-editor.d.ts +28 -0
  205. package/dist/schema-editor-routes-DRXBAbZy.js +136 -0
  206. package/dist/schema-editor-routes-DRXBAbZy.js.map +1 -0
  207. package/dist/serve-spa.d.ts +58 -0
  208. package/dist/services/outbound-post.d.ts +50 -0
  209. package/dist/services/outbound-url-guard.d.ts +34 -5
  210. package/dist/services/routed-realtime-service.d.ts +16 -0
  211. package/dist/services/webhook-service.d.ts +66 -14
  212. package/dist/singleton.d.ts +9 -5
  213. package/dist/soft-delete-params-CBryZXMq.js +668 -0
  214. package/dist/soft-delete-params-CBryZXMq.js.map +1 -0
  215. package/dist/sql-export-routes-D8ZaRvoR.js +240 -0
  216. package/dist/sql-export-routes-D8ZaRvoR.js.map +1 -0
  217. package/dist/{src-vkcwKXbT.js → src-D5Y3N-S4.js} +440 -20
  218. package/dist/src-D5Y3N-S4.js.map +1 -0
  219. package/dist/storage/GCSStorageController.d.ts +48 -2
  220. package/dist/storage/LocalStorageController.d.ts +69 -2
  221. package/dist/storage/S3StorageController.d.ts +39 -3
  222. package/dist/storage/image-transform.d.ts +15 -0
  223. package/dist/storage/index.d.ts +2 -2
  224. package/dist/storage/maintenance-routes.d.ts +23 -0
  225. package/dist/storage/maintenance.d.ts +126 -0
  226. package/dist/storage/property-limits.d.ts +41 -6
  227. package/dist/storage/rendition-cache.d.ts +9 -11
  228. package/dist/storage/request-keys.d.ts +15 -0
  229. package/dist/storage/request-principals.d.ts +30 -0
  230. package/dist/storage/requested-object.d.ts +74 -0
  231. package/dist/storage/routes.d.ts +52 -14
  232. package/dist/storage/source-variables.d.ts +10 -0
  233. package/dist/storage/streams.d.ts +42 -0
  234. package/dist/storage/tus-handler.d.ts +76 -7
  235. package/dist/storage/types.d.ts +101 -7
  236. package/dist/storage/upload-validation.d.ts +13 -0
  237. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  238. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  239. package/dist/strings.es-BeZWAodn.js +256 -0
  240. package/dist/strings.es-BeZWAodn.js.map +1 -0
  241. package/dist/upload-validation-CVobI5xO.js +172 -0
  242. package/dist/upload-validation-CVobI5xO.js.map +1 -0
  243. package/dist/utils/byte-size.d.ts +10 -0
  244. package/dist/utils/compression.d.ts +6 -0
  245. package/dist/utils/load-failure.d.ts +14 -0
  246. package/dist/utils/logger.d.ts +21 -0
  247. package/package.json +5 -5
  248. package/dist/GCSStorageController-CjrA4PMo.js +0 -242
  249. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  250. package/dist/S3StorageController-B6pKDNVj.js +0 -264
  251. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  252. package/dist/admin-roles-vYdp_Pil.js +0 -36
  253. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  254. package/dist/admin_block-DxKLmdiv.js +0 -206
  255. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  256. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  257. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  258. package/dist/auth-B-GIMpDG.js.map +0 -1
  259. package/dist/backup-D7YR94N3.js +0 -253
  260. package/dist/backup-D7YR94N3.js.map +0 -1
  261. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  262. package/dist/cron-loader-DfTj2Hbi.js +0 -118
  263. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  264. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  265. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  266. package/dist/cron-store-TcoGz-xS.js +0 -305
  267. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  268. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  269. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  270. package/dist/errors-DWsX4yTd.js.map +0 -1
  271. package/dist/function-loader-xnbDAPfa.js.map +0 -1
  272. package/dist/function-routes-Chet4-lB.js.map +0 -1
  273. package/dist/jobs-CazMYhyy.js.map +0 -1
  274. package/dist/jwt-DnQHNFCl.js.map +0 -1
  275. package/dist/logger-DO2PZc4i.js.map +0 -1
  276. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  277. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  278. package/dist/proxy-Czngl3p9.js.map +0 -1
  279. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  280. package/dist/request-timeout-C_4C2BeR.js.map +0 -1
  281. package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
  282. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  283. package/dist/src-pmvW7BFx.js +0 -6688
  284. package/dist/src-pmvW7BFx.js.map +0 -1
  285. package/dist/src-vkcwKXbT.js.map +0 -1
  286. package/dist/types-BfKcm9do.js +0 -39
  287. package/dist/types-BfKcm9do.js.map +0 -1
@@ -2,9 +2,207 @@ 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 { B as fieldKeyForColumn, E as effectiveAccess, G as resolveCollectionRelations, R as getTenantConfig, U as isRelationRequired, V as findRelation, pt as isToMany } from "./src-pmvW7BFx.js";
6
- import "./src-vkcwKXbT.js";
5
+ import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
+ import "./strings.es-BeZWAodn.js";
7
+ import "./src-D5Y3N-S4.js";
8
+ import { r as policy } from "./policy-PdzX-Opd.js";
9
+ import { c as fieldKeyForColumn, f as isRelationRequired, l as findRelation, m as resolveCollectionRelations, n as HARD_DELETE_QUERY_PARAM, o as effectiveAccess, t as DELETED_QUERY_PARAM, u as getTableName } from "./soft-delete-params-CBryZXMq.js";
10
+ //#region ../types/src/types/relations.ts
11
+ /**
12
+ * The key a junction row's own columns are carried under, in both directions.
13
+ *
14
+ * A read that includes a `manyToMany` relation serves each related row with its
15
+ * link's columns nested here — `{ id: 5, name: "ts", _pivot: { role: "owner" } }`
16
+ * — and a membership write may name the same key on an element to state what
17
+ * the link should hold. One constant because the two have to be the same word:
18
+ * a wire name that differs between the read and the write it round-trips
19
+ * through is a shape no client can echo back.
20
+ *
21
+ * Leading underscore, like `_matches`: it reads as metadata about the row
22
+ * rather than as one of its columns. A payload property may not be named
23
+ * `_pivot` either — `checkJunctionPayload` refuses it — so the key means one
24
+ * thing wherever it appears.
25
+ *
26
+ * @group Models
27
+ */
28
+ var JUNCTION_PIVOT_KEY = "_pivot";
29
+ /**
30
+ * The key a row carries the totals of its counted relations under.
31
+ *
32
+ * An include that asks for a count — `include: { tags: { limit: 4, count: true } }`
33
+ * — serves, beside the (capped) `tags`, how many related rows there are in all:
34
+ * `{ id: 1, tags: [ …4 rows ], _count: { tags: 37 } }`. One object per row,
35
+ * keyed by relation name, so counting a second relation adds a key rather than
36
+ * a second top-level name.
37
+ *
38
+ * Leading underscore, like {@link JUNCTION_PIVOT_KEY} and `_matches`: it
39
+ * describes the read, not the record, and no column or form writes it. A
40
+ * collection with a property of this name cannot ask for a count — the read
41
+ * answers 400 rather than writing over the column.
42
+ *
43
+ * @group Models
44
+ */
45
+ var INCLUDE_COUNT_KEY = "_count";
46
+ /** @group Models */
47
+ function hasForeignKeyOnTarget(relation) {
48
+ return relation.kind === "hasOne" || relation.kind === "hasMany";
49
+ }
50
+ /** @group Models */
51
+ function isManyToMany(relation) {
52
+ return relation.kind === "manyToMany";
53
+ }
54
+ /** @group Models */
55
+ function isToMany(relation) {
56
+ return relation.cardinality === "many";
57
+ }
58
+ //#endregion
59
+ //#region ../types/src/types/tenancy.ts
60
+ /** Narrow a {@link TenantSource} to its claim form. @group Models */
61
+ function isTenantClaimSource(source) {
62
+ return typeof source.claim === "string";
63
+ }
64
+ /**
65
+ * The roles tenancy does not apply to, when the collection names none.
66
+ *
67
+ * `admin`, mirroring the security baseline every collection already carries
68
+ * (`<table>_default_admin_read` / `_write`): the Studio, `dataAsAdmin` and a
69
+ * support operator all run with it, and a tenancy rule that locked them out
70
+ * would make the admin panel show an empty table on a collection full of rows.
71
+ *
72
+ * @group Models
73
+ */
74
+ var DEFAULT_TENANT_BYPASS_ROLES = ["admin"];
75
+ //#endregion
76
+ //#region ../common/src/util/tenant.ts
77
+ /**
78
+ * The one reading of `collection.tenant`.
79
+ *
80
+ * Four things have to agree for a tenant-scoped collection to work — the
81
+ * column, the RLS policy, the value stamped on insert and the index — and
82
+ * before this they were four hand-written declarations that nothing compared.
83
+ * The three that are schema become a {@link SecurityRule} and a column effect
84
+ * derived here and in `planSchema`; the fourth, the write path, is
85
+ * {@link resolveTenantWrite}.
86
+ *
87
+ * Everything in this module is pure. The policy it builds is a `SecurityRule`
88
+ * like any other, which is what makes `db push`, the doctor, boot-ensure, the
89
+ * drift detector and the Studio treat the tenancy policy as what it is —
90
+ * generated, named, and recognisable — rather than as somebody's hand-written
91
+ * SQL that a push should offer to drop.
92
+ */
93
+ /**
94
+ * The collection's tenancy declaration, or nothing.
95
+ *
96
+ * Read through this rather than off the object, so the one shape check —
97
+ * `tenant` is an object carrying a `field` and a `from` — is in one place. A
98
+ * config that is *wrong* is refused by `validateCollectionConfig` with a
99
+ * message; this is only asking whether there is one.
100
+ */
101
+ function getTenantConfig(collection) {
102
+ const tenant = collection?.tenant;
103
+ if (!tenant || typeof tenant !== "object") return void 0;
104
+ if (typeof tenant.field !== "string" || !tenant.field) return void 0;
105
+ if (!tenant.from || typeof tenant.from !== "object") return void 0;
106
+ return tenant;
107
+ }
108
+ /** The roles tenancy does not apply to, defaulted. */
109
+ function tenantBypassRoles(tenant) {
110
+ return tenant.bypassRoles ?? DEFAULT_TENANT_BYPASS_ROLES;
111
+ }
112
+ /**
113
+ * The name of the policy a tenant declaration compiles to.
114
+ *
115
+ * Explicit — not a `getPolicyNameHash` of the rule — precisely because the
116
+ * rule's *body* is compiled with more information in some callers than in
117
+ * others (`planSchema` can resolve a relation's target collection and so knows
118
+ * the column's type; the Studio, asking only for names, cannot). A hashed name
119
+ * would then differ between the two, and the same policy would read as drift.
120
+ * A frozen identifier: see `contracts/derived-names.txt`.
121
+ */
122
+ function tenantPolicyName(tableName) {
123
+ return `${tableName}_tenant_scope`;
124
+ }
125
+ /**
126
+ * The condition a tenant declaration means, as a policy expression.
127
+ *
128
+ * `serverContext()` first, for the same reason every injected baseline rule
129
+ * carries it: the trusted plane runs migrations, the auth flows and the boot,
130
+ * and a restrictive policy that excluded it would not protect a tenant, it
131
+ * would stop the server from starting.
132
+ *
133
+ * Then the bypass roles, then the tenancy test itself — a claim comparison or a
134
+ * correlated `EXISTS` over the membership table, which are the two ways a
135
+ * deployment answers "which tenant is this caller in".
136
+ */
137
+ function tenantScopeExpression(tenant) {
138
+ const match = isTenantClaimSource(tenant.from) ? policy.compare(policy.field(tenant.field), "eq", policy.authClaim(tenant.from.claim)) : policy.existsIn({
139
+ collection: tenant.from.membership.collection,
140
+ where: policy.and(policy.compare(policy.field(tenant.from.membership.tenantField), "eq", policy.outerField(tenant.field)), policy.compare(policy.field(tenant.from.membership.userField), "eq", policy.authUid()))
141
+ });
142
+ const bypass = tenantBypassRoles(tenant);
143
+ return bypass.length > 0 ? policy.or(policy.serverContext(), policy.rolesOverlap(bypass), match) : policy.or(policy.serverContext(), match);
144
+ }
145
+ /**
146
+ * The rule a tenant declaration compiles to, or nothing when there is none.
147
+ *
148
+ * **Restrictive**, and that is the whole design. A restrictive policy is ANDed
149
+ * with every other policy on the table, so tenancy narrows what the
150
+ * collection's own `securityRules` allow and can never widen it. A permissive
151
+ * one would OR with them, and a single `access: "public"` rule elsewhere in the
152
+ * file would take the entire tenancy boundary off without contradicting
153
+ * anything a reader could see.
154
+ *
155
+ * One rule with `operation: "all"` rather than four with `operations: [...]`:
156
+ * `FOR ALL` gives Postgres the USING clause for SELECT/UPDATE/DELETE and the
157
+ * WITH CHECK clause for INSERT/UPDATE, which is exactly the coverage wanted,
158
+ * as one policy with one name instead of four.
159
+ */
160
+ function buildTenantSecurityRule(collection) {
161
+ const tenant = getTenantConfig(collection);
162
+ if (!tenant) return void 0;
163
+ const expression = tenantScopeExpression(tenant);
164
+ return {
165
+ name: tenantPolicyName(getTableName(collection)),
166
+ mode: "restrictive",
167
+ operation: "all",
168
+ condition: expression,
169
+ check: expression
170
+ };
171
+ }
172
+ /**
173
+ * The property a `softDelete` collection records a deletion in — its wire
174
+ * name, what a delete stamps and what a restore (an update to `null`) clears —
175
+ * or `undefined` for a collection without soft delete.
176
+ *
177
+ * One reading of the declaration for every layer that needs it: the driver
178
+ * that stamps and filters on it, and the REST route that has to recognise a
179
+ * restore before it looks the row up.
180
+ */
181
+ function softDeleteFieldOf(collection) {
182
+ const declared = collection && "softDelete" in collection ? collection.softDelete : void 0;
183
+ if (!declared) return void 0;
184
+ return typeof declared === "object" && declared.field || "deletedAt";
185
+ }
186
+ /**
187
+ * Whether an update's values restore a soft-deleted row: they set the
188
+ * soft-delete field back to `null`, the documented restore.
189
+ *
190
+ * A restore is an ordinary update of a row every default read hides, so a door
191
+ * that looks the row up before updating it has to include the trashed rows for
192
+ * this update — and only for this one: any other edit of a trashed row is a
193
+ * 404.
194
+ */
195
+ function restoresSoftDeletedRow(collection, values) {
196
+ const field = softDeleteFieldOf(collection);
197
+ return field !== void 0 && values?.[field] === null;
198
+ }
199
+ //#endregion
7
200
  //#region src/api/openapi-generator.ts
201
+ var openapi_generator_exports = /* @__PURE__ */ __exportAll({
202
+ buildCollectionInputSchema: () => buildCollectionInputSchema,
203
+ buildCollectionSchema: () => buildCollectionSchema,
204
+ generateOpenApiSpec: () => generateOpenApiSpec
205
+ });
8
206
  function generateOpenApiSpec(collections, options = {}) {
9
207
  const basePath = options.basePath ?? "/api";
10
208
  const requireAuth = options.requireAuth ?? true;
@@ -47,7 +245,7 @@ function generateOpenApiSpec(collections, options = {}) {
47
245
  type: "integer",
48
246
  minimum: 1
49
247
  },
50
- description: "Page number (alternative to offset). Calculates offset as (page-1)*limit"
248
+ description: "Page number (alternative to offset). Calculates offset as (page-1)*limit. Cannot be combined with `offset` (400 PAGE_WITH_OFFSET)."
51
249
  },
52
250
  {
53
251
  name: "after",
@@ -108,9 +306,20 @@ function generateOpenApiSpec(collections, options = {}) {
108
306
  name: "distinct",
109
307
  in: "query",
110
308
  schema: { type: "boolean" },
111
- description: "`SELECT DISTINCT` over the returned columns. Only meaningful alongside `fields`: the primary key is always in the projection, so without narrowing it every row is already distinct. The response has no `meta.total` — the rows before deduplication are a different set, so their count would not describe the page — and `meta.hasMore` is true when the page came back full. Refused (400) alongside `searchString` or a vector search, which attach a per-row score that makes every row distinct by construction, and (400 DISTINCT_ORDER_BY_NOT_SELECTED) when `orderBy` names a column `fields` does not return.",
309
+ description: "`SELECT DISTINCT` over the returned columns. Only meaningful alongside `fields`: the primary key is always in the projection, so without narrowing it every row is already distinct. The response has no `meta.total` — the rows before deduplication are a different set, so their count would not describe the page — and `meta.hasMore` comes from reading one row past the page. Refused (400) alongside `searchString` or a vector search, which attach a per-row score that makes every row distinct by construction, and (400 DISTINCT_ORDER_BY_NOT_SELECTED) when `orderBy` names a column `fields` does not return.",
112
310
  example: "true"
113
311
  },
312
+ {
313
+ name: "count",
314
+ in: "query",
315
+ schema: {
316
+ type: "string",
317
+ enum: ["exact", "none"],
318
+ default: "exact"
319
+ },
320
+ description: "Whether to count every matching row for `meta.total`. `exact` (the default) runs a `COUNT(*)` beside the page, which on a large table costs more than the page itself; `none` skips it, and the response then has no `meta.total`. `meta.hasMore` is exact either way — without a count it comes from reading one row past the page — so a walk that pages until `hasMore` is false needs no count. Anything else is a 400 INVALID_COUNT.",
321
+ example: "none"
322
+ },
114
323
  {
115
324
  name: "searchString",
116
325
  in: "query",
@@ -149,7 +358,7 @@ function generateOpenApiSpec(collections, options = {}) {
149
358
  name: "vector_threshold",
150
359
  in: "query",
151
360
  schema: { type: "number" },
152
- description: "Drop rows farther than this distance. Rows are returned closest-first with a `_distance` field."
361
+ description: "Drop rows farther than this distance. Rows are returned closest-first with a `_distance` field. The response then has no `meta.total`: counting the rows within a distance computes it for every row of the table. `meta.hasMore` is exact, from reading one row past the page."
153
362
  }
154
363
  ];
155
364
  const spec = {
@@ -183,7 +392,7 @@ function generateOpenApiSpec(collections, options = {}) {
183
392
  properties: {
184
393
  total: {
185
394
  type: "integer",
186
- description: "Total number of matching records. Absent on a `distinct` read, which has no count of the rows it returns."
395
+ description: "Total number of matching records. Absent on a `distinct` read, which has no count of the rows it returns, on a vector search with a `vector_threshold`, whose count would compute a distance for every row, and when the request asked for `count=none`."
187
396
  },
188
397
  limit: {
189
398
  type: "integer",
@@ -220,7 +429,50 @@ function generateOpenApiSpec(collections, options = {}) {
220
429
  const paths = spec.paths;
221
430
  const schemas = spec.components.schemas;
222
431
  const tags = spec.tags;
223
- const reservedParameterNames = new Set(listQueryParameters().map((p) => p.name));
432
+ const reservedParameterNames = /* @__PURE__ */ new Set([...listQueryParameters().map((p) => p.name), DELETED_QUERY_PARAM]);
433
+ /**
434
+ * `?deleted=` on the reads of a `softDelete` collection. A delete there
435
+ * moves the row to the trash, and every read hides trashed rows unless it
436
+ * asks for them; the list, the single row, the count and the aggregate all
437
+ * honour this. Empty for any other collection.
438
+ */
439
+ const deletedParameters = (collection) => {
440
+ const field = softDeleteFieldOf(collection);
441
+ if (!field) return [];
442
+ return [{
443
+ name: DELETED_QUERY_PARAM,
444
+ in: "query",
445
+ required: false,
446
+ schema: {
447
+ type: "string",
448
+ enum: ["include", "only"]
449
+ },
450
+ description: `Rows a delete moved to the trash (\`${field}\` set) are left out unless this asks for them: \`include\` reads them with the live rows, \`only\` reads them alone. Any other value is a 400 INVALID_DELETED_PARAM.`
451
+ }];
452
+ };
453
+ /**
454
+ * `?hard=` on the deletes of a `softDelete` collection: delete for good
455
+ * rather than move to the trash. Empty for any other collection.
456
+ */
457
+ const hardDeleteParameters = (collection) => {
458
+ if (!softDeleteFieldOf(collection)) return [];
459
+ return [{
460
+ name: HARD_DELETE_QUERY_PARAM,
461
+ in: "query",
462
+ required: false,
463
+ schema: {
464
+ type: "boolean",
465
+ default: false
466
+ },
467
+ description: "`true` deletes for good instead of moving to the trash — a row already in the trash included, which is how the trash is emptied. Only `true`/`1` and `false`/`0` are accepted; anything else is a 400 INVALID_HARD_PARAM."
468
+ }];
469
+ };
470
+ /** What a delete does to a `softDelete` collection's row, for the delete operations' descriptions. */
471
+ const softDeleteBehaviour = (collection) => {
472
+ const field = softDeleteFieldOf(collection);
473
+ if (!field) return void 0;
474
+ return `This collection soft-deletes: a delete moves the row to the trash by stamping \`${field}\`, and every read leaves it out unless it passes \`?deleted=include\` or \`?deleted=only\`. A PATCH setting \`${field}\` back to \`null\` restores it. Pass \`?hard=true\` to delete for good.`;
475
+ };
224
476
  const registeredSchemas = new Set((collections || []).map(schemaNameFor));
225
477
  /**
226
478
  * `Prefer: return=minimal`, on every route that would otherwise send a row
@@ -387,27 +639,59 @@ function generateOpenApiSpec(collections, options = {}) {
387
639
  schemas[`${schemaName}Input`] = buildCollectionInputSchema(collection);
388
640
  schemas[`${schemaName}Update`] = buildCollectionUpdateSchema(collection);
389
641
  const dataPath = `/data/${slug}`;
390
- paths[`${dataPath}/count`] = { get: {
391
- tags: [collection.name],
392
- summary: `Count ${collection.name}`,
393
- description: "The number of rows the same filters would return, without returning them. Takes the filter and search parameters of the list endpoint; `limit`, `offset` and `orderBy` are not part of the question and are ignored.",
394
- operationId: `count${schemaName}`,
395
- parameters: [...listQueryParameters().filter((p) => p.name === "searchString"), ...buildFilterParameters(collection, reservedParameterNames)],
396
- responses: {
397
- 200: {
398
- description: "The number of matching rows",
399
- content: { "application/json": { schema: {
400
- type: "object",
401
- required: ["count"],
402
- properties: { count: {
403
- type: "integer",
404
- description: "Rows matching the filters"
405
- } }
406
- } } }
407
- },
408
- ...errorResponses(requireAuth)
409
- }
410
- } };
642
+ const softDeleteNote = softDeleteBehaviour(collection);
643
+ const countNarrowing = /* @__PURE__ */ new Set([
644
+ "where",
645
+ "or",
646
+ "and",
647
+ "not",
648
+ "searchString",
649
+ "vector_search",
650
+ "vector",
651
+ "vector_distance",
652
+ "vector_threshold"
653
+ ]);
654
+ const countParameters = [
655
+ ...listQueryParameters().filter((p) => countNarrowing.has(p.name)).map((p) => p.name === "vector_threshold" ? {
656
+ ...p,
657
+ description: "Count only the rows within this distance of `vector`."
658
+ } : p),
659
+ ...deletedParameters(collection),
660
+ ...buildFilterParameters(collection, reservedParameterNames)
661
+ ];
662
+ const countResponses = {
663
+ 200: {
664
+ description: "The number of matching rows",
665
+ content: { "application/json": { schema: {
666
+ type: "object",
667
+ required: ["count"],
668
+ properties: { count: {
669
+ type: "integer",
670
+ description: "Rows matching the filters"
671
+ } }
672
+ } } }
673
+ },
674
+ ...errorResponses(requireAuth)
675
+ };
676
+ paths[`${dataPath}/count`] = {
677
+ get: {
678
+ tags: [collection.name],
679
+ summary: `Count ${collection.name}`,
680
+ description: "The number of rows the same filters would return, without returning them. Takes the filter and search parameters of the list endpoint; `limit`, `offset` and `orderBy` are not part of the question and are ignored.",
681
+ operationId: `count${schemaName}`,
682
+ parameters: countParameters,
683
+ responses: countResponses
684
+ },
685
+ post: queryOperation({
686
+ tags: [collection.name],
687
+ summary: `Count ${collection.name}, parameters in the body`,
688
+ description: "The count `GET` answers, with its query parameters in a JSON body instead of the URL: the same filters, search and vector search, the same row-level security, and the same errors. It reads and never writes, so a credential needs `data:read` for it. Use it when the URL would not fit: a vector search's embedding, a long `in` list. A parameter also on the URL is a 400 INVALID_QUERY_BODY.",
689
+ operationId: `count${schemaName}Query`,
690
+ parameters: countParameters,
691
+ responses: countResponses,
692
+ reserved: reservedParameterNames
693
+ })
694
+ };
411
695
  paths[`${dataPath}/aggregate`] = { get: {
412
696
  tags: [collection.name],
413
697
  summary: `Aggregate ${collection.name}`,
@@ -439,6 +723,7 @@ function generateOpenApiSpec(collections, options = {}) {
439
723
  schema: { type: "string" },
440
724
  example: "count:desc"
441
725
  },
726
+ ...deletedParameters(collection),
442
727
  ...buildFilterParameters(collection, reservedParameterNames)
443
728
  ],
444
729
  responses: {
@@ -474,28 +759,42 @@ function generateOpenApiSpec(collections, options = {}) {
474
759
  ...errorResponses(requireAuth)
475
760
  }
476
761
  } };
762
+ const listParameters = [
763
+ ...listQueryParameters(),
764
+ ...deletedParameters(collection),
765
+ ...buildFilterParameters(collection, reservedParameterNames)
766
+ ];
767
+ const listResponses = {
768
+ 200: {
769
+ description: "Paginated list of entities",
770
+ content: { "application/json": { schema: {
771
+ type: "object",
772
+ properties: {
773
+ data: {
774
+ type: "array",
775
+ items: { $ref: `#/components/schemas/${schemaName}` }
776
+ },
777
+ meta: { $ref: "#/components/schemas/PaginationMeta" }
778
+ }
779
+ } } }
780
+ },
781
+ ...errorResponses(requireAuth)
782
+ };
783
+ paths[`${dataPath}/query`] = { post: queryOperation({
784
+ tags: [collection.name],
785
+ summary: `List ${collection.name}, parameters in the body`,
786
+ operationId: `query${schemaName}`,
787
+ parameters: listParameters,
788
+ responses: listResponses,
789
+ reserved: reservedParameterNames
790
+ }) };
477
791
  paths[dataPath] = {
478
792
  get: {
479
793
  tags: [collection.name],
480
794
  summary: `List ${collection.name}`,
481
795
  operationId: `list${schemaName}`,
482
- parameters: [...listQueryParameters(), ...buildFilterParameters(collection, reservedParameterNames)],
483
- responses: {
484
- 200: {
485
- description: "Paginated list of entities",
486
- content: { "application/json": { schema: {
487
- type: "object",
488
- properties: {
489
- data: {
490
- type: "array",
491
- items: { $ref: `#/components/schemas/${schemaName}` }
492
- },
493
- meta: { $ref: "#/components/schemas/PaginationMeta" }
494
- }
495
- } } }
496
- },
497
- ...errorResponses(requireAuth)
498
- }
796
+ parameters: listParameters,
797
+ responses: listResponses
499
798
  },
500
799
  post: {
501
800
  tags: [collection.name],
@@ -506,8 +805,8 @@ function generateOpenApiSpec(collections, options = {}) {
506
805
  in: "query",
507
806
  required: false,
508
807
  schema: { type: "string" },
509
- description: "Comma-separated columns to upsert on, turning the create into INSERT ... ON CONFLICT DO UPDATE. They must carry a declared uniqueness guarantee — `validation.unique`, a `unique` index, or the primary key — or the request is refused with a 400 naming the targets that do exist. Left off, this is a plain insert and a duplicate key still raises.",
510
- example: "email"
808
+ description: "Comma-separated columns to upsert on, turning the create into INSERT ... ON CONFLICT DO UPDATE. They must carry a declared uniqueness guarantee — `validation.unique`, a `unique` index, or the primary key — or the request is refused with a 400 naming the targets that do exist. Left off, this is a plain insert and a duplicate key still raises. Refused (400 `INVALID_CONFLICT_TARGET`) on the auth collection, whose rows are the users: create them with a plain POST and change them with PATCH.",
809
+ example: "sku"
511
810
  }, preferHeader],
512
811
  requestBody: {
513
812
  required: true,
@@ -564,7 +863,7 @@ function generateOpenApiSpec(collections, options = {}) {
564
863
  },
565
864
  upsert: {
566
865
  type: "boolean",
567
- description: "Write each row as INSERT ... ON CONFLICT DO UPDATE."
866
+ description: "Write each row as INSERT ... ON CONFLICT DO UPDATE. Refused (400 `INVALID_CONFLICT_TARGET`) on the auth collection, whose rows are the users."
568
867
  },
569
868
  onConflict: {
570
869
  type: "array",
@@ -649,9 +948,9 @@ function generateOpenApiSpec(collections, options = {}) {
649
948
  paths[`/data/${slug}/bulk/delete`] = { post: {
650
949
  tags: [collection.name],
651
950
  summary: `Delete many ${collection.name} in one transaction`,
652
- description: "A POST, not `DELETE /bulk` with a body. Bodies on DELETE are permitted but widely dropped by proxies and CDNs, and several generators ignore `requestBody` on a DELETE operation — a generated client would send the request with no ids at all. Takes ids rather than a filter: a mistyped condition that empties a table cannot be reviewed at the call site the way an explicit list can. `beforeDelete`/`afterDelete` fire per row.",
951
+ description: "A POST, not `DELETE /bulk` with a body. Bodies on DELETE are permitted but widely dropped by proxies and CDNs, and several generators ignore `requestBody` on a DELETE operation — a generated client would send the request with no ids at all. Takes ids rather than a filter: a mistyped condition that empties a table cannot be reviewed at the call site the way an explicit list can. `beforeDelete`/`afterDelete` fire per row." + (softDeleteNote ? `\n\n${softDeleteNote}` : ""),
653
952
  operationId: `deleteMany${schemaName}`,
654
- parameters: [idempotencyHeader],
953
+ parameters: [idempotencyHeader, ...hardDeleteParameters(collection)],
655
954
  requestBody: {
656
955
  required: true,
657
956
  content: { "application/json": { schema: {
@@ -665,7 +964,7 @@ function generateOpenApiSpec(collections, options = {}) {
665
964
  },
666
965
  responses: {
667
966
  200: {
668
- description: "How many rows were deleted",
967
+ description: softDeleteNote ? "How many rows were moved to the trash, or deleted for good with `?hard=true`" : "How many rows were deleted",
669
968
  content: { "application/json": { schema: {
670
969
  type: "object",
671
970
  properties: { meta: {
@@ -708,7 +1007,8 @@ function generateOpenApiSpec(collections, options = {}) {
708
1007
  schema: { type: "string" },
709
1008
  description: "Comma-separated columns to return, as a SELECT projection. The primary key always survives and `excludeFromApi` columns stay hidden.",
710
1009
  example: "id,title"
711
- }
1010
+ },
1011
+ ...deletedParameters(collection)
712
1012
  ],
713
1013
  responses: {
714
1014
  200: {
@@ -735,6 +1035,7 @@ function generateOpenApiSpec(collections, options = {}) {
735
1035
  delete: {
736
1036
  tags: [collection.name],
737
1037
  summary: `Delete ${collection.singularName || collection.name}`,
1038
+ ...softDeleteNote ? { description: softDeleteNote } : {},
738
1039
  operationId: `delete${schemaName}`,
739
1040
  parameters: [
740
1041
  {
@@ -751,10 +1052,11 @@ function generateOpenApiSpec(collections, options = {}) {
751
1052
  required: false,
752
1053
  schema: { type: "string" },
753
1054
  description: "Names this delete so a retry replays its answer. A delete replayed after the first attempt committed would otherwise answer 404 — which an offline queue reads as a permanent failure for a delete that in fact succeeded."
754
- }
1055
+ },
1056
+ ...hardDeleteParameters(collection)
755
1057
  ],
756
1058
  responses: {
757
- 204: { description: "Deleted successfully" },
1059
+ 204: { description: softDeleteNote ? "Moved to the trash: the row still exists and can be restored. With `?hard=true`, deleted for good" : "Deleted successfully" },
758
1060
  404: {
759
1061
  description: "Entity not found",
760
1062
  content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
@@ -781,34 +1083,44 @@ function generateOpenApiSpec(collections, options = {}) {
781
1083
  const relationName = relation.relationName;
782
1084
  const targetSchema = schemaNameFor(relation.target());
783
1085
  const subPath = `/data/${slug}/{parentId}/${relationName}`;
1086
+ const nestedParameters = [{
1087
+ name: "parentId",
1088
+ in: "path",
1089
+ required: true,
1090
+ schema: { type: "string" },
1091
+ description: `${collection.singularName || collection.name} ID`
1092
+ }, ...listQueryParameters()];
1093
+ const nestedResponses = {
1094
+ 200: {
1095
+ description: `List of related ${relationName}`,
1096
+ content: { "application/json": { schema: {
1097
+ type: "object",
1098
+ properties: {
1099
+ data: {
1100
+ type: "array",
1101
+ items: schemas[targetSchema] ? { $ref: `#/components/schemas/${targetSchema}` } : { type: "object" }
1102
+ },
1103
+ meta: { $ref: "#/components/schemas/PaginationMeta" }
1104
+ }
1105
+ } } }
1106
+ },
1107
+ ...errorResponses(requireAuth)
1108
+ };
784
1109
  paths[subPath] = { get: {
785
1110
  tags: [collection.name],
786
1111
  summary: `List ${relationName} for ${withIndefiniteArticle(collection.singularName || collection.name)}`,
787
1112
  operationId: `list${schemaName}${toPascalCase(relationName)}`,
788
- parameters: [{
789
- name: "parentId",
790
- in: "path",
791
- required: true,
792
- schema: { type: "string" },
793
- description: `${collection.singularName || collection.name} ID`
794
- }, ...listQueryParameters()],
795
- responses: {
796
- 200: {
797
- description: `List of related ${relationName}`,
798
- content: { "application/json": { schema: {
799
- type: "object",
800
- properties: {
801
- data: {
802
- type: "array",
803
- items: schemas[targetSchema] ? { $ref: `#/components/schemas/${targetSchema}` } : { type: "object" }
804
- },
805
- meta: { $ref: "#/components/schemas/PaginationMeta" }
806
- }
807
- } } }
808
- },
809
- ...errorResponses(requireAuth)
810
- }
1113
+ parameters: nestedParameters,
1114
+ responses: nestedResponses
811
1115
  } };
1116
+ paths[`${subPath}/query`] = { post: queryOperation({
1117
+ tags: [collection.name],
1118
+ summary: `List ${relationName} for ${withIndefiniteArticle(collection.singularName || collection.name)}, parameters in the body`,
1119
+ operationId: `query${schemaName}${toPascalCase(relationName)}`,
1120
+ parameters: nestedParameters,
1121
+ responses: nestedResponses,
1122
+ reserved: reservedParameterNames
1123
+ }) };
812
1124
  }
813
1125
  }
814
1126
  return spec;
@@ -1104,6 +1416,10 @@ function updateOperation(collection, schemaName, requireAuth, shared) {
1104
1416
  responses: {
1105
1417
  200: {
1106
1418
  description: "Updated entity",
1419
+ headers: { ETag: {
1420
+ schema: { type: "string" },
1421
+ description: "The version this write produced — the tag a `GET` of the row now answers. Send it as `If-Match` on the next write to chain conditional edits without reading the row back. Absent on an answer replayed from an `Idempotency-Key`."
1422
+ } },
1107
1423
  content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
1108
1424
  },
1109
1425
  404: {
@@ -1157,7 +1473,7 @@ function buildCollectionInputSchema(collection) {
1157
1473
  if ("isId" in property && property.isId && property.isId !== "manual" && property.isId !== true) continue;
1158
1474
  properties[key] = convertPropertyToSchema(property);
1159
1475
  emitted.add(key);
1160
- if (property.validation?.required) required.push(key);
1476
+ if (property.validation?.required && !property.databaseDefault) required.push(key);
1161
1477
  }
1162
1478
  if (!emitted.has(idKey)) {
1163
1479
  properties[idKey] = {
@@ -1390,6 +1706,65 @@ function buildFilterParameters(collection, reservedNames = /* @__PURE__ */ new S
1390
1706
  return params;
1391
1707
  }
1392
1708
  /**
1709
+ * A `POST …/query` operation: the listing whose `parameters` are given, with
1710
+ * its query parameters moved into a JSON body.
1711
+ *
1712
+ * Built from the `GET`'s own parameter list, so the body documents exactly
1713
+ * what the route accepts — it turns the body back into those parameters and
1714
+ * runs the `GET`'s parser (`query-body.ts`). A filter parameter takes a list
1715
+ * where the URL would repeat it, and the four parameters whose URL value is
1716
+ * JSON take the JSON value itself.
1717
+ */
1718
+ function queryOperation(operation) {
1719
+ const jsonValued = {
1720
+ where: { oneOf: [{
1721
+ type: "object",
1722
+ additionalProperties: true
1723
+ }, { type: "string" }] },
1724
+ include: { oneOf: [{
1725
+ type: "object",
1726
+ additionalProperties: true
1727
+ }, { type: "string" }] },
1728
+ orderBy: { oneOf: [{
1729
+ type: "array",
1730
+ items: {}
1731
+ }, { type: "string" }] },
1732
+ vector: { oneOf: [{
1733
+ type: "array",
1734
+ items: { type: "number" }
1735
+ }, { type: "string" }] }
1736
+ };
1737
+ const properties = {};
1738
+ for (const parameter of operation.parameters) {
1739
+ if (parameter.in !== "query") continue;
1740
+ const name = String(parameter.name);
1741
+ const schema = parameter.schema;
1742
+ const isFilter = !operation.reserved.has(name);
1743
+ properties[name] = {
1744
+ ...jsonValued[name] ?? (isFilter ? { oneOf: [schema, {
1745
+ type: "array",
1746
+ items: schema
1747
+ }] } : schema),
1748
+ ...parameter.description ? { description: parameter.description } : {}
1749
+ };
1750
+ }
1751
+ return {
1752
+ tags: operation.tags,
1753
+ summary: operation.summary,
1754
+ description: operation.description ?? "The listing `GET` answers, with its query parameters in a JSON body instead of the URL: the same filters, page, count, cursor and includes, the same row-level security, and the same errors. It reads and never writes, so a credential needs `data:read` for it. Use it when the URL would not fit: a vector search's embedding is a JSON array in `vector`, and 768 dimensions of it is 17 KB of URL, past what Node and most proxies accept. A parameter also on the URL is a 400 INVALID_QUERY_BODY.",
1755
+ operationId: operation.operationId,
1756
+ parameters: operation.parameters.filter((parameter) => parameter.in !== "query"),
1757
+ requestBody: {
1758
+ required: true,
1759
+ content: { "application/json": { schema: {
1760
+ type: "object",
1761
+ properties
1762
+ } } }
1763
+ },
1764
+ responses: operation.responses
1765
+ };
1766
+ }
1767
+ /**
1393
1768
  * Standard error responses included on every endpoint.
1394
1769
  */
1395
1770
  function errorResponses(requireAuth) {
@@ -1446,6 +1821,6 @@ function toPascalCase(str) {
1446
1821
  return str.replace(/[^a-zA-Z0-9]+/g, " ").split(" ").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1447
1822
  }
1448
1823
  //#endregion
1449
- export { generateOpenApiSpec };
1824
+ export { softDeleteFieldOf as a, JUNCTION_PIVOT_KEY as c, restoresSoftDeletedRow as i, hasForeignKeyOnTarget as l, buildCollectionSchema as n, buildTenantSecurityRule as o, openapi_generator_exports as r, INCLUDE_COUNT_KEY as s, buildCollectionInputSchema as t, isManyToMany as u };
1450
1825
 
1451
- //# sourceMappingURL=openapi-generator-O_O24MAT.js.map
1826
+ //# sourceMappingURL=openapi-generator-CeGW2dLC.js.map