@rebasepro/server 0.24.0 → 0.24.1-canary.gcb918fc

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 (224) hide show
  1. package/README.md +27 -2
  2. package/dist/GCSStorageController-fv6JAZ87.js +402 -0
  3. package/dist/GCSStorageController-fv6JAZ87.js.map +1 -0
  4. package/dist/S3StorageController-BNg-pXiY.js +480 -0
  5. package/dist/S3StorageController-BNg-pXiY.js.map +1 -0
  6. package/dist/api/mount-path.d.ts +30 -0
  7. package/dist/api/mount.d.ts +6 -13
  8. package/dist/api/rest/api-generator.d.ts +53 -1
  9. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  10. package/dist/api/rest/field-access-query.d.ts +28 -8
  11. package/dist/api/rest/field-ops.d.ts +19 -7
  12. package/dist/api/rest/query-body.d.ts +10 -0
  13. package/dist/api/rest/write-validation.d.ts +7 -5
  14. package/dist/api/schema-editor-routes.d.ts +10 -1
  15. package/dist/api/sql-export-routes.d.ts +8 -0
  16. package/dist/api/types.d.ts +35 -0
  17. package/dist/{ast-schema-editor-CWqS_sLJ.js → ast-schema-editor-mCeSC03x.js} +3 -3
  18. package/dist/{ast-schema-editor-CWqS_sLJ.js.map → ast-schema-editor-mCeSC03x.js.map} +1 -1
  19. package/dist/auth/access.d.ts +57 -8
  20. package/dist/auth/admin-user-ops.d.ts +24 -2
  21. package/dist/auth/admin-users-route.d.ts +1 -0
  22. package/dist/auth/api-keys/api-key-middleware.d.ts +10 -3
  23. package/dist/auth/api-keys/api-key-routes.d.ts +4 -1
  24. package/dist/auth/builtin-auth-adapter.d.ts +13 -0
  25. package/dist/auth/email-change-routes.d.ts +3 -0
  26. package/dist/auth/expired-token-sweep.d.ts +5 -1
  27. package/dist/auth/interfaces.d.ts +10 -0
  28. package/dist/auth/jwks-routes.d.ts +5 -1
  29. package/dist/auth/jwt-crypto.d.ts +7 -0
  30. package/dist/auth/jwt-keys.d.ts +4 -0
  31. package/dist/auth/jwt.d.ts +282 -166
  32. package/dist/auth/magic-link-routes.d.ts +3 -0
  33. package/dist/auth/mfa-crypto.d.ts +9 -3
  34. package/dist/auth/mfa-gate.d.ts +4 -2
  35. package/dist/auth/mfa-routes.d.ts +6 -0
  36. package/dist/auth/middleware.d.ts +9 -2
  37. package/dist/auth/otp-routes.d.ts +3 -0
  38. package/dist/auth/rate-limiter.d.ts +61 -34
  39. package/dist/auth/registration-policy.d.ts +17 -0
  40. package/dist/auth/require-auth.d.ts +1 -1
  41. package/dist/auth/routes.d.ts +16 -0
  42. package/dist/auth/session-routes.d.ts +6 -0
  43. package/dist/auth/user-picking.d.ts +37 -0
  44. package/dist/auth/verify-credential.d.ts +25 -4
  45. package/dist/{auth-DMLngxn_.js → auth-GnONzteu.js} +2869 -1871
  46. package/dist/auth-GnONzteu.js.map +1 -0
  47. package/dist/auth_adapter-CIItCTc8.js +21 -0
  48. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  49. package/dist/backend/create-backend.d.ts +281 -0
  50. package/dist/backend/default-backend.d.ts +35 -0
  51. package/dist/backend/options.d.ts +912 -0
  52. package/dist/backend/scope.d.ts +23 -0
  53. package/dist/backend/services.d.ts +54 -0
  54. package/dist/backend-DTAOsLQc.js.map +1 -1
  55. package/dist/backup/backup-common.d.ts +14 -3
  56. package/dist/backup/backup-routes.d.ts +23 -2
  57. package/dist/backup/backup-storage.d.ts +3 -1
  58. package/dist/backup/index.d.ts +3 -1
  59. package/dist/{backup-CN0s50D2.js → backup-BnvkSLHi.js} +176 -35
  60. package/dist/backup-BnvkSLHi.js.map +1 -0
  61. package/dist/boot/boot.d.ts +2 -2
  62. package/dist/boot/driver.d.ts +3 -0
  63. package/dist/boot/env.d.ts +9 -2
  64. package/dist/boot/http-server.d.ts +22 -0
  65. package/dist/boot/options.d.ts +16 -1
  66. package/dist/boot/provision.d.ts +4 -2
  67. package/dist/boot/runtime-cors.d.ts +17 -0
  68. package/dist/boot/sources.d.ts +2 -0
  69. package/dist/boot/static-routing.d.ts +9 -0
  70. package/dist/{contract-routes-fz8i4pxs.js → contract-routes-Ey6ftnKH.js} +4 -20
  71. package/dist/contract-routes-Ey6ftnKH.js.map +1 -0
  72. package/dist/cron/cron-loader.d.ts +77 -18
  73. package/dist/cron/cron-routes.d.ts +1 -1
  74. package/dist/cron/cron-scheduler.d.ts +69 -3
  75. package/dist/cron/cron-store.d.ts +33 -5
  76. package/dist/cron/define-cron.d.ts +6 -2
  77. package/dist/cron/index.d.ts +3 -3
  78. package/dist/cron-loader-B-u0snqq.js +202 -0
  79. package/dist/cron-loader-B-u0snqq.js.map +1 -0
  80. package/dist/{cron-routes-Bc-SB0Se.js → cron-routes-OD6BstUj.js} +27 -4
  81. package/dist/cron-routes-OD6BstUj.js.map +1 -0
  82. package/dist/{cron-scheduler-CYQgco86.js → cron-scheduler-idtLPoN-.js} +299 -33
  83. package/dist/cron-scheduler-idtLPoN-.js.map +1 -0
  84. package/dist/cron-store-CZ30ZBi-.js +577 -0
  85. package/dist/cron-store-CZ30ZBi-.js.map +1 -0
  86. package/dist/email/smtp-email-service.d.ts +23 -0
  87. package/dist/email/types.d.ts +18 -0
  88. package/dist/env.d.ts +0 -37
  89. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  90. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  91. package/dist/{function-loader-D7o5Epjj.js → function-loader-iKhOZatS.js} +68 -38
  92. package/dist/function-loader-iKhOZatS.js.map +1 -0
  93. package/dist/{function-routes-CaNG4waN.js → function-routes-j9TwpvQe.js} +5 -4
  94. package/dist/function-routes-j9TwpvQe.js.map +1 -0
  95. package/dist/functions/context.d.ts +9 -0
  96. package/dist/functions/define-function.d.ts +10 -6
  97. package/dist/functions/function-loader.d.ts +75 -18
  98. package/dist/functions/function-routes.d.ts +2 -1
  99. package/dist/functions/index.d.ts +5 -4
  100. package/dist/functions/index.js +42 -13
  101. package/dist/functions/index.js.map +1 -1
  102. package/dist/functions/internal.d.ts +2 -2
  103. package/dist/functions/proxy.d.ts +6 -2
  104. package/dist/functions/request-timeout.d.ts +7 -5
  105. package/dist/history/history-routes.d.ts +9 -0
  106. package/dist/history/retention-sweep.d.ts +81 -0
  107. package/dist/{history-recorder-Nr8zLvoU.js → history-recorder-BfWRwmPy.js} +3 -3
  108. package/dist/{history-recorder-Nr8zLvoU.js.map → history-recorder-BfWRwmPy.js.map} +1 -1
  109. package/dist/{history-store-rcAm_xFR.js → history-store-DPofGVDp.js} +2 -2
  110. package/dist/{history-store-rcAm_xFR.js.map → history-store-DPofGVDp.js.map} +1 -1
  111. package/dist/index.d.ts +23 -10
  112. package/dist/index.es.js +24423 -20038
  113. package/dist/index.es.js.map +1 -1
  114. package/dist/init/docs.d.ts +27 -10
  115. package/dist/init/process-safety.d.ts +7 -2
  116. package/dist/init/shutdown.d.ts +18 -3
  117. package/dist/init.d.ts +49 -865
  118. package/dist/internal-tables-L2t39RdT.js +136 -0
  119. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  120. package/dist/jobs/job-queue.d.ts +8 -0
  121. package/dist/jobs/job-store.d.ts +24 -0
  122. package/dist/jobs/types.d.ts +25 -4
  123. package/dist/{jobs-DqYNfquG.js → jobs-BCbflZj2.js} +234 -24
  124. package/dist/jobs-BCbflZj2.js.map +1 -0
  125. package/dist/{jwt-R6bSPMjk.js → jwt-BlvuKoR6.js} +529 -454
  126. package/dist/jwt-BlvuKoR6.js.map +1 -0
  127. package/dist/{keys-GAVZqbqx.js → keys-COzQ2aE8.js} +2 -2
  128. package/dist/{keys-GAVZqbqx.js.map → keys-COzQ2aE8.js.map} +1 -1
  129. package/dist/load-failure-L5hvfDoG.js +80 -0
  130. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  131. package/dist/mcp/mcp-routes.d.ts +6 -0
  132. package/dist/mcp/mcp-tools.d.ts +8 -0
  133. package/dist/mcp/oauth-routes.d.ts +7 -0
  134. package/dist/metrics/index.d.ts +18 -0
  135. package/dist/metrics/pool-wait.d.ts +50 -0
  136. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  137. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  138. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  139. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  140. package/dist/{openapi-generator-DAq_XVDu.js → openapi-generator-D9zUQmEp.js} +366 -82
  141. package/dist/openapi-generator-D9zUQmEp.js.map +1 -0
  142. package/dist/policy-PdzX-Opd.js +121 -0
  143. package/dist/policy-PdzX-Opd.js.map +1 -0
  144. package/dist/project_manifest-R6y_KHms.js +59 -0
  145. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  146. package/dist/{proxy-qRlqeUmO.js → proxy-CkTlMGmh.js} +4 -3
  147. package/dist/proxy-CkTlMGmh.js.map +1 -0
  148. package/dist/{query-parser-BgiKJKvc.js → query-parser-4WkMoA_K.js} +510 -15
  149. package/dist/query-parser-4WkMoA_K.js.map +1 -0
  150. package/dist/{request-timeout-DgH7j8qO.js → request-timeout-Dnl2_OBc.js} +15 -8
  151. package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
  152. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  153. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  154. package/dist/schema-edit/apply-schema-change.d.ts +10 -0
  155. package/dist/schema-edit/local-source.d.ts +29 -0
  156. package/dist/schema-edit/remote-source.d.ts +13 -3
  157. package/dist/schema-edit/source-editor.d.ts +28 -0
  158. package/dist/{schema-editor-routes-oIyuWl3L.js → schema-editor-routes-C11Tumyd.js} +54 -10
  159. package/dist/schema-editor-routes-C11Tumyd.js.map +1 -0
  160. package/dist/services/outbound-post.d.ts +50 -0
  161. package/dist/services/outbound-url-guard.d.ts +34 -5
  162. package/dist/services/routed-realtime-service.d.ts +5 -0
  163. package/dist/services/webhook-service.d.ts +66 -14
  164. package/dist/singleton.d.ts +9 -5
  165. package/dist/soft-delete-params-DfV3vVN5.js +668 -0
  166. package/dist/soft-delete-params-DfV3vVN5.js.map +1 -0
  167. package/dist/sql-export-routes-ClRwzIsC.js +240 -0
  168. package/dist/sql-export-routes-ClRwzIsC.js.map +1 -0
  169. package/dist/{src-CatHFUym.js → src-D5Y3N-S4.js} +2 -1
  170. package/dist/{src-CatHFUym.js.map → src-D5Y3N-S4.js.map} +1 -1
  171. package/dist/storage/GCSStorageController.d.ts +46 -2
  172. package/dist/storage/LocalStorageController.d.ts +67 -2
  173. package/dist/storage/S3StorageController.d.ts +37 -3
  174. package/dist/storage/image-transform.d.ts +5 -0
  175. package/dist/storage/maintenance-routes.d.ts +23 -0
  176. package/dist/storage/maintenance.d.ts +126 -0
  177. package/dist/storage/rendition-cache.d.ts +9 -11
  178. package/dist/storage/request-principals.d.ts +30 -0
  179. package/dist/storage/routes.d.ts +20 -0
  180. package/dist/storage/source-variables.d.ts +10 -0
  181. package/dist/storage/streams.d.ts +42 -0
  182. package/dist/storage/tus-handler.d.ts +46 -2
  183. package/dist/storage/types.d.ts +82 -7
  184. package/dist/storage/upload-validation.d.ts +13 -0
  185. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  186. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  187. package/dist/strings.es-BeZWAodn.js +256 -0
  188. package/dist/strings.es-BeZWAodn.js.map +1 -0
  189. package/dist/upload-validation-Dd-fnsbA.js +172 -0
  190. package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
  191. package/dist/utils/byte-size.d.ts +10 -0
  192. package/dist/utils/compression.d.ts +6 -0
  193. package/dist/utils/load-failure.d.ts +14 -0
  194. package/package.json +5 -5
  195. package/dist/GCSStorageController-BSiP1c-f.js +0 -256
  196. package/dist/GCSStorageController-BSiP1c-f.js.map +0 -1
  197. package/dist/S3StorageController-CAwFRgjV.js +0 -276
  198. package/dist/S3StorageController-CAwFRgjV.js.map +0 -1
  199. package/dist/auth-DMLngxn_.js.map +0 -1
  200. package/dist/backup-CN0s50D2.js.map +0 -1
  201. package/dist/contract-routes-fz8i4pxs.js.map +0 -1
  202. package/dist/cron-loader-CwaANlOG.js +0 -118
  203. package/dist/cron-loader-CwaANlOG.js.map +0 -1
  204. package/dist/cron-routes-Bc-SB0Se.js.map +0 -1
  205. package/dist/cron-scheduler-CYQgco86.js.map +0 -1
  206. package/dist/cron-store-D2Q9-Aco.js +0 -300
  207. package/dist/cron-store-D2Q9-Aco.js.map +0 -1
  208. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  209. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  210. package/dist/function-loader-D7o5Epjj.js.map +0 -1
  211. package/dist/function-routes-CaNG4waN.js.map +0 -1
  212. package/dist/jobs-DqYNfquG.js.map +0 -1
  213. package/dist/jwt-R6bSPMjk.js.map +0 -1
  214. package/dist/openapi-generator-DAq_XVDu.js.map +0 -1
  215. package/dist/proxy-qRlqeUmO.js.map +0 -1
  216. package/dist/query-parser-BgiKJKvc.js.map +0 -1
  217. package/dist/request-timeout-DgH7j8qO.js.map +0 -1
  218. package/dist/schema-editor-routes-oIyuWl3L.js.map +0 -1
  219. package/dist/soft-delete-params-BWPilMPF.js +0 -59
  220. package/dist/soft-delete-params-BWPilMPF.js.map +0 -1
  221. package/dist/src-I3aG1PcY.js +0 -6870
  222. package/dist/src-I3aG1PcY.js.map +0 -1
  223. package/dist/types-BfKcm9do.js +0 -39
  224. package/dist/types-BfKcm9do.js.map +0 -1
@@ -3,9 +3,172 @@ import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
5
  import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
- import { B as findRelation, H as isRelationRequired, L as getTenantConfig, T as effectiveAccess, W as resolveCollectionRelations, ft as isToMany, z as fieldKeyForColumn } from "./src-I3aG1PcY.js";
7
- import "./src-CatHFUym.js";
8
- import { n as HARD_DELETE_QUERY_PARAM, t as DELETED_QUERY_PARAM } from "./soft-delete-params-BWPilMPF.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-DfV3vVN5.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
+ }
9
172
  /**
10
173
  * The property a `softDelete` collection records a deletion in — its wire
11
174
  * name, what a delete stamps and what a restore (an update to `null`) clears —
@@ -82,7 +245,7 @@ function generateOpenApiSpec(collections, options = {}) {
82
245
  type: "integer",
83
246
  minimum: 1
84
247
  },
85
- 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)."
86
249
  },
87
250
  {
88
251
  name: "after",
@@ -143,9 +306,20 @@ function generateOpenApiSpec(collections, options = {}) {
143
306
  name: "distinct",
144
307
  in: "query",
145
308
  schema: { type: "boolean" },
146
- 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.",
147
310
  example: "true"
148
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
+ },
149
323
  {
150
324
  name: "searchString",
151
325
  in: "query",
@@ -184,7 +358,7 @@ function generateOpenApiSpec(collections, options = {}) {
184
358
  name: "vector_threshold",
185
359
  in: "query",
186
360
  schema: { type: "number" },
187
- 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."
188
362
  }
189
363
  ];
190
364
  const spec = {
@@ -218,7 +392,7 @@ function generateOpenApiSpec(collections, options = {}) {
218
392
  properties: {
219
393
  total: {
220
394
  type: "integer",
221
- 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`."
222
396
  },
223
397
  limit: {
224
398
  type: "integer",
@@ -466,31 +640,58 @@ function generateOpenApiSpec(collections, options = {}) {
466
640
  schemas[`${schemaName}Update`] = buildCollectionUpdateSchema(collection);
467
641
  const dataPath = `/data/${slug}`;
468
642
  const softDeleteNote = softDeleteBehaviour(collection);
469
- paths[`${dataPath}/count`] = { get: {
470
- tags: [collection.name],
471
- summary: `Count ${collection.name}`,
472
- 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.",
473
- operationId: `count${schemaName}`,
474
- parameters: [
475
- ...listQueryParameters().filter((p) => p.name === "searchString"),
476
- ...deletedParameters(collection),
477
- ...buildFilterParameters(collection, reservedParameterNames)
478
- ],
479
- responses: {
480
- 200: {
481
- description: "The number of matching rows",
482
- content: { "application/json": { schema: {
483
- type: "object",
484
- required: ["count"],
485
- properties: { count: {
486
- type: "integer",
487
- description: "Rows matching the filters"
488
- } }
489
- } } }
490
- },
491
- ...errorResponses(requireAuth)
492
- }
493
- } };
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
+ };
494
695
  paths[`${dataPath}/aggregate`] = { get: {
495
696
  tags: [collection.name],
496
697
  summary: `Aggregate ${collection.name}`,
@@ -558,32 +759,42 @@ function generateOpenApiSpec(collections, options = {}) {
558
759
  ...errorResponses(requireAuth)
559
760
  }
560
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
+ }) };
561
791
  paths[dataPath] = {
562
792
  get: {
563
793
  tags: [collection.name],
564
794
  summary: `List ${collection.name}`,
565
795
  operationId: `list${schemaName}`,
566
- parameters: [
567
- ...listQueryParameters(),
568
- ...deletedParameters(collection),
569
- ...buildFilterParameters(collection, reservedParameterNames)
570
- ],
571
- responses: {
572
- 200: {
573
- description: "Paginated list of entities",
574
- content: { "application/json": { schema: {
575
- type: "object",
576
- properties: {
577
- data: {
578
- type: "array",
579
- items: { $ref: `#/components/schemas/${schemaName}` }
580
- },
581
- meta: { $ref: "#/components/schemas/PaginationMeta" }
582
- }
583
- } } }
584
- },
585
- ...errorResponses(requireAuth)
586
- }
796
+ parameters: listParameters,
797
+ responses: listResponses
587
798
  },
588
799
  post: {
589
800
  tags: [collection.name],
@@ -594,8 +805,8 @@ function generateOpenApiSpec(collections, options = {}) {
594
805
  in: "query",
595
806
  required: false,
596
807
  schema: { type: "string" },
597
- 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.",
598
- 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"
599
810
  }, preferHeader],
600
811
  requestBody: {
601
812
  required: true,
@@ -652,7 +863,7 @@ function generateOpenApiSpec(collections, options = {}) {
652
863
  },
653
864
  upsert: {
654
865
  type: "boolean",
655
- 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."
656
867
  },
657
868
  onConflict: {
658
869
  type: "array",
@@ -872,34 +1083,44 @@ function generateOpenApiSpec(collections, options = {}) {
872
1083
  const relationName = relation.relationName;
873
1084
  const targetSchema = schemaNameFor(relation.target());
874
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
+ };
875
1109
  paths[subPath] = { get: {
876
1110
  tags: [collection.name],
877
1111
  summary: `List ${relationName} for ${withIndefiniteArticle(collection.singularName || collection.name)}`,
878
1112
  operationId: `list${schemaName}${toPascalCase(relationName)}`,
879
- parameters: [{
880
- name: "parentId",
881
- in: "path",
882
- required: true,
883
- schema: { type: "string" },
884
- description: `${collection.singularName || collection.name} ID`
885
- }, ...listQueryParameters()],
886
- responses: {
887
- 200: {
888
- description: `List of related ${relationName}`,
889
- content: { "application/json": { schema: {
890
- type: "object",
891
- properties: {
892
- data: {
893
- type: "array",
894
- items: schemas[targetSchema] ? { $ref: `#/components/schemas/${targetSchema}` } : { type: "object" }
895
- },
896
- meta: { $ref: "#/components/schemas/PaginationMeta" }
897
- }
898
- } } }
899
- },
900
- ...errorResponses(requireAuth)
901
- }
1113
+ parameters: nestedParameters,
1114
+ responses: nestedResponses
902
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
+ }) };
903
1124
  }
904
1125
  }
905
1126
  return spec;
@@ -1195,6 +1416,10 @@ function updateOperation(collection, schemaName, requireAuth, shared) {
1195
1416
  responses: {
1196
1417
  200: {
1197
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
+ } },
1198
1423
  content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
1199
1424
  },
1200
1425
  404: {
@@ -1248,7 +1473,7 @@ function buildCollectionInputSchema(collection) {
1248
1473
  if ("isId" in property && property.isId && property.isId !== "manual" && property.isId !== true) continue;
1249
1474
  properties[key] = convertPropertyToSchema(property);
1250
1475
  emitted.add(key);
1251
- if (property.validation?.required) required.push(key);
1476
+ if (property.validation?.required && !property.databaseDefault) required.push(key);
1252
1477
  }
1253
1478
  if (!emitted.has(idKey)) {
1254
1479
  properties[idKey] = {
@@ -1481,6 +1706,65 @@ function buildFilterParameters(collection, reservedNames = /* @__PURE__ */ new S
1481
1706
  return params;
1482
1707
  }
1483
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
+ /**
1484
1768
  * Standard error responses included on every endpoint.
1485
1769
  */
1486
1770
  function errorResponses(requireAuth) {
@@ -1537,6 +1821,6 @@ function toPascalCase(str) {
1537
1821
  return str.replace(/[^a-zA-Z0-9]+/g, " ").split(" ").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1538
1822
  }
1539
1823
  //#endregion
1540
- export { softDeleteFieldOf as a, restoresSoftDeletedRow as i, buildCollectionSchema as n, openapi_generator_exports as r, buildCollectionInputSchema as t };
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 };
1541
1825
 
1542
- //# sourceMappingURL=openapi-generator-DAq_XVDu.js.map
1826
+ //# sourceMappingURL=openapi-generator-D9zUQmEp.js.map