@rebasepro/server-postgres 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BranchService-ClklxyAQ.js → BranchService-CWBnDhcv.js} +25 -9
- package/dist/BranchService-CWBnDhcv.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +7 -0
- package/dist/auth/services.d.ts +8 -1
- package/dist/auth/verify-auth-tables.d.ts +21 -0
- package/dist/{backup-cli-gijeXe0u.js → backup-cli-PoRG-zEq.js} +3 -3
- package/dist/{backup-cli-gijeXe0u.js.map → backup-cli-PoRG-zEq.js.map} +1 -1
- package/dist/{boot-ddl-DYH95Qeg.js → boot-ddl-EsgqDkx0.js} +2 -2
- package/dist/{boot-ddl-DYH95Qeg.js.map → boot-ddl-EsgqDkx0.js.map} +1 -1
- package/dist/branch-diff-CxE4VlxT.js +1396 -0
- package/dist/branch-diff-CxE4VlxT.js.map +1 -0
- package/dist/branch-diff-report-DpvVf7hR.js +48 -0
- package/dist/branch-diff-report-DpvVf7hR.js.map +1 -0
- package/dist/branching/branch-diff-report.d.ts +7 -0
- package/dist/branching/branch-diff.d.ts +115 -0
- package/dist/{cli-console-CQrQ0lAY.js → cli-console-f0k6_TsJ.js} +38 -5
- package/dist/cli-console-f0k6_TsJ.js.map +1 -0
- package/dist/cli-console.d.ts +17 -1
- package/dist/{cli-errors-DiA0XFIS.js → cli-errors-DsYyNYzd.js} +53 -2
- package/dist/cli-errors-DsYyNYzd.js.map +1 -0
- package/dist/cli.js +102 -122
- package/dist/cli.js.map +1 -1
- package/dist/{collections-schema-version-T6cGvrDi.js → collections-schema-version-D7bDI0PJ.js} +13 -9
- package/dist/collections-schema-version-D7bDI0PJ.js.map +1 -0
- package/dist/{deferred-index-build-BE0I6sYU.js → deferred-index-build-CqaOv1Xx.js} +3 -3
- package/dist/{deferred-index-build-BE0I6sYU.js.map → deferred-index-build-CqaOv1Xx.js.map} +1 -1
- package/dist/{doctor-BT--1kq3.js → doctor-DW_mlTzz.js} +81 -33
- package/dist/doctor-DW_mlTzz.js.map +1 -0
- package/dist/{ensure-collection-policies-BnGOURpG.js → ensure-collection-policies-DLWgabQ2.js} +5 -5
- package/dist/{ensure-collection-policies-BnGOURpG.js.map → ensure-collection-policies-DLWgabQ2.js.map} +1 -1
- package/dist/{ensure-collection-tables-Cet4650m.js → ensure-collection-tables-CFIRq8ni.js} +5 -5
- package/dist/{ensure-collection-tables-Cet4650m.js.map → ensure-collection-tables-CFIRq8ni.js.map} +1 -1
- package/dist/{ensure-tables-l_OgFQjN.js → ensure-tables-4nA-tfwi.js} +4 -4
- package/dist/{ensure-tables-l_OgFQjN.js.map → ensure-tables-4nA-tfwi.js.map} +1 -1
- package/dist/{enum-labels-kept-nheku1pB.js → enum-labels-kept-PcPW65xy.js} +2 -2
- package/dist/{enum-labels-kept-nheku1pB.js.map → enum-labels-kept-PcPW65xy.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +7 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.es.js +226 -8
- package/dist/index.es.js.map +1 -1
- package/dist/{db-subcommands-BPoHCxJR.js → migrate-baseline-B-2Ef4Mv.js} +266 -41
- package/dist/migrate-baseline-B-2Ef4Mv.js.map +1 -0
- package/dist/{migrate-policy-stamps-3eqfhes6.js → migrate-policy-stamps-BQtFHApx.js} +5 -5
- package/dist/{migrate-policy-stamps-3eqfhes6.js.map → migrate-policy-stamps-BQtFHApx.js.map} +1 -1
- package/dist/{open-rebase-EmqVmS14.js → open-rebase-C8jQ6s5a.js} +292 -46
- package/dist/open-rebase-C8jQ6s5a.js.map +1 -0
- package/dist/open-rebase.d.ts +9 -1
- package/dist/{pg-error-utils-CFEnm2m9.js → pg-error-utils-DhiUTlVx.js} +2 -2
- package/dist/{pg-error-utils-CFEnm2m9.js.map → pg-error-utils-DhiUTlVx.js.map} +1 -1
- package/dist/{push-index-prebuild-COd7i6wm.js → push-index-prebuild-UTvB5pqH.js} +3 -3
- package/dist/{push-index-prebuild-COd7i6wm.js.map → push-index-prebuild-UTvB5pqH.js.map} +1 -1
- package/dist/push-kept-defaults-BGOlOqRQ.js +137 -0
- package/dist/push-kept-defaults-BGOlOqRQ.js.map +1 -0
- package/dist/{push-legacy-renames-CLgYYwPE.js → push-legacy-renames-BwaKi8xF.js} +3 -3
- package/dist/{push-legacy-renames-CLgYYwPE.js.map → push-legacy-renames-BwaKi8xF.js.map} +1 -1
- package/dist/{push-policy-plan-D2gpQsdv.js → push-policy-plan-DQ2tWceD.js} +2 -2
- package/dist/{push-policy-plan-D2gpQsdv.js.map → push-policy-plan-DQ2tWceD.js.map} +1 -1
- package/dist/{rls-enforcement-DDyNoOSy.js → rls-enforcement-BOHuHVlc.js} +42 -3
- package/dist/rls-enforcement-BOHuHVlc.js.map +1 -0
- package/dist/schema/destructive-sql.d.ts +16 -0
- package/dist/schema/doctor-cli.js +3 -3
- package/dist/schema/introspect-db.js +1 -1
- package/dist/schema/migration-freshness.d.ts +69 -0
- package/dist/schema/push-kept-defaults.d.ts +57 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +47 -10
- package/dist/security/rls-enforcement.d.ts +18 -0
- package/dist/services/FetchService.d.ts +12 -0
- package/dist/services/channel-presence.d.ts +3 -1
- package/dist/services/nested-path.d.ts +16 -0
- package/dist/services/realtimeService.d.ts +18 -1
- package/dist/{websocket-BKZwsjmf.js → websocket-CKYc3V4N.js} +41 -12
- package/dist/websocket-CKYc3V4N.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-ClklxyAQ.js.map +0 -1
- package/dist/cli-console-CQrQ0lAY.js.map +0 -1
- package/dist/cli-errors-DiA0XFIS.js.map +0 -1
- package/dist/collections-schema-version-T6cGvrDi.js.map +0 -1
- package/dist/db-subcommands-BPoHCxJR.js.map +0 -1
- package/dist/doctor-BT--1kq3.js.map +0 -1
- package/dist/open-rebase-EmqVmS14.js.map +0 -1
- package/dist/rls-enforcement-DDyNoOSy.js.map +0 -1
- package/dist/websocket-BKZwsjmf.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"open-rebase-EmqVmS14.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/schema/classify-change.ts","../src/schema/generate-schema-commit.ts","../src/services/sql-script.ts","../src/services/pg-notify-listener.ts","../src/PostgresBackendDriver.ts","../src/schema/auth-schema.ts","../src/services/cdc/junction-tables.ts","../src/services/cdc/identity-columns.ts","../src/services/read-field-access.ts","../src/services/cdc/trigger-cdc.ts","../src/services/cdc/CdcListener.ts","../src/services/socket-liveness.ts","../src/schema/drizzle-ddl.ts","../src/services/channel-history.ts","../src/services/channel-presence.ts","../src/services/channel-bus/ChannelBus.ts","../src/services/channel-bus/PostgresChannelBus.ts","../src/services/channel-bus/index.ts","../src/services/realtimeService.ts","../src/collections/PostgresCollectionRegistry.ts","../src/types.ts","../src/collections/validate-relations.ts","../src/collections/buildRegistry.ts","../src/auth/services.ts","../src/history/ensure-history-table.ts","../src/history/HistoryService.ts","../src/utils/pg-array-null-patch.ts","../src/utils/pg-numeric-number-patch.ts","../src/PostgresBootstrapper.ts","../src/open-rebase.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 * What a collection change means for a live database.\n *\n * The live schema editor may only make changes the boot-time ensure path can\n * actually carry out, because that is the one mechanism that changes a schema\n * (see `ensure-collection-tables.ts`). Its vocabulary is small and deliberately\n * so: create a table, add a column, create an enum *type*, create an index, add\n * a foreign key, and rename a column via the legacy-name path. There is no\n * `ALTER COLUMN TYPE` and no `DROP` of anything.\n *\n * So this module answers one question per change: **can the ensure path express\n * it, and if it can, will the result actually match what the config says?**\n *\n * ## Three answers, not two\n *\n * The obvious split is safe / unsafe. It is not enough, because the most\n * dangerous case is neither: a change the ensure path *partly* applies, leaving\n * a database that does not match the configuration and says nothing about it.\n * Two of those exist today and both are documented in the code they come from:\n *\n * - **A required property added to an existing collection.** `ensure` withholds\n * `NOT NULL` on a table that already exists, because the constraint is checked\n * against live rows. The column arrives nullable. The config says required;\n * the database does not enforce it.\n * - **A value added to an existing enum.** `ensure` skips an enum type it\n * already sees — `if (existing.enums.has(name)) continue`. The new value never\n * reaches the database, and the first insert using it fails.\n *\n * Both would read as \"applied successfully\" to anyone watching. Calling them\n * `diverges` is the whole point of this module: an editor that reports them as\n * safe is worse than one that refuses them.\n *\n * ## Read from the plan, not from the properties\n *\n * Whether an edit changes the database is decided by comparing what\n * `planSchema` — the one reading of a `Property` every emitter renders — makes\n * of the collections before and after it: each table's columns, their types,\n * nullability, defaults, uniqueness and foreign keys, the junction tables,\n * indexes and triggers. It used to be decided by a hand-picked list of property\n * fields, which knew nothing of `validation.integer`, of a string becoming an\n * enum, of an array's element type or of anything about a relation beyond its\n * name — and every one of those was reported \"safe, no change\", committed, and\n * never applied. A difference in the plan cannot be missed that way: a new kind\n * of edit either changes the plan, and is classified here from the difference,\n * or it does not change the database.\n *\n * The property-level reading is still here, for the parts only it can name: a\n * property added or removed, a required flag, an enum's values, a key renamed.\n *\n * ## Why refusing is the right default for the rest\n *\n * Dropping a column, narrowing a type, changing a primary key: each is\n * expressible in SQL and none is expressible by `ensure`. They need a migration\n * somebody wrote and read. `needs-migration` says exactly that, and naming the\n * change is more useful than attempting it.\n */\nimport {\n hasForeignKeyOnTarget,\n isManyToMany,\n type CollectionConfig,\n type Property,\n type RelationProperty,\n type ResolvedRelation,\n type SchemaChange,\n type SchemaChangeKind,\n type SchemaChangeVerdict,\n type ClassifiedSchemaChanges\n} from \"@rebasepro/types\";\nimport { findRelation, getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { resolveColumnName } from \"./column-plan-helpers\";\nimport { planSchema } from \"./plan/plan-schema\";\nimport { renderPgType } from \"./plan/render-ddl\";\nimport { typesAgree } from \"./column-type-drift\";\nimport type { ColumnPlan, ForeignKeyPlan, SchemaPlan, TablePlan } from \"./plan/types\";\n\n/**\n * The vocabulary lives in `@rebasepro/types` so that `@rebasepro/server`, which\n * cannot import this package, can still describe a change. Re-exported here\n * under the names this module has always used.\n */\nexport type ChangeVerdict = SchemaChangeVerdict;\nexport type ChangeKind = SchemaChangeKind;\nexport type { SchemaChange };\nexport type ClassifiedChanges = ClassifiedSchemaChanges;\n\nconst VERDICT_RANK: Record<SchemaChangeVerdict, number> = {\n safe: 0,\n diverges: 1,\n \"needs-migration\": 2\n};\n\nconst bySlug = (collections: CollectionConfig[]): Map<string, CollectionConfig> => {\n const map = new Map<string, CollectionConfig>();\n for (const collection of collections) {\n if (collection.slug) map.set(collection.slug, collection);\n }\n return map;\n};\n\nconst propertiesOf = (collection: CollectionConfig): Record<string, Property> =>\n (collection.properties ?? {}) as Record<string, Property>;\n\n/** Enum values a string property declares, or undefined when it is not an enum. */\nconst enumValuesOf = (prop: Property): string[] | undefined => {\n const values = (prop as { enum?: unknown }).enum;\n if (!Array.isArray(values)) return undefined;\n return values.map(value =>\n typeof value === \"string\" ? value : String((value as { id?: unknown })?.id ?? value)\n );\n};\n\nconst isRequired = (prop: Property): boolean => prop.validation?.required === true;\n\nconst isIdProperty = (prop: Property): boolean => Boolean((prop as { isId?: unknown }).isId);\n\n/**\n * Facts about the database a change is destined for.\n *\n * Three of the verdicts below cannot be reached from the collections alone:\n * whether a NOT NULL can be added comes down to whether the table holds rows,\n * and whether an enum value will land comes down to which values the type\n * already has. Without these, the classifier answers conservatively — the\n * change *may* diverge — which is the right answer for a caller that has no\n * database to look at, and the wrong one to show somebody staring at theirs.\n */\nexport interface SchemaFacts {\n /** `schema.table` → columns. */\n tables: Map<string, Set<string>>;\n /** Tables known to hold at least one row. */\n populatedTables?: Set<string>;\n /** `schema.table.column` for every column the database marks NOT NULL. */\n notNullColumns?: Set<string>;\n /** `schema.typename` → the values that type currently holds. */\n enumValues?: Map<string, string[]>;\n /** `schema.table.column` → the type Postgres reports for it (`udt_name`). */\n columnTypes?: Map<string, string>;\n /** `schema.table.column` of every column that has a DEFAULT. */\n columnDefaults?: Set<string>;\n}\n\n/**\n * The warning a column left behind needs when nothing will write it any more:\n * NOT NULL with no default rejects every insert that omits it — which, once no\n * property names it, is every insert. The `products.sku` case of 2026-09-08,\n * where the demo seed died with 23502 and left every later table empty.\n */\nfunction orphanedNotNullWarning(table: string, column: string, facts: SchemaFacts | undefined): string {\n const key = `${table}.${column}`;\n if (!facts?.notNullColumns?.has(key) || facts.columnDefaults?.has(key)) return \"\";\n const [schema, bare] = table.split(\".\");\n return ` \"${column}\" is NOT NULL with no default, so every insert into \"${table}\" fails until it is ` +\n `dropped or made nullable — ALTER TABLE \"${schema}\".\"${bare}\" ALTER COLUMN \"${column}\" DROP NOT NULL.`;\n}\n\n/** `products.sku`: the bare table and column, the way a commit subject names it. */\nconst shortColumn = (collection: CollectionConfig, column: string): string => `${getTableName(collection)}.${column}`;\n\n/** Whether the table behind a collection exists and is empty. */\nconst tableIsEmpty = (collection: CollectionConfig, facts?: SchemaFacts): boolean => {\n if (!facts?.populatedTables) return false;\n const key = qualifiedTable(collection);\n // A table the database does not have yet is one this change creates, and a\n // table being created has no rows to check a constraint against.\n if (!facts.tables.has(key)) return true;\n return !facts.populatedTables.has(key);\n};\n\nconst qualifiedTable = (collection: CollectionConfig): string => {\n const schema = (collection as { schema?: string }).schema ?? \"public\";\n return `${schema}.${getTableName(collection)}`;\n};\n\n/** The two plans a change is read from. `before` is absent when it cannot be planned. */\ninterface Plans {\n before?: SchemaPlan;\n after: SchemaPlan;\n}\n\nconst collectionTableOf = (plan: SchemaPlan | undefined, collection: CollectionConfig): TablePlan | undefined =>\n plan?.tables.find(table => table.kind === \"collection\" && table.slug === collection.slug);\n\nconst keyText = (table: TablePlan): string => `(${table.primaryKey.map(column => `\"${column}\"`).join(\", \")})`;\n\n/**\n * \" The primary key goes from (\"id\") to (\"id\", \"locale\").\" — or \"\" when the\n * plans do not say, or say the key is unchanged.\n *\n * Several `isId` properties are one composite key, so marking or unmarking one\n * of them moves the whole constraint, not one column's flag; naming the key on\n * both sides is what tells the reader which constraint the migration replaces.\n */\nconst keyMoveText = (before: TablePlan | undefined, after: TablePlan | undefined): string =>\n before && after && keyText(before) !== keyText(after)\n ? ` The primary key goes from ${keyText(before)} to ${keyText(after)}.`\n : \"\";\n\n/** The migration that moves a table's primary key from `before`'s to `after`'s. */\nconst keyMoveRemedy = (before: TablePlan | undefined, after: TablePlan | undefined): string =>\n \"A primary key change rewrites the table and every foreign key into it. Migration only\" +\n (before && after && keyText(before) !== keyText(after)\n ? `: ALTER TABLE \"${after.schema}\".\"${after.table}\" DROP CONSTRAINT \"${after.table}_pkey\", ` +\n `ADD PRIMARY KEY ${keyText(after)} — after checking that no two rows share the new key.`\n : \".\");\n\n/** The resolved relation behind a relation property, when it resolves. */\nconst relationOf = (collection: CollectionConfig, propName: string, prop: Property): ResolvedRelation | undefined => {\n if (prop.type !== \"relation\") return undefined;\n const name = (prop as RelationProperty).relation?.relationName ?? propName;\n try {\n return findRelation(resolveCollectionRelations(collection), name);\n } catch {\n return undefined;\n }\n};\n\n/** What one property puts in the database: its columns, and the junction tables it implies. */\ninterface Footprint {\n columns: ColumnPlan[];\n junctions: TablePlan[];\n}\n\nconst PROPERTY_SOURCES = new Set([\"property\", \"relation\", \"reference\"]);\n\nfunction footprintOf(plan: SchemaPlan | undefined, collection: CollectionConfig, propName: string, prop: Property): Footprint {\n const table = collectionTableOf(plan, collection);\n const columns = table?.columns.filter(column =>\n column.source.propName === propName && PROPERTY_SOURCES.has(column.source.kind)) ?? [];\n const junctions: TablePlan[] = [];\n const relation = relationOf(collection, propName, prop);\n if (plan && relation && isManyToMany(relation)) {\n const junction = plan.tables.find(t => t.kind === \"junction\" && t.table === relation.through.table);\n if (junction) junctions.push(junction);\n }\n return { columns, junctions };\n}\n\n/** \"column \"x\"\", \"columns \"x\", \"y\"\", \"junction table \"a_b\"\" — what a footprint is, in words. */\nfunction describeFootprint(footprint: Footprint): string {\n const parts: string[] = [];\n if (footprint.columns.length > 0) {\n const names = footprint.columns.map(c => `\"${c.column}\"`).join(\", \");\n parts.push(`${footprint.columns.length === 1 ? \"column\" : \"columns\"} ${names}`);\n }\n for (const junction of footprint.junctions) parts.push(`junction table \"${junction.qualified}\"`);\n return parts.join(\" and \");\n}\n\n/** A column's type as one comparable string. An enum is its type, not its labels. */\nfunction typeKey(column: ColumnPlan): string {\n if (column.generated) return `generated:${renderPgType(column.type)}:${column.generated.expression}`;\n if (column.sqlDefinition) return `sql:${column.sqlDefinition}`;\n if (column.type.kind === \"enum\") return `enum:${column.type.schema}.${column.type.name}`;\n const identity = column.default?.kind === \"identity\" ? \" IDENTITY\" : \"\";\n return `${renderPgType(column.type)}${identity}`;\n}\n\n/** A column's type, for a sentence. */\nfunction typeText(column: ColumnPlan): string {\n if (column.type.kind === \"enum\") return `the enum ${column.type.name}`;\n return renderPgType(column.type);\n}\n\nfunction defaultText(column: ColumnPlan): string | undefined {\n if (!column.default || column.default.kind === \"identity\") return undefined;\n return column.default.kind === \"sql\" ? column.default.expression : column.default.sql;\n}\n\nfunction foreignKeyText(fk: ForeignKeyPlan | undefined): string | undefined {\n if (!fk) return undefined;\n return `\"${fk.targetSchema}\".\"${fk.targetTable}\" (\"${fk.targetColumn}\") ON DELETE ${fk.onDelete.toUpperCase()}` +\n (fk.onUpdate ? ` ON UPDATE ${fk.onUpdate.toUpperCase()}` : \"\");\n}\n\n/** Plan a collection set, or say why it cannot be planned. */\nfunction tryPlan(collections: CollectionConfig[]): SchemaPlan | Error {\n try {\n return planSchema(collections);\n } catch (err) {\n return err instanceof Error ? err : new Error(String(err));\n }\n}\n\n/**\n * Classify the difference between two collection sets.\n *\n * `before` is what the running database was built from; `after` is what the\n * editor is proposing. Order within each array is irrelevant.\n *\n * `facts` is what the database actually looks like. Supplied by the live\n * editor; omitted by callers reasoning about collections in the abstract, who\n * get the conservative reading.\n */\nexport function classifyCollectionChanges(\n before: CollectionConfig[],\n after: CollectionConfig[],\n facts?: SchemaFacts\n): ClassifiedChanges {\n const previous = bySlug(before);\n const next = bySlug(after);\n const changes: SchemaChange[] = [];\n\n const beforePlan = tryPlan(before);\n const afterPlan = tryPlan(after);\n if (afterPlan instanceof Error) {\n // A proposal the planner refuses is not a change the database can\n // take, whatever else is true of it — and it is the planner's message\n // that says what to fix.\n changes.push({\n kind: \"invalid-collection\",\n verdict: \"needs-migration\",\n collection: [...next.keys()].find(slug => !previous.has(slug)) ?? [...next.keys()][0] ?? \"\",\n detail: `The proposed collections cannot be planned: ${afterPlan.message}`,\n remedy: \"Fix the collection so its schema can be generated, then try again.\"\n });\n }\n const plans: Plans | undefined = afterPlan instanceof Error\n ? undefined\n : { before: beforePlan instanceof Error ? undefined : beforePlan, after: afterPlan };\n\n for (const [slug, collection] of next) {\n if (!previous.has(slug)) {\n changes.push({\n kind: \"add-collection\",\n verdict: \"safe\",\n collection: slug,\n detail: `New collection \"${slug}\" — creates table \"${getTableName(collection)}\".`\n });\n continue;\n }\n const was = previous.get(slug)!;\n // The slug is the identity, so a collection that kept it and changed\n // its table is the same collection *moved*. The ensure path would\n // create the new table and never touch the old one: the collection\n // would come back empty, with every row it had left behind.\n if (qualifiedTable(was) !== qualifiedTable(collection)) {\n changes.push({\n kind: \"rename-table\",\n verdict: \"needs-migration\",\n collection: slug,\n detail:\n `\"${slug}\" moves from table \"${qualifiedTable(was)}\" to \"${qualifiedTable(collection)}\", ` +\n \"which would create the new table empty and leave every row in the old one.\",\n remedy:\n \"The ensure path never renames or moves a table. Do it in a migration you have read \" +\n \"(ALTER TABLE … RENAME TO, or SET SCHEMA), then change the collection to match.\"\n });\n continue;\n }\n classifyProperties(was, collection, changes, facts, plans);\n if (plans) {\n classifyTable(was, collection, collectionTableOf(plans.before, was), collectionTableOf(plans.after, collection), changes, facts);\n }\n }\n\n for (const [slug, collection] of previous) {\n if (next.has(slug)) continue;\n changes.push({\n kind: \"remove-collection\",\n verdict: \"needs-migration\",\n collection: slug,\n detail:\n `Collection \"${slug}\" was removed, which would drop table ` +\n `\"${getTableName(collection)}\" and everything in it.`,\n remedy:\n \"The ensure path never drops anything, so this cannot be applied here. Remove the \" +\n \"collection in a migration you have read, or keep it and stop serving it.\"\n });\n }\n\n if (plans?.before) {\n classifyJunctions(plans.before, plans.after, previous, next, changes);\n classifySecurityRules(plans.before, plans.after, changes);\n }\n\n for (const change of changes) {\n if (change.verdict === \"safe\" || change.sourceOnly) continue;\n Object.assign(change, sourceOnlyOf(change, previous.get(change.collection)));\n }\n\n const verdict = changes.reduce<ChangeVerdict>(\n (worst, change) => (VERDICT_RANK[change.verdict] > VERDICT_RANK[worst] ? change.verdict : worst),\n \"safe\"\n );\n\n return { changes, verdict, applicable: verdict === \"safe\" };\n}\n\n/**\n * What \"Edit source only\" leaves behind for the kinds whose consequence does\n * not depend on the column at hand. A kind with no entry here, and no text\n * from where it was classified, is not offered source-only: a primary key\n * moved in the source alone, a relation reading a column nobody creates, a\n * column of the wrong type re-used, a proposal the planner refuses — each\n * leaves a project that does not start or reads its data wrong.\n */\nfunction sourceOnlyOf(change: SchemaChange, was: CollectionConfig | undefined): Pick<SchemaChange, \"sourceOnly\" | \"kept\"> {\n const table = was ? getTableName(was) : change.collection;\n switch (change.kind) {\n case \"remove-collection\":\n return {\n sourceOnly: `Table \"${table}\" and every row in it stay in the database, and nothing serves them.`,\n kept: `table ${table} kept`\n };\n case \"rename-table\":\n return {\n sourceOnly: `The collection reads a new table, which the next start creates empty; every row stays in \"${table}\".`,\n kept: `rows stay in ${table}`\n };\n case \"change-required\":\n return {\n sourceOnly: `The column keeps accepting nulls; it is required in the panel only, until every row has a value and the constraint is added.`,\n kept: `${table}.${change.property ?? \"\"} still nullable`\n };\n case \"add-property\":\n return change.verdict === \"diverges\"\n ? {\n sourceOnly: \"The next start adds the column nullable: it is required in the panel only, until every row has a value and the constraint is added.\",\n kept: `${table}.${change.property ?? \"\"} added nullable`\n }\n : {};\n case \"add-enum-value\":\n return { sourceOnly: \"The value is added to the type when the server next starts.\", kept: \"enum value added at next start\" };\n case \"remove-enum-value\":\n return {\n sourceOnly: \"The type keeps the value, and the rows holding it keep it — the panel will show them as an unknown value.\",\n kept: \"enum value kept\"\n };\n default:\n return {};\n }\n}\n\n/** A change on this property already refuses it — a second refusal would only repeat it. */\nconst alreadyRefused = (changes: SchemaChange[], slug: string, property: string | undefined): boolean =>\n property !== undefined &&\n changes.some(change => change.collection === slug && change.property === property && change.verdict === \"needs-migration\");\n\nfunction classifyProperties(\n before: CollectionConfig,\n after: CollectionConfig,\n changes: SchemaChange[],\n facts: SchemaFacts | undefined,\n plans: Plans | undefined\n): void {\n const slug = after.slug ?? \"\";\n const previous = propertiesOf(before);\n const next = propertiesOf(after);\n const empty = tableIsEmpty(after, facts);\n\n // A key renamed with its column kept — the editor sets `columnName` to the\n // old column — moves no data: the database sees the same column, and the\n // plans agree. It used to read as a removal plus an addition, and was\n // refused as dropping a column nothing dropped.\n const renamed = new Map<string, string>();\n if (plans?.before) {\n const removedKeys = Object.keys(previous).filter(key => !next[key]);\n const addedKeys = Object.keys(next).filter(key => !previous[key]);\n for (const oldKey of removedKeys) {\n const was = footprintOf(plans.before, before, oldKey, previous[oldKey]).columns.map(c => c.column).sort();\n if (was.length === 0) continue;\n const newKey = addedKeys.find(key => !renamed.has(key) &&\n JSON.stringify(footprintOf(plans.after, after, key, next[key]).columns.map(c => c.column).sort()) === JSON.stringify(was));\n if (newKey) renamed.set(newKey, oldKey);\n }\n }\n const renamedFrom = new Set(renamed.values());\n\n for (const [name, prop] of Object.entries(next)) {\n const oldKey = renamed.get(name);\n if (oldKey) {\n const columns = footprintOf(plans?.after, after, name, prop).columns.map(c => `\"${c.column}\"`).join(\", \");\n changes.push({\n kind: \"rename-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail:\n `\"${oldKey}\" is now \"${name}\" and keeps column ${columns} — no data moves. ` +\n `API clients reading \"${oldKey}\" read \"${name}\" from now on.`\n });\n classifyProperty(slug, after, name, previous[oldKey], prop, changes, facts, plans, oldKey);\n continue;\n }\n const old = previous[name];\n if (!old) {\n classifyAddedProperty(slug, after, name, prop, changes, empty, plans, facts);\n continue;\n }\n classifyProperty(slug, after, name, old, prop, changes, facts, plans);\n }\n\n for (const [name, prop] of Object.entries(previous)) {\n if (next[name] || renamedFrom.has(name)) continue;\n classifyRemovedProperty(slug, before, after, name, prop, changes, plans, facts);\n }\n}\n\nfunction classifyAddedProperty(\n slug: string,\n collection: CollectionConfig,\n name: string,\n prop: Property,\n changes: SchemaChange[],\n empty: boolean,\n plans: Plans | undefined,\n facts: SchemaFacts | undefined\n): void {\n const footprint = footprintOf(plans?.after, collection, name, prop);\n const physical = footprint.columns.length > 0;\n if (refuseUnbackedRelation(slug, collection, name, prop, changes, plans)) return;\n if (reusesLeftoverColumn(slug, collection, name, prop, footprint, changes, empty, facts)) return;\n\n // A NOT NULL is checked against rows that are already there, so whether\n // this is safe is a question about the data, not about the configuration.\n // On an empty table the constraint cannot fail and the ensure path applies\n // it; on a populated one it is withheld and the column arrives nullable,\n // which is a database that disagrees with its own config — still worth\n // refusing, now for a reason the reader can act on.\n if (isRequired(prop) && physical && !empty) {\n changes.push({\n kind: \"add-property\",\n verdict: \"diverges\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" is required, but \"${getTableName(collection)}\" already holds rows, so ` +\n \"NOT NULL would be checked against data that has no value for it yet. The \" +\n \"column would arrive nullable.\",\n remedy:\n \"Add it optional, backfill every row, then make it required — the editor \" +\n \"will apply the constraint once no row violates it.\"\n });\n return;\n }\n\n const what = describeFootprint(footprint);\n const relation = relationOf(collection, name, prop);\n const adds = what\n ? `adds ${what}`\n : relation && hasForeignKeyOnTarget(relation)\n ? `adds no column — its links are read from \"${relation.foreignKeyOnTarget}\" on the collection it points at`\n : \"adds nothing to the database\";\n changes.push({\n kind: \"add-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: isRequired(prop) && physical\n ? `New required property \"${name}\" — ${adds} NOT NULL, which \"${getTableName(collection)}\" ` +\n \"can take because it holds no rows.\"\n : `New ${isRequired(prop) ? \"required\" : \"optional\"} property \"${name}\" — ${adds}.`\n });\n}\n\n/**\n * A property added over a column the database already has.\n *\n * \"Edit source only\" removes a property and leaves its column — that is what\n * it is for — so the same name can come back later over a column holding the\n * old values. `ADD COLUMN IF NOT EXISTS` is then a no-op: the plan said \"adds\n * column\", nothing ran, and a property of another type read the old values as\n * its own. Re-using a column of the same type is fine and is said; a column of\n * another type needs converting first.\n */\nfunction reusesLeftoverColumn(\n slug: string,\n collection: CollectionConfig,\n name: string,\n prop: Property,\n footprint: Footprint,\n changes: SchemaChange[],\n empty: boolean,\n facts: SchemaFacts | undefined\n): boolean {\n const table = qualifiedTable(collection);\n const present = facts?.tables.get(table);\n const leftover = footprint.columns.filter(column => present?.has(column.column));\n if (leftover.length === 0) return false;\n\n for (const column of leftover) {\n const actual = facts?.columnTypes?.get(`${table}.${column.column}`);\n const declared = renderPgType(column.type);\n if (actual && !typesAgree(declared, actual)) {\n changes.push({\n kind: \"add-property\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" would use column \"${column.column}\", which is already there as ${actual} — ` +\n `left by an earlier removal — while \"${name}\" declares ${declared}. The values in it ` +\n \"would be read as the wrong type.\",\n remedy:\n `Drop or convert \"${column.column}\" in a migration you have read, or give \"${name}\" ` +\n \"another `columnName`.\"\n });\n return true;\n }\n }\n const columns = leftover.map(c => `\"${c.column}\"`).join(\", \");\n changes.push(isRequired(prop) && !empty\n ? {\n kind: \"add-property\",\n verdict: \"diverges\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" re-uses column ${columns}, which is already there with the values an earlier ` +\n \"property left in it, and is required — NOT NULL would be checked against those rows.\",\n remedy: \"Add it optional, backfill the rows with no value, then make it required.\"\n }\n : {\n kind: \"add-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail:\n `New ${isRequired(prop) ? \"required\" : \"optional\"} property \"${name}\" — re-uses column ` +\n `${columns}, which is already there and keeps the values an earlier property left in it.`\n });\n return true;\n}\n\n/**\n * A `hasMany`/`hasOne` reads its links from a column on the collection it\n * points at. When nothing creates that column, the relation is empty forever,\n * and nothing about planning it said so: the plan was \"safe\", the detail said\n * it added a column it did not add, and no statement ran.\n */\nfunction refuseUnbackedRelation(\n slug: string,\n collection: CollectionConfig,\n name: string,\n prop: Property,\n changes: SchemaChange[],\n plans: Plans | undefined\n): boolean {\n const relation = relationOf(collection, name, prop);\n if (!plans || !relation || !hasForeignKeyOnTarget(relation)) return false;\n let target: CollectionConfig | undefined;\n try {\n target = relation.target();\n } catch {\n return false;\n }\n const targetTable = target ? collectionTableOf(plans.after, target) : undefined;\n if (!targetTable || targetTable.columns.some(column => column.column === relation.foreignKeyOnTarget)) return false;\n changes.push({\n kind: \"change-relation\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" reads its links from column \"${relation.foreignKeyOnTarget}\" of \"${targetTable.qualified}\", ` +\n \"which nothing creates — the relation would always be empty.\",\n remedy:\n `Give \"${target!.slug}\" a belongsTo whose column is \"${relation.foreignKeyOnTarget}\", or point ` +\n \"`foreignKeyOnTarget` at the column that holds the link.\"\n });\n return true;\n}\n\nfunction classifyRemovedProperty(\n slug: string,\n before: CollectionConfig,\n after: CollectionConfig,\n name: string,\n prop: Property,\n changes: SchemaChange[],\n plans: Plans | undefined,\n facts: SchemaFacts | undefined\n): void {\n const footprint = footprintOf(plans?.before, before, name, prop);\n // Only what the proposal no longer has is dropped. A column another\n // property still produces, or a junction the far side still declares,\n // stays where it is.\n const afterTable = collectionTableOf(plans?.after, after);\n const dropped: Footprint = plans?.before\n ? {\n columns: footprint.columns.filter(c => !afterTable?.columns.some(a => a.column === c.column)),\n junctions: footprint.junctions.filter(j => !plans.after.tables.some(t => t.qualified === j.qualified))\n }\n : { columns: [{ column: resolveColumnName(name, prop) } as ColumnPlan], junctions: [] };\n\n if (dropped.columns.length === 0 && dropped.junctions.length === 0) {\n changes.push({\n kind: \"remove-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: footprint.columns.length + footprint.junctions.length > 0\n ? `\"${name}\" was removed; ${describeFootprint(footprint)} stays, because the collections still use it.`\n : `\"${name}\" was removed — it had nothing in the database of its own, so nothing there changes.`\n });\n return;\n }\n const table = qualifiedTable(before);\n const warnings = dropped.columns.map(c => orphanedNotNullWarning(table, c.column, facts)).join(\"\");\n changes.push({\n kind: \"remove-property\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" was removed, which would drop ${describeFootprint(dropped)} and the data in it.`,\n remedy:\n \"The ensure path never drops anything. Remove it in a migration you have read, or \" +\n \"leave the column and stop exposing the property.\",\n sourceOnly:\n `${describeFootprint(dropped)} stays in the database, holding its data, and nothing reads or ` +\n `writes it.${warnings}`,\n kept: [\n ...dropped.columns.map(c => `column ${shortColumn(before, c.column)} kept`),\n ...dropped.junctions.map(j => `junction ${j.table} kept`)\n ].join(\", \")\n });\n}\n\nfunction classifyProperty(\n slug: string,\n collection: CollectionConfig,\n name: string,\n before: Property,\n after: Property,\n changes: SchemaChange[],\n facts: SchemaFacts | undefined,\n plans: Plans | undefined,\n /** The key `before` had, when the key itself was renamed. */\n beforeName = name\n): void {\n // A relation's column is its `localKey`, which the plan comparison reads;\n // `columnName` names the column of every other property.\n if (before.type !== \"relation\" && after.type !== \"relation\") {\n const beforeColumn = resolveColumnName(beforeName, before);\n const afterColumn = resolveColumnName(name, after);\n if (beforeColumn !== afterColumn) {\n changes.push({\n kind: \"rename-column\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail: `\"${name}\" changes column from \"${beforeColumn}\" to \"${afterColumn}\".`,\n remedy:\n \"The ensure path only renames a column through its legacy-name path, which this is \" +\n \"not. Rename it in a migration, or the old column stays and the new one is created \" +\n \"empty beside it.\",\n sourceOnly:\n `The data stays in \"${beforeColumn}\". The next start adds \"${afterColumn}\" empty, and every ` +\n `row already there reads blank until the values are copied across.` +\n orphanedNotNullWarning(qualifiedTable(collection), beforeColumn, facts),\n kept: `data stays in ${shortColumn(collection, beforeColumn)}`\n });\n }\n }\n\n if (isIdProperty(before) !== isIdProperty(after)) {\n const tableBefore = collectionTableOf(plans?.before, collection);\n const tableAfter = collectionTableOf(plans?.after, collection);\n changes.push({\n kind: \"change-primary-key\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail: `\"${name}\" changes whether it is the primary key.${keyMoveText(tableBefore, tableAfter)}`,\n remedy: keyMoveRemedy(tableBefore, tableAfter)\n });\n return;\n }\n\n if (before.type === \"relation\" && after.type === \"relation\") {\n const was = relationOf(collection, name, before);\n const is = relationOf(collection, name, after);\n if (was && is && was.kind !== is.kind) {\n changes.push({\n kind: \"change-relation\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" changes from a ${was.kind} to a ${is.kind} relation. Its links are stored in a ` +\n \"different place for each, and the ones already stored are not moved.\",\n remedy:\n \"Move the links in a migration you have read — copy them into the new place — then change \" +\n \"the relation to match.\",\n sourceOnly:\n `The links stay where the ${was.kind} kept them, and the ${is.kind} reads its own place, which ` +\n \"starts empty — the relation reads as empty until they are moved.\",\n kept: `links of ${slug}.${name} not moved`\n });\n return;\n }\n if (refuseUnbackedRelation(slug, collection, name, after, changes, plans)) return;\n }\n\n const afterColumn = resolveColumnName(name, after);\n if (!isRequired(before) && isRequired(after)) {\n // Tightening. `SET NOT NULL` scans the table, so this comes down to\n // whether anything in it is null — which on an empty table is nothing.\n const empty = tableIsEmpty(collection, facts);\n changes.push(empty\n ? {\n kind: \"change-required\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" became required — sets NOT NULL on \"${afterColumn}\", which ` +\n `\"${getTableName(collection)}\" can take because it holds no rows.`\n }\n : {\n kind: \"change-required\",\n verdict: \"diverges\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" became required, but \"${getTableName(collection)}\" holds rows and ` +\n \"SET NOT NULL is checked against every one of them. The database would keep \" +\n \"accepting nulls.\",\n remedy:\n `Backfill first — UPDATE the rows where \"${afterColumn}\" IS NULL — then apply ` +\n \"this again.\"\n });\n }\n\n if (isRequired(before) && !isRequired(after)) {\n // Relaxing. `DROP NOT NULL` cannot fail and cannot lose data, so this is\n // safe on any table; the editor plans it because a reviewed change is\n // the one context in which touching an existing column's constraints is\n // something somebody asked for.\n changes.push({\n kind: \"change-required\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: `\"${name}\" is no longer required — drops NOT NULL from \"${afterColumn}\".`\n });\n }\n\n classifyEnum(slug, name, before, after, changes, facts);\n}\n\n/**\n * Everything the plans say changed about a table that already exists — the\n * part no reading of the properties can be trusted to find.\n *\n * Each difference is something the ensure path either does (a new index, a\n * new trigger, a default) or does not (a column's type, its uniqueness, its\n * foreign key, a column the plan no longer has). A difference already refused\n * by the property-level reading — a removed property, a renamed column — is not\n * refused twice.\n */\nfunction classifyTable(\n wasCollection: CollectionConfig,\n collection: CollectionConfig,\n before: TablePlan | undefined,\n after: TablePlan | undefined,\n changes: SchemaChange[],\n facts: SchemaFacts | undefined\n): void {\n if (!before || !after) return;\n const slug = collection.slug ?? \"\";\n /** \"string to number — \" when the property's own type changed too. */\n const propertyTypes = (property: string | undefined): string => {\n if (!property) return \"\";\n const was = propertiesOf(wasCollection)[property]?.type;\n const is = propertiesOf(collection)[property]?.type;\n return was && is && was !== is ? `from ${was} to ${is} — ` : \"\";\n };\n const push = (change: Omit<SchemaChange, \"collection\">) => {\n if (alreadyRefused(changes, slug, change.property)) return;\n changes.push({ ...change, collection: slug });\n };\n const label = (column: ColumnPlan) => column.source.propName ?? column.column;\n\n // The key as a whole. A composite key is one constraint over several\n // columns, so a column joining or leaving it moves that constraint — and the\n // ensure path declares a primary key only when it creates the table. A\n // property newly added with `isId` reaches here only: as a new column it\n // reads as an addition, and applied as one it would leave the key as it was.\n // Reported once per collection; the property that changed its own `isId`\n // has already said it.\n if (keyText(before) !== keyText(after) && !changes.some(c => c.collection === slug && c.kind === \"change-primary-key\")) {\n const moved = [\n ...after.primaryKey.filter(column => !before.primaryKey.includes(column)),\n ...before.primaryKey.filter(column => !after.primaryKey.includes(column))\n ];\n const movedColumn = after.columns.find(c => moved.includes(c.column)) ?? before.columns.find(c => moved.includes(c.column));\n changes.push({\n kind: \"change-primary-key\",\n verdict: \"needs-migration\",\n collection: slug,\n property: movedColumn?.source.propName,\n detail: `The primary key of \"${after.table}\" changes.${keyMoveText(before, after)}`,\n remedy: keyMoveRemedy(before, after)\n });\n }\n\n for (const column of after.columns) {\n const was = before.columns.find(c => c.column === column.column);\n if (!was) continue;\n const property = column.source.propName ?? was.source.propName;\n const where = `\"${label(column)}\"`;\n\n if (typeKey(was) !== typeKey(column)) {\n push({\n kind: \"change-property-type\",\n verdict: \"needs-migration\",\n property,\n detail: `${where} changes ${propertyTypes(property)}column \"${column.column}\" goes from ` +\n `${typeText(was)} to ${typeText(column)}.`,\n remedy:\n \"There is no ALTER COLUMN TYPE in the ensure path, and a cast can fail on data that \" +\n \"is already there. Change it in a migration.\",\n sourceOnly:\n `Column \"${column.column}\" keeps its type, ${typeText(was)}: the property is read and written ` +\n `through it, and a value ${typeText(was)} cannot hold is refused.`,\n kept: `column ${shortColumn(collection, column.column)} stays ${typeText(was)}`\n });\n continue;\n }\n // Membership of the key, not `primaryKey`: that flag marks a key\n // declared inline on its one column, and a column that stays in the key\n // as it becomes composite loses the flag without leaving the key.\n if (before.primaryKey.includes(was.column) !== after.primaryKey.includes(column.column)) {\n push({\n kind: \"change-primary-key\",\n verdict: \"needs-migration\",\n property,\n detail: `${where} changes whether \"${column.column}\" is part of the primary key.${keyMoveText(before, after)}`,\n remedy: keyMoveRemedy(before, after)\n });\n continue;\n }\n if (was.unique !== column.unique) {\n push({\n kind: \"change-constraint\",\n verdict: \"needs-migration\",\n property,\n detail: column.unique\n ? `${where} becomes unique — a UNIQUE constraint on \"${column.column}\", checked against every existing row.`\n : `${where} is no longer unique — the UNIQUE constraint on \"${column.column}\" stays until it is dropped.`,\n remedy: column.unique\n ? `The ensure path never constrains an existing column. Check for duplicates, then ALTER TABLE ` +\n `\"${after.schema}\".\"${after.table}\" ADD UNIQUE (\"${column.column}\") in a migration.`\n : `Drop it in a migration: ALTER TABLE \"${after.schema}\".\"${after.table}\" DROP CONSTRAINT …`,\n sourceOnly: column.unique\n ? `\"${column.column}\" keeps accepting duplicates until the constraint is added.`\n : `The UNIQUE constraint on \"${column.column}\" stays and keeps refusing duplicates.`,\n kept: `${shortColumn(collection, column.column)} uniqueness unchanged`\n });\n }\n const fkWas = foreignKeyText(was.foreignKey);\n const fkIs = foreignKeyText(column.foreignKey);\n if (fkWas !== fkIs) {\n if (!fkWas) {\n push({\n kind: \"change-relation\",\n verdict: \"safe\",\n property,\n detail:\n `${where} gains a foreign key — \"${column.column}\" REFERENCES ${fkIs}. It is checked ` +\n \"against every existing row, and a value pointing at nothing makes it fail.\"\n });\n } else {\n push({\n kind: \"change-relation\",\n verdict: \"needs-migration\",\n property,\n detail: fkIs\n ? `${where} changes its foreign key from ${fkWas} to ${fkIs}. The constraint already ` +\n \"exists under its name, so the ensure path would leave the old one in place.\"\n : `${where} no longer declares its foreign key, but the constraint on \"${column.column}\" ` +\n `(${fkWas}) stays and keeps refusing values.`,\n remedy:\n \"Drop and re-create the constraint in a migration you have read — ALTER TABLE … DROP \" +\n \"CONSTRAINT, then ADD CONSTRAINT.\",\n sourceOnly: `The constraint on \"${column.column}\" stays as it is: ${fkWas}.`,\n kept: `foreign key on ${shortColumn(collection, column.column)} unchanged`\n });\n }\n }\n const defaultWas = defaultText(was);\n const defaultIs = defaultText(column);\n if (defaultWas !== defaultIs) {\n // A DEFAULT binds future writes only: setting, changing or dropping\n // it cannot fail and touches no row. `generateSchemaCommit` adds the\n // statement the additive ensure would not.\n push({\n kind: \"change-default\",\n verdict: \"safe\",\n property,\n detail: defaultIs\n ? `${where} defaults to ${defaultIs} — SET DEFAULT on \"${column.column}\"; rows already there keep their values.`\n : `${where} no longer has a default — DROP DEFAULT on \"${column.column}\"; rows already there keep their values.`\n });\n }\n }\n\n for (const column of before.columns) {\n if (after.columns.some(c => c.column === column.column)) continue;\n const property = column.source.propName;\n // A removed property, a column renamed through `columnName`, a\n // relation whose kind changed: already refused, naming the property.\n if (alreadyRefused(changes, slug, property)) continue;\n push({\n kind: column.source.kind === \"relation\" ? \"change-relation\" : \"remove-property\",\n verdict: \"needs-migration\",\n property,\n detail: column.source.kind === \"relation\"\n ? `\"${label(column)}\" no longer keeps its links in column \"${column.column}\", and the links ` +\n \"already there would not be moved — the new place starts empty.\"\n : `Column \"${column.column}\" is no longer part of \"${slug}\", which would drop it and its data.`,\n remedy:\n \"Move the data in a migration you have read, or keep the relation's column where it is \" +\n \"(`localKey`).\",\n sourceOnly:\n `Column \"${column.column}\" stays, holding its data, and nothing reads or writes it.` +\n orphanedNotNullWarning(after.qualified, column.column, facts),\n kept: `column ${shortColumn(collection, column.column)} kept`\n });\n }\n\n for (const index of after.indexes) {\n if (before.indexes.some(i => i.indexName === index.indexName)) continue;\n push({\n kind: \"change-constraint\",\n verdict: \"safe\",\n // A derived foreign-key index on a table that already exists is\n // not among the statements this change applies: the next boot\n // builds it in the background, concurrently — see\n // `deferred-index-build`. Said, so the plan does not claim it.\n detail: index.derived === \"foreign-key\"\n ? `Indexes the foreign key \"${index.keys[0].column}\" (\"${index.indexName}\" on \"${after.qualified}\") — ` +\n \"with the table when it is new, otherwise in the background on the next boot.\"\n : `Creates ${index.unique ? \"unique \" : \"\"}index \"${index.indexName}\" on \"${after.qualified}\".`\n });\n }\n for (const index of before.indexes) {\n if (after.indexes.some(i => i.indexName === index.indexName)) continue;\n push({\n kind: \"change-constraint\",\n verdict: index.unique ? \"needs-migration\" : \"safe\",\n detail: index.unique\n ? `Unique index \"${index.indexName}\" is no longer declared, but it stays and keeps refusing duplicates.`\n : `Index \"${index.indexName}\" is no longer declared; it stays in the database until it is dropped.`,\n remedy: index.unique ? `Drop it in a migration: DROP INDEX \"${after.schema}\".\"${index.indexName}\".` : undefined,\n sourceOnly: index.unique ? `Index \"${index.indexName}\" stays and keeps refusing duplicates.` : undefined,\n kept: index.unique ? `index ${index.indexName} kept` : undefined\n });\n }\n for (const trigger of before.triggers) {\n if (after.triggers.some(t => t.name === trigger.name)) continue;\n push({\n kind: \"change-constraint\",\n verdict: \"needs-migration\",\n property: before.columns.find(c => c.column === trigger.column)?.source.propName,\n detail:\n `\"${trigger.column}\" is no longer stamped on update, but trigger \"${trigger.name}\" stays and ` +\n \"keeps stamping it.\",\n remedy: `Drop it in a migration: DROP TRIGGER \"${trigger.name}\" ON \"${after.schema}\".\"${after.table}\".`,\n sourceOnly: `Trigger \"${trigger.name}\" stays and keeps stamping \"${trigger.column}\" on every update.`,\n kept: `trigger ${trigger.name} kept`\n });\n }\n for (const trigger of after.triggers) {\n if (before.triggers.some(t => t.name === trigger.name)) continue;\n push({\n kind: \"change-constraint\",\n verdict: \"safe\",\n property: after.columns.find(c => c.column === trigger.column)?.source.propName,\n detail: `\"${trigger.column}\" is stamped with now() on every update — creates trigger \"${trigger.name}\".`\n });\n }\n}\n\n/**\n * Junction tables that appear or disappear without a collection or a property\n * being added or removed — a relation that changed kind, or the far side of a\n * many-to-many dropping out.\n */\nfunction classifyJunctions(\n before: SchemaPlan,\n after: SchemaPlan,\n previous: Map<string, CollectionConfig>,\n next: Map<string, CollectionConfig>,\n changes: SchemaChange[]\n): void {\n const mentioned = (table: string) => changes.some(change => change.detail.includes(`\"${table}\"`));\n for (const table of before.tables) {\n if (table.kind !== \"junction\" || after.tables.some(t => t.qualified === table.qualified)) continue;\n const slugs = table.declaringSlugs ?? [];\n // A removed collection already says it takes everything with it.\n if (slugs.length > 0 && slugs.every(slug => !next.has(slug))) continue;\n if (mentioned(table.qualified)) continue;\n changes.push({\n kind: \"change-relation\",\n verdict: \"needs-migration\",\n collection: slugs[0] ?? \"\",\n detail: `Junction table \"${table.qualified}\" is no longer declared, which would drop it and every link in it.`,\n remedy: \"The ensure path never drops a table. Drop it in a migration you have read, or keep the relation.\",\n sourceOnly: `Junction table \"${table.qualified}\" stays, holding its links, and nothing reads it.`,\n kept: `junction ${table.table} kept`\n });\n }\n for (const table of after.tables) {\n if (table.kind !== \"junction\" || before.tables.some(t => t.qualified === table.qualified)) continue;\n const slugs = table.declaringSlugs ?? [];\n if (slugs.length > 0 && slugs.every(slug => !previous.has(slug))) continue;\n if (mentioned(table.qualified)) continue;\n changes.push({\n kind: \"change-relation\",\n verdict: \"safe\",\n collection: slugs[0] ?? \"\",\n detail: `Creates junction table \"${table.qualified}\", empty.`\n });\n }\n}\n\n/**\n * A table whose compiled policies differ — a security rule added, removed or\n * changed.\n *\n * Safe: the policies are replaced wholesale whenever the server starts\n * (`ensureCollectionPolicies`), and nothing about the table's data changes.\n * But it is a change, with an effect on who can read what, and it was planned\n * as none and committed as `chore(schema): no change`.\n *\n * Compared as compiled policies rather than as rules, so a rule spelled\n * differently into the same policy is not a change, and one whose meaning\n * changed is — a policy's name embeds a hash of what it does.\n */\nfunction classifySecurityRules(before: SchemaPlan, after: SchemaPlan, changes: SchemaChange[]): void {\n const policyKey = (table: TablePlan) => JSON.stringify(\n table.policies.map(({ name, operation, mode, roles, using, withCheck }) =>\n ({ name, operation, mode, roles, using, withCheck }))\n .sort((a, b) => a.name.localeCompare(b.name))\n );\n for (const table of after.tables) {\n if (table.kind !== \"collection\" || !table.slug) continue;\n const was = before.tables.find(t => t.qualified === table.qualified && t.kind === \"collection\");\n if (!was || policyKey(was) === policyKey(table)) continue;\n const names = (plan: TablePlan) => new Set(plan.policies.map(p => p.name));\n const added = [...names(table)].filter(name => !names(was).has(name)).length;\n const removed = [...names(was)].filter(name => !names(table).has(name)).length;\n const counts = [\n added > 0 ? `${added} ${added === 1 ? \"policy\" : \"policies\"} added` : \"\",\n removed > 0 ? `${removed} removed` : \"\"\n ].filter(Boolean).join(\", \");\n changes.push({\n kind: \"change-security-rules\",\n verdict: \"safe\",\n collection: table.slug,\n detail:\n `Who can read and write \"${table.qualified}\" changes${counts ? ` (${counts})` : \"\"}. Row-level ` +\n \"security policies are replaced at the server's next start — at once under `rebase dev`, which \" +\n \"restarts on the commit, and at the next deploy elsewhere. Until then the old policies apply.\"\n });\n }\n}\n\nfunction classifyEnum(\n slug: string,\n name: string,\n before: Property,\n after: Property,\n changes: SchemaChange[],\n facts?: SchemaFacts\n): void {\n const oldValues = enumValuesOf(before);\n const newValues = enumValuesOf(after);\n if (!oldValues || !newValues) return;\n\n const added = newValues.filter(value => !oldValues.includes(value));\n const removed = oldValues.filter(value => !newValues.includes(value));\n\n if (added.length > 0) {\n // `ADD VALUE` is additive, idempotent with IF NOT EXISTS, and needs no\n // table scan, so the ensure path now carries it — but only when it can\n // see which values the type already has. A caller with no database\n // cannot know that, and for them this is still the change that silently\n // does not land.\n const seesTheType = facts?.enumValues !== undefined;\n changes.push(seesTheType\n ? {\n kind: \"add-enum-value\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: `\"${name}\" gains ${added.map(v => `\"${v}\"`).join(\", \")} — ALTER TYPE … ADD VALUE.`\n }\n : {\n kind: \"add-enum-value\",\n verdict: \"diverges\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" gains ${added.map(v => `\"${v}\"`).join(\", \")}, and the values this ` +\n \"type already has are not known here, so whether they would land cannot be said.\",\n remedy: \"Plan this against the database it is destined for.\"\n });\n }\n\n if (removed.length > 0) {\n changes.push({\n kind: \"remove-enum-value\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail: `\"${name}\" drops ${removed.map(v => `\"${v}\"`).join(\", \")}.`,\n remedy:\n \"Postgres cannot remove a value from an enum type. Recreate the type in a migration, \" +\n \"after rewriting every row still using the value.\"\n });\n }\n}\n\n/**\n * The statements a default change needs that the additive ensure does not\n * plan: it sets a DEFAULT only on a column that has none, and never changes or\n * drops one. Both are metadata-only — a DEFAULT binds future writes and touches\n * no row — so a reviewed change may make them.\n */\nexport function defaultChangeStatements(\n before: CollectionConfig[],\n after: CollectionConfig[],\n existingTables: Map<string, Set<string>>\n): string[] {\n const beforePlan = tryPlan(before);\n const afterPlan = tryPlan(after);\n if (beforePlan instanceof Error || afterPlan instanceof Error) return [];\n const statements: string[] = [];\n for (const table of afterPlan.tables) {\n const was = beforePlan.tables.find(t => t.qualified === table.qualified);\n if (!was) continue;\n for (const column of table.columns) {\n const old = was.columns.find(c => c.column === column.column);\n if (!old || !existingTables.get(table.qualified)?.has(column.column)) continue;\n if (old.default?.kind === \"identity\" || column.default?.kind === \"identity\" || column.generated) continue;\n const from = defaultText(old);\n const to = defaultText(column);\n if (from === to) continue;\n const target = `ALTER TABLE \"${table.schema}\".\"${table.table}\" ALTER COLUMN \"${column.column}\"`;\n statements.push(to ? `${target} SET DEFAULT ${to};` : `${target} DROP DEFAULT;`);\n }\n }\n return statements;\n}\n\n/** A one-line summary, for a log or a refusal message. */\nexport function summarizeChanges(classified: ClassifiedChanges): string {\n if (classified.changes.length === 0) return \"No schema changes.\";\n\n const counts = classified.changes.reduce<Record<ChangeVerdict, number>>(\n (acc, change) => ({ ...acc, [change.verdict]: (acc[change.verdict] ?? 0) + 1 }),\n { safe: 0, diverges: 0, \"needs-migration\": 0 }\n );\n\n const parts = ([\"needs-migration\", \"diverges\", \"safe\"] as ChangeVerdict[])\n .filter(verdict => counts[verdict] > 0)\n .map(verdict => `${counts[verdict]} ${verdict}`);\n\n return `${classified.changes.length} change(s): ${parts.join(\", \")}.`;\n}\n","/**\n * Everything a schema change has to write, as file contents.\n *\n * A live schema editor that only edits the collection source produces a repo\n * that does not build: `backend/src/schema.generated.ts` is a committed\n * artifact, and a stale one has broken every deploy at least once. So the unit\n * of a schema change is not a file, it is a **commit** — and this module\n * produces one, without touching a disk, a database or a network.\n *\n * Pure on purpose. The risky half of \"commit, then apply\" is generating a\n * correct commit; keeping it a function from collections to file contents is\n * what lets that half be tested by building a database from the result and\n * comparing it to the one the change describes.\n *\n * ## Where the migration comes from, and why not from Atlas\n *\n * `rebase db generate` mints migrations by running Atlas over the generated\n * `schema.sql`. Atlas is an external binary, it wants a dev database, and it\n * maintains an `atlas.sum` integrity file whose hash this module would have to\n * reproduce byte-for-byte to stay valid.\n *\n * None of that is necessary here, because of what the editor is allowed to do.\n * `classify-change.ts` refuses anything the boot-time ensure path cannot\n * express, which leaves only additive statements — and those are computable as\n * a plain difference between two ensure plans:\n *\n * plan(after, nothing) − plan(before, nothing)\n *\n * Both plans are pure functions of the collections, every statement is\n * idempotent, and the difference is exactly what the change adds. No diff\n * engine, no database, no binary.\n *\n * The statements are *returned* rather than written into a migration file. A\n * project provisioned by boot-ensure needs no migration at all — its\n * collections are the schema — while a project provisioned by migrations needs\n * the file to carry an Atlas hash, which only Atlas can mint. Writing a\n * migration this module cannot make valid would be worse than handing the\n * statements to a caller who knows which kind of project it is.\n */\nimport {\n DEFAULT_COMMIT_PATHS,\n type CollectionConfig,\n type SchemaCommitPaths\n} from \"@rebasepro/types\";\nimport { generateSchema } from \"./generate-drizzle-schema-logic\";\nimport {\n planCollectionSchemaEnsure,\n type EnsureOptions,\n type ExistingSchema,\n type WithheldConstraint\n} from \"./ensure-collection-tables\";\nimport { classifyCollectionChanges, defaultChangeStatements, type ClassifiedChanges } from \"./classify-change\";\n\n/**\n * Re-exported from the shared kernel. `@rebasepro/server` derives these for a\n * project in a subdirectory and cannot import a driver to do it, so the shape\n * and the defaults live in `@rebasepro/types`.\n */\nexport { DEFAULT_COMMIT_PATHS, type SchemaCommitPaths } from \"@rebasepro/types\";\n\nexport interface SchemaCommitFile {\n path: string;\n contents: string;\n}\n\nexport interface SchemaCommitInput {\n /** What the running database was built from. */\n before: CollectionConfig[];\n /** What the editor is proposing. */\n after: CollectionConfig[];\n /**\n * Files the caller has already produced — in practice the rewritten\n * collection source from the AST editor. Carried through unchanged so the\n * commit is complete in one object.\n */\n sourceFiles?: SchemaCommitFile[];\n paths?: Partial<SchemaCommitPaths>;\n /**\n * What the database this change is destined for actually has.\n *\n * Supplied by the live editor, which read it a moment ago; omitted by the\n * pure callers, which have no database. It decides two things a plan cannot\n * know from the collections alone — whether a table holds rows, and which\n * values an enum type already carries — and both are the difference between\n * a statement that applies and one that is rejected.\n */\n existing?: ExistingSchema;\n /**\n * Plan a commit of the source alone — \"Edit source only\". An unapplicable\n * change is not refused: it is committed with no statements, under a\n * message that names what the database keeps.\n */\n sourceOnly?: boolean;\n}\n\nexport interface SchemaCommit {\n /** Every file the commit writes, source and generated alike. */\n files: SchemaCommitFile[];\n /**\n * The additive statements this change adds, in dependency order.\n *\n * Empty when the change needs no DDL. Not written to a migration file —\n * see the module comment.\n */\n statements: string[];\n classified: ClassifiedChanges;\n /** A commit message describing the change in the terms a reader wants. */\n message: string;\n /**\n * Constraints this change asks for that the statements do not carry, and\n * why. Empty for almost every change; when it is not, it is the thing the\n * person confirming needs to read before they confirm.\n */\n withheldConstraints: WithheldConstraint[];\n}\n\nexport class SchemaCommitError extends Error {\n constructor(message: string, readonly classified: ClassifiedChanges) {\n super(message);\n this.name = \"SchemaCommitError\";\n }\n}\n\n/** An `ExistingSchema` describing a database that has nothing in it. */\nconst nothing = () => ({ tables: new Map<string, Set<string>>(), enums: new Set<string>() });\n\n/**\n * The statements that take `before` to `after`.\n *\n * Both sides are planned against the *same* database and the difference is\n * taken by exact statement text. That works because the planner is\n * deterministic: the same collections against the same schema produce the same\n * strings, so anything in the second plan and absent from the first is what\n * this change adds — and nothing else. Planning both sides is what keeps\n * pre-existing drift, which belongs to neither side of the edit, out of the\n * statements this change gets credited with.\n *\n * ## Why `existing` matters more than it looks\n *\n * Planned against `nothing()`, every table reads as one this plan is creating,\n * and the planner is then free to attach constraints that only hold on a table\n * with no rows: a new required property comes out as\n * `ADD COLUMN \"x\" TEXT NOT NULL`, which is right for a fresh table and fails\n * against a live one holding rows. Those statements would be generated,\n * committed, and then rejected by the very database they were written for.\n *\n * So a caller holding a real database passes it, and gets statements that\n * describe that database. `nothing()` stays the default for the pure uses —\n * generating a commit for inspection, and the tests that compare two plans —\n * where there is no database to describe.\n */\nexport function additiveStatements(\n before: CollectionConfig[],\n after: CollectionConfig[],\n existing: ExistingSchema = nothing(),\n options: EnsureOptions = {}\n): string[] {\n const previous = new Set(planCollectionSchemaEnsure(before, existing, options).statements);\n return planCollectionSchemaEnsure(after, existing, options).statements\n .filter(statement => !previous.has(statement));\n}\n\n/**\n * The commit of an \"Edit source only\": what changed in the source, and what\n * the database keeps — `chore(schema): remove sku from products (source only —\n * column products.sku kept)`. The body lists what each change leaves behind.\n */\nexport function sourceOnlyCommitMessage(classified: ClassifiedChanges): string {\n const blocking = classified.changes.filter(change => change.verdict !== \"safe\");\n const kept = blocking.map(change => change.kept).filter(Boolean).join(\"; \");\n const subject = `${subjectOf(classified)} (source only — ${kept || \"the database is unchanged\"})`;\n const body = classified.changes.map(change =>\n `- ${change.detail}${change.sourceOnly && change.verdict !== \"safe\" ? `\\n Left in the database: ${change.sourceOnly}` : \"\"}`\n ).join(\"\\n\");\n return `chore(schema): ${subject}\\n\\n${body}\\n`;\n}\n\n/** What a commit is about, in the words of its subject line. */\nfunction subjectOf(classified: ClassifiedChanges): string {\n const { changes } = classified;\n const collections = [...new Set(changes.map(change => change.collection))].sort();\n const only = changes.length === 1 ? changes[0] : undefined;\n if (only?.kind === \"add-collection\") return `add the ${only.collection} collection`;\n if (only?.kind === \"remove-collection\") return `remove the ${only.collection} collection`;\n if (only?.kind === \"add-property\") return `add ${only.property} to ${only.collection}`;\n if (only?.kind === \"remove-property\") return `remove ${only.property} from ${only.collection}`;\n if (changes.length > 0 && changes.every(c => c.kind === \"change-security-rules\")) {\n return `change the security rules of ${collections.join(\", \")}`;\n }\n if (collections.length === 1) return `${changes.length} change(s) to ${collections[0]}`;\n return `${changes.length} change(s) across ${collections.length} collections`;\n}\n\n/** A commit message that says what changed rather than that something did. */\nexport function commitMessage(classified: ClassifiedChanges): string {\n const { changes } = classified;\n if (changes.length === 0) return \"chore(schema): no change\";\n const body = changes.map(change => `- ${change.detail}`).join(\"\\n\");\n return `feat(schema): ${subjectOf(classified)}\\n\\n${body}\\n`;\n}\n\n/**\n * Build the commit.\n *\n * Refuses when the change is not applicable — a commit describing a schema the\n * ensure path will not produce is a commit that makes the repository lie about\n * the database. The classification travels on the error so a caller can show\n * exactly which change was the problem.\n */\nexport async function generateSchemaCommit(input: SchemaCommitInput): Promise<SchemaCommit> {\n const paths = { ...DEFAULT_COMMIT_PATHS, ...input.paths };\n const existing = input.existing ?? nothing();\n // `converge` because every statement this produces is shown to somebody\n // before it runs. See `ConstraintPolicy` for why the unattended boot does\n // not get the same latitude.\n const options: EnsureOptions = { constraints: \"converge\" };\n const classified = classifyCollectionChanges(input.before, input.after, input.existing);\n\n if (input.sourceOnly) {\n // A change with no account of what it leaves behind is one that may\n // not be written to the source alone — see `sourceOnlyOf`.\n const unoffered = classified.changes.filter(change => change.verdict !== \"safe\" && !change.sourceOnly);\n if (unoffered.length > 0) {\n throw new SchemaCommitError(\n \"This change cannot be written to the collection source alone:\\n\" +\n unoffered.map(change => ` • ${change.detail}${change.remedy ? `\\n ${change.remedy}` : \"\"}`).join(\"\\n\"),\n classified\n );\n }\n // Nothing runs: the source is committed, the database is left as it\n // is, and the message says what it keeps.\n return {\n files: [...(input.sourceFiles ?? []), { path: paths.schemaFile, contents: generateSchema(input.after) }],\n statements: [],\n classified,\n message: sourceOnlyCommitMessage(classified),\n withheldConstraints: []\n };\n }\n\n if (!classified.applicable) {\n const blocking = classified.changes.filter(change => change.verdict !== \"safe\");\n throw new SchemaCommitError(\n `This change cannot be applied to a running database:\\n` +\n blocking.map(change =>\n ` • ${change.detail}${change.remedy ? `\\n ${change.remedy}` : \"\"}`\n ).join(\"\\n\"),\n classified\n );\n }\n\n // The Drizzle schema alone. The SQL `db push` and `db generate` render is\n // not part of a schema change: both regenerate it from the collections\n // before reading it, into the gitignored `.rebase/sql/`, so a copy in the\n // commit would be read by nothing — and was worse than dead weight, since\n // it landed at the project root while the CLI writes beside the backend.\n const generated: SchemaCommitFile[] = [\n { path: paths.schemaFile, contents: generateSchema(input.after) }\n ];\n\n // Both sides planned once, here, rather than through `additiveStatements` —\n // which would plan `after` a second time for the withheld constraints. The\n // planner is pure, so a second call is only wasted work rather than a\n // correctness problem, but this runs on every `/plan` — every change the\n // panel reviews — and again on `/apply`.\n const previous = planCollectionSchemaEnsure(input.before, existing, options);\n const next = planCollectionSchemaEnsure(input.after, existing, options);\n const already = new Set(previous.statements);\n const statements = next.statements.filter(statement => !already.has(statement));\n // A changed or dropped DEFAULT, which the additive ensure never makes and\n // the classifier calls safe — it binds future writes and touches no row.\n for (const statement of defaultChangeStatements(input.before, input.after, existing.tables)) {\n if (!statements.includes(statement)) statements.push(statement);\n }\n\n return {\n files: [...(input.sourceFiles ?? []), ...generated],\n statements,\n classified,\n message: commitMessage(classified),\n // From the `after` plan alone rather than differenced against `before`:\n // a constraint that was already unenforceable is still something the\n // person confirming this change should see named.\n withheldConstraints: next.withheldConstraints\n };\n}\n","import { Client, DatabaseError, type Connection, type Pool, type PoolClient, type Submittable } from \"pg\";\nimport type { SqlCursor, SqlScriptColumn, SqlScriptNotice, SqlScriptResult, SqlScriptTable } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Running a SQL script a person wrote — the Studio console — and saying what\n * came back, column by column.\n *\n * The console used to receive bare rows and guess, from the query's text, which\n * table a cell belonged to and which column held its key. The guess wrote\n * `UPDATE authors SET name = … WHERE id = <the post's id>` for a cell of\n * `SELECT p.id, a.name FROM posts p JOIN authors a …`, and `UPDATE users SET\n * name = …` for `lower(email) AS name`. The database knows the answer: every\n * column of a result's row description carries the table and column it was\n * read from, or zero when it was computed. This reads it off the wire.\n *\n * Values come back as the text the database wrote, never parsed. Parsed, a\n * `text[]` read as a JavaScript array and went back as JSON — a malformed array\n * literal; a `bytea` as a serialized Buffer; a `timestamp` as a Date shifted by\n * the server's timezone; a `numeric` as a float. Text is what the database\n * reads back exactly.\n */\n\n/** The parts of a `RowDescription` field this reads. */\ninterface WireField {\n name: string;\n /** The OID of the table the column was read from; 0 when it was computed. */\n tableID: number;\n /** Its attribute number in that table; 0 when it was computed. */\n columnID: number;\n dataTypeID: number;\n}\n\n/** One statement of the script, as it came back. */\nexport interface StatementOutcome {\n fields: WireField[];\n rows: Record<string, string | null>[];\n /** How many rows the database sent for it, kept or not. */\n received: number;\n /** Rows past the {@link SqlResultLimits} were counted and not kept. */\n truncated: boolean;\n /** The command tag, e.g. `SELECT 3`, `INSERT 0 1`, `CREATE TABLE`. */\n tag?: string;\n}\n\n/** How much of one statement's result a run keeps. */\nexport interface SqlResultLimits {\n /** Rows kept; the rest are counted and let go. */\n maxRows: number;\n /** Characters of values kept, summed over the kept rows. */\n maxTextLength: number;\n}\n\n/**\n * What the Studio console keeps of a result: its first 10,000 rows, or its\n * first 32 Mi characters of values, whichever ends first.\n *\n * Every row used to be kept. `TABLE events` on a million rows held 418 MB of\n * row objects and built a 475 MB frame, and a server with 1 GiB died of it —\n * every other user's connection with it. Ten times the console's own\n * `LIMIT 1000`, so a run that asked for a page always gets it whole; the text\n * budget is for the rows a count does not bound — a document column, a\n * `jsonb` blob — and keeps the frame, the string it is built from and the\n * socket's copy of it around 100 MB at worst.\n *\n * Postgres still sends every row (a simple query cannot be stopped short of\n * cancelling it, which would abort the statements after it); they are counted,\n * so the result says how many there were.\n */\nexport const SQL_CONSOLE_RESULT_LIMITS: SqlResultLimits = { maxRows: 10_000, maxTextLength: 32 * 1024 * 1024 };\n\n/** The rows of one result kept so far, against {@link SqlResultLimits}. */\nclass ResultBudget {\n private rows = 0;\n private textLength = 0;\n\n constructor(private readonly limits: SqlResultLimits) {}\n\n /** Whether a row with `textLength` characters of values is kept; counted when it is. */\n admit(textLength: number): boolean {\n if (this.rows >= this.limits.maxRows || this.textLength + textLength > this.limits.maxTextLength) return false;\n this.rows++;\n this.textLength += textLength;\n return true;\n }\n}\n\nfunction textLengthOf(values: readonly (string | null)[]): number {\n let length = 0;\n for (const value of values) if (value !== null) length += value.length;\n return length;\n}\n\n/**\n * The script, sent as one simple query and read message by message.\n *\n * A `Submittable` rather than `client.query(text)`: node-postgres parses every\n * value through its type parsers and drops each field's origin, and for a\n * multi-statement script returns an array the driver's callers took for a\n * result with no rows. The message handlers below are the ones node-postgres\n * calls on the active query — the same protocol `pg-cursor` implements.\n */\nexport class ScriptRun implements Submittable {\n /**\n * Set by node-postgres when the pool has a `query_timeout`: a wrapper that\n * clears its read timer. Called on every outcome, so the timer never\n * outlives the run.\n */\n callback?: (error: Error | null) => void;\n\n /**\n * Every statement of the script, as it came back. Only the last holds its\n * rows: the console shows the last, and a statement's rows are let go when\n * the next one begins — `SELECT * FROM big; SELECT 1` held every row of\n * `big` to the end, to show one.\n */\n readonly statements: StatementOutcome[] = [];\n readonly done: Promise<StatementOutcome[]>;\n\n /** The statement whose row description arrived and whose command has not completed. */\n private open?: StatementOutcome;\n /** What {@link open} has kept of its rows. */\n private budget: ResultBudget;\n private settled = false;\n private resolveRun: (statements: StatementOutcome[]) => void = () => undefined;\n private rejectRun: (error: Error) => void = () => undefined;\n\n constructor(private readonly text: string, private readonly limits: SqlResultLimits = SQL_CONSOLE_RESULT_LIMITS) {\n this.budget = new ResultBudget(limits);\n this.done = new Promise((resolve, reject) => {\n this.resolveRun = resolve;\n this.rejectRun = reject;\n });\n }\n\n submit(connection: Connection): void {\n connection.query(this.text);\n }\n\n handleRowDescription(message: { fields: WireField[] }): void {\n this.releaseRows();\n this.budget = new ResultBudget(this.limits);\n this.open = {\n fields: message.fields.map(({ name, tableID, columnID, dataTypeID }) => ({ name, tableID, columnID, dataTypeID })),\n rows: [],\n received: 0,\n truncated: false\n };\n this.statements.push(this.open);\n }\n\n handleDataRow(message: { fields: (string | null)[] }): void {\n const statement = this.open;\n if (!statement) return;\n statement.received++;\n if (statement.truncated) return;\n if (!this.budget.admit(textLengthOf(message.fields))) {\n statement.truncated = true;\n return;\n }\n const row: Record<string, string | null> = {};\n statement.fields.forEach((field, index) => {\n row[field.name] = message.fields[index] ?? null;\n });\n statement.rows.push(row);\n }\n\n handleCommandComplete(message: { text: string }): void {\n const statement = this.open ?? { fields: [], rows: [], received: 0, truncated: false };\n if (!this.open) {\n this.releaseRows();\n this.statements.push(statement);\n }\n this.open = undefined;\n statement.tag = message.text;\n }\n\n handleEmptyQuery(): void {\n // An empty statement (`;;`) completes nothing and returns nothing.\n }\n\n handlePortalSuspended(): void {\n // Only the extended protocol suspends a portal; a simple query never does.\n }\n\n /**\n * `COPY … FROM STDIN` waits for data the console has no way to send. Fail\n * the copy rather than leave the session waiting for it.\n */\n handleCopyInResponse(connection: Connection): void {\n if (\"sendCopyFail\" in connection && typeof connection.sendCopyFail === \"function\") {\n connection.sendCopyFail(\"COPY FROM STDIN has no source in the SQL console\");\n }\n }\n\n handleCopyData(): void {\n // `COPY … TO STDOUT` streams data the console does not show.\n }\n\n handleError(error: Error): void {\n this.settle(error);\n }\n\n handleReadyForQuery(): void {\n this.settle(null);\n }\n\n /** Let go of the rows of the statement before the one beginning: only the last is shown. */\n private releaseRows(): void {\n const previous = this.statements[this.statements.length - 1];\n if (previous) previous.rows = [];\n }\n\n private settle(error: Error | null): void {\n if (this.settled) return;\n this.settled = true;\n this.callback?.(error);\n if (error) this.rejectRun(error);\n else this.resolveRun(this.statements);\n }\n}\n\n/**\n * `INSERT 0 1` → `INSERT`, 1. `CREATE TABLE` → `CREATE TABLE`, no count.\n *\n * Only the commands whose tag ends in a count have one; the numbers are\n * stripped from the end of every other tag, of which there are none.\n */\nexport function parseCommandTag(tag: string | undefined): { command?: string; rowCount?: number } {\n if (!tag) return {};\n const counted = /^(SELECT|INSERT|UPDATE|DELETE|MERGE|COPY|MOVE|FETCH)(?: \\d+)? (\\d+)$/.exec(tag);\n if (counted) return { command: counted[1], rowCount: Number(counted[2]) };\n return { command: tag };\n}\n\n/** `pg_class.relkind` → what {@link SqlScriptTable.kind} calls it. */\nfunction relationKind(relkind: string): SqlScriptTable[\"kind\"] {\n switch (relkind) {\n case \"r\": return \"table\";\n case \"p\": return \"partitioned table\";\n case \"v\": return \"view\";\n case \"m\": return \"materialized view\";\n case \"f\": return \"foreign table\";\n default: return \"other\";\n }\n}\n\n/**\n * Name the tables, columns and types the row description referred to by OID.\n *\n * On the run's own session, after it was reset: the catalogue is readable by\n * every role, and the OIDs belong to this database. A failure here costs the\n * result its provenance, never its rows — a column with no source is one the\n * console will not edit.\n */\nasync function describeColumns(connection: PoolClient, fields: WireField[]): Promise<{ columns: SqlScriptColumn[]; tables: SqlScriptTable[] }> {\n const unnamed = { columns: fields.map(field => ({ name: field.name })), tables: [] };\n if (fields.length === 0) return unnamed;\n try {\n const typeOids = [...new Set(fields.map(field => field.dataTypeID))];\n const tableOids = [...new Set(fields.filter(field => field.tableID !== 0).map(field => field.tableID))];\n\n const types = await connection.query<{ oid: string; name: string }>(\n \"SELECT oid::text AS oid, format_type(oid, NULL) AS name FROM pg_type WHERE oid = ANY($1::oid[])\",\n [typeOids]\n );\n const typeNames = new Map(types.rows.map(row => [Number(row.oid), row.name]));\n\n const relations = tableOids.length === 0 ? [] : (await connection.query<{\n oid: string;\n schema: string;\n table: string;\n relkind: string;\n has_inheritors: boolean;\n primary_key: string[] | null;\n }>(\n `SELECT c.oid::text AS oid, n.nspname AS schema, c.relname AS \"table\", c.relkind::text AS relkind,\n (c.relkind = 'r' AND c.relhassubclass) AS has_inheritors,\n (SELECT array_agg(a.attname::text ORDER BY k.ord)\n FROM pg_index i\n CROSS JOIN LATERAL unnest(i.indkey::int2[]) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum\n WHERE i.indrelid = c.oid AND i.indisprimary) AS primary_key\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE c.oid = ANY($1::oid[])`,\n [tableOids]\n )).rows;\n const attributes = tableOids.length === 0 ? [] : (await connection.query<{ oid: string; attnum: number; name: string }>(\n `SELECT attrelid::text AS oid, attnum, attname::text AS name\n FROM pg_attribute\n WHERE attrelid = ANY($1::oid[]) AND attnum > 0 AND NOT attisdropped`,\n [tableOids]\n )).rows;\n\n const relationByOid = new Map(relations.map(relation => [Number(relation.oid), relation]));\n const attributeName = new Map(attributes.map(attribute => [`${attribute.oid}:${attribute.attnum}`, attribute.name]));\n\n const columns = fields.map((field): SqlScriptColumn => {\n const column: SqlScriptColumn = { name: field.name };\n const type = typeNames.get(field.dataTypeID);\n if (type) column.type = type;\n const relation = field.tableID !== 0 ? relationByOid.get(field.tableID) : undefined;\n const sourceColumn = attributeName.get(`${field.tableID}:${field.columnID}`);\n if (relation && sourceColumn) {\n column.source = { schema: relation.schema, table: relation.table, column: sourceColumn };\n }\n return column;\n });\n const tables = relations.map((relation): SqlScriptTable => ({\n schema: relation.schema,\n table: relation.table,\n kind: relationKind(relation.relkind),\n primaryKey: relation.primary_key ?? [],\n hasInheritors: relation.has_inheritors\n }));\n return { columns, tables };\n } catch (error: unknown) {\n logger.warn(\"[SQL console] Could not describe the result's columns; none of them will be editable\", { error });\n return unnamed;\n }\n}\n\n/**\n * What puts a session back after caller SQL: the session user and role, then\n * every setting. Two statements, because the extended protocol refuses a\n * multi-command string. Pinned settings (`search_path` from the connection's\n * startup options) come back as they were pinned.\n */\nexport const RESET_SESSION_STATEMENTS = [\"SET SESSION AUTHORIZATION DEFAULT\", \"RESET ALL\"] as const;\n\n/**\n * A connection's transaction state, as its last ReadyForQuery reported it:\n * `I` idle, `T` in a transaction, `E` in a failed one. `null` when the client\n * does not say (node-postgres has since 8.16; the package requires 8.22).\n */\nexport function transactionStatus(connection: object): string | null {\n if (!(\"getTransactionStatus\" in connection) || typeof connection.getTransactionStatus !== \"function\") return null;\n const status: unknown = connection.getTransactionStatus();\n return typeof status === \"string\" ? status : null;\n}\n\n/**\n * SQL a person ran began a transaction and did not end it.\n *\n * Each run is a session of its own, so a transaction cannot outlive the run\n * that began it: a `BEGIN` alone, then an `UPDATE` in the next run, then a\n * `ROLLBACK` in a third, committed the UPDATE and reported three successes.\n * Refused instead, with the transaction rolled back.\n */\nexport class SqlTransactionLeftOpenError extends Error {\n readonly code = \"SQL_TRANSACTION_LEFT_OPEN\";\n\n constructor() {\n super(\n \"This SQL began a transaction and did not end it, so it was rolled back: nothing it did was kept. \" +\n \"Each run is a session of its own, and a transaction cannot outlive the run that began it — \" +\n \"put the BEGIN and its COMMIT (or ROLLBACK) in the same run.\"\n );\n this.name = \"SqlTransactionLeftOpenError\";\n }\n}\n\nexport interface OwnSessionOptions {\n /**\n * Put the session in the role the SQL runs as. Called once, before it, at\n * session level — so the role holds for every statement, a `COMMIT` in the\n * middle included. `SET LOCAL ROLE` inside a wrapping transaction did not:\n * the caller's `COMMIT` ended that transaction, and every statement after\n * it ran as the connection owner.\n */\n assumeRole?: (connection: PoolClient) => Promise<void>;\n /**\n * Cancels the run when aborted: the statement in progress is stopped with\n * `pg_cancel_backend`, and the run fails with the database's \"canceling\n * statement due to user request\". A signal already aborted runs nothing.\n */\n signal?: AbortSignal;\n}\n\n/** A run whose signal was aborted before it began. */\nexport class SqlRunCancelledError extends Error {\n readonly code = \"SQL_RUN_CANCELLED\";\n\n constructor() {\n super(\"The run was cancelled before it started: nothing ran.\");\n this.name = \"SqlRunCancelledError\";\n }\n}\n\n/**\n * Ask the database to cancel what backend `pid` is running, from a connection\n * of its own: never one of the pool's, since at `REBASE_DB_POOL_MAX=1` the\n * pool's one connection is the busy one. A cancel that cannot be delivered is\n * dropped — the run then ends on its own, bounded by `statement_timeout`.\n */\nasync function cancelBackend(pool: Pool, pid: number): Promise<void> {\n const client = new Client(pool.options);\n // An unreachable server answers through `connect()`; a stray error event\n // after it must not be thrown out of the process.\n client.on(\"error\", () => undefined);\n try {\n await client.connect();\n await client.query(\"SELECT pg_cancel_backend($1)\", [pid]);\n } catch (error: unknown) {\n logger.warn(\"[SQL] could not cancel a console run\", { pid, error: error instanceof Error ? error.message : String(error) });\n } finally {\n await client.end().catch(() => undefined);\n }\n}\n\n/**\n * Run `work` — SQL a person wrote — on a connection of its own from `pool`,\n * and put the connection back the way it was handed out.\n *\n * A transaction the SQL left open is rolled back, and the run refused with\n * {@link SqlTransactionLeftOpenError}; a run that failed with one open says\n * that it was rolled back. The session is then reset — role, session\n * authorization, settings — and destroyed instead when it cannot be. Last,\n * `afterReset` reads what it needs on the clean session, as the owner.\n */\nexport async function onOwnSession<T>(\n pool: Pool,\n options: OwnSessionOptions,\n work: (connection: PoolClient) => Promise<T>,\n afterReset?: (connection: PoolClient, value: T) => Promise<T>\n): Promise<T> {\n const connection = await pool.connect();\n let reusable = true;\n // A cancel in flight. The connection goes back to the pool only once it\n // has settled, so it can land on nothing but this run — at worst on its\n // reset, which then fails and has the connection destroyed.\n let cancelling: Promise<void> | undefined;\n let stopListening = () => {};\n try {\n let outcome: { value: T } | { error: unknown };\n try {\n const signal = options.signal;\n if (signal) {\n if (signal.aborted) throw new SqlRunCancelledError();\n const pidResult = await connection.query<{ pid: number }>(\"SELECT pg_backend_pid() AS pid\");\n const pid = pidResult.rows[0].pid;\n const onAbort = () => {\n cancelling ??= cancelBackend(pool, pid);\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n stopListening = () => signal.removeEventListener(\"abort\", onAbort);\n // Aborted while the pid was read: nothing has run yet.\n if (signal.aborted) throw new SqlRunCancelledError();\n }\n await options.assumeRole?.(connection);\n outcome = { value: await work(connection) };\n } catch (error: unknown) {\n outcome = { error };\n // pg rejects a query on the server's ErrorResponse and records the\n // transaction status from the ReadyForQuery that follows it. When\n // the two arrive in separate packets, the status read now is the\n // one from before the run, and a transaction the run left aborted\n // goes unnoticed: no ROLLBACK, and no word that it was rolled back.\n // pg runs a queued statement only after that ReadyForQuery, and\n // this one changes nothing whatever state the session is in.\n await connection.query(\"SELECT 1\").catch(() => undefined);\n }\n\n let leftOpen = false;\n const status = transactionStatus(connection);\n if (status === \"T\" || status === \"E\") {\n leftOpen = true;\n try {\n await connection.query(\"ROLLBACK\");\n } catch {\n reusable = false;\n }\n }\n if (reusable) {\n try {\n for (const statement of RESET_SESSION_STATEMENTS) await connection.query(statement);\n } catch {\n reusable = false;\n }\n }\n\n if (\"error\" in outcome) {\n // The database's own error, kept, and told what became of the\n // transaction. On the deepest cause: that is the message the\n // socket reports, so a note on a wrapper would never be read.\n if (leftOpen) {\n const reported = deepestCause(outcome.error);\n if (reported) reported.message = `${reported.message}. The transaction this run began was rolled back: nothing it did was kept.`;\n }\n throw outcome.error;\n }\n if (leftOpen) throw new SqlTransactionLeftOpenError();\n return afterReset && reusable ? await afterReset(connection, outcome.value) : outcome.value;\n } finally {\n stopListening();\n if (cancelling) {\n await cancelling;\n // A cancel delivered as the reset finished may still be on its\n // way to the backend; the connection is not handed to anyone else.\n reusable = false;\n }\n connection.release(reusable ? undefined : true);\n }\n}\n\n/** The innermost `Error` of a `cause` chain — Drizzle wraps the database's error in its own. */\nfunction deepestCause(error: unknown): Error | undefined {\n let deepest: Error | undefined;\n for (let current: unknown = error; current instanceof Error; current = current.cause) deepest = current;\n return deepest;\n}\n\n/** How many notices one run keeps; a loop that raises one per row is not a report. */\nconst MAX_NOTICES = 100;\n\n/**\n * Run `sql` on a connection of its own from `pool` and return what the last\n * statement returned, with each column's provenance and what the database\n * said along the way — `there is no transaction in progress` for a ROLLBACK\n * with nothing to roll back, which used to read as success.\n */\nexport async function runSqlScriptOnPool(pool: Pool, sql: string, options: OwnSessionOptions = {}): Promise<SqlScriptResult> {\n const notices: SqlScriptNotice[] = [];\n const run = await onOwnSession(pool, options, async (connection) => {\n const listen = (notice: { severity?: string; message?: string }) => {\n if (notices.length < MAX_NOTICES) notices.push({ severity: notice.severity ?? \"NOTICE\", message: notice.message ?? \"\" });\n };\n connection.on(\"notice\", listen);\n try {\n const script = new ScriptRun(sql);\n connection.query(script);\n const statements = await script.done;\n const last: StatementOutcome = statements[statements.length - 1] ?? { fields: [], rows: [], received: 0, truncated: false };\n return { last, described: { columns: last.fields.map((field): SqlScriptColumn => ({ name: field.name })), tables: [] as SqlScriptTable[] } };\n } finally {\n connection.removeListener(\"notice\", listen);\n }\n }, async (connection, value) => ({ ...value, described: await describeColumns(connection, value.last.fields) }));\n\n const tag = parseCommandTag(run.last.tag);\n return {\n rows: run.last.rows,\n columns: run.described.columns,\n tables: run.described.tables,\n ...tag,\n // The tag counts every row the statement returned; a command whose\n // tag carries no count was counted here.\n ...(run.last.truncated ? { truncated: true, rowCount: tag.rowCount ?? run.last.received } : {}),\n notices\n };\n}\n\n/**\n * The same shape for rows that came back parsed, from a handle that is not a\n * pool (PGlite in process): every value as text, and no column with a source —\n * nothing to say where a value came from, so nothing the console will edit.\n */\nexport function sqlScriptResultFromRows(rows: Record<string, unknown>[], limits: SqlResultLimits = SQL_CONSOLE_RESULT_LIMITS): SqlScriptResult {\n const names = rows.length > 0 ? Object.keys(rows[0]) : [];\n const budget = new ResultBudget(limits);\n const kept: Record<string, string | null>[] = [];\n for (const row of rows) {\n const entries = Object.entries(row).map(([key, value]): [string, string | null] => [key, valueAsText(value)]);\n if (!budget.admit(textLengthOf(entries.map(([, value]) => value)))) break;\n kept.push(Object.fromEntries(entries));\n }\n return {\n rows: kept,\n columns: names.map(name => ({ name })),\n tables: [],\n ...(kept.length < rows.length ? { truncated: true, rowCount: rows.length } : {}),\n notices: []\n };\n}\n\nfunction valueAsText(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === \"string\") return value;\n if (value instanceof Date) return value.toISOString();\n if (value instanceof Uint8Array) return `\\\\x${Buffer.from(value).toString(\"hex\")}`;\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * How many bytes of rows one answer of the admin `executeSql` door — the\n * WebSocket `EXECUTE_SQL` without `mode: \"script\"` — may carry: 128 MiB.\n *\n * That door had no limit at all. A million-row `SELECT` held every row parsed\n * (432 MB of heap on the console's door before it was capped), then built the\n * frame from them, and a server with 1 GiB died of it — with every other\n * user's connection. Unlike the console's script door it does not keep a\n * prefix: a program reading rows through it would take a prefix for the whole\n * answer, so past the budget the answer is refused, {@link SqlResultTooLargeError}.\n *\n * Generous on purpose. The catalogue reads the admin UI makes through this door\n * are far below it — the SQL console's schema browser reads one\n * `information_schema.columns` row per column, 7 MB for the 100,000 columns of\n * 5,000 tables — and anything near it is a result no browser tab should hold.\n */\nexport const SQL_RESULT_BYTE_BUDGET = 128 * 1024 * 1024;\n\n/**\n * A result larger than the bytes its caller allowed, refused rather than\n * returned in part. Its message is for the person who wrote the SQL.\n */\nexport class SqlResultTooLargeError extends Error {\n readonly code = \"RESULT_TOO_LARGE\";\n\n constructor(readonly limitBytes: number) {\n super(\n `This result is larger than ${describeBytes(limitBytes)} of rows, so none of it was sent. ` +\n \"Add a LIMIT, or select fewer columns, to read it in parts.\"\n );\n this.name = \"SqlResultTooLargeError\";\n }\n}\n\nfunction describeBytes(bytes: number): string {\n const mib = bytes / (1024 * 1024);\n return mib >= 1 ? `${Number.isInteger(mib) ? mib : mib.toFixed(1)} MB` : `${bytes} bytes`;\n}\n\n/**\n * The bytes a row takes as the database sends it: each value's text and four\n * bytes of length. For rows that come back parsed, where there is no message\n * to measure — PGlite, a client that is not node-postgres's own.\n */\nfunction rowBytes(row: Record<string, unknown>): number {\n let bytes = 6;\n for (const value of Object.values(row)) bytes += 4 + textBytes(value);\n return bytes;\n}\n\nfunction textBytes(value: unknown): number {\n if (value === null || value === undefined) return 0;\n if (typeof value === \"string\") return value.length;\n if (value instanceof Date) return 24;\n if (value instanceof Uint8Array) return 2 + value.byteLength * 2;\n if (typeof value === \"object\") return JSON.stringify(value).length;\n return String(value).length;\n}\n\n/**\n * Refuse rows that came back whole and are past `maxBytes`.\n *\n * Where nothing could watch the read, it is measured once done — and the\n * answer still refused rather than sent.\n */\nexport function refuseOversizedRows<T extends Record<string, unknown>>(rows: T[], maxBytes: number): T[] {\n let bytes = 0;\n for (const row of rows) {\n bytes += rowBytes(row);\n if (bytes > maxBytes) throw new SqlResultTooLargeError(maxBytes);\n }\n return rows;\n}\n\n/** A `DataRow` message's length, as node-postgres's parser hands it over. */\nfunction messageLength(message: unknown): number {\n return typeof message === \"object\" && message !== null && \"length\" in message && typeof message.length === \"number\"\n ? message.length\n : 0;\n}\n\n/**\n * Run `work` — one query on `connection` — and stop reading once its rows pass\n * `maxBytes`.\n *\n * node-postgres keeps every row of a query until the query completes, and a\n * simple query cannot be stopped short of its end, so the only way to stop\n * holding rows is to stop receiving them: the socket is closed. Postgres sees\n * its client gone and abandons the statement; the session — and anything it\n * held — goes with it, and `onOwnSession` destroys the connection rather than\n * return it to the pool. What the caller gets is {@link SqlResultTooLargeError},\n * never the rows read so far.\n *\n * Counted from the wire, message by message, as they arrive: what is held when\n * it stops is the budget plus one network read.\n */\nexport async function readWithinBudget<T extends Record<string, unknown>>(\n connection: PoolClient,\n maxBytes: number,\n work: () => Promise<T[]>\n): Promise<T[]> {\n if (!(connection instanceof Client)) return refuseOversizedRows(await work(), maxBytes);\n const wire = connection.connection;\n let received = 0;\n let exceeded = false;\n const count = (message: unknown) => {\n if (exceeded) return;\n received += messageLength(message);\n if (received <= maxBytes) return;\n exceeded = true;\n wire.stream.destroy();\n };\n // A checked-out client has no pool listener, and node-postgres emits the\n // closed socket as an `error` on the client: unheard, that ends the process.\n const ignore = () => undefined;\n connection.on(\"error\", ignore);\n wire.on(\"dataRow\", count);\n try {\n const rows = await work();\n if (exceeded) throw new SqlResultTooLargeError(maxBytes);\n return rows;\n } catch (error: unknown) {\n if (exceeded) throw new SqlResultTooLargeError(maxBytes);\n throw error;\n } finally {\n wire.removeListener(\"dataRow\", count);\n // A closed connection may still say so; it is discarded, not reused.\n if (!exceeded) connection.removeListener(\"error\", ignore);\n }\n}\n\n/** Rows one read of a {@link SqlCursor} fetches. */\nexport const SQL_CURSOR_BATCH_ROWS = 1000;\n\n/**\n * How long a cursor's session may sit between reads before Postgres ends it.\n * A download whose reader stopped reading holds a connection and an open\n * transaction; this is the most it holds them for.\n */\nconst SQL_CURSOR_IDLE_TIMEOUT_MS = 60_000;\n\nconst SQL_CURSOR_NAME = \"rebase_sql_export\";\n\n/**\n * One `FETCH`, read message by message as {@link ScriptRun} reads a script:\n * every value the text the database wrote, each row an array — so two columns\n * of one name stay two columns.\n */\nclass FetchRun implements Submittable {\n /** Set by node-postgres when the pool has a `query_timeout`; see {@link ScriptRun.callback}. */\n callback?: (error: Error | null) => void;\n columns?: string[];\n readonly rows: (string | null)[][] = [];\n readonly done: Promise<void>;\n private settled = false;\n private resolveRun: () => void = () => undefined;\n private rejectRun: (error: Error) => void = () => undefined;\n\n constructor(private readonly text: string) {\n this.done = new Promise((resolve, reject) => {\n this.resolveRun = resolve;\n this.rejectRun = reject;\n });\n }\n\n submit(connection: Connection): void {\n connection.query(this.text);\n }\n\n handleRowDescription(message: { fields: WireField[] }): void {\n this.columns = message.fields.map(field => field.name);\n }\n\n handleDataRow(message: { fields: (string | null)[] }): void {\n this.rows.push(message.fields);\n }\n\n handleCommandComplete(): void {\n // The rows are counted by the caller; the tag says nothing more.\n }\n\n handleEmptyQuery(): void {\n // A FETCH is never empty.\n }\n\n handlePortalSuspended(): void {\n // Only the extended protocol suspends a portal.\n }\n\n handleError(error: Error): void {\n this.settle(error);\n }\n\n handleReadyForQuery(): void {\n this.settle(null);\n }\n\n private settle(error: Error | null): void {\n if (this.settled) return;\n this.settled = true;\n this.callback?.(error);\n if (error) this.rejectRun(error);\n else this.resolveRun();\n }\n}\n\n/** A cursor on a pooled session of its own. See {@link openSqlCursorOnPool}. */\nclass PoolSqlCursor implements SqlCursor {\n columns: string[] = [];\n /** The first batch, read when the cursor was opened. */\n pending: (string | null)[][] | null = null;\n private exhausted = false;\n private closed = false;\n /**\n * What the session said when it died between reads — the idle timeout, a\n * restart. Heard here because a checked-out client has no pool listener,\n * and an unheard `error` event ends the process.\n */\n private failure?: Error;\n private readonly listen = (error: Error) => { this.failure = error; };\n\n constructor(private readonly connection: PoolClient, private readonly batchRows: number) {\n connection.on(\"error\", this.listen);\n }\n\n /** The next batch from Postgres; the last one is shorter than a batch. */\n async fetch(): Promise<(string | null)[][]> {\n if (this.failure) throw this.failure;\n const run = new FetchRun(`FETCH FORWARD ${this.batchRows} FROM ${SQL_CURSOR_NAME}`);\n this.connection.query(run);\n await run.done;\n if (run.columns) this.columns = run.columns;\n if (run.rows.length < this.batchRows) this.exhausted = true;\n return run.rows;\n }\n\n async read(): Promise<(string | null)[][] | null> {\n if (this.closed) return null;\n const pending = this.pending;\n this.pending = null;\n const rows = pending ?? (this.exhausted ? [] : await this.fetch());\n return rows.length > 0 ? rows : null;\n }\n\n /**\n * Roll the read-only transaction back, reset the session and give it back;\n * destroyed instead when it cannot be reset.\n */\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n let reusable = !this.failure;\n if (reusable) {\n try {\n await this.connection.query(\"ROLLBACK\");\n for (const statement of RESET_SESSION_STATEMENTS) await this.connection.query(statement);\n } catch {\n reusable = false;\n }\n }\n // A dead session may still say so once more; it is discarded, so keep listening.\n if (reusable) this.connection.removeListener(\"error\", this.listen);\n this.connection.release(reusable ? undefined : true);\n }\n}\n\n/**\n * The query refused before a row was read, with what to do about it: Postgres\n * answers `DECLARE … CURSOR FOR INSERT …` with a syntax error at `INSERT`,\n * which says nothing to a person who never wrote `DECLARE`.\n */\nfunction explainCursorRefusal(error: unknown, prefixLength: number): unknown {\n if (!(error instanceof DatabaseError)) return error;\n if (error.code === \"42601\" && Number(error.position) === prefixLength + 1) {\n error.message = \"Only a query that returns rows can be downloaded — SELECT, WITH … SELECT, TABLE or VALUES. \" +\n \"Run any other statement in the console.\";\n } else if (error.code === \"42601\" && /multiple commands/.test(error.message)) {\n error.message = \"A download runs one query, and this SQL holds more than one statement. Run the query on its own, then download it.\";\n } else if (error.code === \"25006\") {\n error.message = `A download runs read-only: ${error.message}.`;\n }\n return error;\n}\n\n/**\n * Open a cursor over ONE query a person wrote — the SQL console's \"Download\n * full result as CSV\".\n *\n * On a connection of its own, in the role asked for, inside\n * `BEGIN TRANSACTION READ ONLY`: the database refuses a write, a\n * data-modifying `WITH` and a function that writes. The query is declared over\n * the extended protocol, which refuses a second statement — `SELECT 1; DROP\n * TABLE t` cannot ride along. Rows are then fetched {@link SQL_CURSOR_BATCH_ROWS}\n * at a time, each `FETCH` under the session's `statement_timeout`; nothing\n * fetches ahead of the reader, so memory holds one batch. The first batch is\n * read here, so a query that fails on its first rows fails before anything has\n * been sent.\n *\n * The caller closes the cursor — always. A reader that stops reading without\n * closing is ended by `idle_in_transaction_session_timeout` after\n * {@link SQL_CURSOR_IDLE_TIMEOUT_MS}.\n */\nexport async function openSqlCursorOnPool(\n pool: Pool,\n sql: string,\n options: OwnSessionOptions & { batchRows?: number } = {}\n): Promise<SqlCursor> {\n const connection = await pool.connect();\n const cursor = new PoolSqlCursor(connection, options.batchRows ?? SQL_CURSOR_BATCH_ROWS);\n const prefix = `DECLARE ${SQL_CURSOR_NAME} NO SCROLL CURSOR FOR `;\n try {\n await options.assumeRole?.(connection);\n await connection.query(\"BEGIN TRANSACTION READ ONLY\");\n await connection.query(`SET LOCAL idle_in_transaction_session_timeout = ${SQL_CURSOR_IDLE_TIMEOUT_MS}`);\n // `queryMode` is node-postgres's switch to the extended protocol; the\n // typings do not declare it.\n const declare = { text: prefix + sql.trim().replace(/;\\s*$/, \"\"), queryMode: \"extended\" };\n await connection.query(declare).catch((error: unknown) => {\n throw explainCursorRefusal(error, prefix.length);\n });\n cursor.pending = await cursor.fetch();\n return cursor;\n } catch (error: unknown) {\n await cursor.close();\n throw error;\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 * Why a connect failed, in one line: the message, and the SQLSTATE or socket\n * code when the message does not already carry it.\n */\nfunction connectFailureReason(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n const code = err instanceof Error && \"code\" in err && typeof err.code === \"string\" ? err.code : undefined;\n return code && !message.includes(code) ? `${message} [${code}]` : message;\n}\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 /** Whether this outage's \"could not be opened\" has been said. Cleared once it listens again. */\n private outageWarned = false;\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.outageWarned = false;\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 // Handled: it is retried, and `/health` reports it down meanwhile.\n // One line per outage, with the reason — the stack of a refused\n // connection is not news, and an ERROR every few seconds for a\n // fallback that is working reads as a crash.\n const retry = `${logLabel} LISTEN connection could not be opened (${connectFailureReason(err)}); ` +\n `retrying every ${Math.round((this.options.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS) / 1000)} s.`;\n if (this.outageWarned) {\n logger.debug(retry);\n } else {\n this.outageWarned = true;\n logger.warn(`⚠️ ${retry}`);\n }\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 { DataService } from \"./services/dataService\";\nimport { createStampKeys } from \"./services/PersistService\";\nimport { BranchService } from \"./services/BranchService\";\nimport { RealtimeService, type SubscriptionAuthContext } from \"./services/realtimeService\";\nimport { DatabasePoolManager } from \"./databasePoolManager\";\nimport { DrizzleClient } from \"./interfaces\";\nimport {\n ANONYMOUS_USER_ID,\n DatabaseAdmin,\n SQLAdmin,\n DataDriver,\n DeleteProps,\n CollectionConfig,\n FetchCollectionProps,\n FetchOneProps,\n FilterValues,\n ListenCollectionProps,\n ListenOneProps,\n RebaseCallContext,\n RebaseServerClient,\n RebaseData,\n RebaseSdkData,\n RestFetchService,\n BatchWriteProps,\n SaveManyProps,\n SaveProps,\n SqlCursor,\n SqlScriptResult,\n StorageSource,\n UpdateManyProps,\n UpdateRelationPivotProps,\n DeleteManyProps,\n EntityValues,\n EntityStatus,\n Properties,\n Property,\n TableColumnInfo,\n TableForeignKeyInfo,\n TableJunctionInfo,\n TableMetadata,\n TablePolicyInfo,\n User,\n parseEnvBoolean\n} from \"@rebasepro/types\";\nimport { sql as drizzleSql } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { DatabaseError, Pool, type PoolClient } from \"pg\";\nimport { sqlRows, applyDefaultValuesOnCreate, buildPropertyCallbacks, hasFieldAccessRules, buildSdkData, callbackRefusal, createAccessorSlugResolver, classifyTable, detectJunctionTables, getTenantConfig, requireCallbackClient, requireCallbackCollection, resolveCollectionRelations, resolveTenantWrite, restoresSoftDeletedRow, tenantBypassRoles, toCallbackError, updateDateAutoValues, updateUserAutoValues } from \"@rebasepro/common\";\nimport { PostgresCollectionRegistry } from \"./collections/PostgresCollectionRegistry\";\nimport { deriveRowAddress, getPrimaryKeys, parseIdValues } from \"./services/collection-helpers\";\nimport { isJunctionBackedRelation, isNestedPath, resolveNestedPath, splitPathSegments } from \"./services/nested-path\";\nimport { resolveSoftDelete, rowInTrashError } from \"./services/soft-delete\";\nimport { currentWriteScope, runInWriteScope, WriteTransactionScope } from \"./services/write-transaction-scope\";\nimport { inWriteFrame, type WriteFrame } from \"./services/write-depth\";\nimport { HistoryService } from \"./history/HistoryService\";\nimport { ApiError, logger, resolveBatchRefs } from \"@rebasepro/server\";\nimport { isRoleSwitchingPermissionError, reachedDatabase } from \"./utils/pg-error-utils\";\nimport { rowsOfResult } from \"./utils/result-rows\";\nimport { applyAuthContext } from \"./security/rls-enforcement\";\nimport { withFieldViewer } from \"./services/field-viewer\";\nimport { generateSchemaCommit } from \"./schema/generate-schema-commit\";\nimport { readSchemaFactsFor, type Queryable } from \"./schema/ensure-collection-tables\";\nimport { onOwnSession, openSqlCursorOnPool, readWithinBudget, refuseOversizedRows, RESET_SESSION_STATEMENTS, runSqlScriptOnPool, sqlScriptResultFromRows } from \"./services/sql-script\";\nimport { PgNotifyListener } from \"./services/pg-notify-listener\";\n\n/**\n * Has an operator opted out of database role switching entirely?\n *\n * `DISABLE_DB_ROLE_SWITCHING=true` is documented (README, the configuration\n * page, the backend skill) as \"run Studio SQL Editor queries as the connection\n * owner\", for deployments whose application roles have no database role behind\n * them. It is the only sanctioned way a statement that named a role runs\n * without it — every other route now refuses.\n *\n * Any spelling of yes, through the platform's one parser. It was an exact\n * `\"true\"` until that parser existed, deliberately, so that this variable would\n * not be fixed alone and left disagreeing with the rest.\n */\nexport function isRoleSwitchingOptedOut(): boolean {\n return parseEnvBoolean(process.env.DISABLE_DB_ROLE_SWITCHING) === true;\n}\n\n/**\n * The main database, when this process serves a branch of it.\n *\n * `rebase dev` on a checkout switched to a branch connects to the branch, and\n * names the database it was branched from in `REBASE_BRANCH_PARENT_DATABASE`.\n * Branches are kept there — the registry, and the default source of a new\n * one — as they are when the main database itself is served: a branch's own\n * `rebase.branches` is a snapshot, taken before its row was written.\n */\nexport function branchParentDatabase(): string | undefined {\n return process.env.REBASE_BRANCH_PARENT_DATABASE?.trim() || undefined;\n}\n\n/**\n * The role a statement will actually have run as, given the role it asked for.\n *\n * For the audit log, which recorded `options.role` — the *requested* role — and\n * so restated the caller's request as though it were the outcome. The two part\n * company exactly when {@link isRoleSwitchingOptedOut} holds, because every\n * other divergence is now an error rather than a quiet substitution.\n */\nexport function effectiveSqlRole(requestedRole?: string): string {\n if (!requestedRole) return CONNECTION_OWNER;\n return isRoleSwitchingOptedOut() ? CONNECTION_OWNER : requestedRole;\n}\n\n/** How {@link effectiveSqlRole} names \"whatever role the connection holds\". */\nexport const CONNECTION_OWNER = \"<connection owner>\";\n\n/**\n * The role a console statement ran as, given the role it asked for — said to\n * the console with its rows, so it can say when that is not the role picked.\n *\n * The requested role whenever role switching is on: a statement that cannot\n * assume its role is refused, never run as anyone else. With\n * `DISABLE_DB_ROLE_SWITCHING`, every statement runs as the connection owner,\n * named by asking a session of its own on the same database — a role the\n * session already held is then still the one requested.\n */\nexport async function sqlRoleRanAs(\n admin: Pick<SQLAdmin, \"executeSql\">,\n requestedRole: string,\n database: string | undefined\n): Promise<string> {\n if (!isRoleSwitchingOptedOut()) return requestedRole;\n try {\n const rows = await admin.executeSql(\"SELECT current_user AS role\", { database, isolateSession: true });\n const owner = rows[0]?.role;\n return typeof owner === \"string\" ? owner : CONNECTION_OWNER;\n } catch {\n return CONNECTION_OWNER;\n }\n}\n\n/**\n * A statement named a database role the connection cannot assume.\n *\n * Its own type because the tempting recovery — run it anyway, as the owner — is\n * the one thing that must not happen. Callers that catch this should report it,\n * not retry unscoped.\n */\nexport class RoleSwitchUnavailableError extends Error {\n readonly code = \"ROLE_SWITCH_UNAVAILABLE\";\n readonly role: string;\n /** The underlying Postgres error, when the refusal came from a live attempt. */\n readonly pgError?: unknown;\n\n constructor(role: string, pgError?: unknown) {\n super(\n `Cannot execute SQL as role \"${role}\": this connection is not permitted to SET ROLE. ` +\n `The statement was NOT executed — running it as the connection owner would return ` +\n `owner-visible rows, which is a different question from the one that was asked. ` +\n `Grant the connection user membership in \"${role}\", or set DISABLE_DB_ROLE_SWITCHING=true ` +\n `to run SQL Editor queries as the connection owner.`\n );\n this.name = \"RoleSwitchUnavailableError\";\n this.role = role;\n this.pgError = pgError;\n }\n}\n\n/**\n * Fold what a `beforeSave` returned into the values being saved.\n *\n * The hook returns the values that will be saved, so a key it returns replaces\n * the value outright: an array it filtered stays filtered, a map it narrowed\n * stays narrow. A key it leaves out keeps the value it was given, so a hook\n * that returns only the field it computed still saves the rest.\n *\n * Not a deep merge. `mergeDeep` merges arrays of objects element by element and\n * keeps every element past the end of the shorter one, so a hook that dropped\n * one line item of three had the last one merged back in and the order was\n * stored with a line duplicated.\n */\nfunction applyBeforeSaveResult<T extends object, R extends object>(values: T, result: R): T & R {\n return { ...values, ...result };\n}\n\n/**\n * The keys of a create's final values that its INSERT alone may write: those\n * the caller did not send, no `beforeSave` hook wrote, and that are not an\n * `on_update` stamp — a declared default, a tenant stamp, a create-time stamp.\n * See `PersistSaveOptions.insertOnlyKeys`.\n */\nfunction insertOnlyKeysOf(\n finalValues: object,\n callerValues: object | undefined,\n beforeHooks: Record<string, unknown>,\n afterHooks: Record<string, unknown>,\n properties: Properties | undefined\n): string[] {\n const sent = new Set(Object.keys(callerValues ?? {}));\n const updateStamps = new Set(Object.entries(properties ?? {})\n .filter(([, property]) => (property?.type === \"date\" && property.autoValue === \"on_update\")\n || (property?.type === \"string\" && property.autoValue === \"user_on_update\"))\n .map(([key]) => key));\n return Object.keys(finalValues).filter(key =>\n !sent.has(key)\n && !updateStamps.has(key)\n && beforeHooks[key] === afterHooks[key]);\n}\n\n/**\n * Refuse an update that stamps a `softDelete` collection's field.\n *\n * Stamping it IS the delete — every read hides the row from then on — so it is\n * `delete`'s to do, where the permission is `delete`, `beforeDelete` can veto,\n * `afterDelete` runs and history says \"delete\". Written by an update, a caller\n * holding only write access trashed rows, past all of that. Clearing it stays\n * an update: that is how a row is restored, and what a form sends back for a\n * live row.\n */\nfunction assertUpdateDoesNotSoftDelete(\n collection: CollectionConfig | undefined,\n values: Record<string, unknown> | undefined,\n path: string\n): void {\n const softDelete = resolveSoftDelete(collection);\n if (!softDelete) return;\n const value = values?.[softDelete.field];\n if (value === undefined || value === null) return;\n const slug = collection?.slug ?? path;\n const message = `'${softDelete.field}' on '${slug}' records a delete, so an update may only clear it ` +\n \"(null restores the row). Delete the row to set it.\";\n throw ApiError.badRequest(message, \"FIELD_NOT_WRITABLE\", {\n collection: slug,\n violations: [{ field: softDelete.field, code: \"soft_delete\", message }]\n });\n}\n\n/** A key that names a row: not absent, and not the empty string a form sends. */\nfunction isPresentKey(id: string | number | undefined | null): id is string | number {\n return id !== undefined && id !== null && id !== \"\";\n}\n\n/**\n * Refuse an update whose values move the row to another key.\n *\n * The row is addressed by its key, and everything after the write addresses\n * it the same way: the read-back, `afterSave`, history, the realtime event.\n * A key changed underneath them answered 500 \"Could not fetch row after save.\"\n * on every door — and through `driver.data`, which runs outside a request's\n * transaction, the row had already moved when the call threw, with no\n * `afterSave`, no history entry and no event for the move. A key equal to the\n * address (a form that sends the whole row back) is not a change.\n *\n * Asked twice: of what the caller sent, before any hook runs, and of what the\n * hooks hand on, since a `beforeSave` can set the key too.\n */\nfunction assertKeyUnchanged(\n collection: CollectionConfig | undefined,\n registry: PostgresCollectionRegistry,\n id: string | number,\n values: Record<string, unknown> | undefined,\n path: string\n): void {\n if (!collection || !values) return;\n const primaryKeys = getPrimaryKeys(collection, registry);\n if (primaryKeys.length === 0) return;\n let address: Record<string, string | number>;\n try {\n address = parseIdValues(id, primaryKeys);\n } catch {\n // An address the key cannot parse is the write's own 404 to give.\n return;\n }\n const moved = primaryKeys\n .map(key => key.fieldName)\n .filter(field => values[field] !== undefined && String(values[field]) !== String(address[field]));\n if (moved.length === 0) return;\n const slug = collection.slug ?? path;\n const violations = moved.map(field => {\n const message = `'${field}' is the key of '${slug}', and an update cannot change it: this row is \"${id}\". `\n + \"Create a row under the new key and delete this one.\";\n return { field, code: \"key_immutable\", message };\n });\n throw ApiError.badRequest(violations[0].message, \"KEY_IMMUTABLE\", { collection: slug, violations });\n}\n\n/**\n * Refuse a write that would leave a *required* `created_by` / `updated_by`\n * column null because nobody is acting.\n *\n * A `user_on_create` column takes the uid from the call context, and an\n * anonymous request, a service token and an in-process write all have none.\n * Storing `null` is the right answer where the column allows it — plenty of\n * rows are legitimately written by the server. Where the collection has said\n * `required`, it is not: that declaration means \"a row must record who made\n * it\", and a write that cannot answer the question is a write this collection\n * does not accept. Better a 400 naming the field than a 23502 naming the\n * column, three layers down, after the hooks have run.\n */\nfunction assertActingUserForAutoValues(\n properties: Properties,\n status: EntityStatus,\n uid: string | undefined,\n path: string\n): void {\n if (uid) return;\n for (const [key, property] of Object.entries(properties)) {\n const prop = property as (Property & { autoValue?: string }) | undefined;\n if (!prop || prop.type !== \"string\") continue;\n if (prop.autoValue !== \"user_on_create\" && prop.autoValue !== \"user_on_update\") continue;\n if (!prop.validation?.required) continue;\n // `user_on_create` says nothing about an update — the column already\n // holds the creator's uid, and this write is not rewriting it.\n if (status === \"existing\" && prop.autoValue === \"user_on_create\") continue;\n throw ApiError.badRequest(\n `'${key}' on '${path}' records the acting user and is required, and this request has none. ` +\n \"Sign in, or drop `required` from the property to let the server write rows anonymously.\",\n \"VALIDATION_CONSTRAINT\",\n {\n collection: path,\n violations: [{ field: key, code: \"required\", message: `'${key}' records the acting user, and there is none.` }]\n }\n );\n }\n}\n\n/**\n * A listener's `onError`, in the shape the realtime service calls it with.\n *\n * Registered with the subscription so a failed refetch reaches it. Before, only\n * the initial fetch did: a refetch after a change failed into a log line, and\n * the listener kept its last rows as if they were current. The error arrives as\n * thrown, not masked the way a socket frame is: this is trusted server code.\n */\nfunction fetchErrorListener(onError: ((error: Error) => void) | undefined): ((error: unknown) => void) | undefined {\n if (!onError) return undefined;\n return (error) => onError(error instanceof Error ? error : new Error(String(error)));\n}\n\n/**\n * The principal a user's reads run as, for the transaction a request opens and\n * for a subscription alike.\n *\n * One definition because there were two. The subscription's copy was built by\n * hand after the fact and left out `isAnonymous`, so a guest's refetches ran as\n * an account: a policy that excludes guests filtered the first read and not the\n * ones after a change.\n */\nfunction authContextOf(user: User | undefined): SubscriptionAuthContext {\n return {\n uid: user?.uid || ANONYMOUS_USER_ID,\n roles: user?.roles ?? [],\n isAnonymous: user?.isAnonymous === true,\n // A tenancy policy reads a claim. Spread rather than set, so a caller\n // carrying none produces no key at all.\n ...(user?.claims ? { claims: user.claims } : {})\n };\n}\n\n/**\n * Whether any tier declares a hook — application code that runs inside the\n * write, and so may catch a failed statement (see\n * `WriteTransactionScope.noteApplicationCode`).\n */\nfunction declaresAnyHook(...tiers: (object | undefined)[]): boolean {\n return tiers.some(tier => tier !== undefined && Object.values(tier).some(value => typeof value === \"function\"));\n}\n\n/**\n * A bulk or batch update's answer for an id that names no row this caller can\n * see: a 404 naming the id, which the entry's own label then wraps.\n */\nfunction assertBatchRowFound(stored: Record<string, unknown> | undefined, id: unknown): void {\n if (stored) return;\n throw Object.assign(new Error(`No row with id ${JSON.stringify(id)}`), {\n statusCode: 404,\n code: \"NOT_FOUND\"\n });\n}\n\nexport class PostgresBackendDriver implements DataDriver {\n key = \"postgres\";\n initialised = true;\n /** `save` and `delete` read the row they address and run a caller's precondition on it. */\n readonly checksRowPreconditions = true;\n\n public dataService: DataService;\n public realtimeService: RealtimeService;\n public historyService?: HistoryService;\n public branchService?: BranchService;\n public user?: User;\n public data: RebaseSdkData;\n\n /**\n * The server singleton, attached by `initializeRebaseBackend` after boot —\n * a driver exists before the client does. Typed as what is attached: a\n * `RebaseServerClient` has no `data`, and callbacks read it as\n * `context.client`.\n */\n public client?: RebaseServerClient;\n\n /**\n * Auto-set to `true` once a `SET LOCAL ROLE` has failed with insufficient\n * privileges, so later statements refuse without spending the round trip\n * to be refused again.\n *\n * Deliberately NOT a mirror of `DISABLE_DB_ROLE_SWITCHING`, which it used\n * to be described as. The env var is an operator saying \"run these as the\n * connection owner\"; this flag is the database saying \"I cannot give you\n * the role you asked for\". The first is a decision and permits the\n * fallback, the second is a failure and must not — see the SECURITY note\n * in {@link executeSql}.\n */\n private _roleSwitchingUnavailable = false;\n\n /**\n * Restricted role that authenticated (user-context) requests run as (via\n * `SET LOCAL ROLE`) so RLS binds every statement — reads *and* writes. Set\n * by the bootstrapper after posture detection: defined when the connection\n * would otherwise bypass RLS (superuser / BYPASSRLS / table owner),\n * undefined when RLS already applies natively. The base (server-context)\n * driver never switches — it is the trusted owner plane (auth flows,\n * migrations, `dataAsAdmin`).\n */\n public rlsUserRole?: string;\n\n /**\n * The direct (non-pooled) URL {@link listen} holds its `LISTEN` on — this\n * source's, set by the bootstrapper. Never read from `DATABASE_DIRECT_URL`\n * here: that variable is the default source's, and a second database\n * listening on it hears none of its own notifications.\n */\n public listenConnectionString?: string;\n\n /**\n * When true, realtime notifications are deferred until after the\n * wrapping transaction commits. Set by `withAuth` → `withTransaction`.\n */\n _deferNotifications = false;\n _pendingNotifications: Array<{\n path: string;\n id: string;\n row: Record<string, unknown> | null;\n databaseId?: string;\n }> = [];\n\n constructor(\n public db: DrizzleClient,\n realtimeService: RealtimeService,\n public readonly registry: PostgresCollectionRegistry,\n user?: User,\n public poolManager?: DatabasePoolManager,\n historyService?: HistoryService\n ) {\n // The context every `beforeQuery` hook on this driver's reads runs\n // with, established once here rather than threaded through a dozen read\n // signatures. Lazy because `this.client` is attached after construction\n // and `this.data` reads through whichever handle this driver holds.\n this.dataService = new DataService(db, registry, () => this.buildCallContext());\n this.realtimeService = realtimeService;\n this.historyService = historyService;\n this.user = user;\n // `data.myNotes` is the collection whose accessor is `myNotes` (the\n // `my-notes` slug, whatever its table), as the generated types say.\n this.data = buildSdkData(this, { resolveAccessor: createAccessorSlugResolver(() => registry?.getCollections()) });\n\n // Initialize BranchService when adminConnectionString is configured\n if (poolManager) {\n this.branchService = new BranchService(db, poolManager, {\n database: async () => branchParentDatabase() ?? await this.appDatabaseName(),\n registry: async () => {\n const parent = branchParentDatabase();\n return parent && parent !== await this.appDatabaseName() ? poolManager.getDrizzle(parent) : this.db;\n }\n });\n }\n\n }\n\n /**\n * Typed admin capabilities (SQLAdmin + SchemaAdmin + BranchAdmin).\n * Implemented as a getter so method references are resolved at call-time,\n * allowing test spies applied after construction to take effect.\n */\n get admin(): DatabaseAdmin {\n return {\n executeSql: (...args: Parameters<NonNullable<DatabaseAdmin[\"executeSql\"]>>) => this.executeSql(...args),\n runSqlScript: (...args: Parameters<NonNullable<DatabaseAdmin[\"runSqlScript\"]>>) => this.runSqlScript(...args),\n openSqlCursor: (...args: Parameters<NonNullable<DatabaseAdmin[\"openSqlCursor\"]>>) => this.openSqlCursor(...args),\n fetchAvailableDatabases: () => this.fetchAvailableDatabases(),\n fetchAvailableRoles: () => this.fetchAvailableRoles(),\n fetchApplicationRoles: () => this.fetchApplicationRoles(),\n fetchCurrentDatabase: () => this.fetchCurrentDatabase(),\n fetchUnmappedTables: (...args: Parameters<NonNullable<DatabaseAdmin[\"fetchUnmappedTables\"]>>) => this.fetchUnmappedTables(...args),\n fetchTableMetadata: (...args: Parameters<NonNullable<DatabaseAdmin[\"fetchTableMetadata\"]>>) => this.fetchTableMetadata(...args),\n // Planning a schema change is engine-specific — it renders DDL, a\n // Drizzle schema and the declarative SQL artifacts — so it lives\n // here and the server detects it structurally, the same way it\n // detects SQL. Planning only: applying is `executeSql` above, and\n // committing belongs to whatever holds the repository.\n // Planned against what this database actually has, not against an\n // empty one. Whether a NOT NULL can be added comes down to whether\n // the table holds rows, and whether an enum value will land comes\n // down to the values the type already carries — neither is knowable\n // from the collections, and both decide whether the statements this\n // returns are accepted or rejected by the database they name.\n planSchemaChange: async (before, after, options) => generateSchemaCommit({\n before: before as CollectionConfig[],\n after: after as CollectionConfig[],\n paths: options?.paths,\n sourceOnly: options?.sourceOnly,\n existing: await readSchemaFactsFor(\n this.schemaFactsQueryable(),\n after as CollectionConfig[]\n )\n }),\n listen: (...args: Parameters<NonNullable<SQLAdmin[\"listen\"]>>) => this.listen(...args),\n // Branch operations (only available when poolManager is configured)\n ...(this.branchService ? {\n createBranch: this.branchService.createBranch.bind(this.branchService),\n deleteBranch: this.branchService.deleteBranch.bind(this.branchService),\n listBranches: this.branchService.listBranches.bind(this.branchService),\n getBranchInfo: this.branchService.getBranchInfo.bind(this.branchService)\n } : {})\n };\n }\n\n /**\n * `SQLAdmin.listen`: a `LISTEN` on a connection of its own, through the\n * listener CDC and the channel bus use, which reconnects and heartbeats.\n *\n * The connection is the one the realtime listener uses: this source's\n * direct URL ({@link listenConnectionString}) when the bootstrapper\n * resolved one — a transaction-mode pooler such as PgBouncer does not keep\n * a `LISTEN` — else the pool's own URL. The in-process development\n * database has neither, and is refused, as is a first connect that fails:\n * the caller polls instead.\n */\n async listen(\n channel: string,\n onNotify: (payload: string) => void,\n options?: { onReconnect?: () => void }\n ): Promise<() => Promise<void>> {\n const pool = \"$client\" in this.db ? this.db.$client : undefined;\n const connectionString = this.listenConnectionString\n || (pool instanceof Pool ? pool.options.connectionString : undefined);\n if (!connectionString) {\n throw new Error(\"no database URL to hold a LISTEN connection on (set DATABASE_DIRECT_URL)\");\n }\n const listener = new PgNotifyListener({\n connectionString,\n channel,\n logLabel: `[LISTEN ${channel}]`,\n onPayload: onNotify,\n onReconnect: options?.onReconnect\n });\n await listener.start();\n return () => listener.stop();\n }\n\n /**\n * The catalogue-reading shim the schema planner wants.\n *\n * Text in, rows out — every statement it issues is a catalogue read keyed by\n * schema name, and schema names are identifiers rather than bindable values,\n * so there is nothing to parameterise. Runs on the driver's own handle, which\n * is the connection whose privileges are already known to work.\n */\n private schemaFactsQueryable(): Queryable {\n return {\n query: async <T>(text: string): Promise<{ rows: T[] }> => {\n return { rows: sqlRows<T>(await this.db.execute(drizzleSql.raw(text))) };\n }\n };\n }\n\n /**\n * REST-optimised fetch service (include-aware eager-loading).\n * Delegates to the underlying FetchService (include-aware eager loading),\n * then runs the afterRead pipeline on the results. The raw FetchService does\n * NOT run callbacks, so masking must be applied here — otherwise every\n * REST/SDK read leaks unmasked data (see {@link applyAfterReadForRest}).\n */\n get restFetchService(): RestFetchService {\n const raw = this.dataService.getFetchService();\n return {\n fetchCollectionForRest: async (collectionPath, options, include) => {\n const rows = await raw.fetchCollectionForRest(collectionPath, options, include);\n return this.applyAfterReadForRest(rows, collectionPath);\n },\n fetchOneForRest: async (collectionPath, id, include, databaseId, options) => {\n const row = await raw.fetchOneForRest(collectionPath, id, include, databaseId, options);\n if (!row) return row;\n const [masked] = await this.applyAfterReadForRest([row], collectionPath);\n return masked;\n },\n // Forwarded, not omitted. `FetchService.aggregate` has existed for\n // as long as the route has, and this object is the only thing the\n // route can see — so leaving it out made\n // `GET /api/data/:slug/aggregate` answer 501 on every deployment,\n // including the Postgres one whose absence the 501's own comment\n // says it is describing.\n //\n // No afterRead pass: the callbacks shape rows, and an aggregate\n // returns sums and counts rather than rows. Row-level authorization\n // is not skipped — it is applied one layer down, by the\n // request-scoped handle this service reads through.\n aggregate: async (collectionPath, options) => {\n return raw.aggregate(collectionPath, options);\n },\n // Forwarded for the same reason `aggregate` is, and it was missed\n // the same way: this object is all the route can see, so a method\n // left off it does not fall back to the real one — it reads as a\n // driver that cannot do the thing. `readPage` asks `cursorFor` for\n // the `meta.nextCursor` of every page, so without this the server\n // accepted `?after=` and never issued a cursor to put in it, and\n // keyset pagination could not be started by any client.\n //\n // No transaction and no afterRead: it derives a string from a row\n // that has already been fetched and authorized.\n cursorFor: (collectionPath, row, orderBy) => raw.cursorFor(collectionPath, row, orderBy)\n };\n }\n\n /**\n * Build the context handed to every collection callback.\n *\n * Note `data: this.data` — `this` is whichever driver is running the\n * operation, so the callback's data plane inherits that driver's privilege.\n * On a user request `AuthenticatedPostgresBackendDriver.withTransaction`\n * constructs a fresh base driver bound to the RLS-scoped transaction and\n * runs the operation on it, so `this.data` speaks through that connection\n * and policies apply. On server-context work `this` is the base driver on\n * the owner connection, and they do not. Pinned by the\n * `\"scopes context.data to the caller\"` case in the `rls-enforcement` e2e\n * suite, because it is the kind of property that is easy to break from a\n * distance and impossible to notice.\n *\n * Previously returned through `as RebaseCallContext`, which\n * disabled checking for the whole object and let `driver` — documented in\n * the callbacks guide — sit on the runtime context while absent from the\n * contract. Both are declared now, so this is a plain typed return.\n *\n * `client` went through a narrower cast, `as RebaseCallContext[\"client\"]`,\n * and it lied twice. It said `RebaseClient`, `data` included, about the\n * server singleton, which has no `data` — so `context.client.data` compiled\n * and threw in production. And it dropped the `| undefined` of a client\n * that is attached after construction. The type now leaves `data` off, and\n * a driver that was never given a client refuses by name, at the read.\n */\n private buildCallContext(): RebaseCallContext {\n const client = this.client;\n return {\n user: this.user,\n driver: this,\n data: this.data,\n get client() {\n return requireCallbackClient(client);\n },\n storageSource: client?.storage as StorageSource\n };\n }\n\n private resolveCollectionCallbacks<M extends Record<string, unknown>>(collection: CollectionConfig<M> | undefined, path: string) {\n if (!collection && !path) return {\n collection: undefined,\n callbacks: undefined,\n globalCallbacks: undefined,\n propertyCallbacks: undefined\n };\n const registryCollection = this.registry?.getCollectionByPath(path);\n const resolvedCollection = registryCollection\n ? {\n ...collection,\n ...registryCollection\n } as CollectionConfig<M>\n : collection as CollectionConfig<M>;\n\n const callbacks = resolvedCollection?.callbacks;\n const globalCallbacks = this.registry?.getGlobalCallbacks();\n const properties = resolvedCollection?.properties;\n let propertyCallbacks;\n if (properties) {\n propertyCallbacks = buildPropertyCallbacks(properties);\n }\n return {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n };\n }\n\n /**\n * Run the three-tier afterRead pipeline (global → collection → property) on a\n * single row for a collection whose callbacks have already been resolved.\n */\n private async applyAfterReadToRow(\n row: Record<string, unknown>,\n path: string,\n resolved: ReturnType<PostgresBackendDriver[\"resolveCollectionCallbacks\"]>,\n contextForCallback: RebaseCallContext\n ): Promise<Record<string, unknown>> {\n const { collection: resolvedCollection, callbacks, globalCallbacks, propertyCallbacks } = resolved;\n let out = row;\n if (!PostgresBackendDriver.hasAfterRead(resolved)) return out;\n // Resolved once for all three tiers — see `requireCallbackCollection`.\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n if (globalCallbacks?.afterRead) {\n out = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path, row: out, context: contextForCallback\n }) ?? out;\n }\n if (callbacks?.afterRead) {\n out = await callbacks.afterRead({\n collection: callbackCollection,\n path, row: out, context: contextForCallback\n }) ?? out;\n }\n if (propertyCallbacks?.afterRead) {\n out = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path, row: out, context: contextForCallback\n }) ?? out;\n }\n return out;\n }\n\n private static hasAfterRead(resolved: ReturnType<PostgresBackendDriver[\"resolveCollectionCallbacks\"]>): boolean {\n return !!(resolved.globalCallbacks?.afterRead || resolved.callbacks?.afterRead || resolved.propertyCallbacks?.afterRead);\n }\n\n /**\n * Apply afterRead to REST/SDK read results.\n *\n * The REST / `include` path fetches rows through the raw fetch service, which\n * does NOT run callbacks — so without this, `afterRead` transforms (e.g. PII\n * masking) are silently skipped on every SDK/REST read, leaking raw data.\n * This choke point guarantees afterRead runs there too, matching the driver's\n * fetchCollection/fetchOne paths.\n *\n * It also masks embedded relation data one level deep by running the TARGET\n * collection's afterRead (so `post.author.email` is masked by the authors\n * collection, not left raw).\n */\n /**\n * One stored row as `restFetchService.fetchOneForRest` serves it: through\n * `afterRead`, on a copy, so the row the write itself goes on with (its\n * previous values, its history) stays the stored one. A precondition\n * compares against what the caller was served — an `ETag` is a hash of that.\n */\n private async asRestServes(\n row: Record<string, unknown> | null | undefined,\n path: string\n ): Promise<Record<string, unknown> | undefined> {\n if (!row) return undefined;\n const [served] = await this.applyAfterReadForRest([{ ...row }], path);\n return served;\n }\n\n async applyAfterReadForRest(\n rows: Record<string, unknown>[],\n path: string\n ): Promise<Record<string, unknown>[]> {\n if (!rows || rows.length === 0) return rows;\n\n const resolved = this.resolveCollectionCallbacks(undefined, path);\n const contextForCallback = this.buildCallContext();\n const hasOwn = PostgresBackendDriver.hasAfterRead(resolved);\n\n // Resolve embedded relation targets (relationKey -> { path, resolved callbacks })\n // once, keeping only those whose target collection actually has an afterRead.\n const relationTargets: Record<string, { path: string; resolved: ReturnType<PostgresBackendDriver[\"resolveCollectionCallbacks\"]> }> = {};\n if (resolved.collection) {\n try {\n const rels = resolveCollectionRelations(resolved.collection as CollectionConfig);\n for (const [key, rel] of Object.entries(rels)) {\n const target = typeof (rel as { target?: unknown }).target === \"function\"\n ? (rel as { target: () => CollectionConfig }).target()\n : undefined;\n const targetPath = target?.slug;\n if (!targetPath) continue;\n // The relation's own target, not just its slug: the target\n // config is in hand here, so a relation pointing at a\n // collection the registry cannot resolve by slug still\n // masks through the config it declared, instead of handing\n // the tiers a collection that is not there.\n const targetResolved = this.resolveCollectionCallbacks(target, targetPath);\n if (PostgresBackendDriver.hasAfterRead(targetResolved)) {\n relationTargets[key] = { path: targetPath, resolved: targetResolved };\n }\n }\n } catch {\n // Ignore relation resolution errors (e.g. incomplete config during setup)\n }\n }\n const relKeys = Object.keys(relationTargets);\n\n if (!hasOwn && relKeys.length === 0) return rows;\n // `afterRead` is application code; see `noteApplicationCode`.\n currentWriteScope()?.noteApplicationCode();\n\n const maskEmbedded = async (value: unknown, target: { path: string; resolved: ReturnType<PostgresBackendDriver[\"resolveCollectionCallbacks\"]> }): Promise<unknown> => {\n if (Array.isArray(value)) {\n return Promise.all(value.map((v) => maskEmbedded(v, target)));\n }\n if (!value || typeof value !== \"object\") return value;\n const obj = value as Record<string, unknown>;\n // A pre-fetched relation payload may nest the row under `.data`.\n if (obj.__type === \"relation\" && obj.data && typeof obj.data === \"object\") {\n return { ...obj, data: await this.applyAfterReadToRow(obj.data as Record<string, unknown>, target.path, target.resolved, contextForCallback) };\n }\n // A bare reference pointer carries no row data to mask.\n if (obj.__type === \"reference\") return obj;\n return this.applyAfterReadToRow(obj, target.path, target.resolved, contextForCallback);\n };\n\n return Promise.all(rows.map(async (row) => {\n let out = hasOwn ? await this.applyAfterReadToRow(row, path, resolved, contextForCallback) : row;\n for (const key of relKeys) {\n if (out[key] === undefined || out[key] === null) continue;\n out = { ...out, [key]: await maskEmbedded(out[key], relationTargets[key]) };\n }\n return out;\n }));\n }\n\n async fetchCollection<M extends Record<string, unknown>>(\n // Forwarded whole rather than re-listed, as `listenCollection` does:\n // the list named ten of the query's fields, so `logical`, `include`,\n // `fields`, `distinct`, `withDeleted` and `searchExplain` were accepted\n // and dropped — an `or(...)` read returned every row while `count`\n // with the same group answered 1.\n { path, collection, startAfter, ...query }: FetchCollectionProps<M>\n ): Promise<Record<string, unknown>[]> {\n\n const rows = await this.dataService.fetchCollection<M>(path, {\n ...query,\n startAfter: startAfter as Record<string, unknown> | undefined,\n databaseId: collection?.databaseId\n });\n\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, path);\n\n if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {\n const contextForCallback = this.buildCallContext();\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n return Promise.all(rows.map(async (row) => {\n let fetched = row;\n // `?? fetched` on every tier. An `afterRead` that mutates the row\n // and returns nothing is a reasonable thing to write — the guide's\n // own signature says the return is a transform, not a requirement\n // — and on the tiers that lacked the fallback it replaced the row\n // with `undefined`. The collection tier tolerated it, so the same\n // callback worked or emptied the response depending on which\n // block it was registered in.\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n fetched = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row: fetched,\n context: contextForCallback\n }) ?? fetched;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n fetched = await callbacks.afterRead({\n collection: callbackCollection,\n path,\n row: fetched,\n context: contextForCallback\n }) ?? fetched;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n fetched = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row: fetched,\n context: contextForCallback\n }) ?? fetched;\n }\n return fetched;\n }));\n }\n\n return rows;\n }\n\n /**\n * Listen to a collection: the rows now, then again after every change.\n *\n * The realtime service runs every read, the first one included, as\n * `authContext` — which {@link AuthenticatedPostgresBackendDriver} supplies,\n * and which defaults to this driver's own user (a driver bound to a\n * request's transaction has one). With neither, the reads run as the\n * anonymous user, as the refetches always did.\n */\n listenCollection<M extends Record<string, unknown>>(\n // Forwarded whole rather than re-listed: the list named nine of the\n // query's fields, so `logical` was dropped and an `or(...)` listener\n // was handed every row. Held back, as they always were here:\n // `vectorSearch`, which a subscription cannot serve, and `page` and\n // `withDeleted`, which the stored request has no field for.\n { path, collection, startAfter, onUpdate, onError, vectorSearch: _vectorSearch, page: _page, withDeleted: _withDeleted, ...query }: ListenCollectionProps<M>,\n authContext: SubscriptionAuthContext | undefined = this.user ? authContextOf(this.user) : undefined\n ): () => void {\n const subscriptionId = this.generateSubscriptionId();\n\n this.realtimeService.startDataDriverSubscription(subscriptionId, {\n type: \"collection\",\n path,\n collectionRequest: {\n ...query,\n startAfter: startAfter as Record<string, unknown> | undefined,\n databaseId: collection?.databaseId\n },\n authContext,\n onError: fetchErrorListener(onError)\n }, (rows) => {\n if (Array.isArray(rows)) onUpdate(rows);\n });\n\n return () => this.realtimeService.unsubscribe(subscriptionId);\n }\n\n async fetchOne<M extends Record<string, unknown>>({\n path,\n id,\n databaseId,\n collection,\n withDeleted\n }: FetchOneProps<M>): Promise<Record<string, unknown> | undefined> {\n let row = await this.dataService.fetchOne<M>(\n path,\n id,\n databaseId || collection?.databaseId,\n withDeleted\n );\n\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, path);\n\n if (row && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {\n const contextForCallback = this.buildCallContext();\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n // `?? row` on every tier — see the note in `fetchCollection`.\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n row = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row,\n context: contextForCallback\n }) ?? row;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n row = await callbacks.afterRead({\n collection: callbackCollection,\n path,\n row,\n context: contextForCallback\n }) ?? row;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n row = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row,\n context: contextForCallback\n }) ?? row;\n }\n }\n\n return row;\n }\n\n /**\n * Listen to one row: the row now, then again after every change, and\n * `null` when it is not there — deleted, never created, or not readable by\n * the subscriber. Reads as {@link listenCollection} does.\n */\n listenOne<M extends Record<string, unknown>>(\n { path, id, include, onUpdate, onError }: ListenOneProps<M>,\n authContext: SubscriptionAuthContext | undefined = this.user ? authContextOf(this.user) : undefined\n ): () => void {\n const subscriptionId = this.generateSubscriptionId();\n\n this.realtimeService.startDataDriverSubscription(subscriptionId, {\n type: \"single\",\n path,\n id,\n include,\n authContext,\n onError: fetchErrorListener(onError)\n }, (row) => {\n // `null` is delivered, not dropped: it is how a listener learns its\n // row is gone. Dropped, the listener kept the last row it had.\n if (!Array.isArray(row)) onUpdate(row);\n });\n\n return () => this.realtimeService.unsubscribe(subscriptionId);\n }\n\n /**\n * How many membership rows a single write will read to answer \"which\n * tenants is this caller in\".\n *\n * A cap, not a limit on the feature: past it the API-level check stops\n * being able to prove a tenant is *not* the caller's, and defers to the\n * policy's `WITH CHECK`, which decides either way. The alternative — an\n * uncapped read of a membership table on every insert — makes the cost of a\n * write depend on how many organizations one user happens to belong to.\n */\n private static readonly TENANT_MEMBERSHIP_CAP = 100;\n\n /**\n * Stamp, or refuse, the tenant on a write to a `tenant`-scoped collection.\n *\n * A no-op for every collection that declares no tenancy, which is the\n * common case and costs one property read.\n */\n private async applyTenantScope<M extends Record<string, unknown>>(\n collection: CollectionConfig | undefined,\n path: string,\n values: Partial<EntityValues<M>>,\n status: EntityStatus,\n previousValues: Record<string, unknown> | undefined\n ): Promise<Partial<EntityValues<M>>> {\n const tenant = getTenantConfig(collection);\n if (!tenant) return values;\n\n // The same set the policy lets past: the trusted server context (no\n // user at all) and the bypass roles. Deciding it here rather than\n // inside the pure helper keeps \"who is calling\" in the one place that\n // knows.\n const bypassRoles = tenantBypassRoles(tenant);\n const callerRoles = (this.user?.roles ?? []).map(r =>\n typeof r === \"string\" ? r : String((r as { id?: unknown })?.id ?? r));\n const bypass = !this.user || callerRoles.some(r => bypassRoles.includes(r));\n\n const { tenants, complete } = bypass\n ? { tenants: [] as unknown[], complete: true }\n : await this.resolveCallerTenants(tenant, collection);\n\n const decision = resolveTenantWrite({\n tenant,\n values: values as Record<string, unknown>,\n status,\n callerTenants: tenants,\n callerTenantsComplete: complete,\n bypass,\n previousValues,\n slug: collection?.slug ?? path\n });\n\n if (decision.refusal) {\n const { code, field, message } = decision.refusal;\n throw ApiError.badRequest(message, code, {\n collection: path,\n violations: [{ field, code, message }]\n });\n }\n return decision.values as Partial<EntityValues<M>>;\n }\n\n /**\n * The tenants this caller may write into.\n *\n * Two sources, and they answer at different costs. A `claim` is already on\n * the request — no query. A `membership` is rows in a table, and reading\n * them is a real query, so it is capped (see\n * {@link PostgresBackendDriver.TENANT_MEMBERSHIP_CAP}) and `complete` says\n * whether the cap was hit.\n *\n * The membership read runs in the caller's own context, so the membership\n * collection's RLS applies to it — exactly as it does inside the generated\n * policy's subquery. That is deliberate: if the two disagreed, the API\n * would refuse writes the database would have accepted, or accept writes it\n * refuses. `validateCollectionConfig` is what makes sure the membership\n * collection is readable by its own members in the first place.\n */\n private async resolveCallerTenants(\n tenant: NonNullable<ReturnType<typeof getTenantConfig>>,\n collection: CollectionConfig | undefined\n ): Promise<{ tenants: unknown[]; complete: boolean }> {\n if (\"claim\" in tenant.from) {\n const value = this.user?.claims?.[tenant.from.claim];\n const empty = value === undefined || value === null || value === \"\";\n return { tenants: empty ? [] : [value], complete: true };\n }\n\n const uid = this.user?.uid;\n if (!uid) return { tenants: [], complete: true };\n\n const { collection: slug, userField, tenantField } = tenant.from.membership;\n const cap = PostgresBackendDriver.TENANT_MEMBERSHIP_CAP;\n try {\n // Read on the trusted plane for its fields — the database still\n // scopes the rows to the caller. This is the server deciding a\n // write, not a read served to anyone, and the caller's `access.read`\n // rules describe what they may receive: applied here, a tenant\n // column members may not read would leave every member in no\n // tenant at all.\n const rows = await withFieldViewer(undefined, () => this.dataService.fetchCollection(slug, {\n filter: { [userField]: [\"==\", uid] } as never,\n limit: cap + 1,\n databaseId: collection?.databaseId\n }));\n const tenants = Array.from(new Set(\n rows.slice(0, cap)\n .map(row => (row as Record<string, unknown>)[tenantField])\n .filter(value => value !== null && value !== undefined)\n .map(value => (typeof value === \"object\"\n ? (value as { id?: unknown }).id ?? value\n : value))\n ));\n return { tenants, complete: rows.length <= cap };\n } catch (err) {\n // A membership table this caller cannot read is not a reason to\n // fail the write here — the policy is about to refuse it anyway,\n // and refusing with the wrong message would be worse than letting\n // the database answer. `complete: false` is what says \"no evidence\n // of absence\"; see `resolveTenantWrite`. A statement the database\n // refused is the exception: it has aborted the write's\n // transaction, so the database can no longer answer anything but\n // 25P02, and its own refusal is the better message.\n if (reachedDatabase(err)) throw err;\n logger.debug(`[save] Could not read '${slug}' to resolve the caller's tenants`, {\n detail: err instanceof Error ? err.message : String(err)\n });\n return { tenants: [], complete: false };\n }\n }\n\n /**\n * Where an update reads the row it addresses before writing it.\n *\n * The path itself, except through a many-to-many relation: there the path\n * names the parent's set, and an update may *link* a row that exists but is\n * not in that set yet (the persistence layer attaches it). Read through the\n * path, such a row is absent, so it is read from its own collection.\n */\n private storedRowPath(path: string): string {\n if (!isNestedPath(path)) return path;\n const hop = resolveNestedPath(path, this.registry);\n return hop && isJunctionBackedRelation(hop.relation) ? hop.targetCollection.slug : path;\n }\n\n /**\n * The row an update addresses, as this caller reads it there: what a\n * caller's precondition (the REST routes' 404 and `If-Match`) judges.\n *\n * `stored` is the row read from {@link storedRowPath}. Off a many-to-many\n * path that is the address itself. Through one, the row is judged as a\n * member of the parent's set when it is one. When it is not yet, this\n * update is the link that makes it one (the persistence layer writes the\n * junction row), so the row is judged as it is stored — provided the caller\n * can read the parent it is being linked to. A parent that is not there, or\n * that their policies hide, is no set to join, and the row reads as absent.\n */\n private async rowAnUpdateAddresses(\n path: string,\n storedPath: string,\n id: string | number,\n stored: Record<string, unknown> | null,\n databaseId: string | undefined,\n withDeleted: true | undefined\n ): Promise<Record<string, unknown> | null> {\n if (storedPath === path) return stored;\n const fetchService = this.dataService.getFetchService();\n const member = await fetchService.fetchOneForRest(path, id, undefined, databaseId, { withDeleted });\n if (member || !stored) return member;\n const segments = splitPathSegments(path);\n const parent = await fetchService.fetchOneForRest(\n segments.slice(0, -2).join(\"/\"), segments[segments.length - 2]\n );\n return parent ? stored : null;\n }\n\n /**\n * The stored row an upsert's key names, if this caller can address it —\n * with its address and the values to update it with.\n *\n * Read as the caller reads: their policies and `beforeQuery` scope. On the\n * trusted plane for its fields only, like {@link resolveCallerTenants}:\n * the key columns are the server's to see.\n *\n * Soft-deleted rows are read too, and a key one of them holds is refused\n * with `ROW_IN_TRASH` (see {@link rowInTrashError}). Hidden, it was not\n * found here, the write went down the create pipeline, and the INSERT's\n * conflict branch wrote into the trashed row: `201 Created`, hooks told\n * \"new\", a second `create` in history, and the row still in the trash.\n *\n * `undefined` when the key is incomplete (the write is a plain insert,\n * which is the persistence layer's rule too), when a key value is not a\n * plain value, or when no such row is visible. None of those can write a\n * stored row wrongly: the statement's own conflict branch sets only what\n * the caller sent, only on a row in their scope, and never on one in the\n * trash.\n *\n * The values drop the key and the conflict target, which the stored row\n * already has: a natural-key upsert carrying a key the caller invented\n * must not move the row onto it.\n */\n private async findUpsertTarget<M extends Record<string, unknown>>(\n path: string,\n collection: CollectionConfig,\n values: Partial<EntityValues<M>>,\n id: string | number | undefined,\n onConflict: readonly string[] | undefined\n ): Promise<{ id: string; values: Partial<EntityValues<M>>; row: Record<string, unknown> } | undefined> {\n const primaryKeys = getPrimaryKeys(collection, this.registry);\n const key: Record<string, unknown> = { ...values };\n if (isPresentKey(id)) Object.assign(key, parseIdValues(id, primaryKeys));\n const targetFields = onConflict && onConflict.length > 0\n ? [...onConflict]\n : primaryKeys.map(info => info.fieldName);\n if (targetFields.length === 0) return undefined;\n\n const filter: FilterValues<string> = {};\n for (const field of targetFields) {\n const value = key[field];\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value !== \"string\" && typeof value !== \"number\" && typeof value !== \"boolean\") return undefined;\n filter[field] = [\"==\", value];\n }\n\n const softDelete = resolveSoftDelete(collection);\n const [row] = await withFieldViewer(undefined, () => this.dataService.fetchCollectionForRest(path, {\n filter,\n limit: 1,\n databaseId: collection.databaseId,\n withDeleted: softDelete ? true : undefined\n }));\n // The row must be the one the key names. A filter key the table does\n // not have is dropped rather than refused under the process-wide\n // \"warn\" mode, and an unfiltered `limit: 1` is somebody else's row.\n if (!row || targetFields.some(field => String(row[field]) !== String(key[field]))) return undefined;\n if (softDelete && row[softDelete.field] !== null && row[softDelete.field] !== undefined) {\n throw rowInTrashError(path, targetFields.map(field => String(key[field])).join(\", \"), softDelete);\n }\n\n const rest: Record<string, unknown> = { ...values };\n for (const field of [...primaryKeys.map(info => info.fieldName), ...targetFields]) delete rest[field];\n return {\n id: deriveRowAddress(row, collection, this.registry),\n values: rest as Partial<EntityValues<M>>,\n row\n };\n }\n\n async save<M extends Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>> {\n // A frame per write, so a hook whose `context.data` write runs that\n // hook again is refused at a bounded depth instead of looping inside\n // the request's transaction. See `write-depth.ts`.\n return inWriteFrame(props.path, \"save\", frame => this.saveInFrame(props, frame));\n }\n\n private async saveInFrame<M extends Record<string, unknown>>({\n path,\n id,\n values,\n collection,\n status,\n upsert,\n onConflict,\n precondition\n }: SaveProps<M>, frame: WriteFrame,\n /**\n * The stored row, when this write already read it in\n * this transaction (an upsert continuing as an update).\n */\n knownStored?: Record<string, unknown>): Promise<Record<string, unknown>> {\n\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, path);\n if (declaresAnyHook(callbacks, globalCallbacks, propertyCallbacks)) currentWriteScope()?.noteApplicationCode();\n // Before anything runs, hooks included: a write whose audit entry\n // cannot be recorded is refused, not made and rolled back.\n if (resolvedCollection?.history) this.historyService?.assertRecordable(resolvedCollection.slug);\n\n // An upsert that finds its row is an update of that row, and runs as\n // one: the update gate, `beforeSave` told \"existing\" with the stored\n // values, `on_update` stamps only, history \"update\". Every upsert door\n // (`?on_conflict=`, bulk and `_batch` upserts, the SDK, the socket)\n // reached the create pipeline instead, and the INSERT's conflict branch\n // wrote it over the stored row: omitted fields reset to their\n // `defaultValue`, `user_on_create` handed to whoever ran the import,\n // hooks told the row was new, and a row the caller's `beforeQuery`\n // hides updated and then answered 500. The read runs in the write's\n // transaction; a row it cannot see is still met safely by the\n // statement's own conflict branch below.\n const assertUpdateShape = () => {\n if (status !== \"existing\") return;\n assertUpdateDoesNotSoftDelete(resolvedCollection as CollectionConfig | undefined, values, path);\n if (isPresentKey(id)) {\n assertKeyUnchanged(resolvedCollection as CollectionConfig | undefined, this.registry, id, values as Record<string, unknown>, path);\n }\n };\n // A caller's precondition is answered first, as the REST routes\n // answered their own read of the row before handing the body on: a\n // body that is wrong for a row that is not there is still a 404.\n const preconditionFirst = precondition !== undefined && status === \"existing\" && Boolean(id);\n if (!preconditionFirst) assertUpdateShape();\n\n const upserting = upsert === true && status !== \"existing\";\n if (upserting && resolvedCollection) {\n const stored = await this.findUpsertTarget<M>(\n path, resolvedCollection as CollectionConfig, values, id, onConflict\n );\n if (stored) {\n // The same write, continued as an update: the same frame. The\n // row the key named was just read, in this transaction, and is\n // the update's previous values rather than a second read of\n // it — unless the two reads could differ: a `beforeQuery` may\n // narrow the \"list\" that found it differently from a \"get\", the\n // find ran on the trusted plane for per-field read access, and a\n // nested path reads the row from elsewhere.\n const reusable = !callbacks?.beforeQuery && !globalCallbacks?.beforeQuery\n && !hasFieldAccessRules(resolvedCollection as CollectionConfig) && !isNestedPath(path);\n return this.saveInFrame<M>(\n { path, id: stored.id, values: stored.values, collection, status: \"existing\" },\n frame,\n reusable ? stored.row : undefined\n );\n }\n }\n\n let updatedValues = values;\n const contextForCallback = this.buildCallContext();\n\n // Declared defaults are filled in BEFORE the hooks, not after, so a\n // `beforeSave` sees the row as it will be stored rather than a version\n // of it missing every key the caller happened to omit. A hook that\n // reads `values.currency` to pick a tax rate was reading `undefined`\n // on exactly the writes the default exists to cover.\n //\n // Before validation too: `required` on create is satisfied by a\n // default, which is why `assertWriteValuesValid` skips a property that\n // declares one.\n if ((status === \"new\" || status === \"copy\") && resolvedCollection?.properties) {\n updatedValues = applyDefaultValuesOnCreate<M>(\n updatedValues,\n resolvedCollection.properties\n ) as Partial<EntityValues<M>>;\n }\n\n // Fetch previous values for callbacks AND history recording. Same walk\n // as the saved row the callbacks receive (`fetchOneForRest`), so\n // `values` and `previousValues` compare like with like — a Date on one\n // side and its ISO string on the other reads as a change that never\n // happened.\n let previousValuesForHistory: Partial<M> | undefined;\n if (status === \"existing\" && id) {\n // An update addresses a row the caller can read, and a key that\n // reads as nothing is a 404 — answered *here*, before any hook runs\n // and before the write, as `delete` answers it. The read is the\n // caller's: their policies, their `beforeQuery`, and the trash\n // hidden. So a row in the trash is a 404 too, unless this update\n // is its restore (`restoresSoftDeletedRow`), which is the one edit\n // that reaches a stamped row.\n //\n // This used to refuse only when the collection declared a\n // `beforeQuery`, and REST's own pre-read was the rule everywhere\n // else. Every other door wrote straight through: the socket, MCP\n // and `driver.data` ran `beforeSave` for a key no row has before\n // the 404, and edited a trashed row — hooks, history and all —\n // that REST answered 404 for. And a restore read its previous\n // values with the trash hidden, found none, and history skipped\n // the update it had nothing to compare with: every restore went\n // unrecorded, and the trail said \"deleted\" for a live row.\n //\n // Through a many-to-many path an update can also *link* a row\n // that exists but is not linked yet, so there the row is read from\n // its own collection. A read that fails for another reason (a\n // collection whose key the registry cannot resolve) stays\n // best-effort enrichment and is not a reason to fail a write.\n //\n // A caller's `precondition` (the REST routes' 404 and `If-Match`)\n // judges this same read, inside this transaction — after the row\n // is locked, when it asks for that — so the routes no longer read\n // the row in a transaction of their own first.\n try {\n const withDeleted = restoresSoftDeletedRow(\n resolvedCollection as CollectionConfig | undefined,\n values as Record<string, unknown>\n ) ? true : undefined;\n if (precondition?.lock) await this.dataService.getPersistService().lockRow(path, id);\n const storedPath = this.storedRowPath(path);\n const existing = knownStored && !precondition\n ? knownStored\n : await this.dataService.getFetchService().fetchOneForRest(\n storedPath, id, undefined, resolvedCollection?.databaseId, { withDeleted }\n );\n if (precondition) {\n const addressed = await this.rowAnUpdateAddresses(\n path, storedPath, id, existing, resolvedCollection?.databaseId, withDeleted\n );\n await precondition.check(await this.asRestServes(addressed, path));\n }\n if (!existing) {\n throw ApiError.notFound(`No row \"${id}\" in \"${path}\" to update.`);\n }\n // Every column, the key among them — the same shape as the\n // saved row history compares it with. Without the key on this\n // side, every update listed it as changed.\n previousValuesForHistory = existing as Partial<M>;\n if (preconditionFirst) assertUpdateShape();\n } catch (err) {\n // The refusal above is the answer, not a failed enrichment.\n if (err instanceof ApiError) throw err;\n // So is a statement the database refused: on a request it\n // has aborted the write's transaction, and carrying on only\n // turns the next statement into an unrelated 25P02.\n if (reachedDatabase(err)) throw err;\n // And so is whatever a caller's precondition threw: the write\n // it guards must not go ahead on a read that failed.\n if (precondition) throw err;\n // Best-effort enrichment: callbacks and history run without\n // previous values rather than the save failing on a read the\n // write itself does not need (e.g. a collection whose key the\n // registry cannot resolve).\n logger.debug(`[save] Could not fetch previous values for \"${path}\"`, { detail: err instanceof Error ? err.message : String(err) });\n }\n }\n\n /** The row as the hooks receive it, to tell what they wrote. */\n const beforeHooks: Partial<EntityValues<M>> = updatedValues;\n\n // A `before*` callback is the application speaking, not the server\n // failing: a bare `throw` is the documented way to block a write, so it\n // answers 400 with the author's message rather than a masked 500.\n try {\n frame.stage = \"beforeSave\";\n if (globalCallbacks?.beforeSave || callbacks?.beforeSave || propertyCallbacks?.beforeSave) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n // 1. Global callbacks first\n if (globalCallbacks?.beforeSave) {\n const result = await globalCallbacks.beforeSave({\n collection: callbackCollection,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = applyBeforeSaveResult(updatedValues, result);\n }\n\n // 2. Collection callbacks second\n if (callbacks?.beforeSave) {\n const result = await callbacks.beforeSave({\n collection: callbackCollection,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = applyBeforeSaveResult(updatedValues, result);\n }\n\n // 3. Property callbacks third\n if (propertyCallbacks?.beforeSave) {\n const result = await propertyCallbacks.beforeSave({\n collection: callbackCollection,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = applyBeforeSaveResult(updatedValues, result);\n }\n\n }\n } catch (callbackError) {\n throw toCallbackError(callbackError, \"beforeSave\", path);\n }\n const afterHooks: Partial<EntityValues<M>> = updatedValues;\n if (status === \"existing\" && isPresentKey(id)) {\n assertKeyUnchanged(resolvedCollection as CollectionConfig | undefined, this.registry, id, updatedValues as Record<string, unknown>, path);\n }\n\n // Apply autoValue timestamps (on_create / on_update) at the application layer.\n // This handles updated_at fields for all writes that flow through the Rebase backend.\n if (resolvedCollection?.properties) {\n updatedValues = updateDateAutoValues({\n inputValues: updatedValues,\n properties: resolvedCollection.properties,\n status: status ?? \"new\",\n timestampNowValue: new Date()\n });\n // The identity half of the same idea: `created_by` / `updated_by`\n // taken from the call context, never from the body. Stamped after\n // the hooks for the same reason the timestamps are — a hook must\n // not be able to attribute a write to another user either.\n assertActingUserForAutoValues(\n resolvedCollection.properties,\n status ?? \"new\",\n this.user?.uid,\n path\n );\n updatedValues = updateUserAutoValues({\n inputValues: updatedValues,\n properties: resolvedCollection.properties,\n status: status ?? \"new\",\n uid: this.user?.uid\n });\n // And an update leaves the create-time stamps alone. They record\n // who made the row and when, which no later write can change; left\n // to the body (or a hook), any row writer could reattribute a row\n // they edited — `created_by` is the column that exists to stop that.\n if (status === \"existing\") {\n const kept: Record<string, unknown> = { ...updatedValues };\n for (const key of createStampKeys(resolvedCollection.properties)) delete kept[key];\n updatedValues = kept as Partial<EntityValues<M>>;\n }\n }\n\n // Tenancy, last of the three stamps and for the same reason they are\n // here rather than in a hook: the tenant comes from the *call context*,\n // never from the body, so a caller (or a `beforeSave`) cannot attribute\n // a row to another tenant. The database enforces the same rule through\n // the restrictive tenancy policy — this only reaches the same answer\n // one layer earlier, with the field named. See `@rebasepro/common`'s\n // `resolveTenantWrite`.\n updatedValues = await this.applyTenantScope(\n resolvedCollection as CollectionConfig | undefined,\n path,\n updatedValues,\n status ?? \"new\",\n previousValuesForHistory as Record<string, unknown> | undefined\n ) as Partial<EntityValues<M>>;\n\n // A create that names its key is still a create. The persistence layer\n // reads an `id` as \"update this row\", so a new row's key travels inside\n // the values, where the INSERT takes it — which is what the REST create\n // does with a key in the body. Handed down as `id`, in-process\n // `create(values, id)`, MCP `create_document { id }` and a socket\n // `SAVE { id, status: \"new\" }` were all UPDATEs: a new id answered 404,\n // and an existing one was overwritten with create-time values instead\n // of refused. Only `status: \"existing\"` addresses a stored row; a\n // duplicate key here is the driver's 409, as it is for any create.\n let writeId = id;\n if ((status === \"new\" || status === \"copy\") && !upsert && isPresentKey(id) && resolvedCollection) {\n updatedValues = {\n ...updatedValues,\n ...parseIdValues(id, getPrimaryKeys(resolvedCollection as CollectionConfig, this.registry))\n } as Partial<EntityValues<M>>;\n writeId = undefined;\n }\n\n // For an upsert whose INSERT meets a stored row anyway — one the read\n // above could not see, or one inserted concurrently — what the\n // conflict-update may set: what the caller sent, what a hook wrote, and\n // the `on_update` stamps. Everything else here the create alone decided\n // (declared defaults, a tenant stamp; the create-time stamps are left\n // out by the persistence layer), and a stored row keeps its own.\n const insertOnlyKeys = upserting\n ? insertOnlyKeysOf(updatedValues, values, beforeHooks, afterHooks, resolvedCollection?.properties)\n : undefined;\n // A stamped soft-delete field may create a row already trashed, and\n // may not trash a stored one: that is a delete (see\n // `assertUpdateDoesNotSoftDelete`).\n const softDeleteField = resolveSoftDelete(resolvedCollection as CollectionConfig | undefined)?.field;\n if (insertOnlyKeys && softDeleteField\n && updatedValues[softDeleteField] !== undefined && updatedValues[softDeleteField] !== null) {\n insertOnlyKeys.push(softDeleteField);\n }\n\n try {\n const storedRow = await this.dataService.save<M>(\n path,\n updatedValues,\n writeId,\n resolvedCollection?.databaseId,\n { upsert: upserting, onConflict, insertOnlyKeys }\n );\n\n // Two rows from here on, and which one goes where is the point.\n // `storedRow` is what the table now holds: `afterSave` judges it,\n // history records it, its address is derived from it. `savedRow` is\n // what the CALLER reads — `afterRead`'s view, for the response and\n // its realtime echo. They used to be one variable, so a masking\n // `afterRead` (`email` → `********`) was what history recorded and\n // what a revert then wrote into the column, and a computed field it\n // added made every version un-revertable. A copy goes in, so an\n // `afterRead` that edits its argument in place edits only the view.\n let savedRow = storedRow;\n if (storedRow && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n savedRow = { ...storedRow };\n // `?? savedRow` on every tier — see the note in `fetchCollection`.\n // Here it decided what the write's own response body contained.\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n savedRow = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row: savedRow,\n context: contextForCallback\n }) ?? savedRow;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n savedRow = await callbacks.afterRead({\n collection: callbackCollection,\n path,\n row: savedRow,\n context: contextForCallback\n }) ?? savedRow;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n savedRow = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row: savedRow,\n context: contextForCallback\n }) ?? savedRow;\n }\n }\n\n // The row is exactly its columns, so its address is derived, not read\n // off it: `storedRow.id` is undefined for every table whose key is not\n // literally named `id`, and is ordinary data for a table that has such\n // a column without it being the key. From the stored row: an\n // `afterRead` may reshape or mask the key columns of the view.\n const savedId = deriveRowAddress(\n storedRow,\n (resolvedCollection ?? collection) as CollectionConfig,\n this.registry\n );\n // `values` are the row's columns — all of them, as stored. For an\n // `id`-keyed table that includes `id`, which used to be stripped here\n // because it was the synthesized address rather than the column it now is.\n const savedValues = storedRow;\n\n // `afterSave` runs INSIDE the write's transaction and is awaited, so a\n // throw here rolls the row back — the write and its consequences\n // commit together or not at all. That is the contract the docs state,\n // and it only holds if the throw is answerable: routed through the\n // same `toCallbackError` path a `before*` hook uses, it becomes a 400\n // `CALLBACK_REJECTED` naming the stage instead of a masked 500 whose\n // message only the server log ever sees. Side effects that must not\n // hold a transaction open (HTTP calls, mail) belong in a job — one\n // enqueued in a transaction that rolls back was never enqueued.\n try {\n frame.stage = \"afterSave\";\n if (globalCallbacks?.afterSave || callbacks?.afterSave || propertyCallbacks?.afterSave) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n // 1. Global callbacks first\n if (globalCallbacks?.afterSave) {\n await globalCallbacks.afterSave({\n collection: callbackCollection,\n path,\n id: savedId,\n values: savedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n }\n // 2. Collection callbacks second\n if (callbacks?.afterSave) {\n await callbacks.afterSave({\n collection: callbackCollection,\n path,\n id: savedId,\n values: savedValues as Partial<M>,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterSave) {\n await propertyCallbacks.afterSave({\n collection: callbackCollection,\n path,\n id: savedId,\n values: savedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n }\n }\n } catch (callbackError) {\n throw toCallbackError(callbackError, \"afterSave\", path);\n }\n\n // Awaited, inside the write's transaction, so the entry commits with\n // its row. It used to be dispatched and dropped — the promise was\n // not held, the service swallowed its own errors — which meant the\n // audit trail of a collection that opted into one had silent,\n // unbounded gaps. See `HistoryService.recordHistory` for the trade.\n if (this.historyService && resolvedCollection?.history) {\n await this.historyService.recordHistory({\n // The collection's slug, which is what the history route\n // reads — not the path the write came in on. Keyed by\n // `owners/1/docs`, a write made through a parent never\n // appeared in the row's own history.\n tableName: resolvedCollection.slug,\n id: savedId,\n action: status === \"existing\" ? \"update\" : \"create\",\n values: savedValues as Record<string, unknown>,\n previousValues: previousValuesForHistory as Record<string, unknown> | undefined,\n updatedBy: this.user?.uid\n });\n }\n\n // Notify real-time subscribers (deferred if inside a transaction)\n const notifications: PostgresBackendDriver[\"_pendingNotifications\"] = [{\n path,\n id: savedId,\n row: savedRow,\n databaseId: resolvedCollection?.databaseId\n }, ...this.childListNotifications(resolvedCollection, savedId, updatedValues as Record<string, unknown>)];\n if (this._deferNotifications) {\n this._pendingNotifications.push(...notifications);\n } else {\n for (const notification of notifications) {\n await this.realtimeService.notifyUpdate(\n notification.path,\n notification.id,\n notification.row,\n notification.databaseId\n );\n }\n }\n\n return savedRow;\n } catch (error) {\n if (globalCallbacks?.afterSaveError || callbacks?.afterSaveError || propertyCallbacks?.afterSaveError) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n // Global, then collection, then property — each on its own,\n // and guarded: a hook that throws cannot replace the failure\n // the caller is owed with its own.\n const runHooks = async (context: RebaseCallContext) => {\n // What the hook exists to see. It was documented from the\n // start and never passed, so `props.error` was `undefined`\n // in every handler ever written against the guide. `id` is\n // the caller's when there is one, and `previousValues` is\n // whatever the pre-write read managed to fetch.\n const errorProps = {\n collection: callbackCollection,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n error,\n context\n };\n for (const [tier, hook] of [\n [\"global\", globalCallbacks?.afterSaveError],\n [\"collection\", callbacks?.afterSaveError],\n [\"property\", propertyCallbacks?.afterSaveError]\n ] as const) {\n if (!hook) continue;\n try {\n await hook(errorProps);\n } catch (hookError) {\n logger.error(`[save] The ${tier} afterSaveError hook for \"${path}\" threw; answering with the save's own failure`, { error: hookError });\n }\n }\n };\n // The hook is for alerting, and the documented way to alert is\n // a job. Inside a request's write it waits for that write's\n // transaction to end: it used to run inside it, so what it\n // enqueued rode the transaction this failure rolls back, and\n // vanished with it. Run after, it commits on its own.\n if (!currentWriteScope()?.afterSettled(runHooks)) {\n frame.stage = \"afterSaveError\";\n await runHooks(contextForCallback);\n }\n }\n throw error;\n }\n }\n\n /**\n * Write many rows through the same pipeline as {@link save}.\n *\n * The batch runs in one transaction of its own, so a failure part-way leaves\n * nothing behind — the point of a batch is that a re-run starts from a known\n * state. When this driver is already inside a transaction (the authenticated\n * path, via `withTransaction`) the nested call becomes a savepoint, which is\n * still atomic and still commits once.\n *\n * Rows are applied in order, so a batch that touches the same key twice ends\n * with the last write winning, exactly as separate calls would.\n */\n /**\n * Run a batch in one transaction of its own, on a driver bound to it, and\n * tell realtime subscribers about its rows only once it has committed.\n *\n * Inside a request's transaction the events already wait for that one to\n * commit, and the batch adds to its list. On the server's own driver they\n * did not wait at all: each row was announced as it was written, before\n * the COMMIT — a subscriber's refetch found nothing yet, and a batch that\n * then rolled back had announced rows that never existed.\n */\n private async inBatchTransaction<T>(run: (txDriver: PostgresBackendDriver) => Promise<T>): Promise<T> {\n const ownNotifications = !this._deferNotifications;\n const pending: PostgresBackendDriver[\"_pendingNotifications\"] = ownNotifications ? [] : this._pendingNotifications;\n const result = await this.db.transaction(async (tx) => {\n const txDriver = this.bindToTransaction(tx);\n txDriver._deferNotifications = true;\n txDriver._pendingNotifications = pending;\n return run(txDriver);\n });\n if (ownNotifications) {\n for (const notification of pending) {\n try {\n await this.realtimeService.notifyUpdate(\n notification.path, notification.id, notification.row, notification.databaseId\n );\n } catch (error) {\n logger.error(\"[DataDriver] Error flushing deferred notification\", { error });\n }\n }\n }\n return result;\n }\n\n private bindToTransaction(tx: DrizzleClient): PostgresBackendDriver {\n // Bind the whole batch to the transaction handle. Without this the rows\n // would be written through `this.db` and survive a rollback.\n const txDriver = new PostgresBackendDriver(\n tx, this.realtimeService, this.registry, this.user, this.poolManager, this.historyService\n );\n txDriver.dataService = new DataService(tx, this.registry, () => {\n // Asked for by a `beforeQuery` about to run: application code.\n currentWriteScope()?.noteApplicationCode();\n return txDriver.buildCallContext();\n });\n txDriver.client = this.client;\n // Carry the caller's notification batching through, so a bulk write\n // nested in an outer transaction still holds its events until commit.\n txDriver._deferNotifications = this._deferNotifications;\n txDriver._pendingNotifications = this._pendingNotifications;\n return txDriver;\n }\n\n /**\n * The callback context for work already running inside `tx` on behalf of\n * `user` — for a caller that opened the user-scoped transaction itself.\n *\n * The realtime refetch is that caller: it applies the subscriber's auth\n * context and role switch on its own connection. Its `afterRead` hooks used\n * to get a context assembled by hand, and it was wrong in both directions:\n * `data` was THIS driver's — the base one, on the owner connection, outside\n * that transaction — so a hook reading related rows through `context.data`\n * bypassed RLS on every subscription frame while the REST read of the same\n * rows did not; and `client` and `storageSource` were absent, though the\n * type promises both. This builds the context the REST path builds, bound to\n * the transaction the rows came from.\n */\n callContextWithin(tx: DrizzleClient, user: User): RebaseCallContext {\n const txDriver = new PostgresBackendDriver(\n tx, this.realtimeService, this.registry, user, this.poolManager, this.historyService\n );\n txDriver.dataService = new DataService(tx, this.registry, () => {\n // Asked for by a `beforeQuery` about to run: application code.\n currentWriteScope()?.noteApplicationCode();\n return txDriver.buildCallContext();\n });\n txDriver.client = this.client;\n return txDriver.buildCallContext();\n }\n\n async saveMany<M extends Record<string, unknown>>({\n path,\n rows,\n collection,\n upsert,\n onConflict\n }: SaveManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.inBatchTransaction(async (txDriver) => {\n\n const saved: Record<string, unknown>[] = [];\n\n for (let i = 0; i < rows.length; i++) {\n const values = rows[i];\n const id = (values as Record<string, unknown>)?.id as string | number | undefined;\n try {\n saved.push(await txDriver.save<M>({\n path,\n values,\n // No `id` argument, deliberately: passing one selects the\n // UPDATE path, and an import's rows usually carry a natural\n // key for a row that does not exist yet — which would 404 on\n // every one. Leaving the key inside `values` is what\n // single-row `create(data, id)` does, and it inserts.\n // Callers who want existing rows overwritten pass `upsert`.\n collection,\n status: \"new\",\n upsert,\n onConflict\n }));\n } catch (error) {\n // One bad row in ten thousand is impossible to find from a\n // message that only says the batch failed. Say which row, and\n // keep the original error as the cause so its status survives.\n const label = id !== undefined ? `id ${JSON.stringify(id)}` : \"no id\";\n throw Object.assign(\n new Error(`Row ${i} of ${rows.length} (${label}) failed: ${(error as Error)?.message ?? error}`, { cause: error }),\n {\n statusCode: (error as { statusCode?: number })?.statusCode,\n code: (error as { code?: string })?.code,\n name: (error as Error)?.name\n }\n );\n }\n }\n\n return saved;\n });\n }\n\n /**\n * Update many rows through the same pipeline as {@link save}, in one\n * transaction.\n *\n * Structurally the mirror of {@link saveMany} — same tx-bound sub-driver,\n * same deferred notifications, same per-row error labelling — but it calls\n * `save` with an explicit `id` and `status: \"existing\"`, which is precisely\n * what `saveMany` cannot do: that one passes `status: \"new\"` and keeps the\n * key inside `values`, so it inserts or upserts and can never target a\n * particular row.\n *\n * All-or-nothing, so an id matching no row aborts the batch. A partial\n * update is the outcome with no good recovery: the caller cannot tell which\n * half landed without re-reading everything.\n */\n async updateMany<M extends Record<string, unknown>>({\n path,\n updates,\n collection\n }: UpdateManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.inBatchTransaction(async (txDriver) => {\n\n const saved: Record<string, unknown>[] = [];\n\n for (let i = 0; i < updates.length; i++) {\n const { id, values } = updates[i];\n try {\n // A missing row is a 404 rather than a silent no-op, judged\n // on the read `save` makes of the row it updates (a restore\n // reading the trash too). That read used to be preceded by\n // one of its own here, through the admin view-model walk,\n // which loaded every declared relation of the row — every\n // comment of a post — to learn that it existed.\n saved.push(await txDriver.save<M>({\n path,\n id: String(id),\n values,\n collection,\n status: \"existing\",\n precondition: { check: stored => assertBatchRowFound(stored, id) }\n }));\n } catch (error) {\n // Say which entry, as saveMany does: \"the batch failed\" is\n // unactionable at a thousand rows.\n throw Object.assign(\n new Error(`Update ${i} of ${updates.length} (id ${JSON.stringify(id)}) failed: ${(error as Error)?.message ?? error}`, { cause: error }),\n {\n statusCode: (error as { statusCode?: number })?.statusCode,\n code: (error as { code?: string })?.code,\n name: (error as Error)?.name\n }\n );\n }\n }\n\n return saved;\n });\n }\n\n /**\n * Delete many rows in one transaction, running the full delete pipeline —\n * `beforeDelete`, the delete, `afterDelete` — for each.\n *\n * Looping the single-row {@link delete} rather than emitting one\n * `DELETE ... WHERE id = ANY($1)` is the deliberate choice: a single\n * statement would be faster and would skip every callback, so a collection\n * relying on `beforeDelete` to veto or on `afterDelete` to clean up\n * dependents would behave differently depending on how many rows the caller\n * happened to delete at once. Same pipeline, one transaction.\n */\n async deleteMany<M extends Record<string, unknown>>({\n path,\n ids,\n collection,\n hard\n }: DeleteManyProps<M>): Promise<void> {\n await this.inBatchTransaction(async (txDriver) => {\n\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n try {\n // No read first: `delete` reads the row itself, and an id\n // that matches nothing is its 404, which rolls the batch\n // back like any other failure.\n await txDriver.delete<M>({\n row: { id: String(id), path },\n collection,\n hard\n });\n } catch (error) {\n throw Object.assign(\n new Error(`Delete ${i} of ${ids.length} (id ${JSON.stringify(id)}) failed: ${(error as Error)?.message ?? error}`, { cause: error }),\n {\n statusCode: (error as { statusCode?: number })?.statusCode,\n code: (error as { code?: string })?.code,\n name: (error as Error)?.name\n }\n );\n }\n }\n });\n }\n\n /**\n * A mixed list of writes across collections, as one unit of work.\n *\n * Same transaction, same tx-bound sub-driver and same per-entry error\n * labelling as {@link saveMany} — deliberately the same plumbing rather\n * than a second copy of it, because the three ways this could drift\n * (notifications not deferred, the sub-driver not bound, an error losing\n * its status) are all silent.\n *\n * The one thing this adds is `$ref`. An operation may name itself, and a\n * later one may stand a `{ \"$ref\": \"order.id\" }` where a value goes; the\n * substitution happens here because inside the transaction is the only\n * place the row the reference points at exists. Backward references only —\n * the REST layer refuses a forward one before the transaction opens, so a\n * body that cannot work never costs a rollback.\n */\n async batchWrite<M extends Record<string, unknown>>({\n operations\n }: BatchWriteProps<M>): Promise<(Record<string, unknown> | null)[]> {\n return this.inBatchTransaction(async (txDriver) => {\n\n const results: (Record<string, unknown> | null)[] = [];\n /** What each named operation wrote, for the `$ref`s after it. */\n const named = new Map<string, Record<string, unknown>>();\n\n for (let i = 0; i < operations.length; i++) {\n const operation = operations[i];\n try {\n const values = operation.values\n ? resolveBatchRefs(operation.values, named) as Partial<EntityValues<M>>\n : undefined;\n const rawId = operation.id !== undefined\n ? resolveBatchRefs(operation.id, named)\n : undefined;\n const id = rawId === undefined || rawId === null ? undefined : String(rawId);\n\n let row: Record<string, unknown> | null = null;\n switch (operation.op) {\n case \"create\":\n row = await txDriver.save<M>({\n path: operation.path,\n values: values ?? ({} as Partial<EntityValues<M>>),\n collection: operation.collection,\n status: \"new\"\n });\n break;\n case \"upsert\":\n row = await txDriver.save<M>({\n path: operation.path,\n values: values ?? ({} as Partial<EntityValues<M>>),\n collection: operation.collection,\n status: \"new\",\n upsert: true,\n onConflict: operation.onConflict\n });\n break;\n case \"update\": {\n // An id matching no row is a 404 rather than an\n // UPDATE that matches nothing and reports success —\n // the same rule `updateMany` and the single-row\n // route apply, on the read `save` makes anyway.\n row = await txDriver.save<M>({\n path: operation.path,\n id: id!,\n values: values ?? ({} as Partial<EntityValues<M>>),\n collection: operation.collection,\n status: \"existing\",\n precondition: { check: stored => assertBatchRowFound(stored, id) }\n });\n break;\n }\n case \"delete\":\n // `delete` reads the row itself and 404s on a miss.\n await txDriver.delete<M>({\n row: { id: id!, path: operation.path },\n collection: operation.collection\n });\n row = null;\n break;\n }\n\n if (operation.ref && row) named.set(operation.ref, row);\n results.push(row);\n } catch (error) {\n // Which operation, as the bulk methods say which row: a\n // batch mixes collections, so \"the batch failed\" does not\n // even say which table to go and look at.\n throw Object.assign(\n new Error(\n `Operation ${i} of ${operations.length} (${operation.op} on \"${operation.path}\") failed: `\n + `${(error as Error)?.message ?? error}`,\n { cause: error }\n ),\n {\n statusCode: (error as { statusCode?: number })?.statusCode,\n code: (error as { code?: string })?.code,\n name: (error as Error)?.name\n }\n );\n }\n }\n\n return results;\n });\n }\n\n async delete<M extends Record<string, unknown>>(props: DeleteProps<M>): Promise<void> {\n // A frame per write, as `save` has: see `write-depth.ts`.\n return inWriteFrame(props.row.path, \"delete\", frame => this.deleteInFrame(props, frame));\n }\n\n private async deleteInFrame<M extends Record<string, unknown>>({\n row,\n collection,\n hard,\n precondition\n }: DeleteProps<M>, frame: WriteFrame): Promise<void> {\n\n const targetPath = row.path;\n\n // Resolve from backend registry to restore callbacks lost during WebSocket serialization\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, targetPath);\n if (declaresAnyHook(callbacks, globalCallbacks, propertyCallbacks)) currentWriteScope()?.noteApplicationCode();\n\n // Through a many-to-many path the target row is not what is deleted —\n // see `unlinking` below — and an unlink is no entry in its history.\n const hop = isNestedPath(targetPath) ? resolveNestedPath(targetPath, this.registry) : undefined;\n const unlinking = hop !== undefined && isJunctionBackedRelation(hop.relation);\n // Before anything runs, as `save` does.\n if (resolvedCollection?.history && !unlinking) this.historyService?.assertRecordable(resolvedCollection.slug);\n\n // The row being deleted is read here, from the database, by this\n // driver — which on a user request is bound to that user's\n // transaction, so the read sees what their policies let them see. It\n // is what the callbacks judge and what history records. It used to\n // come in on the props, and over the WebSocket the props were the\n // client's frame: the caller wrote the audit record of their own\n // deletion, and a `beforeDelete` refusing on a column was bypassed by\n // sending `values: {}`. The in-process SDK sent `{}` on every delete.\n //\n // The same read the REST route makes, `withDeleted` included: a hard\n // delete of a row already in the trash is how the trash is emptied.\n //\n // Raw, not through `this.fetchOne`: that runs `afterRead`, which shapes\n // what a caller reads — and its view (a masked `email`, a computed\n // field) is not the row being deleted. It was what the delete hooks\n // judged and what history recorded as the row's final state.\n //\n // And the row as REST serves it — columns, dates as timestamps, a\n // foreign key as a key — which is what a save hands the same hooks and\n // history. The admin's view-model walk wrapped every date as\n // `{ __type: \"date\" }` and every relation in a ref carrying the related\n // row, and that is what the delete's history entry held: a revert to\n // it handed the envelope to Postgres as the column's value.\n //\n // A caller's `precondition` (the REST route's 404 and `If-Match`)\n // judges this same read, after the row is locked when it asks for that.\n if (precondition?.lock) await this.dataService.getPersistService().lockRow(targetPath, row.id);\n const stored = await this.dataService.fetchOneForRest<M>(\n targetPath,\n row.id,\n undefined,\n resolvedCollection?.databaseId,\n { withDeleted: hard ? true : undefined }\n );\n if (precondition) await precondition.check(await this.asRestServes(stored, targetPath));\n // Not found is answered before any callback runs: a callback handed a\n // row that is not there — or not there for this caller — would be\n // judging something it cannot see.\n if (!stored) {\n throw ApiError.notFound(`No row \"${row.id}\" in \"${targetPath}\" to delete.`);\n }\n // The callbacks' `row` is the row: its columns, nothing else. The address\n // travels beside it as `id`, so merging it in here only ever invented an\n // `id` field for tables that have no such column. A copy, so a callback\n // that edits it does not edit what history records.\n const targetRow: Record<string, unknown> = { ...stored };\n\n const contextForCallback = this.buildCallContext();\n\n // A `before*` callback is the application speaking, not the server\n // failing: a bare `throw` is the documented way to block a write, so it\n // answers 400 with the author's message rather than a masked 500.\n try {\n frame.stage = \"beforeDelete\";\n if (globalCallbacks?.beforeDelete || callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, targetPath);\n let preventDefault = false;\n // 1. Global callbacks first\n if (globalCallbacks?.beforeDelete) {\n const result = await globalCallbacks.beforeDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n if (result === false) {\n preventDefault = true;\n }\n }\n // 2. Collection callbacks second\n if (callbacks?.beforeDelete) {\n const result = await callbacks.beforeDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n if (result === false) {\n preventDefault = true;\n }\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.beforeDelete) {\n const result = await propertyCallbacks.beforeDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n if (result === false) {\n preventDefault = true;\n }\n }\n if (preventDefault) {\n // A veto, not a silent no-op. Returning early left the route\n // answering `204 No Content` — \"the row is gone\" — for a row\n // that is still there, so the panel dropped it from the list\n // and the next reload brought it back. Same code a throw\n // produces, so one branch handles both.\n throw callbackRefusal(\"beforeDelete\", targetPath);\n }\n }\n } catch (callbackError) {\n throw toCallbackError(callbackError, \"beforeDelete\", targetPath);\n }\n\n // A soft delete is a delete as far as everything above this line is\n // concerned — `beforeDelete` can still veto it, `afterDelete` still\n // fires, the realtime event below still says the row is gone. What\n // changes is only how the table records it: a timestamp in the declared\n // field instead of a `DELETE`. `hard` opts back into the real thing and\n // needs no extra permission, because it is the same verb.\n //\n // Through a many-to-many path the target row is not what is deleted:\n // it is shared, and the delete removes this parent's link to it (the\n // persistence layer's rule for a junction). Its collection's soft\n // delete says how *its* rows are removed, which this is not — stamped\n // here, it trashed the tag for every post and re-asserted the link.\n const softDelete = hard || unlinking\n ? undefined\n : resolveSoftDelete(resolvedCollection as CollectionConfig | undefined);\n if (softDelete) {\n await this.dataService.save(\n targetPath,\n { [softDelete.field]: new Date() } as Partial<EntityValues<M>>,\n row.id,\n resolvedCollection?.databaseId\n );\n } else {\n await this.dataService.delete(\n targetPath,\n row.id,\n resolvedCollection?.databaseId\n );\n }\n\n // Same contract as `afterSave`: inside the transaction, awaited, and a\n // throw undoes the delete rather than leaving the row gone and the\n // cleanup half-done. See the comment on the `afterSave` block.\n try {\n frame.stage = \"afterDelete\";\n if (globalCallbacks?.afterDelete || callbacks?.afterDelete || propertyCallbacks?.afterDelete) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, targetPath);\n // 1. Global callbacks first\n if (globalCallbacks?.afterDelete) {\n await globalCallbacks.afterDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n }\n // 2. Collection callbacks second\n if (callbacks?.afterDelete) {\n await callbacks.afterDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterDelete) {\n await propertyCallbacks.afterDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n }\n }\n } catch (callbackError) {\n throw toCallbackError(callbackError, \"afterDelete\", targetPath);\n }\n\n // Awaited, for the same reason the save's entry is: a delete is the one\n // change whose history nothing else can reconstruct, because the row it\n // describes is gone.\n // An unlink changed a parent's set, not the target row, so it is not\n // an entry in that row's history.\n if (this.historyService && resolvedCollection?.history && !unlinking) {\n await this.historyService.recordHistory({\n // Keyed like the save's entry: the slug, whatever the path.\n tableName: resolvedCollection.slug,\n id: row.id.toString(),\n action: \"delete\",\n values: stored,\n updatedBy: this.user?.uid\n });\n }\n\n // Notify real-time subscribers (deferred if inside a transaction)\n if (this._deferNotifications) {\n this._pendingNotifications.push({\n path: targetPath,\n id: row.id.toString(),\n row: null,\n databaseId: resolvedCollection?.databaseId\n });\n } else {\n await this.realtimeService.notifyUpdate(\n targetPath,\n row.id.toString(),\n null,\n resolvedCollection?.databaseId\n );\n }\n\n }\n\n /**\n * The child lists a save rewrote through the parent, as notifications —\n * when nothing else will announce them.\n *\n * `save(\"posts\", \"p-1\", { tags: [...] })` rewrites the junction, and\n * `{ comments: [...] }` on a `hasMany` re-points the children's foreign\n * keys: either way the contents of `posts/p-1/<relation>` changed, and the\n * row notification names the parent, whose paths land on `posts`. Under\n * change capture the junction's or the children's own triggers announce\n * it. Without capture these did not reach anyone: a live list of a post's\n * tags kept the old set. Addressed to every row of the list, which also\n * reaches the target collection's own lists — a `hasMany` changed those\n * rows' foreign keys.\n */\n private childListNotifications(\n collection: CollectionConfig | undefined,\n id: string,\n values: Record<string, unknown>\n ): PostgresBackendDriver[\"_pendingNotifications\"] {\n if (!collection) return [];\n const relations = resolveCollectionRelations(collection);\n const written = Object.keys(values).filter((key) => relations[key]?.cardinality === \"many\");\n if (written.length === 0 || this.realtimeService.isCdcActive()) return [];\n return written.map((key) => ({\n path: `${collection.slug}/${id}/${key}`,\n id: \"*\",\n row: { _rebase_invalidated: true },\n databaseId: collection.databaseId\n }));\n }\n\n async deleteAll(path: string): Promise<void> {\n await this.dataService.deleteAll(path);\n // Notify real-time subscribers of bulk change\n await this.realtimeService.notifyUpdate(path, \"*\", null);\n }\n\n async updateRelationPivot({ path, targetId, pivot }: UpdateRelationPivotProps): Promise<void> {\n await this.dataService.updateRelationPivot(path, targetId, pivot);\n // The junction row itself is what changed. Under change capture the\n // junction table's trigger reaches the subscribers of\n // `<parent>/<id>/<relation>` — the same route a link or an unlink\n // takes — and a notify here would only duplicate that frame. Without\n // capture nothing else will: a live list of a post's tags never saw\n // the edited link columns.\n if (this.realtimeService.isCdcActive()) return;\n const notification = { path, id: String(targetId), row: { _rebase_invalidated: true }, databaseId: undefined };\n if (this._deferNotifications) {\n this._pendingNotifications.push(notification);\n } else {\n await this.realtimeService.notifyUpdate(notification.path, notification.id, notification.row);\n }\n }\n\n async checkUniqueField(\n path: string,\n name: string,\n value: unknown,\n id?: string,\n collection?: CollectionConfig\n ): Promise<boolean> {\n return this.dataService.checkUniqueField(\n path,\n name,\n value,\n id,\n collection?.databaseId\n );\n }\n\n async count<M extends Record<string, unknown>>({\n path,\n collection,\n filter,\n logical,\n searchString,\n vectorSearch,\n withDeleted\n }: FetchCollectionProps<M>): Promise<number> {\n return this.dataService.count(\n path,\n {\n filter,\n // Counted as well as filtered, or `meta.total` describes a\n // different set of rows from the `data` beside it. The same\n // held for a `vectorSearch` carrying a `threshold`: it narrows\n // the fetch, so it has to narrow the count. And the same for\n // soft delete: a listing that hides four rows and a total that\n // counts them is a page saying \"1 of 5\".\n logical,\n searchString,\n vectorSearch,\n withDeleted\n }\n );\n }\n\n /** The answer to {@link appDatabaseName}, once it has been given. */\n private appDatabase?: Promise<string>;\n\n /**\n * The database this driver's own connection is on: the app's, which every\n * data request and the CMS read and write.\n *\n * Asked of the connection rather than read off the admin connection\n * string, which can name another database — the server's `postgres`, or\n * the main database while this process runs on a branch. The SQL console\n * preselects this name, a statement sent to it runs on this connection,\n * and branching copies it by default.\n */\n async appDatabaseName(): Promise<string> {\n this.appDatabase ??= this.db.execute(drizzleSql.raw(\"SELECT current_database() AS name\")).then((result) => {\n const name = result.rows?.[0]?.name;\n if (typeof name !== \"string\") {\n throw new Error(\"The database did not say which database this connection is on.\");\n }\n return name;\n });\n try {\n return await this.appDatabase;\n } catch (error: unknown) {\n // Asked again next time, rather than failing every later call.\n this.appDatabase = undefined;\n throw error;\n }\n }\n\n private async getTargetDb(databaseName?: string): Promise<DrizzleClient> {\n if (!databaseName || databaseName === await this.appDatabaseName()) {\n return this.db;\n }\n if (!this.poolManager) {\n throw new Error(\n \"Cross-database execution requires adminConnectionString to be configured in the backend.\"\n );\n }\n return this.poolManager.getDrizzle(databaseName);\n }\n\n /**\n * Build one statement, binding `$n` placeholders as real parameters.\n *\n * Shared by the role-switched path (inside a transaction) and the\n * unswitched one. They held byte-identical copies of this loop, which is\n * exactly the shape where a fix lands in one copy and not the other.\n */\n private buildStatement(sqlText: string, params?: unknown[]) {\n if (!params || params.length === 0) return drizzleSql.raw(sqlText);\n const parts = sqlText.split(/\\$(\\d+)/);\n const chunks: ReturnType<typeof drizzleSql.raw | typeof drizzleSql.param>[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n if (parts[i].length > 0) chunks.push(drizzleSql.raw(parts[i]));\n } else {\n chunks.push(drizzleSql.param(params[Number(parts[i]) - 1]));\n }\n }\n return drizzleSql.join(chunks, drizzleSql.raw(\"\"));\n }\n\n async executeSql(sqlText: string, options?: {\n database?: string,\n role?: string,\n params?: unknown[],\n isolateSession?: boolean,\n maxResultBytes?: number\n }): Promise<Record<string, unknown>[]> {\n if (options?.isolateSession) {\n return this.onIsolatedSession(\n await this.getTargetDb(options.database),\n options.role,\n (db, role) => this.executeSqlOn(db, sqlText, { ...options, role }),\n options.maxResultBytes\n );\n }\n const rows = !options?.database && !options?.role\n ? await this.dataService.executeSql(sqlText, options?.params)\n : await this.executeSqlOn(await this.getTargetDb(options?.database), sqlText, options);\n return options?.maxResultBytes === undefined ? rows : refuseOversizedRows(rows, options.maxResultBytes);\n }\n\n /**\n * Run a script a person wrote — the Studio console — and describe its\n * result. See `SQLAdmin.runSqlScript` and `services/sql-script.ts`.\n */\n async runSqlScript(sqlText: string, options?: {\n database?: string,\n role?: string,\n signal?: AbortSignal\n }): Promise<SqlScriptResult> {\n const targetDb = await this.getTargetDb(options?.database);\n const pool = \"$client\" in targetDb ? targetDb.$client : undefined;\n if (!(pool instanceof Pool)) {\n // One session in process: nothing on the wire to read a column's\n // origin from, so nothing in the result is said to have one. And\n // no backend of its own to cancel: `signal` is not read.\n return sqlScriptResultFromRows(await this.executeSql(sqlText, {\n database: options?.database,\n role: options?.role,\n isolateSession: true\n }));\n }\n const role = options?.role;\n return runSqlScriptOnPool(pool, sqlText, {\n assumeRole: role ? (connection) => this.assumeSessionRole(connection, role) : undefined,\n signal: options?.signal\n });\n }\n\n /**\n * Read the whole result of one query a person wrote, a batch at a time —\n * see `SQLAdmin.openSqlCursor` and `openSqlCursorOnPool`. The database's\n * refusal of the query (a write, a second statement, a syntax error, a\n * role it may not take) is the caller's to read, as on the socket.\n */\n async openSqlCursor(sqlText: string, options?: {\n database?: string,\n role?: string,\n batchSize?: number\n }): Promise<SqlCursor> {\n const targetDb = await this.getTargetDb(options?.database);\n const pool = \"$client\" in targetDb ? targetDb.$client : undefined;\n if (!(pool instanceof Pool)) {\n // One session in process: a cursor held open across a download\n // would hold every other query of the app behind it.\n throw new ApiError(501, \"SQL_EXPORT_UNSUPPORTED\",\n \"Downloading a whole result reads it through a cursor on a connection of its own, and the \" +\n \"in-process development database has one connection. Point DATABASE_URL at a Postgres of your \" +\n \"own (`rebase dev --docker` starts one), or add a LIMIT and export the rows on screen.\");\n }\n const role = options?.role;\n try {\n return await openSqlCursorOnPool(pool, sqlText, {\n assumeRole: role ? (connection) => this.assumeSessionRole(connection, role) : undefined,\n batchRows: options?.batchSize\n });\n } catch (error: unknown) {\n if (error instanceof DatabaseError || error instanceof RoleSwitchUnavailableError) {\n throw new ApiError(400, \"SQL_ERROR\", error.message);\n }\n throw error;\n }\n }\n\n /**\n * `SET ROLE` for the rest of a session of a script's own — not `SET LOCAL`\n * inside a transaction, which a `COMMIT` in the script ends, and every\n * statement after it ran as the connection owner. The session is reset\n * after the script, so the role does not outlive it.\n *\n * The same refusals as {@link executeSqlOn}: a role the session already\n * has is no switch; `DISABLE_DB_ROLE_SWITCHING` runs as the owner on\n * purpose; and a connection that may not `SET ROLE` refuses, never falls\n * back to the owner.\n */\n private async assumeSessionRole(connection: PoolClient, role: string): Promise<void> {\n const current = await connection.query<{ role: string }>(\"SELECT current_user AS role\");\n if (current.rows[0]?.role === role) return;\n if (isRoleSwitchingOptedOut()) {\n logger.debug(\n `[PostgresBackendDriver] DISABLE_DB_ROLE_SWITCHING=true — running as the ` +\n `connection owner rather than \"${role}\".`\n );\n return;\n }\n if (this._roleSwitchingUnavailable) throw new RoleSwitchUnavailableError(role);\n try {\n await connection.query(`SET ROLE \"${role.replace(/\"/g, \"\\\"\\\"\")}\"`);\n } catch (roleError: unknown) {\n if (isRoleSwitchingPermissionError(roleError)) {\n this._roleSwitchingUnavailable = true;\n throw new RoleSwitchUnavailableError(role, roleError);\n }\n throw roleError;\n }\n }\n\n /**\n * Run `fn` on a session of its own, and put that session back the way it\n * was handed out before anything else can use it.\n *\n * For SQL a person wrote — the Studio editor. It may `SET ROLE`,\n * `SET SESSION AUTHORIZATION` or `set_config(…, false)`, which outlive the\n * statement (and a transaction's commit) and stay on the connection. On a\n * pooled one they were inherited by whatever checked it out next: an auth\n * lookup, the job store or history running as `rebase_user`, on one\n * connection in N, until a restart. `SET SESSION AUTHORIZATION DEFAULT`\n * clears the role as well, which `RESET ALL` does not.\n *\n * On a pool, `role` is assumed for the whole session, and a transaction\n * the SQL leaves open is rolled back and the run refused — see\n * `onOwnSession`. `fn` is then handed no role: there is nothing left to\n * switch. A handle that is already one session (PGlite in process, a\n * single client) is reset in place, and `fn` switches the role itself.\n *\n * With `maxResultBytes`, a result past it is refused — see\n * `readWithinBudget`: on a pool the read stops there, in process the rows\n * are measured once read.\n */\n private async onIsolatedSession(\n targetDb: DrizzleClient,\n role: string | undefined,\n fn: (db: DrizzleClient, role: string | undefined) => Promise<Record<string, unknown>[]>,\n maxResultBytes?: number\n ): Promise<Record<string, unknown>[]> {\n const pool = \"$client\" in targetDb ? targetDb.$client : undefined;\n if (!(pool instanceof Pool)) {\n try {\n // One session in process: the rows are read whole before\n // anything could stop them, and refused afterwards all the same.\n const rows = await fn(targetDb, role);\n return maxResultBytes === undefined ? rows : refuseOversizedRows(rows, maxResultBytes);\n } finally {\n for (const statement of RESET_SESSION_STATEMENTS) {\n await targetDb.execute(drizzleSql.raw(statement)).catch((error: unknown) =>\n logger.error(\"[PostgresBackendDriver] Could not reset the session after caller SQL\", { error }));\n }\n }\n }\n return onOwnSession(\n pool,\n { assumeRole: role ? (connection) => this.assumeSessionRole(connection, role) : undefined },\n (connection) => maxResultBytes === undefined\n ? fn(drizzle(connection), undefined)\n : readWithinBudget(connection, maxResultBytes, () => fn(drizzle(connection), undefined))\n );\n }\n\n private async executeSqlOn(targetDb: DrizzleClient, sqlText: string, options?: {\n database?: string,\n role?: string,\n params?: unknown[]\n }): Promise<Record<string, unknown>[]> {\n try {\n // Does this actually need a role switch?\n //\n // Asking for the role the session already runs as is a no-op, not a\n // downgrade — the statement really does execute as the requested\n // role — so it stays allowed even where switching is unavailable.\n // That is the ordinary Studio path: the role picker defaults to\n // `current_user`.\n let needsRoleSwitch = false;\n if (options?.role) {\n try {\n const currentRoleResult = await targetDb.execute(drizzleSql.raw(\"SELECT current_user AS role\"));\n const currentRole = (currentRoleResult.rows?.[0] as Record<string, unknown>)?.role as string | undefined;\n needsRoleSwitch = !!currentRole && currentRole !== options.role;\n } catch {\n // Current role unknown. Assume a switch is needed rather\n // than assume the session already is the requested role:\n // attempting and refusing beats guessing in our own favour.\n needsRoleSwitch = true;\n }\n }\n\n if (needsRoleSwitch && options?.role) {\n if (isRoleSwitchingOptedOut()) {\n // The one sanctioned way to run this unswitched, and a\n // decision somebody made rather than a failure: the env var\n // is documented (README, docs/getting-started/configuration)\n // as \"run SQL Editor queries as the connection owner\", for\n // deployments whose application roles have no database role\n // behind them. `effectiveSqlRole` reads the same switch, so\n // the audit log records the role that actually applied.\n logger.debug(\n `[PostgresBackendDriver] DISABLE_DB_ROLE_SWITCHING=true — running as the ` +\n `connection owner rather than \"${options.role}\".`\n );\n } else if (this._roleSwitchingUnavailable) {\n // Already learned this connection cannot SET ROLE; refuse\n // without spending the round trip to be told again.\n throw new RoleSwitchUnavailableError(options.role);\n } else {\n const safeRole = options.role.replace(/\"/g, \"\\\"\\\"\");\n try {\n return await targetDb.transaction(async (tx) => {\n await tx.execute(drizzleSql.raw(`SET LOCAL ROLE \"${safeRole}\"`));\n return rowsOfResult(await tx.execute(this.buildStatement(sqlText, options?.params)));\n });\n } catch (roleError: unknown) {\n if (isRoleSwitchingPermissionError(roleError)) {\n // SECURITY: do NOT fall through and run this as the\n // owner.\n //\n // The caller asked for a *constrained* execution.\n // Owner rows are not a degraded answer to that\n // question, they are a confident wrong one: the only\n // reason to pass a role is to see what the database\n // looks like under RLS, and owner output makes a\n // protected table read as exposed. This used to warn\n // and continue — and latch, so a single failure\n // silently unscoped every later call in the process.\n //\n // `applyAuthContext` (the user request path) and\n // `scopeDataDriver` both fail closed. This is the\n // same question, so it gets the same answer.\n this._roleSwitchingUnavailable = true;\n throw new RoleSwitchUnavailableError(options.role, roleError);\n }\n throw roleError;\n }\n }\n }\n\n return rowsOfResult(await targetDb.execute(this.buildStatement(sqlText, options?.params)));\n } catch (error: unknown) {\n if (error instanceof RoleSwitchUnavailableError) throw error;\n const msg = error instanceof Error ? error.message : String(error);\n // Provide a user-friendly message for connection/auth errors\n if (msg.includes(\"pg_hba.conf\") || msg.includes(\"no encryption\") || msg.includes(\"connection refused\")) {\n const dbName = options?.database || \"unknown\";\n throw new Error(`Cannot connect to database \"${dbName}\": the server rejected the connection. This database may require SSL or is not accessible from this host.`);\n }\n throw error;\n }\n }\n\n async fetchAvailableDatabases(): Promise<string[]> {\n // Exclude template databases, Cloud SQL internal databases, and the default 'postgres' system db\n const result = await this.executeSql(\n `SELECT datname FROM pg_database \n WHERE datistemplate = false \n AND datname NOT IN ('postgres', 'cloudsqladmin', '_cloudsqladmin')\n ORDER BY datname;`\n );\n const databases = result.map((r: Record<string, unknown>) => r.datname as string);\n // Ensure the current connected database is always first in the list\n const currentDb = await this.appDatabaseName();\n if (!databases.includes(currentDb)) {\n databases.unshift(currentDb);\n } else {\n // Move it to the front\n const idx = databases.indexOf(currentDb);\n if (idx > 0) {\n databases.splice(idx, 1);\n databases.unshift(currentDb);\n }\n }\n return databases;\n }\n\n async fetchAvailableRoles(): Promise<string[]> {\n const result = await this.executeSql(\n \"SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;\"\n );\n return result.map((r: Record<string, unknown>) => r.rolname as string);\n }\n\n /**\n * Application-level roles actually in use in this project.\n *\n * Distinct from {@link fetchAvailableRoles}, which returns native\n * PostgreSQL roles from `pg_roles` (`postgres`, `rebase_user`, …). Those\n * are the roles the SQL editor can `SET ROLE` to. *These* are the strings\n * held in the users table's `roles` column, injected per-transaction as\n * `rebase.roles()` and matched by `SecurityRule.roles`. Feeding the pg roles\n * into a `SecurityRule.roles` field produces a condition no user can ever\n * satisfy, so the two must not be conflated.\n *\n * Roles have no registry table — they were migrated out of\n * `rebase.user_roles` onto an inline `roles TEXT[]` column — so the live\n * set is derived from what is assigned. A role that is declared in a policy\n * but held by nobody yet cannot be discovered here; callers that need it\n * should union in the roles they already know about.\n */\n async fetchApplicationRoles(): Promise<string[]> {\n // The users table lives in `rebase` for a default (public) setup, but\n // follows the configured schema otherwise — locate it rather than\n // assuming. The `roles` ARRAY column is what makes it the auth table.\n const located = await this.executeSql(`\n SELECT table_schema, table_name\n FROM information_schema.columns\n WHERE column_name = 'roles'\n AND data_type = 'ARRAY'\n AND table_name = 'users'\n AND table_schema NOT IN ('information_schema', 'pg_catalog')\n ORDER BY (table_schema = 'rebase') DESC, table_schema\n LIMIT 1;\n `);\n if (located.length === 0) return [];\n\n const schema = located[0].table_schema as string;\n const table = located[0].table_name as string;\n // Identifiers come from information_schema, not user input, but they\n // are still interpolated — quote them so odd-but-legal names survive.\n const qualified = `\"${schema.replace(/\"/g, \"\\\"\\\"\")}\".\"${table.replace(/\"/g, \"\\\"\\\"\")}\"`;\n\n const rows = await this.executeSql(`\n SELECT DISTINCT unnest(roles) AS role\n FROM ${qualified}\n WHERE roles IS NOT NULL\n ORDER BY role;\n `);\n return rows\n .map((r) => r.role as string)\n .filter((r): r is string => typeof r === \"string\" && r.length > 0);\n }\n\n async fetchCurrentDatabase(): Promise<string | undefined> {\n return this.appDatabaseName();\n }\n\n /**\n * Fetch public tables that are not yet mapped to a collection.\n * Excludes internal tables (_rebase_*, _auth_*, auth tables, etc.)\n * and junction/connection tables used for many-to-many relations.\n */\n async fetchUnmappedTables(mappedPaths?: string[]): Promise<string[]> {\n const result = await this.executeSql(`\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_type = 'BASE TABLE'\n ORDER BY table_name;\n `);\n\n const allTables = result\n .map((r: Record<string, unknown>) => r.table_name as string)\n .filter((name: string) => classifyTable(name, \"public\") !== \"rebase-internal\");\n\n // Detect junction tables: tables where every column is part of a foreign key.\n // These are typically many-to-many connection tables and shouldn't be suggested.\n let junctionTables = new Set<string>();\n try {\n junctionTables = await detectJunctionTables(this.executeSql.bind(this));\n } catch (e) {\n logger.warn(\"Could not detect junction tables\", { error: e });\n }\n\n const filteredTables = allTables.filter(name => !junctionTables.has(name));\n\n if (!mappedPaths || mappedPaths.length === 0) return filteredTables;\n\n const mappedSet = new Set(mappedPaths.map(p => p.toLowerCase()));\n return filteredTables.filter((name: string) => !mappedSet.has(name.toLowerCase()));\n }\n\n /**\n * Fetch metadata for a given table from information_schema (columns, policies, constraints).\n */\n async fetchTableMetadata(tableName: string): Promise<TableMetadata> {\n // Sanitize table name as defense-in-depth (parameterized below)\n const safeName = tableName.replace(/[^a-zA-Z0-9_]/g, \"\");\n\n // 1. Fetch Columns\n const result = await this.db.execute(drizzleSql`\n SELECT column_name, data_type, udt_name, is_nullable, column_default, character_maximum_length\n FROM information_schema.columns\n WHERE table_schema = 'public'\n AND table_name = ${safeName}\n ORDER BY ordinal_position\n `);\n const columns = result.rows as Record<string, unknown>[];\n\n // The labels of every enum a USER-DEFINED column uses, in one query —\n // it was one per column, in turn, each a round trip. A USER-DEFINED\n // type that is not an enum (a composite, a PostGIS type) has none.\n const enumColumns = columns.filter((c) => c.data_type === \"USER-DEFINED\");\n if (enumColumns.length > 0) {\n const typeNames = [...new Set(enumColumns.map(col => String(col.udt_name)))];\n let labelsByType = new Map<string, unknown[]>();\n try {\n const enumResult = await this.db.execute(drizzleSql`\n SELECT t.typname AS type_name, array_agg(e.enumlabel::text ORDER BY e.enumsortorder) AS labels\n FROM pg_type t\n JOIN pg_enum e ON t.oid = e.enumtypid\n WHERE t.typname IN (${drizzleSql.join(typeNames.map(name => drizzleSql`${name}`), drizzleSql`, `)})\n GROUP BY t.typname\n `);\n labelsByType = new Map((enumResult.rows as Record<string, unknown>[])\n .map(row => [String(row.type_name), Array.isArray(row.labels) ? row.labels : []]));\n } catch {\n // As before: a column whose labels cannot be read has none.\n }\n for (const col of enumColumns) {\n col.enum_values = labelsByType.get(String(col.udt_name)) ?? [];\n }\n }\n // Rows from hand-written SQL. The element type is the one claim nothing\n // at this layer can check — no column list is available to check it\n // against — but the shape is `sqlRows`' job, not an assertion's.\n const typedColumns = sqlRows<TableColumnInfo>(columns);\n\n // 2. Fetch Foreign Keys\n const fkResult = await this.db.execute(drizzleSql`\n SELECT\n kcu.column_name as column_name,\n ccu.table_name AS foreign_table_name,\n ccu.column_name AS foreign_column_name\n FROM \n information_schema.table_constraints AS tc \n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = ${safeName};\n `);\n // SAFETY: Raw SQL result rows match TableForeignKeyInfo shape from the SELECT aliases\n const foreignKeys = fkResult.rows as TableForeignKeyInfo[];\n\n // 3. Fetch Junction Tables (Many-to-Many)\n // A simple junction table is one that has foreign keys to our table and other tables\n const junctionsResult = await this.db.execute(drizzleSql`\n SELECT \n tc1.table_name as junction_table_name,\n kcu1.column_name as source_column_name,\n ccu2.table_name as target_table_name,\n kcu2.column_name as target_column_name\n FROM information_schema.table_constraints tc1\n JOIN information_schema.key_column_usage kcu1 ON tc1.constraint_name = kcu1.constraint_name\n JOIN information_schema.constraint_column_usage ccu1 ON ccu1.constraint_name = tc1.constraint_name\n JOIN information_schema.table_constraints tc2 ON tc1.table_name = tc2.table_name AND tc2.constraint_type = 'FOREIGN KEY'\n JOIN information_schema.key_column_usage kcu2 ON tc2.constraint_name = kcu2.constraint_name\n JOIN information_schema.constraint_column_usage ccu2 ON ccu2.constraint_name = tc2.constraint_name\n WHERE tc1.constraint_type = 'FOREIGN KEY' \n AND ccu1.table_name = ${safeName}\n AND ccu2.table_name != ${safeName};\n `);\n // SAFETY: Raw SQL result rows match TableJunctionInfo shape from the SELECT aliases\n const junctions = junctionsResult.rows as TableJunctionInfo[];\n\n // 4. Fetch RLS Policies\n //\n // From `pg_policies`, not `pg_policy`: the importer writes each row\n // into a security rule, and needs the command named (`pg_policy.polcmd`\n // is `r`, `a`, `*`), `TO public` spelled `public` (the raw oid list\n // casts it to `-`) and whether the policy is restrictive, which a\n // rule read back as permissive would OR with every grant beside it.\n const policiesResult = await this.db.execute(drizzleSql`\n SELECT\n policyname AS policy_name,\n permissive,\n cmd,\n roles::text[] AS roles,\n qual,\n with_check\n FROM pg_policies\n WHERE schemaname = 'public' AND tablename = ${safeName};\n `);\n // SAFETY: Raw SQL result rows match TablePolicyInfo shape from the SELECT aliases\n const policies = policiesResult.rows as TablePolicyInfo[];\n\n return {\n columns: typedColumns,\n foreignKeys,\n junctions,\n policies\n };\n }\n\n private generateSubscriptionId(): string {\n return `sub_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n }\n\n /**\n * Create a new delegate instance with authenticated context.\n * Starts a transaction and sets the current_user_id and current_user_roles\n * configuration parameters for PostgreSQL Row Level Security.\n */\n async withAuth(user: User): Promise<DataDriver> {\n return new AuthenticatedPostgresBackendDriver(this, user);\n }\n}\n\nexport class AuthenticatedPostgresBackendDriver implements DataDriver {\n key = \"postgres\";\n initialised = true;\n /** Its delegate's, run inside this caller's transaction. */\n readonly checksRowPreconditions = true;\n\n public user: User;\n public data: RebaseSdkData;\n\n constructor(\n public delegate: PostgresBackendDriver,\n user: User\n ) {\n this.user = user;\n this.data = buildSdkData(this, {\n resolveAccessor: createAccessorSlugResolver(() => delegate.registry?.getCollections())\n });\n\n // Delegate admin ops to the base driver (no RLS wrapping for admin)\n this.admin = delegate.admin;\n }\n\n /**\n * Typed admin capabilities — delegates to the base driver.\n */\n admin: DatabaseAdmin;\n\n get restFetchService(): RestFetchService {\n return {\n // The base driver's restFetchService already applies the afterRead\n // pipeline, so we only wrap it in the authenticated transaction here.\n fetchCollectionForRest: async (collectionPath, options, include) => {\n return this.withTransaction(async (delegate) => {\n return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);\n }, { accessMode: \"read only\" });\n },\n fetchOneForRest: async (collectionPath, id, include, databaseId, options) => {\n return this.withTransaction(async (delegate) => {\n return delegate.restFetchService.fetchOneForRest(collectionPath, id, include, databaseId, options);\n }, { accessMode: \"read only\" });\n },\n // In the same read-only transaction as the two above, which is what\n // sets the RLS GUCs and drops to the restricted role. That is not a\n // detail to leave to the base implementation: an aggregate is an\n // efficient way to learn about rows the caller cannot select, and\n // `count(*)` over a table whose policies return nothing has to be\n // zero rather than the true row count.\n aggregate: async (collectionPath, options) => {\n return this.withTransaction(async (delegate) => {\n return delegate.restFetchService.aggregate!(collectionPath, options);\n }, { accessMode: \"read only\" });\n },\n // Not in a transaction, unlike its neighbours: it reads no rows. It\n // derives a cursor from a row the caller has already been served,\n // so there is nothing here for RLS to scope.\n cursorFor: (collectionPath, row, orderBy) =>\n this.delegate.restFetchService.cursorFor?.(collectionPath, row, orderBy)\n };\n }\n\n /**\n * Several reads in one read-only transaction — see\n * {@link DataDriver.readTogether}. The same transaction every read on this\n * driver opens for itself, with the same identity, role and field viewer;\n * `work` is handed the transaction's driver, whose reads run on it directly\n * instead of each opening another.\n *\n * `REPEATABLE READ`, because the point of reading together is reading one\n * snapshot: under the default `READ COMMITTED` each statement takes its\n * own, and a count could still describe a different set from the page\n * beside it. A read-only transaction cannot fail serialization at this\n * level, so it costs nothing.\n */\n async readTogether<T>(work: (driver: DataDriver) => Promise<T>): Promise<T> {\n return this.withTransaction((delegate) => work(delegate), {\n accessMode: \"read only\",\n isolationLevel: \"repeatable read\"\n });\n }\n\n private async withTransaction<T>(\n operation: (delegate: PostgresBackendDriver) => Promise<T>,\n options?: {\n accessMode?: \"read only\" | \"read write\";\n isolationLevel?: \"read uncommitted\" | \"read committed\" | \"repeatable read\" | \"serializable\"\n }\n ): Promise<T> {\n const pendingNotifications: PostgresBackendDriver[\"_pendingNotifications\"] = [];\n\n // What a write's callbacks hand off — a job, a history entry, a\n // webhook — commits with the write or not at all, and is found through\n // this scope by code that is not handed the transaction. Writes only:\n // a read has nothing to roll back, and inside one the scope is\n // cleared, so a read nested in a write's callback is not taken for it.\n // See `write-transaction-scope.ts`.\n const writeScope = options?.accessMode === \"read only\" ? undefined : new WriteTransactionScope();\n // What a hook deferred to the end of this write (`afterSaveError`) is\n // handed: this caller, on this driver, where every call is a\n // transaction of its own — the write's is over by then.\n const client = this.delegate.client;\n writeScope?.setSettledContext({\n user: this.user,\n driver: this,\n data: this.data,\n get client() {\n return requireCallbackClient(client);\n },\n storageSource: client?.storage as StorageSource\n });\n\n // The same identity the transaction is about to hand Postgres, made\n // available to the row walk so per-field `access.read` is applied to\n // whatever this read serves. Established here rather than threaded\n // through `DataService` → `FetchService` → the pipeline for the reason\n // set out in `field-viewer.ts`: those layers carry no user, and every\n // exit from the pipeline needs it. `run` and not `enterWith`, so a\n // `dataAsAdmin` read nested inside a user request restores the user's\n // viewer when it returns.\n const result = await withFieldViewer({ roles: this.user?.roles ?? [] }, async () =>\n await runInWriteScope(writeScope, () => this.delegate.db.transaction(async (tx) => {\n if (!this.user?.uid) {\n logger.warn(\"[DataDriver] User ID (uid) is missing for authenticated delegate. Using 'anonymous'. User object\", { detail: this.user });\n }\n if (!this.user?.roles) {\n logger.warn(\"[DataDriver] User roles are missing for authenticated delegate. Using empty array. User object\", { detail: this.user });\n }\n\n // Set the RLS GUCs and downgrade to the restricted user role so RLS\n // binds every statement in this transaction — reads AND writes.\n // This is user context: the collection's securityRules are the\n // authorization model. The BASE driver never reaches here, so it\n // stays on the owner connection and bypasses RLS — but note that\n // `rebase.dataAsAdmin` is NOT the base driver: `init.ts` scopes it\n // with `withAuth(SERVICE_IDENTITY)`, so it arrives here like any\n // other user, with uid 'service' and the admin role, and its\n // statements are RLS-evaluated. The comment used to list it as a\n // bypass and five docblocks followed. The GUCs are transaction-local and\n // remain readable after the role switch, so `rebase.uid()` /\n // `rebase.roles()` in policies still resolve.\n //\n // Fails closed: if the switch cannot be performed, the transaction\n // aborts rather than falling back to an RLS-bypassing connection.\n // `isAnonymous` rides along so a policy can tell a GUEST from an\n // account. Anonymous sign-in mints a real user row and a real uid,\n // so without it the two are the same principal inside the database\n // and every rule meaning \"signed in\" also means \"anybody who called\n // POST /auth/anonymous\". See `rebase.is_anonymous()`. The\n // token's custom claims reach the database as `rebase.jwt()`,\n // which is what a tenancy policy reads. The same principal a\n // listener on this driver subscribes as — see `authContextOf`.\n await applyAuthContext(tx, authContextOf(this.user), this.delegate.rlsUserRole);\n\n // Bound to `tx`, so a `beforeQuery` on this request sees the\n // caller and reads through the same RLS-scoped transaction the\n // rows come from — the correction `callContextWithin` was\n // written for, applied to the hook as well as to `afterRead`.\n // Built at most once per request, and only for a collection\n // that declares a hook.\n let hookContext: RebaseCallContext | undefined;\n const txEntityService = new DataService(\n tx, this.delegate.registry,\n () => {\n // Asked for by a `beforeQuery` about to run: application code.\n writeScope?.noteApplicationCode();\n return (hookContext ??= this.delegate.callContextWithin(tx, this.user));\n }\n );\n const txDelegate = new PostgresBackendDriver(tx, this.delegate.realtimeService, this.delegate.registry, this.user, this.delegate.poolManager, this.delegate.historyService);\n\n txDelegate.dataService = txEntityService;\n txDelegate._deferNotifications = true;\n txDelegate._pendingNotifications = pendingNotifications;\n txDelegate.client = this.delegate.client;\n\n writeScope?.bind(tx, (sqlText, params) => txEntityService.executeSql(sqlText, params));\n let out: T;\n try {\n out = await operation(txDelegate);\n } catch (error) {\n // Closed before the rethrow, so before the ROLLBACK is\n // queued: a statement accepted after it would run on this\n // connection in autocommit, and commit.\n writeScope?.close();\n throw error;\n }\n // Last, inside the transaction: whatever the callbacks started\n // on it finishes before the commit rather than after it.\n await writeScope?.settle();\n // A failure a callback caught has still aborted the\n // transaction, and its COMMIT would be a ROLLBACK that\n // reports success. Refused here, so the caller is told.\n await writeScope?.assertCommittable();\n return out;\n }, options)).finally(() => writeScope?.close())\n ).catch(async (error: unknown) => {\n await writeScope?.settled();\n throw error;\n });\n\n writeScope?.committed();\n await writeScope?.settled();\n\n for (const notification of pendingNotifications) {\n try {\n await this.delegate.realtimeService.notifyUpdate(\n notification.path,\n notification.id,\n notification.row,\n notification.databaseId\n );\n } catch (e) {\n logger.error(\"[DataDriver] Error flushing deferred notification\", { error: e });\n }\n }\n\n return result;\n }\n\n async fetchCollection<M extends Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<Record<string, unknown>[]> {\n return this.withTransaction((delegate) => delegate.fetchCollection(props), { accessMode: \"read only\" });\n }\n\n /**\n * The delegate is the base driver, on the owner connection. The user's\n * identity goes in with the subscription, so the first read runs as them\n * like every refetch after it. It used to be stamped onto the last\n * registered subscription once the delegate had returned — by which time\n * the first read had been issued unscoped, and a listener was handed rows\n * its policies deny.\n */\n listenCollection<M extends Record<string, unknown>>(props: ListenCollectionProps<M>): () => void {\n return this.delegate.listenCollection(props, authContextOf(this.user));\n }\n\n async fetchOne<M extends Record<string, unknown>>(props: FetchOneProps<M>): Promise<Record<string, unknown> | undefined> {\n return this.withTransaction((delegate) => delegate.fetchOne(props), { accessMode: \"read only\" });\n }\n\n listenOne<M extends Record<string, unknown>>(props: ListenOneProps<M>): () => void {\n return this.delegate.listenOne(props, authContextOf(this.user));\n }\n\n async save<M extends Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>> {\n return this.withTransaction((delegate) => delegate.save(props));\n }\n\n /**\n * One transaction for the whole batch, rather than one per row.\n *\n * This is the point of the method: `save` opens a transaction per call, so\n * importing 10k rows through it means 10k transactions (and, over HTTP, 10k\n * round trips). Here the RLS context is established once and every row lands\n * or none does. Realtime notifications are already deferred to commit by\n * `withTransaction`, so a batch does not flood subscribers mid-flight.\n */\n async saveMany<M extends Record<string, unknown>>(props: SaveManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.withTransaction((delegate) => delegate.saveMany(props));\n }\n\n /**\n * Present for the same reason `saveMany` is, and absent until now.\n *\n * Every request that reaches the REST layer is served by *this* class — the\n * base driver never sees one — and the routes ask `if (!driver.updateMany)`\n * before doing anything. So `PATCH /api/data/<c>/bulk` and\n * `POST /api/data/<c>/bulk/delete` answered `BULK_UNSUPPORTED` on Postgres,\n * the one backend that implements them, for every authenticated caller.\n * The methods existed one class down and nothing forwarded to them.\n */\n async updateMany<M extends Record<string, unknown>>(props: UpdateManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.withTransaction((delegate) => delegate.updateMany(props));\n }\n\n async deleteMany<M extends Record<string, unknown>>(props: DeleteManyProps<M>): Promise<void> {\n return this.withTransaction((delegate) => delegate.deleteMany(props));\n }\n\n /**\n * One transaction, one RLS context, every collection the batch touches.\n *\n * The whole point is that it runs as the caller: a batch that dropped to\n * the base driver would write across collections with row-level security\n * switched off, which is the opposite of what a cross-collection write\n * needs.\n */\n async batchWrite<M extends Record<string, unknown>>(\n props: BatchWriteProps<M>\n ): Promise<(Record<string, unknown> | null)[]> {\n return this.withTransaction((delegate) => delegate.batchWrite(props));\n }\n\n async delete<M extends Record<string, unknown>>(props: DeleteProps<M>): Promise<void> {\n return this.withTransaction((delegate) => delegate.delete(props));\n }\n\n async deleteAll(path: string): Promise<void> {\n return this.withTransaction((delegate) => delegate.deleteAll(path));\n }\n\n async updateRelationPivot(props: UpdateRelationPivotProps): Promise<void> {\n return this.withTransaction((delegate) => delegate.updateRelationPivot(props));\n }\n\n async checkUniqueField(\n path: string,\n name: string,\n value: unknown,\n id?: string,\n collection?: CollectionConfig\n ): Promise<boolean> {\n return this.withTransaction((delegate) => delegate.checkUniqueField(path, name, value, id, collection), { accessMode: \"read only\" });\n }\n\n async count<M extends Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<number> {\n return this.withTransaction((delegate) => delegate.count(props), { accessMode: \"read only\" });\n }\n\n}\n","import { pgSchema, pgTable, uuid, timestamp, boolean, jsonb, text, unique, index, integer, bigint } from \"drizzle-orm/pg-core\";\nimport { relations } from \"drizzle-orm\";\n\n/**\n * Factory function to dynamically create the auth tables bound to the specified schema names.\n *\n * This module builds queries; it does not create tables. `ensureAuthTablesExist`\n * owns the DDL, which makes everything here a *claim* about a database it cannot\n * enforce — and the claims drifted. Every column below was declared\n * `varchar(n)` while the DDL created it as `TEXT`: `user_agent` as varchar(500),\n * `ip_address` as varchar(45), `secret_encrypted` as varchar(500), every\n * `token_hash` as varchar(255). None of it was true of any database this\n * framework ever provisioned. Harmless at runtime — drizzle does not enforce a\n * length client-side, so the widths only ever misled the next reader — but a\n * schema module that describes columns that do not exist is worse than no\n * schema module. They are `text` here now because they are TEXT there.\n */\nexport function createAuthSchema(usersSchemaName = \"rebase\") {\n const usersSchema = usersSchemaName === \"public\" ? null : pgSchema(usersSchemaName);\n\n const tableCreator = (usersSchema ? usersSchema.table.bind(usersSchema) : pgTable) as typeof pgTable;\n const usersTableCreator = tableCreator;\n\n /**\n * Users table - stores both email/password and OAuth users\n */\n const users = usersTableCreator(\"users\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n email: text(\"email\").notNull().unique(),\n passwordHash: text(\"password_hash\"), // NULL for OAuth-only users\n displayName: text(\"display_name\"),\n photoUrl: text(\"photo_url\"),\n emailVerified: boolean(\"email_verified\").default(false).notNull(),\n emailVerificationToken: text(\"email_verification_token\"),\n emailVerificationSentAt: timestamp(\"email_verification_sent_at\"),\n isAnonymous: boolean(\"is_anonymous\").default(false).notNull(),\n roles: text(\"roles\").array().default([]).notNull(),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>().default({}).notNull(),\n /**\n * Sessions that began before this instant are dead, whatever tokens\n * they still hold. Password resets and admin revocations stamp it.\n *\n * Deleting the user's refresh-token rows (which we also do) is not\n * sufficient on its own: a request already in flight can insert a\n * freshly rotated row microseconds after the delete and survive it.\n * This timestamp cannot be outrun that way — it is checked against\n * `refresh_tokens.session_started_at`, which rotation carries forward.\n */\n tokensValidAfter: timestamp(\"tokens_valid_after\"),\n /** When an administrator switched the account off; NULL while it is on. */\n disabledAt: timestamp(\"disabled_at\", { withTimezone: true }),\n /**\n * A self-service address change waiting for its link: the address\n * the account is moving to, the hash of the token mailed there, and\n * when it was mailed. The address is not the account's, and is not\n * reserved, until the link is followed.\n */\n pendingEmail: text(\"pending_email\"),\n emailChangeToken: text(\"email_change_token\"),\n emailChangeSentAt: timestamp(\"email_change_sent_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n updatedAt: timestamp(\"updated_at\").defaultNow().notNull()\n });\n\n\n /**\n * Refresh tokens for long-lived sessions.\n *\n * A row is one token, not one device. Every token minted from the same\n * sign-in shares a `sessionId`, and rotation ADDS a row rather than\n * replacing one: the superseded token stays on file, flagged `revoked`\n * with a `rotatedAt` stamp. That record is what lets the refresh endpoint\n * tell a client replaying a token it never got an answer for (a response\n * lost to a redeploy, a second tab racing on boot) apart from a stranger\n * presenting a token that was never issued. Deleting the old row on sight\n * — the previous behaviour — made those two cases indistinguishable, and\n * the legitimate one is overwhelmingly the common one.\n *\n * There is deliberately NO unique constraint on (uid, user_agent,\n * ip_address). Keying a session on the IP meant one row per \"device\",\n * so a second browser profile behind the same NAT silently evicted the\n * first, and a phone changing networks orphaned a row on every hop.\n * User agent and IP are descriptive metadata for the sessions list;\n * `sessionId` is the identity.\n */\n const refreshTokens = tableCreator(\"refresh_tokens\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n sessionId: uuid(\"session_id\").defaultRandom().notNull(),\n tokenHash: text(\"token_hash\").notNull().unique(),\n expiresAt: timestamp(\"expires_at\").notNull(),\n revoked: boolean(\"revoked\").default(false).notNull(),\n rotatedAt: timestamp(\"rotated_at\"),\n /**\n * When the sign-in this token descends from happened — carried across\n * every rotation, unlike `createdAt`. `users.tokensValidAfter` is\n * compared against this, so a revocation cannot be outrun by a token\n * that rotates immediately after it.\n */\n sessionStartedAt: timestamp(\"session_started_at\").defaultNow().notNull(),\n /**\n * The assurance level the sign-in was established at — `aal2` only\n * where a second factor was actually presented. Carried across\n * rotations, because refresh is not a new authentication and has\n * nothing else to read the level from.\n */\n aal: text(\"aal\"),\n /**\n * How the sign-in happened — `password`, `anonymous`, `magic-link`,\n * `otp`, `mfa` or a provider id — and what `providerId` says for the\n * session. Carried across rotations like `aal`.\n */\n method: text(\"method\"),\n userAgent: text(\"user_agent\"),\n ipAddress: text(\"ip_address\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull()\n }, (table) => ({\n sessionIdx: index(\"idx_refresh_tokens_session\").on(table.sessionId)\n }));\n\n /**\n * Password reset tokens for forgot password flow\n */\n const passwordResetTokens = tableCreator(\"password_reset_tokens\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n tokenHash: text(\"token_hash\").notNull().unique(),\n expiresAt: timestamp(\"expires_at\").notNull(),\n usedAt: timestamp(\"used_at\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull()\n });\n\n /**\n * App config - key/value store for custom settings\n */\n const appConfig = tableCreator(\"app_config\", {\n key: text(\"key\").primaryKey(),\n value: jsonb(\"value\").notNull(),\n updatedAt: timestamp(\"updated_at\").defaultNow().notNull()\n });\n\n /**\n * User identities - maps external OAuth profiles back to local users\n */\n const userIdentities = tableCreator(\"user_identities\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n provider: text(\"provider\").notNull(), // e.g. 'google', 'linkedin'\n providerId: text(\"provider_id\").notNull(),\n profileData: jsonb(\"profile_data\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n updatedAt: timestamp(\"updated_at\").defaultNow().notNull()\n }, (table) => ({\n uniqueProviderId: unique(\"unique_provider_id\").on(table.provider, table.providerId)\n }));\n\n /**\n * MFA factors table - stores enrolled MFA methods\n */\n const mfaFactors = tableCreator(\"mfa_factors\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n factorType: text(\"factor_type\").notNull(), // 'totp'\n secretEncrypted: text(\"secret_encrypted\").notNull(),\n friendlyName: text(\"friendly_name\"),\n verified: boolean(\"verified\").default(false).notNull(),\n /**\n * The highest TOTP time step ever accepted for this factor. RFC 6238\n * §5.2 forbids accepting an OTP twice, and the ±1 step window that\n * exists for clock drift is also a 90-second replay window: without\n * this, one observed code buys a fresh session for a minute and a half.\n */\n lastUsedCounter: bigint(\"last_used_counter\", { mode: \"number\" }),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n updatedAt: timestamp(\"updated_at\").defaultNow().notNull()\n });\n\n /**\n * MFA challenges table - tracks active MFA verification attempts\n */\n const mfaChallenges = tableCreator(\"mfa_challenges\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n factorId: uuid(\"factor_id\").notNull().references(() => mfaFactors.id, { onDelete: \"cascade\" }),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n verifiedAt: timestamp(\"verified_at\"),\n ipAddress: text(\"ip_address\"),\n /** Failed guesses recorded against this challenge; bounded by the route. */\n attempts: integer(\"attempts\").default(0).notNull(),\n expiresAt: timestamp(\"expires_at\").notNull()\n });\n\n /**\n * Recovery codes table - backup codes for MFA\n */\n const recoveryCodes = tableCreator(\"recovery_codes\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n codeHash: text(\"code_hash\").notNull(),\n usedAt: timestamp(\"used_at\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull()\n });\n\n /**\n * Magic link tokens for passwordless email login\n */\n const magicLinkTokens = tableCreator(\"magic_link_tokens\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n tokenHash: text(\"token_hash\").notNull().unique(),\n expiresAt: timestamp(\"expires_at\").notNull(),\n usedAt: timestamp(\"used_at\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull()\n });\n\n return {\n usersSchema,\n users,\n refreshTokens,\n passwordResetTokens,\n appConfig,\n userIdentities,\n mfaFactors,\n mfaChallenges,\n recoveryCodes,\n magicLinkTokens\n };\n}\n\n// Instantiate default schema and tables using the default \"rebase\" schema\nconst defaultAuthSchema = createAuthSchema(\"rebase\");\n\nexport const usersSchema = defaultAuthSchema.usersSchema;\n\nexport const users = defaultAuthSchema.users;\nexport const refreshTokens = defaultAuthSchema.refreshTokens;\nexport const passwordResetTokens = defaultAuthSchema.passwordResetTokens;\nexport const appConfig = defaultAuthSchema.appConfig;\nexport const userIdentities = defaultAuthSchema.userIdentities;\nexport const mfaFactors = defaultAuthSchema.mfaFactors;\nexport const mfaChallenges = defaultAuthSchema.mfaChallenges;\nexport const recoveryCodes = defaultAuthSchema.recoveryCodes;\nexport const magicLinkTokens = defaultAuthSchema.magicLinkTokens;\n\n// Relations\nexport const usersRelations = relations(users, ({ many }) => ({\n refreshTokens: many(refreshTokens),\n passwordResetTokens: many(passwordResetTokens),\n userIdentities: many(userIdentities),\n mfaFactors: many(mfaFactors),\n recoveryCodes: many(recoveryCodes),\n magicLinkTokens: many(magicLinkTokens)\n}));\n\nexport const refreshTokensRelations = relations(refreshTokens, ({ one }) => ({\n user: one(users, {\n fields: [refreshTokens.uid],\n references: [users.id]\n })\n}));\n\nexport const passwordResetTokensRelations = relations(passwordResetTokens, ({ one }) => ({\n user: one(users, {\n fields: [passwordResetTokens.uid],\n references: [users.id]\n })\n}));\n\nexport const userIdentitiesRelations = relations(userIdentities, ({ one }) => ({\n user: one(users, {\n fields: [userIdentities.uid],\n references: [users.id]\n })\n}));\n\nexport const mfaFactorsRelations = relations(mfaFactors, ({ one, many }) => ({\n user: one(users, {\n fields: [mfaFactors.uid],\n references: [users.id]\n }),\n challenges: many(mfaChallenges)\n}));\n\nexport const mfaChallengesRelations = relations(mfaChallenges, ({ one }) => ({\n factor: one(mfaFactors, {\n fields: [mfaChallenges.factorId],\n references: [mfaFactors.id]\n })\n}));\n\nexport const recoveryCodesRelations = relations(recoveryCodes, ({ one }) => ({\n user: one(users, {\n fields: [recoveryCodes.uid],\n references: [users.id]\n })\n}));\n\nexport const magicLinkTokensRelations = relations(magicLinkTokens, ({ one }) => ({\n user: one(users, {\n fields: [magicLinkTokens.uid],\n references: [users.id]\n })\n}));\n\n// Type exports\nexport type User = typeof users.$inferSelect;\nexport type NewUser = typeof users.$inferInsert;\nexport type RefreshToken = typeof refreshTokens.$inferSelect;\nexport type PasswordResetToken = typeof passwordResetTokens.$inferSelect;\nexport type AppConfig = typeof appConfig.$inferSelect;\nexport type UserIdentity = typeof userIdentities.$inferSelect;\nexport type NewUserIdentity = typeof userIdentities.$inferInsert;\nexport type MfaFactorRow = typeof mfaFactors.$inferSelect;\nexport type MfaChallengeRow = typeof mfaChallenges.$inferSelect;\nexport type RecoveryCodeRow = typeof recoveryCodes.$inferSelect;\nexport type MagicLinkToken = typeof magicLinkTokens.$inferSelect;\n","import { CollectionConfig, ResolvedRelation, isManyToMany } from \"@rebasepro/types\";\nimport { resolveCollectionRelations } from \"@rebasepro/common\";\n\nimport { PostgresCollectionRegistry } from \"../../collections/PostgresCollectionRegistry\";\n\n/**\n * One end of a many-to-many, as seen from the junction table.\n *\n * A junction table is not a collection, so nothing in the registry maps it to\n * one — which is why a change to it was invisible to change capture. But its\n * rows are exactly the contents of a parent's child list, so a write to it is a\n * change to `<parentSlug>/<sourceId>/<relationKey>` and to nothing else.\n */\nexport interface JunctionLink {\n schema: string;\n /** The junction table itself, e.g. `posts_tags`. */\n table: string;\n /** The collection whose relation this is, e.g. `posts`. */\n parentCollection: CollectionConfig;\n /** The relation's key — the path segment a child list is addressed by. */\n relationKey: string;\n /** Junction column holding the parent's id. */\n sourceColumn: string;\n /** Junction column holding the target's id. */\n targetColumn: string;\n}\n\n/**\n * Every junction table reachable from a registered collection, once per\n * relation that uses it.\n *\n * A junction is listed once per *direction* when both sides declare it, because\n * each direction addresses a different child list: `posts/1/tags` and\n * `tags/t/posts` both change when one link is written.\n */\nexport function collectJunctionLinks(registry: PostgresCollectionRegistry): JunctionLink[] {\n const links: JunctionLink[] = [];\n const seen = new Set<string>();\n\n for (const collection of registry.getCollections()) {\n let relations: Record<string, ResolvedRelation>;\n try {\n relations = resolveCollectionRelations(collection);\n } catch {\n // A collection whose relations cannot be resolved (an unresolvable\n // target, typically mid-migration) simply contributes none.\n continue;\n }\n\n for (const [relationKey, relation] of Object.entries(relations)) {\n if (!isManyToMany(relation)) continue;\n const through = relation.through;\n\n // Same relation registered under both its canonical name and the\n // declaring property key would otherwise notify the same path twice.\n const key = `${collection.slug}::${relationKey}::${through.table}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n links.push({\n schema: (collection as { schema?: string }).schema ?? \"public\",\n table: through.table,\n parentCollection: collection,\n relationKey,\n sourceColumn: through.sourceColumn,\n targetColumn: through.targetColumn\n });\n }\n }\n\n return links;\n}\n\n/**\n * Index {@link collectJunctionLinks} by table, under both the qualified and the\n * bare name — a change event carries whatever the trigger reports, and a\n * collection need not declare a schema.\n */\nexport function buildJunctionLinkMap(registry: PostgresCollectionRegistry): Map<string, JunctionLink[]> {\n const map = new Map<string, JunctionLink[]>();\n\n for (const link of collectJunctionLinks(registry)) {\n for (const key of [`${link.schema}.${link.table}`, link.table]) {\n const existing = map.get(key);\n if (existing) existing.push(link);\n else map.set(key, [link]);\n }\n }\n\n return map;\n}\n","import { getTableColumns } from \"drizzle-orm\";\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { getTableName } from \"@rebasepro/common\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\nimport type { PostgresCollectionRegistry } from \"../../collections/PostgresCollectionRegistry\";\nimport { getPrimaryKeys } from \"../collection-helpers\";\n\n/** One key field of a collection, and the column it is stored in. */\nexport interface KeyColumn {\n fieldName: string;\n columnName: string;\n}\n\n/**\n * The columns a collection's rows are addressed by.\n *\n * A change notification names its row by column — that is all a trigger sees —\n * and an address is built from the key's *field* names. Both ends read the\n * mapping from here: the provisioner, to attach the trigger with exactly these\n * columns (and nothing else leaves the database), and the consumer, to turn the\n * captured columns back into an address. A key declared as `userId` over\n * `user_id` used to be looked up by field name on the captured row, never\n * found, and every external write reached no single-row subscriber.\n */\nexport function collectionKeyColumns(collection: CollectionConfig, registry: PostgresCollectionRegistry): KeyColumn[] {\n const table = registry.getTable(getTableName(collection));\n const columns = table ? getTableColumns(table) : undefined;\n return getPrimaryKeys(collection, registry).map((pk) => {\n const property = collection.properties?.[pk.fieldName];\n const declared = property && \"columnName\" in property ? property.columnName : undefined;\n return {\n fieldName: pk.fieldName,\n columnName: columns?.[pk.fieldName]?.name ?? declared ?? toSnakeCase(pk.fieldName)\n };\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","import { logger } from \"@rebasepro/server\";\nimport type { RawSqlRunner } from \"../../security/rls-enforcement\";\nimport { BOOT_LOCK_ATTEMPTS, BOOT_LOCK_TIMEOUT_MS, isLockTimeout, sleep } from \"../../schema/boot-ddl\";\n\n/**\n * Trigger-based Change Data Capture (CDC).\n *\n * The preferred CDC source is the write-ahead log (logical replication), which\n * — like Supabase Realtime — sees *every* commit regardless of how it was made.\n * When logical replication is unavailable (managed Postgres without\n * `wal_level=logical`, no replication privilege, no `REPLICA IDENTITY`), this\n * trigger-based fallback provides the same guarantee:\n *\n * AFTER INSERT/UPDATE/DELETE/TRUNCATE statement trigger → pg_notify('rebase_cdc', payload)\n *\n * once per statement, naming the changed rows by key in batches of about a\n * thousand — a 1M-row UPDATE is ~1,000 notifications, where a row trigger sent\n * a million into Postgres's shared notification queue, to every instance.\n *\n * A single dedicated LISTEN client per backend instance consumes the channel\n * (see {@link CdcListener}) and feeds the change into the existing\n * `RealtimeService.notifyUpdate` pipeline, so subscribers see the change no\n * matter what wrote it — psql, a cron in another service, raw Drizzle/SQL, or\n * the Studio SQL editor.\n *\n * Provisioning runs from the framework's own bootstrap as the owner (server)\n * context, alongside the RLS role provisioning. It is idempotent.\n */\n\n/** Postgres NOTIFY channel carrying database-level change events. */\nexport const CDC_CHANNEL = \"rebase_cdc\";\n\n/**\n * Schema-qualified name of the row-level trigger function: one notification\n * per changed row. What every version before statement-level capture attached\n * everywhere, and still what a partitioned or foreign table gets — see\n * {@link levelForRelkind}.\n */\nexport const CDC_TRIGGER_FUNCTION = \"rebase.rebase_cdc_notify\";\n\n/**\n * Schema-qualified name of the statement-level trigger function: one\n * notification per ~1,000 changed rows. See {@link buildCdcFunctionSql}.\n *\n * A function of its own rather than a new body for the row function, because\n * an instance of an older version replaces the row function's body every time\n * it boots: a statement trigger calling it would then run a row trigger's body,\n * for every write, until the next boot of this version.\n */\nexport const CDC_STATEMENT_FUNCTION = \"rebase.rebase_cdc_notify_statement\";\n\n/** Name of the per-table row-level trigger (unqualified — triggers are namespaced by table). */\nexport const CDC_TRIGGER_NAME = \"rebase_cdc_trigger\";\n\n/** How a table is instrumented: one notification per statement's ~1,000 rows, or one per row. */\nexport type CdcTriggerLevel = \"statement\" | \"row\";\n\n/** One trigger this module creates, as `pg_trigger` describes it. */\ninterface CdcTriggerSpec {\n name: string;\n /** The function it calls. */\n fn: \"row\" | \"statement\";\n /** `CREATE TRIGGER` between the name and `ON`. */\n timing: string;\n /** `CREATE TRIGGER` between the table and `EXECUTE`. */\n forEach: string;\n /** `tgtype`: ROW (1), BEFORE (2), INSERT (4), DELETE (8), UPDATE (16), TRUNCATE (32), INSTEAD (64). */\n type: number;\n /** `tgnewtable` / `tgoldtable`: the transition tables it captures. */\n newTable: string | null;\n oldTable: string | null;\n}\n\n/** The transition tables the statement function reads. */\nconst NEW_ROWS = \"rebase_cdc_new\";\nconst OLD_ROWS = \"rebase_cdc_old\";\n\n/**\n * The statement-level triggers a plain table gets. One per event, because\n * Postgres refuses transition tables on a trigger with more than one event.\n * The TRUNCATE one is new with them: a row trigger never fires on `TRUNCATE`,\n * so a truncated table used to be announced to nobody.\n */\nexport const CDC_STATEMENT_TRIGGERS: readonly CdcTriggerSpec[] = [\n { name: \"rebase_cdc_insert\", fn: \"statement\", timing: \"AFTER INSERT\", forEach: `REFERENCING NEW TABLE AS ${NEW_ROWS} FOR EACH STATEMENT`, type: 4, newTable: NEW_ROWS, oldTable: null },\n { name: \"rebase_cdc_update\", fn: \"statement\", timing: \"AFTER UPDATE\", forEach: `REFERENCING NEW TABLE AS ${NEW_ROWS} FOR EACH STATEMENT`, type: 16, newTable: NEW_ROWS, oldTable: null },\n { name: \"rebase_cdc_delete\", fn: \"statement\", timing: \"AFTER DELETE\", forEach: `REFERENCING OLD TABLE AS ${OLD_ROWS} FOR EACH STATEMENT`, type: 8, newTable: null, oldTable: OLD_ROWS },\n { name: \"rebase_cdc_truncate\", fn: \"statement\", timing: \"AFTER TRUNCATE\", forEach: \"FOR EACH STATEMENT\", type: 32, newTable: null, oldTable: null }\n];\n\n/**\n * The row-level trigger: ROW (1) + INSERT (4) + DELETE (8) + UPDATE (16), and\n * neither BEFORE (2), TRUNCATE (32) nor INSTEAD OF (64) — so AFTER.\n */\nconst CDC_ROW_TRIGGER: CdcTriggerSpec = {\n name: CDC_TRIGGER_NAME, fn: \"row\", timing: \"AFTER INSERT OR UPDATE OR DELETE\", forEach: \"FOR EACH ROW\",\n type: 1 + 4 + 8 + 16, newTable: null, oldTable: null\n};\n\n/** Every trigger name this module owns, whichever level a table is at. */\nconst ALL_TRIGGER_NAMES = [CDC_TRIGGER_NAME, ...CDC_STATEMENT_TRIGGERS.map((t) => t.name)];\n\nconst triggersFor = (level: CdcTriggerLevel): readonly CdcTriggerSpec[] =>\n level === \"statement\" ? CDC_STATEMENT_TRIGGERS : [CDC_ROW_TRIGGER];\n\n/**\n * The level a table is instrumented at, by `pg_class.relkind`.\n *\n * A plain table gets statement triggers. A partitioned table keeps the row\n * trigger: Postgres clones a row trigger onto every partition, so a write made\n * straight to a partition is still captured, while a statement trigger on the\n * parent fires only for statements against the parent. A foreign table\n * cannot have transition tables at all.\n */\nexport const levelForRelkind = (relkind: string | null | undefined): CdcTriggerLevel =>\n relkind === \"p\" || relkind === \"f\" ? \"row\" : \"statement\";\n\n/**\n * How long after a boot first converts a table from the row trigger, the\n * statement triggers still announce every change one row per notification.\n *\n * The triggers are the database's, and an instance of the previous version\n * still running beside this one — a rolling deploy, a rollback — reads the\n * same channel with a parser that knows one row per notification: given a\n * batch it sees no key, so its lists refetch but its single-row and\n * many-to-many subscribers hear nothing. While any such instance may be up,\n * the statement triggers speak its protocol; after this window they batch.\n * Every version that batches reads both. A boot of the older version re-attaches its row trigger (it\n * creates a missing one), and the next boot of this one converts it back and\n * re-opens the window, so a rollback is covered the same way.\n */\nexport const CDC_ROW_COMPAT_MS = 24 * 60 * 60 * 1000;\n\n/**\n * pg_notify hard-caps payloads at 8000 bytes and *aborts the triggering\n * statement* if the limit is exceeded. The payload is an identity, so it fits\n * unless a key is enormous; one that does not is sent with no key at all (a\n * collection-wide invalidation) rather than breaking the write. 7900 leaves\n * headroom for the envelope.\n */\nconst MAX_NOTIFY_BYTES = 7900;\n\n/**\n * Room left in a batch for the key that starts it: a chunk is cut by the\n * running length of its keys, so it can run over its budget by its first key.\n * A chunk that still overflows — a key longer than this — is sent one key per\n * notification.\n */\nconst CHUNK_SLACK_BYTES = 64;\n\nconst quoteIdent = (name: string): string => `\"${name.replace(/\"/g, \"\\\"\\\"\")}\"`;\nconst quoteLiteral = (value: string): string => `'${value.replace(/'/g, \"''\")}'`;\n\n/**\n * SQL that (re)creates both CDC trigger functions. Safe to run repeatedly:\n * `CREATE OR REPLACE` updates in place without dropping dependent triggers —\n * which is also how a database instrumented by an older version gets these\n * bodies on its next boot, for every table at once, re-attached or not.\n *\n * Both functions emit `{ schema, table, op, ... }`, and the rows are named by\n * **identity, never the tuple**. Postgres puts no privilege on `LISTEN`: any\n * role that can connect can listen on this channel, whatever it may `SELECT`.\n * When `row` was `to_jsonb(NEW)`, a login with no grant on anything received\n * every changed row of every instrumented table — `password_hash` and\n * verification tokens from the auth table included — past RLS and column\n * grants. The consumer never read anything but the key: every subscriber\n * re-reads the row under its own scope.\n *\n * The identity is, in order:\n * - the columns the trigger was attached with (`TG_ARGV`) — the key the\n * consumer addresses the row by, and for a junction table the two ids that\n * name the child list it changed; see {@link buildCdcTriggerSql};\n * - otherwise the table's primary key, read from the catalogue — a trigger\n * attached with no columns, by an older boot or on a table no collection\n * maps any more;\n * - otherwise `id`, if the table has one, and else nothing: a change the\n * consumer can only treat as collection-wide.\n *\n * `compatUntil` is the end of the window in which the statement function\n * still sends one row per notification — see {@link CDC_ROW_COMPAT_MS}. A\n * timestamp, or `-infinity` for none.\n */\nexport function buildCdcFunctionSql(options: { compatUntil?: string } = {}): string {\n return `CREATE SCHEMA IF NOT EXISTS rebase;\\n\\n${buildCdcRowFunctionSql()}\\n\\n${buildCdcStatementFunctionSql(options.compatUntil ?? \"-infinity\")}`;\n}\n\n/**\n * The row-level function: `{ schema, table, op, row }`, one per changed row.\n *\n * A trigger attached by `id` alone reads that one column straight off the\n * row. Reaching any other key goes through `to_jsonb` of the whole row, which\n * fetches every TOASTed column and serialises it: on a table with a rich-text\n * or JSON document per row, a write that only flipped `status` cost the\n * trigger 150–260 µs per row instead of ~5, to read a key. The payload is the\n * same text either way. A column renamed since the trigger was attached raises\n * `undefined_column` on that read, which falls through to the general path\n * rather than failing the write.\n */\nfunction buildCdcRowFunctionSql(): string {\n return `\nCREATE OR REPLACE FUNCTION ${CDC_TRIGGER_FUNCTION}() RETURNS trigger\nLANGUAGE plpgsql AS $rebase_cdc$\nDECLARE\n rec jsonb;\n ident jsonb := '{}'::jsonb;\n col text;\n payload text;\nBEGIN\n -- The common key, read on its own: no other column is fetched.\n IF TG_NARGS = 1 AND TG_ARGV[0] = 'id' THEN\n BEGIN\n IF (TG_OP = 'DELETE') THEN\n ident := jsonb_build_object('id', OLD.id);\n ELSE\n ident := jsonb_build_object('id', NEW.id);\n END IF;\n EXCEPTION WHEN undefined_column THEN\n ident := '{}'::jsonb;\n END;\n END IF;\n\n IF ident = '{}'::jsonb THEN\n -- The tuple is read here and never leaves this function: only the\n -- identity below is put on the channel.\n IF (TG_OP = 'DELETE') THEN\n rec := to_jsonb(OLD);\n ELSE\n rec := to_jsonb(NEW);\n END IF;\n\n FOR n IN 0 .. TG_NARGS - 1 LOOP\n col := TG_ARGV[n];\n IF rec ? col THEN\n ident := ident || jsonb_build_object(col, rec -> col);\n END IF;\n END LOOP;\n\n IF ident = '{}'::jsonb THEN\n SELECT coalesce(jsonb_object_agg(a.attname, rec -> a.attname), '{}'::jsonb) INTO ident\n FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY (i.indkey)\n WHERE i.indrelid = TG_RELID AND i.indisprimary;\n END IF;\n\n IF ident = '{}'::jsonb AND rec ? 'id' THEN\n ident := jsonb_build_object('id', rec -> 'id');\n END IF;\n END IF;\n\n payload := json_build_object(\n 'schema', TG_TABLE_SCHEMA,\n 'table', TG_TABLE_NAME,\n 'op', TG_OP,\n 'row', ident\n )::text;\n\n -- Never let CDC abort the write: a key too large to notify is sent as no\n -- key, which the consumer treats as a change to the whole collection.\n IF (octet_length(payload) > ${MAX_NOTIFY_BYTES}) THEN\n payload := json_build_object(\n 'schema', TG_TABLE_SCHEMA,\n 'table', TG_TABLE_NAME,\n 'op', TG_OP,\n 'row', '{}'::jsonb,\n 'truncated', true\n )::text;\n END IF;\n\n PERFORM pg_notify(${quoteLiteral(CDC_CHANNEL)}, payload);\n RETURN NULL;\nEND;\n$rebase_cdc$;\n`.trim();\n}\n\n/** The line of the statement function that holds its compatibility window, and how to read it back. */\nconst COMPAT_DECLARATION = \"compat_until CONSTANT timestamptz := \";\nconst COMPAT_PATTERN = `${COMPAT_DECLARATION}'([^']*)'`;\n\n/**\n * The statement-level function: what a plain table's four triggers call.\n *\n * It reads the statement's transition table, so a 1M-row UPDATE is one call,\n * not a million, and announces the changed keys in batches:\n *\n * { schema, table, op, cols: [\"id\"], keys: [1, 2, 3, …] }\n *\n * cut to stay under the 8000-byte NOTIFY limit — about a thousand integer ids\n * per notification, so ~1,000 notifications where the row trigger sent 1M.\n * With one column, a key is the value; with several, an array in `cols`\n * order. A statement that changed one row sends the row trigger's\n * `{ schema, table, op, row }`, so the common single-row write is announced\n * exactly as it always was, and a statement that changed none sends nothing.\n * `TRUNCATE` sends `{ schema, table, op: \"TRUNCATE\" }`.\n *\n * Until `compat_until`, every row is announced on its own in the row\n * trigger's shape — see {@link CDC_ROW_COMPAT_MS}.\n *\n * The `id` key — what provisioning attaches every collection keyed by `id`\n * with — is read by a query planned once per trigger. Any other key is read\n * by a query built per statement, after checking which of the requested\n * columns the table still has. Neither reads any other column, so no TOASTed\n * document is fetched to name its row.\n */\nfunction buildCdcStatementFunctionSql(compatUntil: string): string {\n const channel = quoteLiteral(CDC_CHANNEL);\n // The batch as the id path builds it: `head` + `cols` + `keys`, and the\n // envelope around the keys counted out of the budget.\n const idEnvelope = `,\"cols\":[\"id\"],\"keys\":[]}`;\n const chunked = (source: string, keyExpr: string) =>\n `SELECT string_agg(k, ',') AS keys\n FROM (SELECT k, (sum(octet_length(k) + 1) OVER (ROWS UNBOUNDED PRECEDING) - 1) / budget AS c\n FROM (SELECT ${keyExpr} AS k FROM ${source} t) s) s\n GROUP BY c`;\n const unkeyed = `head || ',\"row\":{},\"truncated\":true}'`;\n const perRow = (source: string) =>\n `PERFORM pg_notify(${channel}, CASE WHEN octet_length(p) > ${MAX_NOTIFY_BYTES} THEN ${unkeyed} ELSE p END)\n FROM (SELECT head || ',\"row\":' || json_build_object('id', t.id)::text || '}' AS p FROM ${source} t) s;`;\n const firstTwo = (source: string) =>\n `SELECT count(*), max(json_build_object('id', t.id)::text) INTO n, one FROM (SELECT id FROM ${source} LIMIT 2) t;`;\n const countTwo = (source: string) => `SELECT count(*) INTO n FROM (SELECT FROM ${source} LIMIT 2) s;`;\n return `\nCREATE OR REPLACE FUNCTION ${CDC_STATEMENT_FUNCTION}() RETURNS trigger\nLANGUAGE plpgsql AS $rebase_cdc$\nDECLARE\n -- One row per notification until then: see CDC_ROW_COMPAT_MS in trigger-cdc.ts.\n ${COMPAT_DECLARATION}${quoteLiteral(compatUntil)};\n head text;\n cols text[];\n collist text;\n n bigint;\n one text;\n budget bigint;\n chunks text[];\n chunk text;\n payload text;\nBEGIN\n head := format('{\"schema\":%s,\"table\":%s,\"op\":%s', to_json(TG_TABLE_SCHEMA::text), to_json(TG_TABLE_NAME::text), to_json(TG_OP::text));\n IF TG_OP = 'TRUNCATE' THEN\n PERFORM pg_notify(${channel}, head || '}');\n RETURN NULL;\n END IF;\n\n IF TG_NARGS = 1 AND TG_ARGV[0] = 'id' THEN\n BEGIN\n -- None, one, or more (no further), and the one named as the row trigger names it.\n IF TG_OP = 'DELETE' THEN\n ${firstTwo(OLD_ROWS)}\n ELSE\n ${firstTwo(NEW_ROWS)}\n END IF;\n IF n = 0 THEN\n RETURN NULL;\n END IF;\n IF n = 1 THEN\n payload := head || ',\"row\":' || one || '}';\n -- A key too large to notify is sent as no key: never let CDC abort the write.\n PERFORM pg_notify(${channel}, CASE WHEN octet_length(payload) > ${MAX_NOTIFY_BYTES} THEN ${unkeyed} ELSE payload END);\n RETURN NULL;\n END IF;\n IF now() < compat_until THEN\n IF TG_OP = 'DELETE' THEN\n ${perRow(OLD_ROWS)}\n ELSE\n ${perRow(NEW_ROWS)}\n END IF;\n RETURN NULL;\n END IF;\n budget := greatest(${MAX_NOTIFY_BYTES} - octet_length(head) - ${idEnvelope.length} - ${CHUNK_SLACK_BYTES}, ${CHUNK_SLACK_BYTES});\n IF TG_OP = 'DELETE' THEN\n chunks := ARRAY(${chunked(OLD_ROWS, \"to_json(t.id)::text\")});\n ELSE\n chunks := ARRAY(${chunked(NEW_ROWS, \"to_json(t.id)::text\")});\n END IF;\n cols := ARRAY['id'];\n EXCEPTION WHEN undefined_column THEN\n -- \\`id\\` is gone since the trigger was attached: named below like any other key.\n cols := NULL;\n END;\n END IF;\n\n IF cols IS NULL THEN\n IF TG_OP = 'DELETE' THEN\n ${countTwo(OLD_ROWS)}\n ELSE\n ${countTwo(NEW_ROWS)}\n END IF;\n IF n = 0 THEN\n RETURN NULL;\n END IF;\n cols := ARRAY(SELECT x.col FROM unnest(TG_ARGV) WITH ORDINALITY AS x(col, i)\n WHERE EXISTS (SELECT FROM pg_attribute a\n WHERE a.attrelid = TG_RELID AND a.attname = x.col AND a.attnum > 0 AND NOT a.attisdropped)\n ORDER BY x.i);\n IF cardinality(cols) = 0 THEN\n cols := ARRAY(SELECT a.attname::text FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY (i.indkey)\n WHERE i.indrelid = TG_RELID AND i.indisprimary\n ORDER BY a.attnum);\n END IF;\n IF cardinality(cols) = 0 AND EXISTS (SELECT FROM pg_attribute a\n WHERE a.attrelid = TG_RELID AND a.attname = 'id' AND a.attnum > 0 AND NOT a.attisdropped) THEN\n cols := ARRAY['id'];\n END IF;\n IF cardinality(cols) = 0 THEN\n -- Nothing names a row of this table: a change to all of it.\n PERFORM pg_notify(${channel}, head || ',\"row\":{}}');\n RETURN NULL;\n END IF;\n\n IF n = 1 OR now() < compat_until THEN\n EXECUTE format(\n 'SELECT pg_notify(%L, CASE WHEN octet_length(p) > ${MAX_NOTIFY_BYTES} THEN %L ELSE p END)\n FROM (SELECT %L || json_build_object(%s)::text || ''}'' AS p FROM %I t) s',\n ${channel}, ${unkeyed}, head || ',\"row\":',\n (SELECT string_agg(format('%L, t.%I', c, c), ', ') FROM unnest(cols) AS c),\n CASE TG_OP WHEN 'DELETE' THEN '${OLD_ROWS}' ELSE '${NEW_ROWS}' END);\n RETURN NULL;\n END IF;\n\n collist := (SELECT string_agg(format('t.%I', c), ', ') FROM unnest(cols) AS c);\n budget := greatest(${MAX_NOTIFY_BYTES} - octet_length(head) - octet_length(to_json(cols)::text) - 20 - ${CHUNK_SLACK_BYTES}, ${CHUNK_SLACK_BYTES});\n EXECUTE format(\n 'SELECT ARRAY(SELECT string_agg(k, '','')\n FROM (SELECT k, (sum(octet_length(k) + 1) OVER (ROWS UNBOUNDED PRECEDING) - 1) / %s AS c\n FROM (SELECT %s AS k FROM %I t) s) s\n GROUP BY c)',\n budget,\n CASE WHEN cardinality(cols) = 1 THEN format('to_json(%s)::text', collist)\n ELSE format('json_build_array(%s)::text', collist) END,\n CASE TG_OP WHEN 'DELETE' THEN '${OLD_ROWS}' ELSE '${NEW_ROWS}' END) INTO chunks;\n END IF;\n\n head := head || ',\"cols\":' || to_json(cols)::text || ',\"keys\":[';\n FOREACH chunk IN ARRAY chunks LOOP\n payload := head || chunk || ']}';\n IF octet_length(payload) <= ${MAX_NOTIFY_BYTES} THEN\n PERFORM pg_notify(${channel}, payload);\n ELSE\n -- A key long enough to overflow its batch: each of the batch's keys on its own.\n PERFORM pg_notify(${channel}, CASE WHEN octet_length(p) > ${MAX_NOTIFY_BYTES} THEN ${unkeyed} ELSE p END)\n FROM (SELECT head || k.value::text || ']}' AS p FROM json_array_elements(('[' || chunk || ']')::json) AS k) s;\n END IF;\n END LOOP;\n RETURN NULL;\nEND;\n$rebase_cdc$;\n`.trim();\n}\n\n/**\n * SQL that (re)attaches the CDC triggers to a single table, at `level`.\n * Every trigger this module owns is dropped first, whichever level it was\n * created at, so a table moves between levels — a row trigger left by an\n * older version becomes the statement triggers — in one transaction, with no\n * moment at which a write is announced by neither.\n *\n * `identityColumns` are what the payload names the changed rows by — see\n * {@link buildCdcFunctionSql}. Passed as trigger arguments, so one shared\n * function serves every table. Empty means \"the primary key\".\n */\nexport function buildCdcTriggerSql(\n schema: string,\n table: string,\n identityColumns: readonly string[] = [],\n level: CdcTriggerLevel = \"statement\"\n): string {\n const qualified = `${quoteIdent(schema)}.${quoteIdent(table)}`;\n const drops = ALL_TRIGGER_NAMES.map((name) => `DROP TRIGGER IF EXISTS ${quoteIdent(name)} ON ${qualified};\\n`).join(\"\");\n return drops + buildCdcTriggerCreateSql(schema, table, identityColumns, level);\n}\n\n/**\n * `CREATE TRIGGER` alone, for a table that has no capture trigger yet.\n *\n * Not {@link buildCdcTriggerSql}: its `DROP TRIGGER IF EXISTS` takes an ACCESS\n * EXCLUSIVE lock on the table even when there is nothing to drop, while\n * `CREATE TRIGGER` takes SHARE ROW EXCLUSIVE, which readers never wait for.\n */\nconst buildCdcTriggerCreateSql = (\n schema: string,\n table: string,\n identityColumns: readonly string[],\n level: CdcTriggerLevel\n): string => {\n const qualified = `${quoteIdent(schema)}.${quoteIdent(table)}`;\n const args = [...new Set(identityColumns)].map(quoteLiteral).join(\", \");\n return triggersFor(level)\n .map((spec) =>\n `CREATE TRIGGER ${quoteIdent(spec.name)} ${spec.timing} ON ${qualified} ${spec.forEach} ` +\n `EXECUTE FUNCTION ${spec.fn === \"statement\" ? CDC_STATEMENT_FUNCTION : CDC_TRIGGER_FUNCTION}(${args});`)\n .join(\"\\n\");\n};\n\nexport interface CdcTableRef {\n schema: string;\n table: string;\n /**\n * The columns a change to this table is announced by — the collection's\n * key columns, or a junction's two id columns. Absent means the primary\n * key. Nothing else ever leaves the trigger; see {@link buildCdcFunctionSql}.\n */\n identityColumns?: string[];\n}\n\nexport interface ProvisionResult {\n /** Tables the trigger was successfully attached to. */\n installed: CdcTableRef[];\n /** Tables that could not be provisioned (e.g. not yet migrated), with the error. */\n skipped: Array<CdcTableRef & { reason: string }>;\n}\n\n/** `to_regprocedure` of each function, as a SQL expression. */\nconst ROW_FUNCTION_OID = `to_regprocedure(${quoteLiteral(`${CDC_TRIGGER_FUNCTION}()`)})`;\nconst STATEMENT_FUNCTION_OID = `to_regprocedure(${quoteLiteral(`${CDC_STATEMENT_FUNCTION}()`)})`;\n/** The installed statement function's `compat_until` literal, or NULL when it is not installed. */\nconst INSTALLED_COMPAT_SQL =\n `(SELECT substring(p.prosrc FROM ${quoteLiteral(COMPAT_PATTERN)}) FROM pg_proc p WHERE p.oid = ${STATEMENT_FUNCTION_OID})`;\n\n/** The window end a `compat_until` literal names, in epoch ms; `-Infinity` for none or unreadable. */\nconst compatMs = (literal: unknown): number => {\n if (typeof literal !== \"string\" || literal === \"\" || literal === \"-infinity\") return -Infinity;\n const ms = Date.parse(literal);\n return Number.isFinite(ms) ? ms : -Infinity;\n};\n\nconst compatLiteral = (ms: number): string => (Number.isFinite(ms) ? new Date(ms).toISOString() : \"-infinity\");\n\n/**\n * What a boot compares the installed functions with, so one that changes\n * nothing leaves them alone: every boot used to run `CREATE OR REPLACE` on\n * both, rewriting their catalogue rows to the same text.\n *\n * `prosrc` is the body exactly as written between the dollar quotes, so the\n * comparison is of text. The statement function's is compared without its\n * `compat_until` line: the window is read on its own, and the code is\n * current whatever window it carries. The bodies are ASCII, so the\n * comparison holds in a database of any encoding.\n */\nconst DOLLAR_QUOTE = \"$rebase_cdc$\";\nconst functionBody = (sql: string): string =>\n sql.slice(sql.indexOf(DOLLAR_QUOTE) + DOLLAR_QUOTE.length, sql.lastIndexOf(DOLLAR_QUOTE));\nconst COMPAT_LINE_SQL_PATTERN = `${COMPAT_DECLARATION}'[^']*'`;\nconst withoutCompat = (body: string): string => body.replace(new RegExp(COMPAT_LINE_SQL_PATTERN), \"\");\n\n/** Whether each installed function is the current one, as SQL columns of one row. */\nconst functionsCurrentSql = (): string =>\n `(SELECT p.prosrc = ${quoteLiteral(functionBody(buildCdcRowFunctionSql()))} ` +\n `FROM pg_proc p WHERE p.oid = ${ROW_FUNCTION_OID}) AS row_function_current, ` +\n `(SELECT regexp_replace(p.prosrc, ${quoteLiteral(COMPAT_LINE_SQL_PATTERN)}, '') = ` +\n `${quoteLiteral(withoutCompat(functionBody(buildCdcStatementFunctionSql(\"-infinity\"))))} ` +\n `FROM pg_proc p WHERE p.oid = ${STATEMENT_FUNCTION_OID}) AS statement_code_current, ` +\n `${INSTALLED_COMPAT_SQL} AS compat_until`;\n\n/** The installed functions, as {@link functionsCurrentSql} reads them. */\ninterface InstalledFunctions {\n rowCurrent: boolean;\n statementCodeCurrent: boolean;\n /** The statement function's `compat_until` literal, as written; `null` when it is not installed. */\n compatLiteral: string | null;\n}\n\nconst readInstalledFunctions = (row: Record<string, unknown> | undefined): InstalledFunctions => ({\n rowCurrent: row?.row_function_current === true,\n statementCodeCurrent: row?.statement_code_current === true,\n compatLiteral: typeof row?.compat_until === \"string\" ? row.compat_until : null\n});\n\n/**\n * The function DDL this boot needs: none when both are already exactly what\n * it would write, `compatUntil` included.\n */\nfunction functionSqlToRun(installed: InstalledFunctions | undefined, compatUntil: string, only?: { row: boolean; statement: boolean }): string | undefined {\n const sql: string[] = [];\n if ((only?.row ?? true) && !installed?.rowCurrent) sql.push(buildCdcRowFunctionSql());\n if ((only?.statement ?? true) && !(installed?.statementCodeCurrent && installed.compatLiteral === compatUntil)) {\n sql.push(buildCdcStatementFunctionSql(compatUntil));\n }\n return sql.length > 0 ? `CREATE SCHEMA IF NOT EXISTS rebase;\\n\\n${sql.join(\"\\n\\n\")}` : undefined;\n}\n\n/**\n * Replace the already-installed trigger functions with the current bodies, and\n * install nothing where there is none.\n *\n * For a process that owns the schema but is not provisioning capture on this\n * boot — `REALTIME_CDC=off`, or no direct URL to listen on. The triggers an\n * earlier boot attached keep firing whether anything consumes them or not, so\n * a database instrumented by a version whose function put whole rows on the\n * channel kept doing so, for every login that cares to `LISTEN`, until capture\n * was switched back on. The statement function keeps its compatibility\n * window. Returns whether a function was there to replace.\n */\nexport async function refreshInstalledCdcFunction(run: RawSqlRunner): Promise<boolean> {\n const rows = await run(\n `SELECT ${ROW_FUNCTION_OID} IS NOT NULL AS installed, ` +\n `${STATEMENT_FUNCTION_OID} IS NOT NULL AS statement_installed, ` +\n functionsCurrentSql()\n );\n const row = rows[0];\n const rowInstalled = row?.installed === true;\n const statementInstalled = row?.statement_installed === true;\n if (!rowInstalled && !statementInstalled) return false;\n const installed = readInstalledFunctions(row);\n const sql = functionSqlToRun(installed, compatLiteral(compatMs(installed.compatLiteral)), { row: rowInstalled, statement: statementInstalled });\n if (sql) await run(sql);\n return true;\n}\n\n/**\n * Take change capture off the database: detach every capture trigger and drop\n * both functions. Returns how many triggers were attached.\n *\n * For `REALTIME_CDC=off`. Capture switched off used to leave the triggers in\n * place, so every write to every instrumented table still ran one and sent a\n * notification on a channel nothing listened to. Nothing depends on them once\n * capture is off: a process without capture announces its own writes on the\n * cross-instance channel, and the instances that serve realtime listen there.\n *\n * `DROP FUNCTION … CASCADE` takes the triggers with it in one statement. It\n * waits for each table's lock like any trigger DDL, so it runs with the boot's\n * lock budget and is retried on a lock timeout; a database still contended\n * after that keeps its triggers and the caller reports it.\n */\nexport async function removeTriggerCdc(run: RawSqlRunner): Promise<number> {\n const rows = await run(\n \"SELECT count(*)::int AS n FROM pg_trigger t \" +\n `WHERE t.tgfoid IN (${ROW_FUNCTION_OID}, ${STATEMENT_FUNCTION_OID})`\n );\n const attached = Number(rows[0]?.n ?? 0);\n const functions = await run(\n `SELECT ${ROW_FUNCTION_OID} IS NOT NULL AS row_installed, ${STATEMENT_FUNCTION_OID} IS NOT NULL AS statement_installed`\n );\n if (attached === 0 && functions[0]?.row_installed !== true && functions[0]?.statement_installed !== true) return 0;\n await attachWithLockBudget(\n run,\n `DROP FUNCTION IF EXISTS ${CDC_TRIGGER_FUNCTION}() CASCADE;\n` +\n `DROP FUNCTION IF EXISTS ${CDC_STATEMENT_FUNCTION}() CASCADE;`\n );\n return attached;\n}\n\n/**\n * What a boot that does not provision capture does with capture an earlier\n * boot installed.\n *\n * - `capture: \"off\"` — `REALTIME_CDC=off` — removes it ({@link removeTriggerCdc}).\n * - `capture: \"not-provisioned\"` — capture is wanted but this boot could not\n * install it (no direct URL, `auto` fell back) — leaves the triggers and\n * brings the functions up to date ({@link refreshInstalledCdcFunction}),\n * since they keep firing.\n */\nexport async function settleInstalledCapture(\n run: RawSqlRunner,\n options: { capture: \"off\" | \"not-provisioned\" }\n): Promise<{ removed: number } | { refreshed: boolean }> {\n if (options.capture === \"off\") return { removed: await removeTriggerCdc(run) };\n return { refreshed: await refreshInstalledCdcFunction(run) };\n}\n\n/**\n * The columns a table's trigger is attached with: its primary key, read from\n * the catalogue, then each requested column the table actually has — and the\n * table's `relkind`, which decides its level.\n *\n * The key is always included, because it is what the consumer falls back on\n * when a requested column was named wrong — a column guessed from a field name,\n * say — and a payload naming half a composite key would address the wrong row.\n * A column that does not exist is dropped here rather than left to be skipped\n * on every write. If the catalogue cannot be read, the request stands as given.\n */\nasync function resolveIdentityColumns(run: RawSqlRunner, ref: CdcTableRef): Promise<{ identity: string[]; relkind: string | null }> {\n const requested = ref.identityColumns ?? [];\n const qualified = `${quoteIdent(ref.schema)}.${quoteIdent(ref.table)}`;\n let columns: Array<{ name: string; primary: boolean }>;\n let relkind: string | null = null;\n try {\n const rows = await run(\n \"SELECT a.attname AS name, coalesce(a.attnum = ANY (i.indkey), false) AS is_primary, c.relkind::text AS relkind \" +\n \"FROM pg_attribute a \" +\n \"JOIN pg_class c ON c.oid = a.attrelid \" +\n \"LEFT JOIN pg_index i ON i.indrelid = a.attrelid AND i.indisprimary \" +\n `WHERE a.attrelid = to_regclass(${quoteLiteral(qualified)}) AND a.attnum > 0 AND NOT a.attisdropped ` +\n \"ORDER BY a.attnum\"\n );\n columns = rows.map((row) => ({ name: String(row.name), primary: row.is_primary === true }));\n relkind = typeof rows[0]?.relkind === \"string\" ? rows[0].relkind : null;\n } catch {\n return { identity: [...new Set(requested)], relkind: null };\n }\n return { identity: identityFrom(columns, requested), relkind };\n}\n\n/** {@link resolveIdentityColumns}' rule, over columns already read. */\nfunction identityFrom(columns: Array<{ name: string; primary: boolean }>, requested: readonly string[]): string[] {\n if (columns.length === 0) return [...new Set(requested)];\n const existing = new Set(columns.map((column) => column.name));\n return [...new Set([\n ...columns.filter((column) => column.primary).map((column) => column.name),\n ...requested.filter((column) => existing.has(column))\n ])];\n}\n\n/** One installed trigger with a name this module owns, as the catalogue has it. */\ninterface InstalledTrigger {\n name: string;\n /** Which of this module's functions it calls, if either. */\n fn: \"row\" | \"statement\" | \"other\";\n type: number;\n /** `tgenabled`: `O` is the default — fires on origin and local writes. */\n enabled: string;\n /** The arguments it was attached with, in order. */\n args: string[];\n /** A `WHEN` clause, an `UPDATE OF` column list or a constraint trigger: none of which this module creates. */\n qualified: boolean;\n newTable: string | null;\n oldTable: string | null;\n}\n\n/** One table's kind, columns and installed capture triggers, as the catalogue has them. */\ninterface CaptureState {\n /** `pg_class.relkind`, or `null` when the table does not exist. */\n relkind: string | null;\n /** In `attnum` order. Empty when the table does not exist. */\n columns: Array<{ name: string; primary: boolean }>;\n /** The installed triggers named like this module's, at either level. */\n triggers: InstalledTrigger[];\n}\n\n/** `tgargs` is each argument followed by a NUL byte; read as hex so no byte is lost to an encoding. */\nconst decodeTriggerArgs = (hex: string): string[] => {\n if (!hex) return [];\n const parts = Buffer.from(hex, \"hex\").toString(\"utf8\").split(\"\\0\");\n parts.pop();\n return parts;\n};\n\n/**\n * Every listed table's kind, columns and capture triggers, and the installed\n * statement function's compatibility window, in one catalogue query.\n *\n * It replaced a key read per table followed by an unconditional `DROP TRIGGER;\n * CREATE TRIGGER` per table: 2N round trips, N commits and N ACCESS EXCLUSIVE\n * locks on every boot, measured holding a live `SELECT` behind the boot for\n * 4.4 s while a report read the table.\n */\nasync function readCaptureState(\n run: RawSqlRunner,\n refs: CdcTableRef[]\n): Promise<{ tables: Map<string, CaptureState>; compatUntil: number; functions: InstalledFunctions }> {\n const targets = refs\n .map((ref) => `(${quoteLiteral(`${ref.schema}.${ref.table}`)}, to_regclass(${quoteLiteral(`${quoteIdent(ref.schema)}.${quoteIdent(ref.table)}`)}))`)\n .join(\", \");\n const rows = await run(\n `WITH target(key, rel) AS (VALUES ${targets})\n SELECT t.key,\n (SELECT c.relkind::text FROM pg_class c WHERE c.oid = t.rel) AS relkind,\n (SELECT coalesce(json_agg(json_build_object('name', a.attname,\n 'primary', coalesce(a.attnum = ANY (i.indkey), false))\n ORDER BY a.attnum), '[]'::json)\n FROM pg_attribute a\n LEFT JOIN pg_index i ON i.indrelid = a.attrelid AND i.indisprimary\n WHERE a.attrelid = t.rel AND a.attnum > 0 AND NOT a.attisdropped) AS columns,\n (SELECT coalesce(json_agg(json_build_object(\n 'name', tr.tgname,\n 'fn', CASE WHEN tr.tgfoid = ${ROW_FUNCTION_OID} THEN 'row'\n WHEN tr.tgfoid = ${STATEMENT_FUNCTION_OID} THEN 'statement'\n ELSE 'other' END,\n 'type', tr.tgtype::int,\n 'enabled', tr.tgenabled::text,\n 'args', encode(tr.tgargs, 'hex'),\n 'qualified', tr.tgqual IS NOT NULL OR coalesce(array_length(tr.tgattr::int2[], 1), 0) > 0\n OR tr.tgconstraint <> 0,\n 'newTable', tr.tgnewtable,\n 'oldTable', tr.tgoldtable)), '[]'::json)\n FROM pg_trigger tr\n WHERE tr.tgrelid = t.rel AND tr.tgname IN (${ALL_TRIGGER_NAMES.map(quoteLiteral).join(\", \")})) AS triggers,\n f.*\n FROM target t, (SELECT ${functionsCurrentSql()}) f`\n );\n const parseJson = <T>(value: unknown): T => (typeof value === \"string\" ? JSON.parse(value) : value) as T;\n const tables = new Map<string, CaptureState>();\n let compatUntil = -Infinity;\n for (const row of rows) {\n const columns = parseJson<Array<{ name: string; primary: boolean }> | null>(row.columns) ?? [];\n const triggers = parseJson<Array<Record<string, unknown>> | null>(row.triggers) ?? [];\n compatUntil = Math.max(compatUntil, compatMs(row.compat_until));\n tables.set(String(row.key), {\n relkind: typeof row.relkind === \"string\" ? row.relkind : null,\n columns: columns.map((column) => ({ name: String(column.name), primary: column.primary === true })),\n triggers: triggers.map((trigger) => ({\n name: String(trigger.name),\n fn: trigger.fn === \"row\" || trigger.fn === \"statement\" ? trigger.fn : \"other\",\n type: Number(trigger.type),\n enabled: String(trigger.enabled),\n args: decodeTriggerArgs(typeof trigger.args === \"string\" ? trigger.args : \"\"),\n qualified: trigger.qualified === true,\n newTable: typeof trigger.newTable === \"string\" ? trigger.newTable : null,\n oldTable: typeof trigger.oldTable === \"string\" ? trigger.oldTable : null\n }))\n });\n }\n return { tables, compatUntil, functions: readInstalledFunctions(rows[0]) };\n}\n\n/** Whether `trigger` is exactly what {@link buildCdcTriggerSql} creates for `spec`. */\nconst matchesSpec = (trigger: InstalledTrigger, spec: CdcTriggerSpec, args: readonly string[]): boolean =>\n trigger.fn === spec.fn &&\n trigger.type === spec.type &&\n // A trigger someone disabled by hand is not current: re-creating it is\n // what every boot did before, and what turns capture back on.\n trigger.enabled === \"O\" &&\n !trigger.qualified &&\n trigger.newTable === spec.newTable &&\n trigger.oldTable === spec.oldTable &&\n trigger.args.length === args.length &&\n trigger.args.every((arg, i) => arg === args[i]);\n\n/**\n * Whether the installed triggers are exactly the ones {@link buildCdcTriggerSql}\n * would create at `level` — each of them, and none of the other level's.\n */\nconst triggersAreCurrent = (installed: readonly InstalledTrigger[], identity: readonly string[], level: CdcTriggerLevel): boolean => {\n const args = [...new Set(identity)];\n const wanted = triggersFor(level);\n if (installed.length !== wanted.length) return false;\n return wanted.every((spec) => {\n const trigger = installed.find((t) => t.name === spec.name);\n return trigger !== undefined && matchesSpec(trigger, spec, args);\n });\n};\n\n/**\n * Run one table's trigger DDL with a lock budget, retrying a lock timeout.\n *\n * `lock_timeout` because a `DROP TRIGGER` waits for ACCESS EXCLUSIVE, and every\n * live query on the table queues behind a waiting lock — see `schema/boot-ddl`.\n * A table still contended after the last attempt is reported by the caller like\n * any other table it could not instrument; its existing trigger, if any, is\n * untouched and keeps firing.\n */\nasync function attachWithLockBudget(run: RawSqlRunner, sql: string): Promise<void> {\n for (let attempt = 1; ; attempt++) {\n try {\n await run(`SET LOCAL lock_timeout = '${BOOT_LOCK_TIMEOUT_MS}ms';\\n${sql}`);\n return;\n } catch (err) {\n if (!isLockTimeout(err) || attempt >= BOOT_LOCK_ATTEMPTS) throw err;\n await sleep(500 * attempt * (1 + Math.random()));\n }\n }\n}\n\n/**\n * Idempotently install the CDC trigger functions and per-table triggers.\n *\n * A plain table gets the statement-level triggers, a partitioned or foreign\n * one the row trigger ({@link levelForRelkind}). A table whose triggers are\n * already exactly the ones this would create — same function, same events,\n * enabled, same identity arguments — is left alone; one with a trigger\n * missing, disabled, attached with different arguments or at the other level\n * is (re-)attached, all of it in one transaction.\n *\n * Converting a table from a row trigger an older version attached opens the\n * statement function's compatibility window ({@link CDC_ROW_COMPAT_MS}): the\n * instances that attached it may still be listening.\n *\n * Runs as the owner (server) connection at bootstrap. A table that does not yet\n * exist in the database (schema drift) is skipped with a warning rather than\n * aborting the whole install, so one un-migrated collection cannot disable CDC\n * for the rest.\n */\nexport async function provisionTriggerCdc(\n run: RawSqlRunner,\n tables: CdcTableRef[],\n options: { now?: number } = {}\n): Promise<ProvisionResult> {\n const now = options.now ?? Date.now();\n\n // One trigger set per managed table. A table listed more than once — a\n // junction, once per direction of its relation — is attached once, with\n // every identity column any listing asked for.\n const byTable = new Map<string, CdcTableRef>();\n for (const ref of tables) {\n const key = `${ref.schema}.${ref.table}`;\n const existing = byTable.get(key);\n if (!existing) {\n byTable.set(key, { ...ref, identityColumns: [...(ref.identityColumns ?? [])] });\n } else {\n existing.identityColumns = [...new Set([...(existing.identityColumns ?? []), ...(ref.identityColumns ?? [])])];\n }\n }\n const installed: CdcTableRef[] = [];\n const skipped: ProvisionResult[\"skipped\"] = [];\n\n // What is already attached, read once. Triggers that are exactly the ones\n // this would create are left alone: the functions below are replaced in\n // place (`CREATE OR REPLACE` keeps their OIDs), so an attached trigger\n // picks up a new body without being re-created. If the read itself fails,\n // every table takes the old path — read its key, drop and re-create.\n let states: Map<string, CaptureState> | undefined;\n let installedCompat = -Infinity;\n let functions: InstalledFunctions | undefined;\n try {\n if (byTable.size > 0) {\n const read = await readCaptureState(run, [...byTable.values()]);\n states = read.tables;\n installedCompat = read.compatUntil;\n functions = read.functions;\n } else {\n states = new Map();\n }\n } catch (err) {\n logger.debug(\"[CDC] Could not read the installed triggers in one query; re-attaching each table\", {\n detail: err instanceof Error ? err.message : String(err)\n });\n }\n\n // A row trigger on a table that is to get statement triggers was attached\n // by an older version, whose instances may still be listening. Unknown\n // when the read failed, so assumed.\n const known = states;\n const convertsFromRows = !known || [...byTable.keys()].some((key) => {\n const state = known.get(key);\n return state !== undefined && levelForRelkind(state.relkind) === \"statement\" &&\n state.triggers.some((trigger) => trigger.name === CDC_TRIGGER_NAME);\n });\n const compatUntil = convertsFromRows ? Math.max(installedCompat, now + CDC_ROW_COMPAT_MS) : installedCompat;\n\n // The shared trigger functions (once), unless they are already exactly these.\n const functionSql = functionSqlToRun(functions, compatLiteral(compatUntil));\n if (functionSql) await run(functionSql);\n\n let unchanged = 0;\n for (const [key, ref] of byTable) {\n try {\n const state = states?.get(key);\n if (state) {\n const identity = identityFrom(state.columns, ref.identityColumns ?? []);\n const level = levelForRelkind(state.relkind);\n if (triggersAreCurrent(state.triggers, identity, level)) {\n installed.push(ref);\n unchanged++;\n continue;\n }\n await attachWithLockBudget(run, state.triggers.length > 0\n ? buildCdcTriggerSql(ref.schema, ref.table, identity, level)\n : buildCdcTriggerCreateSql(ref.schema, ref.table, identity, level));\n } else {\n const { identity, relkind } = await resolveIdentityColumns(run, ref);\n await attachWithLockBudget(run, buildCdcTriggerSql(ref.schema, ref.table, identity, levelForRelkind(relkind)));\n }\n installed.push(ref);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n skipped.push({ ...ref, reason });\n logger.warn(\n `⚠️ [CDC] Could not attach change-capture trigger to \"${key}\" — ` +\n `is the table migrated? Writes to it won't emit database-level events.`,\n { detail: reason }\n );\n }\n }\n\n // Wiring detail. The single `Realtime source = …` line in the\n // bootstrapper is the fact a developer acts on; how many triggers it\n // took is for a diagnosis, and the skipped-table warning above still\n // fires on its own.\n logger.debug(\n `📡 [CDC] Trigger-based change capture provisioned on ${installed.length} table(s)` +\n (unchanged ? ` (${unchanged} already current)` : \"\") +\n (skipped.length ? ` (${skipped.length} skipped)` : \"\") +\n (compatUntil > now ? `; one row per notification until ${compatLiteral(compatUntil)}` : \"\") + \".\"\n );\n\n return { installed, skipped };\n}\n","import { logger } from \"@rebasepro/server\";\nimport { CDC_CHANNEL } from \"./trigger-cdc\";\nimport { PgNotifyListener, type PgNotifyListenerStatus } from \"../pg-notify-listener\";\n\n/**\n * A single database change captured by the CDC triggers and delivered over the\n * `rebase_cdc` NOTIFY channel — one row of a notification, which may name many.\n */\nexport interface CdcChangeEvent {\n schema: string;\n table: string;\n /** `TRUNCATE` changed every row, and `row` is empty. */\n op: \"INSERT\" | \"UPDATE\" | \"DELETE\" | \"TRUNCATE\";\n /**\n * The changed row's identity, by column: its key, and for a junction table\n * the two ids naming the child list (from NEW for insert/update, OLD for\n * delete). Never the row's other values — any login can LISTEN; see\n * `buildCdcFunctionSql`.\n */\n row: Record<string, unknown>;\n /** True when even the key was too large to notify, so `row` is empty. */\n truncated?: boolean;\n}\n\nconst OPS = new Set([\"INSERT\", \"UPDATE\", \"DELETE\", \"TRUNCATE\"]);\n\n/**\n * Parse a `rebase_cdc` NOTIFY payload into the changes it names, one per row.\n * Returns `null` for anything malformed so a single bad message can never\n * crash the listener.\n *\n * Reads every shape a capture trigger sends, whichever version installed it —\n * during a rolling deploy the database's triggers and this instance need not\n * be the same version:\n * - `{ schema, table, op, row }` — one row: the row trigger, and a statement\n * that changed one row;\n * - `{ schema, table, op, cols, keys }` — a statement's rows in a batch. With\n * one column a key is the value; with several, an array in `cols` order;\n * - `{ schema, table, op: \"TRUNCATE\" }`.\n */\nexport function parseCdcPayload(payload: string): CdcChangeEvent[] | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n\n const obj = parsed as Record<string, unknown>;\n const schema = typeof obj.schema === \"string\" ? obj.schema : undefined;\n const table = typeof obj.table === \"string\" ? obj.table : undefined;\n const op = obj.op;\n if (!schema || !table) return null;\n if (typeof op !== \"string\" || !OPS.has(op)) return null;\n const kind = op as CdcChangeEvent[\"op\"];\n\n if (kind === \"TRUNCATE\") return [{ schema, table, op: kind, row: {}, truncated: false }];\n\n if (Array.isArray(obj.keys)) {\n const cols = obj.cols;\n if (!Array.isArray(cols) || cols.length === 0 || !cols.every((col) => typeof col === \"string\")) return null;\n const names = cols as string[];\n const events: CdcChangeEvent[] = [];\n for (const key of obj.keys) {\n let row: Record<string, unknown>;\n if (names.length === 1) {\n row = { [names[0]]: key };\n } else {\n if (!Array.isArray(key) || key.length !== names.length) return null;\n row = {};\n names.forEach((name, i) => { row[name] = key[i]; });\n }\n events.push({ schema, table, op: kind, row, truncated: false });\n }\n return events;\n }\n\n const row = obj.row && typeof obj.row === \"object\" && !Array.isArray(obj.row) ? (obj.row as Record<string, unknown>) : {};\n return [{\n schema,\n table,\n op: kind,\n row,\n truncated: obj.truncated === true\n }];\n}\n\n/**\n * Dedicated Postgres LISTEN client for database-level CDC.\n *\n * A {@link PgNotifyListener} — a connection outside the Drizzle pool that stays\n * open and repairs itself — plus the parsing that turns a `rebase_cdc` payload\n * into a change event. Each backend instance runs one, so every instance\n * observes every committed change regardless of which instance (or external\n * process) made the write.\n */\nexport class CdcListener {\n private readonly listener: PgNotifyListener;\n\n /**\n * @param onReconnect Called when the connection is listening again after a\n * drop. Every change committed in the gap was notified to nobody, so\n * this is where subscribers are told to look again.\n */\n constructor(\n connectionString: string,\n onEvents: (events: CdcChangeEvent[]) => void | Promise<void>,\n onReconnect?: () => void,\n applicationName?: string\n ) {\n this.listener = new PgNotifyListener({\n connectionString,\n channel: CDC_CHANNEL,\n logLabel: \"[CDC]\",\n onReconnect,\n applicationName,\n onPayload: (payload) => {\n const events = parseCdcPayload(payload);\n if (!events) {\n logger.warn(\"⚠️ [CDC] Dropping unparseable change notification.\");\n return;\n }\n return onEvents(events);\n }\n });\n }\n\n /**\n * Connect and begin listening. Idempotent.\n *\n * The **initial** connection is validated synchronously: if it cannot be\n * established (or `LISTEN` is refused), this rejects so callers — notably\n * `REALTIME_CDC=auto` — can detect an unusable connection and fall back to\n * app-level realtime. Once the initial connection succeeds, later drops\n * self-heal in the background.\n */\n async start(): Promise<void> {\n if (this.listener.active) {\n logger.warn(\"⚠️ [CDC] CdcListener.start() called but already running. Ignoring.\");\n return;\n }\n await this.listener.start();\n }\n\n /** Stop listening and release the connection. */\n async stop(): Promise<void> {\n await this.listener.stop();\n }\n\n /** Listening on a connection that answered its last heartbeat — see {@link PgNotifyListener}. */\n get connected(): boolean {\n return this.listener.connected;\n }\n\n status(): PgNotifyListenerStatus {\n return this.listener.status();\n }\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","/**\n * The server's DDL bootstrapper, over a Drizzle handle.\n *\n * `createDdlBootstrapper` in `@rebasepro/server` wants a plain\n * `(sql: string) => Promise<rows>`; the driver's internal stores hold a Drizzle\n * database. This is the adapter between them, and it exists so the retry policy\n * has exactly one definition. A second copy of the SQLSTATE list living in the\n * driver is how the two drift apart, and the drift is invisible: both versions\n * work perfectly on every single-instance deployment.\n */\nimport { sql } from \"drizzle-orm\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { createDdlBootstrapper, type DdlBootstrapper } from \"@rebasepro/server\";\nimport { sqlRows } from \"@rebasepro/common\";\n\n/**\n * A {@link DdlBootstrapper} that runs its statements through `db.execute`.\n *\n * @param db the Drizzle handle the calling store already holds\n * @param scope log prefix identifying the caller, e.g. `\"channel-presence\"`\n */\nexport function drizzleDdlBootstrapper(\n db: NodePgDatabase<Record<string, unknown>>,\n scope: string\n): DdlBootstrapper {\n return createDdlBootstrapper(async (statement: string) => {\n // `sql.raw`, because everything reaching this path is DDL assembled from\n // identifiers that were validated before they got here — there is no\n // parameter to bind, and Drizzle's tagged template would treat the whole\n // statement as one.\n return sqlRows(await db.execute(sql.raw(statement)));\n }, scope);\n}\n","/**\n * Ordered, replayable per-channel message history.\n *\n * Broadcast on its own is fire-and-forget to whoever is connected at the\n * instant it is sent: fine for presence and for \"someone saved\" notifications,\n * not enough for op-based collaborative editing, where a client that blinks\n * out for two seconds has to resync a whole document rather than catch up on\n * the four operations it missed. This adds the missing half — every retained\n * broadcast gets a per-channel sequence number, and a client can ask for\n * everything after the last one it saw.\n *\n * Three decisions worth stating, because each rules out a simpler-looking one:\n *\n * - **Retention is server-side and opt-in.** A channel is created by whoever\n * names it, so a client-supplied history depth would let any visitor commit\n * the backend to unbounded storage. And presence channels — the common case\n * — must not pay for this: with no rules configured nothing is written, no\n * table is created, and `broadcast` runs exactly the code it ran before.\n *\n * - **Sequence numbers come from the database, not from a counter in this\n * process.** They have to survive a restart and be shared across instances;\n * an in-memory counter would restart at 1 after a deploy and hand a\n * reconnecting client a replay from the wrong era, silently.\n *\n * - **The cursor row outlives the messages it numbered.** Pruning is what\n * makes retention affordable, but pruning the cursor along with the messages\n * would restart the sequence and make `sinceSeq` mean something different\n * before and after — the worst kind of bug, because replay would still\n * return rows and they would look plausible. Cursors are tiny and are kept\n * forever; see {@link prune}, which touches only `channel_messages`.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type { ChannelHistoryEntry, ChannelRetentionRule } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\nimport { revokeInternalTableSql } from \"@rebasepro/common\";\nimport { drizzleDdlBootstrapper } from \"../schema/drizzle-ddl\";\n\n/** How many messages a replay returns when the caller does not say. */\nconst DEFAULT_REPLAY_LIMIT = 200;\n\n/**\n * Hard ceiling on one replay, whatever the caller asks for.\n *\n * A reconnecting client names its own `limit`, so this is the only thing\n * standing between a stale `sinceSeq` and a single frame carrying a channel's\n * entire retained history. A client that is further behind than this is told so\n * via `latestSeq` and can decide to resync wholesale instead of paging.\n */\nconst MAX_REPLAY_LIMIT = 1000;\n\n/** Minimum gap between two prunes of the same channel. */\nconst PRUNE_THROTTLE_MS = 30_000;\n\n/**\n * Parse a retention TTL into milliseconds.\n *\n * Accepts a raw millisecond count or a short duration string (`\"30s\"`, `\"15m\"`,\n * `\"24h\"`, `\"7d\"`). Returns undefined for anything unparseable, which the\n * caller treats as \"no TTL\" — a misspelt duration must not silently become an\n * aggressive one.\n */\nexport function parseTtlMs(ttl: number | string | undefined): number | undefined {\n if (ttl === undefined || ttl === null) return undefined;\n if (typeof ttl === \"number\") return Number.isFinite(ttl) && ttl > 0 ? ttl : undefined;\n\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d)\\s*$/i.exec(ttl);\n if (!match) {\n logger.warn(`⚠️ [ChannelHistory] Ignoring unparseable retention ttl \"${ttl}\" — expected e.g. \"30s\", \"15m\", \"24h\", \"7d\".`);\n return undefined;\n }\n const value = parseFloat(match[1]);\n const unit = match[2].toLowerCase();\n const multiplier = unit === \"ms\" ? 1\n : unit === \"s\" ? 1_000\n : unit === \"m\" ? 60_000\n : unit === \"h\" ? 3_600_000\n : 86_400_000;\n const ms = value * multiplier;\n return ms > 0 ? ms : undefined;\n}\n\n/**\n * Whether `channel` is covered by `rule`.\n *\n * Exact match, or a trailing `*` acting as a prefix. Not a general glob: this\n * decides what reaches disk, and a pattern language whose reach is not obvious\n * at a glance is the wrong tool for that job.\n */\nexport function channelMatchesRule(channel: string, rule: ChannelRetentionRule): boolean {\n const pattern = rule.match;\n if (pattern === \"*\") return true;\n if (pattern.endsWith(\"*\")) return channel.startsWith(pattern.slice(0, -1));\n return channel === pattern;\n}\n\n/** A rule with its TTL already resolved to milliseconds. */\nexport interface ResolvedRetention {\n limit?: number;\n ttlMs?: number;\n}\n\n/**\n * A bus frame to send in the statement that numbers a retained message — see\n * {@link ChannelHistoryStore.append}.\n *\n * It is always a pointer, `{ kind: \"broadcast_ref\", sid, channel, seq }`, and\n * never the message. Postgres puts no privilege on `LISTEN`, so a NOTIFY payload\n * reaches every role that can connect to the database; the body stays in\n * `rebase.channel_messages`, behind the table grants, and each receiving\n * instance reads it back by `(channel, seq)` — the sender's id with it.\n */\nexport interface RetainedAnnouncement {\n /** The NOTIFY channel the bus listens on. */\n notifyChannel: string;\n /** The sending instance. */\n sid: string;\n}\n\n/**\n * Persistence and replay for retained channels.\n *\n * Inert unless constructed with at least one rule: {@link enabled} is false,\n * {@link ensureTables} does nothing, and {@link retentionFor} answers undefined\n * for every channel, so the realtime service never reaches the SQL below.\n */\nexport class ChannelHistoryStore {\n private rules: ChannelRetentionRule[];\n /**\n * Each rule with its retention resolved, once. Matched per call rather\n * than cached per channel name: a cache keyed by the names clients invent\n * grew by every one of them for the life of the process, and matching a\n * handful of prefixes costs less than the hashing a lookup does.\n */\n private resolvedRules: Array<{ rule: ChannelRetentionRule; retention: ResolvedRetention }>;\n /**\n * Channel → timestamp of its last prune, for {@link PRUNE_THROTTLE_MS}.\n * An entry older than the window throttles nothing, so they are swept once\n * a window: the map holds the channels pruned recently, not every channel\n * ever pruned.\n */\n private lastPruned = new Map<string, number>();\n private nextPruneSweepAt = 0;\n private tablesReady = false;\n\n constructor(private db: NodePgDatabase<Record<string, unknown>>, rules: ChannelRetentionRule[] = []) {\n this.rules = rules.filter(rule => {\n if (!rule?.match) {\n logger.warn(\"⚠️ [ChannelHistory] Ignoring a retention rule with no `match`.\");\n return false;\n }\n const hasBound = rule.limit !== undefined || rule.ttl !== undefined;\n if (!hasBound) {\n // Unbounded retention is almost never intended and cannot be\n // walked back once the table has grown, so it is refused rather\n // than honoured.\n logger.warn(`⚠️ [ChannelHistory] Retention rule \"${rule.match}\" sets neither \\`limit\\` nor \\`ttl\\` — ignoring it, as it would retain forever.`);\n return false;\n }\n return true;\n });\n this.resolvedRules = this.rules.map(rule => ({ rule, retention: { limit: rule.limit, ttlMs: parseTtlMs(rule.ttl) } }));\n }\n\n /** Whether any channel retains anything at all. */\n get enabled(): boolean {\n return this.rules.length > 0;\n }\n\n /**\n * The retention that applies to `channel`, or undefined when none does.\n *\n * First matching rule wins, so callers order them most-specific first.\n */\n retentionFor(channel: string): ResolvedRetention | undefined {\n if (!this.enabled || !channel) return undefined;\n return this.resolvedRules.find(({ rule }) => channelMatchesRule(channel, rule))?.retention;\n }\n\n /**\n * Create the history tables. Idempotent, and a no-op when no rule is set —\n * a deployment that never retains anything gets no schema for it.\n */\n async ensureTables(): Promise<void> {\n if (!this.enabled || this.tablesReady) return;\n\n // Contained, retrying steps rather than one straight sequence — see the\n // note on `ChannelPresenceStore.ensureTables`. The failure mode here is\n // the same and the stakes are the same: the two `REVOKE`s at the end are\n // what keep retained broadcasts off the end-user role, and a lost create\n // race used to skip them.\n const ddl = drizzleDdlBootstrapper(this.db, \"channel-history\");\n\n await ddl.ensureObject(\"rebase schema\", \"CREATE SCHEMA IF NOT EXISTS rebase\");\n\n // The primary key is exactly the replay query's access path\n // (`channel = $1 AND seq > $2 ORDER BY seq`), so it needs no further\n // index of its own.\n await ddl.ensureObject(\"channel_messages table\", `\n CREATE TABLE IF NOT EXISTS rebase.channel_messages (\n channel TEXT NOT NULL,\n seq BIGINT NOT NULL,\n event TEXT NOT NULL,\n payload JSONB,\n sender_id TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n PRIMARY KEY (channel, seq)\n )\n `);\n\n // Only for the TTL arm of pruning; the limit arm rides the primary key.\n await ddl.ensureObject(\"channel_messages created_at index\", `\n CREATE INDEX IF NOT EXISTS idx_channel_messages_created\n ON rebase.channel_messages (created_at)\n `);\n\n // Never pruned — see the note at the top of this file. One row per\n // channel that has ever retained a message.\n await ddl.ensureObject(\"channel_cursors table\", `\n CREATE TABLE IF NOT EXISTS rebase.channel_cursors (\n channel TEXT PRIMARY KEY,\n last_seq BIGINT NOT NULL\n )\n `);\n\n // Retained broadcasts for every channel in one table, with no RLS: who\n // may replay a channel is decided before the read, by the channel gate\n // in `realtimeService.authorizeChannelAction` — a replay is answered\n // only for a client that has joined the channel, plus whatever an\n // installed `ChannelAuthorizer` adds. That gate is the entire reason\n // this table can sit outside the RLS model, so it fails closed; the\n // rule language it does not yet have is written up in\n // `docs/channel-authorization.md`. The driver's schema-wide\n // grant reaches these (created here, after it ran), so take the\n // privilege back.\n //\n // Driven off a probe of what exists rather than off who won each create.\n const [messagesReady, cursorsReady] = await Promise.all([\n ddl.isReadable(\"rebase.channel_messages\"),\n ddl.isReadable(\"rebase.channel_cursors\")\n ]);\n if (messagesReady) {\n await ddl.step(\"channel_messages revoke\", () =>\n this.db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"channel_messages\")))\n );\n }\n if (cursorsReady) {\n await ddl.step(\"channel_cursors revoke\", () =>\n this.db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"channel_cursors\")))\n );\n }\n\n if (!messagesReady || !cursorsReady) {\n // Left un-ready on purpose so the next call retries. Announcing\n // \"ready\" here is what would turn a half-created schema into replays\n // that answer empty forever.\n logger.warn(\n \"[ChannelHistory] Retained-channel tables are not both present; history is not ready yet.\"\n );\n return;\n }\n\n this.tablesReady = true;\n logger.info(`✅ [ChannelHistory] Retained channels ready (${this.rules.length} rule(s)).`);\n }\n\n /**\n * Append a broadcast and return the sequence number it was given.\n *\n * The sequence is allocated by the same statement that stores the message,\n * so a crash between the two is not a possibility. `ON CONFLICT DO UPDATE`\n * takes a row lock on the channel's cursor, which is what makes concurrent\n * broadcasts to one channel line up in a single order — and what keeps\n * different channels from contending with each other at all.\n */\n async append(\n channel: string,\n event: string,\n payload: unknown,\n senderId?: string,\n announce?: RetainedAnnouncement\n ): Promise<{ seq: number; at: string }> {\n const payloadJson = JSON.stringify(payload ?? null);\n // With an announcement, the bus frame is sent by the same statement\n // that takes the number. The upsert holds the channel's cursor row\n // until commit, so appends to a channel commit in sequence order — and\n // a NOTIFY is delivered at commit, in commit order. Sent afterwards, by\n // whichever instance took the number, two instances' messages reached\n // a third in whatever order their sends landed.\n //\n // The frame is the message's address and nothing else — not its event,\n // its body or its sender. Any database login can LISTEN; only a reader\n // of `rebase.channel_messages` can turn the address into the message.\n const result = announce\n ? await this.db.execute(sql`\n WITH next AS (\n INSERT INTO rebase.channel_cursors (channel, last_seq)\n VALUES (${channel}, 1)\n ON CONFLICT (channel)\n DO UPDATE SET last_seq = rebase.channel_cursors.last_seq + 1\n RETURNING last_seq\n ), stored AS (\n INSERT INTO rebase.channel_messages (channel, seq, event, payload, sender_id)\n SELECT ${channel}, next.last_seq, ${event}, ${payloadJson}::jsonb, ${senderId ?? null}\n FROM next\n RETURNING seq, created_at\n )\n SELECT stored.seq, stored.created_at, pg_notify(${announce.notifyChannel}, json_build_object(\n 'kind', 'broadcast_ref', 'sid', ${announce.sid}::text, 'channel', ${channel}::text, 'seq', stored.seq\n )::text)\n FROM stored\n `)\n : await this.db.execute(sql`\n WITH next AS (\n INSERT INTO rebase.channel_cursors (channel, last_seq)\n VALUES (${channel}, 1)\n ON CONFLICT (channel)\n DO UPDATE SET last_seq = rebase.channel_cursors.last_seq + 1\n RETURNING last_seq\n )\n INSERT INTO rebase.channel_messages (channel, seq, event, payload, sender_id)\n SELECT ${channel}, next.last_seq, ${event}, ${payloadJson}::jsonb, ${senderId ?? null}\n FROM next\n RETURNING seq, created_at\n `);\n\n const row = result.rows[0] as { seq: string | number; created_at: Date | string } | undefined;\n if (!row) throw new Error(`Failed to append to channel history for \"${channel}\"`);\n\n return {\n // BIGINT comes back as a string from node-postgres; the wire type is\n // a number, and a channel would need 2^53 messages to notice.\n seq: Number(row.seq),\n at: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at)\n };\n }\n\n /**\n * Everything retained for `channel` after `sinceSeq`, oldest first.\n *\n * `latestSeq` is reported whether or not the messages were capped, so a\n * client that is further behind than one page can tell.\n */\n async replay(\n channel: string,\n sinceSeq = 0,\n limit = DEFAULT_REPLAY_LIMIT\n ): Promise<{ messages: ChannelHistoryEntry[]; latestSeq: number }> {\n const capped = Math.max(1, Math.min(Math.floor(limit) || DEFAULT_REPLAY_LIMIT, MAX_REPLAY_LIMIT));\n const after = Number.isFinite(sinceSeq) && sinceSeq > 0 ? Math.floor(sinceSeq) : 0;\n\n const result = await this.db.execute(sql`\n SELECT seq, event, payload, sender_id, created_at\n FROM rebase.channel_messages\n WHERE channel = ${channel} AND seq > ${after}\n ORDER BY seq ASC\n LIMIT ${capped}\n `);\n\n const messages = (result.rows as Array<{\n seq: string | number;\n event: string;\n payload: unknown;\n sender_id: string | null;\n created_at: Date | string;\n }>).map(row => ({\n seq: Number(row.seq),\n event: row.event,\n payload: row.payload,\n senderId: row.sender_id ?? undefined,\n at: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at)\n }));\n\n // Read from the cursor rather than from the messages: the cursor is the\n // authority on how far the channel has got, and still says so after\n // pruning has removed the messages it counted.\n const cursor = await this.db.execute(sql`\n SELECT last_seq FROM rebase.channel_cursors WHERE channel = ${channel}\n `);\n const cursorRow = cursor.rows[0] as { last_seq: string | number } | undefined;\n const latestSeq = cursorRow ? Number(cursorRow.last_seq) : 0;\n\n return { messages, latestSeq };\n }\n\n /**\n * One retained message by its address.\n *\n * This is what makes the cross-instance pointer path work: a broadcast too\n * large to travel inside a `pg_notify` payload is already stored here, so\n * the notification carries `(channel, seq)` and each receiving instance\n * reads the body back. Returns null when the message has since been pruned\n * — a receiver that is that far behind has nothing useful to deliver, and\n * the client's own `channel_history` replay is the repair path.\n */\n async getBySeq(channel: string, seq: number): Promise<ChannelHistoryEntry | null> {\n const result = await this.db.execute(sql`\n SELECT seq, event, payload, sender_id, created_at\n FROM rebase.channel_messages\n WHERE channel = ${channel} AND seq = ${seq}\n `);\n\n const row = result.rows[0] as {\n seq: string | number;\n event: string;\n payload: unknown;\n sender_id: string | null;\n created_at: Date | string;\n } | undefined;\n if (!row) return null;\n\n return {\n seq: Number(row.seq),\n event: row.event,\n payload: row.payload,\n senderId: row.sender_id ?? undefined,\n at: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at)\n };\n }\n\n /**\n * Enforce a channel's retention bounds.\n *\n * Throttled per channel, so a burst of operations prunes once rather than\n * once per message — the cost then tracks elapsed time instead of write\n * volume, which is what makes retention affordable on a hot channel.\n */\n async prune(channel: string, retention: ResolvedRetention): Promise<number> {\n const now = Date.now();\n if (now >= this.nextPruneSweepAt) {\n for (const [name, at] of this.lastPruned) {\n if (now - at >= PRUNE_THROTTLE_MS) this.lastPruned.delete(name);\n }\n this.nextPruneSweepAt = now + PRUNE_THROTTLE_MS;\n }\n const last = this.lastPruned.get(channel) ?? 0;\n if (now - last < PRUNE_THROTTLE_MS) return 0;\n this.lastPruned.set(channel, now);\n\n let deleted = 0;\n\n if (retention.ttlMs !== undefined) {\n const result = await this.db.execute(sql`\n DELETE FROM rebase.channel_messages\n WHERE channel = ${channel}\n AND created_at < NOW() - MAKE_INTERVAL(secs => ${retention.ttlMs / 1000})\n `);\n deleted += result.rowCount ?? 0;\n }\n\n if (retention.limit !== undefined && retention.limit > 0) {\n // OFFSET past the newest `limit` rows to find the highest seq that\n // is no longer wanted, then delete everything at or below it. Fewer\n // rows than the limit leaves the subquery empty, and the comparison\n // with NULL deletes nothing.\n const result = await this.db.execute(sql`\n DELETE FROM rebase.channel_messages\n WHERE channel = ${channel}\n AND seq <= (\n SELECT seq FROM rebase.channel_messages\n WHERE channel = ${channel}\n ORDER BY seq DESC\n OFFSET ${Math.floor(retention.limit)} LIMIT 1\n )\n `);\n deleted += result.rowCount ?? 0;\n }\n\n return deleted;\n }\n\n /** Forget the prune throttle. Called on shutdown. */\n clear(): void {\n this.lastPruned.clear();\n }\n}\n","/**\n * The shared presence roster.\n *\n * Broadcast only ever needed *fan-out* to work across instances — a frame goes\n * out, whoever is connected receives it. Presence needs more than that, because\n * `presence_state` is a question (\"who is in this document?\") and a per-process\n * `Map` can only answer for the clients that happen to share a replica with the\n * asker. Two people editing the same scene through different pods would each\n * see an empty room while broadcasting cursors at each other perfectly.\n *\n * So presence gets one row per tracked client, in Postgres, readable by every\n * instance. Three consequences worth stating:\n *\n * - **The table is the roster; the in-process map is a cache of our own\n * clients.** Reads answer from the table when this store is active, so the\n * answer is the same whichever instance is asked.\n *\n * - **`last_seen` is the liveness signal, and it is already there.** The client\n * heartbeats presence every ~20 s against a 30 s window; the sweep that has\n * always reaped local stale entries now also reaps rows belonging to\n * instances that stopped writing — which is exactly what a crashed pod looks\n * like. Crash recovery is a property of the TTL, not a separate mechanism.\n *\n * - **The sweep deletes with `RETURNING`.** Whichever instance wins the delete\n * is the one that announces the departures, so a stale client produces one\n * `presence_diff` for the cluster rather than one per replica.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { revokeInternalTableSql } from \"@rebasepro/common\";\nimport { drizzleDdlBootstrapper } from \"../schema/drizzle-ddl\";\n\n/** A tracked client, as any instance sees it. */\nexport interface PresenceRow {\n channel: string;\n clientId: string;\n state: Record<string, unknown>;\n}\n\nexport class ChannelPresenceStore {\n private tablesReady = false;\n\n constructor(\n private readonly db: NodePgDatabase<Record<string, unknown>>,\n private readonly instanceId: string\n ) {}\n\n /**\n * Create the roster table. Idempotent, and safe to run on every instance at\n * once.\n *\n * Written as separate contained steps rather than one straight sequence for\n * a reason that only bites with more than one replica, which is exactly the\n * deployment shape this table exists to serve: `CREATE … IF NOT EXISTS`\n * reads the catalog and then writes to it non-atomically, so peers booting\n * together collide, and the loser used to abandon everything after it —\n * including the trailing `REVOKE`. That revoke is the only thing keeping the\n * roster off the end-user role, so losing a boot race silently left the\n * whole channel roster readable by every signed-in user.\n *\n * `tablesReady` is now set from a probe of what exists, not from having been\n * the instance that created it.\n */\n async ensureTables(): Promise<void> {\n if (this.tablesReady) return;\n\n const ddl = drizzleDdlBootstrapper(this.db, \"channel-presence\");\n\n await ddl.ensureObject(\"rebase schema\", \"CREATE SCHEMA IF NOT EXISTS rebase\");\n\n // Keyed by (channel, client_id): a client id is globally unique, so the\n // instance is a column rather than part of the identity — a client that\n // reconnects onto another replica replaces its own row instead of\n // appearing twice in the roster.\n await ddl.ensureObject(\"channel_presence table\", `\n CREATE TABLE IF NOT EXISTS rebase.channel_presence (\n channel TEXT NOT NULL,\n client_id TEXT NOT NULL,\n instance_id TEXT NOT NULL,\n state JSONB NOT NULL DEFAULT '{}'::jsonb,\n last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n PRIMARY KEY (channel, client_id)\n )\n `);\n\n // The sweep's access path; the roster read rides the primary key.\n await ddl.ensureObject(\"channel_presence last_seen index\", `\n CREATE INDEX IF NOT EXISTS idx_channel_presence_last_seen\n ON rebase.channel_presence (last_seen)\n `);\n\n // A socket's close removes its rows by client alone, which the primary\n // key — led by the channel — cannot answer: every close scanned the\n // roster, and a deploy closes every socket at once.\n await ddl.ensureObject(\"channel_presence client index\", `\n CREATE INDEX IF NOT EXISTS idx_channel_presence_client\n ON rebase.channel_presence (client_id)\n `);\n\n // The roster of every client on every channel, with no RLS — a row\n // policy has nothing to match on here, since a channel name is a string\n // a client invents rather than a row anyone owns. What guards it is the\n // channel gate in `realtimeService.authorizeChannelAction`: presence is\n // readable only to a client that has joined the channel, plus whatever\n // an installed `ChannelAuthorizer` adds. That gate is the entire reason\n // this table can sit outside the RLS model, so it fails closed —\n // see `docs/channel-authorization.md` for what it does *not*\n // yet decide. Revoke the schema-wide grant the driver handed out\n // before this table existed.\n //\n // Driven off the probe, not off who won the create: the privilege has to\n // come off whether this instance created the table or found it.\n if (await ddl.isReadable(\"rebase.channel_presence\")) {\n await ddl.step(\"channel_presence revoke\", () =>\n this.db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"channel_presence\")))\n );\n this.tablesReady = true;\n }\n }\n\n /** Record (or refresh) a client's presence. */\n async track(channel: string, clientId: string, state: Record<string, unknown>): Promise<void> {\n await this.db.execute(sql`\n INSERT INTO rebase.channel_presence (channel, client_id, instance_id, state, last_seen)\n VALUES (${channel}, ${clientId}, ${this.instanceId}, ${JSON.stringify(state ?? {})}::jsonb, NOW())\n ON CONFLICT (channel, client_id) DO UPDATE\n SET state = EXCLUDED.state,\n instance_id = EXCLUDED.instance_id,\n last_seen = NOW()\n `);\n }\n\n /** Drop one client's presence in one channel. */\n async remove(channel: string, clientId: string): Promise<void> {\n await this.db.execute(sql`\n DELETE FROM rebase.channel_presence\n WHERE channel = ${channel} AND client_id = ${clientId}\n `);\n }\n\n /** Drop a client from every channel — used when its socket closes. */\n async removeClient(clientId: string): Promise<void> {\n await this.db.execute(sql`\n DELETE FROM rebase.channel_presence WHERE client_id = ${clientId}\n `);\n }\n\n /** The global roster for a channel. */\n async roster(channel: string): Promise<Record<string, Record<string, unknown>>> {\n const result = await this.db.execute(sql`\n SELECT client_id, state FROM rebase.channel_presence WHERE channel = ${channel}\n `);\n\n const presences: Record<string, Record<string, unknown>> = {};\n for (const row of result.rows as Array<{ client_id: string; state: Record<string, unknown> | null }>) {\n presences[row.client_id] = row.state ?? {};\n }\n return presences;\n }\n\n /**\n * Reap rows this instance is not responsible for and that have gone quiet.\n *\n * Own rows are excluded because the in-process sweep already handles them —\n * and handles them better, since it can tell \"the socket is gone\" from \"the\n * heartbeat is late\". What is left is precisely the interesting case: rows\n * written by an instance that is no longer writing.\n *\n * Returns what was removed, so the caller can announce it.\n */\n async sweepStale(ttlMs: number): Promise<PresenceRow[]> {\n const result = await this.db.execute(sql`\n DELETE FROM rebase.channel_presence\n WHERE instance_id <> ${this.instanceId}\n AND last_seen < NOW() - MAKE_INTERVAL(secs => ${ttlMs / 1000})\n RETURNING channel, client_id, state\n `);\n\n return (result.rows as Array<{ channel: string; client_id: string; state: Record<string, unknown> | null }>)\n .map(row => ({ channel: row.channel, clientId: row.client_id, state: row.state ?? {} }));\n }\n\n /**\n * Remove every row this instance owns. Called on graceful shutdown so a\n * rolling deploy does not leave a TTL window of ghosts in every roster.\n */\n async removeInstance(): Promise<void> {\n await this.db.execute(sql`\n DELETE FROM rebase.channel_presence WHERE instance_id = ${this.instanceId}\n `);\n }\n}\n","/**\n * Runtime pieces of the channel bus that are not the contract itself.\n *\n * The interface, the frame shape and the implementer's contract live in\n * `@rebasepro/types` (`types/channel_bus.ts`), so a transport shipped as its own\n * package — a Redis one, say — depends on the contract and not on this database\n * adapter. They are re-exported here for convenience: code already importing\n * from the adapter should not have to know where the types are declared.\n */\n\nimport type { ChannelBusFrame } from \"@rebasepro/types\";\n\nexport type {\n ChannelBus,\n ChannelBusFrame,\n ChannelBusHandler,\n ChannelBusConfig,\n ChannelBusSetting\n} from \"@rebasepro/types\";\nexport { isChannelBusInstance } from \"@rebasepro/types\";\n\n/**\n * The default: no cross-instance delivery at all.\n *\n * This is what every deployment ran before the bus existed, and what a\n * single-instance deployment should keep running — `publish` resolves without\n * touching the network, so the broadcast path is the same handful of `ws.send`\n * calls it always was.\n */\nexport class MemoryChannelBus {\n readonly kind = \"memory\" as const;\n readonly maxFrameBytes = Infinity;\n\n async start(): Promise<void> { /* nothing to connect */ }\n\n async publish(): Promise<void> { /* nowhere to publish to */ }\n\n async stop(): Promise<void> { /* nothing to release */ }\n}\n\n/** Encoded size of a frame, for a transport's size check. */\nexport function frameByteLength(frame: ChannelBusFrame): number {\n return Buffer.byteLength(JSON.stringify(frame), \"utf8\");\n}\n","/**\n * Channel bus over Postgres LISTEN/NOTIFY.\n *\n * Chosen because it needs nothing that a Rebase deployment does not already\n * have — the same database, the same direct URL the CDC listener uses. Three\n * properties of `NOTIFY` shape everything below:\n *\n * - **8000 bytes per payload.** Presence and cursors fit with room to spare; a\n * scene snapshot does not. A message on a *retained* channel never travels\n * here at all, whatever its size: the statement that numbers it notifies a\n * pointer — the body is already in `rebase.channel_messages` with a sequence\n * number, so the receiver reads it back (`ChannelHistoryStore.append`). That\n * is the same trick the entity path uses (notify an address, refetch the\n * row), applied to a different table, and for the same reason: any database\n * login can `LISTEN`. On an ephemeral channel there is nothing to point at,\n * so the frame carries the message, and an oversized one is refused loudly\n * instead of reaching some instances and not others.\n *\n * - **A notify is a query on the primary database.** Not a slow one, but it\n * competes with the application's real queries, and that — not throughput —\n * is what actually limits this transport. Measured, it carried ~10k\n * cross-instance messages/second and stayed flat out to eight instances; what\n * it should not do is spend 10k queries/second of the database's budget on\n * cursor movement. Hence the batching below.\n *\n * - **Delivery is best-effort.** Retained channels repair themselves through\n * the client's history replay, so a lost frame costs a live update rather\n * than correctness. That is what makes coalescing safe.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\nimport { PgNotifyListener, type PgNotifyListenerStatus } from \"../pg-notify-listener\";\nimport { ChannelBus, ChannelBusFrame, ChannelBusHandler, frameByteLength } from \"./ChannelBus\";\nimport { unref } from \"@rebasepro/utils\";\n\n/** NOTIFY channel carrying channel-bus frames. */\nexport const CHANNEL_BUS_NOTIFY_CHANNEL = \"rebase_channel_bus\";\n\n/**\n * Postgres refuses a NOTIFY payload of 8000 bytes or more. The margin below it\n * is for nothing in particular — it is there so that a payload which passes this\n * check cannot fail at the server for being a few bytes over.\n */\nexport const PG_NOTIFY_MAX_PAYLOAD_BYTES = 7500;\n\n/**\n * How long a batching window stays open.\n *\n * Ten milliseconds is below the threshold where a human notices a cursor lag,\n * and it is the difference between one query per message and one query per\n * window under load. Set to 0 to disable coalescing entirely.\n */\nexport const DEFAULT_BATCH_WINDOW_MS = 10;\n\n/** JSON overhead per frame inside a batch: the wrapping array's comma. */\nconst BATCH_SEPARATOR_BYTES = 1;\n/** JSON overhead of the batch envelope itself: `{\"batch\":[]}`. */\nconst BATCH_ENVELOPE_BYTES = 12;\n\ninterface PendingFrame {\n frame: ChannelBusFrame;\n bytes: number;\n resolve: () => void;\n reject: (error: unknown) => void;\n}\n\nexport class PostgresChannelBus implements ChannelBus {\n readonly kind = \"postgres\" as const;\n readonly maxFrameBytes = PG_NOTIFY_MAX_PAYLOAD_BYTES;\n\n private listener?: PgNotifyListener;\n private readonly batchWindowMs: number;\n\n /**\n * Frames waiting for the current window to close.\n *\n * The window is opened by a publish that found none open, and that publish\n * is sent *immediately* rather than joining a batch — see {@link publish}.\n */\n private pending: PendingFrame[] = [];\n private pendingBytes = BATCH_ENVELOPE_BYTES;\n private windowTimer?: ReturnType<typeof setTimeout>;\n private stopped = false;\n\n constructor(\n private readonly db: NodePgDatabase<Record<string, unknown>>,\n private readonly connectionString: string,\n options: { batchWindowMs?: number } = {}\n ) {\n const configured = options.batchWindowMs;\n this.batchWindowMs = typeof configured === \"number\" && configured >= 0\n ? configured\n : DEFAULT_BATCH_WINDOW_MS;\n }\n\n async start(handler: ChannelBusHandler): Promise<void> {\n this.stopped = false;\n this.listener = new PgNotifyListener({\n connectionString: this.connectionString,\n channel: CHANNEL_BUS_NOTIFY_CHANNEL,\n logLabel: \"[ChannelBus]\",\n onPayload: async (payload) => {\n const frames = parseChannelBusPayload(payload);\n if (!frames.length) {\n logger.warn(\"⚠️ [ChannelBus] Dropping unparseable payload.\");\n return;\n }\n // In order: a batch preserves the sender's publish order, and a\n // retained channel's consumers rely on it.\n for (const frame of frames) await handler(frame);\n }\n });\n await this.listener.start();\n }\n\n /**\n * Publish, coalescing under load.\n *\n * The window is *leading edge*: a publish arriving when no window is open is\n * sent straight away and opens one, so an idle channel pays no added latency\n * at all. Frames arriving while it is open are collected and leave together\n * when it closes. The effect is that cost tracks elapsed time rather than\n * message count — one query per window instead of one per message — which is\n * the same shape as the retention pruning throttle, for the same reason.\n *\n * The returned promise settles when the frame has actually left, not when it\n * was queued, so the contract (\"reaches the other instances, or rejects\")\n * still holds.\n */\n async publish(frame: ChannelBusFrame): Promise<void> {\n if (this.batchWindowMs === 0 || this.stopped) {\n await this.send([frame]);\n return;\n }\n\n if (!this.windowTimer) {\n this.openWindow();\n await this.send([frame]);\n return;\n }\n\n const bytes = frameByteLength(frame) + BATCH_SEPARATOR_BYTES;\n\n // A batch is one NOTIFY payload, so the 8 KB ceiling applies to the\n // whole batch. Send what we have rather than let the frame push it over.\n if (this.pending.length && this.pendingBytes + bytes > this.maxFrameBytes) {\n this.flush();\n }\n\n return new Promise<void>((resolve, reject) => {\n this.pending.push({ frame, bytes, resolve, reject });\n this.pendingBytes += bytes;\n });\n }\n\n /** The LISTEN connection's state, for `/health` — see {@link PgNotifyListener}. */\n status(): PgNotifyListenerStatus | undefined {\n return this.listener?.status();\n }\n\n async stop(): Promise<void> {\n this.stopped = true;\n if (this.windowTimer) {\n clearTimeout(this.windowTimer);\n this.windowTimer = undefined;\n }\n // Anything still queued belongs to clients that are already waiting on\n // it; dropping it on shutdown would be a silent loss where a flush costs\n // one more query.\n this.flush();\n await this.listener?.stop();\n this.listener = undefined;\n }\n\n private openWindow(): void {\n this.windowTimer = setTimeout(() => {\n this.windowTimer = undefined;\n if (this.pending.length) {\n // Still busy: send this window's frames and open the next one,\n // so a sustained stream keeps costing one query per window.\n this.flush();\n this.openWindow();\n }\n // Otherwise leave it closed, so the next publish after a quiet\n // moment goes out immediately.\n }, this.batchWindowMs);\n\n // Housekeeping must never hold the process open.\n unref(this.windowTimer);\n }\n\n /** Send everything queued and settle the promises waiting on it. */\n private flush(): void {\n if (!this.pending.length) return;\n\n const batch = this.pending;\n this.pending = [];\n this.pendingBytes = BATCH_ENVELOPE_BYTES;\n\n this.send(batch.map(p => p.frame))\n .then(() => { for (const p of batch) p.resolve(); })\n .catch((error) => { for (const p of batch) p.reject(error); });\n }\n\n /**\n * One NOTIFY.\n *\n * A single frame goes out in the plain, unwrapped shape. That is not just\n * economy: during a rolling deploy an instance running the previous build\n * understands only that shape, and low-rate traffic — presence, the tail of\n * a session — is exactly what is flowing while pods restart. Batching only\n * appears under load, which shrinks the mixed-version window to almost\n * nothing.\n */\n private async send(frames: ChannelBusFrame[]): Promise<void> {\n if (!frames.length) return;\n const payload = frames.length === 1\n ? JSON.stringify(frames[0])\n : JSON.stringify({ batch: frames });\n\n await this.db.execute(sql`SELECT pg_notify(${CHANNEL_BUS_NOTIFY_CHANNEL}, ${payload})`);\n }\n}\n\n/**\n * Parse a bus payload into the frames it carries.\n *\n * Accepts both wire shapes — a bare frame and a `{ batch: [...] }` envelope —\n * so an instance on the new build understands one on the old. Returns an empty\n * array for anything unrecognisable: a malformed or future-versioned message\n * must never take the listener down.\n */\nexport function parseChannelBusPayload(payload: string): ChannelBusFrame[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n return [];\n }\n if (!parsed || typeof parsed !== \"object\") return [];\n\n const batch = (parsed as { batch?: unknown }).batch;\n if (Array.isArray(batch)) {\n return batch\n .map(entry => coerceFrame(entry))\n .filter((frame): frame is ChannelBusFrame => frame !== null);\n }\n\n const single = coerceFrame(parsed);\n return single ? [single] : [];\n}\n\n/**\n * Parse a single bus frame, returning null for anything that is not a frame we\n * understand.\n */\nexport function parseChannelBusFrame(payload: string): ChannelBusFrame | null {\n try {\n return coerceFrame(JSON.parse(payload));\n } catch {\n return null;\n }\n}\n\nfunction coerceFrame(value: unknown): ChannelBusFrame | null {\n if (!value || typeof value !== \"object\") return null;\n\n const obj = value as Record<string, unknown>;\n const sid = typeof obj.sid === \"string\" ? obj.sid : undefined;\n const channel = typeof obj.channel === \"string\" ? obj.channel : undefined;\n if (!sid || !channel) return null;\n\n switch (obj.kind) {\n case \"broadcast\":\n if (typeof obj.event !== \"string\") return null;\n return {\n kind: \"broadcast\",\n sid,\n channel,\n event: obj.event,\n from: typeof obj.from === \"string\" ? obj.from : undefined,\n seq: typeof obj.seq === \"number\" ? obj.seq : undefined,\n payload: obj.payload\n };\n case \"broadcast_ref\":\n if (typeof obj.seq !== \"number\") return null;\n return {\n kind: \"broadcast_ref\",\n sid,\n channel,\n from: typeof obj.from === \"string\" ? obj.from : undefined,\n seq: obj.seq\n };\n case \"presence_diff\":\n return {\n kind: \"presence_diff\",\n sid,\n channel,\n joins: (obj.joins ?? {}) as Record<string, Record<string, unknown>>,\n leaves: (obj.leaves ?? {}) as Record<string, Record<string, unknown>>\n };\n default:\n return null;\n }\n}\n","/**\n * Resolution of the channel bus from config, environment, or a supplied instance.\n *\n * Opt-in, like every other cross-cutting realtime switch here: with nothing\n * configured a deployment gets the memory bus and behaves exactly as it did\n * before this existed. Unlike `REALTIME_CDC=auto`, there is no \"try it and see\"\n * default — a bus changes where messages go, and quietly turning on a Postgres\n * NOTIFY per broadcast because a direct URL happened to be set is not a\n * decision to make on the user's behalf.\n *\n * Two transports ship, and neither adds a service to a deployment. A third is\n * not a code change here: `realtime.bus` also accepts an already-constructed\n * {@link ChannelBus}, so a transport published as its own package plugs in\n * without this file learning about it. See `@rebasepro/types` →\n * `types/channel_bus.ts` for the contract such a package implements.\n */\n\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { isChannelBusInstance, type ChannelBus, type ChannelBusConfig, type ChannelBusSetting } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\nimport { MemoryChannelBus } from \"./ChannelBus\";\nimport { PostgresChannelBus } from \"./PostgresChannelBus\";\n\nexport * from \"./ChannelBus\";\nexport {\n PostgresChannelBus,\n CHANNEL_BUS_NOTIFY_CHANNEL,\n PG_NOTIFY_MAX_PAYLOAD_BYTES,\n DEFAULT_BATCH_WINDOW_MS,\n parseChannelBusFrame,\n parseChannelBusPayload\n} from \"./PostgresChannelBus\";\n\nexport interface ChannelBusDeps {\n db: NodePgDatabase<Record<string, unknown>>;\n /**\n * Direct (non-pooled) Postgres URL for the LISTEN client. `LISTEN` is\n * session state, so behind PgBouncer in transaction mode this must be the\n * database itself and not the pooler.\n */\n directUrl?: string;\n}\n\n/**\n * Merge `REALTIME_CHANNEL_BUS` into the configured bus.\n *\n * The environment wins over a *named* built-in, so the transport can be changed\n * per deployment without a rebuild — the same reason `REALTIME_CDC` is an env\n * var. It does **not** win over a supplied instance: the env var can only name\n * transports this package knows how to construct, so honouring it there would\n * mean silently discarding the object the application handed us.\n */\nexport function resolveChannelBusSetting(\n configured?: ChannelBusSetting,\n env: NodeJS.ProcessEnv = process.env\n): ChannelBusSetting {\n const raw = (env.REALTIME_CHANNEL_BUS || \"\").trim().toLowerCase();\n\n if (isChannelBusInstance(configured)) {\n if (raw && raw !== configured.kind) {\n logger.warn(\n `⚠️ [ChannelBus] REALTIME_CHANNEL_BUS=\"${raw}\" is ignored because realtime.bus was given a ` +\n `\"${configured.kind}\" transport instance directly. Remove one of the two to make the intent clear.`\n );\n }\n return configured;\n }\n\n if (!raw) return configured ?? { type: \"memory\" };\n\n if (raw !== \"memory\" && raw !== \"postgres\") {\n logger.warn(\n `⚠️ [ChannelBus] Unknown REALTIME_CHANNEL_BUS value \"${raw}\" — expected memory|postgres, or pass a ` +\n \"ChannelBus instance as realtime.bus for a transport that ships separately. Falling back to the \" +\n \"configured bus.\"\n );\n return configured ?? { type: \"memory\" };\n }\n\n // Keep the configured options (an explicit connection string) when the env\n // var only restates the type it was already set to.\n if (configured?.type === raw) return configured;\n return raw === \"memory\" ? { type: \"memory\" } : { type: \"postgres\" };\n}\n\n/**\n * Produce the bus a setting asks for.\n *\n * An instance is handed straight back — constructing it was the application's\n * job, and this function has nothing to add. A named built-in that turns out to\n * be unusable degrades to the memory bus, with the reason logged, rather than\n * throwing: a misconfigured bus should cost a deployment its cross-instance\n * fan-out, not its ability to boot.\n */\nexport function createChannelBus(setting: ChannelBusSetting, deps: ChannelBusDeps): ChannelBus {\n if (isChannelBusInstance(setting)) return setting;\n\n switch (setting.type) {\n case \"postgres\": {\n const connectionString = setting.connectionString || deps.directUrl;\n if (!connectionString) {\n logger.warn(\n \"⚠️ [ChannelBus] realtime.bus is \\\"postgres\\\" but no direct database URL is available \" +\n \"(set DATABASE_DIRECT_URL or realtime.bus.connectionString) — channel broadcast and presence \" +\n \"stay per-instance.\"\n );\n return new MemoryChannelBus();\n }\n return new PostgresChannelBus(deps.db, connectionString, {\n batchWindowMs: setting.batchWindowMs\n });\n }\n case \"memory\":\n default:\n return new MemoryChannelBus();\n }\n}\n","import { WebSocket } from \"ws\";\nimport { EventEmitter } from \"events\";\nimport { createHash, randomUUID } from \"crypto\";\nimport { DataService } from \"./dataService\";\n\nimport { ANONYMOUS_USER_ID, FetchCollectionProps, ListenCollectionProps, ListenOneProps, DataDriver, CollectionUpdateMessage, CollectionUpdateMeta, IncludeSpec, SingleUpdateMessage, CollectionPatchMessage, WebSocketMessage, FilterValues, LogicalCondition, OrderByTuple, CollectionConfig, RebaseCallContext, resolveClientListLimit, ListLimitError } from \"@rebasepro/types\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { Pool } from \"pg\";\nimport { sql as drizzleSql } from \"drizzle-orm\";\nimport { RealtimeProvider, CollectionSubscriptionConfig, SingleSubscriptionConfig, DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { buildPropertyCallbacks, deserializeFilter, getTableName, normalizeDriverOrderBy, OrderBySpecError, parseOrderBySpecStrict, requireCallbackCollection, UnknownFilterOperatorError } from \"@rebasepro/common\";\nimport { applyAuthContext } from \"../security/rls-enforcement\";\nimport { withFieldViewer } from \"./field-viewer\";\nimport { buildJunctionLinkMap, type JunctionLink } from \"./cdc/junction-tables\";\nimport { collectionKeyColumns } from \"./cdc/identity-columns\";\nimport { ApiError, logger, rawQueryLoggingEnabled, rowETag } from \"@rebasepro/server\";\nimport { assertReadRequestReadable } from \"./read-field-access\";\nimport { sanitizeErrorForClient } from \"../utils/pg-error-utils\";\nimport { CdcListener, type CdcChangeEvent } from \"./cdc/CdcListener\";\nimport { PgNotifyListener } from \"./pg-notify-listener\";\nimport { terminateIfBacklogged } from \"./socket-liveness\";\nimport { deriveRowAddress, getPrimaryKeys, type PrimaryKeyInfo } from \"./collection-helpers\";\nimport { isNestedPath } from \"./nested-path\";\nimport { ChannelHistoryStore, type ResolvedRetention } from \"./channel-history\";\nimport { ChannelPresenceStore } from \"./channel-presence\";\nimport { CHANNEL_BUS_NOTIFY_CHANNEL, ChannelBus, ChannelBusFrame, MemoryChannelBus, PostgresChannelBus, frameByteLength } from \"./channel-bus\";\nimport type { ChannelHistoryEntry, ChannelRetentionRule, RealtimeListenerHealth, User } from \"@rebasepro/types\";\nimport { unref } from \"@rebasepro/utils\";\n\n/** Channel name used for Postgres LISTEN/NOTIFY cross-instance realtime. */\nconst PG_NOTIFY_CHANNEL = \"rebase_entity_changes\";\n\n/**\n * How every realtime LISTEN connection names itself (`application_name`,\n * followed by the instance id), so an instance can count the others serving\n * realtime on the same database.\n */\nconst REALTIME_LISTENER_APPLICATION_PREFIX = \"rebase-realtime:\";\n\n/**\n * The id of a change that names no row — a `TRUNCATE`, a key too large to\n * notify, a table with no key. Every list on the path refetches, and so does\n * every single-row subscription on it: the change may be to any row.\n */\nconst ANY_ROW = \"*\";\n\n/**\n * Auth context stored per-subscription so real-time refetches respect RLS.\n * Mirrors the session variables set by PostgresBackendDriver.withAuth().\n */\nexport interface SubscriptionAuthContext {\n uid: string;\n roles: string[];\n /**\n * Whether this session is a guest — anonymous sign-in rather than an\n * account. Carried so a refetch's policies see the same principal the\n * initial fetch did; without it a rule that excludes guests would filter on\n * the REST read and not on the frames that follow, which is the shape that\n * makes a realtime leak invisible from the surface people test.\n */\n isAnonymous?: boolean;\n /**\n * The custom claims on the socket's token, for the same reason\n * `isAnonymous` is here: a refetch has to evaluate the same policies\n * against the same principal the initial fetch did. A tenancy policy reads\n * a claim, so a subscription that carried none would answer every frame\n * from the tenant of nobody — which is no rows, silently, on a collection\n * whose first page loaded fine.\n */\n claims?: Record<string, unknown>;\n}\n\n/**\n * A driver that can build a callback context inside a transaction it did not\n * open. `PostgresBackendDriver.callContextWithin` is the one implementation.\n */\ninterface CallContextMinter {\n callContextWithin(tx: DrizzleClient, user: User): RebaseCallContext;\n}\n\nfunction mintsCallContexts(driver: DataDriver | undefined): driver is DataDriver & CallContextMinter {\n return driver !== undefined && \"callContextWithin\" in driver && typeof driver.callContextWithin === \"function\";\n}\n\n/**\n * The context a subscription frame's `afterRead` hooks run with.\n *\n * It used to be assembled here by hand — `{ user, driver, data }` cast to\n * `RebaseCallContext` — and the cast hid two faults. `data` was the realtime\n * service's own driver, the base one on the owner connection, outside the\n * transaction this frame's rows were read in: a hook enriching rows through\n * `context.data` bypassed RLS on every frame, while the REST read of the same\n * rows, whose hooks run on the caller's transaction, did not. And `client` and\n * `storageSource`, which the type declares required, were missing, so a hook\n * that signs a URL or invokes a function worked over REST and failed every\n * `.listen()`. The driver now builds it, bound to `tx`, the way the REST path\n * builds it.\n *\n * Refuses rather than falling back: the fallback IS the unscoped context.\n */\nfunction callbackContextWithin(driver: DataDriver | undefined, tx: DrizzleClient, auth: SubscriptionAuthContext): RebaseCallContext {\n if (!mintsCallContexts(driver)) {\n throw new Error(\"This driver cannot run afterRead hooks inside the subscriber's transaction.\");\n }\n return driver.callContextWithin(tx, {\n uid: auth.uid,\n roles: auth.roles,\n isAnonymous: auth.isAnonymous === true,\n // What a socket does not carry, stated rather than invented.\n displayName: null,\n email: null,\n photoURL: null,\n providerId: \"realtime\",\n ...(auth.claims ? { claims: auth.claims } : {})\n });\n}\n\n/** What a channel frame is asking to do. */\nexport type ChannelAction = \"join\" | \"broadcast\" | \"presence\" | \"history\";\n\n/** Everything an authorizer is told about the frame it is asked to allow. */\nexport interface ChannelAuthorizationRequest {\n /** The channel the frame names, exactly as the client wrote it. */\n channel: string;\n action: ChannelAction;\n /** The socket, not the principal — one user may hold several. */\n clientId: string;\n /** The socket's authenticated principal, or the anonymous one. */\n user?: SubscriptionAuthContext;\n}\n\n/**\n * The extension point for channel access rules.\n *\n * **This is deliberately not a product API yet.** The rule *language* — a\n * config key, a per-pattern DSL, how it composes with `securityRules` — is an\n * open design question (see `docs/channel-authorization.md`), and\n * inventing one here would be inventing the answer. What exists is the single\n * place every channel frame passes through, so that whatever shape the rules\n * eventually take has exactly one seam to plug into and no arm of the switch\n * can be forgotten.\n *\n * Returning `false` — or throwing — refuses the frame. It is consulted *after*\n * the membership floor below, so an authorizer can only ever narrow access,\n * never widen it.\n */\nexport type ChannelAuthorizer = (request: ChannelAuthorizationRequest) => boolean | Promise<boolean>;\n\ninterface DataDriverWithData extends DataDriver {\n data: unknown;\n}\n\ntype RealTimeListenCollectionProps = ListenCollectionProps & {\n subscriptionId: string\n};\n\n/**\n * The narrowing a collection subscription was created with, kept so that every\n * refetch answers the same query the initial fetch did.\n *\n * Named once because it used to be written out inline in five places, and a\n * field missing from one of them is accepted over the wire and then silently\n * ignored: `offset` was declared on the incoming props and never stored, so a\n * live list on page three served page one, and `logical` was never stored\n * either, so an `or(...)` subscription was pushed every row in the table.\n */\ntype StoredCollectionRequest = {\n filter?: Record<string, unknown>;\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n databaseId?: string;\n searchString?: string;\n /** Ask each row which declared search field matched — populates `_matches`. */\n searchExplain?: boolean;\n /**\n * Relations to load, exactly as a REST list would.\n *\n * A subscription could not name any, and the refetch loaded **every**\n * relation because it passed none — so `find()` returned a row with a\n * foreign key and `listen()` returned the same row with a nested object\n * where that key was. Same query, two shapes, and a client rendering both\n * saw the row change the moment a write landed.\n */\n include?: IncludeSpec;\n /** Columns to return — the same projection `?fields=` asks for. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n /**\n * A subscription to the count alone (`listenCount`): no rows are read,\n * and each frame carries `rows: []` and the `meta` with the total. A\n * server that predates it reads the rows it was asked for (the SDK asks\n * for one) and counts them as usual, so the same frame answers it.\n */\n countOnly?: boolean;\n};\n\ntype RealTimeListenEntityProps = ListenOneProps & {\n subscriptionId: string;\n /**\n * Ask for the row's `ETag` on every `single_update` frame. Opt-in, so a\n * client that does not read it costs nothing, and a server that predates\n * it ignores it — the client then gets frames without a tag, as before.\n */\n etag?: boolean;\n};\n\n/**\n * What an in-process listener asks for: the read, and who it reads as.\n * See {@link RealtimeService.startDataDriverSubscription}.\n */\nexport type DataDriverSubscriptionRequest = {\n path: string;\n /** The subscriber. Absent reads as the anonymous user. */\n authContext?: SubscriptionAuthContext;\n onError?: (error: unknown) => void;\n} & (\n | { type: \"collection\"; collectionRequest: StoredCollectionRequest }\n | { type: \"single\"; id: string | number; include?: IncludeSpec }\n);\n\n/**\n * A registered subscription, plus the two counters that order its deliveries.\n *\n * Every update a subscription delivers is a full re-fetch, and more than one\n * thing starts one for the same subscription without coordinating: the initial\n * fetch at subscribe time, and a debounced refetch per notification (app\n * mutation, cross-instance NOTIFY, or CDC). A fetch that started earlier can\n * finish later, and the delivery replaces everything the subscriber has — so\n * the subscriber goes back to the state before the change and stays there,\n * silently, until the next write to that collection.\n *\n * The debounce is not a fix for this. It collapses a burst into one refetch and\n * does nothing about two refetches that overlap: notification A fires its timer\n * and starts fetch A, notification B arrives while A is still in flight, and B's\n * timer fires and starts fetch B regardless. See class 44 in\n * `docs/bug-classes.md`.\n *\n * `started` is taken before the work, `delivered` after it — which makes the\n * last delivery *started* the last one *delivered*.\n */\ntype Subscription = {\n /** The id the client named it by — what every frame for it carries. */\n subscriptionId: string;\n clientId: string;\n /** The {@link SubscriptionGroup} whose refetch answers it. */\n groupKey: string;\n type: \"collection\" | \"single\";\n path: string;\n id?: string | number;\n // Store full collection request parameters for proper refetching\n collectionRequest?: StoredCollectionRequest;\n /**\n * The relations a single-row subscription loads into every frame — the\n * `include` a `findById` takes. A list keeps its own in `collectionRequest`.\n */\n include?: IncludeSpec;\n /** A single-row subscriber that asked for the row's `ETag` on every frame. */\n etag?: boolean;\n // Auth context for RLS — when set, refetches run in a transaction\n // with set_config('app.uid', ...) / set_config('app.user_roles', ...)\n authContext?: SubscriptionAuthContext;\n /**\n * An in-process listener's error callback, called when a refetch fails.\n * Kept on the subscription so it goes wherever the subscription goes: a\n * replaced or cancelled one takes its listener with it.\n */\n onError?: (error: unknown) => void;\n /** How many deliveries have been started for this subscription. */\n started: number;\n /** The highest started-sequence that has already reached the subscriber. */\n delivered: number;\n /**\n * A socket subscriber's last data frame, as {@link frameDigest} of what\n * follows its subscription id; unset before the first and after an error\n * frame. A refetch that reads the same rows sends it nothing — see\n * {@link sendDataFrame}.\n */\n lastFrame?: string;\n};\n\n/**\n * What `beginDelivery` returns. Called, it claims the slot for a delivery;\n * `mayReportFailure()` asks whether this delivery's failure may be reported.\n */\ntype DeliveryCheck = (() => boolean) & { mayReportFailure: () => boolean };\n\n/**\n * Subscriptions one read answers: the same path, the same request, read as the\n * same principal.\n *\n * Every delivery is a refetch under the subscriber's own scope, which is what\n * keeps a frame from carrying a row its reader may not see. It used to be one\n * refetch — and, for a list, one count — per subscription, so a write to a\n * list a thousand people had open cost two thousand transactions, queued on the\n * pool every REST request shares. Subscriptions whose refetch would run the\n * same query as the same principal get the same rows, so they share one: the\n * group's. A frame is still built from a read under the reader's own identity;\n * only who else asked the identical question changes.\n *\n * An in-process listener is a group of one — its callback receives the rows\n * themselves, and two listeners must not share an array either may mutate.\n */\ntype SubscriptionGroup = {\n key: string;\n path: string;\n type: \"collection\" | \"single\";\n /** The row a single-row group reads, as a string. */\n id?: string;\n collectionRequest?: StoredCollectionRequest;\n /** The relations a single-row group's read loads. See {@link Subscription.include}. */\n include?: IncludeSpec;\n /**\n * A list group's last total and when it was counted, reused while\n * {@link RealtimeService.recountIntervalMs} allows. See `collectionMeta`.\n */\n lastCount?: { total: number; at: number };\n /** Whether the group's frames carry the row's `ETag`. See {@link Subscription.etag}. */\n etag?: boolean;\n authContext?: SubscriptionAuthContext;\n /** The subscriptions in the group, by internal key. */\n members: Set<string>;\n /**\n * Who the next refetch is for: `\"all\"` after a change on the path, or the\n * members that asked on their own — a re-scoped subscription, say.\n */\n pending: Set<string> | \"all\";\n /** When the last change arrived; the refetch waits for a quiet debounce window. */\n lastChangeAt: number;\n /**\n * When the oldest change the next refetch answers arrived, or `undefined`\n * with nothing pending. Bounds the debounce: see `REFETCH_MAX_WAIT_MS`.\n */\n firstPendingAt?: number;\n timer?: ReturnType<typeof setTimeout>;\n};\n\n/**\n * How many group refetches may run at once: a quarter of the connection pool\n * the service shares with every REST request, and never fewer than two.\n *\n * One write to a list watched by many different principals arms a refetch\n * per principal, and they all came due in the same tick. Started together,\n * 200 of them were 200 transactions queued on a 20-connection pool, and an\n * unrelated request arriving meanwhile waited behind every one (measured: a\n * `SELECT 1` took 521 ms). Capped, the refetches queue here instead, and\n * three quarters of the pool stays free for the requests. A pool this cannot\n * read (a test double, a client that is not a `pg` pool) is taken to be the\n * default 20-connection one.\n */\nexport function refetchConcurrencyFor(db: object): number {\n const pool = \"$client\" in db && db.$client instanceof Pool ? db.$client : undefined;\n // `pg`'s own default when a pool is built without a `max`.\n const poolMax = pool ? (pool.options.max ?? 10) : 20;\n return Math.max(2, Math.floor(poolMax / 4));\n}\n\n/**\n * Exactly what a refetch binds of its subscriber — `applyAuthContext` writes\n * these GUCs and the JWT claims from them, and the field viewer reads the\n * roles — and nothing else. Roles keep their order: `app.user_roles` is\n * written in it. A subscription with no identity reads as the anonymous user.\n */\nfunction principalOf(authContext: SubscriptionAuthContext | undefined): Record<string, unknown> {\n const auth = authContext ?? { uid: ANONYMOUS_USER_ID, roles: [\"anon\"] };\n return {\n uid: auth.uid,\n roles: auth.roles ?? [],\n isAnonymous: auth.isAnonymous === true,\n claims: auth.claims\n };\n}\n\n/** `JSON.stringify` with object keys sorted, so equal requests have one spelling. */\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_key, nested: unknown) => {\n if (!nested || typeof nested !== \"object\" || Array.isArray(nested)) return nested;\n return Object.fromEntries(Object.entries(nested).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));\n });\n}\n\n/**\n * The default ceiling on subscriptions one socket may hold.\n *\n * Each collection subscription is a refetch on every write to its collection;\n * without a ceiling one socket could open tens of thousands and turn every\n * write anyone made into as many transactions. The SDK shares identical\n * subscriptions on a socket, so a real page holds one per distinct list or\n * record on screen — the admin panel's tables, forms and reference previews\n * stay well under this. See `REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET`.\n */\nexport const DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET = 1000;\n\n/**\n * Read a subscription ceiling, refusing anything that is not a positive whole\n * number. A typo here does not fall back to a default: the ceiling is a limit\n * someone chose, and silently running with another one is how it stops\n * meaning anything.\n */\nexport function parseMaxSubscriptionsPerSocket(value: unknown, source: string): number {\n const parsed = typeof value === \"string\" && value.trim() !== \"\" ? Number(value.trim()) : value;\n if (typeof parsed !== \"number\" || !Number.isInteger(parsed) || parsed < 1) {\n throw new Error(\n `${source} must be a positive whole number of subscriptions per socket — got ${JSON.stringify(value)}.`\n );\n }\n return parsed;\n}\n\n/** See `RealtimeService.REFETCH_MAX_WAIT_MS`; `REALTIME_REFETCH_MAX_WAIT_MS` moves it. */\nconst DEFAULT_REFETCH_MAX_WAIT_MS = 1000;\n\n/**\n * The smallest last total at which a group's count is reused under\n * `REALTIME_RECOUNT_INTERVAL_MS`, unless `REALTIME_RECOUNT_MIN_ROWS` says\n * otherwise. Below it a count is cheap, and stays exact.\n */\nexport const DEFAULT_RECOUNT_MIN_ROWS = 100_000;\n\n/**\n * Read one of the realtime tuning numbers (`REALTIME_REFETCH_MAX_WAIT_MS`,\n * `REALTIME_RECOUNT_INTERVAL_MS`, `REALTIME_RECOUNT_MIN_ROWS` and their\n * `realtime.*` options). A value someone set and mistyped stops the boot\n * rather than quietly becoming the default.\n */\nexport function parseRealtimeTuning(value: unknown, source: string, unit: string): number {\n const parsed = typeof value === \"string\" && value.trim() !== \"\" ? Number(value.trim()) : value;\n if (typeof parsed !== \"number\" || !Number.isInteger(parsed) || parsed < 1) {\n throw new Error(`${source} must be a positive whole number of ${unit} — got ${JSON.stringify(value)}.`);\n }\n return parsed;\n}\n\n/**\n * Read `REALTIME_MAX_SOCKETS_PER_USER` / `realtime.maxSocketsPerUser`. Like the\n * subscription ceiling, a value someone set and mistyped stops the boot rather\n * than quietly becoming no ceiling.\n */\nexport function parseMaxSocketsPerUser(value: unknown, source: string): number {\n const parsed = typeof value === \"string\" && value.trim() !== \"\" ? Number(value.trim()) : value;\n if (typeof parsed !== \"number\" || !Number.isInteger(parsed) || parsed < 1) {\n throw new Error(\n `${source} must be a positive whole number of sockets per user — got ${JSON.stringify(value)}. ` +\n \"Leave it unset for no ceiling.\"\n );\n }\n return parsed;\n}\n\n/**\n * How long a client gets to answer the close frame at shutdown before its\n * socket is dropped. `ws` would otherwise wait its own 30s close timeout, which\n * is longer than the whole shutdown budget.\n */\nconst WEBSOCKET_CLOSE_GRACE_MS = 1_000;\n\n/**\n * Close each socket with 1001, and terminate the ones still open after\n * `graceMs`. Nothing here waits: the HTTP server's close() is what waits for\n * the sockets to end.\n */\nfunction closeWebSockets(sockets: WebSocket[], graceMs: number): void {\n for (const ws of sockets) {\n if (ws.readyState === WebSocket.OPEN) ws.close(1001, \"server shutting down\");\n // A no-op on a socket that has closed by the time it fires.\n if (ws.readyState !== WebSocket.CLOSED) unref(setTimeout(() => ws.terminate(), graceMs));\n }\n}\n\n/**\n * PostgreSQL-specific realtime service.\n * Handles WebSocket connections and subscriptions for real-time row updates.\n *\n * Implements the RealtimeProvider interface for database abstraction.\n */\nexport class RealtimeService extends EventEmitter implements RealtimeProvider {\n /**\n * Declares to the multi-engine router that channel frames can be handled\n * here. Read by `createRoutedRealtimeService`, which otherwise would have to\n * guess — and guessed \"the default provider\", whichever engine that is.\n */\n public readonly supportsChannels = true;\n\n private clients = new Map<string, WebSocket>();\n /** What each socket opted into when it opened — see `socket-opt-ins.ts`. */\n private clientOptIns = new Map<string, ReadonlySet<string>>();\n\n // Broadcast channels: channel name → set of client IDs\n private channels = new Map<string, Set<string>>();\n\n // Presence: channel → Map<clientId, { state, lastSeen }>\n private presence = new Map<string, Map<string, { state: Record<string, unknown>; lastSeen: number }>>();\n\n /**\n * The reverse of {@link channels} and {@link presence}: the channels each\n * client is a member of, and those it has a presence entry in. Kept in\n * step wherever either map gains or loses a client, so a disconnect visits\n * what the client was in. It used to walk every channel on the instance:\n * with a channel per document, 5.4 ms per disconnect at 50k channels.\n */\n private channelsByClient = new Map<string, Set<string>>();\n private presenceByClient = new Map<string, Set<string>>();\n\n /**\n * Ordered, replayable history for channels that opt into it.\n *\n * Undefined until {@link configureChannelHistory} is called, and inert even\n * then unless retention rules were supplied — so presence and ephemeral\n * notification channels never touch it. See `channel-history.ts`.\n */\n private channelHistory?: ChannelHistoryStore;\n\n /**\n * One promise chain per retained channel, so that assigning a sequence\n * number and fanning the message out happen in the same order for every\n * message on that channel.\n *\n * Without it, two concurrent broadcasts can be numbered 4 and 5 by the\n * database and still reach subscribers as 5 then 4 — live order and replay\n * order would disagree, which is exactly the divergence sequence numbers\n * are supposed to rule out. Keyed by channel, so unrelated channels never\n * wait on each other.\n */\n private channelSendQueues = new Map<string, Promise<void>>();\n /** The receiving half of the same ordering: retained bus frames per channel, one at a time. */\n private channelReceiveQueues = new Map<string, Promise<void>>();\n\n /**\n * Cross-instance transport for channel frames and presence.\n *\n * Defaults to the memory bus, which publishes nowhere — so a single-instance\n * deployment runs the same fan-out it always did, with one resolved promise\n * per broadcast for company. See `channel-bus/ChannelBus.ts`.\n */\n private bus: ChannelBus = new MemoryChannelBus();\n\n /**\n * The shared presence roster, present only when a real bus is active.\n *\n * Fan-out alone is not enough for presence: `presence_state` has to answer\n * with everyone in the channel, and per-process maps can only answer for\n * this replica's clients. See `channel-presence.ts`.\n */\n private presenceStore?: ChannelPresenceStore;\n\n /** Sweeps roster rows left behind by instances that stopped heartbeating. */\n private presenceSweepInterval?: ReturnType<typeof setInterval>;\n\n /**\n * The clients that have tracked presence through this instance — the only\n * ones with roster rows to delete when their socket closes. Most sockets\n * never track presence, and each of their closes was a `DELETE` too.\n */\n private presenceTrackers = new Set<string>();\n\n /**\n * Channels whose oversized ephemeral broadcasts have already been reported,\n * so a hot channel logs the problem once rather than once per message.\n * Forgotten with the channel, so it holds live channels only — not every\n * name a client ever broadcast on.\n */\n private oversizedBroadcastWarned = new Set<string>();\n\n /**\n * Optional narrowing on top of the membership floor — see\n * {@link ChannelAuthorizer}. Unset by default, which leaves membership as\n * the whole of the rule.\n */\n private channelAuthorizer?: ChannelAuthorizer;\n\n /**\n * Whether a notification from another instance has ever arrived.\n *\n * The entity LISTEN handler sees a foreign `sid` on every cross-instance\n * change, which is proof that this deployment runs more than one pod — the\n * one fact needed to tell \"the memory bus is fine here\" from \"broadcast and\n * presence silently reach a fraction of your users\".\n */\n private foreignInstanceSeen = false;\n\n /** So the multi-pod memory-bus warning is emitted once, not once per join. */\n private memoryBusWarned = false;\n /** When the database was last asked whether another instance listens — see {@link warnIfMemoryBusOnMultiplePods}. */\n private otherInstancesCheckedAt?: number;\n /** The check in flight, so a burst of joins asks once. Awaited by tests. */\n private otherInstancesCheck?: Promise<void>;\n private static readonly OTHER_INSTANCES_CHECK_INTERVAL_MS = 5 * 60_000;\n\n private presenceInterval?: ReturnType<typeof setInterval>;\n private static readonly PRESENCE_TIMEOUT_MS = 30000; // 30s\n /** How often stale roster rows from other instances are reaped. */\n private static readonly PRESENCE_SWEEP_INTERVAL_MS = 10000; // 10s\n private dataService: DataService;\n /**\n * Every subscription, by {@link subscriptionKey} — the client's own id\n * qualified by the client that chose it.\n *\n * Keyed by the bare id, this was one namespace for every socket: two raw\n * clients that both counted from `\"sub-1\"` replaced each other's\n * subscription without a word, and either one's `unsubscribe` ended the\n * other's.\n */\n private _subscriptions = new Map<string, Subscription>();\n\n /** In-process listeners' callbacks, by subscription key. */\n private subscriptionCallbacks = new Map<string, (data: Record<string, unknown>[] | Record<string, unknown> | null) => void>();\n\n /** Each client's subscriptions, by key — for its ceiling and its disconnect. */\n private subscriptionsByClient = new Map<string, Set<string>>();\n\n /**\n * The in-process subscriptions — started by `startDataDriverSubscription`,\n * `subscribeToCollection` or `subscribeToOne` — by the id their caller\n * gave them, for {@link unsubscribe}, which has nothing else to go on. It\n * used to walk every subscription on the server, sockets' included.\n */\n private inProcessKeysById = new Map<string, Set<string>>();\n\n /** Subscriptions one refetch answers — see {@link SubscriptionGroup}. */\n private groups = new Map<string, SubscriptionGroup>();\n /**\n * Groups whose refetch is due and waiting for a slot, in the order they\n * came due. A group is in it at most once; changes that arrive while it\n * waits join the read it is waiting for, which starts after them.\n */\n private refetchQueue = new Set<SubscriptionGroup>();\n /** Group refetches running now — at most {@link maxConcurrentRefetches}. */\n private refetchesRunning = 0;\n /** See {@link refetchConcurrencyFor}. */\n public maxConcurrentRefetches: number;\n /**\n * The collection groups on each path, and the single-row groups at each\n * `path` + id: what a change looks up, instead of walking every\n * subscription on the server once per changed row.\n */\n private collectionGroupsByPath = new Map<string, Set<SubscriptionGroup>>();\n private singleGroupsByAddress = new Map<string, Set<SubscriptionGroup>>();\n /** The single-row groups on each path, for a change that names no row ({@link ANY_ROW}). */\n private singleGroupsByPath = new Map<string, Set<SubscriptionGroup>>();\n /**\n * When something on each path a list group reads last changed: the\n * debounce clock those groups share, moved once per changed row instead\n * of once per row per group. See {@link notifyPathUpdate}.\n */\n private listPathChangedAt = new Map<string, number>();\n /**\n * The list groups on each path that a change still has to schedule: new\n * ones, and those whose last refetch has started. The rest already have a\n * refetch for every member pending, which the next change only has to\n * keep waiting — the clock above does that.\n */\n private unscheduledListGroups = new Map<string, Set<SubscriptionGroup>>();\n /**\n * Every path a subscription holds, with the collection it lands on and how\n * many hold it — the candidates `aliasPaths` considers, resolved once.\n */\n private subscribedPaths = new Map<string, { slug?: string; count: number }>();\n\n /**\n * How many subscriptions one socket may hold; set from config at boot. See\n * {@link DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET}.\n */\n public maxSubscriptionsPerSocket = DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET;\n\n /**\n * The longest a change waits for its refetch, in ms; set from config at\n * boot (`REALTIME_REFETCH_MAX_WAIT_MS`). See {@link REFETCH_MAX_WAIT_MS},\n * the default.\n */\n public refetchMaxWaitMs = DEFAULT_REFETCH_MAX_WAIT_MS;\n\n /**\n * How long a list's total may be reused before it is counted again, in ms\n * (`REALTIME_RECOUNT_INTERVAL_MS`). Unset, the default, counts with every\n * frame. Only for a group whose last total was at least\n * {@link recountMinRows}: a count is cheap on a small set, and exact there.\n */\n public recountIntervalMs: number | undefined = undefined;\n\n /** See {@link recountIntervalMs} (`REALTIME_RECOUNT_MIN_ROWS`). */\n public recountMinRows = DEFAULT_RECOUNT_MIN_ROWS;\n\n private driver?: DataDriver;\n\n // ── Cross-instance LISTEN/NOTIFY ──\n /** Unique identifier for this process instance, used to skip own notifications. */\n private readonly instanceId = `inst_${randomUUID().slice(0, 8)}`;\n /** What this instance's LISTEN connections report in `pg_stat_activity`. */\n private readonly listenerApplicationName = `${REALTIME_LISTENER_APPLICATION_PREFIX}${this.instanceId}`;\n /** The LISTEN connection on {@link PG_NOTIFY_CHANNEL}, outside the Drizzle pool. */\n private crossInstanceListener?: PgNotifyListener;\n /** Whether cross-instance broadcasting is active. */\n private broadcasting = false;\n /**\n * Changes waiting to be announced on {@link PG_NOTIFY_CHANNEL}, and the\n * send that will announce them — see {@link broadcastChange}.\n */\n private pendingBroadcasts = new Map<string, { p: string; eid: string; db: string | null }>();\n private broadcastFlush?: Promise<void>;\n /** Debounce window (ms) for coalescing rapid row updates into a single correctness refetch. */\n private static readonly REFETCH_DEBOUNCE_MS = 300;\n /**\n * The longest a change waits for its refetch, quiet window or not.\n *\n * A pure debounce waits for 300 ms without a change, and a collection\n * written more often than that — an orders, events or chat table at a few\n * writes a second — never went quiet: its subscribers received nothing for\n * as long as the writes continued. Under a steady stream a group now\n * refetches once per this interval, which is the whole cost.\n */\n private static readonly REFETCH_MAX_WAIT_MS = DEFAULT_REFETCH_MAX_WAIT_MS;\n\n // ── Database-level Change Data Capture (CDC) ──\n /** Dedicated LISTEN client for DB-level change events (undefined unless CDC is enabled). */\n private cdcListener?: CdcListener;\n /** Whether database-level CDC is the active cross-instance change source. */\n private cdcActive = false;\n /** Junction table → the child lists its rows belong to, built when CDC starts. */\n private junctionLinkMap?: Map<string, JunctionLink[]>;\n\n /** Changes waiting for a turn of the event loop — see {@link handleCdcEvents}. */\n private cdcBacklog: CdcChangeEvent[][] = [];\n /** Changes handled in the current turn. */\n private cdcTurnRows = 0;\n /** How many changed rows one turn of the event loop routes: ~5–10 ms of work. */\n private static readonly CDC_ROWS_PER_TURN = 5000;\n\n /** Reverse lookup: `schema.table` (and bare `table`) → collection, built when CDC starts. */\n private cdcTableMap?: Map<string, CollectionConfig>;\n /**\n * Short-lived record of `path/id` keys this instance just fanned out via the\n * app path (a Rebase-API mutation). When CDC echoes the same committed change\n * back to *this* instance, we suppress the duplicate — the change was already\n * delivered locally. Other instances have no such record, so they still\n * deliver the CDC event. External writes (psql, cron, SQL editor) never match\n * and always flow through. Keyed → expiry timestamp (ms).\n */\n private recentAppEmits = new Map<string, number>();\n /**\n * How long an app-emit key suppresses its own CDC echo: the refetch\n * debounce, and no longer.\n *\n * The mark cannot tell an echo from another writer's change to the same\n * row, and a save that touches no row of the table (a to-many relation\n * only, an empty payload) is announced with no echo ever coming to consume\n * it. Held for seconds, it swallowed the next change psql, a cron or\n * another instance made to that row. Inside the debounce nothing is lost:\n * the refetch the app emit scheduled has not started yet — its timer fires\n * no earlier than this — and it reads after any commit whose NOTIFY has\n * already arrived. An echo later than that costs one more refetch.\n */\n private static readonly CDC_DEDUP_WINDOW_MS = RealtimeService.REFETCH_DEBOUNCE_MS;\n\n constructor(private db: NodePgDatabase<any>, private registry: PostgresCollectionRegistry) {\n super();\n this.maxConcurrentRefetches = refetchConcurrencyFor(db);\n // No call-context provider, deliberately. This service is the\n // *no-driver* fallback — the branches below that cannot apply a database\n // auth context either — and without a driver there is no context to\n // build a `beforeQuery` hook from. So a hooked collection's read here\n // refuses rather than being served unnarrowed, which is the direction a\n // fallback has to fail in: the fallbacks above it are already written\n // around not handing out *more* than the path they stand in for.\n this.dataService = new DataService(db, registry);\n }\n\n /**\n * Restricted role that auth-scoped refetches run as (via `SET LOCAL ROLE`)\n * so RLS `select` policies bind. Set by the bootstrapper alongside\n * `PostgresBackendDriver.rlsUserRole`; undefined when the connection\n * is already subject to RLS natively. Without this, realtime refetches\n * would leak rows the initial (isolated) fetch correctly hid.\n */\n public rlsUserRole?: string;\n\n /**\n * Verbose subscription tracing.\n *\n * Through the logger at `debug`, behind the same switch that lifts the\n * `Failed query:` redaction: these lines quote paths, filters and refetch\n * SQL, and a `console.debug` gated on `NODE_ENV` alone wrote them to stdout\n * whatever `LOG_LEVEL` said, without passing through the one function where\n * redaction lives.\n */\n private debugLog(...args: unknown[]) {\n if (!this.debugEnabled()) return;\n logger.debug(args.map(a => (typeof a === \"string\" ? a : JSON.stringify(a))).join(\" \"));\n }\n\n /**\n * {@link rawQueryLoggingEnabled}, read at most once a second.\n *\n * It reads two environment variables, and `process.env` is a getenv per\n * read: called three or four times per changed row, it was the largest\n * cost of a change notification here — 1.4 µs of every ~3.5 µs per row,\n * so ~1.4 s of event loop for a 1M-row statement. A switch flipped at run\n * time is honoured within a second.\n */\n private debugEnabled(): boolean {\n const now = Date.now();\n if (now - this.debugCheckedAt >= 1000) {\n this.debugFlag = rawQueryLoggingEnabled();\n this.debugCheckedAt = now;\n }\n return this.debugFlag;\n }\n private debugFlag = false;\n private debugCheckedAt = -Infinity;\n\n setDataDriver(driver: DataDriver) {\n this.driver = driver;\n }\n\n /**\n * The live subscriptions by the id their client gave them — for tests and\n * diagnostics. Two clients may use one id; this view shows the last.\n */\n get subscriptions(): ReadonlyMap<string, Subscription> {\n return new Map([...this._subscriptions.values()].map((subscription) => [subscription.subscriptionId, subscription]));\n }\n\n // =============================================================================\n // Subscription registry and refetch groups\n // =============================================================================\n\n /** The internal key of `subscriptionId` as `clientId` named it. */\n private subscriptionKey(clientId: string, subscriptionId: string): string {\n return `${clientId}\\u0000${subscriptionId}`;\n }\n\n /**\n * The group a subscription's refetch belongs to. Socket subscriptions that\n * would run the same read as the same principal share one; an in-process\n * listener is always alone — see {@link SubscriptionGroup}.\n */\n private groupKeyFor(\n key: string,\n shared: boolean,\n target: { type: \"collection\" | \"single\"; path: string; id?: string | number; collectionRequest?: StoredCollectionRequest; include?: IncludeSpec; etag?: boolean; authContext?: SubscriptionAuthContext }\n ): string {\n if (!shared) return `listener\\u0000${key}`;\n return canonicalJson({\n type: target.type,\n path: target.path,\n id: target.type === \"single\" ? String(target.id) : undefined,\n // Two readers of one row asking for different relations are two\n // reads: one frame cannot answer both.\n include: target.type === \"single\" ? target.include : undefined,\n // A frame with a tag and one without are two frames. Left out\n // when not asked, so those groups keep the key they always had.\n etag: target.type === \"single\" && target.etag === true ? true : undefined,\n request: target.type === \"collection\" ? (target.collectionRequest ?? {}) : undefined,\n principal: principalOf(target.authContext)\n });\n }\n\n /**\n * Register (or replace) a subscription under `key`, in its group and in\n * every index that finds it.\n */\n private registerSubscription(key: string, subscription: Subscription): void {\n const previous = this._subscriptions.get(key);\n if (previous) this.leaveGroup(key, previous);\n this._subscriptions.set(key, subscription);\n\n let owned = this.subscriptionsByClient.get(subscription.clientId);\n if (!owned) this.subscriptionsByClient.set(subscription.clientId, owned = new Set());\n owned.add(key);\n\n let group = this.groups.get(subscription.groupKey);\n if (!group) {\n group = {\n key: subscription.groupKey,\n path: subscription.path,\n type: subscription.type,\n ...(subscription.type === \"single\" ? { id: String(subscription.id), include: subscription.include, etag: subscription.etag } : {}),\n collectionRequest: subscription.collectionRequest,\n authContext: subscription.authContext,\n members: new Set(),\n pending: new Set(),\n lastChangeAt: 0\n };\n this.groups.set(group.key, group);\n const [index, address] = group.type === \"collection\"\n ? [this.collectionGroupsByPath, group.path]\n : [this.singleGroupsByAddress, `${group.path}\\u0000${group.id}`];\n let bucket = index.get(address);\n if (!bucket) index.set(address, bucket = new Set());\n bucket.add(group);\n if (group.type === \"collection\") {\n this.markListGroupUnscheduled(group);\n } else {\n let onPath = this.singleGroupsByPath.get(group.path);\n if (!onPath) this.singleGroupsByPath.set(group.path, onPath = new Set());\n onPath.add(group);\n }\n }\n group.members.add(key);\n\n // Counted once per registration; `leaveGroup` above took the replaced\n // one's count back.\n const held = this.subscribedPaths.get(subscription.path);\n if (held) held.count++;\n else this.subscribedPaths.set(subscription.path, { slug: this.collectionSlugAt(subscription.path), count: 1 });\n }\n\n /** Remember an in-process subscription by its caller's id — see {@link inProcessKeysById}. */\n private indexInProcess(subscriptionId: string, key: string): void {\n let keys = this.inProcessKeysById.get(subscriptionId);\n if (!keys) this.inProcessKeysById.set(subscriptionId, keys = new Set());\n keys.add(key);\n }\n\n /** Remove the subscription under `key` from everything that holds it. */\n private dropSubscription(key: string): void {\n const subscription = this._subscriptions.get(key);\n this.subscriptionCallbacks.delete(key);\n if (!subscription) return;\n this._subscriptions.delete(key);\n const inProcess = this.inProcessKeysById.get(subscription.subscriptionId);\n if (inProcess?.delete(key) && inProcess.size === 0) this.inProcessKeysById.delete(subscription.subscriptionId);\n this.leaveGroup(key, subscription);\n const owned = this.subscriptionsByClient.get(subscription.clientId);\n owned?.delete(key);\n if (owned?.size === 0) this.subscriptionsByClient.delete(subscription.clientId);\n }\n\n /** Take `subscription` out of its group and path count; a group left empty goes, timer and all. */\n private leaveGroup(key: string, subscription: Subscription): void {\n const held = this.subscribedPaths.get(subscription.path);\n if (held && --held.count <= 0) this.subscribedPaths.delete(subscription.path);\n\n const group = this.groups.get(subscription.groupKey);\n if (!group) return;\n group.members.delete(key);\n if (group.pending !== \"all\") group.pending.delete(key);\n if (group.members.size > 0) return;\n if (group.timer) clearTimeout(group.timer);\n this.refetchQueue.delete(group);\n this.groups.delete(group.key);\n const [index, address] = group.type === \"collection\"\n ? [this.collectionGroupsByPath, group.path]\n : [this.singleGroupsByAddress, `${group.path}\\u0000${group.id}`];\n const bucket = index.get(address);\n bucket?.delete(group);\n if (bucket?.size === 0) index.delete(address);\n if (group.type === \"single\") {\n const onPath = this.singleGroupsByPath.get(group.path);\n onPath?.delete(group);\n if (onPath?.size === 0) this.singleGroupsByPath.delete(group.path);\n }\n if (group.type === \"collection\") {\n this.unscheduledListGroups.get(group.path)?.delete(group);\n if (!this.collectionGroupsByPath.has(group.path)) {\n this.unscheduledListGroups.delete(group.path);\n this.listPathChangedAt.delete(group.path);\n }\n }\n }\n\n /** See {@link unscheduledListGroups}. */\n private markListGroupUnscheduled(group: SubscriptionGroup): void {\n let unscheduled = this.unscheduledListGroups.get(group.path);\n if (!unscheduled) this.unscheduledListGroups.set(group.path, unscheduled = new Set());\n unscheduled.add(group);\n }\n\n /**\n * Ask for a refetch of `group` once changes have been quiet for the\n * debounce window — for every member, or only for `member` — and in any\n * case no later than {@link REFETCH_MAX_WAIT_MS} after the first of them.\n *\n * The window is measured from the last change, as a debounce is, but the\n * timer is not re-armed per change: a 10k-row statement is 10k changes,\n * and re-arming a timer per change per group was work in proportion to\n * both. A change only moves the timestamp; the timer, when it fires early,\n * waits out the rest.\n */\n private scheduleGroupRefetch(group: SubscriptionGroup, member?: string): void {\n if (member === undefined) group.pending = \"all\";\n else if (group.pending !== \"all\") group.pending.add(member);\n const now = Date.now();\n group.lastChangeAt = now;\n group.firstPendingAt ??= now;\n // A group already waiting for a slot needs no timer: its read starts\n // after this change, and answers it.\n if (!group.timer && !this.refetchQueue.has(group)) this.armGroupTimer(group, RealtimeService.REFETCH_DEBOUNCE_MS);\n }\n\n private armGroupTimer(group: SubscriptionGroup, delayMs: number): void {\n group.timer = setTimeout(() => {\n group.timer = undefined;\n if (this.groups.get(group.key) !== group) return;\n const now = Date.now();\n const lastChangeAt = group.type === \"collection\"\n ? Math.max(group.lastChangeAt, this.listPathChangedAt.get(group.path) ?? 0)\n : group.lastChangeAt;\n const quietFor = now - lastChangeAt;\n const waitedFor = now - (group.firstPendingAt ?? now);\n if (quietFor < RealtimeService.REFETCH_DEBOUNCE_MS && waitedFor < this.refetchMaxWaitMs) {\n this.armGroupTimer(group, Math.min(\n RealtimeService.REFETCH_DEBOUNCE_MS - quietFor,\n this.refetchMaxWaitMs - waitedFor\n ));\n return;\n }\n this.refetchQueue.add(group);\n this.drainRefetchQueue();\n }, delayMs);\n }\n\n /**\n * Start queued group refetches while fewer than\n * {@link maxConcurrentRefetches} are running. Each one that settles starts\n * the next, so the fan-out of one write waits its turn here rather than\n * in the pool, ahead of every REST request.\n */\n private drainRefetchQueue(): void {\n while (this.refetchesRunning < this.maxConcurrentRefetches) {\n const next = this.refetchQueue.values().next();\n if (next.done) return;\n const group = next.value;\n this.refetchQueue.delete(group);\n if (this.groups.get(group.key) !== group) continue;\n this.refetchesRunning++;\n void this.refetchGroup(group).finally(() => {\n this.refetchesRunning--;\n this.drainRefetchQueue();\n });\n }\n }\n\n /**\n * One read for the group, delivered to each member it is for.\n *\n * Each member still claims its own delivery slot before the read and\n * checks it after (see {@link beginDelivery}), so a member that left, was\n * replaced or was overtaken while the read ran is skipped exactly as it\n * was when it had a read of its own. A socket frame is serialised once and\n * only its subscription id differs per member.\n */\n private async refetchGroup(group: SubscriptionGroup): Promise<void> {\n const keys = group.pending === \"all\" ? [...group.members] : [...group.pending];\n group.pending = new Set();\n group.firstPendingAt = undefined;\n // A change from here on is one this read may not have seen.\n if (group.type === \"collection\" && this.groups.get(group.key) === group) this.markListGroupUnscheduled(group);\n\n const targets: Array<{ key: string; subscription: Subscription; canDeliver: DeliveryCheck; callback?: (data: Record<string, unknown>[] | Record<string, unknown> | null) => void }> = [];\n for (const key of keys) {\n const subscription = this._subscriptions.get(key);\n if (!subscription || subscription.groupKey !== group.key) continue;\n if (subscription.clientId === \"driver\") {\n const callback = this.subscriptionCallbacks.get(key);\n if (!callback) continue;\n targets.push({ key, subscription, canDeliver: this.beginDelivery(key, subscription), callback });\n } else if (this.clients.has(subscription.clientId)) {\n targets.push({ key, subscription, canDeliver: this.beginDelivery(key, subscription) });\n }\n }\n if (targets.length === 0) return;\n\n try {\n if (group.type === \"collection\") {\n const request = group.collectionRequest ?? {};\n const toSockets = targets.some((target) => !target.callback);\n const { rows, meta } = await this.fetchCollectionWithAuth(group.path, request, group.authContext, { meta: toSockets, group });\n let frame: { body: string; digest: string } | undefined;\n for (const target of targets) {\n if (!target.canDeliver()) continue;\n if (target.callback) {\n target.callback(rows);\n continue;\n }\n frame ??= this.dataFrame(this.collectionFrameTail(rows, group.path, meta));\n this.sendDataFrame(target.subscription, \"collection_update\", frame);\n }\n } else {\n const { row, etag } = await this.fetchEntityFrame(group.path, group.id!, group.authContext, group.include, group.etag === true);\n let frame: { body: string; digest: string } | undefined;\n for (const target of targets) {\n if (!target.canDeliver()) continue;\n if (target.callback) {\n target.callback(row);\n continue;\n }\n frame ??= this.dataFrame(this.singleFrameTail(row, etag));\n this.sendDataFrame(target.subscription, \"single_update\", frame);\n }\n }\n } catch (error) {\n for (const target of targets) {\n if (target.callback) {\n logger.error(`❌ [RealtimeService] Error in a refetch for DataDriver subscription ${target.subscription.subscriptionId}`, { error });\n this.reportDriverFetchFailure(target.subscription, target.canDeliver, error);\n } else {\n this.reportSocketFetchFailure(target.subscription.clientId, target.subscription.subscriptionId, group.path, error, target.canDeliver, target.subscription);\n }\n }\n }\n }\n\n /**\n * Claim a delivery slot for a subscription, before doing the work.\n *\n * Returns the check to run immediately before delivering. It refuses in\n * three cases, all of which used to deliver:\n *\n * - **Out of order.** A newer refetch has already delivered, so this one is\n * stale — the subscriber would go back to the state before the change.\n * - **Unsubscribed.** The subscription was cancelled while the fetch was in\n * flight. The `has(subscriptionId)` check the debounced refetches ran\n * *before* the await cannot answer this; only a check after it can.\n * - **Replaced.** The same id can name a *different* subscription by the\n * time a fetch lands — a re-subscribe overwrites the map entry, and the\n * old filter's rows would be delivered to the new subscriber.\n *\n * The last two are identity, not presence: the map has to still hold *this\n * exact object*, not merely something under this id.\n *\n * A failure goes through the same slot, by `mayReportFailure()`: an error\n * frame from a stale, cancelled or replaced fetch would mark a view that\n * already shows newer rows as failed, or fail another subscription's view.\n * It also answers yes to the delivery that already claimed the slot, since\n * that is the send itself failing (a row that will not serialise) after the\n * check passed and before anything reached the subscriber.\n */\n private beginDelivery(key: string, subscription: Subscription): DeliveryCheck {\n const seq = ++subscription.started;\n const canDeliver = () => {\n if (this._subscriptions.get(key) !== subscription) return false;\n if (seq <= subscription.delivered) return false;\n subscription.delivered = seq;\n return true;\n };\n const mayReportFailure = () =>\n canDeliver() || (this._subscriptions.get(key) === subscription && subscription.delivered === seq);\n return Object.assign(canDeliver, { mayReportFailure });\n }\n\n /**\n * Start an in-process listener: register it, then deliver its first rows.\n *\n * The first fetch runs here, as the subscriber and through a delivery slot,\n * the way every refetch after it does. `PostgresBackendDriver.listen*` used\n * to run it themselves, outside this service, and got all three wrong: a\n * listener on `withAuth(user)` read its first rows on the owner connection,\n * so it was handed rows its policies deny; a slow first fetch landed over a\n * newer refetch, and after an unsubscribe; and a row listener never heard a\n * `null`. The subscriber's identity is part of the request because a\n * subscription registered first and stamped with it afterwards has a first\n * fetch that ran before the stamp.\n *\n * No `authContext` reads as the anonymous user, first fetch and refetches\n * alike — see {@link fetchCollectionWithAuth}.\n */\n startDataDriverSubscription(\n subscriptionId: string,\n request: DataDriverSubscriptionRequest,\n callback: (data: Record<string, unknown>[] | Record<string, unknown> | null) => void\n ): void {\n this.debugLog(\"📋 [RealtimeService] Starting DataDriver subscription:\", subscriptionId, request.authContext ? \"(with auth)\" : \"(no auth)\");\n const key = this.subscriptionKey(\"driver\", subscriptionId);\n const subscription: Subscription = {\n subscriptionId,\n clientId: \"driver\",\n groupKey: this.groupKeyFor(key, false, request),\n ...request,\n started: 0,\n delivered: 0\n };\n this.registerSubscription(key, subscription);\n this.indexInProcess(subscriptionId, key);\n this.subscriptionCallbacks.set(key, callback);\n void this.deliverFirstToDataDriver(key, subscription, callback);\n }\n\n private async deliverFirstToDataDriver(\n key: string,\n subscription: Subscription,\n callback: (data: Record<string, unknown>[] | Record<string, unknown> | null) => void\n ): Promise<void> {\n // Claimed before the fetch, as the socket's first fetch claims it: this\n // is the oldest read, so a refetch started while it runs outranks it.\n const canDeliver = this.beginDelivery(key, subscription);\n try {\n const data = subscription.type === \"collection\"\n ? (await this.fetchCollectionWithAuth(subscription.path, subscription.collectionRequest ?? {}, subscription.authContext, { meta: false })).rows\n : (await this.fetchEntityWithAuth(subscription.path, String(subscription.id), subscription.authContext, subscription.include)) ?? null;\n if (canDeliver()) callback(data);\n } catch (error) {\n logger.error(`❌ [RealtimeService] Error in the first fetch for DataDriver subscription ${subscription.subscriptionId}`, { error: error });\n this.reportDriverFetchFailure(subscription, canDeliver, error);\n }\n }\n\n // =============================================================================\n // RealtimeProvider Interface Methods\n // =============================================================================\n\n /**\n * Subscribe to collection changes (RealtimeProvider interface)\n */\n subscribeToCollection(\n subscriptionId: string,\n config: CollectionSubscriptionConfig,\n callback?: (rows: Record<string, unknown>[]) => void,\n onError?: (error: unknown) => void\n ): void {\n const key = this.subscriptionKey(config.clientId, subscriptionId);\n const target = {\n type: \"collection\" as const,\n path: config.path,\n collectionRequest: {\n filter: config.filter as Record<string, unknown> | undefined,\n orderBy: config.orderBy,\n order: config.order,\n limit: config.limit,\n startAfter: config.startAfter as Record<string, unknown> | undefined,\n databaseId: config.databaseId,\n searchString: config.searchString,\n searchExplain: config.searchExplain\n }\n };\n this.registerSubscription(key, {\n subscriptionId,\n clientId: config.clientId,\n groupKey: this.groupKeyFor(key, false, target),\n ...target,\n onError,\n started: 0,\n delivered: 0\n });\n this.indexInProcess(subscriptionId, key);\n\n if (callback) {\n this.subscriptionCallbacks.set(key, callback as (data: Record<string, unknown>[] | Record<string, unknown> | null) => void);\n }\n }\n\n /**\n * Subscribe to single row changes (RealtimeProvider interface)\n */\n subscribeToOne(\n subscriptionId: string,\n config: SingleSubscriptionConfig,\n callback?: (row: Record<string, unknown> | null) => void,\n onError?: (error: unknown) => void\n ): void {\n const key = this.subscriptionKey(config.clientId, subscriptionId);\n const target = { type: \"single\" as const, path: config.path, id: config.id };\n this.registerSubscription(key, {\n subscriptionId,\n clientId: config.clientId,\n groupKey: this.groupKeyFor(key, false, target),\n ...target,\n onError,\n started: 0,\n delivered: 0\n });\n this.indexInProcess(subscriptionId, key);\n\n if (callback) {\n this.subscriptionCallbacks.set(key, callback as (data: Record<string, unknown>[] | Record<string, unknown> | null) => void);\n }\n }\n\n /**\n * Unsubscribe an in-process subscription (RealtimeProvider interface).\n *\n * By id alone, because the in-process callers hold nothing else — so a\n * socket's subscription under the same id is not this one, and is left\n * alone. A socket ends its own through the `unsubscribe` frame.\n */\n unsubscribe(subscriptionId: string): void {\n for (const key of [...(this.inProcessKeysById.get(subscriptionId) ?? [])]) {\n const subscription = this._subscriptions.get(key);\n if (!subscription || this.clients.has(subscription.clientId)) continue;\n this.dropSubscription(key);\n }\n }\n\n // =============================================================================\n // WebSocket Client Management\n // =============================================================================\n\n addClient(clientId: string, ws: WebSocket) {\n this.clients.set(clientId, ws);\n\n ws.on(\"close\", () => {\n this.removeClient(clientId);\n });\n\n ws.on(\"error\", (error) => {\n logger.error(\"WebSocket error for client\", { detail: clientId, error });\n this.removeClient(clientId);\n });\n }\n\n // Public method to handle messages from external sources (like main WebSocket handler)\n async handleClientMessage(clientId: string, message: WebSocketMessage, authContext?: SubscriptionAuthContext) {\n await this.handleMessage(clientId, message, authContext);\n }\n\n /** Record what a socket opted into when it opened. See `socket-opt-ins.ts`. */\n setClientOptIns(clientId: string, optIns: ReadonlySet<string>): void {\n this.clientOptIns.set(clientId, optIns);\n }\n\n async removeClient(clientId: string) {\n this.clients.delete(clientId);\n this.clientOptIns.delete(clientId);\n\n // Remove all subscriptions and callbacks for this client; a group it\n // was the last member of goes with its pending refetch.\n for (const key of [...(this.subscriptionsByClient.get(clientId) ?? [])]) {\n this.dropSubscription(key);\n }\n\n // The shared rows go before the announcement, not after it. Every\n // `removePresence` below publishes a departure — to local members and\n // over the bus — while `sendPresenceState` answers a *newly arriving*\n // subscriber from this table. Announcing first leaves a window where the\n // table still lists someone who has left: a client hydrating inside it is\n // handed the ghost, and the diff that would have corrected it was\n // broadcast before that client existed, so it holds the ghost until the\n // TTL sweep rather than for the length of one statement.\n //\n // One statement for every channel the client was in, rather than one per\n // channel below — a disconnect is the common case, not a rare one. The\n // subscription and timer cleanup above stays synchronous on purpose: it\n // is what leaks if the database is slow, and it owes nothing to the\n // shared table. With no bus configured this is a no-op that never awaits\n // a query, and so is it for a client that never tracked presence here:\n // it has no rows.\n if (this.presenceTrackers.delete(clientId)) {\n await this.presenceStoreOp(() => this.presenceStore!.removeClient(clientId), \"client removal\");\n }\n\n // Remove from the broadcast channels it joined\n for (const channel of [...(this.channelsByClient.get(clientId) ?? [])]) {\n const members = this.channels.get(channel);\n if (members?.has(clientId)) {\n members.delete(clientId);\n this.removePresence(clientId, channel, { skipStore: true });\n if (members.size === 0) this.forgetChannel(channel);\n }\n }\n this.channelsByClient.delete(clientId);\n\n // And from every presence it still holds\n for (const channel of [...(this.presenceByClient.get(clientId) ?? [])]) {\n this.removePresence(clientId, channel, { skipStore: true });\n }\n this.presenceByClient.delete(clientId);\n }\n\n /** Record that `clientId` is in `channel`, in a reverse index — see {@link channelsByClient}. */\n private indexClientChannel(index: Map<string, Set<string>>, clientId: string, channel: string): void {\n let channels = index.get(clientId);\n if (!channels) index.set(clientId, channels = new Set());\n channels.add(channel);\n }\n\n private unindexClientChannel(index: Map<string, Set<string>>, clientId: string, channel: string): void {\n const channels = index.get(clientId);\n if (!channels) return;\n channels.delete(channel);\n if (channels.size === 0) index.delete(clientId);\n }\n\n private async handleMessage(clientId: string, message: WebSocketMessage, authContext?: SubscriptionAuthContext) {\n const payload = message.payload as Record<string, unknown> | undefined;\n switch (message.type) {\n case \"subscribe_collection\":\n await this.handleCollectionSubscription(clientId, message.payload as RealTimeListenCollectionProps, authContext);\n break;\n case \"subscribe_one\":\n await this.handleEntitySubscription(clientId, message.payload as RealTimeListenEntityProps, authContext);\n break;\n case \"unsubscribe\":\n await this.handleUnsubscribe(clientId, message.subscriptionId!);\n break;\n\n // ── Broadcast Channels & Presence ──\n //\n // One arm for all of them, because every one has to pass the same\n // gate and a switch with seven arms is a place to forget it once.\n // See `handleChannelMessage`.\n case \"join_channel\":\n case \"leave_channel\":\n case \"broadcast\":\n case \"channel_history\":\n case \"presence_track\":\n case \"presence_untrack\":\n case \"presence_state\":\n await this.handleChannelMessage(clientId, message.type, payload, authContext);\n break;\n\n default:\n this.sendError(clientId, \"Unknown message type \" + message.type, message.subscriptionId);\n }\n }\n\n /**\n * Refuse a new subscription past the socket's ceiling, and say so.\n *\n * Every collection subscription is a refetch on every write to its\n * collection, so a socket allowed to open them without limit could turn\n * each write anyone makes into tens of thousands of transactions. Replacing\n * a subscription the socket already holds — the same id again — is not a\n * new one and always passes. Returns whether the subscription may go ahead.\n */\n private admitSubscription(clientId: string, subscriptionId: string): boolean {\n const held = this.subscriptionsByClient.get(clientId);\n if (!held || held.size < this.maxSubscriptionsPerSocket) return true;\n if (held.has(this.subscriptionKey(clientId, subscriptionId))) return true;\n const message =\n `This connection already holds ${held.size} subscriptions, the most one connection may hold ` +\n `(REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET, or realtime.maxSubscriptionsPerSocket). Unsubscribe from one first.`;\n logger.warn(`[RealtimeService] Refused a subscription for ${clientId}: ${held.size} held, ceiling ${this.maxSubscriptionsPerSocket}`);\n this.sendError(clientId, message, subscriptionId, \"TOO_MANY_SUBSCRIPTIONS\");\n return false;\n }\n\n private async handleCollectionSubscription(clientId: string, request: RealTimeListenCollectionProps, authContext?: SubscriptionAuthContext) {\n const subscriptionId = request.subscriptionId;\n const key = this.subscriptionKey(clientId, subscriptionId);\n // Out here so the `catch` can check it. It is claimed before the fetch\n // starts, so a failed fetch always has one.\n let canDeliver: DeliveryCheck | undefined;\n\n try {\n // Early validation: ensure the requested collection exists in the registry\n const collection = this.registry.getCollectionByPath(request.path);\n if (!collection) {\n const registered = this.registry.getCollections().map(c => c.slug).join(\", \");\n const msg = `Collection not found: '${request.path}'. Registered: [${registered}]`;\n logger.error(`[RealtimeService] ${msg}`);\n this.sendError(clientId, msg, subscriptionId);\n return;\n }\n\n if (!this.admitSubscription(clientId, subscriptionId)) return;\n\n // A vector search cannot be served here, and the parameter used to\n // be read for one thing only — the limit default below — and then\n // dropped: the stored request carries no `vectorSearch` and the\n // refetch has no branch for one. So `.vectorSearch(…).listen()`\n // delivered an ordinary `id DESC` listing, with no `_distance` and\n // no error, forever. Refusing says what the silence did not.\n if (request.vectorSearch) {\n const msg =\n \"Realtime subscriptions do not support vector search: a subscription is re-run on every \" +\n \"matching write, and nothing here computes distances. Use `.vectorSearch(...).find()` for \" +\n \"the query, and subscribe without it if you need live updates.\";\n logger.warn(`[RealtimeService] ${msg}`);\n this.sendError(clientId, msg, subscriptionId, \"VECTOR_SEARCH_NOT_LIVE\");\n return;\n }\n\n // Bound the client-supplied limit with the SAME guarantee the REST\n // ingress applies (`resolveClientListLimit`): default an absent\n // limit by mode, refuse one above the ceiling. A subscription is\n // re-fetched on every matching write, so an unbounded one is a DoS\n // amplified per write — resolve it once and reuse for the stored\n // request and the initial fetch.\n //\n // Refusing matters more here than on the REST route: a\n // `collection_update` frame carries rows and nothing else — no\n // `total`, no `hasMore` — so a subscriber handed a quietly smaller\n // page has no way at all to learn it is not seeing the collection.\n let boundedLimit: number;\n try {\n boundedLimit = resolveClientListLimit(request.limit);\n } catch (e) {\n if (!(e instanceof ListLimitError)) throw e;\n logger.warn(`[RealtimeService] Refused subscription to '${request.path}': ${e.message}`);\n this.sendError(clientId, e.message, subscriptionId, \"INVALID_LIMIT\");\n return;\n }\n\n // The sort arrives as whatever JSON the client put in the frame, so\n // its *shape* is checked here the way the REST ingress checks the\n // query parameter. Unchecked, a malformed entry reads as a field\n // name that resolves to no column, and under lenient unknown-field\n // handling the subscription then streams rows in no order at all\n // while reporting nothing wrong.\n let orderBy: OrderByTuple[] | undefined;\n try {\n orderBy = parseOrderBySpecStrict(request.orderBy, request.order);\n } catch (e) {\n if (!(e instanceof OrderBySpecError)) throw e;\n logger.warn(`[RealtimeService] Refused subscription to '${request.path}': ${e.message}`);\n this.sendError(clientId, e.message, subscriptionId, e.code);\n return;\n }\n\n // The filter, read by the codec every other door uses — REST,\n // MCP, the socket's request frames, the in-process SDK — so a\n // two-element value array is an implicit `in` here too, and an\n // operator-shaped typo is refused by name rather than compiled.\n let filter: FilterValues<string> | undefined;\n try {\n filter = request.filter ? deserializeFilter(request.filter) : undefined;\n } catch (e) {\n if (!(e instanceof UnknownFilterOperatorError)) throw e;\n logger.warn(`[RealtimeService] Refused subscription to '${request.path}': ${e.message}`);\n this.sendError(clientId, e.message, subscriptionId, e.code);\n return;\n }\n\n // No filter, sort or projection over a field the subscriber may not\n // read — the rule `GET` and the socket's request frames apply. The\n // strip keeps the value out of every frame; a subscription filtered\n // on it would still answer, row by row, whether it matches. Judged\n // against the subscriber's roles, and `anon` for a socket that never\n // signed in: an absent viewer is the trusted server plane. The\n // request itself, with the sort as parsed above — not a re-listed\n // copy of some of its keys, which is how the stored `include`\n // went unchecked while every refetch applied it.\n try {\n assertReadRequestReadable(\n { ...request, filter, orderBy },\n collection,\n { roles: authContext?.roles ?? [\"anon\"] }\n );\n } catch (e) {\n if (!(e instanceof ApiError)) throw e;\n logger.warn(`[RealtimeService] Refused subscription to '${request.path}': ${e.message}`);\n this.sendError(clientId, e.message, subscriptionId, e.code);\n return;\n }\n\n // Store subscription with full request parameters and auth context for RLS\n const collectionRequest: StoredCollectionRequest = {\n filter,\n logical: request.logical,\n orderBy,\n order: request.order,\n limit: boundedLimit,\n // The SDK's `listen()` sends `page` beside the `offset` it\n // resolved from it, so the two agree and `page` decides here.\n // A caller's own `page` + `offset` never gets this far: the\n // SDK refuses the pair (`PAGE_WITH_OFFSET`) before sending, as\n // the REST parser does — one window, one rule.\n offset: request.page != null && request.page > 0\n ? (request.page - 1) * boundedLimit\n : request.offset,\n startAfter: request.startAfter as Record<string, unknown> | undefined,\n databaseId: request.collection?.databaseId,\n searchString: request.searchString,\n searchExplain: request.searchExplain,\n // Stored, so every refetch answers the same query the\n // initial fetch did. A field declared on the incoming props\n // and not stored here is accepted over the wire and then\n // silently ignored — which is what `offset` and `logical`\n // both were.\n include: request.include,\n fields: request.fields,\n distinct: request.distinct,\n ...(request.countOnly === true && { countOnly: true })\n };\n const target = { type: \"collection\" as const, path: request.path, collectionRequest, authContext };\n const subscription: Subscription = {\n subscriptionId,\n clientId,\n groupKey: this.groupKeyFor(key, true, target),\n ...target,\n started: 0,\n delivered: 0\n };\n this.registerSubscription(key, subscription);\n\n // The subscription is registered before this fetch runs, so a write\n // arriving in that window starts a refetch of its own — with nothing\n // ordering the two. Claim a slot first: this fetch is the oldest, so\n // if the refetch answers first, this one no longer delivers.\n canDeliver = this.beginDelivery(key, subscription);\n\n // Send initial data. Built from the request the subscription just\n // stored, so the first answer and every refetch after it cannot\n // describe different queries.\n const { rows, meta } = await this.fetchCollectionWithAuth(\n request.path,\n subscription.collectionRequest!,\n authContext,\n { meta: true }\n );\n\n if (canDeliver()) {\n this.sendDataFrame(subscription, \"collection_update\", this.dataFrame(this.collectionFrameTail(rows, request.path, meta)));\n }\n\n } catch (error) {\n this.reportSocketFetchFailure(clientId, subscriptionId, request.path, error, canDeliver, this._subscriptions.get(key));\n }\n }\n\n private async handleEntitySubscription(clientId: string, request: RealTimeListenEntityProps, authContext?: SubscriptionAuthContext) {\n const subscriptionId = request.subscriptionId;\n const key = this.subscriptionKey(clientId, subscriptionId);\n // As in the collection case: claimed before the fetch, checked in the `catch`.\n let canDeliver: DeliveryCheck | undefined;\n\n try {\n // Early validation: ensure the requested collection exists in the registry\n const collection = this.registry.getCollectionByPath(request.path);\n if (!collection) {\n const registered = this.registry.getCollections().map(c => c.slug).join(\", \");\n const msg = `Collection not found: '${request.path}'. Registered: [${registered}]`;\n logger.error(`[RealtimeService] ${msg}`);\n this.sendError(clientId, msg, subscriptionId);\n return;\n }\n\n if (!this.admitSubscription(clientId, subscriptionId)) return;\n\n // The relations it asks for are read as the subscriber, under the\n // check a list subscription's `include` passes (and `GET /:id`'s).\n try {\n assertReadRequestReadable(\n { include: request.include },\n collection,\n { roles: authContext?.roles ?? [\"anon\"] }\n );\n } catch (e) {\n if (!(e instanceof ApiError)) throw e;\n logger.warn(`[RealtimeService] Refused subscription to '${request.path}': ${e.message}`);\n this.sendError(clientId, e.message, subscriptionId, e.code);\n return;\n }\n\n // Store subscription in memory with auth context for RLS. The\n // `include` is stored with it, so every refetch reads what the\n // first frame did: without one, a row's frames carried no to-many\n // relation at all, and the record form opened by URL showed a\n // many-to-many field empty — saving it then unlinked every row.\n const target = {\n type: \"single\" as const,\n path: request.path,\n id: request.id,\n include: request.include,\n ...(request.etag === true && { etag: true }),\n authContext\n };\n const subscription: Subscription = {\n subscriptionId,\n clientId,\n groupKey: this.groupKeyFor(key, true, target),\n ...target,\n started: 0,\n delivered: 0\n };\n this.registerSubscription(key, subscription);\n\n // Same race as the collection case: a write landing between the\n // registration above and this fetch starts a refetch that can answer\n // first, and this one must not overwrite it afterwards.\n canDeliver = this.beginDelivery(key, subscription);\n\n // Send initial data\n const { row, etag } = await this.fetchEntityFrame(\n request.path,\n String(request.id),\n authContext,\n request.include,\n subscription.etag === true\n );\n\n if (canDeliver()) {\n this.sendDataFrame(subscription, \"single_update\", this.dataFrame(this.singleFrameTail(row, etag)));\n }\n\n } catch (error) {\n this.reportSocketFetchFailure(clientId, subscriptionId, request.path, error, canDeliver, this._subscriptions.get(key));\n }\n }\n\n /**\n * Answer a subscriber's failed fetch with an error frame, through the slot\n * its rows would have used.\n *\n * The error is always logged (`sanitizeErrorForClient` does that), but the\n * frame goes out only while this fetch holds the newest slot. A failed\n * straggler used to send one regardless. The client routes errors by\n * `subscriptionId`, so it called `onError` for a view already showing newer\n * rows, or for the subscription that replaced this one under the same id.\n *\n * With no slot, the subscribe failed before its fetch began. That part runs\n * synchronously, so nothing else can have answered the request yet.\n */\n private reportSocketFetchFailure(\n clientId: string,\n subscriptionId: string,\n path: string,\n error: unknown,\n canDeliver: DeliveryCheck | undefined,\n subscription?: Subscription\n ) {\n const sanitized = sanitizeErrorForClient(error, path);\n if (canDeliver && !canDeliver.mayReportFailure()) return;\n this.sendError(clientId, sanitized.message, subscriptionId, sanitized.code);\n // The subscriber now shows an error: the rows it last had are news again.\n if (subscription && subscription.subscriptionId === subscriptionId && subscription.clientId === clientId) {\n subscription.lastFrame = undefined;\n }\n }\n\n /**\n * Re-judge everything a socket holds open as the identity it has now.\n *\n * A socket re-authenticates on every session refresh, and when a role is\n * taken away, a tenant claim removed or a different account signs in, the\n * next token says so. Requests read as the new identity at once; the\n * subscriptions kept refetching as the one they were opened with, so a\n * demoted user went on receiving editor-only or another tenant's rows for\n * as long as the view stayed mounted.\n *\n * Each of this client's subscriptions is replaced by one carrying the new\n * identity — a replacement, so a refetch already running as the old one\n * cannot deliver — and refetched, so the view narrows now rather than at\n * the next write. A subscription whose filter, sort or projection names a\n * field the new roles may not read is ended with the refusal a new\n * subscription would get. Channel memberships are put to the authorizer\n * again, when one is installed, and left when it refuses.\n */\n async rescopeClient(clientId: string, authContext: SubscriptionAuthContext): Promise<void> {\n const principal = canonicalJson(principalOf(authContext));\n for (const key of [...(this.subscriptionsByClient.get(clientId) ?? [])]) {\n const subscription = this._subscriptions.get(key);\n if (!subscription) continue;\n const subscriptionId = subscription.subscriptionId;\n\n if (subscription.type === \"collection\" && subscription.collectionRequest) {\n const collection = this.registry.getCollectionByPath(subscription.path);\n const request = subscription.collectionRequest;\n try {\n if (collection) {\n assertReadRequestReadable(\n {\n // Stored from the subscription's own request, which carried it as one.\n filter: request.filter as FilterValues<string>,\n logical: request.logical,\n orderBy: request.orderBy,\n fields: request.fields\n },\n collection,\n { roles: authContext.roles ?? [\"anon\"] }\n );\n }\n } catch (e) {\n if (!(e instanceof ApiError)) throw e;\n await this.handleUnsubscribe(clientId, subscriptionId);\n this.sendError(clientId, e.message, subscriptionId, e.code);\n continue;\n }\n }\n\n // The same principal — a token refresh, which the SDK sends about\n // hourly — reads the same rows: the subscription stays as it is,\n // in its group, with nothing to refetch. Everything a refetch\n // binds is in the comparison, so this decides only whether to\n // look again, never what is shown. The readability check above\n // still ran: the collection's rules may have changed since.\n if (canonicalJson(principalOf(subscription.authContext)) === principal) continue;\n\n // A new identity is a new group: the read it shares is now the one\n // its new principal's subscriptions run.\n const rescoped: Subscription = {\n ...subscription,\n authContext,\n groupKey: this.groupKeyFor(key, true, { ...subscription, authContext }),\n started: 0,\n delivered: 0\n };\n this.registerSubscription(key, rescoped);\n const group = this.groups.get(rescoped.groupKey);\n if (group) this.scheduleGroupRefetch(group, key);\n }\n\n const authorizer = this.channelAuthorizer;\n if (!authorizer) return;\n for (const channel of [...(this.channelsByClient.get(clientId) ?? [])]) {\n if (!this.channels.get(channel)?.has(clientId)) continue;\n let allowed: boolean;\n try {\n allowed = await authorizer({ channel, action: \"join\", clientId, user: authContext });\n } catch (error) {\n logger.error(`❌ [Channels] Authorizer threw re-checking \"${channel}\" after a sign-in — leaving it`, { error });\n allowed = false;\n }\n if (!allowed) {\n this.leaveChannel(clientId, channel);\n this.denyChannelAction(clientId, channel, \"join\", \"refused by the channel authorizer for the identity this socket signed in as\");\n }\n }\n }\n\n /**\n * End a socket's own subscription. Only its own: the id is the client's,\n * so another socket's subscription under the same id is a different one.\n */\n private async handleUnsubscribe(clientId: string, subscriptionId: string) {\n this.dropSubscription(this.subscriptionKey(clientId, subscriptionId));\n }\n\n /**\n * Enhanced notification method that handles nested relation updates.\n * @param broadcast When true (default), also sends a pg_notify so other instances\n * pick up the change. Set to false when handling an incoming\n * cross-instance notification to avoid infinite loops.\n * @param origin `\"app\"` (default) — a Rebase-API mutation on this instance;\n * `\"cdc\"` — a database-level change observed via CDC (any writer,\n * any instance). The origin drives de-duplication: an app emit\n * records the change so this instance can suppress the matching\n * CDC echo, while an unmatched CDC event is delivered normally.\n */\n async notifyUpdate(path: string, id: string, row: Record<string, unknown> | null, databaseId?: string, broadcast = true, origin: \"app\" | \"cdc\" = \"app\") {\n this.debugLog(\"🔔 [RealtimeService] notifyUpdate called for path:\", path, \"id:\", id, \"isDelete:\", row === null, \"origin:\", origin);\n\n // De-duplicate against database-level CDC. The app path (a mutation made\n // through the Rebase API) fans out locally AND, once CDC is active, the\n // same committed change is echoed back to this instance via the WAL /\n // trigger stream. Record app emits so we can drop that echo here; deliver\n // any CDC event we did not originate (external writes, other instances).\n // The same row under the other paths a subscriber can address it by —\n // see `aliasPaths`.\n const aliases = this.aliasPaths(path);\n\n if (this.cdcActive) {\n const key = this.dedupKey(path, id, databaseId);\n if (origin === \"cdc\") {\n if (this.consumeAppEmit(key)) {\n this.debugLog(\"🔁 [RealtimeService] Suppressing CDC echo of local app mutation:\", key);\n return;\n }\n } else {\n this.markAppEmit(key);\n // CDC reports the change under the table's own collection, so a\n // write through a nested path echoes back under the root one.\n // The aliases below have already delivered it there.\n if (isNestedPath(path)) {\n const root = aliases.find(alias => !isNestedPath(alias));\n if (root) this.markAppEmit(this.dedupKey(root, id, databaseId));\n }\n }\n }\n\n // Get all paths that need to be notified - the direct path plus any parent paths\n const pathsToNotify = [path];\n\n // If this is a nested relation path (like \"posts/70/tags\"), also notify parent paths\n if (path.includes(\"/\") && path.split(\"/\").length > 1) {\n const parentPaths = this.getParentPaths(path);\n pathsToNotify.push(...parentPaths);\n this.debugLog(`🔗 [RealtimeService] Nested path detected. Will notify paths: ${pathsToNotify.join(\", \")}`);\n }\n\n // Process each path that needs notification\n for (const notifyPath of pathsToNotify) {\n this.notifyPathUpdate(notifyPath, path, id);\n }\n\n // Each alias is an address of this very row, so its single-row\n // subscribers match by id exactly as the written path's do.\n for (const alias of aliases) {\n this.notifyPathUpdate(alias, alias, id);\n }\n\n // Broadcast to other instances via pg_notify (only for local mutations).\n // When CDC is active it IS the cross-instance channel — every instance\n // observes every commit through the change stream — so the legacy\n // per-mutation broadcast is redundant (and would double-deliver). Skip it.\n //\n // Not awaited: the changes of one burst — a bulk save announces each\n // row after commit, one `notifyUpdate` at a time — go out together, a\n // few notifications instead of one round trip per row ahead of the\n // response.\n if (broadcast && this.broadcasting && !this.cdcActive) {\n this.broadcastChange(path, id, databaseId).catch((err) => {\n logger.error(\"❌ [RealtimeService] Failed to broadcast change via pg_notify\", { error: err });\n });\n }\n\n this.debugLog(\"🔔 [RealtimeService] notifyUpdate completed for path:\", path);\n }\n\n /**\n * Notify subscriptions for a specific path.\n *\n * **A subscriber only ever receives rows re-read under its own scope.**\n * `row` is used to decide *that* something changed, never to say *what* —\n * every delivery below goes through a refetch that binds the subscription's\n * own auth context.\n *\n * It used to be conditional. The CDC path already did the right thing: it\n * discards the captured tuple and emits `{_rebase_invalidated: true}`, and\n * that marker selected the refetch branch. But the marker is produced in\n * exactly two places, and the *other* side of each branch here shipped the\n * row it was handed straight to the socket. Two of the three entry paths\n * took that side — every API mutation (`PostgresBackendDriver.save` passes\n * the row it just wrote, read under the **writer's** scope) and the legacy\n * cross-instance LISTEN handler (which re-reads on the owner connection,\n * bypassing RLS altogether). Path matching was the only filter applied: the\n * subscription's own `filter`/`logical` was never evaluated, and any\n * `afterRead` redaction was the writer's rather than the reader's.\n *\n * A single-row subscription was the sharpest case. `subscribe_one` on a row\n * RLS denies is accepted and answered `null`; the next update then pushed\n * the full row with no later correction. The collection variant was merely\n * papered over ~300 ms later by the debounced refetch — after the bytes had\n * already reached the browser.\n *\n * The same defect was found and fixed on the Mongo driver in `065e2b615`\n * (see `packages/server-mongo/test/realtime-authorization.test.ts`); this is\n * the Postgres half, stated as one rule rather than three patched branches.\n *\n * The cost is the instant row-level patch that used to precede the refetch:\n * cross-tab feedback now waits for the debounce. That is the price of not\n * being able to know, without asking the database as this subscriber,\n * whether this subscriber may see the row at all.\n */\n private notifyPathUpdate(notifyPath: string, originalPath: string, id: string) {\n this.debugLog(`📡 [RealtimeService] Notifying path: ${notifyPath} (original: ${originalPath})`);\n\n // Every list on the path may have changed — but every changed row of a\n // statement says so, and the lists' answer is one refetch each however\n // many rows asked. A row only moves the path's debounce clock and\n // schedules the groups that have no refetch pending yet; it used to\n // visit every group on the path, so a 100k-row UPDATE on a list open\n // as 1,000 principals was 100M visits, ~3.9 s of event loop no socket\n // or request on this instance got past.\n if (this.collectionGroupsByPath.has(notifyPath)) {\n this.listPathChangedAt.set(notifyPath, Date.now());\n const unscheduled = this.unscheduledListGroups.get(notifyPath);\n if (unscheduled?.size) {\n for (const group of unscheduled) this.scheduleGroupRefetch(group);\n unscheduled.clear();\n }\n }\n // A single-row subscription is asked only at the written path, and\n // only for its own row: a parent path is a different record. Its\n // bucket is per row already.\n if (notifyPath !== originalPath) return;\n // A change that names no row may be to any of them.\n const singles = id === ANY_ROW\n ? this.singleGroupsByPath.get(notifyPath)\n : this.singleGroupsByAddress.get(`${notifyPath}\\u0000${id}`);\n for (const group of singles ?? []) {\n this.scheduleGroupRefetch(group);\n }\n }\n\n /**\n * Tell an in-process listener its refetch failed, through the slot the\n * rows would have used.\n *\n * The two driver-refetch `catch` blocks used to log and stop, so the\n * listener heard nothing and kept its last rows as if they were current.\n * The error goes to it as thrown: this is trusted server code, and the\n * masking on the socket path is for clients. The slot keeps a failed\n * straggler from reporting over newer rows, and keeps a failure away from\n * a subscription that was cancelled or replaced while it ran.\n */\n private reportDriverFetchFailure(\n subscription: Subscription,\n canDeliver: () => boolean,\n error: unknown\n ) {\n if (!subscription.onError || !canDeliver()) return;\n try {\n subscription.onError(error);\n } catch (listenerError) {\n // Contained: this runs in a timer, where a throw would be an\n // unhandled rejection.\n logger.error(`❌ [RealtimeService] onError threw for DataDriver subscription ${subscription.subscriptionId}`, { error: listenerError });\n }\n }\n\n /**\n * Fetch a collection with optional RLS auth context, and with `meta: true`\n * the `meta` a socket frame carries beside the rows.\n *\n * When authContext is provided, the fetch runs inside a transaction\n * with set_config calls so PostgreSQL RLS policies are enforced. The count\n * behind `meta` runs in that same transaction — one pool acquisition and\n * one `applyAuthContext` per refetch, where it used to take a second\n * transaction of its own — see {@link collectionMeta}.\n */\n private async fetchCollectionWithAuth(\n notifyPath: string,\n collectionRequest: StoredCollectionRequest,\n authContext: SubscriptionAuthContext | undefined,\n options: { meta: boolean; group?: SubscriptionGroup }\n ): Promise<{ rows: Record<string, unknown>[]; meta?: CollectionUpdateMeta }> {\n if (this.driver) {\n const collection = this.registry.getCollectionByPath(notifyPath);\n // Always wrap in a transaction with session vars, defaulting to anonymous context if missing.\n // Refetches are reads: apply the same GUCs + reader-role downgrade as the\n // driver's read path, so realtime cannot leak rows the initial fetch hid.\n // `READ ONLY` like that path too (here, in the count and in the row\n // refetch): an `afterRead` that writes is refused with 25006 on every\n // request read, and a frame must not be the one door where it commits\n // — once per row, per subscriber, on every change anyone makes.\n const activeAuth = authContext || { uid: ANONYMOUS_USER_ID,\nroles: [\"anon\"] };\n // The subscriber this frame is for, so per-field `access.read` is\n // applied to it. A frame is a read like any other and reaches the\n // same row pipeline; without this the initial `GET` would withhold a\n // field and the first `.listen()` update would hand it over.\n return await withFieldViewer({ roles: activeAuth.roles ?? [] }, async () =>\n await this.db.transaction(async (tx) => {\n await applyAuthContext(\n tx,\n {\n uid: activeAuth.uid,\n roles: activeAuth.roles,\n isAnonymous: activeAuth.isAnonymous === true,\n // A refetch is a read under the same identity, and a\n // tenancy policy reads a claim — without these a\n // subscriber would receive rows the initial GET hid.\n claims: activeAuth.claims\n },\n this.rlsUserRole\n );\n // Bound to the subscriber's own transaction and identity, so\n // a `beforeQuery` narrows a subscription frame exactly as it\n // narrows the `find()` that asked the same question. Lazy and\n // memoized: nothing is built for a collection with no hook.\n let hookContext: RebaseCallContext | undefined;\n const txEntityService = new DataService(\n tx, this.registry,\n () => (hookContext ??= callbackContextWithin(this.driver, tx, activeAuth))\n );\n // The REST pipeline, not the driver's own fetch — one\n // query, one include loader, one place search is decided.\n //\n // These are the rows a subscriber receives, and a subscriber\n // asked the same question a `find()` asks — so they are the\n // same rows, in the same shape, for every consumer on this\n // wire. The admin panel included: it builds the view model\n // it renders (dates as `Date`, relations as refs) in the\n // browser, from the collection config it already has — see\n // `toViewModelValues` in `buildRebaseData`. There is no\n // second wire shape to keep in step with this one.\n // A count subscription reads no rows: the count below is the frame.\n const fetchedEntities = collectionRequest.countOnly ? [] : await txEntityService.fetchCollectionForRest(notifyPath, {\n filter: collectionRequest.filter as FilterValues<string>,\n // The subscription stored a group; the search branch used to\n // drop it, so a filtered live search widened to every row\n // matching the text.\n logical: collectionRequest.logical,\n orderBy: collectionRequest.orderBy,\n order: collectionRequest.order,\n limit: collectionRequest.limit,\n offset: collectionRequest.offset,\n startAfter: collectionRequest.startAfter,\n searchString: collectionRequest.searchString,\n searchExplain: collectionRequest.searchExplain,\n databaseId: collectionRequest.databaseId,\n fields: collectionRequest.fields,\n distinct: collectionRequest.distinct\n }, collectionRequest.include);\n\n // Re-apply `afterRead` lifecycle hooks to ensure consistent data structures\n // between the initial driver fetch and this RLS-bound refetch.\n const registryCollection = this.registry.getCollectionByPath(notifyPath);\n // `| undefined`, and now it says so: the registry answers\n // nothing for a path it does not know. The cast used to\n // erase that one line above the `?.` that admits it.\n const resolvedCollection: CollectionConfig | undefined = collection\n ? { ...collection, ...registryCollection } as CollectionConfig\n : registryCollection;\n\n const callbacks = resolvedCollection?.callbacks;\n const globalCallbacks = this.registry?.getGlobalCallbacks();\n const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : undefined;\n\n let rows = fetchedEntities;\n if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {\n // Bound to THIS transaction — the subscriber's — see\n // callbackContextWithin below for what this used to be.\n const contextForCallback = callbackContextWithin(this.driver, tx, activeAuth);\n\n const callbackCollection = requireCallbackCollection(resolvedCollection, notifyPath);\n rows = await Promise.all(fetchedEntities.map(async (fetchedRow) => {\n let processedEntity = fetchedRow;\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n processedEntity = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n processedEntity = await callbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n processedEntity = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n return processedEntity;\n }));\n }\n\n // Last, after every hook has had the transaction: a count\n // that fails leaves it aborted, and its rows are already read.\n return {\n rows,\n meta: options.meta ? await this.collectionMeta(txEntityService, notifyPath, collectionRequest, rows, options.group) : undefined\n };\n }, { accessMode: \"read only\" })\n );\n }\n\n // No driver — use dataService directly (no auth wrapping possible).\n // The `logical` group is carried here as well: this branch answers the\n // same subscription as the one above, and a fallback that drops a\n // condition returns *more* rows than the path it stands in for.\n //\n // The field viewer IS established, unlike the database's auth context:\n // the subscriber's roles are in hand either way, and leaving the scope\n // off would make this the one path where a role-restricted field is\n // served to everybody — a fallback that hands out *more* than the path\n // it stands in for, which is the defect the paragraph above describes\n // one clause at a time.\n return await withFieldViewer({ roles: authContext?.roles ?? [] }, async () => {\n const rows = collectionRequest.countOnly ? [] : await this.dataService.fetchCollectionForRest(notifyPath, {\n filter: collectionRequest.filter as FilterValues<string>,\n logical: collectionRequest.logical,\n orderBy: collectionRequest.orderBy,\n order: collectionRequest.order,\n limit: collectionRequest.limit,\n offset: collectionRequest.offset,\n startAfter: collectionRequest.startAfter,\n searchString: collectionRequest.searchString,\n searchExplain: collectionRequest.searchExplain,\n databaseId: collectionRequest.databaseId,\n fields: collectionRequest.fields,\n distinct: collectionRequest.distinct\n }, collectionRequest.include);\n return {\n rows,\n meta: options.meta ? await this.collectionMeta(this.dataService, notifyPath, collectionRequest, rows, options.group) : undefined\n };\n });\n }\n\n /**\n * The `meta` a `collection_update` frame carries beside its rows, counted\n * through `service` — the subscriber's own transaction, read only, with\n * its auth context and `beforeQuery` narrowing already bound.\n *\n * Frames used to carry rows and primary keys and nothing else, so the\n * client issued a `GET /count` **per push** to fill in a total it needed to\n * render the same list it had just been handed — one extra round trip per\n * write, per subscriber, forever. The refetch already knows the query; it\n * counts once, here, under the same RLS transaction that read the rows, so\n * the total describes the same set they came from.\n *\n * A first page that came back short of its limit *is* the whole set — the\n * same filter, the same narrowing, read as the same principal — so its\n * length is the total and nothing is counted. Not for a later page, a\n * cursor page, a search (whose page and count are not the same query) or\n * a `distinct` read (whose count counts rows, not values): those count.\n */\n private async collectionMeta(\n service: DataService,\n notifyPath: string,\n collectionRequest: StoredCollectionRequest,\n rows: Record<string, unknown>[],\n group?: SubscriptionGroup\n ): Promise<CollectionUpdateMeta> {\n const limit = collectionRequest.limit ?? rows.length;\n const offset = collectionRequest.offset ?? 0;\n\n const wholeSet = collectionRequest.limit !== undefined && rows.length < collectionRequest.limit &&\n offset === 0 && !collectionRequest.startAfter && !collectionRequest.searchString && !collectionRequest.distinct &&\n // No rows were read, so their number says nothing.\n !collectionRequest.countOnly;\n\n let total: number;\n // When a big group's total was counted recently enough, it is reused\n // and the frame says when it was taken (see `recountIntervalMs`).\n let totalAsOf: string | undefined;\n const reusable = group?.lastCount;\n if (wholeSet) {\n total = rows.length;\n } else if (reusable && this.recountIntervalMs !== undefined && reusable.total >= this.recountMinRows\n && Date.now() - reusable.at < this.recountIntervalMs) {\n total = reusable.total;\n totalAsOf = new Date(reusable.at).toISOString();\n } else {\n try {\n total = await service.count(notifyPath, {\n filter: collectionRequest.filter as FilterValues<string>,\n logical: collectionRequest.logical,\n searchString: collectionRequest.searchString,\n databaseId: collectionRequest.databaseId\n });\n } catch (error) {\n // A count that failed says nothing about the size of the\n // collection, and a frame with no `meta` is one the client can fall\n // back on. Reporting `rows.length` would claim a page read at\n // offset 10 held two rows.\n logger.warn(`[RealtimeService] Could not count '${notifyPath}' for a subscription frame`, { error });\n return { limit, offset, hasMore: false, partial: true };\n }\n if (group) group.lastCount = { total, at: Date.now() };\n }\n\n const last = rows[rows.length - 1];\n const hasMore = offset + rows.length < total;\n const nextCursor = (hasMore && last)\n ? this.dataService.cursorFor?.(\n notifyPath, last, normalizeDriverOrderBy(collectionRequest.orderBy, collectionRequest.order)\n )\n : undefined;\n\n return { total, limit, offset, hasMore, ...(nextCursor && { nextCursor }), ...(totalAsOf && { totalAsOf }) };\n }\n\n /**\n * A `single_update` frame's row, and — when the subscriber asked — the\n * `ETag` a `GET /:id` of the same row answers.\n *\n * The tag names the row, not the read: `GET` hashes the row as\n * `restFetchService.fetchOneForRest` serves it with no `include`, and a\n * write's `If-Match` is compared against that same rendering. A frame\n * whose relations are loaded therefore takes its tag from a second read\n * without them, read first — as `GET /:id?include=` does — so a write\n * landing between the two leaves the tag older than the body (a 412 the\n * client recovers from) and never newer, which would let an edit made\n * against the older body pass over a change it never saw.\n */\n private async fetchEntityFrame(\n path: string,\n id: string,\n authContext: SubscriptionAuthContext | undefined,\n include: IncludeSpec | undefined,\n withETag: boolean\n ): Promise<{ row: Record<string, unknown> | null; etag?: string }> {\n if (!withETag) return { row: (await this.fetchEntityWithAuth(path, id, authContext, include)) ?? null };\n const loadsRelations = include !== undefined && !(Array.isArray(include) && include.length === 0);\n const tagged = loadsRelations ? await this.fetchEntityWithAuth(path, id, authContext) : undefined;\n const row = (await this.fetchEntityWithAuth(path, id, authContext, include)) ?? null;\n if (!row) return { row };\n let collection: CollectionConfig | undefined;\n try {\n collection = this.registry.getCollectionByPath(path);\n } catch {\n collection = undefined;\n }\n const etag = await rowETag(loadsRelations ? tagged : row, collection);\n return { row, ...(etag && { etag }) };\n }\n\n /**\n * Fetch a single row with optional RLS auth context.\n */\n private async fetchEntityWithAuth(\n notifyPath: string,\n id: string | number,\n authContext?: SubscriptionAuthContext,\n include?: IncludeSpec\n ): Promise<Record<string, unknown> | undefined> {\n if (this.driver) {\n const collection = this.registry.getCollectionByPath(notifyPath);\n // Always wrap in a transaction with session vars, defaulting to anonymous context if missing.\n // Same read isolation as collection refetches: GUCs + reader-role downgrade.\n const activeAuth = authContext || { uid: ANONYMOUS_USER_ID,\nroles: [\"anon\"] };\n // The subscriber this frame is for, so per-field `access.read` is\n // applied to it. A frame is a read like any other and reaches the\n // same row pipeline; without this the initial `GET` would withhold a\n // field and the first `.listen()` update would hand it over.\n return await withFieldViewer({ roles: activeAuth.roles ?? [] }, async () =>\n await this.db.transaction(async (tx) => {\n await applyAuthContext(\n tx,\n {\n uid: activeAuth.uid,\n roles: activeAuth.roles,\n isAnonymous: activeAuth.isAnonymous === true,\n // A refetch is a read under the same identity, and a\n // tenancy policy reads a claim — without these a\n // subscriber would receive rows the initial GET hid.\n claims: activeAuth.claims\n },\n this.rlsUserRole\n );\n // Bound to the subscriber's own transaction and identity, so\n // a `beforeQuery` narrows a subscription frame exactly as it\n // narrows the `find()` that asked the same question. Lazy and\n // memoized: nothing is built for a collection with no hook.\n let hookContext: RebaseCallContext | undefined;\n const txEntityService = new DataService(\n tx, this.registry,\n () => (hookContext ??= callbackContextWithin(this.driver, tx, activeAuth))\n );\n // The REST pipeline, for the same reason the collection refetch\n // uses it: `listenById()` and `findById()` are the same read,\n // and `fetchOne` renders the admin's view model — every relation\n // eagerly loaded, each under a `{ __type: \"relation\" }`\n // envelope. A subscriber got one shape and a fetch the other.\n let processedEntity = await txEntityService.fetchOneForRest(\n notifyPath, id, include, collection?.databaseId\n ) ?? undefined;\n\n if (processedEntity) {\n const registryCollection = this.registry.getCollectionByPath(notifyPath);\n // See the note on the collection refetch above.\n const resolvedCollection: CollectionConfig | undefined = collection\n ? { ...collection, ...registryCollection } as CollectionConfig\n : registryCollection;\n\n const callbacks = resolvedCollection?.callbacks;\n const globalCallbacks = this.registry?.getGlobalCallbacks();\n const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : undefined;\n\n if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {\n const contextForCallback = callbackContextWithin(this.driver, tx, activeAuth);\n const callbackCollection = requireCallbackCollection(resolvedCollection, notifyPath);\n\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n processedEntity = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n processedEntity = await callbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n processedEntity = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n }\n }\n\n return processedEntity;\n }, { accessMode: \"read only\" })\n );\n }\n\n // Same reasoning as the collection fallback above: no database auth\n // context is available here, but the subscriber's roles are.\n return await withFieldViewer(\n { roles: authContext?.roles ?? [] },\n async () => (await this.dataService.fetchOneForRest(notifyPath, id)) ?? undefined\n );\n }\n\n /**\n * Everything in a `collection_update` frame after its subscription id,\n * serialised once for every member of a group: the rows, the collection's\n * key columns, and the `meta` beside them (see {@link CollectionUpdateMeta}\n * for the round trip that removes). The order is the one\n * {@link CollectionUpdateMessage} declares.\n */\n private collectionFrameTail(rows: Record<string, unknown>[], path: string, meta?: CollectionUpdateMeta): string {\n const tail: Omit<CollectionUpdateMessage, \"type\" | \"subscriptionId\"> = {\n rows,\n pks: this.primaryKeysForPath(path),\n ...(meta && { meta })\n };\n return JSON.stringify(tail).slice(1);\n }\n\n /** Everything in a `single_update` frame after its subscription id. */\n private singleFrameTail(row: Record<string, unknown> | null, etag?: string): string {\n const tail: Omit<SingleUpdateMessage, \"type\" | \"subscriptionId\"> = { row, ...(row && etag && { etag }) };\n return JSON.stringify(tail).slice(1);\n }\n\n /** A frame tail with its {@link frameDigest}, computed once for every member it goes to. */\n private dataFrame(body: string): { body: string; digest: string } {\n return { body, digest: this.frameDigest(body) };\n }\n\n /**\n * What a subscriber was last sent, in 44 bytes: holding the frames\n * themselves would keep a page of rows per subscriber in memory.\n */\n private frameDigest(body: string): string {\n return createHash(\"sha256\").update(body).digest(\"base64\");\n }\n\n /**\n * Send a data frame to a socket subscriber — unless it is exactly the one\n * the subscriber was last sent.\n *\n * A write anywhere in a collection refetches every list of it, and the\n * first 50 rows of a 10k-row list come back the same for 99.5% of writes.\n * Each of those frames was sent anyway: ~30 KB per viewer per write,\n * parsed, merged and re-rendered by every client to change nothing. The\n * comparison is per subscriber, because the members of one group need not\n * have been sent the same frame last — one may have joined since.\n */\n private sendDataFrame(subscription: Subscription, type: \"collection_update\" | \"single_update\", frame: { body: string; digest: string }): void {\n if (subscription.lastFrame === frame.digest) return;\n subscription.lastFrame = frame.digest;\n this.sendRaw(\n subscription.clientId,\n `{\"type\":\"${type}\",\"subscriptionId\":${JSON.stringify(subscription.subscriptionId)},${frame.body}`\n );\n }\n\n /**\n * Send a lightweight row-level patch to a collection subscriber.\n * The client can merge this into its cached data for instant feedback.\n *\n * The key columns ride along: the patch names a row by address, and the\n * client has to find that row among the ones it cached — which carry\n * columns and no address. The SDK holds no collection config to derive one\n * from, so this is the only place the mapping can come from.\n */\n /** The key columns of the collection at `path`, if they can be resolved. */\n private primaryKeysForPath(path: string): PrimaryKeyInfo[] | undefined {\n try {\n const collection = this.registry.getCollectionByPath(path);\n if (!collection) return undefined;\n const keys = getPrimaryKeys(collection, this.registry);\n return keys.length > 0 ? keys : undefined;\n } catch {\n // `getCollectionByPath` throws on a path it cannot walk — and this\n // is called for parent paths too, which include entity paths like\n // `posts/1` that name no collection. Telling the subscriber nothing\n // is right here; letting it throw would drop the notification.\n return undefined;\n }\n }\n\n /**\n * `channel` addresses the error to the channel frame it is about.\n *\n * Without it the client had nowhere to deliver a channel error: channel\n * frames are fire-and-forget, so there is no pending request to reject and\n * no subscription id to match, and `CHANNEL_FORBIDDEN`, `RATE_LIMITED` and\n * the two history failures fell through every branch of the client's\n * message handler into a console warning. The client already routes\n * channel-addressed frames by name — `onChannelMessage(channel, …)` — so\n * naming the channel is all that was missing.\n *\n * Additive on the wire: a client that does not read it behaves as before.\n */\n private sendError(clientId: string, error: string, subscriptionId?: string, code?: string, channel?: string) {\n const message = {\n type: \"error\" as const,\n subscriptionId,\n ...(channel !== undefined && { channel }),\n payload: {\n error: code ? { message: error, code } : error,\n ...(channel !== undefined && { channel })\n },\n error\n };\n this.sendMessage(clientId, message);\n }\n\n private sendMessage(clientId: string, message: CollectionUpdateMessage | SingleUpdateMessage | CollectionPatchMessage | { type: string; subscriptionId?: string; error?: string; payload?: unknown }) {\n this.sendRaw(clientId, JSON.stringify(message));\n }\n\n /**\n * Send an already-serialised frame to a client, if it is still connected.\n *\n * Every frame to a socket goes through here, so this is where a client\n * that does not read what it is sent is let go of — see\n * {@link terminateIfBacklogged}. Terminated, it closes like any other\n * socket, and {@link removeClient} drops what it held.\n */\n private sendRaw(clientId: string, frame: string) {\n const client = this.clients.get(clientId);\n if (!client || client.readyState !== WebSocket.OPEN) return;\n if (terminateIfBacklogged(client, clientId)) return;\n client.send(frame);\n }\n\n /**\n * Extract parent paths from a nested path like \"posts/70/tags\"\n * Returns [\"posts\", \"posts/70\"] for the example above\n */\n /**\n * The other paths a subscriber can address the row written at `path` by.\n *\n * `authors/1/posts` and `posts` are two addresses for the same rows, and\n * subscriptions were matched by the written path's exact string. So a post\n * saved through `authors/1/posts` never reached a subscriber of `posts` or\n * of `posts/43`, and one saved through `posts` never reached a subscriber\n * of `authors/1/posts`, whose list it may just have joined or left.\n *\n * The aliases are the target collection's root path, and every nested path\n * a live subscription holds that lands on the same collection. Each still\n * refetches under its own scope and its own parent, so naming a path here\n * decides only who is asked to look again — never what they are shown.\n */\n private aliasPaths(path: string): string[] {\n const slug = this.collectionSlugAt(path);\n if (!slug) return [];\n const aliases = new Set<string>();\n if (slug !== path) aliases.add(slug);\n // The paths subscriptions hold, each resolved once when it was first\n // subscribed — not every subscription on the server, once per change.\n for (const [subscribed, held] of this.subscribedPaths) {\n if (subscribed === path || aliases.has(subscribed) || !isNestedPath(subscribed)) continue;\n if (held.slug === slug) aliases.add(subscribed);\n }\n return [...aliases];\n }\n\n /** The slug of the collection a path lands on, or `undefined` for one that names none. */\n private collectionSlugAt(path: string): string | undefined {\n try {\n return this.registry.getCollectionByPath(path)?.slug;\n } catch {\n // A malformed or stale path names no collection, and a\n // notification is not the place to refuse it.\n return undefined;\n }\n }\n\n private getParentPaths(path: string): string[] {\n const segments = path.split(\"/\").filter(s => s.length > 0);\n const parentPaths: string[] = [];\n\n // Build parent paths progressively\n for (let i = 1; i < segments.length; i += 2) {\n const parentPath = segments.slice(0, i).join(\"/\");\n if (parentPath) {\n parentPaths.push(parentPath);\n }\n\n // If there's an row ID, add the path including the row\n if (i + 1 < segments.length) {\n const pathWithEntity = segments.slice(0, i + 1).join(\"/\");\n parentPaths.push(pathWithEntity);\n }\n }\n\n return parentPaths;\n }\n\n // =============================================================================\n // Broadcast Channels\n // =============================================================================\n\n /**\n * Install a channel authorizer — see {@link ChannelAuthorizer}.\n *\n * Nothing in the framework calls this yet: it is the seam a rules API will\n * be built on, kept deliberately separate from the membership floor so the\n * floor holds whether or not anyone uses it.\n */\n setChannelAuthorizer(authorizer: ChannelAuthorizer | undefined): void {\n this.channelAuthorizer = authorizer;\n }\n\n /** Which action each channel frame is asking to perform. */\n private static readonly CHANNEL_ACTIONS: Record<string, ChannelAction> = {\n join_channel: \"join\",\n broadcast: \"broadcast\",\n channel_history: \"history\",\n presence_track: \"join\",\n presence_state: \"presence\"\n };\n\n /**\n * The one door every channel frame comes through.\n *\n * Returns synchronously — and so dispatches synchronously — unless an\n * authorizer is installed. That matters: a client sends `join_channel`,\n * `presence_state` and `channel_history` back to back on connect, and the\n * socket's message handler processes each frame up to its first `await`,\n * so a gate that always yielded would let the reads overtake the join that\n * is about to authorize them.\n */\n private handleChannelMessage(\n clientId: string,\n type: string,\n payload: Record<string, unknown> | undefined,\n authContext?: SubscriptionAuthContext\n ): void | Promise<void> {\n const channel = payload?.channel as string;\n\n // Leaving and untracking only ever remove the caller's own state, so\n // they need no permission — refusing them could only strand a client.\n if (type === \"leave_channel\") {\n this.leaveChannel(clientId, channel);\n return;\n }\n if (type === \"presence_untrack\") {\n this.removePresence(clientId, channel);\n return;\n }\n\n const action = RealtimeService.CHANNEL_ACTIONS[type];\n const allowed = this.authorizeChannelAction(clientId, channel, action, authContext);\n if (allowed === false) return;\n if (allowed === true) return this.dispatchChannelMessage(clientId, type, channel, payload);\n return allowed.then((ok) => {\n if (ok) return this.dispatchChannelMessage(clientId, type, channel, payload);\n });\n }\n\n /** Perform an already-authorized channel frame. */\n private dispatchChannelMessage(\n clientId: string,\n type: string,\n channel: string,\n payload: Record<string, unknown> | undefined\n ): void | Promise<void> {\n switch (type) {\n case \"join_channel\":\n this.joinChannel(clientId, channel);\n return;\n case \"broadcast\":\n this.broadcastToChannel(clientId, channel, payload?.event as string, payload?.payload);\n return;\n case \"channel_history\":\n return this.handleChannelHistoryRequest(\n clientId,\n channel,\n payload?.sinceSeq as number | undefined,\n payload?.limit as number | undefined\n );\n case \"presence_track\":\n // Auto-join the channel so presence works without a separate join\n this.joinChannel(clientId, channel);\n this.trackPresence(clientId, channel, payload?.state as Record<string, unknown> ?? {});\n return;\n case \"presence_state\":\n this.sendPresenceState(clientId, channel);\n return;\n }\n }\n\n /**\n * Decide whether a client may perform an action on a channel.\n *\n * **Membership is the floor.** Reading a channel's presence roster, replaying\n * its retained history and broadcasting into it all require that this client\n * has joined it. That is a low bar — joining is open to anyone who can name\n * the channel — but it is not the bar that was there before, which was none\n * at all: `channel_history` and `presence_state` answered any socket about\n * any channel, and a broadcast fanned out to members the sender had never\n * joined. Two internal tables (`rebase.channel_presence`,\n * `rebase.channel_messages`) are held outside RLS on the strength of this\n * check, so it fails closed: an authorizer that throws refuses the frame.\n *\n * Anything richer than membership belongs in a {@link ChannelAuthorizer};\n * this method is where it is consulted, and the only place.\n */\n private authorizeChannelAction(\n clientId: string,\n channel: string,\n action: ChannelAction,\n authContext?: SubscriptionAuthContext\n ): boolean | Promise<boolean> {\n // Joining is what establishes membership, so it cannot require it.\n if (action !== \"join\" && !this.channels.get(channel)?.has(clientId)) {\n this.denyChannelAction(clientId, channel, action, \"not a member of the channel\");\n return false;\n }\n\n const authorizer = this.channelAuthorizer;\n if (!authorizer) return true;\n\n let verdict: boolean | Promise<boolean>;\n try {\n verdict = authorizer({ channel, action, clientId, user: authContext });\n } catch (error) {\n logger.error(`❌ [Channels] Authorizer threw for ${action} on \"${channel}\" — refusing`, { error });\n this.denyChannelAction(clientId, channel, action, \"channel authorization failed\");\n return false;\n }\n\n if (typeof verdict === \"boolean\") {\n if (!verdict) this.denyChannelAction(clientId, channel, action, \"refused by the channel authorizer\");\n return verdict;\n }\n\n return verdict.then(\n (ok) => {\n if (!ok) this.denyChannelAction(clientId, channel, action, \"refused by the channel authorizer\");\n return ok;\n },\n (error) => {\n logger.error(`❌ [Channels] Authorizer rejected for ${action} on \"${channel}\" — refusing`, { error });\n this.denyChannelAction(clientId, channel, action, \"channel authorization failed\");\n return false;\n }\n );\n }\n\n /** Tell the client why its channel frame went nowhere, and say so in the log. */\n private denyChannelAction(clientId: string, channel: string, action: ChannelAction, reason: string): void {\n this.debugLog(`🚫 [Channels] Refused ${action} on \"${channel}\" for ${clientId}: ${reason}`);\n this.sendError(\n clientId,\n `Refused ${action} on channel \"${channel}\": ${reason}`,\n undefined,\n \"CHANNEL_FORBIDDEN\",\n channel\n );\n }\n\n /** Join a broadcast channel */\n joinChannel(clientId: string, channel: string): void {\n if (!this.channels.has(channel)) {\n this.channels.set(channel, new Set());\n }\n this.channels.get(channel)!.add(clientId);\n this.indexClientChannel(this.channelsByClient, clientId, channel);\n this.warnIfMemoryBusOnMultiplePods();\n this.debugLog(`📡 [Broadcast] Client ${clientId} joined channel: ${channel}`);\n }\n\n /**\n * Say something the first time channels are used on a deployment that is\n * demonstrably multi-pod while the bus is still the in-memory default.\n *\n * Every other warning in this subsystem covers a *configured* bus failing —\n * the case where the operator already knew a bus mattered. The common\n * misconfiguration is the opposite one: scaled to two replicas, never\n * touched `realtime.bus`, and broadcast and presence quietly serve a\n * fraction of the room. The evidence is already in the process, so use it.\n */\n private warnIfMemoryBusOnMultiplePods(): void {\n if (this.memoryBusWarned) return;\n if (this.bus.kind !== \"memory\") return;\n if (!this.foreignInstanceSeen) {\n this.checkForOtherInstances();\n return;\n }\n this.memoryBusWarned = true;\n logger.warn(\n \"⚠️ [ChannelBus] Channels are in use with the in-memory bus, but notifications from another \" +\n \"instance have been seen — this deployment runs more than one process. Broadcast and presence \" +\n \"reach only the clients connected to this one. Set `realtime.bus` (or REALTIME_CHANNEL_BUS=postgres) \" +\n \"to make channels cross-instance.\"\n );\n }\n\n /**\n * Ask the database whether another process holds a realtime LISTEN\n * connection on it, at most once per {@link OTHER_INSTANCES_CHECK_INTERVAL_MS}.\n *\n * The legacy entity channel proves a second instance with every foreign\n * `sid`, but under change capture — the default — nothing crosses that\n * channel, so the warning above could never fire on exactly the\n * deployments it was written for. Every LISTEN connection names itself\n * (`application_name`, which `pg_stat_activity` shows to any role), so\n * the count is one cheap query. Only a process that listens is asked: a\n * single process with no LISTEN connection has nothing to compare with.\n */\n private checkForOtherInstances(): void {\n if (this.otherInstancesCheck) return;\n if (!this.cdcListener && !this.crossInstanceListener) return;\n const now = Date.now();\n if (this.otherInstancesCheckedAt !== undefined &&\n now - this.otherInstancesCheckedAt < RealtimeService.OTHER_INSTANCES_CHECK_INTERVAL_MS) return;\n this.otherInstancesCheckedAt = now;\n this.otherInstancesCheck = (async () => {\n try {\n const result = await this.db.execute(drizzleSql`\n SELECT count(*)::int AS n FROM pg_stat_activity\n WHERE datname = current_database()\n AND starts_with(application_name, ${REALTIME_LISTENER_APPLICATION_PREFIX})\n AND application_name <> ${this.listenerApplicationName}`);\n const n = Number((result.rows?.[0] as { n?: unknown } | undefined)?.n ?? 0);\n if (n > 0) {\n this.foreignInstanceSeen = true;\n this.warnIfMemoryBusOnMultiplePods();\n }\n } catch (error) {\n this.debugLog(\"[ChannelBus] Could not count other realtime instances\", error);\n } finally {\n this.otherInstancesCheck = undefined;\n }\n })();\n }\n\n /** Drop a channel its last local member left, and what was kept per channel for it. */\n private forgetChannel(channel: string): void {\n this.channels.delete(channel);\n this.oversizedBroadcastWarned.delete(channel);\n }\n\n /** Leave a broadcast channel */\n leaveChannel(clientId: string, channel: string): void {\n const members = this.channels.get(channel);\n if (members) {\n members.delete(clientId);\n if (members.size === 0) this.forgetChannel(channel);\n }\n this.unindexClientChannel(this.channelsByClient, clientId, channel);\n // Also remove presence\n this.removePresence(clientId, channel);\n }\n\n /**\n * Broadcast a message to all clients in a channel except the sender.\n *\n * On a channel with no retention rule this is what it always was: a\n * synchronous fan-out to whoever is connected, with no sequence number, no\n * SQL and no await — the body below runs to completion before returning.\n *\n * On a retained channel the message is durably numbered first and only then\n * delivered, through a per-channel queue so that delivery order matches\n * sequence order. That ordering is the whole point: a client that catches up\n * with `sinceSeq` has to arrive at the same state as one that never\n * disconnected.\n */\n broadcastToChannel(clientId: string, channel: string, event: string, payload: unknown): void {\n const retention = this.channelHistory?.retentionFor(channel);\n if (!retention) {\n this.fanOutBroadcast(clientId, channel, event, payload);\n // Other instances get the same frame, but never before the clients\n // on this one: the local fan-out above is synchronous and the\n // publish is not, which is also what keeps the ephemeral path free\n // of any await for a single-instance deployment.\n this.publishBroadcast(clientId, channel, event, payload);\n return;\n }\n\n const previous = this.channelSendQueues.get(channel) ?? Promise.resolve();\n const next = previous\n // A failed predecessor must not poison the chain — the next message\n // on this channel is independent and still deserves to be sent.\n .catch(() => { /* already reported below */ })\n .then(() => this.persistAndFanOut(clientId, channel, event, payload, retention));\n\n this.channelSendQueues.set(channel, next);\n void next.finally(() => {\n // Only clear if nothing has queued behind us in the meantime.\n if (this.channelSendQueues.get(channel) === next) this.channelSendQueues.delete(channel);\n });\n }\n\n /**\n * Number a broadcast, store it, then deliver it.\n *\n * A message that cannot be stored is **not** delivered. Delivering it would\n * put it in front of live subscribers while leaving it absent from every\n * future replay — the two views of the channel would disagree permanently,\n * and no later message could repair the gap. Failing loudly to the sender\n * instead lets it retry, which for an operation stream is the only outcome\n * that keeps clients convergent.\n */\n private async persistAndFanOut(\n clientId: string,\n channel: string,\n event: string,\n payload: unknown,\n retention: ResolvedRetention\n ): Promise<void> {\n // Over the Postgres bus the message is announced by the statement that\n // numbers it, and every instance — this one too — delivers it when the\n // announcement arrives: in commit order, which is sequence order. Fanned\n // out here first, a reader on this instance got this instance's N+1\n // before another's N, and a client that keeps a watermark dropped N for\n // good. The announcement is a pointer, whatever the message's size: any\n // database login can LISTEN, so the body stays in the history table and\n // each instance reads it back. See `ChannelHistoryStore.append`.\n const ordered = this.bus instanceof PostgresChannelBus;\n let seq: number;\n try {\n ({ seq } = await this.channelHistory!.append(channel, event, payload, clientId, ordered ? {\n notifyChannel: CHANNEL_BUS_NOTIFY_CHANNEL,\n sid: this.instanceId\n } : undefined));\n } catch (error) {\n logger.error(`❌ [ChannelHistory] Could not persist broadcast on \"${channel}\" — message dropped`, { error });\n this.sendError(\n clientId,\n `Could not persist broadcast on retained channel \"${channel}\"`,\n undefined,\n \"CHANNEL_HISTORY_WRITE_FAILED\",\n channel\n );\n return;\n }\n\n if (!ordered) {\n this.fanOutBroadcast(clientId, channel, event, payload, seq);\n this.publishBroadcast(clientId, channel, event, payload, seq);\n }\n\n try {\n await this.channelHistory!.prune(channel, retention);\n } catch (error) {\n // Retention is a housekeeping concern; the message is already\n // delivered and durable, so a failed prune must not surface as a\n // broadcast failure. It will be retried on the next message.\n logger.warn(`⚠️ [ChannelHistory] Prune failed for \"${channel}\"`, { error });\n }\n }\n\n /** Deliver a broadcast frame to every member of a channel but the sender. */\n private fanOutBroadcast(clientId: string, channel: string, event: string, payload: unknown, seq?: number): void {\n const members = this.channels.get(channel);\n if (!members) return;\n\n const message = JSON.stringify({\n type: \"broadcast\",\n channel,\n event,\n payload,\n ...(seq !== undefined ? { seq } : {})\n });\n\n for (const memberId of members) {\n if (memberId === clientId) {\n // Never echoed back to the sender. A sender that watches the\n // sequence (`channel_seq`) is told the number its message\n // took instead, so its own message leaves no hole in it: a\n // retained channel's numbers reach every member, one way or\n // the other, and a hole is always a message that was missed.\n if (seq !== undefined && this.clientOptIns.get(memberId)?.has(\"channel_seq\")) {\n this.sendRaw(memberId, JSON.stringify({ type: \"broadcast_ack\", channel, seq }));\n }\n continue;\n }\n this.sendRaw(memberId, message);\n }\n }\n\n // =============================================================================\n // Cross-Instance Channel Bus\n // =============================================================================\n\n /**\n * Install the transport that carries channel frames between instances.\n *\n * Called once at boot. A bus that cannot start is reported and replaced with\n * the memory bus: losing cross-instance fan-out degrades collaboration to\n * what it was before this existed, whereas refusing to boot takes the whole\n * backend down for it.\n */\n async configureChannelBus(bus: ChannelBus): Promise<void> {\n if (bus.kind === \"memory\") {\n this.bus = bus;\n return;\n }\n\n try {\n await bus.start((frame) => this.handleBusFrame(frame));\n } catch (error) {\n logger.warn(\n `⚠️ [ChannelBus] Could not start the \"${bus.kind}\" channel bus — channel broadcast and presence ` +\n \"stay per-instance. Clients served by different replicas will not see each other.\",\n { error }\n );\n await bus.stop().catch(() => { /* best effort */ });\n this.bus = new MemoryChannelBus();\n return;\n }\n\n this.bus = bus;\n\n // Presence needs shared *state*, not just shared fan-out — see\n // `channel-presence.ts`. It comes up with the bus and only with it.\n try {\n const store = new ChannelPresenceStore(this.db, this.instanceId);\n await store.ensureTables();\n this.presenceStore = store;\n this.ensurePresenceSweep();\n } catch (error) {\n logger.warn(\n \"⚠️ [ChannelBus] Could not create the shared presence table — presence rosters will only list \" +\n \"clients connected to this instance (broadcast is unaffected).\",\n { error }\n );\n this.presenceStore = undefined;\n }\n\n logger.info(\n `📡 [ChannelBus] Cross-instance channels active via ${bus.kind} (instanceId: ${this.instanceId}).`\n );\n }\n\n /** Which transport is in use — `\"memory\"` means per-instance only. */\n public getChannelBusKind(): ChannelBus[\"kind\"] {\n return this.bus.kind;\n }\n\n /**\n * Send a broadcast to the other instances.\n *\n * Fire-and-forget by design: the clients on this instance have already been\n * served, and a bus that is briefly unreachable must not turn a broadcast\n * into an error for the sender.\n */\n private publishBroadcast(clientId: string, channel: string, event: string, payload: unknown, seq?: number): void {\n if (this.bus.kind === \"memory\") return;\n\n const frame: ChannelBusFrame = {\n kind: \"broadcast\",\n sid: this.instanceId,\n channel,\n event,\n from: clientId,\n ...(seq !== undefined ? { seq } : {}),\n payload\n };\n\n // Postgres caps a NOTIFY payload at 8 KB. A retained message is already\n // durable and addressable, so it travels as a pointer and each receiver\n // reads the body back — the same shape as the entity path, which\n // notifies an address and refetches the row.\n if (frameByteLength(frame) > this.bus.maxFrameBytes) {\n if (seq === undefined) {\n this.reportOversizedBroadcast(clientId, channel);\n return;\n }\n void this.publishFrame({\n kind: \"broadcast_ref\",\n sid: this.instanceId,\n channel,\n from: clientId,\n seq\n });\n return;\n }\n\n void this.publishFrame(frame);\n }\n\n private async publishFrame(frame: ChannelBusFrame): Promise<void> {\n try {\n await this.bus.publish(frame);\n } catch (error) {\n logger.error(\"❌ [ChannelBus] Failed to publish frame — other instances did not receive it\", {\n detail: `${frame.kind} on \"${frame.channel}\"`,\n error\n });\n }\n }\n\n /**\n * Tell the sender that a message was delivered locally but nowhere else.\n *\n * Staying quiet here would be the worst option available: on one instance\n * the app works, on two it works for half the users, and nothing in the\n * logs connects the two. The fix is a one-liner in config — give the\n * channel a retention rule and the message travels as a pointer instead —\n * so the message says exactly that.\n */\n private reportOversizedBroadcast(clientId: string, channel: string): void {\n const remedy =\n `Add a retention rule for \"${channel}\" (realtime.channels) — retained messages travel by reference ` +\n \"and have no size limit.\";\n\n if (!this.oversizedBroadcastWarned.has(channel)) {\n this.oversizedBroadcastWarned.add(channel);\n logger.warn(\n `⚠️ [ChannelBus] A broadcast on ephemeral channel \"${channel}\" exceeds the ` +\n `${this.bus.maxFrameBytes}-byte limit of the ${this.bus.kind} bus and reached only this instance. ` +\n remedy\n );\n }\n this.sendError(\n clientId,\n `Broadcast on \"${channel}\" was too large to reach other instances. ${remedy}`,\n undefined,\n \"CHANNEL_BUS_PAYLOAD_TOO_LARGE\",\n channel\n );\n }\n\n /**\n * Deliver a frame published by another instance to this one's clients.\n *\n * Frames we published ourselves are dropped on arrival — the local fan-out\n * happened before the publish — exactly as the entity-change handler skips\n * its own `sid`.\n */\n private async handleBusFrame(frame: ChannelBusFrame): Promise<void> {\n const retained = (frame.kind === \"broadcast\" || frame.kind === \"broadcast_ref\") && frame.seq !== undefined;\n // Our own frames were fanned out before they were published — except a\n // retained one over the Postgres bus, which reaches this instance's\n // clients only this way. See `persistAndFanOut`.\n if (frame.sid === this.instanceId && !(retained && this.bus instanceof PostgresChannelBus)) return;\n if (!retained) return this.deliverBusFrame(frame);\n\n // In the order they arrived, per channel: a pointer is read back from\n // the history table, and the frame after it must not overtake it.\n const previous = this.channelReceiveQueues.get(frame.channel) ?? Promise.resolve();\n const next = previous.catch(() => { /* reported where it failed */ }).then(() => this.deliverBusFrame(frame));\n this.channelReceiveQueues.set(frame.channel, next);\n void next.finally(() => {\n if (this.channelReceiveQueues.get(frame.channel) === next) this.channelReceiveQueues.delete(frame.channel);\n }).catch(() => { /* reported where it failed */ });\n return next;\n }\n\n private async deliverBusFrame(frame: ChannelBusFrame): Promise<void> {\n switch (frame.kind) {\n case \"broadcast\":\n this.fanOutBroadcast(frame.from ?? \"\", frame.channel, frame.event, frame.payload, frame.seq);\n return;\n\n case \"broadcast_ref\": {\n // Nothing to read back for: skip the query rather than pay for\n // a message no client here is waiting for.\n if (!this.channels.get(frame.channel)?.size) return;\n\n const entry = await this.channelHistory?.getBySeq(frame.channel, frame.seq);\n if (!entry) {\n logger.warn(\n `⚠️ [ChannelBus] Message ${frame.seq} on \"${frame.channel}\" is no longer retained — ` +\n \"clients on this instance will need to replay (channel_history) to catch up.\"\n );\n return;\n }\n // The ordered path's pointer leaves the sender out — the stored\n // row names it, and the sender is not echoed its own message.\n this.fanOutBroadcast(frame.from ?? entry.senderId ?? \"\", frame.channel, entry.event, entry.payload, entry.seq);\n return;\n }\n\n case \"presence_diff\":\n this.deliverPresenceDiff(frame.channel, frame.joins, frame.leaves);\n return;\n }\n }\n\n // =============================================================================\n // Channel History\n // =============================================================================\n\n /**\n * Install retention rules and create the tables they need.\n *\n * Safe to call with no rules (and safe not to call at all): the store stays\n * inert, no schema is created, and broadcast keeps its original\n * fire-and-forget path.\n */\n async configureChannelHistory(\n rules: ChannelRetentionRule[] | undefined,\n options?: { provision?: boolean }\n ): Promise<void> {\n // The store is built in every process, whether or not this one creates\n // the tables: retaining a message is what a process does when it\n // *publishes* to a retained channel, and a function handler publishes as\n // readily as a websocket client does. Only the DDL is owned.\n this.channelHistory = new ChannelHistoryStore(this.db, rules ?? []);\n if (!this.channelHistory.enabled) return;\n if (options?.provision === false) return;\n await this.channelHistory.ensureTables();\n }\n\n /** Whether any channel is configured to retain messages. */\n public isChannelHistoryEnabled(): boolean {\n return this.channelHistory?.enabled ?? false;\n }\n\n /**\n * Answer a client's catch-up request.\n *\n * A channel with no retention rule is answered with `retained: false`\n * rather than an empty list, so the client can tell \"you missed nothing\"\n * apart from \"this channel never keeps anything\" — the second means its\n * reconnect strategy has to be a full resync, and silence would leave it\n * guessing.\n */\n private async handleChannelHistoryRequest(\n clientId: string,\n channel: string,\n sinceSeq?: number,\n limit?: number\n ): Promise<void> {\n if (!channel) return;\n\n const retention = this.channelHistory?.retentionFor(channel);\n if (!retention) {\n this.sendChannelHistory(clientId, channel, [], false);\n return;\n }\n\n try {\n const { messages, latestSeq } = await this.channelHistory!.replay(channel, sinceSeq, limit);\n this.sendChannelHistory(clientId, channel, messages, true, latestSeq);\n } catch (error) {\n logger.error(`❌ [ChannelHistory] Replay failed for \"${channel}\"`, { error });\n this.sendError(clientId, `Could not replay history for channel \"${channel}\"`, undefined, \"CHANNEL_HISTORY_READ_FAILED\", channel);\n }\n }\n\n private sendChannelHistory(\n clientId: string,\n channel: string,\n messages: ChannelHistoryEntry[],\n retained: boolean,\n latestSeq?: number\n ): void {\n this.sendRaw(clientId, JSON.stringify({\n type: \"channel_history\",\n channel,\n messages,\n retained,\n ...(latestSeq !== undefined ? { latestSeq } : {})\n }));\n }\n\n // =============================================================================\n // Presence\n // =============================================================================\n\n /**\n * Track presence in a channel.\n *\n * The client re-sends this every ~20s as a heartbeat against the 30s\n * timeout, so most calls carry the state that is already recorded. Those\n * refresh `last_seen` and stop there: re-announcing an unchanged state to\n * every instance would put a bus message per client per heartbeat on the\n * wire to tell everyone nothing happened.\n */\n trackPresence(clientId: string, channel: string, state: Record<string, unknown>): void {\n if (!this.presence.has(channel)) {\n this.presence.set(channel, new Map());\n }\n\n const channelPresence = this.presence.get(channel)!;\n this.presenceTrackers.add(clientId);\n const previous = channelPresence.get(clientId);\n const changed = !previous || JSON.stringify(previous.state) !== JSON.stringify(state);\n channelPresence.set(clientId, { state,\nlastSeen: Date.now() });\n this.indexClientChannel(this.presenceByClient, clientId, channel);\n\n // Refresh the shared roster on every heartbeat — that timestamp is what\n // tells other instances this client is still here.\n void this.presenceStoreOp(() => this.presenceStore!.track(channel, clientId, state), \"track\");\n\n // Announce a join or a state change, here and to the other instances —\n // and nothing else. An unchanged heartbeat used to be delivered to\n // every local member regardless: n² frames per heartbeat round, each\n // copying the roster on the client, to change no roster anywhere.\n if (changed) {\n this.deliverPresenceDiff(channel, { [clientId]: state }, {});\n this.publishPresenceDiff(channel, { [clientId]: state }, {});\n }\n\n // Start cleanup interval if not running\n this.ensurePresenceCleanup();\n }\n\n /**\n * Remove presence from a channel.\n *\n * `skipStore` is for the socket-close path, which clears every channel at\n * once and then deletes the client's rows in a single statement instead of\n * one per channel.\n */\n removePresence(clientId: string, channel: string, options?: { skipStore?: boolean }): void {\n const channelPresence = this.presence.get(channel);\n if (!channelPresence) return;\n\n const entry = channelPresence.get(clientId);\n if (entry) {\n channelPresence.delete(clientId);\n this.unindexClientChannel(this.presenceByClient, clientId, channel);\n this.deliverPresenceDiff(channel, {}, { [clientId]: entry.state });\n this.publishPresenceDiff(channel, {}, { [clientId]: entry.state });\n if (!options?.skipStore) {\n void this.presenceStoreOp(() => this.presenceStore!.remove(channel, clientId), \"remove\");\n }\n }\n\n if (channelPresence.size === 0) {\n this.presence.delete(channel);\n }\n }\n\n /**\n * Send the full roster for a channel to one client.\n *\n * Answered from the shared table when there is one, because \"who is in this\n * document?\" has a single answer that must not depend on which replica the\n * asker happens to be connected to. Without a bus there is nothing to share\n * and the local map *is* the roster — that path stays synchronous, which is\n * what it always was.\n */\n sendPresenceState(clientId: string, channel: string): void {\n if (!this.presenceStore) {\n this.sendPresenceStateMessage(clientId, channel, this.localPresences(channel));\n return;\n }\n\n void this.presenceStore.roster(channel)\n .then((presences) => {\n this.sendPresenceStateMessage(clientId, channel, presences);\n })\n .catch((error) => {\n // A roster the asker can act on beats none: fall back to the\n // clients we can see rather than leaving the request unanswered.\n logger.warn(`⚠️ [Presence] Could not read the shared roster for \"${channel}\" — answering with this instance's clients only.`, { error });\n this.sendPresenceStateMessage(clientId, channel, this.localPresences(channel));\n });\n }\n\n /** Presence of the clients connected to this instance. */\n private localPresences(channel: string): Record<string, Record<string, unknown>> {\n const channelPresence = this.presence.get(channel);\n const presences: Record<string, Record<string, unknown>> = {};\n if (channelPresence) {\n for (const [id, { state }] of channelPresence) {\n presences[id] = state;\n }\n }\n return presences;\n }\n\n private sendPresenceStateMessage(\n clientId: string,\n channel: string,\n presences: Record<string, Record<string, unknown>>\n ): void {\n this.sendRaw(clientId, JSON.stringify({\n type: \"presence_state\",\n channel,\n presences\n }));\n }\n\n /** Deliver a presence diff to this instance's members of the channel. */\n private deliverPresenceDiff(\n channel: string,\n joins: Record<string, Record<string, unknown>>,\n leaves: Record<string, Record<string, unknown>>\n ): void {\n const members = this.channels.get(channel);\n if (!members) return;\n\n const message = JSON.stringify({\n type: \"presence_diff\",\n channel,\n joins,\n leaves\n });\n\n for (const memberId of members) {\n this.sendRaw(memberId, message);\n }\n }\n\n /** Tell the other instances about a presence change. */\n private publishPresenceDiff(\n channel: string,\n joins: Record<string, Record<string, unknown>>,\n leaves: Record<string, Record<string, unknown>>\n ): void {\n if (this.bus.kind === \"memory\") return;\n void this.publishFrame({ kind: \"presence_diff\", sid: this.instanceId, channel, joins, leaves });\n }\n\n /** Run a roster write when there is a roster, and never let it throw. */\n private async presenceStoreOp(op: () => Promise<void>, label: string): Promise<void> {\n if (!this.presenceStore) return;\n try {\n await op();\n } catch (error) {\n logger.warn(`⚠️ [Presence] Shared roster ${label} failed`, { error });\n }\n }\n\n /** Periodic cleanup for stale presences */\n private ensurePresenceCleanup(): void {\n if (this.presenceInterval) return;\n this.presenceInterval = setInterval(() => {\n const now = Date.now();\n for (const [channel, channelPresence] of this.presence) {\n for (const [clientId, entry] of channelPresence) {\n if (now - entry.lastSeen > RealtimeService.PRESENCE_TIMEOUT_MS) {\n this.removePresence(clientId, channel);\n }\n }\n }\n // Stop interval if no presences tracked\n if (this.presence.size === 0 && this.presenceInterval) {\n clearInterval(this.presenceInterval);\n this.presenceInterval = undefined;\n }\n }, 10000); // Check every 10s\n }\n\n /**\n * Reap roster rows whose owning instance stopped heartbeating.\n *\n * This is the cross-instance half of the sweep above, and it doubles as\n * crash recovery: a pod that dies takes its clients with it but leaves\n * their rows behind, and after one TTL window they look exactly like any\n * other client that went quiet. The delete returns what it removed, so\n * whichever instance wins the race is the one that announces the\n * departures — once for the cluster, not once per replica.\n */\n private ensurePresenceSweep(): void {\n if (this.presenceSweepInterval || !this.presenceStore) return;\n\n this.presenceSweepInterval = setInterval(\n () => void this.sweepStalePresence(),\n RealtimeService.PRESENCE_SWEEP_INTERVAL_MS\n );\n\n // Never hold the process open for housekeeping.\n unref(this.presenceSweepInterval);\n }\n\n /** One pass of the stale-roster sweep. See {@link ensurePresenceSweep}. */\n private async sweepStalePresence(): Promise<void> {\n if (!this.presenceStore) return;\n try {\n const removed = await this.presenceStore.sweepStale(RealtimeService.PRESENCE_TIMEOUT_MS);\n for (const row of removed) {\n this.debugLog(`👻 [Presence] Reaped stale presence ${row.clientId} on \"${row.channel}\"`);\n this.deliverPresenceDiff(row.channel, {}, { [row.clientId]: row.state });\n this.publishPresenceDiff(row.channel, {}, { [row.clientId]: row.state });\n }\n } catch (error) {\n logger.warn(\"⚠️ [Presence] Stale-roster sweep failed\", { error });\n }\n }\n\n // =============================================================================\n // Lifecycle / Cleanup\n // =============================================================================\n\n /**\n * Gracefully tear down all realtime resources.\n *\n * This MUST be called during process shutdown, **before** `pool.end()`.\n * It ensures:\n * 1. All debounced refetch timers are cancelled (prevents queries after pool closes).\n * 2. All subscription state and callbacks are cleared.\n * 3. The dedicated LISTEN client (outside the pool) is disconnected.\n * 4. All WebSocket clients are closed with 1001 (\"going away\"); one that\n * does not answer the close frame is dropped after a short grace.\n */\n async destroy(): Promise<void> {\n // 1. Cancel every pending debounced refetch timer\n for (const group of this.groups.values()) {\n if (group.timer) clearTimeout(group.timer);\n group.timer = undefined;\n }\n\n // 2. Clear subscriptions, callbacks and the groups and indexes over them\n this.unscheduledListGroups.clear();\n this.listPathChangedAt.clear();\n this.refetchQueue.clear();\n this._subscriptions.clear();\n this.subscriptionCallbacks.clear();\n this.subscriptionsByClient.clear();\n this.groups.clear();\n this.collectionGroupsByPath.clear();\n this.singleGroupsByAddress.clear();\n this.singleGroupsByPath.clear();\n this.inProcessKeysById.clear();\n this.subscribedPaths.clear();\n\n // 3. Clear broadcast channels and presence\n this.channels.clear();\n this.presence.clear();\n this.channelsByClient.clear();\n this.presenceByClient.clear();\n this.presenceTrackers.clear();\n // Pending history writes hold the pool open; let them settle before the\n // caller closes it, but never let a rejected one break shutdown.\n await Promise.allSettled([...this.channelSendQueues.values(), ...this.channelReceiveQueues.values()]);\n this.channelSendQueues.clear();\n this.channelReceiveQueues.clear();\n this.channelHistory?.clear();\n if (this.presenceInterval) {\n clearInterval(this.presenceInterval);\n this.presenceInterval = undefined;\n }\n if (this.presenceSweepInterval) {\n clearInterval(this.presenceSweepInterval);\n this.presenceSweepInterval = undefined;\n }\n this.oversizedBroadcastWarned.clear();\n\n // Drop this instance's roster rows now rather than leaving every other\n // replica to wait out a TTL window on ghosts — a rolling deploy would\n // otherwise show 30s of departed users on every restart.\n if (this.presenceStore) {\n try {\n await this.presenceStore.removeInstance();\n } catch (error) {\n logger.warn(\"⚠️ [Presence] Could not clear this instance's roster rows on shutdown\", { error });\n }\n this.presenceStore = undefined;\n }\n\n // 4. Disconnect the dedicated LISTEN client(s), once any changes still\n // waiting to be announced have gone out.\n await this.broadcastFlush?.catch(() => undefined);\n await this.stopListening();\n await this.stopCdc();\n await this.bus.stop().catch((error) =>\n logger.warn(\"⚠️ [ChannelBus] Error while stopping the channel bus\", { error }));\n this.bus = new MemoryChannelBus();\n\n // 5. Close the client sockets. The HTTP server's close() waits on an\n // upgraded connection for as long as the browser keeps it open, and\n // closeAllConnections() does not reach one either, so a socket left\n // open here holds the whole shutdown until its force timer.\n const sockets = [...this.clients.values()];\n this.clients.clear();\n closeWebSockets(sockets, WEBSOCKET_CLOSE_GRACE_MS);\n\n this.debugLog(\"🧹 [RealtimeService] destroy() complete — all resources released.\");\n }\n\n // =============================================================================\n // Database-level Change Data Capture (CDC)\n // =============================================================================\n\n /**\n * Whether database-level change capture is the source and is listening.\n *\n * Configured is not enough: a CDC connection that went half-open used to\n * leave this `true` while nothing arrived. False while the connection is\n * down and being replaced.\n */\n public isCdcActive(): boolean {\n return this.cdcActive && this.cdcListener?.connected === true;\n }\n\n /** The LISTEN connections this service depends on — see `RealtimeProvider.health`. */\n health(): RealtimeListenerHealth[] {\n const listeners: RealtimeListenerHealth[] = [];\n if (this.cdcActive && this.cdcListener) {\n const { connected, downSince } = this.cdcListener.status();\n listeners.push({ name: \"cdc\", connected, ...(downSince !== undefined ? { downSince } : {}) });\n }\n if (this.crossInstanceListener) {\n const { connected, downSince } = this.crossInstanceListener.status();\n listeners.push({ name: \"cross-instance\", connected, ...(downSince !== undefined ? { downSince } : {}) });\n }\n const bus = this.bus instanceof PostgresChannelBus ? this.bus.status() : undefined;\n if (bus) {\n listeners.push({ name: \"channel-bus\", connected: bus.connected, ...(bus.downSince !== undefined ? { downSince: bus.downSince } : {}) });\n }\n return listeners;\n }\n\n /**\n * Enable database-level change capture as the realtime source.\n *\n * A dedicated LISTEN client consumes committed changes from the `rebase_cdc`\n * channel (fed by CDC triggers — see {@link provisionTriggerCdc}) and routes\n * them into the same {@link notifyUpdate} pipeline used by API mutations. The\n * effect: subscribers see a change no matter how it was written — psql, a\n * cron in another service, raw SQL, or the Studio SQL editor — exactly like\n * Supabase Realtime tailing the WAL.\n *\n * Because CDC observes every commit on every instance, it also *replaces* the\n * legacy per-mutation cross-instance broadcast (see the guard in\n * {@link notifyUpdate}); callers should not also call {@link startListening}.\n *\n * @param connectionString Direct Postgres connection for the LISTEN client\n * (bypass PgBouncer — LISTEN needs a session connection).\n */\n async enableCdc(connectionString: string): Promise<void> {\n if (this.cdcActive) {\n logger.warn(\"⚠️ [CDC] enableCdc called but CDC is already active. Ignoring.\");\n return;\n }\n this.cdcTableMap = this.buildCdcTableMap();\n this.junctionLinkMap = buildJunctionLinkMap(this.registry);\n this.cdcListener = new CdcListener(\n connectionString,\n (events) => this.handleCdcEvents(events),\n () => this.resyncSubscriptions(),\n this.listenerApplicationName\n );\n try {\n // start() validates the initial connection; if it can't be established\n // it rejects here, and we leave CDC inactive so the caller can fall\n // back to app-level realtime rather than silently dropping events.\n await this.cdcListener.start();\n } catch (err) {\n await this.cdcListener.stop().catch(() => { /* best effort */ });\n this.cdcListener = undefined;\n this.cdcTableMap = undefined;\n this.junctionLinkMap = undefined;\n throw err;\n }\n this.cdcActive = true;\n // The bootstrapper says the same thing one line later, in the\n // vocabulary of the setting that produced it (REALTIME_CDC).\n logger.debug(\n `📡 [RealtimeService] Database-level change capture ACTIVE — writes from ANY source now emit realtime events ` +\n `(${this.cdcTableMap.size} mapped table key(s)).`\n );\n }\n\n /**\n * Refetch every live subscription, as a change to its path would.\n *\n * Called when a LISTEN connection (CDC, or the cross-instance broadcast)\n * is listening again after a drop. Postgres does not queue NOTIFY for a\n * session that is not listening, so every change committed while it was\n * down — by another instance, psql, a cron — reached nobody here, and a\n * subscriber held its pre-gap rows until some later change to the same\n * collection happened to arrive. Each refetch runs under the\n * subscription's own scope, so this decides only who looks again.\n */\n private resyncSubscriptions(): void {\n for (const group of this.groups.values()) {\n this.scheduleGroupRefetch(group);\n }\n }\n\n /** Stop the CDC listener and clear its state. */\n async stopCdc(): Promise<void> {\n this.cdcActive = false;\n if (this.cdcListener) {\n await this.cdcListener.stop();\n this.cdcListener = undefined;\n }\n this.cdcTableMap = undefined;\n this.junctionLinkMap = undefined;\n this.recentAppEmits.clear();\n this.cdcBacklog = [];\n }\n\n /**\n * Build the reverse map from database table → collection. A change event\n * carries `schema` + `table`; realtime subscriptions are keyed by collection\n * path (slug). We index by both `schema.table` and bare `table` so the lookup\n * works whether or not the collection declares an explicit schema.\n */\n private buildCdcTableMap(): Map<string, CollectionConfig> {\n const map = new Map<string, CollectionConfig>();\n for (const collection of this.registry.getCollections()) {\n const table = getTableName(collection);\n if (!table) continue;\n const schema = (collection as { schema?: string }).schema ?? \"public\";\n map.set(`${schema}.${table}`, collection);\n // Bare-table fallback; first registration wins to keep it deterministic.\n if (!map.has(table)) map.set(table, collection);\n }\n return map;\n }\n\n private resolveCollectionForTable(schema: string, table: string): CollectionConfig | undefined {\n if (!this.cdcTableMap) return undefined;\n return this.cdcTableMap.get(`${schema}.${table}`) ?? this.cdcTableMap.get(table);\n }\n\n /**\n * Route a captured database change into the realtime pipeline.\n *\n * Delivery is RLS-safe by construction: the event carries the changed row's\n * key and nothing else, and even that is NOT forwarded to subscribers. Instead the change is marked invalidated, so\n * every matching subscription re-reads the row under its own auth context via\n * {@link fetchCollectionWithAuth} / {@link fetchEntityWithAuth}. A subscriber\n * therefore only ever receives rows its RLS policies permit — filtering is per\n * subscriber, never per publisher.\n */\n /**\n * Route the rows one notification names — a statement's batch, or one row.\n *\n * Each is handled as a change on its own (the app-emit de-duplication and\n * single-row addressing are per row), at most {@link CDC_ROWS_PER_TURN} in\n * one turn of the event loop; the rest wait in order for the next turns. A\n * socket read hands the listener up to 2 MB of notifications at once, and\n * batched that is ~250k rows: handled in one go, the event loop stalled\n * for ~400 ms. Sliced, a 1M-row statement is ~200 short turns that timers,\n * sockets and requests run between.\n */\n private handleCdcEvents(events: CdcChangeEvent[]): void {\n if (events.length === 0) return;\n this.cdcBacklog.push(events);\n this.drainCdcBacklog();\n }\n\n private drainCdcBacklog(): void {\n while (this.cdcBacklog.length > 0) {\n const room = RealtimeService.CDC_ROWS_PER_TURN - this.cdcTurnRows;\n if (room <= 0) return;\n const head = this.cdcBacklog[0];\n const slice = head.length <= room ? head : head.splice(0, room);\n if (slice === head) this.cdcBacklog.shift();\n if (this.cdcTurnRows === 0) {\n // The next turn starts a new budget, and carries on with what is left.\n setImmediate(() => {\n this.cdcTurnRows = 0;\n this.drainCdcBacklog();\n });\n }\n this.cdcTurnRows += slice.length;\n for (const event of slice) {\n this.handleCdcEvent(event).catch((error) => {\n logger.error(\"❌ [CDC] Failed to route a change notification\", { error });\n });\n }\n }\n }\n\n private async handleCdcEvent(event: CdcChangeEvent): Promise<void> {\n const collection = this.resolveCollectionForTable(event.schema, event.table);\n if (!collection) {\n // A junction table backs no collection, but its rows *are* a child\n // list. Route the change to the lists it changes before giving up.\n if (await this.handleJunctionCdcEvent(event)) return;\n\n // Unmapped table (not backed by a collection) — nothing to deliver.\n this.debugLog(`📡 [CDC] Ignoring change on unmapped table ${event.schema}.${event.table}`);\n return;\n }\n\n const path = collection.slug;\n const databaseId = (collection as { databaseId?: string }).databaseId;\n // A TRUNCATE names no row: it changed every one.\n const id = event.op === \"TRUNCATE\" ? ANY_ROW : this.extractIdFromCdcRow(collection, event.row);\n\n // Deletes carry a null row (subscribers drop the id); inserts/updates carry\n // an invalidation marker that forces a per-subscriber RLS-bound refetch.\n const row = event.op === \"DELETE\" || event.op === \"TRUNCATE\" ? null : { _rebase_invalidated: true };\n\n await this.notifyUpdate(path, id, row, databaseId, /* broadcast */ false, /* origin */ \"cdc\");\n }\n\n /**\n * Deliver a change on a many-to-many junction table as a change to the child\n * lists it belongs to.\n *\n * Linking a tag to a post writes only `posts_tags`. That table backs no\n * collection, so change capture dropped the event as unmapped and the\n * subscribers of `posts/1/tags` never heard about it — every other write in\n * the system was realtime, and this one silently was not. The junction row\n * carries both ids, so it names its own paths exactly.\n *\n * Notifies the nested path rather than either endpoint collection, because\n * invalidation walks *parent* paths and never child ones: telling `tags` it\n * changed would not reach a subscription on `posts/1/tags`.\n *\n * Returns whether the table was recognised as a junction.\n */\n private async handleJunctionCdcEvent(event: CdcChangeEvent): Promise<boolean> {\n const links = this.junctionLinkMap?.get(`${event.schema}.${event.table}`)\n ?? this.junctionLinkMap?.get(event.table);\n if (!links?.length) return false;\n\n if (event.op === \"TRUNCATE\") {\n // Every link is gone, so every list of this relation that is\n // subscribed to has changed — whichever parent it belongs to.\n for (const link of links) {\n const prefix = `${link.parentCollection.slug}/`;\n const suffix = `/${link.relationKey}`;\n const lists = [...this.subscribedPaths.keys()].filter((path) =>\n path.startsWith(prefix) && path.endsWith(suffix) && path.split(\"/\").length === 3);\n for (const path of lists) {\n await this.notifyUpdate(path, ANY_ROW, null, (link.parentCollection as { databaseId?: string }).databaseId, /* broadcast */ false, /* origin */ \"cdc\");\n }\n }\n return true;\n }\n\n for (const link of links) {\n const sourceId = event.row?.[link.sourceColumn];\n const targetId = event.row?.[link.targetColumn];\n if (sourceId === undefined || sourceId === null || targetId === undefined || targetId === null) {\n this.debugLog(\n `📡 [CDC] Junction row on ${event.table} is missing '${link.sourceColumn}'/'${link.targetColumn}' — skipping.`\n );\n continue;\n }\n\n const path = `${link.parentCollection.slug}/${String(sourceId)}/${link.relationKey}`;\n // An unlink removes the target from this list; a link invalidates it\n // so each subscriber refetches under its own RLS context.\n const row = event.op === \"DELETE\" ? null : { _rebase_invalidated: true };\n\n await this.notifyUpdate(\n path,\n String(targetId),\n row,\n (link.parentCollection as { databaseId?: string }).databaseId,\n /* broadcast */ false,\n /* origin */ \"cdc\"\n );\n }\n\n return true;\n }\n\n /**\n * Compute the canonical (possibly composite) id string from a captured row,\n * or {@link ANY_ROW} when it names none — a key too large to notify, a\n * table with no key.\n */\n private extractIdFromCdcRow(collection: CollectionConfig, row: Record<string, unknown>): string {\n // Unaddressable is a change to the whole collection. It used to reach\n // its lists only: a single-row subscriber of the row it was never heard.\n return deriveRowAddress(this.cdcRowByKeyFields(collection, row), collection, this.registry) || ANY_ROW;\n }\n\n /**\n * A captured row with its key under the key's *field* names.\n *\n * The trigger captures the tuple by column — `user_id` — and an address is\n * built from the key's property names — `userId`. Read by property name\n * alone, a key declared apart from its column was never on the captured\n * row, the address fell back to `*`, and no single-row subscriber heard\n * about any write made outside this server.\n */\n private cdcRowByKeyFields(collection: CollectionConfig, row: Record<string, unknown>): Record<string, unknown> {\n const keyed: Record<string, unknown> = { ...row };\n for (const { fieldName, columnName } of collectionKeyColumns(collection, this.registry)) {\n if (fieldName in row) continue;\n if (columnName in row) keyed[fieldName] = row[columnName];\n }\n return keyed;\n }\n\n // ── App/CDC de-duplication ──\n\n private dedupKey(path: string, id: string, databaseId?: string): string {\n return `${databaseId ?? \"\"}::${path}::${id}`;\n }\n\n /**\n * Record that this instance just delivered `key` via the app path.\n *\n * Expired entries are purged so the map cannot grow unbounded under write\n * load — once it holds over 1,000, and at most once per dedup window. The\n * purge walks the whole map, and it used to run on every write past 1,000:\n * inside one window nothing has expired yet, so a burst of writes walked\n * all of it each time (703 ms of event loop for 20,000 marks). Once a\n * window, the map holds at most two windows' writes.\n */\n private markAppEmit(key: string): void {\n const now = Date.now();\n this.recentAppEmits.set(key, now + RealtimeService.CDC_DEDUP_WINDOW_MS);\n if (this.recentAppEmits.size > 1000 && now >= this.nextAppEmitPurgeAt) {\n this.nextAppEmitPurgeAt = now + RealtimeService.CDC_DEDUP_WINDOW_MS;\n for (const [k, expiry] of this.recentAppEmits) {\n if (expiry <= now) this.recentAppEmits.delete(k);\n }\n }\n }\n /** When {@link markAppEmit} may next purge expired entries. */\n private nextAppEmitPurgeAt = 0;\n\n /** Consume a matching app-emit record if present and unexpired; true ⇒ suppress the CDC echo. */\n private consumeAppEmit(key: string): boolean {\n const expiry = this.recentAppEmits.get(key);\n if (expiry === undefined) return false;\n this.recentAppEmits.delete(key);\n return expiry > Date.now();\n }\n\n // =============================================================================\n // Cross-Instance LISTEN/NOTIFY\n // =============================================================================\n\n /**\n * Enable cross-instance realtime broadcasting via Postgres LISTEN/NOTIFY.\n * Listens on a dedicated connection (outside the Drizzle pool) for the\n * changes other instances publish, through a {@link PgNotifyListener}: it\n * proves itself with a heartbeat, is replaced when it drops or stops\n * answering, and every subscription is refetched once it is back, since\n * whatever was published meanwhile reached nobody here.\n *\n * A database that cannot be reached at boot does not fail this call: the\n * listener reports itself down (see {@link health}) and keeps dialling.\n * There is no other cross-instance path to fall back to.\n *\n * This is an **optional** feature — if never called, the backend operates\n * in single-instance mode (the default, perfectly fine for most setups).\n *\n * @param connectionString Raw Postgres connection string for the LISTEN client.\n */\n async startListening(connectionString: string): Promise<void> {\n if (this.broadcasting) {\n logger.warn(\"⚠️ [RealtimeService] startListening called but already listening. Ignoring.\");\n return;\n }\n\n this.broadcasting = true;\n this.crossInstanceListener = new PgNotifyListener({\n connectionString,\n channel: PG_NOTIFY_CHANNEL,\n logLabel: \"[RealtimeService]\",\n onPayload: (payload) => this.handleCrossInstanceNotification(payload),\n onReconnect: () => this.resyncSubscriptions(),\n retryInitialConnect: true,\n applicationName: this.listenerApplicationName\n });\n await this.crossInstanceListener.start();\n logger.info(`📡 [RealtimeService] Cross-instance realtime enabled (instanceId: ${this.instanceId})`);\n }\n\n /**\n * Announce this process's writes on {@link PG_NOTIFY_CHANNEL} without\n * listening for anyone else's.\n *\n * For a process that serves no websockets (`worker`, `functions`) while\n * change capture is not in effect. It has no subscriber of its own, so it\n * holds no LISTEN connection — but without capture the announcement is\n * the only way the `api` hears that a cron or a function wrote a row.\n * Sending needs only the pool.\n */\n startPublishing(): void {\n if (this.broadcasting) return;\n this.broadcasting = true;\n logger.info(`📡 [RealtimeService] Announcing this process's writes to the instances that serve realtime (instanceId: ${this.instanceId})`);\n }\n\n /**\n * Stop listening and clean up the dedicated LISTEN connection.\n */\n async stopListening(): Promise<void> {\n this.broadcasting = false;\n const listener = this.crossInstanceListener;\n if (!listener) return;\n this.crossInstanceListener = undefined;\n await listener.stop();\n logger.info(\"📡 [RealtimeService] Cross-instance realtime disabled.\");\n }\n\n /**\n * Announce a change to the other instances on {@link PG_NOTIFY_CHANNEL},\n * with the rest of its burst. Resolves once the notification carrying it\n * has been sent. Uses the main Drizzle connection (pooled), not the LISTEN\n * client.\n *\n * Changes queued in the same turn of the event loop go out together, as\n * many to a notification as fit under the NOTIFY ceiling: a 1,000-row save\n * used to be 1,000 round trips. A burst of one keeps the single-change\n * shape `{ sid, p, eid, db }`, which an older instance in a rolling deploy\n * still reads; a batch is `{ sid, c: [[p, eid, db], …] }`. Both carry\n * addresses and nothing else.\n */\n private broadcastChange(path: string, id: string, databaseId?: string): Promise<void> {\n const change = { p: path, eid: id, db: databaseId ?? null };\n this.pendingBroadcasts.set(JSON.stringify([change.p, change.eid, change.db]), change);\n this.broadcastFlush ??= new Promise<void>((resolve) => setImmediate(resolve)).then(() => this.flushBroadcasts());\n return this.broadcastFlush;\n }\n\n private async flushBroadcasts(): Promise<void> {\n this.broadcastFlush = undefined;\n const changes = [...this.pendingBroadcasts.values()];\n this.pendingBroadcasts.clear();\n for (const payload of packEntityChanges(this.instanceId, changes)) {\n await this.db.execute(drizzleSql`SELECT pg_notify(${PG_NOTIFY_CHANNEL}, ${payload})`);\n }\n }\n\n /** One notification another instance published on {@link PG_NOTIFY_CHANNEL}: one change, or a batch. */\n private async handleCrossInstanceNotification(payload: string): Promise<void> {\n try {\n const parsed = JSON.parse(payload) as {\n sid: string;\n p?: string;\n eid?: string;\n db?: string | null;\n c?: Array<[string, string, string | null]>;\n };\n\n // Skip our own notifications — already processed locally\n if (parsed.sid === this.instanceId) return;\n\n // A foreign sid is proof of a second process. Nothing here\n // needs that fact, but the channel path does — see\n // `warnIfMemoryBusOnMultiplePods`.\n this.foreignInstanceSeen = true;\n\n const changes: Array<[string, string, string | null]> = Array.isArray(parsed.c)\n ? parsed.c\n : [[parsed.p!, parsed.eid!, parsed.db ?? null]];\n this.debugLog(`📡 [RealtimeService] Received ${changes.length} cross-instance change(s) from ${parsed.sid}`);\n\n // Local fan-out only (broadcast=false, so nothing is re-announced).\n // The row is not read here: every subscriber's frame is a refetch\n // under its own scope, so the marker is all a change has to say.\n for (const [path, id, db] of changes) {\n await this.notifyUpdate(path, id, { _rebase_invalidated: true }, db ?? undefined, false);\n }\n } catch (err) {\n logger.error(\"❌ [RealtimeService] Error processing cross-instance notification\", { error: err });\n }\n }\n}\n\n/**\n * The bytes a notification on the entity channel may take. `pg_notify` refuses\n * 8000 and over; the rest is headroom.\n */\nconst ENTITY_NOTIFY_MAX_BYTES = 7500;\n\n/**\n * Pack `changes` into as few payloads as fit under\n * {@link ENTITY_NOTIFY_MAX_BYTES}. One change keeps the single-change shape.\n */\nexport function packEntityChanges(sid: string, changes: Array<{ p: string; eid: string; db: string | null }>): string[] {\n if (changes.length === 1) {\n const [{ p, eid, db }] = changes;\n return [JSON.stringify({ sid, p, eid, db })];\n }\n const payloads: string[] = [];\n const head = `{\"sid\":${JSON.stringify(sid)},\"c\":[`;\n let batch: string[] = [];\n let bytes = Buffer.byteLength(head) + 2;\n for (const { p, eid, db } of changes) {\n const entry = JSON.stringify([p, eid, db]);\n const entryBytes = Buffer.byteLength(entry) + 1;\n if (batch.length > 0 && bytes + entryBytes > ENTITY_NOTIFY_MAX_BYTES) {\n payloads.push(`${head}${batch.join(\",\")}]}`);\n batch = [];\n bytes = Buffer.byteLength(head) + 2;\n }\n batch.push(entry);\n bytes += entryBytes;\n }\n if (batch.length > 0) payloads.push(`${head}${batch.join(\",\")}]}`);\n return payloads;\n}\n\n/**\n * Alias for RealtimeService for consistent naming with other database implementations.\n * This allows code to use PostgresRealtimeProvider alongside future MongoRealtimeProvider, etc.\n */\nexport const PostgresRealtimeProvider = RealtimeService;\n","import { CollectionRegistry, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { type CollectionConfig } from \"@rebasepro/types\";\nimport { PgEnum, PgTable } from \"drizzle-orm/pg-core\";\nimport { Relations } from \"drizzle-orm\";\nimport { CollectionRegistryInterface } from \"../interfaces\";\nimport { getTableName } from \"@rebasepro/common\";\n\n/**\n * PostgreSQL-specific collection registry.\n * Extends the base CollectionRegistry with support for Drizzle ORM tables, enums, and relations.\n *\n * Satisfies CollectionRegistryInterface through inheritance from CollectionRegistry.\n */\nexport class PostgresCollectionRegistry extends CollectionRegistry implements CollectionRegistryInterface {\n\n private tables = new Map<string, PgTable>();\n private enums = new Map<string, PgEnum<[string, ...string[]]>>();\n private relations = new Map<string, Relations>();\n\n registerTable(table: PgTable, tableName: string) {\n this.tables.set(tableName, table);\n }\n\n getTable(tableName: string): PgTable | undefined {\n return this.tables.get(tableName);\n }\n\n /**\n * Checks if a specific collection has a registered table\n */\n hasTableForCollection(tableName: string): boolean {\n return this.tables.has(tableName);\n }\n\n /**\n * Returns all registered table names.\n */\n getTableNames(): string[] {\n return Array.from(this.tables.keys());\n }\n\n /**\n * Finds collections assigned to a specific data source that do not have a registered table.\n */\n getCollectionsWithoutTables(dataSourceKey = \"(default)\"): CollectionConfig[] {\n const collections = this.getCollections().filter(\n c => c.dataSource === dataSourceKey || (!c.dataSource && dataSourceKey === \"(default)\")\n );\n return collections.filter(c => !this.tables.has(getTableName(c)));\n }\n\n registerEnums(enums: Record<string, PgEnum<[string, ...string[]]>>) {\n Object.entries(enums).forEach(([name, value]) => this.enums.set(name, value));\n }\n\n registerRelations(relations: Record<string, Relations>) {\n Object.entries(relations).forEach(([name, value]) => this.relations.set(name, value));\n }\n\n getEnum(name: string): PgEnum<[string, ...string[]]> | undefined {\n return this.enums.get(name);\n }\n\n getRelation(name: string): Relations | undefined {\n return this.relations.get(name);\n }\n\n getAllEnums(): Record<string, PgEnum<[string, ...string[]]>> {\n return Object.fromEntries(this.enums.entries());\n }\n\n getAllRelations(): Record<string, Relations> {\n return Object.fromEntries(this.relations.entries());\n }\n\n /**\n * Get the merged schema object (tables + relations) for use with Drizzle's\n * relational query API (`db.query`).\n */\n getMergedSchema(): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [name, table] of this.tables.entries()) {\n result[name] = table;\n }\n for (const [name, relation] of this.relations.entries()) {\n result[name] = relation;\n }\n return result;\n }\n\n /**\n * Get the available Drizzle relation keys for a given collection path.\n * Maps from the collection's relation property names to the Drizzle relation names\n * defined in the schema.\n */\n getRelationKeysForCollection(collectionPath: string): string[] {\n const collection = this.getCollectionByPath(collectionPath);\n if (!collection) return [];\n // Resolved, not authored. `relationName` is optional at the authoring\n // surface and defaults to the property key or the target's slug, so\n // reading the raw field dropped every relation that relied on the\n // default — and never saw relations declared inline on a property at\n // all, since those are not in the `relations` array.\n return Object.keys(resolveCollectionRelations(collection));\n }\n\n}\n\n","import type { PgTable, AnyPgColumn } from \"drizzle-orm/pg-core\";\n\n/** Drizzle PgTable with column access by name. Runtime Drizzle tables satisfy this shape. */\nexport type RebasePgTable = PgTable & Record<string, AnyPgColumn>;\n\n/**\n * Read a statically-declared drizzle table as one whose columns are looked up\n * by name.\n *\n * The auth services take either the tables this package declares or a host\n * application's own, and reach their columns through strings — `has(\"aal\")`,\n * `col(\"sessionId\")` — because which columns exist is a property of the\n * database in front of them, not of the schema this package compiled against.\n * That is what {@link RebasePgTable} is for.\n *\n * A concrete table does not satisfy it: its columns have specific types and it\n * carries methods and symbols besides, so `Record<string, AnyPgColumn>` is not\n * a supertype and a direct `as` is refused. Written out, that refusal was\n * answered six times with `as unknown as RebasePgTable`, which also discarded\n * the check that the thing being converted is a table at all — the parameter\n * below is what restores it.\n */\nexport function asRebasePgTable(table: PgTable): RebasePgTable {\n return table as RebasePgTable;\n}\n","import { getTableColumns } from \"drizzle-orm\";\nimport { PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, ResolvedRelation } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { generateForeignKeyName, legacyForeignKeyName } from \"@rebasepro/utils\";\n\nimport { PostgresCollectionRegistry } from \"./PostgresCollectionRegistry\";\nimport { getPrimaryKeys } from \"../services/collection-helpers\";\n\n/**\n * Check every relation against the schema it actually runs on, at boot.\n *\n * \"Actually runs on\" is now literal: the registry's tables are read back from\n * `information_schema` (see `catalogue-schema.ts`), so this compares the\n * collections against the database. It used to compare them against the\n * committed `schema.generated.ts`, which made a *stale file* able to refuse a\n * boot — the exact incident class from 0.13, where boot-ensure had already\n * renamed the column and the file had not caught up.\n *\n * The tagged union made the *shape* of a relation impossible to get wrong: a\n * `manyToMany` cannot carry a `foreignKeyOnTarget`, a to-many cannot carry a\n * `localKey`. What it cannot know is whether any of the names are real —\n * whether `posts_tags` is a table, whether `author_id` is a column, whether a\n * `joinPath` connects the tables it claims to. Those are facts about the\n * database, and the type system never sees them.\n *\n * Until now nothing checked them until a query ran, and the failures were the\n * quiet kind. A missing junction table logged a warning and returned no rows,\n * so `posts/1/tags` answered `[]` — indistinguishable from a post with no tags.\n * The relation looked configured, the admin drew the tab, the tab was empty,\n * and nothing anywhere said why.\n *\n * The junction default is the sharp edge this exists for. `through.table`\n * defaults to the two table names sorted and joined, so renaming a table\n * silently re-points the relation at a name that was never created. It is the\n * one default whose output changes when you edit something that looks\n * unrelated.\n */\nexport interface RelationDefect {\n /** Slug of the collection declaring the relation. */\n collection: string;\n relationName: string;\n kind: ResolvedRelation[\"kind\"];\n /** What is wrong, in terms of the schema. */\n problem: string;\n /** The edit that fixes it. */\n fix: string;\n}\n\n/**\n * Every name a column answers to: the key in the drizzle schema and the real\n * column name in Postgres. A relation may legitimately be written with either,\n * and reporting a working relation as broken is worse than not checking.\n */\nfunction columnNames(table: PgTable): Set<string> {\n const names = new Set<string>();\n for (const [key, col] of Object.entries(getTableColumns(table))) {\n names.add(key);\n const dbName = (col as { name?: string })?.name;\n if (dbName) names.add(dbName);\n }\n return names;\n}\n\nconst quote = (xs: Iterable<string>) => Array.from(xs).map(s => `\\`${s}\\``).join(\", \");\n\n/** `on.from` / `on.to` accept a single column or a composite tuple. */\nconst asColumns = (value: string | string[]): string[] => Array.isArray(value) ? value : [value];\n\n/**\n * A one-column link into a key of several columns, or `undefined`.\n *\n * A `localKey`, a `foreignKeyOnTarget` and a junction column are each one\n * column, and one column cannot reference a composite key: Postgres refuses\n * `REFERENCES t (a)` when `a` alone is not unique, and a lookup on `a` matches\n * every row that shares it, so the related row served is whichever came first.\n * The schema generators refuse to emit such a link; this refuses to serve one.\n *\n * `via` is exempt — its steps name every column they compare — and so is a\n * `hasOne`/`hasMany` with a `sourceKey`, which names the one column it points\n * at instead of the key.\n */\nfunction compositeKeyDefect(\n relation: ResolvedRelation,\n source: CollectionConfig,\n target: CollectionConfig,\n registry: PostgresCollectionRegistry\n): Pick<RelationDefect, \"problem\" | \"fix\"> | undefined {\n const viaFix = \"or express the link as `kind: \\\"via\\\"`, whose `joinPath` compares every key column\";\n const refuse = (linkColumn: string, keyed: CollectionConfig, fix: string) => {\n const keys = getPrimaryKeys(keyed, registry);\n if (keys.length < 2) return undefined;\n return {\n problem: `${linkColumn} is one column, and \\`${keyed.slug}\\` is keyed on ` +\n `${quote(keys.map(key => key.fieldName))} together — one column cannot reference a composite key`,\n fix\n };\n };\n\n switch (relation.kind) {\n case \"belongsTo\":\n return refuse(`\\`localKey: \"${relation.localKey}\"\\``, target,\n `point the relation at a collection with a single-column key, ${viaFix}`);\n case \"hasOne\":\n case \"hasMany\":\n if (relation.sourceKey) return undefined;\n return refuse(`\\`foreignKeyOnTarget: \"${relation.foreignKeyOnTarget}\"\\``, source,\n `give the relation a \\`sourceKey\\` naming a single unique column on \\`${source.slug}\\` to point at, ${viaFix}`);\n case \"manyToMany\":\n return refuse(`\\`through.sourceColumn: \"${relation.through.sourceColumn}\"\\``, source, `a junction column points at a single-column key; ${viaFix}`)\n ?? refuse(`\\`through.targetColumn: \"${relation.through.targetColumn}\"\\``, target, `a junction column points at a single-column key; ${viaFix}`);\n case \"via\":\n return undefined;\n }\n}\n\n/**\n * Distinguish \"this column name is wrong\" from \"this database was never\n * migrated\".\n *\n * They present identically here — a relation asks for a column the table does\n * not have — but they are opposite problems with opposite fixes, and getting\n * them the wrong way round is how the 0.12 → 0.13 upgrade bricked projects.\n *\n * 0.13 changed the rule that derives foreign-key names: `categories` yields\n * `category_id` where it used to yield `categorie_id`. Boot-ensure renames the\n * database column to match — but only in a process that provisions. Where\n * nothing has, the database still carries the old spelling while the config\n * derives the new one, and the generic fix (\"set `through.targetColumn` to one\n * of: …\", listing the legacy name because that is what the table still has)\n * talks the reader into pinning a column that is about to be renamed out from\n * under them.\n *\n * So when the wanted name is what the current rule derives, and the table\n * carries what the *previous* rule would have derived from the same source, say\n * that instead.\n *\n * @param wanted the column the relation asks for\n * @param available every column the registered table has\n * @param sources names the default could have been derived from (a slug, a\n * relation name) — checking against these rather than guessing\n * backwards from `wanted` keeps the match exact\n */\nfunction staleCodegenRename(\n wanted: string,\n available: Set<string>,\n sources: string[]\n): { legacy: string; current: string } | null {\n for (const source of sources) {\n if (!source) continue;\n const current = generateForeignKeyName(source);\n const legacy = legacyForeignKeyName(source);\n // Only a name that actually moved, and only when the table still has the\n // old spelling and not the new one.\n if (current !== wanted || legacy === current) continue;\n if (available.has(legacy) && !available.has(current)) return { legacy, current };\n }\n return null;\n}\n\n/** The shared explanation, so every relation kind reports it identically. */\nfunction staleCodegenDefect(\n table: string,\n { legacy, current }: { legacy: string; current: string }\n): Pick<RelationDefect, \"problem\" | \"fix\"> {\n return {\n problem:\n `the database still has \\`${legacy}\\` on \\`${table}\\`, but this release derives ` +\n `\\`${current}\\` — the column predates the foreign-key naming fix and nothing has ` +\n \"renamed it yet\",\n fix:\n \"let boot-ensure rename it (redeploy, or start with schema provisioning enabled), or \" +\n \"run `rebase db push`. Then run `rebase schema generate` so the committed schema file \" +\n `agrees. To keep \\`${legacy}\\` instead, name it explicitly on the relation.`\n };\n}\n\n/**\n * Relations whose names do not resolve against the registered schema.\n *\n * Fails open wherever it cannot see enough to be sure — an unregistered source\n * table, a target belonging to another backend — because a false alarm here\n * costs more than a missed one: it would block boot on a working app.\n */\nexport function findRelationDefects(\n collections: CollectionConfig[],\n registry: PostgresCollectionRegistry,\n /**\n * The collections the backend was given, when the registry also holds\n * ones it reached only as a relation's target. A target outside them has\n * no table built for it, and the fix is the array, not the database.\n */\n given?: readonly CollectionConfig[]\n): RelationDefect[] {\n const defects: RelationDefect[] = [];\n const registeredSlugs = new Set(registry.getCollections().map(c => c.slug));\n const givenSlugs = given ? new Set(given.map(c => c.slug)) : undefined;\n\n for (const collection of collections) {\n const sourceTableName = getTableName(collection);\n const sourceTable = registry.getTable(sourceTableName);\n // Nothing to check against. Another boot warning already covers this.\n if (!sourceTable) continue;\n\n const sourceColumns = columnNames(sourceTable);\n const relations = resolveCollectionRelations(collection);\n\n for (const relation of Object.values(relations)) {\n const at = { collection: collection.slug,\nrelationName: relation.relationName,\nkind: relation.kind };\n\n let targetCollection: CollectionConfig;\n try {\n targetCollection = relation.target();\n } catch (e) {\n defects.push({\n ...at,\n problem: `its \\`target()\\` threw: ${e instanceof Error ? e.message : String(e)}`,\n fix: \"a target thunk usually throws because of a circular import — make sure it is `() => otherCollection` and not evaluated at module load\"\n });\n continue;\n }\n\n // A target this registry has never heard of belongs to another\n // backend; its tables are not ours to check.\n if (!registeredSlugs.has(targetCollection.slug)) continue;\n\n const targetTableName = getTableName(targetCollection);\n const targetTable = registry.getTable(targetTableName);\n if (!targetTable) {\n defects.push(givenSlugs && !givenSlugs.has(targetCollection.slug)\n ? {\n ...at,\n problem: `it points at collection \\`${targetCollection.slug}\\`, which is not among the collections ` +\n \"this backend was given — it is registered only as this relation's target, so no table was built for it\",\n fix: `add the \\`${targetCollection.slug}\\` collection to the \\`collections\\` you pass, ` +\n \"or pass every collection the relations reach\"\n }\n : {\n ...at,\n problem: `it points at collection \\`${targetCollection.slug}\\`, which has no table \\`${targetTableName}\\` in the schema`,\n fix: `create the \\`${targetTableName}\\` table, or correct \\`table\\` on the \\`${targetCollection.slug}\\` collection`\n });\n continue;\n }\n const targetColumns = columnNames(targetTable);\n\n // A single-column link into a composite key, whichever side the\n // key is on.\n const composite = compositeKeyDefect(relation, collection, targetCollection, registry);\n if (composite) {\n defects.push({ ...at, ...composite });\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\": {\n if (!sourceColumns.has(relation.localKey)) {\n // `localKey` defaults to the relation name run through\n // the foreign-key rule, so it moves with that rule.\n const stale = staleCodegenRename(\n relation.localKey,\n sourceColumns,\n [relation.relationName, targetCollection.slug]\n );\n defects.push(stale\n ? { ...at, ...staleCodegenDefect(sourceTableName, stale) }\n : {\n ...at,\n problem: `\\`localKey: \"${relation.localKey}\"\\` is not a column on \\`${sourceTableName}\\``,\n fix: `add the column, or set \\`localKey\\` to one of: ${quote(sourceColumns)}`\n });\n }\n break;\n }\n\n case \"hasOne\":\n case \"hasMany\": {\n if (!targetColumns.has(relation.foreignKeyOnTarget)) {\n // The default is derived from *this* collection's slug —\n // the column on the target that points back here.\n const stale = staleCodegenRename(\n relation.foreignKeyOnTarget,\n targetColumns,\n [collection.slug]\n );\n defects.push(stale\n ? { ...at, ...staleCodegenDefect(targetTableName, stale) }\n : {\n ...at,\n problem: `\\`foreignKeyOnTarget: \"${relation.foreignKeyOnTarget}\"\\` is not a column on the target table \\`${targetTableName}\\``,\n fix: `add the column, or set \\`foreignKeyOnTarget\\` to one of: ${quote(targetColumns)}`\n });\n }\n // `sourceKey` is the easiest of the two to put on the wrong\n // side — it is the only column in a `hasMany` that lives\n // here rather than on the target, and naming a target column\n // reads perfectly well right next to `foreignKeyOnTarget`.\n if (relation.sourceKey && !sourceColumns.has(relation.sourceKey)) {\n defects.push({\n ...at,\n problem: `\\`sourceKey: \"${relation.sourceKey}\"\\` is not a column on \\`${sourceTableName}\\``,\n fix: targetColumns.has(relation.sourceKey)\n ? `it is a column on the *target* table \\`${targetTableName}\\` — \\`sourceKey\\` names ` +\n \"the column on this collection that the target's foreign key points at, so it \" +\n `must be one of: ${quote(sourceColumns)}`\n : `add the column, or set \\`sourceKey\\` to one of: ${quote(sourceColumns)}`\n });\n }\n break;\n }\n\n case \"manyToMany\": {\n const { table, sourceColumn, targetColumn } = relation.through;\n const junction = registry.getTable(table);\n if (!junction) {\n defects.push({\n ...at,\n problem: `its junction table \\`${table}\\` does not exist`,\n fix: `create \\`${table}\\`, or name the real one with \\`through: { table: \"...\" }\\`. ` +\n \"Note that an omitted `through.table` is derived from the two table names sorted \" +\n \"and joined, so renaming a table changes it\"\n });\n break;\n }\n const junctionColumns = columnNames(junction);\n // Junction columns are the ones that actually moved in 0.13:\n // each defaults to its endpoint collection's *slug* run\n // through the foreign-key rule, and slugs are plural.\n const derivedFrom = {\n sourceColumn: [collection.slug],\n targetColumn: [targetCollection.slug]\n } as const;\n for (const [label, column] of [[\"sourceColumn\", sourceColumn], [\"targetColumn\", targetColumn]] as const) {\n if (!junctionColumns.has(column)) {\n const stale = staleCodegenRename(column, junctionColumns, [...derivedFrom[label]]);\n defects.push(stale\n ? { ...at, ...staleCodegenDefect(table, stale) }\n : {\n ...at,\n problem: `\\`through.${label}: \"${column}\"\\` is not a column on the junction table \\`${table}\\``,\n fix: `set \\`through.${label}\\` to one of: ${quote(junctionColumns)}` +\n (label === \"sourceColumn\" ? \" — it is the column naming *this* collection\" : \"\")\n });\n }\n }\n break;\n }\n\n case \"via\": {\n if (relation.joinPath.length === 0) {\n defects.push({\n ...at,\n problem: \"its `joinPath` is empty, so it joins nothing\",\n fix: \"add at least one step, ending at the target's table\"\n });\n break;\n }\n\n // Walk the chain: each step's `from` names columns on the\n // previous table, its `to` names columns on its own.\n let prevName = sourceTableName;\n let prevColumns = sourceColumns;\n let broken = false;\n\n for (const [i, step] of relation.joinPath.entries()) {\n const stepTable = registry.getTable(step.table);\n if (!stepTable) {\n defects.push({\n ...at,\n problem: `step ${i + 1} of its \\`joinPath\\` joins \\`${step.table}\\`, which is not a table in the schema`,\n fix: `correct \\`joinPath[${i}].table\\``\n });\n broken = true;\n break;\n }\n const stepColumns = columnNames(stepTable);\n\n for (const column of asColumns(step.on.from)) {\n if (!prevColumns.has(column)) {\n defects.push({\n ...at,\n problem: `step ${i + 1} joins \\`${prevName}.${column}\\` → \\`${step.table}\\`, but \\`${column}\\` is not a column on \\`${prevName}\\``,\n fix: `\\`joinPath[${i}].on.from\\` names columns on ${i === 0 ? \"this collection's table\" : `the previous step's table (\\`${prevName}\\`)`}: ${quote(prevColumns)}`\n });\n }\n }\n for (const column of asColumns(step.on.to)) {\n if (!stepColumns.has(column)) {\n defects.push({\n ...at,\n problem: `step ${i + 1} joins into \\`${step.table}.${column}\\`, but \\`${column}\\` is not a column on \\`${step.table}\\``,\n fix: `\\`joinPath[${i}].on.to\\` names columns on \\`${step.table}\\`: ${quote(stepColumns)}`\n });\n }\n }\n\n if (asColumns(step.on.from).length !== asColumns(step.on.to).length) {\n defects.push({\n ...at,\n problem: `step ${i + 1} compares ${asColumns(step.on.from).length} column(s) against ${asColumns(step.on.to).length}`,\n fix: `\\`from\\` and \\`to\\` must name the same number of columns in \\`joinPath[${i}]\\``\n });\n }\n\n prevName = step.table;\n prevColumns = stepColumns;\n }\n\n // The chain has to end where the relation says it points,\n // or the rows it returns are not the target's rows.\n if (!broken && prevName !== targetTableName) {\n defects.push({\n ...at,\n problem: `its \\`joinPath\\` ends at \\`${prevName}\\`, but it targets \\`${targetCollection.slug}\\` (table \\`${targetTableName}\\`)`,\n fix: `make the last step join \\`${targetTableName}\\`, or point \\`target\\` at the collection backed by \\`${prevName}\\``\n });\n }\n break;\n }\n\n default: {\n const exhaustive: never = relation;\n throw new Error(`Unhandled relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n }\n }\n\n return defects;\n}\n\n/**\n * Fail boot on any relation that cannot resolve, listing all of them at once.\n *\n * Deliberately fatal rather than a warning. Every one of these produces an\n * empty result at query time and nothing else — an empty tab, an empty\n * `include`, a subcollection that looks like it has no rows. A server that\n * refuses to start is recoverable in a minute; a relation that quietly answers\n * \"nothing\" is the kind of bug found in production, weeks later, by a user\n * asking where their data went.\n */\nexport function assertRelationsResolve(\n collections: CollectionConfig[],\n registry: PostgresCollectionRegistry,\n given?: readonly CollectionConfig[]\n): void {\n const defects = findRelationDefects(collections, registry, given);\n if (defects.length === 0) return;\n\n const lines = defects.map(d =>\n ` • ${d.collection}.${d.relationName} (${d.kind})\\n` +\n ` ${d.problem}\\n` +\n ` fix: ${d.fix}`\n );\n\n throw new Error(\n `${defects.length} relation${defects.length === 1 ? \"\" : \"s\"} cannot resolve against ` +\n \"the database this server is connected to.\\n\\n\" +\n \"Each of these would return no rows at query time rather than reporting an error, \" +\n \"so they are fatal at boot instead.\\n\\n\" +\n // The columns listed below were read from `information_schema`, not from\n // `schema.generated.ts`. That distinction is the whole diagnosis after an\n // upgrade: this check used to read the committed file, so a file older\n // than the database refused a boot that was fine, and its advice named\n // columns that no longer existed. What is left is a real disagreement\n // between the collections and the database — either the schema has not\n // been applied here, or the relation names something that was never\n // there.\n \"If the schema has not been applied to this database yet, apply it — `rebase db push`\\n\" +\n \"(dev), `rebase db migrate` (prod), or a redeploy, which creates tables at boot. If it\\n\" +\n \"has, then the collection is what disagrees with the database:\\n\\n\" +\n lines.join(\"\\n\\n\") + \"\\n\"\n );\n}\n","import { isTable, getTableName, Relations } from \"drizzle-orm\";\nimport { PgEnum, PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\nimport { PostgresCollectionRegistry } from \"./PostgresCollectionRegistry\";\nimport { warnOnKeysTheAdminCannotResolve } from \"../services/collection-helpers\";\nimport { assertRelationsResolve } from \"./validate-relations\";\n\n/**\n * Everything a registry is built from: the collections, and the drizzle schema\n * they are backed by. In BaaS mode all of it is introspected from the live\n * database; when collections are declared it comes from the config and the generated schema.\n */\nexport interface RegistrySchema {\n collections?: CollectionConfig[];\n tables?: Record<string, unknown>;\n enums?: Record<string, PgEnum<[string, ...string[]]>>;\n relations?: Record<string, Relations>;\n}\n\n/**\n * Build the collection registry for a driver.\n *\n * The order matters and is the reason this is one function rather than a run of\n * statements in the bootstrapper. Keys are resolved from the drizzle schema, so\n * anything that inspects them has to run *after* the tables are registered —\n * and `warnOnKeysTheAdminCannotResolve` fails open if it does not, because a\n * collection whose table it cannot look up is one it has nothing to say about.\n * Warned too early, it would skip every collection and report nothing, which\n * reads exactly like having nothing to report.\n */\nexport function buildCollectionRegistry(schema: RegistrySchema): PostgresCollectionRegistry {\n const registry = new PostgresCollectionRegistry();\n\n if (schema.collections) {\n registry.registerMultiple(schema.collections);\n // `Auto-discovered collections` already reports the count and the\n // directory they came from; this is the same fact with the names.\n logger.debug(\n `📋 [PostgresRegistry] Registered ${registry.getCollections().length} collections: ` +\n `[${registry.getCollections().map(c => c.slug).join(\", \")}]`\n );\n }\n\n if (schema.tables) {\n Object.values(schema.tables).forEach((table) => {\n if (isTable(table)) {\n registry.registerTable(table as PgTable, getTableName(table));\n }\n });\n }\n\n if (schema.enums) registry.registerEnums(schema.enums);\n if (schema.relations) registry.registerRelations(schema.relations);\n\n // Now that the keys resolve: say which of them the admin cannot see. It\n // compiles the same collection files into its bundle but never the drizzle\n // schema, and nothing serves it one, so only an edit to the config fixes it.\n warnOnKeysTheAdminCannotResolve(registry.getCollections(), registry);\n\n // And now that the tables resolve: refuse to start on a relation whose\n // names do not exist. The union checks a relation's shape at compile time;\n // only here is there a schema to check its *names* against. Every one of\n // these used to surface as an empty result at query time and nothing else.\n assertRelationsResolve(registry.getCollections(), registry, schema.collections);\n\n return registry;\n}\n","import { eq, getTableName, sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { getTableConfig, type AnyPgColumn } from \"drizzle-orm/pg-core\";\nimport { asRebasePgTable, type RebasePgTable } from \"../types\";\nimport { users, refreshTokens, passwordResetTokens, userIdentities, magicLinkTokens } from \"../schema/auth-schema\";\nimport {\n UserRepository,\n TokenRepository,\n MfaRepository,\n AuthRepository,\n UserData,\n CreateUserData,\n RefreshTokenInfo,\n RefreshTokenSession,\n PasswordResetTokenInfo,\n MagicLinkTokenInfo,\n UserIdentityData,\n ListUsersOptions,\n PaginatedUsersResult,\n MfaFactor,\n MfaChallengeInfo,\n AccountAccessState,\n PendingEmailChange,\n ApiError\n} from \"@rebasepro/server\";\nimport { toSnakeCase, camelCase } from \"@rebasepro/utils\";\nimport { escapeLikePattern } from \"../utils/drizzle-conditions\";\nimport { extractPgError } from \"../utils/pg-error-utils\";\n\nexport interface AuthSchemaTables {\n users: RebasePgTable;\n refreshTokens: RebasePgTable;\n passwordResetTokens: RebasePgTable;\n appConfig: RebasePgTable;\n userIdentities: RebasePgTable;\n}\n\nfunction getColumnKey(table: RebasePgTable | undefined, ...keys: string[]): string | undefined {\n if (!table) return undefined;\n for (const key of keys) {\n if (key in table) return key;\n const snake = toSnakeCase(key);\n if (snake in table) return snake;\n const camel = camelCase(key);\n if (camel in table) return camel;\n }\n return undefined;\n}\n\n/** `\"schema\".\"table\"`, for the statements written as raw SQL. */\nfunction qualifiedTableName(table: RebasePgTable): string {\n const schema = getTableConfig(table).schema || \"public\";\n return `\"${schema}\".\"${getTableName(table)}\"`;\n}\n\n/** The shape of a session id sign-in mints (`randomUUID()`). */\nconst UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nfunction getColumn(table: RebasePgTable | undefined, ...keys: string[]): RebasePgTable[string] | undefined {\n if (!table) return undefined;\n const key = getColumnKey(table, ...keys);\n return key ? table[key] : undefined;\n}\n\n/**\n * The single definition of what an email address looks like in storage.\n *\n * Reads have always folded case; writes did not, and normalising was left to\n * each caller. That asymmetry is only ever one forgotten `.toLowerCase()` away\n * from a row no lookup can find — the account exists, every sign-in path\n * reports no such user, and the byte-exact UNIQUE on the column does not stop a\n * duplicate differing only in case. Applied on both sides here so the guarantee\n * belongs to the repository rather than to its callers' discipline; the\n * `lower(email)` unique index added in `ensureAuthTablesExist` is the database\n * half of the same rule.\n *\n * Whitespace goes too: a trailing space survives the fold and reproduces the\n * problem exactly.\n *\n * Re-exported rather than defined here: `@rebasepro/server` and\n * `@rebasepro/server-mongo` write this column too, and a second copy of this\n * rule is the defect it exists to prevent.\n */\nimport { normalizeEmail } from \"@rebasepro/common\";\nexport { normalizeEmail };\n\n/**\n * PostgreSQL implementation of UserRepository.\n * Handles all user-related database operations using Drizzle ORM.\n */\nexport class UserService implements UserRepository {\n private usersTable: RebasePgTable;\n private userIdentitiesTable: RebasePgTable;\n /** Read by `getAccountAccessState`, to say whether one sign-in is still live. */\n private refreshTokensTable: RebasePgTable;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).users)) {\n const tables = tableOrTables as Partial<AuthSchemaTables>;\n this.usersTable = (tables.users || users) as RebasePgTable;\n this.userIdentitiesTable = (tables.userIdentities || userIdentities) as RebasePgTable;\n this.refreshTokensTable = (tables.refreshTokens || refreshTokens) as RebasePgTable;\n } else {\n const table = tableOrTables as RebasePgTable | undefined;\n this.usersTable = table || asRebasePgTable(users);\n this.userIdentitiesTable = asRebasePgTable(userIdentities);\n this.refreshTokensTable = asRebasePgTable(refreshTokens);\n }\n }\n\n private getQualifiedUsersTableName(): string {\n const name = getTableName(this.usersTable);\n const schema = getTableConfig(this.usersTable).schema || \"public\";\n return `\"${schema}\".\"${name}\"`;\n }\n\n /**\n * Run a privileged auth write with an explicitly cleared RLS context.\n *\n * The auth services run on the base/owner connection, which by design\n * carries a NULL `app.uid` so the `rebase.uid() IS NULL` server-escape\n * in the default policies applies. That NULL is normally guaranteed by\n * `set_config(..., is_local = true)` resetting at transaction end — but a\n * GUC that survives on a pooled connection (or a connection role that\n * doesn't bypass RLS: FORCE ROW LEVEL SECURITY, or a non-owner role)\n * turns the trusted write into an RLS-scoped one and denies it with\n * SQLSTATE 42501. Clearing the GUCs here, transaction-locally at the\n * single chokepoint, makes the server context deterministic instead of\n * trusting whatever state the pool hands us. `rebase.uid()` reads '' as\n * NULL via NULLIF, so '' is the server context.\n */\n private async withServerContext<T>(fn: (db: NodePgDatabase) => Promise<T>): Promise<T> {\n return await this.db.transaction(async (tx) => {\n await tx.execute(sql`\n SELECT set_config('app.uid', '', true),\n set_config('app.user_id', '', true),\n set_config('app.user_roles', '', true),\n set_config('app.jwt', '', true)\n `);\n return await fn(tx as NodePgDatabase);\n });\n }\n\n private mapRowToUser(row: Record<string, unknown>): UserData {\n if (!row) return row as UserData;\n\n const id = (row.id ?? row.uid) as string;\n const email = row.email as string;\n const passwordHash = (row.password_hash ?? row.passwordHash ?? null) as string | null | undefined;\n const displayName = (row.display_name ?? row.displayName ?? null) as string | null | undefined;\n const photoUrl = (row.photo_url ?? row.photoUrl ?? row.photoURL ?? null) as string | null | undefined;\n const emailVerified = (row.email_verified ?? row.emailVerified ?? false) as boolean;\n const emailVerificationToken = (row.email_verification_token ?? row.emailVerificationToken ?? null) as string | null | undefined;\n const emailVerificationSentAt = (row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null) as string | number | Date | null;\n const isAnonymous = (row.is_anonymous ?? row.isAnonymous ?? false) as boolean;\n const disabledAt = row.disabled_at ?? row.disabledAt ?? null;\n const createdAt = (row.created_at ?? row.createdAt) as string | number | Date | undefined;\n const updatedAt = (row.updated_at ?? row.updatedAt) as string | number | Date | undefined;\n\n const metadata: Record<string, any> = { ...((row.metadata as Record<string, any> | undefined) || {}) };\n\n const knownKeys = new Set([\n \"id\", \"uid\", \"email\",\n \"password_hash\", \"passwordHash\",\n \"display_name\", \"displayName\",\n \"photo_url\", \"photoUrl\", \"photoURL\",\n \"email_verified\", \"emailVerified\",\n \"email_verification_token\", \"emailVerificationToken\",\n \"email_verification_sent_at\", \"emailVerificationSentAt\",\n \"is_anonymous\", \"isAnonymous\",\n \"disabled_at\", \"disabledAt\",\n \"tokens_valid_after\", \"tokensValidAfter\",\n // Never metadata: the change is the account's business, and the\n // token's hash is a secret.\n \"pending_email\", \"pendingEmail\",\n \"email_change_token\", \"emailChangeToken\",\n \"email_change_sent_at\", \"emailChangeSentAt\",\n \"roles\",\n \"created_at\", \"createdAt\",\n \"updated_at\", \"updatedAt\",\n \"metadata\"\n ]);\n\n for (const [key, val] of Object.entries(row)) {\n if (!knownKeys.has(key)) {\n const camelKey = camelCase(key);\n metadata[camelKey] = val;\n }\n }\n\n return {\n id,\n email,\n passwordHash,\n displayName,\n photoUrl,\n emailVerified,\n emailVerificationToken,\n emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,\n isAnonymous,\n ...(disabledAt ? { disabled: true } : {}),\n createdAt: createdAt ? new Date(createdAt) : new Date(),\n updatedAt: updatedAt ? new Date(updatedAt) : new Date(),\n metadata\n };\n }\n\n private mapPayload(data: Partial<CreateUserData>): Record<string, unknown> {\n if (!data) return {};\n\n const payload: Record<string, unknown> = {};\n\n const idKey = getColumnKey(this.usersTable, \"id\") || \"id\";\n const emailKey = getColumnKey(this.usersTable, \"email\") || \"email\";\n const passwordHashKey = getColumnKey(this.usersTable, \"passwordHash\", \"password_hash\") || \"passwordHash\";\n const displayNameKey = getColumnKey(this.usersTable, \"displayName\", \"display_name\") || \"displayName\";\n const photoUrlKey = getColumnKey(this.usersTable, \"photoUrl\", \"photo_url\") || \"photoUrl\";\n const emailVerifiedKey = getColumnKey(this.usersTable, \"emailVerified\", \"email_verified\") || \"emailVerified\";\n const emailVerificationTokenKey = getColumnKey(this.usersTable, \"emailVerificationToken\", \"email_verification_token\") || \"emailVerificationToken\";\n const emailVerificationSentAtKey = getColumnKey(this.usersTable, \"emailVerificationSentAt\", \"email_verification_sent_at\") || \"emailVerificationSentAt\";\n const isAnonymousKey = getColumnKey(this.usersTable, \"isAnonymous\", \"is_anonymous\") || \"isAnonymous\";\n const createdAtKey = getColumnKey(this.usersTable, \"createdAt\", \"created_at\") || \"createdAt\";\n const updatedAtKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n const metadataKey = getColumnKey(this.usersTable, \"metadata\") || \"metadata\";\n\n if (\"id\" in data) payload[idKey] = data.id;\n if (\"email\" in data) payload[emailKey] = normalizeEmail(data.email);\n if (\"passwordHash\" in data) payload[passwordHashKey] = data.passwordHash;\n if (\"displayName\" in data) payload[displayNameKey] = data.displayName;\n if (\"photoUrl\" in data) payload[photoUrlKey] = data.photoUrl;\n if (\"emailVerified\" in data) payload[emailVerifiedKey] = data.emailVerified;\n if (\"emailVerificationToken\" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;\n if (\"emailVerificationSentAt\" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;\n if (\"isAnonymous\" in data) payload[isAnonymousKey] = data.isAnonymous;\n if (\"createdAt\" in data) payload[createdAtKey] = data.createdAt;\n if (\"updatedAt\" in data) payload[updatedAtKey] = data.updatedAt;\n\n const metadata: Record<string, any> = { ...(data.metadata || {}) };\n const remainingMetadata: Record<string, any> = {};\n\n for (const [key, val] of Object.entries(metadata)) {\n const tableColKey = getColumnKey(this.usersTable, key);\n if (tableColKey &&\n tableColKey !== idKey &&\n tableColKey !== emailKey &&\n tableColKey !== passwordHashKey &&\n tableColKey !== displayNameKey &&\n tableColKey !== photoUrlKey &&\n tableColKey !== emailVerifiedKey &&\n tableColKey !== emailVerificationTokenKey &&\n tableColKey !== emailVerificationSentAtKey &&\n tableColKey !== isAnonymousKey &&\n tableColKey !== createdAtKey &&\n tableColKey !== updatedAtKey &&\n tableColKey !== metadataKey) {\n payload[tableColKey] = val;\n } else {\n remainingMetadata[key] = val;\n }\n }\n\n if (metadataKey in this.usersTable) {\n payload[metadataKey] = remainingMetadata;\n }\n\n return payload;\n }\n\n /**\n * @see UserRepository.createUser — an email already in use is a 409.\n *\n * The route checks first and answers 409; this is the same answer for the\n * requests that get past the check, which two clicks on a signup button\n * are enough to produce. `PersistService` has mapped `23505` to a conflict\n * for collection writes since the layer that holds the SQLSTATE was made\n * responsible for saying whose fault a failure is; the auth writes never\n * got the same treatment and reached the client as \"Internal Server Error\".\n */\n async createUser(data: CreateUserData): Promise<UserData> {\n const payload = this.mapPayload(data);\n try {\n const [row] = await this.withServerContext(async (db) =>\n (await db.insert(this.usersTable).values(payload).returning()) as Record<string, unknown>[]\n );\n return this.mapRowToUser(row);\n } catch (error) {\n // Drizzle wraps the pg error, so the SQLSTATE is down the `cause`\n // chain rather than on the error itself.\n if (extractPgError(error)?.code === \"23505\") {\n throw ApiError.conflict(\"Email already registered\", \"EMAIL_EXISTS\");\n }\n throw error;\n }\n }\n\n async getUserById(id: string): Promise<UserData | null> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return null;\n const [row] = await this.db.select().from(this.usersTable).where(eq(idCol, id));\n return row ? this.mapRowToUser(row as Record<string, unknown>) : null;\n }\n\n async getUserByEmail(email: string): Promise<UserData | null> {\n const emailCol = getColumn(this.usersTable, \"email\");\n if (!emailCol) return null;\n const [row] = await this.db.select().from(this.usersTable).where(eq(emailCol, normalizeEmail(email)));\n return row ? this.mapRowToUser(row as Record<string, unknown>) : null;\n }\n\n async getUserByIdentity(provider: string, providerId: string): Promise<UserData | null> {\n const userIdCol = getColumn(this.usersTable, \"id\");\n if (!userIdCol) return null;\n\n const result = await this.db\n .select({ user: this.usersTable })\n .from(this.usersTable)\n .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.uid))\n .where(\n sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`\n )\n .limit(1);\n\n if (result.length === 0) return null;\n return this.mapRowToUser(result[0].user as Record<string, unknown>);\n }\n\n async getUserIdentities(uid: string): Promise<UserIdentityData[]> {\n const schema = getTableConfig(this.userIdentitiesTable).schema || \"public\";\n const result = await this.db.execute(sql`\n SELECT id, uid, provider, provider_id, profile_data, created_at, updated_at\n FROM ${sql.raw(`\"${schema}\".\"user_identities\"`)}\n WHERE uid = ${uid}\n `);\n\n return result.rows.map((row: Record<string, unknown>) => ({\n id: row.id as string,\n uid: row.uid as string,\n provider: row.provider as string,\n providerId: row.provider_id as string,\n profileData: (row.profile_data as Record<string, unknown> | null) ?? null,\n createdAt: row.created_at as Date,\n updatedAt: row.updated_at as Date\n }));\n }\n\n /**\n * @see UserRepository.unlinkUserIdentity — scoped to the user as well as\n * the identity, so it can only ever detach an identity from the account\n * it names.\n */\n async unlinkUserIdentity(uid: string, provider: string, providerId: string): Promise<void> {\n await this.withServerContext(async (db) => db.delete(this.userIdentitiesTable).where(\n sql`${this.userIdentitiesTable.uid} = ${uid} AND ${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`\n ));\n }\n\n async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {\n await this.withServerContext(async (db) => db.insert(this.userIdentitiesTable).values({\n uid,\n provider,\n providerId,\n profileData: profileData || null\n }).onConflictDoNothing({ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId] }));\n }\n\n async updateUser(id: string, data: Partial<Omit<CreateUserData, \"id\">>): Promise<UserData | null> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return null;\n const payload = this.mapPayload(data);\n const updatedAtKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n payload[updatedAtKey] = new Date();\n\n let row: Record<string, unknown> | undefined;\n try {\n [row] = await this.withServerContext(async (db) =>\n (await db\n .update(this.usersTable)\n .set(payload)\n .where(eq(idCol, id))\n .returning()) as Record<string, unknown>[]\n );\n } catch (error) {\n // The unique index `createUser` maps, reached by an email change\n // onto an address another account holds — the same 409.\n if (extractPgError(error)?.code === \"23505\") {\n throw ApiError.conflict(\"Email already registered\", \"EMAIL_EXISTS\");\n }\n throw error;\n }\n return row ? this.mapRowToUser(row) : null;\n }\n\n async deleteUser(id: string): Promise<void> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return;\n await this.withServerContext(async (db) => db.delete(this.usersTable).where(eq(idCol, id)));\n }\n\n async listUsers(): Promise<UserData[]> {\n const rows = await this.db.select().from(this.usersTable);\n return (rows as Record<string, unknown>[]).map(row => this.mapRowToUser(row));\n }\n\n /**\n * @see UserRepository.firstUserIds — an unordered `LIMIT` read of the\n * primary key, not `listUsersPaginated`'s count and sort of every row.\n */\n async firstUserIds(limit: number): Promise<string[]> {\n const idCol = getColumn(this.usersTable, \"id\");\n const idColumn = idCol ? idCol.name : \"id\";\n const result = await this.db.execute(sql`\n SELECT ${sql.identifier(idColumn)} AS id FROM ${sql.raw(this.getQualifiedUsersTableName())} LIMIT ${limit}\n `);\n // Read as `mapRowToUser` reads it, so an id compares equal to the\n // `UserData.id` the same repository returned for that row.\n return (result.rows as Array<{ id: string }>).map(row => row.id);\n }\n\n async listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult> {\n const limit = options?.limit ?? 25;\n const offset = options?.offset ?? 0;\n const search = options?.search?.trim() || \"\";\n const orderBy = options?.orderBy || \"createdAt\";\n const orderDir = options?.orderDir || \"desc\";\n const roleId = options?.roleId;\n\n const orderCol = getColumn(this.usersTable, orderBy);\n const orderColumn = orderCol ? orderCol.name : \"created_at\";\n const direction = orderDir === \"asc\" ? sql`ASC` : sql`DESC`;\n\n const emailCol = getColumn(this.usersTable, \"email\");\n const emailColumn = emailCol ? emailCol.name : \"email\";\n const displayNameCol = getColumn(this.usersTable, \"displayName\", \"display_name\");\n const displayNameColumn = displayNameCol ? displayNameCol.name : \"display_name\";\n const idCol = getColumn(this.usersTable, \"id\");\n const idColumn = idCol ? idCol.name : \"id\";\n\n const usersTableName = this.getQualifiedUsersTableName();\n const conditions = [];\n if (roleId) {\n conditions.push(sql`${roleId} = ANY(${sql.raw(usersTableName)}.roles)`);\n }\n if (search) {\n // `search` is a substring search over the admin user list, not a\n // pattern the caller writes: the same reasoning as the collection\n // search path, so it shares that path's helper rather than growing\n // a second copy that can drift. See `escapeLikePattern`.\n //\n // Email, name, any role the user holds, and the uid: what an\n // administrator has in hand when looking someone up — Studio's\n // \"Run as\" picker says it searches by name, email or role, and a\n // uid is what the users table and the logs show.\n const pattern = `%${escapeLikePattern(search)}%`;\n const table = sql.raw(usersTableName);\n conditions.push(sql`(${table}.${sql.raw(emailColumn)} ILIKE ${pattern}\n OR ${table}.${sql.raw(displayNameColumn)} ILIKE ${pattern}\n OR EXISTS (SELECT 1 FROM unnest(${table}.roles) AS user_role(name) WHERE user_role.name ILIKE ${pattern})\n OR CAST(${table}.${sql.raw(idColumn)} AS text) ILIKE ${pattern})`);\n }\n\n const whereClause = conditions.length > 0 ? sql`WHERE ${sql.join(conditions, sql` AND `)}` : sql``;\n\n // Sorting: users with roles first if no role filter, then by requested order\n const orderByClause = roleId\n ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`\n : sql`ORDER BY array_length(${sql.raw(usersTableName)}.roles, 1) DESC NULLS LAST, ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`;\n\n const countResult = await this.db.execute(sql`\n SELECT count(*)::int as total FROM ${sql.raw(usersTableName)}\n ${whereClause}\n `);\n const total = (countResult.rows[0] as { total: number }).total;\n\n const dataResult = await this.db.execute(sql`\n SELECT * FROM ${sql.raw(usersTableName)}\n ${whereClause}\n ${orderByClause}\n LIMIT ${limit} OFFSET ${offset}\n `);\n const rows = dataResult.rows;\n\n // Map rows to camelCase UserData\n const mappedUsers: UserData[] = (rows as Record<string, unknown>[]).map((row) => this.mapRowToUser(row));\n\n return { users: mappedUsers,\n total,\n limit,\n offset };\n }\n\n /**\n * Update user's password hash; `null` removes the password.\n */\n async updatePassword(id: string, passwordHash: string | null): Promise<void> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return;\n const passwordHashColKey = getColumnKey(this.usersTable, \"passwordHash\", \"password_hash\") || \"passwordHash\";\n const updatedAtColKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n\n await this.withServerContext(async (db) => db\n .update(this.usersTable)\n .set({\n [passwordHashColKey]: passwordHash,\n [updatedAtColKey]: new Date()\n })\n .where(eq(idCol, id)));\n }\n\n /**\n * Set email verification status\n */\n async setEmailVerified(id: string, verified: boolean): Promise<void> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return;\n const emailVerifiedColKey = getColumnKey(this.usersTable, \"emailVerified\", \"email_verified\") || \"emailVerified\";\n const emailVerificationTokenColKey = getColumnKey(this.usersTable, \"emailVerificationToken\", \"email_verification_token\") || \"emailVerificationToken\";\n const updatedAtColKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n\n await this.withServerContext(async (db) => db\n .update(this.usersTable)\n .set({\n [emailVerifiedColKey]: verified,\n [emailVerificationTokenColKey]: null,\n [updatedAtColKey]: new Date()\n })\n .where(eq(idCol, id)));\n }\n\n /**\n * Set email verification token\n */\n async setVerificationToken(id: string, token: string | null): Promise<void> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return;\n const emailVerificationTokenColKey = getColumnKey(this.usersTable, \"emailVerificationToken\", \"email_verification_token\") || \"emailVerificationToken\";\n const emailVerificationSentAtColKey = getColumnKey(this.usersTable, \"emailVerificationSentAt\", \"email_verification_sent_at\") || \"emailVerificationSentAt\";\n const updatedAtColKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n\n await this.withServerContext(async (db) => db\n .update(this.usersTable)\n .set({\n [emailVerificationTokenColKey]: token,\n [emailVerificationSentAtColKey]: token ? new Date() : null,\n [updatedAtColKey]: new Date()\n })\n .where(eq(idCol, id)));\n }\n\n /**\n * Find user by email verification token\n */\n async getUserByVerificationToken(token: string): Promise<UserData | null> {\n const tokenCol = getColumn(this.usersTable, \"emailVerificationToken\", \"email_verification_token\");\n if (!tokenCol) return null;\n const [row] = await this.db\n .select()\n .from(this.usersTable)\n .where(eq(tokenCol, token));\n return row ? this.mapRowToUser(row as Record<string, unknown>) : null;\n }\n\n /**\n * Get role IDs for a user\n */\n async getUserRoleIds(uid: string): Promise<string[]> {\n const usersTableName = this.getQualifiedUsersTableName();\n const result = await this.db.execute(sql`\n SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${uid}\n `);\n\n if (result.rows.length === 0) return [];\n\n const row = result.rows[0] as { roles: string[] | null };\n return row.roles ?? [];\n }\n\n /**\n * Set roles for a user (replaces existing roles)\n */\n async setUserRoles(uid: string, roleIds: string[]): Promise<void> {\n const usersTableName = this.getQualifiedUsersTableName();\n const rolesArray = `{${roleIds.join(\",\")}}`;\n await this.withServerContext(async (db) => db.execute(sql`\n UPDATE ${sql.raw(usersTableName)}\n SET roles = ${rolesArray}::text[], updated_at = NOW()\n WHERE id = ${uid}\n `));\n }\n\n /**\n * Assign a specific role to new user (appends if not present)\n */\n async assignDefaultRole(uid: string, roleId: string): Promise<void> {\n const usersTableName = this.getQualifiedUsersTableName();\n await this.withServerContext(async (db) => db.execute(sql`\n UPDATE ${sql.raw(usersTableName)}\n SET roles = array_append(roles, ${roleId}), updated_at = NOW()\n WHERE id = ${uid} AND NOT (${roleId} = ANY(roles))\n `));\n }\n\n /**\n * One read of what an access token is judged against: whether the account\n * exists, its roles, and its revocation watermark. See\n * `TokenRepository.getAccountAccessState`.\n *\n * The watermark is read only where the table declares it, as\n * `RefreshTokenService.getTokensValidAfter` reads it — a table without the\n * column has no watermark rather than an error on every request.\n */\n async getAccountAccessState(uid: string, sessionId?: string): Promise<AccountAccessState | null> {\n const usersTableName = this.getQualifiedUsersTableName();\n const watermark = getColumnKey(this.usersTable, \"tokensValidAfter\", \"tokens_valid_after\")\n ? sql`u.tokens_valid_after`\n : sql`NULL::timestamptz`;\n // A session is live while one of its refresh tokens is not revoked:\n // logout and `DELETE /auth/sessions/:id` revoke every row of it, and\n // signing out everywhere deletes them. Asked only for an id shaped like\n // the ones sign-in mints, so a `uuid` column is never handed text it\n // cannot cast, and only where the table groups tokens by session.\n const tokens = this.refreshTokensTable;\n const canAskSession = Boolean(sessionId)\n && UUID_SHAPE.test(sessionId ?? \"\")\n && Boolean(getColumnKey(tokens, \"sessionId\", \"session_id\"));\n const revokedClause = getColumnKey(tokens, \"revoked\")\n ? sql`AND rt.revoked IS NOT TRUE`\n : sql``;\n const sessionActive = canAskSession\n ? sql`EXISTS (\n SELECT 1 FROM ${sql.raw(qualifiedTableName(tokens))} rt\n WHERE rt.uid = u.id AND rt.session_id = ${sessionId} ${revokedClause}\n )`\n : sql`NULL::boolean`;\n const disabled = await this.hasDisabledColumn()\n ? sql`u.disabled_at IS NOT NULL`\n : sql`FALSE`;\n const result = await this.db.execute(sql`\n SELECT u.roles, ${watermark} AS tokens_valid_after, ${sessionActive} AS session_active, ${disabled} AS disabled\n FROM ${sql.raw(usersTableName)} u WHERE u.id = ${uid}\n `);\n if (result.rows.length === 0) return null;\n const row = result.rows[0] as { roles: string[] | null; tokens_valid_after: Date | string | null; session_active: boolean | null; disabled: boolean };\n return {\n roles: row.roles ?? [],\n tokensValidAfter: row.tokens_valid_after ? new Date(row.tokens_valid_after) : null,\n ...(row.session_active === null ? {} : { sessionActive: row.session_active }),\n ...(row.disabled ? { disabled: true } : {})\n };\n }\n\n /**\n * Whether the users table has `disabled_at`. `ensureAuthTablesExist` adds\n * it to every auth table at boot, but the app's own drizzle object may\n * predate it, so the database is asked — once — rather than the object.\n */\n private disabledColumn?: Promise<boolean>;\n private hasDisabledColumn(): Promise<boolean> {\n this.disabledColumn ??= (async () => {\n const name = getTableName(this.usersTable);\n const schema = getTableConfig(this.usersTable).schema || \"public\";\n const result = await this.db.execute(sql`\n SELECT 1 FROM information_schema.columns\n WHERE table_schema = ${schema} AND table_name = ${name} AND column_name = 'disabled_at'\n `);\n return result.rows.length > 0;\n })().catch(() => {\n this.disabledColumn = undefined;\n return false;\n });\n return this.disabledColumn;\n }\n\n /**\n * Whether the users table has the address-change columns. Added at boot to\n * every auth table, but the app's drizzle object never declares them, so\n * the database is asked — once — as for `disabled_at`.\n */\n private emailChangeColumns?: Promise<boolean>;\n private hasEmailChangeColumns(): Promise<boolean> {\n this.emailChangeColumns ??= (async () => {\n const name = getTableName(this.usersTable);\n const schema = getTableConfig(this.usersTable).schema || \"public\";\n const result = await this.db.execute(sql`\n SELECT count(*)::int AS present FROM information_schema.columns\n WHERE table_schema = ${schema} AND table_name = ${name}\n AND column_name IN ('pending_email', 'email_change_token', 'email_change_sent_at')\n `);\n return (result.rows[0] as { present: number }).present === 3;\n })().catch(() => {\n this.emailChangeColumns = undefined;\n return false;\n });\n return this.emailChangeColumns;\n }\n\n private async requireEmailChangeColumns(): Promise<void> {\n if (!(await this.hasEmailChangeColumns())) {\n throw new ApiError(501, \"NOT_SUPPORTED\", \"The users table has no pending_email column; restart the server so it is added.\");\n }\n }\n\n /** @see UserRepository.setPendingEmailChange */\n async setPendingEmailChange(uid: string, change: { email: string; tokenHash: string } | null): Promise<void> {\n await this.requireEmailChangeColumns();\n const usersTableName = this.getQualifiedUsersTableName();\n await this.withServerContext(async (db) => db.execute(change\n ? sql`\n UPDATE ${sql.raw(usersTableName)}\n SET pending_email = ${normalizeEmail(change.email)}, email_change_token = ${change.tokenHash},\n email_change_sent_at = NOW(), updated_at = NOW()\n WHERE id = ${uid}`\n : sql`\n UPDATE ${sql.raw(usersTableName)}\n SET pending_email = NULL, email_change_token = NULL, email_change_sent_at = NULL, updated_at = NOW()\n WHERE id = ${uid}`));\n }\n\n /** @see UserRepository.getPendingEmailChange */\n async getPendingEmailChange(uid: string): Promise<PendingEmailChange | null> {\n if (!(await this.hasEmailChangeColumns())) return null;\n const usersTableName = this.getQualifiedUsersTableName();\n const result = await this.db.execute(sql`\n SELECT pending_email, email_change_sent_at FROM ${sql.raw(usersTableName)}\n WHERE id = ${uid} AND pending_email IS NOT NULL AND email_change_sent_at IS NOT NULL\n `);\n const row = result.rows[0] as { pending_email: string; email_change_sent_at: Date | string } | undefined;\n return row ? { email: row.pending_email, sentAt: new Date(row.email_change_sent_at) } : null;\n }\n\n /** @see UserRepository.findPendingEmailChange */\n async findPendingEmailChange(tokenHash: string): Promise<{ user: UserData; change: PendingEmailChange } | null> {\n if (!(await this.hasEmailChangeColumns())) return null;\n const usersTableName = this.getQualifiedUsersTableName();\n const result = await this.db.execute(sql`\n SELECT * FROM ${sql.raw(usersTableName)}\n WHERE email_change_token = ${tokenHash} AND pending_email IS NOT NULL AND email_change_sent_at IS NOT NULL\n `);\n const row = result.rows[0] as Record<string, unknown> | undefined;\n if (!row) return null;\n return {\n user: this.mapRowToUser(row),\n change: { email: row.pending_email as string, sentAt: new Date(row.email_change_sent_at as Date | string) }\n };\n }\n\n /** @see UserRepository.applyPendingEmailChange — one write, held to the token. */\n async applyPendingEmailChange(uid: string, tokenHash: string): Promise<UserData | null> {\n await this.requireEmailChangeColumns();\n const usersTableName = this.getQualifiedUsersTableName();\n try {\n const result = await this.withServerContext(async (db) => db.execute(sql`\n UPDATE ${sql.raw(usersTableName)}\n SET email = pending_email, email_verified = TRUE,\n email_verification_token = NULL, email_verification_sent_at = NULL,\n pending_email = NULL, email_change_token = NULL, email_change_sent_at = NULL,\n updated_at = NOW()\n WHERE id = ${uid} AND email_change_token = ${tokenHash} AND pending_email IS NOT NULL\n RETURNING *\n `));\n const row = result.rows[0] as Record<string, unknown> | undefined;\n return row ? this.mapRowToUser(row) : null;\n } catch (error) {\n // The address was taken while the link waited: the unique index\n // on it says so, and the change does not happen.\n if (extractPgError(error)?.code === \"23505\") {\n throw ApiError.conflict(\"Email already registered\", \"EMAIL_EXISTS\");\n }\n throw error;\n }\n }\n\n /** @see UserRepository.setUserDisabled */\n async setUserDisabled(uid: string, disabled: boolean): Promise<void> {\n if (!(await this.hasDisabledColumn())) {\n throw new ApiError(501, \"NOT_SUPPORTED\", \"The users table has no disabled_at column; restart the server so it is added.\");\n }\n const usersTableName = this.getQualifiedUsersTableName();\n await this.withServerContext(async (db) => db.execute(sql`\n UPDATE ${sql.raw(usersTableName)}\n SET disabled_at = ${disabled ? sql`NOW()` : sql`NULL`}, updated_at = NOW()\n WHERE id = ${uid}\n `));\n }\n\n /**\n * Get user with their roles\n */\n async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: string[] } | null> {\n const user = await this.getUserById(uid);\n if (!user) return null;\n\n const roles = await this.getUserRoleIds(uid);\n return { user,\n roles };\n }\n}\n\n\nexport class RefreshTokenService {\n private refreshTokensTable: RebasePgTable;\n private usersTable: RebasePgTable | null;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {\n this.refreshTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || refreshTokens) as RebasePgTable;\n this.usersTable = ((tableOrTables as Partial<AuthSchemaTables>).users || users) as RebasePgTable;\n } else {\n this.refreshTokensTable = (tableOrTables as RebasePgTable) || asRebasePgTable(refreshTokens);\n this.usersTable = asRebasePgTable(users);\n }\n }\n\n /**\n * Whether the table actually carries a column, so a host application that\n * supplied its own `refresh_tokens` table — one that predates session\n * grouping — degrades instead of throwing on every sign-in.\n */\n private has(column: string): boolean {\n return Boolean((this.refreshTokensTable as Record<string, unknown>)[column]);\n }\n\n private col(column: string) {\n return this.refreshTokensTable[column];\n }\n\n /** The columns to read back, narrowed to the ones this table has. */\n private selection() {\n // `AnyPgColumn`, not `Record<string, never>`. `never` is assignable to\n // everything, so the old type made this object fit whatever `.select()`\n // wanted while checking nothing about what was in it — including that\n // these are columns at all. `RebasePgTable` already indexes to\n // `AnyPgColumn`, so this needs no conversion either way.\n const selection: Record<string, AnyPgColumn> = {\n id: this.refreshTokensTable.id,\n uid: this.refreshTokensTable.uid,\n tokenHash: this.refreshTokensTable.tokenHash,\n expiresAt: this.refreshTokensTable.expiresAt,\n createdAt: this.refreshTokensTable.createdAt,\n userAgent: this.refreshTokensTable.userAgent,\n ipAddress: this.refreshTokensTable.ipAddress\n };\n for (const optional of [\"sessionId\", \"rotatedAt\", \"revoked\", \"sessionStartedAt\", \"aal\", \"method\"]) {\n if (this.has(optional)) selection[optional] = this.col(optional);\n }\n return selection;\n }\n\n async createToken(\n uid: string,\n tokenHash: string,\n expiresAt: Date,\n userAgent?: string,\n ipAddress?: string,\n session?: RefreshTokenSession\n ): Promise<boolean> {\n // Empty strings rather than NULLs: the device-session UNIQUE constraint\n // that needed them is gone, but sessions-list UIs already render \"\" as\n // \"unknown device\" and would start showing blanks otherwise.\n const safeUserAgent = userAgent || \"\";\n const safeIpAddress = ipAddress || \"\";\n\n // A plain INSERT. Rotation ADDS a token; it does not replace a device's\n // row. Two refreshes racing on the same session therefore both succeed\n // and both end holding a usable token, where the previous upsert had\n // them overwrite each other and logged one of the two tabs out.\n const values: Record<string, unknown> = {\n uid,\n tokenHash,\n expiresAt,\n userAgent: safeUserAgent,\n ipAddress: safeIpAddress\n };\n if (session && this.has(\"sessionId\")) values.sessionId = session.id;\n if (session && this.has(\"sessionStartedAt\")) values.sessionStartedAt = session.startedAt;\n // Written on every token of the session, including the ones rotation\n // mints, because refresh reads the level off whichever row was\n // presented. A table without the column degrades to `aal1` on read,\n // which is the restrictive answer rather than a bypass.\n if (session?.aal && this.has(\"aal\")) values.aal = session.aal;\n // The sign-in method, on every token of the session for the same\n // reason: refresh reads it off whichever row was presented.\n if (session?.method && this.has(\"method\")) values.method = session.method;\n\n // A rotation writes only while the token it replaces is live. Without\n // a `rotatedAt` column `markRotated` deletes that token, so there is\n // no row left to hold the rotation to and the write goes ahead as it\n // always did.\n const parentHash = session?.rotatedFrom && this.has(\"rotatedAt\") ? session.rotatedFrom : undefined;\n if (!parentHash) {\n await this.db.insert(this.refreshTokensTable).values(values);\n return true;\n }\n return await this.db.transaction(async (tx) => {\n // Share-locked until the insert commits. A sign-out that got to\n // the row first has revoked or deleted it by the time this lock is\n // granted, so the read finds nothing; one that comes after waits\n // for this transaction — see `revokeSession`.\n const live = this.has(\"revoked\")\n ? sql`${this.refreshTokensTable.tokenHash} = ${parentHash} AND ${this.col(\"revoked\")} IS NOT TRUE`\n : eq(this.refreshTokensTable.tokenHash, parentHash);\n const [parent] = await tx\n .select({ id: this.refreshTokensTable.id })\n .from(this.refreshTokensTable)\n .where(live)\n .for(\"share\");\n if (!parent) return false;\n await tx.insert(this.refreshTokensTable).values(values);\n return true;\n });\n }\n\n /**\n * Build a {@link RefreshTokenInfo} out of a row of {@link selection}.\n *\n * `selection()` is assembled at runtime from the columns this table actually\n * has, so drizzle types the row as an open record and nothing static\n * connects it to the interface. Both call sites used to assert the\n * connection — `row as unknown as RefreshTokenInfo` — which claims every\n * optional field is present and correctly typed on a row where, by\n * construction, some of them were never selected.\n *\n * Constructing it says the true thing instead: the five required fields come\n * from columns `selection()` always includes, and each optional one is\n * copied only when the row carries it. A host application's own\n * `refresh_tokens` table predating session grouping is the case this whole\n * class is shaped around, and it is exactly the case the assertion\n * misdescribed.\n */\n private static toInfo(row: Record<string, unknown>): RefreshTokenInfo {\n // node-postgres returns `timestamptz` as a Date; a custom table mapped\n // through a different driver can hand back the ISO string instead.\n const asDate = (value: unknown): Date => value instanceof Date ? value : new Date(String(value));\n const info: RefreshTokenInfo = {\n // Copied as the driver returned them, not coerced. `String(...)`\n // here would turn a missing column into the *string* `\"undefined\"`,\n // which then travels into comparisons and logs looking like a value;\n // and rejecting a row whose `id` came back as a number would log out\n // every user of a host table that declares it `serial`. Which type a\n // column has is the database's business, and these five are always\n // in `selection()`, so absence is not a case that arises.\n id: row.id as string,\n uid: row.uid as string,\n tokenHash: row.tokenHash as string,\n expiresAt: asDate(row.expiresAt),\n createdAt: asDate(row.createdAt)\n };\n if (row.userAgent !== undefined) info.userAgent = row.userAgent as string | null;\n if (row.ipAddress !== undefined) info.ipAddress = row.ipAddress as string | null;\n if (row.sessionId !== undefined && row.sessionId !== null) info.sessionId = String(row.sessionId);\n if (row.rotatedAt !== undefined) info.rotatedAt = row.rotatedAt === null ? null : asDate(row.rotatedAt);\n if (row.revoked !== undefined) info.revoked = Boolean(row.revoked);\n if (row.sessionStartedAt !== undefined && row.sessionStartedAt !== null) {\n info.sessionStartedAt = asDate(row.sessionStartedAt);\n }\n // Checked rather than asserted, because this one decides an assurance\n // level. The column is a plain text column on a table the host may own,\n // so a value that is neither label is reachable — and asserting the\n // union would have carried it through as if it were one. Anything that\n // is not exactly \"aal2\" is left unset, which the reader treats as\n // `aal1`: the restrictive answer, as the interface documents.\n if (row.aal === \"aal1\" || row.aal === \"aal2\") info.aal = row.aal;\n // A label, not a grant: any non-empty text is carried as it is.\n if (typeof row.method === \"string\" && row.method.length > 0) info.method = row.method;\n return info;\n }\n\n async findByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {\n const [token] = await this.db\n .select(this.selection())\n .from(this.refreshTokensTable)\n .where(eq(this.refreshTokensTable.tokenHash, tokenHash));\n\n return token ? RefreshTokenService.toInfo(token) : null;\n }\n\n /**\n * Record that a token was rotated away, keeping the row.\n *\n * The row is what lets `/auth/refresh` distinguish \"you already used this,\n * here is a fresh one\" from \"no idea what this is\". Deleting it — which is\n * what this used to do — collapsed both into a 401 and signed the user out\n * for the crime of losing a response.\n */\n async markRotated(tokenHash: string): Promise<void> {\n if (!this.has(\"rotatedAt\")) {\n await this.deleteByHash(tokenHash);\n return;\n }\n await this.db\n .update(this.refreshTokensTable)\n .set({ rotatedAt: new Date() })\n .where(eq(this.refreshTokensTable.tokenHash, tokenHash));\n }\n\n /**\n * Final kill of one sign-in: logout, or revoking a device remotely.\n *\n * Lock first, write second, in one transaction. A rotation in flight\n * holds a share lock on its parent — a row of this session — until the\n * token it mints is committed (see `createToken`), so the lock here waits\n * for it, and the write, being a new statement, reads a snapshot that has\n * that token in it. A lone UPDATE would work from a snapshot taken before\n * the token existed and leave it live: the sign-out a refresh in flight\n * used to undo.\n */\n async revokeSession(sessionId: string): Promise<void> {\n if (!this.has(\"sessionId\")) return;\n const inSession = eq(this.col(\"sessionId\"), sessionId);\n await this.db.transaction(async (tx) => {\n await tx\n .select({ id: this.refreshTokensTable.id })\n .from(this.refreshTokensTable)\n .where(inSession)\n .for(\"update\");\n if (this.has(\"revoked\")) {\n await tx\n .update(this.refreshTokensTable)\n .set({ revoked: true, ...(this.has(\"rotatedAt\") ? { rotatedAt: new Date() } : {}) })\n .where(inSession);\n return;\n }\n await tx.delete(this.refreshTokensTable).where(inSession);\n });\n }\n\n /**\n * Housekeeping: rotation would otherwise leave a row per refresh forever.\n * Superseded rows are only needed for as long as a straggler might still\n * present them, and expired ones are dead weight everywhere.\n */\n async prune(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {\n const uidCol = this.refreshTokensTable.uid;\n const expiresCol = this.refreshTokensTable.expiresAt;\n if (!this.has(\"rotatedAt\") || !this.has(\"sessionId\")) {\n await this.db.delete(this.refreshTokensTable)\n .where(sql`${uidCol} = ${uid} AND ${expiresCol} < NOW()`);\n return;\n }\n const rotatedCol = this.col(\"rotatedAt\");\n const sessionCol = this.col(\"sessionId\");\n await this.db.delete(this.refreshTokensTable).where(sql`\n ${uidCol} = ${uid}\n AND (\n ${expiresCol} < NOW()\n OR (\n ${sessionCol} = ${sessionId}\n AND ${rotatedCol} IS NOT NULL\n AND ${rotatedCol} < ${supersededBefore}\n )\n )\n `);\n }\n\n async getTokensValidAfter(uid: string): Promise<Date | null> {\n if (!this.usersTable || !(this.usersTable as Record<string, unknown>).tokensValidAfter) return null;\n const [row] = await this.db\n .select({ tokensValidAfter: this.usersTable.tokensValidAfter })\n .from(this.usersTable)\n .where(eq(this.usersTable.id, uid));\n const value = (row as { tokensValidAfter?: Date | string | null } | undefined)?.tokensValidAfter;\n return value ? new Date(value) : null;\n }\n\n async setTokensValidAfter(uid: string, at: Date): Promise<void> {\n if (!this.usersTable || !(this.usersTable as Record<string, unknown>).tokensValidAfter) return;\n await this.db\n .update(this.usersTable)\n .set({ tokensValidAfter: at })\n .where(eq(this.usersTable.id, uid));\n }\n\n async deleteByHash(tokenHash: string): Promise<void> {\n await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));\n }\n\n async deleteAllForUser(uid: string): Promise<void> {\n await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.uid, uid));\n }\n\n async listForUser(uid: string): Promise<RefreshTokenInfo[]> {\n const tokens = await this.db\n .select(this.selection())\n .from(this.refreshTokensTable)\n .where(eq(this.refreshTokensTable.uid, uid))\n .orderBy(this.refreshTokensTable.createdAt);\n\n return tokens.map(RefreshTokenService.toInfo);\n }\n\n async deleteById(id: string, uid: string): Promise<void> {\n await this.db.delete(this.refreshTokensTable)\n .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.uid} = ${uid}`);\n }\n\n /**\n * Every token past its expiry, whoever holds it. `prune` clears a user's\n * expired rows when that user refreshes; a session nobody comes back to\n * is only ever cleared here.\n */\n async deleteExpired(): Promise<void> {\n await this.db.delete(this.refreshTokensTable)\n .where(sql`${this.refreshTokensTable.expiresAt} < NOW()`);\n }\n}\n\n/**\n * Password reset token service\n */\nexport class PasswordResetTokenService {\n private passwordResetTokensTable: RebasePgTable;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {\n this.passwordResetTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || passwordResetTokens) as RebasePgTable;\n } else {\n this.passwordResetTokensTable = (tableOrTables as RebasePgTable) || asRebasePgTable(passwordResetTokens);\n }\n }\n\n private getQualifiedPasswordResetTokensTableName(): string {\n const name = getTableName(this.passwordResetTokensTable);\n const schema = getTableConfig(this.passwordResetTokensTable).schema || \"public\";\n return `\"${schema}\".\"${name}\"`;\n }\n\n /**\n * Create a password reset token\n */\n async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n // Delete any existing unused tokens for this user\n const tableName = this.getQualifiedPasswordResetTokensTableName();\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} \n WHERE uid = ${uid} AND used_at IS NULL\n `);\n\n await this.db.insert(this.passwordResetTokensTable).values({\n uid,\n tokenHash,\n expiresAt\n });\n }\n\n /**\n * Find a valid (not expired, not used) token by hash\n */\n async findValidByHash(tokenHash: string): Promise<{ uid: string; expiresAt: Date } | null> {\n const [token] = await this.db\n .select({\n uid: this.passwordResetTokensTable.uid,\n expiresAt: this.passwordResetTokensTable.expiresAt\n })\n .from(this.passwordResetTokensTable)\n .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as Array<{ uid: string; expiresAt: Date }>;\n\n if (!token) return null;\n\n // Check if expired or used\n const tableName = this.getQualifiedPasswordResetTokensTableName();\n const result = await this.db.execute(sql`\n SELECT uid, expires_at \n FROM ${sql.raw(tableName)} \n WHERE token_hash = ${tokenHash} \n AND used_at IS NULL \n AND expires_at > NOW()\n `);\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0] as { uid: string; expires_at: string | number | Date };\n return {\n uid: row.uid,\n expiresAt: new Date(row.expires_at)\n };\n }\n\n /**\n * Mark token as used\n */\n async markAsUsed(tokenHash: string): Promise<void> {\n await this.db\n .update(this.passwordResetTokensTable)\n .set({ usedAt: new Date() })\n .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));\n }\n\n /**\n * Delete all tokens for a user\n */\n async deleteAllForUser(uid: string): Promise<void> {\n await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.uid, uid));\n }\n\n /**\n * Clean up expired tokens\n */\n async deleteExpired(): Promise<void> {\n const tableName = this.getQualifiedPasswordResetTokensTableName();\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} \n WHERE expires_at < NOW()\n `);\n }\n}\n\n/**\n * Magic link token service.\n * Handles magic link token storage for passwordless email login.\n */\nexport class MagicLinkTokenService {\n private magicLinkTokensTable: RebasePgTable;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n this.magicLinkTokensTable = asRebasePgTable(magicLinkTokens);\n }\n\n private getQualifiedTableName(): string {\n const name = getTableName(this.magicLinkTokensTable);\n const schema = getTableConfig(this.magicLinkTokensTable).schema || \"public\";\n return `\"${schema}\".\"${name}\"`;\n }\n\n async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n // Delete any existing unused tokens for this user\n const tableName = this.getQualifiedTableName();\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} \n WHERE uid = ${uid} AND used_at IS NULL\n `);\n\n await this.db.insert(this.magicLinkTokensTable).values({\n uid,\n tokenHash,\n expiresAt\n });\n }\n\n async findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null> {\n const tableName = this.getQualifiedTableName();\n const result = await this.db.execute(sql`\n SELECT uid, expires_at \n FROM ${sql.raw(tableName)} \n WHERE token_hash = ${tokenHash} \n AND used_at IS NULL \n AND expires_at > NOW()\n `);\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0] as { uid: string; expires_at: string | number | Date };\n return {\n uid: row.uid,\n expiresAt: new Date(row.expires_at)\n };\n }\n\n /** Links and email codes past their expiry, used or not. */\n async deleteExpired(): Promise<void> {\n const tableName = this.getQualifiedTableName();\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)}\n WHERE expires_at < NOW()\n `);\n }\n\n async markAsUsed(tokenHash: string): Promise<void> {\n await this.db\n .update(this.magicLinkTokensTable)\n .set({ usedAt: new Date() })\n .where(eq(this.magicLinkTokensTable.tokenHash, tokenHash));\n }\n}\n\n/**\n * PostgreSQL implementation of TokenRepository.\n * Combines refresh token and password reset token operations.\n */\nexport class PostgresTokenRepository implements TokenRepository {\n private refreshTokenService: RefreshTokenService;\n private passwordResetTokenService: PasswordResetTokenService;\n private magicLinkTokenService: MagicLinkTokenService;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n this.refreshTokenService = new RefreshTokenService(db, tableOrTables);\n this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);\n this.magicLinkTokenService = new MagicLinkTokenService(db, tableOrTables);\n }\n\n // Refresh token operations\n\n async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<boolean> {\n return this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session);\n }\n\n async markRefreshTokenRotated(tokenHash: string): Promise<void> {\n await this.refreshTokenService.markRotated(tokenHash);\n }\n\n async revokeRefreshTokenSession(sessionId: string): Promise<void> {\n await this.refreshTokenService.revokeSession(sessionId);\n }\n\n async pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {\n await this.refreshTokenService.prune(uid, sessionId, supersededBefore);\n }\n\n async getTokensValidAfter(uid: string): Promise<Date | null> {\n return this.refreshTokenService.getTokensValidAfter(uid);\n }\n\n async setTokensValidAfter(uid: string, at: Date): Promise<void> {\n await this.refreshTokenService.setTokensValidAfter(uid, at);\n }\n\n async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {\n return this.refreshTokenService.findByHash(tokenHash);\n }\n\n async deleteRefreshToken(tokenHash: string): Promise<void> {\n await this.refreshTokenService.deleteByHash(tokenHash);\n }\n\n async deleteAllRefreshTokensForUser(uid: string): Promise<void> {\n await this.refreshTokenService.deleteAllForUser(uid);\n }\n\n async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {\n return this.refreshTokenService.listForUser(uid);\n }\n\n async deleteRefreshTokenById(id: string, uid: string): Promise<void> {\n await this.refreshTokenService.deleteById(id, uid);\n }\n\n // Password reset token operations\n\n async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n await this.passwordResetTokenService.createToken(uid, tokenHash, expiresAt);\n }\n\n async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {\n return this.passwordResetTokenService.findValidByHash(tokenHash);\n }\n\n async markPasswordResetTokenUsed(tokenHash: string): Promise<void> {\n await this.passwordResetTokenService.markAsUsed(tokenHash);\n }\n\n async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {\n await this.passwordResetTokenService.deleteAllForUser(uid);\n }\n\n /**\n * @see TokenRepository.deleteExpiredTokens — reset links, magic links and\n * email codes, and refresh tokens, each past its own expiry.\n */\n async deleteExpiredTokens(): Promise<void> {\n await this.passwordResetTokenService.deleteExpired();\n await this.magicLinkTokenService.deleteExpired();\n await this.refreshTokenService.deleteExpired();\n }\n\n // Magic link token operations\n\n async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n await this.magicLinkTokenService.createToken(uid, tokenHash, expiresAt);\n }\n\n async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {\n return this.magicLinkTokenService.findValidByHash(tokenHash);\n }\n\n async markMagicLinkTokenUsed(tokenHash: string): Promise<void> {\n await this.magicLinkTokenService.markAsUsed(tokenHash);\n }\n}\n\n/**\n * PostgreSQL implementation of AuthRepository.\n * Combines user, role, and token repository operations.\n * This provides a convenient single-class interface for all auth operations.\n */\nexport class PostgresAuthRepository implements AuthRepository {\n private userService: UserService;\n private tokenRepository: PostgresTokenRepository;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n this.userService = new UserService(db, tableOrTables);\n this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);\n }\n\n // User operations (delegate to UserService)\n\n async createUser(data: CreateUserData): Promise<UserData> {\n return this.userService.createUser(data);\n }\n\n async getUserById(id: string): Promise<UserData | null> {\n return this.userService.getUserById(id);\n }\n\n async getUserByEmail(email: string): Promise<UserData | null> {\n return this.userService.getUserByEmail(email);\n }\n\n async getUserByIdentity(provider: string, providerId: string): Promise<UserData | null> {\n return this.userService.getUserByIdentity(provider, providerId);\n }\n\n async getUserIdentities(uid: string): Promise<UserIdentityData[]> {\n return this.userService.getUserIdentities(uid);\n }\n\n async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {\n return this.userService.linkUserIdentity(uid, provider, providerId, profileData);\n }\n\n async unlinkUserIdentity(uid: string, provider: string, providerId: string): Promise<void> {\n await this.userService.unlinkUserIdentity(uid, provider, providerId);\n }\n\n async updateUser(id: string, data: Partial<Omit<CreateUserData, \"id\">>): Promise<UserData | null> {\n return this.userService.updateUser(id, data);\n }\n\n async deleteUser(id: string): Promise<void> {\n await this.userService.deleteUser(id);\n }\n\n async listUsers(): Promise<UserData[]> {\n return this.userService.listUsers();\n }\n\n async listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult> {\n return this.userService.listUsersPaginated(options);\n }\n\n async firstUserIds(limit: number): Promise<string[]> {\n return this.userService.firstUserIds(limit);\n }\n\n async updatePassword(id: string, passwordHash: string | null): Promise<void> {\n await this.userService.updatePassword(id, passwordHash);\n }\n\n async setEmailVerified(id: string, verified: boolean): Promise<void> {\n await this.userService.setEmailVerified(id, verified);\n }\n\n async setVerificationToken(id: string, token: string | null): Promise<void> {\n await this.userService.setVerificationToken(id, token);\n }\n\n async getUserByVerificationToken(token: string): Promise<UserData | null> {\n return this.userService.getUserByVerificationToken(token);\n }\n\n async getUserRoleIds(uid: string): Promise<string[]> {\n return this.userService.getUserRoleIds(uid);\n }\n\n async setUserRoles(uid: string, roleIds: string[]): Promise<void> {\n await this.userService.setUserRoles(uid, roleIds);\n }\n\n async assignDefaultRole(uid: string, roleId: string): Promise<void> {\n await this.userService.assignDefaultRole(uid, roleId);\n }\n\n async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: string[] } | null> {\n return this.userService.getUserWithRoles(uid);\n }\n\n async getAccountAccessState(uid: string, sessionId?: string): Promise<AccountAccessState | null> {\n return this.userService.getAccountAccessState(uid, sessionId);\n }\n\n async setUserDisabled(uid: string, disabled: boolean): Promise<void> {\n await this.userService.setUserDisabled(uid, disabled);\n }\n\n async setPendingEmailChange(uid: string, change: { email: string; tokenHash: string } | null): Promise<void> {\n await this.userService.setPendingEmailChange(uid, change);\n }\n\n async getPendingEmailChange(uid: string): Promise<PendingEmailChange | null> {\n return this.userService.getPendingEmailChange(uid);\n }\n\n async findPendingEmailChange(tokenHash: string): Promise<{ user: UserData; change: PendingEmailChange } | null> {\n return this.userService.findPendingEmailChange(tokenHash);\n }\n\n async applyPendingEmailChange(uid: string, tokenHash: string): Promise<UserData | null> {\n return this.userService.applyPendingEmailChange(uid, tokenHash);\n }\n\n // Token operations (delegate to PostgresTokenRepository)\n\n async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<boolean> {\n return this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session);\n }\n\n async markRefreshTokenRotated(tokenHash: string): Promise<void> {\n await this.tokenRepository.markRefreshTokenRotated(tokenHash);\n }\n\n async revokeRefreshTokenSession(sessionId: string): Promise<void> {\n await this.tokenRepository.revokeRefreshTokenSession(sessionId);\n }\n\n async pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {\n await this.tokenRepository.pruneRefreshTokens(uid, sessionId, supersededBefore);\n }\n\n async getTokensValidAfter(uid: string): Promise<Date | null> {\n return this.tokenRepository.getTokensValidAfter(uid);\n }\n\n async setTokensValidAfter(uid: string, at: Date): Promise<void> {\n await this.tokenRepository.setTokensValidAfter(uid, at);\n }\n\n async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {\n return this.tokenRepository.findRefreshTokenByHash(tokenHash);\n }\n\n async deleteRefreshToken(tokenHash: string): Promise<void> {\n await this.tokenRepository.deleteRefreshToken(tokenHash);\n }\n\n async deleteAllRefreshTokensForUser(uid: string): Promise<void> {\n await this.tokenRepository.deleteAllRefreshTokensForUser(uid);\n }\n\n async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {\n return this.tokenRepository.listRefreshTokensForUser(uid);\n }\n\n async deleteRefreshTokenById(id: string, uid: string): Promise<void> {\n await this.tokenRepository.deleteRefreshTokenById(id, uid);\n }\n\n async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n await this.tokenRepository.createPasswordResetToken(uid, tokenHash, expiresAt);\n }\n\n async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {\n return this.tokenRepository.findValidPasswordResetToken(tokenHash);\n }\n\n async markPasswordResetTokenUsed(tokenHash: string): Promise<void> {\n await this.tokenRepository.markPasswordResetTokenUsed(tokenHash);\n }\n\n async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {\n await this.tokenRepository.deleteAllPasswordResetTokensForUser(uid);\n }\n\n async deleteExpiredTokens(): Promise<void> {\n await this.tokenRepository.deleteExpiredTokens();\n await this.getMfaService().deleteExpiredChallenges();\n }\n\n // Magic link token operations\n\n async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n await this.tokenRepository.createMagicLinkToken(uid, tokenHash, expiresAt);\n }\n\n async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {\n return this.tokenRepository.findValidMagicLinkToken(tokenHash);\n }\n\n async markMagicLinkTokenUsed(tokenHash: string): Promise<void> {\n await this.tokenRepository.markMagicLinkTokenUsed(tokenHash);\n }\n\n // MFA operations (delegate to MfaService)\n\n private _mfaService: MfaService | null = null;\n private getMfaService(): MfaService {\n if (!this._mfaService) {\n this._mfaService = new MfaService(this.db);\n }\n return this._mfaService;\n }\n\n async createMfaFactor(uid: string, factorType: \"totp\", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {\n return this.getMfaService().createMfaFactor(uid, factorType, secretEncrypted, friendlyName);\n }\n\n async getMfaFactors(uid: string): Promise<MfaFactor[]> {\n return this.getMfaService().getMfaFactors(uid);\n }\n\n async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {\n return this.getMfaService().getMfaFactorById(factorId);\n }\n\n async verifyMfaFactor(factorId: string): Promise<void> {\n return this.getMfaService().verifyMfaFactor(factorId);\n }\n\n async updateMfaFactorSecret(factorId: string, secretEncrypted: string): Promise<void> {\n return this.getMfaService().updateMfaFactorSecret(factorId, secretEncrypted);\n }\n\n async deleteMfaFactor(factorId: string, uid: string): Promise<void> {\n return this.getMfaService().deleteMfaFactor(factorId, uid);\n }\n\n async createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo> {\n return this.getMfaService().createMfaChallenge(factorId, ipAddress);\n }\n\n async getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null> {\n return this.getMfaService().getMfaChallengeById(challengeId);\n }\n\n async verifyMfaChallenge(challengeId: string): Promise<void> {\n return this.getMfaService().verifyMfaChallenge(challengeId);\n }\n\n async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {\n return this.getMfaService().createRecoveryCodes(uid, codeHashes);\n }\n\n async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {\n return this.getMfaService().useRecoveryCode(uid, codeHash);\n }\n\n async getUnusedRecoveryCodeCount(uid: string): Promise<number> {\n return this.getMfaService().getUnusedRecoveryCodeCount(uid);\n }\n\n async deleteAllRecoveryCodes(uid: string): Promise<void> {\n return this.getMfaService().deleteAllRecoveryCodes(uid);\n }\n\n async hasVerifiedMfaFactors(uid: string): Promise<boolean> {\n return this.getMfaService().hasVerifiedMfaFactors(uid);\n }\n\n async claimMfaFactorCounter(factorId: string, counter: number): Promise<boolean> {\n return this.getMfaService().claimMfaFactorCounter(factorId, counter);\n }\n\n async recordMfaChallengeAttempt(challengeId: string): Promise<number> {\n return this.getMfaService().recordMfaChallengeAttempt(challengeId);\n }\n}\n\n// =============================================================================\n// MFA SERVICE\n// =============================================================================\n\n/**\n * PostgreSQL implementation of MfaRepository.\n * Handles all MFA-related database operations.\n */\nexport class MfaService implements MfaRepository {\n constructor(private db: NodePgDatabase, private schemaName = \"rebase\") {}\n\n private qualify(tableName: string): string {\n return `\"${this.schemaName}\".\"${tableName}\"`;\n }\n\n async createMfaFactor(\n uid: string,\n factorType: \"totp\",\n secretEncrypted: string,\n friendlyName?: string\n ): Promise<MfaFactor> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n INSERT INTO ${sql.raw(tableName)} (uid, factor_type, secret_encrypted, friendly_name)\n VALUES (${uid}, ${factorType}, ${secretEncrypted}, ${friendlyName ?? null})\n RETURNING id, uid, factor_type, friendly_name, verified, created_at, updated_at\n `);\n\n const row = result.rows[0] as Record<string, unknown>;\n return {\n id: row.id as string,\n uid: row.uid as string,\n factorType: row.factor_type as \"totp\",\n friendlyName: (row.friendly_name as string | null) ?? undefined,\n verified: row.verified as boolean,\n createdAt: new Date(row.created_at as string),\n updatedAt: new Date(row.updated_at as string)\n };\n }\n\n async getMfaFactors(uid: string): Promise<MfaFactor[]> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n SELECT id, uid, factor_type, friendly_name, verified, created_at, updated_at\n FROM ${sql.raw(tableName)}\n WHERE uid = ${uid}\n ORDER BY created_at\n `);\n\n return (result.rows as Array<Record<string, unknown>>).map(row => ({\n id: row.id as string,\n uid: row.uid as string,\n factorType: row.factor_type as \"totp\",\n friendlyName: (row.friendly_name as string | null) ?? undefined,\n verified: row.verified as boolean,\n createdAt: new Date(row.created_at as string),\n updatedAt: new Date(row.updated_at as string)\n }));\n }\n\n async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n SELECT id, uid, factor_type, secret_encrypted, friendly_name, verified, last_used_counter, created_at, updated_at\n FROM ${sql.raw(tableName)}\n WHERE id = ${factorId}\n `);\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0] as Record<string, unknown>;\n return {\n id: row.id as string,\n uid: row.uid as string,\n factorType: row.factor_type as \"totp\",\n secretEncrypted: row.secret_encrypted as string,\n friendlyName: (row.friendly_name as string | null) ?? undefined,\n verified: row.verified as boolean,\n // BIGINT comes back as a string from node-postgres.\n lastUsedCounter: row.last_used_counter === null || row.last_used_counter === undefined\n ? null\n : Number(row.last_used_counter),\n createdAt: new Date(row.created_at as string),\n updatedAt: new Date(row.updated_at as string)\n };\n }\n\n /**\n * Spend a TOTP time step, once and only once.\n *\n * One statement: the `WHERE` is the check, the `UPDATE` is the act, and\n * `RETURNING` reports which of two concurrent requests carrying the same\n * six digits won. Reading the counter and then writing it would let both\n * pass — the exact replay this closes.\n */\n async claimMfaFactorCounter(factorId: string, counter: number): Promise<boolean> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET last_used_counter = ${counter}, updated_at = NOW()\n WHERE id = ${factorId}\n AND (last_used_counter IS NULL OR last_used_counter < ${counter})\n RETURNING id\n `);\n\n return result.rows.length > 0;\n }\n\n async verifyMfaFactor(factorId: string): Promise<void> {\n const tableName = this.qualify(\"mfa_factors\");\n await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET verified = TRUE, updated_at = NOW()\n WHERE id = ${factorId}\n `);\n }\n\n async updateMfaFactorSecret(factorId: string, secretEncrypted: string): Promise<void> {\n const tableName = this.qualify(\"mfa_factors\");\n await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET secret_encrypted = ${secretEncrypted}, updated_at = NOW()\n WHERE id = ${factorId}\n `);\n }\n\n async deleteMfaFactor(factorId: string, uid: string): Promise<void> {\n const tableName = this.qualify(\"mfa_factors\");\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)}\n WHERE id = ${factorId} AND uid = ${uid}\n `);\n }\n\n async createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo> {\n const tableName = this.qualify(\"mfa_challenges\");\n // Challenges expire in 5 minutes\n const expiresAt = new Date(Date.now() + 5 * 60 * 1000);\n const result = await this.db.execute(sql`\n INSERT INTO ${sql.raw(tableName)} (factor_id, ip_address, expires_at)\n VALUES (${factorId}, ${ipAddress ?? null}, ${expiresAt})\n RETURNING id, factor_id, created_at, verified_at, ip_address\n `);\n\n const row = result.rows[0] as Record<string, unknown>;\n return {\n id: row.id as string,\n factorId: row.factor_id as string,\n createdAt: new Date(row.created_at as string),\n verifiedAt: row.verified_at ? new Date(row.verified_at as string) : undefined,\n ipAddress: (row.ip_address as string | null) ?? undefined\n };\n }\n\n async getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null> {\n const tableName = this.qualify(\"mfa_challenges\");\n const result = await this.db.execute(sql`\n SELECT id, factor_id, created_at, verified_at, ip_address, attempts, expires_at\n FROM ${sql.raw(tableName)}\n WHERE id = ${challengeId} AND expires_at > NOW() AND verified_at IS NULL\n `);\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0] as Record<string, unknown>;\n return {\n id: row.id as string,\n factorId: row.factor_id as string,\n createdAt: new Date(row.created_at as string),\n verifiedAt: row.verified_at ? new Date(row.verified_at as string) : undefined,\n ipAddress: (row.ip_address as string | null) ?? undefined,\n attempts: Number(row.attempts ?? 0)\n };\n }\n\n /**\n * Count one guess against a challenge and report the new total. The route\n * claims it before judging the code, so the total it returns is what the\n * cap is decided on.\n *\n * Incremented in the database rather than in the route so that guesses\n * arriving in parallel — the shape any real brute-force takes — cannot\n * share a single increment.\n */\n async recordMfaChallengeAttempt(challengeId: string): Promise<number> {\n const tableName = this.qualify(\"mfa_challenges\");\n const result = await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET attempts = attempts + 1\n WHERE id = ${challengeId}\n RETURNING attempts\n `);\n\n if (result.rows.length === 0) return 0;\n return Number((result.rows[0] as { attempts: number | string }).attempts);\n }\n\n /** Challenges past their five minutes, answered or not. */\n async deleteExpiredChallenges(): Promise<void> {\n const tableName = this.qualify(\"mfa_challenges\");\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)}\n WHERE expires_at < NOW()\n `);\n }\n\n async verifyMfaChallenge(challengeId: string): Promise<void> {\n const tableName = this.qualify(\"mfa_challenges\");\n await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET verified_at = NOW()\n WHERE id = ${challengeId}\n `);\n }\n\n async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {\n const tableName = this.qualify(\"recovery_codes\");\n // Delete existing codes first\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}\n `);\n\n // Insert new codes\n for (const hash of codeHashes) {\n await this.db.execute(sql`\n INSERT INTO ${sql.raw(tableName)} (uid, code_hash)\n VALUES (${uid}, ${hash})\n `);\n }\n }\n\n async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {\n const tableName = this.qualify(\"recovery_codes\");\n const result = await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET used_at = NOW()\n WHERE uid = ${uid} AND code_hash = ${codeHash} AND used_at IS NULL\n RETURNING id\n `);\n\n return result.rows.length > 0;\n }\n\n async getUnusedRecoveryCodeCount(uid: string): Promise<number> {\n const tableName = this.qualify(\"recovery_codes\");\n const result = await this.db.execute(sql`\n SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}\n WHERE uid = ${uid} AND used_at IS NULL\n `);\n\n return (result.rows[0] as { count: number }).count;\n }\n\n async deleteAllRecoveryCodes(uid: string): Promise<void> {\n const tableName = this.qualify(\"recovery_codes\");\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}\n `);\n }\n\n async hasVerifiedMfaFactors(uid: string): Promise<boolean> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}\n WHERE uid = ${uid} AND verified = TRUE\n `);\n\n return (result.rows[0] as { count: number }).count > 0;\n }\n}\n\n// =============================================================================\n// PostgreSQL Type Aliases (for consistent naming with other implementations)\n// =============================================================================\n\n/** PostgreSQL user repository implementation */\nexport type PostgresUserRepository = UserService;\n","import { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\nimport { REBASE_USER_ROLE, revokeInternalTableSql, sqlRows } from \"@rebasepro/common\";\nimport type { DeferredIndexBuild } from \"../schema/plan/diff-plan\";\nimport { BOOT_LOCK_RETRY_BUDGET_MS, isLockTimeout, resolveBootLockOptions, sleep, type BootLockOptions } from \"../schema/boot-ddl\";\n\n/** How a history entry gets written from inside the write it records. */\nexport const RECORD_HISTORY_FUNCTION = \"rebase.record_history\";\n/** The door for an entry that names its `format` — see {@link HistoryTableState.entryFormat}. */\nconst RECORD_HISTORY_SIGNATURE = `${RECORD_HISTORY_FUNCTION}(text, text, text, text[], jsonb, jsonb, text, smallint)`;\n/**\n * The door before entries had a `format`. Still created where the column could\n * not be added, and left in place everywhere: an instance of the previous\n * version, mid rolling deploy, still calls it, and its entries are the whole\n * row — which is what the column's default, `1`, says.\n */\nconst LEGACY_RECORD_HISTORY_SIGNATURE = `${RECORD_HISTORY_FUNCTION}(text, text, text, text[], jsonb, jsonb, text)`;\n\n/** What {@link ensureHistoryTableExists} left in place, for `HistoryService`. */\nexport interface HistoryTableState {\n /**\n * {@link RECORD_HISTORY_FUNCTION} exists, so an entry inside a write goes\n * on that write's transaction. Without it, entries go through the pool and\n * commit on their own.\n */\n inTransaction: boolean;\n /**\n * What new entries are stored as. `2` — an update keeps the previous values\n * of its changed fields only (`EntityHistoryEntry.format`) — once the table\n * has its `format` column; `1`, the whole previous row, where the column\n * could not be added this boot.\n */\n entryFormat: 1 | 2;\n}\n\n/** A row's entries, newest first: the history list, the inline prune, and — by its prefix — every per-row lookup. */\nconst HISTORY_TIME_INDEX = \"idx_history_time\";\n/** Entries by age alone, for the retention sweep (`HistoryService.pruneExpired`). */\nexport const HISTORY_SWEEP_INDEX = \"idx_history_updated_at\";\n/**\n * `(table_name, entity_id)`: a strict prefix of {@link HISTORY_TIME_INDEX},\n * so it answered nothing the other could not, and every recorded change paid\n * for a third btree. Dropped at boot where it exists.\n */\nconst REDUNDANT_ENTITY_INDEX = \"idx_history_entity\";\n\n/**\n * Auto-create the row history table if it doesn't exist.\n * This runs on startup when history is enabled, following the same\n * pattern as `ensureAuthTablesExist`.\n *\n * Resolves to what the history service may rely on: see {@link HistoryTableState}.\n */\nexport async function ensureHistoryTableExists(db: NodePgDatabase, options: BootLockOptions = {}): Promise<HistoryTableState> {\n logger.debug(\"🔍 Checking row history table...\");\n let entryFormat: 1 | 2;\n\n try {\n // Create the rebase schema (idempotent — may already exist from auth init)\n await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);\n\n await db.execute(sql`\n CREATE TABLE IF NOT EXISTS rebase.entity_history (\n id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,\n table_name TEXT NOT NULL,\n entity_id TEXT NOT NULL,\n action TEXT NOT NULL,\n changed_fields TEXT[],\n \"values\" JSONB,\n previous_values JSONB,\n updated_by TEXT,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n format SMALLINT NOT NULL DEFAULT 1\n )\n `);\n // A table from before 0.24 gains the column once. A constant default\n // makes it a catalogue change, but `ALTER TABLE` still needs the\n // table's lock for an instant, so it waits within the boot's budget —\n // and a table held throughout keeps writing whole rows until the next\n // boot adds it.\n entryFormat = await formatColumnExists(db) || await underLockBudget(db, ADD_FORMAT_COLUMN, options) ? 2 : 1;\n if (entryFormat === 1) {\n logger.warn(\n \"⚠️ rebase.entity_history could not gain its `format` column this boot: another session held the \" +\n \"table for the whole lock budget. History entries keep storing the whole previous row until a boot adds it.\"\n );\n }\n\n // One index serves every per-row read and prune: its prefix is the\n // `(table_name, entity_id)` lookup. The time-only one the retention\n // sweep reads is built in the background — see `ensureHistoryIndexes`.\n await db.execute(sql.raw(`\n CREATE INDEX IF NOT EXISTS ${HISTORY_TIME_INDEX}\n ON rebase.entity_history(table_name, entity_id, updated_at DESC)\n `));\n\n // Every previous value of every audited row, in one table with no RLS\n // and no tenant scoping — so a readable copy defeats the row policies on\n // the tables it shadows. The driver's schema-wide grant reaches it\n // (created here, after that grant ran), so take it back.\n await db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"entity_history\")));\n\n logger.debug(\"✅ Entity history table ready\");\n } catch (error) {\n logger.error(\"❌ Failed to create row history table\", { error: error });\n logger.warn(\"⚠️ Continuing without creating history table.\");\n return { inTransaction: false, entryFormat: 1 };\n }\n\n // The entry has to commit with the row it describes, so it is written on\n // the write's transaction — which runs as the request role, revoked from\n // this table just above. A `SECURITY DEFINER` function is the one door:\n // it inserts exactly an entry, with the owner's rights, for that single\n // statement, and only the request role may call it. The same reasoning,\n // at more length, is on `rebase.enqueue_job` in the job store.\n const signature = entryFormat === 2 ? RECORD_HISTORY_SIGNATURE : LEGACY_RECORD_HISTORY_SIGNATURE;\n try {\n await db.execute(sql.raw(entryFormat === 2\n ? `\n CREATE OR REPLACE FUNCTION ${RECORD_HISTORY_FUNCTION}(\n p_table_name text, p_entity_id text, p_action text, p_changed_fields text[],\n p_values jsonb, p_previous_values jsonb, p_updated_by text, p_format smallint\n ) RETURNS void\n LANGUAGE sql\n SECURITY DEFINER\n SET search_path = pg_catalog, pg_temp\n AS $fn$\n INSERT INTO rebase.entity_history\n (table_name, entity_id, action, changed_fields, \"values\", previous_values, updated_by, format)\n VALUES (p_table_name, p_entity_id, p_action, p_changed_fields, p_values, p_previous_values, p_updated_by, p_format)\n $fn$\n `\n : `\n CREATE OR REPLACE FUNCTION ${RECORD_HISTORY_FUNCTION}(\n p_table_name text, p_entity_id text, p_action text, p_changed_fields text[],\n p_values jsonb, p_previous_values jsonb, p_updated_by text\n ) RETURNS void\n LANGUAGE sql\n SECURITY DEFINER\n SET search_path = pg_catalog, pg_temp\n AS $fn$\n INSERT INTO rebase.entity_history\n (table_name, entity_id, action, changed_fields, \"values\", previous_values, updated_by)\n VALUES (p_table_name, p_entity_id, p_action, p_changed_fields, p_values, p_previous_values, p_updated_by)\n $fn$\n `));\n await db.execute(sql.raw(`REVOKE ALL ON FUNCTION ${signature} FROM PUBLIC`));\n await db.execute(sql.raw(`\n DO $grant$ BEGIN\n IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${REBASE_USER_ROLE}') THEN\n GRANT EXECUTE ON FUNCTION ${signature} TO ${REBASE_USER_ROLE};\n END IF;\n END $grant$\n `));\n return { inTransaction: true, entryFormat };\n } catch (error) {\n logger.warn(\n `⚠️ ${RECORD_HISTORY_FUNCTION} could not be created, so a history entry commits on its own: ` +\n \"it survives a write that rolls back.\",\n { error }\n );\n return { inTransaction: false, entryFormat };\n }\n}\n\n/**\n * What an earlier boot left in place, read without changing anything — for a\n * driver opened without provisioning. Entries go on the write's transaction\n * when the function for the table's entry format is there, and the format is\n * `2` when the table has its `format` column. `tableExists` is false on a\n * database no boot with history has provisioned.\n */\nexport async function readHistoryTableState(db: NodePgDatabase): Promise<HistoryTableState & { tableExists: boolean }> {\n const entryFormat: 1 | 2 = await formatColumnExists(db) ? 2 : 1;\n const signature = entryFormat === 2 ? RECORD_HISTORY_SIGNATURE : LEGACY_RECORD_HISTORY_SIGNATURE;\n const rows = sqlRows<{ table: boolean; fn: boolean }>(await db.execute(sql`\n SELECT to_regclass('rebase.entity_history') IS NOT NULL AS \"table\",\n to_regprocedure(${signature}) IS NOT NULL AS fn\n `));\n return { inTransaction: rows[0]?.fn === true, entryFormat, tableExists: rows[0]?.table === true };\n}\n\n/**\n * Why history cannot be recorded on a database {@link readHistoryTableState}\n * read, or `undefined` when it can. `requireTransaction` also counts a\n * missing {@link RECORD_HISTORY_FUNCTION}: without it an entry would commit on\n * its own instead of with its row.\n */\nexport function historyUnavailableReason(\n state: HistoryTableState & { tableExists: boolean },\n options: { requireTransaction: boolean }\n): string | undefined {\n if (!state.tableExists) return \"the rebase.entity_history table\";\n if (options.requireTransaction && !state.inTransaction) return `the ${RECORD_HISTORY_FUNCTION} function`;\n return undefined;\n}\n\nconst ADD_FORMAT_COLUMN = \"ALTER TABLE rebase.entity_history ADD COLUMN IF NOT EXISTS format SMALLINT NOT NULL DEFAULT 1\";\n\n/** Whether `rebase.entity_history` has its `format` column — a catalogue read, no lock. */\nasync function formatColumnExists(db: NodePgDatabase): Promise<boolean> {\n const rows = sqlRows<{ present: boolean }>(await db.execute(sql`\n SELECT EXISTS (\n SELECT 1 FROM pg_attribute\n WHERE attrelid = to_regclass('rebase.entity_history') AND attname = 'format' AND NOT attisdropped\n ) AS present\n `));\n return rows[0]?.present === true;\n}\n\n/**\n * Bring the history table's indexes to what its reads need, and say which one\n * is left to build in the background.\n *\n * - `idx_history_entity` is dropped: `idx_history_time` leads with the same two\n * columns, so it served nothing the other does not, and cost a btree insert\n * per recorded change. A `DROP INDEX` takes the table's `ACCESS EXCLUSIVE`\n * lock for an instant, so it runs under the boot's lock budget — one second\n * per attempt, a few attempts — and a table something holds the whole time\n * keeps the index until the next boot. Only once `idx_history_time` is there\n * and valid, so a per-row lookup never loses its index.\n * - `idx_history_updated_at`, which the retention sweep reads, is returned for\n * `buildDeferredIndexes`: on a table that already holds years of history the\n * build takes seconds to minutes, so it is built `CONCURRENTLY` after the boot,\n * blocking no read or write. Nothing is returned when it is already valid.\n *\n * Never throws: an index that cannot be dropped or read about is logged, and\n * the next boot asks again.\n */\nexport async function ensureHistoryIndexes(db: NodePgDatabase, options: BootLockOptions = {}): Promise<DeferredIndexBuild[]> {\n let states: Map<string, boolean>;\n try {\n states = await readHistoryIndexStates(db);\n } catch (error) {\n logger.warn(\"⚠️ Could not read the row history table's indexes; leaving them as they are this boot.\", { error });\n return [];\n }\n\n if (states.has(REDUNDANT_ENTITY_INDEX) && states.get(HISTORY_TIME_INDEX) === true) {\n if (await underLockBudget(db, `DROP INDEX IF EXISTS rebase.${REDUNDANT_ENTITY_INDEX}`, options)) {\n logger.info(`[history] Dropped rebase.${REDUNDANT_ENTITY_INDEX}: rebase.${HISTORY_TIME_INDEX} serves every read it did.`);\n } else {\n logger.warn(\n `⚠️ rebase.${REDUNDANT_ENTITY_INDEX} was not dropped this boot: another session held rebase.entity_history ` +\n \"for the whole lock budget. It only costs a little on each recorded change; the next boot tries again.\"\n );\n }\n }\n\n if (states.get(HISTORY_SWEEP_INDEX) === true) return [];\n return [{\n schema: \"rebase\",\n table: \"entity_history\",\n indexName: HISTORY_SWEEP_INDEX,\n sql: `CREATE INDEX CONCURRENTLY IF NOT EXISTS \"${HISTORY_SWEEP_INDEX}\" ON \"rebase\".\"entity_history\" (updated_at);`,\n invalid: states.get(HISTORY_SWEEP_INDEX) === false\n }];\n}\n\n/** Each history index that exists, and whether it is valid (a cancelled concurrent build leaves an invalid one). */\nasync function readHistoryIndexStates(db: NodePgDatabase): Promise<Map<string, boolean>> {\n const rows = sqlRows<{ name: string; valid: boolean }>(await db.execute(sql`\n SELECT c.relname AS name, i.indisvalid AS valid\n FROM pg_index i\n JOIN pg_class c ON c.oid = i.indexrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = 'rebase'\n AND c.relname IN (${REDUNDANT_ENTITY_INDEX}, ${HISTORY_TIME_INDEX}, ${HISTORY_SWEEP_INDEX})\n `));\n return new Map(rows.map(row => [row.name, row.valid === true]));\n}\n\n/**\n * Run one DDL statement that needs the history table's lock, waiting for it at\n * most `lockTimeoutMs` per attempt within the boot's retry budget — so live\n * queries on the table never queue behind the boot for longer than that.\n * `true` when it ran; `false` when something held the table throughout.\n * Any other failure is thrown.\n */\nasync function underLockBudget(db: NodePgDatabase, statement: string, options: BootLockOptions): Promise<boolean> {\n const lock = resolveBootLockOptions(options);\n const deadline = Date.now() + (options.retryBudgetMs ?? BOOT_LOCK_RETRY_BUDGET_MS);\n for (let round = 1; ; round++) {\n try {\n await db.transaction(async tx => {\n await tx.execute(sql.raw(`SET LOCAL lock_timeout = '${Math.max(1, Math.round(lock.lockTimeoutMs))}ms'`));\n await tx.execute(sql.raw(statement));\n });\n return true;\n } catch (error) {\n if (!isLockTimeout(error)) throw error;\n if (round >= lock.attempts || Date.now() >= deadline) return false;\n await sleep(Math.min(lock.backoffMs(round), Math.max(0, deadline - Date.now())));\n }\n }\n}\n","import { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport type { EntityHistoryEntry } from \"@rebasepro/types\";\n\nexport type {\n RecordHistoryParams,\n FetchHistoryOptions,\n HistoryRetentionConfig\n} from \"@rebasepro/types\";\nimport type { RecordHistoryParams, FetchHistoryOptions, HistoryRetentionConfig } from \"@rebasepro/types\";\nimport { firstSqlRow, sqlRows, withRelationReferencesOnly } from \"@rebasepro/common\";\nimport { currentWriteScope } from \"../services/write-transaction-scope\";\nimport { RECORD_HISTORY_FUNCTION, type HistoryTableState } from \"./ensure-history-table\";\n\n/**\n * A Postgres history row is already the wire shape — `updated_at` comes back\n * from the driver as a string. Kept as an alias because the name is used\n * throughout this package and in `PostgresBackendDriver`.\n */\nexport type HistoryEntry = EntityHistoryEntry;\n\nconst DEFAULT_RETENTION: HistoryRetentionConfig = {\n maxEntries: 200,\n ttlDays: 90\n};\n\n/** Entries one statement of {@link HistoryService.pruneExpired} deletes at most. */\nexport const PRUNE_BATCH_SIZE = 5_000;\n\n/**\n * Service for recording and querying row change history.\n * Stores history entries in the `rebase.entity_history` table.\n */\nexport class HistoryService {\n public retention: HistoryRetentionConfig;\n\n /**\n * Whether {@link RECORD_HISTORY_FUNCTION} exists, so an entry recorded\n * inside a write goes on that write's transaction. Without it, entries go\n * through the pool and commit on their own.\n */\n private readonly inTransaction: boolean;\n\n /**\n * What new entries are stored as — see `EntityHistoryEntry.format`. `2`,\n * the previous values of the changed fields only, needs the table's\n * `format` column, so it is what `ensureHistoryTableExists` found; a\n * service built without saying stores whole rows, as it always did.\n */\n private readonly entryFormat: 1 | 2;\n\n /**\n * What the database lacks for history to be recorded, when it lacks\n * something — `the rebase.entity_history table`. Set for a driver opened\n * without provisioning on a database no boot with history has prepared:\n * writes to a `history: true` collection are then refused before they\n * start (see {@link assertRecordable}), rather than made without their\n * audit entries.\n */\n private readonly unavailable?: string;\n\n /**\n * Whether recording an entry also prunes that row's history to\n * {@link retention}. Off for a process that does not know the retention the\n * project's server is configured with — `openRebase` — which would\n * otherwise delete entries the server keeps. What it leaves is pruned by\n * the server: the row's next write, and the hourly sweep.\n */\n private readonly pruneOnRecord: boolean;\n\n constructor(\n private db: NodePgDatabase,\n retention?: Partial<HistoryRetentionConfig>,\n options?: Partial<HistoryTableState> & { unavailable?: string; pruneOnRecord?: boolean }\n ) {\n this.retention = { ...DEFAULT_RETENTION,\n...retention };\n this.inTransaction = options?.inTransaction === true;\n this.entryFormat = options?.entryFormat === 2 ? 2 : 1;\n this.unavailable = options?.unavailable;\n this.pruneOnRecord = options?.pruneOnRecord !== false;\n }\n\n /**\n * Refuse a write to `collection` (a `history: true` collection's slug)\n * when its entry could not be recorded. Called by the driver before the\n * write runs anything — hooks included — so a refused write has no side\n * effects. Reads are never refused.\n */\n assertRecordable(collection: string): void {\n if (!this.unavailable) return;\n throw ApiError.serviceUnavailable(\n `Collection \"${collection}\" keeps history (\\`history: true\\`), but this database is missing ` +\n `${this.unavailable} — no backend with history enabled has provisioned it. The write was refused ` +\n \"rather than made without its audit entry; reads still work. Provision it by starting the backend \" +\n \"once with `history` enabled and `provision: true` (`rebase dev` does).\",\n \"HISTORY_UNAVAILABLE\"\n );\n }\n\n /**\n * Record a history entry for a row change.\n *\n * ## Why this is allowed to fail the write\n *\n * It used to swallow every error into a log line, and the driver called it\n * without `await`, so the promise was dropped on the floor. Both halves of\n * that made the same claim: that the audit trail is a nice-to-have. It is\n * not — `history: true` is opted into by collections whose changes somebody\n * has to be able to reconstruct, and a trail with gaps is worse than no\n * trail, because nothing distinguishes \"no change was made\" from \"the entry\n * did not get written\". The gaps were silent and unbounded: a transient\n * error on the history insert lost the entry for a row that committed\n * anyway, and nobody found out.\n *\n * So it throws, and the driver awaits it inside the write's transaction.\n * The row and its history entry commit together or neither does — which\n * needs the entry written *on* that transaction, not merely while it is\n * open. This service is built once, on the pool, and for a long time it\n * wrote there: a batch that rolled back after its first row left that\n * row's entry behind, describing a row that never existed. Inside a write\n * it now goes through {@link RECORD_HISTORY_FUNCTION} on the write's own\n * transaction (found through `currentWriteScope`). That is a\n * real trade — a broken `rebase.entity_history` now fails writes to the\n * collections that declare history, where before it failed quietly — and it\n * is the right side of it for a table that exists to answer \"who changed\n * this\". The table is created at boot (`ensureHistoryTableExists`), so the\n * failure mode is a database that is broken in other ways too.\n *\n * The pruning pass stays non-blocking: a prune that does not run leaves\n * *extra* history, which is not a loss and is fixed by the next write.\n */\n async recordHistory(params: RecordHistoryParams): Promise<void> {\n const { tableName, id, action, updatedBy } = params;\n this.assertRecordable(tableName);\n // A relation is stored as its reference, never the related row's\n // values: reading this row's history is not reading the rows it\n // points at. See `withRelationReferencesOnly`.\n const values = params.values ? withRelationReferencesOnly(params.values) : params.values;\n const previousValues = params.previousValues ? withRelationReferencesOnly(params.previousValues) : params.previousValues;\n\n const changedFields = previousValues && values\n ? findChangedFields(previousValues, values)\n : null;\n\n\n // Skip recording if this is an update with zero actual changes\n\n if (action === \"update\" && (!changedFields || changedFields.length === 0)) {\n return;\n }\n\n // An update's previous values, for the fields it changed — every other\n // field held what it holds now, which `values` already records. Two\n // copies of the whole row was 1,108 of a 1,256-byte entry for a\n // one-field change to a 20-field row. A revert writes `values`, so it\n // reads the same entry either way.\n const compact = this.entryFormat === 2;\n const storedPrevious = compact && previousValues && changedFields\n ? previousValuesOf(previousValues, changedFields)\n : previousValues;\n const format = compact ? sql`, ${2}::smallint` : sql``;\n\n try {\n // Checked and used with no `await` between, so the transaction\n // cannot close in the gap.\n const scope = this.inTransaction ? currentWriteScope() : undefined;\n if (scope?.tx) {\n await scope.track(scope.tx.execute(sql`\n SELECT ${sql.raw(RECORD_HISTORY_FUNCTION)}(\n ${tableName},\n ${String(id)},\n ${action},\n ${changedFields ? sql`ARRAY[${sql.join(changedFields.map(f => sql`${f}`), sql`, `)}]::text[]` : sql`NULL::text[]`},\n ${values ? sql`${JSON.stringify(values)}::jsonb` : sql`NULL::jsonb`},\n ${storedPrevious ? sql`${JSON.stringify(storedPrevious)}::jsonb` : sql`NULL::jsonb`},\n ${updatedBy ?? null}::text${format}\n )\n `));\n } else await this.db.execute(sql`\n INSERT INTO rebase.entity_history\n (table_name, entity_id, action, changed_fields, \"values\", previous_values, updated_by${compact ? sql`, format` : sql``})\n VALUES (\n ${tableName},\n ${String(id)},\n ${action},\n ${changedFields ? sql`ARRAY[${sql.join(changedFields.map(f => sql`${f}`), sql`, `)}]::text[]` : sql`NULL`},\n ${values ? sql`${JSON.stringify(values)}::jsonb` : sql`NULL`},\n ${storedPrevious ? sql`${JSON.stringify(storedPrevious)}::jsonb` : sql`NULL`},\n ${updatedBy ?? null}${format}\n )\n `);\n } catch (error) {\n logger.error(\"Failed to record row history\", { error });\n // Rethrown, not swallowed. See the docblock: the entry commits with\n // its row or the write does not happen. Wrapped so the caller reads\n // a sentence naming the audit trail rather than a bare SQLSTATE\n // from a table they did not know they were writing to.\n throw new Error(\n `Could not record the history entry for \"${tableName}\" (${String(id)}). ` +\n \"The write was rolled back: this collection declares `history: true`, and a row \" +\n \"that commits without its audit entry leaves a gap nothing can distinguish from \" +\n \"\\\"no change was made\\\".\",\n { cause: error }\n );\n }\n\n // Non-blocking prune for this specific row. Outside the try, and\n // deliberately not awaited: a prune that fails leaves *extra* history,\n // which is not a loss and is fixed by the next write to this row.\n if (!this.pruneOnRecord) return;\n this.pruneEntity(tableName, id).catch(err =>\n logger.error(\"History prune failed\", { error: err })\n );\n }\n\n /**\n * Fetch history entries for an row, ordered by most recent first.\n */\n async fetchHistory(\n tableName: string,\n id: string,\n options: FetchHistoryOptions = {}\n ): Promise<{ data: HistoryEntry[]; total: number }> {\n const limit = options.limit ?? 20;\n const offset = options.offset ?? 0;\n\n const [countResult, dataResult] = await Promise.all([\n this.db.execute(sql`\n SELECT COUNT(*) as count\n FROM rebase.entity_history\n WHERE table_name = ${tableName}\n AND entity_id = ${String(id)}\n `),\n this.db.execute(sql`\n SELECT id, table_name, entity_id, action, changed_fields,\n \"values\", previous_values, updated_by, updated_at, ${this.formatColumn()}\n FROM rebase.entity_history\n WHERE table_name = ${tableName}\n AND entity_id = ${String(id)}\n ORDER BY updated_at DESC\n LIMIT ${limit}\n OFFSET ${offset}\n `)\n ]);\n\n const total = parseInt(\n (countResult.rows[0] as Record<string, string>)?.count ?? \"0\",\n 10\n );\n\n return {\n data: sqlRows<HistoryEntry>(dataResult),\n total\n };\n }\n\n /**\n * Fetch a single history entry by ID.\n */\n async fetchHistoryEntry(historyId: string): Promise<HistoryEntry | null> {\n const result = await this.db.execute(sql`\n SELECT id, table_name, entity_id, action, changed_fields,\n \"values\", previous_values, updated_by, updated_at, ${this.formatColumn()}\n FROM rebase.entity_history\n WHERE id = ${historyId}\n `);\n\n return firstSqlRow<HistoryEntry>(result) ?? null;\n }\n\n /**\n * Each entry's `format`. A table without the column holds only entries\n * that store the whole previous row, which is what `1` says.\n */\n private formatColumn() {\n return this.entryFormat === 2 ? sql`format` : sql`1::smallint AS format`;\n }\n\n // ───────── Retention / Pruning ─────────\n\n /**\n * Prune history for a single row: enforce maxEntries and TTL.\n */\n async pruneEntity(tableName: string, id: string): Promise<number> {\n // One statement for both rules: older than `ttlDays`, or beyond the\n // newest `maxEntries`. They were two DELETEs, each its own pool\n // checkout after every recorded change — 2,000 of them queued by a\n // 1,000-row bulk. Together they remove the same rows: an expired entry\n // is older than every live one, so the newest `maxEntries` of all of\n // them are the newest of the live ones whenever there are that many,\n // and otherwise every entry past them has expired anyway.\n const result = await this.db.execute(sql`\n DELETE FROM rebase.entity_history\n WHERE table_name = ${tableName}\n AND entity_id = ${String(id)}\n AND (\n updated_at < NOW() - MAKE_INTERVAL(days => ${this.retention.ttlDays})\n OR id IN (\n SELECT id FROM rebase.entity_history\n WHERE table_name = ${tableName}\n AND entity_id = ${String(id)}\n ORDER BY updated_at DESC\n OFFSET ${this.retention.maxEntries}\n )\n )\n `);\n return result.rowCount ?? 0;\n }\n\n /**\n * Delete every entry older than `ttlDays`, whichever row it belongs to.\n *\n * The inline prune above only ever visits a row that was just written,\n * so a row nobody writes again — every deleted one — kept its history for\n * good, whatever `ttlDays` said. This is the other half: the backend runs\n * it once an hour, on one instance of the fleet (see\n * `startHistoryRetentionSweep` in `@rebasepro/server`).\n *\n * In batches of {@link PRUNE_BATCH_SIZE}, each its own statement, repeated\n * until one comes back short: a first sweep over a year of backlog is many\n * short deletes, not one that holds millions of row locks and a pool\n * connection for minutes. `SKIP LOCKED`, so two instances sweeping at once\n * (or a sweep meeting an inline prune) split the work instead of waiting\n * on each other. Each batch reads `idx_history_updated_at`, built in the\n * background at boot (`ensureHistoryIndexes`).\n *\n * `signal` ends it after the batch in flight: a shutdown waits for the\n * sweep, and a first sweep over a large backlog is hundreds of batches.\n * What is left goes in the next sweep.\n */\n async pruneExpired(options?: { signal?: AbortSignal }): Promise<number> {\n let total = 0;\n for (;;) {\n if (options?.signal?.aborted) return total;\n const result = await this.db.execute(sql`\n DELETE FROM rebase.entity_history\n WHERE id IN (\n SELECT id FROM rebase.entity_history\n WHERE updated_at < NOW() - MAKE_INTERVAL(days => ${this.retention.ttlDays})\n LIMIT ${PRUNE_BATCH_SIZE}\n FOR UPDATE SKIP LOCKED\n )\n `);\n const deleted = result.rowCount ?? 0;\n total += deleted;\n if (deleted < PRUNE_BATCH_SIZE) return total;\n }\n }\n}\n\n\n/**\n * What `previous` held in `fields`. A field it did not have is left out, so it\n * reads as absent before the change, as it was.\n */\nfunction previousValuesOf(previous: Record<string, unknown>, fields: readonly string[]): Record<string, unknown> {\n const kept: Record<string, unknown> = {};\n for (const field of fields) {\n if (Object.prototype.hasOwnProperty.call(previous, field)) kept[field] = previous[field];\n }\n return kept;\n}\n\n/**\n * Deep equality without JSON.stringify.\n * Handles primitives, arrays, Dates, and plain objects recursively.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null) return false;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((v, i) => deepEqual(v, b[i]));\n }\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every(k => deepEqual(aObj[k], bObj[k]));\n }\n return false;\n}\n\n/**\n * Shallow comparison to find top-level keys that changed between two objects.\n */\nexport function findChangedFields(\n oldValues: Record<string, unknown>,\n newValues: Record<string, unknown>\n): string[] | null {\n const changed: string[] = [];\n const allKeys = new Set([\n ...Object.keys(oldValues),\n ...Object.keys(newValues)\n ]);\n\n for (const key of allKeys) {\n const oldVal = oldValues[key];\n const newVal = newValues[key];\n\n // Skip internal metadata\n if (key.startsWith(\"__\")) continue;\n\n if (oldVal !== newVal) {\n // For objects/arrays, use structural comparison\n if (\n typeof oldVal === \"object\" && oldVal !== null &&\n typeof newVal === \"object\" && newVal !== null\n ) {\n if (!deepEqual(oldVal, newVal)) {\n changed.push(key);\n }\n } else {\n changed.push(key);\n }\n }\n }\n\n return changed.length > 0 ? changed : null;\n}\n","import { getTableColumns } from \"drizzle-orm\";\nimport { PgArray, PgTable } from \"drizzle-orm/pg-core\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Patches all PgArray columns on the given tables to handle NULL values safely.\n *\n * Drizzle ORM's `PgArray.mapFromDriverValue` calls `value.map(...)` without\n * guarding against `null`. When a PostgreSQL native array column (`text[]`,\n * `integer[]`, etc.) contains NULL, the pg driver returns `null` in JavaScript,\n * and `null.map(...)` throws `TypeError: value.map is not a function`.\n *\n * This function walks every column of every registered table and, for any\n * `PgArray` column, wraps its `mapFromDriverValue` to return `null` when the\n * database value is nullish.\n *\n * This is a workaround for a known Drizzle ORM issue. Should be removed once\n * Drizzle handles nullable arrays natively.\n */\nexport function patchPgArrayNullSafety(tables: Record<string, unknown>): void {\n let patchedCount = 0;\n\n for (const tableOrRelation of Object.values(tables)) {\n if (!(tableOrRelation instanceof PgTable)) continue;\n\n const columns = getTableColumns(tableOrRelation);\n for (const column of Object.values(columns)) {\n if (column instanceof PgArray) {\n const original = column.mapFromDriverValue.bind(column);\n column.mapFromDriverValue = function (value: unknown) {\n if (value == null) return null;\n return original(value as string | unknown[]);\n };\n patchedCount++;\n }\n }\n }\n\n if (patchedCount > 0) {\n logger.debug(`[PgArray] Patched ${patchedCount} array column(s) for null-safety`);\n }\n}\n","import { getTableColumns } from \"drizzle-orm\";\nimport { PgArray, PgNumeric, PgTable } from \"drizzle-orm/pg-core\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Serve NUMERIC columns as JavaScript numbers.\n *\n * A property declared `type: \"number\"` without `validation.integer` becomes a\n * Postgres `numeric` column (see `generate-drizzle-schema-logic`). Postgres\n * sends `numeric` as text, node-postgres registers no parser for OID 1700, and\n * drizzle's `PgNumeric.mapFromDriverValue` keeps the string — so a price\n * written as `2.5` reads back as `\"2.5\"`. The generated SDK types that field\n * `number`, the OpenAPI document this server publishes says `type: number`, and\n * the admin's view model parses it as a number, so the string is the one shape\n * nothing in the system claims. It also breaks *asymmetrically*: the create\n * response carries the number and the read that follows carries the string, so\n * a client that multiplies a price works until the first refresh.\n *\n * Cast here — in the driver's row mapping — rather than in one of the read\n * paths, because there are several of them (the relational query builder, the\n * `select` fallback, the realtime frame, the admin's view model) and only the\n * REST renderer used to coerce. Every one of them goes through the column's\n * `mapFromDriverValue`, so this is the single place that cannot be bypassed.\n *\n * The cast is safe for what a `number` property can hold: declaring a property\n * `number` already promises double precision — that is what the admin has\n * always parsed it to and what the generated `Row` type says. A column wider\n * than a double (a hand-written `numeric(38, 0)` reached through an\n * introspected collection) loses precision, which is the trade the declared\n * type already made.\n *\n * `null` stays `null`; an unparseable value stays whatever the driver sent,\n * because inventing `NaN` for it would be worse than passing it through.\n */\nexport function patchPgNumericToNumber(tables: Record<string, unknown>): void {\n let patchedCount = 0;\n\n const patch = (column: PgNumeric<never>): void => {\n // Idempotent: bootstrapping twice in one process (two data sources\n // sharing a table object) must not stack wrappers.\n const marked = column as unknown as { __rebaseNumericPatched?: boolean };\n if (marked.__rebaseNumericPatched) return;\n marked.__rebaseNumericPatched = true;\n\n column.mapFromDriverValue = function (value: unknown) {\n if (value === null || value === undefined) return value as never;\n if (typeof value === \"number\") return value as never;\n const parsed = Number(value);\n return (Number.isNaN(parsed) ? value : parsed) as never;\n };\n patchedCount++;\n };\n\n for (const tableOrRelation of Object.values(tables)) {\n if (!(tableOrRelation instanceof PgTable)) continue;\n\n for (const column of Object.values(getTableColumns(tableOrRelation))) {\n if (column instanceof PgNumeric) {\n patch(column as PgNumeric<never>);\n continue;\n }\n // `numeric[]`: the array column parses the literal and hands each\n // element to its base column, so the base column is what to patch.\n if (column instanceof PgArray && column.baseColumn instanceof PgNumeric) {\n patch(column.baseColumn as PgNumeric<never>);\n }\n }\n }\n\n if (patchedCount > 0) {\n logger.debug(`[PgNumeric] ${patchedCount} numeric column(s) will be served as numbers`);\n }\n}\n","/**\n * PostgresBootstrapper\n *\n * Implements the `BackendBootstrapper` interface for PostgreSQL.\n */\n\nimport { Relations, sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { PgTable } from \"drizzle-orm/pg-core\";\nimport { asRebasePgTable, type RebasePgTable } from \"./types\";\nimport {\n type AuthAdapter,\n BackendBootstrapper,\n BootstrappedAuth,\n DatabaseAdmin,\n type DataDriver,\n CollectionConfig,\n DEFAULT_DATA_SOURCE_KEY,\n isRelationalCollectionConfig,\n type HistoryConfig,\n InitializedDriver,\n type DatabaseAdapterInitConfig,\n RealtimeProvider,\n resourceEnvSuffix,\n type RealtimeChannelsConfig,\n type RealtimeSocketOptions\n} from \"@rebasepro/types\";\nimport { PostgresBackendDriver } from \"./PostgresBackendDriver\";\nimport { RealtimeService, parseMaxSocketsPerUser, parseMaxSubscriptionsPerSocket, parseRealtimeTuning } from \"./services/realtimeService\";\nimport { buildCollectionRegistry } from \"./collections/buildRegistry\";\nimport { DatabasePoolManager } from \"./databasePoolManager\";\nimport { PostgresCollectionRegistry } from \"./collections/PostgresCollectionRegistry\";\nimport { createEmailService, type EmailConfig, type EmailService, logger } from \"@rebasepro/server\";\nimport { getTableName as getCollectionTableName, sqlRows } from \"@rebasepro/common\";\nimport { ensureAuthTablesExist } from \"./auth/ensure-tables\";\nimport { assertAuthSchemaCompatible, probeAuthSchema, resolveAuthSchema } from \"./auth/schema-version\";\nimport { AuthSchemaTables, PostgresAuthRepository, UserService } from \"./auth/services\";\nimport { createAuthSchema } from \"./schema/auth-schema\";\nimport { HistoryService } from \"./history/HistoryService\";\nimport { ensureHistoryIndexes, ensureHistoryTableExists, historyUnavailableReason, readHistoryTableState } from \"./history/ensure-history-table\";\nimport { patchPgArrayNullSafety } from \"./utils/pg-array-null-patch\";\nimport { patchPgNumericToNumber } from \"./utils/pg-numeric-number-patch\";\nimport { buildCollectionsFromSchema, introspectSchema, readRlsStatus } from \"./schema/introspect-runtime\";\nimport { buildBaasSchema, readCatalogueSchema } from \"./schema/catalogue-schema\";\nimport { diffGeneratedSchemaAgainstCatalogue, warnOnGeneratedSchemaDrift } from \"./schema/generated-schema-diff\";\nimport type { TableMeta } from \"./schema/introspect-db-logic\";\nimport { detectConnectionPosture, ensureAppRole, ensureAppRoleGrants, revokeUserRoleSql, validatePolicyPgRoles, warnOnAnonymousGrants, warnOnLegacyRlsFunctions, warnOnRoleSchemaCollision, REBASE_USER_ROLE, type RawSqlRunner } from \"./security/rls-enforcement\";\nimport { provisionTriggerCdc, settleInstalledCapture, type CdcTableRef } from \"./services/cdc/trigger-cdc\";\nimport { collectJunctionLinks } from \"./services/cdc/junction-tables\";\nimport { collectionKeyColumns } from \"./services/cdc/identity-columns\";\nimport { createChannelBus, resolveChannelBusSetting } from \"./services/channel-bus\";\nimport { isChannelBusInstance } from \"@rebasepro/types\";\nimport { configureUnknownFilterFields, type UnknownFilterFieldsMode } from \"./utils/drizzle-conditions\";\n\nexport interface PostgresDriverConfig {\n connectionString?: string;\n adminConnectionString?: string;\n /**\n * This database's read replica: reads are routed there, writes stay on\n * the primary. Unset, the boot ({@link postgresDriverConfigFromEnv}) fills\n * it from `DATABASE_READ_URL` for the default source only; any other\n * source has none unless its config names one — the managed runtime\n * resolves `DATABASE_READ_URL__<KEY>` into this for each source.\n */\n readConnectionString?: string;\n /**\n * A direct (non-pooled) URL to this database, for the connections that\n * hold a `LISTEN`: change capture, cross-instance realtime, the Postgres\n * channel bus and `admin.listen`. A transaction-mode pooler such as\n * PgBouncer does not keep a `LISTEN`, so behind one this has to name the\n * database itself.\n *\n * Unset, the boot fills it from `DATABASE_DIRECT_URL` for the default\n * source only, and any other source listens on its own\n * `connectionString` — see {@link resolveSourceUrls}. The managed runtime\n * resolves `DATABASE_DIRECT_URL__<KEY>` into this for each source.\n */\n directConnectionString?: string;\n connection?: unknown;\n /**\n * The project's generated Drizzle module, for **diagnostics only**.\n *\n * The tables this driver serves are read from `information_schema` at boot\n * (see `catalogue-schema.ts`); the only thing this is used for is telling\n * the developer that their committed `schema.generated.ts` no longer\n * describes the database, which still matters for Atlas, `db push`, `eject`\n * and any code importing it. Leaving it out costs the warning and nothing\n * else.\n */\n schema?: {\n tables?: Record<string, unknown>;\n enums?: Record<string, unknown>;\n relations?: Record<string, unknown>;\n };\n /**\n * PostgreSQL schema to read when deriving collections from the database\n * (BaaS mode). Defaults to `public`.\n */\n introspectionSchema?: string;\n /**\n * Realtime options, among them:\n *\n * - `channels` — retention. Without rules no channel keeps any history and\n * broadcast stays fire-and-forget. See {@link ChannelRetentionRule}.\n * - `bus` — the cross-instance transport for channel broadcast and\n * presence. Defaults to in-process only, which is correct for a single\n * instance and wrong for two. See {@link ChannelBusConfig}.\n * - `cdc` — how changes are captured (`auto` by default).\n *\n * At boot each has an environment variable that wins over it — see\n * {@link postgresDriverConfigFromEnv}.\n */\n realtime?: RealtimeChannelsConfig;\n /**\n * What to do with a filter field that resolves to no column at all.\n * Defaults to `\"error\"` — a filter that cannot be compiled would otherwise\n * be dropped, and a dropped condition can only widen the result set.\n * Set to `\"warn\"` to restore the pre-fix behaviour of dropping it silently.\n */\n unknownFilterFields?: UnknownFilterFieldsMode;\n}\n\n/**\n * Opaque internals bag that PostgresBootstrapper stores during `initializeDriver()`\n * and re-uses in subsequent lifecycle hooks.\n */\nexport interface PostgresDriverInternals {\n db: NodePgDatabase<any>;\n readDb?: NodePgDatabase<any>;\n registry: PostgresCollectionRegistry;\n realtimeService: RealtimeService;\n driver: PostgresBackendDriver;\n poolManager?: DatabasePoolManager;\n /**\n * Whether the driver was opened with provisioning (`provision`, default\n * true). The hooks that run after it — auth, history, the final security\n * posture — read it, so `provision: false` means no DDL anywhere on the\n * driver's path, not only while it opens.\n */\n provisioned: boolean;\n /**\n * How authenticated requests are isolated on this connection, in one\n * line: run as the restricted role, or filtered natively.\n *\n * Returned rather than logged by {@link openPostgresDriver}. Announcing it\n * is the server boot's business — `initializeDriver` logs it at info — and\n * a script's `openRebase` logs it at debug, so a script's stdout holds what\n * the script prints.\n */\n rlsEnforcement?: string;\n /**\n * Attach CDC triggers to tables that did not exist when the driver\n * bootstrapped. Only set when database-level capture is actually active.\n *\n * Auth owns its own tables and creates them later in boot, so at driver\n * bootstrap they are legitimately missing and get skipped; without this\n * they would stay uninstrumented until the next restart.\n */\n provisionCdcForTables?: (tables: CdcTableRef[]) => Promise<void>;\n}\n\n// Re-export from shared CLI error utilities\nimport { closedDatabaseName, deepestErrorMessage, diagnoseDbError, isEconnrefused, parseHostInfo } from \"./cli-errors\";\nimport { classifyConnectFailure, extractPgError } from \"./utils/pg-error-utils\";\nimport type { OpenIndexBuildSession, ScheduledIndexBuild } from \"./schema/deferred-index-build\";\n\n/**\n * How long after the policies step the deferred index builds start: past the\n * rest of boot, so the server is listening by then.\n */\nconst DEFERRED_INDEX_BUILD_DELAY_MS = 5_000;\n\n/**\n * Which table name the boot-time drift check should look for, for one collection.\n *\n * A declared `table` IS the table name, not a hint to be second-guessed. This\n * used to ask the registry whether it had indexed the declared name and fall\n * back to the SLUG when it had not — but \"the registry does not know this table\"\n * is exactly the condition the drift check exists to report, so the fallback\n * fired precisely when it was most harmful.\n *\n * A collection with `slug: \"usage-daily\", table: \"usage_daily\"` was reported as\n * missing table `usage-daily`: a name that does not exist, should never exist,\n * and that nobody can find by looking. Worse, the remediation the caller prints\n * says to run `rebase db push` — which would then CREATE that invented table\n * beside the correct one, the same \"second copy\" hazard the misplaced-schema\n * branch further down exists to prevent. Seen in production, where a correctly\n * migrated database reported drift on every boot.\n *\n * The slug is used only when nothing was declared, which is the config shape\n * where the slug genuinely is the table name.\n *\n * Exported for its own test: the caller needs a live pool and a real database,\n * and this is the part that was wrong.\n */\nexport function resolveDriftCheckName(\n col: CollectionConfig,\n registeredTableNames: string[]\n): string {\n const declaredTable = isRelationalCollectionConfig(col) ? col.table : undefined;\n return declaredTable\n ?? registeredTableNames.find((k) => k === col.slug)\n ?? col.slug;\n}\n\n/**\n * Why the tables this backend serves are not in the database — the part of the\n * drift warning that has to be true rather than merely plausible.\n *\n * The three answers need three different actions, and only the caller knows\n * which one applies. This warning used to assert the first (\"this runtime\n * applies the collection schema at boot unless REBASE_MIGRATE_ON_BOOT=none\")\n * and then point at that variable and at driver-version skew. For an app whose\n * boot path contained no provisioning step at all, every word of that was a\n * dead end: nothing read the variable, and the driver was current. The advice\n * cost an investigation, which is a strictly worse outcome than saying less.\n *\n * Exported for its own test: the surrounding check needs a live pool and a real\n * database, and this is the part that was wrong.\n */\nexport function describeSchemaDriftCause(\n provisioning: { attempted: boolean; reason?: string } | undefined\n): string[] {\n // A caller too old to send the signal gets no claim either way — just where\n // to look. Guessing is what got this wrong the first time.\n if (provisioning === undefined) {\n return [\n \" This runtime could not determine whether a schema-creation step ran\",\n \" before this check (the caller predates that signal).\",\n \" • Look for a \\\"Collection schema:\\\" line above. No such line at all\",\n \" means nothing tried to create these tables in this process.\"\n ];\n }\n if (provisioning.attempted) {\n return [\n \" A schema-creation step DID run this boot and these tables are still\",\n \" missing, so it did not create them — check the \\\"schema:\\\" lines above\",\n \" for what it did instead, and for DDL errors.\",\n \" • A collection routed to another engine or data source is not\",\n \" created here; that is reported separately at boot.\",\n \" • Otherwise this is a bug worth reporting, with those lines.\"\n ];\n }\n return [\n \" No schema-creation step ran this boot:\",\n ` ${provisioning.reason ?? \"no reason was given.\"}`,\n \" Resolve that reason — the drift is its consequence, not a separate\",\n \" problem, and re-running a migration tool will not change it.\"\n ];\n}\n\n/**\n * The URLs one source's driver opens besides its pool: the read replica, and\n * the direct connection its `LISTEN`s are held on.\n *\n * The bare `DATABASE_READ_URL` and `DATABASE_DIRECT_URL` belong to the default\n * source. Both used to be read here for every source, so a second database\n * (`database(\"analytics\")`) held its change-capture `LISTEN` on the default\n * database — where none of its notifications are sent, so its realtime feed\n * was silent — and read through the default's replica. A keyed source gets\n * the URLs its config names, and otherwise its own connection and no replica.\n *\n * The default source still falls back to the environment: an application that\n * builds this adapter itself passes a `connectionString` and nothing else, and\n * has always had those two variables honoured.\n *\n * Exported for its own test; the caller needs a live database.\n */\nexport function resolveSourceUrls(\n pgConfig: Pick<PostgresDriverConfig, \"connectionString\" | \"readConnectionString\" | \"directConnectionString\">,\n sourceKey: string,\n env: Record<string, string | undefined> = process.env\n): { readUrl?: string; directUrl?: string } {\n const isDefault = sourceKey === DEFAULT_DATA_SOURCE_KEY;\n return {\n readUrl: pgConfig.readConnectionString || (isDefault ? env.DATABASE_READ_URL : undefined) || undefined,\n directUrl: pgConfig.directConnectionString\n || (isDefault ? env.DATABASE_DIRECT_URL : undefined)\n || pgConfig.connectionString\n || undefined\n };\n}\n\n/**\n * Is this the local database `rebase init` scaffolds — i.e. the one case where\n * \"you are connected as a superuser\" is not news?\n *\n * The scaffold's own `docker-compose.yml` sets `POSTGRES_USER: rebase_app`,\n * which makes that role the cluster superuser, so the superuser advisory below\n * was the only WARN a brand-new project ever saw and it was about a decision\n * the tool had made for the developer.\n *\n * Of the two available fixes — provision a non-superuser table-owner role in\n * the scaffold, or recognise the local shape and stay quiet — this is the\n * second, because the first breaks the scaffold it is meant to improve: a\n * non-superuser owner cannot `CREATE EXTENSION` (search collections need\n * `pg_trgm`/`unaccent`, applied by `rebase db push` and again by the boot\n * schema-ensure), so the very first `pnpm run db:push` on a scaffolded project\n * with a search block would fail. Trading a working first run for a quieter log\n * line is the wrong trade.\n *\n * The condition is deliberately narrow — a *non-production* process talking to\n * a database on the loopback interface. A genuine production superuser\n * connection still warns, and so does a non-production process pointed at a\n * remote database (the usual \"my dev machine writes to staging\" mistake, where\n * the advisory is exactly right). NODE_ENV alone would not do: the scaffold\n * ships `NODE_ENV=development` and some deployments inherit it.\n */\nexport function isScaffoldedLocalDatabase(connectionString: string | undefined): boolean {\n if (process.env.NODE_ENV === \"production\") return false;\n if (!connectionString) return false;\n let host: string;\n try {\n host = new URL(connectionString).hostname;\n } catch {\n return false;\n }\n // `new URL` keeps IPv6 literals in brackets.\n const bare = host.replace(/^\\[|\\]$/g, \"\").toLowerCase();\n return bare === \"localhost\"\n || bare === \"::1\"\n || bare === \"0.0.0.0\"\n || bare === \"\"\n || /^127\\./.test(bare)\n || bare.endsWith(\".localhost\");\n}\n\n/**\n * Default PostgreSQL bootstrapper.\n *\n * Use it to register Postgres with `initializeRebaseBackend()`:\n * ```typescript\n * initializeRebaseBackend({\n * ...config,\n * bootstrappers: [postgresBootstrapper()]\n * });\n * ```\n */\n/**\n * Where the collections schema stamp lives.\n *\n * The runtime's own internal schema, always — unlike the auth stamp, which\n * follows the users collection. `rebase` and `auth` sit outside\n * `introspectionSchema` by construction, so nothing here is ever served as a\n * collection.\n */\nconst SCHEMA_META_SCHEMA = \"rebase\";\n\n/**\n * One line about a statement the ensure could not apply and chose to survive.\n *\n * Each kind degrades differently and the operator's next move differs with it,\n * so they do not share a sentence. What they share is the shape: what is still\n * true, what is no longer true, and the database's own reason — never a bare\n * \"failed\", which is the version of this that taught nobody anything.\n */\nfunction describeEnsureFailure(failure: { kind: string; target: string; error: string }): string {\n switch (failure.kind) {\n case \"comment-column\":\n // The stamp records which `search` block the generated column was\n // built from. Without it the next boot cannot tell a changed block\n // from an unchanged one and adopts the column again instead of\n // refusing.\n return (\n `🔍 [schema] Could not record the search fingerprint on \"${failure.target}\" — search works, ` +\n `but a later change to the \\`search\\` block will not be detected: ${failure.error}`\n );\n case \"create-index\":\n // Not fatal, on purpose: the collection serves, every query returns\n // the same rows, and only latency changes. Said at `warn` every boot\n // because the alternative is a table that is permanently unindexed\n // and looks exactly like one that is not.\n return (\n `🐢 [schema] Could not create an index on \"${failure.target}\" — the collection serves and returns ` +\n \"the same rows, but queries that would have used this index will scan instead. Boot is not failed \" +\n `over an index: ${failure.error}`\n );\n default:\n return (\n `🔗 [schema] Could not add foreign key \"${failure.target}\" — the column exists and the ` +\n `collection still serves, but rows are not policed by this constraint: ${failure.error}`\n );\n }\n}\n\n/**\n * Say what a failed `SELECT 1` actually means, and whether boot can go on.\n *\n * Returns the error to throw, or `undefined` when the failure could be\n * transient and the pool may still recover.\n *\n * Two problems this exists to keep fixed. First, the logged error is Drizzle's\n * wrapper — `Failed query: SELECT 1` and a stack through drizzle internals —\n * while the sentence that actually says what is wrong (\"connect ECONNREFUSED\n * 127.0.0.1:5432\", \"password authentication failed for user …\", \"database …\n * does not exist\") sits in `.cause`, or inside the `AggregateError` a\n * dual-stack host produces. A developer with a stopped database, or a typo in\n * their `DATABASE_URL`, got two walls of stack trace and no cause.\n *\n * Second, \"continuing… the pool may recover\" is only true for a transient\n * fault. A wrong password or a missing database is settled: the next query\n * fails the same way, so the process died seconds later anyway — after printing\n * a reassurance.\n *\n * The banners come from `diagnoseDbError`, the same ones the CLI prints, so a\n * developer sees one diagnosis whether the database was unreachable during\n * `rebase db push` or during boot.\n */\nfunction diagnoseConnectFailure(err: unknown, connectionString: string | undefined): Error | undefined {\n const url = connectionString ?? \"\";\n const hostInfo = parseHostInfo(url);\n\n if (isEconnrefused(err)) {\n logger.error(diagnoseDbError(err, url) ?? `❌ Cannot connect to PostgreSQL at ${hostInfo}`);\n const reason = deepestErrorMessage(err) ?? \"connection refused\";\n return new Error(\n `Cannot connect to PostgreSQL at ${hostInfo}: ${reason}. Is the database running?`,\n { cause: err }\n );\n }\n\n const { fatal, reason, code } = classifyConnectFailure(err);\n const detail = code ? ` [${code}]` : \"\";\n\n // Closed to new connections: settled until someone opens it again, which\n // the banner says how to do.\n const closed = closedDatabaseName(err);\n if (closed) {\n logger.error(diagnoseDbError(err, url) ?? `❌ Database \"${closed}\" at ${hostInfo} is closed to new connections`);\n return new Error(\n `Database \"${closed}\" at ${hostInfo} is closed to new connections: ${reason}${detail}. ` +\n \"Open it again with `rebase db branch list`.\",\n { cause: err }\n );\n }\n\n // Every connection the server accepts is taken: SQLSTATE 53300, what\n // Postgres answers over its `max_connections` and what the managed\n // development database's proxy answers over its few. Not a database that\n // is down, so it says which, with the managed database's own remedy (its\n // refusal's hint) where it sent one. Not settled either: a connection may\n // free up, so a caller that can carry on still does.\n if (code === \"53300\") {\n const remedy = extractPgError(err)?.hint\n ?? \"Close another client, cap this process's pool with REBASE_DB_POOL_MAX, or raise the server's max_connections.\";\n logger.error(\n `\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n` +\n ` ❌ PostgreSQL at ${hostInfo} has no free connection\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n` +\n `\\n` +\n ` ${reason}${detail}\\n` +\n `\\n` +\n ` ${remedy}\\n` +\n `\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n`\n );\n return undefined;\n }\n\n if (fatal) {\n logger.error(\n diagnoseDbError(err, url) ??\n `\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n` +\n ` ❌ PostgreSQL at ${hostInfo} refused the connection\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n` +\n `\\n` +\n ` ${reason}${detail}\\n` +\n `\\n` +\n ` The server is reachable, so this is the credentials or the\\n` +\n ` database name in DATABASE_URL — check them in your .env.\\n` +\n `\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n`\n );\n return new Error(`PostgreSQL at ${hostInfo} refused the connection: ${reason}${detail}`, { cause: err });\n }\n\n logger.error(`❌ Failed to connect to PostgreSQL at ${hostInfo}: ${reason}${detail}`, { error: err });\n return undefined;\n}\n\n/** The capture modes `realtime.cdc` takes. */\nconst REALTIME_CDC_MODES = [\"auto\", \"wal\", \"trigger\", \"off\"] as const;\ntype RealtimeCdcMode = typeof REALTIME_CDC_MODES[number];\n\nfunction isRealtimeCdcMode(value: string): value is RealtimeCdcMode {\n return REALTIME_CDC_MODES.some((mode) => mode === value);\n}\n\n/**\n * Read a capture mode, case and surrounding space aside. An unknown one is\n * `auto`, said out loud — a typo must not switch capture off.\n */\nfunction parseRealtimeCdcMode(value: string, source: string): RealtimeCdcMode {\n const mode = value.trim().toLowerCase();\n if (isRealtimeCdcMode(mode)) return mode;\n logger.warn(`⚠️ [CDC] Unknown ${source} value \"${mode}\" — expected auto|wal|trigger|off. Defaulting to \"auto\".`);\n return \"auto\";\n}\n\n/**\n * `config` with the environment read into it, the way the server's boot reads\n * it. The one place this driver reads environment variables:\n *\n * | variable | setting |\n * |----------------------------------------|----------------------------------------|\n * | `DATABASE_READ_URL` | `readConnectionString`, when unset (*) |\n * | `DATABASE_DIRECT_URL` | `directConnectionString`, when unset (*)|\n * | `REALTIME_CDC` | `realtime.cdc` |\n * | `REALTIME_CHANNEL_BUS` | `realtime.bus` (a named transport) |\n * | `REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET`| `realtime.maxSubscriptionsPerSocket` |\n * | `REALTIME_MAX_SOCKETS_PER_USER` | `realtime.maxSocketsPerUser` |\n * | `REALTIME_REFETCH_MAX_WAIT_MS` | `realtime.refetchMaxWaitMs` |\n * | `REALTIME_RECOUNT_INTERVAL_MS` | `realtime.recountIntervalMs` |\n * | `REALTIME_RECOUNT_MIN_ROWS` | `realtime.recountMinRows` |\n *\n * A `REALTIME_*` variable that is set wins over the option, so a deployment\n * can change it without a rebuild; the URLs fill in only what the config left\n * empty. A mistyped number throws, naming the variable.\n *\n * (*) For the default source only. The bare URL variables name the default\n * database; read for a second source (`database(\"analytics\")`), they put its\n * change-capture `LISTEN` on the default database, where none of its\n * notifications arrive, and its reads on the default's replica. A keyed\n * source's URLs come from its own config — the managed runtime passes\n * `DATABASE_*_URL__<KEY>` there — see {@link resolveSourceUrls}.\n *\n * `createPostgresAdapter` applies this when the server boots, and\n * {@link openPostgresDriver} does not — a script that opens the driver itself\n * gets exactly the config it passed.\n */\nexport function postgresDriverConfigFromEnv(\n config: PostgresDriverConfig,\n env: NodeJS.ProcessEnv = process.env,\n sourceKey: string = DEFAULT_DATA_SOURCE_KEY\n): PostgresDriverConfig {\n const read = (name: string): string | undefined => env[name]?.trim() || undefined;\n const realtime: RealtimeChannelsConfig = { ...config.realtime };\n\n const subscriptions = read(\"REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET\");\n if (subscriptions) {\n realtime.maxSubscriptionsPerSocket = parseMaxSubscriptionsPerSocket(subscriptions, \"REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET\");\n }\n const sockets = read(\"REALTIME_MAX_SOCKETS_PER_USER\");\n if (sockets) realtime.maxSocketsPerUser = parseMaxSocketsPerUser(sockets, \"REALTIME_MAX_SOCKETS_PER_USER\");\n const refetchMaxWaitMs = read(\"REALTIME_REFETCH_MAX_WAIT_MS\");\n if (refetchMaxWaitMs) realtime.refetchMaxWaitMs = parseRealtimeTuning(refetchMaxWaitMs, \"REALTIME_REFETCH_MAX_WAIT_MS\", \"milliseconds\");\n const recountIntervalMs = read(\"REALTIME_RECOUNT_INTERVAL_MS\");\n if (recountIntervalMs) realtime.recountIntervalMs = parseRealtimeTuning(recountIntervalMs, \"REALTIME_RECOUNT_INTERVAL_MS\", \"milliseconds\");\n const recountMinRows = read(\"REALTIME_RECOUNT_MIN_ROWS\");\n if (recountMinRows) realtime.recountMinRows = parseRealtimeTuning(recountMinRows, \"REALTIME_RECOUNT_MIN_ROWS\", \"rows\");\n const cdc = read(\"REALTIME_CDC\");\n if (cdc) realtime.cdc = parseRealtimeCdcMode(cdc, \"REALTIME_CDC\");\n realtime.bus = resolveChannelBusSetting(config.realtime?.bus, env);\n\n const isDefaultSource = sourceKey === DEFAULT_DATA_SOURCE_KEY;\n const readConnectionString = config.readConnectionString || (isDefaultSource ? read(\"DATABASE_READ_URL\") : undefined);\n const directConnectionString = config.directConnectionString || (isDefaultSource ? read(\"DATABASE_DIRECT_URL\") : undefined);\n return {\n ...config,\n ...(readConnectionString ? { readConnectionString } : {}),\n ...(directConnectionString ? { directConnectionString } : {}),\n realtime\n };\n}\n\n/**\n * Open the Postgres data driver from explicit settings.\n *\n * What `createPostgresAdapter` runs when the server boots, once it has read\n * the environment into `pgConfig` (see {@link postgresDriverConfigFromEnv});\n * this function reads no environment variable itself. `config` says what the\n * caller wants opened: which collections, whether opening may change the\n * database (`provision`, default true), and what part this process takes in\n * realtime (`realtime`, or `false` for none at all).\n *\n * `openRebase` is this with `provision: false` and `realtime: false`.\n */\nexport async function openPostgresDriver(\n pgConfig: PostgresDriverConfig,\n config: DatabaseAdapterInitConfig\n): Promise<InitializedDriver & { internals: PostgresDriverInternals }> {\n const { collections, introspectCollections, baas, schemaProvisioning, realtime } = config;\n // `false` keeps every DDL statement out of this open: the restricted\n // role, the branch table, channel-history tables, capture triggers.\n const provision = config.provision !== false;\n // `realtime: false` is a process that takes no part in realtime: nothing\n // installed, nothing consumed, its writes not announced.\n const realtimeOff = realtime === false;\n // Absent means a caller that predates the field, and every one of\n // those is a single process that both serves websockets and owns the\n // schema. Defaulting to false here would silently disable realtime\n // for them — the failure this whole area is prone to.\n const realtimeSubscribes = realtimeOff ? false : realtime?.subscribe ?? true;\n const realtimeProvisions = provision && !realtimeOff && (realtime?.provision ?? true);\n // Secure by default: a table with no RLS is not served.\n const unprotectedTables = baas?.unprotectedTables ?? \"exclude\";\n\n const connection = pgConfig.connection;\n const rawClient = (connection && typeof connection === \"object\" && \"$client\" in connection\n ? (connection as Record<string, unknown>).$client\n : connection) as import(\"pg\").Pool;\n\n // ── No declared collections: derive the schema from the database ──\n // No collection files and no generated drizzle schema exist, so read\n // the live database and build both from what is actually there.\n let introspectedCollections: CollectionConfig[] | undefined;\n // The tables skipped for having no authorization model, for the server to\n // name if it then has nothing to serve.\n let skippedUnprotected: { schema: string; table: string }[] | undefined;\n let introspectedTables: Record<string, PgTable> | undefined;\n let introspectedRelations: Record<string, Relations> | undefined;\n if (introspectCollections && (!collections || collections.length === 0)) {\n const pgSchemaName = pgConfig.introspectionSchema ?? \"public\";\n const schema = await introspectSchema(rawClient, pgSchemaName);\n\n // ── Only serve what the database protects ────────────────\n // Requests run as rebase_user, which is granted DML on the\n // schema. A table with RLS disabled therefore has no\n // authorization model at all: serving it hands every row to\n // every authenticated user. baas never runs `db push`, so\n // nothing here would have enabled RLS on the user's behalf.\n const rlsStatus = await readRlsStatus(rawClient, pgSchemaName);\n const unprotected = [...schema.tablesMap.keys()].filter(\n (t) => !schema.joinTables.has(t) && !rlsStatus.get(t)?.rlsEnabled\n );\n const policyless = [...schema.tablesMap.keys()].filter(\n (t) => !schema.joinTables.has(t) && rlsStatus.get(t)?.rlsEnabled && rlsStatus.get(t)?.policyCount === 0\n );\n\n if (unprotected.length > 0) {\n if (unprotectedTables === \"serve\") {\n logger.warn(\n `🔓 [rls] Serving ${unprotected.length} table(s) with row-level security DISABLED: ${unprotected.join(\", \")}. ` +\n \"Every authenticated request can read and write every row of these. \" +\n \"This is baas.unprotectedTables: \\\"serve\\\".\"\n );\n } else {\n logger.warn(\n `🔒 [rls] Not serving ${unprotected.length} table(s) — row-level security is disabled, so they have no ` +\n `authorization model: ${unprotected.join(\", \")}\\n` +\n unprotected.map((t) => ` ALTER TABLE \"${pgSchemaName}\".\"${t}\" ENABLE ROW LEVEL SECURITY; -- then add a policy`).join(\"\\n\") +\n \"\\n To serve them without RLS anyway, pass baas: { unprotectedTables: \\\"serve\\\" } to the server \" +\n \"entry: an option `rebase eject` gives you, which the managed runtime does not read from config/ or \" +\n \"the environment.\"\n );\n for (const t of unprotected) schema.tablesMap.delete(t);\n skippedUnprotected = unprotected.map((table) => ({ schema: pgSchemaName, table }));\n }\n }\n if (policyless.length > 0) {\n // Legal, and silently returns nothing — worth saying out loud,\n // since an empty table reads exactly like one with no rows.\n logger.warn(\n `🔒 [rls] ${policyless.length} table(s) have RLS enabled but no policies, so they will return no rows: ${policyless.join(\", \")}`\n );\n }\n\n introspectedCollections = buildCollectionsFromSchema(schema, pgSchemaName);\n // Tables keyed the way the collections above name their fields,\n // and relations derived from the foreign keys — the only\n // evidence a database with no config offers. Without the\n // relations, drizzle's relational path can't resolve the ones\n // those collections advertise.\n ({ tables: introspectedTables, relations: introspectedRelations } =\n buildBaasSchema(schema.tablesMap, pgSchemaName, introspectedCollections));\n logger.info(\n `🔍 [PostgresRegistry] BaaS mode: derived ${introspectedCollections.length} collections from schema \"${pgSchemaName}\" [${introspectedCollections.map(c => c.slug).join(\", \")}]`\n );\n }\n\n // ── Declared collections: read the tables back from the database ──\n //\n // Every source, the default included. The bundle's generated Drizzle\n // module used to be authoritative here for the default source, and a\n // file one commit behind the database changed what the server did:\n // a renamed foreign-key column 500'd every request in production, a\n // property boot-ensure had already created answered 400\n // VALIDATION_UNKNOWN_FIELDS on first save, and relations the module\n // lacked went silently missing from list reads. Boot-ensure runs\n // before this, so `information_schema` is the current, complete\n // description of what is actually there — and it cannot drift from\n // itself. The module is now read only to tell the developer their\n // file is stale (`warnOnGeneratedSchemaDrift`, below).\n //\n // Restricted to the collections that are this source's, because the\n // driver is handed every collection the project declares and must\n // not go looking for the default's tables in the analytics database.\n // Without that filter the registry held no table for `events`, and\n // every routed write failed with \"table not found\" on a database\n // that had the table.\n let ownTables: Record<string, PgTable> | undefined;\n let ownRelations: Record<string, Relations> | undefined;\n let ownCatalogue: Map<string, TableMeta> | undefined;\n let ownCollections: CollectionConfig[] = [];\n const sourceKey = config.dataSourceKey ?? DEFAULT_DATA_SOURCE_KEY;\n if (!introspectedCollections && collections && collections.length > 0) {\n ownCollections = collections.filter(c =>\n c.dataSource === sourceKey || (!c.dataSource && sourceKey === DEFAULT_DATA_SOURCE_KEY)\n );\n if (ownCollections.length > 0) {\n // Reading the catalogue is the first query this driver\n // issues, so it is the one that meets an unreachable\n // database — and the diagnosis that names the host, the\n // port and `docker compose up -d db` lives on the `SELECT\n // 1` further down, which would never be reached. Asked\n // here, in the same words, so a stopped database still says\n // so instead of failing inside the introspector.\n try {\n await rawClient.query(\"SELECT 1\");\n } catch (err) {\n const fatal = diagnoseConnectFailure(err, pgConfig.connectionString);\n if (fatal) throw fatal;\n }\n const catalogue = await readCatalogueSchema({\n client: rawClient,\n collections: ownCollections,\n defaultSchema: pgConfig.introspectionSchema ?? \"public\"\n });\n ownTables = catalogue.tables;\n ownRelations = catalogue.relations;\n ownCatalogue = catalogue.meta;\n if (catalogue.missing.length > 0) {\n logger.warn(\n `[PostgresRegistry] Data source \"${sourceKey}\" has no table yet for: ${catalogue.missing.join(\", \")}. ` +\n \"Their collections will answer \\\"table not found\\\" until the schema is provisioned.\"\n );\n }\n }\n }\n\n const activeCollections = introspectedCollections ?? collections;\n const schemaTables = introspectedTables ?? ownTables;\n const schemaRelations = introspectedRelations ?? ownRelations;\n\n // The one remaining reader of the generated module: a warning per\n // difference, and no effect on anything served. Wrapped because a\n // diagnostic must never be the reason a server does not start.\n if (ownCatalogue) {\n try {\n warnOnGeneratedSchemaDrift(diffGeneratedSchemaAgainstCatalogue({\n generated: pgConfig.schema?.tables,\n catalogue: ownCatalogue,\n collections: ownCollections\n }));\n } catch (err) {\n logger.debug(\"[schema] Could not compare schema.generated.ts against the database\", { error: err });\n }\n }\n\n // Create a fresh registry for this driver. Registration order is\n // load-bearing, so it lives in one place — see `buildCollectionRegistry`.\n //\n // No enums: a catalogue-built enum column is `text`, which is what a\n // generated `pgEnum` column served anyway (drizzle validates no\n // labels; Postgres does), and nothing reads the registry's enum map.\n const registry = buildCollectionRegistry({\n collections: activeCollections,\n tables: schemaTables,\n relations: schemaRelations\n });\n\n // Patch Drizzle's PgArray columns to handle NULL values safely.\n // Drizzle's mapFromDriverValue crashes with \"value.map is not a function\"\n // when a native array column (text[], integer[], etc.) contains NULL.\n if (schemaTables) {\n patchPgArrayNullSafety(schemaTables as Record<string, unknown>);\n // A `number` property is a `numeric` column unless it declared\n // itself integral, and Postgres sends `numeric` as text. Cast it\n // back here, where every read path goes through.\n patchPgNumericToNumber(schemaTables as Record<string, unknown>);\n }\n\n // Build schema-aware Drizzle connection\n const mergedSchema: Record<string, unknown> = {\n ...schemaTables,\n ...(schemaRelations || {})\n };\n const { drizzle: createDrizzle } = await import(\"drizzle-orm/node-postgres\");\n const schemaAwareDb = createDrizzle(rawClient, { schema: mergedSchema });\n\n // Verify connection — fail fast if the database is unreachable\n try {\n await schemaAwareDb.execute(sql`SELECT 1`);\n } catch (err: unknown) {\n const fatal = diagnoseConnectFailure(err, pgConfig.connectionString);\n if (fatal) throw fatal;\n logger.warn(\"⚠️ Continuing without initial database verification. Drizzle/PG will attempt to connect on subsequent queries.\");\n }\n\n // ── Which collections are THIS database's? ────────────────────────\n //\n // The driver is handed every collection the project declares —\n // relations resolve across sources, and the API routes by slug — but\n // three passes below speak about this database and only this one:\n // the change-capture attach, the drift check and the RLS/grants\n // pass. Reading the registry unfiltered made a second source\n // (`database(\"analytics\")`) report the default's tables as SCHEMA\n // DRIFT, try to attach CDC triggers to tables it does not hold, and\n // validate the default's policies against its own catalogue. Same\n // rule as the table read-back above.\n //\n // In BaaS mode the registry holds what THIS database was introspected\n // for, so every registered collection is already this source's.\n const introspectedHere = Boolean(introspectedCollections);\n const collectionsOnThisSource = (): CollectionConfig[] => {\n const all = registry.getCollections() as CollectionConfig[];\n if (introspectedHere) return all;\n return all.filter(c => c.dataSource === sourceKey || (!c.dataSource && sourceKey === DEFAULT_DATA_SOURCE_KEY));\n };\n\n // Create services\n const realtimeService = new RealtimeService(schemaAwareDb, registry);\n // The per-socket subscription ceiling. Checked here, before anything\n // else boots: a ceiling someone set and mistyped must not quietly\n // become another number.\n if (pgConfig.realtime?.maxSubscriptionsPerSocket !== undefined) {\n realtimeService.maxSubscriptionsPerSocket = parseMaxSubscriptionsPerSocket(\n pgConfig.realtime.maxSubscriptionsPerSocket, \"realtime.maxSubscriptionsPerSocket\"\n );\n }\n // How long a change may wait for its refetch, and how often a big\n // list is recounted. Unset, both are what they always were.\n const tuning = (option: number | undefined, optionName: string, unit: string): number | undefined =>\n option !== undefined ? parseRealtimeTuning(option, optionName, unit) : undefined;\n const refetchMaxWaitMs = tuning(pgConfig.realtime?.refetchMaxWaitMs, \"realtime.refetchMaxWaitMs\", \"milliseconds\");\n if (refetchMaxWaitMs !== undefined) realtimeService.refetchMaxWaitMs = refetchMaxWaitMs;\n realtimeService.recountIntervalMs = tuning(pgConfig.realtime?.recountIntervalMs, \"realtime.recountIntervalMs\", \"milliseconds\");\n const recountMinRows = tuning(pgConfig.realtime?.recountMinRows, \"realtime.recountMinRows\", \"rows\");\n if (recountMinRows !== undefined) realtimeService.recountMinRows = recountMinRows;\n\n // This source's own replica and direct URL, never the default's. No\n // environment here: the boot read it into `pgConfig` already, for the\n // default source only (see postgresDriverConfigFromEnv).\n const { readUrl, directUrl } = resolveSourceUrls(pgConfig, sourceKey, {});\n\n // Initialize read replica connection if configured\n let readDb: import(\"drizzle-orm/node-postgres\").NodePgDatabase<any> | undefined;\n if (readUrl && readUrl !== pgConfig.connectionString) {\n try {\n const { createReadReplicaConnection } = await import(\"./connection\");\n const readResources = createReadReplicaConnection(readUrl, mergedSchema);\n readDb = readResources.db;\n logger.info(\"📖 [PostgresBootstrapper] Read replica connection established\");\n } catch (err) {\n logger.warn(\"⚠️ Could not connect to read replica, falling back to primary for all queries\", { error: err });\n }\n }\n const poolManager = pgConfig.adminConnectionString\n ? new DatabasePoolManager(pgConfig.adminConnectionString)\n : undefined;\n const driver = new PostgresBackendDriver(schemaAwareDb, realtimeService, registry, undefined, poolManager);\n driver.listenConnectionString = directUrl;\n realtimeService.setDataDriver(driver);\n\n // ── RLS enforcement (user context) ───────────────────────────────\n // Authenticated requests are authorized entirely by RLS policies,\n // but a privileged connection (superuser / BYPASSRLS / table owner)\n // bypasses RLS. Detect the posture and, when privileged, provision\n // the restricted `rebase_user` role and route authenticated\n // requests (reads AND writes) through it. The server context (base\n // driver / auth flows / `rebase.sql`) stays on the owner connection\n // and bypasses; `rebase.dataAsAdmin` does NOT — it is scoped with\n // `withAuth({ uid: \"service\", roles: [\"admin\"] })` at boot, so it\n // runs as `rebase_user` and its policies are evaluated.\n // Default-on: a privileged connection that cannot be\n // isolated fails the boot rather than serving unenforced requests.\n let rlsEnforcement: string | undefined;\n {\n const runSql: RawSqlRunner = async (text) => {\n const res = await schemaAwareDb.execute(sql.raw(text));\n return (res.rows ?? []) as Record<string, unknown>[];\n };\n // Said before anything else touches the schema: if the role and\n // a schema share a name, unqualified SQL from any tool that does\n // not pin `search_path` has been landing in the wrong place, and\n // that is worth knowing before reading the drift report below.\n await warnOnRoleSchemaCollision(runSql);\n\n const collectionSchemas = registry.getCollections()\n .map((c) => (c as { schema?: string }).schema)\n .filter((s): s is string => typeof s === \"string\");\n // `auth` is deliberately absent: the RLS helpers moved into\n // `rebase`, and granting USAGE on a schema Rebase does not\n // own would, on a Supabase database, hand the end-user role\n // access to theirs.\n const managedSchemas = [\"public\", \"rebase\", ...collectionSchemas];\n\n const posture = await detectConnectionPosture(runSql, managedSchemas);\n if (posture.privileged) {\n // Two different failures hide behind one call here, and they\n // deserve different answers. A connection that bypasses RLS\n // *right now* and cannot be switched must not serve: that is\n // the original contract and it stays fatal. A connection that\n // merely *could become* the owner — it owns nothing yet, but\n // may create tables in a schema the server provisions — is\n // constrained by RLS today, so refusing to boot over it would\n // break deployments that have been correct all along\n // (Postgres before 15 grants CREATE on public to PUBLIC, so\n // that describes a great many ordinary app roles).\n //\n // For the second case the answer is: try, warn, and ask again\n // once provisioning has run. See finalizeSecurityPosture.\n const bypassesNow = posture.superuser || posture.bypassRLS || posture.ownsTables;\n let switched = true;\n if (!provision) {\n // Nothing is created here, so the switch is to a role an\n // earlier boot made. A connection that bypasses RLS switches\n // whether or not it exists: a user's request then fails on\n // the missing role, where not switching would run it with\n // no policy applied. One that does not bypass yet is filtered\n // natively and switches only when there is a role to use.\n const roleExists = (await runSql(`SELECT 1 FROM pg_roles WHERE rolname = '${REBASE_USER_ROLE}'`)).length > 0;\n switched = roleExists || bypassesNow;\n if (!roleExists && bypassesNow) {\n logger.warn(\n `⚠️ RLS enforcement: connection role \"${posture.role}\" bypasses row-level security and the ` +\n `restricted role \"${REBASE_USER_ROLE}\" does not exist yet — this driver was opened without ` +\n \"provisioning, so it was not created. Requests made as a user will fail until a server \" +\n \"boot (or `rebase db push`) creates it; requests as the connection itself are unaffected.\"\n );\n }\n } else {\n try {\n await ensureAppRole(runSql, managedSchemas);\n } catch (err) {\n if (bypassesNow) throw err;\n switched = false;\n logger.warn(\n `⚠️ RLS enforcement: connection role \"${posture.role}\" owns no tables yet and is ` +\n `subject to RLS, but it may create tables in ${managedSchemas.join(\", \")} — and a ` +\n \"table's owner bypasses every non-FORCE policy on it. The restricted role could not \" +\n `be provisioned: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n if (switched) {\n driver.rlsUserRole = REBASE_USER_ROLE;\n realtimeService.rlsUserRole = REBASE_USER_ROLE;\n const why = posture.superuser\n ? \"superuser\"\n : posture.bypassRLS\n ? \"BYPASSRLS\"\n : posture.ownsTables\n ? \"table owner\"\n : \"may create (and would then own) tables in the schemas it serves\";\n rlsEnforcement = `🔐 RLS enforcement active: authenticated requests run as \"${REBASE_USER_ROLE}\" (connection \"${posture.role}\" bypasses RLS: ${why})`;\n }\n\n if (posture.superuser || posture.bypassRLS) {\n const message =\n `The database connection runs as ${posture.superuser ? \"a superuser\" : \"a BYPASSRLS role\"} (\"${posture.role}\"). ` +\n `User requests are isolated via SET LOCAL ROLE, but connect as a non-superuser ` +\n `table-owner role in production so the server/owner context is least-privilege.`;\n if (isScaffoldedLocalDatabase(pgConfig.connectionString)) {\n logger.debug(`🔐 ${message}`);\n } else {\n logger.warn(`⚠️ ${message}`);\n }\n }\n } else {\n rlsEnforcement = `🔐 RLS enforcement: connection role \"${posture.role}\" is subject to RLS natively; no role switch needed.`;\n }\n\n // Independent of posture: a policy targeting a role the request\n // never runs as filters every row, so the collection reads as\n // empty rather than erroring. Applies to both branches — the\n // role that matters is whichever one requests actually use.\n await validatePolicyPgRoles(\n runSql,\n collectionsOnThisSource() as never,\n driver.rlsUserRole ?? posture.role\n );\n\n // The same habit one surface over, and the dangerous direction:\n // a rule that reads as \"signed in only\" but is true for every\n // caller grants the data away rather than hiding it.\n warnOnAnonymousGrants(collectionsOnThisSource() as never);\n\n // Raw policy SQL written against the pre-1.0 helper schema. It\n // is rewritten on compile, so this is the only place the project\n // is ever told the spelling moved.\n warnOnLegacyRlsFunctions(collectionsOnThisSource() as never);\n }\n\n // Ensure branch metadata table exists when branching is available\n if (provision && driver.branchService) {\n try {\n await driver.branchService.ensureBranchMetadataTable();\n } catch (err) {\n logger.warn(\"⚠️ Could not initialize branch metadata table\", { error: err });\n }\n }\n\n // ── Channel history ──────────────────────────────────────────────\n // Opt-in per channel pattern. With no rules this creates no tables\n // and leaves broadcast on its original fire-and-forget path, so\n // presence-only apps pay nothing for it.\n try {\n if (!realtimeOff) {\n await realtimeService.configureChannelHistory(\n pgConfig.realtime?.channels,\n { provision: realtimeProvisions }\n );\n }\n } catch (err) {\n logger.warn(\"⚠️ Could not initialize channel history tables — retained channels will not replay\", { error: err });\n }\n\n // ── Realtime change source ───────────────────────────────────────\n // A direct URL bypasses PgBouncer, which does not keep a LISTEN.\n // `directUrl` (resolved above) bypasses PgBouncer for LISTEN/NOTIFY.\n\n // ── Cross-instance channel bus ───────────────────────────────────\n // Entity changes already span instances (CDC / LISTEN below).\n // Channel broadcast and presence did not — they lived in per-process\n // maps, so behind two replicas the clients of one were invisible to\n // the other. Opt-in, and a no-op when left at \"memory\".\n try {\n const busSetting = pgConfig.realtime?.bus ?? { type: \"memory\" };\n // A supplied instance is always installed; a named built-in only\n // when it is not the in-process default, so the common case\n // touches none of this machinery.\n const wantsBus = !realtimeOff && (isChannelBusInstance(busSetting) || busSetting.type !== \"memory\");\n if (wantsBus) {\n await realtimeService.configureChannelBus(\n createChannelBus(busSetting, {\n db: schemaAwareDb as NodePgDatabase<Record<string, unknown>>,\n directUrl\n })\n );\n }\n } catch (err) {\n logger.warn(\"⚠️ [ChannelBus] Could not configure the channel bus — channel broadcast and presence stay per-instance\", { error: err });\n }\n\n // Database-level Change Data Capture. When active, realtime events are\n // emitted for EVERY committed write — including ones that bypass the\n // Rebase API (psql, another service's cron, raw SQL, the Studio SQL\n // editor) — matching Supabase Realtime's WAL-tailing model. CDC also\n // becomes the cross-instance channel, so the legacy per-mutation\n // LISTEN/NOTIFY is not started alongside it.\n // `realtime.cdc` (`REALTIME_CDC` at boot):\n // auto → default: enable where the connection supports it;\n // silently fall back to app-level otherwise\n // trigger → force trigger-based capture (warns if it can't)\n // wal → prefer WAL logical replication (degrades to trigger)\n // off → app-level realtime only\n const cdcMode = parseRealtimeCdcMode(pgConfig.realtime?.cdc ?? \"auto\", \"realtime.cdc\");\n\n // `auto` tries CDC but treats \"can't\" as a normal outcome (info log);\n // explicit trigger/wal was asked for, so a failure is worth a warning.\n // A process that consumes nothing needs no capture *started*, and a\n // process that does not own the schema installs no triggers. They\n // come apart: the `api` in a split with an external migration Job\n // subscribes without provisioning, and both answers are correct.\n const wantsCdc = cdcMode !== \"off\" && (realtimeSubscribes || realtimeProvisions);\n const explicitCdc = cdcMode === \"trigger\" || cdcMode === \"wal\";\n let cdcEnabled = false;\n let provisionCdcForTables: PostgresDriverInternals[\"provisionCdcForTables\"];\n const cdcRunSql: RawSqlRunner = async (text) => {\n const res = await schemaAwareDb.execute(sql.raw(text));\n return (res.rows ?? []) as Record<string, unknown>[];\n };\n /** Whether this boot left the trigger function at the current body. */\n let cdcFunctionCurrent = false;\n\n if (wantsCdc && !directUrl) {\n const reason = \"no direct database connection is available for the realtime LISTEN client \" +\n `(set DATABASE_DIRECT_URL${resourceEnvSuffix(sourceKey)})`;\n if (explicitCdc) logger.warn(`⚠️ [CDC] REALTIME_CDC=${cdcMode} but ${reason} — using app-level realtime.`);\n else logger.info(`ℹ️ [CDC] Using app-level realtime — ${reason}.`);\n } else if (wantsCdc && directUrl) {\n if (cdcMode === \"wal\") {\n // Native WAL logical-replication streaming requires wal_level=logical,\n // a replication-privileged role and a replication slot, none of which\n // are bundled with this adapter yet. Degrade to trigger-based capture,\n // which provides equivalent database-level coverage.\n logger.warn(\n \"⚠️ [CDC] REALTIME_CDC=wal: native WAL streaming is not bundled in this build; \" +\n \"using trigger-based change capture instead (equivalent database-level coverage).\"\n );\n }\n try {\n // Each table is attached with the columns its rows are\n // addressed by, and the payload carries those and nothing\n // else — any login can LISTEN. See `buildCdcFunctionSql`.\n const cdcTables: CdcTableRef[] = collectionsOnThisSource()\n .map((c) => ({\n schema: (c as { schema?: string }).schema ?? \"public\",\n table: getCollectionTableName(c),\n identityColumns: collectionKeyColumns(c, registry).map((k) => k.columnName)\n }))\n .filter((t) => Boolean(t.table) && registry.hasTableForCollection(t.table));\n // Junction tables back no collection, so the list above misses\n // them — and a link or unlink is a write nobody would hear\n // about. Their rows are the contents of a child list, which is\n // as much a change as a write to the rows themselves.\n // Filtered by the parent's source for the same reason as the\n // list above: `posts_tags` lives in the database `posts`\n // lives in, and asking a second database to instrument it\n // produced one \"Could not attach change-capture trigger\"\n // warning per junction on every boot.\n const ownSlugs = new Set(collectionsOnThisSource().map(c => c.slug));\n for (const link of collectJunctionLinks(registry)) {\n if (!ownSlugs.has(link.parentCollection.slug)) continue;\n // A junction row is addressed by the two ids that name\n // the child list it belongs to, whatever its own key is.\n cdcTables.push({\n schema: link.schema,\n table: link.table,\n identityColumns: [link.sourceColumn, link.targetColumn]\n });\n }\n // Provisioning throws only when the connection can't create the\n // trigger function (insufficient privilege); enableCdc throws when\n // the LISTEN connection can't be established. Either → fall back.\n if (realtimeProvisions) {\n await provisionTriggerCdc(cdcRunSql, cdcTables);\n cdcFunctionCurrent = true;\n }\n if (realtimeSubscribes) await realtimeService.enableCdc(directUrl);\n cdcEnabled = true;\n // Boot steps that create their own tables (auth) run after\n // this one and use it to instrument what they just created.\n // Left undefined where this process installs nothing, so a\n // later boot step cannot re-enter the DDL path by the side\n // door — the callers already treat it as optional, because a\n // driver without CDC never sets it either.\n if (realtimeProvisions) {\n provisionCdcForTables = async (tables) => {\n await provisionTriggerCdc(cdcRunSql, tables);\n };\n }\n // Say which half ran. \"All writes now emit realtime events\"\n // is a claim about the database and stays true for a process\n // that only installed the triggers; what changes is whether\n // *this* process is listening, and an operator reading one\n // pod's log should not have to infer that from its role.\n logger.info(\n `📡 [CDC] Realtime source = database-level change capture (mode: ${cdcMode === \"wal\" ? \"wal→trigger\" : \"trigger\"}). ` +\n `All writes now emit realtime events regardless of origin.` +\n (realtimeSubscribes ? \"\" : \" This process installs the capture but does not consume it.\")\n );\n } catch (err) {\n if (explicitCdc) {\n // Handled — realtime carries on, app-level — so one line with\n // the reason, not a stack.\n logger.warn(\n \"⚠️ [CDC] Could not enable database-level change capture — falling back to app-level realtime.\",\n { detail: err instanceof Error ? err.message : String(err) }\n );\n } else {\n logger.info(\n \"ℹ️ [CDC] Database-level change capture unavailable (likely insufficient privileges to create triggers, \" +\n \"or the LISTEN connection was refused) — using app-level realtime. Set REALTIME_CDC=off to silence this.\",\n { detail: err instanceof Error ? err.message : String(err) }\n );\n }\n }\n }\n\n // Triggers an earlier boot attached keep firing whether or not this\n // one provisions capture. With capture switched off they are taken\n // off the tables: each write ran one and announced itself on a\n // channel nothing listens to. Otherwise (capture wanted, not\n // installed this boot) a function from a version that sent whole\n // rows is replaced — any login can LISTEN to what it sends. Nothing\n // is installed here either way.\n if (realtimeProvisions && !cdcFunctionCurrent) {\n const capture = cdcMode === \"off\" ? \"off\" : \"not-provisioned\";\n try {\n const settled = await settleInstalledCapture(cdcRunSql, { capture });\n if (\"removed\" in settled && settled.removed > 0) {\n logger.info(\n `ℹ️ [CDC] REALTIME_CDC=off: removed the change-capture triggers an earlier boot attached ` +\n `(${settled.removed}). Realtime is app-level; unset REALTIME_CDC to capture changes again.`\n );\n }\n } catch (err) {\n logger.warn(\n capture === \"off\"\n ? \"⚠️ [CDC] REALTIME_CDC=off, but the change-capture triggers an earlier boot attached could not be \" +\n \"removed; they keep firing on every write. Remove them with \" +\n \"`DROP FUNCTION rebase.rebase_cdc_notify() CASCADE; DROP FUNCTION rebase.rebase_cdc_notify_statement() CASCADE`.\"\n : \"⚠️ [CDC] A change-capture trigger function from an earlier boot is installed and could not be \" +\n \"brought up to date. Older versions of it send every changed row on a channel any database \" +\n \"login can LISTEN to; if capture is not wanted, set REALTIME_CDC=off, or remove it with \" +\n \"`DROP FUNCTION rebase.rebase_cdc_notify() CASCADE`.\",\n { detail: err instanceof Error ? err.message : String(err) }\n );\n }\n }\n\n // Legacy cross-instance realtime (app-level). Skipped when CDC is\n // active because CDC already spans instances.\n if (!cdcEnabled && directUrl && realtimeSubscribes) {\n try {\n await realtimeService.startListening(directUrl);\n } catch (err) {\n logger.warn(\"⚠️ Cross-instance realtime could not be started\", { detail: err instanceof Error ? err.message : String(err) });\n }\n } else if (!cdcEnabled && !realtimeSubscribes && !realtimeOff) {\n // A worker or a functions process, with no capture to publish\n // its writes for it (REALTIME_CDC=off, or `auto` fell back).\n // It has nobody to deliver to, but the `api` does: announce\n // each write the way an `all` process would, minus the\n // LISTEN. Where the `api` turns out to run capture after all,\n // nothing listens on this channel and the notification is\n // dropped by the database.\n realtimeService.startPublishing();\n }\n\n // ── Startup Schema Validation ────────────────────────────────────\n // One-directional: only checks collections → DB (extra DB tables\n // that aren't mapped to collections are perfectly fine).\n try {\n const registeredCollections = collectionsOnThisSource();\n if (registeredCollections.length > 0) {\n // Deliberately unfiltered by schema: a table that is missing\n // from where the collection says it lives is very often\n // sitting in another schema entirely (see the misplaced-table\n // report below), and knowing *which* is the whole answer.\n const result = await schemaAwareDb.execute(sql.raw(`\n SELECT table_name, table_schema\n FROM information_schema.tables\n WHERE table_schema NOT IN ('pg_catalog', 'information_schema')\n AND table_type = 'BASE TABLE'\n `));\n const tablesByName = new Map<string, string[]>();\n for (const row of result.rows as Array<{ table_name: string; table_schema: string }>) {\n const schemas = tablesByName.get(row.table_name) ?? [];\n schemas.push(row.table_schema);\n tablesByName.set(row.table_name, schemas);\n }\n const dbTables = new Set(\n (result.rows as Array<{ table_name: string; table_schema: string }>).map(r =>\n r.table_schema === \"public\" ? r.table_name : `${r.table_schema}.${r.table_name}`\n )\n );\n const missing: Array<{ slug: string; table: string; foundIn: string[] }> = [];\n for (const col of registeredCollections) {\n // Auth owns its table and creates it later in this same\n // boot (initializeAuth → ensureAuthTablesExist), so it is\n // legitimately absent right now. Reporting it as drift\n // tells the user to `db:push` a table that is about to\n // exist — and on an introspected database, one that the\n // database was never supposed to hold.\n if ((col as { auth?: { enabled?: boolean } }).auth?.enabled) continue;\n\n const schemaName = \"schema\" in col && col.schema ? col.schema : \"public\";\n const checkName = resolveDriftCheckName(col, registry.getTableNames());\n const fullCheckName = schemaName === \"public\" ? checkName : `${schemaName}.${checkName}`;\n if (!dbTables.has(fullCheckName)) {\n // Report what was actually looked up: an unqualified\n // \"users\" sends people hunting for public.users.\n missing.push({ slug: col.slug,\ntable: fullCheckName,\nfoundIn: (tablesByName.get(checkName) ?? []).filter(s => s !== schemaName) });\n }\n }\n if (missing.length > 0) {\n const lines = missing.map(\n m => ` • collection \"${m.slug}\" → table \"${m.table}\"` +\n (m.foundIn.length > 0\n ? ` — but a table of that name exists in ${m.foundIn.map(s => `\"${s}\"`).join(\", \")}`\n : \"\")\n );\n // A table that exists under the same name in another\n // schema is not ordinary drift: it is almost always this\n // framework's own `search_path` hazard. Postgres resolves\n // unqualified SQL through `\"$user\", public`, and this\n // project creates a schema named `rebase` while every\n // template names the database role `rebase` too — so an\n // unqualified CREATE TABLE (a hand-written migration, the\n // SQL editor, drizzle-kit) lands in `rebase`, and the\n // runtime, which now pins `search_path=public`, cannot see\n // it. Say so, because \"missing table\" sends people to\n // re-run a push that will create a *second* copy.\n const misplaced = missing.filter(m => m.foundIn.length > 0);\n const misplacedHelp = misplaced.length === 0 ? [] : [\n \" Those tables exist — in the wrong schema. Unqualified SQL run by a\",\n \" role whose name matches a schema (`rebase` is both, by default)\",\n \" resolves through search_path's \\\"$user\\\" and creates there. Move them:\",\n ...misplaced.map(m =>\n ` ALTER TABLE \"${m.foundIn[0]}\".\"${m.table.split(\".\").pop()}\" SET SCHEMA \"${m.table.includes(\".\") ? m.table.split(\".\")[0] : \"public\"}\";`\n ),\n \" and qualify the SQL that created them, or pin search_path in DATABASE_URL\",\n \" (`?options=-c%20search_path%3Dpublic`).\",\n \"\"\n ];\n // What to tell the operator depends entirely on whether a\n // create step ran in this process, and the caller is the\n // only thing that knows. This warning used to assert that\n // it had (\"this runtime applies the collection schema at\n // boot unless REBASE_MIGRATE_ON_BOOT=none\") and send\n // people to that variable and to driver-version skew. For\n // an app whose boot path contained no provisioning step,\n // both were dead ends: nothing read that variable, and the\n // driver was current. Say which case this is instead of\n // guessing, and say nothing when the caller is too old to\n // tell us.\n const cause = describeSchemaDriftCause(schemaProvisioning);\n logger.warn([\n \"\",\n \"⚠️ SCHEMA DRIFT — the database is missing tables this backend serves:\",\n ...lines,\n \"\",\n ...misplacedHelp,\n ...cause,\n \"\",\n \" To apply this project's schema:\",\n \" • Managed cloud: the runtime creates tables and RLS at boot. `rebase db\",\n \" push` cannot reach a tenant's in-cluster database — redeploy instead.\",\n \" • Self-host: run `rebase db push` (dev) or `rebase db migrate` (prod)\",\n \" against DATABASE_URL.\",\n \"\"\n ].join(\"\\n\"));\n }\n }\n } catch (err) {\n logger.warn(\"⚠️ Startup schema validation could not run\", {\n error: err instanceof Error ? err.message : String(err)\n });\n }\n\n const internals: PostgresDriverInternals = {\n db: schemaAwareDb,\n readDb,\n registry,\n realtimeService,\n driver,\n poolManager,\n provisioned: provision,\n rlsEnforcement,\n provisionCdcForTables\n };\n\n return {\n driver,\n realtimeProvider: realtimeService,\n collectionRegistry: registry,\n // Only set in baas mode — tells the server which collections the\n // database turned out to have.\n collections: introspectedCollections,\n ...(skippedUnprotected && { unprotectedTables: skippedUnprotected }),\n internals\n };\n}\n\nexport function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): BackendBootstrapper {\n /**\n * `pgConfig` with the environment read into it — this is the boot, and\n * the boot is where `DATABASE_READ_URL`, `DATABASE_DIRECT_URL` and the\n * `REALTIME_*` variables are honoured. Read once, when the first hook\n * that needs it runs, so a mistyped value fails that hook and a warning\n * about one is said once.\n */\n let resolvedConfig: PostgresDriverConfig | undefined;\n // A bootstrapper opens one source, so the first caller's key is the\n // key: the bare URL variables are read for the default source only.\n const effectiveConfig = (sourceKey?: string): PostgresDriverConfig =>\n resolvedConfig ??= postgresDriverConfigFromEnv(pgConfig, process.env, sourceKey);\n\n // Applied at construction rather than threaded through every read: the\n // condition builder's static methods are reached from call sites that\n // carry no config. See `UnknownFilterFieldsMode`.\n if (pgConfig.unknownFilterFields) {\n configureUnknownFilterFields(pgConfig.unknownFilterFields);\n }\n\n /**\n * The handle the schema/policy hooks issue their DDL through.\n *\n * Both hooks run BEFORE `initializeDriver`, so `driverResult` is a stand-in\n * the caller may not have: the bundle path can synthesize one from the\n * connection its coordinator opened, but an application that built this\n * adapter itself never handed the framework a connection — it handed it to\n * *us*, as `pgConfig.connection`. Falling back to that is what lets a\n * self-built adapter provision at all; requiring the argument is what left\n * those apps with no tables and a 500 on every data route.\n *\n * Either handle is equivalent here. The driver's `schemaAwareDb` differs\n * only by the drizzle schema object registered on it — relevant to the query\n * builder, not to `execute(sql.raw(...))` — and every statement these hooks\n * emit is schema-qualified DDL, so neither depends on `search_path`.\n */\n /**\n * The drizzle handle itself, for the statements that want parameters.\n *\n * `provisioningQueryable` below hands back a `query(text)` shim because the\n * DDL it serves is built as text. The schema stamp writes a *value*, so it\n * wants the parameterised form — building that string by hand would be one\n * more place a quoted literal has to be got right for no benefit.\n */\n const provisioningDb = (driverResult?: InitializedDriver) => {\n const internals = driverResult?.internals as PostgresDriverInternals | undefined;\n return internals?.db ?? (pgConfig.connection as PostgresDriverInternals[\"db\"] | undefined);\n };\n\n const requireProvisioningDb = (driverResult?: InitializedDriver) => {\n const db = provisioningDb(driverResult);\n if (!db) {\n throw new Error(\n \"Cannot provision the collection schema: this Postgres adapter was created without a \" +\n \"`connection`, and no initialized driver was supplied to fall back on. Pass `connection` \" +\n \"to `createPostgresAdapter` (see `createPostgresDatabaseConnection`).\"\n );\n }\n return db;\n };\n\n const provisioningQueryable = (driverResult?: InitializedDriver) => {\n const db = requireProvisioningDb(driverResult);\n return {\n async query<T>(text: string): Promise<{ rows: T[] }> {\n return { rows: sqlRows<T>(await db.execute(sql.raw(text))) };\n }\n };\n };\n\n /**\n * A connection of its own for the index builds on live tables — see\n * `poolIndexBuildSession` for why it cannot be the pool's next free one. A\n * handle with no pool behind it (PGlite, an application's own client)\n * gets the shared queryable, and the builder leaves session settings\n * alone on it.\n */\n const openIndexBuildSession = (driverResult?: InitializedDriver): OpenIndexBuildSession => async () => {\n const db = requireProvisioningDb(driverResult);\n const pool = \"$client\" in db ? db.$client : undefined;\n const { isPgPool, poolIndexBuildSession } = await import(\"./schema/deferred-index-build\");\n if (isPgPool(pool)) return poolIndexBuildSession(pool)();\n const queryable = provisioningQueryable(driverResult);\n return { dedicated: false, query: <T>(text: string) => queryable.query<T>(text), release: () => undefined };\n };\n\n /**\n * The indexes the last schema ensure left for later on live tables —\n * see `schema/deferred-index-build`. Started once the policies step is\n * done, the last DDL a boot runs, so the build never holds a table that\n * step needs; and by its own fallback timer if that step never runs here.\n */\n let pendingIndexBuild: ScheduledIndexBuild | undefined;\n\n /**\n * Create the `rebase` schema and the helper functions every generated\n * policy calls — `rebase.uid()`, `rebase.roles()` and their siblings — on\n * whichever database the queryable points at.\n *\n * Idempotent and cheap to repeat: `CREATE SCHEMA IF NOT EXISTS` plus a\n * handful of `CREATE OR REPLACE FUNCTION`.\n */\n const provisionRlsRuntime = async (driverResult?: InitializedDriver): Promise<void> => {\n const db = requireProvisioningDb(driverResult);\n const { RLS_BOOTSTRAP_STATEMENTS, rlsRuntimeIsCurrent } = await import(\"./schema/rls-bootstrap-sql\");\n // Nothing to replace: a `CREATE OR REPLACE` of an unchanged function\n // still rewrites it and invalidates every cached plan that calls it.\n const run = async (text: string) => sqlRows<Record<string, unknown>>(await db.execute(sql.raw(text)));\n if (await rlsRuntimeIsCurrent(run).catch(() => false)) return;\n // One statement per call — this handle speaks the extended query\n // protocol, which rejects multi-command strings. Advisory-locked\n // for the same reason the auth path is, and on the same key: two\n // instances booting against one fresh database must not race on\n // CREATE OR REPLACE.\n //\n // A transaction lock, inside the transaction the statements run in,\n // exactly as `auth/ensure-tables.ts` takes it. The handle is a pool,\n // and a session lock taken by one statement and released by another\n // could be released on a connection that never held it — behind a\n // transaction-mode pooler, a different server connection every time.\n // The unlock then answered `false`, the lock stayed on an idle pooled\n // connection, and the next boot waited on it until\n // `statement_timeout`. This one is released by the commit or the\n // rollback, on the connection that holds it.\n await db.transaction(async (tx) => {\n await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);\n for (const statement of RLS_BOOTSTRAP_STATEMENTS) {\n await tx.execute(sql.raw(statement));\n }\n });\n };\n\n /**\n * Tables the policies step could not lock, whose user-role privileges it\n * took back instead — `schema.table` → its parts.\n *\n * Kept because the boot grants again after that step: the final posture\n * check runs `ensureAppRole`, whose `GRANT … ON ALL TABLES IN SCHEMA`\n * covers these tables too. It handed every withdrawn privilege straight\n * back — RLS off, DML granted, every authenticated request reading every\n * row — while the log said the table was closed. So whatever grants\n * schema-wide after the policies step withholds these again.\n */\n const withheldTables = new Map<string, { schema: string; table: string }>();\n\n const withholdAgain = async (runSql: RawSqlRunner): Promise<void> => {\n for (const [qualified, { schema, table }] of withheldTables) {\n try {\n await runSql(revokeUserRoleSql(schema, table));\n } catch (err) {\n throw new Error(\n `Refusing to start: row-level security could not be enabled on \"${qualified}\", and the ` +\n `privileges the schema-wide grant just gave ${REBASE_USER_ROLE} on it could not be taken ` +\n \"back. The table would be served with no row filtering. Fix the database permissions \" +\n \"(the connection role must own the table, or be able to ALTER it) and boot again: \" +\n (err instanceof Error ? err.message : String(err))\n );\n }\n }\n };\n\n return {\n type: \"postgres\",\n\n async initializeDriver(config: unknown): Promise<InitializedDriver> {\n const init = config as DatabaseAdapterInitConfig;\n const opened = await openPostgresDriver(effectiveConfig(init.dataSourceKey), init);\n // The boot announces the connection's posture; `openRebase` does not.\n if (opened.internals.rlsEnforcement) logger.info(opened.internals.rlsEnforcement);\n return opened;\n },\n\n async initializeAuth(config: unknown, driverResult: InitializedDriver): Promise<BootstrappedAuth | undefined> {\n const authConfig = config as Record<string, unknown> | undefined;\n if (!authConfig) return undefined;\n\n const internals = driverResult.internals as PostgresDriverInternals;\n const db = internals.db;\n const registry = internals.registry;\n\n // Resolve the auth collection from the explicit config.\n // This replaces the old `registry.getTable(\"users\")` magic string lookup.\n const authCollection = authConfig.collection as CollectionConfig | undefined;\n\n // ensureAuthTablesExist works with the collection abstraction — no Drizzle leakage.\n // A driver opened without provisioning creates and migrates nothing:\n // the tables are another process's to make. The one read that\n // guards every login still runs — a database a newer runtime has\n // migrated is refused here, not at the first sign-in.\n if (internals.provisioned !== false) {\n await ensureAuthTablesExist(db, authCollection);\n } else {\n await assertAuthSchemaCompatible(db, resolveAuthSchema(authCollection));\n }\n\n // The driver bootstrapped before these tables existed, so CDC skipped\n // them. Instrument them now, or writes to the user table emit no\n // realtime events until the next restart.\n if (authCollection && internals.provisionCdcForTables) {\n const authSchema = \"schema\" in authCollection && typeof authCollection.schema === \"string\"\n ? authCollection.schema\n : \"rebase\";\n const authTable = \"table\" in authCollection && typeof authCollection.table === \"string\"\n ? authCollection.table\n : authCollection.slug;\n if (authTable) {\n try {\n await internals.provisionCdcForTables([{\n schema: authSchema,\n table: authTable,\n // The auth table above all: its rows carry password\n // hashes and verification tokens, and the payload\n // names the row by its key and nothing else.\n identityColumns: collectionKeyColumns(authCollection, registry).map((k) => k.columnName)\n }]);\n } catch (err) {\n logger.warn(\n `⚠️ [CDC] Could not attach change-capture to the auth table \"${authSchema}.${authTable}\" — ` +\n \"writes to it won't emit database-level events.\",\n { detail: err instanceof Error ? err.message : String(err) }\n );\n }\n }\n }\n\n let emailService: EmailService | undefined;\n if (authConfig.email) {\n emailService = createEmailService(authConfig.email as EmailConfig);\n }\n\n // Resolve the Drizzle table for the internal UserService/AuthRepository.\n // These are internal Postgres-specific services that need the Drizzle table reference.\n const tableName = authCollection\n ? (\"table\" in authCollection && typeof authCollection.table === \"string\"\n ? authCollection.table\n : authCollection.slug)\n : undefined;\n const usersTable = tableName\n ? registry.getTable(tableName) as RebasePgTable | undefined\n : undefined;\n\n let usersSchemaName = \"rebase\";\n if (authCollection && \"schema\" in authCollection && typeof authCollection.schema === \"string\") {\n usersSchemaName = authCollection.schema;\n }\n\n // Named field by field through `asRebasePgTable`, not asserted\n // wholesale. `createAuthSchema` returns ten tables and\n // `AuthSchemaTables` declares five, so the assertion also claimed\n // the other five were absent while handing them straight through —\n // and if one of the five were ever renamed there, nothing here would\n // have said so.\n const schema = createAuthSchema(usersSchemaName);\n const authTables: AuthSchemaTables = {\n users: asRebasePgTable(schema.users),\n refreshTokens: asRebasePgTable(schema.refreshTokens),\n passwordResetTokens: asRebasePgTable(schema.passwordResetTokens),\n appConfig: asRebasePgTable(schema.appConfig),\n userIdentities: asRebasePgTable(schema.userIdentities)\n };\n if (usersTable) {\n authTables.users = usersTable as RebasePgTable;\n }\n\n const userService = new UserService(db, authTables);\n const authRepository = new PostgresAuthRepository(db, authTables);\n\n return { userService,\nemailService,\nauthRepository,\n// Bound to the same schema `ensureAuthTablesExist` just migrated, so the\n// health endpoint reports on the tables auth actually reads.\nschemaHealthCheck: () => probeAuthSchema(db, resolveAuthSchema(authCollection)) };\n },\n\n async initializeHistory(config: HistoryConfig, driverResult: InitializedDriver): Promise<{ historyService: HistoryService } | undefined> {\n if (!config) return undefined;\n\n const internals = driverResult.internals as PostgresDriverInternals;\n const db = internals.db;\n\n // Without provisioning, history uses what an earlier boot left —\n // read, not made — and builds no index.\n if (internals.provisioned === false) {\n const retention = typeof config === \"object\" ? config.retention : undefined;\n const state = await readHistoryTableState(db);\n return {\n historyService: new HistoryService(db, retention ? { ttlDays: retention } : undefined, {\n inTransaction: state.inTransaction,\n entryFormat: state.entryFormat,\n unavailable: historyUnavailableReason(state, { requireTransaction: false })\n })\n };\n }\n\n const { BOOT_LOCK_TIMEOUT_MS } = await import(\"./schema/boot-ddl\");\n const historyTable = await ensureHistoryTableExists(db, { lockTimeoutMs: BOOT_LOCK_TIMEOUT_MS });\n\n // The index the hourly retention sweep reads, built like any\n // index a live table is missing: concurrently, after the boot —\n // or right here in a provisioning-only run, which exits when boot\n // returns. See `ensureHistoryIndexes`.\n const historyIndexes = await ensureHistoryIndexes(db, { lockTimeoutMs: BOOT_LOCK_TIMEOUT_MS });\n if (historyIndexes.length > 0) {\n const { buildDeferredIndexes, scheduleDeferredIndexBuild } = await import(\"./schema/deferred-index-build\");\n const indexOptions = { log: (message: string) => logger.info(`[history] ${message}`), lockTimeoutMs: BOOT_LOCK_TIMEOUT_MS };\n const provisionOnly = [\"1\", \"true\"].includes((process.env.REBASE_PROVISION_ONLY ?? \"\").trim().toLowerCase());\n if (provisionOnly) {\n await buildDeferredIndexes(openIndexBuildSession(driverResult), historyIndexes, indexOptions);\n } else {\n scheduleDeferredIndexBuild(openIndexBuildSession(driverResult), historyIndexes, {\n ...indexOptions,\n delayMs: DEFERRED_INDEX_BUILD_DELAY_MS\n }).start();\n }\n }\n\n const retention = typeof config === \"object\" ? config.retention : undefined;\n const historyService = new HistoryService(db, retention ? { ttlDays: retention } : undefined, historyTable);\n\n return { historyService };\n },\n\n async initializeRealtime(_config: unknown, driverResult: InitializedDriver): Promise<RealtimeProvider | undefined> {\n const internals = driverResult.internals as PostgresDriverInternals;\n return internals.realtimeService;\n },\n\n /**\n * The cheapest round trip there is, run before boot's first real query.\n *\n * Boot provisions the collection schema *before* `initializeDriver`, so\n * the connection diagnosis above — the box naming the host, the port and\n * `docker compose up -d db` — never ran for the one failure it was\n * written for. A developer whose database was not running saw Drizzle's\n * `Failed query: [redacted]` and a stack through drizzle internals.\n *\n * Same diagnosis, one step earlier. Anything short of \"the database\n * answered\" is fatal here: provisioning is the next thing to happen and\n * it needs the connection, so continuing only moves the failure to a\n * frame with less to say about it.\n */\n async verifyConnection(driverResult?: InitializedDriver): Promise<void> {\n const db = provisioningDb(driverResult);\n // No handle to probe through is not a failure to report: an adapter\n // constructed without a `connection` is the case\n // `provisioningQueryable` already refuses, with a message about the\n // adapter rather than about the network.\n if (!db) return;\n try {\n await db.execute(sql`SELECT 1`);\n } catch (err: unknown) {\n const hostInfo = parseHostInfo(pgConfig.connectionString ?? \"\");\n const { reason, code } = classifyConnectFailure(err);\n throw diagnoseConnectFailure(err, pgConfig.connectionString)\n ?? new Error(\n code === \"53300\"\n ? `PostgreSQL at ${hostInfo} has no free connection: ${reason} [53300]`\n : `Cannot reach PostgreSQL at ${hostInfo} to provision the collection schema.`,\n { cause: err }\n );\n }\n },\n\n /**\n * Create any collection tables, columns and enum types the database is\n * missing — additively, never destructively.\n *\n * This is what lets the managed runtime boot a project against a fresh\n * database and actually serve it. Before this, only auth tables were\n * ensured, so a managed tenant came up with working sign-in and a 500 on\n * every data route.\n *\n * Runs through the drizzle handle's underlying session so it uses the\n * same connection (and therefore the same privileges) the driver already\n * proved it can bootstrap with.\n */\n async ensureCollectionSchema(\n collections: unknown[],\n driverResult?: InitializedDriver,\n log?: (message: string) => void\n ): Promise<{ applied: number }> {\n const { ensureCollectionTables } = await import(\"./schema/ensure-collection-tables\");\n // Runs through the drizzle handle the driver already bootstrapped\n // with, so it uses exactly the connection and privileges that were\n // proven to work. Every statement is DDL or a catalogue read with no\n // bindable values (schema names are identifiers), and the module\n // validates them before they reach a string.\n const queryable = provisioningQueryable(driverResult);\n const { BOOT_LOCK_TIMEOUT_MS } = await import(\"./schema/boot-ddl\");\n // A provisioning-only run exits as soon as boot returns, so an\n // index left for later would never be built by it — and the\n // serving processes of such a deployment provision nothing. It is\n // a migration job: building inline is what it is for.\n const provisionOnly = [\"1\", \"true\"].includes((process.env.REBASE_PROVISION_ONLY ?? \"\").trim().toLowerCase());\n const plan = await ensureCollectionTables(\n queryable,\n collections as Parameters<typeof ensureCollectionTables>[1],\n log,\n // A change to a table a long reader holds waits in one-second\n // slices, so live queries on it are never held behind the boot\n // for longer than that. See `schema/boot-ddl`. And a table this\n // boot creates is built in a few transactions, not one commit\n // per column.\n {\n lockTimeoutMs: BOOT_LOCK_TIMEOUT_MS,\n batchNewTables: true,\n deferIndexBuilds: !provisionOnly,\n openIndexBuildSession: openIndexBuildSession(driverResult)\n }\n );\n for (const failure of plan.failures) {\n logger.warn(describeEnsureFailure(failure));\n }\n pendingIndexBuild?.cancel();\n pendingIndexBuild = undefined;\n if (!provisionOnly && plan.deferredIndexes.length > 0) {\n const { scheduleDeferredIndexBuild } = await import(\"./schema/deferred-index-build\");\n logger.info(\n `[schema] ${plan.deferredIndexes.length} index(es) are missing, or were left INVALID, on tables that ` +\n \"already exist. They are built in the background once the server is serving, one at a time, \" +\n \"without blocking reads or writes.\"\n );\n pendingIndexBuild = scheduleDeferredIndexBuild(openIndexBuildSession(driverResult), plan.deferredIndexes, {\n log: message => logger.info(`[schema] ${message}`),\n lockTimeoutMs: BOOT_LOCK_TIMEOUT_MS,\n delayMs: DEFERRED_INDEX_BUILD_DELAY_MS\n });\n }\n return { applied: plan.actions.length - plan.failures.length };\n },\n\n /**\n * Apply the collections' RLS policies — ENABLE ROW LEVEL SECURITY and the\n * `securityRules` compiled to `CREATE POLICY` — so a freshly provisioned\n * database serves data instead of denying every user-context read.\n *\n * The companion to {@link ensureCollectionSchema}: that creates the\n * tables, this makes them servable. Boot runs it *after* auth\n * initialization, because the generated policies call `rebase.uid()` /\n * `rebase.roles()`, and `CREATE POLICY` validates those functions exist.\n *\n * Runs through the same drizzle handle, one transaction per table under\n * a lock budget (see `schema/ensure-collection-policies`). Failures are\n * per-table and non-fatal: a table left un-policed stays RLS-enabled, so\n * it denies rather than leaks, and a table whose lock another session\n * held throughout is left exactly as it was.\n */\n /**\n * The helper functions every generated policy calls — `rebase.uid()`,\n * `rebase.roles()` and their siblings — on THIS database.\n *\n * On the default source they arrive with the auth tables, because auth\n * lives there. A second database (`database(\"analytics\")`) has no auth\n * tables and used to have no helpers either, so every `CREATE POLICY`\n * on it failed and every user-context read was denied. Same statements\n * as the auth path and the migration preamble, from the same constant.\n */\n async ensureRlsRuntime(driverResult?: InitializedDriver): Promise<void> {\n await provisionRlsRuntime(driverResult);\n },\n\n async ensureCollectionPolicies(\n collections: unknown[],\n driverResult?: InitializedDriver,\n log?: (message: string) => void\n ): Promise<{ applied: number }> {\n const { ensureCollectionPolicies, describeRulesDropped } = await import(\"./schema/ensure-collection-policies\");\n const queryable = provisioningQueryable(driverResult);\n // The order this depends on is set by the caller, and a caller that\n // gets it wrong fails silently-but-permanently: `CREATE POLICY`\n // validates the functions its expression calls, so every policy on\n // the source is refused and the table stays RLS-enabled with no way\n // in. That is what a second database did for a whole release — the\n // helpers arrived with the auth tables, and auth lives on the\n // default source only. So the invariant is checked here, where the\n // policies are actually written, instead of being an ordering rule\n // three modules away. One catalogue lookup when it already holds.\n const { rows } = await queryable.query<{ present: boolean }>(\n \"SELECT to_regprocedure('rebase.roles()') IS NOT NULL AS present\"\n );\n if (!rows[0]?.present) await provisionRlsRuntime(driverResult);\n const outcome = await ensureCollectionPolicies(\n queryable,\n collections as CollectionConfig[],\n log\n );\n // This run covers every collection on the source, so what it could\n // not lock is the whole set — a table locked since is dropped from it.\n withheldTables.clear();\n for (const u of outcome.unsecured.filter(u => u.grantWithdrawn)) {\n const dot = u.table.indexOf(\".\");\n withheldTables.set(u.table, { schema: u.table.slice(0, dot), table: u.table.slice(dot + 1) });\n }\n\n for (const skip of outcome.skipped) {\n logger.warn(`🔐 [rls] Policies not applied to \"${skip.table}\": ${skip.reason}`);\n }\n for (const failure of outcome.failures) {\n logger.warn(\n `🔐 [rls] Could not fully apply policies to \"${failure.table}\" — RLS is on, so it denies until this is resolved: ${failure.error}`\n );\n }\n // Applied — the declaration wins, and the table is left admin-only\n // rather than open — but never in silence: it changes who reads\n // the table on every server sharing this database.\n for (const dropped of outcome.rulesDropped) logger.warn(describeRulesDropped(dropped));\n // Not a failure: the table's transaction rolled back, so it is\n // exactly as it was — RLS on, its existing policies in force. Only\n // worth more than a warning when those are not the declared ones.\n for (const deferred of outcome.deferred) {\n const message =\n `🔐 [rls] Policies on \"${deferred.table}\" were not re-applied: ${deferred.error}. ` +\n \"RLS is on and the table keeps the policies it already had\";\n if (deferred.rulesChanged) {\n logger.error(\n `${message} — which are NOT the ones this deploy declares, so its new security rules are ` +\n \"not in force on this table yet. Boot again once that session has finished.\"\n );\n } else {\n logger.warn(`${message}; they are re-applied on the next boot.`);\n }\n }\n\n // RLS could not be turned on at all. The schema-wide grant to the\n // user role has already run by this point, so without the revoke\n // below the table is readable and writable by every authenticated\n // request with no row filtering — the one state this boot path must\n // never leave behind, and the one it used to describe as \"locked\".\n const unrevoked = outcome.unsecured.filter(u => !u.grantWithdrawn);\n for (const u of outcome.unsecured.filter(u => u.grantWithdrawn)) {\n logger.error(\n `🔐 [rls] Could not enable row-level security on \"${u.table}\": ${u.error}. ` +\n `Its privileges have been revoked from ${REBASE_USER_ROLE}, so the table is ` +\n \"unreachable rather than unprotected. Reads and writes to that collection will \" +\n \"fail until RLS can be enabled.\"\n );\n }\n if (unrevoked.length > 0) {\n // Neither securable nor closable. Serving here would mean\n // handing every authenticated caller unfiltered access, so the\n // boot fails instead — this is the one failure that is not\n // survivable per-table.\n throw new Error(\n \"Refusing to start: row-level security could not be enabled on \" +\n unrevoked.map(u => `\"${u.table}\" (${u.error})`).join(\", \") +\n `, and the privileges granted to ${REBASE_USER_ROLE} could not be revoked either. ` +\n \"The table would be served with no row filtering. Fix the database permissions \" +\n \"(the connection role must own these tables, or be able to ALTER them) and boot again.\"\n );\n }\n\n // Retire the pre-1.0 `auth` schema now that the policies above no\n // longer call into it. Deliberately after, and deliberately quiet:\n // Postgres refuses to drop a function an RLS policy still\n // references, so on a database where some table has not been\n // recompiled yet this is expected to fail and succeed on a later\n // boot. See DROP_LEGACY_AUTH_SCHEMA_SQL for the guards that keep it\n // off a Supabase `auth` schema.\n try {\n const { dropLegacyAuthSchema } = await import(\"./schema/rls-bootstrap-sql\");\n await dropLegacyAuthSchema(\n async (text) => (await queryable.query<Record<string, unknown>>(text)).rows,\n { info: (m) => logger.info(m), warn: (m) => logger.warn(m) }\n );\n } catch (err) {\n logger.info(\n \"Left the legacy `auth` schema in place: \" +\n (err instanceof Error ? err.message : String(err))\n );\n }\n\n // The last DDL this boot runs; the deferred index builds may have\n // the tables now.\n pendingIndexBuild?.start();\n return { applied: outcome.policiesApplied };\n },\n\n /**\n * Read what the last provisioning boot recorded, or `null`.\n *\n * The meta schema is `rebase` rather than the auth stamp's — see\n * `schema/collections-schema-version.ts` for why the two can differ.\n */\n async readCollectionsSchemaVersion(driverResult?: InitializedDriver): Promise<string | null> {\n const db = provisioningDb(driverResult);\n if (!db) return null;\n const { readCollectionsSchemaVersion } = await import(\"./schema/collections-schema-version\");\n return readCollectionsSchemaVersion(db as never, SCHEMA_META_SCHEMA);\n },\n\n /** Record what this process just applied. Only the provisioning process calls this. */\n async stampCollectionsSchemaVersion(version: string, driverResult?: InitializedDriver): Promise<void> {\n const db = provisioningDb(driverResult);\n if (!db) return;\n const { stampCollectionsSchemaVersion } = await import(\"./schema/collections-schema-version\");\n await stampCollectionsSchemaVersion(db as never, SCHEMA_META_SCHEMA, version);\n },\n\n /**\n * Ask the RLS question again, now that the schema exists.\n *\n * `initializeDriver` decides the posture before anything auth or\n * introspection creates, and on a genuinely fresh database the catalogue\n * has nothing to answer with: the role owns no tables, so it does not\n * bypass, so no role switch is configured — and then this very process\n * creates the tables and owns every one of them. Table owners bypass all\n * non-FORCE policies, so from that point on the process serves every\n * request unfiltered, for its whole lifetime, while the boot log records\n * that RLS was fine. The next restart sees the tables and quietly fixes\n * it, which is why the state is so easy to miss.\n *\n * This runs after provisioning and after auth has created its own tables,\n * and asks once more. Three outcomes:\n *\n * - a switch is already configured — nothing to do;\n * - the connection still owns nothing — the original answer held;\n * - the connection now owns tables — the answer has changed under us, so\n * configure the switch, and if that is impossible, FAIL. A boot that\n * crashes with the reason in its logs is recoverable. A boot that\n * serves every row to everyone is not.\n */\n async finalizeSecurityPosture(driverResult: InitializedDriver): Promise<void> {\n const internals = driverResult.internals as PostgresDriverInternals;\n const { driver, realtimeService, registry, db } = internals;\n if (!db) return;\n // Opened without provisioning: nothing has been created since the\n // posture was read at open, so there is nothing new to grant on,\n // and granting is DDL this process was told not to run.\n if (internals.provisioned === false) return;\n\n const runSql: RawSqlRunner = async (text) => {\n const res = await db.execute(sql.raw(text));\n return (res.rows ?? []) as Record<string, unknown>[];\n };\n\n const collectionSchemas = registry.getCollections()\n .map((c) => (c as { schema?: string }).schema)\n .filter((s): s is string => typeof s === \"string\");\n const managedSchemas = [\"public\", \"rebase\", ...collectionSchemas];\n\n // The switch was configured at connect — and its grants were made\n // then, on the schemas that existed then. On a fresh database that\n // is `public` alone: `rebase` (the RLS helpers, the history table,\n // `rebase.record_history`, the job store) and a collection's own\n // schema are created by provisioning, after. The grant skipped for\n // a missing schema was never made, so every write to a\n // `history: true` collection answered \"permission denied for schema\n // rebase\" until a restart found the schema at connect time. Made\n // now, once provisioning has created them.\n //\n // The grant tier only — the role and the membership were settled at\n // connect, in this process — and only what the catalogue says is\n // still missing: on a database whose grants already hold, one read.\n // It used to repeat every grant and every internal-table revoke the\n // connect-time call had just made.\n if (driver.rlsUserRole) {\n try {\n await ensureAppRoleGrants(runSql, managedSchemas);\n } catch (err) {\n // Fails closed — a missing grant refuses requests, it does\n // not widen them — so a warning, not a refusal to serve.\n logger.warn(\n `⚠️ RLS enforcement: could not grant \"${REBASE_USER_ROLE}\" on the schemas provisioning ` +\n `created (${managedSchemas.join(\", \")}). Requests that touch them will be refused with ` +\n `\"permission denied for schema …\" until a boot can: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n await withholdAgain(runSql);\n return;\n }\n\n const posture = await detectConnectionPosture(runSql, managedSchemas);\n // Only ownership matters here. `canCreateTables` was already weighed\n // at boot, and re-failing on it now would turn the tolerant branch\n // above into a hard failure by the back door.\n if (!posture.superuser && !posture.bypassRLS && !posture.ownsTables) return;\n\n try {\n await ensureAppRole(runSql, managedSchemas);\n } catch (err) {\n throw new Error(\n `The database connection role \"${posture.role}\" now owns tables in this database, so it ` +\n \"bypasses every row-level security policy on them — but the restricted role it should \" +\n \"switch to could not be provisioned, so authenticated requests would be served with no \" +\n `policy applied at all. Refusing to serve.\\n\\n${err instanceof Error ? err.message : String(err)}`\n );\n }\n await withholdAgain(runSql);\n\n driver.rlsUserRole = REBASE_USER_ROLE;\n realtimeService.rlsUserRole = REBASE_USER_ROLE;\n logger.info(\n `🔐 RLS enforcement active: the connection role \"${posture.role}\" became a table owner during ` +\n `this boot, so authenticated requests now run as \"${REBASE_USER_ROLE}\".`\n );\n },\n\n getAdmin(driverResult: InitializedDriver): DatabaseAdmin | undefined {\n const internals = driverResult.internals as PostgresDriverInternals;\n return internals.driver.admin;\n },\n\n async initializeWebsockets(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown, adapter?: unknown, limits?: RealtimeSocketOptions): Promise<void> {\n const { createPostgresWebSocket, isSocketRealtimeService, socketRealtimeMissing } = await import(\"./websocket\");\n // Narrowed, not cast: on a multi-source project this is the routed\n // composite, and a cast to the Postgres service is what let the\n // socket call a method the composite did not have.\n if (!isSocketRealtimeService(realtimeService)) {\n throw new Error(\n \"The realtime service handed to the Postgres socket cannot serve it — it has no \" +\n `${socketRealtimeMissing(realtimeService).join(\", \")}. Every socket would fail on its first frame.`\n );\n }\n // The per-identity socket ceiling, opt-in. Read here, at boot, and\n // refused if mistyped rather than quietly becoming no ceiling.\n const realtimeConfig = effectiveConfig().realtime;\n const maxSocketsPerUser = realtimeConfig?.maxSocketsPerUser !== undefined\n ? parseMaxSocketsPerUser(realtimeConfig.maxSocketsPerUser, \"realtime.maxSocketsPerUser\")\n : undefined;\n // The path this adapter was told to answer on wins over the one\n // the server derived from its mount point.\n const path = realtimeConfig?.path ?? limits?.path;\n createPostgresWebSocket(\n server as import(\"http\").Server,\n realtimeService,\n driver as PostgresBackendDriver,\n config as { requireAuth?: boolean; jwtSecret?: string; serviceKey?: string },\n adapter as AuthAdapter | undefined,\n {\n ...limits,\n ...(maxSocketsPerUser !== undefined ? { maxSocketsPerUser } : {}),\n ...(path !== undefined ? { path } : {})\n }\n );\n }\n };\n}\n","/**\n * `openRebase` — a project's data from a script, a REPL or a test, with no\n * server around it.\n *\n * The same vocabulary a function gets from the server (`dataAsAdmin`, `sql`),\n * one call to act as a user under row-level security, and `close()`. Nothing\n * else: no HTTP, no schema changes, no environment variables read, no realtime,\n * no timers and no process listeners. What the server's boot adds on top of\n * this is the boot's business.\n */\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type { Pool } from \"pg\";\nimport {\n DEFAULT_DATA_SOURCE_KEY,\n isSQLAdmin,\n resourceKeyOf,\n type CollectionConfig,\n type RebaseSdkData,\n type RebaseServerClient,\n type RegisteredDatabase,\n type User\n} from \"@rebasepro/types\";\nimport { buildSdkData, createAccessorSlugResolver } from \"@rebasepro/common\";\nimport { logger } from \"@rebasepro/server\";\nimport { deepestErrorMessage, parseHostInfo } from \"./cli-errors\";\nimport { extractPgError } from \"./utils/pg-error-utils\";\nimport { createPostgresDatabaseConnection, type PostgresPoolConfig } from \"./connection\";\nimport { openPostgresDriver } from \"./PostgresBootstrapper\";\nimport { HistoryService } from \"./history/HistoryService\";\nimport { historyUnavailableReason, readHistoryTableState } from \"./history/ensure-history-table\";\n\n/** Who a request runs as: what row-level security policies read. */\nexport interface RebaseIdentity {\n uid: string;\n roles?: string[];\n /** A guest — an anonymous sign-in — rather than an account. */\n isAnonymous?: boolean;\n /** Custom token claims, as `rebase.jwt()` reads them in a policy. */\n claims?: Record<string, unknown>;\n}\n\nexport interface OpenRebaseOptions {\n /**\n * The database to open. `openRebase` creates the pool and `close()` ends\n * it. One of this and `connection`.\n */\n databaseUrl?: string;\n /**\n * A connection you already hold — `createPostgresDatabaseConnection(…).db`.\n * It stays yours: `close()` leaves it open.\n */\n connection?: NodePgDatabase<Record<string, unknown>> & { $client: Pool };\n /**\n * The project's collections — what the server is configured with. Empty\n * reads them from the database instead, the way a server with no\n * collection files does.\n */\n collections: CollectionConfig[];\n /** Pool tuning, when `databaseUrl` is given. */\n pool?: PostgresPoolConfig;\n /** The data source these collections are served from. Defaults to `(default)`. */\n dataSourceKey?: string;\n}\n\n/**\n * An open project. `dataAsAdmin` and `sql` are the accessors of the same name\n * a server-side function gets (`RebaseServerClient`).\n *\n * Typed by `DB`, which defaults to the `Database` the project registered\n * (`rebase generate-sdk --register`, see `Register` in `@rebasepro/types`) —\n * the type a function's `rebase.dataAsAdmin` has — and is untyped when it\n * registered none.\n */\nexport interface OpenedRebase<DB = RegisteredDatabase> extends Pick<RebaseServerClient<DB>, \"dataAsAdmin\" | \"sql\"> {\n /**\n * The data as `user` sees it: every read and write runs as them, filtered\n * by the collections' row-level security policies — the scoping an HTTP\n * request from that user gets. Named after the driver method it uses.\n */\n withAuth(user: RebaseIdentity): Promise<{ data: RebaseSdkData<DB> }>;\n /** The collections this was opened with, or the ones the database described. */\n readonly collections: CollectionConfig[];\n /** End the pool `openRebase` created. Safe to call twice; a `connection` you passed stays open. */\n close(): Promise<void>;\n}\n\n/**\n * The identity `dataAsAdmin` is scoped as — the server's `SERVICE_IDENTITY`, so\n * it reads and writes what a function's `rebase.dataAsAdmin` does: admin, and\n * still evaluated by policies.\n */\nconst SERVICE_IDENTITY: RebaseIdentity = { uid: \"service\", roles: [\"admin\"] };\n\n/** The `User` the driver's `withAuth` takes, for an identity with no account details. */\nfunction userFor(identity: RebaseIdentity): User {\n return {\n uid: identity.uid,\n roles: identity.roles ?? [],\n isAnonymous: identity.isAnonymous === true,\n displayName: null,\n email: null,\n photoURL: null,\n providerId: \"script\",\n ...(identity.claims ? { claims: identity.claims } : {})\n };\n}\n\n/**\n * Open a project's database the way its server would serve it, from code.\n *\n * ```ts\n * const rebase = await openRebase({ databaseUrl, collections });\n * const posts = await rebase.dataAsAdmin.posts.find({ limit: 10 });\n * const ada = await rebase.withAuth({ uid: \"ada\", roles: [\"user\"] });\n * const hers = await ada.data.posts.find();\n * await rebase.close();\n * ```\n *\n * Opening changes nothing in the database: no table, role, trigger or\n * function is created. A write to a `history: true` collection records its\n * entry as a server write does, on the write's transaction; on a database no\n * backend with history has provisioned, such writes are refused with\n * `HISTORY_UNAVAILABLE` (reads are not) rather than made without their audit\n * entry. A `withAuth` request on a connection that bypasses row-level\n * security runs as the restricted role a server boot provisions; against a\n * database no server has booted on, it fails rather than run unfiltered.\n *\n * Its accessors are typed from the `Database` the project registered, as a\n * function's are; `openRebase<Database>(…)` names one for this call instead.\n */\nexport async function openRebase<DB = RegisteredDatabase>(options: OpenRebaseOptions): Promise<OpenedRebase<DB>> {\n if (!options.databaseUrl && !options.connection) {\n throw new Error(\"openRebase needs a `databaseUrl` or a `connection`.\");\n }\n if (options.databaseUrl && options.connection) {\n throw new Error(\"openRebase takes a `databaseUrl` or a `connection`, not both.\");\n }\n\n // The pool size is what was asked for: `{}` keeps the environment's\n // `REBASE_DB_POOL_MAX` ceiling out of it.\n const owned = options.databaseUrl\n ? createPostgresDatabaseConnection(options.databaseUrl, undefined, options.pool, {})\n : undefined;\n const db = owned?.db ?? options.connection;\n const connectionString = owned?.connectionString ?? options.connection?.$client.options.connectionString;\n\n // Asked first, and answered by the error alone. Opening the driver on a\n // database it cannot reach logs the server boot's banner and then throws\n // the same diagnosis, so a caller that prints what it caught — `rebase\n // console` does — said it twice, under an empty `❌ [ERROR]` line.\n const pool = owned?.pool ?? options.connection?.$client;\n try {\n await pool?.query(\"SELECT 1\");\n } catch (error) {\n await owned?.pool.end().catch(() => undefined);\n // With Postgres' hint, where it gave one: the managed development\n // database's refusal for want of a free connection says what to do in it.\n const hint = extractPgError(error)?.hint;\n throw new Error(\n `Cannot connect to PostgreSQL at ${parseHostInfo(connectionString ?? \"\")}: ` +\n `${deepestErrorMessage(error) ?? \"the connection failed\"}.${hint ? ` ${hint}` : \"\"}`,\n { cause: error }\n );\n }\n\n let opened: Awaited<ReturnType<typeof openPostgresDriver>>;\n try {\n opened = await openPostgresDriver({ connection: db, connectionString }, {\n collections: options.collections,\n dataSourceKey: options.dataSourceKey ?? DEFAULT_DATA_SOURCE_KEY,\n introspectCollections: options.collections.length === 0,\n provision: false,\n realtime: false,\n schemaProvisioning: {\n attempted: false,\n reason: \"openRebase never changes the schema — apply it with `rebase db push`, or by booting the server.\"\n }\n });\n } catch (error) {\n await owned?.pool.end().catch(() => undefined);\n throw error;\n }\n\n const driver = opened.internals.driver;\n const registry = opened.internals.registry;\n // The server boot announces this at info. A script's stdout is its own\n // output — `tsx script.ts > out.json` — so here it is there only for a\n // reader who asked for debug.\n if (opened.internals.rlsEnforcement) logger.debug(opened.internals.rlsEnforcement);\n\n // A `history: true` collection is audited from a script exactly as from\n // the server: the same service, the entry on the write's transaction,\n // recorded as whoever the write runs as. Nothing is created for it — the\n // table and its function are read — and where an earlier boot never made\n // them, writes to those collections are refused up front instead of going\n // unaudited. Reads are unaffected. Entries are not pruned here: the\n // retention is the server's setting, which a script does not know, and\n // pruning to the default could delete entries the server keeps.\n const collections = opened.collections ?? options.collections;\n if (collections.some(collection => collection.history)) {\n try {\n const state = await readHistoryTableState(opened.internals.db);\n driver.historyService = new HistoryService(opened.internals.db, undefined, {\n inTransaction: state.inTransaction,\n entryFormat: state.entryFormat,\n unavailable: historyUnavailableReason(state, { requireTransaction: true }),\n pruneOnRecord: false\n });\n } catch (error) {\n await owned?.pool.end().catch(() => undefined);\n throw error;\n }\n }\n const resolveAccessor = createAccessorSlugResolver(() => registry.getCollections());\n const dataAs = async (identity: RebaseIdentity): Promise<RebaseSdkData<DB>> =>\n buildSdkData<DB>(await driver.withAuth(userFor(identity)), { resolveAccessor });\n\n const dataAsAdmin = await dataAs(SERVICE_IDENTITY);\n const admin = driver.admin;\n let closed = false;\n\n return {\n dataAsAdmin,\n collections,\n async sql(query, sqlOptions) {\n if (!isSQLAdmin(admin)) throw new Error(\"This driver cannot run raw SQL.\");\n return admin.executeSql(query, sqlOptions && {\n role: sqlOptions.role,\n params: sqlOptions.params,\n ...(sqlOptions.database !== undefined ? { database: resourceKeyOf(sqlOptions.database) } : {})\n });\n },\n async withAuth(user) {\n return { data: await dataAs(user) };\n },\n async close() {\n if (closed) return;\n closed = true;\n await owned?.pool.end();\n }\n };\n}\n"],"mappings":"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"}
|