@rebasepro/server-postgres 0.23.0 → 0.24.1-canary.g56af4a4

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 (180) hide show
  1. package/dist/{BranchService-DGPL6G_C.js → BranchService-Bsn2wfaj.js} +36 -18
  2. package/dist/BranchService-Bsn2wfaj.js.map +1 -0
  3. package/dist/PostgresBackendDriver.d.ts +166 -13
  4. package/dist/auth/ensure-tables.d.ts +12 -0
  5. package/dist/auth/services.d.ts +78 -14
  6. package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
  7. package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
  8. package/dist/backup/backup-cli.d.ts +14 -0
  9. package/dist/backup/backup-service.d.ts +2 -2
  10. package/dist/backup/pg-binary.d.ts +5 -0
  11. package/dist/{backup-cli-24v4OlSp.js → backup-cli-aUfRv16h.js} +37 -29
  12. package/dist/backup-cli-aUfRv16h.js.map +1 -0
  13. package/dist/{backup-service-HQ9GC4tN.js → backup-service-BTBB7RXn.js} +60 -28
  14. package/dist/backup-service-BTBB7RXn.js.map +1 -0
  15. package/dist/boot-ddl-DVpsFN6r.js +105 -0
  16. package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
  17. package/dist/cli-errors-CaI4ntyD.js +1919 -0
  18. package/dist/cli-errors-CaI4ntyD.js.map +1 -0
  19. package/dist/cli-errors.d.ts +93 -5
  20. package/dist/{cli-flags-BglvpjHv.js → cli-flags-B-Dawocl.js} +5 -2
  21. package/dist/cli-flags-B-Dawocl.js.map +1 -0
  22. package/dist/cli-helpers.d.ts +52 -12
  23. package/dist/cli-output.d.ts +16 -28
  24. package/dist/cli-push.d.ts +87 -0
  25. package/dist/cli.js +625 -190
  26. package/dist/cli.js.map +1 -1
  27. package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-drZIsSNf.js} +42 -26
  28. package/dist/column-plan-helpers-drZIsSNf.js.map +1 -0
  29. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-BPoNILnD.js} +19 -110
  30. package/dist/connection-BPoNILnD.js.map +1 -0
  31. package/dist/connection.d.ts +9 -1
  32. package/dist/data-transformer.d.ts +0 -8
  33. package/dist/databasePoolManager-BkEy2w18.js +119 -0
  34. package/dist/databasePoolManager-BkEy2w18.js.map +1 -0
  35. package/dist/deferred-index-build-DP1_SNFo.js +1051 -0
  36. package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
  37. package/dist/{doctor-Cb2thZ8s.js → doctor-C4WiftHm.js} +279 -87
  38. package/dist/doctor-C4WiftHm.js.map +1 -0
  39. package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
  40. package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
  41. package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
  42. package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
  43. package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-CHsgyLqg.js} +178 -75
  44. package/dist/ensure-tables-CHsgyLqg.js.map +1 -0
  45. package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
  46. package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
  47. package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
  48. package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
  49. package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-B74WN37S.js} +28 -10
  50. package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
  51. package/dist/generate-files-CdEckX6c.js +199 -0
  52. package/dist/generate-files-CdEckX6c.js.map +1 -0
  53. package/dist/generate-postgres-ddl-logic-B64O2oZF.js +152 -0
  54. package/dist/generate-postgres-ddl-logic-B64O2oZF.js.map +1 -0
  55. package/dist/generated-sql.d.ts +34 -0
  56. package/dist/history/HistoryService.d.ts +38 -6
  57. package/dist/history/ensure-history-table.d.ts +42 -4
  58. package/dist/index.es.js +5557 -7612
  59. package/dist/index.es.js.map +1 -1
  60. package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-C-Di8dA9.js} +562 -350
  61. package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
  62. package/dist/introspect-db-queries-D6kAqReI.js +339 -0
  63. package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
  64. package/dist/pg-error-utils-vJezlXdI.js +388 -0
  65. package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
  66. package/dist/{plan-schema-C0fxM8dY.js → plan-schema-B1YF_tYq.js} +278 -533
  67. package/dist/plan-schema-B1YF_tYq.js.map +1 -0
  68. package/dist/{policy-drift-DljYdrpW.js → policy-drift-Dm8i-s1J.js} +24 -7
  69. package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
  70. package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
  71. package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
  72. package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
  73. package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
  74. package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CtC3y9Ol.js} +17 -151
  75. package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
  76. package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1046
  77. package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
  78. package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-DvzqlaL1.js} +137 -17
  79. package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
  80. package/dist/schema/auth-schema.d.ts +172 -2
  81. package/dist/schema/boot-ddl.d.ts +47 -0
  82. package/dist/schema/classify-change.d.ts +29 -1
  83. package/dist/schema/collection-index.d.ts +45 -0
  84. package/dist/schema/column-plan-helpers.d.ts +38 -21
  85. package/dist/schema/deferred-index-build.d.ts +131 -0
  86. package/dist/schema/destructive-sql.d.ts +27 -1
  87. package/dist/schema/doctor-cli.js +110 -6
  88. package/dist/schema/doctor-cli.js.map +1 -1
  89. package/dist/schema/doctor-search-scale.d.ts +45 -0
  90. package/dist/schema/doctor.d.ts +53 -1
  91. package/dist/schema/ensure-collection-policies.d.ts +95 -35
  92. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  93. package/dist/schema/enum-labels-kept.d.ts +26 -0
  94. package/dist/schema/generate-drizzle-schema.js +1 -1
  95. package/dist/schema/generate-files.d.ts +44 -0
  96. package/dist/schema/generate-next-step.d.ts +0 -13
  97. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
  98. package/dist/schema/generate-postgres-ddl.js +10 -81
  99. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  100. package/dist/schema/generate-schema-commit.d.ts +12 -0
  101. package/dist/schema/introspect-db-logic.d.ts +43 -0
  102. package/dist/schema/introspect-db-queries.d.ts +12 -2
  103. package/dist/schema/introspect-db-search.d.ts +22 -0
  104. package/dist/schema/introspect-db-storage.d.ts +83 -0
  105. package/dist/schema/introspect-db-structure.d.ts +10 -0
  106. package/dist/schema/introspect-db.js +17 -6
  107. package/dist/schema/introspect-db.js.map +1 -1
  108. package/dist/schema/migration-post-process.d.ts +34 -0
  109. package/dist/schema/plan/diff-plan.d.ts +127 -1
  110. package/dist/schema/plan/plan-schema.d.ts +16 -8
  111. package/dist/schema/plan/render-ddl.d.ts +6 -0
  112. package/dist/schema/plan/types.d.ts +45 -11
  113. package/dist/schema/push-index-prebuild.d.ts +25 -0
  114. package/dist/schema/push-legacy-renames.d.ts +20 -0
  115. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  116. package/dist/search-column-BM-GV6vH.js +442 -0
  117. package/dist/search-column-BM-GV6vH.js.map +1 -0
  118. package/dist/security/policy-drift.d.ts +1 -1
  119. package/dist/security/rls-enforcement.d.ts +39 -4
  120. package/dist/services/BranchService.d.ts +22 -1
  121. package/dist/services/FetchService.d.ts +63 -12
  122. package/dist/services/PersistService.d.ts +15 -0
  123. package/dist/services/RelationService.d.ts +106 -3
  124. package/dist/services/cdc/CdcListener.d.ts +26 -10
  125. package/dist/services/cdc/identity-columns.d.ts +19 -0
  126. package/dist/services/cdc/trigger-cdc.d.ts +180 -18
  127. package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
  128. package/dist/services/channel-history.d.ts +32 -5
  129. package/dist/services/collection-helpers.d.ts +26 -1
  130. package/dist/services/field-viewer.d.ts +15 -1
  131. package/dist/services/pg-notify-listener.d.ts +95 -6
  132. package/dist/services/realtimeService.d.ts +467 -53
  133. package/dist/services/row-pipeline.d.ts +16 -9
  134. package/dist/services/socket-liveness.d.ts +85 -0
  135. package/dist/services/soft-delete.d.ts +12 -0
  136. package/dist/services/sql-script.d.ts +275 -0
  137. package/dist/services/vector-iterative-scan.d.ts +40 -0
  138. package/dist/services/write-depth.d.ts +22 -0
  139. package/dist/services/write-transaction-scope.d.ts +15 -3
  140. package/dist/socket-liveness-D3UD5_BW.js +7570 -0
  141. package/dist/socket-liveness-D3UD5_BW.js.map +1 -0
  142. package/dist/socket-opt-ins.d.ts +64 -0
  143. package/dist/source-DBDbBTL4.js +811 -0
  144. package/dist/source-DBDbBTL4.js.map +1 -0
  145. package/dist/tsx-loader.d.ts +14 -0
  146. package/dist/utils/drizzle-conditions.d.ts +63 -3
  147. package/dist/utils/error-cause.d.ts +7 -0
  148. package/dist/utils/pg-error-utils.d.ts +2 -4
  149. package/dist/utils/result-rows.d.ts +10 -0
  150. package/dist/websocket.d.ts +63 -18
  151. package/package.json +7 -7
  152. package/dist/BranchService-DGPL6G_C.js.map +0 -1
  153. package/dist/backup-cli-24v4OlSp.js.map +0 -1
  154. package/dist/backup-service-HQ9GC4tN.js.map +0 -1
  155. package/dist/cli-errors-B8qHg02P.js +0 -798
  156. package/dist/cli-errors-B8qHg02P.js.map +0 -1
  157. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  158. package/dist/cli-output-CNdMql-L.js +0 -47
  159. package/dist/cli-output-CNdMql-L.js.map +0 -1
  160. package/dist/collection-index-DdnsxiJ_.js +0 -286
  161. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  162. package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
  163. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  164. package/dist/doctor-Cb2thZ8s.js.map +0 -1
  165. package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
  166. package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
  167. package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
  168. package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
  169. package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
  170. package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
  171. package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
  172. package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
  173. package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
  174. package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
  175. package/dist/plan-schema-C0fxM8dY.js.map +0 -1
  176. package/dist/policy-drift-DljYdrpW.js.map +0 -1
  177. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
  178. package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
  179. package/dist/source-Br7L7GOI.js +0 -410
  180. package/dist/source-Br7L7GOI.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-liveness-D3UD5_BW.js","names":[],"sources":["../src/services/field-viewer.ts","../src/services/collection-helpers.ts","../src/utils/drizzle-conditions.ts","../src/services/vector-iterative-scan.ts","../src/services/row-pipeline.ts","../src/services/write-denial.ts","../src/services/junction-writes.ts","../src/services/soft-delete.ts","../src/services/read-scope.ts","../src/services/RelationService.ts","../src/data-transformer.ts","../src/services/nested-path.ts","../src/services/FetchService.ts","../src/services/RelationWriteService.ts","../src/services/field-op-sql.ts","../src/services/write-depth.ts","../src/services/write-transaction-scope.ts","../src/services/PersistService.ts","../src/utils/result-rows.ts","../src/services/dataService.ts","../src/services/pg-notify-listener.ts","../src/services/read-field-access.ts","../src/services/socket-liveness.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { apiHiddenFieldNames, type FieldViewer } from \"@rebasepro/common\";\nimport type { CollectionConfig } from \"@rebasepro/types\";\n\n/**\n * Who the current read is *for*, available to the row walk without threading it\n * through every read signature.\n *\n * Per-field `access.read` has to be applied wherever a row leaves this driver,\n * and that is not one place: a REST list, a single get, a relation target\n * inlined into a parent, the same target rendered as an admin ref, a realtime\n * frame, a history entry. The row pipeline is the one function they all share —\n * and it is reached through `FetchService`, `RelationService` and `DataService`,\n * none of which carry a user. Threading one through would be a parameter added\n * to a dozen signatures and forgotten on the thirteenth, which is precisely how\n * `stripExcluded` came to be missing from the relation-ref branch for a year.\n *\n * So the viewer is ambient, established at the two places that already\n * establish the *database's* auth context for a read — `applyAuthContext` is\n * called on the same line in both:\n *\n * - `AuthenticatedPostgresBackendDriver.withTransaction`, which every REST,\n * SDK and WebSocket read runs inside;\n * - `RealtimeService`'s RLS-bound refetches, which build the frames a\n * subscriber receives.\n *\n * Deliberately `run()` and not `enterWith()`. `enterWith` persists past the call\n * that made it, so a `rebase.dataAsAdmin` read inside a user's request — a\n * `beforeSave` hook, an `afterRead` hook — would leave the admin viewer in place\n * for the rest of that request and strip nothing from what the user is served.\n *\n * @module\n */\n\n/**\n * `undefined` inside the store is meaningful and is not the same as no store at\n * all: {@link FieldViewer} reads absence as the trusted server plane. Nothing\n * currently stores `undefined`, but the store's type says the difference is\n * carried rather than flattened.\n */\nconst storage = new AsyncLocalStorage<FieldViewer | undefined>();\n\n/**\n * Run `fn` with `viewer` as the caller every row strip judges against.\n *\n * Nested calls shadow: a `dataAsAdmin` read inside a user request runs under the\n * service identity for its duration and the user's viewer is restored after it,\n * which is the behaviour both halves need and the reason this is not a variable.\n */\nexport function withFieldViewer<T>(viewer: FieldViewer | undefined, fn: () => T): T {\n return storage.run(viewer, fn);\n}\n\n/**\n * The caller the current read is for, or `undefined` on the trusted server plane\n * — an in-process `rebase.data` call on the base driver, a migration, the auth\n * adapter reading a password hash to verify it.\n *\n * Absence cannot be reached from outside: every API ingress scopes its driver\n * with `withAuth`, including an unauthenticated one (`roles: [\"anon\"]`), and\n * that is what establishes the viewer.\n */\nexport function currentFieldViewer(): FieldViewer | undefined {\n return storage.getStore();\n}\n\n/**\n * The names an API caller's query may not see on `collection`: its\n * `excludeFromApi` fields, in every spelling. Empty on the trusted server plane\n * (no viewer), which may filter and sort on those columns — the auth adapter\n * looks rows up by them — and empty with no collection in hand.\n *\n * A query naming one of these is answered exactly as a query naming a column\n * the table does not have. The REST check passes such a name through to this\n * driver rather than refusing it itself, so the two answers come from one throw\n * and cannot drift: a \"not readable\" there would confirm a column OpenAPI and\n * `list_collections` hide.\n */\nexport function namesHiddenFromCaller(collection: CollectionConfig | undefined): ReadonlySet<string> {\n if (!collection || currentFieldViewer() === undefined) return NO_NAMES;\n return apiHiddenFieldNames(collection);\n}\n\nconst NO_NAMES: ReadonlySet<string> = new Set();\n","import { PgTable, AnyPgColumn, getTableConfig } from \"drizzle-orm/pg-core\";\nimport { and, eq, getTableColumns, type SQL } from \"drizzle-orm\";\nimport { CollectionConfig, Property, ResolvedHasMany, ResolvedHasOne } from \"@rebasepro/types\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { fieldKeyForColumn, getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { ApiError, logger } from \"@rebasepro/server\";\n\n// Row identity is derived on both sides of the wire — the driver parses an\n// incoming address into key columns, the admin derives one from a served row —\n// so the implementation lives in `common` and both agree by construction.\nexport { buildCompositeId, parseIdValues, isAddressableId, COMPOSITE_ID_SEPARATOR } from \"@rebasepro/common\";\nexport type { PrimaryKeyInfo } from \"@rebasepro/common\";\nimport { buildCompositeId, COMPOSITE_ID_SEPARATOR, getDeclaredPrimaryKeys, isAddressableId, parseIdValues } from \"@rebasepro/common\";\nimport type { PrimaryKeyInfo } from \"@rebasepro/common\";\n\n/**\n * Shared helper functions for row operations.\n * These are used by FetchService, PersistService, and RelationService.\n *\n * All functions that need collection/table lookups require an explicit\n * `PostgresCollectionRegistry` instance — there is no global singleton.\n */\n\n/**\n * Interface for Drizzle column metadata introspection.\n * Replaces unsafe `as Record<string, unknown>` double-cast chains.\n */\nexport interface DrizzleColumnMeta {\n columnType?: string;\n dataType?: string;\n primary?: boolean;\n}\n\n/** Safely extract Drizzle column metadata from a column object. */\nexport function getColumnMeta(col: AnyPgColumn): DrizzleColumnMeta {\n // `columnType`, `dataType` and `primary` are declared members of drizzle's\n // `Column`, so there is nothing to convert. The `typeof` checks stay: this\n // is called on columns from runtime-introspected tables as well as declared\n // ones, and the generic parameters those carry are not always resolved.\n return {\n columnType: typeof col.columnType === \"string\" ? col.columnType : undefined,\n dataType: typeof col.dataType === \"string\" ? col.dataType : undefined,\n primary: typeof col.primary === \"boolean\" ? col.primary : undefined\n };\n}\n\n/**\n * Whether an address could name a row in this table, judged by the columns.\n *\n * {@link getPrimaryKeys} lets a config's `isId: \"uuid\"` win over the schema, so\n * its `isUUID` is a claim rather than a fact — right for deriving addresses,\n * wrong for refusing a query. Here the Drizzle column type decides, because it\n * is what Postgres will enforce: a `uuid` column meets `/c/products/new` with\n * `22P02`, which aborts the surrounding transaction and turns every later\n * statement into an unrelated-looking `25P02`.\n */\nexport function idCanAddressTable(\n id: string | number,\n table: PgTable,\n idInfoArray: PrimaryKeyInfo[]\n): boolean {\n const columnBacked = idInfoArray.map(info => {\n const col = table[info.fieldName as keyof typeof table] as AnyPgColumn | undefined;\n const meta = col ? getColumnMeta(col) : undefined;\n // No column to ask (a key the schema does not carry) leaves the id\n // addressable: refusing on a guess would 404 rows that do exist.\n if (!meta?.columnType) return info;\n return { ...info,\n isUUID: meta.columnType === \"PgUUID\" };\n });\n return isAddressableId(id, columnBacked);\n}\n\n/**\n * The condition that names one row: every key column equal to its part of the\n * address.\n *\n * Every key column, not the first. A composite-key read that matched only the\n * leading column answered `p1:::bob` with whichever `p1` row Postgres reached\n * first, and a delete then judged that row's `beforeDelete` while deleting bob —\n * the writes have always matched the whole key, so the reads must too.\n */\nexport function rowIdentityCondition(\n table: PgTable,\n idInfoArray: PrimaryKeyInfo[],\n id: string | number,\n collectionPath: string\n): SQL {\n // Throws on a collection with no key: no condition at all is a WHERE that\n // matches every row, which is the opposite of naming one.\n const columns = primaryKeyColumns(table, idInfoArray, collectionPath);\n const parsed = parseIdValues(id, idInfoArray);\n const conditions = columns.map((column, i) => eq(column, parsed[idInfoArray[i].fieldName]));\n return conditions.length === 1 ? conditions[0] : and(...conditions) as SQL;\n}\n\n/**\n * The columns of a table's composite primary-key constraint, or `[]`.\n *\n * Fails open for anything that is not a real Drizzle table — a test double, a\n * hand-built registry entry — because \"no key\" is a legitimate answer here and\n * throwing would take out every caller that only wanted to ask.\n */\nfunction compositePrimaryKeyColumns(table: PgTable): AnyPgColumn[] {\n try {\n const config = getTableConfig(table);\n return (config.primaryKeys?.[0]?.columns ?? []) as AnyPgColumn[];\n } catch {\n return [];\n }\n}\n\n/**\n * The Drizzle key a column is registered under, falling back to its name.\n *\n * Matched on the SQL name, which is unique within a table, as well as on object\n * identity: the columns a `primaryKey({ columns })` constraint holds are the\n * copies drizzle builds for a table's extra config, never the objects\n * `getTableColumns` returns, so identity alone never finds them. The fallback\n * is not the field either on a table keyed by wire name — `payment_id` is\n * served as `paymentId` — and a key resolved to a field the table does not\n * have is a key no read or write can build a condition on.\n */\nfunction columnFieldName(table: PgTable, column: AnyPgColumn): string {\n for (const [key, candidate] of Object.entries(getTableColumns(table))) {\n if (candidate === column || candidate.name === column.name) return key;\n }\n return column.name;\n}\n\nexport function getCollectionByPath(collectionPath: string, registry: PostgresCollectionRegistry): CollectionConfig {\n const collection = registry.getCollectionByPath(collectionPath);\n if (!collection) {\n const registered = registry.getCollections().map(c => c.slug).join(\", \");\n throw new Error(`Collection not found: ${collectionPath}. Registered collections: [${registered}]`);\n }\n return collection;\n}\n\n/**\n * Reject a write naming something that is not a column of the table.\n *\n * \"The table\" is the one read back from `information_schema` at boot — see\n * `catalogue-schema.ts` — so this asks the database, not a file. It used to ask\n * the generated module, which made the commonest cause of this error a stale\n * `schema.generated.ts`: a property added to a collection was created in the\n * database by boot-ensure and still answered `400` on its first save, because\n * the module had not been regenerated. That cause no longer exists, and the\n * remedy below no longer names it.\n *\n * Drizzle builds INSERT from `Object.entries(table[Symbol.Columns])` and UPDATE\n * from `Object.keys(tableColumns)`, so a key the table does not carry is not\n * rejected by anything — it is *left out of the statement*. The insert answers\n * 201 having stored nothing under that name; the update, if the key was the\n * only one, builds `update \"posts\" set where …` and Postgres raises a syntax\n * error (SQLSTATE 42601), which is neither class 22 nor 23 and so surfaces as a\n * 500 for what is a caller's typo.\n *\n * That makes this the last honest place to check, and the only one every write\n * passes through. `assertKnownWriteFields` in the REST layer checks the same\n * thing against the *config* and is skipped on four paths — `strictWrites:\n * false`, a collection declaring no properties, an auth adapter that owns the\n * body's shape, and a nested route whose target cannot be walked — and it never\n * sees an in-process `rebase.dataAsAdmin` write at all.\n *\n * It also gives `strictWrites: false` a truthful implementation. The flag is\n * documented for \"a column that really does exist which the config never\n * declared\", and skipping the config check alone could not deliver that: the\n * value was dropped a layer later regardless. Skipping the config check and\n * keeping this one does exactly what the flag says — the column must exist,\n * the property need not.\n */\nexport function assertWritableColumns(\n values: Record<string, unknown>,\n table: PgTable,\n collectionPath: string\n): void {\n // Only a real Drizzle table carries a column list. A stand-in that does not\n // (a test double, a registry entry built by hand) has nothing to check\n // against, and inventing an answer from its own keys would reject writes\n // over the shape of the double rather than the shape of the table.\n const columns = getTableColumns(table) as Record<string, unknown> | undefined;\n if (!columns || Object.keys(columns).length === 0) return;\n\n const unknown = Object.keys(values).filter(key => !(key in columns));\n if (unknown.length === 0) return;\n\n // One key is worth naming precisely: the *column* of a field whose wire name\n // differs from it. A table is keyed by the wire name — `authorId` for\n // `author_id` — so writing the column reaches here as \"no such column\",\n // which is the least helpful true statement available about it. Says the\n // name to use instead.\n const byColumnName = new Map<string, string>();\n for (const [key, column] of Object.entries(columns)) {\n const name = (column as { name?: unknown } | undefined)?.name;\n if (typeof name === \"string\" && name !== key) byColumnName.set(name, key);\n }\n const misspelled = unknown.filter(key => byColumnName.has(key));\n if (misspelled.length === unknown.length) {\n throw ApiError.badRequest(\n `'${collectionPath}' has no field ${misspelled.map(key => `'${key}'`).join(\", \")} — ` +\n `${misspelled.length > 1 ? \"those are column names\" : \"that is a column name\"}. ` +\n `Write ${misspelled.map(key => `'${byColumnName.get(key)}'`).join(\", \")} instead: a field is ` +\n \"addressed by its property key, and the column behind it is an implementation detail.\",\n \"VALIDATION_UNKNOWN_FIELDS\"\n );\n }\n\n // The offending keys, and deliberately not the list of real ones: this\n // error is reachable on paths where the REST field check was skipped, and\n // an `excludeFromApi` column is documented as never being served to a\n // caller. Naming what was sent is the actionable half anyway.\n //\n // The remedy names the schema, not the schema *file*: the columns here were\n // read from the database this request is about to write to, so a key that is\n // not among them is a key no column exists for — either a typo, or a\n // property whose column was never created because nothing has applied the\n // collection schema to this database yet.\n throw ApiError.badRequest(\n `'${collectionPath}' has no column${unknown.length > 1 ? \"s\" : \"\"} ` +\n `${unknown.map(key => `'${key}'`).join(\", \")}, so the value${unknown.length > 1 ? \"s\" : \"\"} ` +\n \"would have been dropped before the statement was built. \" +\n \"If you just added the propert\" + (unknown.length > 1 ? \"ies\" : \"y\") +\n \", the database does not have the column yet: apply the schema \" +\n \"(`rebase db push`, or redeploy so boot creates it).\",\n \"VALIDATION_UNKNOWN_FIELDS\"\n );\n}\n\n/**\n * A relation whose names do not resolve against the registered schema.\n *\n * Every one of these used to be a `logger.warn` followed by `continue`, so a\n * save reported success for a relation it had not written and a read answered\n * `[]` for one it could not resolve. `assertRelationsResolve` (validate-relations)\n * fails boot on the same defects, which is where they belong — a server that\n * refuses to start is recoverable in a minute. This is the second line, for the\n * paths that assemble a registry by hand, and it exists so that \"cannot resolve\"\n * is never again reported as \"done\".\n *\n * @param label `<collection>.<relation>`\n * @param detail what does not resolve, in terms of the schema\n */\nexport function relationMisconfigured(label: string, detail: string): ApiError {\n return ApiError.internal(\n `Relation '${label}' does not resolve against the registered schema: ${detail}. ` +\n \"The operation was refused rather than skipped — silently dropping it would report \" +\n \"success for a write that never happened, or emptiness for rows that exist. Run \" +\n \"`rebase schema generate` if the generated schema is older than the database.\",\n \"RELATION_MISCONFIGURED\"\n );\n}\n\nexport function getTableForCollection(collection: CollectionConfig, registry: PostgresCollectionRegistry): PgTable<any> {\n const tableName = getTableName(collection);\n const table = registry.getTable(tableName);\n if (!table) {\n throw new Error(`Table not found for collection '${collection.slug}' (table: ${tableName})`);\n }\n return table;\n}\n\n/**\n * The key columns a collection's rows are addressed by.\n *\n * Three tiers, in order: properties marked `isId`, the primary keys of the\n * registered table, and finally a column literally named `id`. Only the first is\n * visible to the browser, which is why a key known only to the table is reported\n * at boot — see {@link warnOnKeysTheAdminCannotResolve}.\n *\n * The second tier reads the table the registry holds, which is built from\n * `information_schema` — the database's own primary key, not a generated file's\n * claim about it. Composite keys are declared as a table constraint rather than\n * on each column (that is the only legal way to say it), so both shapes are\n * asked for.\n *\n * Returns `[]` when nothing resolves, rather than throwing. It used to open by\n * resolving the table, which throws when there is none — so the `isId` tier,\n * which needs no table at all, was unreachable for exactly the collections\n * most likely to have no table registered. Every caller that wanted \"no keys\"\n * to mean \"no keys\" had to spell that out in a try/catch.\n *\n * Callers that cannot proceed without a key must say so themselves, naming the\n * collection: an empty array here means \"this collection has no address\", which\n * is a different answer in a notification (broadcast a wildcard) than in a save\n * (fail).\n */\nexport function getPrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): PrimaryKeyInfo[] {\n // Explicitly declared `isId` properties win, and need no table.\n if (collection.properties) {\n const idProps = Object.entries(collection.properties)\n .filter(([_, prop]) => \"isId\" in (prop as object) && Boolean((prop as { isId?: unknown }).isId))\n .map(([key, prop]) => ({\n fieldName: key,\n type: prop.type === \"number\" ? \"number\" as const : \"string\" as const,\n isUUID: (prop as { isId?: unknown }).isId === \"uuid\"\n }));\n\n if (idProps.length > 0) {\n return idProps;\n }\n }\n\n // The remaining tiers read the drizzle schema, so they need the table. A\n // collection without one — another engine's, or simply unregistered — has\n // nothing more to offer.\n const table = registry.getTable(getTableName(collection));\n if (!table) return [];\n\n // Otherwise infer from Drizzle schema\n const keys: PrimaryKeyInfo[] = [];\n for (const [key, colRaw] of Object.entries(table)) {\n const col = colRaw as AnyPgColumn;\n if (col && typeof col === \"object\" && \"primary\" in col && col.primary) {\n const meta = getColumnMeta(col);\n const type = col.dataType === \"number\" || meta.columnType === \"PgSerial\" || meta.columnType === \"PgInteger\" ? \"number\" : \"string\";\n const isUUID = meta.columnType === \"PgUUID\";\n keys.push({ fieldName: key,\ntype,\nisUUID });\n }\n }\n\n // A composite key is not a flag on each column — Postgres has one\n // constraint naming several, and that is how the table declares it too. The\n // generated module happened to mark each member `primaryKey()` individually,\n // which is not valid DDL but did leave `col.primary` set; reading the\n // constraint is what keeps a two-column key resolvable now that the table\n // describes it honestly.\n if (keys.length === 0) {\n for (const column of compositePrimaryKeyColumns(table)) {\n const meta = getColumnMeta(column);\n keys.push({\n fieldName: columnFieldName(table, column),\n type: column.dataType === \"number\" || meta.columnType === \"PgSerial\" || meta.columnType === \"PgInteger\" ? \"number\" : \"string\",\n isUUID: meta.columnType === \"PgUUID\"\n });\n }\n }\n\n // A collection that declares no primary key gets the implicit `id` column,\n // which is what the schema generators emit for it — so read it back the\n // same way rather than reporting the table as keyless.\n if (keys.length === 0 && \"id\" in table) {\n const idCol = table[\"id\" as keyof typeof table] as AnyPgColumn;\n const idMeta = getColumnMeta(idCol);\n const type = idCol.dataType === \"number\" || idMeta.columnType === \"PgSerial\" || idMeta.columnType === \"PgInteger\" ? \"number\" : \"string\";\n const isUUID = idMeta.columnType === \"PgUUID\";\n keys.push({ fieldName: \"id\",\ntype,\nisUUID });\n }\n\n return keys;\n}\n\n/**\n * The key columns, for callers that cannot do their job without one.\n *\n * {@link getPrimaryKeys} answers \"what keys, if any\" and returns `[]` for a\n * collection with no address. Most of this driver, though, is building a WHERE\n * clause and has no meaning without a key — for those, an empty array is not an\n * answer, and indexing `[0]` into it produces `Cannot read properties of\n * undefined` three frames from where the real problem is. This says what is\n * wrong and which collection it is wrong about.\n */\nexport function requirePrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): PrimaryKeyInfo[] {\n const keys = getPrimaryKeys(collection, registry);\n if (keys.length === 0) {\n throw new Error(\n `Collection '${collection.slug}' has no primary key, so its rows cannot be addressed. ` +\n `Mark the key property with \\`isId\\` in its config, or register a table whose schema declares one.`\n );\n }\n return keys;\n}\n\n/**\n * The table's key columns, in key order.\n *\n * Throws when there are none, or when the table has no column for one of them:\n * a WHERE built from fewer columns than the key matches every row that shares\n * the columns it did find, so there is no partial answer worth returning.\n *\n * @param collectionPath named in the error.\n */\nexport function primaryKeyColumns(\n table: PgTable,\n primaryKeys: PrimaryKeyInfo[],\n collectionPath: string\n): AnyPgColumn[] {\n if (primaryKeys.length === 0) {\n throw new Error(`Collection '${collectionPath}' has no primary key, so its rows cannot be addressed.`);\n }\n return primaryKeys.map(info => {\n const column = table[info.fieldName as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n throw new Error(`ID field '${info.fieldName}' not found in table for collection '${collectionPath}'`);\n }\n return column;\n });\n}\n\n/**\n * The column a `joinPath` step names, on a table that may key it either way.\n *\n * A `JoinStep` is written in database terms — `on: { from: \"author_id\" }` —\n * because there is no collection to translate through: the step names a raw\n * table. A table the boot reads back out of the catalogue is keyed by the\n * *wire* name, `authorId`, while a hand-written Drizzle schema may key it by\n * the column name. Looking the step's name up as a key therefore found\n * nothing on every booted table, and a `via` relation loaded nothing at all.\n *\n * The key first, as the step may already name one; then the column whose SQL\n * name it is. `undefined` when the table has neither, which the caller turns\n * into an error naming the step.\n */\nexport function columnNamed(table: PgTable, name: string): AnyPgColumn | undefined {\n const columns: Record<string, AnyPgColumn> | undefined = getTableColumns(table);\n if (!columns) {\n // A stand-in without Drizzle's column map — a test double. Its keys\n // are all there is to go by.\n return (table[name as keyof typeof table] as AnyPgColumn | undefined) || undefined;\n }\n if (Object.prototype.hasOwnProperty.call(columns, name)) return columns[name];\n return Object.values(columns).find(column => column.name === name);\n}\n\n/**\n * The column on the *source* table that a `hasOne`/`hasMany` link points at.\n *\n * `sourceKey` is authored when the two sides join on a natural key — an\n * external identity id, a SKU — and left off when they join on the row id,\n * which is the overwhelming majority. That makes `undefined` the only optional\n * field on a resolved relation, so it gets exactly one reader: this function.\n * Every consumer that needs the column asks here, and none of them re-derives\n * \"or else the primary key\" for itself. That is the whole point — the fallback\n * chains this codebase removed from relation resolution were dangerous because\n * they were *duplicated* and could disagree, not because they existed.\n */\nexport function sourceKeyField(\n relation: ResolvedHasOne | ResolvedHasMany,\n sourceCollection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): string {\n // A **field**, as the name says: callers index the Drizzle table and the\n // rows it returns with what comes back from here, and both are keyed by the\n // wire name. `sourceKey` is authored in *column* terms, like every other\n // link on a relation, so it is translated here rather than at each of the\n // call sites — where it used to be `tenant_id` looked up on a table whose\n // key is `tenantId`, finding nothing.\n if (relation.sourceKey) return fieldKeyForColumn(sourceCollection, relation.sourceKey);\n return requirePrimaryKeys(sourceCollection, registry)[0].fieldName;\n}\n\n/**\n * Whether this link joins on something other than the source's primary key.\n *\n * Callers that hold a parent *id* — which is most of them, since an id is what\n * a URL carries — must translate it to the source key's value before it can be\n * compared with the target's foreign key. Those that hold the parent *row*, or\n * that build a correlated subquery over the source table, can read the column\n * directly and skip the lookup.\n */\nexport function joinsOnNaturalKey(\n relation: ResolvedHasOne | ResolvedHasMany,\n sourceCollection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): boolean {\n if (!relation.sourceKey) return false;\n // Compared as fields, because the right-hand side is one.\n return fieldKeyForColumn(sourceCollection, relation.sourceKey)\n !== requirePrimaryKeys(sourceCollection, registry)[0].fieldName;\n}\n\n/**\n * Collections whose key the *browser* cannot resolve, and what it will do\n * instead.\n *\n * The two sides resolve keys from different evidence. This driver reads, in\n * order: properties marked `isId`, the primary keys of the Drizzle schema, then\n * a column literally named `id`. The admin shares the `CollectionConfig` — it\n * compiles the same collection files into its bundle — but never the Drizzle\n * schema, so the middle tier is invisible to it.\n *\n * Nothing can normalize this at runtime: the server does not serve the admin\n * its collections, so a key resolved here cannot be handed over there. The\n * config files are the only thing both sides read, so the fix is an edit to\n * them, and the most this can do is say exactly which edit.\n *\n * Two shapes, and the second is the dangerous one:\n *\n * - No `isId`, no `id` property → the admin resolves no address, warns in the\n * console, and rows cannot be opened or linked.\n * - No `isId`, but an `id` property that is *not* the key → the admin addresses\n * rows by `id` while this driver reads the address as the real key. Nothing\n * errors: the addresses look right and route wrong.\n */\nexport function findUnresolvableKeyCollections(\n collections: CollectionConfig[],\n registry: PostgresCollectionRegistry\n): { collection: CollectionConfig; keys: PrimaryKeyInfo[]; shadowedByIdProperty: boolean }[] {\n const findings: { collection: CollectionConfig; keys: PrimaryKeyInfo[]; shadowedByIdProperty: boolean }[] = [];\n\n for (const collection of collections) {\n // Declared `isId` is the tier both sides share: if it is there, they agree.\n if (getDeclaredPrimaryKeys(collection).length > 0) continue;\n\n // No registered table resolves to no keys, and a collection this cannot\n // resolve a key for is one it has nothing to say about.\n const keys = getPrimaryKeys(collection, registry);\n if (keys.length === 0) continue;\n\n // A single key named `id` is the last tier on both sides: they agree\n // without anything being declared.\n if (keys.length === 1 && keys[0].fieldName === \"id\") continue;\n\n findings.push({\n collection,\n keys,\n shadowedByIdProperty: Boolean(collection.properties?.id)\n });\n }\n\n return findings;\n}\n\n/**\n * The `isId` to suggest for one key column, written as it goes in the config.\n *\n * `true` means the application supplies the value; `\"increment\"` and `\"uuid\"`\n * mean the database generates it, so the admin withholds the field when a row\n * is created. Suggesting a generating strategy for a column the database does\n * not generate is therefore worse than suggesting none: the form sends no\n * value, and the insert fails on a NULL key.\n *\n * The tables this server reads carry each column's type and whether it is part\n * of the key, not its default, so a generating strategy is suggested only where\n * the key's shape makes it near-certain — a lone `uuid` or integer column that\n * points at nothing — and `true` everywhere else:\n *\n * - a column of a **composite** key holds part of the row's identity, which is\n * the caller's to give (`company_translation (id, locale)` is the id of a\n * company and a locale, neither of which this table invents);\n * - a **foreign-key** column holds its parent's key;\n * - a `text` or `varchar` column has no generating strategy at all.\n */\nfunction suggestedIdStrategy(key: PrimaryKeyInfo, keys: PrimaryKeyInfo[], collection: CollectionConfig): string {\n if (keys.length > 1) return \"true\";\n\n const foreignKeyFields = new Set<string>();\n for (const relation of Object.values(resolveCollectionRelations(collection))) {\n if (relation.kind === \"belongsTo\") foreignKeyFields.add(fieldKeyForColumn(collection, relation.localKey));\n }\n for (const [name, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property).type === \"reference\") foreignKeyFields.add(name);\n }\n if (foreignKeyFields.has(key.fieldName)) return \"true\";\n\n if (key.isUUID) return \"\\\"uuid\\\"\";\n if (key.type === \"number\") return \"\\\"increment\\\"\";\n return \"true\";\n}\n\n/**\n * Report the collections from {@link findUnresolvableKeyCollections} at boot,\n * with the edit that fixes each one.\n *\n * Grouped by failure, not by collection: the shadowed case is a routing bug and\n * the silent case is a missing feature, and they deserve different urgency.\n */\nexport function warnOnKeysTheAdminCannotResolve(\n collections: CollectionConfig[],\n registry: PostgresCollectionRegistry\n): void {\n const findings = findUnresolvableKeyCollections(collections, registry);\n if (findings.length === 0) return;\n\n const edit = (f: { collection: CollectionConfig; keys: PrimaryKeyInfo[] }): string => {\n // `isId` is declared on string, number and reference properties only.\n // A key column declared as anything else cannot be marked as it is, and\n // marking the other columns alone would make them the whole key.\n const unmarkable = f.keys.flatMap(key => {\n const type = (f.collection.properties?.[key.fieldName] as Property | undefined)?.type;\n return type !== undefined && type !== \"string\" && type !== \"number\" && type !== \"reference\"\n ? [`\\`${key.fieldName}\\` (a \\`${type}\\` property)`]\n : [];\n });\n if (unmarkable.length > 0) {\n return `${f.collection.slug}: its key includes ${unmarkable.join(\" and \")}, which cannot carry ` +\n \"`isId`. Declare that column as a `string` property, then mark every key column \" +\n `(${f.keys.map(key => `\\`${key.fieldName}\\``).join(\", \")}) with \\`isId: true\\``;\n }\n const marks = f.keys.map(key => ({\n field: `\\`${key.fieldName}\\``,\n strategy: `\\`isId: ${suggestedIdStrategy(key, f.keys, f.collection)}\\``\n }));\n const same = marks.every(mark => mark.strategy === marks[0].strategy);\n const what = same\n ? `${marks.map(mark => mark.field).join(\" and \")} with ${marks[0].strategy}`\n : marks.map(mark => `${mark.field} with ${mark.strategy}`).join(\" and \");\n // Several columns are one key, and the admin addresses a row by all of\n // them — saying how is what makes a link like `/1:::en_US` readable.\n const composite = f.keys.length > 1\n ? ` — one composite key, whose rows are addressed as \\`${f.keys.map(key => `<${key.fieldName}>`).join(COMPOSITE_ID_SEPARATOR)}\\``\n : \"\";\n return `${f.collection.slug}: mark ${what}${composite}`;\n };\n\n const shadowed = findings.filter(f => f.shadowedByIdProperty);\n const silent = findings.filter(f => !f.shadowedByIdProperty);\n\n if (shadowed.length > 0) {\n logger.warn(\n `⚠️ These collections declare no \\`isId\\`, and their key is only in the drizzle schema — but they ` +\n `do have a property called \\`id\\`. The admin has no way to know \\`id\\` is not the key, so it will ` +\n `address rows by it while this server reads the address as the real key: the links look right and ` +\n `route wrong. Nothing will error.\\n\\n` +\n shadowed.map(f => ` • ${edit(f)}`).join(\"\\n\") + \"\\n\"\n );\n }\n\n if (silent.length > 0) {\n logger.warn(\n `⚠️ These collections declare no \\`isId\\`, and their key is only in the drizzle schema, which the ` +\n `admin never sees. It will resolve no address for their rows, so detail links, caching and ` +\n `relations will not work for them:\\n\\n` +\n silent.map(f => ` • ${edit(f)}`).join(\"\\n\") + \"\\n\"\n );\n }\n}\n\n/**\n * The address of a row: derived from the collection's primary keys, because a\n * row does not carry one — it is exactly its columns.\n *\n * Falls back to a literal `id` column, for a row that reached us from somewhere\n * other than this driver. Returns `\"\"` when there is no key and no `id` —\n * callers decide what that means, since \"unaddressable\" is a different answer\n * in a notification (broadcast a wildcard) than in a save (fail).\n */\nexport function deriveRowAddress(\n row: Record<string, unknown>,\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): string {\n const composite = buildCompositeId(row, getPrimaryKeys(collection, registry));\n // An all-empty composite is indistinguishable from a missing key, and\n // `buildCompositeId` returns \"\" for no keys at all.\n if (composite && composite.split(COMPOSITE_ID_SEPARATOR).some(part => part !== \"\")) {\n return composite;\n }\n if (row.id !== undefined && row.id !== null) return String(row.id);\n return \"\";\n}\n\n","import { and, eq, not, or, sql, SQL, ilike, inArray, getTableColumns, getTableName as drizzleTableName } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport {\n ALL_WHERE_FILTER_OPS,\n CollectionConfig, FilterValues, WhereFilterOp, JoinStep, LogicalCondition, FilterCondition,\n ResolvedRelation, ResolvedBelongsTo, ResolvedHasOne, ResolvedHasMany,\n ResolvedForeignKeyOnTarget, ResolvedManyToMany, hasForeignKeyOnTarget, isManyToMany,\n encodeRelationAggregateSort, type Property, type RelationAggregateFn, type RelationAggregateSort\n} from \"@rebasepro/types\";\nimport {\n apiHiddenFieldNames,\n canReadField,\n queryableFieldNames,\n unknownFilterField,\n unknownVectorProperty,\n unknownRelationTargetField,\n fieldKeyForColumn, getColumnName, getTableName, normalizeToEntityRelation, resolveCollectionRelations, toFilterTuples\n} from \"@rebasepro/common\";\nimport { currentFieldViewer, namesHiddenFromCaller } from \"../services/field-viewer\";\nimport { generateForeignKeyName, splitSearchTerms, toWireKey } from \"@rebasepro/utils\";\n/**\n * Postgres's own default for `pg_trgm.word_similarity_threshold`. Named here\n * because the fuzzy predicate has to know when the index-backed operator agrees\n * with the collection's declared threshold and when it would narrow too far.\n */\nconst PG_TRGM_WORD_SIMILARITY_DEFAULT = 0.6;\n/**\n * What a substring-only hit contributes to `_score` under\n * {@link SearchMode} `\"hybrid\"`.\n *\n * Below the smallest `ts_rank` a real lexeme match can produce (a single\n * D-weighted hit is 0.1 × 0.0607927 ≈ 0.006), so a row found by the indexed\n * half always outranks one found only by substring.\n */\nconst HYBRID_SUBSTRING_SCORE = 0.001;\nimport { buildSearchColumnSpec, SEARCH_UNACCENT_FN, type SearchColumnSpec } from \"../schema/search-column\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { ConditionBuilderStatic } from \"../interfaces\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport { columnNamed, getColumnMeta } from \"../services/collection-helpers\";\n\n/**\n * What to do with a filter field that resolves to no column at all.\n *\n * - `\"error\"` (default) — reject the request. A filter that cannot be\n * compiled is *dropped*, and dropping a condition can only ever widen the\n * result set. On a data plane where row-level security is the last line of\n * defence, a typo'd or renamed filter key therefore runs the query without\n * that condition and returns everything RLS happens to allow.\n * - `\"warn\"` — the historical behaviour: log and silently drop the condition.\n * Only for a deployment that knowingly sends filter keys the table does not\n * have and has satisfied itself that widening is safe there.\n */\nexport type UnknownFilterFieldsMode = \"error\" | \"warn\";\n\n\n/**\n * The fields an unknown-field answer lists, sorted — never an `excludeFromApi`\n * one, for any caller: the list is a hint for a typo, and naming a hidden\n * column there answers the question the unknown-field answer leaves open.\n *\n * Read off the collection when there is one, with the function the REST check\n * uses to answer a query on a hidden field — so its answer and this one are\n * the same bytes. Off the table only when no collection is in hand.\n */\nexport function listedColumnKeys(table: PgTable<any>, collection: CollectionConfig | undefined): string[] {\n if (collection) return queryableFieldNames(collection);\n try {\n return Object.keys(getTableColumns(table)).sort();\n } catch {\n // A table stand-in without Drizzle's column symbols — the message is\n // worth less without the list, but not worth failing over.\n return [];\n }\n}\n\n/**\n * A user's search term, made safe to drop inside a `%…%` LIKE pattern.\n *\n * The term is already a bind parameter, so this is not about injection. It is\n * about the two things a LIKE metacharacter does when it arrives from a search\n * box:\n *\n * 1. **It changes the query.** `%` and `_` are wildcards, so searching for\n * `50%` returned every row and `a_c` matched `abc`. Nothing the caller\n * could type would find a literal `%`.\n * 2. **It is a cost the caller chooses.** Postgres matches LIKE by\n * backtracking: each `%` re-tries every remaining offset, so\n * `?searchString=a%a%a%a%a%a%a%b` is polynomial with an attacker-chosen\n * exponent — evaluated per row, OR-ed across every string property of the\n * collection, on a sequential scan (a leading `%` cannot use an index), and\n * the page limit does not bound it because the scan happens first.\n *\n * This is the server-side half of the pattern `like-pattern-redos.test.ts`\n * hardened the offline evaluator against; that test's own note (\"the same\n * translation in the Mongo driver hands the expression to the database, where\n * it occupies a server thread instead\") describes this call site.\n *\n * Backslash is the default `ESCAPE` character for LIKE, and the pattern is\n * bound rather than interpolated, so a single backslash here reaches the\n * matcher as one. Escaping the escape character first is what keeps a term\n * ending in `\\` from swallowing the closing `%`.\n *\n * Note this is a *substring search*, not the `like` filter operator: a caller\n * who wants wildcards has `?title=like.foo%` for that, where the pattern is the\n * documented input.\n */\nexport const escapeLikePattern = (value: string): string =>\n value.replace(/[\\\\%_]/g, ch => `\\\\${ch}`);\n\n/**\n * The Drizzle column a relation's column name addresses on a table.\n *\n * A relation names its link in *column* terms — `localKey: \"author_id\"`,\n * `foreignKeyOnTarget: \"author_id\"` — because that is what the database and\n * every FK constraint call it. A Drizzle table is keyed by the *wire* name,\n * `authorId`. Indexing the table with the column, which is what every one of\n * these call sites used to do, therefore finds nothing the moment the two\n * differ: for a `columnName`-carrying property that was already true, and it is\n * now true of every derived foreign key.\n *\n * `undefined` rather than a throw: each caller already has a message naming the\n * relation it was resolving, which is worth more than a generic one here.\n */\n/**\n * The junction table a `manyToMany` relation names is not in the registry.\n *\n * Six sites raised `Junction table not found: <name>`, which is a fact and not\n * a diagnosis. The name in the message is the one the relation *declares*, and\n * the two things it can mean are opposite: the table exists in the database and\n * the generated Drizzle schema predates it, or the table was never created and\n * the relation's `through.table` is a typo. Both are one command away from\n * being settled, and neither was named.\n */\nfunction junctionTableMissing(junctionName: string): Error {\n return new Error(\n `The relation declares a junction table \"${junctionName}\", and no such table is registered. ` +\n \"Either the generated Drizzle schema predates it — run `rebase schema generate` — or the \" +\n \"table does not exist yet: `rebase db push` creates the junctions a relation declares. \" +\n \"If neither helps, check the `through.table` name on the relation.\"\n );\n}\n\nconst relationColumn = (\n table: PgTable<any>,\n collection: CollectionConfig | undefined,\n column: string\n): AnyPgColumn | undefined => {\n const key = fieldKeyForColumn(collection, column);\n return (key in table ? table[key as keyof typeof table] as AnyPgColumn : undefined) || undefined;\n};\n\n/**\n * A table's column by the name a `joinPath` step gives it — the SQL column — or\n * by its Drizzle property key.\n *\n * A column has two names. A generated schema keys a collection table's columns\n * by property (`orderId: uuid(\"order_id\")`) while a step names the column\n * (`from: \"order_id\"`), so a lookup by key alone found nothing on a real app,\n * and the hand-written fixtures — keyed by column name — never showed it.\n * Junction tables are generated keyed by column name, which is why a\n * many-to-many was unaffected.\n */\nexport function joinColumn(table: PgTable<any>, name: string): AnyPgColumn | undefined {\n return columnNamed(table, getColumnName(name));\n}\n\n/** The target collection of a relation, or `undefined` if its thunk cannot resolve. */\nconst targetOf = (relation: ResolvedRelation): CollectionConfig | undefined => {\n try {\n return relation.target();\n } catch {\n return undefined;\n }\n};\n\n/**\n * The SQL each aggregate sort function compiles to.\n *\n * A lookup rather than interpolation, and `sql.raw` applied to five constants\n * written out here rather than to anything derived from a request. The key is\n * already validated by `parseRelationAggregateSort` — this is the second lock\n * on the same door, so that a future caller reaching the builder directly\n * cannot put a string from the wire where a function name goes.\n */\nconst AGGREGATE_SQL: Record<RelationAggregateFn, SQL> = {\n min: sql.raw(\"min\"),\n max: sql.raw(\"max\"),\n count: sql.raw(\"count\"),\n sum: sql.raw(\"sum\"),\n avg: sql.raw(\"avg\")\n};\n\n/** Column types `ILIKE '%…%'` is defined on. */\nconst ILIKE_SQL_TYPES = /^(text|varchar|character varying|char|character|bpchar|citext)\\b/;\n\n/**\n * Can this column be matched with `ILIKE`?\n *\n * Asked of the column's *declared SQL type*, never with `instanceof`. The\n * previous version tested `column instanceof PgVarchar || … PgText || … PgChar`,\n * and `instanceof` compares class identity: it is only true when the column was\n * constructed by the very same copy of `drizzle-orm` that this module imported.\n *\n * An application's generated schema builds its tables with the app's own\n * `drizzle-orm`, and this driver declares its own dependency on one. When the\n * two ranges do not overlap — an app scaffolded against `^0.44` with a driver\n * asking for `^0.45` — a strict installer gives the driver a second copy, every\n * check returns false, no condition is produced, and the caller compiles that\n * into an impossible `WHERE`. The result is a 200 with an empty page for every\n * search on every collection without a `search` block: the failure looks\n * exactly like \"nothing matched\". Observed in production, not theorised.\n *\n * `getSQLType()` is a value the column reports about itself, so it crosses\n * module instances the way a class identity cannot. It also happens to fix\n * `citext`, which the `instanceof` list never covered.\n */\nconst supportsILike = (column: AnyPgColumn): boolean => {\n const sqlType = typeof column?.getSQLType === \"function\" ? column.getSQLType().toLowerCase() : \"\";\n return ILIKE_SQL_TYPES.test(sqlType);\n};\n\n/**\n * What `LIKE` / `ILIKE` compare for this column, or `undefined` where a pattern\n * means nothing (a number, a date, a uuid).\n *\n * Postgres defines `~~` on the text family only, and a string `enum` property\n * is an enum type: `status LIKE 'dr%'` is `operator does not exist: post_status\n * ~~ unknown`, a 500 on a filter the admin offers. A pattern over an enum is a\n * fair question about its label, so it is asked of `status::text`.\n *\n * The cast is written for every text column, not only the declared enums: a\n * catalogue-built table declares an enum column as `text`\n * (`dynamic-tables.ts`), so the declared type cannot tell the two apart, and\n * `::text` on a text column is no change at all. `citext` is the exception —\n * its own `LIKE` is case-insensitive, and casting it to `text` would quietly\n * make it case-sensitive.\n */\nconst patternOperand = (column: AnyPgColumn, operand: SQL | AnyPgColumn): SQL | undefined => {\n const sqlType = typeof column?.getSQLType === \"function\" ? column.getSQLType().toLowerCase() : \"\";\n if (/^citext\\b/.test(sqlType)) return sql`${operand}`;\n if (ILIKE_SQL_TYPES.test(sqlType) || column?.columnType === \"PgEnumColumn\") return sql`${operand}::text`;\n return undefined;\n};\n\n/**\n * Process-wide default, set once when the driver is constructed.\n *\n * The condition builder is a set of *static* methods reached from a dozen\n * `FetchService` call sites, none of which carry the driver's config — the\n * service is built from `(db, registry)` alone. Threading an option from\n * `createPostgresAdapter` down to each of them would mean touching every\n * intermediate signature to plumb a value that is a single deployment-wide\n * switch. A module-level default set at adapter construction, plus an explicit\n * per-call override for callers that have one (tests, mainly), buys the same\n * control for none of the churn. It is safe by default, so the only reason to\n * set it at all is to opt *out*.\n */\nlet defaultUnknownFilterFieldsMode: UnknownFilterFieldsMode = \"error\";\n\n/** Set the process-wide behaviour for unresolvable filter fields. */\nexport function configureUnknownFilterFields(mode: UnknownFilterFieldsMode): void {\n defaultUnknownFilterFieldsMode = mode;\n}\n\n/** The process-wide behaviour for unresolvable filter fields. */\nexport function getUnknownFilterFieldsMode(): UnknownFilterFieldsMode {\n return defaultUnknownFilterFieldsMode;\n}\n\n/** Per-call context for compiling a filter into SQL. */\nexport interface FilterCompilationOptions {\n /**\n * Overrides the process-wide {@link UnknownFilterFieldsMode} for this call.\n */\n unknownFields?: UnknownFilterFieldsMode;\n /**\n * The collection the filter is written against. Its resolved relations are\n * what turn an owning-relation filter key into the foreign-key column it\n * actually lives in; without it only the default key shapes can be guessed.\n */\n collection?: CollectionConfig;\n /**\n * The driver's registry, for relations whose link is not on this row at\n * all. A `manyToMany` compiles to an `EXISTS` over its junction and a\n * `hasMany`/`hasOne` to one over the target table — neither of which this\n * builder can reach from the collection alone.\n */\n registry?: PostgresCollectionRegistry;\n /**\n * The key column of the table being filtered — what those `EXISTS`\n * subqueries correlate back to.\n *\n * It has to be the Drizzle column object rather than a name: a column\n * renders qualified with its own table, which is what binds it to the\n * *outer* row instead of to the junction or target aliased inside the\n * subquery. See {@link DrizzleConditionBuilder.buildRelationFilterCondition}.\n */\n sourceIdColumn?: AnyPgColumn;\n}\n\n/**\n * What a filter field turns out to name.\n *\n * A field naming a column compiles to a comparison on it. A field naming a\n * relation that owns no column here compiles to a whole `EXISTS` condition\n * instead, so there is no column to hand back — which is why resolution\n * answers with a discriminated result rather than a column. The caller cannot\n * tell the two apart from the field name, and the difference is not cosmetic:\n * one is `column <op> value`, the other is a correlated subquery.\n */\ntype FilterTarget =\n | { kind: \"column\"; column: AnyPgColumn }\n | {\n /** A path *inside* a json/jsonb column — `metadata->>country`. */\n kind: \"json\";\n column: AnyPgColumn;\n /** The keys to walk, outermost first. Always at least one. */\n path: string[];\n }\n | {\n kind: \"relation\";\n relation: ResolvedForeignKeyOnTarget | ResolvedManyToMany;\n /** Bound here so the compile step cannot be reached without them. */\n registry: PostgresCollectionRegistry;\n sourceIdColumn: AnyPgColumn;\n }\n | {\n /**\n * A *column of the related row* — `applications.status`. Same `EXISTS`\n * as `relation`, with the predicate moved off the target's id and onto\n * one of its columns.\n */\n kind: \"relation-field\";\n /**\n * `via` is not among them: it is refused at resolution, and leaving it\n * in the type would let a later edit reach the compile step with a\n * relation there is no correlation for.\n */\n relation: ResolvedBelongsTo | ResolvedForeignKeyOnTarget | ResolvedManyToMany;\n registry: PostgresCollectionRegistry;\n /** The column on *this* table the subquery correlates back to. */\n sourceIdColumn: AnyPgColumn;\n /** The table the predicate is asked of, already resolved. */\n targetTable: PgTable<any>;\n /** The column on {@link targetTable} the predicate compares. */\n targetColumn: AnyPgColumn;\n };\n\n/**\n * Split `metadata->address->>city` into its column and its path.\n *\n * The arrows are PostgREST's spelling and Postgres's own, so the filter reads\n * the same as the SQL it becomes — and, more usefully, the same as what someone\n * would have written by hand in the SQL console while working out what to ask\n * for. A field with no arrow is not a JSON path and returns `undefined`, which\n * leaves every existing filter on exactly the path it took before.\n *\n * Both arrows are accepted and mean the same thing here: the extraction is\n * always compiled to `->>` (text) at the leaf, because that is the only form a\n * comparison can be made against. `->` is allowed because people write it out\n * of habit, and refusing it would be pedantry about a distinction this layer\n * erases anyway.\n */\n/**\n * Split `applications.status` into the relation and the column it addresses.\n *\n * One dot, and only the first one: a relation name cannot contain a dot, and\n * everything after it is handed to the target as a single column key. A second\n * dot would be a second hop — `talents.applications.job.title` — which is a\n * different feature (it needs a chain of subqueries, and a chain has to decide\n * what \"some\" means at every level), so it is refused rather than silently read\n * as a column named `job.title` that no table has.\n *\n * Returns `undefined` for a field with no dot, which leaves every existing\n * filter on exactly the path it took before.\n */\nfunction parseRelationFieldPath(field: string): { relationKey: string; fieldKey: string } | undefined {\n const dot = field.indexOf(\".\");\n if (dot <= 0 || dot === field.length - 1) return undefined;\n return { relationKey: field.slice(0, dot), fieldKey: field.slice(dot + 1) };\n}\n\nfunction parseJsonFieldPath(field: string): { columnKey: string; path: string[] } | undefined {\n if (!field.includes(\"->\")) return undefined;\n\n const segments = field.split(/->>?/).map(s => s.trim()).filter(Boolean);\n if (segments.length < 2) return undefined;\n\n const [columnKey, ...path] = segments;\n return { columnKey, path };\n}\n\n/**\n * Filter values may arrive as relation wire objects — `EntityRelation`\n * instances or their JSON form `{ __type: \"relation\", id, path }` — e.g. when\n * the admin filters a relation column. SQL comparisons need the raw id, so\n * unwrap them here (element-wise for list operators like `in` / `not-in`).\n */\nfunction unwrapRelationFilterValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(unwrapRelationFilterValue);\n const relation = normalizeToEntityRelation(value);\n return relation ? relation.id : value;\n}\n\n/**\n * The operand of `in`/`not-in`, as a list.\n *\n * A scalar is the one-element list, because that is what it means and because\n * the wire produces one: `?filter=id.in.5` parses to the string `\"5\"`, not to\n * `[\"5\"]` — the REST dialect only builds an array when the value is\n * parenthesised. Treating that as malformed and dropping the condition turned\n * a perfectly ordinary query into an unfiltered read.\n *\n * The empty list stays empty. Callers must decide what \"no candidates\" means\n * for their operator — it is `FALSE` for `in` and `TRUE` for `not-in` — and\n * neither of those is \"no condition at all\".\n */\nfunction toMembershipList(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [value];\n}\n\n/** Drizzle dynamic query builder — accepts innerJoin + where chaining */\n\nexport interface DrizzleDynamicQuery {\n innerJoin(table: PgTable<any>, condition: SQL): this;\n where(condition: SQL | undefined): this;\n limit(limit: number): this;\n}\n\n/**\n * Unified condition builder for Drizzle/PostgreSQL queries.\n *\n * This class uses static methods and satisfies the ConditionBuilderStatic<SQL> type.\n * It translates Rebase filter conditions to Drizzle SQL conditions.\n *\n * @example\n * const builder: ConditionBuilderStatic<SQL> = DrizzleConditionBuilder;\n */\nexport class DrizzleConditionBuilder {\n\n /**\n * Express \"reachable from this parent through this relation\" as a plain\n * `WHERE` condition on the target table.\n *\n * This is the primitive that lets a relation be a *filter* rather than an\n * addressing scheme. A nested listing used to be served by its own query\n * builder — `fetchEntitiesUsingJoins`, which grew joins the root pipeline\n * did not have and lost the options the root pipeline did have (offset,\n * filter, orderBy, include). Reduced to a condition, the same listing runs\n * through the ordinary collection query, so it inherits all of them and\n * there is one read path instead of two.\n *\n * The shapes:\n * - inverse FK → `target.<fk> = :parentId`, a column comparison.\n * - `through` → `EXISTS (SELECT 1 FROM junction …)`, correlated on the\n * target's key, so the junction never multiplies rows the\n * way an `INNER JOIN` would.\n * - `joinPath` → the same `EXISTS`, with the path's steps joined inside\n * it and the final step correlating to the outer row.\n */\n static buildRelationScopeCondition(\n relation: ResolvedRelation,\n /**\n * Lazy: `via`, `belongsTo`, and a foreign key that points at a\n * `sourceKey` need the parent's own table. A junction and a plain\n * foreign key are expressible from the parent's *id* alone, and\n * requiring the table for them would make a child listing fail on a\n * parent whose table isn't registered.\n *\n * `key` is the parent row's key, every column with its value: those\n * three shapes find the parent row itself, and a lookup on the first\n * column of a composite key finds every row that shares it.\n */\n parent: () => { table: PgTable<any>; key: { column: AnyPgColumn; value: string | number }[] },\n /**\n * The value a plain foreign key or a junction column holds for this\n * parent. One column, so it reaches a single-column key only —\n * `findRelationDefects` refuses those links into a composite one.\n */\n parentId: string | number,\n targetTable: PgTable<any>,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry\n ): SQL {\n // The parent row, inside a subquery that names its table unaliased.\n const parentRow = (key: { column: AnyPgColumn; value: string | number }[]): SQL =>\n sql.join(key.map(({ column, value }) => sql`${column} = ${value}`), sql` AND `);\n\n switch (relation.kind) {\n case \"via\": {\n const { table, key } = parent();\n return this.buildJoinPathScopeCondition(\n relation.joinPath, table, key, targetTable, registry\n );\n }\n\n case \"manyToMany\": {\n const { table: junctionName, sourceColumn, targetColumn } = relation.through;\n const junctionTable = registry.getTable(junctionName);\n if (!junctionTable) {\n throw junctionTableMissing(junctionName);\n }\n const sourceCol = junctionTable[sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const targetCol = junctionTable[targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n if (!sourceCol || !targetCol) {\n throw new Error(\n `Junction columns '${sourceColumn}'/'${targetColumn}' not found in '${junctionName}'`\n );\n }\n // Correlated, not joined: a join through a junction multiplies\n // the target rows by the number of matching links and silently\n // breaks `limit`/`offset`.\n //\n // The junction is aliased and referenced by identifier, never as a\n // Drizzle column. A column object carries no table qualifier of its\n // own — it is rendered against whatever the surrounding builder\n // thinks the current table is — so inside `db.query.findMany`, which\n // aliases the root table, `${sourceCol}` came out qualified with the\n // *target's* alias: `podcast.podcast_id`, a column that does not\n // exist. That aborts the transaction, and the fallback read then\n // fails on the poisoned transaction rather than on anything to do\n // with the relation. Only `targetIdColumn` stays a column object,\n // because that one *must* bind to the outer row to correlate.\n //\n // Aliasing also disambiguates a self-referential many-to-many, where\n // the junction and the target are the same table.\n const junctionAlias = \"__rel_m2m\";\n const junctionRef = (column: AnyPgColumn) =>\n sql`${sql.identifier(junctionAlias)}.${sql.identifier(column.name)}`;\n return sql`EXISTS (SELECT 1 FROM ${junctionTable} AS ${sql.identifier(junctionAlias)} WHERE ${junctionRef(targetCol)} = ${targetIdColumn} AND ${junctionRef(sourceCol)} = ${parentId})`;\n }\n\n case \"hasOne\":\n case \"hasMany\": {\n const fkColumn = relationColumn(targetTable, targetOf(relation), relation.foreignKeyOnTarget);\n if (!fkColumn) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of ` +\n `relation '${relation.relationName}'.`\n );\n }\n if (!relation.sourceKey) return eq(fkColumn, parentId);\n\n // A link on a natural key: the foreign key holds a column of the\n // parent row, not its id, so the parent has to be read. As a\n // subquery rather than a prior SELECT, for the same reason\n // `belongsTo` below is — one statement sees one snapshot, and a\n // scope condition that read the key separately could be built\n // from a value the very next statement no longer agrees with.\n const { table, key } = parent();\n return sql`${fkColumn} = (SELECT ${sql.identifier(relation.sourceKey)} FROM ${table} WHERE ${parentRow(key)})`;\n }\n\n case \"belongsTo\": {\n // The single target row the parent's foreign key points at.\n const { table, key } = parent();\n return sql`${targetIdColumn} = (SELECT ${sql.identifier(relation.localKey)} FROM ${table} WHERE ${parentRow(key)})`;\n }\n\n default: {\n // Exhaustive. There is no \"declares nothing\" case to fall\n // through to any more — every kind names its own link.\n const exhaustive: never = relation;\n throw new Error(`Unknown relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n }\n\n /**\n * `EXISTS` for an explicit `joinPath`.\n *\n * The path is declared source → target. The subquery replays every step but\n * the last from inside, and turns the last one into the correlation with the\n * outer target row — so the target table is never named twice and needs no\n * alias. Each intermediate table is aliased positionally, which keeps a path\n * that revisits a table (a self-referencing many-to-many) unambiguous.\n */\n private static buildJoinPathScopeCondition(\n joinPath: JoinStep[],\n parentTable: PgTable<any>,\n /** The parent row's key, every column with its value. */\n parentKey: { column: AnyPgColumn; value: string | number }[],\n targetTable: PgTable<any>,\n registry: PostgresCollectionRegistry\n ): SQL {\n const sourceAlias = \"__rel_src\";\n const aliasFor = (index: number) => `__rel_j${index}`;\n\n // Column reference against the previous hop: the aliased source for the\n // first step, the previous aliased join table after that.\n const fromRef = (stepIndex: number, column: string) =>\n sql`${sql.identifier(stepIndex === 0 ? sourceAlias : aliasFor(stepIndex - 1))}.${sql.identifier(getColumnName(column))}`;\n\n const pairs = (step: JoinStep): { from: string; to: string }[] => {\n const from = Array.isArray(step.on.from) ? step.on.from : [step.on.from];\n const to = Array.isArray(step.on.to) ? step.on.to : [step.on.to];\n if (from.length !== to.length) {\n throw new Error(`Join step on '${step.table}' has ${from.length} \\`from\\` columns and ${to.length} \\`to\\` columns`);\n }\n return from.map((f, i) => ({ from: f, to: to[i] }));\n };\n\n const inner = joinPath.slice(0, -1);\n const last = joinPath[joinPath.length - 1];\n\n const joins: SQL[] = inner.map((step, index) => {\n const table = registry.getTable(step.table);\n if (!table) throw new Error(`Join table not found: ${step.table}`);\n const on = pairs(step).map(({ from, to }) =>\n sql`${fromRef(index, from)} = ${sql.identifier(aliasFor(index))}.${sql.identifier(getColumnName(to))}`\n );\n return sql`JOIN ${table} AS ${sql.identifier(aliasFor(index))} ON ${sql.join(on, sql` AND `)}`;\n });\n\n // The last step correlates to the outer row instead of joining the\n // target table into the subquery.\n //\n // On the step's own `to` column, which is not always the target's\n // primary key: a one-step path like `{ table: \"posts\", on: { from:\n // \"id\", to: \"author_id\" } }` correlates through a foreign key, and\n // matching `authors.id = posts.id` there compares two unrelated\n // identifiers — which returns nothing, quietly. Referencing the Drizzle\n // column rather than a bare name also keeps it qualified, so it binds\n // to the outer target and not to a table joined inside the EXISTS.\n const targetColumn = (name: string): AnyPgColumn => {\n const column = joinColumn(targetTable, name);\n if (!column) {\n throw new Error(`Join step column '${name}' not found in the target table of this joinPath`);\n }\n return column;\n };\n\n const correlation = sql.join(\n pairs(last).map(({ from, to }) => sql`${fromRef(inner.length, from)} = ${targetColumn(to)}`),\n sql` AND `\n );\n\n const joinsSql = joins.length > 0 ? sql` ${sql.join(joins, sql` `)}` : sql``;\n\n const parentRow = sql.join(\n parentKey.map(({ column, value }) => sql`${sql.identifier(sourceAlias)}.${sql.identifier(column.name)} = ${value}`),\n sql` AND `\n );\n\n return sql`EXISTS (SELECT 1 FROM ${parentTable} AS ${sql.identifier(sourceAlias)}${joinsSql} WHERE ${parentRow} AND ${correlation})`;\n }\n\n /**\n * What a filter field names, or `undefined` if it names nothing.\n *\n * Three ways a field resolves. It may address its column directly; it may\n * be an owning relation, whose foreign key is a column here; or it may be\n * a relation whose link lives on another table entirely, which compiles to\n * a subquery instead of a column. Only a field that resolves to *none* of\n * them is an error, and by default it is one: see\n * {@link UnknownFilterFieldsMode} for why silently dropping it is a\n * data-exposure primitive rather than a convenience.\n *\n * For an owning relation the relation's own `localKey` is the authority,\n * not `<field>_id`. The default local key is `generateForeignKeyName`,\n * which snake-cases *and singularises* — `userProfile` → `user_profile_id`,\n * `users` → `user_id` — and it can be overridden outright. Guessing\n * `<field>_id` therefore misses perfectly ordinary owning relations, and\n * with this resolution failing closed that miss is a 400 on a filter that\n * has nothing wrong with it. The guesses stay, last, for callers that hand\n * over no collection to resolve against.\n *\n * The subquery kinds need a registry and the source table's key column on\n * top of the collection. A caller that supplies neither gets the behaviour\n * it had before they were compilable — unresolvable, and so fail-closed —\n * rather than a half-built condition.\n */\n private static resolveFilterTarget(\n table: PgTable<any>,\n field: string,\n collectionPath: string,\n mode: UnknownFilterFieldsMode,\n options: FilterCompilationOptions\n ): FilterTarget | undefined {\n const { collection, registry, sourceIdColumn } = options;\n\n const columnAt = (key: string): AnyPgColumn | undefined =>\n (key in table ? table[key as keyof typeof table] as AnyPgColumn : undefined) || undefined;\n\n const direct = columnAt(field);\n if (direct) return { kind: \"column\", column: direct };\n\n // Checked after the direct lookup, so a column literally named with an\n // arrow — which Postgres permits, if someone quoted it — still wins.\n const jsonPath = parseJsonFieldPath(field);\n if (jsonPath) {\n const base = columnAt(jsonPath.columnKey);\n if (base) {\n const meta = getColumnMeta(base);\n // Refused rather than compiled: `->>` on a text column is a\n // Postgres error at execution time, which surfaces as a 500 on\n // a request whose only fault is a typo'd column name.\n if (meta.dataType !== \"json\" && meta.columnType !== \"PgJsonb\" && meta.columnType !== \"PgJson\") {\n throw ApiError.badRequest(\n `Cannot filter inside \"${jsonPath.columnKey}\" — it is not a json or jsonb column.`,\n \"INVALID_FILTER_FIELD\"\n );\n }\n return { kind: \"json\", column: base, path: jsonPath.path };\n }\n }\n\n if (collection) {\n const relation = resolveCollectionRelations(collection)[field];\n\n // Owning relation, resolved: the relation names its own local key.\n if (relation?.kind === \"belongsTo\") {\n const foreignKey = relationColumn(table, collection, relation.localKey);\n if (foreignKey) return { kind: \"column\", column: foreignKey };\n }\n\n // The link is on the target table or in a junction. `via` is left\n // out: its join path is authored source → target with no stated\n // inverse, so reversing it into a filter is a different problem\n // from the two shapes below rather than a third case of them.\n if (relation && (hasForeignKeyOnTarget(relation) || isManyToMany(relation)) && registry && sourceIdColumn) {\n // The `EXISTS` correlates the target's foreign key with a column\n // on *this* table, and that is the primary key only when the\n // link joins on it. A `sourceKey` names a different one, and\n // correlating on the id anyway silently matches nothing —\n // \"filter by this relation\" would quietly return zero rows.\n const correlationColumn = hasForeignKeyOnTarget(relation) && relation.sourceKey\n ? relationColumn(table, collection, relation.sourceKey)\n : sourceIdColumn;\n if (!correlationColumn) {\n throw new Error(\n `\\`sourceKey: \"${(relation as ResolvedForeignKeyOnTarget).sourceKey}\"\\` on relation ` +\n `'${relation.relationName}' is not a column on '${collectionPath}', so a filter on ` +\n \"that relation has nothing to correlate against.\"\n );\n }\n return { kind: \"relation\", relation, registry, sourceIdColumn: correlationColumn };\n }\n\n // `applications.status` — a column of the related row rather than\n // its id. Resolved last of the relation shapes, so a relation\n // literally named with a dot still wins above.\n const relationField = parseRelationFieldPath(field);\n if (relationField && registry && sourceIdColumn) {\n const target = this.resolveRelationFieldTarget(\n table, relationField, collection, registry, sourceIdColumn, field, collectionPath\n );\n if (target) return target;\n }\n }\n\n // No collection in hand — the shapes an owning relation's key takes by\n // default (e.g. `project` → `projectId`, `userProfile` →\n // `userProfileId`). The snake forms stay in the list because a project\n // may have authored the property under its column name, which is still\n // its wire name.\n for (const guess of [\n `${field}Id`,\n toWireKey(generateForeignKeyName(field)),\n `${field}_id`,\n generateForeignKeyName(field)\n ]) {\n const foreignKey = columnAt(guess);\n if (foreignKey) return { kind: \"column\", column: foreignKey };\n }\n\n return this.unknownFilterField(table, field, collectionPath, mode, collection);\n }\n\n /**\n * The answer to a filter field that names nothing. Built by the function\n * the REST check answers an `excludeFromApi` field with, so the two cannot\n * be told apart by a byte.\n */\n private static unknownFilterField(\n table: PgTable<any>,\n field: string,\n collectionPath: string,\n mode: UnknownFilterFieldsMode,\n collection: CollectionConfig | undefined\n ): undefined {\n if (mode === \"warn\") {\n logger.warn(`Filtering by field '${field}', but it does not exist in table for collection '${collectionPath}'`);\n return undefined;\n }\n\n // Not `expected`: unlike an anonymous token refresh, this is never a\n // routine outcome. It means a filter key and the schema have drifted\n // apart, which used to widen results silently — exactly the thing an\n // operator wants in the log at warn.\n const answer = unknownFilterField(field, collectionPath, listedColumnKeys(table, collection));\n throw ApiError.badRequest(answer.message, \"UNKNOWN_FILTER_FIELD\", answer.details);\n }\n\n /**\n * `applications.status` — the relation, and the column of the target it\n * addresses.\n *\n * `undefined` when the first segment names no relation: the field simply is\n * not a relation path, and resolution carries on to the guesses and then to\n * the unknown-field answer, which is where a typo belongs. A segment that\n * *does* name a relation is a different matter — the author plainly meant\n * this shape — so everything after that point throws rather than returning,\n * naming what went wrong. Falling through would report \"unknown filter\n * field 'applications.status'\" and list the columns of the wrong table.\n *\n * `via` is refused for the reason it is absent from\n * `filterableRelationKinds`: its join path is authored source → target with\n * no stated inverse, so there is nothing to correlate a subquery back to.\n */\n private static resolveRelationFieldTarget(\n table: PgTable<any>,\n path: { relationKey: string; fieldKey: string },\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry,\n sourceIdColumn: AnyPgColumn,\n field: string,\n collectionPath: string\n ): FilterTarget | undefined {\n const relation = resolveCollectionRelations(collection)[path.relationKey];\n if (!relation) return undefined;\n\n if (relation.kind === \"via\") {\n throw ApiError.badRequest(\n `Cannot filter by '${field}' on collection '${collectionPath}': '${path.relationKey}' is a ` +\n \"`via` relation, whose join path is authored one way only, so there is nothing to correlate \" +\n \"a subquery back to.\",\n \"UNSUPPORTED_RELATION_FILTER\",\n { field, collection: collectionPath, relation: path.relationKey, kind: relation.kind }\n );\n }\n\n const targetCollection = targetOf(relation);\n const targetTable = targetCollection && registry.getTable(getTableName(targetCollection));\n if (!targetCollection || !targetTable) {\n throw new Error(\n `Table not found for the target of relation '${relation.relationName}' on '${collectionPath}', ` +\n `so '${field}' has nothing to filter against.`\n );\n }\n\n const targetColumn = relationColumn(targetTable, targetCollection, path.fieldKey)\n ?? (path.fieldKey in targetTable\n ? targetTable[path.fieldKey as keyof typeof targetTable] as AnyPgColumn\n : undefined);\n if (!targetColumn) {\n const answer = unknownRelationTargetField(\n { field, relationKey: path.relationKey, fieldKey: path.fieldKey },\n { slug: targetCollection.slug, validFields: listedColumnKeys(targetTable, targetCollection) },\n collectionPath\n );\n throw ApiError.badRequest(answer.message, \"UNKNOWN_FILTER_FIELD\", answer.details);\n }\n\n // The column on *this* table the subquery correlates back to. Only\n // `hasMany`/`hasOne` can name a different one; `belongsTo` correlates\n // from its own foreign key, and a many-to-many from the primary key the\n // junction was built against.\n const correlationColumn = relation.kind === \"belongsTo\"\n ? relationColumn(table, collection, relation.localKey)\n : hasForeignKeyOnTarget(relation) && relation.sourceKey\n ? relationColumn(table, collection, relation.sourceKey)\n : sourceIdColumn;\n if (!correlationColumn) {\n throw new Error(\n `Relation '${relation.relationName}' on '${collectionPath}' names a key that is not a column ` +\n `there, so '${field}' has nothing to correlate against.`\n );\n }\n\n return {\n kind: \"relation-field\",\n relation,\n registry,\n sourceIdColumn: correlationColumn,\n targetTable,\n targetColumn\n };\n }\n\n /**\n * Build filter conditions from FilterValues\n */\n static buildFilterConditions<M extends Record<string, unknown>>(\n filter: FilterValues<Extract<keyof M, string>>,\n table: PgTable<any>,\n collectionPath: string,\n options: FilterCompilationOptions = {}\n ): SQL[] {\n const mode = options.unknownFields ?? defaultUnknownFilterFieldsMode;\n const conditions: SQL[] = [];\n\n for (const [field, filterParam] of Object.entries(filter)) {\n if (!filterParam) continue;\n\n const target = this.resolveFilterTarget(table, field, collectionPath, mode, options);\n if (!target) continue;\n\n // One tuple or an array of them — the grammar, read the same way by\n // every compiler. See `toFilterTuples`.\n for (const [op, value] of toFilterTuples(filterParam)) {\n const condition = this.compileFilterTarget(target, op, value, field, collectionPath);\n if (condition) {\n conditions.push(condition);\n }\n }\n }\n\n return conditions;\n }\n\n /**\n * Build logical conditions recursively from LogicalCondition or FilterCondition\n */\n static buildLogicalConditions(\n cond: LogicalCondition | FilterCondition,\n table: PgTable<any>,\n collectionPath: string,\n options: FilterCompilationOptions = {}\n ): SQL | null {\n if (\"type\" in cond) {\n const subSQLs = cond.conditions\n .map(c => this.buildLogicalConditions(c, table, collectionPath, options))\n .filter((sql): sql is SQL => sql !== null);\n if (subSQLs.length === 0) return null;\n if (cond.type === \"or\") return or(...subSQLs) ?? null;\n const conjunction = and(...subSQLs) ?? null;\n // `not` negates the *conjunction* of its conditions — the rule\n // stated on `LogicalCondition`, applied here as a real `NOT (...)`.\n //\n // Not by inverting the operators underneath: SQL is three-valued,\n // so `NOT (a AND b)` and `(NOT a) OR (NOT b)` stop agreeing the\n // moment a NULL is involved, and only one of them is the query the\n // caller wrote. `NOT (status = 'draft')` also excludes rows whose\n // status is NULL, which is what `NOT` means and what a caller\n // reaching for it is asking for.\n if (cond.type === \"not\") return conjunction ? not(conjunction) : null;\n return conjunction;\n } else {\n // A dropped leaf is worse here than in a flat filter: inside an\n // `or(...)` the disjunction loses a branch, so the surviving\n // branches match on their own and the result set widens by\n // everything the dropped leaf would have excluded.\n const target = this.resolveFilterTarget(\n table,\n cond.column,\n collectionPath,\n options.unknownFields ?? defaultUnknownFilterFieldsMode,\n options\n );\n if (!target) return null;\n return this.compileFilterTarget(\n target, cond.operator as WhereFilterOp, cond.value, cond.column, collectionPath\n );\n }\n }\n\n /** Dispatch a resolved filter field onto the shape it actually compiles to. */\n private static compileFilterTarget(\n target: FilterTarget,\n op: WhereFilterOp,\n value: unknown,\n field: string,\n collectionPath: string\n ): SQL | null {\n if (target.kind === \"column\") {\n return this.buildSingleFilterCondition(target.column, op, value);\n }\n if (target.kind === \"json\") {\n return this.buildJsonPathCondition(target.column, target.path, op, value);\n }\n if (target.kind === \"relation-field\") {\n return this.buildRelationFieldCondition(target, op, value, field, collectionPath);\n }\n return this.buildRelationFilterCondition(\n target.relation, op, value, target.sourceIdColumn, target.registry, field, collectionPath\n );\n }\n\n /**\n * A comparison against a value extracted from a json/jsonb column.\n *\n * The path is walked with `->` and the leaf taken with `->>`, so what comes\n * out is always **text**. That is the whole of the type story, and it is\n * the part worth being explicit about, because the alternatives are all\n * worse:\n *\n * - text comparison alone makes `[\"<\", 100]` compare lexically, where\n * `\"9\"` is greater than `\"100\"`;\n * - casting unconditionally makes every filter on a non-numeric value a\n * runtime `invalid input syntax for type numeric` — a 500 on a row whose\n * JSON simply holds a string.\n *\n * So the *filter value* decides. A number on an ordering comparison casts\n * both sides to numeric; everything else compares as text, with booleans\n * rendered the way `->>` renders them (`\"true\"` / `\"false\"`). A row whose\n * JSON holds a non-numeric value at a path being compared numerically is\n * excluded rather than fatal, which is what `IS NOT NULL`-style filtering\n * means everywhere else in this file.\n *\n * The path segments are bound as parameters, never interpolated: they come\n * from a query string, and `->>` takes a text parameter perfectly well.\n */\n private static buildJsonPathCondition(\n column: AnyPgColumn,\n path: string[],\n op: WhereFilterOp,\n value: unknown\n ): SQL | null {\n // Every segment but the last with `->` (staying in json), the last\n // with `->>` (leaving as text).\n let expr: SQL = sql`${column}`;\n for (const key of path.slice(0, -1)) {\n expr = sql`${expr} -> ${key}`;\n }\n const leaf = sql`${expr} ->> ${path[path.length - 1]}`;\n\n const numericComparison = typeof value === \"number\" &&\n (op === \">\" || op === \">=\" || op === \"<\" || op === \"<=\");\n\n if (numericComparison) {\n // The guard is what keeps this from being a 500: rows whose value\n // at this path is not a number are excluded, not fatal.\n const numeric = sql`CASE WHEN ${leaf} ~ '^-?[0-9]+(\\\\.[0-9]+)?$' THEN (${leaf})::numeric END`;\n switch (op) {\n case \">\": return sql`${numeric} > ${value}`;\n case \">=\": return sql`${numeric} >= ${value}`;\n case \"<\": return sql`${numeric} < ${value}`;\n case \"<=\": return sql`${numeric} <= ${value}`;\n }\n }\n\n const asText = (v: unknown): string => typeof v === \"boolean\" ? String(v) : String(v);\n\n switch (op) {\n case \"==\":\n return value === null || value === undefined ? sql`${leaf} IS NULL` : sql`${leaf} = ${asText(value)}`;\n case \"!=\":\n return value === null || value === undefined ? sql`${leaf} IS NOT NULL` : sql`${leaf} != ${asText(value)}`;\n case \">\": return sql`${leaf} > ${asText(value)}`;\n case \">=\": return sql`${leaf} >= ${asText(value)}`;\n case \"<\": return sql`${leaf} < ${asText(value)}`;\n case \"<=\": return sql`${leaf} <= ${asText(value)}`;\n case \"like\": return sql`${leaf} LIKE ${asText(value)}`;\n case \"ilike\": return sql`${leaf} ILIKE ${asText(value)}`;\n case \"not-like\": return sql`${leaf} NOT LIKE ${asText(value)}`;\n case \"not-ilike\": return sql`${leaf} NOT ILIKE ${asText(value)}`;\n case \"is-null\": return sql`${leaf} IS NULL`;\n case \"is-not-null\": return sql`${leaf} IS NOT NULL`;\n case \"in\":\n case \"not-in\": {\n if (value === null || value === undefined) {\n return op === \"in\" ? sql`${leaf} IS NULL` : sql`${leaf} IS NOT NULL`;\n }\n const values = toMembershipList(value).map(asText);\n // Same inversion guard as the column path: an empty list\n // matches nothing, and dropping the condition would match\n // everything.\n if (values.length === 0) return op === \"in\" ? sql`FALSE` : sql`TRUE`;\n const list = sql.join(values.map(v => sql`${v}`), sql`, `);\n return op === \"in\" ? sql`${leaf} IN (${list})` : sql`${leaf} NOT IN (${list})`;\n }\n default:\n // `array-contains` and friends are about the column, not a\n // scalar inside it — `metadata @> '{\"tags\":[\"x\"]}'` is the\n // question, and it is asked of the column directly.\n throw ApiError.badRequest(\n `Operator \"${op}\" is not supported on a JSON path. Use it on the column itself.`,\n \"INVALID_FILTER_OPERATOR\"\n );\n }\n }\n\n /**\n * A filter on a relation that owns no column on this row — `EXISTS` over\n * the rows it reaches.\n *\n * `posts` filtered by `tags == <tagId>` is not a comparison on `posts`; it\n * is a question about the junction:\n *\n * EXISTS (SELECT 1 FROM posts_tags AS j\n * WHERE j.post_id = posts.id AND j.tag_id = <tagId>)\n *\n * which is {@link buildRelationScopeCondition}'s many-to-many shape with\n * source and target swapped — there the junction's *target* column\n * correlates and the source is pinned; here the *source* column correlates\n * and the target is what the filter constrains.\n *\n * `hasMany`/`hasOne` are the same shape one table over: the target row\n * carries the foreign key, so the correlation is on that key and the\n * compared column is the target's own id.\n *\n * `EXISTS` and not a join, for the reason the scope condition gives: a join\n * through a junction multiplies the outer rows by the number of matching\n * links, which duplicates results and silently breaks `limit`/`offset`.\n *\n * Everything inside the subquery is referenced by identifier against a\n * local alias, and only `sourceIdColumn` stays a Drizzle column object —\n * again see {@link buildRelationScopeCondition}, which explains why a\n * column object renders against whatever table the surrounding builder\n * thinks is current and so cannot be used for the inner references. The\n * alias is also what keeps a self-referential relation unambiguous\n * (`categories.children`, or a many-to-many whose junction and target are\n * the same table), where the subquery's table and the outer one coincide.\n */\n static buildRelationFilterCondition(\n relation: ResolvedForeignKeyOnTarget | ResolvedManyToMany,\n op: WhereFilterOp,\n value: unknown,\n sourceIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry,\n field: string,\n collectionPath: string\n ): SQL {\n const alias = \"__rel_filter\";\n const ref = (column: AnyPgColumn) =>\n sql`${sql.identifier(alias)}.${sql.identifier(column.name)}`;\n\n let scanTable: PgTable<any>;\n let correlation: SQL;\n let comparedColumn: AnyPgColumn;\n\n if (relation.kind === \"manyToMany\") {\n const { table: junctionName, sourceColumn, targetColumn } = relation.through;\n const junctionTable = registry.getTable(junctionName);\n if (!junctionTable) {\n throw junctionTableMissing(junctionName);\n }\n const sourceCol = junctionTable[sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const targetCol = junctionTable[targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n if (!sourceCol || !targetCol) {\n throw new Error(\n `Junction columns '${sourceColumn}'/'${targetColumn}' not found in '${junctionName}'`\n );\n }\n scanTable = junctionTable;\n correlation = sql`${ref(sourceCol)} = ${sourceIdColumn}`;\n comparedColumn = targetCol;\n } else {\n const targetCollection = relation.target();\n const targetTable = registry.getTable(getTableName(targetCollection));\n if (!targetTable) {\n throw new Error(\n `Table not found for the target of relation '${relation.relationName}' ` +\n `(collection '${targetCollection.slug}')`\n );\n }\n const fkColumn = relationColumn(targetTable, targetCollection, relation.foreignKeyOnTarget);\n if (!fkColumn) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of ` +\n `relation '${relation.relationName}'.`\n );\n }\n // The filter value is a target row's id, so that is what the\n // subquery compares — the foreign key is spent on the correlation.\n const targetIdColumn = this.primaryKeyColumn(targetTable);\n if (!targetIdColumn) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}', ` +\n `so a filter on it has nothing to match against.`\n );\n }\n scanTable = targetTable;\n correlation = sql`${ref(fkColumn)} = ${sourceIdColumn}`;\n comparedColumn = targetIdColumn;\n }\n\n const { predicate, negate } = this.buildRelationFilterPredicate(\n ref(comparedColumn), op, value, field, collectionPath\n );\n\n const where = predicate ? sql`${correlation} AND ${predicate}` : correlation;\n const exists = sql`EXISTS (SELECT 1 FROM ${scanTable} AS ${sql.identifier(alias)} WHERE ${where})`;\n return negate ? sql`NOT ${exists}` : exists;\n }\n\n /**\n * The inner predicate of a relation filter, and whether the `EXISTS`\n * wrapping it is negated.\n *\n * Negation is `NOT EXISTS` of the *positive* predicate, never `EXISTS` of a\n * negated one. On a many-valued relation the two are different questions:\n * `EXISTS (… AND tag_id != X)` asks \"does some tag differ from X\", which is\n * true of nearly every post with more than one tag and answers nothing\n * anybody asked. `NOT EXISTS (… AND tag_id = X)` asks \"is X absent\", which\n * is what unticking a value in a filter control means — and it makes `==`\n * and `!=` partition the rows, the way a filter implies they do.\n *\n * `is-null`/`is-not-null` drop the predicate entirely: with nothing but the\n * correlation left, they become \"has no related row at all\" and \"has at\n * least one\", which is the only reading of null a link can have.\n *\n * Under RLS, \"no related row\" means *no row this reader can see*. A junction\n * with row-level security but no `SELECT` policy for `rebase_user` is opaque\n * to it, so every row comes back looking unlinked and `is-null` matches all\n * of them. That is not a leak — the outer table's own policies still decide\n * which rows exist at all, and the positive direction correctly returns\n * nothing — but it over-reports, and the cause is a missing junction policy\n * rather than anything here. Rebase derives one for a declared many-to-many;\n * a hand-written schema has to supply it.\n *\n * `in`/`not-in` against a *null value* mean the same thing, rather than\n * membership of an empty list. Membership against null is not a membership\n * question, and the admin's \"filter for null values\" control emits the\n * operator that happens to be selected — on a to-many relation that is\n * always `in` or `not-in`, because those are the only ones the multi-select\n * can produce. Reading `[\"in\", null]` as an empty list would answer \"posts\n * with no tags\" with no posts at all.\n *\n * An empty `in` list compiles to `FALSE` rather than being dropped. Dropped\n * is what the column path does, and dropping a condition widens the result\n * — the whole reason this resolution fails closed. `in []` matches nothing\n * and `not-in []` matches everything, and `NOT EXISTS (… AND FALSE)` gives\n * the second for free.\n *\n * Anything else is rejected. Returning `null` for an operator this cannot\n * express would drop the condition, and the operators the admin offers for\n * a relation are exactly the six below.\n */\n private static buildRelationFilterPredicate(\n ref: SQL,\n op: WhereFilterOp,\n value: unknown,\n field: string,\n collectionPath: string\n ): { predicate?: SQL; negate: boolean } {\n value = unwrapRelationFilterValue(value);\n const isNullish = value === null || value === undefined;\n\n const equals = () => sql`${ref} = ${value}`;\n const inList = () => {\n // Same reading as the column path: a scalar is the one-element\n // list, an empty list is `FALSE`. Here `FALSE` also gives\n // `not-in []` its answer for free — `NOT EXISTS (… AND FALSE)`\n // is true of every row, which is what excluding nothing means.\n const values = toMembershipList(value);\n return values.length === 0\n ? sql`FALSE`\n : sql`${ref} IN (${sql.join(values.map(v => sql`${v}`), sql`, `)})`;\n };\n\n switch (op) {\n case \"==\":\n return isNullish ? { negate: true } : { predicate: equals(), negate: false };\n case \"!=\":\n return isNullish ? { negate: false } : { predicate: equals(), negate: true };\n case \"in\":\n return isNullish ? { negate: true } : { predicate: inList(), negate: false };\n case \"not-in\":\n return isNullish ? { negate: false } : { predicate: inList(), negate: true };\n case \"is-null\":\n return { negate: true };\n case \"is-not-null\":\n return { negate: false };\n // A to-many relation *is* the list, so the array operators ask the\n // same two questions under different names: \"contains X\" is \"some\n // related row is X\", and \"contains any of [X, Y]\" is `in`. They\n // reach here because the admin offers them for a property that is\n // an *array of* relations, and rejecting a question the shape\n // answers perfectly well would put a 400 behind a working control.\n case \"array-contains\":\n return isNullish ? { negate: true } : { predicate: equals(), negate: false };\n case \"array-contains-any\":\n return isNullish ? { negate: true } : { predicate: inList(), negate: false };\n default:\n throw ApiError.badRequest(\n `Operator '${op}' cannot be applied to relation field '${field}' on collection ` +\n `'${collectionPath}'. A relation with no column on this row is filtered by ` +\n \"membership: ==, !=, in, not-in, array-contains, array-contains-any, is-null, \" +\n \"is-not-null.\",\n \"UNSUPPORTED_RELATION_FILTER_OPERATOR\",\n { field, collection: collectionPath, operator: op }\n );\n }\n }\n\n /**\n * A filter on a *column of the related row* — `applications.status`.\n *\n * The same `EXISTS` {@link buildRelationFilterCondition} builds, with the\n * predicate moved off the target's id and onto one of its columns:\n *\n * EXISTS (SELECT 1 FROM talent_applications AS t\n * WHERE t.talent_id = talents.id\n * AND t.status IN ('applied', 'reviewing', 'interview'))\n *\n * which is the shape every \"who is waiting\" queue is written in. Without\n * it the only way to ask is to fetch every row and filter in the browser,\n * and a filter the client applies after paging is not a filter — the page\n * was already chosen without it.\n *\n * A many-to-many needs one more table than the id filter does. That one\n * stops at the junction, because the junction already holds the value it\n * compares; a column of the target is a table further out, so the subquery\n * joins the target to the junction and correlates from the junction. The\n * join is inside `EXISTS`, so it cannot multiply the outer rows the way a\n * top-level join through a junction would.\n *\n * `belongsTo` is included even though its foreign key is a column here:\n * `author.name` is a column of another table either way, and refusing the\n * one relation kind that reads most naturally would be a rule about\n * implementation rather than about meaning.\n *\n * Under RLS the subquery runs as the reader, so it sees the target rows\n * that reader's policies allow and no others. On the positive direction\n * that is exactly right. On the negative — `!=`, `not-in`, and any\n * `NOT EXISTS` — \"no related row satisfies this\" and \"no related row this\n * reader can see satisfies this\" are the same sentence, so a target table\n * with row-level security and no `SELECT` policy for `rebase_user` makes\n * every row look unmatched and the negative filter over-reports. Nothing is\n * leaked: the outer table's own policies still decide which rows exist. The\n * cause is a missing policy on the target rather than anything here, and it\n * is the same caveat the id-filter path carries.\n */\n static buildRelationFieldCondition(\n target: Extract<FilterTarget, { kind: \"relation-field\" }>,\n op: WhereFilterOp,\n value: unknown,\n field: string,\n collectionPath: string\n ): SQL {\n const alias = \"__rel_field\";\n const { relation, targetTable, targetColumn } = target;\n const ref = sql`${sql.identifier(alias)}.${sql.identifier(targetColumn.name)}`;\n\n const { predicate, negate } = this.buildRelationColumnPredicate(\n ref, targetColumn, op, value, field, collectionPath\n );\n\n // Everything inside the subquery is referenced by identifier against a\n // local alias — see `buildRelationScopeCondition` for why a Drizzle\n // column object cannot be used there. Only `sourceIdColumn` stays a\n // column object, which is what binds it to the *outer* row.\n let from: SQL;\n let correlation: SQL;\n\n if (relation.kind === \"manyToMany\") {\n const { table: junctionName, sourceColumn, targetColumn: junctionTargetColumn } = relation.through;\n const junctionTable = target.registry.getTable(junctionName);\n if (!junctionTable) {\n throw junctionTableMissing(junctionName);\n }\n const sourceCol = junctionTable[sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const targetCol = junctionTable[junctionTargetColumn as keyof typeof junctionTable] as AnyPgColumn;\n if (!sourceCol || !targetCol) {\n throw new Error(\n `Junction columns '${sourceColumn}'/'${junctionTargetColumn}' not found in '${junctionName}'`\n );\n }\n const targetKey = this.primaryKeyColumn(targetTable);\n if (!targetKey) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}', ` +\n `so '${field}' has nothing to join the junction against.`\n );\n }\n const junctionAlias = \"__rel_field_junction\";\n from = sql`${targetTable} AS ${sql.identifier(alias)} INNER JOIN ${junctionTable} AS ${sql.identifier(junctionAlias)} ON ${sql.identifier(junctionAlias)}.${sql.identifier(targetCol.name)} = ${sql.identifier(alias)}.${sql.identifier(targetKey.name)}`;\n correlation = sql`${sql.identifier(junctionAlias)}.${sql.identifier(sourceCol.name)} = ${target.sourceIdColumn}`;\n } else if (relation.kind === \"belongsTo\") {\n const targetKey = this.primaryKeyColumn(targetTable);\n if (!targetKey) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}', ` +\n `so '${field}' has nothing to correlate against.`\n );\n }\n from = sql`${targetTable} AS ${sql.identifier(alias)}`;\n correlation = sql`${sql.identifier(alias)}.${sql.identifier(targetKey.name)} = ${target.sourceIdColumn}`;\n } else {\n const foreignKey = relationColumn(targetTable, targetOf(relation), relation.foreignKeyOnTarget);\n if (!foreignKey) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of ` +\n `relation '${relation.relationName}'.`\n );\n }\n from = sql`${targetTable} AS ${sql.identifier(alias)}`;\n correlation = sql`${sql.identifier(alias)}.${sql.identifier(foreignKey.name)} = ${target.sourceIdColumn}`;\n }\n\n const where = predicate ? sql`${correlation} AND ${predicate}` : correlation;\n const exists = sql`EXISTS (SELECT 1 FROM ${from} WHERE ${where})`;\n return negate ? sql`NOT ${exists}` : exists;\n }\n\n /**\n * The inner predicate of a relation *column* filter, and whether the\n * `EXISTS` wrapping it is negated.\n *\n * The negation rule is the one {@link buildRelationFilterPredicate} states\n * and holds for exactly the same reason, one column over. A negative\n * operator is `NOT EXISTS` of the **positive** predicate, never `EXISTS` of\n * a negated one: `EXISTS (… AND status != 'hired')` asks \"does some\n * application differ from hired\", which is true of nearly every candidate\n * with more than one application and answers nothing anybody asked.\n * `NOT EXISTS (… AND status = 'hired')` asks \"is there no hired\n * application\", which is what unticking a value means — and it makes `==`\n * and `!=` partition the rows, the way a filter implies they do.\n *\n * `is-null` and `is-not-null` are the exception, and deliberately not a\n * complementary pair here. On a column they compile to `EXISTS (… AND col\n * IS NULL)` and `EXISTS (… AND col IS NOT NULL)` — \"has a related row whose\n * column is unset\" and \"has one where it is set\" — which is the plain\n * reading of `applications.status is-not-null` and the useful one. They are\n * both true of a candidate with two applications, one of each. Making\n * `is-not-null` the negation instead would make it \"no application has an\n * unset status\", which is true of a candidate with no applications at all\n * and so answers a queue with the very rows the queue exists to exclude.\n *\n * Unlike the id path, every operator is available: the compared value is an\n * ordinary column, so `>=` on a date and `ilike` on a name mean here what\n * they mean anywhere else. Only an operator that does not exist is refused,\n * and it throws rather than returning `null` — a dropped condition widens\n * the read, which is the whole reason this file fails closed.\n */\n private static buildRelationColumnPredicate(\n ref: SQL,\n column: AnyPgColumn,\n op: WhereFilterOp,\n value: unknown,\n field: string,\n collectionPath: string\n ): { predicate?: SQL; negate: boolean } {\n value = unwrapRelationFilterValue(value);\n const isNullish = value === null || value === undefined;\n\n const equals = () => sql`${ref} = ${value}`;\n const inList = (): SQL => {\n const values = toMembershipList(value);\n // An empty list matches nothing, and `NOT EXISTS (… AND FALSE)`\n // gives `not-in []` — which excludes nothing — for free. Dropping\n // the condition instead would match everything.\n return values.length === 0\n ? sql`FALSE`\n : sql`${ref} IN (${sql.join(values.map(v => sql`${v}`), sql`, `)})`;\n };\n const isNull = () => sql`${ref} IS NULL`;\n const contains = (): SQL => {\n const meta = getColumnMeta(column);\n const isNativeArray = meta.dataType === \"array\" || meta.columnType === \"PgArray\";\n if (op === \"array-contains-any\") {\n if (Array.isArray(value) && value.length === 0) return sql`FALSE`;\n if (Array.isArray(value) && value.length > 0) {\n return isNativeArray\n ? sql`${ref} && ARRAY[${sql.join(value.map(v => sql`${v}`), sql`, `)}]`\n : sql`${ref} ?| array[${sql.join(value.map(v => sql`${String(v)}`), sql`, `)}]`;\n }\n }\n return isNativeArray\n ? sql`${ref} @> ARRAY[${value}]`\n : sql`${ref} @> ${JSON.stringify([value])}`;\n };\n\n switch (op) {\n case \"==\":\n return { predicate: isNullish ? isNull() : equals(), negate: false };\n case \"!=\":\n return { predicate: isNullish ? isNull() : equals(), negate: true };\n case \">\":\n return { predicate: sql`${ref} > ${value}`, negate: false };\n case \">=\":\n return { predicate: sql`${ref} >= ${value}`, negate: false };\n case \"<\":\n return { predicate: sql`${ref} < ${value}`, negate: false };\n case \"<=\":\n return { predicate: sql`${ref} <= ${value}`, negate: false };\n case \"in\":\n return { predicate: isNullish ? isNull() : inList(), negate: false };\n case \"not-in\":\n return { predicate: isNullish ? isNull() : inList(), negate: true };\n case \"like\":\n case \"not-like\":\n case \"ilike\":\n case \"not-ilike\": {\n // A pattern is only defined on text (an enum is matched by its\n // label — see `patternOperand`). Asking it of a date or an\n // integer is a Postgres error at execution time — a 500 on a\n // request whose only fault is an operator the admin offered\n // for the wrong column.\n const operand = patternOperand(column, ref);\n if (!operand) {\n throw ApiError.badRequest(\n `Operator '${op}' cannot be applied to '${field}' on collection '${collectionPath}': ` +\n `'${column.name}' is ${column.getSQLType?.() ?? \"not a text column\"}, and pattern ` +\n \"matching is only defined on text.\",\n \"UNSUPPORTED_RELATION_FILTER_OPERATOR\",\n { field, collection: collectionPath, operator: op }\n );\n }\n const like = op === \"like\" || op === \"not-like\" ? sql`LIKE` : sql`ILIKE`;\n return { predicate: sql`${operand} ${like} ${String(value)}`, negate: op.startsWith(\"not-\") };\n }\n case \"is-null\":\n return { predicate: isNull(), negate: false };\n case \"is-not-null\":\n return { predicate: sql`${ref} IS NOT NULL`, negate: false };\n case \"array-contains\":\n case \"array-contains-any\":\n return { predicate: contains(), negate: false };\n default:\n throw ApiError.badRequest(\n `Unknown filter operator '${op}'. Valid operators: ${ALL_WHERE_FILTER_OPS.join(\", \")}.`,\n \"UNKNOWN_FILTER_OPERATOR\",\n { operator: op, validOperators: ALL_WHERE_FILTER_OPS }\n );\n }\n }\n\n /**\n * An aggregate over the rows a relation reaches, as a scalar expression —\n * what `orderBy: [{ relation: \"applications\", field: \"created_at\", agg:\n * \"min\" }, \"asc\"]` compiles to.\n *\n * (SELECT min(t.created_at) FROM talent_applications AS t\n * WHERE t.talent_id = talents.id)\n *\n * A correlated scalar subquery rather than a `LEFT JOIN LATERAL`: the join\n * would have to be threaded into a query the relational query builder\n * assembles, while a scalar expression drops straight into `ORDER BY` and\n * into the keyset comparison behind cursor paging — which has to be the\n * *same* expression, or paging and ordering disagree and rows are skipped.\n *\n * `correlateTo` is what the subquery is pinned against. Left out, it is the\n * outer row's key column and the expression is correlated in the ordinary\n * way. Given a literal — the cursor row's id — the subquery stops being\n * correlated at all, so Postgres evaluates it once for the whole statement\n * rather than per row. That is how a cursor pages over an aggregate it has\n * no stored value for: the value is recomputed from the id it does have.\n *\n * Over zero related rows `count` is 0 and every other function is NULL,\n * which is what puts \"nobody waiting\" at a defined end of the order rather\n * than wherever a missing value would land. See `buildOrderExpressions` for\n * where that end is pinned.\n *\n * Under RLS the subquery runs as the reader, so a related row the reader\n * cannot see does not contribute — an aggregate is over the rows that\n * reader can see, which is the only total it could honestly report.\n */\n static buildRelationAggregateExpression(\n spec: RelationAggregateSort,\n table: PgTable<any>,\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry,\n sourceIdColumn: AnyPgColumn,\n collectionPath: string,\n correlateTo?: unknown\n ): SQL {\n // A relation the API does not have is not a relation there, for this\n // caller — the same answer, byte for byte, as one never declared.\n const relation = namesHiddenFromCaller(collection).has(spec.relation)\n ? undefined\n : resolveCollectionRelations(collection)[spec.relation];\n if (!relation) {\n throw ApiError.badRequest(\n `Cannot sort by '${encodeRelationAggregateSort(spec)}' on collection '${collectionPath}': ` +\n `'${spec.relation}' is not a relation there.`,\n \"UNKNOWN_ORDER_BY_FIELD\",\n { field: encodeRelationAggregateSort(spec), collection: collectionPath, relation: spec.relation }\n );\n }\n if (relation.kind === \"via\") {\n throw ApiError.badRequest(\n `Cannot sort by '${encodeRelationAggregateSort(spec)}' on collection '${collectionPath}': ` +\n \"`via` relations are authored one way only, so there is nothing to correlate a subquery back to.\",\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: encodeRelationAggregateSort(spec), collection: collectionPath, kind: relation.kind }\n );\n }\n\n const targetCollection = targetOf(relation);\n const targetTable = targetCollection && registry.getTable(getTableName(targetCollection));\n if (!targetCollection || !targetTable) {\n throw new Error(\n `Table not found for the target of relation '${relation.relationName}' on '${collectionPath}', ` +\n \"so there is nothing to aggregate.\"\n );\n }\n\n const alias = \"__rel_agg\";\n // `count` with no field counts the related rows themselves. Every other\n // function needs something to aggregate, and a key that named no column\n // never got this far — `parseRelationAggregateSort` refuses it.\n let aggregand: SQL = sql`*`;\n if (spec.field) {\n const column = namesHiddenFromCaller(targetCollection).has(spec.field)\n ? undefined\n : relationColumn(targetTable, targetCollection, spec.field)\n ?? (spec.field in targetTable\n ? targetTable[spec.field as keyof typeof targetTable] as AnyPgColumn\n : undefined);\n if (!column) {\n const validFields = listedColumnKeys(targetTable, targetCollection);\n throw ApiError.badRequest(\n `Unknown field '${spec.field}' on '${targetCollection.slug}', the target of relation ` +\n `'${spec.relation}' on collection '${collectionPath}'` +\n (validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\"),\n \"UNKNOWN_ORDER_BY_FIELD\",\n {\n field: encodeRelationAggregateSort(spec),\n collection: collectionPath,\n relation: spec.relation,\n targetCollection: targetCollection.slug,\n ...(validFields.length > 0 && { validFields })\n }\n );\n }\n aggregand = sql`${sql.identifier(alias)}.${sql.identifier(column.name)}`;\n } else if (spec.agg !== \"count\") {\n throw ApiError.badRequest(\n `'${spec.agg}' needs a field to aggregate — only 'count' means something on its own.`,\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: encodeRelationAggregateSort(spec), collection: collectionPath }\n );\n }\n\n // The literal pins the subquery to one row; the column object binds it\n // to the outer row, because a Drizzle column renders qualified with its\n // own table. See `buildRelationScopeCondition`.\n const source: SQL = correlateTo === undefined ? sql`${sourceIdColumn}` : sql`${correlateTo}`;\n\n let from: SQL;\n let correlation: SQL;\n\n if (relation.kind === \"manyToMany\") {\n const { table: junctionName, sourceColumn, targetColumn } = relation.through;\n const junctionTable = registry.getTable(junctionName);\n if (!junctionTable) {\n throw junctionTableMissing(junctionName);\n }\n const sourceCol = junctionTable[sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const targetCol = junctionTable[targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n if (!sourceCol || !targetCol) {\n throw new Error(\n `Junction columns '${sourceColumn}'/'${targetColumn}' not found in '${junctionName}'`\n );\n }\n const targetKey = this.primaryKeyColumn(targetTable);\n if (!targetKey) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}'.`\n );\n }\n const junctionAlias = \"__rel_agg_junction\";\n from = sql`${targetTable} AS ${sql.identifier(alias)} INNER JOIN ${junctionTable} AS ${sql.identifier(junctionAlias)} ON ${sql.identifier(junctionAlias)}.${sql.identifier(targetCol.name)} = ${sql.identifier(alias)}.${sql.identifier(targetKey.name)}`;\n correlation = sql`${sql.identifier(junctionAlias)}.${sql.identifier(sourceCol.name)} = ${source}`;\n } else if (relation.kind === \"belongsTo\") {\n const targetKey = this.primaryKeyColumn(targetTable);\n const localKey = relationColumn(table, collection, relation.localKey);\n if (!targetKey || !localKey) {\n throw new Error(\n `Relation '${relation.relationName}' on '${collectionPath}' names a key that is not a column, ` +\n \"so there is nothing to aggregate against.\"\n );\n }\n from = sql`${targetTable} AS ${sql.identifier(alias)}`;\n // A to-one reaches one row, so the aggregate is that row's value.\n // Pinning by a literal cursor id means looking the key up on the\n // cursor row rather than reading it off the outer one.\n const owner: SQL = correlateTo === undefined\n ? sql`${localKey}`\n : sql`(SELECT ${sql.identifier(localKey.name)} FROM ${table} WHERE ${sourceIdColumn} = ${correlateTo})`;\n correlation = sql`${sql.identifier(alias)}.${sql.identifier(targetKey.name)} = ${owner}`;\n } else {\n const foreignKey = relationColumn(targetTable, targetCollection, relation.foreignKeyOnTarget);\n if (!foreignKey) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of ` +\n `relation '${relation.relationName}'.`\n );\n }\n // `sourceKey` names a column other than the primary key when the\n // link joins on one; correlating on the id anyway aggregates over\n // nothing and every row sorts as though it had no related rows.\n const sourceKeyColumn = relation.sourceKey\n ? relationColumn(table, collection, relation.sourceKey)\n : sourceIdColumn;\n if (!sourceKeyColumn) {\n throw new Error(\n `\\`sourceKey: \"${relation.sourceKey}\"\\` on relation '${relation.relationName}' is not a ` +\n `column on '${collectionPath}'.`\n );\n }\n const pinned: SQL = correlateTo === undefined\n ? sql`${sourceKeyColumn}`\n : relation.sourceKey\n ? sql`(SELECT ${sql.identifier(sourceKeyColumn.name)} FROM ${table} WHERE ${sourceIdColumn} = ${correlateTo})`\n : sql`${correlateTo}`;\n from = sql`${targetTable} AS ${sql.identifier(alias)}`;\n correlation = sql`${sql.identifier(alias)}.${sql.identifier(foreignKey.name)} = ${pinned}`;\n }\n\n // The function name is not interpolated from input: it comes off a\n // five-member union the parser validated, and is written out here so\n // nothing string-shaped reaches the statement.\n const aggregate = AGGREGATE_SQL[spec.agg];\n return sql`(SELECT ${aggregate}(${aggregand}) FROM ${from} WHERE ${correlation})`;\n }\n\n /** The column a table's rows are keyed by: its primary key, else `id`. */\n private static primaryKeyColumn(table: PgTable<any>): AnyPgColumn | undefined {\n return (Object.values(table).find((col: Record<string, unknown>) => col.primary)\n ?? Object.values(table).find((col: Record<string, unknown>) => col.name === \"id\")) as AnyPgColumn | undefined;\n }\n\n /**\n * Build a single filter condition for a specific operator and value\n */\n static buildSingleFilterCondition(\n column: AnyPgColumn,\n op: WhereFilterOp,\n value: unknown\n ): SQL | null {\n value = unwrapRelationFilterValue(value);\n switch (op) {\n case \"==\":\n if (value === null || value === undefined) {\n return sql`${column} IS NULL`;\n }\n return eq(column, value);\n case \"!=\":\n if (value === null || value === undefined) {\n return sql`${column} IS NOT NULL`;\n }\n return sql`${column} != ${value}`;\n case \">\":\n return sql`${column} > ${value}`;\n case \">=\":\n return sql`${column} >= ${value}`;\n case \"<\":\n return sql`${column} < ${value}`;\n case \"<=\":\n return sql`${column} <= ${value}`;\n case \"in\": {\n // Membership against a null *value* is a null check, not an\n // empty list — the admin's \"filter for null values\" control\n // emits whichever operator is selected, so `[\"in\", null]` is\n // how it asks for a null foreign key when the user picked\n // `in`. Reading it as an empty list dropped the condition\n // outright, which widened the read to every row.\n if (value === null || value === undefined) {\n return sql`${column} IS NULL`;\n }\n const values = toMembershipList(value);\n // An empty list matches nothing. Returning no condition — what\n // this did — matches *everything*, which is the same inversion\n // one layer down from the one `UnknownFilterFieldsMode` exists\n // for. It is the dangerous shape too: `filter: { id: [\"in\",\n // teamIds] }` with no teams is how a caller asks for nothing,\n // and it answered with the whole table.\n return values.length === 0 ? sql`FALSE` : inArray(column, values);\n }\n case \"array-contains\": {\n const meta = getColumnMeta(column);\n if (meta.dataType === \"array\" || meta.columnType === \"PgArray\") {\n return sql`${column} @> ARRAY[${value}]`;\n }\n // For JSONB arrays: checks if the column contains the given value\n return sql`${column} @> ${JSON.stringify([value])}`;\n }\n case \"array-contains-any\": {\n const meta = getColumnMeta(column);\n const isNativeArray = meta.dataType === \"array\" || meta.columnType === \"PgArray\";\n // \"Overlaps nothing\" is false, not a licence to skip the\n // condition. The single-value fallback below is for a *scalar*\n // operand; an empty array fell into it and built\n // `@> ARRAY[$1]` around an empty binding.\n if (Array.isArray(value) && value.length === 0) {\n return sql`FALSE`;\n }\n if (Array.isArray(value) && value.length > 0) {\n if (isNativeArray) {\n return sql`${column} && ARRAY[${sql.join(value.map(v => sql`${v}`), sql`, `)}]`;\n } else {\n // Use the ?| operator for JSONB overlap with text array\n const textValues = value.map(v => String(v));\n return sql`${column} ?| array[${sql.join(textValues.map(v => sql`${v}`), sql`, `)}]`;\n }\n }\n // Single value fallback: treat as array-contains\n if (isNativeArray) {\n return sql`${column} @> ARRAY[${value}]`;\n }\n return sql`${column} @> ${JSON.stringify([value])}`;\n }\n case \"not-in\": {\n // The mirror of `in` above, including the empty list — which\n // excludes nothing, and so matches every row. Same condition\n // the old code produced by accident, now on purpose and for\n // the empty list only.\n if (value === null || value === undefined) {\n return sql`${column} IS NOT NULL`;\n }\n const values = toMembershipList(value);\n if (values.length === 0) return sql`TRUE`;\n return sql`${column} NOT IN (${sql.join(values.map(v => sql`${v}`), sql`, `)})`;\n }\n case \"like\":\n case \"ilike\":\n case \"not-like\":\n case \"not-ilike\": {\n // See `patternOperand`: an enum is matched by its label, and a\n // pattern over a number or a date is the caller's mistake, not\n // a Postgres error to pass on as a 500.\n const operand = patternOperand(column, column);\n if (!operand) {\n throw ApiError.badRequest(\n `Operator '${op}' cannot be applied to '${column.name}': it is `\n + `${column.getSQLType?.() ?? \"not a text column\"}, and pattern matching is only defined on text.`,\n \"INVALID_FILTER_OPERATOR\",\n { column: column.name, operator: op }\n );\n }\n const like = op === \"like\" || op === \"not-like\" ? sql`LIKE` : sql`ILIKE`;\n return op.startsWith(\"not-\")\n ? sql`${operand} NOT ${like} ${String(value)}`\n : sql`${operand} ${like} ${String(value)}`;\n }\n case \"is-null\":\n return sql`${column} IS NULL`;\n case \"is-not-null\":\n return sql`${column} IS NOT NULL`;\n default:\n // The relation path five hundred lines up already refuses this,\n // and says why: \"returning `null` for an operator this cannot\n // express would drop the condition\", and a dropped condition\n // widens the result. The column path is its twin and kept the\n // warning — so `{ status: [\"contains\", \"x\"] }` filtered on\n // nothing and answered 200 with every row, which reads as data\n // that matched.\n //\n // The wire layer rejects operator-shaped unknowns before they\n // arrive (`UnknownFilterOperatorError`, 400). What reaches here\n // came from in-process `rebase.dataAsAdmin`, a stored filter preset or\n // a config — none of them typechecked at the call site, all of\n // them able to name an operator that no longer exists.\n throw ApiError.badRequest(\n `Unknown filter operator '${op}'. Valid operators: ${ALL_WHERE_FILTER_OPS.join(\", \")}.`,\n \"UNKNOWN_FILTER_OPERATOR\",\n { operator: op, validOperators: ALL_WHERE_FILTER_OPS }\n );\n }\n }\n\n /**\n * Build relation-based conditions for different relation types\n */\n /**\n * Joins and where-conditions that reach a relation's target rows.\n *\n * One case per kind. This used to be a chain of six `else if`s over\n * `cardinality`/`direction`/`through`, ending in\n * `findCorrespondingJunctionTable` — a search through the *target's* own\n * relations to work out whether an \"inverse many\" was a one-to-many or the\n * far side of a junction. That search is gone: the kind says which it is.\n *\n * The owning/inverse split for junctions is gone too. Both variants built\n * the identical condition — `through` is always written from the declaring\n * side's point of view — so the second was a distinction without a\n * difference and one of the places the two could drift apart.\n */\n static buildRelationConditions(\n relation: ResolvedRelation,\n parentId: string | number | (string | number)[],\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry\n ): {\n joinConditions: { table: PgTable<any>; condition: SQL }[];\n whereConditions: SQL[];\n } {\n const joinConditions: { table: PgTable<any>; condition: SQL }[] = [];\n const whereConditions: SQL[] = [];\n\n switch (relation.kind) {\n case \"via\": {\n const { joins, finalCondition } = this.buildJoinPathConditions(\n relation.joinPath, targetTable, parentTable, parentIdColumn, parentId, registry\n );\n joinConditions.push(...joins);\n whereConditions.push(finalCondition);\n break;\n }\n\n case \"manyToMany\": {\n const junctionResult = this.buildJunctionTableConditions(\n relation.through, targetIdColumn, parentId, registry\n );\n joinConditions.push(junctionResult.join);\n whereConditions.push(junctionResult.condition);\n break;\n }\n\n case \"hasOne\":\n case \"hasMany\":\n case \"belongsTo\":\n whereConditions.push(\n this.buildSimpleRelationCondition(relation, targetTable, parentTable, parentId)\n );\n break;\n\n default: {\n const exhaustive: never = relation;\n throw new Error(`Unknown relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n\n return { joinConditions,\nwhereConditions };\n }\n\n /**\n * Build conditions for join path relations\n */\n private static buildJoinPathConditions(\n joinPath: JoinStep[],\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n parentId: string | number | (string | number)[],\n registry: PostgresCollectionRegistry\n ): {\n joins: { table: PgTable<any>; condition: SQL }[];\n finalCondition: SQL;\n } {\n const joins: { table: PgTable<any>; condition: SQL }[] = [];\n let currentTable = targetTable;\n const sourceTableName = drizzleTableName(parentTable);\n\n // Process join steps in reverse order to build path back to parent\n for (let index = joinPath.length - 1; index >= 0; index--) {\n const joinStep = joinPath[index];\n const { fromTableName, toTableName } = this.joinStepTableNames(joinPath, index, sourceTableName);\n const fromColName = this.getColumnNamesFromColumns(joinStep.on.from)[0];\n const toColName = this.getColumnNamesFromColumns(joinStep.on.to)[0];\n\n const fromTable = registry.getTable(fromTableName);\n const toTable = registry.getTable(toTableName);\n\n if (!fromTable || !toTable) {\n throw new Error(`Join tables not found for step: from ${fromTableName} to ${toTableName}`);\n }\n\n const {\n joinTable,\n condition,\n additionalJoins\n } = this.buildSingleJoinCondition(\n currentTable,\n fromTable,\n toTable,\n fromColName,\n toColName,\n fromTableName,\n toTableName,\n registry\n );\n\n joins.push({\n table: joinTable,\n condition\n });\n currentTable = joinTable;\n\n // Add any additional joins needed for many-to-many relationships\n if (additionalJoins && additionalJoins.length > 0) {\n joins.push(...additionalJoins);\n }\n }\n\n // Ensure we've connected back to the parent table\n // For junction tables, we might end up at the junction table instead of the parent table\n if (currentTable !== parentTable) {\n // `getTableName` is Drizzle's own accessor, and both of these are\n // declared `PgTable<any>`, so there is nothing to assert.\n //\n // What stood here reached for the name four ways — the\n // `Symbol.for(\"drizzle:Name\")` slot, then `._.name`, `.tableName`\n // and `.name` — through eight `as unknown as` casts and a\n // `\"unknown\"` sentinel for when all four missed. `getTableName`\n // reads the first of those and nothing else, because on a real\n // `PgTable` the other three are all `undefined`; they were dead\n // branches, and the casts were what kept anyone from seeing that\n // (each `as string` on a `||` chain asserts a type for a value the\n // compiler was told nothing about).\n const currentTableName = drizzleTableName(currentTable);\n const parentTableName = drizzleTableName(parentTable);\n\n // Junction tables are named for the two ends they join, so an\n // underscore is the signal that this walk plausibly stopped on one\n // rather than going wrong. The two `=== \"unknown\"` arms that used\n // to sit beside it went with the sentinel: a name that could not be\n // read was never a case that could arise here, only one the\n // fallback chain made it look like it could.\n if (!currentTableName.includes(\"_\")) {\n throw new Error(`Join path did not result in connecting to parent table. Current: ${currentTableName}, Parent: ${parentTableName}`);\n }\n }\n\n // Handle both single ID and array of IDs\n const finalCondition = Array.isArray(parentId)\n ? inArray(parentIdColumn, parentId)\n : eq(parentIdColumn, parentId);\n\n return {\n joins,\n finalCondition\n };\n }\n\n /**\n * Build a single join condition between tables\n */\n private static buildSingleJoinCondition(\n currentTable: PgTable<any>,\n fromTable: PgTable<any>,\n toTable: PgTable<any>,\n fromColName: string,\n toColName: string,\n fromTableName: string,\n toTableName: string,\n registry?: PostgresCollectionRegistry\n ): { joinTable: PgTable<any>; condition: SQL; additionalJoins?: { table: PgTable<any>; condition: SQL }[] } {\n let joinTable: PgTable<any>;\n let condition: SQL;\n const additionalJoins: { table: PgTable<any>; condition: SQL }[] = [];\n\n if (currentTable === toTable) {\n // current -> toTable, so join the fromTable\n // `getTableColumns` is Drizzle's own accessor and is already how the\n // rest of this file reads a column map. Indexing the table object\n // directly — which is what the two casts here were for — also\n // reaches its methods and symbols, so a relation whose column is\n // named `name` or `enableRLS` got one of those back and passed the\n // `!left || !right` guard below with something that is not a column.\n const left = joinColumn(fromTable, fromColName);\n const right = joinColumn(currentTable, toColName);\n\n if (!left || !right) {\n // Check if this might be a many-to-many relationship requiring a junction table\n if (registry) {\n const junctionResult = this.tryBuildJunctionJoin(\n currentTable,\n fromTable,\n fromColName,\n toColName,\n fromTableName,\n toTableName,\n registry\n );\n if (junctionResult) {\n return junctionResult;\n }\n }\n throw new Error(`Join columns not found: ${fromTableName}.${fromColName} = ${toTableName}.${toColName}`);\n }\n\n joinTable = fromTable;\n condition = eq(left, right);\n } else if (currentTable === fromTable) {\n // current -> fromTable, so join the toTable\n const left = joinColumn(toTable, toColName);\n const right = joinColumn(currentTable, fromColName);\n\n if (!left || !right) {\n // Check if this might be a many-to-many relationship requiring a junction table\n if (registry) {\n const junctionResult = this.tryBuildJunctionJoin(\n currentTable,\n toTable,\n fromColName,\n toColName,\n fromTableName,\n toTableName,\n registry\n );\n if (junctionResult) {\n return junctionResult;\n }\n }\n throw new Error(`Join columns not found: ${toTableName}.${toColName} = ${fromTableName}.${fromColName}`);\n }\n\n joinTable = toTable;\n condition = eq(left, right);\n } else {\n throw new Error(`Join step does not match current table. Current table does not match from: ${fromTableName} or to: ${toTableName}`);\n }\n\n return {\n joinTable,\n condition,\n additionalJoins\n };\n }\n\n /**\n * Try to build a junction table join when direct foreign key relationship is not found\n */\n private static tryBuildJunctionJoin(\n currentTable: PgTable<any>,\n targetTable: PgTable<any>,\n fromColName: string,\n toColName: string,\n fromTableName: string,\n toTableName: string,\n registry: PostgresCollectionRegistry\n ): { joinTable: PgTable<any>; condition: SQL; additionalJoins: { table: PgTable<any>; condition: SQL }[] } | null {\n // Try to find a junction table that connects these two tables\n // Common naming patterns: table1_table2, table1Table2, etc.\n const possibleJunctionNames = [\n `${fromTableName}_${toTableName}`,\n `${toTableName}_${fromTableName}`,\n `${fromTableName}${toTableName.charAt(0).toUpperCase() + toTableName.slice(1)}`,\n `${toTableName}${fromTableName.charAt(0).toUpperCase() + fromTableName.slice(1)}`\n ];\n\n for (const junctionName of possibleJunctionNames) {\n const junctionTable = registry.getTable(junctionName);\n if (junctionTable) {\n // Try to find the appropriate columns in the junction table\n const sourceColName = `${fromTableName.slice(0, -1)}_id`; // Remove 's' and add '_id'\n const targetColName = `${toTableName.slice(0, -1)}_id`;\n\n const junctionSourceCol = junctionTable[sourceColName as keyof typeof junctionTable] as AnyPgColumn;\n const junctionTargetCol = junctionTable[targetColName as keyof typeof junctionTable] as AnyPgColumn;\n\n if (junctionSourceCol && junctionTargetCol) {\n // Found a valid junction table setup\n const currentTableIdCol = Object.values(currentTable).find((col: Record<string, unknown>) => col.primary) as AnyPgColumn;\n const targetTableIdCol = Object.values(targetTable).find((col: Record<string, unknown>) => col.primary) as AnyPgColumn;\n\n if (!currentTableIdCol || !targetTableIdCol) {\n continue; // Skip if we can't find primary keys\n }\n\n // Determine which direction to join\n if (currentTable === targetTable) {\n // We're joining through junction to reach the other table\n return {\n joinTable: targetTable,\n condition: eq(targetTableIdCol, junctionTargetCol),\n additionalJoins: [\n {\n table: junctionTable,\n condition: eq(currentTableIdCol, junctionSourceCol)\n }\n ]\n };\n } else {\n // Standard junction join\n return {\n joinTable: junctionTable,\n condition: eq(currentTableIdCol, junctionSourceCol),\n additionalJoins: [\n {\n table: targetTable,\n condition: eq(targetTableIdCol, junctionTargetCol)\n }\n ]\n };\n }\n }\n }\n }\n\n return null; // No junction table found\n }\n\n /**\n * Build conditions for junction table (many-to-many) relations\n */\n private static buildJunctionTableConditions(\n through: { table: string; sourceColumn: string; targetColumn: string },\n targetIdColumn: AnyPgColumn,\n parentId: string | number | (string | number)[],\n registry: PostgresCollectionRegistry\n ): { join: { table: PgTable<any>; condition: SQL }; condition: SQL } {\n const junctionTable = registry.getTable(through.table);\n if (!junctionTable) {\n throw junctionTableMissing(through.table);\n }\n\n const junctionSourceCol = junctionTable[through.sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const junctionTargetCol = junctionTable[through.targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n\n if (!junctionSourceCol) {\n throw new Error(`Source column '${through.sourceColumn}' not found in junction table '${through.table}'`);\n }\n if (!junctionTargetCol) {\n throw new Error(`Target column '${through.targetColumn}' not found in junction table '${through.table}'`);\n }\n\n // Handle both single ID and array of IDs\n const condition = Array.isArray(parentId)\n ? inArray(junctionSourceCol, parentId)\n : eq(junctionSourceCol, parentId);\n\n return {\n join: {\n table: junctionTable,\n condition: eq(targetIdColumn, junctionTargetCol)\n },\n condition\n };\n }\n\n\n /**\n * The condition for a relation whose link is a single column.\n *\n * Two cases. It had five: two of them existed only to throw (\"should not be\n * called directly\", \"lacks proper configuration\"), and one guessed a column\n * name by appending `_id` to `inverseRelationName` when no foreign key had\n * been resolved. All three were reachable only because the old type let a\n * relation arrive here under-specified. It cannot now.\n */\n private static buildSimpleRelationCondition(\n relation: ResolvedBelongsTo | ResolvedHasOne | ResolvedHasMany,\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentId: string | number | (string | number)[]\n ): SQL {\n const match = (column: AnyPgColumn) =>\n Array.isArray(parentId) ? inArray(column, parentId) : eq(column, parentId);\n\n if (relation.kind === \"belongsTo\") {\n // `parentId` is the foreign key's value, matched against the\n // target's own key.\n const targetIdCol = this.primaryKeyColumn(targetTable);\n if (!targetIdCol) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}'.`\n );\n }\n return match(targetIdCol);\n }\n\n const foreignKeyCol = relationColumn(targetTable, targetOf(relation), relation.foreignKeyOnTarget);\n if (!foreignKeyCol) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of relation ` +\n `'${relation.relationName}'. A link through a junction is \\`kind: \"manyToMany\"\\`.`\n );\n }\n return match(foreignKeyCol);\n }\n\n /**\n * Combine multiple conditions with AND operator\n */\n static combineConditionsWithAnd(conditions: SQL[]): SQL | undefined {\n if (conditions.length === 0) return undefined;\n if (conditions.length === 1) return conditions[0];\n return and(...conditions);\n }\n\n /**\n * Combine multiple conditions with OR operator\n */\n static combineConditionsWithOr(conditions: SQL[]): SQL | undefined {\n if (conditions.length === 0) return undefined;\n if (conditions.length === 1) return conditions[0];\n return or(...conditions);\n }\n\n /**\n * Build search conditions for text fields.\n *\n * Two shapes, chosen by whether the collection declared a `search` block:\n *\n * - **Declared** — one `@@ websearch_to_tsquery` against the generated\n * `tsvector` column. Stems, drops stopwords, AND-es the terms, reaches\n * inside JSONB and arrays, and uses the GIN index.\n * - **Not declared** — `ILIKE '%term%'` per term, OR-ed across top-level\n * string properties and AND-ed across the terms, with each term escaped\n * (see {@link escapeLikePattern}) so it is matched as the literal text\n * the user typed.\n *\n * The second is the default and stays the default, and it AND-es terms for\n * the same reason `websearch_to_tsquery` does: a person typing two words\n * into a search box means both, and a name lives in two columns. Matching\n * the whole string per column instead — which is what this did — cannot\n * find `sebastian melendez` on a row with `first_name`/`last_name`, and\n * could not find `sebastian ` at all.\n *\n * Returns at most one condition. Every caller ORs what it gets back, which\n * is right across columns and wrong across terms, so the AND is built here.\n *\n * `collection` is optional so that the callers which genuinely have no\n * collection in hand — nested paths, derived views — keep working; without\n * one there is no `search` block to read and the ILIKE path is correct.\n */\n static buildSearchConditions(\n searchString: string,\n properties: Record<string, unknown>,\n table: PgTable<any>,\n collection?: CollectionConfig\n ): SQL[] {\n const searchConditions: SQL[] = [];\n\n const ftsCondition = collection\n ? DrizzleConditionBuilder.buildFullTextCondition(searchString, table, collection)\n : undefined;\n if (ftsCondition) return [ftsCondition];\n\n let declaredStringProperties = 0;\n const viewer = currentFieldViewer();\n const searchableColumns: AnyPgColumn[] = [];\n\n for (const [key, prop] of Object.entries(properties)) {\n const p = prop as Record<string, unknown>;\n // A field this caller cannot read is not one they may search. The\n // strip keeps the value out of the response; without this the same\n // value is still recoverable a substring at a time by watching which\n // searches return the row, which is the disclosure the read rule\n // exists to prevent. `excludeFromApi` expands to the same rule, so a\n // password hash stops being ILIKE-matchable here too.\n if (!canReadField(prop as Property, viewer)) continue;\n // Only include string properties that don't have enum defined\n // PostgreSQL enum and uuid columns don't support ILIKE, so we skip them\n if (p.type === \"string\" && !p.enum && p.isId !== \"uuid\") {\n declaredStringProperties++;\n const fieldColumn = table[key as keyof typeof table] as AnyPgColumn;\n if (fieldColumn && supportsILike(fieldColumn)) {\n searchableColumns.push(fieldColumn);\n }\n }\n }\n\n // Every string property was rejected, so the caller is about to turn an\n // empty condition list into \"match nothing\" — a 200 with an empty page,\n // which reads as \"no such row\" rather than as the breakage it is. Say so\n // once per query: this is how the `instanceof` version of\n // {@link supportsILike} failed silently in the field for months.\n if (declaredStringProperties > 0 && searchableColumns.length === 0) {\n logger.warn(\n `[search] \"${collection?.slug ?? \"collection\"}\" declares ${declaredStringProperties} string ` +\n \"property(ies) but none compiled to a searchable column, so this search can only return nothing. \" +\n \"Check that the generated schema's column types are text/varchar/char.\"\n );\n }\n if (searchableColumns.length === 0) return [];\n\n // Every term has to match *something*, but not the same something:\n // \"sebastian melendez\" is a first name and a last name, and matching the\n // whole string per column finds neither. See {@link splitSearchTerms}.\n //\n // A string with no terms at all is only whitespace. It stays the single\n // literal pattern it has always been rather than becoming \"no rows\": an\n // empty condition list means \"match nothing\" to every caller here, and a\n // stray space is not a reason to answer that.\n const terms = splitSearchTerms(searchString);\n const perTerm = (terms.length > 0 ? terms : [searchString]).map(term => {\n const pattern = `%${escapeLikePattern(term)}%`;\n return DrizzleConditionBuilder.combineConditionsWithOr(\n searchableColumns.map(column => ilike(column, pattern))\n ) as SQL;\n });\n\n // One condition, already AND-ed across terms, because every caller ORs\n // what it gets back — which is right for \"this column or that one\" and\n // wrong for \"this term and that one\". A single-term search compiles to\n // exactly the SQL it compiled to before this existed.\n searchConditions.push(DrizzleConditionBuilder.combineConditionsWithAnd(perTerm) as SQL);\n\n return searchConditions;\n }\n\n /**\n * The `@@` predicate for a collection that declared a `search` block, or\n * undefined for one that did not.\n *\n * The query is normalized exactly as the indexed content was — same text\n * search configuration, same accent folding. Skipping that on the query\n * side is the subtle way to get a search that matches nothing: the column\n * would hold `gestion` while the query asked for `gestión`.\n *\n * `websearch_to_tsquery` rather than `plainto_tsquery` because it is the\n * one that behaves the way a search box looks like it should — quoted\n * phrases, `or`, and a leading `-` to exclude — and because it never throws\n * on user input, which `to_tsquery` does on so much as a stray parenthesis.\n */\n static buildFullTextCondition(\n searchString: string,\n table: PgTable<any>,\n collection: CollectionConfig\n ): SQL | undefined {\n let spec: SearchColumnSpec | undefined;\n try {\n spec = buildSearchColumnSpec(collection);\n } catch {\n // Reported at boot. Falling back to ILIKE here keeps reads serving.\n return undefined;\n }\n if (!spec) return undefined;\n\n const column = table[spec.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n // The block is declared but the column is not on the table yet —\n // a database that has not been migrated. ILIKE still answers.\n return undefined;\n }\n\n const query = DrizzleConditionBuilder.normalizedTsQuery(searchString, spec);\n const exact = sql`${column} @@ ${query}`;\n\n // `hybrid`: the indexed whole-lexeme predicate OR a substring match over\n // the same declared fields. One collection then answers both halves of\n // what a search box is, which neither branch does on its own —\n // measured, on the same five rows, in `search-mode-matrix.test.ts`:\n //\n // query | fts + unaccent | ILIKE default | hybrid\n // munoz | Muñoz, Munoz | Munoz | Muñoz, Munoz\n // seb | — | Sebastian, Sebastián| Sebastian, Sebastián\n // audit | — | Lead Auditor | Lead Auditor\n //\n // OR-ed rather than replacing the `@@`: the index still serves the half\n // it can serve, and the exact half is what carries the ranking.\n const substring = spec.mode === \"hybrid\"\n ? DrizzleConditionBuilder.buildFoldedSubstringCondition(searchString, spec)\n : undefined;\n const matched = substring ? sql`(${exact} OR ${substring})` : exact;\n\n if (!spec.fuzzy) return matched;\n\n const fuzzyColumn = table[spec.fuzzy.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!fuzzyColumn) return matched;\n\n const needle = spec.unaccent\n ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})`\n : sql`${searchString}`;\n\n // `word_similarity(query, document)`, not `similarity`. `similarity`\n // scores two strings as wholes, so a short query against a whole row's\n // text scores near zero however well it matches part of it — measured:\n // \"iso 14001 auditor\" against one candidate's concatenated fields\n // scores 0.228 by `similarity` and 0.783 by `word_similarity`. The\n // first is below any usable threshold, which would have made `fuzzy`\n // a setting that quietly did nothing.\n //\n // Argument order matters: the first operand is the needle, and the\n // score is its similarity to the best-matching extent of the second.\n //\n // Both the function and the operator are schema-qualified: pg_trgm is\n // installed into `public`, and an unqualified reference resolves\n // through `search_path`, which does not necessarily reach it.\n const similar = sql`public.word_similarity(${needle}, ${fuzzyColumn}) >= ${spec.fuzzy.threshold}`;\n // `<%` is the index-backed form, but it tests against the session's\n // `pg_trgm.word_similarity_threshold` (0.6), not ours. Above that\n // default the operator narrows using the trigram index and the explicit\n // score refines; at or below it, the operator would exclude rows the\n // declared threshold admits, so the score stands alone and the planner\n // scans — correct either way, and only the faster path is conditional.\n const fuzzy = spec.fuzzy.threshold > PG_TRGM_WORD_SIMILARITY_DEFAULT\n ? sql`(${needle} OPERATOR(public.<%) ${fuzzyColumn} AND ${similar})`\n : similar;\n\n return sql`(${matched} OR ${fuzzy})`;\n }\n\n /**\n * The substring half of {@link SearchMode} `\"hybrid\"`: every term found\n * somewhere in the declared fields, with accents folded on both sides.\n *\n * Terms are AND-ed and fields OR-ed, exactly as the ILIKE default does and\n * for the same reason — a person typing two words means both, and a name\n * lives in two columns (`splitSearchTerms`). What it does *not* share with\n * the default is its reach: these are the declared `search.fields`, so it\n * folds accents, joins `text[]` and walks into JSONB through the same\n * IMMUTABLE helpers the generated column uses.\n *\n * The needle is folded with the same function as the document, so\n * `munoz` and `Muñoz` meet in the middle rather than one side being\n * normalized and the other not — the subtle way to build a search that\n * matches nothing.\n *\n * Interpolated as raw SQL because the field expressions are generated (they\n * name columns and helper functions, which are identifiers, not values);\n * the *term* is a bind parameter, escaped for LIKE first — see\n * {@link escapeLikePattern} for what that escape is for.\n */\n static buildFoldedSubstringCondition(\n searchString: string,\n spec: SearchColumnSpec\n ): SQL | undefined {\n if (spec.fields.length === 0) return undefined;\n\n const perTerm = DrizzleConditionBuilder.foldedNeedles(searchString).map(needle =>\n DrizzleConditionBuilder.combineConditionsWithOr(\n spec.fields.map(f => sql`${sql.raw(f.foldedTextSql)} ILIKE ${needle}`)\n ) as SQL\n );\n\n return DrizzleConditionBuilder.combineConditionsWithAnd(perTerm);\n }\n\n /**\n * Whether one text expression holds **any** term of the search string.\n *\n * The per-field question, where {@link buildFoldedSubstringCondition} asks\n * the per-row one: `OR` across terms rather than `AND`, because a two-word\n * query satisfied across two fields has each field carrying one term. Used\n * by `_matches` to name the fields that caused a substring hit.\n */\n static foldedSubstringOf(searchString: string, text: SQL): SQL {\n return DrizzleConditionBuilder.combineConditionsWithOr(\n DrizzleConditionBuilder.foldedNeedles(searchString)\n .map(needle => sql`${text} ILIKE ${needle}`)\n ) as SQL;\n }\n\n /**\n * One accent-folded `%term%` bind parameter per search term.\n *\n * The needle is folded with the same function as the document, so `munoz`\n * and `Muñoz` meet in the middle instead of one side being normalized and\n * the other not — the subtle way to build a search that matches nothing.\n *\n * A string with no terms at all is only whitespace, and stays the single\n * literal pattern it is on the ILIKE path rather than becoming \"no rows\",\n * so both paths answer a stray space the same way.\n */\n private static foldedNeedles(searchString: string): SQL[] {\n const terms = splitSearchTerms(searchString);\n const effective = terms.length > 0 ? terms : [searchString];\n return effective.map(term =>\n sql`${sql.raw(SEARCH_UNACCENT_FN)}(${`%${escapeLikePattern(term)}%`})`\n );\n }\n\n /**\n * `websearch_to_tsquery(<config>, <normalized search string>)`.\n *\n * Split out because the ranking expression needs the identical query — a\n * row ranked against a different tsquery than it was matched against is a\n * ranking of something else.\n */\n static normalizedTsQuery(searchString: string, spec: SearchColumnSpec): SQL {\n const normalized = spec.unaccent\n ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})`\n : sql`${searchString}`;\n return sql`websearch_to_tsquery(${spec.language}, ${normalized})`;\n }\n\n /**\n * A JSONB array of `{ field, snippet }` naming which declared fields matched\n * and showing the text around each hit — what backs `_matches`.\n *\n * A ranked list answers \"which rows\", never \"why this row\". For a talent\n * pool that difference is the product: a candidate surfacing for\n * \"iso 14001\" on a *certification* is a different candidate from one whose\n * bio happens to mention the standard, and the score cannot tell them apart.\n *\n * Built as a correlated subquery over a `VALUES` list of the declared\n * fields, rather than one `CASE` per field, so the shape does not change\n * with the number of fields and the empty result is a plain `[]`.\n *\n * `ts_headline` runs over the same normalized text that was indexed. Over\n * the *original* text it would find nothing to mark whenever `unaccent` is\n * on — the query's lexemes are folded and the document's are not — and\n * would return the text silently unhighlighted. Folded-but-marked beats\n * pretty-but-inert.\n *\n * Undefined when the collection has not opted in, when the column is not on\n * the table yet, or when the caller did not ask: this costs a `ts_headline`\n * per field per row and `ts_headline` re-parses the document.\n */\n static buildSearchMatchesExpression(\n searchString: string,\n table: PgTable<any>,\n collection: CollectionConfig\n ): SQL | undefined {\n let spec: SearchColumnSpec | undefined;\n try {\n spec = buildSearchColumnSpec(collection);\n } catch {\n return undefined;\n }\n if (!spec || spec.fields.length === 0) return undefined;\n if (!table[spec.column as keyof typeof table]) return undefined;\n\n const query = DrizzleConditionBuilder.normalizedTsQuery(searchString, spec);\n const config = sql`${spec.language}`;\n\n // `ord` keeps the author's declared field order in the output, so the\n // most important field they named reads first rather than whichever\n // Postgres aggregated first.\n //\n // Two text columns per field under `hybrid`: `txt` is what the tsvector\n // was built from and what `ts_headline` reads, `folded` is what the\n // substring half matched. They differ whenever `unaccent` is off, and a\n // single column would make one of the two halves report the wrong\n // fields.\n const rows = spec.fields.map((f, i) =>\n sql`(${i}, ${f.path}, ${sql.raw(f.textSql)}, ${sql.raw(f.foldedTextSql)})`\n );\n\n // Which fields a row reports. Under `hybrid` a field that matched only\n // by substring is reported too — it is the field that caused the hit,\n // and leaving it out would answer \"matched nothing\" for the row the\n // mode exists to find. Its snippet comes back with nothing marked:\n // `ts_headline` marks lexemes, and half a word is not one.\n const matchedField = spec.mode === \"hybrid\"\n ? sql`(to_tsvector(${config}::regconfig, f.txt) @@ ${query} OR ${DrizzleConditionBuilder.foldedSubstringOf(searchString, sql`f.folded`)})`\n : sql`to_tsvector(${config}::regconfig, f.txt) @@ ${query}`;\n\n return sql`(\n SELECT coalesce(jsonb_agg(s.m ORDER BY f.ord), '[]'::jsonb)\n FROM (VALUES ${sql.join(rows, sql`, `)}) AS f(ord, path, txt, folded)\n CROSS JOIN LATERAL (\n SELECT jsonb_build_object(\n 'field', f.path,\n 'snippet', ts_headline(${config}::regconfig, f.txt, ${query},\n 'StartSel=<mark>,StopSel=</mark>,MaxWords=14,MinWords=1,MaxFragments=1,FragmentDelimiter= … ')\n ) AS m\n WHERE ${matchedField}\n ) s\n )`;\n }\n\n /**\n * `ts_rank(<column>, <query>)` for the collection, or undefined when it has\n * not opted in. This is what backs `orderBy: [\"_score\", \"desc\"]`.\n */\n static buildSearchRankExpression(\n searchString: string,\n table: PgTable<any>,\n collection: CollectionConfig\n ): SQL | undefined {\n let spec: SearchColumnSpec | undefined;\n try {\n spec = buildSearchColumnSpec(collection);\n } catch {\n return undefined;\n }\n if (!spec) return undefined;\n const column = table[spec.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) return undefined;\n\n const base = sql`ts_rank(${column}, ${DrizzleConditionBuilder.normalizedTsQuery(searchString, spec)})`;\n\n // Under `hybrid`, `ts_rank` alone is not a ranking for the same reason\n // it is not one under `fuzzy`: it is **zero** for every row the\n // substring half matched and the `@@` half did not, which is the whole\n // population of a prefix query like `seb`. Ordering by it would return\n // those rows in whatever order the table felt like.\n //\n // A constant, not a score, because a substring match carries no\n // measurable strength — `audit` is inside `Auditor` and that is all\n // there is to know. It sits below any real `ts_rank`: the smallest a\n // matching `ts_rank` can be is a single D-weighted hit, 0.1 × 0.0607927\n // ≈ 0.006, so `0.001` keeps every lexeme match above every\n // substring-only one and leaves the substring-only rows to the\n // deterministic id tiebreaker the order builder already appends.\n const rank = spec.mode === \"hybrid\"\n ? (() => {\n const substring = DrizzleConditionBuilder.buildFoldedSubstringCondition(searchString, spec);\n // A literal, cast, rather than a bind parameter: Postgres infers\n // a `CASE` branch's type from its siblings, so a bound `0.001`\n // beside `ELSE 0` is read as an integer and refused\n // (\"invalid input syntax for type integer\").\n return substring\n ? sql`(${base} + CASE WHEN ${substring} THEN ${sql.raw(`${HYBRID_SUBSTRING_SCORE}::real`)} ELSE 0 END)`\n : base;\n })()\n : base;\n\n if (!spec.fuzzy) return rank;\n\n const fuzzyColumn = table[spec.fuzzy.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!fuzzyColumn) return rank;\n\n // With `fuzzy` on, `ts_rank` alone is not a ranking — it is zero for\n // every row the trigram path matched and the exact path did not, which\n // is the whole population of a typo'd query. Measured on the real\n // sustentalent pool: \"auditor de iso14000\" matches four candidates,\n // every one of them at ts_rank 0, so ordering by rank alone returned\n // the best match in whatever order the table felt like.\n //\n // Summed rather than blended with tuned constants: a row that matched\n // exactly contributes both terms, so it outranks a fuzzy-only row of\n // equal similarity without needing a coefficient to say so. Both terms\n // are non-negative and monotonic, which is all the ordering needs.\n const needle = spec.unaccent\n ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})`\n : sql`${searchString}`;\n return sql`(${rank} + public.word_similarity(${needle}, ${fuzzyColumn}))`;\n }\n\n /**\n * Build a unique field check condition: rows holding `value`, other than\n * the one `excludeRow` names.\n *\n * `excludeRow` is the whole identity of the row being saved — every key\n * column — so excluding one member of a composite-key table does not\n * exclude every row sharing its first key part.\n */\n static buildUniqueFieldCondition(\n fieldColumn: AnyPgColumn,\n value: unknown,\n excludeRow?: SQL\n ): SQL[] {\n const conditions: SQL[] = [eq(fieldColumn, value)];\n\n if (excludeRow) {\n conditions.push(not(excludeRow));\n }\n\n return conditions;\n }\n\n /**\n * Build relation-based query with joins and conditions\n */\n static buildRelationQuery<T extends DrizzleDynamicQuery>(\n baseQuery: T,\n relation: ResolvedRelation,\n parentId: string | number | (string | number)[],\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry,\n additionalFilters?: SQL[]\n ): T {\n const { joinConditions, whereConditions } = this.buildRelationConditions(\n relation,\n parentId,\n targetTable,\n parentTable,\n parentIdColumn,\n targetIdColumn,\n registry\n );\n\n let query = baseQuery;\n\n // Apply joins\n for (const { table, condition } of joinConditions) {\n query = query.innerJoin(table, condition);\n }\n\n // Combine all conditions\n const allConditions = [...whereConditions];\n if (additionalFilters) {\n allConditions.push(...additionalFilters);\n }\n\n // Apply where conditions\n if (allConditions.length > 0) {\n query = query.where(and(...allConditions));\n }\n\n return query;\n }\n\n /**\n * A count over a relation's target rows.\n *\n * The junction case counts `distinct` because the caller's query joins the\n * junction; the owning/inverse pair that used to sit here built the same\n * query twice.\n */\n static buildRelationCountQuery<T extends DrizzleDynamicQuery>(\n baseCountQuery: T,\n relation: ResolvedRelation,\n parentId: string | number,\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry,\n additionalFilters?: SQL[]\n ): T {\n switch (relation.kind) {\n case \"via\":\n return this.buildJoinPathCountQuery(\n baseCountQuery, relation.joinPath, targetTable, parentTable,\n parentIdColumn, parentId, registry, additionalFilters\n );\n\n case \"manyToMany\":\n return this.buildJunctionCountQuery(\n baseCountQuery, relation.through, targetIdColumn, parentId, registry, additionalFilters\n );\n\n case \"belongsTo\":\n case \"hasOne\":\n case \"hasMany\": {\n const allConditions = [\n this.buildSimpleRelationCondition(relation, targetTable, parentTable, parentId),\n ...(additionalFilters ?? [])\n ];\n return baseCountQuery.where(and(...allConditions));\n }\n\n default: {\n const exhaustive: never = relation;\n throw new Error(`Unknown relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n }\n\n /**\n * Build join path conditions for count queries\n */\n private static buildJoinPathCountQuery<T extends DrizzleDynamicQuery>(\n baseCountQuery: T,\n joinPath: JoinStep[],\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n parentId: string | number,\n registry: PostgresCollectionRegistry,\n additionalFilters?: SQL[]\n ): T {\n let query = baseCountQuery;\n let currentTable = targetTable;\n const sourceTableName = drizzleTableName(parentTable);\n\n // Process join steps in reverse order\n for (let index = joinPath.length - 1; index >= 0; index--) {\n const joinStep = joinPath[index];\n const { fromTableName, toTableName } = this.joinStepTableNames(joinPath, index, sourceTableName);\n const fromColName = this.getColumnNamesFromColumns(joinStep.on.from)[0];\n const toColName = this.getColumnNamesFromColumns(joinStep.on.to)[0];\n\n const fromTable = registry.getTable(fromTableName);\n const toTable = registry.getTable(toTableName);\n\n if (!fromTable || !toTable) {\n throw new Error(`Join tables not found for step: from ${fromTableName} to ${toTableName}`);\n }\n\n const { joinTable, condition } = this.buildSingleJoinCondition(\n currentTable,\n fromTable,\n toTable,\n fromColName,\n toColName,\n fromTableName,\n toTableName\n );\n\n query = query.innerJoin(joinTable, condition);\n currentTable = joinTable;\n }\n\n if (currentTable !== parentTable) {\n throw new Error(\"Join path did not result in connecting to parent table\");\n }\n\n const allConditions = [eq(parentIdColumn, parentId)];\n if (additionalFilters) {\n allConditions.push(...additionalFilters);\n }\n\n return query.where(and(...allConditions));\n }\n\n /**\n * Build junction table conditions for count queries\n */\n private static buildJunctionCountQuery<T extends DrizzleDynamicQuery>(\n baseCountQuery: T,\n through: { table: string; sourceColumn: string; targetColumn: string },\n targetIdColumn: AnyPgColumn,\n parentId: string | number,\n registry: PostgresCollectionRegistry,\n additionalFilters?: SQL[]\n ): T {\n const junctionTable = registry.getTable(through.table);\n if (!junctionTable) {\n throw junctionTableMissing(through.table);\n }\n\n const junctionSourceCol = junctionTable[through.sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const junctionTargetCol = junctionTable[through.targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n\n if (!junctionSourceCol) {\n throw new Error(`Source column '${through.sourceColumn}' not found in junction table '${through.table}'`);\n }\n if (!junctionTargetCol) {\n throw new Error(`Target column '${through.targetColumn}' not found in junction table '${through.table}'`);\n }\n\n const baseConditions = [eq(junctionSourceCol, parentId)];\n if (additionalFilters && additionalFilters.length > 0) {\n baseConditions.push(...additionalFilters);\n }\n\n return baseCountQuery\n .innerJoin(junctionTable, eq(targetIdColumn, junctionTargetCol))\n .where(and(...baseConditions));\n }\n\n\n /**\n * Helper method to extract table names from columns\n */\n /**\n * The two tables one step of a join path joins, by the rule `JoinStep`\n * documents: `on.from` is a column of the previous table — the source\n * collection's for the first step — and `on.to` is a column of\n * `step.table`. A `table.column` spelling names its table outright.\n *\n * The tables used to come from that spelling alone, so a step written the\n * documented way, `{ from: \"id\", to: \"product_id\" }`, named no table at\n * all: every count and include over such a `via` threw \"Join tables not\n * found for step: from to \", and since a record read at a nested address\n * is gated on that count, opening an order from a product's Orders tab\n * failed. The tab's own listing worked — it walks the path forward from\n * the parent, by position — so only the record behind it broke.\n */\n private static joinStepTableNames(\n joinPath: JoinStep[],\n index: number,\n sourceTableName: string\n ): { fromTableName: string; toTableName: string } {\n const step = joinPath[index];\n return {\n fromTableName: this.getTableNamesFromColumns(step.on.from)[0]\n || (index === 0 ? sourceTableName : joinPath[index - 1].table),\n toTableName: this.getTableNamesFromColumns(step.on.to)[0] || step.table\n };\n }\n\n static getTableNamesFromColumns(columns: string | string[]): string[] {\n if (Array.isArray(columns)) {\n return columns.map(col => col.includes(\".\") ? col.split(\".\")[0] : \"\");\n }\n return [columns.includes(\".\") ? columns.split(\".\")[0] : \"\"];\n }\n\n /**\n * Helper method to extract column names from columns\n */\n static getColumnNamesFromColumns(columns: string | string[]): string[] {\n if (Array.isArray(columns)) {\n return columns.map(col => getColumnName(col));\n }\n return [getColumnName(columns)];\n }\n\n\n /**\n * Build vector similarity search expressions for pgvector.\n *\n * Returns:\n * - `orderBy`: SQL expression to ORDER BY distance (ascending = closest first)\n * - `filter`: optional WHERE clause for distance threshold\n * - `distanceSelect`: SQL expression for selecting the distance as `_distance`\n *\n * `property` is `?vector_search=` off the querystring, so it is an untrusted\n * *name*, and it used to be looked up straight in the drizzle table object.\n * Two ways that went wrong, both answering 500 to a malformed request:\n * `?vector_search=title` built `\"title\" <=> '[1,2]'::vector`, which the\n * database rejects with \"operator does not exist\"; and a table object also\n * carries non-column keys (`_`, methods), which passed the `if (!column)`\n * guard and compiled to nonsense. The name is resolved against the table's\n * actual columns and required to be a `vector` — anything else is the\n * caller's mistake and gets a 400 that says so.\n */\n static buildVectorSearchConditions(\n table: PgTable<any>,\n vectorSearch: {\n property: string;\n vector: number[];\n distance?: \"cosine\" | \"l2\" | \"inner_product\";\n threshold?: number;\n },\n collection?: CollectionConfig\n ): { orderBy: SQL; filter?: SQL; distanceSelect: SQL } {\n const column = DrizzleConditionBuilder.resolveVectorColumn(table, vectorSearch.property, collection);\n\n // The vector is interpolated as a raw SQL literal below (pgvector has no\n // bind form for the `::vector` cast), so every element must be a finite\n // number. The REST query parser already enforces this, but this builder\n // is a shared entry point — validate here too so no future caller can\n // turn an unchecked value into SQL injection.\n if (\n !Array.isArray(vectorSearch.vector) ||\n vectorSearch.vector.length === 0 ||\n !vectorSearch.vector.every((n) => typeof n === \"number\" && Number.isFinite(n))\n ) {\n throw new Error(\"Vector search requires a non-empty array of finite numbers\");\n }\n\n const vectorLiteral = `'[${vectorSearch.vector.join(\",\")}]'::vector`;\n const distanceFn = vectorSearch.distance || \"cosine\";\n\n let operator: string;\n switch (distanceFn) {\n case \"cosine\":\n operator = \"<=>\";\n break;\n case \"l2\":\n operator = \"<->\";\n break;\n case \"inner_product\":\n operator = \"<#>\";\n break;\n }\n\n const distanceExpr = sql`${column} ${sql.raw(operator)} ${sql.raw(vectorLiteral)}`;\n\n return {\n orderBy: distanceExpr,\n filter: vectorSearch.threshold != null\n ? sql`(${column} ${sql.raw(operator)} ${sql.raw(vectorLiteral)}) < ${vectorSearch.threshold}`\n : undefined,\n distanceSelect: sql`(${column} ${sql.raw(operator)} ${sql.raw(vectorLiteral)})`\n };\n }\n\n /**\n * The `vector` column a request named, or a 400 explaining what it named.\n *\n * `getTableColumns` rather than a key lookup: it returns only the columns,\n * so `_`, `getSQL` and every other property of a drizzle table stop looking\n * like candidates. The type check is on the *physical* column\n * (`vector(1536)`) rather than on the declared property, so it holds for an\n * introspected collection too, where the property carries no Rebase type.\n *\n * The answer for a name that is not a column lists no `excludeFromApi`\n * property; the REST check answers a hidden one with the same function, so\n * the two cannot be told apart.\n */\n private static resolveVectorColumn(table: PgTable<any>, property: string, collection?: CollectionConfig): AnyPgColumn {\n const columns = getTableColumns(table) as Record<string, AnyPgColumn> | undefined;\n const column = columns?.[property];\n if (!column) {\n const hidden = collection ? apiHiddenFieldNames(collection) : undefined;\n const known = Object.entries(columns ?? {})\n .filter(([name, c]) => isVectorColumn(c) && !hidden?.has(name))\n .map(([name]) => name)\n .sort();\n const answer = unknownVectorProperty(property, known);\n throw ApiError.badRequest(answer.message, \"UNKNOWN_VECTOR_PROPERTY\", answer.details);\n }\n if (!isVectorColumn(column)) {\n throw ApiError.badRequest(\n `Property \"${property}\" is not a vector column (it is \\`${columnSqlType(column) || \"unknown\"}\\`), ` +\n \"so it has no distance operator. Name the property declared as `{ type: \\\"vector\\\" }`.\",\n \"UNKNOWN_VECTOR_PROPERTY\"\n );\n }\n return column;\n }\n}\n\n/** The column's SQL type, for a value that may not be a drizzle column at all. */\nconst columnSqlType = (column: unknown): string => {\n const getSQLType = (column as { getSQLType?: () => string })?.getSQLType;\n return typeof getSQLType === \"function\" ? getSQLType.call(column).toLowerCase() : \"\";\n};\n\n/** True for `vector(1536)` and its pgvector siblings, whatever the width. */\nconst isVectorColumn = (column: unknown): boolean =>\n /^(vector|halfvec|sparsevec)\\b/.test(columnSqlType(column));\n\n/**\n * Alias for DrizzleConditionBuilder for consistent naming with other database implementations.\n * This allows code to use PostgresConditionBuilder alongside future MongoConditionBuilder, etc.\n */\nexport const PostgresConditionBuilder = DrizzleConditionBuilder;\n\n","import { sql } from \"drizzle-orm\";\nimport { PgTransaction } from \"drizzle-orm/pg-core\";\nimport type { DrizzleClient } from \"../interfaces\";\n\n/**\n * pgvector's iterative index scans, for a vector read that also filters.\n *\n * An HNSW index answers a nearest-neighbour query with the `hnsw.ef_search`\n * (40 by default) candidates it found, and every other condition of the query\n * — a `where`, a search, a soft-delete stamp, a row-level-security policy — is\n * applied to those candidates afterwards. Where few of them pass, the page\n * comes back short, or empty, though plenty of rows match: measured on 20,000\n * rows with half of them matching a `status` filter far from the query vector,\n * `LIMIT 10` served 0 rows. Offset pages over the index were empty or short the\n * same way, while deeper pages, where the planner switched to an exact scan,\n * served rows, so a walk skipped some and could repeat others.\n *\n * From pgvector 0.8.0, `hnsw.iterative_scan` makes the index keep scanning\n * until the query has its rows (bounded by `hnsw.max_scan_tuples`, 20,000 by\n * default). `strict_order` and not `relaxed_order`: the planner reads the\n * index's output as already sorted by distance, so out-of-order rows would be\n * cut at the wrong place by `LIMIT` and the same row could land on two pages.\n * Measured on the case above: 0/10 → 10/10, and the first 200 rows by offset\n * pages of 10 with no duplicates (`relaxed_order`: 18).\n *\n * Set with `set_config(…, true)` — local to the transaction the read runs in,\n * so it never outlives it on a pooled connection. A read not in a transaction\n * is given one.\n *\n * pgvector before 0.8.0 has no such setting, and with its library loaded it\n * refuses an unknown `hnsw.` parameter, so the setting is applied only where\n * the extension's version has it; there a filtered vector read keeps the\n * behaviour above. IVFFlat indexes are not touched: their iterative scan only\n * comes in relaxed order.\n */\n\n/** Whether a pgvector version (`extversion`, e.g. `\"0.8.1\"`) has iterative index scans. */\nexport function hasIterativeIndexScans(version: string | undefined | null): boolean {\n if (!version) return false;\n const [major, minor] = version.split(\".\").map(part => parseInt(part, 10));\n if (!Number.isFinite(major) || !Number.isFinite(minor)) return false;\n return major > 0 || minor >= 8;\n}\n\n/**\n * The answer per database, asked once. Keyed by an object that lives exactly as\n * long as the database connection does — the driver's collection registry,\n * shared by its request-scoped copies — so two data sources on two pgvector\n * versions each get their own.\n */\nconst support = new WeakMap<object, Promise<boolean>>();\n\n/** Whether `db`'s pgvector has iterative index scans. Asked once per `key`. */\nexport function iterativeScansAvailable(db: DrizzleClient, key: object): Promise<boolean> {\n let known = support.get(key);\n if (!known) {\n // Through `then`, so a client that cannot `execute` at all rejects\n // into the `catch` below rather than throwing past it.\n known = Promise.resolve()\n .then(() => db.execute(sql`SELECT extversion FROM pg_extension WHERE extname = 'vector'`))\n .then(result => {\n const version = (result.rows[0] as Record<string, unknown> | undefined)?.extversion;\n return hasIterativeIndexScans(typeof version === \"string\" ? version : undefined);\n })\n .catch(() => {\n // Not remembered: a failed lookup says nothing about the\n // version, and the next read asks again.\n support.delete(key);\n return false;\n });\n support.set(key, known);\n }\n return known;\n}\n\n/** Whether `db` is a transaction, in which a `set_config(…, true)` stays. */\nexport function inTransaction(db: DrizzleClient): boolean {\n return db instanceof PgTransaction;\n}\n\n/** Turn on strict-order iterative HNSW scans for the rest of `db`'s transaction. */\nexport async function enableIterativeScan(db: DrizzleClient): Promise<void> {\n await db.execute(sql`SELECT set_config('hnsw.iterative_scan', 'strict_order', true)`);\n}\n","import { CollectionConfig, INCLUDE_COUNT_KEY, JUNCTION_PIVOT_KEY, Property, ResolvedRelation, isManyToMany, type ResolvedVia } from \"@rebasepro/types\";\nimport { canReadField, resolveCollectionRelations, findRelation, createRelationRefWithData, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { currentFieldViewer } from \"./field-viewer\";\nimport { normalizeDbValues } from \"../data-transformer\";\nimport { deriveRowAddress } from \"./collection-helpers\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\n\n/**\n * Turning a drizzle result into a row we serve.\n *\n * There are two shapes, and they are the same walk. Both take a row whose\n * relation fields hold nested objects, both unwrap junction rows to reach the\n * target behind them, and both leave every other column alone. They differ only\n * in what they put where the relation was:\n *\n * - `\"ref\"` — a `{ id, path, __type: \"relation\" }` reference carrying the\n * target's values. This is what the admin renders.\n * - `\"inline\"` — the target's own columns, flat. This is what REST serves, and\n * — since the in-process SDK reads through the same pipeline — what\n * `rebase.dataAsAdmin` / `context.data` serve too. A developer never sees a ref.\n *\n * They used to be two functions that happened to agree, and the agreement was\n * not enforced by anything: the row-identity bug had to be fixed five times\n * across differently-shaped copies of this walk, and one of the copies was\n * dead code nobody had noticed. Whatever the next cross-cutting change is, it\n * is one edit here.\n */\nexport type RelationStyle = \"ref\" | \"inline\";\n\n/**\n * Whether a many-relation reaches its target through a junction table.\n *\n * Also used to build the drizzle `with` config, which is why it is exported:\n * the query has to nest one level deeper for a junction, and the row walk has\n * to unwrap that same level back out.\n */\nexport function isJunctionRelation(relation: ResolvedRelation): boolean {\n // An explicit `through` says so outright.\n if (isManyToMany(relation)) return true;\n // A multi-hop join path is the same thing spelled longhand.\n return relation.kind === \"via\" && relation.joinPath.length > 1;\n}\n\n/**\n * Reach the target row inside a junction row.\n *\n * A junction row looks like `{ post_id: 1, tag_id: { id: 5, name: \"ts\" } }` —\n * the foreign keys, and the target nested under one of them. The target is the\n * only object among them, so that is how it is found. A junction row that has\n * not been nested (no `with` on the join) has no object and is returned as-is.\n *\n * \"The only object\" stopped being true when a junction gained\n * `through.properties`: a payload `map` is an object too, and it sits in the\n * row beside the target with no guaranteed order between them — so this would\n * have unwrapped to the payload and served it as the tag. The payload keys are\n * known from the relation, so they are skipped by name rather than by shape.\n */\nfunction unwrapJunctionRow(\n item: Record<string, unknown>,\n relation: ResolvedRelation\n): Record<string, unknown> {\n const payload = isManyToMany(relation) ? relation.through.properties : undefined;\n const nestedKey = Object.keys(item).find(\n key => key !== JUNCTION_PIVOT_KEY\n && !(payload && key in payload)\n && typeof item[key] === \"object\" && item[key] !== null && !Array.isArray(item[key])\n );\n return nestedKey ? item[nestedKey] as Record<string, unknown> : item;\n}\n\n/**\n * Give back the number a `number` property was declared to be.\n *\n * Postgres returns NUMERIC as a string and drizzle keeps it that way, since a\n * numeric can hold more precision than a double. But the collection declared\n * this property `number` and the OpenAPI spec this server publishes says\n * `type: number`, so serving `\"9.99\"` breaks its own contract — and breaks it\n * asymmetrically, because a create answers with the number and the read that\n * follows answers with the string. Any client that multiplies a price works\n * until the first refresh.\n *\n * Declaring a property `number` already accepts double precision — that is what\n * the admin has always parsed it to. Columns REST serves that no property\n * declares are left exactly as the database returned them.\n */\nfunction coerceDeclaredNumber(value: unknown, property: Property | undefined): unknown {\n if (property?.type !== \"number\" || typeof value !== \"string\") return value;\n const parsed = parseFloat(value);\n return isNaN(parsed) ? null : parsed;\n}\n\n/**\n * Serve a `date` column as the timestamp this API says it serves.\n *\n * The OpenAPI document this server publishes types a `date` property as\n * `string, format: date-time` — RFC 3339 — and node-postgres hands over the\n * Postgres literal: `\"2026-08-24 01:37:57.647+02\"`, with a space where the `T`\n * belongs and a two-digit offset. V8 parses that by accident; Safari's\n * `new Date()` does not have to, and the spec never promised it. A column whose\n * documented type only parses in some browsers is not a contract.\n *\n * A date-only column (`mode: \"date\"`, `format: \"date\"` in the spec) is already\n * RFC 3339 as `YYYY-MM-DD` and is left alone — widening it to a timestamp would\n * invent a time of day and a timezone the column does not have.\n */\nfunction toRestDate(value: unknown): unknown {\n if (value instanceof Date) return isNaN(value.getTime()) ? null : value.toISOString();\n // An upstream walk may already have tagged it for the admin's view model.\n if (value && typeof value === \"object\" && (value as { __type?: unknown }).__type === \"date\") {\n return (value as { value?: unknown }).value ?? null;\n }\n if (typeof value !== \"string\" && typeof value !== \"number\") return value;\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n const date = new Date(value);\n // Unparseable is left exactly as it arrived: inventing `null` would erase a\n // value the database holds, and this is a rendering, not a validator.\n return isNaN(date.getTime()) ? value : date.toISOString();\n}\n\n/**\n * One scalar, as REST serves it: declared numbers as numbers, declared dates as\n * RFC 3339, everything else exactly as the database returned it.\n */\nfunction toRestScalar(value: unknown, property: Property | undefined): unknown {\n if (property?.type === \"date\") return toRestDate(value);\n return coerceDeclaredNumber(value, property);\n}\n\n/**\n * Apply {@link toRestScalar} across a row, leaving undeclared columns alone.\n *\n * A target rendered differently from its parent is the shape bug this whole\n * file exists to prevent — a date was a string at the top level and a\n * `{ __type: \"date\" }` envelope one level down, in the same response. The\n * include loader renders its targets with {@link toRestRow}, through\n * `RelationService`'s `\"rest\"` style, for the same reason.\n */\nfunction toRestValues(\n row: Record<string, unknown>,\n collection: CollectionConfig\n): Record<string, unknown> {\n const properties = collection.properties;\n if (!properties) return row;\n\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row)) {\n out[key] = toRestScalar(value, properties[key] as Property | undefined);\n }\n return out;\n}\n\n/** Render one target row in the requested style. */\n/**\n * Drop every column this caller may not read.\n *\n * Password hashes and verification tokens have to be readable server-side but\n * must never reach a client — and \"never\" has to mean every exit from this\n * pipeline, including relation targets, or a secret leaks through whichever\n * path was overlooked. Keyed by both the property name and its column name,\n * since a row can arrive keyed either way depending on the caller.\n *\n * `excludeFromApi` is the case with no roles in it: `effectiveAccess` expands\n * the flag to `read: []`, which no caller satisfies, so the flag needs no branch\n * of its own here and no longer has one. `access.read: [\"hr\"]` is the same walk\n * asking a different question of {@link currentFieldViewer}.\n *\n * The field is **deleted**, never nulled. A withheld value that arrives as\n * `null` is indistinguishable from a stored `null`, which turns a permission\n * boundary into a question a client can answer by counting nulls — and it makes\n * an `update` that echoes the row back overwrite the real value with the null it\n * was handed.\n *\n * A withheld to-one relation takes its foreign key with it. `bandId: 7` names\n * the salary band exactly as `band: { id: 7 }` does, and the row carries the\n * column beside the relation — so withholding only the relation withheld\n * nothing.\n *\n * `_matches` is filtered rather than deleted: it is the list of fields a text\n * search hit, and a field the caller cannot read must not appear in it even\n * though the array itself is theirs to see.\n */\nexport function stripUnreadable(\n row: Record<string, unknown>,\n collection: CollectionConfig\n): Record<string, unknown> {\n const properties = collection.properties as Record<string, Property> | undefined;\n if (!properties) return row;\n\n const viewer = currentFieldViewer();\n const hidden = new Set<string>();\n const hide = (name: string) => {\n hidden.add(name);\n delete row[name];\n };\n // Resolved only when a withheld property is a relation, which is rare: this\n // runs on every row of every read.\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n\n for (const [key, property] of Object.entries(properties)) {\n if (canReadField(property, viewer)) continue;\n hide(key);\n if (property.columnName) hide(property.columnName);\n if (property.type === \"relation\") {\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, key);\n if (relation?.kind === \"belongsTo\") {\n hide(fieldKeyForColumn(collection, relation.localKey));\n hide(relation.localKey);\n }\n }\n }\n\n // A relation withheld from this viewer is withheld whole: its total under\n // `_count` says as much about it as its rows would.\n const counts = row[INCLUDE_COUNT_KEY];\n if (hidden.size > 0 && counts && typeof counts === \"object\") {\n for (const name of hidden) delete (counts as Record<string, unknown>)[name];\n if (Object.keys(counts).length === 0) delete row[INCLUDE_COUNT_KEY];\n }\n if (hidden.size > 0 && Array.isArray(row._matches)) {\n row._matches = (row._matches as unknown[]).filter(\n match => !hidden.has(typeof match === \"string\" ? match : String((match as { field?: unknown })?.field))\n );\n }\n return row;\n}\n\n/**\n * May the current caller be handed the rows of this relation of `collection`?\n *\n * A relation property's value *is* its related rows, so a property the caller\n * may not read withholds the relation whole: an `include` neither loads it nor\n * counts it. {@link stripUnreadable} cannot be the only guard here, because the\n * REST rows are stripped before the include loader runs (`toRestRow`, then\n * `loadIncludes`) — the loader wrote the rows back onto the key the strip had\n * just deleted, and `_count` beside them.\n *\n * Judged by every `type: \"relation\"` property that names this relation: by its\n * own key (the include key of an inline relation), or by the `relationName` it\n * addresses in the collection's `relations` array. A relation declared only in\n * `relations` has no property, so no field rule, and stays readable.\n */\nexport function canReadRelation(\n collection: CollectionConfig,\n key: string,\n relation: ResolvedRelation\n): boolean {\n const properties = collection.properties as Record<string, Property> | undefined;\n if (!properties) return true;\n\n const viewer = currentFieldViewer();\n if (!canReadField(properties[key], viewer)) return false;\n\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n for (const [name, property] of Object.entries(properties)) {\n if (property?.type !== \"relation\" || canReadField(property, viewer)) continue;\n relations ??= resolveCollectionRelations(collection);\n if (findRelation(relations, name) === relation) return false;\n const addressed = property.relation?.relationName;\n if (addressed && findRelation(relations, addressed) === relation) return false;\n }\n return true;\n}\n\nfunction renderTarget(\n targetRow: Record<string, unknown>,\n targetCollection: CollectionConfig,\n style: RelationStyle,\n registry: PostgresCollectionRegistry\n): unknown {\n if (style === \"inline\") {\n // The target's columns, and only those: its address is the consumer's\n // to derive, and merging one in overwrites a real `id` column.\n return stripUnreadable(toRestValues({ ...targetRow }, targetCollection), targetCollection);\n }\n\n const address = relationTargetAddress(targetRow, targetCollection, registry);\n const path = targetCollection.slug;\n return createRelationRefWithData(address, path, {\n id: address,\n path,\n // Stripped here as well as in the inline branch above. The two branches\n // are the same data in two renderings — REST inlines the target's\n // columns, the admin and every realtime frame carry a ref with those\n // columns attached — and only the first one was filtered. So a password\n // hash that REST correctly withheld rode out on every `.listen()` frame\n // and every WebSocket fetch of anything with a relation to users.\n values: stripUnreadable(\n normalizeDbValues(targetRow, targetCollection) as Record<string, unknown>,\n targetCollection\n )\n });\n}\n\n/**\n * The address a relation ref points at.\n *\n * The whole key, not its first column: a composite-keyed target addressed by\n * `tenant_id` alone points at every row that shares it. A target whose key\n * cannot be resolved at all used to throw here — reading `[0]` of an empty\n * array — taking down the parent's fetch over a relation it may not even have\n * asked for. The first column is a guess, but a ref that resolves to nothing\n * beats no rows at all.\n */\nexport function relationTargetAddress(\n targetRow: Record<string, unknown>,\n targetCollection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): string {\n const address = deriveRowAddress(targetRow, targetCollection, registry);\n if (address) return address;\n return String(targetRow[Object.keys(targetRow)[0]] ?? \"\");\n}\n\n/**\n * The row the admin renders: every column, with relations as references.\n *\n * Values are normalized (dates, numbers, NaN) because the admin's view-model\n * expects real types. The row's own address is *not* among the columns — it is\n * derived by the consumer from the collection's primary keys.\n */\nexport function toFlatRow(\n row: Record<string, unknown>,\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): Record<string, unknown> {\n const resolvedRelations = resolveCollectionRelations(collection);\n const normalized = normalizeDbValues(row, collection) as Record<string, unknown>;\n\n // Keyed by relation, reading the drizzle relation name off the raw row: the\n // relation's property key and the name drizzle nested it under are not\n // always the same, and the value is written back under the property key.\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n const relData = row[relation.relationName || key];\n if (relData === undefined || relData === null) continue;\n\n if (relation.cardinality === \"many\" && Array.isArray(relData)) {\n const targetCollection = relation.target();\n normalized[key] = relData.map((item: Record<string, unknown>) =>\n renderTarget(\n isJunctionRelation(relation) ? unwrapJunctionRow(item, relation) : item,\n targetCollection,\n \"ref\",\n registry\n ));\n } else if (relation.cardinality === \"one\" && typeof relData === \"object\" && !Array.isArray(relData)) {\n normalized[key] = renderTarget(relData as Record<string, unknown>, relation.target(), \"ref\", registry);\n }\n }\n\n return stripUnreadable(normalized, collection);\n}\n\n/**\n * The row REST serves: every column under its own name, with the value Postgres\n * returned, and relations inlined as the target's columns.\n *\n * Values are the ones the database returned, except where that contradicts the\n * declared type: a `number` property is served as a number (see\n * {@link coerceDeclaredNumber}) and a `date` as RFC 3339, which is what this\n * server's own OpenAPI document says a date column is (see\n * {@link toRestDate}).\n *\n * Keyed by the row rather than by the relation list — a REST fetch only loads\n * the relations `include` asked for, so the row is the authority on which are\n * actually there.\n */\nexport function toRestRow(\n row: Record<string, unknown>,\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): Record<string, unknown> {\n const resolvedRelations = resolveCollectionRelations(collection);\n const flat: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(row)) {\n const relation = findRelation(resolvedRelations, key);\n\n if (relation && Array.isArray(value)) {\n flat[key] = value.map((item: Record<string, unknown>) =>\n renderTarget(\n isJunctionRelation(relation) ? unwrapJunctionRow(item, relation) : item,\n relation.target(),\n \"inline\",\n registry\n ));\n } else if (relation && typeof value === \"object\" && value !== null) {\n flat[key] = renderTarget(value as Record<string, unknown>, relation.target(), \"inline\", registry);\n } else {\n flat[key] = toRestScalar(value, collection.properties?.[key] as Property | undefined);\n }\n }\n\n return stripUnreadable(flat, collection);\n}\n","import { and, sql, SQL } from \"drizzle-orm\";\nimport { PgTable } from \"drizzle-orm/pg-core\";\nimport { ApiError } from \"@rebasepro/server\";\nimport { DrizzleClient } from \"../interfaces\";\n\n/**\n * Explain a write that matched no rows.\n *\n * Row-level security filters UPDATE and DELETE through the policy's USING\n * clause instead of raising: a denied write is reported by Postgres exactly\n * like a successful one that happened to match nothing. Left unchecked, a\n * caller cannot tell \"denied\" from \"done\" — the write returns 200/204 and the\n * row is untouched. An agent handed a key with `orders:delete` and no delete\n * policy is the case that makes it concrete: it deletes nothing, forever, and\n * is told it worked every time.\n *\n * Re-reading the target over the *same* RLS-scoped handle separates the two\n * cases. A visible row means the policy rejected the write (403); an invisible\n * one means there is nothing there to write for this caller (404, matching what\n * a GET would say). The re-read is bound by the caller's own policies, so it\n * discloses nothing a plain read wouldn't.\n *\n * Only reached when zero rows matched, so the happy path pays nothing.\n *\n * It lives here, rather than beside its first caller, because every zero-row\n * write has to answer the same question and answer it identically: the rule\n * that a readable-but-unwritable target is a 403 is the contract, and a second\n * copy of it is a second chance to get it wrong.\n *\n * @param handle The RLS-scoped connection the write ran on — not a fresh\n * one, or the re-read would answer for a different caller.\n * @param table The table the write targeted (the junction, for a link).\n * @param conditions The write's own WHERE terms, reused verbatim.\n * @param denied Message for the 403: the target is there and was refused.\n * @param missing Message for the 404: there is nothing there for this caller.\n */\nexport async function explainZeroRowWrite(\n handle: DrizzleClient,\n table: PgTable,\n conditions: SQL[],\n denied: string,\n missing: string\n): Promise<ApiError> {\n const visible = await handle\n .select({ present: sql<number>`1` })\n .from(table)\n .where(and(...conditions))\n .limit(1);\n\n if (visible.length > 0) {\n return ApiError.forbidden(denied, \"WRITE_DENIED\");\n }\n\n return ApiError.notFound(missing);\n}\n","import { and, eq, inArray, sql, type SQL } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport type { Properties, ResolvedVia } from \"@rebasepro/types\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { relationMisconfigured } from \"./collection-helpers\";\nimport { explainZeroRowWrite } from \"./write-denial\";\n\n/**\n * Writing a many-to-many means writing rows in a junction table, and doing that\n * needs three things: the table, the column naming the row being written from,\n * and the column naming the far side.\n *\n * Those three used to be re-derived at each of the four call sites — twice for\n * `through`, twice for `joinPath`, in the owning and the inverse direction —\n * and the two `joinPath` derivations did not agree. The inverse one keyed off\n * `step.table`; the owning one asked `getTableNamesFromColumns` which table a\n * step's columns belonged to, and that answers `\"\"` for an unqualified column\n * name. So for `{ table: \"posts_tags\", on: { from: \"id\", to: \"tag_id\" } }` —\n * the form the docs and every fixture use — no branch matched, both columns\n * stayed null, and the write was skipped with a warning nobody reads. Writing\n * a to-many `via` relation did nothing at all.\n *\n * Hence one binder, keyed on `step.table` (always present) and accepting the\n * qualified `table.column` form where it is used.\n */\nexport interface JunctionBinding {\n table: PgTable;\n /** The junction column holding the id of the row being written from. */\n parentColumn: AnyPgColumn;\n /** The junction column holding the id of the row on the far side. */\n targetColumn: AnyPgColumn;\n /** `<collection>.<relation>`, for error messages. */\n label: string;\n /**\n * The link's own columns — `through.properties` — as authored. Empty for a\n * junction that carries nothing but its two keys, and for every `via`\n * relation (a join chain has no declared payload).\n */\n payload: Properties;\n}\n\n/**\n * One element of a membership write: a target id, and optionally what the link\n * to it should hold.\n *\n * The pivot is kept apart from the id rather than merged into a row shape,\n * because the two are answered by different collections: the id is parsed\n * against the *target's* primary key, the payload validated against the\n * junction's own properties.\n */\nexport interface JunctionLinkWrite {\n id: unknown;\n /** Payload values keyed by property key, already serialized for the driver. */\n pivot?: Record<string, unknown>;\n}\n\n/**\n * The target rows a membership write can see, when that is not all of them —\n * the target's `beforeQuery` scope and its soft delete.\n *\n * The diff reads \"what is linked now\" through it, so a link to a row the caller\n * was never shown is in neither list and is left alone rather than unlinked.\n */\nexport interface VisibleTargets {\n table: PgTable;\n /** The target column the junction's `targetColumn` holds values of. */\n idColumn: AnyPgColumn;\n condition: SQL;\n}\n\n/** A junction that cannot be resolved is a broken relation, not a no-op. */\nconst misconfigured = (label: string, detail: string): ApiError =>\n relationMisconfigured(label, detail);\n\nfunction column(table: PgTable, name: string | undefined, label: string, role: string): AnyPgColumn {\n const col = name ? table[name as keyof typeof table] as AnyPgColumn : undefined;\n if (!col) {\n throw misconfigured(label, `no ${role} column '${name ?? \"?\"}' on the junction table`);\n }\n return col;\n}\n\n/** The junction a `manyToMany` names outright. */\nexport function bindThroughJunction(\n registry: PostgresCollectionRegistry,\n through: { table: string; sourceColumn: string; targetColumn: string; properties?: Properties },\n label: string\n): JunctionBinding {\n const table = registry.getTable(through.table);\n if (!table) {\n throw misconfigured(label, `no table '${through.table}' in the registry`);\n }\n return {\n table,\n parentColumn: column(table, through.sourceColumn, label, \"source\"),\n targetColumn: column(table, through.targetColumn, label, \"target\"),\n label,\n payload: through.properties ?? {}\n };\n}\n\n/** The bare column name, whether written as `col` or `table.col`. */\nconst columnPart = (spec: string | string[]): string => {\n const one = Array.isArray(spec) ? spec[0] : spec;\n return one.includes(\".\") ? one.split(\".\")[1] : one;\n};\n\n/** The table a column spec names, or undefined when it is unqualified. */\nconst tablePart = (spec: string | string[]): string | undefined => {\n const one = Array.isArray(spec) ? spec[0] : spec;\n return one.includes(\".\") ? one.split(\".\")[0] : undefined;\n};\n\n/**\n * The junction a `via` relation reaches through, from either end.\n *\n * A step is `{ table: T, on: { from, to } }` where `from` names a column on\n * whatever the walk was standing on and `to` names one on `T`. That positional\n * meaning is what makes the unqualified form work at all, so the walk carries\n * the previous table rather than asking the column names where they live.\n */\nexport function bindJoinPathJunction(\n registry: PostgresCollectionRegistry,\n joinPath: ResolvedVia[\"joinPath\"],\n parentTableName: string,\n targetTableName: string,\n label: string\n): JunctionBinding {\n const junctionName = joinPath\n .map(step => step.table)\n .find(table => table !== parentTableName && table !== targetTableName);\n\n if (!junctionName) {\n throw misconfigured(label, \"its joinPath has no table between the two ends to hold the links\");\n }\n\n const table = registry.getTable(junctionName);\n if (!table) {\n throw misconfigured(label, `no table '${junctionName}' in the registry`);\n }\n\n let parentColumnName: string | undefined;\n let targetColumnName: string | undefined;\n let previousTable = parentTableName;\n\n for (const step of joinPath) {\n const fromTable = tablePart(step.on.from) ?? previousTable;\n const toTable = tablePart(step.on.to) ?? step.table;\n\n // Take the column on the junction's own side of the step, and let the\n // other side say which end of the relation this step reached.\n if (toTable === junctionName && fromTable === parentTableName) {\n parentColumnName = columnPart(step.on.to);\n } else if (fromTable === junctionName && toTable === parentTableName) {\n parentColumnName = columnPart(step.on.from);\n } else if (toTable === junctionName && fromTable === targetTableName) {\n targetColumnName = columnPart(step.on.to);\n } else if (fromTable === junctionName && toTable === targetTableName) {\n targetColumnName = columnPart(step.on.from);\n }\n\n previousTable = step.table;\n }\n\n if (!parentColumnName || !targetColumnName) {\n throw misconfigured(\n label,\n `its joinPath does not connect '${parentTableName}' and '${targetTableName}' through '${junctionName}'`\n );\n }\n\n return {\n table,\n parentColumn: column(table, parentColumnName, label, \"source\"),\n targetColumn: column(table, targetColumnName, label, \"target\"),\n label,\n // A `via` reaches through a table it did not declare, so there is no\n // `through.properties` to read and nothing to write beyond the keys.\n payload: {}\n };\n}\n\n/**\n * Remove one link, leaving the row on the far side alone.\n *\n * This is what `DELETE authors/1/tags/5` has to mean for a many-to-many: the\n * target is shared, so deleting the row would remove the tag from every other\n * post that uses it.\n */\nexport async function removeJunctionLink(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n targetId: unknown,\n subject: { parent: string; relation: string }\n): Promise<void> {\n const conditions = [\n eq(binding.parentColumn, parentId),\n eq(binding.targetColumn, targetId)\n ];\n const result = await tx.delete(binding.table).where(and(...conditions));\n\n // The link is the row here, so the junction's own policies decide. Callers\n // establish membership first, over this same handle, which is what makes a\n // zero-row delete meaningful rather than ambiguous.\n if ((result.rowCount ?? 0) === 0) {\n throw await explainZeroRowWrite(\n tx,\n binding.table,\n conditions,\n `Not allowed to unlink \"${targetId}\" from \"${subject.parent}\" \"${parentId}\": ` +\n \"a row-level security policy rejected the write.\",\n `No \"${subject.relation}\" link between \"${subject.parent}\" \"${parentId}\" ` +\n `and \"${targetId}\" to remove.`\n );\n }\n\n logger.info(`Unlinked '${subject.relation}' ${targetId} from ${subject.parent} ${parentId}`);\n}\n\n/**\n * Whether a stored link already holds every value of `wanted`.\n *\n * A client that edits one member's role sends the whole array back, each\n * element with the `_pivot` it was read with; updating every one of them made\n * one statement and one CDC event per member for a change to one. Equal only\n * when that is certain — the same string, number or boolean, the same instant —\n * and never for a pair that might differ (a JSON value, a `numeric` read back\n * as text): those are written, as every one of them used to be.\n */\nconst PIVOT_SELECTION_PREFIX = \"pivot:\";\n\nfunction holdsPivot(stored: Record<string, unknown> | undefined, wanted: Record<string, unknown>): boolean {\n if (!stored) return false;\n return Object.entries(wanted).every(([key, value]) => samePivotValue(value, stored[key]));\n}\n\nfunction samePivotValue(wanted: unknown, stored: unknown): boolean {\n if (wanted === null || wanted === undefined) return stored === null || stored === undefined;\n if (wanted instanceof Date) return stored instanceof Date && stored.getTime() === wanted.getTime();\n if (typeof wanted === \"string\" || typeof wanted === \"number\" || typeof wanted === \"boolean\") return wanted === stored;\n return false;\n}\n\n/**\n * Make the junction say that `parentId` is linked to exactly `targetIds`, by\n * diffing against what is linked now rather than replacing the set.\n *\n * A save of the parent used to delete every junction row for it and re-insert\n * the ids the browser sent — a list the browser assembled from a read it did\n * earlier. Three things followed, all data loss rather than display:\n *\n * - **Lost update.** Two editors with post 7 open: A adds tag X and saves, B\n * saves any field from a form that predates it, and X is gone with nothing\n * reported to either of them.\n * - **A partially-read set is a partially-deleted set.** The read that fills\n * the form runs under RLS, so a user who may edit the parent but cannot see\n * some of the linked rows gets a shorter list — and writing it back deleted\n * the links they were never shown. The select that drives the diff runs in\n * this same transaction under the same policies, and through\n * `visibleTargets` — the target's `beforeQuery` scope and soft delete, which\n * RLS knows nothing of — so a link the caller cannot read is in neither list\n * and survives the save.\n * - **Junction payload columns.** A junction carrying its own columns\n * (`position`, `role`, `created_at`) lost them on every save, because every\n * row was re-inserted with only the two keys. Untouched links are left alone.\n *\n * The insert is `ON CONFLICT DO NOTHING`, so two sessions adding the same link\n * concurrently is a no-op rather than a unique violation.\n *\n * ## The payload\n *\n * An element may carry `pivot` — the link's own columns, from a `_pivot` on the\n * wire. The array still *sets membership*: what is not in it is unlinked, and\n * what is in it is linked. The payload is what the link should hold once it\n * exists, so a new link is inserted with it (columns the caller left out take\n * their DEFAULT) and an existing link named with a payload is updated to match.\n *\n * The alternative — ignoring the payload on a link that already exists — makes\n * `PATCH { members: [{ id: 7, _pivot: { role: \"admin\" } }] }` a no-op that\n * answers 200, which is the one outcome a caller cannot tell from success. An\n * element with no `pivot` at all leaves the existing link exactly as it was;\n * that is what keeps a plain `[1, 2, 3]` membership write from wiping the\n * `role`s off the links it did not mention.\n */\nexport async function applyJunctionMembership(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n links: JunctionLinkWrite[],\n visibleTargets?: VisibleTargets\n): Promise<void> {\n // The link's own columns are read with its key, so a link sent back with\n // the values it already holds is not rewritten below.\n // Flat, under a prefix no key column can carry, so a payload column named\n // like the key cannot shadow it.\n const selection: Record<string, AnyPgColumn> = { targetId: binding.targetColumn };\n const payloadKeys: string[] = [];\n for (const key of Object.keys(binding.payload)) {\n const payloadColumn = binding.table[key as keyof typeof binding.table] as AnyPgColumn | undefined;\n if (!payloadColumn) continue;\n selection[`${PIVOT_SELECTION_PREFIX}${key}`] = payloadColumn;\n payloadKeys.push(key);\n }\n const existingRows = visibleTargets\n ? await tx\n .select(selection)\n .from(binding.table)\n .innerJoin(visibleTargets.table, eq(binding.targetColumn, visibleTargets.idColumn))\n .where(and(eq(binding.parentColumn, parentId), visibleTargets.condition))\n : await tx\n .select(selection)\n .from(binding.table)\n .where(eq(binding.parentColumn, parentId));\n\n // Keyed by `String(...)` because a junction key can come back from the\n // driver as a string where the parsed value is a number, and a diff that\n // missed that would delete and re-insert every link on every save.\n const existingById = new Map<string, unknown>();\n const storedPivotById = new Map<string, Record<string, unknown>>();\n for (const row of existingRows as Array<Record<string, unknown>>) {\n if (row.targetId === null || row.targetId === undefined) continue;\n existingById.set(String(row.targetId), row.targetId);\n if (payloadKeys.length > 0) {\n storedPivotById.set(\n String(row.targetId),\n Object.fromEntries(payloadKeys.map(key => [key, row[`${PIVOT_SELECTION_PREFIX}${key}`]]))\n );\n }\n }\n\n const wantedById = new Map<string, JunctionLinkWrite>();\n for (const link of links) {\n if (link.id === null || link.id === undefined) continue;\n wantedById.set(String(link.id), link);\n }\n\n const removed = [...existingById.entries()]\n .filter(([key]) => !wantedById.has(key))\n .map(([, value]) => value);\n const added = [...wantedById.entries()]\n .filter(([key]) => !existingById.has(key))\n .map(([, link]) => link);\n // Already linked AND named with a payload: the caller is stating what the\n // link holds, not re-stating that it exists.\n const repayloaded = [...wantedById.entries()]\n .filter(([key, link]) => existingById.has(key) && link.pivot && Object.keys(link.pivot).length > 0\n && !holdsPivot(storedPivotById.get(key), link.pivot))\n .map(([, link]) => link);\n\n if (removed.length > 0) {\n await removeLinks(tx, binding, parentId, removed);\n }\n\n if (added.length > 0) {\n await tx.insert(binding.table)\n .values(added.map(link => ({\n [binding.parentColumn.name]: parentId,\n [binding.targetColumn.name]: link.id,\n ...(link.pivot ?? {})\n })))\n .onConflictDoNothing();\n }\n\n for (const link of repayloaded) {\n await tx.update(binding.table)\n .set(link.pivot!)\n .where(and(\n eq(binding.parentColumn, parentId),\n eq(binding.targetColumn, link.id)\n ));\n }\n}\n\n/**\n * Set the columns of ONE existing link, without touching the membership.\n *\n * The membership array cannot express this on its own: sending one element\n * would unlink everything else, and sending the whole set to change one link's\n * `role` is the lost-update race {@link applyJunctionMembership}'s diff exists\n * to avoid, reintroduced by the caller. So there is a route for it —\n * `PATCH /api/data/:slug/:id/:relation/:targetId` with a `_pivot` body.\n *\n * A link that is not there is a **404**, not a silent insert: \"update the role\n * on this membership\" and \"create this membership\" are different requests, and\n * a caller who mistyped an id should hear about it rather than get a new link\n * with a partial payload. Zero rows is also how a policy refusal arrives on\n * Postgres, which is why the distinction is drawn by\n * {@link explainZeroRowWrite} rather than by this function guessing.\n */\nexport async function updateJunctionPivot(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n targetId: unknown,\n values: Record<string, unknown>,\n subject: { parent: string; relation: string }\n): Promise<void> {\n const conditions = [\n eq(binding.parentColumn, parentId),\n eq(binding.targetColumn, targetId)\n ];\n const result = await tx.update(binding.table).set(values).where(and(...conditions));\n\n if ((result.rowCount ?? 0) === 0) {\n throw await explainZeroRowWrite(\n tx,\n binding.table,\n conditions,\n `Not allowed to update the link between \"${subject.parent}\" \"${parentId}\" and ` +\n `\"${targetId}\": a row-level security policy rejected the write.`,\n `No \"${subject.relation}\" link between \"${subject.parent}\" \"${parentId}\" ` +\n `and \"${targetId}\" to update.`\n );\n }\n\n logger.info(`Updated '${subject.relation}' link ${targetId} on ${subject.parent} ${parentId}`);\n}\n\n/**\n * Drop the named links, and refuse to call it done if the database kept any.\n *\n * Every id here came out of the select in {@link applyJunctionMembership}, on\n * this same handle, so all of them were visible. Fewer deletions than that means\n * something refused them — and a save that reports success while the membership\n * it stored is not the membership it was given has told the caller something\n * untrue about the database.\n */\nasync function removeLinks(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n removed: unknown[]\n): Promise<void> {\n const conditions = [\n eq(binding.parentColumn, parentId),\n inArray(binding.targetColumn, removed)\n ];\n const result = await tx.delete(binding.table).where(and(...conditions));\n const deleted = result.rowCount ?? 0;\n\n if (deleted >= removed.length) return;\n\n const survivors = await tx\n .select({ present: sql<number>`1` })\n .from(binding.table)\n .where(and(...conditions))\n .limit(1);\n\n // Nothing survived: a concurrent session removed the rest between the\n // select and the delete. The membership is what the caller asked for,\n // which is all this promised.\n if (survivors.length === 0) return;\n\n throw ApiError.forbidden(\n `Not allowed to remove ${removed.length - deleted} of ${removed.length} link(s) ` +\n `for relation '${binding.label}': a row-level security policy rejected the write.`,\n \"WRITE_DENIED\"\n );\n}\n","import { and, isNotNull, isNull, SQL } from \"drizzle-orm\";\nimport type { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { getTableColumns } from \"drizzle-orm\";\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { softDeleteFieldOf } from \"@rebasepro/common\";\nimport { ApiError } from \"@rebasepro/server\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\n/**\n * Soft delete, in one place.\n *\n * A collection with `softDelete` records a deletion as a timestamp instead of\n * removing the row, which means two things have to hold everywhere and not\n * almost everywhere:\n *\n * 1. a delete writes the stamp rather than issuing `DELETE`;\n * 2. **every** read hides stamped rows unless asked otherwise.\n *\n * The second is the one that goes wrong. A listing that filters and a `count`\n * that does not is a page saying \"1 of 4 results\"; a relation load that does not\n * is a deleted comment reappearing under its post. So the condition is built by\n * exactly one function, here, and the read paths call it — rather than each\n * path growing its own `isNull(...)`, which is how these features rot.\n *\n * The field is a `date` property the collection declares itself. This module\n * resolves it and nothing more: it does not add the column, and a config that\n * turns the flag on without the property is refused at boot\n * (`assertCollectionConfigs`), where the answer is a config change, rather than\n * at the first delete, where it would be a 500 for whoever pressed the button.\n */\n\n/** How a caller asks about deleted rows. See `FetchCollectionProps.withDeleted`. */\nexport type WithDeleted = boolean | \"only\" | undefined;\n\nexport interface SoftDeleteField {\n /** The property key — the wire name, what a `PATCH` sets to `null`. */\n field: string;\n /** The physical column, `columnName ?? snake_case(field)`. */\n columnName: string;\n}\n\n/**\n * The soft-delete field this collection uses, or `undefined` if it has none.\n *\n * Returns the field even when the property is missing, because the *boot* check\n * is what refuses that config; making this return `undefined` for it would turn\n * a misconfiguration into \"soft delete quietly off\", which is the failure mode\n * worth avoiding — deletes would silently start removing rows.\n */\nexport function resolveSoftDelete(collection: CollectionConfig | undefined): SoftDeleteField | undefined {\n const field = softDeleteFieldOf(collection);\n if (!field) return undefined;\n const property = (collection?.properties as Record<string, { columnName?: string }> | undefined)?.[field];\n return {\n field,\n columnName: property?.columnName ?? toSnakeCase(field)\n };\n}\n\n/**\n * The drizzle column behind the soft-delete field, if the table has one.\n *\n * Matched on the column's own `name` rather than on the key, because a\n * generated schema keys columns by the property name while an introspected one\n * may key them by the column — and this has to work for both.\n */\nexport function softDeleteColumn(\n collection: CollectionConfig | undefined,\n table: PgTable\n): AnyPgColumn | undefined {\n const resolved = resolveSoftDelete(collection);\n if (!resolved) return undefined;\n // A registry can hand back something that is not a drizzle table (a stub in\n // a test, a collection whose schema has not been generated). No columns is\n // \"no soft-delete column\", not a crash on a read.\n const columns = (table ? getTableColumns(table) : undefined) as Record<string, AnyPgColumn> | undefined;\n if (!columns) return undefined;\n const direct = columns[resolved.field] ?? columns[resolved.columnName];\n if (direct) return direct;\n for (const column of Object.values(columns)) {\n if ((column as { name?: string }).name === resolved.columnName) return column;\n }\n return undefined;\n}\n\n/**\n * `deleted_at IS NULL` — or nothing, when the caller asked for the deleted rows\n * or the collection has no soft delete at all.\n *\n * The one place the rule is written. Call it from every read; `undefined` means\n * \"add no condition\", which composes with `and(...)` unchanged.\n */\nexport function softDeleteCondition(\n collection: CollectionConfig | undefined,\n table: PgTable,\n withDeleted?: WithDeleted\n): SQL | undefined {\n if (withDeleted === true) return undefined;\n const column = softDeleteColumn(collection, table);\n if (!column) return undefined;\n return withDeleted === \"only\" ? isNotNull(column) : isNull(column);\n}\n\n/**\n * Fold the soft-delete condition into a list of conditions that is being built.\n *\n * A convenience for the call sites that already collect `SQL[]`, so adding soft\n * delete to one of them is a single line rather than three.\n */\nexport function withSoftDelete(\n conditions: SQL[],\n collection: CollectionConfig | undefined,\n table: PgTable,\n withDeleted?: WithDeleted\n): SQL[] {\n const condition = softDeleteCondition(collection, table, withDeleted);\n if (condition) conditions.push(condition);\n return conditions;\n}\n\n/**\n * Combine an existing `where` with the soft-delete condition.\n *\n * For the call sites that hold one composed condition rather than a list.\n */\nexport function andSoftDelete(\n where: SQL | undefined,\n collection: CollectionConfig | undefined,\n table: PgTable,\n withDeleted?: WithDeleted\n): SQL | undefined {\n const condition = softDeleteCondition(collection, table, withDeleted);\n if (!condition) return where;\n return where ? and(where, condition) : condition;\n}\n\n/**\n * The refusal of an upsert whose key belongs to a row in the trash.\n *\n * The key is taken — the stamped row still holds it — so the write cannot be a\n * create, and an upsert is not the operation that brings a deleted row back:\n * that is the restore, an update setting the field to `null`, which goes\n * through the update gate and is what history records as one. Left to the\n * statement, `INSERT … ON CONFLICT DO UPDATE` wrote the new values into the\n * hidden row and the caller was told \"created\", with the row still in the trash.\n */\nexport function rowInTrashError(path: string, key: string, softDelete: SoftDeleteField): ApiError {\n return ApiError.conflict(\n `\"${key}\" in \"${path}\" is in the trash, so an upsert cannot write it. Restore the row first `\n + `(an update setting '${softDelete.field}' to null), or purge it (a delete with ?hard=true) and upsert again.`,\n \"ROW_IN_TRASH\"\n );\n}\n","/**\n * `beforeQuery`: the one place a collection callback can change *which* rows a\n * read asks for.\n *\n * `afterRead` runs over rows that have already been fetched, so until this\n * existed an application developer who needed the read itself narrowed — a\n * tenant scope, a visibility window, a per-role row filter — had two options:\n * patch this driver, or `rebase eject`.\n *\n * Two properties are load-bearing, and both belong to the *shape* of the thing\n * rather than to how carefully each call site was written.\n *\n * ## It can only narrow\n *\n * A hook returns a {@link QueryNarrowing} — a declarative filter, optionally a\n * logical group — which is AND-ed into the query the caller sent. `AND(q, c)`\n * is a subset of `q` for every `c`, so there is no `c` a hook can return that\n * widens a read. The obvious alternative shape, `(query) => query`, can: a\n * hook that drops a condition on the way through runs the query without it,\n * and on an RLS data plane a dropped condition returns everything the policies\n * happen to allow. That is the same reasoning behind\n * `UnknownFilterFieldsMode` defaulting to `\"error\"`, and it is why that mode is\n * **forced** to `\"error\"` for a hook's own filter here whatever the\n * process-wide setting is: a scope condition naming a renamed column has to\n * refuse the request, never be dropped from it.\n *\n * ## It cannot be skipped\n *\n * The hooks are resolved *by the read path itself*, out of the registry that\n * read already holds — not handed to it by a caller who might forget. So a\n * read that compiles a WHERE compiles this one too, or it throws.\n *\n * The one thing a read path cannot derive is the identity the hook runs as:\n * `FetchService`, `RelationService` and `DataService` carry no user. That\n * arrives as a {@link ReadCallContextProvider}, threaded from the driver that\n * constructed them — the driver is the only object that knows both the caller\n * and the connection, and it constructs a service per transaction anyway, so\n * there is exactly one place per instance to pass it.\n *\n * Absence is the interesting case, and it fails closed: a service built with no\n * provider refuses any read of a collection that declares `beforeQuery`, with\n * a message naming the collection. So a future construction site that forgets\n * the provider breaks loudly on the first scoped collection instead of quietly\n * serving every row — which is the failure mode this whole module exists to\n * rule out. A deployment with no `beforeQuery` anywhere never reaches any of\n * this: the check is one map lookup and the compiled SQL is byte-identical.\n *\n * @module\n */\nimport { and, SQL } from \"drizzle-orm\";\nimport { PgTable } from \"drizzle-orm/pg-core\";\nimport type {\n BeforeQueryProps,\n CollectionConfig,\n CollectionCallbacks,\n QueryNarrowing,\n ReadOperation,\n ReadQuery,\n RebaseCallContext\n} from \"@rebasepro/types\";\nimport type { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport {\n DrizzleConditionBuilder,\n type FilterCompilationOptions,\n type UnknownFilterFieldsMode\n} from \"../utils/drizzle-conditions\";\n\n/**\n * How a read path reaches the context its `beforeQuery` hooks run with.\n *\n * A function rather than the context itself, because the driver's transaction\n * handle and its `data` plane are established *after* it constructs the\n * services that read through them — and because building a context costs an\n * object that a collection with no hooks should never pay for.\n */\nexport type ReadCallContextProvider = () => RebaseCallContext;\n\n/**\n * The `beforeQuery` hooks that apply to one collection, in the order they run.\n *\n * Global first, then the collection's own — the same order `afterRead` and\n * `beforeSave` use. The order changes nothing about the outcome here (both are\n * AND-ed in, so neither can relax the other) and is kept because a hook that\n * logs or measures should see the same sequence everywhere.\n *\n * There is no property tier: a property callback shapes one value on a row, and\n * a row filter is not a value.\n */\nfunction resolveBeforeQuery(\n registry: PostgresCollectionRegistry | undefined,\n collection: CollectionConfig | undefined\n): NonNullable<CollectionCallbacks[\"beforeQuery\"]>[] {\n const hooks: NonNullable<CollectionCallbacks[\"beforeQuery\"]>[] = [];\n const global = registry?.getGlobalCallbacks();\n if (global?.beforeQuery) hooks.push(global.beforeQuery);\n const own = collection?.callbacks;\n if (own?.beforeQuery) hooks.push(own.beforeQuery);\n return hooks;\n}\n\n/** Whether anything at all would run for this collection. */\nexport function hasBeforeQuery(\n registry: PostgresCollectionRegistry | undefined,\n collection: CollectionConfig | undefined\n): boolean {\n return resolveBeforeQuery(registry, collection).length > 0;\n}\n\n/**\n * What a read path is about to compile, in the form a hook is shown it.\n *\n * Built by the caller rather than derived here, because only the caller knows\n * which of its options are part of the query and which are transport — a\n * `databaseId`, an `include` tree, a `withDeleted` flag are not \"which rows\".\n */\nexport interface ReadQueryDescription {\n operation: ReadOperation;\n query: ReadQuery;\n}\n\n/** Everything `beforeQueryConditions` needs that is not about this one read. */\nexport interface BeforeQueryEnv {\n registry: PostgresCollectionRegistry | undefined;\n /**\n * The context provider the driver handed down. `undefined` means no driver\n * built this service, which is a refusal rather than a bypass — see the\n * module comment.\n */\n callContext: ReadCallContextProvider | undefined;\n}\n\n/**\n * The conditions every `beforeQuery` hook for this collection asks to add.\n *\n * Returned as a list rather than pre-combined so a caller pushes them onto the\n * same `allConditions` array its own filter goes into. Every caller AND-es\n * that array, which is what makes these additive — the hook is not trusted to\n * narrow, it is only ever given a way to.\n */\nexport async function beforeQueryConditions(\n env: BeforeQueryEnv,\n collection: CollectionConfig | undefined,\n path: string,\n table: PgTable<never>,\n description: ReadQueryDescription,\n filterContext: Omit<FilterCompilationOptions, \"unknownFields\">\n): Promise<SQL[]> {\n const hooks = resolveBeforeQuery(env.registry, collection);\n if (hooks.length === 0) return [];\n\n if (!collection) {\n // A hook reaching here with no collection can only be a global one, and\n // a global hook has no properties to compile its filter against. It\n // cannot be honoured — so it is refused, not skipped.\n throw new Error(\n `[beforeQuery] \"${path}\" resolves to no registered collection, so a global \\`beforeQuery\\` cannot ` +\n \"be compiled against it. Register the collection, or narrow the hook to the collections it applies to.\"\n );\n }\n\n if (!env.callContext) {\n throw new Error(\n `[beforeQuery] \"${collection.slug}\" declares a \\`beforeQuery\\` hook, and this read was compiled by a ` +\n \"service that was built without a call-context provider, so the hook could not run. Serving the read \" +\n \"unnarrowed is not an option — a hook that does not run widens the result set. Construct the \" +\n \"service through the driver (which passes one), or pass a provider explicitly.\"\n );\n }\n const context = env.callContext();\n\n // Always fatal, whatever `configureUnknownFilterFields` was set to. The\n // process-wide `\"warn\"` mode exists for a deployment that knowingly sends\n // filter keys its tables do not have; a scope condition is not that, and\n // dropping one is precisely the widening that mode's own doc comment is\n // about.\n const unknownFields: UnknownFilterFieldsMode = \"error\";\n const options: FilterCompilationOptions = { ...filterContext, unknownFields };\n\n const conditions: SQL[] = [];\n for (const hook of hooks) {\n const props: BeforeQueryProps = {\n collection,\n path,\n operation: description.operation,\n query: description.query,\n context\n };\n const narrowing: QueryNarrowing | void = await hook(props);\n\n if (!narrowing) continue;\n\n // A hook that returned an object but expressed nothing in it is almost\n // certainly a branch that meant to build a filter and did not, and the\n // failure is invisible: the read is served whole. Refusing is the loud\n // half, and it comes before the compile so the message is about the\n // hook rather than about an empty condition list.\n if (!narrowing.filter && !narrowing.logical) {\n throw new Error(\n `[beforeQuery] \"${collection.slug}\" returned an object with neither \\`filter\\` nor \\`logical\\`. ` +\n \"Return nothing (or `undefined`) to add no conditions; an empty object reads as a scope that was \" +\n \"meant to be built and was not.\"\n );\n }\n\n if (narrowing.filter) {\n conditions.push(...DrizzleConditionBuilder.buildFilterConditions(\n narrowing.filter, table, path, options\n ));\n }\n if (narrowing.logical) {\n const logical = DrizzleConditionBuilder.buildLogicalConditions(\n narrowing.logical, table, path, options\n );\n if (logical) conditions.push(logical);\n }\n }\n return conditions;\n}\n\n/**\n * {@link beforeQueryConditions}, pre-combined, for the read paths that hold a\n * single `SQL` rather than a list of them — a single get, a relation loader.\n */\nexport async function beforeQueryCondition(\n env: BeforeQueryEnv,\n collection: CollectionConfig | undefined,\n path: string,\n table: PgTable<never>,\n description: ReadQueryDescription,\n filterContext: Omit<FilterCompilationOptions, \"unknownFields\">\n): Promise<SQL | undefined> {\n const conditions = await beforeQueryConditions(env, collection, path, table, description, filterContext);\n if (conditions.length === 0) return undefined;\n if (conditions.length === 1) return conditions[0];\n return and(...conditions);\n}\n","import { and, eq, getTableColumns, inArray, lte, notInArray, or, sql, SQL, getTableName as drizzleTableName } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { CollectionConfig, FilterValues, OrderByTuple, ResolvedRelation, ResolvedManyToMany, ResolvedHasMany, ResolvedHasOne } from \"@rebasepro/types\";\nimport { getTableName, getJunctionConfigForRelation, resolveCollectionRelations, findRelation, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { hasForeignKeyOnTarget, isManyToMany, type ResolvedVia } from \"@rebasepro/types\";\nimport { DrizzleConditionBuilder, joinColumn } from \"../utils/drizzle-conditions\";\nimport {\n getCollectionByPath,\n getTableForCollection,\n relationMisconfigured,\n requirePrimaryKeys,\n parseIdValues,\n buildCompositeId,\n joinsOnNaturalKey,\n rowIdentityCondition,\n primaryKeyColumns,\n sourceKeyField,\n columnNamed,\n type PrimaryKeyInfo\n} from \"./collection-helpers\";\nimport { parseDataFromServer } from \"../data-transformer\";\nimport { stripUnreadable, toRestRow } from \"./row-pipeline\";\nimport { hiddenColumnsOption } from \"../schema/search-column\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport type { NestedPathHop } from \"./nested-path\";\nimport { bindThroughJunction } from \"./junction-writes\";\n// A soft-deleted row must not come back through a relation either: a deleted\n// comment reappearing under its post is the same bug as one reappearing in the\n// listing. See `soft-delete.ts`.\nimport { andSoftDelete, softDeleteCondition } from \"./soft-delete\";\nimport { beforeQueryCondition, type ReadCallContextProvider } from \"./read-scope\";\n\n/**\n * The ids in a to-many relation write, whatever shape the caller sent.\n *\n * A membership list is written as either the related rows (`[{ id: 1 }]`, what\n * the admin UI sends back after reading them) or as bare keys (`[1]`, `[\"t-1\"]`,\n * what anyone writing the API by hand sends). Only the first was read, via a\n * blind `.map(rel => rel.id)`, and a bare key therefore became `undefined`:\n * on a numeric-keyed target that surfaced as `Invalid numeric ID: undefined`,\n * and on a string-keyed one it did not surface at all — `String(undefined)`\n * wrote a junction row pointing at the literal `\"undefined\"`, which no read\n * would ever match. Both shapes are accepted here, in one place, because both\n * call sites had the same assumption.\n *\n * An element that carries no key is refused rather than skipped: dropping it\n * would silently write a shorter membership list than the caller asked for.\n */\nfunction relationTargetIds(value: unknown, relationName: string, collectionSlug: string): (string | number)[] {\n if (!Array.isArray(value)) return [];\n\n return value.map((element, index) => {\n if (typeof element === \"string\" || typeof element === \"number\") return element;\n if (element && typeof element === \"object\") {\n const id = (element as { id?: unknown }).id;\n if (typeof id === \"string\" || typeof id === \"number\") return id;\n }\n throw new Error(\n `Cannot write relation \"${relationName}\" on \"${collectionSlug}\": element ${index} carries no id. ` +\n \"Pass either the related rows (`[{ id: … }]`) or their keys (`[1, 2]`), not \" +\n `${element === null ? \"null\" : typeof element}.`\n );\n });\n}\n\n/**\n * Typed wrapper for Drizzle dynamic query innerJoin.\n * Drizzle's `$dynamic()` queries lose the `innerJoin` method from\n * their static type, but it exists at runtime. This helper bridges\n * the gap with a single confined cast.\n *\n * Exported so `data-transformer` uses this one rather than restating the same\n * fact — it wrote `query.innerJoin(...) as unknown as typeof query`, which is\n * the same claim about the same drizzle behaviour in a second spelling.\n */\nexport function applyDynamicJoin<T>(query: T, joinTable: PgTable, condition: SQL): T {\n return (query as unknown as { innerJoin(t: PgTable, c: SQL): T }).innerJoin(joinTable, condition) as T;\n}\n\n/**\n * The ON condition of one `joinPath` step: every `from` column equal to its\n * `to` column, or `undefined` naming the first column either table lacks.\n *\n * `on.from`/`on.to` are a column or a tuple, and a tuple is how a step joins on\n * a composite key. Comparing the first pair alone joined each row to every row\n * sharing that column — every locale of a translation, not the one addressed.\n */\nexport function joinStepCondition(\n currentTable: PgTable,\n joinTable: PgTable,\n step: { on: { from: string | string[]; to: string | string[] } }\n): { condition: SQL } | { missing: string } {\n const from = Array.isArray(step.on.from) ? step.on.from : [step.on.from];\n const to = Array.isArray(step.on.to) ? step.on.to : [step.on.to];\n const pairs: SQL[] = [];\n for (let i = 0; i < Math.max(from.length, to.length); i++) {\n const fromColumn = from[i] ?? \"(none)\";\n const toColumn = to[i] ?? \"(none)\";\n // By SQL name or property key: a generated table keys `order_id` as\n // `orderId`, and a step names the column. See `joinColumn`.\n const fromCol = joinColumn(currentTable, fromColumn);\n const toCol = joinColumn(joinTable, toColumn);\n if (!fromCol || !toCol) return { missing: `${fromColumn} -> ${toColumn}` };\n pairs.push(eq(fromCol, toCol));\n }\n return { condition: pairs.length === 1 ? pairs[0] : and(...pairs)! };\n}\n\n/**\n * Typed wrapper for DrizzleConditionBuilder.buildRelationQuery on dynamic queries.\n * The method returns a widened generic that doesn't reassign cleanly;\n * this helper confines the cast.\n */\nfunction applyDynamicRelationQuery<T>(\n query: T,\n ...args: Parameters<typeof DrizzleConditionBuilder.buildRelationQuery>\n): T {\n return DrizzleConditionBuilder.buildRelationQuery(...args) as T;\n}\n\n/**\n * The relation's own matching condition, AND-ed with an include's `where`.\n *\n * Written once because the batch loaders reach the same decision from six\n * places — one per relation kind, twice over for the to-one and to-many\n * variants — and a `where` dropped from one of them is a filter the caller\n * asked for that silently did not apply.\n */\nfunction narrowed(base: SQL, narrow?: SQL): SQL {\n return narrow ? and(base, narrow)! : base;\n}\n\n/**\n * Two optional conditions, AND-ed: either, both, or neither.\n *\n * The include's own `where` and the target collection's `beforeQuery` arrive\n * separately and are both optional, and they have to reach the loaders as one\n * condition because that is the shape every relation kind threads through.\n */\nfunction narrowBoth(a?: SQL, b?: SQL): SQL | undefined {\n if (a && b) return and(a, b)!;\n return a ?? b;\n}\n\n/**\n * What an include asks of ONE to-many relation's rows, per parent: how many,\n * and in what order. See {@link RelationService.batchFetchRelatedEntitiesMany}.\n */\nexport interface RelatedRowWindow {\n /** Rows to keep per parent. */\n limit?: number;\n /** Their order, by the target's own columns. */\n orderBy?: OrderByTuple[];\n /**\n * Filled, when given and a `limit` is set, with each parent's number of\n * matching related rows *before* the limit — what `include: { x: { limit,\n * count: true } }` serves under `_count`. Counted in the ranking subquery,\n * so it is the same statement, the same conditions and the same RLS\n * context as the rows. A parent with no related row is absent (zero).\n * Without a limit nothing is cut, and the caller counts what it got.\n */\n totals?: Map<string, number>;\n}\n\n/**\n * How the batch loaders render a target row.\n *\n * - `\"admin\"` — the view model the admin panel reads: declared types parsed\n * (`parseDataFromServer`), the target's own to-one relations as\n * `{ __type: \"relation\" }` refs, undeclared columns dropped.\n * - `\"rest\"` — the row the target's own route serves (`toRestRow`): what an\n * `include` embeds. The REST contract is that a developer never sees a ref,\n * and an included row is rendered exactly as the same row is at the top\n * level.\n */\nexport type RelatedRowStyle = \"admin\" | \"rest\";\n\n/** The alias of the subquery a limited relation read joins back to. */\nconst RANKED = \"__ranked\";\n\n/**\n * The order one parent's related rows come in: the include's own keys, then\n * the target's key ascending.\n *\n * The key ascending is the order an unordered batch read handed rows back in\n * on an append-only table, and so the rows a `limit` with no `orderBy` kept —\n * stated now rather than left to the plan. A key that names no column of the\n * target sorts nothing, as it sorted nothing when the sort ran over the\n * returned rows in JavaScript; the caller's field access on it was already\n * judged at the route.\n *\n * NULLs go where a top-level `orderBy` puts them: the stated placement, else\n * Postgres's own (last ascending, first descending).\n */\nexport function relatedRowOrder(targetTable: PgTable, targetKeys: AnyPgColumn[], orderBy: OrderByTuple[] | undefined): SQL[] {\n const columns = getTableColumns(targetTable);\n const order: SQL[] = [];\n for (const [field, direction, nulls] of orderBy ?? []) {\n const column = Object.prototype.hasOwnProperty.call(columns, field) ? columns[field] : undefined;\n if (!column) continue;\n const nullsLast = nulls ? nulls === \"last\" : direction === \"asc\";\n if (direction === \"desc\") {\n order.push(nullsLast ? sql`${column} DESC NULLS LAST` : sql`${column} DESC NULLS FIRST`);\n } else {\n order.push(nullsLast ? sql`${column} ASC NULLS LAST` : sql`${column} ASC NULLS FIRST`);\n }\n }\n for (const key of targetKeys) order.push(sql`${key} ASC`);\n return order;\n}\n\n/**\n * The SELECT list of a ranked-keys subquery: the columns that identify one\n * related row under one parent, each under its own alias, and the row's rank\n * among that parent's related rows.\n *\n * `dense_rank`, not `row_number`: a `via` chain can reach the same target from\n * one parent along two paths, and those rows tie on every key, so they share a\n * rank and count once against the limit — the same multiplicity the unlimited\n * read serves them with.\n */\nfunction rankedKeyFields(\n keys: AnyPgColumn[],\n partitionBy: AnyPgColumn[],\n order: SQL[],\n /**\n * Also select the partition's number of related rows, as `total` — set\n * for `include: { x: { count: true } }`.\n *\n * - `\"rows\"`: `count(*)`, where each related row is one row of the\n * subquery (a foreign key on the target).\n * - the target's key columns: distinct keys, where one target can appear\n * twice — a `via` chain reaching it along two paths, a junction without\n * a key on its pair linking it twice. Those rows tie on rank and count\n * once against the limit, so they count once here too. `count(DISTINCT\n * …) OVER` does not exist, so it is the classic pair of ranks: ascending\n * plus descending over the key, less one, is the number of distinct keys\n * in the partition.\n */\n total?: \"rows\" | AnyPgColumn[]\n): Record<string, SQL.Aliased<unknown>> {\n const fields: Record<string, SQL.Aliased<unknown>> = {};\n keys.forEach((key, i) => {\n fields[`k${i}`] = sql`${key}`.as(`__k${i}`);\n });\n const partition = sql.join(partitionBy, sql`, `);\n fields.rank = sql`dense_rank() over (partition by ${partition} order by ${sql.join(order, sql`, `)})`.as(\"__rank\");\n if (total === \"rows\") {\n fields.total = sql<number>`(count(*) over (partition by ${partition}))::int`.as(\"__total\");\n } else if (total) {\n const ascending = sql.join(total.map(key => sql`${key} ASC`), sql`, `);\n const descending = sql.join(total.map(key => sql`${key} DESC`), sql`, `);\n fields.total = sql<number>`(dense_rank() over (partition by ${partition} order by ${ascending}) + dense_rank() over (partition by ${partition} order by ${descending}) - 1)::int`.as(\"__total\");\n }\n return fields;\n}\n\n/**\n * Record one parent's total from a ranked row, for {@link RelatedRowWindow.totals}.\n *\n * Every row of a parent carries the same total, so the first one seen wins and\n * the rest are a no-op.\n */\nfunction recordTotal(window: RelatedRowWindow | undefined, parentId: string, row: Record<string, unknown>): void {\n if (!window?.totals || window.totals.has(parentId)) return;\n const total = (row[RANKED] as { total?: unknown } | undefined)?.total;\n if (total !== undefined && total !== null) window.totals.set(parentId, Number(total));\n}\n\n/**\n * The registry has no table for a collection a relation just asked about.\n *\n * \"Parent table not found\" was the whole message, four times over, and it names\n * neither the collection nor the table nor anything to do next. The state it\n * reports is a real one with two distinct causes worth telling apart, so the\n * message names both: the collection was never registered (its file is not\n * exported from `config/collections/index.ts`, so the driver never saw it), or\n * the generated schema has no such table (the collection is declared and\n * `schema.generated.ts` predates it).\n */\nfunction unregisteredTable(collectionSlug: string, table: string, role: string): Error {\n return new Error(\n `No registered table \"${table}\" for the ${role} collection \"${collectionSlug}\". ` +\n \"Either the collection is not exported from config/collections/index.ts, so this driver \" +\n \"never registered it, or the generated Drizzle schema predates it — run \" +\n \"`rebase schema generate` and restart.\"\n );\n}\n\n/**\n * Service for handling all relation-related operations.\n * Handles fetching, updating, and managing row relations.\n */\n/**\n * A related record resolved by {@link RelationService}: the target row's\n * values plus the identity (`id`) and originating collection (`path`)\n * needed to build relation references. Internal to the postgres driver —\n * flattened to plain rows at the fetch-service boundary.\n */\nexport interface RelatedRow<M extends Record<string, unknown> = Record<string, unknown>> {\n id: string | number;\n path: string;\n values: M;\n /**\n * The junction row's own columns, for a `manyToMany` whose `through`\n * declares `properties`. Absent for every other relation kind and for a\n * junction that carries nothing but its two keys.\n *\n * Kept beside `values` rather than merged into them, because it is not the\n * target's data: two posts sharing a tag see the same tag row and different\n * links. The include pipeline serves it under\n * {@link JUNCTION_PIVOT_KEY}.\n */\n pivot?: Record<string, unknown>;\n}\n\nexport class RelationService {\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) { }\n\n /**\n * The **target** collection's `beforeQuery` narrowing, for rows reached\n * through a relation.\n *\n * The target's, not the parent's, because these are the target's rows: a\n * scope on `comments` has to hold whether they are listed at\n * `/comments` or loaded as `post.comments`. Without this an `include` is a\n * way around every row filter a collection declares — the exact hole\n * `stripUnreadable` was once missing from the relation-ref branch.\n *\n * Folded into the `narrow` condition the loaders already thread through\n * every relation kind, so a `via` join, a junction, a foreign key and the\n * dynamic relation builder all apply it or none of them do.\n */\n private narrowTargetRead(\n parentCollection: CollectionConfig,\n relation: ResolvedRelation,\n parentId?: string | number\n ): Promise<SQL | undefined> {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n return beforeQueryCondition(\n { registry: this.registry, callContext: this.callContext },\n targetCollection,\n targetCollection.slug,\n targetTable as PgTable<never>,\n {\n operation: \"relation\",\n query: {\n relatedTo: {\n parentSlug: parentCollection.slug,\n parentId,\n relationName: relation.relationName\n }\n }\n },\n { collection: targetCollection, registry: this.registry }\n );\n }\n\n /**\n * The target rows a caller may see through `relation`: the target's\n * `beforeQuery` scope and its soft delete, as one condition on the target\n * table, or `undefined` when neither applies.\n *\n * What every loader here narrows a read by, exposed for the membership\n * writes. A write that diffs a new link set against \"what is linked now\"\n * has to diff against what the caller could have read — otherwise saving\n * back the list they were shown unlinks every row they were not.\n */\n async visibleTargetCondition(\n parentCollection: CollectionConfig,\n relation: ResolvedRelation,\n parentId?: string | number\n ): Promise<SQL | undefined> {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n return andSoftDelete(\n await this.narrowTargetRead(parentCollection, relation, parentId),\n targetCollection,\n targetTable\n );\n }\n\n /**\n * One target row, as the {@link RelatedRow} everything here returns.\n *\n * Eight sites built this by hand, which is how the address came to be the\n * target's first key column in all eight — one edit, eight places to miss.\n *\n * `resolveNested` is the one thing they did not agree on, and the\n * disagreement was invisible: the single-parent fetches pass `db` and\n * `registry` to `parseDataFromServer`, so the target's *own* relations get\n * resolved too, while the batch paths deliberately do not — a query per\n * target row is the N+1 the batching exists to avoid. Naming the parameter\n * makes that a decision rather than a difference between two call sites\n * nobody was comparing.\n */\n private async toRelatedRow<M extends Record<string, unknown>>(\n targetRow: Record<string, unknown>,\n targetCollection: CollectionConfig,\n targetPks: PrimaryKeyInfo[],\n options?: {\n resolveNested?: boolean;\n /** The REST rendering, when the caller asked for it — see {@link restRendering}. */\n rest?: (row: Record<string, unknown>) => Record<string, unknown>;\n }\n ): Promise<RelatedRow<M>> {\n let values: Record<string, unknown>;\n if (options?.rest) {\n values = options.rest(targetRow);\n } else {\n values = options?.resolveNested\n ? await parseDataFromServer(targetRow, targetCollection, this.db, this.registry)\n : await parseDataFromServer(targetRow, targetCollection);\n\n // The target's OWN exclusions, applied to the target's own columns.\n // This is the second rendering of a relation target (row-pipeline's\n // `renderTarget` is the first) and it reached the same clients by a\n // different route: WebSocket fetches and every realtime frame come\n // through here. REST was clean and `.listen()` was not, which is the\n // worst version of this to have — the leak is invisible from the surface\n // people test.\n stripUnreadable(values, targetCollection);\n }\n\n return {\n // The whole key: a composite target addressed by its first column\n // names every row that shares it.\n id: buildCompositeId(targetRow, targetPks),\n path: targetCollection.slug,\n values: values as M\n };\n }\n\n /**\n * A target row as its own REST route serves it, for the `\"rest\"` style.\n *\n * The loaders read the target with `SELECT *`, so the columns a REST read\n * never selects — a generated search vector — are dropped first; then\n * `toRestRow` renders the rest, which applies the target's field access.\n * Built once per batch: the hidden set is a property of the table.\n *\n * It replaces a second pass. An include used to render each target row as\n * the admin's view model (`parseDataFromServer`, which also minted a\n * `{ __type: \"relation\" }` ref for the target's own to-one relations) and\n * then re-render that for REST: 3.4 µs a row against 1.4 µs for the row\n * itself, and an envelope in a response whose contract has none.\n */\n private restRendering(\n targetCollection: CollectionConfig,\n targetTable: PgTable\n ): (row: Record<string, unknown>) => Record<string, unknown> {\n const hidden = Object.keys(hiddenColumnsOption(getTableColumns(targetTable), targetCollection) ?? {});\n return (row) => {\n if (hidden.length === 0) return toRestRow(row, targetCollection, this.registry);\n const visible = { ...row };\n for (const name of hidden) delete visible[name];\n return toRestRow(visible, targetCollection, this.registry);\n };\n }\n\n /**\n * The junction row's own columns, as the caller may see them.\n *\n * Read through the same two steps the target's columns go through:\n * `parseDataFromServer` gives the declared type (a `date` is a Date, a\n * NUMERIC is a number) and `stripUnreadable` drops what this caller may not\n * read. The second is the point — `access.read: [\"hr\"]` on a payload\n * property has to mean the same thing it means on a collection's, and the\n * synthetic junction config carries the property verbatim, so the same\n * `effectiveAccess` answers both.\n *\n * Only the payload keys: the two foreign keys are the link's identity, and\n * the caller already has both — one is the row they asked from, the other\n * is the target's own id.\n */\n private async readPivot(\n junctionRow: Record<string, unknown>,\n junctionConfig: CollectionConfig,\n payloadKeys: string[]\n ): Promise<Record<string, unknown>> {\n const raw: Record<string, unknown> = {};\n for (const key of payloadKeys) {\n if (key in junctionRow) raw[key] = junctionRow[key];\n }\n const parsed = await parseDataFromServer(raw, junctionConfig) as Record<string, unknown>;\n return stripUnreadable(parsed, junctionConfig);\n }\n\n /**\n * A WHERE matching any of `parentIds`, by the whole key.\n *\n * A single key is an `IN (…)`. A composite one cannot be: matching\n * `tenant_id IN (1, 1)` collects every row of tenant 1, so two parents that\n * share their first column each receive the other's relations. It becomes\n * an OR of ANDs — one exact address per parent — which Postgres indexes the\n * same way it would a multi-column key lookup.\n */\n private parentKeyCondition(\n parentTable: PgTable,\n parentPks: PrimaryKeyInfo[],\n parentIds: (string | number)[]\n ): SQL {\n const columnFor = (fieldName: string) => {\n const col = parentTable[fieldName as keyof typeof parentTable] as AnyPgColumn;\n if (!col) throw new Error(`Key column '${fieldName}' not found in parent table`);\n return col;\n };\n\n if (parentPks.length === 1) {\n const values = parentIds.map(id => parseIdValues(id, parentPks)[parentPks[0].fieldName]);\n return inArray(columnFor(parentPks[0].fieldName), values);\n }\n\n const perParent = parentIds.map(id => {\n const values = parseIdValues(id, parentPks);\n return and(...parentPks.map(pk => eq(columnFor(pk.fieldName), values[pk.fieldName])));\n });\n return or(...perParent) as SQL;\n }\n\n /**\n * Reject a relation that cannot express a composite-keyed parent.\n *\n * `localKey` and `foreignKeyOnTarget` are single column names: one column\n * cannot reference a two-column key, so such a relation has no correct\n * reading. Left alone it would silently match on the first key column and\n * hand a tenant's rows to its neighbour — say so instead.\n */\n private assertSingleKeyAddressable(\n parentCollection: CollectionConfig,\n parentPks: PrimaryKeyInfo[],\n via: string,\n relation?: ResolvedRelation\n ): void {\n // A `sourceKey` names the single column the link actually points at, so\n // the parent's key can be as wide as it likes — nothing here references\n // it. That is the one way out of this error, and the message below now\n // has to say so.\n if (relation && hasForeignKeyOnTarget(relation) && relation.sourceKey) return;\n\n if (parentPks.length > 1) {\n throw new Error(\n `Relation on '${parentCollection.slug}' uses '${via}', a single foreign-key column, but ` +\n `'${parentCollection.slug}' is keyed on ${parentPks.map(k => `'${k.fieldName}'`).join(\" + \")}. ` +\n `One column cannot reference a composite key — give the relation a \\`sourceKey\\` naming ` +\n `a single unique column to point at, or express it with \\`joinPath\\`, whose ` +\n `\\`on.from\\`/\\`on.to\\` take every key column.`\n );\n }\n }\n\n /**\n * What the target's foreign key holds, for each of these parent rows.\n *\n * Ordinarily the parent's id, and then this is free. When the relation\n * declares a `sourceKey` the two are different values, and the mapping\n * between them lives in the source table — so it costs one SELECT, issued\n * once for the whole batch rather than per parent.\n *\n * Both directions come back because both are needed and deriving one from\n * the other by hand is how a batch loader ends up attributing a child to the\n * wrong parent: reads translate id → key to build the WHERE, and then\n * translate key → id to attribute each row that comes back.\n */\n /**\n * The value a related row's foreign key must hold to belong to this parent.\n *\n * `undefined` when the parent's source key is null — which is not an error\n * here, only in the callers that were about to write it. Exposed for\n * {@link PersistService}, which stamps this onto a child created under a\n * nested path and would otherwise write the id and lose the row.\n */\n async parentKeyValue(\n parentCollection: CollectionConfig,\n relation: ResolvedHasOne | ResolvedHasMany,\n parentId: string | number,\n db: DrizzleClient = this.db\n ): Promise<string | number | undefined> {\n const { keyByParentId } = await this.resolveSourceKeys(parentCollection, relation, [parentId], db);\n return keyByParentId.get(String(parentId));\n }\n\n /**\n * Shared with {@link RelationWriteService}: a write needs the same source\n * key a read does, and resolving it twice is how the two would disagree.\n */\n async resolveSourceKeys(\n parentCollection: CollectionConfig,\n relation: ResolvedHasOne | ResolvedHasMany,\n parentIds: (string | number)[],\n // Writes pass their transaction: reading the source key on the pool\n // while the same transaction is holding an uncommitted change to it\n // would translate the id against a stale value.\n db: DrizzleClient = this.db\n ): Promise<{ keyByParentId: Map<string, string | number>; parentIdByKey: Map<string, string | number> }> {\n const keyByParentId = new Map<string, string | number>();\n const parentIdByKey = new Map<string, string | number>();\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n\n if (!joinsOnNaturalKey(relation, parentCollection, this.registry)) {\n // The ordinary case, and it must stay free of a round-trip: the\n // key IS the id. Parsed, not passed through — an id arrives as the\n // string from a URL, and comparing \"7\" against an integer column is\n // how this returns nothing at all.\n for (const id of parentIds) {\n const value = parseIdValues(id, parentPks)[parentPks[0].fieldName];\n keyByParentId.set(String(id), value);\n parentIdByKey.set(String(value), id);\n }\n return { keyByParentId, parentIdByKey };\n }\n\n const field = sourceKeyField(relation, parentCollection, this.registry);\n const parentTable = getTableForCollection(parentCollection, this.registry);\n const keyColumn = parentTable[field as keyof typeof parentTable] as AnyPgColumn;\n if (!keyColumn) {\n throw new Error(\n `\\`sourceKey: \"${field}\"\\` on relation '${relation.relationName}' is not a column on ` +\n `'${parentCollection.slug}'. It names a column on the source table, not on the target.`\n );\n }\n\n const rows = await db\n .select()\n .from(parentTable)\n .where(this.parentKeyCondition(parentTable, parentPks, parentIds));\n\n for (const row of rows as Array<Record<string, unknown>>) {\n const keyValue = row[field] as string | number | null;\n if (keyValue === null || keyValue === undefined) continue;\n const parentId = buildCompositeId(row, parentPks);\n keyByParentId.set(String(parentId), keyValue);\n // A duplicate here means the source key is not unique, which makes\n // \"which parent does this child belong to\" unanswerable. Refuse\n // rather than pick the last one seen.\n const existing = parentIdByKey.get(String(keyValue));\n if (existing !== undefined && String(existing) !== String(parentId)) {\n throw new Error(\n `\\`sourceKey: \"${field}\"\\` on relation '${relation.relationName}' is not unique on ` +\n `'${parentCollection.slug}': rows '${existing}' and '${parentId}' both hold ` +\n `'${keyValue}'. Add a unique constraint — a link that addresses more than one source ` +\n `row cannot say which one a related row belongs to.`\n );\n }\n parentIdByKey.set(String(keyValue), parentId);\n }\n\n return { keyByParentId, parentIdByKey };\n }\n\n /**\n * Fetch rows related to a parent row through a specific relation\n */\n async fetchRelatedEntities<M extends Record<string, unknown>>(\n parentCollectionPath: string,\n parentId: string | number,\n relationKey: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n databaseId?: string;\n } = {}\n ): Promise<RelatedRow<M>[]> {\n const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);\n const resolvedRelations = resolveCollectionRelations(parentCollection);\n const relation = findRelation(resolvedRelations, relationKey);\n\n if (!relation) {\n const available = Object.keys(resolvedRelations).join(\", \") || \"(none)\";\n // A path segment naming a relation the collection does not declare\n // is a bad URL, not a server fault. It answered 500 INTERNAL_ERROR,\n // which tells the caller to retry or report an outage over a\n // request that will never succeed — and buries a message that\n // already says exactly what is wrong.\n //\n // 404, like an unknown collection: the URL names nothing. The\n // available relations stay in the message because reaching this\n // point means the caller could already read the parent collection,\n // and `?include=` would list the same names.\n throw ApiError.notFound(\n `Relation '${relationKey}' not found in collection '${parentCollectionPath}'. Available relations: [${available}]`,\n \"UNKNOWN_RELATION\"\n );\n }\n\n return this.fetchEntitiesUsingJoins<M>(parentCollection, parentId, relation, options);\n }\n\n /**\n * Fetch rows using join paths for complex relations\n */\n async fetchEntitiesUsingJoins<M extends Record<string, unknown>>(\n parentCollection: CollectionConfig,\n parentId: string | number,\n relation: ResolvedRelation,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n databaseId?: string;\n } = {}\n ): Promise<RelatedRow<M>[]> {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const idInfo = requirePrimaryKeys(targetCollection, this.registry);\n const idField = targetTable[idInfo[0].fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parsedParentIdObj = parseIdValues(parentId, parentPks);\n const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];\n const parentTableName = getTableName(parentCollection);\n const parentTable = this.registry.getTable(parentTableName);\n if (!parentTable) throw unregisteredTable(parentCollection.slug, parentTableName, \"parent\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n\n // The target collection's `beforeQuery`, resolved before either branch\n // builds a WHERE. `via` used to be the branch that carried none of the\n // conditions the other one does, so a scope applied there and not here\n // would be a read-only relation kind that answers with every row.\n const relatedNarrowing = await this.narrowTargetRead(parentCollection, relation, parentId);\n\n // Handle join path relations\n if (relation.kind === \"via\") {\n let query = this.db.select().from(parentTable).$dynamic();\n let currentTable = parentTable;\n\n // Apply each join in the path\n for (const join of relation.joinPath) {\n const joinTable = this.registry.getTable(join.table);\n if (!joinTable) {\n throw new Error(`Join table not found: ${join.table}`);\n }\n\n const step = joinStepCondition(currentTable, joinTable, join);\n if (\"missing\" in step) {\n throw new Error(`Join columns not found: ${step.missing}`);\n }\n\n query = applyDynamicJoin(query, joinTable, step.condition);\n currentTable = joinTable;\n }\n\n // The parent row by every key column — the join starts at the\n // parent's own table, so a condition on the first column alone\n // reads the related rows of every parent that shares it — and the\n // target's own soft delete beside its scope: the other branch below\n // carries both, and a join path is only another way to reach the\n // same rows.\n query = query.where(narrowed(\n rowIdentityCondition(parentTable, parentPks, parentId, parentCollection.slug),\n andSoftDelete(relatedNarrowing, targetCollection, targetTable)\n ));\n\n if (options.limit) {\n query = query.limit(options.limit);\n }\n\n const results = await query;\n const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;\n\n // Process results\n const rows: RelatedRow<M>[] = [];\n for (const row of results as Array<Record<string, unknown>>) {\n const targetRow = (row[targetTableName] as Record<string, unknown>) || row;\n rows.push(await this.toRelatedRow<M>(targetRow, targetCollection, idInfo, { resolveNested: true }));\n }\n\n return rows;\n }\n\n // Handle other relation types.\n //\n // The query builder compares the target's foreign key against a value,\n // and for a link on a natural key that value is not the id in the URL.\n // Resolved first, before anything is built, so a parent that reaches\n // nothing costs one statement rather than two.\n const matchValue = hasForeignKeyOnTarget(relation)\n ? (await this.resolveSourceKeys(parentCollection, relation, [parentId]))\n .keyByParentId.get(String(parentId))\n : parsedParentId;\n\n // A parent whose source key is null reaches nothing: NULL never equals\n // a foreign key. Say so with an empty list rather than an `= NULL`.\n if (matchValue === undefined) return [];\n\n let query = this.db.select().from(targetTable).$dynamic();\n\n // Build additional filter conditions\n const additionalFilters: SQL[] = [];\n\n // Soft delete on the *target*: the rows being loaded belong to the\n // target collection, so it is that collection's flag that decides.\n const relatedSoftDelete = softDeleteCondition(targetCollection, targetTable);\n if (relatedSoftDelete) additionalFilters.push(relatedSoftDelete);\n\n // And the target's `beforeQuery`, for the same reason.\n if (relatedNarrowing) additionalFilters.push(relatedNarrowing);\n\n // Handle search conditions if searchString is provided\n if (options.searchString) {\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString,\n targetCollection.properties,\n targetTable,\n targetCollection\n );\n\n if (searchConditions.length === 0) {\n // No searchable fields found, return empty results\n return [];\n }\n\n const searchCombined = DrizzleConditionBuilder.combineConditionsWithOr(searchConditions);\n if (searchCombined) {\n additionalFilters.push(searchCombined);\n }\n }\n\n // Use unified relation query builder\n query = applyDynamicRelationQuery(\n query,\n query,\n relation,\n matchValue,\n targetTable,\n parentTable,\n parentIdCol,\n idField,\n this.registry,\n additionalFilters\n );\n\n if (options.limit) {\n query = query.limit(options.limit);\n }\n\n const results = await query;\n\n // Process results - ensure results is iterable\n if (!results || !Array.isArray(results)) {\n return [];\n }\n\n const rows: RelatedRow<M>[] = [];\n for (const row of results) {\n const targetRow = row[getTableName(targetCollection)] || row;\n rows.push(await this.toRelatedRow<M>(targetRow as Record<string, unknown>, targetCollection, idInfo, { resolveNested: true }));\n }\n\n return rows;\n }\n\n /**\n * Count related rows for a parent row\n */\n async countRelatedEntities<M extends Record<string, unknown>>(\n parentCollectionPath: string,\n parentId: string | number,\n relationKey: string,\n options: { filter?: FilterValues<Extract<keyof M, string>>; databaseId?: string } = {}\n ): Promise<number> {\n const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);\n const resolvedRelations = resolveCollectionRelations(parentCollection);\n const relation = findRelation(resolvedRelations, relationKey);\n if (!relation) {\n const available = Object.keys(resolvedRelations).join(\", \") || \"(none)\";\n // A path segment naming a relation the collection does not declare\n // is a bad URL, not a server fault. It answered 500 INTERNAL_ERROR,\n // which tells the caller to retry or report an outage over a\n // request that will never succeed — and buries a message that\n // already says exactly what is wrong.\n //\n // 404, like an unknown collection: the URL names nothing. The\n // available relations stay in the message because reaching this\n // point means the caller could already read the parent collection,\n // and `?include=` would list the same names.\n throw ApiError.notFound(\n `Relation '${relationKey}' not found in collection '${parentCollectionPath}'. Available relations: [${available}]`,\n \"UNKNOWN_RELATION\"\n );\n }\n\n return this.countRelatedRows(parentCollection, parentId, relation, []);\n }\n\n /**\n * Count the target rows a parent reaches through `relation`, narrowed by\n * `additionalFilters` (conditions on the target table).\n *\n * Shared by the public count and by {@link isRelated}, so \"how many children\n * does this parent have\" and \"is this row one of them\" are answered by the\n * same join — a membership test that reconstructed the join separately would\n * be free to disagree with the listing it is supposed to gate.\n */\n private async countRelatedRows(\n parentCollection: CollectionConfig,\n parentId: string | number,\n relation: ResolvedRelation,\n additionalFilters: SQL[]\n ): Promise<number> {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parsedParentIdObj = parseIdValues(parentId, parentPks);\n const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];\n const parentTableName = getTableName(parentCollection);\n const parentTable = this.registry.getTable(parentTableName);\n if (!parentTable) throw unregisteredTable(parentCollection.slug, parentTableName, \"parent\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n\n // Same translation the listing does, and it has to be: `isRelated`\n // gates writes on this count, so a count built from a different value\n // than the read would authorise rows the read never returned.\n const matchValue = hasForeignKeyOnTarget(relation)\n ? (await this.resolveSourceKeys(parentCollection, relation, [parentId]))\n .keyByParentId.get(String(parentId))\n : parsedParentId;\n\n if (matchValue === undefined) return 0;\n\n // Soft delete, so a relation's count agrees with what loading it\n // returns — `isRelated` gates writes on this number.\n const countSoftDelete = softDeleteCondition(targetCollection, targetTable);\n if (countSoftDelete) additionalFilters = [...additionalFilters, countSoftDelete];\n\n // The target's `beforeQuery`, so the count and the listing describe the\n // same rows. `isRelated` gates reads, updates and deletes at a nested\n // address on this number, so a count that saw rows the listing does not\n // would authorise a write to a row the caller cannot read.\n const countNarrowing = await this.narrowTargetRead(parentCollection, relation, parentId);\n if (countNarrowing) additionalFilters = [...additionalFilters, countNarrowing];\n\n // Start count with distinct to avoid duplicates from junction tables\n let query = this.db.select({ count: sql<number>`count(distinct ${targetIdField})` }).from(targetTable).$dynamic();\n\n // Every `via` is counted through the scope condition a nested listing\n // filters by, so the count and the listing are one condition rather\n // than two that have to agree.\n //\n // The join-path count `buildRelationCountQuery` builds is not that\n // condition. It joins each step on its first column pair and matches\n // the parent on its first key column, so for a parent keyed on several\n // columns it counted the rows every parent sharing that column reaches\n // — `isRelated` would authorise a write through `1:::en_US` to a row\n // only `1:::de_DE` reaches. And it looks a step's columns up by the\n // Drizzle property key, where a step names the SQL column: on a\n // generated schema (`orderId: uuid(\"order_id\")`) every count threw\n // \"Join columns not found\", and since `isRelated` gates every read at a\n // nested address, an order opened from a product's Orders tab failed to\n // load while the tab listed it fine.\n if (relation.kind === \"via\") {\n const scope = DrizzleConditionBuilder.buildRelationScopeCondition(\n relation,\n () => ({\n table: parentTable,\n key: primaryKeyColumns(parentTable, parentPks, parentCollection.slug)\n .map((column, i) => ({ column, value: parsedParentIdObj[parentPks[i].fieldName] }))\n }),\n parentId,\n targetTable,\n targetIdField,\n this.registry\n );\n const result = await query.where(and(scope, ...additionalFilters));\n return Number(result[0]?.count || 0);\n }\n\n // Use unified count query builder from DrizzleConditionBuilder\n query = DrizzleConditionBuilder.buildRelationCountQuery(\n query,\n relation,\n matchValue,\n targetTable,\n parentTable,\n parentIdCol,\n targetIdField,\n this.registry,\n additionalFilters\n );\n\n const result = await query;\n return Number(result[0]?.count || 0);\n }\n\n /**\n * Whether `targetId` is actually reachable from the parent named in `hop`.\n *\n * A nested address like `authors/1/posts/43` used to resolve to the target\n * collection and then match on the primary key alone, so the parent segment\n * decided nothing: the row came back, and was updated or deleted, whoever it\n * belonged to. Reads, updates and deletes now all gate on this.\n */\n async isRelated(hop: NestedPathHop, targetId: string | number): Promise<boolean> {\n const targetTable = getTableForCollection(hop.targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(hop.targetCollection, this.registry);\n const parsedTargetId = parseIdValues(targetId, targetPks);\n\n const identity: SQL[] = targetPks.map(pk => {\n const column = targetTable[pk.fieldName as keyof typeof targetTable] as AnyPgColumn;\n if (!column) {\n throw new Error(`ID field '${pk.fieldName}' not found in table for collection '${hop.targetCollection.slug}'`);\n }\n return eq(column, parsedTargetId[pk.fieldName]);\n });\n\n return (await this.countRelatedRows(hop.parentCollection, hop.parentId, hop.relation, identity)) > 0;\n }\n\n /**\n * Batch fetch related rows for multiple parent rows to avoid N+1 queries.\n *\n * `narrow` is an extra condition on the *target* table — what a per-include\n * `where` compiles to. It is pushed into the query rather than applied to\n * the rows that come back, because filtering afterwards reads every related\n * row of every parent in order to discard most of them.\n */\n async batchFetchRelatedEntities(\n parentCollectionPath: string,\n parentIds: (string | number)[],\n _relationKey: string,\n relation: ResolvedRelation,\n narrow?: SQL,\n options?: {\n /** How each target row is rendered; see {@link RelatedRowStyle}. */\n style?: RelatedRowStyle;\n /**\n * The parent rows themselves, in `parentIds` order, when the caller\n * holds them. A `belongsTo` reads each one's foreign key off its\n * row rather than selecting it again.\n */\n parentRows?: Record<string, unknown>[];\n }\n ): Promise<Map<string, RelatedRow<Record<string, unknown>>>> {\n if (parentIds.length === 0) return new Map();\n\n const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const render = { rest: options?.style === \"rest\" ? this.restRendering(targetCollection, targetTable) : undefined };\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parentTableName = getTableName(parentCollection);\n const parentTable = this.registry.getTable(parentTableName);\n if (!parentTable) throw unregisteredTable(parentCollection.slug, parentTableName, \"parent\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n\n // Parse all parent IDs once\n const parsedParentIds = parentIds.map(id => parseIdValues(id, parentPks)[parentIdInfo.fieldName]);\n\n // Soft delete, once, for every branch below.\n //\n // These loaders are the only path an `include` takes now, and they\n // reach the target four different ways — a `via` join, a junction, a\n // foreign key, the dynamic relation builder. A `deleted_at IS NULL`\n // written into one of them is a deleted comment that stays hidden under\n // its post on one relation kind and reappears on another, which is\n // exactly the rot `soft-delete.ts` exists to prevent. Folded into\n // `narrow` here, it composes with the include's own `where` and every\n // branch below applies both or neither.\n // The target collection's own `beforeQuery`, folded in beside the\n // soft-delete stamp for the same reason and at the same point.\n const narrowTarget = andSoftDelete(\n narrowBoth(narrow, await this.narrowTargetRead(parentCollection, relation)),\n targetCollection, targetTable\n );\n\n // Handle join path relations with batching\n if (relation.kind === \"via\") {\n let query = this.db.select().from(parentTable).$dynamic();\n let currentTable = parentTable;\n\n // Apply each join in the path\n for (const join of relation.joinPath) {\n const joinTable = this.registry.getTable(join.table);\n if (!joinTable) {\n throw new Error(`Join table not found: ${join.table}`);\n }\n\n const step = joinStepCondition(currentTable, joinTable, join);\n if (\"missing\" in step) {\n throw new Error(`Join columns not found: ${step.missing}`);\n }\n\n query = applyDynamicJoin(query, joinTable, step.condition);\n currentTable = joinTable;\n }\n\n // Match every parent at once, each by its whole key.\n query = query.where(narrowed(this.parentKeyCondition(parentTable, parentPks, parentIds), narrowTarget));\n\n const results = await query;\n const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>>();\n\n // Group by the parent's address — the same token the caller looks\n // results up by, derived the same way on both sides.\n for (const row of results as Array<Record<string, unknown>>) {\n const parentRow = (row[getTableName(parentCollection)] || row) as Record<string, unknown>;\n const targetRow = (row[targetTableName] || row) as Record<string, unknown>;\n\n resultMap.set(\n buildCompositeId(parentRow, parentPks),\n await this.toRelatedRow(targetRow, targetCollection, targetPks, render)\n );\n }\n\n return resultMap;\n }\n\n // Handle owning relations with proper FK-based batching.\n // For owning relations, parent rows hold the FK (e.g. posts.author_id).\n // We need to:\n // 1. Fetch FK values from the parent table in a single query\n // 2. Query the target table with unique FK values\n // 3. Map results back to parent rows via their FK values\n if (relation.kind === \"belongsTo\") {\n this.assertSingleKeyAddressable(parentCollection, parentPks, relation.localKey);\n // `localKey` is the column; the generated table is keyed by the wire\n // name, so `author_id` finds nothing where the key is `authorId`.\n const localKeyField = fieldKeyForColumn(parentCollection, relation.localKey);\n const localKeyCol = parentTable[localKeyField as keyof typeof parentTable] as AnyPgColumn;\n if (!localKeyCol) {\n throw new Error(`Local key column '${relation.localKey}' not found in parent table`);\n }\n\n // Step 1: the foreign key of every parent. A parent row the caller\n // already holds carries it under `localKeyField`, and is read from\n // there; only the parents whose row does not — a `fields`\n // projection that left it out, a key withheld by field access —\n // cost a query, the ONE query this step always used to be. It\n // re-read keys the page had just selected: a statement per\n // belongsTo per include level, for nothing.\n const parentToFk = new Map<string, unknown>();\n const uniqueFkValues: unknown[] = [];\n const seenFks = new Set<string>();\n const remember = (parentId: string, fkValue: unknown) => {\n if (fkValue === null || fkValue === undefined) return;\n parentToFk.set(parentId, fkValue);\n const fkStr = String(fkValue);\n if (!seenFks.has(fkStr)) {\n seenFks.add(fkStr);\n uniqueFkValues.push(fkValue);\n }\n };\n\n const toRead: (string | number)[] = [];\n parentIds.forEach((parentId, i) => {\n const row = options?.parentRows?.[i];\n if (row && Object.prototype.hasOwnProperty.call(row, localKeyField)) {\n remember(String(parentId), row[localKeyField]);\n } else {\n toRead.push(parsedParentIds[i]);\n }\n });\n\n if (toRead.length > 0) {\n const fkRows = await this.db\n .select({\n parentId: parentIdCol,\n fkValue: localKeyCol\n })\n .from(parentTable)\n .where(inArray(parentIdCol, toRead));\n for (const row of fkRows) {\n remember(String(row.parentId), row.fkValue);\n }\n }\n\n if (uniqueFkValues.length === 0) return new Map();\n\n // Step 2: Fetch all target rows in ONE query\n const targetResults = await this.db\n .select()\n .from(targetTable)\n .where(narrowed(inArray(targetIdField, uniqueFkValues), narrowTarget));\n\n // Index target rows by their ID\n const targetById = new Map<string, Record<string, unknown>>();\n for (const row of targetResults as Array<Record<string, unknown>>) {\n const tid = String(row[targetIdInfo.fieldName]);\n targetById.set(tid, row);\n }\n\n // Step 3: Map back to parent rows\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>>();\n for (const [parentIdStr, fkValue] of parentToFk) {\n const targetRow = targetById.get(String(fkValue));\n if (targetRow) {\n resultMap.set(parentIdStr, await this.toRelatedRow(targetRow, targetCollection, targetPks, render));\n }\n }\n\n return resultMap;\n }\n\n // Handle inverse relation types with batching. The parent is named by a\n // single FK column on the target, so a composite-keyed parent has no\n // correct reading here either.\n this.assertSingleKeyAddressable(\n parentCollection,\n parentPks,\n hasForeignKeyOnTarget(relation) ? relation.foreignKeyOnTarget : relation.relationName,\n relation\n );\n\n // One lookup for the whole batch, both directions: the WHERE is built\n // from the source-key values, and each row that comes back carries one\n // of those values rather than a parent id.\n const { keyByParentId, parentIdByKey } = hasForeignKeyOnTarget(relation)\n ? await this.resolveSourceKeys(parentCollection, relation, parentIds)\n : { keyByParentId: new Map<string, string | number>(), parentIdByKey: new Map<string, string | number>() };\n\n const matchValues = hasForeignKeyOnTarget(relation)\n ? [...keyByParentId.values()]\n : parsedParentIds;\n if (matchValues.length === 0) return new Map();\n\n let query = this.db.select().from(targetTable).$dynamic();\n\n // Build the relation query with ALL parent IDs\n query = applyDynamicRelationQuery(\n query,\n query,\n relation,\n matchValues, // Pass array instead of single ID\n targetTable,\n parentTable,\n parentIdCol,\n targetIdField,\n this.registry,\n // Where a per-include `where` lands on this relation kind: the\n // builder already AND-s `additionalFilters` into the relation's own\n // conditions, so there is nothing to invent here.\n narrowTarget ? [narrowTarget] : []\n );\n\n const results = await query;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>>();\n\n // Map results back to parent rows\n for (const row of results as Array<Record<string, unknown>>) {\n const targetRow = (row[getTableName(targetCollection)] || row) as Record<string, unknown>;\n\n // The parent's key is on the target row, in the relation's own\n // column. There used to be a second branch here that guessed the\n // column by appending `_id` to `inverseRelationName`, reached only\n // when the foreign key had not been resolved — which cannot happen\n // now that resolution fills it in.\n if (!hasForeignKeyOnTarget(relation)) continue;\n\n // Keyed by string throughout: Drizzle returns a numeric column as a\n // number or a string depending on the column type and the driver.\n const foreignKeyValue = targetRow[\n fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget)\n ] as string | number | undefined;\n if (foreignKeyValue === undefined || foreignKeyValue === null) continue;\n\n const parentId = parentIdByKey.get(String(foreignKeyValue));\n if (parentId !== undefined) {\n resultMap.set(String(parentId), await this.toRelatedRow(targetRow, targetCollection, targetPks, render));\n }\n }\n\n return resultMap;\n }\n\n /**\n * Batch fetch many-cardinality related rows for multiple parent rows.\n * Returns a Map<parentId, RelatedRow[]> instead of Map<parentId, RelatedRow>.\n * Uses a single SQL query with IN clause to avoid N+1.\n *\n * `window` is an include's own `limit` and `orderBy`, and both are applied\n * by Postgres. A limit is *per parent*, so it is a rank within each\n * parent's rows rather than a `LIMIT`: a subquery ranks the keys of every\n * matching row — under the same relation condition, `narrow`, soft delete\n * and `beforeQuery` the unlimited read applies — and the read joins back to\n * the ones ranked within the limit. It used to read every related row of\n * every parent and cut the list in JavaScript: 200 rows served out of\n * 200,000 read, parsed and rendered for `{ messages: { limit: 2 } }` on a\n * page of 100 threads. Rows come back in the window's order, so the\n * per-parent lists are already sorted.\n */\n async batchFetchRelatedEntitiesMany(\n parentCollectionPath: string,\n parentIds: (string | number)[],\n _relationKey: string,\n relation: ResolvedRelation,\n /** An extra condition on the target — see {@link batchFetchRelatedEntities}. */\n narrow?: SQL,\n window?: RelatedRowWindow,\n /** `style` — how each target row is rendered; see {@link RelatedRowStyle}. */\n options?: { style?: RelatedRowStyle }\n ): Promise<Map<string, RelatedRow<Record<string, unknown>>[]>> {\n if (parentIds.length === 0) return new Map();\n\n const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const render = { rest: options?.style === \"rest\" ? this.restRendering(targetCollection, targetTable) : undefined };\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parentTableName = getTableName(parentCollection);\n const parentTable = this.registry.getTable(parentTableName);\n if (!parentTable) throw unregisteredTable(parentCollection.slug, parentTableName, \"parent\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n\n const parsedParentIds = parentIds.map(id => parseIdValues(id, parentPks)[parentIdInfo.fieldName]);\n\n // Soft delete, once, for every branch below.\n //\n // These loaders are the only path an `include` takes now, and they\n // reach the target four different ways — a `via` join, a junction, a\n // foreign key, the dynamic relation builder. A `deleted_at IS NULL`\n // written into one of them is a deleted comment that stays hidden under\n // its post on one relation kind and reappears on another, which is\n // exactly the rot `soft-delete.ts` exists to prevent. Folded into\n // `narrow` here, it composes with the include's own `where` and every\n // branch below applies both or neither.\n // The target collection's own `beforeQuery`, folded in beside the\n // soft-delete stamp for the same reason and at the same point.\n const narrowTarget = andSoftDelete(\n narrowBoth(narrow, await this.narrowTargetRead(parentCollection, relation)),\n targetCollection, targetTable\n );\n\n // Handle join path relations (many-to-many through junction tables)\n if (relation.kind === \"via\") {\n const joins: { table: PgTable; condition: SQL }[] = [];\n let currentTable = parentTable;\n\n for (const join of relation.joinPath) {\n const joinTable = this.registry.getTable(join.table);\n if (!joinTable) throw new Error(`Join table not found: ${join.table}`);\n\n const step = joinStepCondition(currentTable, joinTable, join);\n if (\"missing\" in step) throw new Error(`Join columns not found: ${step.missing}`);\n\n joins.push({ table: joinTable, condition: step.condition });\n currentTable = joinTable;\n }\n const throughPath = <Q>(query: Q): Q =>\n joins.reduce((joined, { table, condition }) => applyDynamicJoin(joined, table, condition), query);\n\n const scope = narrowed(this.parentKeyCondition(parentTable, parentPks, parentIds), narrowTarget);\n const endTable = currentTable;\n const targetKeys = targetPks.map(pk => endTable[pk.fieldName as keyof typeof endTable] as AnyPgColumn);\n const order = relatedRowOrder(endTable, targetKeys, window?.orderBy);\n\n let results: unknown[];\n if (window?.limit !== undefined) {\n // Ranked over distinct (parent, target) pairs: two paths to\n // one target are one related row, and share a rank.\n const parentKeys = parentPks.map(pk => parentTable[pk.fieldName as keyof typeof parentTable] as AnyPgColumn);\n const keys = [...parentKeys, ...targetKeys];\n const ranked = throughPath(\n this.db.selectDistinct(rankedKeyFields(keys, parentKeys, order, window.totals ? targetKeys : undefined)).from(parentTable).$dynamic()\n ).where(scope).as(RANKED);\n results = await throughPath(this.db.select().from(parentTable).$dynamic())\n .innerJoin(ranked, and(...keys.map((key, i) => eq(key, ranked[`k${i}`])))!)\n .where(lte(ranked.rank, window.limit))\n .orderBy(ranked.rank);\n } else {\n const query = throughPath(this.db.select().from(parentTable).$dynamic()).where(scope);\n results = window?.orderBy?.length ? await query.orderBy(...order) : await query;\n }\n const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>[]>();\n\n for (const row of results as Array<Record<string, unknown>>) {\n const parentRow = (row[getTableName(parentCollection)] || row) as Record<string, unknown>;\n const targetRow = (row[targetTableName] || row) as Record<string, unknown>;\n const parentId = buildCompositeId(parentRow, parentPks);\n recordTotal(window, parentId, row);\n const arr = resultMap.get(parentId) || [];\n arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks, render));\n resultMap.set(parentId, arr);\n }\n\n return resultMap;\n }\n\n // Handle many-to-many owning relations with junction table (relation.through)\n // This is the standard path for posts→tags style relations where\n // sanitizeRelation populated the `through` config.\n if (relation.kind === \"manyToMany\") {\n // The junction names its parent with one column, so the same\n // single-key limit applies as for a direct foreign key.\n this.assertSingleKeyAddressable(parentCollection, parentPks, `${relation.through.table}.${relation.through.sourceColumn}`);\n\n // An empty map here is a lie a caller cannot see through: `posts/1/tags`\n // answering `[]` is exactly what a post with no tags looks like. That\n // is the failure `assertRelationsResolve` was written to make\n // impossible at boot; this says the same thing if one gets past it.\n const { table: junctionTable, parentColumn: sourceJunctionCol, targetColumn: targetJunctionCol } =\n bindThroughJunction(\n this.registry,\n relation.through,\n `${parentCollection.slug}.${relation.relationName}`\n );\n\n // SELECT target.*, junction.sourceColumn FROM junction\n // INNER JOIN target ON junction.targetColumn = target.id\n // WHERE junction.sourceColumn IN (parentIds)\n // The target is joined in, so its soft-delete stamp is a condition\n // this query can carry.\n const scope = narrowed(inArray(sourceJunctionCol, parsedParentIds), narrowTarget);\n const order = relatedRowOrder(targetTable, [targetIdField], window?.orderBy);\n\n let results: unknown[];\n if (window?.limit !== undefined) {\n // A link is named by its two ends, so that pair is the key the\n // read joins back on — the same target is a different related\n // row under each parent. Distinct, so a junction without a\n // primary key on the pair cannot multiply the rows it joins.\n const keys = [sourceJunctionCol, targetJunctionCol];\n const ranked = this.db\n .selectDistinct(rankedKeyFields(keys, [sourceJunctionCol], order, window.totals ? [targetJunctionCol] : undefined))\n .from(junctionTable)\n .innerJoin(targetTable, eq(targetJunctionCol, targetIdField))\n .where(scope)\n .as(RANKED);\n results = await this.db\n .select()\n .from(junctionTable)\n .innerJoin(targetTable, eq(targetJunctionCol, targetIdField))\n .innerJoin(ranked, and(eq(sourceJunctionCol, ranked.k0), eq(targetJunctionCol, ranked.k1)))\n .where(lte(ranked.rank, window.limit))\n .orderBy(ranked.rank);\n } else {\n const query = this.db\n .select()\n .from(junctionTable)\n .innerJoin(targetTable, eq(targetJunctionCol, targetIdField))\n .where(scope);\n results = window?.orderBy?.length ? await query.orderBy(...order) : await query;\n }\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>[]>();\n const targetTableName = getTableName(targetCollection);\n // The junction's own columns, when it declares any. Built once per\n // batch rather than per row: it is derived from the relation, and\n // the relation does not change between rows.\n const payloadKeys = Object.keys(relation.through.properties);\n const junctionConfig = payloadKeys.length > 0\n ? getJunctionConfigForRelation(relation.through)\n : undefined;\n\n for (const row of results as Array<Record<string, unknown>>) {\n // The junction table data is namespaced under its table name\n const junctionData = (row[relation.through.table] || row) as Record<string, unknown>;\n const targetData = (row[targetTableName] || row) as Record<string, unknown>;\n\n const parentId = String(junctionData[relation.through.sourceColumn]);\n recordTotal(window, parentId, row);\n const arr = resultMap.get(parentId) || [];\n const related = await this.toRelatedRow(targetData, targetCollection, targetPks, render);\n if (junctionConfig) {\n related.pivot = await this.readPivot(junctionData, junctionConfig, payloadKeys);\n }\n arr.push(related);\n resultMap.set(parentId, arr);\n }\n\n return resultMap;\n }\n\n // Handle FK-based relations (one-to-many inverse). One column on the\n // target names the parent, so a composite-keyed parent cannot be named.\n this.assertSingleKeyAddressable(\n parentCollection,\n parentPks,\n hasForeignKeyOnTarget(relation) ? relation.foreignKeyOnTarget : relation.relationName,\n relation\n );\n\n const { keyByParentId, parentIdByKey } = hasForeignKeyOnTarget(relation)\n ? await this.resolveSourceKeys(parentCollection, relation, parentIds)\n : { keyByParentId: new Map<string, string | number>(), parentIdByKey: new Map<string, string | number>() };\n\n const matchValues = hasForeignKeyOnTarget(relation)\n ? [...keyByParentId.values()]\n : parsedParentIds;\n if (matchValues.length === 0) return new Map();\n\n const targetKeys = targetPks.map(pk => targetTable[pk.fieldName as keyof typeof targetTable] as AnyPgColumn);\n const order = relatedRowOrder(targetTable, targetKeys, window?.orderBy);\n const foreignKeyColumn = hasForeignKeyOnTarget(relation)\n ? targetTable[fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget) as keyof typeof targetTable] as AnyPgColumn | undefined\n : undefined;\n\n let results: unknown[];\n if (window?.limit !== undefined && foreignKeyColumn) {\n // The relation's own condition, the include's `where`, the soft\n // delete and `beforeQuery`: everything the unlimited read applies.\n let rankedQuery = this.db.select(rankedKeyFields(targetKeys, [foreignKeyColumn], order, window.totals ? \"rows\" : undefined)).from(targetTable).$dynamic();\n rankedQuery = applyDynamicRelationQuery(\n rankedQuery, rankedQuery, relation, matchValues, targetTable, parentTable,\n parentIdCol, targetIdField, this.registry, narrowTarget ? [narrowTarget] : []\n );\n const ranked = rankedQuery.as(RANKED);\n results = await this.db\n .select()\n .from(targetTable)\n .innerJoin(ranked, and(...targetKeys.map((key, i) => eq(key, ranked[`k${i}`])))!)\n .where(lte(ranked.rank, window.limit))\n .orderBy(ranked.rank);\n } else {\n let query = this.db.select().from(targetTable).$dynamic();\n query = applyDynamicRelationQuery(\n query, query, relation, matchValues, targetTable, parentTable,\n parentIdCol, targetIdField, this.registry, narrowTarget ? [narrowTarget] : []\n );\n results = window?.orderBy?.length ? await query.orderBy(...order) : await query;\n }\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>[]>();\n\n for (const row of results as Array<Record<string, unknown>>) {\n const targetRow = (row[getTableName(targetCollection)] || row) as Record<string, unknown>;\n\n // Junction-backed relations returned earlier in this method, so\n // what reaches here names the parent with a column on the target.\n if (!hasForeignKeyOnTarget(relation)) continue;\n\n const foreignKeyValue = targetRow[\n fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget)\n ] as string | number | undefined;\n if (foreignKeyValue === undefined || foreignKeyValue === null) continue;\n\n const parentId = parentIdByKey.get(String(foreignKeyValue));\n if (parentId !== undefined) {\n const key = String(parentId);\n recordTotal(window, key, row);\n const arr = resultMap.get(key) || [];\n arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks, render));\n resultMap.set(key, arr);\n }\n }\n\n return resultMap;\n }\n}\n","import { eq } from \"drizzle-orm\";\nimport { AnyPgColumn } from \"drizzle-orm/pg-core\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { CollectionConfig, Properties, Property, ResolvedRelation, RelationProperty, Vector, BinaryProperty, hasForeignKeyOnTarget, type ResolvedBelongsTo, type ResolvedForeignKeyOnTarget, type ResolvedVia } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, findRelation, fieldKeyForColumn, createRelationRef, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE } from \"@rebasepro/common\";\nimport { isPrototypePollutingKey } from \"@rebasepro/utils\";\nimport { PostgresCollectionRegistry } from \"./collections/PostgresCollectionRegistry\";\nimport { getPrimaryKeys, buildCompositeId, rowIdentityCondition } from \"./services/collection-helpers\";\nimport { applyDynamicJoin, joinStepCondition } from \"./services/RelationService\";\nimport { ApiError, logger } from \"@rebasepro/server\";\n\n/**\n * Data transformation utilities for converting between frontend and database formats.\n */\n\n/**\n * Typed result from `serializeDataToServer`.\n * Replaces the hidden `__inverseRelationUpdates` / `__joinPathRelationUpdates`\n * dunder-property mutation pattern with explicit, typed state management.\n */\nexport interface SerializedEntityData {\n /** Scalar column values ready for INSERT/UPDATE. */\n scalarData: Record<string, unknown>;\n /**\n * Inverse relation updates that must be applied to target tables.\n *\n * No address here: the row being written does not know its own. These are\n * applied by `PersistService`, which addresses them with the id it holds —\n * the one it was given for an update, or the one the INSERT returned — and\n * that is the authority. This item used to carry a `currentId` derived from\n * the *input* values, which nothing ever read.\n */\n inverseRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedRelation;\n newValue: unknown;\n }>;\n /** JoinPath relation updates that require multi-hop writes. */\n joinPathRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedVia;\n newTargetId: string | number | null;\n }>;\n}\n/**\n * Helper function to sanitize and convert dates to ISO strings\n */\nexport function sanitizeAndConvertDates(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return null;\n }\n\n if (typeof obj === \"number\" && isNaN(obj)) {\n return null;\n }\n\n if (typeof obj === \"string\" && obj.toLowerCase() === \"nan\") {\n return null;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(v => sanitizeAndConvertDates(v));\n }\n\n if (obj instanceof Date) {\n return obj.toISOString();\n }\n\n if (typeof obj === \"object\") {\n const newObj: Record<string, unknown> = {};\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\n // `JSON.parse` creates `__proto__` as an *own* property, so it gets\n // past the check above — and `newObj[key] = …` then invokes the\n // prototype setter instead of creating a property. The row's\n // prototype becomes whatever the request body supplied, so\n // `row.isAdmin` answers `true` while `Object.keys(row)` shows\n // nothing of the sort. No column can be reached by these names.\n if (isPrototypePollutingKey(key)) continue;\n newObj[key] = sanitizeAndConvertDates((obj as Record<string, unknown>)[key]);\n }\n return newObj;\n }\n\n if (typeof obj === \"string\") {\n const isoDateRegex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z$/;\n const jsDateRegex = /^\\w{3} \\w{3} \\d{2} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT[+-]\\d{4} \\(.+\\)$/;\n if (isoDateRegex.test(obj) || jsDateRegex.test(obj)) {\n const date = new Date(obj);\n if (!isNaN(date.getTime())) {\n return date.toISOString();\n }\n }\n }\n\n return obj;\n}\n\n/**\n * Transform relations for database storage (relation objects to IDs)\n */\nexport function serializeDataToServer<M extends Record<string, unknown>>(\n row: M,\n properties: Properties,\n collection?: CollectionConfig,\n registry?: PostgresCollectionRegistry\n): SerializedEntityData {\n if (!row || !properties) return { scalarData: row ?? {},\ninverseRelationUpdates: [],\njoinPathRelationUpdates: [] };\n\n const result: Record<string, unknown> = {};\n\n // Get normalized relations if collection is provided\n const resolvedRelations = collection ? resolveCollectionRelations(collection) : {};\n\n // Track inverse relations that need to be handled separately\n const inverseRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedRelation;\n newValue: unknown;\n }> = [];\n const joinPathRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedVia;\n newTargetId: string | number | null;\n }> = [];\n\n // Pre-calculate all local keys used as foreign keys.\n //\n // Keyed by the *wire* name: the payload arrives from a client, which knows\n // the field as `authorId`, never as the `author_id` column behind it.\n const foreignKeys = new Set<string>();\n Object.values(resolvedRelations).forEach(relation => {\n if (relation.kind === \"belongsTo\") foreignKeys.add(fieldKeyForColumn(collection, relation.localKey));\n });\n\n for (const [key, value] of Object.entries(row)) {\n // Same reasoning as `sanitizeAndConvertDates`: these keys come from the\n // request body, and no column answers to them.\n if (isPrototypePollutingKey(key)) continue;\n\n // `{ subtitle: undefined }` means \"I have no value for this\", not \"set\n // this column to NULL\" — it is what spreading an optional field\n // produces, and what Drizzle itself skips. The key used to survive with\n // `undefined` and `sanitizeAndConvertDates` then turned it into `null`,\n // so `update(id, { title, subtitle: payload.subtitle })` wiped\n // `subtitle` whenever the payload happened not to carry one. Unreachable\n // over HTTP (JSON has no `undefined`); the in-process data API passes\n // the caller's object straight through, so it is reachable there.\n if (value === undefined) continue;\n\n const property = properties[key as keyof M] as Property;\n\n // Coerce empty strings to null for any field that acts as a foreign key\n const effectiveValue = (foreignKeys.has(key) && value === \"\") ? null : value;\n\n if (!property) {\n result[key] = effectiveValue;\n continue;\n }\n\n\n // Handle relation properties specially\n if (property.type === \"relation\" && collection) {\n const relation = findRelation(resolvedRelations, key);\n if (relation) {\n if (relation.kind === \"belongsTo\") {\n // Owning relation: Map relation object to FK column on current table\n const serializedValue = serializePropertyToServer(effectiveValue, property, key);\n if (serializedValue !== undefined) {\n // The Drizzle key, not the column: Drizzle maps it back\n // to `author_id` when it builds the statement.\n result[fieldKeyForColumn(collection, relation.localKey)] = serializedValue;\n }\n // Don't add the original relation property to the result\n continue;\n } else if (hasForeignKeyOnTarget(relation)) {\n // Inverse relation: Need to update the target table's FK\n const serializedValue = serializePropertyToServer(effectiveValue, property, key);\n inverseRelationUpdates.push({\n relationKey: key,\n relation,\n newValue: serializedValue\n });\n // Don't add the original relation property to the result\n continue;\n } else if (relation.kind === \"via\") {\n // A join chain is written through its own machinery, one\n // row at a time for a to-one and as a set for a to-many.\n // There used to be two arms here — \"owning\" and \"inverse\"\n // joinPath — but a join chain has no owning side, so they\n // only ever differed by which list they pushed to.\n const serializedValue = serializePropertyToServer(effectiveValue, property, key);\n if (relation.cardinality === \"one\") {\n // Ordering matters: PersistService applies these BEFORE\n // the main UPDATE, so the mapping reads the pre-update\n // foreign key rather than a value the same save changed.\n joinPathRelationUpdates.push({\n relationKey: key,\n relation,\n newTargetId: serializedValue as string | number | null\n });\n } else {\n inverseRelationUpdates.push({\n relationKey: key,\n relation,\n newValue: serializedValue\n });\n }\n continue;\n }\n }\n }\n\n result[key] = serializePropertyToServer(effectiveValue, property, key);\n }\n\n return {\n scalarData: result,\n inverseRelationUpdates,\n joinPathRelationUpdates\n };\n}\n\n/**\n * How to name a rejected value in an error, without quoting it back.\n *\n * The value may be anything the caller sent, including a secret in the wrong\n * field, so the message describes its shape rather than echoing it — an echoed\n * value ends up in logs and in error-reporting services.\n */\nfunction describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n if (value instanceof Date) return \"a date\";\n const type = typeof value;\n return type === \"object\" ? \"an object\" : `a ${type}`;\n}\n\n/**\n * Serialize a single property value for database storage.\n *\n * `propertyKey` is only ever used to phrase errors and warnings. Without it the\n * one trace a bad value left was `Expected array value for array property, got\n * string` — no collection, no property, no value, which in the log of a\n * thousand-row import names nothing at all.\n */\n/** `HH:MM[:SS[.fff]]`, the text Postgres reads and writes for a `time` column. */\nconst TIME_OF_DAY = /^(\\d{2}):(\\d{2})(?::(\\d{2})(\\.\\d+)?)?$/;\n\n/**\n * A `time` column's text as the date it is served as: that time of day, UTC,\n * on 1970-01-01. `undefined` for text that is not a time of day.\n */\nfunction timeOfDayToDate(text: string): Date | undefined {\n const match = TIME_OF_DAY.exec(text.trim());\n if (!match) return undefined;\n const [, hh, mm, ss = \"00\", fraction = \"\"] = match;\n const date = new Date(`1970-01-01T${hh}:${mm}:${ss}${fraction ? fraction.slice(0, 4) : \"\"}Z`);\n return isNaN(date.getTime()) ? undefined : date;\n}\n\n/**\n * What a `time` column is written: the UTC time of day of a date, so neither\n * the server's zone nor the caller's moves it. Text already shaped as a time of\n * day is kept as written.\n */\nfunction dateToTimeOfDay(value: unknown): unknown {\n if (typeof value === \"string\" && TIME_OF_DAY.test(value.trim())) return value.trim();\n const date = value instanceof Date ? value : typeof value === \"string\" ? new Date(value) : undefined;\n if (!date || isNaN(date.getTime())) return value;\n return date.toISOString().slice(11, 23);\n}\n\nexport function serializePropertyToServer(value: unknown, property: Property, propertyKey?: string): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n const fieldLabel = propertyKey ? `'${propertyKey}'` : `a '${property.type}' field`;\n\n const propertyType = property.type;\n\n switch (propertyType) {\n case \"date\":\n return property.columnType === \"time\" ? dateToTimeOfDay(value) : value;\n\n case \"relation\":\n if (Array.isArray(value)) {\n return value.map(v => serializePropertyToServer(v, property, propertyKey));\n } else if (typeof value === \"object\" && value !== null && \"id\" in value) {\n return (value as Record<string, unknown>).id;\n }\n if (value === \"\") return null;\n return value;\n\n case \"array\":\n if (Array.isArray(value)) {\n if (property.of) {\n return value.map(item => serializePropertyToServer(item, property.of as Property, propertyKey));\n } else if (property.oneOf) {\n const typeField = property.oneOf.typeField ?? DEFAULT_ONE_OF_TYPE;\n const valueField = property.oneOf.valueField ?? DEFAULT_ONE_OF_VALUE;\n return value.map((e) => {\n if (e === null) return null;\n if (typeof e !== \"object\") return e;\n const rec = e as Record<string, unknown>;\n const type = rec[typeField] as string;\n const childProperty = property.oneOf?.properties[type];\n if (!type || !childProperty) return e;\n return {\n [typeField]: type,\n [valueField]: serializePropertyToServer(rec[valueField], childProperty, propertyKey)\n };\n });\n }\n return value;\n }\n // A non-array value used to become `[]` here — the caller's value\n // destroyed, answered 200/201, and the row reading back as an empty\n // list on every later fetch. `POST /posts {\"tags\":\"news\"}` from a\n // client that sent a single tag as a scalar, or a CSV import that\n // did not split a column, was unrecoverable data loss on a `text[]`\n // column. The stated reason for coercing — \"avoid .map() crashes\n // downstream\" — is better served by refusing the value at the\n // boundary, which is what a 400 does.\n //\n // The read-side twin (`parsePropertyFromServer`) still coerces, and\n // should: a row already in the database is not this caller's fault.\n throw ApiError.badRequest(\n `${fieldLabel} expects an array, but received ${describeValue(value)}.`,\n \"VALIDATION_INVALID_VALUE\"\n );\n\n case \"geopoint\": {\n // Stored as `jsonb`, in the `{ latitude, longitude }` shape the\n // OpenAPI schema and the generated TS type both promise. Nothing\n // used to handle `geopoint` on either side, which is why the type\n // was documented, code-generated, admin-editable — and dropped.\n if (typeof value !== \"object\" || Array.isArray(value)) {\n throw ApiError.badRequest(\n `${fieldLabel} expects a geopoint object with \\`latitude\\` and \\`longitude\\`, ` +\n `but received ${describeValue(value)}.`,\n \"VALIDATION_INVALID_VALUE\"\n );\n }\n const point = value as Record<string, unknown>;\n if (typeof point.latitude !== \"number\" || typeof point.longitude !== \"number\") {\n throw ApiError.badRequest(\n `${fieldLabel} expects a geopoint object with numeric \\`latitude\\` and \\`longitude\\`.`,\n \"VALIDATION_INVALID_VALUE\"\n );\n }\n return { latitude: point.latitude,\nlongitude: point.longitude };\n }\n\n\n case \"map\":\n if (typeof value === \"object\" && property.properties) {\n const result: Record<string, unknown> = {};\n for (const [subKey, subValue] of Object.entries(value)) {\n const subProperty = (property.properties as Properties)[subKey];\n if (subProperty) {\n result[subKey] = serializePropertyToServer(subValue, subProperty, propertyKey ? `${propertyKey}.${subKey}` : subKey);\n } else {\n result[subKey] = subValue;\n }\n }\n return result;\n }\n return value;\n case \"vector\": {\n if (value instanceof Vector) {\n return value.value;\n }\n if (value && typeof value === \"object\" && \"value\" in value && Array.isArray((value as { value: unknown }).value)) {\n return (value as { value: unknown[] }).value.map(Number);\n }\n if (Array.isArray(value)) {\n return value.map(Number);\n }\n return value;\n }\n\n case \"binary\": {\n const decoded = tryDecodeBase64DataUrl(value);\n if (decoded) return decoded;\n if (Buffer.isBuffer(value)) return value;\n return value;\n }\n\n case \"string\":\n default: {\n const decoded = tryDecodeBase64DataUrl(value);\n if (decoded) return decoded;\n return value;\n }\n }\n}\n\n/**\n * Transform IDs back to relation objects for frontend\n */\nexport async function parseDataFromServer<M extends Record<string, unknown>>(\n data: M,\n collection: CollectionConfig,\n db?: NodePgDatabase<Record<string, unknown>>,\n registry?: PostgresCollectionRegistry\n): Promise<M> {\n const properties = collection.properties;\n if (!data || !properties) return data;\n\n // Get the normalized relations once\n const resolvedRelations = resolveCollectionRelations(collection);\n\n // Shared scalar + relation value normalization\n const result = normalizeScalarValues(data, properties, collection, resolvedRelations, { skipRelations: false });\n\n // Add relation properties that should be populated from FK values or inverse queries\n for (const [propKey, property] of Object.entries(properties)) {\n if (property.type === \"relation\" && !(propKey in result)) {\n // Find the normalized relation for this property\n const relation = findRelation(resolvedRelations, propKey);\n if (relation) {\n const localField = relation.kind === \"belongsTo\"\n ? fieldKeyForColumn(collection, relation.localKey)\n : \"\";\n if (relation.kind === \"belongsTo\" && localField in data) {\n // Owning relation: FK is in current table, under its wire name\n const fkValue = data[localField as keyof M];\n if (fkValue !== null && fkValue !== undefined) {\n try {\n const targetCollection = relation.target();\n result[propKey] = createRelationRef(fkValue.toString(), targetCollection.slug);\n } catch (e) {\n logger.warn(`Could not resolve target collection for relation property: ${propKey}`, { error: e });\n }\n }\n } else if (hasForeignKeyOnTarget(relation) && db && registry) {\n // Inverse relation: FK is in target table, need to query for it\n try {\n const targetCollection = relation.target();\n const targetTable = registry.getTable(getTableName(targetCollection));\n const pks = getPrimaryKeys(collection, registry!);\n // What the target's foreign key holds. Ordinarily this\n // row's id; for a link on a natural key, the value of\n // the column it points at — which is on the row already,\n // so this costs nothing extra.\n const currentId = relation.sourceKey\n ? (data as Record<string, unknown>)[relation.sourceKey] as string | number | undefined\n : buildCompositeId(data, pks);\n\n if (targetTable && currentId !== undefined && currentId !== null && currentId !== \"\") {\n const fkFieldKey = fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget);\n const foreignKeyColumn = targetTable[fkFieldKey as keyof typeof targetTable] as AnyPgColumn;\n if (foreignKeyColumn) {\n // Query the target table to find row that references this row\n const relatedRows = await db\n .select()\n .from(targetTable)\n .where(eq(foreignKeyColumn, currentId))\n .limit(relation.cardinality === \"one\" ? 1 : 100); // Limit for one-to-one vs one-to-many\n\n if (relatedRows.length > 0) {\n if (relation.cardinality === \"one\") {\n // One-to-one: return single relation object\n const targetPks = getPrimaryKeys(targetCollection, registry!);\n const relatedRow = relatedRows[0] as Record<string, unknown>;\n result[propKey] = createRelationRef(buildCompositeId(relatedRow, targetPks), targetCollection.slug);\n } else {\n // One-to-many: return array of relation objects\n const targetPks = getPrimaryKeys(targetCollection, registry!);\n result[propKey] = relatedRows.map((row: Record<string, unknown>) =>\n createRelationRef(buildCompositeId(row, targetPks), targetCollection.slug)\n );\n }\n }\n }\n }\n } catch (e) {\n logger.warn(`Could not resolve inverse relation property: ${propKey}`, { error: e });\n }\n } else if (relation.kind === \"via\" && db && registry) {\n // Join path relation: Multi-hop relation using joins\n try {\n const targetCollection = relation.target();\n const pks = getPrimaryKeys(collection, registry!);\n const currentId = buildCompositeId(data, pks);\n\n if (currentId) {\n // Build the join query following the join path\n const sourceTable = registry.getTable(getTableName(collection));\n if (!sourceTable) {\n logger.warn(`Source table not found for collection: ${collection.slug}`);\n continue;\n }\n\n let query = db.select().from(sourceTable);\n let currentTable = sourceTable;\n\n // Apply each join in the path\n for (const join of relation.joinPath) {\n const joinTable = registry.getTable(join.table);\n if (!joinTable) {\n logger.warn(`Join table not found: ${join.table}`);\n break;\n }\n\n // Every column pair of the step, for a step that joins on a tuple.\n const step = joinStepCondition(currentTable, joinTable, join);\n if (\"missing\" in step) {\n logger.warn(`Join columns not found: ${step.missing}`);\n break;\n }\n\n query = applyDynamicJoin(query, joinTable, step.condition);\n currentTable = joinTable;\n }\n\n // The current row, by every key column. Without a\n // condition on each of them the join reads the\n // related rows of every row sharing the first one —\n // or, with none at all, of the whole table.\n const currentRow = rowIdentityCondition(sourceTable, pks, currentId, collection.slug);\n\n // Execute the query\n const joinResults = await query.where(currentRow).limit(relation.cardinality === \"one\" ? 1 : 100);\n\n if (joinResults.length > 0) {\n const targetPks = getPrimaryKeys(targetCollection, registry!);\n const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;\n\n if (relation.cardinality === \"one\") {\n // One-to-one: return single relation object\n const joinResult = joinResults[0] as Record<string, unknown>;\n const targetRow = (joinResult[targetTableName] || joinResult) as Record<string, unknown>;\n result[propKey] = createRelationRef(buildCompositeId(targetRow, targetPks), targetCollection.slug);\n } else {\n // One-to-many: return array of relation objects\n result[propKey] = joinResults.map((joinResult: Record<string, unknown>) => {\n const targetRow = (joinResult[targetTableName] || joinResult) as Record<string, unknown>;\n return createRelationRef(buildCompositeId(targetRow, targetPks), targetCollection.slug);\n });\n }\n }\n }\n } catch (e) {\n logger.warn(`Could not resolve join path relation property: ${propKey}`, { error: e });\n }\n }\n }\n }\n }\n\n return result as M;\n}\n\n/**\n * Try to decode a `data:application/octet-stream;base64,...` data URL string\n * into a Buffer. Returns null if the value is not a matching data URL.\n */\nfunction tryDecodeBase64DataUrl(value: unknown): Buffer | null {\n if (typeof value !== \"string\") return null;\n if (!value.startsWith(\"data:application/octet-stream;base64,\")) return null;\n const base64Data = value.split(\",\")[1];\n return base64Data ? Buffer.from(base64Data, \"base64\") : null;\n}\n\n/**\n * Try to resolve an unknown value into a Buffer.\n * Handles native Buffers and `{ type: \"Buffer\", data: number[] }` objects (from JSON deserialization).\n * Returns null if the value is not a buffer.\n */\nfunction tryResolveBuffer(value: unknown): Buffer | null {\n if (Buffer.isBuffer(value)) return value;\n if (typeof value === \"object\" && value !== null) {\n const rawVal = value as Record<string, unknown>;\n if (rawVal.type === \"Buffer\" && Array.isArray(rawVal.data)) {\n return Buffer.from(rawVal.data as number[]);\n }\n }\n return null;\n}\n\n/**\n * Convert a Buffer to a UTF-8 string if all bytes are printable ASCII,\n * otherwise return a base64 data URL.\n */\nfunction bufferToStringOrBase64(buf: Buffer): string {\n for (let i = 0; i < buf.length; i++) {\n const b = buf[i];\n // Allow standard printable ASCII + common whitespace (\\r, \\n, \\t)\n if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {\n return `data:application/octet-stream;base64,${buf.toString(\"base64\")}`;\n }\n }\n return buf.toString(\"utf8\");\n}\n\nexport function parsePropertyFromServer(value: unknown, property: Property, collection: CollectionConfig, propertyKey?: string): unknown {\n if (value === null || value === undefined) return value;\n\n switch (property.type) {\n case \"binary\": {\n const buf = tryResolveBuffer(value);\n if (buf) {\n return `data:application/octet-stream;base64,${buf.toString(\"base64\")}`;\n }\n return value;\n }\n\n case \"string\": {\n if (typeof value === \"string\") return value;\n\n // Handle Buffer objects (e.g. from PostgreSQL bytea columns)\n const buf = tryResolveBuffer(value);\n if (buf) {\n return bufferToStringOrBase64(buf);\n }\n\n if (typeof value === \"object\" && value !== null) {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n }\n\n case \"relation\":\n // Transform ID back to relation object with type information\n if (typeof value === \"string\" || typeof value === \"number\") {\n // Normalization stamps the resolved relation onto the property;\n // fall back to the collection's map for a property that never\n // went through the registry.\n let relationDef: ResolvedRelation | undefined = (property as RelationProperty).resolvedRelation;\n if (!relationDef && propertyKey) {\n relationDef = findRelation(resolveCollectionRelations(collection), propertyKey);\n }\n\n if (!relationDef) {\n logger.warn(`ResolvedRelation not defined in property for key: ${propertyKey || \"unknown\"}`);\n return value;\n }\n\n try {\n const targetCollection = relationDef.target();\n return createRelationRef(value.toString(), targetCollection.slug);\n } catch (e) {\n logger.warn(`Could not resolve target collection for relation property: ${propertyKey || \"unknown\"}`, { error: e });\n return value;\n }\n }\n return value;\n\n case \"array\":\n if (Array.isArray(value)) {\n if (property.of) {\n return value.map(item => parsePropertyFromServer(item, property.of as Property, collection));\n } else if (property.oneOf) {\n const typeField = property.oneOf.typeField ?? DEFAULT_ONE_OF_TYPE;\n const valueField = property.oneOf.valueField ?? DEFAULT_ONE_OF_VALUE;\n return value.map((e) => {\n if (e === null) return null;\n if (typeof e !== \"object\") return e;\n const rec = e as Record<string, unknown>;\n const type = rec[typeField] as string;\n const childProperty = property.oneOf?.properties[type];\n if (!type || !childProperty) return e;\n return {\n [typeField]: type,\n [valueField]: parsePropertyFromServer(rec[valueField], childProperty, collection)\n };\n });\n }\n } else {\n // Non-array value from DB for an array property — coerce to avoid .map() crashes\n logger.warn(`Expected array value from DB for array property, got ${typeof value}. Coercing to array.`);\n return typeof value === \"string\" ? [value] : [];\n }\n return value;\n\n case \"map\":\n if (typeof value === \"object\" && property.properties) {\n const result: Record<string, unknown> = {};\n for (const [subKey, subValue] of Object.entries(value)) {\n const subProperty = (property.properties as Properties)[subKey];\n if (subProperty) {\n result[subKey] = parsePropertyFromServer(subValue, subProperty, collection);\n } else {\n result[subKey] = subValue;\n }\n }\n return result;\n }\n return value;\n\n case \"number\":\n if (typeof value === \"string\") {\n const parsed = parseFloat(value);\n return isNaN(parsed) ? null : parsed;\n }\n return value;\n\n case \"geopoint\":\n // A `jsonb` column, so node-postgres hands back the object already.\n // Named explicitly rather than left to `default:`, which would run\n // it past the Buffer probe — and because a type with a write\n // serializer and no read counterpart is how this one went missing.\n return value;\n\n case \"vector\": {\n let nums: number[] = [];\n if (typeof value === \"string\") {\n nums = value.slice(1, -1).split(\",\").map(Number);\n } else if (Array.isArray(value)) {\n nums = value.map(Number);\n } else if (value instanceof Vector) {\n nums = value.value;\n } else if (typeof value === \"object\" && value !== null && \"value\" in value) {\n const valObj = value as { value: unknown };\n if (Array.isArray(valObj.value)) {\n nums = valObj.value.map(Number);\n }\n }\n return {\n __type: \"Vector\",\n value: nums\n };\n }\n\n case \"date\": {\n let date: Date | undefined;\n if (property.columnType === \"time\" && typeof value === \"string\") {\n // A time of day, served like a date-only column: on a fixed\n // day, in UTC. `new Date(\"09:30:00\")` is Invalid, which read\n // every time column as null.\n date = timeOfDayToDate(value);\n } else if (value instanceof Date) {\n date = value;\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n const parsedDate = new Date(value);\n if (!isNaN(parsedDate.getTime())) {\n date = parsedDate;\n }\n }\n if (date) {\n return {\n __type: \"date\",\n value: date.toISOString()\n };\n }\n return null;\n }\n\n default: {\n // Fallback for buffers in case they are mapped to something other than string\n const buf = tryResolveBuffer(value);\n if (buf) {\n return bufferToStringOrBase64(buf);\n }\n return value;\n }\n }\n}\n\n/**\n * Shared internal helper: normalizes scalar column values from a DB row\n * into frontend format. Handles FK-column preservation, type coercion\n * (dates, numbers, NaN), and property filtering.\n *\n * @param skipRelations When `true`, relation-typed properties are omitted\n * from the result (used by `normalizeDbValues` where\n * Drizzle's relational API already hydrates them).\n */\n/**\n * Keys a query computes and attaches to a row, rather than reads from a column.\n *\n * An explicit set rather than a `_` prefix rule: a user's column may perfectly\n * well be called `_internal`, and passing it through here would put a value on\n * the row that no property describes and nothing downstream knows how to type.\n */\nconst QUERY_METADATA_KEYS = new Set([\"_score\", \"_distance\", \"_matches\"]);\n\nfunction normalizeScalarValues<M extends Record<string, unknown>>(\n data: M,\n properties: Properties,\n collection: CollectionConfig,\n resolvedRelations: Record<string, ResolvedRelation>,\n options: { skipRelations: boolean }\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n // Identify FK columns used only for relations and not exposed as properties\n const internalFKColumns = new Set<string>();\n Object.values(resolvedRelations).forEach(relation => {\n if (relation.kind !== \"belongsTo\") return;\n // The key a row carries this foreign key under, which is the wire name\n // — `authorId`, not the `author_id` column.\n const localField = fieldKeyForColumn(collection, relation.localKey);\n if (!properties[localField]) internalFKColumns.add(localField);\n });\n\n for (const [key, value] of Object.entries(data)) {\n // Keep internal FK columns as primitives\n if (internalFKColumns.has(key)) {\n result[key] = value === null ? null : (typeof value === \"number\" ? value : String(value));\n continue;\n }\n\n // Query-computed metadata, which by definition is not a column and so\n // has no property to be found. Dropped here until now — which meant\n // `_distance` was computed for every vector search on this path and\n // then discarded before the caller ever saw it.\n if (QUERY_METADATA_KEYS.has(key)) {\n result[key] = value;\n continue;\n }\n\n const property = properties[key as keyof M] as Property;\n if (!property) continue; // Skip DB columns not defined in properties\n\n if (options.skipRelations && property.type === \"relation\") continue;\n\n result[key] = parsePropertyFromServer(value, property, collection, key);\n }\n\n return result;\n}\n\n/**\n * Lightweight value normalization for db.query results.\n * Only handles type coercion (dates, numbers, NaN) and property filtering.\n * Does NOT query the database for relations — those are already resolved\n * by Drizzle's relational query API.\n *\n * Use this instead of `parseDataFromServer` when processing results from\n * `db.query.findFirst/findMany` which return pre-hydrated relation data.\n */\nexport function normalizeDbValues<M extends Record<string, unknown>>(\n data: M,\n collection: CollectionConfig\n): M {\n const properties = collection.properties;\n if (!data || !properties) return data;\n\n const resolvedRelations = resolveCollectionRelations(collection);\n return normalizeScalarValues(data, properties, collection, resolvedRelations, { skipRelations: true }) as M;\n}\n","import { CollectionConfig, ResolvedRelation } from \"@rebasepro/types\";\nimport { apiHiddenFieldNames, findRelation, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { namesHiddenFromCaller } from \"./field-viewer\";\nimport { ApiError } from \"@rebasepro/server\";\n\nimport { getCollectionByPath } from \"./collection-helpers\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\n\n/**\n * The last hop of a nested collection path, e.g. `authors/1/posts`.\n *\n * The walk that produces this was written out four separate times — in\n * `FetchService.fetchCollectionFromPath`, `FetchService.countEntitiesFromPath`,\n * `PersistService.save` and `CollectionRegistry.getCollectionByPath` — and had\n * drifted, so the read path and the write path did not agree on which relation\n * a path named. It lives here once now.\n */\nexport interface NestedPathHop {\n /** The collection the final relation is declared on (e.g. `authors`). */\n parentCollection: CollectionConfig;\n /** The parent's id as it appeared in the path, unparsed. */\n parentId: string;\n /** The path segment that named the relation (e.g. `posts`). */\n relationKey: string;\n relation: ResolvedRelation;\n /** `relation.target()`, resolved once. */\n targetCollection: CollectionConfig;\n}\n\n/**\n * True when `path` addresses rows through a relation rather than a root\n * collection.\n *\n * Any separator at all counts — a root collection slug never contains one — so\n * a malformed path like `collection/id` is a *broken* nested path and gets\n * reported as one by {@link resolveNestedPath}, rather than being looked up as\n * a root collection whose slug happens to contain a slash.\n */\nexport function isNestedPath(path: string): boolean {\n return path.includes(\"/\");\n}\n\nexport function splitPathSegments(path: string): string[] {\n return path.split(\"/\").filter(s => s && s !== \"undefined\");\n}\n\n/**\n * Walk a nested collection path down to the relation it ends in.\n *\n * Returns `undefined` for a plain root-collection path so callers can keep the\n * root case on its existing code path. Throws when the path is malformed, or\n * when a segment names a relation that does not exist — the same errors the\n * individual walks used to raise, with the available names attached.\n */\nexport function resolveNestedPath(\n path: string,\n registry: PostgresCollectionRegistry\n): NestedPathHop | undefined {\n if (!isNestedPath(path)) return undefined;\n\n const segments = splitPathSegments(path);\n if (segments.length < 3 || segments.length % 2 === 0) {\n throw new Error(`Invalid relation path: ${path}. Expected format: collection/id/relation`);\n }\n\n let parentCollection = getCollectionByPath(segments[0], registry);\n let parentId = segments[1];\n\n for (let i = 2; i < segments.length; i += 2) {\n const relationKey = segments[i];\n const resolvedRelations = resolveCollectionRelations(parentCollection);\n // A relation the API does not have is a segment that names nothing,\n // for this caller — and is never offered among the ones there are.\n const relation = namesHiddenFromCaller(parentCollection).has(relationKey)\n ? undefined\n : findRelation(resolvedRelations, relationKey);\n\n if (!relation) {\n const apiHidden = apiHiddenFieldNames(parentCollection);\n const available = Object.keys(resolvedRelations).filter(key => !apiHidden.has(key)).join(\", \") || \"(none)\";\n // A path segment naming a relation the collection does not declare\n // is a bad URL, not a server fault. It answered 500 INTERNAL_ERROR,\n // which tells the caller to retry or report an outage over a\n // request that will never succeed — and buries a message that\n // already says exactly what is wrong.\n //\n // 404, like an unknown collection: the URL names nothing. The\n // available relations stay in the message because reaching this\n // point means the caller could already read the parent collection,\n // and `?include=` would list the same names.\n throw ApiError.notFound(\n `Relation '${relationKey}' not found in collection '${parentCollection.slug}'. Available relations: [${available}]`,\n \"UNKNOWN_RELATION\"\n );\n }\n\n const targetCollection = relation.target();\n\n if (i === segments.length - 1) {\n return {\n parentCollection,\n parentId,\n relationKey,\n relation,\n targetCollection\n };\n }\n\n parentCollection = targetCollection;\n parentId = segments[i + 1];\n }\n\n // Unreachable: the loop returns on the final segment, and the odd-length\n // check above guarantees there is one.\n throw new Error(`Unable to resolve path: ${path}`);\n}\n\n/**\n * A relation reached through a junction table — many-to-many, or a multi-hop\n * `joinPath`. The target row is shared with other parents, so writing \"through\"\n * such a path addresses the *link*, not the row.\n */\nexport function isJunctionBackedRelation(relation: ResolvedRelation): boolean {\n return relation.shared;\n}\n\n/**\n * Reject a nested write whose final segment is a to-one relation.\n *\n * There is no column on the target row that records a to-one parent — the\n * foreign key lives on the *parent* table. The write path used to fall through\n * to `relation.localKey` here and stamp the parent's own FK column onto the\n * target row, which either raised an opaque \"column does not exist\" or, when a\n * column of that name happened to exist on the target, silently wrote the wrong\n * one.\n */\nexport function assertWritableThrough(hop: NestedPathHop, path: string): void {\n const { relation } = hop;\n\n // Read the flag rather than re-deriving the rule. This guard used to test\n // `cardinality !== \"many\"`, which let a to-many `via` through even though\n // its type declares `writable: false` — a second expression of one rule,\n // free to disagree with the first, which is the shape of defect this union\n // exists to remove.\n if (relation.writable && relation.cardinality === \"many\") return;\n\n if (!relation.writable) {\n throw ApiError.badRequest(\n `\"${path}\" ends in '${hop.relationKey}', a \\`via\\` relation. Rebase will not infer how to write ` +\n `through an arbitrary join chain — write the row at \"${hop.targetCollection.slug}\" directly.`,\n \"RELATION_NOT_WRITABLE\"\n );\n }\n\n throw ApiError.badRequest(\n `\"${path}\" ends in the to-one relation '${hop.relationKey}', which cannot be written through: ` +\n `the foreign key for a to-one relation lives on '${hop.parentCollection.slug}', not on ` +\n `'${hop.targetCollection.slug}'. Write the target row at \"${hop.targetCollection.slug}\" and set ` +\n `'${hop.relationKey}' on the parent instead.`,\n \"RELATION_NOT_WRITABLE\"\n );\n}\n","import { and, asc, count, desc, eq, getTableColumns, getTableName, gt, is, isNotNull, isNull, lt, or, sql, SQL, TableRelationalConfig, TablesRelationalConfig } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable, PgTimestamp, PgTimestampString } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, FilterValues, INCLUDE_COUNT_KEY, JUNCTION_PIVOT_KEY, MAX_INCLUDE_DEPTH, OrderByTuple, ResolvedRelation, LogicalCondition, parseRelationAggregateSort } from \"@rebasepro/types\";\nimport type { IncludeSpec, NullsPlacement, ReadOperation, ReadQuery, VectorSearchParams } from \"@rebasepro/types\";\nimport { includableRelationNames, queryableFieldNames, unknownAggregateField, unknownIncludeRelation, unknownOrderByField, unknownProjectionField, resolveCollectionRelations, findRelation, fieldKeyForColumn, createRelationRefWithData, hasFieldAccessRules, normalizeDriverOrderBy, normalizeInclude, encodeCursor, type IncludeNode, type NormalizedInclude } from \"@rebasepro/common\";\nimport { generateForeignKeyName, toWireKey } from \"@rebasepro/utils\";\nimport { DrizzleConditionBuilder, getUnknownFilterFieldsMode, listedColumnKeys, type FilterCompilationOptions } from \"../utils/drizzle-conditions\";\nimport { namesHiddenFromCaller } from \"./field-viewer\";\nimport { enableIterativeScan, inTransaction, iterativeScansAvailable } from \"./vector-iterative-scan\";\nimport {\n getCollectionByPath,\n getTableForCollection,\n getPrimaryKeys,\n requirePrimaryKeys,\n deriveRowAddress,\n parseIdValues,\n idCanAddressTable,\n rowIdentityCondition,\n primaryKeyColumns,\n buildCompositeId,\n COMPOSITE_ID_SEPARATOR,\n type PrimaryKeyInfo\n} from \"./collection-helpers\";\nimport { parseDataFromServer } from \"../data-transformer\";\nimport { RelationService } from \"./RelationService\";\nimport { RelationalQueryBuilder } from \"drizzle-orm/pg-core/query-builders/query\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { toFlatRow, toRestRow, stripUnreadable, canReadRelation } from \"./row-pipeline\";\nimport { visibleColumnProjection, hiddenColumnsOption } from \"../schema/search-column\";\nimport { isNestedPath, resolveNestedPath, type NestedPathHop } from \"./nested-path\";\n// One rule, one place. See `soft-delete.ts` for why every read has to ask.\nimport { andSoftDelete, withSoftDelete, type WithDeleted } from \"./soft-delete\";\nimport { beforeQueryCondition, beforeQueryConditions, hasBeforeQuery, type ReadCallContextProvider } from \"./read-scope\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport { reachedDatabase } from \"../utils/pg-error-utils\";\n\n/** Type-safe accessor for Drizzle's relational query API via dynamic table name */\ntype DbQueryAccessor = Record<string, RelationalQueryBuilder<any, any>> | undefined;\n\n/**\n * One sort key, with the column or expression it was resolved to.\n *\n * `cursorTarget` is the same expression pinned to the cursor row instead of the\n * outer one, and is set only for a key that has no stored value to compare a\n * later page against — an aggregate over a relation. Where it is present the\n * keyset comparison recomputes the cursor row's value in SQL rather than\n * reading it off the cursor; see {@link FetchService.buildKeysetComparison}.\n */\ntype ResolvedOrderKey = {\n field: string;\n direction: \"asc\" | \"desc\";\n /**\n * Where this key's NULLs go. Absent means the direction's own convention —\n * see {@link FetchService.nullsLast}, which is the one place the two\n * functions that have to agree about it both read.\n */\n nulls?: NullsPlacement;\n target: AnyPgColumn | SQL;\n cursorTarget?: SQL;\n};\n\n/**\n * Service for handling all row read operations.\n * Handles fetching, searching, counting, and filtering rows.\n */\n/**\n * Which aggregate aliases hold a number that Postgres handed back as a string.\n *\n * `count`, `sum` and `avg` always do: bigint and numeric are returned as text\n * because they do not fit a JS number in general. `min` and `max` are uncast,\n * so they do it too — but only over a numeric column, and those are the two\n * functions that also apply to text and dates.\n *\n * Decided by the column's DECLARED type, not by trying `Number()` on the value.\n * `?select=avg(price),max(price)` used to answer `{avg_price: 5,\n * max_price: \"8.5\"}` — one column, two functions, two JSON types, and\n * arithmetic on the second one silently concatenates. Parsing whatever looks\n * numeric would fix that and break something worse: a `min(sku)` of `\"00123\"`\n * would come back as `123`, a different value, and no NaN check would catch it.\n *\n * Exported to be tested: the parsing itself needs a database, this decision\n * does not.\n */\nexport function numericAggregateAliases(\n aggregates: { fn: \"count\" | \"sum\" | \"avg\" | \"min\" | \"max\"; field?: string; alias: string }[],\n properties: Record<string, { type?: string }>\n): Set<string> {\n const isNumberField = (field?: string): boolean =>\n Boolean(field) && properties[field as string]?.type === \"number\";\n return new Set(\n aggregates\n .filter(a =>\n a.fn === \"count\" || a.fn === \"sum\" || a.fn === \"avg\"\n || ((a.fn === \"min\" || a.fn === \"max\") && isNumberField(a.field)))\n .map(a => a.alias)\n );\n}\n\n/**\n * Refuse a read that names a relation property its caller may not read.\n *\n * The relation-shaped twin of the REST refusal of `?fields=salary`\n * (`assertQueryFieldsReadable`): a nested URL (`/departments/1/members`, and\n * `/departments/1/members/7` — is 7 one of them?) hands over the property's\n * value, its related rows; an aggregate sort (`orderBy=count(members)`) orders\n * the parents by it. An `include` omits such a relation instead (see\n * {@link canReadRelation} in `loadIncludes`), as the row strip omits any\n * withheld field from a row; a request that *names* one as what to read or\n * sort by is refused, naming it — field names are public, values are not.\n *\n * Except an `excludeFromApi` relation's name, which is not public. Its callers\n * answer it as a relation the collection does not declare before they get\n * here, so reaching this with one is a missed door — refused all the same.\n */\nfunction assertRelationReadable(\n collection: CollectionConfig,\n key: string,\n relation: ResolvedRelation,\n use: string\n): void {\n if (canReadRelation(collection, key, relation)) return;\n throw ApiError.badRequest(\n `'${key}' is not readable on '${collection.slug}' with your roles, so it cannot be used in ${use}.`,\n \"FIELD_NOT_READABLE\",\n {\n collection: collection.slug,\n fields: [key],\n violations: [{ field: key, code: \"access\", message: `'${key}' is not readable with your roles.` }]\n }\n );\n}\n\nexport class FetchService {\n private relationService: RelationService;\n\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) {\n this.relationService = new RelationService(db, registry, callContext);\n }\n\n /**\n * Get the relational query builder for a given table name.\n * Safely narrows the DrizzleClient union type to access db.query[tableName].\n */\n private getQueryBuilder(tableName: string): RelationalQueryBuilder<TablesRelationalConfig, TableRelationalConfig> | undefined {\n const query = (this.db as { query?: DbQueryAccessor }).query;\n return query?.[tableName] as RelationalQueryBuilder<TablesRelationalConfig, TableRelationalConfig> | undefined;\n }\n\n /**\n * The context the condition builder needs to compile a filter key that is\n * not a column name outright.\n *\n * Two such keys. An owning relation's key resolves through the collection's\n * relations to its foreign-key column; a relation whose link lives on the\n * target table or in a junction resolves to a correlated `EXISTS`, which\n * needs the registry to reach that other table and this table's key column\n * to correlate back.\n *\n * Looked up rather than passed: every read path already has the path, only\n * some have the collection, and a path that names no registered collection\n * (a nested/derived one) is not an error here — the builder simply falls\n * back to guessing the default key shapes, and a relation filter it cannot\n * compile stays unresolvable and so fails closed.\n */\n private filterContext(collectionPath: string, table: PgTable<any>): FilterCompilationOptions {\n const collection = this.registry.getCollectionByPath(collectionPath) ?? undefined;\n return {\n collection,\n registry: this.registry,\n sourceIdColumn: collection ? this.resolveIdColumn(collection, table) : undefined\n };\n }\n\n /**\n * The conditions this collection's `beforeQuery` hooks ask to add, for a\n * read that is about to be compiled.\n *\n * Resolved here, from the registry this service already holds, rather than\n * handed in by a caller — so a read path that builds a WHERE cannot serve\n * one of these collections without the hook. `[]` when nothing is declared,\n * which leaves the compiled SQL byte-identical.\n *\n * Every caller pushes the result onto the same `allConditions` array its\n * own filter goes into, and every one of those arrays is AND-ed. That is\n * what keeps a hook additive: it is not trusted to narrow, it is only ever\n * given a way to.\n */\n private narrowRead(\n collectionPath: string,\n collection: CollectionConfig,\n table: PgTable<any>,\n operation: ReadOperation,\n query: ReadQuery\n ): Promise<SQL[]> {\n return beforeQueryConditions(\n { registry: this.registry, callContext: this.callContext },\n collection, collectionPath, table as PgTable<never>,\n { operation, query }, this.filterContext(collectionPath, table)\n );\n }\n\n /** {@link narrowRead}, pre-combined, for the reads that hold one `SQL`. */\n private narrowReadCondition(\n collectionPath: string,\n collection: CollectionConfig,\n table: PgTable<any>,\n operation: ReadOperation,\n query: ReadQuery\n ): Promise<SQL | undefined> {\n return beforeQueryCondition(\n { registry: this.registry, callContext: this.callContext },\n collection, collectionPath, table as PgTable<never>,\n { operation, query }, this.filterContext(collectionPath, table)\n );\n }\n\n /**\n * The read the hooks are shown, from the options a read path was given.\n *\n * Transport is left out on purpose — a `databaseId`, an `include` tree, a\n * `withDeleted` flag are not part of \"which rows\". What is in is what a\n * scope might reasonably branch on.\n */\n private static describeRead(options: {\n filter?: ReadQuery[\"filter\"];\n logical?: LogicalCondition;\n searchString?: string;\n limit?: number;\n offset?: number;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n fields?: string[];\n relatedTo?: NestedPathHop;\n }): ReadQuery {\n const orderBy = normalizeDriverOrderBy(options.orderBy, options.order);\n return {\n filter: options.filter,\n logical: options.logical,\n searchString: options.searchString,\n limit: options.limit,\n offset: options.offset,\n orderBy: orderBy && orderBy.length > 0 ? orderBy : undefined,\n fields: options.fields,\n relatedTo: options.relatedTo\n ? {\n parentSlug: options.relatedTo.parentCollection.slug,\n parentId: options.relatedTo.parentId,\n relationName: options.relatedTo.relation.relationName\n }\n : undefined\n };\n }\n\n /**\n * The table column this collection's rows are keyed by, or `undefined`.\n *\n * `getPrimaryKeys` rather than `requirePrimaryKeys`: a collection with no\n * resolvable key is not an error on the filter path — it only means the\n * relation filters that would correlate on it cannot be compiled, which\n * the builder already handles by failing that field closed.\n */\n private resolveIdColumn(collection: CollectionConfig, table: PgTable<any>): AnyPgColumn | undefined {\n const [idInfo] = getPrimaryKeys(collection, this.registry);\n if (!idInfo) return undefined;\n return table[idInfo.fieldName as keyof typeof table] as AnyPgColumn | undefined;\n }\n\n /**\n * Build filter conditions from FilterValues\n * Delegates to DrizzleConditionBuilder.buildFilterConditions\n */\n buildFilterConditions<M extends Record<string, unknown>>(\n filter: FilterValues<Extract<keyof M, string>>,\n table: PgTable<any>,\n collectionPath: string\n ): SQL[] {\n return DrizzleConditionBuilder.buildFilterConditions(\n filter, table, collectionPath, this.filterContext(collectionPath, table)\n );\n }\n\n // =============================================================\n // DRIZZLE QUERY HELPERS\n // =============================================================\n\n /**\n * Resolves the correct Drizzle column for sorting.\n * Automatically maps owning relation property keys to their underlying foreign key column.\n *\n * The relation's own `localKey` is the authority for that foreign key, not\n * `<field>_id`. The default local key comes from `generateForeignKeyName`,\n * which snake-cases *and singularises* — `userProfile` → `user_profile_id`,\n * `users` → `user_id` — and an author can override it outright. A wrong\n * guess resolves to nothing, the caller drops the `ORDER BY`, and the rows\n * come back in whatever order Postgres pleases: paging over that repeats\n * and skips rows rather than erroring. The guesses stay, last, for a\n * caller that hands over no collection to resolve against.\n */\n /**\n * The ORDER BY target, which may be relevance rather than a column.\n *\n * `_score` is only meaningful for a collection that declared a `search`\n * block *and* for a request that carried a search string — ranking rows\n * against no query ranks them all at zero. Outside those two conditions it\n * is an unknown field and gets the same 400 as any other typo, which is the\n * behaviour that matters: a sort that is silently dropped returns 200 with\n * rows in arbitrary order, and paging over that repeats and skips rows.\n */\n static readonly SCORE_FIELD = \"_score\";\n\n /**\n * The distance a vector search attaches to every row it serves, and to no\n * other. Like `_score` it is computed per query and stored nowhere.\n */\n static readonly DISTANCE_FIELD = \"_distance\";\n\n private resolveOrderTarget(\n table: PgTable<any>,\n orderBy: string,\n collection?: CollectionConfig,\n searchString?: string\n ): AnyPgColumn | SQL | undefined {\n if (orderBy === FetchService.SCORE_FIELD && collection && searchString) {\n const rank = DrizzleConditionBuilder.buildSearchRankExpression(searchString, table, collection);\n if (rank) return rank;\n }\n return this.resolveOrderByField(table, orderBy, collection);\n }\n\n /**\n * The aggregate a sort key names, as an expression, or `undefined` if the\n * key is not one.\n *\n * `cursorId` builds the same expression pinned to the cursor row — see\n * {@link DrizzleConditionBuilder.buildRelationAggregateExpression}.\n *\n * A key that *parses* as an aggregate but names no relation, or a column\n * the target does not have, throws rather than falling through to the\n * column path. Falling through would report `min(applications.created_at)`\n * as an unknown column and list the columns of the wrong table.\n */\n private resolveAggregateOrderTarget(\n table: PgTable<any>,\n orderBy: string,\n collection: CollectionConfig | undefined,\n collectionPath: string | undefined,\n cursorId?: unknown\n ): SQL | undefined {\n const spec = parseRelationAggregateSort(orderBy);\n if (!spec) return undefined;\n if (!collection || !collectionPath) {\n throw ApiError.badRequest(\n `Cannot sort by '${orderBy}': an aggregate sort needs the collection it is written against, ` +\n \"and this query carries none.\",\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: orderBy }\n );\n }\n // Ordering the parents by a relation's count is a reading of it, as\n // `orderBy=salary` is of a column. An unknown relation is left to the\n // builder below, which refuses it naming the ones there are.\n // A relation the API does not have is the builder's to refuse too, as\n // the unknown relation it is for this caller.\n const sorted = namesHiddenFromCaller(collection).has(spec.relation)\n ? undefined\n : findRelation(resolveCollectionRelations(collection), spec.relation);\n if (sorted) assertRelationReadable(collection, spec.relation, sorted, \"`orderBy`\");\n const primaryKeys = getPrimaryKeys(collection, this.registry);\n const idColumn = primaryKeys.length === 1\n ? table[primaryKeys[0].fieldName as keyof typeof table] as AnyPgColumn\n : undefined;\n if (!idColumn) {\n throw ApiError.badRequest(\n `Cannot sort by '${orderBy}' on collection '${collectionPath}': the subquery correlates on a ` +\n \"single key column, and this collection has none or has a composite one.\",\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: orderBy, collection: collectionPath }\n );\n }\n return DrizzleConditionBuilder.buildRelationAggregateExpression(\n spec, table, collection, this.registry, idColumn, collectionPath, cursorId\n );\n }\n\n /**\n * Resolve every sort key to the expression it orders by, in order of\n * significance.\n *\n * A key that resolves to nothing is dropped rather than skipping the rest:\n * `resolveOrderByField` only *returns* undefined under the lenient\n * unknown-field mode, where dropping is the configured answer, and dropping\n * one key of several still honours the ones that did resolve.\n */\n private resolveOrderKeys(\n table: PgTable<any>,\n keys: OrderByTuple[],\n collection?: CollectionConfig,\n searchString?: string,\n collectionPath?: string,\n cursorId?: unknown\n ): ResolvedOrderKey[] {\n const resolved: ResolvedOrderKey[] = [];\n for (const [field, direction, nulls] of keys) {\n // Checked before the column path: an aggregate key is not a column\n // name and would otherwise be reported as a typo'd one.\n const aggregate = this.resolveAggregateOrderTarget(table, field, collection, collectionPath);\n if (aggregate) {\n resolved.push({\n field,\n direction,\n nulls,\n target: aggregate,\n // Built only when a cursor is in play: it is a second\n // subquery, and a listing with no `startAfter` has nothing\n // to compare against.\n ...(cursorId !== undefined && {\n cursorTarget: this.resolveAggregateOrderTarget(\n table, field, collection, collectionPath, cursorId\n )\n })\n });\n continue;\n }\n const target = this.resolveOrderTarget(table, field, collection, searchString);\n if (target) resolved.push({ field,\ndirection,\nnulls,\ntarget });\n }\n return resolved;\n }\n\n /**\n * Whether this key's NULLs sort *after* its real values.\n *\n * The default is Postgres's own — `NULLS LAST` ascending, `NULLS FIRST`\n * descending — and it was previously hardcoded in two places that had to\n * agree: the `ORDER BY` and the keyset comparison behind cursor paging. A\n * key that states a placement overrides it, in both, because they read the\n * answer from here.\n *\n * The default is also the thing a \"newest first\" list gets wrong: every row\n * with no date sorts to the very top, ahead of everything real, and the only\n * way out used to be an `is-not-null` filter that dropped those rows\n * entirely. `orderBy: [[\"published_at\", \"desc\", \"last\"]]` is the fix.\n */\n private static nullsLast(key: Pick<ResolvedOrderKey, \"direction\" | \"nulls\">): boolean {\n if (key.nulls) return key.nulls === \"last\";\n return key.direction === \"asc\";\n }\n\n /**\n * The full `ORDER BY`: the caller's keys, then the primary key.\n *\n * The key is always last and always descending, every column of it. It is\n * not decoration — it is what makes the ordering *total*, and a cursor over\n * a non-total order repeats and skips rows among the ties. Every keyset\n * comparison built by {@link buildCursorConditions} ends on the same key\n * `DESC`, and the two have to agree: they did not, and an ascending sort\n * paged with `id >` against an `ORDER BY … , id DESC`, so rows sharing a\n * sort value were dropped from every page after the first. Nor is the\n * first column of a composite key enough: it ties every row that shares\n * it, which is exactly the rows a cursor then skipped.\n *\n * Where the NULLs go is written out rather than inherited. Postgres already\n * defaults to `NULLS LAST` ascending and `NULLS FIRST` descending, so this\n * changes no query — but {@link buildKeysetComparison} encodes that exact\n * placement, and an invariant two functions depend on should be stated in\n * both rather than assumed in one. It matters most for the keys that are\n * *always* nullable: an aggregate over a relation is NULL for every row the\n * relation reaches nothing from, which is precisely the \"nobody waiting\"\n * end of a queue.\n */\n private buildOrderExpressions(\n keys: ResolvedOrderKey[],\n /**\n * The primary key columns, appended as the final tie-breaker.\n *\n * Empty for a `SELECT DISTINCT`, which does not select the key: Postgres\n * requires every ORDER BY expression of a distinct read to be in the\n * select list and answers a bare `42P10` otherwise. There is also\n * nothing for it to do there — a distinct read returns a set of values,\n * not rows, so there are no ties between rows to break.\n */\n keyColumns: AnyPgColumn[]\n ): SQL[] {\n // Four literal branches rather than a nested `sql` fragment for the\n // placement: a nested fragment renders as a child SQL node, which is\n // correct in the statement and invisible to anything reading the\n // expression's own chunks — including the test that asserts a\n // descending sort really does say `NULLS LAST` when it was asked to.\n const expressions = keys.map((key) => {\n const nullsLast = FetchService.nullsLast(key);\n if (key.direction === \"asc\") {\n return nullsLast ? sql`${key.target} ASC NULLS LAST` : sql`${key.target} ASC NULLS FIRST`;\n }\n return nullsLast ? sql`${key.target} DESC NULLS LAST` : sql`${key.target} DESC NULLS FIRST`;\n });\n for (const column of keyColumns) expressions.push(desc(column));\n return expressions as SQL[];\n }\n\n private resolveOrderByField(\n table: PgTable<any>,\n orderBy: string,\n collection?: CollectionConfig\n ): AnyPgColumn | undefined {\n const columnAt = (key: string): AnyPgColumn | undefined =>\n (key in table ? table[key as keyof typeof table] as AnyPgColumn : undefined) || undefined;\n\n const direct = columnAt(orderBy);\n if (direct) return direct;\n\n // Owning relation, resolved: the relation names its own local key.\n const declaredRelation = collection ? resolveCollectionRelations(collection)[orderBy] : undefined;\n if (declaredRelation?.kind === \"belongsTo\") {\n // `localKey` is the column; the table is keyed by the wire name.\n const foreignKey = columnAt(fieldKeyForColumn(collection, declaredRelation.localKey));\n if (foreignKey) return foreignKey;\n }\n\n // No collection in hand — the shapes an owning relation's key takes by\n // default (e.g. `project` → `projectId`, `userProfile` →\n // `userProfileId`), then the snake forms for a project that authored the\n // property under its column name.\n for (const guess of [\n `${orderBy}Id`,\n toWireKey(generateForeignKeyName(orderBy)),\n `${orderBy}_id`,\n generateForeignKeyName(orderBy)\n ]) {\n const foreignKey = columnAt(guess);\n if (foreignKey) return foreignKey;\n }\n\n // Nothing resolved. Returning `undefined` is exactly what the docblock\n // above describes: the caller drops the ORDER BY and hands back rows in\n // whatever order Postgres pleases, while the requester believes they\n // are sorted. `?orderBy=titel` answered 200 with unsorted data and no\n // hint that the sort had been ignored.\n //\n // A *filter* naming a field that does not exist is already refused for\n // precisely this reason — it \"used to widen results silently\". An\n // unresolvable sort field is the same drift between a query and the\n // schema, so it answers the same way and honours the same switch: one\n // knob, because a deployment that wants the lenient behaviour wants it\n // for both.\n const collectionName = collection?.slug ?? collection?.name;\n const onCollection = collectionName ? ` on collection '${collectionName}'` : \"\";\n\n // A declared to-many relation is a different mistake from a typo, and\n // saying \"unknown field\" about a field the collection plainly declares\n // sends the reader looking for a spelling error that is not there.\n // There is simply no single value per row to order by — `posts.tags` is\n // a set — so no ORDER BY exists to write, with or without a typo.\n if (declaredRelation && declaredRelation.kind !== \"belongsTo\") {\n throw ApiError.badRequest(\n `Cannot sort by '${orderBy}'${onCollection}: it is a to-many relation ` +\n `(${declaredRelation.kind}), which has no single value per row to order by.`,\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: orderBy, kind: declaredRelation.kind, ...(collectionName && { collection: collectionName }) }\n );\n }\n\n if (getUnknownFilterFieldsMode() === \"warn\") {\n logger.warn(\n `Sorting by field '${orderBy}'${onCollection}, but it does not exist in the table — ` +\n \"the ORDER BY was dropped and these rows are unsorted.\"\n );\n return undefined;\n }\n\n // The REST check answers an `excludeFromApi` sort key with the same\n // function, so a hidden field and a typo cannot be told apart.\n const answer = unknownOrderByField(orderBy, collectionName, listedColumnKeys(table, collection));\n throw ApiError.badRequest(answer.message, \"UNKNOWN_ORDER_BY_FIELD\", answer.details);\n }\n\n // =============================================================\n // THE INCLUDE PIPELINE\n //\n // One implementation, reached by every read: the REST list, the REST\n // get-by-id, the in-process accessor and the realtime refetch. It used to\n // be four. `db.query.findMany({ with })` served REST — the lateral-join\n // path this file's own comments call \"catastrophically slow… 7s+ for 350\n // rows\" — while the admin fetch batched through `RelationService` and the\n // realtime refetch loaded EVERY relation because it passed no `include` at\n // all. So `find()` and `listen()` answered the same query with different\n // rows, and the fast path was the one nobody could reach.\n //\n // What runs now is the batched one, for all of them: one query per relation\n // per level, never per row.\n // =============================================================\n\n /**\n * The relations one level of an include tree names, resolved.\n *\n * A name that is not a relation is a **400 `UNKNOWN_RELATION`**. It used to\n * be dropped silently, which answers 200 with the field missing — and a\n * missing relation field is indistinguishable from a row that genuinely has\n * no related row, so a typo in an `include` looked like empty data.\n */\n private resolveIncludeLevel(\n collection: CollectionConfig,\n tree: Record<string, IncludeNode>,\n wildcard: boolean\n ): { key: string; node: IncludeNode; relation: ResolvedRelation }[] {\n const resolvedRelations = resolveCollectionRelations(collection);\n const out: { key: string; node: IncludeNode; relation: ResolvedRelation }[] = [];\n\n if (wildcard) {\n // `*` is \"every relation, one hop\", tab-only relations included.\n // The admin panel names the relations its properties render and\n // sends `*` only when it cannot resolve them in the browser. A\n // relation also named explicitly keeps its options.\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n out.push({ key, node: tree[key] ?? { children: {} }, relation });\n }\n }\n\n for (const [key, node] of Object.entries(tree)) {\n if (wildcard && key in resolvedRelations) continue;\n const relation = resolvedRelations[key];\n if (!relation) {\n // The REST check answers an include of an `excludeFromApi`\n // relation with the same function, and neither lists one.\n const answer = unknownIncludeRelation(\n key, collection.slug ?? collection.name, includableRelationNames(collection)\n );\n throw ApiError.badRequest(answer.message, \"UNKNOWN_RELATION\", answer.details);\n }\n out.push({ key, node, relation });\n }\n\n return out;\n }\n\n /**\n * Resolve every level of an include tree against the collections, without\n * loading anything — the 400 for an unknown relation, decided before the\n * read rather than by it.\n *\n * {@link loadIncludes} resolves a level only once it has rows to load it\n * onto. On its own that made the refusal depend on the data: a typo in\n * `include` against an empty table, under a filter that matched nothing,\n * or below a relation that loaded no rows answered 200, and the same\n * request became a 400 the first time a row matched.\n */\n private assertIncludeResolvable(\n collection: CollectionConfig,\n include: NormalizedInclude,\n depth = 1\n ): void {\n if (depth > MAX_INCLUDE_DEPTH) return;\n const levels = this.resolveIncludeLevel(collection, include.tree, include.wildcard);\n this.assertCountsAllowed(collection, levels);\n for (const { node, relation } of levels) {\n if (Object.keys(node.children).length === 0) continue;\n this.assertIncludeResolvable(\n relation.target(), { wildcard: false, tree: node.children }, depth + 1\n );\n }\n }\n\n /**\n * The extra `WHERE` an include's own `where`/`logical` puts on the target.\n *\n * Pushed into the batch query rather than applied to the rows it returns:\n * filtering afterwards reads every related row of every parent to throw\n * most of them away, and on a to-many relation that is the whole table.\n */\n private includeNarrowing(node: IncludeNode, targetCollection: CollectionConfig): SQL | undefined {\n if (!node.where && !node.logical) return undefined;\n const targetPath = targetCollection.slug;\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const conditions: SQL[] = [];\n if (node.where) {\n conditions.push(...this.buildFilterConditions(node.where, targetTable, targetPath));\n }\n if (node.logical) {\n const logical = DrizzleConditionBuilder.buildLogicalConditions(\n node.logical, targetTable, targetPath, this.filterContext(targetPath, targetTable)\n );\n if (logical) conditions.push(logical);\n }\n return conditions.length > 0\n ? DrizzleConditionBuilder.combineConditionsWithAnd(conditions)\n : undefined;\n }\n\n /**\n * Refuse a `count` the loader cannot serve honestly, before anything runs.\n *\n * - On a to-one relation: there is nothing to count past a row or none,\n * and answering 0 or 1 under `_count` would invent a shape for it.\n * - On a collection with a property named `_count`: the total would be\n * written over that column's value. A silent overwrite answers 200 with\n * the column gone, so it is a 400 naming why.\n */\n private assertCountsAllowed(\n collection: CollectionConfig,\n levels: { key: string; node: IncludeNode; relation: ResolvedRelation }[]\n ): void {\n const counted = levels.filter(({ node }) => node.count);\n if (counted.length === 0) return;\n const collectionName = collection.slug ?? collection.name;\n for (const { key, relation } of counted) {\n if (relation.cardinality === \"one\") {\n throw ApiError.badRequest(\n `Cannot count relation '${key}' on collection '${collectionName}': ` +\n \"it is a to-one relation. `count` totals the rows of a to-many relation.\",\n \"INVALID_INCLUDE\",\n { relation: key, collection: collectionName }\n );\n }\n }\n if (Object.prototype.hasOwnProperty.call(collection.properties ?? {}, INCLUDE_COUNT_KEY)) {\n throw ApiError.badRequest(\n `Cannot serve \\`count\\` on collection '${collectionName}': it declares a property named ` +\n `'${INCLUDE_COUNT_KEY}', which is where the totals would go.`,\n \"INVALID_INCLUDE\",\n { collection: collectionName, field: INCLUDE_COUNT_KEY }\n );\n }\n }\n\n /**\n * Cap and project the rows of ONE relation, per parent.\n *\n * The order and the limit are Postgres's: the batch loader ranks each\n * parent's related rows by the include's `orderBy` and reads back only the\n * ones within its `limit` (see\n * {@link RelationService.batchFetchRelatedEntitiesMany}), so `rows`\n * arrives sorted and already cut. The slice here is what holds the limit\n * where a `via` chain reaches one target along two paths — those rows share\n * a rank — and it costs nothing where nothing is left to cut.\n */\n private shapeRelatedRows(\n rows: Record<string, unknown>[],\n node: IncludeNode,\n targetCollection: CollectionConfig\n ): Record<string, unknown>[] {\n let out = rows;\n if (node.limit !== undefined && out.length > node.limit) out = out.slice(0, node.limit);\n if (node.fields && node.fields.length > 0) {\n const keep = new Set(node.fields);\n // The key always survives: a related row nobody can address cannot\n // be followed, updated or deduplicated by the caller.\n for (const pk of getPrimaryKeys(targetCollection, this.registry)) keep.add(pk.fieldName);\n // So does the link's own data. `fields` narrows the TARGET's\n // columns — it is keyed off the target's collection and validated\n // against it — and `_pivot` is not one of them: dropping it would\n // make `include=members&fields=id` silently lose the `role` that is\n // the reason the relation carries a payload at all.\n keep.add(JUNCTION_PIVOT_KEY);\n out = out.map(row => {\n const projected: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row)) {\n if (keep.has(key)) projected[key] = value;\n }\n return projected;\n });\n }\n return out;\n }\n\n /**\n * Load an include tree onto `rows`, in place, one batched query per\n * relation per level.\n *\n * Recurses into nested includes with the rows it just loaded, so\n * `comments.author` is two queries for a page of posts rather than one per\n * comment. The depth bound lives on the normalizer\n * ({@link MAX_INCLUDE_DEPTH}); this asserts it again because the tree can\n * also be built in-process, where nothing normalized it.\n */\n private async loadIncludes(\n rows: Record<string, unknown>[],\n collection: CollectionConfig,\n collectionPath: string,\n include: NormalizedInclude,\n depth = 1\n ): Promise<void> {\n if (depth === 1) this.assertIncludeResolvable(collection, include);\n if (rows.length === 0 || depth > MAX_INCLUDE_DEPTH) return;\n\n const idInfoArray = getPrimaryKeys(collection, this.registry);\n if (idInfoArray.length === 0) return;\n\n // The address the batch groups its results by. Both sides derive it the\n // same way, so they agree by construction rather than by both happening\n // to pick column zero.\n const addressOf = (row: Record<string, unknown>): string | undefined => {\n const address = buildCompositeId(row, idInfoArray);\n return address && address.split(COMPOSITE_ID_SEPARATOR).some(part => part !== \"\")\n ? address\n : undefined;\n };\n const addressable = rows.filter(row => addressOf(row) !== undefined);\n if (addressable.length === 0) return;\n const rowIds = addressable.map(row => addressOf(row) as string);\n\n const requested = this.resolveIncludeLevel(collection, include.tree, include.wildcard);\n this.assertCountsAllowed(collection, requested);\n\n // A relation property this caller may not read is withheld whole: not\n // loaded, so neither embedded nor counted — absent from the row, as the\n // strip leaves any withheld field. Decided here, the one loader every\n // door reaches (REST, `POST …/query`, the in-process SDK, socket fetches\n // and subscriptions), and at every level of a nested include. The REST\n // rows were stripped before this runs, so the strip cannot catch it.\n const levels = requested.filter(({ key, relation }) => canReadRelation(collection, key, relation));\n\n for (const { key, node, relation } of levels) {\n const targetCollection = relation.target();\n // Deliberately unguarded: a `where` inside an include that names a\n // column the target does not have is the caller's mistake, and it\n // is already an ApiError. Anything else is not this loop's to\n // swallow either, so both travel to the caller untouched.\n const narrow: SQL | undefined = this.includeNarrowing(node, targetCollection);\n\n // Rows loaded at this level, to recurse into. Collected as the\n // objects actually attached to the parents, so a nested include\n // mutates what the caller will see rather than a copy of it.\n const loaded: Record<string, unknown>[] = [];\n\n try {\n if (relation.cardinality === \"one\") {\n // Rendered by the loader exactly as the target's own route\n // renders it (`style: \"rest\"`): a target left in the walk's\n // own types is how a date came back as a string at the top\n // level and a `{ __type: \"date\" }` envelope one level down,\n // in one response — and how the target's own to-one\n // relations arrived as `{ __type: \"relation\" }` refs.\n // The rows go along so a `belongsTo` takes each foreign\n // key from the row rather than selecting it again.\n const results = await this.relationService.batchFetchRelatedEntities(\n collectionPath, rowIds, key, relation, narrow, { style: \"rest\", parentRows: addressable }\n );\n for (const row of addressable) {\n const related = results.get(String(addressOf(row)));\n // `null`, not \"absent\": a to-one relation that reaches\n // nothing is a fact about the row, and leaving the key\n // off makes it indistinguishable from not having asked.\n if (!related) { row[key] = null; continue; }\n const [shaped] = this.shapeRelatedRows([related.values], node, targetCollection);\n row[key] = shaped;\n loaded.push(shaped);\n }\n } else {\n // The include's own order and limit, which the loader\n // applies per parent in SQL — and, for `count`, the totals\n // the same statement counts before the limit.\n const totals = node.count ? new Map<string, number>() : undefined;\n const window = node.limit !== undefined || (node.orderBy && node.orderBy.length > 0) || totals\n ? { limit: node.limit, orderBy: node.orderBy, totals }\n : undefined;\n const results = await this.relationService.batchFetchRelatedEntitiesMany(\n collectionPath, rowIds, key, relation, narrow, window, { style: \"rest\" }\n );\n for (const row of addressable) {\n const address = String(addressOf(row));\n const related = results.get(address) ?? [];\n if (totals) {\n // Without a limit nothing was cut: the total is\n // the related rows themselves, one per target.\n const total = node.limit !== undefined\n ? totals.get(address) ?? 0\n : new Set(related.map(e => String(e.id))).size;\n const counts = (row[INCLUDE_COUNT_KEY] ??= {}) as Record<string, number>;\n counts[key] = total;\n }\n const shaped = this.shapeRelatedRows(\n // The junction's own columns ride along under\n // `_pivot` — the link's data, not the target's, so\n // it is nested rather than merged: two posts sharing\n // a tag see one tag row and two different links, and\n // a flattened `role` would collide with a column of\n // the target's own. It is not part of the target's\n // REST rendering for the same reason: the target's\n // collection does not describe the link's columns.\n related.map(e => (e.pivot ? { ...e.values, [JUNCTION_PIVOT_KEY]: e.pivot } : e.values)),\n node, targetCollection\n );\n row[key] = shaped;\n loaded.push(...shaped);\n }\n }\n } catch (e) {\n // An `include` that failed is not an `include` that matched\n // nothing. A Postgres error also poisons the surrounding\n // transaction, so swallowing one loses every later relation in\n // the same request too — one failure, several missing fields.\n if (e instanceof ApiError) throw e;\n if (reachedDatabase(e)) throw e;\n logger.warn(`[include] Failed to load relation '${key}' on ${collectionPath}`, { error: e });\n continue;\n }\n\n const children = Object.keys(node.children).length > 0\n ? { wildcard: false, tree: node.children }\n : undefined;\n if (children && loaded.length > 0) {\n await this.loadIncludes(\n loaded, targetCollection, targetCollection.slug, children, depth + 1\n );\n }\n }\n }\n\n /**\n * The SELECT list for a read: the visible columns, narrowed to `fields`.\n *\n * `?fields=` used to be a trim of the *response* — every column read out of\n * the database and most of them thrown away in JavaScript. It is a\n * projection now, so asking for two columns of a wide row reads two\n * columns; that is also what makes `distinct` mean anything.\n *\n * Two things survive a narrowing regardless:\n *\n * - the **primary key**, because a row nobody can address cannot be\n * updated, deleted, or paged past — and `meta.nextCursor` is derived from\n * it, so a projection without it would silently disable seeking. The one\n * exception is `distinct`, below;\n * - the exclusions. `excludeFromApi` and the generated search columns are\n * removed *after* the narrowing, so naming one in `fields` does not\n * un-hide it. That was the shape of the `?searchString=` leak: a path\n * that skipped the projection returned a password hash the plain read\n * correctly withheld.\n *\n * An unknown column name is a 400 rather than a silent omission: a caller\n * who mistypes `?fields=titel` otherwise gets rows without titles and no\n * hint why.\n *\n * **`distinct` drops the primary key.** Keeping it is what the refusal of\n * `distinct` beside a search or vector query already describes: a value\n * that differs on every row makes the whole row distinct by construction,\n * so the query \"would answer 200 having done nothing\". A surrogate key does\n * that more reliably than any score — `?fields=status&distinct=true` came\n * back with one row per row, every one carrying its `id`. So a distinct\n * read is a read of the named columns and nothing else. It addresses no\n * rows, which is the honest shape for one: `cursorFor` finds no key on the\n * row and issues no cursor, and the result is a set of values rather than a\n * set of rows to update or delete.\n */\n private columnProjection(\n table: PgTable<any>,\n collection: CollectionConfig,\n fields: string[] | undefined,\n idInfoArray: { fieldName: string; type: \"string\" | \"number\" }[],\n distinct?: boolean\n ): Record<string, unknown> | undefined {\n const visible = visibleColumnProjection(getTableColumns(table), collection);\n if (!fields || fields.length === 0) return visible;\n\n let tableColumns: Record<string, unknown>;\n try {\n tableColumns = getTableColumns(table) as Record<string, unknown>;\n } catch {\n // Not a real drizzle table — a stub in a test, a derived path. No\n // projection is the right answer, as it is for the exclusions.\n return visible;\n }\n const available = visible ?? tableColumns;\n\n const keep = new Set(fields);\n if (!distinct) for (const pk of idInfoArray) keep.add(pk.fieldName);\n const relations = resolveCollectionRelations(collection);\n\n const projection: Record<string, unknown> = {};\n for (const name of keep) {\n const column = available[name];\n if (column !== undefined) { projection[name] = column; continue; }\n // Present on the table but not in `available` means it was excluded\n // — hidden, and staying hidden. Absent from both is a typo.\n if (tableColumns[name] !== undefined) continue;\n // A relation is not a column: `include` loads it, and naming it in\n // `fields` too is how a caller says to return it. Read as a typo,\n // `fields=title,author&include=author` was a 400 on every door.\n if (name in relations) continue;\n // The REST check answers an `excludeFromApi` field with the same\n // function, so a hidden field and a typo cannot be told apart.\n const answer = unknownProjectionField(\n name, collection.slug ?? collection.name, queryableFieldNames(collection)\n );\n throw ApiError.badRequest(answer.message, \"UNKNOWN_FIELD\", answer.details);\n }\n return projection;\n }\n\n /**\n * The opaque cursor that continues a listing after `row`. See\n * {@link RestFetchService.cursorFor}.\n *\n * Here rather than at the route because deriving it needs the collection's\n * primary key, which may be named anything and span several columns — the\n * driver's knowledge, not the HTTP layer's.\n */\n cursorFor(\n collectionPath: string,\n row: Record<string, unknown>,\n orderBy?: OrderByTuple[]\n ): string | undefined {\n // Relevance is computed per query rather than stored, so there is no\n // value on this row to compare a later page against — and two requests\n // with different search strings produce scores that are not on the same\n // scale at all. `buildCursorConditions` refuses such a cursor; issuing\n // one here would be handing out a string whose only use is a 400.\n if (orderBy?.some(([field]) => field === FetchService.SCORE_FIELD)) return undefined;\n // A vector read orders by distance whatever `orderBy` says, so the same\n // holds for its rows — which are the ones carrying a distance.\n if (FetchService.DISTANCE_FIELD in row) return undefined;\n try {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const pks = getPrimaryKeys(collection, this.registry);\n if (pks.length === 0) return undefined;\n const address = buildCompositeId(row, pks);\n if (!address || !address.split(COMPOSITE_ID_SEPARATOR).some(part => part !== \"\")) return undefined;\n // A single key's own value, so it compares against the column as\n // it came; a composite key's whole address, because the keyset\n // comparison ends on every key column and the first alone ties\n // every row that shares it.\n return encodeCursor(orderBy, row, pks.length === 1 ? row[pks[0].fieldName] : address);\n } catch {\n // A path with no registered collection — a nested or derived one.\n // No cursor is the honest answer; the listing pages by offset.\n return undefined;\n }\n }\n\n /**\n * Every relation of one row, as the refs the admin renders, attached in\n * place.\n *\n * Loaded by the batched loaders an `include` takes, so a target reached from\n * this row is read as the caller may see it: its `beforeQuery` scope and its\n * soft delete applied, its unreadable fields stripped. Populated through\n * drizzle's relational `with`, the single get knew neither, and a\n * tenant-scoped user fetching an owner was handed another tenant's docs —\n * and the trashed ones — under a relation `?include=` answered correctly.\n *\n * A to-many relation that reaches nothing is `[]`; a to-one is left off. A\n * relation that fails for a reason other than the database is logged and\n * left off, as on the include path, rather than failing the whole row.\n */\n private async loadRelationRefs(\n row: Record<string, unknown>,\n address: string,\n collection: CollectionConfig,\n collectionPath: string,\n /** The row as read, so a `belongsTo` takes its foreign key from it. */\n sourceRow: Record<string, unknown>\n ): Promise<void> {\n if (!address) return;\n const toRef = (related: { id: string | number; path: string; values: Record<string, unknown> }) =>\n createRelationRefWithData(related.id, related.path, {\n id: related.id,\n path: related.path,\n values: related.values\n });\n\n for (const [key, relation] of Object.entries(resolveCollectionRelations(collection))) {\n try {\n if (relation.cardinality === \"one\") {\n const related = (await this.relationService.batchFetchRelatedEntities(\n collectionPath, [address], key, relation, undefined, { parentRows: [sourceRow] }\n )).get(address);\n if (related) row[key] = toRef(related);\n } else {\n const related = (await this.relationService.batchFetchRelatedEntitiesMany(\n collectionPath, [address], key, relation\n )).get(address) ?? [];\n row[key] = related.map(toRef);\n }\n } catch (e) {\n if (e instanceof ApiError) throw e;\n if (reachedDatabase(e)) throw e;\n logger.warn(`[FetchService] Could not load relation '${key}' on ${collectionPath}`, { error: e });\n }\n }\n }\n\n /**\n * Extract cursor pagination conditions from startAfter options.\n *\n * \"Every row that sorts after this one\", written out as a comparison over\n * the same keys the `ORDER BY` uses and ending on the same primary key,\n * descending. With one sort key that is the familiar\n * `k > v OR (k = v AND id < cursorId)`; with several it nests, each key's\n * tie handing the decision to the next.\n */\n private buildCursorConditions(\n table: PgTable<any>,\n idInfoArray: PrimaryKeyInfo[],\n options: { orderBy?: string | OrderByTuple[]; order?: \"desc\" | \"asc\"; startAfter?: Record<string, unknown> },\n collectionPath: string\n ): SQL[] {\n if (!options.startAfter) return [];\n const cursor = options.startAfter;\n const keys = normalizeDriverOrderBy(options.orderBy, options.order);\n const keyColumns = FetchService.keyColumns(table, idInfoArray, collectionPath);\n const cursorKey = FetchService.cursorKey(cursor, idInfoArray, collectionPath);\n\n if (keys) {\n // Relevance is computed per query, not stored, so there is no value\n // on the cursor row to compare a later page against — and two\n // requests with different search strings would produce scores that\n // are not on the same scale at all. Refusing is the only honest\n // answer: a dropped cursor condition silently repeats and skips\n // rows, which is precisely what paging exists to prevent.\n if (keys.some(([field]) => field === FetchService.SCORE_FIELD)) {\n throw ApiError.badRequest(\n \"Cursor pagination (`startAfter`) cannot be combined with `orderBy: \\\"_score\\\"`. \" +\n \"Relevance is computed per query rather than stored, so it cannot key a cursor. \" +\n \"Use `limit`/`offset` for relevance-ordered pages, or order by a column.\",\n \"SCORE_CURSOR_UNSUPPORTED\",\n { field: FetchService.SCORE_FIELD }\n );\n }\n const collection = getCollectionByPath(collectionPath, this.registry);\n const resolved = this.resolveOrderKeys(\n table, keys, collection, undefined, collectionPath,\n // An aggregate key correlates on a single key column — a\n // composite-key collection is refused one — so only a single\n // key's value can pin its subquery to the cursor row.\n cursorKey?.length === 1 ? cursorKey[0] : undefined\n );\n\n if (resolved.length > 0 && cursorKey) {\n const cursorValues = cursor.values as Record<string, unknown> | undefined;\n // `in`, not `??`: a cursor row whose sort value is genuinely\n // NULL is a row this has to be able to page past, and `??`\n // read it as \"the cursor did not carry this key\" and dropped\n // the whole condition.\n const values = resolved.map(({ field, cursorTarget }) => cursorTarget\n // An aggregate is not stored on the row, so the cursor\n // never carried it and never could. Its value is recomputed\n // from the cursor id instead, in SQL, by `cursorTarget` —\n // there is nothing for this list to supply.\n ? null\n : (cursorValues && field in cursorValues) ? cursorValues[field] : cursor[field]);\n // Every key needs a value from the cursor row. A missing one\n // cannot be guessed, and a comparison built from the keys that\n // happen to be present is not the same comparison — so this\n // falls through to no cursor condition, which is what a single\n // missing sort value has always done here.\n if (values.every((value, i) => resolved[i].cursorTarget || value !== undefined)) {\n const seekValues = values.map((value, i) =>\n this.preciseCursorValue(table, resolved[i].target, value, keyColumns, cursorKey));\n return [this.buildKeysetComparison(resolved, seekValues, keyColumns, cursorKey)];\n }\n }\n } else if (cursorKey) {\n return [FetchService.keyAfter(keyColumns, cursorKey)];\n }\n\n return [];\n }\n\n /** The table's primary key columns, in key order. */\n private static keyColumns(table: PgTable<any>, idInfoArray: PrimaryKeyInfo[], collectionPath: string): AnyPgColumn[] {\n return idInfoArray.map(info => {\n const column = table[info.fieldName as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n throw new Error(`ID field '${info.fieldName}' not found in table for collection '${collectionPath}'`);\n }\n return column;\n });\n }\n\n /**\n * The cursor row's key, one value per key column — or `undefined` for a\n * cursor that names no row.\n *\n * A cursor carries the row's address, which for a composite key is every\n * part joined (see {@link cursorFor}). One that carries fewer parts than\n * the key has columns — the first column's value alone, say — cannot say\n * which row it stopped at, and is refused rather than read as a guess.\n */\n private static cursorKey(\n cursor: Record<string, unknown>,\n idInfoArray: PrimaryKeyInfo[],\n collectionPath: string\n ): unknown[] | undefined {\n const address = cursor.id;\n if (idInfoArray.length === 1) {\n const value = address ?? cursor[idInfoArray[0].fieldName];\n return value === undefined || value === null ? undefined : [value];\n }\n if (address === undefined || address === null) {\n const parts = idInfoArray.map(info => cursor[info.fieldName]);\n return parts.some(part => part === undefined || part === null) ? undefined : parts;\n }\n let parsed: Record<string, string | number>;\n try {\n parsed = parseIdValues(String(address), idInfoArray);\n } catch {\n throw ApiError.badRequest(\n `Invalid cursor: its row id does not name every key column of '${collectionPath}' ` +\n `(${idInfoArray.map(info => info.fieldName).join(\", \")}). Pass back the \\`nextCursor\\` ` +\n \"from the previous page unchanged — it is opaque and must not be built by hand.\",\n \"INVALID_CURSOR\"\n );\n }\n return idInfoArray.map(info => parsed[info.fieldName]);\n }\n\n /**\n * \"Sorts after the cursor row\" on the key alone: the key descending, as the\n * `ORDER BY` ends.\n *\n * Every column runs the same direction, so for a composite key the\n * row-value comparison is exactly the lexicographic order the `ORDER BY`\n * produces.\n */\n private static keyAfter(keyColumns: AnyPgColumn[], cursorKey: unknown[]): SQL {\n if (keyColumns.length === 1) return lt(keyColumns[0], cursorKey[0]);\n const values = keyColumns.map((column, i) => sql.param(cursorKey[i], column));\n return sql`(${sql.join(keyColumns, sql`, `)}) < (${sql.join(values, sql`, `)})`;\n }\n\n /**\n * The cursor row's value for a timestamp key, to the microsecond.\n *\n * A cursor is built from the row as served, and a timestamp is served to\n * the millisecond while Postgres stores it to the microsecond. Seeking past\n * the served value compares the column against something up to 999µs short\n * of the cursor row's own, so every row in that sliver lands on the wrong\n * side of it: paging `createdAt desc` over rows written in one transaction\n * stopped after the first page, and ascending repeated the cursor row\n * forever.\n *\n * So the exact value is read back from the cursor row by its key — but only\n * while that row still holds the millisecond the cursor carries. A row\n * deleted or re-stamped since the page was served falls back to the stored\n * value, which is where the listing was when the cursor was issued.\n *\n * Any other key, and a NULL, is returned as it came.\n */\n private preciseCursorValue(\n table: PgTable<any>,\n target: AnyPgColumn | SQL,\n value: unknown,\n keyColumns: AnyPgColumn[],\n cursorKey: unknown[]\n ): unknown {\n if (value === null || value === undefined) return value;\n if (!is(target, PgTimestamp) && !is(target, PgTimestampString)) return value;\n // A column that keeps no more than milliseconds is served whole.\n if (target.precision !== undefined && target.precision <= 3) return value;\n\n const stored = sql`CAST(${value instanceof Date ? value.toISOString() : String(value)} AS ${sql.raw(target.getSQLType())})`;\n const alias = sql.identifier(\"__cursor_row\");\n const column = sql`${alias}.${sql.identifier(target.name)}`;\n const cursorRow = sql.join(keyColumns.map((key, i) =>\n sql`${alias}.${sql.identifier(key.name)} = ${sql.param(cursorKey[i], key)}`), sql` AND `);\n return sql`COALESCE((SELECT ${column} FROM ${table} AS ${alias} WHERE ${cursorRow} AND ${column} >= ${stored} AND ${column} < ${stored} + INTERVAL '1 millisecond' LIMIT 1), ${stored})`;\n }\n\n /**\n * \"Sorts strictly after the cursor row\", over `keys` and then the primary key.\n *\n * Built by recursion rather than as a row-value comparison — `(a, b) > (x, y)`\n * would be shorter, but it is only correct when every key runs the same\n * direction, and `roles ASC, created_at DESC` is exactly the case this\n * exists to serve.\n *\n * NULLs are compared by the rule Postgres sorts them under (last ascending,\n * first descending) rather than by `>`/`<`, which answer *unknown* against\n * NULL and therefore match nothing. Ordering by a nullable column and paging\n * used to drop every row whose sort value was NULL from page two onward.\n */\n private buildKeysetComparison(\n keys: ResolvedOrderKey[],\n values: unknown[],\n keyColumns: AnyPgColumn[],\n cursorKey: unknown[],\n index = 0\n ): SQL {\n // Past the last key, the primary key settles it. It is ordered `DESC`,\n // so \"after\" the cursor row means a smaller key.\n if (index >= keys.length) return FetchService.keyAfter(keyColumns, cursorKey);\n\n const { direction, cursorTarget } = keys[index];\n // Which end the NULLs sort at — the direction's convention unless the\n // key stated one. Read from the same place `buildOrderExpressions`\n // reads it, because a comparison that disagrees with the ORDER BY it\n // pages over repeats and skips rows and reports nothing wrong.\n const nullsLast = FetchService.nullsLast(keys[index]);\n // A column or an expression. `_score` is an expression too, but a\n // cursor over relevance is refused before this is reached; an aggregate\n // over a relation is the one that gets here. Drizzle's comparison\n // helpers are typed per operand kind, so the union has to be resolved\n // here rather than at the call site.\n const target = keys[index].target as AnyPgColumn;\n const value = values[index];\n const rest = this.buildKeysetComparison(keys, values, keyColumns, cursorKey, index + 1);\n\n // No stored value to compare against — the cursor row's is recomputed\n // by an expression instead, and whether it is NULL is a question only\n // SQL can answer. So both branches of the null test below have to exist\n // in the statement rather than being chosen here.\n //\n // `cursorTarget` references only the cursor id, never the outer row, so\n // Postgres evaluates it once for the whole statement rather than per\n // row — repeating it across the branches costs nothing.\n // \"Sorts after\" for two non-null values is decided by the *direction*;\n // where the NULLs sit is decided by `nullsLast`. The two used to be the\n // same test, which was correct only while the placement was the\n // direction's default and silently wrong the moment a key named one.\n const after = (a: AnyPgColumn | SQL, b: unknown) =>\n direction === \"asc\" ? gt(a as AnyPgColumn, b) : lt(a as AnyPgColumn, b);\n const afterSql = (a: AnyPgColumn | SQL, b: SQL) =>\n direction === \"asc\" ? sql`${a} > ${b}` : sql`${a} < ${b}`;\n\n if (cursorTarget) {\n return nullsLast\n // NULLS LAST. A cursor row among the NULLs has only later NULLs\n // after it; otherwise everything that sorts later, then the\n // NULLs, then the ties.\n ? or(\n and(isNull(cursorTarget), isNull(target), rest),\n and(\n isNotNull(cursorTarget),\n or(\n afterSql(target, cursorTarget),\n isNull(target),\n and(sql`${target} = ${cursorTarget}`, rest)\n )\n )\n )!\n // NULLS FIRST. A cursor row among the NULLs still has every\n // non-null row after it; a non-null cursor row has the NULLs\n // already behind it.\n : or(\n and(isNull(cursorTarget), or(isNotNull(target), and(isNull(target), rest))),\n and(\n isNotNull(cursorTarget),\n or(\n afterSql(target, cursorTarget),\n and(sql`${target} = ${cursorTarget}`, rest)\n )\n )\n )!;\n }\n\n if (value === null) {\n // The cursor row sorts among the NULLs.\n return nullsLast\n // NULLS LAST: nothing non-null is left, so only later NULLs.\n ? and(isNull(target), rest)!\n // NULLS FIRST: every non-null row is still ahead, plus later NULLs.\n : or(isNotNull(target), and(isNull(target), rest))!;\n }\n\n return nullsLast\n // NULLS LAST, so the NULLs are still ahead of a non-null cursor row.\n ? or(after(target, value), isNull(target), and(eq(target, value), rest))!\n // NULLS FIRST, so they are behind it and nothing has to admit them.\n : or(after(target, value), and(eq(target, value), rest))!;\n }\n\n /**\n * Compile \"rows reachable from this parent\" into a `WHERE` condition on the\n * target table, so a nested listing can run as an ordinary collection query.\n */\n private buildRelationScope(hop: NestedPathHop): SQL {\n // The rows of this relation, reached by URL rather than by `include`.\n assertRelationReadable(hop.parentCollection, hop.relationKey, hop.relation, \"a nested path\");\n const parentPks = requirePrimaryKeys(hop.parentCollection, this.registry);\n const parsedParent = parseIdValues(hop.parentId, parentPks);\n // What a single-column link compares against. A link from a composite\n // key has no such value, and `findRelationDefects` refuses one at boot.\n const parsedParentId = parsedParent[parentPks[0].fieldName];\n\n // The parent row by every key column, for the shapes that read it.\n const parent = () => {\n const table = getTableForCollection(hop.parentCollection, this.registry);\n const columns = primaryKeyColumns(table, parentPks, hop.parentCollection.slug);\n return {\n table,\n key: columns.map((column, i) => ({ column, value: parsedParent[parentPks[i].fieldName] }))\n };\n };\n\n const targetTable = getTableForCollection(hop.targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(hop.targetCollection, this.registry);\n const targetIdColumn = targetTable[targetPks[0].fieldName as keyof typeof targetTable] as AnyPgColumn;\n if (!targetIdColumn) {\n throw new Error(`ID field '${targetPks[0].fieldName}' not found in table for collection '${hop.targetCollection.slug}'`);\n }\n\n return DrizzleConditionBuilder.buildRelationScopeCondition(\n hop.relation,\n parent,\n parsedParentId as string | number,\n targetTable,\n targetIdColumn,\n this.registry\n );\n }\n\n /**\n * Whether `id` is actually reachable at `collectionPath`.\n *\n * Trivially true for a root path. For a nested one it is a real question:\n * the path resolves to the target collection, and matching on the primary\n * key alone made the parent segment decorative — `authors/1/posts/43`\n * returned post 43 whoever wrote it, and the REST layer's delete then\n * deleted it. A row that is not under this parent is reported as absent,\n * which is what a caller addressing it through the parent should see.\n */\n private async isAddressableUnder(collectionPath: string, id: string | number): Promise<boolean> {\n if (!isNestedPath(collectionPath)) return true;\n const hop = resolveNestedPath(collectionPath, this.registry);\n if (!hop) return true;\n // \"Is row 7 one of this parent's members\" is the relation's value one\n // row at a time — the same reading the listing below is refused.\n assertRelationReadable(hop.parentCollection, hop.relationKey, hop.relation, \"a nested path\");\n return this.relationService.isRelated(hop, id);\n }\n\n /**\n * Fetch a single row by ID\n */\n async fetchOne<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n databaseId?: string,\n withDeleted?: WithDeleted\n ): Promise<Record<string, unknown> | undefined> {\n if (!await this.isAddressableUnder(collectionPath, id)) return undefined;\n\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n\n // An address the key columns cannot hold names no row — the same answer\n // as a well-formed id nobody has. Asking Postgres instead raises 22P02\n // and aborts the transaction around this read.\n if (!idCanAddressTable(id, table, idInfoArray)) return undefined;\n\n const addressed = rowIdentityCondition(table, idInfoArray, id, collectionPath);\n\n // Part of the identity, so a row the hook excludes is absent here\n // exactly as it is from the listing.\n const narrowing = await this.narrowReadCondition(\n collectionPath, collection, table, \"get\", {}\n );\n const identity = narrowing\n ? and(addressed, narrowing) as SQL\n : addressed;\n\n // Primary path: db.query.findFirst for the row, then its relations\n const tableName = getTableName(table);\n\n const qb = this.getQueryBuilder(tableName);\n if (qb) {\n // `null` until the query answers: `undefined` is its answer for a\n // row that is not there.\n let row: Record<string, unknown> | undefined | null = null;\n try {\n const hidden = hiddenColumnsOption(getTableColumns(table), collection);\n\n row = await qb.findFirst({\n // Soft delete: a stamped row answers 404 like any other\n // absent one, so `findById` and `find` agree about which\n // rows exist.\n where: andSoftDelete(identity, collection, table, withDeleted),\n ...(hidden ? { columns: hidden } : {})\n } as Parameters<NonNullable<typeof qb>[\"findFirst\"]>[0]);\n } catch (e) {\n if (reachedDatabase(e)) throw e;\n logger.warn(`[FetchService] db.query.findFirst failed for ${collectionPath}, falling back to db.select`, { error: e });\n }\n\n if (row === undefined) return undefined;\n if (row !== null) {\n const flatRow = toFlatRow(row, collection, this.registry);\n await this.loadRelationRefs(flatRow, buildCompositeId(row, idInfoArray), collection, collectionPath, row);\n\n // Again, now the relations are on the row: a relation property\n // the caller may not read is withheld like any other field.\n return stripUnreadable(flatRow, collection);\n }\n }\n\n // Fallback: db.select, then the same relation loader\n const visibleOne = visibleColumnProjection(getTableColumns(table), collection);\n const result = await this.db\n .select(visibleOne as never)\n .from(table)\n .where(andSoftDelete(identity, collection, table, withDeleted))\n .limit(1);\n\n if (result.length === 0) return undefined;\n\n const raw = result[0] as M;\n // Without `db`/`registry`: the parse only types the columns and turns an\n // owned foreign key into a ref. Given them, it queried each inverse\n // relation itself, with neither the target's scope nor its soft delete.\n const values = await parseDataFromServer(raw, collection) as Record<string, unknown>;\n await this.loadRelationRefs(values, buildCompositeId(raw, idInfoArray), collection, collectionPath, raw);\n\n // The primary path strips inside `toFlatRow`; this one parses the row\n // itself, so it strips itself. Without it a fetch that fell back served\n // every declared column, `excludeFromApi` ones included.\n stripUnreadable(values, collection);\n\n return {\n ...values,\n id: id.toString()\n };\n }\n\n /**\n * Unified method to fetch rows with optional search functionality\n */\n async fetchRowsWithConditions<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n searchExplain?: boolean;\n databaseId?: string;\n vectorSearch?: VectorSearchParams;\n logical?: LogicalCondition;\n /** Narrow to the rows reachable from a parent through a relation. */\n relatedTo?: NestedPathHop;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n /**\n * Relations to load. **Absent means none** — the same as it means\n * over REST.\n *\n * It used to mean *all of them*, unconditionally, and this is the\n * method the realtime refetch goes through. So a subscription was\n * pushed rows carrying every relation the collection declares while\n * the identical `find()` returned rows carrying none, and a client\n * that rendered both saw the row change shape when a write landed.\n */\n include?: IncludeSpec;\n /** Columns to read — a projection pushed into the SELECT. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n const idInfo = idInfoArray[0];\n const idField = table[idInfo.fieldName as keyof typeof table] as AnyPgColumn;\n\n if (!idField) {\n throw new Error(`ID field '${idInfo.fieldName}' not found in table for collection '${collectionPath}'`);\n }\n\n // ONE read, then relations.\n //\n // This method used to hold a `db.query.findMany` branch and a full\n // second copy of the `db.select` builder below it — the same\n // conditions, ordering, vector and rank handling as\n // `fetchRowsWithConditionsRaw`, written out twice. Two copies of a\n // query builder is two answers to every question asked of it, and they\n // had already drifted: only one of them read `startAfter`, so cursor\n // paging worked on one path and silently did nothing on the other.\n const results = await this.fetchRowsWithConditionsRaw<M>(collectionPath, options);\n\n return this.processRowResults<M>(\n results, collection, collectionPath, idInfo, options.databaseId,\n normalizeInclude(options.include), idInfoArray\n );\n }\n\n /**\n * Fallback path used when db.query is unavailable.\n *\n * The primary path runs the results through `toFlatRow`, which maps\n * relations from what drizzle already nested — no query per row. This one\n * has no nesting to read, so it resolves relations itself, in batches.\n *\n * Process raw database results into flat rows with relations.\n */\n private async processRowResults<M extends Record<string, unknown>>(\n results: Record<string, unknown>[],\n collection: CollectionConfig,\n collectionPath: string,\n idInfo: { fieldName: string; type: \"string\" | \"number\" },\n _databaseId?: string,\n /**\n * The relations to load. **Absent means none.**\n *\n * It was a `skipRelations` boolean defaulting to *load everything*, and\n * that default is what made the realtime refetch return rows of a\n * different shape from the REST list serving the identical query. The\n * parameter now says which relations rather than whether, so the two\n * callers state the same thing and neither can quietly mean \"all\".\n */\n include?: NormalizedInclude,\n _idInfoArray?: { fieldName: string; type: \"string\" | \"number\" }[]\n ): Promise<Record<string, unknown>[]> {\n if (results.length === 0) {\n // An unknown relation is a 400 whether or not a row matched.\n if (include) this.assertIncludeResolvable(collection, include);\n return [];\n }\n\n // First pass: parse all rows WITHOUT per-row relation queries.\n // We deliberately omit db/registry so parseDataFromServer only does type\n // coercion (dates, numbers, FK→relation stubs for owning relations) and\n // does NOT issue individual SQL queries for inverse relations. The\n // include pass below batch-loads what the caller asked for, one query\n // per relation per level, avoiding the N+1 that plagued the old path.\n void idInfo;\n const values = await Promise.all(results.map(async (rawRow: Record<string, unknown>) =>\n await parseDataFromServer(rawRow as M, collection) as Record<string, unknown>));\n\n // Second pass: the relations the caller named, through the same batched\n // loader every other read uses — so a row from here and a row from the\n // REST list carry the same relations, loaded the same way.\n if (include) {\n await this.loadIncludes(values, collection, collectionPath, include);\n }\n\n // Last, once the includes have read the key columns they group by: the\n // fields this caller may not read. The REST list strips in `toRestRow`\n // and the single get in `toFlatRow`; this parse is the third exit from\n // the pipeline and it is what the socket's `FETCH_COLLECTION`, a text\n // search and the MCP tools are served from. Without it every one of\n // them handed a plain user each row's password hash.\n for (const row of values) stripUnreadable(row, collection);\n\n // Columns only — the address is the consumer's to derive.\n return values;\n }\n\n /**\n * Fetch a collection of rows\n */\n async fetchCollection<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /**\n * An `or(...)`/`and(...)` group, applied alongside `filter`.\n *\n * `fetchRowsWithConditions` below has always applied this; it was\n * simply absent from this signature, so the only callers that could\n * pass one were the ones that went around this method. Realtime\n * came through here, which is why a subscription filtered by a\n * logical group was pushed every row in the table.\n */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n searchExplain?: boolean;\n databaseId?: string;\n vectorSearch?: VectorSearchParams;\n /** Relations to load. Absent means none — see `fetchRowsWithConditions`. */\n include?: IncludeSpec;\n /** Columns to read — a projection pushed into the SELECT. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n // A nested path is the target collection narrowed by a relation — the\n // same query, one condition heavier. It used to be a separate builder\n // that honoured `limit` and nothing else.\n //\n // `relatedTo` is always set, to `undefined` for a root path: these\n // options arrive from a socket frame spread whole, and the scope is\n // this method's to derive from the path, never the caller's to send.\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n return this.fetchRowsWithConditions<M>(\n hop ? hop.targetCollection.slug : collectionPath,\n { ...options, relatedTo: hop }\n );\n }\n\n /**\n * Search rows by text\n */\n async searchRows<M extends Record<string, unknown>>(\n collectionPath: string,\n searchString: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /**\n * An `or(...)`/`and(...)` group, applied alongside `filter`.\n *\n * `fetchRowsWithConditions` has always applied one; it was missing\n * from this signature, so a realtime search subscription carrying a\n * group could not pass it on and served every row matching the text\n * that RLS allowed.\n */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n databaseId?: string;\n /** Ask each row which declared search field matched. */\n searchExplain?: boolean;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n return this.fetchRowsWithConditions<M>(collectionPath, {\n ...options,\n searchString\n });\n }\n\n /**\n * Count rows in a collection\n */\n async count<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n logical?: LogicalCondition;\n searchString?: string;\n databaseId?: string;\n /**\n * Only the `threshold` half of a vector search narrows a count: the\n * distance ordering and the `_distance` column change which rows\n * come back first, not how many there are. Omitting it here left\n * `meta.total` counting rows the threshold had excluded, so a\n * request that was served three rows was told there were nine.\n */\n vectorSearch?: VectorSearchParams;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n } = {}\n ): Promise<number> {\n // Same narrowing as the listing — and, unlike the count it replaces,\n // the same `filter` and `searchString` too, so `total` describes the\n // rows that were actually served.\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n const effectivePath = hop ? hop.targetCollection.slug : collectionPath;\n\n const collection = getCollectionByPath(effectivePath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n\n let query = this.db.select({ count: count() }).from(table).$dynamic();\n const allConditions: SQL[] = [];\n\n if (hop) allConditions.push(this.buildRelationScope(hop));\n\n // Soft delete. A listing that filters and a count that does not is a\n // page saying \"1 of 4 results\".\n withSoftDelete(allConditions, collection, table, options.withDeleted);\n\n if (options.searchString) {\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString, collection.properties, table, collection\n );\n if (searchConditions.length === 0) return 0;\n allConditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions)!);\n }\n\n if (options.filter) {\n const filterConditions = this.buildFilterConditions(options.filter, table, effectivePath);\n if (filterConditions.length > 0) allConditions.push(...filterConditions);\n }\n\n if (options.logical) {\n const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(\n options.logical, table, effectivePath, this.filterContext(effectivePath, table)\n );\n if (logicalCondition) allConditions.push(logicalCondition);\n }\n\n // The same narrowing the listing applies. A `beforeQuery` honoured by\n // one and not the other is a page reading \"1 of 4 results\" — the same\n // failure mode as the soft-delete condition above, which is why they sit\n // together.\n allConditions.push(...await this.narrowRead(\n effectivePath, collection, table, \"count\",\n FetchService.describeRead({ ...options, relatedTo: hop })\n ));\n\n // A `threshold` genuinely narrows the row set on the fetch path, and\n // this count did not apply it — so a similarity-filtered listing\n // reported the size of the *unfiltered* set, and `hasMore` stayed true\n // over pages that were already empty. Only the threshold narrows it:\n // the ORDER BY and the `_distance` projection change which rows come\n // first and what rides along with them, not how many there are.\n if (options.vectorSearch) {\n // Built for any vector search rather than only a thresholded one,\n // because this is also where an unknown or non-vector\n // `vector_search` property is refused with a 400. Without a\n // threshold it contributes no filter, so the count is unchanged and\n // what is gained is that `/count` refuses the request the listing\n // refuses instead of answering it with a number.\n const vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch, collection);\n if (vectorMeta.filter) allConditions.push(vectorMeta.filter);\n }\n\n if (allConditions.length > 0) {\n const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(allConditions);\n if (finalCondition) query = query.where(finalCondition);\n }\n\n const result = await query;\n return Number(result[0]?.count || 0);\n }\n\n /**\n * `count`/`sum`/`avg`/`min`/`max`, optionally grouped.\n *\n * The gap this fills is narrow and constant: every dashboard wants \"revenue\n * by status\" and \"orders per day\", and without it the options were a custom\n * function holding hand-written SQL, or fetching every row and reducing in\n * JavaScript — which is wrong at any size that matters, and silently wrong\n * under a `limit`.\n *\n * It runs through the same request-scoped handle as every other read, so\n * **RLS applies to the rows being aggregated**. That is the property worth\n * protecting here: an aggregate is an effective way to read data you cannot\n * select, and `count(*)` over a table whose policies would return nothing\n * has to be zero.\n */\n async aggregate<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n aggregates: { fn: \"count\" | \"sum\" | \"avg\" | \"min\" | \"max\"; field?: string; alias: string }[];\n groupBy?: string[];\n filter?: FilterValues<Extract<keyof M, string>>;\n logical?: LogicalCondition;\n searchString?: string;\n limit?: number;\n /** Groups to skip. Applied only to a grouped aggregate, like `limit`. */\n offset?: number;\n /**\n * Sort for the groups: by a `groupBy` field or an aggregate's alias.\n * Applied only to a grouped aggregate; see {@link aggregateOrdering}.\n */\n orderBy?: OrderByTuple[];\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n }\n ): Promise<Record<string, unknown>[]> {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const columns = getTableColumns(table);\n\n const columnFor = (field: string, forWhat: string): AnyPgColumn => {\n const column = columns[field as keyof typeof columns] as AnyPgColumn | undefined;\n if (!column) {\n // Lists no `excludeFromApi` field; the REST check answers a\n // hidden one with the same function, so a hidden field and a\n // typo cannot be told apart.\n const answer = unknownAggregateField(field, forWhat, listedColumnKeys(table, collection));\n throw ApiError.badRequest(answer.message, \"UNKNOWN_AGGREGATE_FIELD\", answer.details);\n }\n return column;\n };\n\n const selection: Record<string, SQL> = {};\n\n for (const aggregate of options.aggregates) {\n if (aggregate.fn === \"count\" && !aggregate.field) {\n selection[aggregate.alias] = sql`count(*)`;\n continue;\n }\n const column = columnFor(aggregate.field as string, `${aggregate.fn}()`);\n switch (aggregate.fn) {\n case \"count\": selection[aggregate.alias] = sql`count(${column})`; break;\n // Cast through numeric so what comes back is a string this\n // method parses, rather than a float whose precision depends on\n // the column type — `avg` over an integer column is otherwise\n // one shape here and another there.\n case \"sum\": selection[aggregate.alias] = sql`sum(${column})::numeric`; break;\n case \"avg\": selection[aggregate.alias] = sql`avg(${column})::numeric`; break;\n case \"min\": selection[aggregate.alias] = sql`min(${column})`; break;\n case \"max\": selection[aggregate.alias] = sql`max(${column})`; break;\n }\n }\n\n const groupColumns = (options.groupBy ?? []).map(field => ({\n field,\n column: columnFor(field, \"groupBy\")\n }));\n for (const group of groupColumns) {\n selection[group.field] = sql`${group.column}`;\n }\n\n let query = this.db.select(selection).from(table).$dynamic();\n\n const conditions: SQL[] = [];\n // Soft delete. A dashboard that sums deleted orders is reporting\n // revenue that was withdrawn.\n withSoftDelete(conditions, collection, table, options.withDeleted);\n if (options.searchString) {\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString, collection.properties, table, collection\n );\n // No searchable field means no row matches — the same impossible\n // WHERE the listing uses, rather than an unfiltered aggregate.\n if (searchConditions.length === 0) return [];\n conditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions) as SQL);\n }\n if (options.filter) {\n conditions.push(...this.buildFilterConditions(options.filter, table, collectionPath));\n }\n if (options.logical) {\n const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(\n options.logical, table, collectionPath, this.filterContext(collectionPath, table)\n );\n if (logicalCondition) conditions.push(logicalCondition);\n }\n // An aggregate is an efficient way to learn about rows you cannot\n // select, so it is narrowed by the same hook as the listing. Without\n // this, `count(*)` over a scoped collection reports the unscoped total.\n conditions.push(...await this.narrowRead(\n collectionPath, collection, table, \"aggregate\", FetchService.describeRead(options)\n ));\n if (conditions.length > 0) {\n const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(conditions);\n if (finalCondition) query = query.where(finalCondition);\n }\n\n if (groupColumns.length > 0) {\n query = query.groupBy(...groupColumns.map(g => g.column));\n query = query.orderBy(...this.aggregateOrdering(options.orderBy, groupColumns, options.aggregates, selection));\n // Bounded for the same reason a listing is: grouping by a\n // high-cardinality column is a whole table's worth of rows in one\n // response.\n if (options.limit) query = query.limit(options.limit);\n if (options.offset) query = query.offset(options.offset);\n }\n\n const rows = await query as Record<string, unknown>[];\n\n // `count`, `sum` and `avg` arrive as strings: Postgres returns bigint\n // and numeric that way because they do not fit a JS number in general.\n // They do fit for every aggregate anyone puts on a dashboard, and a\n // caller handed `\"12\"` where they expected `12` has to find that out\n // for themselves. Parsed once, here.\n //\n // `min` and `max` over a NUMBER column too, which they were not. Those\n // are uncast, so a `numeric` column returns them as strings — and\n // `?select=avg(price),max(price)` answered `{avg_price: 5,\n // max_price: \"8.5\"}`: two functions, one column, two JSON types, and\n // arithmetic on the second one silently concatenates.\n //\n // Decided by the column's declared type rather than by trying\n // `Number()` on the value, because `min`/`max` are the two that also\n // apply to text and dates. Parsing whatever happens to look numeric\n // would turn a `min(sku)` of `\"00123\"` into `123`, which is a different\n // value, and the `Number.isNaN` guard below would not catch it.\n const numericAliases = numericAggregateAliases(\n options.aggregates,\n (collection?.properties ?? {}) as Record<string, { type?: string }>\n );\n return rows.map(row => {\n const out: Record<string, unknown> = { ...row };\n for (const alias of numericAliases) {\n if (out[alias] === null || out[alias] === undefined) continue;\n const parsed = Number(out[alias]);\n if (!Number.isNaN(parsed)) out[alias] = parsed;\n }\n return out;\n });\n }\n\n /**\n * The `ORDER BY` of a grouped aggregate: the caller's keys, then every\n * group key they did not name.\n *\n * A grouped aggregate is paged like a listing — `limit` bounds it, so more\n * groups than one page holds are reached with `offset` — and a page is only\n * a page over a total order. With no `ORDER BY` at all, Postgres returns the\n * groups in whatever order its plan produces them, so \"the next fifty\" was\n * not a thing that existed. The group keys identify a group, so ending on\n * them makes the order total and puts every page boundary in the same place\n * on every request.\n *\n * A key may name a `groupBy` field or an aggregate by its alias\n * (`count`, `sum_total`), sorted on the aggregate's own expression — a\n * drizzle selection is not aliased in the SQL, so there is no output name\n * to refer to. Anything else is a 400: Postgres would refuse it too, but as\n * a `GROUP BY` error from inside the statement.\n */\n private aggregateOrdering(\n orderBy: OrderByTuple[] | undefined,\n groupColumns: { field: string; column: AnyPgColumn }[],\n aggregates: { alias: string }[],\n selection: Record<string, SQL>\n ): SQL[] {\n const keys: ResolvedOrderKey[] = [];\n const named = new Set<string>();\n for (const [field, direction, nulls] of orderBy ?? []) {\n const group = groupColumns.find(g => g.field === field);\n const aggregate = aggregates.find(a => a.alias === field);\n const target = group?.column ?? (aggregate ? selection[aggregate.alias] : undefined);\n if (!target) {\n const sortable = [...groupColumns.map(g => g.field), ...aggregates.map(a => a.alias)];\n throw ApiError.badRequest(\n `An aggregate cannot be sorted by '${field}'. It sorts by what it returns: ` +\n `${sortable.map(name => `'${name}'`).join(\", \")}.`,\n \"INVALID_ORDER_BY\"\n );\n }\n keys.push({ field, direction, nulls, target });\n named.add(field);\n }\n for (const group of groupColumns) {\n if (!named.has(group.field)) keys.push({ field: group.field, direction: \"asc\", target: group.column });\n }\n return this.buildOrderExpressions(keys, []);\n }\n\n /**\n * Check if a field value is unique.\n *\n * Deliberately **not** narrowed by `beforeQuery`, and it is the only read\n * here that is not. The question is whether the value exists anywhere in\n * the table, not whether it exists among the rows this caller can see:\n * narrowed, it would answer \"unique\" for a value a hidden row already\n * holds, and the insert would then be rejected by the unique constraint\n * with a message about a row the caller has no way to find. Uniqueness is a\n * property of the table.\n */\n async checkUniqueField(\n collectionPath: string,\n fieldName: string,\n value: unknown,\n excludeEntityId?: string,\n _databaseId?: string\n ): Promise<boolean> {\n if (value === undefined || value === null) return true;\n\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n const field = table[fieldName as keyof typeof table] as AnyPgColumn;\n\n if (!field) return true;\n\n // An address the key columns cannot hold names no row, so there is\n // nothing to exclude — a row being created, for instance.\n const excludeRow = excludeEntityId && idCanAddressTable(excludeEntityId, table, idInfoArray)\n ? rowIdentityCondition(table, idInfoArray, excludeEntityId, collectionPath)\n : undefined;\n const conditions = DrizzleConditionBuilder.buildUniqueFieldCondition(field, value, excludeRow);\n\n const result = await this.db\n .select({ count: count() })\n .from(table)\n .where(and(...conditions));\n\n const countResult = Number(result[0]?.count || 0);\n return countResult === 0;\n }\n\n /**\n * Get the RelationService instance for external use\n */\n getRelationService(): RelationService {\n return this.relationService;\n }\n\n // =============================================================\n // REST API INCLUDE-AWARE METHODS\n // =============================================================\n\n /**\n * Fetch a collection of rows, with the relations `include` names.\n *\n * ## Why this no longer uses `db.query.findMany({ with })`\n *\n * It used to, whenever a `db.query` builder existed for the table — which\n * is Drizzle's relational API, and which compiles a to-many relation into a\n * lateral join. This file's own comment on the *other* read path says what\n * that costs: \"catastrophically slow for large collections (7s+ for 350\n * rows)\". So the admin fetch avoided it and REST took it, and the fast,\n * batched loader that the admin used was unreachable from the HTTP API.\n *\n * There is one loader now, {@link loadIncludes}, and every read reaches it:\n * one query per relation per level, never one per row. That also makes this\n * method and the realtime refetch the same code, which is what stops\n * `find()` and `listen()` returning different shapes for one query.\n *\n * @param include - see {@link IncludeSpec}: names, dotted paths, `[\"*\"]`,\n * or the parametrised tree. An unknown name is a 400 `UNKNOWN_RELATION`.\n */\n async fetchCollectionForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /** An `or(...)`/`and(...)` group, applied alongside `filter`. */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n databaseId?: string;\n vectorSearch?: VectorSearchParams;\n /** Narrow to the rows reachable from a parent through a relation. */\n relatedTo?: NestedPathHop;\n /**\n * See `FetchCollectionProps.withDeleted`. Applied by\n * `fetchRowsWithConditionsRaw` — the one place this pipeline builds\n * a WHERE — so it holds for the listing, the nested-path listing\n * and the realtime refetch alike.\n */\n withDeleted?: WithDeleted;\n /** Columns to read — a projection pushed into the SELECT. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n /** Ask each row which declared search fields matched. */\n searchExplain?: boolean;\n } = {},\n include?: IncludeSpec\n ): Promise<Record<string, unknown>[]> {\n // Resolve a nested path here rather than at the route, so `include`,\n // `offset` and the rest reach a child listing by the same route they\n // reach a root one.\n if (isNestedPath(collectionPath)) {\n const hop = resolveNestedPath(collectionPath, this.registry);\n if (hop) {\n return this.fetchCollectionForRest<M>(\n hop.targetCollection.slug, { ...options,\nrelatedTo: hop }, include\n );\n }\n }\n // `relatedTo` is applied by `fetchRowsWithConditionsRaw`, which builds\n // the scope condition itself — this used to build a second one here and\n // hand it to a `db.query` branch that no longer exists.\n const collection = getCollectionByPath(collectionPath, this.registry);\n\n const normalizedInclude = normalizeInclude(include);\n\n // Base rows first, relations after. One SELECT, then one query per\n // relation per level of the include tree — never one per row, and never\n // the lateral join the relational query API compiles a to-many into.\n const rows = (await this.fetchRowsWithConditionsRaw<M>(collectionPath, options))\n .map(row => toRestRow(row, collection, this.registry));\n\n if (normalizedInclude) {\n await this.loadIncludes(rows, collection, collectionPath, normalizedInclude);\n }\n\n return rows;\n }\n\n /**\n * Fetch a single row, with the relations `include` names.\n *\n * The same two steps `fetchCollectionForRest` takes, for one row, through\n * the same {@link loadIncludes}. It used to be a separate implementation on\n * `db.query.findFirst({ with })` with a hand-written N+1 fallback beneath\n * it, and the two disagreed: the primary path returned a to-one relation as\n * the target's columns, the fallback merged an `id` over them, and a\n * relation that resolved to nothing was *absent* on one path and `null` on\n * the other. `find()[0]` and `findById()` now answer with the same row.\n */\n async fetchOneForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n include?: IncludeSpec,\n databaseId?: string,\n options?: {\n fields?: string[];\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n }\n ): Promise<Record<string, unknown> | null> {\n if (!await this.isAddressableUnder(collectionPath, id)) return null;\n\n const collection = getCollectionByPath(collectionPath, this.registry);\n\n // An unknown relation is a 400 before it is a 404: whether the row\n // exists must not decide whether the request was well-formed.\n const normalizedInclude = normalizeInclude(include);\n if (normalizedInclude) this.assertIncludeResolvable(collection, normalizedInclude);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n\n // See `fetchOne`: an unaddressable id is a 404, not a database error.\n if (!idCanAddressTable(id, table, idInfoArray)) return null;\n\n const addressed = rowIdentityCondition(table, idInfoArray, id, collectionPath);\n\n // Soft delete: a stamped row is a 404 through the REST read too, so\n // `GET /:id` and the listing agree about which rows exist.\n const withDeleted = options?.withDeleted;\n const projection = this.columnProjection(table, collection, options?.fields, idInfoArray);\n // The REST single get is narrowed by the same hook as the listing, so\n // `find()[0]` and `findById()` agree about which rows exist — and so\n // that a row excluded from a scoped listing cannot be read by guessing\n // its id.\n const narrowing = await this.narrowReadCondition(\n collectionPath, collection, table, \"get\",\n FetchService.describeRead({ fields: options?.fields })\n );\n const identity = narrowing\n ? and(addressed, narrowing) as SQL\n : addressed;\n const result = await this.db\n .select(projection as never)\n .from(table)\n .where(andSoftDelete(identity, collection, table, withDeleted))\n .limit(1);\n\n if (result.length === 0) return null;\n\n const row = toRestRow(result[0] as Record<string, unknown>, collection, this.registry);\n\n if (normalizedInclude) {\n await this.loadIncludes([row], collection, collectionPath, normalizedInclude);\n }\n\n return row;\n }\n\n /**\n * {@link fetchOneForRest}'s read of a row an INSERT just wrote, taken from\n * the INSERT's own `RETURNING` instead of a second statement — or\n * `undefined` when the two could answer differently.\n *\n * A write answers with `fetchOneForRest(slug, id, undefined, …, { withDeleted: true })`\n * (see `PersistService.save`): the row's visible columns, rendered by\n * `toRestRow`. For a row the statement itself wrote, with nothing written\n * after it, `RETURNING` those same columns is that row — the same values\n * through the same column decoders, in the same order, rendered the same\n * way. Two things narrow the read and not the statement, and either one\n * keeps the read:\n *\n * - a `beforeQuery` adds conditions to the read (and runs application\n * code): a row outside them is no answer today, and would be one from\n * `RETURNING`;\n * - per-field read access, which projects the read for the viewer.\n *\n * Row-level security is the same either way. `INSERT … RETURNING` holds\n * the new row to the table's SELECT policies and refuses one they hide\n * (42501) — which the INSERT's `RETURNING <key>` already did, so returning\n * more columns changes nothing about who may write or read what.\n */\n insertReturning(collectionPath: string): {\n columns: Record<string, AnyPgColumn>;\n render: (returned: Record<string, unknown>) => Record<string, unknown>;\n } | undefined {\n const collection = getCollectionByPath(collectionPath, this.registry);\n if (hasBeforeQuery(this.registry, collection) || hasFieldAccessRules(collection)) return undefined;\n const table = getTableForCollection(collection, this.registry);\n // Not a real drizzle table (a stub in a test): nothing to return, so\n // the read stays.\n const tableColumns: Record<string, AnyPgColumn> | undefined = getTableColumns(table);\n if (!tableColumns) return undefined;\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n // The read's projection, kept in the table's column order, so the\n // rendered row has its keys in the order the read gives them.\n const visible = this.columnProjection(table, collection, undefined, idInfoArray);\n const columns: Record<string, AnyPgColumn> = {};\n for (const [name, column] of Object.entries(tableColumns)) {\n if (!visible || name in visible) columns[name] = column;\n }\n return { columns, render: returned => toRestRow(returned, collection, this.registry) };\n }\n\n /**\n * Fetch raw rows without any relation processing (for REST fast path)\n */\n private async fetchRowsWithConditionsRaw<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /**\n * An `or(...)`/`and(...)` group, applied alongside `filter`.\n *\n * Declared *and applied*, because this is the path every REST search\n * and vector read takes: `fetchCollectionForRest` skips `db.query`\n * whenever a `searchString` or a `vectorSearch` is present. The\n * group arrived here on `options` from the very beginning and was\n * simply never read, so `?searchString=x&or=(...)` served every row\n * matching `x` that RLS allowed — while `count` (which does apply\n * it) reported the narrowed total, so `meta.total` and `data`\n * described different sets of rows.\n */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n searchExplain?: boolean;\n vectorSearch?: VectorSearchParams;\n relatedTo?: NestedPathHop;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n /** Columns to read — see {@link columnProjection}. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n\n // A vector read orders by distance, and a distance is computed per\n // query and stored nowhere, so there is no value on the cursor row to\n // seek past. Seeking anyway compared ids under a distance order: page\n // two was the rows with a smaller id, which is not the next page.\n if (options.vectorSearch && options.startAfter) {\n throw ApiError.badRequest(\n \"Cursor pagination (`startAfter`) cannot be combined with a vector search. \" +\n \"Its rows are ordered by a distance computed per query, which cannot key a cursor. \" +\n \"Use `limit`/`offset` for the next page of nearest rows.\",\n \"VECTOR_CURSOR_UNSUPPORTED\"\n );\n }\n\n // A vector read runs with pgvector's iterative index scan, which has to\n // be set in a transaction — see `vector-iterative-scan.ts`. This\n // driver's request-scoped copies already read in one; the base driver,\n // on the pool, is given one, read-only, and reads again inside it.\n const iterative = options.vectorSearch !== undefined\n && await iterativeScansAvailable(this.db, this.registry);\n if (iterative && !inTransaction(this.db)) {\n return this.db.transaction(\n (tx) => new FetchService(tx, this.registry, this.callContext).fetchRowsWithConditionsRaw(collectionPath, options),\n { accessMode: \"read only\" }\n );\n }\n\n let vectorMeta: { orderBy: SQL; filter?: SQL; distanceSelect: SQL } | undefined;\n if (options.vectorSearch) {\n vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch, collection);\n }\n\n // A generated search column is an index in column form; `SELECT *`\n // would ship it to every caller. The projection is undefined — and the\n // SQL therefore unchanged — for any table without one. `fields`\n // narrows it further, in SQL rather than after the fact.\n const visible = this.columnProjection(table, collection, options.fields, idInfoArray, options.distinct === true);\n\n // Relevance, alongside the row, exactly as `_distance` rides along with\n // a vector search. Present only when the collection opted in and the\n // request carried a search string, so a caller can order by it, show\n // it, or blend it with a score of their own.\n const rankSelect = options.searchString\n ? DrizzleConditionBuilder.buildSearchRankExpression(options.searchString, table, collection)\n : undefined;\n\n // Only when asked: a `ts_headline` per declared field per row.\n const matchesSelect = options.searchString && options.searchExplain\n ? DrizzleConditionBuilder.buildSearchMatchesExpression(options.searchString, table, collection)\n : undefined;\n\n // `SELECT DISTINCT`, over exactly the projection above.\n //\n // Only on the plain read. A vector or relevance query selects a\n // per-row computed value beside the columns — a distance, a score —\n // and DISTINCT over a set that includes one makes every row distinct\n // by construction: it would answer 200 having done nothing, which is\n // worse than refusing.\n const wantsDistinct = options.distinct === true;\n if (wantsDistinct && (vectorMeta || rankSelect)) {\n throw ApiError.badRequest(\n \"`distinct` cannot be combined with a search or vector query: both attach a per-row \"\n + \"score to every row, so no two rows are ever equal and DISTINCT would have no effect. \"\n + \"Drop one of the two.\",\n \"DISTINCT_NOT_APPLICABLE\"\n );\n }\n const selectFrom = () => {\n const builder = wantsDistinct ? this.db.selectDistinct.bind(this.db) : this.db.select.bind(this.db);\n return visible\n ? builder(visible as never).from(table).$dynamic()\n : builder().from(table).$dynamic();\n };\n\n let query = vectorMeta\n ? this.db.select({ table_row: (visible ?? table) as never,\n_distance: vectorMeta.distanceSelect }).from(table).$dynamic()\n : rankSelect\n ? this.db.select({\n table_row: (visible ?? table) as never,\n _score: rankSelect,\n ...(matchesSelect ? { _matches: matchesSelect } : {})\n }).from(table).$dynamic()\n : selectFrom();\n const allConditions: SQL[] = [];\n\n if (options.relatedTo) allConditions.push(this.buildRelationScope(options.relatedTo));\n\n // Soft delete, on the `db.select` path. Both paths serve the same\n // request, so a deleted row must not come back down one and not the\n // other — which is exactly what happens when each grows its own filter.\n withSoftDelete(allConditions, collection, table, options.withDeleted);\n\n if (options.searchString) {\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString, collection.properties, table, collection\n );\n if (searchConditions.length === 0) return [];\n allConditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions)!);\n }\n\n if (options.filter) {\n const filterConditions = this.buildFilterConditions(options.filter, table, collectionPath);\n if (filterConditions.length > 0) allConditions.push(...filterConditions);\n }\n\n if (options.logical) {\n const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath, this.filterContext(collectionPath, table));\n if (logicalCondition) allConditions.push(logicalCondition);\n }\n\n // This is the one place this pipeline builds a WHERE, so it is the one\n // place the hook has to be applied for the listing, the search, the\n // vector read, the nested-path listing and the realtime refetch.\n allConditions.push(...await this.narrowRead(\n collectionPath, collection, table, \"list\", FetchService.describeRead(options)\n ));\n\n // A vector `threshold` is not a WHERE condition here. The rows come\n // back closest first whatever the plan, so the ones within it are the\n // front of the page — and are cut from it below, after the read. As a\n // condition it was applied to the index's candidates after the scan,\n // like any filter, and under an iterative scan a page asking for more\n // rows than lie within the threshold made the index walk on to its\n // scan limit looking for them: 22 ms against 1 ms for the same page\n // (20,000 rows × 256 dimensions). Cut afterwards, the page and its\n // offset are the same rows either way — those within the threshold\n // are a prefix of the ordering — and no scan looks past the page.\n\n if (allConditions.length > 0) {\n const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(allConditions);\n if (finalCondition) query = query.where(finalCondition);\n }\n\n const sortKeys = normalizeDriverOrderBy(options.orderBy, options.order) ?? [];\n\n // Postgres requires every ORDER BY expression of a `SELECT DISTINCT` to\n // be in the select list, and answers a bare `42P10` if one is not —\n // which reaches the caller as a 500 quoting SQL they never wrote. The\n // condition is knowable here, so it is a 400 that names the column and\n // the fix instead.\n if (wantsDistinct && visible) {\n const selected = new Set(Object.keys(visible));\n const missing = sortKeys.map(([field]) => field).filter(field => !selected.has(field));\n if (missing.length > 0) {\n throw ApiError.badRequest(\n `\\`distinct\\` cannot sort by ${missing.map(f => `'${f}'`).join(\", \")}: a DISTINCT read `\n + \"can only be ordered by columns it returns, or the rows it collapses have no \"\n + `defined order. Add ${missing.map(f => `'${f}'`).join(\", \")} to \\`fields\\`, or drop them `\n + \"from `orderBy`.\",\n \"DISTINCT_ORDER_BY_NOT_SELECTED\",\n { fields: missing }\n );\n }\n }\n\n // Vector search overrides ORDER BY with distance (ascending = closest first)\n const keyColumns = FetchService.keyColumns(table, idInfoArray, collectionPath);\n const orderExpressions = vectorMeta\n ? [asc(vectorMeta.orderBy), ...keyColumns.map(column => desc(column))]\n : this.buildOrderExpressions(\n // `collectionPath` too: an aggregate over a relation\n // (`count(applications)`) resolves its subquery against it, and\n // without it every such sort on this path — the REST list, the\n // SDK's `find()` — was refused as a query that \"carries none\".\n this.resolveOrderKeys(table, sortKeys, collection, options.searchString, collectionPath),\n wantsDistinct ? [] : keyColumns\n );\n if (orderExpressions.length > 0) query = query.orderBy(...orderExpressions);\n\n if (options.startAfter) {\n // Keyset seeking on the REST path. `startAfter` arrived on this\n // signature from the beginning and was never read here, so\n // `?after=` reached the driver and paged nothing: the cursor was\n // decoded, handed over, and dropped one function short of the\n // comparison built to consume it.\n const cursorConditions = this.buildCursorConditions(table, idInfoArray, options, collectionPath);\n if (cursorConditions.length > 0) {\n allConditions.push(...cursorConditions);\n const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(allConditions);\n if (finalCondition) query = query.where(finalCondition);\n }\n }\n\n const limitValue = options.vectorSearch\n ? (options.limit || 10)\n : options.searchString ? (options.limit || 50) : options.limit;\n if (limitValue) query = query.limit(limitValue);\n\n // Offset (numeric pagination)\n if (options.offset && options.offset > 0) query = query.offset(options.offset);\n\n if (iterative) await enableIterativeScan(this.db);\n\n const rawResults = await query;\n\n if (vectorMeta) {\n const threshold = options.vectorSearch?.threshold;\n return (rawResults as { table_row: Record<string, unknown>; _distance: unknown }[])\n .map(r => ({\n ...r.table_row,\n _distance: typeof r._distance === \"number\" ? r._distance : parseFloat(String(r._distance))\n }))\n // `<`, as the SQL condition was: a row at exactly the threshold\n // is outside it, and so is a NaN distance (a zero vector's\n // cosine), which Postgres also orders above every number.\n .filter(row => threshold == null || row._distance < threshold);\n }\n\n if (rankSelect) {\n return (rawResults as { table_row: Record<string, unknown>; _score: unknown; _matches?: unknown }[]).map(r => ({\n ...r.table_row,\n _score: typeof r._score === \"number\" ? r._score : parseFloat(String(r._score)),\n ...(matchesSelect ? { _matches: r._matches ?? [] } : {})\n }));\n }\n\n return rawResults as Record<string, unknown>[];\n }\n}\n","import { and, eq, inArray, notInArray, sql } from \"drizzle-orm\";\nimport { AnyPgColumn } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, JUNCTION_PIVOT_KEY, Properties, Property, ResolvedManyToMany, ResolvedRelation, ResolvedVia } from \"@rebasepro/types\";\nimport { hasForeignKeyOnTarget, isManyToMany } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, findRelation, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { DrizzleConditionBuilder } from \"../utils/drizzle-conditions\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport {\n getTableForCollection,\n relationMisconfigured,\n requirePrimaryKeys,\n parseIdValues,\n sourceKeyField\n} from \"./collection-helpers\";\nimport type { NestedPathHop } from \"./nested-path\";\nimport { RelationService } from \"./RelationService\";\nimport type { ReadCallContextProvider } from \"./read-scope\";\nimport {\n applyJunctionMembership,\n bindJoinPathJunction,\n bindThroughJunction,\n removeJunctionLink,\n updateJunctionPivot,\n type JunctionLinkWrite,\n type VisibleTargets\n} from \"./junction-writes\";\nimport { serializePropertyToServer } from \"../data-transformer\";\nimport { reachedDatabase } from \"../utils/pg-error-utils\";\n\n/**\n * The ids in a to-many relation write, whatever shape the caller sent.\n *\n * A membership list is written as either the related rows (`[{ id: 1 }]`, what\n * the admin UI sends back after reading them) or as bare keys (`[1]`, `[\"t-1\"]`,\n * what anyone writing the API by hand sends). Only the first was read, via a\n * blind `.map(rel => rel.id)`, and a bare key therefore became `undefined`:\n * on a numeric-keyed target that surfaced as `Invalid numeric ID: undefined`,\n * and on a string-keyed one it did not surface at all — `String(undefined)`\n * wrote a junction row pointing at the literal `\"undefined\"`, which no read\n * would ever match. Both shapes are accepted here, in one place, because both\n * call sites had the same assumption.\n *\n * An element that carries no key is refused rather than skipped: dropping it\n * would silently write a shorter membership list than the caller asked for.\n */\nfunction relationTargetIds(value: unknown, relationName: string, collectionSlug: string): (string | number)[] {\n return relationLinkElements(value, relationName, collectionSlug).map(element => element.id);\n}\n\n/** One element of a membership write, as the caller sent it. */\ninterface RelationLinkElement {\n id: string | number;\n /** The `_pivot` the element carried, unvalidated and unserialized. */\n pivot?: Record<string, unknown>;\n}\n\n/**\n * The same reading, keeping the link's own values.\n *\n * An element may be `{ id, _pivot: { role: \"owner\" } }` where the relation is a\n * `manyToMany` whose `through` declares `properties`. The `_pivot` is taken off\n * here and nowhere else: it is not a column of the target, so leaving it on the\n * element would make it look like one to every later step.\n */\nfunction relationLinkElements(\n value: unknown,\n relationName: string,\n collectionSlug: string\n): RelationLinkElement[] {\n if (!Array.isArray(value)) return [];\n\n return value.map((element, index) => {\n if (typeof element === \"string\" || typeof element === \"number\") return { id: element };\n if (element && typeof element === \"object\") {\n const id = (element as { id?: unknown }).id;\n const pivot = (element as Record<string, unknown>)[JUNCTION_PIVOT_KEY];\n if (typeof id === \"string\" || typeof id === \"number\") {\n return {\n id,\n pivot: pivot && typeof pivot === \"object\" && !Array.isArray(pivot)\n ? pivot as Record<string, unknown>\n : undefined\n };\n }\n }\n throw new Error(\n `Cannot write relation \"${relationName}\" on \"${collectionSlug}\": element ${index} carries no id. ` +\n \"Pass either the related rows (`[{ id: … }]`) or their keys (`[1, 2]`), not \" +\n `${element === null ? \"null\" : typeof element}.`\n );\n });\n}\n\n/**\n * A `_pivot` as the junction table's columns.\n *\n * Exported because the payload route needs the identical reading — one function\n * so a value written through `PATCH posts/1 { tags: [{ id, _pivot }] }` and the\n * same value written through `PATCH posts/1/tags/5 { _pivot }` cannot be\n * serialized two ways.\n */\nexport function serializePivot(\n pivot: Record<string, unknown>,\n payload: Properties,\n label: string\n): Record<string, unknown> {\n const declared = Object.keys(payload);\n if (declared.length === 0) {\n throw ApiError.badRequest(\n `Relation '${label}' carries no junction columns, so there is nothing to write under ` +\n `\\`${JUNCTION_PIVOT_KEY}\\`. Declare them in \\`through.properties\\`.`,\n \"VALIDATION_UNKNOWN_FIELDS\",\n { relation: label }\n );\n }\n\n const values: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(pivot)) {\n const property = payload[key as keyof Properties] as Property | undefined;\n if (!property) {\n throw ApiError.badRequest(\n `The junction behind relation '${label}' has no column '${key}'. ` +\n `Its \\`through.properties\\` are: ${declared.sort().map(k => `'${k}'`).join(\", \")}.`,\n \"VALIDATION_UNKNOWN_FIELDS\",\n { relation: label, field: key, validFields: declared.sort() }\n );\n }\n // `undefined` means \"I have no value for this\", not \"set it to NULL\" —\n // the same reading `serializeDataToServer` gives a row's own columns.\n if (value === undefined) continue;\n const serialized = serializePropertyToServer(value, property, key);\n if (serialized !== undefined) values[key] = serialized;\n }\n return values;\n}\n\n/**\n * Writing relations: junction membership, foreign-key stamping, and the links a\n * nested path creates or removes.\n *\n * Split from {@link RelationService}, which now only reads. The two had grown\n * into one 1700-line class doing two unrelated jobs, and sharing one habit —\n * warning about a relation it could not resolve and carrying on, which surfaces\n * as an empty list on the read side and as a successful save on the write side.\n * Separating them is what made that visible as one class of defect rather than\n * eighteen scattered log lines.\n *\n * The reads it still needs — the source key a link joins on — it asks\n * {@link RelationService} for rather than reimplementing.\n */\nexport class RelationWriteService {\n private reads: RelationService;\n\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) {\n this.reads = new RelationService(db, registry, callContext);\n }\n\n\n /**\n * Remove the junction row linking a parent to `targetId`, leaving the target\n * row itself alone.\n *\n * This is what `DELETE authors/1/tags/5` has to mean for a many-to-many: the\n * target is shared, so deleting the row would remove the tag from every other\n * post that uses it. It used to do exactly that — resolve the path to the\n * `tags` table and delete by primary key.\n */\n async unlinkRelatedEntity(\n tx: DrizzleClient,\n hop: NestedPathHop,\n targetId: string | number\n ): Promise<void> {\n if (!isManyToMany(hop.relation)) {\n throw new Error(`Relation '${hop.relationKey}' has no junction table to unlink through`);\n }\n\n const binding = bindThroughJunction(\n this.registry,\n hop.relation.through,\n `${hop.parentCollection.slug}.${hop.relationKey}`\n );\n\n await removeJunctionLink(\n tx,\n binding,\n this.parsedId(hop.parentCollection, hop.parentId),\n this.parsedId(hop.targetCollection, targetId),\n { parent: hop.parentCollection.slug, relation: hop.relationKey }\n );\n }\n\n\n /**\n * Set the columns of one existing link, leaving the membership alone.\n *\n * `PATCH /api/data/:slug/:id/:relation/:targetId` with a `_pivot` body. The\n * membership array cannot say this: one element would unlink everything\n * else, and re-sending the whole set to change one `role` reintroduces the\n * lost update the membership diff exists to avoid.\n */\n async updateRelationPivot(\n tx: DrizzleClient,\n hop: NestedPathHop,\n targetId: string | number,\n pivot: Record<string, unknown>\n ): Promise<void> {\n if (!isManyToMany(hop.relation)) {\n throw ApiError.badRequest(\n `Relation '${hop.relationKey}' on '${hop.parentCollection.slug}' is a ` +\n `${hop.relation.kind}, which has no junction row to carry \\`${JUNCTION_PIVOT_KEY}\\`. ` +\n \"Only a `manyToMany` with `through.properties` does.\",\n \"VALIDATION_UNKNOWN_FIELDS\",\n { relation: hop.relationKey, collection: hop.parentCollection.slug }\n );\n }\n\n const label = `${hop.parentCollection.slug}.${hop.relationKey}`;\n const binding = bindThroughJunction(this.registry, hop.relation.through, label);\n const values = serializePivot(pivot, hop.relation.through.properties, label);\n\n if (Object.keys(values).length === 0) {\n throw ApiError.badRequest(\n `\\`${JUNCTION_PIVOT_KEY}\\` named no junction column to set on '${label}'.`,\n \"VALIDATION_CONSTRAINT\",\n { relation: hop.relationKey, collection: hop.parentCollection.slug }\n );\n }\n\n await updateJunctionPivot(\n tx,\n binding,\n this.parsedId(hop.parentCollection, hop.parentId),\n this.parsedId(hop.targetCollection, targetId),\n values,\n { parent: hop.parentCollection.slug, relation: hop.relationKey }\n );\n }\n\n\n /**\n * The target rows of `relation` this caller can see, for a junction diff —\n * or `undefined` when that is all of them. See\n * {@link RelationService.visibleTargetCondition}.\n */\n private async visibleTargets(\n parentCollection: CollectionConfig,\n relation: ResolvedRelation,\n parentId: string | number\n ): Promise<VisibleTargets | undefined> {\n const condition = await this.reads.visibleTargetCondition(parentCollection, relation, parentId);\n if (!condition) return undefined;\n const targetCollection = relation.target();\n const table = getTableForCollection(targetCollection, this.registry);\n const [idInfo] = requirePrimaryKeys(targetCollection, this.registry);\n const idColumn = table[idInfo.fieldName as keyof typeof table] as AnyPgColumn;\n return { table, idColumn, condition };\n }\n\n /** A collection's id, parsed to the type its primary key column holds. */\n private parsedId(collection: CollectionConfig, id: string | number): unknown {\n const pks = requirePrimaryKeys(collection, this.registry);\n return parseIdValues(id, pks)[pks[0].fieldName];\n }\n\n\n /** The same, for the membership list a to-many write names. */\n private parsedIds(collection: CollectionConfig, ids: Array<string | number>): unknown[] {\n if (ids.length === 0) return [];\n const pks = requirePrimaryKeys(collection, this.registry);\n return ids.map(id => parseIdValues(id, pks)[pks[0].fieldName]);\n }\n\n /**\n * A membership list as links: the target ids parsed, and each element's\n * `_pivot` turned into the columns the junction table holds.\n *\n * The values go through `serializePropertyToServer` — the same function\n * every collection column's value goes through on a save — so a `date`\n * arrives as a Date and a `map` as JSON, keyed by the property key, which is\n * what the drizzle junction table is keyed by. A key the junction does not\n * declare is **refused**, not dropped: drizzle leaves an unknown key out of\n * the statement, so dropping it is a write that reports success having\n * stored nothing.\n */\n private parsedLinks(\n collection: CollectionConfig,\n elements: RelationLinkElement[],\n payload: Properties,\n label: string\n ): JunctionLinkWrite[] {\n if (elements.length === 0) return [];\n const pks = requirePrimaryKeys(collection, this.registry);\n return elements.map(element => ({\n id: parseIdValues(element.id, pks)[pks[0].fieldName],\n pivot: element.pivot ? serializePivot(element.pivot, payload, label) : undefined\n }));\n }\n\n\n /**\n * Update many-to-many and junction relations\n */\n async updateRelationsUsingJoins<M extends Record<string, unknown>>(\n tx: DrizzleClient,\n collection: CollectionConfig,\n id: string | number,\n relationValues: Partial<M>\n ) {\n const resolvedRelations = resolveCollectionRelations(collection);\n\n for (const [key, value] of Object.entries(relationValues)) {\n const relation = findRelation(resolvedRelations, key);\n if (!relation || relation.cardinality !== \"many\") continue;\n\n const elements = relationLinkElements(value, key, collection.slug);\n const targetEntityIds = elements.map(element => element.id);\n const targetCollection = relation.target();\n\n const label = `${collection.slug}.${key}`;\n\n if (relation.kind === \"via\") {\n await applyJunctionMembership(\n tx,\n bindJoinPathJunction(\n this.registry,\n relation.joinPath,\n getTableName(collection),\n getTableName(targetCollection),\n label\n ),\n this.parsedId(collection, id),\n // A `via` declares no payload, so `{}` refuses a `_pivot`\n // sent through one rather than storing it nowhere.\n this.parsedLinks(targetCollection, elements, {}, label),\n await this.visibleTargets(collection, relation, id)\n );\n } else if (relation.kind === \"manyToMany\") {\n await applyJunctionMembership(\n tx,\n bindThroughJunction(this.registry, relation.through, label),\n this.parsedId(collection, id),\n this.parsedLinks(targetCollection, elements, relation.through.properties, label),\n await this.visibleTargets(collection, relation, id)\n );\n } else if (relation.cardinality === \"many\" && hasForeignKeyOnTarget(relation)) {\n // Handle one-to-many (inverse) by updating target FK to point to parent\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdCol = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n // The wire name the target's table is keyed by, not the column.\n const fkField = fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget);\n const fkCol = targetTable[fkField as keyof typeof targetTable] as AnyPgColumn;\n\n if (!fkCol || !targetIdCol) {\n throw relationMisconfigured(\n label,\n `the target table '${getTableName(targetCollection)}' has no ` +\n `${fkCol ? `'${targetIdInfo.fieldName}' key column` : `'${relation.foreignKeyOnTarget}' foreign-key column`}`\n );\n }\n\n // What the children's foreign key must hold — the parent's id\n // unless the link declares a `sourceKey`, and then the value of\n // that column on this parent row.\n const parentKeyValue = (await this.reads.resolveSourceKeys(collection, relation, [id], tx))\n .keyByParentId.get(String(id));\n\n if (parentKeyValue === undefined) {\n throw new Error(\n `Cannot write relation '${key}' on '${collection.slug}': row '${id}' has no value in ` +\n `\\`sourceKey: \"${sourceKeyField(relation, collection, this.registry)}\"\\`, so there is ` +\n \"nothing for the related rows to point at.\"\n );\n }\n\n // Only the rows the caller could have seen linked are theirs to\n // unlink: the list they send back is the list they were shown,\n // which the target's scope and soft delete had already\n // narrowed. Clearing by the foreign key alone set it to NULL on\n // another tenant's rows and on the trashed ones.\n const visible = await this.reads.visibleTargetCondition(collection, relation, id);\n\n // Clear existing links not in the new set\n if (targetEntityIds.length > 0) {\n const parsedTargetIds = targetEntityIds.map(id => parseIdValues(id, targetPks)[targetIdInfo.fieldName]);\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n // `notInArray`, not a hand-built fragment: the sibling\n // update three lines below already uses `inArray`, and\n // the hand-built version called `sql.join` with no\n // separator — the only such call in the workspace —\n // which renders `NOT IN ($1$2$3)`. That is a syntax\n // error, so writing a `hasMany` relation with two or\n // more children aborted the whole save transaction.\n .where(and(eq(fkCol, parentKeyValue), notInArray(targetIdCol as AnyPgColumn, parsedTargetIds as unknown[]), visible));\n\n // Set FK for the provided targets — only those that do not\n // point here already, and only those the caller can see.\n //\n // Already linked: a parent saved with its unchanged list\n // (which the admin form sends back on every save) rewrote\n // every child, fired the CDC trigger and the `updated_at`\n // touch trigger for each, and made them all look edited.\n //\n // Visible: the same scope the clear above holds to. A child\n // the target's `beforeQuery` or the trash hides is a row the\n // caller cannot address, and this took it from whichever\n // parent it had. It is left alone, as an id naming no row\n // is, so the answer says nothing about whether it exists.\n await tx\n .update(targetTable)\n .set({ [fkField]: parentKeyValue })\n .where(and(\n inArray(targetIdCol as AnyPgColumn, parsedTargetIds as unknown[]),\n sql`${fkCol} IS DISTINCT FROM ${parentKeyValue}`,\n visible\n ));\n } else {\n // If empty array provided, clear all existing links for this parent\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n .where(and(eq(fkCol, parentKeyValue), visible));\n }\n } else {\n // Every to-many kind in the union is handled above — TypeScript\n // narrows `relation` to `never` here — so reaching this means a\n // relation resolved to something no writer knows. It used to be\n // skipped with a warning, which told the caller their membership\n // had been stored when nothing had happened.\n throw relationMisconfigured(\n label,\n \"it is a to-many relation with no way to write links — a `manyToMany` \" +\n \"needs `through`, a `hasMany` needs `foreignKeyOnTarget`\"\n );\n }\n }\n }\n\n\n /**\n * Update inverse relations (where FK is on the target table)\n */\n async updateInverseRelations(\n tx: DrizzleClient,\n sourceCollection: CollectionConfig,\n sourceEntityId: string | number,\n inverseRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedRelation;\n newValue: unknown;\n }>\n ) {\n for (const update of inverseRelationUpdates) {\n const { relation, newValue } = update;\n\n try {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);\n const sourceIdInfo = sourcePks[0];\n\n if (relation.kind === \"via\") {\n await this.updateInverseJoinPathRelation(\n tx,\n sourceCollection,\n sourceEntityId,\n targetCollection,\n relation,\n newValue\n );\n continue;\n }\n\n // A many-to-many names its own junction. This used to walk the\n // *target's* relations looking for an owning side whose\n // `relationName` matched `inverseRelationName`, and swap its\n // source/target columns — a search that silently did nothing\n // when the far side was declared differently than expected.\n if (isManyToMany(relation)) {\n await this.updateManyToManyInverseRelation(\n tx,\n sourceCollection,\n sourceEntityId,\n targetCollection,\n relation,\n newValue,\n {\n table: relation.through.table,\n sourceColumn: relation.through.sourceColumn,\n targetColumn: relation.through.targetColumn,\n properties: relation.through.properties\n }\n );\n continue;\n }\n\n // What is left names the parent with a column on the target.\n const label = `${sourceCollection.slug}.${relation.relationName}`;\n if (!hasForeignKeyOnTarget(relation)) {\n throw relationMisconfigured(\n label,\n `a '${relation.kind}' relation names no column on the target to write the link into`\n );\n }\n\n // The wire name the target's table is keyed by, not the column.\n const fkField = fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget!);\n const foreignKeyColumn = targetTable[fkField as keyof typeof targetTable] as AnyPgColumn;\n if (!foreignKeyColumn) {\n throw relationMisconfigured(\n label,\n `'${relation.foreignKeyOnTarget}' is not a column on the target table ` +\n `'${getTableName(targetCollection)}'`\n );\n }\n\n // The value the target's foreign key holds: this row's id, or\n // the column named by `sourceKey` when the link joins on one.\n const sourceKeyValue = (await this.reads.resolveSourceKeys(sourceCollection, relation, [sourceEntityId], tx))\n .keyByParentId.get(String(sourceEntityId));\n\n if (sourceKeyValue === undefined) {\n throw new Error(\n `Cannot write relation '${relation.relationName}' on '${sourceCollection.slug}': row ` +\n `'${sourceEntityId}' has no value in \\`sourceKey: ` +\n `\"${sourceKeyField(relation, sourceCollection, this.registry)}\"\\`, so there is nothing ` +\n \"for the related row to point at.\"\n );\n }\n\n // The same reading as the to-many clear: the row this one\n // points at now is only the caller's to detach if they could\n // have seen it.\n const visible = await this.reads.visibleTargetCondition(sourceCollection, relation, sourceEntityId);\n\n if (newValue === null || newValue === undefined) {\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n .where(and(eq(foreignKeyColumn, sourceKeyValue), visible));\n } else {\n const parsedNewTargetIdObj = parseIdValues(newValue as string | number, targetPks);\n const parsedNewTargetId = parsedNewTargetIdObj[targetIdInfo.fieldName];\n const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n // First, clear any existing FK that points to this source row\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n .where(and(eq(foreignKeyColumn, sourceKeyValue), visible));\n\n // Then, update the new target row to point to this source row\n await tx\n .update(targetTable)\n .set({ [fkField]: sourceKeyValue })\n .where(eq(targetIdField, parsedNewTargetId));\n }\n } catch (e) {\n // This catch is here so that one relation whose columns cannot\n // be resolved does not abort the others. A refusal is not a\n // misconfiguration: swallowing WRITE_DENIED here reported the\n // save as done while the database had rejected the write, which\n // is the whole defect this path was just fixed for. `name` as\n // well as `instanceof`, because a skewed install can hold two\n // copies of the server package. Nor is a statement the\n // database refused: it has aborted the transaction, so the\n // other relations cannot be written anyway, and swallowing it\n // left the next statement to fail with an unrelated 25P02.\n if (e instanceof ApiError || (e as Error)?.name === \"ApiError\" || reachedDatabase(e)) throw e;\n logger.warn(`Failed to update inverse relation '${relation.relationName}'`, { error: e });\n }\n }\n }\n\n\n /**\n * Handle inverse relations with joinPath\n */\n private async updateInverseJoinPathRelation(\n tx: DrizzleClient,\n sourceCollection: CollectionConfig,\n sourceEntityId: string | number,\n targetCollection: CollectionConfig,\n relation: ResolvedVia,\n newValue: unknown\n ) {\n const sourceTableName = getTableName(sourceCollection);\n const targetTableName = getTableName(targetCollection);\n\n // Only a path with exactly one table between the two ends holds links to\n // write. Anything else is a read-only reach, and skipping it is a\n // statement about the relation's shape rather than a failure to resolve\n // one — which is why this is a `return` and the binder below throws.\n const intermediateTables = relation.joinPath\n .map(step => step.table)\n .filter(table => table !== sourceTableName && table !== targetTableName);\n\n if (intermediateTables.length !== 1 || relation.cardinality !== \"many\") return;\n\n try {\n const binding = bindJoinPathJunction(\n this.registry,\n relation.joinPath,\n sourceTableName,\n targetTableName,\n `${sourceCollection.slug}.${relation.relationName}`\n );\n\n // The membership this write asks for. A single value is the\n // one-to-one case and reads as a set of one.\n const elements = Array.isArray(newValue)\n ? relationLinkElements(newValue, relation.relationName, sourceCollection.slug)\n : newValue === null || newValue === undefined\n ? []\n : relationLinkElements([newValue], relation.relationName, sourceCollection.slug);\n\n await applyJunctionMembership(\n tx,\n binding,\n this.parsedId(sourceCollection, sourceEntityId),\n this.parsedLinks(targetCollection, elements, {}, `${sourceCollection.slug}.${relation.relationName}`),\n await this.visibleTargets(sourceCollection, relation, sourceEntityId)\n );\n } catch (error) {\n logger.error(`Failed to update inverse joinPath relation '${relation.relationName}'`, { error: error });\n throw error;\n }\n }\n\n\n /**\n * Handle many-to-many inverse relation updates using junction tables\n */\n private async updateManyToManyInverseRelation(\n tx: DrizzleClient,\n sourceCollection: CollectionConfig,\n sourceEntityId: string | number,\n targetCollection: CollectionConfig,\n relation: ResolvedRelation,\n newValue: unknown,\n junctionInfo: { table: string; sourceColumn: string; targetColumn: string; properties: Properties }\n ) {\n const label = `${sourceCollection.slug}.${relation.relationName}`;\n try {\n const elements = Array.isArray(newValue)\n ? relationLinkElements(newValue, relation.relationName, sourceCollection.slug)\n : [];\n\n await applyJunctionMembership(\n tx,\n bindThroughJunction(this.registry, junctionInfo, label),\n this.parsedId(sourceCollection, sourceEntityId),\n this.parsedLinks(targetCollection, elements, junctionInfo.properties, label),\n await this.visibleTargets(sourceCollection, relation, sourceEntityId)\n );\n } catch (error) {\n logger.error(`Failed to update many-to-many inverse relation '${relation.relationName}'`, { error: error });\n throw error;\n }\n }\n\n\n /**\n * Update one-to-one relations that use joinPath\n */\n async updateJoinPathOneToOneRelations(\n tx: DrizzleClient,\n parentCollection: CollectionConfig,\n parentId: string | number,\n updates: Array<{\n relationKey: string;\n // Only a `via` relation has a join path to write through, and the\n // caller only collects those.\n relation: ResolvedVia;\n newTargetId: string | number | null;\n }>\n ) {\n for (const upd of updates) {\n const { relation, newTargetId } = upd;\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdCol = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n // Determine mapping of columns\n //\n // A join path is authored in SQL terms — `posts.author_id`,\n // `user_profiles.user_id` — because that is what a join is written\n // in. Everything below indexes a Drizzle table or builds a `set`\n // payload with these, and both are keyed by the wire name, so the\n // two are translated here rather than at each of the six uses.\n const { targetFKColName: targetFKColumn, parentSourceColName: parentSourceColumn } =\n this.resolveJoinPathWriteMapping(parentCollection, relation);\n const targetFKColName = fieldKeyForColumn(targetCollection, targetFKColumn);\n const parentSourceColName = fieldKeyForColumn(parentCollection, parentSourceColumn);\n const parentTable = getTableForCollection(parentCollection, this.registry);\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parsedParentIdObj = parseIdValues(parentId, parentPks);\n const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];\n\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n const parentSourceCol = parentTable[parentSourceColName as keyof typeof parentTable] as AnyPgColumn;\n const targetFKCol = targetTable[targetFKColName as keyof typeof targetTable] as AnyPgColumn;\n\n const label = `${parentCollection.slug}.${relation.relationName}`;\n if (!parentSourceCol) {\n throw relationMisconfigured(\n label,\n `its joinPath reads '${parentSourceColName}', which is not a column on ` +\n `'${getTableName(parentCollection)}'`\n );\n }\n if (!targetFKCol) {\n throw relationMisconfigured(\n label,\n `its joinPath writes '${targetFKColName}', which is not a column on ` +\n `'${getTableName(targetCollection)}'`\n );\n }\n\n // Fetch the parent row to obtain the value for parentSourceCol\n const parentRows = await tx\n .select({ val: parentSourceCol })\n .from(parentTable)\n .where(eq(parentIdCol, parsedParentId))\n .limit(1);\n if (parentRows.length === 0) continue;\n const parentFKValue = parentRows[0].val as string | number | null;\n\n if (newTargetId === null || newTargetId === undefined) {\n // Clear any target rows currently linked to this parent via the FK\n if (parentFKValue !== null && parentFKValue !== undefined) {\n await tx.update(targetTable)\n .set({ [targetFKColName]: null })\n .where(eq(targetFKCol, String(parentFKValue)));\n }\n continue;\n }\n\n // Parse the new target id\n const parsedTargetIdObj = parseIdValues(newTargetId, targetPks);\n const parsedTargetId = parsedTargetIdObj[targetIdInfo.fieldName];\n\n // Ensure one-to-one by clearing existing link from any target rows with this parent FK\n if (parentFKValue !== null && parentFKValue !== undefined) {\n await tx.update(targetTable)\n .set({ [targetFKColName]: null })\n .where(eq(targetFKCol, String(parentFKValue)));\n } else {\n // Not a configuration problem: the row is simply missing the\n // value the link joins on, so there is nothing for the target\n // to point at. The `hasMany` writer already refuses this case\n // in the same words rather than saving a row it did not write.\n throw ApiError.badRequest(\n `Cannot write relation '${label}': row '${parentId}' has no value in ` +\n `'${parentSourceColName}', which is the column its joinPath joins on, so there ` +\n \"is nothing for the related row to point at.\",\n \"RELATION_SOURCE_KEY_EMPTY\"\n );\n }\n\n // Now set the FK on the target row\n await tx.update(targetTable)\n .set({ [targetFKColName]: parentFKValue })\n .where(eq(targetIdCol, parsedTargetId));\n }\n }\n\n\n /**\n * Resolve joinPath write mapping for one-to-one relations\n */\n resolveJoinPathWriteMapping(\n parentCollection: CollectionConfig,\n relation: ResolvedVia\n ): { targetFKColName: string; parentSourceColName: string } {\n if (!relation.joinPath || relation.joinPath.length === 0) {\n throw new Error(\"resolveJoinPathWriteMapping requires a joinPath relation\");\n }\n const parentTableName = getTableName(parentCollection);\n const joinPath = relation.joinPath;\n let index = joinPath.length - 1;\n const targetFKColName = DrizzleConditionBuilder.getColumnNamesFromColumns(joinPath[index].on.to)[0];\n let currentFrom = joinPath[index].on.from;\n\n // Back along the path to the step whose `from` is on the parent's\n // table. A `table.column` spelling names that table; a bare column —\n // the form `JoinStep` documents — is on the previous step's table, or\n // the parent's for the first step. Reading the table only from the\n // spelling meant a documented one-to-one `via` (`{ from: \"id\", to:\n // \"product_id\" }`) named no table, matched no step, and refused every\n // write with \"Could not resolve parent source column\".\n let safety = 0;\n while (safety++ < 10) {\n const qualifiedTable = DrizzleConditionBuilder.getTableNamesFromColumns(currentFrom)[0];\n const currentFromTable = qualifiedTable || (index === 0 ? parentTableName : joinPath[index - 1].table);\n if (currentFromTable === parentTableName) {\n break;\n }\n const prevIndex = qualifiedTable\n ? joinPath.findIndex((s) => {\n const to = Array.isArray(s.on.to) ? s.on.to[0] : s.on.to;\n return to === currentFrom;\n })\n : index - 1;\n if (prevIndex < 0) {\n throw new Error(`Could not resolve parent source column for joinPath relation '${relation.relationName}'`);\n }\n index = prevIndex;\n currentFrom = joinPath[index].on.from;\n }\n const parentSourceColName = DrizzleConditionBuilder.getColumnNamesFromColumns(currentFrom)[0];\n return { targetFKColName,\nparentSourceColName };\n }\n\n\n /**\n * Handle junction table creation for many-to-many path-based saves\n */\n async handleJunctionTableCreation(\n tx: DrizzleClient,\n newEntityId: string | number,\n junctionTableInfo: {\n parentCollection: CollectionConfig;\n parentId: string | number;\n // Only a junction-backed relation has a link to write, and the\n // caller only builds this for one — stated here so the body needs\n // no narrowing.\n relation: ResolvedManyToMany;\n relationKey: string;\n }\n ) {\n const { parentCollection, parentId, relation, relationKey } = junctionTableInfo;\n const targetCollection = relation.target();\n\n try {\n const binding = bindThroughJunction(\n this.registry,\n relation.through,\n `${parentCollection.slug}.${relationKey}`\n );\n\n const parsedNewEntityId = this.parsedId(targetCollection, newEntityId);\n\n // Create the junction table entry linking parent to the target row.\n const junctionData = {\n [binding.parentColumn.name]: parentId,\n [binding.targetColumn.name]: parsedNewEntityId\n };\n\n // Idempotent: a link either exists or it does not, so asking for one\n // twice is not an error. This is what lets `PUT parent/id/child/childId`\n // mean \"this row belongs to this parent's set\" — the only way to\n // attach an *existing* row, which previously had none.\n await tx.insert(binding.table).values(junctionData).onConflictDoNothing();\n\n logger.info(`Linked '${relationKey}' ${parsedNewEntityId} to ${parentId}`);\n } catch (error) {\n logger.error(`Failed to create junction table entry for relation '${relationKey}'`, { error: error });\n throw error;\n }\n }\n}\n","import { and, sql, SQL } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { ApiError } from \"@rebasepro/server\";\nimport type { ParsedFieldOp } from \"@rebasepro/server\";\nimport type { ArrayPropertyValidationSchema, NumberPropertyValidationSchema, Properties } from \"@rebasepro/types\";\nimport type { DrizzleClient } from \"../interfaces\";\n\n/**\n * Turning `{ views: { $inc: 1 } }` into the `SET` clause that cannot lose a\n * concurrent write.\n *\n * The contract — which operators exist, what they accept, which property types\n * they are legal on — lives in `@rebasepro/server`. What is here is the part\n * only this driver can know: an `array` property is a native `text[]` on one\n * collection and a jsonb document on the next (see the DDL generator's `array`\n * arm), and the two need entirely different statements. Compiling from the\n * property type alone would emit `array_append` against a jsonb column, which\n * Postgres rejects at plan time with a function-does-not-exist error naming\n * neither the collection nor the field.\n *\n * Every expression is NULL-safe. `NULL + 1` is `NULL`, and a counter that\n * silently empties itself the first time it is incremented from an unset column\n * is worse than one that never worked.\n *\n * What an operation produces answers to the property's declared bounds, and\n * that check is compiled here too, as conditions for the same UPDATE — see\n * {@link compileFieldOpBounds}.\n *\n * @module\n */\n\n/** What the column is, as Postgres sees it: `text[]`, `jsonb`, `integer`, … */\nfunction sqlTypeOf(column: AnyPgColumn): string {\n try {\n return String(column.getSQLType()).toLowerCase();\n } catch {\n return \"\";\n }\n}\n\nfunction isNativeArray(type: string): boolean {\n return type.endsWith(\"[]\") || type.endsWith(\" array\");\n}\n\n/** A `json` column needs the result cast back; a `jsonb` one does not. */\nfunction isPlainJson(type: string): boolean {\n return type === \"json\";\n}\n\nfunction asList(operand: unknown): unknown[] {\n return Array.isArray(operand) ? operand : [operand];\n}\n\n/**\n * The expression one operation compiles to, or a 400 saying why it cannot.\n *\n * `field` is the property key; it is already known to be a column of `table`\n * (the caller checks that against the same table it builds the statement from).\n */\nexport function compileFieldOp(\n table: PgTable,\n field: string,\n op: ParsedFieldOp,\n context: { collectionPath: string }\n): SQL {\n const column = table[field as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n throw ApiError.badRequest(\n `'${field}' is not a column of \"${context.collectionPath}\", so ${op.operator} has nothing to apply to.`,\n \"INVALID_FIELD_OPERATION\"\n );\n }\n const type = sqlTypeOf(column);\n\n switch (op.operator) {\n case \"$inc\": {\n // COALESCE, not a bare `+`: an unset counter is NULL, and NULL + 1\n // is NULL — so the first increment of a column that has never been\n // written would erase it rather than start it at 1.\n return sql`COALESCE(${column}, 0) + ${op.operand as number}`;\n }\n\n case \"$push\": {\n const values = asList(op.operand);\n if (isNativeArray(type)) {\n // Folded with array_append rather than concatenating a literal:\n // the element type is inferred from the column, so the same\n // code serves text[], integer[] and numeric[] without the\n // caller (or this file) having to name the type.\n let expression: SQL = sql`${column}`;\n for (const value of values) {\n expression = sql`array_append(${expression}, ${value})`;\n }\n return expression;\n }\n if (type.startsWith(\"json\")) {\n const appended = sql`COALESCE(${column}::jsonb, '[]'::jsonb) || ${JSON.stringify(values)}::jsonb`;\n return isPlainJson(type) ? sql`(${appended})::json` : appended;\n }\n throw unsupportedColumn(field, op.operator, type, context);\n }\n\n case \"$pull\": {\n const values = asList(op.operand);\n if (isNativeArray(type)) {\n let expression: SQL = sql`${column}`;\n for (const value of values) {\n expression = sql`array_remove(${expression}, ${value})`;\n }\n return expression;\n }\n if (type.startsWith(\"json\")) {\n // jsonb has no array_remove. Re-aggregating the elements that\n // survive is the standard form, and COALESCE around it is what\n // keeps a column that loses its last element as `[]` rather\n // than NULL — jsonb_agg over an empty set is NULL.\n const removals = sql`${JSON.stringify(values)}::jsonb`;\n const filtered = sql`COALESCE((\n SELECT jsonb_agg(element)\n FROM jsonb_array_elements(COALESCE(${column}::jsonb, '[]'::jsonb)) AS element\n WHERE NOT (element IN (SELECT jsonb_array_elements(${removals})))\n ), '[]'::jsonb)`;\n return isPlainJson(type) ? sql`(${filtered})::json` : filtered;\n }\n throw unsupportedColumn(field, op.operator, type, context);\n }\n\n case \"$merge\": {\n if (!type.startsWith(\"json\")) throw unsupportedColumn(field, op.operator, type, context);\n // `||` on jsonb is a shallow merge, which is what $merge promises:\n // a nested object replaces rather than merges, and saying so is\n // better than a deep merge nobody can predict the result of.\n const merged = sql`COALESCE(${column}::jsonb, '{}'::jsonb) || ${JSON.stringify(op.operand)}::jsonb`;\n return isPlainJson(type) ? sql`(${merged})::json` : merged;\n }\n }\n}\n\nfunction unsupportedColumn(\n field: string,\n operator: string,\n type: string,\n context: { collectionPath: string }\n): ApiError {\n return ApiError.badRequest(\n `${operator} cannot be applied to '${field}' on \"${context.collectionPath}\": the column is ` +\n `${type || \"of an unknown type\"}, and ${operator} needs ` +\n (operator === \"$merge\" ? \"a json or jsonb column.\" : \"an array or json column.\"),\n \"INVALID_FIELD_OPERATION\"\n );\n}\n\n/** Compile a whole map of operations into a `SET`-ready object. */\nexport function compileFieldOps(\n table: PgTable,\n fieldOps: Record<string, ParsedFieldOp>,\n context: { collectionPath: string }\n): Record<string, SQL> {\n const compiled: Record<string, SQL> = {};\n for (const [field, op] of Object.entries(fieldOps)) {\n compiled[field] = compileFieldOp(table, field, op, context);\n }\n return compiled;\n}\n\n/**\n * One declared bound an operation's result has to stay inside.\n *\n * `code` and `message` are what a plain value breaking the same rule reports\n * (see `write-validation.ts`), so `{ stock: { $inc: -1000 } }` and\n * `{ stock: -5 }` are refused with one answer.\n */\nexport interface FieldOpBound {\n /** The property key. */\n field: string;\n code: \"min\" | \"max\" | \"more_than\" | \"less_than\" | \"positive\" | \"negative\" | \"min_items\" | \"max_items\";\n /**\n * TRUE when the value the operation writes keeps to the rule, evaluated\n * against the row the statement is changing. Never NULL: an unset column\n * reads as `0` or empty wherever the operation itself reads it so, and an\n * array the operation leaves unset has no size for a bound to judge.\n */\n holds: SQL;\n message: string;\n}\n\n/**\n * The declared bounds each operation's *result* must satisfy, as conditions\n * for the UPDATE that applies it.\n *\n * `{ stock: -5 }` on `stock: { validation: { min: 0 } }` is refused before the\n * statement is built, because the value is in the request. What\n * `{ stock: { $inc: -1000 } }` produces depends on the stored row, which the\n * request never sees, so it can only be judged where the row is: in the WHERE\n * of the UPDATE that does the arithmetic, over the row that statement holds\n * locked. Reading the row first and judging the sum would reopen the race the\n * operation exists to close — two decrements of a stock of 1 would each read\n * `1`, each find room for one more, and both go through. A condition on the\n * UPDATE cannot be raced that way: under READ COMMITTED an UPDATE blocked on a\n * concurrent writer re-evaluates its WHERE against the row that writer\n * committed, so the second decrement sees `0 - 1` and matches nothing.\n *\n * The bound is the whole declared range, applied to the result the way it\n * applies to a value: a number's `min`, `max`, `moreThan`, `lessThan`,\n * `positive` and `negative`, an array's `min` and `max` items. `integer` is not\n * here: a whole increment of a whole number stays whole, and the request\n * already refuses a fractional one. `$merge` has no bounds to hold.\n */\nexport function compileFieldOpBounds(\n table: PgTable,\n fieldOps: Record<string, ParsedFieldOp>,\n properties: Properties,\n context: { collectionPath: string }\n): FieldOpBound[] {\n const bounds: FieldOpBound[] = [];\n for (const [field, op] of Object.entries(fieldOps)) {\n const property = properties[field];\n if (!property?.validation) continue;\n if (property.type === \"number\" && op.operator === \"$inc\" && typeof op.operand === \"number\") {\n const result = compileFieldOp(table, field, op, context);\n bounds.push(...numberBounds(field, property.validation, result, op.operand));\n } else if (property.type === \"array\" && (op.operator === \"$push\" || op.operator === \"$pull\")) {\n const column = table[field as keyof typeof table] as AnyPgColumn | undefined;\n const result = compileFieldOp(table, field, op, context);\n // A native array is counted with `cardinality` (not `array_length`,\n // which is NULL for an empty array); a json document with\n // `jsonb_array_length`, through jsonb so one function serves both\n // `json` and `jsonb` columns.\n const size = column && isNativeArray(sqlTypeOf(column))\n ? sql`cardinality(${result})`\n : sql`jsonb_array_length((${result})::jsonb)`;\n bounds.push(...arrayBounds(field, property.validation, size, op.operator));\n }\n }\n return bounds;\n}\n\nfunction numberBounds(\n field: string,\n rules: NumberPropertyValidationSchema,\n result: SQL,\n operand: number\n): FieldOpBound[] {\n // The bound is a parameter cast to numeric rather than left to inference:\n // Postgres would type it from the column, and `min: 0.5` on an integer\n // column is then a failed cast rather than a comparison.\n const by = `$inc by ${operand}`;\n const bounds: FieldOpBound[] = [];\n if (rules.min !== undefined) {\n bounds.push({ field, code: \"min\", holds: sql`(${result}) >= ${rules.min}::numeric`, message: `'${field}' must be at least ${rules.min}, and ${by} would take it below that.` });\n }\n if (rules.max !== undefined) {\n bounds.push({ field, code: \"max\", holds: sql`(${result}) <= ${rules.max}::numeric`, message: `'${field}' must be at most ${rules.max}, and ${by} would take it above that.` });\n }\n if (rules.moreThan !== undefined) {\n bounds.push({ field, code: \"more_than\", holds: sql`(${result}) > ${rules.moreThan}::numeric`, message: `'${field}' must be greater than ${rules.moreThan}, and ${by} would take it to ${rules.moreThan} or below.` });\n }\n if (rules.lessThan !== undefined) {\n bounds.push({ field, code: \"less_than\", holds: sql`(${result}) < ${rules.lessThan}::numeric`, message: `'${field}' must be less than ${rules.lessThan}, and ${by} would take it to ${rules.lessThan} or above.` });\n }\n if (rules.positive) {\n bounds.push({ field, code: \"positive\", holds: sql`(${result}) > 0`, message: `'${field}' must be positive, and ${by} would take it to 0 or below.` });\n }\n if (rules.negative) {\n bounds.push({ field, code: \"negative\", holds: sql`(${result}) < 0`, message: `'${field}' must be negative, and ${by} would take it to 0 or above.` });\n }\n return bounds;\n}\n\nfunction arrayBounds(\n field: string,\n rules: ArrayPropertyValidationSchema,\n size: SQL,\n operator: \"$push\" | \"$pull\"\n): FieldOpBound[] {\n const items = (n: number) => `${n} item${n === 1 ? \"\" : \"s\"}`;\n // COALESCE to TRUE: a `$pull` from an unset native array leaves it unset,\n // and a range has nothing to say about a value that is not there — the\n // same rule a plain `null` gets.\n const bounds: FieldOpBound[] = [];\n if (rules.min !== undefined) {\n bounds.push({ field, code: \"min_items\", holds: sql`COALESCE(${size} >= ${rules.min}::numeric, TRUE)`, message: `'${field}' must have at least ${items(rules.min)}, and ${operator} would leave it with fewer.` });\n }\n if (rules.max !== undefined) {\n bounds.push({ field, code: \"max_items\", holds: sql`COALESCE(${size} <= ${rules.max}::numeric, TRUE)`, message: `'${field}' must have at most ${items(rules.max)}, and ${operator} would leave it with more.` });\n }\n return bounds;\n}\n\n/**\n * Which bounds the row, as it now stands, would break — for an UPDATE that\n * carried bounds and matched nothing.\n *\n * A guarded UPDATE matching zero rows means one of three things: the row is not\n * there for this caller (404), a row-level security policy refused the write\n * (403), or the operation's result broke a bound (400). The first two are\n * `explainZeroRowWrite`'s; this answers the third, by evaluating each bound\n * against the row over the same handle and the same key conditions. An empty\n * answer — the row is not visible, or every bound holds — leaves the decision to\n * `explainZeroRowWrite`.\n *\n * It is a second statement, so the row can change in between. A row deleted in\n * between is a 404 here as it would have been anyway. A row changed so that the\n * operation now fits reads as \"every bound holds\" and falls through to the\n * row-level-security answer; the write was refused either way, and the\n * refusal is never turned into a write.\n *\n * Only booleans come back — whether each bound holds — never the stored value.\n */\nexport async function brokenFieldOpBounds(\n handle: DrizzleClient,\n table: PgTable,\n conditions: SQL[],\n bounds: FieldOpBound[]\n): Promise<FieldOpBound[]> {\n if (bounds.length === 0) return [];\n const selection: Record<string, SQL<boolean>> = {};\n bounds.forEach((bound, index) => {\n selection[`bound${index}`] = sql<boolean>`(${bound.holds})`;\n });\n const rows = await handle.select(selection).from(table).where(and(...conditions)).limit(1);\n const row = rows[0];\n if (!row) return [];\n return bounds.filter((_bound, index) => row[`bound${index}`] === false);\n}\n\n/** The 400 a write gets when an operation's result breaks a bound. */\nexport function fieldOpBoundsError(collectionSlug: string, broken: FieldOpBound[]): ApiError {\n const violations = broken.map(({ field, code, message }) => ({ field, code, message }));\n const messages = violations.map(violation => violation.message);\n return ApiError.badRequest(messages.join(\" \"), \"VALIDATION_CONSTRAINT\", {\n collection: collectionSlug,\n violations,\n messages\n });\n}\n","/**\n * How deep writes started by hooks may nest — and a refusal past that.\n *\n * A hook's `context.data` write runs the target collection's hooks too, which\n * is what makes a hook that writes its own collection recurse. Unconditional,\n * it never ends: every level is one short statement, so `statement_timeout`\n * never fires, and a request held its transaction and a row lock while its\n * `afterSave` ran tens of thousands of times — long after the client had given\n * up — until the process was killed.\n *\n * Each `save`/`delete` runs as a frame in an async-local chain; a write whose\n * chain is deeper than {@link MAX_WRITE_DEPTH} is refused, naming the hook that\n * started it and the collection it wrote. Siblings — the rows of a bulk write,\n * two writes one after another in a hook — are not nested and never add up.\n */\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { ApiError } from \"@rebasepro/server\";\n\n/**\n * Deep enough for any chain that ends (a hook writing another collection whose\n * hook writes a third is depth 3), shallow enough that a loop stops at once.\n */\nexport const MAX_WRITE_DEPTH = 16;\n\n/** One write in progress, and which of its hooks is running. */\nexport interface WriteFrame {\n readonly path: string;\n readonly operation: \"save\" | \"delete\";\n readonly depth: number;\n readonly parent: WriteFrame | undefined;\n /** The hook running right now — what a nested write was started from. */\n stage: string | undefined;\n}\n\n/**\n * On `globalThis`, like the write-transaction scope beside it: a nested write\n * can reach a second copy of this package (a `dataAsAdmin` call from a hook),\n * and the chain has to be one chain.\n */\nconst FRAME_SLOT = Symbol.for(\"rebase.postgres.writeFrame\");\ntype GlobalWithFrames = typeof globalThis & { [FRAME_SLOT]?: AsyncLocalStorage<WriteFrame> };\nconst frames: AsyncLocalStorage<WriteFrame> =\n ((globalThis as GlobalWithFrames)[FRAME_SLOT] ??= new AsyncLocalStorage<WriteFrame>());\n\n/** Run one write as a frame, refusing it when the chain is already too deep. */\nexport function inWriteFrame<T>(\n path: string,\n operation: \"save\" | \"delete\",\n fn: (frame: WriteFrame) => Promise<T>\n): Promise<T> {\n const parent = frames.getStore();\n const frame: WriteFrame = { path, operation, depth: (parent?.depth ?? 0) + 1, parent, stage: undefined };\n if (frame.depth > MAX_WRITE_DEPTH) {\n return Promise.reject(recursionError(frame));\n }\n return frames.run(frame, () => fn(frame));\n}\n\n/**\n * How deep in hook-started writes the code running now is: 0 outside any\n * write, 1 inside a write a caller started, 2 or more inside a write a hook of\n * another write started (`context.data`).\n */\nexport function currentWriteDepth(): number {\n return frames.getStore()?.depth ?? 0;\n}\n\nfunction recursionError(frame: WriteFrame): ApiError {\n const starter = frame.parent;\n const hook = starter?.stage ?? \"a hook\";\n // The distinct hook → write steps in the chain, innermost first: for the\n // usual loop that is one line, `\"orders\" afterSave → save \"orders\"`.\n const steps: string[] = [];\n for (let f: WriteFrame | undefined = frame; f?.parent; f = f.parent) {\n const step = `\"${f.parent.path}\" ${f.parent.stage ?? \"?\"} → ${f.operation} \"${f.path}\"`;\n if (!steps.includes(step)) steps.push(step);\n }\n return new ApiError(\n 500,\n \"CALLBACK_RECURSION\",\n `Writes started by hooks nested more than ${MAX_WRITE_DEPTH} deep, so this one was refused and the whole write ` +\n `rolled back. The ${hook} hook of \"${starter?.path ?? frame.path}\" ${frame.operation}s \"${frame.path}\" through ` +\n \"`context.data`, which runs that collection's hooks again, and nothing in the chain stops it: \" +\n `${steps.join(\"; \")}. Make the hook's write conditional — act on \\`status === \"new\"\\` only, or skip it ` +\n \"when `values` already hold what it would write.\",\n { chain: steps, depth: MAX_WRITE_DEPTH }\n );\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { sql } from \"drizzle-orm\";\nimport { ApiError, logger, type AmbientTransaction } from \"@rebasepro/server\";\nimport type { RebaseCallContext } from \"@rebasepro/types\";\nimport type { DrizzleClient } from \"../interfaces\";\nimport { extractPgError } from \"../utils/pg-error-utils\";\n\ntype RunSql = (sqlText: string, params?: unknown[]) => Promise<Record<string, unknown>[]>;\n\n/**\n * The write transaction a request is running inside, published to whatever\n * the request's callbacks call into.\n *\n * `AuthenticatedPostgresBackendDriver.withTransaction` opens one of these for\n * every read-write transaction. A collection callback runs inside that\n * transaction, and what it hands off — a job, a queue message, a topic event,\n * a history entry — has to commit with the write or not at all. Those are\n * written by code that is not handed the transaction (the job store lives in\n * `@rebasepro/server` and knows no driver), so the transaction is found here\n * instead: through `AsyncLocalStorage`, and through the resolver\n * `@rebasepro/server` reads.\n *\n * Only write transactions. A read opens its own `READ ONLY` transaction, where\n * an insert would fail with 25006, and nothing a read does is undone by\n * anything — so inside a read the scope is explicitly empty and a queued job\n * commits on its own, as it always did.\n */\nexport class WriteTransactionScope implements AmbientTransaction {\n private run?: RunSql;\n private open = true;\n private readonly inflight = new Set<Promise<unknown>>();\n private readonly commitHooks: Array<() => void> = [];\n /** The first tracked statement that failed, whether or not its caller caught it. */\n private failure?: unknown;\n private readonly settledHooks: Array<(context: RebaseCallContext) => Promise<void>> = [];\n /** Whether application code (a hook) has run on this transaction. See {@link noteApplicationCode}. */\n private applicationCodeRan = false;\n private settledContext?: RebaseCallContext;\n\n /** The transaction handle, for statements in this package that are already written against drizzle. */\n tx?: DrizzleClient;\n\n /** Attach the transaction, once it has begun. */\n bind(tx: DrizzleClient, run: RunSql): void {\n this.tx = tx;\n this.run = run;\n }\n\n /** Bound, and not yet committing. */\n get active(): boolean {\n return this.open && this.run !== undefined;\n }\n\n exec(sqlText: string, params?: unknown[]): Promise<Record<string, unknown>[]> {\n if (!this.active) {\n return Promise.reject(new Error(\"This write's transaction is no longer open to new statements.\"));\n }\n return this.track(this.run!(sqlText, params));\n }\n\n /**\n * Count a statement as part of the transaction, so the commit waits for it.\n * A callback that queues work without awaiting it has still asked for that\n * work to be part of the write.\n *\n * Settled into a native promise first, and only that one is handed back:\n * drizzle's query builders re-run their statement on every `.then()`, so\n * observing one here and awaiting it at the call site would insert twice.\n */\n track<T>(statement: PromiseLike<T>): Promise<T> {\n const settled = Promise.resolve(statement);\n this.inflight.add(settled);\n const done = () => { this.inflight.delete(settled); };\n settled.then(done, (error: unknown) => {\n this.failure ??= error;\n done();\n });\n return settled;\n }\n\n afterCommit(fn: () => void): void {\n this.commitHooks.push(fn);\n }\n\n /**\n * Wait for every tracked statement, then close to new ones — called as the\n * last thing inside the transaction, before its commit. A statement that\n * arrives after this cannot be part of the write, so it runs on its own\n * connection, as if outside any write.\n */\n async settle(): Promise<void> {\n while (this.inflight.size > 0) {\n await Promise.allSettled([...this.inflight]);\n }\n this.open = false;\n }\n\n /**\n * Refuse to commit a transaction a failed statement has aborted.\n *\n * Once any statement fails, Postgres aborts the whole transaction, and\n * catching the error in JavaScript does not undo that. The COMMIT that\n * follows is answered with the tag `ROLLBACK` and no error, so the driver\n * resolves it: the write reported success — 200, webhooks, realtime,\n * the idempotency record — while nothing was stored. A callback that\n * wrapped a lookup or a job enqueue in `try/catch` was enough.\n *\n * So one statement is asked of the transaction before its commit. On an\n * aborted one it fails with `25P02`, and the write is refused and rolled\n * back instead. The database refusing a `context.data` create, update or\n * delete is not caught by this: each of those statements runs in a\n * savepoint, is undone on its own, and leaves the transaction usable —\n * which is what makes catching one safe. (Deletes ran without one, so a\n * caught refused delete landed here, under a message saying catching it\n * was safe.)\n */\n async assertCommittable(): Promise<void> {\n if (!this.tx) return;\n // Only application code can catch a failed statement and carry on:\n // the framework's own catches rethrow anything that reached the\n // database. With no hook run and no tracked statement failed, the\n // transaction cannot be aborted, and asking it costs a round trip on\n // every write for nothing.\n if (!this.applicationCodeRan && this.failure === undefined) return;\n try {\n await this.tx.execute(sql`SELECT 1`);\n } catch (error) {\n if (extractPgError(error)?.code !== \"25P02\") throw error;\n logger.error(\n \"[transaction] A statement on this write's transaction failed and its error was caught; the write was rolled back\",\n { error: this.failure ?? error }\n );\n const refusal = new ApiError(\n 500,\n \"TRANSACTION_ABORTED\",\n \"A statement on this write's transaction failed and its error was caught. That aborts the \" +\n \"transaction in Postgres, so the write was rolled back and nothing was stored. Only a \" +\n \"`context.data` create, update or delete the database refused is undone on its own and may \" +\n \"be caught; any other failed statement — a read (including the one an update or delete makes \" +\n \"to find its row), a job enqueue — has to be let through.\"\n );\n refusal.cause = this.failure ?? error;\n throw refusal;\n }\n }\n\n /**\n * Application code — a collection, global or property hook, a\n * `beforeQuery` — is about to run on this transaction, so it may catch a\n * failed statement and leave the transaction aborted: the commit then has\n * to ask ({@link assertCommittable}).\n */\n noteApplicationCode(): void {\n this.applicationCodeRan = true;\n }\n\n /**\n * What a hook deferred with {@link afterSettled} is handed as `context`:\n * the same caller, on a driver where every call is a transaction of its\n * own. The write's transaction is gone by the time the hook runs, so a\n * context bound to it would be bound to a connection back in the pool.\n */\n setSettledContext(context: RebaseCallContext): void {\n this.settledContext = context;\n }\n\n /**\n * Run `fn` once this write's transaction is over, whichever way it went,\n * outside it — for what a failure hook hands off (a job, a queue message,\n * a webhook), which must not ride the transaction the failure rolls back.\n * `false` when this scope has no context to hand it; the caller runs it\n * itself then.\n */\n afterSettled(fn: (context: RebaseCallContext) => Promise<void>): boolean {\n if (!this.settledContext) return false;\n this.settledHooks.push(fn);\n return true;\n }\n\n /** The transaction committed or rolled back: run what was waiting for either. */\n async settled(): Promise<void> {\n const context = this.settledContext;\n if (!context) return;\n for (const hook of this.settledHooks.splice(0)) {\n try {\n await storage.run(undefined, () => hook(context));\n } catch (error) {\n logger.error(\"[transaction] A hook deferred to the end of the write threw\", { error });\n }\n }\n }\n\n /**\n * The transaction is over, whichever way it went. Nothing may reach it now:\n * its client is back in the pool, and a callback's detached promise still\n * carries this scope in its async context.\n */\n close(): void {\n this.open = false;\n }\n\n /** The transaction committed: run what was waiting for that, outside any write. */\n committed(): void {\n for (const hook of this.commitHooks.splice(0)) {\n try {\n storage.run(undefined, hook);\n } catch (error) {\n logger.error(\"[transaction] An after-commit hook threw; the write had already committed\", { error });\n }\n }\n }\n}\n\n/**\n * On `globalThis`, like the singleton, so two copies of this package share one\n * store: the copy that opened a transaction and the copy whose code asks for it\n * are not guaranteed to be the same.\n */\nconst STORAGE_SLOT = Symbol.for(\"rebase.postgres.writeTransactionScope\");\nconst storage: AsyncLocalStorage<WriteTransactionScope | undefined> =\n ((globalThis as Record<symbol, unknown>)[STORAGE_SLOT] ??=\n new AsyncLocalStorage<WriteTransactionScope | undefined>()) as AsyncLocalStorage<WriteTransactionScope | undefined>;\n\n/** The open write transaction the caller is inside, if any. */\nexport function currentWriteScope(): WriteTransactionScope | undefined {\n const scope = storage.getStore();\n return scope?.active ? scope : undefined;\n}\n\n/**\n * Run `fn` with `scope` as the current write transaction. `undefined` clears\n * it: a read nested inside a write's callback is not part of that write.\n */\nexport function runInWriteScope<T>(scope: WriteTransactionScope | undefined, fn: () => Promise<T>): Promise<T> {\n return storage.run(scope, fn);\n}\n\n/**\n * Publish the scope to `@rebasepro/server`'s job store and webhook dispatcher.\n *\n * Written to the slot directly, not through `setAmbientTransactionResolver`:\n * the runtime image supplies `@rebasepro/server` while a bundle brings this\n * package, and importing a function an older image does not export would fail\n * to link and stop the boot. Through the slot, an older server simply never\n * reads it. The key is frozen — `@rebasepro/server/src/db/ambient-transaction.ts`\n * reads the same one, and `write-transaction-scope-e2e.test.ts` fails if they\n * drift apart.\n */\n(globalThis as Record<symbol, unknown>)[Symbol.for(\"rebase.server.ambientTransactionResolver\")] = currentWriteScope;\n","import { eq, and, isNotNull, isNull, sql, SQL } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\n// import { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { CollectionConfig, JUNCTION_PIVOT_KEY, Properties, ResolvedRelation, type ResolvedManyToMany, isManyToMany, hasForeignKeyOnTarget } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { DrizzleConditionBuilder } from \"../utils/drizzle-conditions\";\nimport {\n assertWritableColumns,\n getCollectionByPath,\n getTableForCollection,\n getPrimaryKeys,\n idCanAddressTable,\n requirePrimaryKeys,\n rowIdentityCondition,\n parseIdValues,\n buildCompositeId\n} from \"./collection-helpers\";\nimport { sanitizeAndConvertDates, serializeDataToServer } from \"../data-transformer\";\nimport { RelationService } from \"./RelationService\";\nimport { beforeQueryCondition, type ReadCallContextProvider } from \"./read-scope\";\nimport { RelationWriteService } from \"./RelationWriteService\";\nimport { FetchService } from \"./FetchService\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport {\n assertWritableThrough,\n isJunctionBackedRelation,\n isNestedPath,\n resolveNestedPath,\n type NestedPathHop\n} from \"./nested-path\";\nimport { ApiError, logger, splitFieldOps } from \"@rebasepro/server\";\nimport { brokenFieldOpBounds, compileFieldOpBounds, compileFieldOps, fieldOpBoundsError } from \"./field-op-sql\";\nimport { extractPgError, extractCauseMessage, pgErrorToFriendlyMessage, isRowLevelSecurityDenial } from \"../utils/pg-error-utils\";\nimport { explainZeroRowWrite } from \"./write-denial\";\nimport { currentWriteDepth } from \"./write-depth\";\nimport { currentWriteScope } from \"./write-transaction-scope\";\nimport { resolveSoftDelete, rowInTrashError, softDeleteColumn } from \"./soft-delete\";\n\n/**\n * The top-level properties whose value is stamped once, when the row is\n * created: `on_create` dates and `user_on_create` identities.\n *\n * What they hold is a fact about the row's creation, so a write to a row that\n * already exists has nothing to say about them — an update, and the\n * conflict-update of an upsert that met a stored row. Top level only: a stamp\n * nested inside a `map` is part of that map's value, not a column a write can\n * leave out.\n */\nexport function createStampKeys(properties: Properties | undefined): string[] {\n const keys: string[] = [];\n for (const [key, property] of Object.entries(properties ?? {})) {\n if ((property?.type === \"date\" && property.autoValue === \"on_create\")\n || (property?.type === \"string\" && property.autoValue === \"user_on_create\")) {\n keys.push(key);\n }\n }\n return keys;\n}\n\n/** How {@link PersistService.save} writes a row that may already be stored. */\nexport interface PersistSaveOptions {\n /** INSERT ... ON CONFLICT DO UPDATE. See `SaveProps.upsert`. */\n upsert?: boolean;\n /** The conflict target, instead of the primary key. See `SaveProps.onConflict`. */\n onConflict?: readonly string[];\n /**\n * Keys of `values` that belong to the INSERT alone: what the create\n * pipeline filled in rather than what the caller wrote — a declared\n * `defaultValue`, a tenant stamp. When an upsert's INSERT meets a stored\n * row, those columns keep the value they have. The create-time stamps\n * (`on_create`, `user_on_create`) are left out of the conflict-update\n * without being named here.\n */\n insertOnlyKeys?: readonly string[];\n}\n\n/**\n * Run `write` in a transaction of its own — a SAVEPOINT inside a write — or,\n * when nothing could catch its failure and carry on, straight on `db`.\n *\n * The savepoint exists for a write a hook started (`context.data`): a hook may\n * catch the database refusing it, and the write it runs inside must stay\n * usable. A write a caller started directly inside a request's transaction\n * (frame depth 1, an open write scope) has no such catcher — the REST route,\n * the socket handler and the bulk and batch loops all rethrow, and the\n * request's transaction rolls back whole — so its savepoint only cost two\n * round trips per row: 2,000 of a 1,000-row bulk's statements. Outside a\n * request's transaction (`db` is the pool) a real transaction is still opened,\n * so the row and its relation writes land together.\n */\nfunction inOwnTransactionUnlessTopLevel<T>(db: DrizzleClient, write: (tx: DrizzleClient) => Promise<T>): Promise<T> {\n // `rollback` is what a transaction handle has and the pool does not.\n if (\"rollback\" in db && currentWriteDepth() === 1 && currentWriteScope()?.tx !== undefined) return write(db);\n return db.transaction(tx => write(tx));\n}\n\n/**\n * Service for handling all row write operations.\n * Handles saving, deleting, and updating rows.\n */\nexport class PersistService {\n /** Reads: whether a row is under a parent, the key a link joins on. */\n private relationService: RelationService;\n /** Writes: junction membership, foreign-key stamping, links. */\n private relationWrites: RelationWriteService;\n private fetchService: FetchService;\n\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) {\n this.relationService = new RelationService(db, registry, callContext);\n this.relationWrites = new RelationWriteService(db, registry, callContext);\n this.fetchService = new FetchService(db, registry, callContext);\n }\n\n\n /**\n * Set the columns of one many-to-many link, without touching the membership.\n *\n * `collectionPath` is the nested address — `posts/1/tags` — and `targetId`\n * the row on the far side, so this is `PATCH posts/1/tags/5` with a\n * `_pivot` body. Separate from {@link save} because the two mean different\n * things at the same URL: a body of the target's own columns edits the tag,\n * a body of `_pivot` edits the *link* to it, and conflating them would let\n * a caller who meant one silently perform the other.\n */\n async updateRelationPivot(\n collectionPath: string,\n targetId: string | number,\n pivot: Record<string, unknown>\n ): Promise<void> {\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n if (!hop) {\n throw ApiError.badRequest(\n `\"${collectionPath}\" is not a relation on a row, so there is no link to update. ` +\n `A \\`${JUNCTION_PIVOT_KEY}\\` write addresses \\`<collection>/<id>/<relation>/<targetId>\\`.`,\n \"VALIDATION_UNKNOWN_FIELDS\",\n { path: collectionPath }\n );\n }\n assertWritableThrough(hop, collectionPath);\n\n // Membership first, for the same reason `delete` establishes it: the\n // update below matches on both keys, so \"no such link\" and \"a policy\n // refused the write\" would otherwise be the same zero rows.\n if (!await this.relationService.isRelated(hop, targetId)) {\n throw ApiError.notFound(`No row \"${targetId}\" in \"${collectionPath}\" to update the link of.`);\n }\n\n // In a transaction of its own — a savepoint inside a write — like every\n // write here; see `delete`.\n await this.db.transaction(tx => this.relationWrites.updateRelationPivot(tx, hop, targetId, pivot));\n }\n\n\n /**\n * Explain a row write that matched nothing — see {@link explainZeroRowWrite}\n * for why a zero-row write cannot be reported as success.\n */\n private explainZeroRowWrite(\n handle: DrizzleClient,\n table: PgTable,\n conditions: SQL[],\n collectionPath: string,\n id: string | number,\n operation: \"update\" | \"delete\"\n ): Promise<ApiError> {\n return explainZeroRowWrite(\n handle,\n table,\n conditions,\n `Not allowed to ${operation} \"${id}\" in \"${collectionPath}\": a row-level security policy rejected the write.`,\n `No row \"${id}\" in \"${collectionPath}\" to ${operation}.`\n );\n }\n\n /**\n * Lock one row against every other write to it until this transaction\n * ends: `FOR NO KEY UPDATE`, the lock an UPDATE of it takes anyway.\n *\n * For a conditional write (`If-Match`): taken before the row is read and\n * compared, so a write that lands meanwhile is either already committed and\n * read, or waits for this one. Without it two writes carrying the same tag\n * both read the old row, both passed, and the second overwrote the first.\n *\n * Under row-level security the lock reads through the caller's UPDATE\n * policies as well as their SELECT ones, so a row they may read but not\n * update is simply not locked — and their write is refused by the database\n * all the same. A nested address locks the row it ends in.\n */\n async lockRow(collectionPath: string, id: string | number): Promise<void> {\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n const collection = hop ? hop.targetCollection : getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n // An id the key cannot hold names no row, and the read after this\n // answers that as a 404 — sent to Postgres it is a 22P02 that aborts\n // the write's transaction instead.\n if (!idCanAddressTable(id, table, idInfoArray)) return;\n const condition = rowIdentityCondition(table, idInfoArray, id, collectionPath);\n await this.db.select({ locked: sql`1` }).from(table).where(condition).for(\"no key update\");\n }\n\n /**\n * Delete an row by ID\n */\n async delete(collectionPath: string, id: string | number, _databaseId?: string): Promise<void> {\n // A nested address deletes *through* a relation, and what that removes\n // depends on who owns the target row.\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n if (hop) {\n assertWritableThrough(hop, collectionPath);\n\n if (!await this.relationService.isRelated(hop, id)) {\n throw ApiError.notFound(`No row \"${id}\" in \"${collectionPath}\" to delete.`);\n }\n\n if (isJunctionBackedRelation(hop.relation)) {\n // Shared target: drop the link, not the row.\n if (!isManyToMany(hop.relation)) {\n throw ApiError.badRequest(\n `\"${collectionPath}\" reaches '${hop.targetCollection.slug}' through a multi-hop joinPath, ` +\n `so there is no single link to remove. Delete the row at \"${hop.targetCollection.slug}\" ` +\n \"directly if that is what you meant.\",\n \"RELATION_NOT_UNLINKABLE\"\n );\n }\n await this.db.transaction(tx => this.relationWrites.unlinkRelatedEntity(tx, hop, id));\n return;\n }\n // Owned child (inverse FK): deleting the row is the right meaning,\n // and membership above has established it is this parent's child.\n }\n\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n // `requirePrimaryKeys`: with no key there is no condition, and a DELETE\n // without one empties the table.\n const condition = rowIdentityCondition(table, requirePrimaryKeys(collection, this.registry), id, collectionPath);\n\n // In a transaction of its own, as `save` is: inside a write's\n // transaction that is a SAVEPOINT, so a delete the database refuses (a\n // foreign key, a trigger) is undone on its own and leaves the write\n // usable. Without one, a hook that caught a failed\n // `context.data.<c>.delete()` — which the platform says is safe to\n // catch — had aborted the whole transaction, and the write answered\n // TRANSACTION_ABORTED (or a 25P02 from the history entry after it).\n let result;\n try {\n result = await inOwnTransactionUnlessTopLevel(this.db, tx => tx\n .delete(table)\n .where(condition));\n } catch (error: unknown) {\n throw this.toUserFriendlyError(error, collection.slug, collection, \"delete\");\n }\n\n if ((result.rowCount ?? 0) === 0) {\n throw await this.explainZeroRowWrite(this.db, table, [condition], collectionPath, id, \"delete\");\n }\n }\n\n /**\n * Delete all rows from a collection\n */\n async deleteAll(collectionPath: string, _databaseId?: string): Promise<void> {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n await this.db.delete(table);\n }\n\n /**\n * The field on the *target* row that records the parent, for a create under\n * a nested one-to-many path.\n *\n * A **field**, not the column: the value is stamped into the caller's\n * payload, which is keyed by wire names — `authorId`, never the `author_id`\n * the relation names its link by. Stamping the column instead put a key on\n * the payload that no property answers to, and `strictWrites` rejected the\n * request the framework had just written to.\n *\n * Returns `undefined` when the link is not a column at all (a multi-hop\n * `joinPath`), so the caller writes the row without stamping anything.\n *\n * `relation.localKey` is deliberately not consulted: it names a column on\n * the *source* table. Falling back to it here — which is what this used to\n * do, and first — stamped the parent's own foreign key onto the child row.\n */\n private resolveParentForeignKeyColumn(hop: NestedPathHop): string | undefined {\n const { relation } = hop;\n\n switch (relation.kind) {\n case \"hasOne\":\n case \"hasMany\":\n return fieldKeyForColumn(hop.targetCollection, relation.foreignKeyOnTarget);\n\n case \"via\":\n // The link lives in an intermediate table, not in a column on\n // the target — nothing to stamp.\n return relation.joinPath.length === 1\n ? fieldKeyForColumn(\n hop.targetCollection,\n DrizzleConditionBuilder.getColumnNamesFromColumns(relation.joinPath[0].on.to)[0]\n )\n : undefined;\n\n default:\n // `belongsTo` names a column on the *parent*, and `manyToMany`\n // is written as a junction row. Neither is a column here, and\n // `assertWritableThrough` has already rejected the first.\n return undefined;\n }\n }\n\n /**\n * Save an row (create or update)\n *\n * With `options.upsert`, the row is written with INSERT ... ON CONFLICT DO\n * UPDATE rather than a plain UPDATE. That is one statement, so it cannot\n * lose a race the way a read-then-write can, and it does not care whether\n * the row already exists — which is what a re-runnable import needs. The\n * conflict is matched on the primary key unless `options.onConflict` names\n * other columns; see {@link SaveProps.onConflict} for why that matters.\n * When the INSERT meets a stored row, the conflict-update sets what the\n * caller wrote and nothing the create alone decides — see\n * {@link PersistSaveOptions.insertOnlyKeys} — and only on a row the caller's\n * `beforeQuery` scope reaches.\n *\n * `values` may carry field operations (`{ views: { $inc: 1 } }`). They are\n * split out here rather than at the REST boundary because every request\n * boundary — HTTP, the WebSocket, in-process `context.data` — reaches this\n * one method, and a rule applied at one door is a rule the other doors do\n * not have. What they compile to is `field-op-sql.ts`.\n */\n async save<M extends Record<string, unknown>>(\n collectionPath: string,\n values: Partial<M>,\n id?: string | number,\n databaseId?: string,\n options?: PersistSaveOptions\n ): Promise<Record<string, unknown>> {\n // If saving under a nested relation path, resolve the relation it ends in.\n let effectiveCollectionPath = collectionPath;\n const effectiveValues: Partial<M> = { ...values };\n let junctionTableInfo: { parentCollection: CollectionConfig; parentId: string | number; relation: ResolvedManyToMany; relationKey: string; } | undefined;\n\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n\n if (hop) {\n assertWritableThrough(hop, collectionPath);\n effectiveCollectionPath = hop.targetCollection.slug;\n\n const parentIdForWrite = () => {\n const parentPks = getPrimaryKeys(hop.parentCollection, this.registry);\n return parseIdValues(hop.parentId, parentPks)[parentPks[0].fieldName];\n };\n\n if (isManyToMany(hop.relation)) {\n // A junction path addresses set membership, so a write through it\n // asserts \"this row is in this parent's set\" — on create *and* on\n // update. The junction row is written after the main write below,\n // idempotently, which is what makes `PUT parent/id/child/childId`\n // able to attach a row that already exists.\n //\n // Unlike an owning foreign key, this takes the row from nobody:\n // its other parents keep it. That is why linking is safe here\n // where reparenting (below) is not.\n junctionTableInfo = {\n parentCollection: hop.parentCollection,\n parentId: parentIdForWrite(),\n relation: hop.relation,\n relationKey: hop.relationKey\n };\n } else if (id !== undefined) {\n // Updating an existing row *through* an owning parent. The parent\n // segment is an assertion about where the row already lives, not\n // an instruction to move it there: injecting the FK here silently\n // reparented whatever id was named, so `PUT authors/1/posts/43`\n // stole post 43 from its real author. Check membership instead,\n // and leave the FK to an explicit value in the body.\n if (!await this.relationService.isRelated(hop, id)) {\n throw ApiError.notFound(`No row \"${id}\" in \"${collectionPath}\" to update.`);\n }\n } else {\n // One-to-many create: stamp the parent's key onto the child's FK.\n const targetColumnName = this.resolveParentForeignKeyColumn(hop);\n\n if (targetColumnName) {\n // Not necessarily the id in the path: a link with a\n // `sourceKey` is pointed at a column on the parent row, and\n // stamping the id would create a child that belongs to\n // nobody — a row the read path would never return again.\n const parentKeyValue = hasForeignKeyOnTarget(hop.relation)\n ? await this.relationService.parentKeyValue(hop.parentCollection, hop.relation, hop.parentId)\n : parentIdForWrite();\n\n if (parentKeyValue === undefined) {\n throw ApiError.badRequest(\n `Cannot create under \"${collectionPath}\": the parent row has no value in ` +\n `\\`sourceKey\\`, so the new row has nothing to point at.`\n );\n }\n\n const existingValue = (effectiveValues as Record<string, unknown>)[targetColumnName];\n if (existingValue !== undefined && existingValue !== null && existingValue !== parentKeyValue) {\n logger.warn(`Overriding provided value '${existingValue}' for FK '${targetColumnName}' with path parent key '${parentKeyValue}'.`);\n }\n (effectiveValues as Record<string, unknown>)[targetColumnName] = parentKeyValue;\n }\n }\n }\n\n const collection = getCollectionByPath(effectiveCollectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = getPrimaryKeys(collection, this.registry);\n const primaryKeyFields = idInfoArray.map(info => info.fieldName);\n\n // Build an object mapping required for dynamic returning\n const returningKeys: Record<string, AnyPgColumn> = {};\n idInfoArray.forEach(info => {\n const field = table[info.fieldName as keyof typeof table] as AnyPgColumn;\n if (!field) throw new Error(`Primary key field '${info.fieldName}' not found in table for collection '${effectiveCollectionPath}'`);\n returningKeys[info.fieldName] = field;\n });\n\n // Operations on the stored value, held back from every step below:\n // `serializeDataToServer` would read `{ $inc: 1 }` on a number column\n // as a value to coerce, and `sanitizeAndConvertDates` would hand the\n // marker object to the column as data.\n const { values: plainValues, fieldOps } = splitFieldOps(\n effectiveValues as Record<string, unknown>,\n collection.properties\n );\n const fieldOpKeys = Object.keys(fieldOps);\n if (fieldOpKeys.length > 0) {\n // An operation describes a change to a value that is already\n // stored, so there is nothing for it to act on here. Saying so is\n // the whole point: `views + 1` over a row being inserted is just\n // `1`, and accepting it would make `$inc` mean two different things\n // depending on a race the caller cannot observe.\n if (!id) {\n throw ApiError.badRequest(\n `Field operations (${fieldOpKeys.map(k => `'${k}'`).join(\", \")}) need an existing row to act on, ` +\n `and this write to \"${effectiveCollectionPath}\" is an insert. Send the value itself instead.`,\n \"INVALID_FIELD_OPERATION\"\n );\n }\n if (options?.upsert) {\n throw ApiError.badRequest(\n `Field operations (${fieldOpKeys.map(k => `'${k}'`).join(\", \")}) cannot be combined with an upsert: ` +\n \"an upsert may insert, and there would be no stored value to operate on. \" +\n \"Use an update, or send the value itself.\",\n \"INVALID_FIELD_OPERATION\"\n );\n }\n }\n\n // Separate relations that require special handling\n const relationValues: Record<string, unknown> = {};\n const otherValues: Partial<M> = { ...(plainValues as Partial<M>) };\n const resolvedRelations = resolveCollectionRelations(collection);\n\n for (const key in resolvedRelations) {\n const relation = resolvedRelations[key];\n if (relation && relation.cardinality === \"many\") {\n if (Object.prototype.hasOwnProperty.call(otherValues, key)) {\n relationValues[key] = otherValues[key as keyof M];\n delete otherValues[key as keyof M];\n }\n }\n }\n\n let savedId: string | number;\n /** The row a plain INSERT handed back, already rendered as the read-back would render it. */\n let returnedRow: Record<string, unknown> | undefined;\n try {\n // Transform relations to IDs, then sanitize\n const serializedResult = serializeDataToServer(otherValues as M, collection.properties as Properties, collection, this.registry);\n\n // Extract relation updates from the typed result\n const inverseRelationUpdates = serializedResult.inverseRelationUpdates;\n const joinPathRelationUpdates = serializedResult.joinPathRelationUpdates;\n // Whether the INSERT is the last thing this save writes. Anything\n // written after it — a relation, a junction row — may change the\n // row (a self-reference, a trigger), so then it is read back.\n const rowIsWrittenLast = inverseRelationUpdates.length === 0 && joinPathRelationUpdates.length === 0\n && Object.keys(relationValues).length === 0 && !junctionTableInfo;\n\n const entityData = sanitizeAndConvertDates(serializedResult.scalarData);\n\n // Everything below builds SQL from the table's own column list, so\n // a key that is not a column is silently left out rather than\n // refused. Say so here, while the key is still in hand.\n assertWritableColumns(entityData as Record<string, unknown>, table, effectiveCollectionPath);\n // The same check for the operated columns. They never reach\n // `entityData` — they are SQL, not values — so without this a\n // `$inc` on a property with no column would be dropped from the\n // statement and answered 200, which is the silent-write defect\n // `assertWritableColumns` exists to prevent.\n if (fieldOpKeys.length > 0) {\n assertWritableColumns(\n Object.fromEntries(fieldOpKeys.map(key => [key, null])),\n table,\n effectiveCollectionPath\n );\n }\n\n savedId = await inOwnTransactionUnlessTopLevel(this.db, async (tx) => {\n let currentId: string | number;\n\n if (id && !options?.upsert) {\n // Update existing row\n currentId = id; // `id` is already the formatted composite or singular string\n\n // Apply joinPath one-to-one relation updates BEFORE the main UPDATE.\n // This ensures parentSourceCol reads the pre-update FK value, preventing\n // stale joinPath values from corrupting related rows when an\n // intermediate FK (e.g., author_id) changes in the same save.\n // Example: changing author A→B with stale profile P1 (A's):\n // reads old author_id=A → clears P1.author_id → re-sets P1.author_id=A (no-op).\n if (joinPathRelationUpdates.length > 0) {\n await this.relationWrites.updateJoinPathOneToOneRelations(tx, collection, currentId, joinPathRelationUpdates);\n }\n\n // Only issue an UPDATE if there are scalar columns to set.\n // When the payload contains only relation data, entityData is\n // empty after relation stripping and Drizzle throws \"No values to set\".\n const scalarKeys = Object.keys(entityData as Record<string, unknown>);\n // Compiled here, inside the transaction that will run them:\n // an operation is an expression over the row's current\n // value, so it is only correct while the row is locked by\n // the very statement that reads it.\n const compiledOps = fieldOpKeys.length > 0\n ? compileFieldOps(table, fieldOps, { collectionPath: effectiveCollectionPath })\n : undefined;\n // What each operation produces, held to the property's\n // declared bounds by the same statement — see\n // `compileFieldOpBounds` for why it cannot be a read first.\n const bounds = fieldOpKeys.length > 0\n ? compileFieldOpBounds(table, fieldOps, collection.properties ?? {}, { collectionPath: effectiveCollectionPath })\n : [];\n if (scalarKeys.length > 0 || compiledOps) {\n const updateQuery = tx.update(table).set({\n ...(entityData as Record<string, unknown>),\n ...(compiledOps ?? {})\n });\n const condition = rowIdentityCondition(table, idInfoArray, id, effectiveCollectionPath);\n const updateResult = await updateQuery.where(and(condition, ...bounds.map(bound => bound.holds)));\n\n // Throwing rolls the transaction back, so relation writes\n // already applied above do not survive a rejected update.\n if ((updateResult.rowCount ?? 0) === 0) {\n const broken = await brokenFieldOpBounds(tx, table, [condition], bounds);\n if (broken.length > 0) throw fieldOpBoundsError(collection.slug, broken);\n throw await this.explainZeroRowWrite(\n tx, table, [condition], effectiveCollectionPath, currentId, \"update\"\n );\n }\n }\n } else {\n const dataForInsert = { ...(entityData as Record<string, unknown>) };\n\n // An explicit id given alongside upsert is the conflict target,\n // so fold it into the row before the empty-key strip below.\n if (id && options?.upsert) {\n Object.assign(dataForInsert, parseIdValues(id, idInfoArray));\n }\n\n // Strip empty primary keys so the database defaults (e.g. uuid_gen(), auto-increment) can trigger\n for (const info of idInfoArray) {\n if (dataForInsert[info.fieldName] === \"\" || dataForInsert[info.fieldName] === null || dataForInsert[info.fieldName] === undefined) {\n delete dataForInsert[info.fieldName];\n }\n }\n\n const insertQuery = tx.insert(table).values(dataForInsert);\n\n // ON CONFLICT needs a real conflict target. The primary key\n // is the one guaranteed to exist and is the default; a\n // caller may name another column set, which the REST layer\n // has already checked carries a uniqueness guarantee (see\n // `resolveConflictTarget`). Either way every target column\n // must be present in the row, or there is nothing to match\n // on — the row is a plain insert and a duplicate should\n // still raise.\n const targetFields = options?.onConflict && options.onConflict.length > 0\n ? [...options.onConflict]\n : idInfoArray.map((info) => info.fieldName);\n const hasFullKey = targetFields.length > 0\n && targetFields.every((field) => dataForInsert[field] !== undefined);\n\n let result;\n if (options?.upsert && hasFullKey) {\n const target = targetFields.map((field) => {\n const column = table[field as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n throw ApiError.badRequest(\n `'${field}' is not a column of \"${effectiveCollectionPath}\", so it cannot be an upsert target.`,\n \"INVALID_CONFLICT_TARGET\"\n );\n }\n return column;\n });\n const set = { ...dataForInsert };\n // Never reassign the target columns to themselves in the\n // UPDATE branch; Postgres rejects that against the\n // conflict target. The primary key is excluded as well\n // when the target is a natural key: a conflict means the\n // stored row already has a key, and overwriting it with\n // one the caller invented would move the row.\n for (const info of idInfoArray) delete set[info.fieldName];\n for (const field of targetFields) delete set[field];\n\n // A conflict means the row was already there, so its\n // create-time stamps (`on_create`, `user_on_create`)\n // are facts about the past and not this write's to\n // redecide. Bulk rows are saved with `status: \"new\"` on\n // purpose (an import's rows carry a natural key for\n // rows that may not exist), which computes the stamps\n // for every row — and the conflict-update then wrote\n // them over the originals. A nightly re-import reset\n // `createdAt` on everything it touched, and handed\n // `createdBy` to whoever ran it. The INSERT branch keeps\n // the values: they are right there.\n for (const key of createStampKeys(collection.properties as Properties | undefined)) {\n delete set[key];\n }\n // The same for everything else the create alone filled\n // in. A `defaultValue` describes a row the caller did\n // not finish describing, not one that exists: written\n // here it reset every omitted field of every stored row\n // an import touched — `status` back to \"draft\".\n for (const key of options.insertOnlyKeys ?? []) delete set[key];\n\n // Only a row the caller's `beforeQuery` scope reaches is\n // theirs to update. Their UPDATE is refused before it\n // runs when the row is out of scope; here the stored row\n // is only known once the INSERT meets it, so the scope\n // rides along as the conflict-update's own WHERE.\n const scope = await this.writeScope(collection, effectiveCollectionPath, table);\n // And never a row in the trash. The driver refuses an\n // upsert onto a trashed key before it gets here (see\n // `findUpsertTarget`); this is the same rule held by\n // the statement, for the row that read could not see\n // or that was trashed since.\n const softDelete = resolveSoftDelete(collection);\n const trashColumn = softDelete ? softDeleteColumn(collection, table) : undefined;\n const writable = trashColumn\n ? (scope ? and(scope, isNull(trashColumn)) : isNull(trashColumn))\n : scope;\n const updating = Object.keys(set).length > 0;\n result = updating\n ? await insertQuery.onConflictDoUpdate({ target, set, setWhere: writable }).returning(returningKeys)\n : await insertQuery.onConflictDoNothing({ target }).returning(returningKeys);\n\n if (result.length === 0) {\n // The key is taken and the statement changed nothing:\n // the stored row is outside the caller's scope, in\n // the trash, or there was nothing to change on it.\n // Asked of the row itself, through the same scope,\n // rather than falling back to an id the caller may\n // not address — which then failed the read-back\n // below as a 500.\n const keyConditions: SQL[] = target.map((column, i) => eq(column, dataForInsert[targetFields[i]]));\n if (scope) keyConditions.push(scope);\n const keyLabel = targetFields.map((field) => String(dataForInsert[field])).join(\", \");\n if (softDelete && trashColumn) {\n const [inTrash] = await tx.select(returningKeys).from(table)\n .where(and(...keyConditions, isNotNull(trashColumn))).limit(1);\n if (inTrash) throw rowInTrashError(effectiveCollectionPath, keyLabel, softDelete);\n }\n const missing = `No row \"${keyLabel}\" in \"${effectiveCollectionPath}\" to update.`;\n if (updating) {\n throw await explainZeroRowWrite(\n tx, table, keyConditions,\n `Not allowed to update \"${keyLabel}\" in \"${effectiveCollectionPath}\": a row-level security policy rejected the write.`,\n missing\n );\n }\n result = await tx.select(returningKeys).from(table).where(and(...keyConditions)).limit(1);\n if (result.length === 0) throw ApiError.notFound(missing);\n }\n } else {\n // The whole row, when it can stand in for the read\n // back below — see `FetchService.insertReturning`.\n const returning = rowIsWrittenLast ? this.fetchService.insertReturning(collection.slug) : undefined;\n result = await insertQuery.returning(returning?.columns ?? returningKeys);\n if (returning && result[0]) returnedRow = returning.render(result[0]);\n }\n\n // DO NOTHING returns no row when it skipped, and an upsert whose\n // UPDATE branch is filtered by RLS returns none either. Fall back\n // to the id we were given rather than reading undefined.\n const resultRow = result[0];\n if (!resultRow) {\n if (id) {\n currentId = id;\n } else {\n throw ApiError.forbidden(\n `Not allowed to write to \"${effectiveCollectionPath}\": the row was rejected by a row-level security policy.`,\n \"WRITE_DENIED\"\n );\n }\n } else {\n currentId = buildCompositeId(resultRow, idInfoArray);\n }\n\n // For inserts, apply joinPath after since the parent row didn't exist before\n if (joinPathRelationUpdates.length > 0) {\n await this.relationWrites.updateJoinPathOneToOneRelations(tx, collection, currentId, joinPathRelationUpdates);\n }\n }\n\n // Handle inverse relation updates\n if (inverseRelationUpdates.length > 0) {\n await this.relationWrites.updateInverseRelations(tx, collection, currentId, inverseRelationUpdates);\n }\n\n // Update many-to-many relations\n if (Object.keys(relationValues).length > 0) {\n await this.relationWrites.updateRelationsUsingJoins(tx, collection, currentId, relationValues);\n }\n\n // Attach the row to the parent's set. Runs for updates as well as\n // creates — it used to be gated on `!id`, which is why a row that\n // already existed could never be added to a parent through its\n // path. The insert is idempotent, so re-asserting a link is a\n // no-op rather than a duplicate-key error.\n if (junctionTableInfo) {\n await this.relationWrites.handleJunctionTableCreation(tx, currentId, junctionTableInfo);\n }\n\n return currentId;\n });\n } catch (error: unknown) {\n throw this.toUserFriendlyError(error, collection.slug, collection);\n }\n\n // Fetch the saved row back through the same walk `GET /:id` serves, so a\n // write's answer is the read that follows it. This used to be `fetchOne`\n // — the admin view-model walk — whose `__type: \"relation\"` refs then\n // leaked into REST responses, afterSave callbacks, history records and\n // app-level realtime payloads, none of which see that shape from any\n // read path. The admin does not need them here either: its rows arrive\n // over the realtime subscription refetch, which still serves the\n // view-model walk.\n //\n // `withDeleted` on the read-back, which is not the same question the\n // caller's own read asks. A soft delete IS a save — it stamps\n // `deleted_at` — and the default read hides stamped rows, so this walk\n // could not see the row it had just written and threw \"Could not fetch\n // row after save.\" inside the transaction, which rolled the stamp back.\n // Delete answered 500 on every soft-delete collection and the row\n // stayed live: the feature did not work at all.\n //\n // Showing it is also the correct answer on its own terms. Whoever just\n // wrote this row is the one party who must be able to see the result of\n // their own write, whatever its deleted state — and an ordinary save is\n // unaffected, because an undeleted row is visible either way.\n //\n // A plain INSERT that was the last thing written already handed this\n // row back: the same columns of the same row, rendered the same way.\n // A bulk create of 1,000 rows was 1,000 of these reads.\n if (returnedRow) return returnedRow;\n const finalEntity = await this.fetchService.fetchOneForRest(\n collection.slug, savedId, undefined, databaseId, { withDeleted: true }\n );\n if (!finalEntity) throw new Error(\"Could not fetch row after save.\");\n return finalEntity;\n }\n\n /**\n * The caller's `beforeQuery` scope over this table, as one condition: what\n * a stored row must satisfy for a write that meets it to change it. The\n * same scope the update gate and the single get apply; `undefined` when the\n * collection declares no hook.\n */\n private writeScope(collection: CollectionConfig, collectionPath: string, table: PgTable): Promise<SQL | undefined> {\n const [key] = getPrimaryKeys(collection, this.registry);\n return beforeQueryCondition(\n { registry: this.registry, callContext: this.callContext },\n collection, collectionPath, table as PgTable<never>,\n { operation: \"get\", query: {} },\n {\n collection,\n registry: this.registry,\n sourceIdColumn: key ? table[key.fieldName as keyof typeof table] as AnyPgColumn | undefined : undefined\n }\n );\n }\n\n /**\n * Get the RelationService instance for external use\n */\n getRelationService(): RelationService {\n return this.relationService;\n }\n\n /**\n * The write half, for external use. Separate from\n * {@link getRelationService} because they are separate objects now: reads\n * answer questions, writes change rows, and the callers of one are not the\n * callers of the other.\n */\n getRelationWriteService(): RelationWriteService {\n return this.relationWrites;\n }\n\n /**\n * Get the FetchService instance for external use\n */\n getFetchService(): FetchService {\n return this.fetchService;\n }\n\n /**\n * Translate raw PostgreSQL / Drizzle errors into user-friendly messages.\n *\n * `collection` is passed so the SQLSTATE can be turned into a *field*: the\n * error carries a physical column and the caller only ever sees wire names,\n * so `author_id` had to be translated to `authorId` by whoever read the\n * message — which nothing does. See `pgFieldViolations`.\n */\n private toUserFriendlyError(\n error: unknown,\n collectionSlug: string,\n collection?: CollectionConfig,\n operation: \"save\" | \"delete\" = \"save\"\n ): Error {\n // Deliberate API errors already carry their own status, code and wording.\n // Re-wrapping one flattens it into a generic Error, and the status is lost\n // on the way out — a policy rejection would surface as a 500. Matched by\n // name as well as instance: a duplicated module copy breaks `instanceof`.\n if (error instanceof ApiError || (error as Error)?.name === \"ApiError\") {\n return error as Error;\n }\n\n const pgError = extractPgError(error);\n\n if (pgError) {\n const { message, code, violations } = pgErrorToFriendlyMessage(pgError, collectionSlug, { collection });\n /** `details` for a 4xx, carrying the field(s) — never the values. */\n const details = violations.length > 0\n ? { collection: collectionSlug, violations }\n : undefined;\n // This is the only layer that holds the SQLSTATE, so it is the only\n // one that can say whose fault a failure was. Returning a bare\n // `Error` threw that away, and the REST layer compensated by calling\n // *every* unclassified error a 400 — so an unreachable database was\n // reported to callers as a bad request. Classes 22 (data exception)\n // and 23 (integrity constraint violation) are the caller's data;\n // everything else — a dropped connection, a missing column, a\n // *privilege* problem — is ours, and stays a 500.\n //\n // Within class 23, a conflict with rows already there is a 409\n // rather than a 400: a duplicate key, and a row other rows still\n // reference. A delete refused by a foreign key is the second\n // whichever way the key is declared — `RESTRICT` raises 23001,\n // `NO ACTION` raises 23503 — while a *save* raising 23503 named a\n // target that does not exist, which is the caller's data.\n if (/^2[23]/.test(code)) {\n const conflict = code === \"23505\" || code === \"23001\"\n || (code === \"23503\" && operation === \"delete\");\n return conflict\n ? ApiError.conflict(message, `PG_${code}`, details)\n : ApiError.badRequest(message, `PG_${code}`, details);\n }\n // With one exception inside class 42: a row-level-security policy\n // refusing the caller is not a fault at all, it is access control\n // working. It fell through to the 500 below, so the client could not\n // tell \"you may not do this\" from \"the server is broken\" — and a\n // 500's message is sanitized on the way out, so the reason was lost\n // too. `expected`, because a caller attempting what their policies\n // forbid is routine and should not page anyone; that is the same\n // treatment `unauthenticated()` gets one status code down.\n // `WRITE_DENIED`, the same code `explainZeroRowWrite` returns for an\n // UPDATE or DELETE that RLS refused. Those already answered 403; only\n // INSERT reached here, because a failed `WITH CHECK` raises 42501 while\n // a refused UPDATE simply matches no rows. Two spellings of one denial\n // should not be two status codes.\n //\n // Left at the default log level rather than `expected`: `ApiError`'s\n // own documentation puts \"a permission the database refused\" in the\n // stays-at-warn column, and the status code is the defect here. The\n // log level is a separate decision that already has an answer.\n if (isRowLevelSecurityDenial(error)) {\n return ApiError.forbidden(message, \"WRITE_DENIED\");\n }\n // `{ cause }` is load-bearing, not tidiness. The API error handler\n // walks the cause chain for a SQLSTATE and classifies from it: a\n // `42P01` becomes `SCHEMA_DRIFT` with \"table X does not exist. Run\n // `rebase db push`\", and a `42501` becomes `DB_PERMISSION_DENIED`\n // naming row-level security. A bare `new Error(message)` severs the\n // chain, so every write against a table the code expects and the\n // database lacks answered `INTERNAL_ERROR` / \"An unexpected error\n // occurred\" — the one failure with the most actionable fix in the\n // product, reported as the least actionable message in it. Reads\n // were classified correctly the whole time, which is why the\n // asymmetry went unnoticed.\n return new Error(message, { cause: error });\n }\n\n // No PG error found — try to extract a useful message from the\n // Drizzle wrapper instead of leaking the raw SQL query + params.\n const causeMessage = extractCauseMessage(error);\n if (causeMessage) {\n return new Error(`Database error in \"${collectionSlug}\": ${causeMessage}`, { cause: error });\n }\n\n // Last resort: generic message, never leak raw SQL\n if (error instanceof Error && error.message.startsWith(\"Failed query:\")) {\n return new Error(`Failed to save row in \"${collectionSlug}\". Check server logs for details.`, { cause: error });\n }\n return new Error(`Database error in \"${collectionSlug}\": ${String(error)}`, { cause: error });\n }\n}\n","/**\n * The rows a query answered with, whatever shape the driver handed them in.\n *\n * node-postgres answers one result for one statement, and an array of results\n * — one per statement — for a string holding several (`SELECT 1; SELECT 2`).\n * Reading `.rows` off that array gave `undefined`, and the caller got no rows\n * at all for a script that ended in a SELECT. The last statement's rows are\n * the script's answer, as in psql.\n */\nexport function rowsOfResult(result: unknown): Record<string, unknown>[] {\n const last = Array.isArray(result) ? result[result.length - 1] : result;\n if (typeof last !== \"object\" || last === null || !(\"rows\" in last)) return [];\n const rows = (last as { rows: unknown }).rows;\n return Array.isArray(rows) ? rows as Record<string, unknown>[] : [];\n}\n","// import { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { FilterValues, IncludeSpec, LogicalCondition, OrderByTuple } from \"@rebasepro/types\";\nimport type { VectorSearchParams } from \"@rebasepro/types\";\nimport { FetchService } from \"./FetchService\";\nimport type { ReadCallContextProvider } from \"./read-scope\";\nimport type { WithDeleted } from \"./soft-delete\";\nimport { PersistService, type PersistSaveOptions } from \"./PersistService\";\nimport { RelationService } from \"./RelationService\";\nimport { DataRepository, FetchCollectionOptions, SearchOptions, CountOptions, DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { logger, rawQueryLoggingEnabled } from \"@rebasepro/server\";\nimport { rowsOfResult } from \"../utils/result-rows\";\n\n// Re-export data transformer functions for external use\nexport { sanitizeAndConvertDates, serializeDataToServer, parseDataFromServer } from \"../data-transformer\";\n\n// Re-export service classes for direct use\nexport { FetchService } from \"./FetchService\";\nexport { PersistService } from \"./PersistService\";\nexport { RelationService } from \"./RelationService\";\n\n// Re-export interfaces\nexport * from \"../interfaces\";\n\n/**\n * DataService - Facade for row operations.\n *\n * This class provides a unified API for row CRUD operations by delegating\n * to specialized services:\n * - FetchService: Read operations (fetch, search, count)\n * - PersistService: Write operations (save, delete)\n * - RelationService: Relation operations (fetch related, update relations)\n *\n * Implements the DataRepository interface for database abstraction.\n */\nexport class DataService implements DataRepository {\n private fetchService: FetchService;\n private persistService: PersistService;\n\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) {\n this.fetchService = new FetchService(db, registry, callContext);\n this.persistService = new PersistService(db, registry, callContext);\n }\n\n // =============================================================\n // READ OPERATIONS - Delegated to FetchService\n // =============================================================\n\n /**\n * Fetch a single row by ID\n */\n async fetchOne<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n databaseId?: string,\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted\n ): Promise<Record<string, unknown> | undefined> {\n return this.fetchService.fetchOne<M>(collectionPath, id, databaseId, withDeleted);\n }\n\n /**\n * Fetch a collection of rows with optional filtering, ordering, and pagination\n */\n async fetchCollection<M extends Record<string, unknown>>(\n collectionPath: string,\n // The service's own options, rather than a second list of them: this\n // facade re-listed eleven fields and the driver re-listed ten of those,\n // so every option added to the fetch had two places to be dropped.\n options: Parameters<FetchService[\"fetchCollection\"]>[1] = {}\n ): Promise<Record<string, unknown>[]> {\n return this.fetchService.fetchCollection<M>(collectionPath, options);\n }\n\n /**\n * The REST read pipeline: flat rows, with exactly the relations `include`\n * names — see {@link FetchService.fetchCollectionForRest}.\n *\n * Exposed on the facade because it is what *every* consumer should be\n * reading through. The realtime refetch used to go via `fetchCollection`\n * above, which nests a relation under a `{ __type: \"relation\" }` envelope\n * and loaded every relation regardless, so a subscription and a REST list\n * answered the same query with two different row shapes.\n */\n async fetchCollectionForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n options: Parameters<FetchService[\"fetchCollectionForRest\"]>[1] = {},\n include?: IncludeSpec\n ): Promise<Record<string, unknown>[]> {\n return this.fetchService.fetchCollectionForRest<M>(collectionPath, options, include);\n }\n\n /** One row, through the same pipeline. See {@link FetchService.fetchOneForRest}. */\n async fetchOneForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n include?: IncludeSpec,\n databaseId?: string,\n options?: {\n fields?: string[];\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n }\n ): Promise<Record<string, unknown> | null> {\n return this.fetchService.fetchOneForRest<M>(collectionPath, id, include, databaseId, options);\n }\n\n /** See {@link FetchService.cursorFor}. */\n cursorFor(\n collectionPath: string,\n row: Record<string, unknown>,\n orderBy?: OrderByTuple[]\n ): string | undefined {\n return this.fetchService.cursorFor(collectionPath, row, orderBy);\n }\n\n /**\n * Search rows by text\n */\n async searchRows<M extends Record<string, unknown>>(\n collectionPath: string,\n searchString: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /** An `or(...)`/`and(...)` group, applied alongside `filter`. */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n databaseId?: string;\n searchExplain?: boolean;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n return this.fetchService.searchRows<M>(collectionPath, searchString, options);\n }\n\n /**\n * Count rows in a collection\n */\n async count<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /** An `or(...)`/`and(...)` group, applied alongside `filter`. */\n logical?: LogicalCondition;\n searchString?: string;\n databaseId?: string;\n /** Only the `threshold` narrows the count — see `FetchService.count`. */\n vectorSearch?: VectorSearchParams;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n } = {}\n ): Promise<number> {\n return this.fetchService.count<M>(collectionPath, options);\n }\n\n /**\n * Check if a field value is unique in a collection\n */\n async checkUniqueField(\n collectionPath: string,\n fieldName: string,\n value: unknown,\n excludeEntityId?: string,\n databaseId?: string\n ): Promise<boolean> {\n return this.fetchService.checkUniqueField(collectionPath, fieldName, value, excludeEntityId, databaseId);\n }\n\n /**\n * Fetch rows related to a parent row\n */\n async fetchRelatedEntities<M extends Record<string, unknown>>(\n parentCollectionPath: string,\n parentId: string | number,\n relationKey: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n databaseId?: string;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n const rows = await this.fetchService.getRelationService().fetchRelatedEntities<M>(\n parentCollectionPath,\n parentId,\n relationKey,\n options\n );\n return rows.map(e => ({ ...e.values, id: e.id }));\n }\n\n // =============================================================\n // WRITE OPERATIONS - Delegated to PersistService\n // =============================================================\n\n /**\n * Save an row (create or update)\n */\n async save<M extends Record<string, unknown>>(\n collectionPath: string,\n values: Partial<M>,\n id?: string | number,\n databaseId?: string,\n options?: PersistSaveOptions\n ): Promise<Record<string, unknown>> {\n return this.persistService.save<M>(collectionPath, values, id, databaseId, options);\n }\n\n /**\n * Delete an row by ID\n */\n async delete(\n collectionPath: string,\n id: string | number,\n databaseId?: string\n ): Promise<void> {\n return this.persistService.delete(collectionPath, id, databaseId);\n }\n\n /**\n * Delete all rows from a collection\n */\n /** See {@link PersistService.updateRelationPivot}. */\n async updateRelationPivot(\n collectionPath: string,\n targetId: string | number,\n pivot: Record<string, unknown>\n ): Promise<void> {\n return this.persistService.updateRelationPivot(collectionPath, targetId, pivot);\n }\n\n async deleteAll(collectionPath: string, databaseId?: string): Promise<void> {\n return this.persistService.deleteAll(collectionPath, databaseId);\n }\n\n\n /**\n * Execute raw SQL\n */\n async executeSql(sqlText: string, params?: unknown[]): Promise<Record<string, unknown>[]> {\n // Through the logger, at `debug`, behind the same switch that lifts the\n // `Failed query:` redaction. A `console.debug` gated on `NODE_ENV`\n // alone put every statement on stdout whatever `LOG_LEVEL` said — a\n // 60-second idle backend emitted 478 lines, 18 of them the job\n // poller's `UPDATE rebase.jobs … FOR UPDATE SKIP LOCKED` — and it\n // bypassed the one function where redaction lives.\n if (rawQueryLoggingEnabled()) {\n logger.debug(`Executing raw SQL: ${sqlText}${params?.length ? ` with ${params.length} params` : \"\"}`);\n }\n const { sql } = await import(\"drizzle-orm\");\n\n let result;\n if (params && params.length > 0) {\n // Build a parameterized query using Drizzle's sql tagged template.\n // Split the SQL text on $1, $2, … placeholders and interleave\n // with sql.param() calls so the underlying pg driver binds them safely.\n const parts = sqlText.split(/\\$(\\d+)/);\n const chunks: ReturnType<typeof sql.raw | typeof sql.param>[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n // Literal SQL text fragment\n if (parts[i].length > 0) {\n chunks.push(sql.raw(parts[i]));\n }\n } else {\n // Parameter reference — $N (1-indexed)\n const paramIndex = Number(parts[i]) - 1;\n chunks.push(sql.param(params[paramIndex]));\n }\n }\n const query = sql.join(chunks, sql.raw(\"\"));\n result = await this.db.execute(query);\n } else {\n result = await this.db.execute(sql.raw(sqlText));\n }\n\n const rows = rowsOfResult(result);\n if (rawQueryLoggingEnabled()) {\n logger.debug(`SQL executed successfully. Returned ${rows.length} rows.`);\n }\n return rows;\n }\n\n // =============================================================\n // SERVICE ACCESSORS\n // =============================================================\n\n /**\n * Get the underlying FetchService for advanced use\n */\n getFetchService(): FetchService {\n return this.fetchService;\n }\n\n /**\n * Get the underlying PersistService for advanced use\n */\n getPersistService(): PersistService {\n return this.persistService;\n }\n\n /**\n * Get the underlying RelationService for advanced use\n */\n getRelationService(): RelationService {\n return this.fetchService.getRelationService();\n }\n}\n","/**\n * A dedicated, self-healing Postgres `LISTEN` connection.\n *\n * Every cross-instance feature in the backend needs the same thing: one\n * connection *outside* the Drizzle pool that stays open, holds a `LISTEN`, and\n * comes back on its own after the database or the network drops it. CDC, the\n * channel bus and the cross-instance broadcast all use this one. By default the\n * **first** connect is validated and rethrown, so a caller can fall back to a\n * different strategy, while every later drop is repaired quietly in the\n * background; `retryInitialConnect` repairs the first one the same way.\n *\n * `LISTEN` is session state, so this connection must not go through a\n * transaction-mode pooler (PgBouncer): give it the direct database URL.\n */\n\nimport { Client as PgClient } from \"pg\";\nimport { logger } from \"@rebasepro/server\";\nimport { unref } from \"@rebasepro/utils\";\n\nexport interface PgNotifyListenerOptions {\n /** Direct Postgres connection string (must bypass a transaction-mode pooler). */\n connectionString: string;\n /** NOTIFY channel to LISTEN on. Must be a plain identifier — it is interpolated. */\n channel: string;\n /** Called for every notification payload received. */\n onPayload: (payload: string) => void | Promise<void>;\n /** Prefix for log lines, e.g. `\"[CDC]\"`. */\n logLabel: string;\n /** Delay before a reconnect attempt. */\n reconnectDelayMs?: number;\n /**\n * Called once the connection is listening again after a drop — never after\n * the first connect. Postgres keeps no NOTIFY for a session that is not\n * listening, so whatever was published while the connection was down is\n * gone for good; a caller that mirrors state from the channel resyncs here.\n */\n onReconnect?: () => void;\n /**\n * How often the connection proves it is alive with a round trip. `0`\n * turns the heartbeat off. See {@link PgNotifyListener} for why it exists.\n */\n heartbeatIntervalMs?: number;\n /** How long a heartbeat may take before the connection is declared dead. */\n heartbeatTimeoutMs?: number;\n /**\n * Treat a failed first connect like any later drop: `start()` resolves,\n * the listener reports itself down, and it keeps dialling in the\n * background. For a caller with nothing to fall back to, for whom a\n * database that is unreachable at boot is an outage to wait out rather\n * than a reason to run without the channel. Off by default: `start()`\n * rejects, so a caller with a fallback can take it.\n */\n retryInitialConnect?: boolean;\n /**\n * The `application_name` the connection reports in `pg_stat_activity`.\n * Visible to every role, which is what lets one instance count the others\n * (see `RealtimeService.warnIfMemoryBusOnMultiplePods`). A connection\n * string that sets its own `application_name` wins.\n */\n applicationName?: string;\n}\n\n/**\n * What a listener can say about itself — read by `/health`.\n *\n * `connected` is \"listening, and the last heartbeat answered\", not \"a socket\n * object exists\": a half-open connection has a perfectly good socket object.\n */\nexport interface PgNotifyListenerStatus {\n channel: string;\n connected: boolean;\n /** Since when (epoch ms) it has been down, while it is down. */\n downSince?: number;\n /** When (epoch ms) it last answered a heartbeat. */\n lastHeartbeatAt?: number;\n}\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\n/**\n * Below every common idle-flow timeout: Azure's load balancer drops an idle\n * flow at 4 minutes, AWS's NLB at 350 s, and NAT and conntrack tables and\n * pooler-side reapers are often shorter. A LISTEN connection is idle by\n * nature, so it is exactly the connection those timeouts kill.\n */\nexport const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;\nexport const DEFAULT_HEARTBEAT_TIMEOUT_MS = 10_000;\n/** Guards the identifier interpolated into `LISTEN`. */\nconst SAFE_CHANNEL = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * A half-open connection — one whose packets stop arriving, with no FIN and no\n * RST, which is what an idle-flow timeout on a load balancer, a NAT, a dead\n * middlebox or a failed-over network looks like — fires no `error` and no\n * `end`. The connection object stays, `LISTEN` stays registered on a backend\n * that may no longer exist, and every notification from then on is lost: CDC,\n * the cross-instance broadcast and the channel bus all went quiet with no\n * error, no reconnect and a green `/health`, while writes made through this\n * instance still looked live because they are delivered locally.\n *\n * So the connection proves itself: TCP keepalive is on, and every\n * {@link DEFAULT_HEARTBEAT_INTERVAL_MS} a `SELECT 1` must come back within\n * {@link DEFAULT_HEARTBEAT_TIMEOUT_MS}. One that does not is torn down and\n * replaced, and `onReconnect` resyncs whatever was missed.\n */\nexport class PgNotifyListener {\n private client?: PgClient;\n private running = false;\n private reconnectTimer?: ReturnType<typeof setTimeout>;\n private heartbeatTimer?: ReturnType<typeof setInterval>;\n private heartbeatInFlight = false;\n private downSince?: number;\n private lastHeartbeatAt?: number;\n\n constructor(private readonly options: PgNotifyListenerOptions) {\n if (!SAFE_CHANNEL.test(options.channel)) {\n throw new Error(`Unsafe NOTIFY channel name \"${options.channel}\" — expected a plain SQL identifier.`);\n }\n }\n\n /** Whether the listener is meant to be connected right now. */\n get active(): boolean {\n return this.running;\n }\n\n /** Whether it is listening on a connection that answered its last heartbeat. */\n get connected(): boolean {\n return this.running && this.client !== undefined;\n }\n\n status(): PgNotifyListenerStatus {\n return {\n channel: this.options.channel,\n connected: this.connected,\n ...(this.downSince !== undefined && !this.connected ? { downSince: this.downSince } : {}),\n ...(this.lastHeartbeatAt !== undefined ? { lastHeartbeatAt: this.lastHeartbeatAt } : {})\n };\n }\n\n /**\n * Connect and begin listening. Idempotent.\n *\n * Rejects if the *initial* connection or `LISTEN` fails, leaving the\n * listener stopped — callers use that to degrade deliberately instead of\n * running blind against a channel nothing is delivering. With\n * `retryInitialConnect` it resolves instead, reports itself down, and\n * keeps dialling.\n */\n async start(): Promise<void> {\n if (this.running) return;\n this.running = true;\n try {\n await this.connect({ first: true });\n } catch (err) {\n this.running = false;\n throw err;\n }\n }\n\n /** Stop listening and release the connection. Idempotent. */\n async stop(): Promise<void> {\n this.running = false;\n this.stopHeartbeat();\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n if (this.client) {\n try {\n await this.client.end();\n } catch { /* ignore close errors */ }\n this.client = undefined;\n }\n }\n\n /**\n * @param first The connect `start()` makes: never a reconnect, so it owes\n * no resync, and — unless `retryInitialConnect` — a failure is\n * thrown to the caller rather than retried.\n */\n private async connect({ first = false }: { first?: boolean } = {}): Promise<void> {\n const { connectionString, channel, onPayload, logLabel, applicationName } = this.options;\n // Held here rather than only inside the `try` so the failure path can\n // still reach it: everything below `connect()` can throw, and until\n // `this.client` is assigned nothing else in this class knows the\n // connection exists. Left unreleased it stays open on the server while\n // `scheduleReconnect` opens another — one leaked backend per attempt,\n // every few seconds, for as long as the failure lasts.\n let pending: PgClient | undefined;\n try {\n // TCP keepalive as well as the heartbeat below: it costs nothing,\n // and it is what eventually errors a dead socket the heartbeat\n // has already given up on.\n const client = new PgClient({\n connectionString,\n keepAlive: true,\n keepAliveInitialDelayMillis: 30_000,\n ...(applicationName ? { application_name: applicationName } : {})\n });\n pending = client;\n\n // Events from a connection already given up on — torn down after\n // a missed heartbeat, say — are not news about the current one.\n client.on(\"error\", (err) => {\n if (client !== this.client) return;\n logger.error(`❌ ${logLabel} LISTEN client error`, { detail: err.message });\n this.abandon(client);\n });\n\n client.on(\"end\", () => {\n if (client !== this.client) return;\n if (this.running) {\n logger.warn(`⚠️ ${logLabel} LISTEN client disconnected unexpectedly.`);\n this.abandon(client);\n }\n });\n\n client.on(\"notification\", (msg) => {\n if (!msg.payload) return;\n // A handler rejection must never surface as an unhandled\n // rejection inside the pg client's event emitter.\n Promise.resolve(onPayload(msg.payload)).catch((err) =>\n logger.error(`❌ ${logLabel} Error handling notification`, { error: err })\n );\n });\n\n await client.connect();\n await client.query(`LISTEN ${channel}`);\n this.client = client;\n // Adopted: `stop()` and `scheduleReconnect` will close it now.\n pending = undefined;\n this.downSince = undefined;\n this.lastHeartbeatAt = Date.now();\n this.startHeartbeat(client);\n logger.debug(`📡 ${logLabel} Listening on channel \"${channel}\".`);\n } catch (err) {\n // Never adopted, so nothing else will ever close it.\n if (pending) {\n try { await pending.end(); } catch { /* already dead */ }\n }\n // Surface the initial failure so callers can choose to fall back;\n // for reconnects, keep retrying quietly in the background.\n if (first && !this.options.retryInitialConnect) throw err;\n logger.error(`❌ ${logLabel} Failed to connect LISTEN client`, { error: err });\n this.scheduleReconnect();\n return;\n }\n // Outside the `try`: a throwing handler is not a failed connection,\n // and must not tear down the one that just came back.\n if (!first && this.options.onReconnect) {\n logger.warn(`⚠️ ${logLabel} LISTEN client reconnected; anything published while it was down was missed.`);\n try {\n this.options.onReconnect();\n } catch (err) {\n logger.error(`❌ ${logLabel} Error resyncing after a reconnect`, { error: err });\n }\n }\n }\n\n /**\n * Give up on the current connection: it is no longer the listener's, so\n * its late events are ignored and `connected` says so at once; it is\n * ended without waiting on it; and a replacement is scheduled.\n */\n private abandon(client: PgClient): void {\n if (client !== this.client) return;\n this.client = undefined;\n this.downSince ??= Date.now();\n this.stopHeartbeat();\n void client.end().catch(() => { /* already dead */ });\n this.scheduleReconnect();\n }\n\n private startHeartbeat(client: PgClient): void {\n this.stopHeartbeat();\n const interval = this.options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n if (interval <= 0) return;\n this.heartbeatTimer = setInterval(() => void this.heartbeat(client), interval);\n // Housekeeping must never hold the process open.\n unref(this.heartbeatTimer);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = undefined;\n this.heartbeatInFlight = false;\n }\n\n /**\n * One round trip on the LISTEN connection. Not answered in time, it is\n * treated as dead: the client is ended — which, with the query still\n * outstanding, destroys the socket rather than waiting on a goodbye that\n * cannot arrive — and replaced.\n */\n private async heartbeat(client: PgClient): Promise<void> {\n if (client !== this.client || this.heartbeatInFlight) return;\n this.heartbeatInFlight = true;\n const timeoutMs = this.options.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n client.query(\"SELECT 1\"),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new Error(`no answer in ${timeoutMs}ms`)), timeoutMs);\n })\n ]);\n if (client === this.client) this.lastHeartbeatAt = Date.now();\n } catch (err) {\n if (client !== this.client) return;\n logger.warn(\n `⚠️ ${this.options.logLabel} LISTEN connection failed its heartbeat (${err instanceof Error ? err.message : String(err)}) — ` +\n \"treating it as dead and reconnecting.\"\n );\n this.abandon(client);\n } finally {\n if (timer) clearTimeout(timer);\n if (client === this.client) this.heartbeatInFlight = false;\n }\n }\n\n private scheduleReconnect(): void {\n if (!this.running || this.reconnectTimer) return;\n this.downSince ??= Date.now();\n\n this.reconnectTimer = setTimeout(async () => {\n this.reconnectTimer = undefined;\n if (!this.running) return;\n await this.connect();\n }, this.options.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS);\n }\n}\n","import type { CollectionConfig, FetchCollectionProps } from \"@rebasepro/types\";\nimport type { FieldViewer } from \"@rebasepro/common\";\nimport { assertQueryFieldsReadable } from \"@rebasepro/server\";\n\n/**\n * Refuse a read that names a field its caller may not read, for the doors that\n * take a driver-shaped request rather than a query string: the socket's\n * `FETCH_COLLECTION`, `COUNT` and `CHECK_UNIQUE_FIELD`, and `subscribe_collection`.\n * Callers hand it the request itself, not a re-listed copy of some of its keys.\n *\n * The row strip keeps a withheld value off the wire; this is the other half, and\n * without it the value is still readable one predicate at a time — a `COUNT`\n * filtered on `passwordHash like '$2b$10$A%'` answers 1 or 0. `GET /api/data`\n * refuses the same request with `FIELD_NOT_READABLE`, and this is that rule, not\n * a copy of it: only the request's shape is translated here.\n *\n * The shape arrives as whatever JSON the client sent, so it is read defensively.\n * A malformed `orderBy` or `fields` is not this check's to reject; the entries\n * that name a field are checked and the rest are left for the driver to refuse.\n */\nexport function assertReadRequestReadable(\n request: Pick<FetchCollectionProps, \"filter\" | \"logical\" | \"orderBy\" | \"fields\" | \"include\" | \"vectorSearch\">,\n collection: CollectionConfig,\n viewer: FieldViewer\n): void {\n // Every input the REST check takes. An `include` reads its target's\n // columns — `{ staff: { where: { salary: [\">\", 100000] } } }` answers which\n // departments employ someone over the line — and a vector search measures\n // the vector it ranks by. Left out, both were refused over HTTP and\n // answered here.\n const vectorProperty = request.vectorSearch && typeof request.vectorSearch === \"object\"\n ? request.vectorSearch.property\n : undefined;\n assertQueryFieldsReadable({\n where: request.filter && typeof request.filter === \"object\" ? request.filter : undefined,\n logical: request.logical,\n orderBy: sortedFields(request.orderBy),\n fields: Array.isArray(request.fields) ? request.fields : undefined,\n include: request.include ?? undefined,\n vectorSearch: typeof vectorProperty === \"string\" ? { property: vectorProperty } : undefined\n }, collection, viewer);\n}\n\n/** The fields an `orderBy` sorts on, in either of its two spellings. */\nfunction sortedFields(orderBy: unknown): { field: string }[] | undefined {\n if (typeof orderBy === \"string\") return [{ field: orderBy }];\n if (!Array.isArray(orderBy)) return undefined;\n return orderBy.flatMap((entry: unknown) =>\n Array.isArray(entry) && typeof entry[0] === \"string\" ? [{ field: entry[0] }] : []);\n}\n","/**\n * Letting go of client sockets the server can no longer reach.\n *\n * A phone that loses signal, a laptop that sleeps, a NAT that forgets the\n * connection: the peer is gone, and nothing tells the server. No close frame\n * arrives and no FIN, so the socket stays open until the operating system gives\n * up on the TCP connection — about two hours on Linux. Meanwhile every\n * subscription it holds is refetched on every write to its collection, and\n * every frame for it is serialised into a send buffer nobody drains.\n *\n * Two checks catch it. The socket must answer a ping: one that has not answered\n * by the next is terminated. And it must read what it is sent: one whose unsent\n * backlog passes {@link MAX_SOCKET_BUFFERED_BYTES} is terminated at the next\n * frame for it, and when all of the process's sockets together hold more than\n * {@link MAX_PROCESS_BUFFERED_BYTES}, the ones with the largest backlogs go\n * first. A terminated socket closes like any other, so the realtime service\n * drops its subscriptions, channels and presence on its `close`.\n */\n\nimport { WebSocket, type WebSocketServer } from \"ws\";\nimport { logger } from \"@rebasepro/server\";\nimport { unref } from \"@rebasepro/utils\";\n\n/**\n * How often every socket is pinged. A socket that has not answered one ping by\n * the next is terminated, so a peer that vanished is let go of within two\n * intervals. Every browser and `ws` client answers a ping by itself.\n */\nexport const SOCKET_PING_INTERVAL_MS = 30_000;\n\n/**\n * The unsent bytes a socket may have queued before it is terminated.\n *\n * A healthy client drains its queue as fast as the network allows; one whose\n * backlog keeps growing is not reading, or is so far behind that what it would\n * read is stale. Set well above a single frame — the largest body the data API\n * accepts is 10 MiB, and a list frame can approach it — so a burst of large\n * frames to a client that is keeping up does not trip it. A terminated client\n * reconnects and resubscribes, which brings it up to date with one frame per\n * subscription instead of the backlog.\n */\nexport const MAX_SOCKET_BUFFERED_BYTES = 16 * 1024 * 1024;\n\n/**\n * The unsent bytes all of this process's sockets may hold between them before\n * the ones with the largest backlogs are terminated.\n *\n * The per-socket ceiling alone bounds nothing for the process: a hundred slow\n * phones during a bulk-update storm could each sit just under it, 1.6 GB of\n * frames between them, before any one was let go. A healthy socket's backlog\n * is close to nothing — the kernel takes a frame as it is written — so a\n * process holding this much unread is already in a storm. 256 MiB is sixteen\n * sockets at their own ceiling, and an eighth of a 2 GiB container; under a\n * smaller memory limit it is a quarter of that limit (see\n * {@link processSendBudget}).\n */\nexport const MAX_PROCESS_BUFFERED_BYTES = 256 * 1024 * 1024;\n\n/**\n * How often, at most, the process budget is summed, in ms. Summing it walks\n * every socket, so it is not done for every frame; between two sums only the\n * frames sent in this window can have been added.\n */\nconst PROCESS_BUDGET_CHECK_MS = 250;\n\n/**\n * The process-wide budget: {@link MAX_PROCESS_BUFFERED_BYTES}, or a quarter of\n * the memory limit the process runs under when that is smaller — never less\n * than one socket's own ceiling. `constrained` is the limit in bytes, 0 for\n * none, as `process.constrainedMemory()` reports it.\n */\nexport function processSendBudget(constrained: number = process.constrainedMemory()): number {\n if (!(constrained > 0)) return MAX_PROCESS_BUFFERED_BYTES;\n return Math.max(MAX_SOCKET_BUFFERED_BYTES, Math.min(MAX_PROCESS_BUFFERED_BYTES, Math.floor(constrained / 4)));\n}\n\n/** Read once: the limit a process runs under does not change while it runs. */\nlet sendBudget: number | undefined;\n\n/** The clients of every socket server in this process that counts in the budget. */\nconst budgetedClients = new Set<Set<WebSocket>>();\nlet lastBudgetCheckAt = 0;\n\n/**\n * Count the sockets of `wss` in the process-wide send budget. Returns the\n * function that stops counting them, for when the server closes.\n */\nexport function budgetSendBuffers(wss: WebSocketServer): () => void {\n const clients: Set<WebSocket> | undefined = wss.clients;\n if (!clients) return () => undefined;\n budgetedClients.add(clients);\n return () => {\n budgetedClients.delete(clients);\n };\n}\n\n/**\n * Terminate sockets, largest backlog first, until what this process's sockets\n * hold unread between them is back within `budget`. Returns the sockets it\n * terminated. A terminated socket closes like any other, and its client\n * reconnects and re-subscribes: one fresh frame per subscription, instead of\n * the backlog.\n */\nexport function enforceProcessSendBudget(budget?: number): WebSocket[] {\n const limit = budget ?? (sendBudget ??= processSendBudget());\n let total = 0;\n const backlogged: Array<{ ws: WebSocket; bytes: number }> = [];\n for (const clients of budgetedClients) {\n for (const ws of clients) {\n const bytes = ws.bufferedAmount;\n if (bytes > 0) {\n total += bytes;\n backlogged.push({ ws, bytes });\n }\n }\n }\n if (total <= limit) return [];\n backlogged.sort((a, b) => b.bytes - a.bytes);\n const held = total;\n const terminated: WebSocket[] = [];\n for (const { ws, bytes } of backlogged) {\n if (total <= limit) break;\n ws.terminate();\n total -= bytes;\n terminated.push(ws);\n }\n // One line for the lot: a storm can let go of hundreds at once.\n logger.warn(\n `[WebSocket Server] Terminated ${terminated.length} socket(s), largest backlog first: the process's sockets held ` +\n `${held} unread bytes between them, past the ${limit}-byte budget for all of them, and hold ${total} now.`\n );\n return terminated;\n}\n\n/**\n * Ping every socket of `wss` each `intervalMs`, and terminate the ones that did\n * not answer the previous ping. Returns the function that stops it.\n */\nexport function reapSilentSockets(wss: WebSocketServer, intervalMs: number = SOCKET_PING_INTERVAL_MS): () => void {\n // Read here rather than on the first tick. A server made with\n // `clientTracking: false` has no `clients`, and finding that out inside the\n // timer throws from a callback nothing catches, which ends the process\n // `intervalMs` later — whatever it is doing by then. That is how a test\n // double without `clients` took down the jest worker that ran it, along with\n // every suite the worker had moved on to.\n const clients: Set<WebSocket> | undefined = wss.clients;\n if (!clients) {\n throw new Error(\"reapSilentSockets needs a WebSocketServer that tracks its clients; this one was created with clientTracking: false.\");\n }\n const awaitingPong = new WeakSet<WebSocket>();\n wss.on(\"connection\", (ws: WebSocket) => {\n ws.on(\"pong\", () => awaitingPong.delete(ws));\n });\n const timer = setInterval(() => {\n for (const ws of clients) {\n if (awaitingPong.has(ws)) {\n logger.debug(`[WebSocket Server] Terminated a socket that did not answer a ping within ${intervalMs}ms.`);\n ws.terminate();\n continue;\n }\n if (ws.readyState !== WebSocket.OPEN) continue;\n awaitingPong.add(ws);\n ws.ping();\n }\n }, intervalMs);\n // Housekeeping must never hold the process open.\n unref(timer);\n return () => clearInterval(timer);\n}\n\n/**\n * Terminate `ws` if its unsent backlog has passed {@link MAX_SOCKET_BUFFERED_BYTES}.\n * Returns whether it did; the caller then sends nothing.\n *\n * Every frame in and out of a socket comes through here, so this is also where\n * the process-wide budget is kept, summed at most every\n * {@link PROCESS_BUDGET_CHECK_MS} — see {@link enforceProcessSendBudget}.\n */\nexport function terminateIfBacklogged(ws: WebSocket, clientId: string): boolean {\n const backlog = ws.bufferedAmount;\n if (backlog > MAX_SOCKET_BUFFERED_BYTES) {\n logger.warn(\n `[WebSocket Server] Terminated ${clientId}: ${backlog} bytes sent to it are still unread, ` +\n `past the ${MAX_SOCKET_BUFFERED_BYTES}-byte ceiling for one socket.`\n );\n ws.terminate();\n return true;\n }\n const now = Date.now();\n // A clock that went back (or a fake one in a test) starts the window again.\n if (now - lastBudgetCheckAt >= PROCESS_BUDGET_CHECK_MS || now < lastBudgetCheckAt) {\n lastBudgetCheckAt = now;\n if (budgetedClients.size > 0 && enforceProcessSendBudget().includes(ws)) return true;\n }\n return false;\n}\n"],"mappings":"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"}