@rebasepro/server-postgres 0.16.0 → 0.16.1-canary.g0d7af95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/PostgresAdapter.d.ts +1 -1
  2. package/dist/PostgresBackendDriver.d.ts +75 -12
  3. package/dist/PostgresBootstrapper.d.ts +6 -6
  4. package/dist/auth/services.d.ts +1 -1
  5. package/dist/backup/backup-cron.d.ts +1 -1
  6. package/dist/backup/backup-service.d.ts +2 -2
  7. package/dist/backup/index.d.ts +4 -4
  8. package/dist/{backup-service-BZoixhVl.js → backup-service-BtgHxfFm.js} +5 -4
  9. package/dist/{backup-service-BZoixhVl.js.map → backup-service-BtgHxfFm.js.map} +1 -1
  10. package/dist/cli-helpers.d.ts +41 -0
  11. package/dist/{auth-users-columns-CgyPWQ18.js → collection-index-DxJBvVTH.js} +486 -503
  12. package/dist/collection-index-DxJBvVTH.js.map +1 -0
  13. package/dist/collections/PostgresCollectionRegistry.d.ts +1 -1
  14. package/dist/collections/buildRegistry.d.ts +1 -1
  15. package/dist/collections/validate-relations.d.ts +1 -1
  16. package/dist/{connection-BuZ97wsr.js → connection-GOKU3Hu5.js} +34 -7
  17. package/dist/connection-GOKU3Hu5.js.map +1 -0
  18. package/dist/connection.d.ts +16 -0
  19. package/dist/data-transformer.d.ts +1 -1
  20. package/dist/{ensure-collection-policies-BVFb2olB.js → ensure-collection-policies-DFpOl8SM.js} +4 -4
  21. package/dist/{ensure-collection-policies-BVFb2olB.js.map → ensure-collection-policies-DFpOl8SM.js.map} +1 -1
  22. package/dist/ensure-collection-tables-DMjOkeRy.js +1952 -0
  23. package/dist/ensure-collection-tables-DMjOkeRy.js.map +1 -0
  24. package/dist/index.d.ts +16 -16
  25. package/dist/index.es.js +19 -7128
  26. package/dist/index.es.js.map +1 -1
  27. package/dist/{rls-bootstrap-sql-B5Sajku6.js → rls-bootstrap-sql-DNzaWd4C.js} +3 -3
  28. package/dist/{rls-bootstrap-sql-B5Sajku6.js.map → rls-bootstrap-sql-DNzaWd4C.js.map} +1 -1
  29. package/dist/{rls-enforcement-Ch0T6OwW.js → rls-enforcement-CInuYj1-.js} +14 -4
  30. package/dist/rls-enforcement-CInuYj1-.js.map +1 -0
  31. package/dist/schema/classify-change.d.ts +82 -0
  32. package/dist/schema/collection-index.d.ts +182 -0
  33. package/dist/schema/dynamic-tables.d.ts +1 -1
  34. package/dist/schema/ensure-collection-policies.d.ts +1 -1
  35. package/dist/schema/ensure-collection-tables.d.ts +93 -2
  36. package/dist/schema/generate-schema-commit.d.ts +136 -0
  37. package/dist/schema/generated-schema-staleness.d.ts +19 -0
  38. package/dist/schema/introspect-db-constraints.d.ts +1 -1
  39. package/dist/schema/introspect-db-logic.d.ts +3 -3
  40. package/dist/schema/introspect-db-project.d.ts +1 -1
  41. package/dist/schema/introspect-db-queries.d.ts +1 -1
  42. package/dist/schema/introspect-db-structure.d.ts +2 -2
  43. package/dist/schema/introspect-runtime.d.ts +1 -1
  44. package/dist/schema/vector-index.d.ts +88 -0
  45. package/dist/services/BranchService.d.ts +2 -2
  46. package/dist/services/FetchService.d.ts +4 -4
  47. package/dist/services/PersistService.d.ts +5 -5
  48. package/dist/services/RelationService.d.ts +3 -3
  49. package/dist/services/RelationWriteService.d.ts +3 -3
  50. package/dist/services/cdc/junction-tables.d.ts +1 -1
  51. package/dist/services/cdc/trigger-cdc.d.ts +1 -1
  52. package/dist/services/channel-bus/PostgresChannelBus.d.ts +1 -1
  53. package/dist/services/channel-bus/index.d.ts +2 -2
  54. package/dist/services/collection-helpers.d.ts +1 -1
  55. package/dist/services/dataService.d.ts +10 -10
  56. package/dist/services/index.d.ts +4 -4
  57. package/dist/services/junction-writes.d.ts +2 -2
  58. package/dist/services/nested-path.d.ts +1 -1
  59. package/dist/services/realtimeService.d.ts +3 -3
  60. package/dist/services/row-pipeline.d.ts +1 -1
  61. package/dist/services/write-denial.d.ts +1 -1
  62. package/dist/{src-BBFsDaeA.js → src-DiDgtX8P.js} +97 -1
  63. package/dist/src-DiDgtX8P.js.map +1 -0
  64. package/dist/utils/drizzle-conditions.d.ts +2 -2
  65. package/dist/websocket-CUnpSe8v.js +8188 -0
  66. package/dist/websocket-CUnpSe8v.js.map +1 -0
  67. package/dist/websocket.d.ts +29 -2
  68. package/package.json +7 -7
  69. package/src/PostgresBackendDriver.ts +190 -59
  70. package/src/backup/backup-service.ts +1 -1
  71. package/src/cli-helpers.ts +117 -2
  72. package/src/cli.ts +22 -0
  73. package/src/connection.ts +37 -3
  74. package/src/databasePoolManager.ts +5 -2
  75. package/src/schema/classify-change.ts +436 -0
  76. package/src/schema/collection-index.ts +427 -0
  77. package/src/schema/ensure-collection-tables.test.ts +168 -1
  78. package/src/schema/ensure-collection-tables.ts +365 -14
  79. package/src/schema/generate-drizzle-schema-logic.ts +23 -11
  80. package/src/schema/generate-drizzle-schema.ts +13 -2
  81. package/src/schema/generate-postgres-ddl-logic.ts +33 -6
  82. package/src/schema/generate-postgres-ddl.ts +13 -2
  83. package/src/schema/generate-schema-commit.ts +242 -0
  84. package/src/schema/generated-schema-staleness.ts +114 -1
  85. package/src/schema/vector-index.ts +278 -0
  86. package/src/services/collection-helpers.ts +3 -2
  87. package/src/websocket.ts +47 -3
  88. package/dist/auth-users-columns-CgyPWQ18.js.map +0 -1
  89. package/dist/connection-BuZ97wsr.js.map +0 -1
  90. package/dist/data_driver-ULAyJEi9.js +0 -193
  91. package/dist/data_driver-ULAyJEi9.js.map +0 -1
  92. package/dist/ensure-collection-tables-BY1pHRD_.js +0 -840
  93. package/dist/ensure-collection-tables-BY1pHRD_.js.map +0 -1
  94. package/dist/rls-enforcement-Ch0T6OwW.js.map +0 -1
  95. package/dist/src-BBFsDaeA.js.map +0 -1
  96. package/dist/utils/table-classification.d.ts +0 -8
  97. package/dist/websocket-BVgDVO-V.js +0 -614
  98. package/dist/websocket-BVgDVO-V.js.map +0 -1
  99. package/src/utils/table-classification.ts +0 -16
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-CUnpSe8v.js","names":[],"sources":["../../types/src/types/backend.ts","../../types/src/types/schema_editing.ts","../../common/src/util/callbacks.ts","../../common/src/data/filter-conditions.ts","../../common/src/table-classification.ts","../src/services/collection-helpers.ts","../src/utils/drizzle-conditions.ts","../src/data-transformer.ts","../src/services/write-denial.ts","../src/services/junction-writes.ts","../src/services/RelationService.ts","../src/services/row-pipeline.ts","../src/services/nested-path.ts","../src/utils/pg-error-utils.ts","../src/services/FetchService.ts","../src/services/RelationWriteService.ts","../src/services/PersistService.ts","../src/services/dataService.ts","../src/services/BranchService.ts","../src/schema/generate-drizzle-schema-logic.ts","../src/schema/classify-change.ts","../src/schema/generate-schema-commit.ts","../src/PostgresBackendDriver.ts","../src/websocket.ts"],"sourcesContent":["import type { CollectionConfig, FilterValues, WhereFilterOp } from \"./collections\";\nimport type { OrderByTuple } from \"./filter-operators\";\nimport type { LogicalCondition } from \"../controllers/data\";\nimport type { AuthAdapter } from \"./auth_adapter\";\nimport type { HistoryConfig } from \"../controllers/client\";\nimport type { ChannelBusSetting } from \"./channel_bus\";\nimport type { SchemaEditingAdmin } from \"./schema_editing\";\n\n// =============================================================================\n// DATABASE CONNECTION INTERFACES\n// =============================================================================\n\n/**\n * Abstract database connection interface.\n * Represents a connection to any database system.\n */\nexport interface DatabaseConnection {\n /**\n * Type identifier for this database (e.g., 'postgres', 'mongodb', 'mysql')\n */\n readonly type: string;\n\n /**\n * Whether the connection is currently active\n */\n readonly isConnected?: boolean;\n\n /**\n * Close the database connection and release resources.\n */\n close?(): Promise<void>;\n}\n\n// =============================================================================\n// QUERY BUILDING INTERFACES\n// =============================================================================\n\n/**\n * A single filter condition for database queries\n */\nexport interface QueryFilter {\n field: string;\n operator: WhereFilterOp;\n value: unknown;\n}\n\n/**\n * Options for fetching a collection of entities\n */\nexport interface FetchCollectionOptions<M extends Record<string, unknown> = Record<string, unknown>> {\n filter?: FilterValues<Extract<keyof M, string>>;\n /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: unknown;\n searchString?: string;\n databaseId?: string;\n collection?: CollectionConfig;\n}\n\n/**\n * Options for searching entities\n */\nexport interface SearchOptions<M extends Record<string, unknown> = Record<string, unknown>> {\n filter?: FilterValues<Extract<keyof M, string>>;\n /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n databaseId?: string;\n collection?: CollectionConfig;\n}\n\n/**\n * Options for counting entities\n */\nexport interface CountOptions<M extends Record<string, unknown> = Record<string, unknown>> {\n filter?: FilterValues<Extract<keyof M, string>>;\n /**\n * An `or(...)`/`and(...)` group, alongside `filter`.\n *\n * Counted as well as fetched, or `total` describes a different set of rows\n * from the one that was served — the same reason `filter` is here.\n */\n logical?: LogicalCondition;\n searchString?: string;\n databaseId?: string;\n}\n\n/**\n * Abstract condition builder interface.\n * Implementations translate Rebase filter conditions to database-specific queries.\n *\n * Note: This interface can be implemented as instance methods or as a class with static methods.\n * For static implementations (like DrizzleConditionBuilder), use the ConditionBuilderStatic type.\n *\n * @template T The type of condition returned by the builder (e.g., SQL for PostgreSQL, Filter<Document> for MongoDB)\n */\nexport interface ConditionBuilder<T = unknown> {\n /**\n * Build filter conditions from Rebase FilterValues\n */\n buildFilterConditions<M extends Record<string, unknown>>(\n filter: FilterValues<Extract<keyof M, string>>,\n collectionPath: string,\n ...args: unknown[]\n ): T[];\n\n /**\n * Build search conditions for text search\n */\n buildSearchConditions(\n searchString: string,\n properties: Record<string, unknown>,\n ...args: unknown[]\n ): T[];\n\n /**\n * Combine multiple conditions with AND operator\n */\n combineConditionsWithAnd(conditions: T[]): T | undefined;\n\n /**\n * Combine multiple conditions with OR operator\n */\n combineConditionsWithOr(conditions: T[]): T | undefined;\n}\n\n/**\n * Static condition builder type for implementations using static methods.\n * Use this type when the class provides static methods rather than instance methods.\n *\n * @example\n * // DrizzleConditionBuilder satisfies this type\n * const builder: ConditionBuilderStatic<SQL> = DrizzleConditionBuilder;\n */\nexport type ConditionBuilderStatic<T = unknown> = {\n buildFilterConditions<M extends Record<string, unknown>>(\n filter: FilterValues<Extract<keyof M, string>>,\n ...args: unknown[]\n ): T[];\n buildSearchConditions(\n searchString: string,\n properties: Record<string, unknown>,\n ...args: unknown[]\n ): T[];\n combineConditionsWithAnd(conditions: T[]): T | undefined;\n combineConditionsWithOr(conditions: T[]): T | undefined;\n};\n\n// =============================================================================\n// ENTITY REPOSITORY INTERFACES\n// =============================================================================\n\n/**\n * Abstract entity repository interface.\n * Handles all CRUD operations for entities in the database.\n *\n * Implementations should handle:\n * - Entity serialization/deserialization\n * - Relation resolution\n * - ID generation and conversion\n */\nexport interface DataRepository {\n /**\n * Fetch a single entity by ID\n */\n fetchOne<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n databaseId?: string\n ): Promise<Record<string, unknown> | undefined>;\n\n /**\n * Fetch a collection of entities with optional filtering, ordering, and pagination\n */\n fetchCollection<M extends Record<string, unknown>>(\n collectionPath: string,\n options?: FetchCollectionOptions<M>\n ): Promise<Record<string, unknown>[]>;\n\n /**\n * Search entities by text\n */\n searchRows<M extends Record<string, unknown>>(\n collectionPath: string,\n searchString: string,\n options?: SearchOptions<M>\n ): Promise<Record<string, unknown>[]>;\n\n /**\n * Count entities in a collection\n */\n count<M extends Record<string, unknown>>(\n collectionPath: string,\n options?: CountOptions<M>\n ): Promise<number>;\n\n /**\n * Save a entity (create or update)\n */\n save<M extends Record<string, unknown>>(\n collectionPath: string,\n values: Partial<M>,\n id?: string | number,\n databaseId?: string\n ): Promise<Record<string, unknown>>;\n\n /**\n * Delete a entity by ID\n */\n delete(\n collectionPath: string,\n id: string | number,\n databaseId?: string\n ): Promise<void>;\n\n /**\n * Check if a field value is unique in a collection\n */\n checkUniqueField(\n collectionPath: string,\n fieldName: string,\n value: unknown,\n excludeEntityId?: string,\n databaseId?: string\n ): Promise<boolean>;\n\n}\n\n// =============================================================================\n// REALTIME INTERFACES\n// =============================================================================\n\n/**\n * Configuration for subscribing to a collection\n */\nexport interface CollectionSubscriptionConfig {\n clientId: string;\n path: string;\n filter?: unknown;\n /**\n * An `or(...)`/`and(...)` group, applied alongside `filter`.\n *\n * Declared here because a subscription is a query, and every field a query\n * has this one needs too. It was missing, so the type-checked boundary\n * dropped it: the client sent the group, nothing rejected it, and the\n * subscription re-fetched with the group gone — pushing every row the\n * caller's policies allowed rather than the ones they asked for. The same\n * defect `FetchCollectionProps.logical` documents, one layer up.\n */\n logical?: LogicalCondition;\n /**\n * Where the subscription's page starts. Missing for the same reason, with\n * a quieter symptom: a subscriber watching page two was pushed page one,\n * and a `collection_update` frame carries no window for it to notice with.\n */\n offset?: number;\n /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n startAfter?: unknown;\n databaseId?: string;\n searchString?: string;\n /** Ask each row which declared search field matched. */\n searchExplain?: boolean;\n}\n\n/**\n * Configuration for subscribing to a single entity\n */\nexport interface SingleSubscriptionConfig {\n clientId: string;\n path: string;\n id: string | number;\n}\n\n/**\n * Opt-in retention for one set of broadcast channels.\n *\n * Retention is configured on the server and nowhere else. A channel is created\n * by whoever names it, so letting a client ask for its own history depth would\n * let any visitor commit the backend to unbounded storage; and presence-only or\n * notification-only channels — the overwhelming majority — must not pay for a\n * feature they never use. With no rules configured nothing is written, no table\n * is created, and broadcast behaves exactly as it did before history existed.\n */\nexport interface ChannelRetentionRule {\n /**\n * Channel name to match. Either exact (`\"doc:42\"`) or a trailing-`*` prefix\n * (`\"doc:*\"`). Deliberately not a full glob or RegExp: this decides what\n * gets written to disk, and a rule whose blast radius is not obvious at a\n * glance is the wrong shape for that.\n */\n match: string;\n /** Keep at most this many of the most recent messages per channel. */\n limit?: number;\n /**\n * Keep messages for at most this long. Accepts a millisecond count or a\n * short duration string (`\"30s\"`, `\"15m\"`, `\"24h\"`, `\"7d\"`).\n */\n ttl?: number | string;\n}\n\n/**\n * Server-side realtime options.\n *\n * The channel bus contract and its config live in `./channel_bus` so that a\n * transport shipped as its own package depends on the contract alone.\n */\nexport interface RealtimeChannelsConfig {\n /**\n * Retention rules, most specific first — the first match wins. Omitted or\n * empty means no channel retains anything.\n */\n channels?: ChannelRetentionRule[];\n /**\n * How channel broadcast and presence reach other backend instances.\n * Defaults to `{ type: \"memory\" }` — i.e. they don't.\n */\n bus?: ChannelBusSetting;\n}\n\n/**\n * Abstract realtime provider interface.\n * Handles real-time subscriptions and notifications for entity changes.\n */\nexport interface RealtimeProvider {\n /**\n * Subscribe to collection changes\n */\n subscribeToCollection(\n subscriptionId: string,\n config: CollectionSubscriptionConfig,\n callback?: (rows: Record<string, unknown>[]) => void\n ): void;\n\n /**\n * Subscribe to single entity changes\n */\n subscribeToOne(\n subscriptionId: string,\n config: SingleSubscriptionConfig,\n callback?: (row: Record<string, unknown> | null) => void\n ): void;\n\n /**\n * Unsubscribe from a subscription\n */\n unsubscribe(subscriptionId: string): void;\n\n /**\n * Notify all relevant subscribers of a entity update\n */\n notifyUpdate(\n path: string,\n id: string,\n row: Record<string, unknown> | null,\n databaseId?: string\n ): Promise<void>;\n\n /**\n * Called when the HTTP server is ready and listening.\n * Useful for providers that need the server address for callbacks.\n */\n onServerReady?(serverInfo: { port: number; hostname?: string }): void;\n\n /**\n * Gracefully shut down the realtime provider.\n * Called during server shutdown to clean up resources.\n */\n destroy?(): Promise<void>;\n\n /**\n * Stop the internal LISTEN client (e.g., PostgreSQL LISTEN/NOTIFY).\n * Called during graceful shutdown before closing database connections.\n */\n stopListening?(): Promise<void>;\n}\n\n// =============================================================================\n// COLLECTION REGISTRY INTERFACES\n// =============================================================================\n\n/**\n * Abstract collection registry interface.\n * Manages registration and lookup of entity collections.\n */\nexport interface CollectionRegistryInterface {\n /**\n * Register a collection\n */\n register(collection: CollectionConfig): void;\n\n /**\n * Get a collection by its path\n */\n getCollectionByPath(path: string): CollectionConfig | undefined;\n\n /**\n * Get all registered collections\n */\n getCollections(): CollectionConfig[];\n\n /**\n * Get the currently registered global callbacks, if any.\n */\n getGlobalCallbacks(): any | undefined;\n}\n\n// =============================================================================\n// DATA TRANSFORMER INTERFACES\n// =============================================================================\n\n/**\n * Abstract data transformer interface.\n * Handles serialization/deserialization between frontend and database formats.\n */\nexport interface DataTransformer {\n /**\n * Transform entity data for storage in the database\n */\n serializeToDatabase<M extends Record<string, unknown>>(\n entity: M,\n collection: CollectionConfig\n ): Record<string, unknown>;\n\n /**\n * Transform database data back to entity format\n */\n deserializeFromDatabase<M extends Record<string, unknown>>(\n data: Record<string, unknown>,\n collection: CollectionConfig\n ): Promise<M>;\n}\n\n// =============================================================================\n// DATABASE ADMIN — CAPABILITY-SPECIFIC INTERFACES (1.3)\n// =============================================================================\n\n/**\n * Administrative operations for SQL-based databases (PostgreSQL, MySQL, etc.).\n * Used by the SQL Editor, RLS Editor, and schema browser.\n *\n * @group Admin\n */\nexport interface SQLAdmin {\n /**\n * Execute raw SQL against the database.\n */\n executeSql(sql: string, options?: { database?: string; role?: string; params?: unknown[] }): Promise<Record<string, unknown>[]>;\n\n /**\n * Fetch the available databases on the server.\n */\n fetchAvailableDatabases?(): Promise<string[]>;\n\n /**\n * Fetch the available *native PostgreSQL* database roles (from `pg_roles`).\n *\n * These are connection-level roles — what the SQL editor can `SET ROLE` to,\n * and what `SecurityRule.pgRoles` targets. They are NOT application roles;\n * for those use {@link fetchApplicationRoles}.\n */\n fetchAvailableRoles?(): Promise<string[]>;\n\n /**\n * Fetch the *application-level* roles in use in this project.\n *\n * These are the strings stored on the users table's `roles` column and\n * exposed to policies as `rebase.roles()` — what `SecurityRule.roles`\n * matches against. Distinct from {@link fetchAvailableRoles}; the two are\n * not interchangeable.\n */\n fetchApplicationRoles?(): Promise<string[]>;\n\n /**\n * Fetch the current database name.\n */\n fetchCurrentDatabase?(): Promise<string | undefined>;\n}\n\n/**\n * Administrative operations for document-based databases (MongoDB, Firestore, etc.).\n * Used by future document administration tools.\n *\n * @group Admin\n */\nexport interface DocumentAdmin {\n /**\n * Execute an aggregation pipeline or equivalent query.\n */\n executeAggregate?(pipeline: Record<string, unknown>[]): Promise<Record<string, unknown>[]>;\n\n /**\n * Fetch statistics for a collection (document count, size, etc.).\n */\n fetchCollectionStats?(collectionName: string): Promise<{ count: number; sizeBytes?: number }>;\n}\n\n/**\n * Administrative operations for schema management.\n * Shared across SQL and document databases.\n *\n * @group Admin\n */\nexport interface SchemaAdmin {\n /**\n * Fetch database tables/collections not yet mapped to a Rebase collection.\n */\n fetchUnmappedTables?(mappedPaths?: string[]): Promise<string[]>;\n\n /**\n * Fetch column/field metadata for a single table/collection.\n * The return type is generic — SQL backends return TableMetadata,\n * document backends may return a different shape.\n */\n fetchTableMetadata?(tableName: string): Promise<unknown>;\n}\n\n/**\n * Metadata for a database branch.\n * @group Admin\n */\nexport interface BranchInfo {\n /** Branch name (without prefix). */\n name: string;\n /** The database this branch was created from. */\n parentDatabase: string;\n /** When the branch was created. */\n createdAt: Date;\n /** Size in bytes, if available from the server. */\n sizeBytes?: number;\n}\n\n/**\n * Administrative operations for database branching.\n * Allows creating isolated database copies for development/preview workflows.\n *\n * @group Admin\n */\nexport interface BranchAdmin {\n /** Create a new branch (database copy) from the current or specified source database. */\n createBranch(name: string, options?: { source?: string }): Promise<BranchInfo>;\n\n /** Delete a branch database. Cannot delete the main/default database. */\n deleteBranch(name: string): Promise<void>;\n\n /** List all branches (databases that were created via branching). */\n listBranches(): Promise<BranchInfo[]>;\n\n /** Get info about a specific branch. */\n getBranchInfo(name: string): Promise<BranchInfo | undefined>;\n}\n\n/**\n * Union type for all admin capabilities.\n * A backend may implement any combination of these interfaces.\n *\n * Use type guards (`isSQLAdmin`, `isDocumentAdmin`, `isSchemaAdmin`, `isBranchAdmin`)\n * to safely narrow the type before calling methods.\n *\n * @group Admin\n */\nexport type DatabaseAdmin = Partial<SQLAdmin> & Partial<DocumentAdmin> & Partial<SchemaAdmin>\n & Partial<BranchAdmin> & Partial<SchemaEditingAdmin>;\n\n/**\n * Type guard: can this admin plan a live schema change?\n *\n * Planning is engine-specific — it renders DDL, a Drizzle schema and the\n * declarative SQL artifacts — so the implementation lives in the driver\n * package. The server detects the capability structurally, exactly as it does\n * for SQL, rather than importing an engine it is supposed to know nothing\n * about.\n *\n * @group Admin\n */\nexport function isSchemaEditingAdmin(admin: DatabaseAdmin | undefined): admin is SchemaEditingAdmin {\n return !!admin && typeof (admin as SchemaEditingAdmin).planSchemaChange === \"function\";\n}\n\n/**\n * Type guard: does this admin support SQL operations?\n * @group Admin\n */\nexport function isSQLAdmin(admin: DatabaseAdmin | undefined): admin is SQLAdmin {\n return !!admin && typeof (admin as SQLAdmin).executeSql === \"function\";\n}\n\n/**\n * Type guard: does this admin support document operations?\n * @group Admin\n */\nexport function isDocumentAdmin(admin: DatabaseAdmin | undefined): admin is DocumentAdmin {\n return !!admin && (\n typeof (admin as DocumentAdmin).executeAggregate === \"function\" ||\n typeof (admin as DocumentAdmin).fetchCollectionStats === \"function\"\n );\n}\n\n/**\n * Type guard: does this admin support schema management?\n * @group Admin\n */\nexport function isSchemaAdmin(admin: DatabaseAdmin | undefined): admin is SchemaAdmin {\n return !!admin && (\n typeof (admin as SchemaAdmin).fetchUnmappedTables === \"function\" ||\n typeof (admin as SchemaAdmin).fetchTableMetadata === \"function\"\n );\n}\n\n/**\n * Type guard: does this admin support database branching?\n * @group Admin\n */\nexport function isBranchAdmin(admin: DatabaseAdmin | undefined): admin is BranchAdmin {\n return !!admin && typeof (admin as BranchAdmin).createBranch === \"function\";\n}\n\n// =============================================================================\n// LIFECYCLE INTERFACES (1.4)\n// =============================================================================\n\n/**\n * Health check result returned by `healthCheck()`.\n * @group Lifecycle\n */\nexport interface HealthCheckResult {\n /** Whether the backend is healthy and able to serve requests. */\n healthy: boolean;\n /** Round-trip latency to the database in milliseconds. */\n latencyMs: number;\n /** Optional details (e.g., pool stats, replication lag). */\n details?: Record<string, unknown>;\n}\n\n/**\n * Lifecycle contract for backend components that hold resources\n * (database connections, WebSocket pools, timers, etc.).\n *\n * All methods are optional — simple backends (e.g., in-memory) can skip them.\n * @group Lifecycle\n */\nexport interface BackendLifecycle {\n /**\n * Initialize the backend: open connections, run migrations, seed data.\n * Called once during startup. Idempotent.\n */\n initialize?(): Promise<void>;\n\n /**\n * Check whether the backend is healthy and reachable.\n * Should be fast (< 1 s) and safe to call frequently.\n */\n healthCheck?(): Promise<HealthCheckResult>;\n\n /**\n * Gracefully shut down: close connections, flush buffers, cancel timers.\n * After calling `destroy()`, no other methods should be called.\n */\n destroy?(): Promise<void>;\n}\n\n// =============================================================================\n// BACKEND FACTORY INTERFACES\n// =============================================================================\n\n/**\n * Configuration for creating a database backend\n */\nexport interface BackendConfig {\n /**\n * Type of database backend\n */\n type: string;\n\n /**\n * Database connection (implementation-specific)\n */\n connection: unknown;\n\n /**\n * Schema definition (implementation-specific, e.g., Drizzle schema for PostgreSQL)\n */\n schema?: unknown;\n}\n\n/**\n * A complete backend instance with all required services.\n *\n * Now includes optional lifecycle management and admin capabilities.\n */\nexport interface BackendInstance extends BackendLifecycle {\n /**\n * Entity repository for CRUD operations\n */\n entityRepository: DataRepository;\n\n /**\n * Realtime provider for subscriptions\n */\n realtimeProvider: RealtimeProvider;\n\n /**\n * Collection registry\n */\n collectionRegistry: CollectionRegistryInterface;\n\n /**\n * The underlying database connection\n */\n connection: DatabaseConnection;\n\n /**\n * Administrative operations (SQL, schema, documents).\n * What's available depends on the backend type — use type guards\n * (`isSQLAdmin`, `isSchemaAdmin`, etc.) to narrow.\n */\n admin?: DatabaseAdmin;\n}\n\n/**\n * Factory function type for creating backend instances\n */\nexport type BackendFactory<TConfig extends BackendConfig = BackendConfig> =\n (config: TConfig) => BackendInstance;\n\n// =============================================================================\n// BACKEND BOOTSTRAPPER (1.2)\n// =============================================================================\n\n/**\n * A `BackendBootstrapper` encapsulates all driver-specific initialization logic.\n *\n * Instead of hard-coding Postgres setup into `initializeRebaseBackend()`,\n * each database backend provides its own bootstrapper that knows how to:\n * - Create the DataDriver from a config object\n * - Optionally initialize auth tables\n * - Optionally create a realtime service\n * - Mount driver-specific API routes\n *\n * The main `initializeRebaseBackend()` becomes a **coordinator** that iterates\n * registered bootstrappers, calls their hooks, and wires the results together.\n *\n * @group Backend\n *\n * @example\n * ```typescript\n * // Third-party MySQL bootstrapper\n * const mysqlBootstrapper: BackendBootstrapper = {\n * type: \"mysql\",\n * initializeDriver: async (config) => new MySQLDataDriver(config.connection),\n * initializeRealtime: async (config) => new MySQLChangeStreamRealtime(config.connection),\n * };\n *\n * initializeRebaseBackend({\n * ...config,\n * bootstrappers: [postgresBootstrapper, mysqlBootstrapper]\n * });\n * ```\n */\nexport interface BackendBootstrapper {\n /**\n * Which driver type this bootstrapper handles.\n * Must match the `type` field on the driver config object\n * (e.g., `\"postgres\"`, `\"mongodb\"`, `\"mysql\"`).\n */\n type: string;\n\n /**\n * Unique identifier for this bootstrapper instance.\n * Used to register the driver in the driver registry.\n * Defaults to `type` if not set.\n */\n id?: string;\n\n /**\n * Whether this bootstrapper provides the default driver.\n * When true, the coordinator uses this driver as the primary one.\n */\n isDefault?: boolean;\n\n /**\n * Run database migrations for this driver.\n * Called by the coordinator after all drivers are initialized.\n */\n runMigrations?(config: unknown, driverResult: InitializedDriver): Promise<void>;\n\n /**\n * Create a DataDriver from the given config.\n * This is the only **required** method.\n */\n initializeDriver(config: unknown): Promise<InitializedDriver>;\n\n /**\n * Initialize auth tables / services if this driver supports them.\n * Return undefined if auth is not supported by this backend.\n */\n initializeAuth?(config: unknown, driverResult: InitializedDriver): Promise<BootstrappedAuth | undefined>;\n\n /**\n * Initialize history tables / services if this driver supports them.\n * Return undefined if history is not supported by this backend.\n */\n initializeHistory?(config: HistoryConfig, driverResult: InitializedDriver): Promise<{ historyService: unknown } | undefined>;\n\n /**\n * Create a realtime provider for this driver.\n * Return undefined if the driver does not support realtime.\n */\n initializeRealtime?(config: unknown, driverResult: InitializedDriver): Promise<RealtimeProvider | undefined>;\n\n /**\n * Mount any driver-specific HTTP routes (e.g., custom admin endpoints).\n * Called after all drivers are initialized.\n */\n mountRoutes?(app: unknown, basePath: string, driverResult: InitializedDriver): void;\n\n /**\n * Return admin capabilities for this driver.\n */\n getAdmin?(driverResult: InitializedDriver): DatabaseAdmin | undefined;\n\n /**\n * Bring the database's collection tables up to date, additively.\n *\n * Optional because it is only meaningful for schema-ful drivers. A managed\n * runtime boots a compiled project against a database it has never seen; auth\n * tables are ensured on boot but collection tables were created by nothing,\n * so every data request answered 500 on a missing relation. The CLI's `db\n * push` cannot fill the gap — it needs Atlas, and the runtime image ships no\n * CLI.\n *\n * Implementations MUST be additive-only: create missing tables, columns and\n * enum types, and never drop, narrow or rewrite anything. This runs\n * unattended against live customer data with nobody reading a diff, so the\n * destructive half stays a deliberate migration.\n *\n * `driverResult` is optional: this runs before `initializeDriver`, and only\n * the bundle path has a pre-init stand-in to pass. An adapter built by an\n * application already holds its own connection and MUST use it when this is\n * `undefined` — dereferencing it unconditionally works for managed tenants\n * and breaks every app that builds its own adapter.\n */\n ensureCollectionSchema?(\n collections: unknown[],\n driverResult?: InitializedDriver,\n log?: (message: string) => void\n ): Promise<{ applied: number }>;\n\n /**\n * Apply the collections' row-level-security policies, additively and\n * idempotently — the companion to {@link ensureCollectionSchema}.\n *\n * That method creates the tables; a table with RLS disabled and no policies\n * is not servable, because authenticated requests run as a restricted role:\n * a read with no `SELECT` policy returns nothing (a public collection\n * answers 401) and a write with no `INSERT`/`UPDATE` policy is denied. The\n * `db push` CLI applies these from the same collections, but it cannot reach\n * a managed tenant's in-cluster database — the runtime, already connected,\n * is the only thing that can.\n *\n * MUST be idempotent (re-run on every boot) and MUST NOT be destructive.\n * Runs after auth initialization, because the generated policies call the\n * `auth.*` helper functions and `CREATE POLICY` validates they exist.\n */\n ensureCollectionPolicies?(\n collections: unknown[],\n driverResult?: InitializedDriver,\n log?: (message: string) => void\n ): Promise<{ applied: number }>;\n\n /**\n * Read the collections schema version this database was last provisioned\n * from, or `null` when nothing has ever stamped it.\n *\n * The companion to {@link stampCollectionsSchemaVersion}: one process writes\n * what it applied, every other process compares itself to it. This is what\n * lets a split deployment — several processes over one database, only one of\n * which provisions — notice that a unit is serving against a schema it was\n * not built for. That failure is otherwise silent in both directions: a\n * column that does not exist is a SQL error on one route, and a policy that\n * was never applied is a 200 with no rows.\n *\n * `null` is not an error and MUST NOT be treated as one — every database\n * provisioned before the stamp existed reads this way, and so does every\n * fresh one until its first provisioning boot finishes.\n */\n readCollectionsSchemaVersion?(\n driverResult?: InitializedDriver\n ): Promise<string | null>;\n\n /**\n * Record the collections schema version this process just applied.\n *\n * Called only by the process that provisions, and only after both\n * {@link ensureCollectionSchema} and {@link ensureCollectionPolicies} have\n * run — a stamp written before the policies would claim a schema that is\n * only half in place, and the half that is missing is the one that fails\n * without an error.\n */\n stampCollectionsSchemaVersion?(\n version: string,\n driverResult?: InitializedDriver\n ): Promise<void>;\n\n /**\n * Initialize WebSocket server for realtime operations.\n */\n initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import(\"../controllers/data_driver\").DataDriver, config?: unknown, authAdapter?: AuthAdapter): Promise<void> | void;\n}\n\n/**\n * Result of `BackendBootstrapper.initializeDriver()`.\n * @group Backend\n */\nexport interface InitializedDriver {\n /** The DataDriver instance, ready for use. */\n driver: import(\"../controllers/data_driver\").DataDriver;\n\n /** The realtime service, if the driver created one during init. */\n realtimeProvider?: RealtimeProvider;\n\n /** A collection registry to register schema / tables into. */\n collectionRegistry?: CollectionRegistryInterface;\n\n /**\n * Collections the driver derived from the live database schema.\n *\n * Set by drivers that introspect in `baas` mode; the server serves these\n * instead of collections loaded from config files.\n */\n collections?: import(\"./collections\").CollectionConfig[];\n\n /** The underlying database connection (for lifecycle management). */\n connection?: DatabaseConnection;\n\n /**\n * Opaque handle that the bootstrapper can use in subsequent hooks\n * (e.g., `initializeAuth`, `mountRoutes`) to access driver internals.\n * Not used by the coordinator.\n */\n internals?: unknown;\n}\n\n/**\n * Result of `BackendBootstrapper.initializeAuth()`.\n * @group Backend\n */\nexport interface BootstrappedAuth {\n /** User management service. */\n userService: unknown;\n /** Role management service (optional, roles are now simple strings). */\n roleService?: unknown;\n /** Email service (optional). */\n emailService?: unknown;\n /** Combined Auth Repository for unified token and user management. */\n authRepository?: unknown;\n /**\n * Whether the auth schema in the database is one this runtime can serve.\n *\n * Folded into `healthCheck()` so a schema mismatch shows up as a degraded\n * health response. Without it, a server whose auth is entirely broken still\n * reports healthy — the database connection it probes is fine, and the\n * mismatch is only discovered one failed login at a time.\n */\n schemaHealthCheck?(): Promise<AuthSchemaHealth>;\n}\n\n/**\n * Result of {@link BootstrappedAuth.schemaHealthCheck}.\n * @group Lifecycle\n */\nexport interface AuthSchemaHealth {\n /** False when this runtime cannot be trusted to serve auth against this database. */\n healthy: boolean;\n /** Human-readable descriptions of each mismatch found. Empty when healthy. */\n problems: string[];\n /** Auth schema version recorded in the database, when it records one. */\n databaseVersion?: number | null;\n /** Auth schema version this runtime expects. */\n runtimeVersion?: number;\n}\n","/**\n * The vocabulary a live schema change is described in.\n *\n * Declared here, and nowhere else, because two packages that must not import\n * each other both need it: `@rebasepro/server-postgres` decides what a change\n * means and renders the files it needs, while `@rebasepro/server` commits those\n * files and serves the routes. Neither can reach the other — the server is\n * engine-agnostic by design — so the shared kernel holds the shapes and the\n * driver is detected structurally through {@link SchemaEditingAdmin}.\n *\n * Nothing here executes anything. These are the nouns.\n */\n\n/**\n * What a change will do to a live database.\n *\n * - `safe` — the boot-time ensure path expresses it, and the result matches the\n * configuration.\n * - `diverges` — the ensure path applies *something*, but the database will not\n * match what the configuration declares, and nothing reports it. This is the\n * category worth having: adding a required property to a populated table\n * yields a nullable column, and adding a value to an existing enum yields\n * nothing at all. Both read as success.\n * - `needs-migration` — the ensure path cannot express it. Dropping anything,\n * changing a type, moving a primary key.\n */\nexport type SchemaChangeVerdict = \"safe\" | \"diverges\" | \"needs-migration\";\n\nexport type SchemaChangeKind =\n | \"add-collection\"\n | \"remove-collection\"\n | \"add-property\"\n | \"remove-property\"\n | \"change-property-type\"\n | \"rename-column\"\n | \"add-enum-value\"\n | \"remove-enum-value\"\n | \"change-required\"\n | \"change-primary-key\";\n\nexport interface SchemaChange {\n kind: SchemaChangeKind;\n verdict: SchemaChangeVerdict;\n /** Collection slug. */\n collection: string;\n /** Property name, where the change is to one. */\n property?: string;\n /** One line, specific: what changed and what it will do. */\n detail: string;\n /** What to do instead, when the verdict is not `safe`. */\n remedy?: string;\n}\n\nexport interface ClassifiedSchemaChanges {\n changes: SchemaChange[];\n /** The worst verdict present, or `safe` for an empty diff. */\n verdict: SchemaChangeVerdict;\n /** True only when every change is `safe` — the one case an editor may apply. */\n applicable: boolean;\n}\n\n/**\n * Where a project's generated schema artifacts live, relative to the **project**\n * root — which is the repository root only when the project is the whole\n * repository.\n *\n * Here rather than in the Postgres package because it is a contract, not an\n * engine detail: `@rebasepro/server` has to derive these for a project in a\n * subdirectory, and it cannot import a driver to do it.\n */\nexport interface SchemaCommitPaths {\n /** Drizzle schema, imported by the backend. */\n schemaFile: string;\n /** Declarative DDL, what `db push` applies and Atlas diffs against. */\n ddlFile: string;\n policiesFile: string;\n searchFile: string;\n}\n\nexport const DEFAULT_COMMIT_PATHS: SchemaCommitPaths = {\n schemaFile: \"backend/src/schema.generated.ts\",\n ddlFile: \"drizzle/schema.sql\",\n policiesFile: \"drizzle/policies.sql\",\n searchFile: \"drizzle/search.sql\"\n};\n\n/** One file the commit writes, as content rather than as a path on a disk. */\nexport interface SchemaChangeFile {\n path: string;\n contents: string;\n}\n\n/**\n * Everything a change needs written and run.\n *\n * Computed without touching a disk or a network. The database is *read* — what\n * a change means depends on what is already there, and a plan that guessed\n * would be guessing about whether the statements it returns will be accepted.\n */\nexport interface SchemaChangePlan {\n /** Every file the commit writes — collection source and generated artifacts. */\n files: SchemaChangeFile[];\n /** The additive DDL this change adds, in dependency order. */\n statements: string[];\n classified: ClassifiedSchemaChanges;\n /** A commit message describing the change rather than announcing one. */\n message: string;\n /**\n * Constraints the configuration asks for that these statements do not\n * carry, and why.\n *\n * Almost always empty. When it is not, it is the part the person confirming\n * needs to read: the change will apply, and the database will still not\n * enforce something the configuration says — a required property over a\n * table that already holds rows with no value for it. Optional so a plan\n * from an engine that does not distinguish these cases stays valid.\n */\n withheldConstraints?: WithheldSchemaConstraint[];\n}\n\n/** A constraint a plan asks for and does not apply. */\nexport interface WithheldSchemaConstraint {\n /** `schema.table.column`. */\n target: string;\n kind: \"not-null\";\n /** What is in the way, naming the obstacle rather than the rule. */\n reason: string;\n /** What would make it applicable. */\n remedy: string;\n}\n\n/**\n * An admin that can plan a schema change.\n *\n * Planning only. Applying is `executeSql`, which every SQL admin already has,\n * and committing belongs to whatever holds the repository — keeping those three\n * apart is what lets the same plan be committed locally on a developer's machine\n * and through a GitHub App from a cloud tenant.\n *\n * @group Admin\n */\nexport interface SchemaEditingAdmin {\n /**\n * Decide what the change means and render everything it needs.\n *\n * Rejects when the change is not applicable, carrying the classification so\n * a caller can say which change was the problem.\n */\n planSchemaChange(\n before: unknown[],\n after: unknown[],\n options?: { paths?: Partial<SchemaCommitPaths> }\n ): Promise<SchemaChangePlan>;\n}\n","import { CollectionCallbacks, Properties, RebaseCallContext } from \"@rebasepro/types\";\n\n/**\n * Context passed to entity lifecycle callbacks.\n * @group Models\n */\nexport type EntityCallbackContext = RebaseCallContext;\n\n\n/**\n * Helper function to recursively check if there are any callbacks in the properties.\n */\nfunction hasPropertyCallbacks(properties: Properties, callbackName: \"afterRead\" | \"beforeSave\"): boolean {\n if (!properties) return false;\n for (const property of Object.values(properties)) {\n if (property.callbacks?.[callbackName]) return true;\n if (property.type === \"map\" && property.properties) {\n if (hasPropertyCallbacks(property.properties, callbackName)) return true;\n } else if (property.type === \"array\" && property.of) {\n const ofs = Array.isArray(property.of) ? property.of : [property.of];\n for (const of of ofs) {\n if (of.callbacks?.[callbackName]) return true;\n if (of.type === \"map\" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Recursively process properties to apply field-level hooks.\n */\nasync function processProperties(\n properties: Properties,\n values: Record<string, unknown>,\n previousValues: Record<string, unknown>,\n propsContext: unknown,\n callbackName: \"afterRead\" | \"beforeSave\"\n): Promise<Record<string, unknown>> {\n if (!values || typeof values !== \"object\") return values;\n\n const result = { ...values };\n\n for (const [key, property] of Object.entries(properties)) {\n if (result[key] === undefined) continue;\n\n let currentValue = result[key];\n const previousValue = previousValues?.[key];\n\n // 1. Array Property\n if (property.type === \"array\" && Array.isArray(currentValue)) {\n // We only support traversing single-type arrays for hooks currently to avoid complex union matching\n if (property.of && !Array.isArray(property.of)) {\n currentValue = await Promise.all(currentValue.map(async (item, index) => {\n const prevItem = Array.isArray(previousValue) ? previousValue[index] : undefined;\n // Mock a properties object to process a single item\n const singlePropData = { \"_tmp\": property.of } as Properties;\n const res = await processProperties(singlePropData, { \"_tmp\": item }, { \"_tmp\": prevItem }, propsContext, callbackName);\n return res[\"_tmp\"];\n }));\n }\n }\n // 2. Map Property\n else if (property.type === \"map\" && property.properties && typeof currentValue === \"object\") {\n currentValue = await processProperties(property.properties, currentValue as Record<string, unknown>, (previousValue ?? {}) as Record<string, unknown>, propsContext, callbackName);\n }\n\n // 3. Property's own callback\n if (property.callbacks?.[callbackName]) {\n\n const cbRes = await Promise.resolve(property.callbacks[callbackName]({\n ...(propsContext as Record<string, unknown>),\n value: currentValue,\n previousValue\n } as never));\n if (cbRes !== undefined) {\n currentValue = cbRes;\n }\n }\n\n result[key] = currentValue;\n }\n return result;\n}\n\n/**\n * Helper function to extract field-level PropertyCallbacks from a properties schema\n * and wrap them into an CollectionCallbacks object recursively.\n */\nexport const buildPropertyCallbacks = (properties: Properties): CollectionCallbacks | undefined => {\n if (!properties) return undefined;\n\n const propertyCallbacks: CollectionCallbacks = {};\n\n if (hasPropertyCallbacks(properties, \"afterRead\")) {\n propertyCallbacks.afterRead = async (props) => {\n const row = props.row;\n const processedValues = await processProperties(\n properties,\n row,\n row,\n props as unknown,\n \"afterRead\"\n );\n return { ...props.row, ...processedValues };\n };\n }\n\n if (hasPropertyCallbacks(properties, \"beforeSave\")) {\n propertyCallbacks.beforeSave = async (props) => {\n return await processProperties(\n properties,\n props.values as Record<string, unknown>,\n (props.previousValues ?? {}) as Record<string, unknown>,\n props as unknown,\n \"beforeSave\"\n );\n };\n }\n\n return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : undefined;\n};\n","/**\n * The `FilterValues` grammar, one level below the wire codec.\n *\n * A field's filter is either one `[op, value]` tuple or an **array** of them —\n * `{ age: [[\">=\", 18], [\"<\", 65]] }` — which is what the fluent builder produces\n * from two `.where()` calls on the same column. Reading that shape is grammar,\n * not a driver detail, so every compiler reads it through here.\n *\n * It lived only inside the Postgres compiler, and the Mongo one destructured\n * `const [op, value] = filterParam` regardless: given the array-of-tuples form\n * `op` bound to `[\">=\", 18]`, no operator matched, and the condition was\n * dropped. Both of them. A read asking for adults under 65 returned every row\n * of the collection with a 200.\n *\n * @module\n */\n\nimport type { WhereFilterOp } from \"@rebasepro/types\";\n\n/** One `[operator, value]` condition. */\nexport type FilterTuple = [WhereFilterOp, unknown];\n\n/**\n * Read one field's filter as the list of conditions it stands for.\n *\n * Accepts both declared shapes and normalises them to a list:\n *\n * ```ts\n * toFilterTuples([\"==\", \"active\"]) // [[\"==\", \"active\"]]\n * toFilterTuples([[\">=\", 18], [\"<\", 65]]) // [[\">=\", 18], [\"<\", 65]]\n * ```\n *\n * A falsy, non-array or empty param has no conditions in it — the empty list,\n * so a caller iterating adds nothing rather than compiling a tuple of\n * `undefined`s and logging about an operator nobody sent.\n */\nexport function toFilterTuples(filterParam: unknown): FilterTuple[] {\n if (!filterParam || !Array.isArray(filterParam) || filterParam.length === 0) return [];\n // The first element discriminates: a condition starts with an operator\n // string, a list of conditions starts with a condition. `[\"in\", [\"a\",\"b\"]]`\n // is one condition whose value happens to be a list.\n if (Array.isArray(filterParam[0])) {\n return filterParam as FilterTuple[];\n }\n return [filterParam as FilterTuple];\n}\n","/**\n * Table Classification\n *\n * Shared constants and pure functions for classifying database tables.\n * Used by both the server-side PostgresBackendDriver and the Studio RLS editor.\n */\n\n/** Possible categories a database table can belong to. */\nexport type TableCategory = \"rebase-internal\" | \"junction\" | \"user\";\n\n/** Schemas that are always considered Rebase-internal. */\nexport const REBASE_INTERNAL_SCHEMAS: readonly string[] = [\"rebase\", \"auth\"];\n\n/** Table-name prefixes that mark a table as Rebase-internal regardless of schema. */\nexport const REBASE_INTERNAL_PREFIXES: readonly string[] = [\n \"_rebase_\",\n \"_auth_\",\n \"drizzle_\",\n];\n\n/**\n * Synchronously classify a table based on naming conventions.\n *\n * @param tableName - The unqualified name of the table.\n * @param schemaName - The schema the table belongs to (e.g. `\"public\"`, `\"rebase\"`).\n * @returns `\"rebase-internal\"` when the table belongs to a reserved schema or\n * carries a reserved prefix; `\"user\"` otherwise.\n *\n * @remarks\n * Junction-table detection requires an async database query and is therefore\n * **not** handled by this function. Use {@link detectJunctionTables} to obtain\n * the set of junction tables, then reclassify as needed.\n */\nexport function classifyTable(\n tableName: string,\n schemaName: string,\n): TableCategory {\n if (\n REBASE_INTERNAL_SCHEMAS.includes(schemaName) ||\n REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))\n ) {\n return \"rebase-internal\";\n }\n\n return \"user\";\n}\n\n/**\n * Convenience predicate that checks whether a table is Rebase-internal.\n *\n * @param tableName - The unqualified name of the table.\n * @param schemaName - The schema the table belongs to.\n * @returns `true` if the table is classified as `\"rebase-internal\"`.\n */\nexport function isRebaseInternalTable(\n tableName: string,\n schemaName: string,\n): boolean {\n return classifyTable(tableName, schemaName) === \"rebase-internal\";\n}\n\n/** SQL query that detects junction tables in the `public` schema. */\nexport const JUNCTION_TABLES_SQL = `\n SELECT t.table_name\n FROM information_schema.tables t\n WHERE t.table_schema = 'public'\n AND t.table_type = 'BASE TABLE'\n AND NOT EXISTS (\n SELECT 1\n FROM information_schema.columns c\n WHERE c.table_schema = t.table_schema\n AND c.table_name = t.table_name\n AND c.column_name NOT IN (\n SELECT kcu.column_name\n FROM information_schema.key_column_usage kcu\n JOIN information_schema.table_constraints tc\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY'\n AND kcu.table_schema = t.table_schema\n AND kcu.table_name = t.table_name\n )\n )\n`;\n\n/**\n * Asynchronously detect junction (link) tables in the `public` schema.\n *\n * A junction table is defined as a table where **every** column participates in\n * at least one foreign-key constraint.\n *\n * @param executeSql - A callback that executes a raw SQL string and returns the\n * resulting rows.\n * @returns A `Set` containing the names of all detected junction tables.\n */\nexport async function detectJunctionTables(\n executeSql: (sql: string) => Promise<Record<string, unknown>[]>,\n): Promise<Set<string>> {\n const rows = await executeSql(JUNCTION_TABLES_SQL);\n const junctionTables = new Set<string>();\n\n for (const row of rows) {\n if (typeof row.table_name === \"string\") {\n junctionTables.add(row.table_name);\n }\n }\n\n return junctionTables;\n}\n","import { PgTable, AnyPgColumn } from \"drizzle-orm/pg-core\";\nimport { getTableColumns } from \"drizzle-orm\";\nimport { CollectionConfig, Property, ResolvedHasMany, ResolvedHasOne } from \"@rebasepro/types\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { fieldKeyForColumn, getTableName } 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 } 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 const raw = col as unknown as Record<string | symbol, unknown>;\n return {\n columnType: typeof raw.columnType === \"string\" ? raw.columnType : undefined,\n dataType: typeof raw.dataType === \"string\" ? raw.dataType : undefined,\n primary: typeof raw.primary === \"boolean\" ? raw.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\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 * 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.data` 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 // 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 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 \"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 * drizzle schema, and finally a column literally named `id`. Only the first is\n * visible to the browser, which is why a key known only to drizzle is reported\n * at boot — see {@link warnOnKeysTheAdminCannotResolve}.\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 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 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 * 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[] }) =>\n `${f.collection.slug}: mark ${f.keys.map(k => `\\`${k.fieldName}\\``).join(\" and \")} with ` +\n `\\`isId: ${f.keys[0].isUUID ? \"\\\"uuid\\\"\" : f.keys[0].type === \"number\" ? \"\\\"increment\\\"\" : \"true\"}\\``;\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, or, sql, SQL, ilike, inArray, getTableColumns } 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 RelationAggregateFn, type RelationAggregateSort\n} from \"@rebasepro/types\";\nimport {\n fieldKeyForColumn, getColumnName, getTableName, normalizeToEntityRelation, resolveCollectionRelations, toFilterTuples\n} from \"@rebasepro/common\";\nimport { generateForeignKeyName, 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;\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 { 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 * 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 */\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/** 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 * 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 parent: () => { table: PgTable<any>; idColumn: AnyPgColumn },\n parentId: string | number,\n targetTable: PgTable<any>,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry\n ): SQL {\n switch (relation.kind) {\n case \"via\": {\n const { table, idColumn } = parent();\n return this.buildJoinPathScopeCondition(\n relation.joinPath, table, idColumn, parentId, 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 new Error(`Junction table not found: ${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, idColumn } = parent();\n return sql`${fkColumn} = (SELECT ${sql.identifier(relation.sourceKey)} FROM ${table} WHERE ${idColumn} = ${parentId})`;\n }\n\n case \"belongsTo\": {\n // The single target row the parent's foreign key points at.\n const { table, idColumn } = parent();\n return sql`${targetIdColumn} = (SELECT ${sql.identifier(relation.localKey)} FROM ${table} WHERE ${idColumn} = ${parentId})`;\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 parentIdColumn: AnyPgColumn,\n parentId: 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 = targetTable[getColumnName(name) as keyof typeof targetTable] as AnyPgColumn;\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 return sql`EXISTS (SELECT 1 FROM ${parentTable} AS ${sql.identifier(sourceAlias)}${joinsSql} WHERE ${sql.identifier(sourceAlias)}.${sql.identifier(parentIdColumn.name)} = ${parentId} 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 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 let validFields: string[] = [];\n try {\n validFields = Object.keys(getTableColumns(table)).sort();\n } catch {\n // A table stand-in without Drizzle's column symbols — the message\n // is worth less without the list, but not worth failing over.\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 throw ApiError.badRequest(\n `Unknown filter field '${field}' on collection '${collectionPath}'` +\n (validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\"),\n \"UNKNOWN_FILTER_FIELD\",\n { field, collection: collectionPath, ...(validFields.length > 0 && { validFields }) }\n );\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 let validFields: string[] = [];\n try {\n validFields = Object.keys(getTableColumns(targetTable)).sort();\n } catch {\n // Same tolerance as the column path: a table stand-in without\n // Drizzle's column symbols still gets the error, just no list.\n }\n throw ApiError.badRequest(\n `Unknown field '${path.fieldKey}' on '${targetCollection.slug}', the target of relation ` +\n `'${path.relationKey}' on collection '${collectionPath}'` +\n (validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\"),\n \"UNKNOWN_FILTER_FIELD\",\n {\n field,\n collection: collectionPath,\n relation: path.relationKey,\n targetCollection: targetCollection.slug,\n ...(validFields.length > 0 && { validFields })\n }\n );\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 return (cond.type === \"or\" ? or(...subSQLs) : and(...subSQLs)) ?? null;\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 new Error(`Junction table not found: ${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 new Error(`Junction table not found: ${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 return { predicate: sql`${ref} LIKE ${String(value)}`, negate: false };\n case \"not-like\":\n return { predicate: sql`${ref} LIKE ${String(value)}`, negate: true };\n case \"ilike\":\n case \"not-ilike\": {\n // `ILIKE` is only defined on the text family. Asking it of a\n // date or an integer is a Postgres error at execution time — a\n // 500 on a request whose only fault is an operator the admin\n // offered for the wrong column.\n if (!supportsILike(column)) {\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 case-insensitive ` +\n \"matching is only defined on text.\",\n \"UNSUPPORTED_RELATION_FILTER_OPERATOR\",\n { field, collection: collectionPath, operator: op }\n );\n }\n return { predicate: sql`${ref} ILIKE ${String(value)}`, negate: op === \"not-ilike\" };\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 const relation = 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 = 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 let validFields: string[] = [];\n try {\n validFields = Object.keys(getTableColumns(targetTable)).sort();\n } catch {\n // A table stand-in without Drizzle's column symbols.\n }\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 new Error(`Junction table not found: ${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 return sql`${column} LIKE ${String(value)}`;\n case \"ilike\":\n return sql`${column} ILIKE ${String(value)}`;\n case \"not-like\":\n return sql`${column} NOT LIKE ${String(value)}`;\n case \"not-ilike\":\n return sql`${column} NOT ILIKE ${String(value)}`;\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.data`, 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\n // Process join steps in reverse order to build path back to parent\n for (const joinStep of [...joinPath].reverse()) {\n const fromTableName = this.getTableNamesFromColumns(joinStep.on.from)[0];\n const toTableName = this.getTableNamesFromColumns(joinStep.on.to)[0];\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 // Try to get table names from the Drizzle table objects\n let currentTableName = \"unknown\";\n let parentTableName = \"unknown\";\n\n // Try multiple ways to extract table names from Drizzle objects\n if (currentTable && typeof currentTable === \"object\") {\n // Check common Drizzle table name properties\n currentTableName = (currentTable as unknown as Record<string | symbol, unknown>)[Symbol.for(\"drizzle:Name\")] as string ||\n ((currentTable as unknown as Record<string, unknown>)._ as Record<string, unknown>)?.name as string ||\n (currentTable as unknown as Record<string, unknown>).tableName as string ||\n (currentTable as unknown as Record<string, unknown>).name as string ||\n \"unknown\";\n }\n\n if (parentTable && typeof parentTable === \"object\") {\n parentTableName = (parentTable as unknown as Record<string | symbol, unknown>)[Symbol.for(\"drizzle:Name\")] as string ||\n ((parentTable as unknown as Record<string, unknown>)._ as Record<string, unknown>)?.name as string ||\n (parentTable as unknown as Record<string, unknown>).tableName as string ||\n (parentTable as unknown as Record<string, unknown>).name as string ||\n \"unknown\";\n }\n\n // For junction table scenarios, be more lenient with validation\n // If we can't determine table names reliably, or if this looks like a junction table scenario,\n // we'll allow it and let the SQL execution validate the correctness\n const couldBeJunctionScenario = currentTableName.includes(\"_\") ||\n currentTableName === \"unknown\" ||\n parentTableName === \"unknown\";\n\n if (!couldBeJunctionScenario) {\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 const left = fromTable[fromColName as keyof typeof fromTable] as AnyPgColumn;\n const right = (currentTable as unknown as Record<string, unknown>)[toColName] as AnyPgColumn;\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 = toTable[toColName as keyof typeof toTable] as AnyPgColumn;\n const right = (currentTable as unknown as Record<string, unknown>)[fromColName] as AnyPgColumn;\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 new Error(`Junction table not found: ${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** — the original `ILIKE '%term%'` OR-ed across top-level\n * string properties, with the term escaped (see {@link escapeLikePattern})\n * so it is matched as the literal text the user typed.\n *\n * The second is the default and stays the default. A collection that has\n * not opted in compiles to exactly the SQL it compiled to before this\n * branch existed, which is the only reason it is safe to have added it.\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\n for (const [key, prop] of Object.entries(properties)) {\n const p = prop as Record<string, unknown>;\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 searchConditions.push(ilike(fieldColumn, `%${escapeLikePattern(searchString)}%`));\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 && searchConditions.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\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 if (!spec.fuzzy) return exact;\n\n const fuzzyColumn = table[spec.fuzzy.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!fuzzyColumn) return exact;\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`(${exact} OR ${fuzzy})`;\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 const rows = spec.fields.map((f, i) =>\n sql`(${i}, ${f.path}, ${sql.raw(f.textSql)})`\n );\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)\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 to_tsvector(${config}::regconfig, f.txt) @@ ${query}\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 rank = sql`ts_rank(${column}, ${DrizzleConditionBuilder.normalizedTsQuery(searchString, spec)})`;\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\n */\n static buildUniqueFieldCondition(\n fieldColumn: AnyPgColumn,\n value: unknown,\n idColumn?: AnyPgColumn,\n excludeId?: string | number\n ): SQL[] {\n const conditions: SQL[] = [eq(fieldColumn, value)];\n\n if (excludeId && idColumn) {\n conditions.push(sql`${idColumn} != ${excludeId}`);\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\n // Process join steps in reverse order\n for (const joinStep of [...joinPath].reverse()) {\n const fromTableName = this.getTableNamesFromColumns(joinStep.on.from)[0];\n const toTableName = this.getTableNamesFromColumns(joinStep.on.to)[0];\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 new Error(`Junction table not found: ${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 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 ): { orderBy: SQL; filter?: SQL; distanceSelect: SQL } {\n const column = DrizzleConditionBuilder.resolveVectorColumn(table, vectorSearch.property);\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 private static resolveVectorColumn(table: PgTable<any>, property: string): AnyPgColumn {\n const columns = getTableColumns(table) as Record<string, AnyPgColumn> | undefined;\n const column = columns?.[property];\n if (!column) {\n const known = Object.entries(columns ?? {})\n .filter(([, c]) => isVectorColumn(c))\n .map(([name]) => name);\n throw ApiError.badRequest(\n `Unknown vector property \"${property}\". ` +\n (known.length > 0\n ? `This collection's vector properties are: ${known.join(\", \")}.`\n : \"This collection declares no `vector` property to search.\"),\n \"UNKNOWN_VECTOR_PROPERTY\"\n );\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 { eq, SQL } 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 { DrizzleConditionBuilder } from \"./utils/drizzle-conditions\";\nimport { getPrimaryKeys, buildCompositeId } from \"./services/collection-helpers\";\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 */\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 \"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 // Parse the join condition - handle both string and array formats\n const fromColumn = Array.isArray(join.on.from) ? join.on.from[0] : join.on.from;\n const toColumn = Array.isArray(join.on.to) ? join.on.to[0] : join.on.to;\n\n const fromParts = fromColumn.split(\".\");\n const toParts = toColumn.split(\".\");\n\n const fromColName = fromParts[fromParts.length - 1];\n const toColName = toParts[toParts.length - 1];\n\n const fromCol = currentTable[fromColName as keyof typeof currentTable] as AnyPgColumn;\n const toCol = joinTable[toColName as keyof typeof joinTable] as AnyPgColumn;\n\n if (!fromCol || !toCol) {\n logger.warn(`Join columns not found: ${fromColumn} -> ${toColumn}`);\n break;\n }\n\n query = query.innerJoin(joinTable, eq(fromCol, toCol)) as unknown as typeof query;\n currentTable = joinTable;\n }\n\n // Add where condition for the current row\n if (pks.length === 1) {\n const sourceIdField = sourceTable[pks[0].fieldName as keyof typeof sourceTable] as AnyPgColumn;\n query = query.where(eq(sourceIdField, currentId)) as typeof query;\n } else {\n // For composite keys, we would need to map the split parts. For now log a warning.\n logger.warn(`Join path resolution for composite primary keys is not yet fully supported: ${collection.slug}`);\n }\n\n // Build additional conditions array\n const additionalFilters: SQL[] = [];\n\n // Combine parent condition with additional filters using AND\n let combinedWhere: SQL | undefined;\n\n if (pks.length === 1) {\n const sourceIdField = sourceTable[pks[0].fieldName as keyof typeof sourceTable] as AnyPgColumn;\n combinedWhere = DrizzleConditionBuilder.combineConditionsWithAnd([\n eq(sourceIdField, currentId),\n ...additionalFilters\n ].filter(Boolean) as SQL[]);\n }\n\n // Execute the query\n const joinResults = await query.where(combinedWhere).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 (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 { 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 } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport type { 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\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 },\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 };\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 };\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 * 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, so a link the caller cannot\n * read is in neither list 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 */\nexport async function applyJunctionMembership(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n targetIds: unknown[]\n): Promise<void> {\n const existingRows = await tx\n .select({ targetId: binding.targetColumn })\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 for (const row of existingRows as Array<{ targetId: unknown }>) {\n if (row.targetId === null || row.targetId === undefined) continue;\n existingById.set(String(row.targetId), row.targetId);\n }\n\n const wantedById = new Map<string, unknown>();\n for (const targetId of targetIds) {\n if (targetId === null || targetId === undefined) continue;\n wantedById.set(String(targetId), targetId);\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(([, value]) => value);\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(targetId => ({\n [binding.parentColumn.name]: parentId,\n [binding.targetColumn.name]: targetId\n })))\n .onConflictDoNothing();\n }\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, eq, inArray, 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, resolveCollectionRelations, findRelation, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { hasForeignKeyOnTarget, isManyToMany, type ResolvedVia } from \"@rebasepro/types\";\nimport { DrizzleConditionBuilder } from \"../utils/drizzle-conditions\";\nimport {\n getCollectionByPath,\n getTableForCollection,\n relationMisconfigured,\n requirePrimaryKeys,\n parseIdValues,\n buildCompositeId,\n joinsOnNaturalKey,\n sourceKeyField,\n type PrimaryKeyInfo\n} from \"./collection-helpers\";\nimport { parseDataFromServer } from \"../data-transformer\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport type { NestedPathHop } from \"./nested-path\";\nimport {\n applyJunctionMembership,\n bindJoinPathJunction,\n bindThroughJunction,\n removeJunctionLink\n} from \"./junction-writes\";\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 */\nfunction 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 * 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 unknown as T;\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\nexport class RelationService {\n constructor(private db: DrizzleClient, private registry: PostgresCollectionRegistry) { }\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?: { resolveNested?: boolean }\n ): Promise<RelatedRow<M>> {\n const values = options?.resolveNested\n ? await parseDataFromServer(targetRow, targetCollection, this.db, this.registry)\n : await parseDataFromServer(targetRow, targetCollection);\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 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 throw new Error(`Relation '${relationKey}' not found in collection '${parentCollectionPath}'. Available relations: [${available}]`);\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 parentTable = this.registry.getTable(getTableName(parentCollection));\n if (!parentTable) throw new Error(\"Parent table not found\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\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 fromColumn = Array.isArray(join.on.from) ? join.on.from[0] : join.on.from;\n const toColumn = Array.isArray(join.on.to) ? join.on.to[0] : join.on.to;\n\n const fromParts = fromColumn.split(\".\");\n const toParts = toColumn.split(\".\");\n\n const fromColName = fromParts[fromParts.length - 1];\n const toColName = toParts[toParts.length - 1];\n\n const fromCol = currentTable[fromColName as keyof typeof currentTable] as AnyPgColumn;\n const toCol = joinTable[toColName as keyof typeof joinTable] as AnyPgColumn;\n\n if (!fromCol || !toCol) {\n throw new Error(`Join columns not found: ${fromColumn} -> ${toColumn}`);\n }\n\n query = applyDynamicJoin(query, joinTable, eq(fromCol, toCol));\n currentTable = joinTable;\n }\n\n // Add where condition for the parent row\n const parentIdField = parentTable[requirePrimaryKeys(parentCollection, this.registry)[0].fieldName as keyof typeof parentTable] as AnyPgColumn;\n query = query.where(eq(parentIdField, parsedParentId));\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 // 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 throw new Error(`Relation '${relationKey}' not found in collection '${parentCollectionPath}'. Available relations: [${available}]`);\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 parentTable = this.registry.getTable(getTableName(parentCollection));\n if (!parentTable) throw new Error(\"Parent table not found\");\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 // 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 // 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 async batchFetchRelatedEntities(\n parentCollectionPath: string,\n parentIds: (string | number)[],\n _relationKey: string,\n relation: ResolvedRelation\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 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 parentTable = this.registry.getTable(getTableName(parentCollection));\n if (!parentTable) throw new Error(\"Parent table not found\");\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 // 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 fromColumn = Array.isArray(join.on.from) ? join.on.from[0] : join.on.from;\n const toColumn = Array.isArray(join.on.to) ? join.on.to[0] : join.on.to;\n\n const fromParts = fromColumn.split(\".\");\n const toParts = toColumn.split(\".\");\n\n const fromColName = fromParts[fromParts.length - 1];\n const toColName = toParts[toParts.length - 1];\n\n const fromCol = currentTable[fromColName as keyof typeof currentTable] as AnyPgColumn;\n const toCol = joinTable[toColName as keyof typeof joinTable] as AnyPgColumn;\n\n if (!fromCol || !toCol) {\n throw new Error(`Join columns not found: ${fromColumn} -> ${toColumn}`);\n }\n\n query = applyDynamicJoin(query, joinTable, eq(fromCol, toCol));\n currentTable = joinTable;\n }\n\n // Match every parent at once, each by its whole key.\n query = query.where(this.parentKeyCondition(parentTable, parentPks, parentIds));\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)\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: Fetch all FK values from parent table in ONE query\n const fkRows = await this.db\n .select({\n parentId: parentIdCol,\n fkValue: localKeyCol\n })\n .from(parentTable)\n .where(inArray(parentIdCol, parsedParentIds));\n\n // Build parentId → fkValue mapping and collect unique FK values\n const parentToFk = new Map<string, string | number>();\n const uniqueFkValues: (string | number)[] = [];\n const seenFks = new Set<string>();\n\n for (const row of fkRows as Array<{ parentId: string | number; fkValue: string | number | null }>) {\n if (row.fkValue == null) continue;\n parentToFk.set(String(row.parentId), row.fkValue);\n const fkStr = String(row.fkValue);\n if (!seenFks.has(fkStr)) {\n seenFks.add(fkStr);\n uniqueFkValues.push(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(inArray(targetIdField, uniqueFkValues));\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));\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 []\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));\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 async batchFetchRelatedEntitiesMany(\n parentCollectionPath: string,\n parentIds: (string | number)[],\n _relationKey: string,\n relation: ResolvedRelation\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 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 parentTable = this.registry.getTable(getTableName(parentCollection));\n if (!parentTable) throw new Error(\"Parent table not found\");\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 // Handle join path relations (many-to-many through junction tables)\n if (relation.kind === \"via\") {\n let query = this.db.select().from(parentTable).$dynamic();\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 fromColumn = Array.isArray(join.on.from) ? join.on.from[0] : join.on.from;\n const toColumn = Array.isArray(join.on.to) ? join.on.to[0] : join.on.to;\n const fromColName = fromColumn.split(\".\").pop()!;\n const toColName = toColumn.split(\".\").pop()!;\n\n const fromCol = currentTable[fromColName as keyof typeof currentTable] as AnyPgColumn;\n const toCol = joinTable[toColName as keyof typeof joinTable] as AnyPgColumn;\n if (!fromCol || !toCol) throw new Error(`Join columns not found: ${fromColumn} -> ${toColumn}`);\n\n query = applyDynamicJoin(query, joinTable, eq(fromCol, toCol));\n currentTable = joinTable;\n }\n\n query = query.where(this.parentKeyCondition(parentTable, parentPks, parentIds));\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 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 const arr = resultMap.get(parentId) || [];\n arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));\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 const query = this.db\n .select()\n .from(junctionTable)\n .innerJoin(targetTable, eq(targetJunctionCol, targetIdField))\n .where(inArray(sourceJunctionCol, parsedParentIds));\n\n const results = await query;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>[]>();\n const targetTableName = getTableName(targetCollection);\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 const arr = resultMap.get(parentId) || [];\n arr.push(await this.toRelatedRow(targetData, targetCollection, targetPks));\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 let query = this.db.select().from(targetTable).$dynamic();\n\n query = applyDynamicRelationQuery(\n query,\n query,\n relation,\n matchValues,\n targetTable,\n parentTable,\n parentIdCol,\n targetIdField,\n this.registry,\n []\n );\n\n const results = await query;\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 const arr = resultMap.get(key) || [];\n arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));\n resultMap.set(key, arr);\n }\n }\n\n return resultMap;\n }\n}\n","import { CollectionConfig, Property, ResolvedRelation, isManyToMany, type ResolvedVia } from \"@rebasepro/types\";\nimport { resolveCollectionRelations, findRelation, createRelationRefWithData } from \"@rebasepro/common\";\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.data` / `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 */\nfunction unwrapJunctionRow(item: Record<string, unknown>): Record<string, unknown> {\n const nestedKey = Object.keys(item).find(\n key => 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/** Apply {@link coerceDeclaredNumber} across a row, leaving every other column alone. */\nfunction coerceDeclaredNumbers(\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] = coerceDeclaredNumber(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 the collection marked `excludeFromApi`.\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 */\nfunction stripExcluded(\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 for (const [key, property] of Object.entries(properties)) {\n if (!property?.excludeFromApi) continue;\n delete row[key];\n if (property.columnName) delete row[property.columnName];\n }\n return row;\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 stripExcluded(coerceDeclaredNumbers({ ...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 values: normalizeDbValues(targetRow, targetCollection)\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) : 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 stripExcluded(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}). Dates stay as the database returned them —\n * JSON has its own opinions about dates that the admin's view-model does not\n * share.\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) : 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] = coerceDeclaredNumber(value, collection.properties?.[key] as Property | undefined);\n }\n }\n\n return stripExcluded(flat, collection);\n}\n","import { CollectionConfig, ResolvedRelation } from \"@rebasepro/types\";\nimport { findRelation, resolveCollectionRelations } from \"@rebasepro/common\";\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 const relation = findRelation(resolvedRelations, relationKey);\n\n if (!relation) {\n const available = Object.keys(resolvedRelations).join(\", \") || \"(none)\";\n throw new Error(\n `Relation '${relationKey}' not found in collection '${parentCollection.slug}'. Available relations: [${available}]`\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","/**\n * Shared PostgreSQL error extraction and user-friendly message formatting.\n *\n * Drizzle wraps native PG errors in a `.cause` chain. These utilities\n * unwrap that chain to get the real PostgreSQL error (identified by a\n * 5-character alphanumeric `code` such as `42P01`) and translate it into\n * a message that is safe and helpful to show to end-users.\n */\n\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Shape of a deliberate client-facing error — `ApiError` from\n * `@rebasepro/server`, or anything else carrying a 4xx `statusCode`.\n *\n * Matched structurally rather than with `instanceof`: `@rebasepro/server` can\n * be loaded twice (published dist vs. workspace source), which breaks class\n * identity — `PersistService` hedges against the same thing by also accepting\n * `name === \"ApiError\"`.\n */\ninterface ClientFacingError extends Error {\n statusCode?: number;\n code?: string;\n /** See `ApiError.expected` — routine outcomes log at debug, not warn. */\n expected?: boolean;\n}\n\n/**\n * Return the error when it is a deliberate 4xx, otherwise null.\n *\n * A thrown `ApiError` is a decision the server made about the request, not a\n * database failure: its message and code are already written for the client.\n */\nfunction asClientFacingError(error: unknown): ClientFacingError | null {\n if (!(error instanceof Error)) return null;\n const e = error as ClientFacingError;\n if (typeof e.statusCode !== \"number\" || e.statusCode < 400 || e.statusCode >= 500) return null;\n return e;\n}\n\n/** Shape of PostgreSQL errors with diagnostic metadata. */\nexport interface PostgresError extends Error {\n code?: string;\n detail?: string;\n hint?: string;\n constraint?: string;\n column?: string;\n table?: string;\n dataType?: string;\n cause?: unknown;\n}\n\n/**\n * Extract the underlying PostgreSQL error from a Drizzle wrapper.\n * Drizzle wraps PG errors in a `cause` property — this function\n * recursively walks the chain until it finds an object with a PG\n * error code (5-char alphanumeric, e.g. `42P01`).\n */\nexport function extractPgError(error: unknown): PostgresError | null {\n if (!error || typeof error !== \"object\") return null;\n if (!(error instanceof Error)) {\n // Check non-Error objects for a cause chain (Drizzle sometimes wraps oddly)\n if (\"cause\" in error && (error as Record<string, unknown>).cause && typeof (error as Record<string, unknown>).cause === \"object\") {\n return extractPgError((error as Record<string, unknown>).cause);\n }\n return null;\n }\n\n // Check if the error itself has a PG error code\n if (\"code\" in error && typeof (error as PostgresError).code === \"string\" && /^[0-9A-Z]{5}$/.test((error as PostgresError).code!)) {\n return error as PostgresError;\n }\n\n // Check the cause chain (Drizzle wraps PG errors)\n if (error.cause && typeof error.cause === \"object\") {\n return extractPgError(error.cause);\n }\n\n return null;\n}\n\n/**\n * Whether the failure came back from Postgres rather than from building the\n * query — which decides whether a fallback query is worth issuing.\n *\n * Reads here run inside a transaction (that is where `SET LOCAL ROLE` binds\n * RLS). Once a statement raises, that transaction is aborted, and every later\n * statement on it returns `25P02` — \"current transaction is aborted, commands\n * ignored until end of transaction block\". So a retry after a database error\n * cannot succeed, and it replaces a precise diagnosis (\"invalid input syntax\n * for type uuid\") with a generic one. Rethrow instead.\n *\n * A query the driver could not even build — a missing reciprocal relation, say\n * — never reached Postgres, leaves the transaction usable, and is exactly what\n * the fallback paths exist for.\n */\nexport function reachedDatabase(error: unknown): boolean {\n return extractPgError(error) !== null;\n}\n\n/**\n * Walk the error cause chain and return the deepest meaningful message.\n */\nexport function extractCauseMessage(error: unknown): string | null {\n if (!error || typeof error !== \"object\") return null;\n if (!(error instanceof Error)) return null;\n\n if (error.cause && typeof error.cause === \"object\") {\n const deeper = extractCauseMessage(error.cause);\n if (deeper) return deeper;\n // The cause itself has a message\n if (error.cause instanceof Error && error.cause.message) {\n return error.cause.message;\n }\n }\n return null;\n}\n\n/**\n * Codes that mean \"this connection will never work as configured\".\n *\n * A wrong password or a database that does not exist is a settled fact about\n * the connection string, not a transient fault — retrying produces the same\n * answer forever.\n */\nconst UNRECOVERABLE_CONNECT_CODES = new Set([\n \"28P01\", // invalid_password\n \"28000\", // invalid_authorization_specification\n \"3D000\", // invalid_catalog_name — the database does not exist\n \"42501\" // insufficient_privilege\n]);\n\nexport interface ConnectFailure {\n /** True when retrying cannot help: the connection string itself is wrong. */\n fatal: boolean;\n /** The deepest message available — the Postgres one where there is one. */\n reason: string;\n /** The `SQLSTATE`, when the failure came from Postgres rather than the socket. */\n code?: string;\n}\n\n/**\n * Describe a failed connection attempt in terms a developer can act on.\n *\n * The error a caller catches is Drizzle's wrapper: its message is\n * `Failed query: SELECT 1` and its stack runs through drizzle internals, while\n * the sentence that says what is actually wrong — \"password authentication\n * failed for user …\", \"database … does not exist\" — sits in `.cause`. Logging\n * the wrapper, as the bootstrapper used to, tells a developer with a typo in\n * their `DATABASE_URL` nothing at all.\n */\nexport function classifyConnectFailure(error: unknown): ConnectFailure {\n const pgError = extractPgError(error);\n const reason =\n pgError?.message ??\n extractCauseMessage(error) ??\n (error instanceof Error ? error.message : String(error));\n return {\n fatal: Boolean(pgError?.code && UNRECOVERABLE_CONNECT_CODES.has(pgError.code)),\n reason,\n code: pgError?.code\n };\n}\n\n/**\n * Detect whether an error is specifically a role-switching permission failure\n * (e.g. \"permission denied to set role\" or \"must be member of role\"),\n * as opposed to a table-level permission denial.\n *\n * This is used by the backend driver to auto-disable role switching when the\n * connection user lacks SET ROLE privileges, rather than surfacing a confusing\n * error to the Studio SQL Editor user.\n */\nexport function isRoleSwitchingPermissionError(error: unknown): boolean {\n const pgError = extractPgError(error);\n if (!pgError || pgError.code !== \"42501\") return false;\n const msg = pgError.message.toLowerCase();\n return msg.includes(\"set role\") || msg.includes(\"member of role\");\n}\n\n/**\n * Was this `42501` the *caller* being refused by a policy, rather than the\n * server lacking a privilege?\n *\n * Both arrive as `insufficient_privilege`, and they are opposite kinds of\n * problem. A row-level-security refusal is a working access-control system\n * doing its job: the caller asked for something their policies do not permit,\n * which is a 403 and nobody's bug. A missing `GRANT` is the deployment being\n * wrong — the connection role cannot touch the table at all, no policy is\n * involved, and nothing the caller changes about the request will help.\n *\n * Postgres distinguishes them in the message, so this does too:\n *\n * new row violates row-level security policy for table \"notes\" → the caller\n * permission denied for table notes → the server\n *\n * Only writes reach this. A read that RLS excludes is not an error — the rows\n * are filtered and the caller gets an empty page — so the erroring case is\n * specifically an `INSERT`/`UPDATE` whose row fails a policy's `WITH CHECK`.\n *\n * Matched on the message because that is the only thing carrying the\n * distinction; the SQLSTATE is identical either way. Narrow by design: anything\n * not naming row-level security stays the server's problem, since reporting a\n * genuine privilege misconfiguration as \"forbidden\" would send an operator\n * hunting for a policy bug that does not exist.\n */\nexport function isRowLevelSecurityDenial(error: unknown): boolean {\n const pgError = extractPgError(error);\n if (!pgError || pgError.code !== \"42501\") return false;\n return pgError.message.toLowerCase().includes(\"row-level security policy\");\n}\n\n/**\n * Translate a raw PostgreSQL error into a user-friendly message.\n *\n * @param pgError - The extracted PostgreSQL error (from {@link extractPgError})\n * @param context - A human-readable context string (e.g. collection slug or path)\n * @returns An object with a `message` safe for the client and the PG `code`.\n */\nexport function pgErrorToFriendlyMessage(pgError: PostgresError, context: string): { message: string; code: string } {\n const detail = pgError.detail as string | undefined;\n const hint = pgError.hint as string | undefined;\n const constraint = pgError.constraint as string | undefined;\n const column = pgError.column as string | undefined;\n const table = pgError.table as string | undefined;\n const dataType = pgError.dataType as string | undefined;\n const pgMessage = pgError.message || \"Unknown database error\";\n const code = pgError.code || \"UNKNOWN\";\n\n const suffix = hint ? ` Hint: ${hint}` : \"\";\n const tableRef = table ?? context;\n\n switch (pgError.code) {\n case \"23503\": // foreign_key_violation\n return {\n message: detail\n ? `Foreign key constraint violated: ${detail}${suffix}`\n : `Cannot complete operation: a foreign key constraint${constraint ? ` (${constraint})` : \"\"} was violated in \"${context}\".${suffix}`,\n code\n };\n case \"23505\": // unique_violation\n return {\n message: detail\n ? `Duplicate value: ${detail}${suffix}`\n : `Cannot complete operation: a unique constraint${constraint ? ` (${constraint})` : \"\"} was violated in \"${context}\".${suffix}`,\n code\n };\n case \"23502\": // not_null_violation\n return {\n message: `Missing required field: \"${column ?? \"unknown\"}\" in \"${tableRef}\" cannot be empty.${suffix}`,\n code\n };\n case \"23514\": // check_violation\n return {\n message: `Validation failed: a check constraint${constraint ? ` (${constraint})` : \"\"} was violated in \"${context}\".${suffix}`,\n code\n };\n case \"22P02\": // invalid_text_representation (e.g. invalid UUID, wrong enum value)\n return {\n message: `Invalid data format in \"${context}\": ${pgMessage}${suffix}`,\n code\n };\n case \"22001\": // string_data_right_truncation (value too long)\n return {\n message: `Value too long for column \"${column ?? \"unknown\"}\" in \"${tableRef}\": ${pgMessage}${suffix}`,\n code\n };\n case \"22003\": // numeric_value_out_of_range\n return {\n message: `Numeric value out of range for column \"${column ?? \"unknown\"}\" in \"${tableRef}\": ${pgMessage}${suffix}`,\n code\n };\n case \"42703\": // undefined_column\n return {\n message: `Unknown column in \"${tableRef}\": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`,\n code\n };\n case \"42P01\": // undefined_table\n return {\n message: `Table not found for \"${context}\": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`,\n code\n };\n case \"42501\": // insufficient_privilege\n // Two unrelated failures share this SQLSTATE, and the old message\n // named both causes because it could not tell them apart — which\n // meant it was half wrong whichever one had happened, and sent the\n // reader to check the other. Postgres says which in its own message.\n return pgMessage.toLowerCase().includes(\"row-level security policy\")\n ? {\n // The caller. Their policies do not permit this row; the\n // deployment is working exactly as configured.\n message: `Not permitted to write this row in \"${tableRef}\": it does not satisfy the row-level security policy.${suffix}`,\n code\n }\n : {\n // The deployment. No policy is involved — the connecting\n // role cannot touch the table at all.\n message: `Permission denied on \"${tableRef}\": the database role this server connects as lacks privileges on it.${suffix}`,\n code\n };\n case \"28000\": // invalid_authorization_specification\n return {\n message: `Authorization failed for \"${context}\". Check your database credentials.${suffix}`,\n code\n };\n default: {\n // Unhandled PG code — still surface the actual database message\n const parts = [`Database error in \"${context}\" [${code}]: ${pgMessage}`];\n if (detail) parts.push(`Detail: ${detail}`);\n if (column) parts.push(`Column: ${column}`);\n if (dataType) parts.push(`Data type: ${dataType}`);\n if (constraint) parts.push(`Constraint: ${constraint}`);\n if (hint) parts.push(`Hint: ${hint}`);\n return { message: parts.join(\". \"), code };\n }\n }\n}\n\n/**\n * Sanitize any error into a message safe and helpful for the client.\n *\n * A deliberate 4xx (`ApiError`) passes through untouched — the server already\n * decided what the client should read. Otherwise the PG error is extracted\n * from the Drizzle cause chain, falling back to a generic message that\n * doesn't leak SQL.\n *\n * @param error - The raw caught error\n * @param context - A human-readable context string (e.g. collection path)\n * @returns An object with `message` (user-friendly) and optional `code`\n * (the `ApiError` code, or the PG SQLSTATE).\n */\nexport function sanitizeErrorForClient(error: unknown, context: string): { message: string; code?: string } {\n // ── A deliberate 4xx is not a database failure ──────────────────\n // Its message and code are written for the client; replacing them with\n // \"Check server logs\" would discard the only diagnosis the caller gets\n // (e.g. the offending filter field and the collection's valid ones).\n // Log level follows the same convention as the HTTP error handler in\n // @rebasepro/server: routine outcomes at debug, everything else at warn.\n const clientError = asClientFacingError(error);\n if (clientError) {\n const line = `[API ${clientError.statusCode} ${clientError.code ?? \"BAD_REQUEST\"}] in \"${context}\": ${clientError.message}`;\n if (clientError.expected) {\n logger.debug(line);\n } else {\n logger.warn(`⚠️ ${line}`);\n }\n return { message: clientError.message, ...(clientError.code && { code: clientError.code }) };\n }\n\n // ── Always log the full, unsanitized error server-side ──────────\n const pgError = extractPgError(error);\n\n if (pgError) {\n logger.error(`[PG ${pgError.code}] Error in \"${context}\"`, {\n code: pgError.code,\n message: pgError.message,\n detail: pgError.detail,\n hint: pgError.hint,\n column: pgError.column,\n table: pgError.table,\n constraint: pgError.constraint,\n dataType: pgError.dataType\n // The outer Drizzle wrapper message used to be logged here \"for\n // full context\": it is `Failed query: <sql>\\nparams: <values>`, so\n // it published the statement and every bound value (an email, a\n // password hash) on every realtime data failure. The SQLSTATE,\n // detail, table, column and constraint above are the diagnostic\n // value; the wrapper added only the leak. `logger` strips the\n // wrapper as well, but the field itself carried nothing else.\n });\n return pgErrorToFriendlyMessage(pgError, context);\n }\n\n // No PG error found — log the raw error as-is\n logger.error(`Database error in \"${context}\" (no PG error extracted)`, {\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n cause: error instanceof Error && error.cause\n ? (error.cause instanceof Error ? error.cause.message : String(error.cause))\n : undefined\n });\n\n // Try to get the deepest cause message\n const causeMessage = extractCauseMessage(error);\n if (causeMessage) {\n return { message: `Database error in \"${context}\": ${causeMessage}` };\n }\n\n // Last resort — generic message, never leak raw SQL\n return { message: `Could not load data for \"${context}\". Check server logs for details.` };\n}\n","import { and, asc, count, desc, eq, getTableColumns, getTableName, gt, isNotNull, isNull, lt, or, sql, SQL, TableRelationalConfig, TablesRelationalConfig } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, FilterValues, OrderByTuple, ResolvedRelation, LogicalCondition, isManyToMany, parseRelationAggregateSort } from \"@rebasepro/types\";\nimport type { VectorSearchParams } from \"@rebasepro/types\";\nimport { resolveCollectionRelations, findRelation, fieldKeyForColumn, createRelationRef, createRelationRefWithData, normalizeDriverOrderBy } from \"@rebasepro/common\";\nimport { generateForeignKeyName, toWireKey } from \"@rebasepro/utils\";\nimport { DrizzleConditionBuilder, getUnknownFilterFieldsMode, type FilterCompilationOptions } from \"../utils/drizzle-conditions\";\nimport {\n getCollectionByPath,\n getTableForCollection,\n getPrimaryKeys,\n requirePrimaryKeys,\n deriveRowAddress,\n parseIdValues,\n idCanAddressTable,\n buildCompositeId,\n COMPOSITE_ID_SEPARATOR\n} from \"./collection-helpers\";\nimport { parseDataFromServer, normalizeDbValues } 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, isJunctionRelation } from \"./row-pipeline\";\nimport { visibleColumnProjection, hiddenColumnsOption } from \"../schema/search-column\";\nimport { isNestedPath, resolveNestedPath, type NestedPathHop } from \"./nested-path\";\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 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 */\nexport class FetchService {\n private relationService: RelationService;\n\n constructor(private db: DrizzleClient, private registry: PostgresCollectionRegistry) {\n this.relationService = new RelationService(db, registry);\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 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 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 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] 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 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,\ntarget });\n }\n return resolved;\n }\n\n /**\n * The full `ORDER BY`: the caller's keys, then the id.\n *\n * The id is always last and always descending. It is not decoration — it is\n * what makes the ordering *total*, and a cursor over a non-total order\n * repeats and skips rows among the ties. Every keyset comparison built by\n * {@link buildCursorConditions} ends on the same `id DESC`, and the two have\n * to agree: they did not, and an ascending sort paged with `id >` against an\n * `ORDER BY … , id DESC`, so rows sharing a sort value were dropped from\n * every page after the first.\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(keys: ResolvedOrderKey[], idField: AnyPgColumn): SQL[] {\n const expressions = keys.map(({ direction, target }) => direction === \"asc\"\n ? sql`${target} ASC NULLS LAST`\n : sql`${target} DESC NULLS FIRST`);\n expressions.push(desc(idField));\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 let validFields: string[] = [];\n try {\n validFields = Object.keys(getTableColumns(table)).sort();\n } catch {\n // A table stand-in without Drizzle's column symbols — the message\n // is worth less without the list, but not worth failing over.\n }\n\n throw ApiError.badRequest(\n `Unknown orderBy field '${orderBy}'${onCollection}` +\n (validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\"),\n \"UNKNOWN_ORDER_BY_FIELD\",\n {\n field: orderBy,\n ...(collectionName && { collection: collectionName }),\n ...(validFields.length > 0 && { validFields })\n }\n );\n }\n\n /**\n * Build the `with` config for Drizzle's relational query API.\n * Converts collection relations to a Drizzle-compatible `with` object.\n *\n * When `include` is provided, only those relations are loaded.\n * When `include` is absent, ALL relations are loaded (the admin path).\n *\n * Automatically detects many-to-many junction tables and nests\n * the target relation so actual row data is returned.\n */\n private buildWithConfig(\n collection: CollectionConfig,\n include?: string[]\n ): Record<string, boolean | { with: Record<string, boolean> }> {\n const resolvedRelations = resolveCollectionRelations(collection);\n const withConfig: Record<string, boolean | { with: Record<string, boolean> }> = {};\n\n const shouldInclude = (key: string) =>\n !include || include.length === 0 || include[0] === \"*\" || include.includes(key);\n\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n if (!shouldInclude(key)) continue;\n\n const drizzleRelName = relation.relationName || key;\n\n // Skip relations that use joinPath as they are not mapped in Drizzle schemas\n if (relation.kind === \"via\") {\n continue;\n }\n\n // Detect many-to-many junction tables:\n // If the relation goes through a junction table (relation.through exists or\n // the Drizzle schema maps to a junction table), we need two-level with.\n if (relation.cardinality === \"many\" && isJunctionRelation(relation)) {\n // The Drizzle relation points to the junction table.\n // We need: { [junctionRelName]: { with: { [targetFkName]: true } } }\n // The target FK name is the relation on the junction table that points to the actual target.\n const targetFkName = this.getJunctionTargetRelationName(relation, collection);\n if (targetFkName) {\n withConfig[drizzleRelName] = { with: { [targetFkName]: true } };\n } else {\n withConfig[drizzleRelName] = true;\n }\n } else {\n withConfig[drizzleRelName] = true;\n }\n }\n\n return withConfig;\n }\n\n /**\n * Get the Drizzle relation name on the junction table that points to the actual target row.\n * For example, for posts_tags junction, this returns \"tag_id\" (the relation pointing to tags).\n */\n private getJunctionTargetRelationName(relation: ResolvedRelation, _collection: CollectionConfig): string | null {\n if (isManyToMany(relation)) {\n // The junction relation on the junction table pointing to the target\n // uses the targetColumn name as the Drizzle relation name\n return relation.through.targetColumn.replace(/_id$/, \"_id\");\n }\n return null;\n }\n\n /**\n * Post-fetch joinPath relations for a single flat row.\n * joinPath relations cannot be expressed via Drizzle's `with` config,\n * so they must be loaded separately after the primary query.\n */\n private async resolveJoinPathRelations<M extends Record<string, unknown>>(\n row: Record<string, unknown>,\n collection: CollectionConfig,\n collectionPath: string,\n parsedId: string | number,\n _databaseId?: string\n ): Promise<void> {\n const resolvedRelations = resolveCollectionRelations(collection);\n\n const promises = Object.entries(resolvedRelations)\n .filter(([key, relation]) => relation.kind === \"via\")\n .map(async ([key, relation]) => {\n try {\n const relatedRows = await this.relationService.fetchRelatedEntities(\n collectionPath,\n parsedId,\n key,\n { limit: relation.cardinality === \"one\" ? 1 : undefined }\n );\n\n if (relation.cardinality === \"one\" && relatedRows.length > 0) {\n const e = relatedRows[0];\n row[key] = createRelationRefWithData(e.id, e.path, e);\n } else if (relation.cardinality === \"many\") {\n row[key] = relatedRows.map(e =>\n createRelationRefWithData(e.id, e.path, e)\n );\n }\n } catch (e) {\n // A relation that failed to load is not a relation that is absent.\n // Without this the request answers 200 with the field quietly\n // missing — and because a Postgres error poisons the surrounding\n // transaction, every later relation in the same request is\n // swallowed too, so one failure becomes a response missing\n // several fields. Same guard the four other catches in this file\n // already use.\n if (reachedDatabase(e)) throw e;\n logger.warn(`Could not resolve joinPath relation '${key}'`, { error: e });\n }\n });\n\n await Promise.all(promises);\n }\n\n /**\n * Resolves joinPath relations for raw REST rows and directly injects them.\n * Uses RelationService to query the database and maps results back to the flattened objects.\n */\n private async resolveJoinPathRelationsBatchRest(\n rows: Record<string, unknown>[],\n collection: CollectionConfig,\n collectionPath: string,\n idInfoArray: { fieldName: string; type: \"string\" | \"number\" }[],\n include?: string[]\n ): Promise<void> {\n if (rows.length === 0) return;\n\n const resolvedRelations = resolveCollectionRelations(collection);\n const propertyKeys = new Set(Object.keys(collection.properties || {}));\n const shouldInclude = (key: string) =>\n !include || include.length === 0 || include[0] === \"*\" || include.includes(key);\n\n const joinPathRelations = Object.entries(resolvedRelations)\n .filter(([key, relation]) => relation.kind === \"via\" && propertyKeys.has(key) && shouldInclude(key));\n\n if (joinPathRelations.length === 0) return;\n\n // These rows carry their key columns verbatim, so the parent's address\n // is derived from them. It used to be parsed back out of a synthesized\n // `id` — which no longer exists on a row, and threw (composite: parts\n // mismatch; numeric: NaN) into the catch below, where a warning is all\n // that separates \"no relations\" from \"relations dropped\".\n //\n // The whole key, because that is what the batch groups its results by:\n // both sides derive the token the same way, so they agree by\n // construction rather than by both happening to pick column zero.\n const parentIdOf = (row: Record<string, unknown>): string | undefined => {\n const address = buildCompositeId(row, idInfoArray);\n return address && address.split(COMPOSITE_ID_SEPARATOR).some(part => part !== \"\") ? address : undefined;\n };\n\n for (const [key, relation] of joinPathRelations) {\n try {\n const addressable = rows.filter(r => parentIdOf(r) !== undefined && parentIdOf(r) !== null);\n if (addressable.length === 0) continue;\n const rowIds = addressable.map(r => parentIdOf(r) as string | number);\n\n if (relation.cardinality === \"one\") {\n const resultMap = await this.relationService.batchFetchRelatedEntities(\n collectionPath,\n rowIds,\n key,\n relation\n );\n\n for (const row of addressable) {\n const relatedRow = resultMap.get(String(parentIdOf(row)));\n // Columns only: the target's address is the consumer's to\n // derive, and merging it last overwrote a real `id` column.\n row[key] = relatedRow ? { ...relatedRow.values } : null;\n }\n } else if (relation.cardinality === \"many\") {\n const resultMap = await this.relationService.batchFetchRelatedEntitiesMany(\n collectionPath,\n rowIds,\n key,\n relation\n );\n\n for (const row of addressable) {\n const relatedList = resultMap.get(String(parentIdOf(row))) || [];\n row[key] = relatedList.map(e => ({ ...e.values }));\n }\n }\n } catch (e) {\n // A relation that failed to load is not a relation that is absent.\n // Without this the request answers 200 with the field quietly\n // missing — and because a Postgres error poisons the surrounding\n // transaction, every later relation in the same request is\n // swallowed too, so one failure becomes a response missing\n // several fields. Same guard the four other catches in this file\n // already use.\n if (reachedDatabase(e)) throw e;\n logger.warn(`Could not batch resolve joinPath relation '${key}' for REST`, { error: e });\n }\n }\n }\n\n /**\n * Build db.query-compatible options from standard fetch options.\n * Handles filter, search, orderBy, limit, and cursor-based pagination.\n */\n private buildDrizzleQueryOptions<M extends Record<string, unknown>>(\n table: PgTable<any>,\n idField: AnyPgColumn,\n idInfo: { fieldName: string; type: \"string\" | \"number\" },\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 logical?: LogicalCondition;\n },\n collectionPath: string,\n withConfig?: Record<string, unknown>,\n scopeCondition?: SQL\n ): Record<string, unknown> {\n const queryOpts: Record<string, unknown> = {};\n\n // Same exclusion the `db.select` fallback applies, in the shape the\n // relational query builder takes. Both paths serve the same request, so\n // a row must not carry the search column down one and not the other.\n const hidden = hiddenColumnsOption(\n getTableColumns(table),\n this.registry.getCollectionByPath(collectionPath) ?? undefined\n );\n if (hidden) queryOpts.columns = hidden;\n\n if (withConfig) queryOpts.with = withConfig;\n\n // Build where conditions\n const allConditions: SQL[] = [];\n\n if (scopeCondition) allConditions.push(scopeCondition);\n\n if (options.searchString) {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString, collection.properties, table, collection\n );\n if (searchConditions.length === 0) {\n // Return options that will produce empty results\n queryOpts.where = and(eq(idField, -99999999)); // impossible condition\n return queryOpts;\n }\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 // Cursor-based pagination (startAfter)\n if (options.startAfter) {\n const cursorConditions = this.buildCursorConditions(table, idField, idInfo, options, collectionPath);\n if (cursorConditions.length > 0) allConditions.push(...cursorConditions);\n }\n\n if (allConditions.length > 0) {\n queryOpts.where = and(...allConditions);\n }\n\n // OrderBy\n const orderKeys = normalizeDriverOrderBy(options.orderBy, options.order);\n const resolvedOrder = orderKeys\n ? this.resolveOrderKeys(table, orderKeys, getCollectionByPath(collectionPath, this.registry), options.searchString)\n : [];\n queryOpts.orderBy = this.buildOrderExpressions(resolvedOrder, idField);\n\n // Limit\n const limitValue = options.searchString ? (options.limit || 50) : options.limit;\n if (limitValue) queryOpts.limit = limitValue;\n\n // Offset (numeric pagination)\n if (options.offset && options.offset > 0) queryOpts.offset = options.offset;\n\n return queryOpts;\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 `id DESC`. With\n * one key that is the familiar `k > v OR (k = v AND id < cursorId)`; with\n * several it nests, each key's tie handing the decision to the next.\n */\n private buildCursorConditions(\n table: PgTable<any>,\n idField: AnyPgColumn,\n idInfo: { fieldName: string; type: \"string\" | \"number\" },\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\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 = collectionPath ? getCollectionByPath(collectionPath, this.registry) : undefined;\n const startAfterId = cursor.id ?? cursor[idInfo.fieldName];\n const resolved = this.resolveOrderKeys(\n table, keys, collection, undefined, collectionPath,\n // A null id addresses no row, so pinning a subquery to it would\n // aggregate over nothing and read as \"the cursor row has no\n // related rows\" rather than as the absent cursor it is.\n startAfterId ?? undefined\n );\n\n if (resolved.length > 0 && startAfterId !== undefined) {\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 return [this.buildKeysetComparison(resolved, values, idField, startAfterId)];\n }\n }\n } else {\n const startAfterId = cursor.id ?? cursor[idInfo.fieldName];\n if (startAfterId !== undefined && startAfterId !== null) {\n const idInfoArray = [idInfo] as Array<{ fieldName: string; type: \"string\" | \"number\" }>;\n const parsedStartAfterIdObj = parseIdValues(startAfterId as string | number, idInfoArray);\n return [lt(idField, parsedStartAfterIdObj[idInfo.fieldName])];\n }\n }\n\n return [];\n }\n\n /**\n * \"Sorts strictly after the cursor row\", over `keys` and then the id.\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 idField: AnyPgColumn,\n cursorId: unknown,\n index = 0\n ): SQL {\n // Past the last key, the id settles it. It is ordered `DESC`, so \"after\"\n // the cursor row means a smaller id.\n if (index >= keys.length) return lt(idField, cursorId);\n\n const { direction, cursorTarget } = 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, idField, cursorId, 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 if (cursorTarget) {\n return direction === \"asc\"\n // NULLS LAST. A cursor row among the NULLs has only later NULLs\n // after it; otherwise everything greater, then the NULLs, then\n // the ties.\n ? or(\n and(isNull(cursorTarget), isNull(target), rest),\n and(\n isNotNull(cursorTarget),\n or(\n sql`${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.\n : or(\n and(isNull(cursorTarget), or(isNotNull(target), and(isNull(target), rest))),\n and(\n isNotNull(cursorTarget),\n or(\n sql`${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 direction === \"asc\"\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 direction === \"asc\"\n // NULLS LAST, so the NULLs are still ahead of a non-null cursor row.\n ? or(gt(target, value), isNull(target), and(eq(target, value), rest))!\n : or(lt(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 const parentPks = requirePrimaryKeys(hop.parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parsedParentId = parseIdValues(hop.parentId, parentPks)[parentIdInfo.fieldName];\n\n const parent = () => {\n const table = getTableForCollection(hop.parentCollection, this.registry);\n const idColumn = table[parentIdInfo.fieldName as keyof typeof table] as AnyPgColumn;\n if (!idColumn) {\n throw new Error(`ID field '${parentIdInfo.fieldName}' not found in table for collection '${hop.parentCollection.slug}'`);\n }\n return { table,\nidColumn };\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 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 ): 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 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 // 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 parsedIdObj = parseIdValues(id, idInfoArray);\n const parsedId = parsedIdObj[idInfo.fieldName];\n\n // Primary path: use db.query.findFirst with relation loading\n\n const tableName = getTableName(table);\n\n const qb = this.getQueryBuilder(tableName);\n if (qb) {\n try {\n const withConfig = this.buildWithConfig(collection);\n\n const hidden = hiddenColumnsOption(getTableColumns(table), collection);\n\n const row = await qb.findFirst({\n where: eq(idField, parsedId),\n with: withConfig,\n ...(hidden ? { columns: hidden } : {})\n } as Parameters<NonNullable<typeof qb>[\"findFirst\"]>[0]);\n\n if (!row) return undefined;\n\n const flatRow = toFlatRow(row, collection, this.registry);\n\n // Post-fetch joinPath relations that Drizzle's `with` can't express\n await this.resolveJoinPathRelations<M>(flatRow, collection, collectionPath, parsedId, databaseId);\n\n return flatRow;\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"not enough information to infer relation\")) {\n logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);\n logger.error(\"Hint: This usually means a relation in your drizzle schema is missing a reciprocal 'one()' or 'many()' definition. Run 'rebase schema generate' to fix this.\");\n }\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\n // Fallback: db.select + N+1 relation loading\n const visibleOne = visibleColumnProjection(getTableColumns(table), collection);\n const result = await this.db\n .select(visibleOne as never)\n .from(table)\n .where(eq(idField, parsedId))\n .limit(1);\n\n if (result.length === 0) return undefined;\n\n const raw = result[0] as M;\n const values = await parseDataFromServer(raw, collection, this.db, this.registry) as Record<string, unknown>;\n\n // Load relations based on cardinality (N+1 — only used in fallback)\n const resolvedRelations = resolveCollectionRelations(collection);\n const propertyKeys = new Set(Object.keys(collection.properties));\n\n const relationPromises = Object.entries(resolvedRelations)\n .filter(([key]) => propertyKeys.has(key))\n .map(async ([key, relation]) => {\n if (relation.cardinality === \"many\") {\n const relatedRows = await this.relationService.fetchRelatedEntities(\n collectionPath,\n parsedId,\n key,\n {}\n );\n values[key] = relatedRows.map(e =>\n createRelationRef(e.id, e.path)\n );\n } else if (relation.cardinality === \"one\") {\n if (values[key] == null) {\n try {\n const relatedRows = await this.relationService.fetchRelatedEntities(\n collectionPath,\n parsedId,\n key,\n { limit: 1 }\n );\n if (relatedRows.length > 0) {\n const e = relatedRows[0];\n values[key] = createRelationRef(e.id, e.path);\n }\n } catch (e) {\n // A relation that failed to load is not a relation that is absent.\n // Without this the request answers 200 with the field quietly\n // missing — and because a Postgres error poisons the surrounding\n // transaction, every later relation in the same request is\n // swallowed too, so one failure becomes a response missing\n // several fields. Same guard the four other catches in this file\n // already use.\n if (reachedDatabase(e)) throw e;\n logger.warn(`Could not resolve one-to-one relation property: ${key}`, { error: e });\n }\n }\n }\n });\n\n await Promise.all(relationPromises);\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 } = {}\n ): Promise<Record<string, unknown>[]> {\n const scopeCondition = options.relatedTo ? this.buildRelationScope(options.relatedTo) : undefined;\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 // Primary path: use db.query.findMany with relation loading\n // Skip when searchString is present (same reason as fetchCollectionForRest)\n // Skip when collection has relations — lateral JOINs are catastrophically\n // slow for large collections (7s+ for 350 rows). The db.select fallback\n // path uses batch relation resolution which is 50x faster.\n\n const tableName = getTableName(table);\n\n const qb = this.getQueryBuilder(tableName);\n const withConfig = this.buildWithConfig(collection);\n const hasRelations = withConfig && Object.keys(withConfig).length > 0;\n\n // Skip db.query path when vectorSearch is present — it doesn't support\n // custom SELECT expressions needed for the _distance column.\n if (qb && !options.searchString && !hasRelations && !options.vectorSearch) {\n try {\n const queryOpts = this.buildDrizzleQueryOptions<M>(\n table, idField, idInfo, options, collectionPath, undefined, scopeCondition\n );\n\n\n const results = await qb.findMany(queryOpts as Parameters<NonNullable<typeof qb>[\"findMany\"]>[0]);\n\n const rows = (results as Record<string, unknown>[]).map(row =>\n toFlatRow(row, collection, this.registry)\n );\n\n return rows;\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"not enough information to infer relation\")) {\n logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);\n logger.error(\"Hint: This usually means a relation in your drizzle schema is missing a reciprocal 'one()' or 'many()' definition. Run 'rebase schema generate' to fix this.\");\n }\n if (reachedDatabase(e)) throw e;\n logger.warn(`[FetchService] db.query.findMany failed for ${collectionPath}, falling back to db.select`, { error: e });\n }\n }\n\n // Fallback: db.select + processRowResults (N+1 for relations)\n // When vectorSearch is present, add _distance to the SELECT.\n let vectorMeta: { orderBy: SQL; filter?: SQL; distanceSelect: SQL } | undefined;\n if (options.vectorSearch) {\n vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch);\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.\n const visible = visibleColumnProjection(getTableColumns(table), collection);\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 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 : (visible ? this.db.select(visible as never).from(table).$dynamic() : this.db.select().from(table).$dynamic());\n const allConditions: SQL[] = [];\n\n if (scopeCondition) allConditions.push(scopeCondition);\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 // Vector distance threshold filter\n if (vectorMeta?.filter) {\n 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 // Vector search overrides ORDER BY with distance (ascending = closest first)\n const orderExpressions = vectorMeta\n ? [asc(vectorMeta.orderBy), desc(idField)]\n : this.buildOrderExpressions(\n this.resolveOrderKeys(\n table,\n normalizeDriverOrderBy(options.orderBy, options.order) ?? [],\n collection,\n options.searchString\n ),\n idField\n );\n query = query.orderBy(...orderExpressions);\n\n if (options.startAfter) {\n const cursorConditions = this.buildCursorConditions(table, idField, idInfo, 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 const rawResults = await query;\n\n // When vector search is active, unwrap the nested select shape and\n // attach _distance to each row's values.\n const results = vectorMeta\n ? (rawResults as { table_row: Record<string, unknown>; _distance: unknown }[]).map(r => ({\n ...r.table_row,\n _distance: typeof r._distance === \"number\" ? r._distance : parseFloat(String(r._distance))\n }))\n // Same nested shape, unwrapped the same way, when a relevance\n // score was selected instead.\n : rankSelect\n ? (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 : rawResults as Record<string, unknown>[];\n\n return this.processRowResults<M>(results, collection, collectionPath, idInfo, options.databaseId, false, idInfoArray);\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 skipRelations = false,\n idInfoArray?: { fieldName: string; type: \"string\" | \"number\" }[]\n ): Promise<Record<string, unknown>[]> {\n if (results.length === 0) return [];\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 second\n // pass below batch-loads all inverse/many relations in O(1) queries per\n // relation type, avoiding the N+1 that plagued the old path.\n const parsedRows = await Promise.all(results.map(async (rawRow: Record<string, unknown>) => {\n const values = await parseDataFromServer(rawRow as M, collection) as Record<string, unknown>;\n return {\n rawRow,\n values\n };\n }));\n\n if (!skipRelations) {\n // Second pass: batch load missing one-to-one relations\n const resolvedRelations = resolveCollectionRelations(collection);\n const propertyKeys = new Set(Object.keys(collection.properties));\n\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n if (!propertyKeys.has(key) || relation.cardinality !== \"one\") continue;\n\n const rowsMissingRelation = parsedRows.filter(item => {\n const val = item.values[key];\n if (val == null) return true;\n if (typeof val === \"object\" && !Array.isArray(val) && (val as Record<string, unknown>).__type === \"relation\" && (val as Record<string, unknown>).data == null) return true;\n return false;\n });\n\n if (rowsMissingRelation.length === 0) continue;\n\n try {\n const rowIds = rowsMissingRelation.map(item => item.rawRow[idInfo.fieldName] as string | number);\n const relationResults = await this.relationService.batchFetchRelatedEntities(\n collectionPath,\n rowIds,\n key,\n relation\n );\n\n rowsMissingRelation.forEach(item => {\n const id = item.rawRow[idInfo.fieldName] as string | number;\n const relatedRow = relationResults.get(String(id));\n if (relatedRow) {\n item.values[key] = createRelationRefWithData(relatedRow.id, relatedRow.path, relatedRow);\n }\n });\n } catch (e) {\n // A relation that failed to load is not a relation that is absent.\n // Without this the request answers 200 with the field quietly\n // missing — and because a Postgres error poisons the surrounding\n // transaction, every later relation in the same request is\n // swallowed too, so one failure becomes a response missing\n // several fields. Same guard the four other catches in this file\n // already use.\n if (reachedDatabase(e)) throw e;\n logger.warn(`Could not batch load one-to-one relation property: ${key}`, { error: e });\n }\n }\n\n // Batch load many-cardinality relations (1 query per relation type\n // instead of N queries per row)\n const manyRelations = Object.entries(resolvedRelations)\n .filter(([key, relation]) => propertyKeys.has(key) && relation.cardinality === \"many\");\n\n for (const [key, relation] of manyRelations) {\n try {\n const rowIds = parsedRows.map(item => item.rawRow[idInfo.fieldName] as string | number);\n const relationResults = await this.relationService.batchFetchRelatedEntitiesMany(\n collectionPath,\n rowIds,\n key,\n relation\n );\n\n parsedRows.forEach(item => {\n const id = String(item.rawRow[idInfo.fieldName]);\n const relatedRows = relationResults.get(id) || [];\n item.values[key] = relatedRows.map(e =>\n createRelationRefWithData(e.id, e.path, e)\n );\n });\n } catch (e) {\n // A relation that failed to load is not a relation that is absent.\n // Without this the request answers 200 with the field quietly\n // missing — and because a Postgres error poisons the surrounding\n // transaction, every later relation in the same request is\n // swallowed too, so one failure becomes a response missing\n // several fields. Same guard the four other catches in this file\n // already use.\n if (reachedDatabase(e)) throw e;\n logger.warn(`Could not batch load many relation property: ${key}`, { error: e });\n }\n }\n }\n\n // Columns only — the address is the consumer's to derive.\n return parsedRows.map(item => item.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 databaseId?: string;\n vectorSearch?: VectorSearchParams;\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 const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n if (hop) {\n return this.fetchRowsWithConditions<M>(hop.targetCollection.slug, { ...options,\nrelatedTo: hop });\n }\n\n return this.fetchRowsWithConditions<M>(collectionPath, options);\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 } = {}\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 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 // 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);\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 }\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 throw ApiError.badRequest(\n `Unknown field '${field}' in ${forWhat}. Valid fields: ${Object.keys(columns).sort().join(\", \")}`,\n \"UNKNOWN_AGGREGATE_FIELD\"\n );\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 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 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 // 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 }\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 const numericAliases = new Set(\n options.aggregates.filter(a => a.fn === \"count\" || a.fn === \"sum\" || a.fn === \"avg\").map(a => a.alias)\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 * Check if a field value is unique\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 idInfo = idInfoArray[0];\n const idField = table[idInfo.fieldName as keyof typeof table] as AnyPgColumn;\n const field = table[fieldName as keyof typeof table] as AnyPgColumn;\n\n if (!field) return true;\n\n const parsedExcludeId = excludeEntityId ? parseIdValues(excludeEntityId, idInfoArray)[idInfo.fieldName] : undefined;\n const conditions = DrizzleConditionBuilder.buildUniqueFieldCondition(\n field,\n value,\n idField,\n parsedExcludeId\n );\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 optional relation includes.\n * When `include` is provided, only the specified relations are populated\n * with full row data (not just { id, path, __type }).\n * When `include` is absent, no relation queries are made (fast path).\n *\n * @param include - Array of relation keys to populate, or [\"*\"] for all\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 include?: string[]\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\n const scopeCondition = options.relatedTo ? this.buildRelationScope(options.relatedTo) : undefined;\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 // Primary path: use db.query.findMany\n // NOTE: Skip db.query path when searchString is present because\n // Drizzle's relational query API doesn't properly apply raw SQL\n // ILIKE conditions — the fallback db.select path handles them correctly.\n\n const tableName = getTableName(table);\n\n const qb = this.getQueryBuilder(tableName);\n // Skip db.query path when vectorSearch is present — needs custom SELECT\n if (qb && !options.searchString && !options.vectorSearch) {\n try {\n const withConfig = (include && include.length > 0)\n ? this.buildWithConfig(collection, include)\n : undefined;\n\n const queryOpts = this.buildDrizzleQueryOptions<M>(\n table, idField, idInfo, options, collectionPath, withConfig, scopeCondition\n );\n\n\n const results = await qb.findMany(queryOpts as Parameters<NonNullable<typeof qb>[\"findMany\"]>[0]);\n\n const restRows = (results as Record<string, unknown>[]).map(row =>\n toRestRow(row, collection, this.registry)\n );\n\n // Drizzle relational query API doesn't resolve joinPath relations, fetch manually\n await this.resolveJoinPathRelationsBatchRest(restRows, collection, collectionPath, idInfoArray, include);\n\n return restRows;\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"not enough information to infer relation\")) {\n logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);\n logger.error(\"Hint: This usually means a relation in your drizzle schema is missing a reciprocal 'one()' or 'many()' definition. Run 'rebase schema generate' to fix this.\");\n }\n if (reachedDatabase(e)) throw e;\n logger.warn(`[fetchCollectionForRest] db.query.findMany failed for ${collectionPath}, falling back`, { error: e });\n }\n }\n\n // Fallback: fetch base rows without relations\n const rows = await this.fetchRowsWithConditionsRaw<M>(collectionPath, options);\n\n if (!include || include.length === 0) {\n return rows;\n }\n\n // Fallback relation loading via batch\n const resolvedRelations = resolveCollectionRelations(collection);\n const propertyKeys = new Set(Object.keys(collection.properties || {}));\n const shouldInclude = (key: string) =>\n include[0] === \"*\" || include.includes(key);\n\n const rowIds = rows.map(e => e[idInfo.fieldName] as string | number);\n\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n if (!propertyKeys.has(key) || !shouldInclude(key) || relation.cardinality !== \"one\") continue;\n try {\n const batchResults = await this.relationService.batchFetchRelatedEntities(\n collectionPath, rowIds, key, relation\n );\n for (const row of rows) {\n const eid = row[idInfo.fieldName] as string | number;\n const related = batchResults.get(String(eid));\n if (related) {\n (row as Record<string, unknown>)[key] = { ...related.values };\n }\n }\n } catch (e) {\n // A relation that failed to load is not a relation that is absent.\n // Without this the request answers 200 with the field quietly\n // missing — and because a Postgres error poisons the surrounding\n // transaction, every later relation in the same request is\n // swallowed too, so one failure becomes a response missing\n // several fields. Same guard the four other catches in this file\n // already use.\n if (reachedDatabase(e)) throw e;\n logger.warn(`[include] Failed to batch load one-to-one '${key}'`, { error: e });\n }\n }\n\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n if (!propertyKeys.has(key) || !shouldInclude(key) || relation.cardinality !== \"many\") continue;\n try {\n const batchResults = await this.batchFetchManyRelatedRows(\n collectionPath, rowIds, key\n );\n for (const row of rows) {\n const eid = row[idInfo.fieldName] as string | number;\n const relatedList = batchResults.get(String(eid)) || [];\n (row as Record<string, unknown>)[key] = relatedList;\n }\n } catch (e) {\n // A relation that failed to load is not a relation that is absent.\n // Without this the request answers 200 with the field quietly\n // missing — and because a Postgres error poisons the surrounding\n // transaction, every later relation in the same request is\n // swallowed too, so one failure becomes a response missing\n // several fields. Same guard the four other catches in this file\n // already use.\n if (reachedDatabase(e)) throw e;\n logger.warn(`[include] Failed to batch load many '${key}'`, { error: e });\n }\n }\n\n return rows;\n }\n\n /**\n * Fetch a single row with optional relation includes for REST API.\n */\n async fetchOneForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n include?: string[],\n databaseId?: string\n ): Promise<Record<string, unknown> | null> {\n if (!await this.isAddressableUnder(collectionPath, id)) return null;\n\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 // See `fetchOne`: an unaddressable id is a 404, not a database error.\n if (!idCanAddressTable(id, table, idInfoArray)) return null;\n\n const parsedIdObj = parseIdValues(id, idInfoArray);\n const parsedId = parsedIdObj[idInfo.fieldName];\n\n // Primary path: use db.query.findFirst\n\n const tableName = getTableName(table);\n\n const qb = this.getQueryBuilder(tableName);\n if (qb) {\n try {\n const withConfig = (include && include.length > 0)\n ? this.buildWithConfig(collection, include)\n : undefined;\n\n\n const row = await qb.findFirst({\n where: eq(idField, parsedId),\n ...(withConfig ? { with: withConfig } : {})\n } as Parameters<NonNullable<typeof qb>[\"findFirst\"]>[0]);\n\n if (!row) return null;\n\n const restRow = toRestRow(row, collection, this.registry);\n\n // Drizzle relational query API doesn't resolve joinPath relations, fetch manually\n await this.resolveJoinPathRelationsBatchRest([restRow], collection, collectionPath, idInfoArray, include);\n\n return restRow;\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"not enough information to infer relation\")) {\n logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);\n logger.error(\"Hint: This usually means a relation in your drizzle schema is missing a reciprocal 'one()' or 'many()' definition. Run 'rebase schema generate' to fix this.\");\n }\n if (reachedDatabase(e)) throw e;\n logger.warn(`[fetchOneForRest] db.query.findFirst failed for ${collectionPath}, falling back`, { error: e });\n }\n }\n\n // Fallback: db.select + N+1 relation loading\n const visibleOne = visibleColumnProjection(getTableColumns(table), collection);\n const result = await this.db\n .select(visibleOne as never)\n .from(table)\n .where(eq(idField, parsedId))\n .limit(1);\n\n if (result.length === 0) return null;\n\n const flatEntity: Record<string, unknown> = { ...(result[0] as Record<string, unknown>) };\n\n if (!include || include.length === 0) {\n return flatEntity;\n }\n\n // Fallback relation population\n const resolvedRelations = resolveCollectionRelations(collection);\n const propertyKeys = new Set(Object.keys(collection.properties || {}));\n const shouldInclude = (key: string) =>\n include[0] === \"*\" || include.includes(key);\n\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n if (!propertyKeys.has(key) || !shouldInclude(key)) continue;\n\n try {\n const relatedRows = await this.relationService.fetchRelatedEntities(\n collectionPath, parsedId, key, {}\n );\n\n if (relation.cardinality === \"one\") {\n if (relatedRows.length > 0) {\n const e = relatedRows[0];\n flatEntity[key] = { id: e.id,\n...e.values };\n }\n } else {\n flatEntity[key] = relatedRows.map(e => ({\n id: e.id,\n...e.values\n }));\n }\n } catch (e) {\n // A relation that failed to load is not a relation that is absent.\n // Without this the request answers 200 with the field quietly\n // missing — and because a Postgres error poisons the surrounding\n // transaction, every later relation in the same request is\n // swallowed too, so one failure becomes a response missing\n // several fields. Same guard the four other catches in this file\n // already use.\n if (reachedDatabase(e)) throw e;\n logger.warn(`[include] Failed to load relation '${key}'`, { error: e });\n }\n }\n\n return flatEntity;\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 } = {}\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 let vectorMeta: { orderBy: SQL; filter?: SQL; distanceSelect: SQL } | undefined;\n if (options.vectorSearch) {\n vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch);\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.\n const visible = visibleColumnProjection(getTableColumns(table), collection);\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 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 : (visible ? this.db.select(visible as never).from(table).$dynamic() : this.db.select().from(table).$dynamic());\n const allConditions: SQL[] = [];\n\n if (options.relatedTo) allConditions.push(this.buildRelationScope(options.relatedTo));\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 if (vectorMeta?.filter) {\n 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 // Vector search overrides ORDER BY with distance (ascending = closest first)\n const orderExpressions = vectorMeta\n ? [asc(vectorMeta.orderBy), desc(idField)]\n : this.buildOrderExpressions(\n this.resolveOrderKeys(\n table,\n normalizeDriverOrderBy(options.orderBy, options.order) ?? [],\n collection,\n options.searchString\n ),\n idField\n );\n query = query.orderBy(...orderExpressions);\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 const rawResults = await query;\n\n if (vectorMeta) {\n return (rawResults as { table_row: Record<string, unknown>; _distance: unknown }[]).map(r => ({\n ...r.table_row,\n _distance: typeof r._distance === \"number\" ? r._distance : parseFloat(String(r._distance))\n }));\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 /**\n * Check if the Drizzle instance has the relational query API available\n * for a given collection path.\n * Note: Primary path now uses inline `getQueryBuilder()` checks.\n */\n private hasDrizzleQueryAPI(collectionPath: string): boolean {\n\n const qb = this.getQueryBuilder(\"__probe__\");\n if (!qb) {\n // If getQueryBuilder returns undefined even for a probe, query API is not available\n return false;\n }\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const tableName = getTableName(table);\n return !!this.getQueryBuilder(tableName);\n }\n\n /**\n * Fallback path used when db.query is unavailable.\n * The primary path uses db.query.findMany with `with` config, which\n * loads all relations in a single query.\n *\n * Batch fetch many-to-many related rows for multiple parent IDs.\n * Groups results by parent ID to avoid N+1.\n */\n private async batchFetchManyRelatedRows(\n parentCollectionPath: string,\n parentIds: (string | number)[],\n relationKey: string\n ): Promise<Map<string, Record<string, unknown>[]>> {\n if (parentIds.length === 0) return new Map();\n\n // Resolve the relation definition so we can use the true batch method\n const collection = getCollectionByPath(parentCollectionPath, this.registry);\n const resolvedRelations = resolveCollectionRelations(collection);\n const relation = resolvedRelations[relationKey];\n\n if (!relation) {\n logger.warn(`[batchFetchManyRelatedRows] ResolvedRelation '${relationKey}' not found, skipping`);\n return new Map();\n }\n\n // Delegate to RelationService.batchFetchRelatedEntitiesMany which\n // uses a single SQL query with IN(...) — O(1) instead of O(N).\n // RelationService returns RelatedRow shapes (id + path + values) — flatten to plain rows here.\n const entityMap = await this.relationService.batchFetchRelatedEntitiesMany(\n parentCollectionPath,\n parentIds,\n relationKey,\n relation\n );\n const flatMap = new Map<string, Record<string, unknown>[]>();\n for (const [key, rows] of entityMap) {\n flatMap.set(key, rows.map(e => ({ ...e.values, id: e.id })));\n }\n return flatMap;\n }\n}\n","import { and, eq, inArray, notInArray } from \"drizzle-orm\";\nimport { AnyPgColumn } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, 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 {\n applyJunctionMembership,\n bindJoinPathJunction,\n bindThroughJunction,\n removeJunctionLink\n} from \"./junction-writes\";\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 * 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(private db: DrizzleClient, private registry: PostgresCollectionRegistry) {\n this.reads = new RelationService(db, registry);\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 /** 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 /**\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 targetEntityIds = relationTargetIds(value, key, collection.slug);\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 this.parsedIds(targetCollection, targetEntityIds)\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.parsedIds(targetCollection, targetEntityIds)\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 // 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[])));\n\n // Set FK for the provided targets\n await tx\n .update(targetTable)\n .set({ [fkField]: parentKeyValue })\n .where(inArray(targetIdCol as AnyPgColumn, parsedTargetIds as unknown[]));\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(eq(fkCol, parentKeyValue));\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 }\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 if (newValue === null || newValue === undefined) {\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n .where(eq(foreignKeyColumn, sourceKeyValue));\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(eq(foreignKeyColumn, sourceKeyValue));\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.\n if (e instanceof ApiError || (e as Error)?.name === \"ApiError\") 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 targetIds = Array.isArray(newValue)\n ? relationTargetIds(newValue, relation.relationName, sourceCollection.slug)\n : newValue === null || newValue === undefined\n ? []\n : relationTargetIds([newValue], relation.relationName, sourceCollection.slug);\n\n await applyJunctionMembership(\n tx,\n binding,\n this.parsedId(sourceCollection, sourceEntityId),\n this.parsedIds(targetCollection, targetIds)\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 }\n ) {\n try {\n const targetIds = Array.isArray(newValue)\n ? relationTargetIds(newValue, relation.relationName, sourceCollection.slug)\n : [];\n\n await applyJunctionMembership(\n tx,\n bindThroughJunction(\n this.registry,\n junctionInfo,\n `${sourceCollection.slug}.${relation.relationName}`\n ),\n this.parsedId(sourceCollection, sourceEntityId),\n this.parsedIds(targetCollection, targetIds)\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 lastStep = relation.joinPath[relation.joinPath.length - 1];\n const targetFKColName = DrizzleConditionBuilder.getColumnNamesFromColumns(lastStep.on.to)[0];\n let currentFrom = lastStep.on.from;\n\n let safety = 0;\n while (safety++ < 10) {\n const currentFromTable = DrizzleConditionBuilder.getTableNamesFromColumns(currentFrom)[0];\n if (currentFromTable === parentTableName) {\n break;\n }\n const prevStep = relation.joinPath.find((s) => {\n const to = Array.isArray(s.on.to) ? s.on.to[0] : s.on.to;\n return to === currentFrom;\n });\n if (!prevStep) {\n throw new Error(`Could not resolve parent source column for joinPath relation '${relation.relationName}'`);\n }\n currentFrom = prevStep.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 { eq, and, sql, SQL } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\n// import { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { CollectionConfig, Properties, Property, 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 parseIdValues,\n buildCompositeId\n} from \"./collection-helpers\";\nimport { sanitizeAndConvertDates, serializeDataToServer } from \"../data-transformer\";\nimport { RelationService } from \"./RelationService\";\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 } from \"@rebasepro/server\";\nimport { extractPgError, extractCauseMessage, pgErrorToFriendlyMessage, isRowLevelSecurityDenial } from \"../utils/pg-error-utils\";\nimport { explainZeroRowWrite } from \"./write-denial\";\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(private db: DrizzleClient, private registry: PostgresCollectionRegistry) {\n this.relationService = new RelationService(db, registry);\n this.relationWrites = new RelationWriteService(db, registry);\n this.fetchService = new FetchService(db, registry);\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 * 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.relationWrites.unlinkRelatedEntity(this.db, 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 const idInfoArray = getPrimaryKeys(collection, this.registry);\n\n const parsedIdObj = parseIdValues(id, idInfoArray);\n\n const conditions = [];\n for (const info of idInfoArray) {\n const field = table[info.fieldName as keyof typeof table] as AnyPgColumn;\n if (!field) {\n throw new Error(`ID field '${info.fieldName}' not found in table for collection '${collectionPath}'`);\n }\n conditions.push(eq(field, parsedIdObj[info.fieldName]));\n }\n\n const result = await this.db\n .delete(table)\n .where(and(...conditions));\n\n if ((result.rowCount ?? 0) === 0) {\n throw await this.explainZeroRowWrite(this.db, table, conditions, 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 against the primary key rather than a plain UPDATE. That is one\n * statement, so it cannot lose a race the way a read-then-write can, and it\n * does not care whether the row already exists — which is what a re-runnable\n * import needs.\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?: { upsert?: boolean }\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 // Separate relations that require special handling\n const relationValues: Record<string, unknown> = {};\n const otherValues: Partial<M> = { ...effectiveValues };\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 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\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\n savedId = await this.db.transaction(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 const idValues = parseIdValues(id, idInfoArray);\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 if (scalarKeys.length > 0) {\n const updateQuery = tx.update(table).set(entityData as Record<string, unknown>);\n const conditions = [];\n for (const info of idInfoArray) {\n const field = table[info.fieldName as keyof typeof table] as AnyPgColumn;\n conditions.push(eq(field, idValues[info.fieldName]));\n }\n\n const updateResult = await updateQuery.where(and(...conditions));\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 throw await this.explainZeroRowWrite(\n tx, table, conditions, 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, and the only one\n // guaranteed to exist is the primary key. Without every key\n // column present there is nothing to match on, so the row is a\n // plain insert and a duplicate should still raise.\n const hasFullKey = idInfoArray.length > 0\n && idInfoArray.every((info) => dataForInsert[info.fieldName] !== undefined);\n\n let result;\n if (options?.upsert && hasFullKey) {\n const target = idInfoArray.map((info) => table[info.fieldName as keyof typeof table] as AnyPgColumn);\n const set = { ...dataForInsert };\n // Never reassign the key columns to themselves in the UPDATE\n // branch; Postgres rejects that against the conflict target.\n for (const info of idInfoArray) delete set[info.fieldName];\n\n // A conflict means the row was already there, so its\n // `on_create` stamp is a fact about the past and not\n // this write's to redecide. Bulk rows are saved with\n // `status: \"new\"` on purpose (an import's rows carry a\n // natural key for rows that may not exist), which\n // computes the creation timestamp for every row — and\n // the conflict-update then wrote it over the original.\n // A nightly re-import reset `createdAt` on everything\n // it touched, and every \"new this week\" query with it.\n // The INSERT branch keeps the value: it is right there.\n for (const [propName, prop] of Object.entries(collection.properties ?? {})) {\n if ((prop as Property).type === \"date\"\n && (prop as { autoValue?: string }).autoValue === \"on_create\") {\n delete set[propName];\n }\n }\n\n result = Object.keys(set).length > 0\n ? await insertQuery.onConflictDoUpdate({ target, set }).returning(returningKeys)\n : await insertQuery.onConflictDoNothing({ target }).returning(returningKeys);\n } else {\n result = await insertQuery.returning(returningKeys);\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);\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 const finalEntity = await this.fetchService.fetchOneForRest(collection.slug, savedId, undefined, databaseId);\n if (!finalEntity) throw new Error(\"Could not fetch row after save.\");\n return finalEntity;\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 private toUserFriendlyError(error: unknown, collectionSlug: string): 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 } = pgErrorToFriendlyMessage(pgError, collectionSlug);\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 if (/^2[23]/.test(code)) {\n return code === \"23505\"\n ? ApiError.conflict(message, `PG_${code}`)\n : ApiError.badRequest(message, `PG_${code}`);\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 return new Error(message);\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}`);\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.`);\n }\n return new Error(`Database error in \"${collectionSlug}\": ${String(error)}`);\n }\n}\n","// import { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { FilterValues, LogicalCondition, OrderByTuple } from \"@rebasepro/types\";\nimport type { VectorSearchParams } from \"@rebasepro/types\";\nimport { FetchService } from \"./FetchService\";\nimport { PersistService } from \"./PersistService\";\nimport { RelationService } from \"./RelationService\";\nimport { DataRepository, FetchCollectionOptions, SearchOptions, CountOptions, DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\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(private db: DrizzleClient, private registry: PostgresCollectionRegistry) {\n this.fetchService = new FetchService(db, registry);\n this.persistService = new PersistService(db, registry);\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 ): Promise<Record<string, unknown> | undefined> {\n return this.fetchService.fetchOne<M>(collectionPath, id, databaseId);\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 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 } = {}\n ): Promise<Record<string, unknown>[]> {\n return this.fetchService.fetchCollection<M>(collectionPath, options);\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 } = {}\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?: { upsert?: boolean }\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 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 if (process.env.NODE_ENV !== \"production\") {\n console.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 = result.rows;\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(`SQL executed successfully. Returned ${Array.isArray(rows) ? rows.length : \"non-array\"} rows.`);\n }\n return rows as Record<string, unknown>[];\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 * BranchService\n *\n * Manages database branching by creating/deleting PostgreSQL databases\n * using `CREATE DATABASE ... TEMPLATE`. Branch metadata is stored in the\n * `rebase.branches` table in the default (main) database, following the\n * same `rebase` schema convention used by entity_history, auth, etc.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { BranchInfo } from \"@rebasepro/types\";\nimport { revokeInternalTableSql } from \"@rebasepro/common\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { DatabasePoolManager } from \"../databasePoolManager\";\nimport { extractPgError, extractCauseMessage } from \"../utils/pg-error-utils\";\n\n/** Internal prefix applied to branch database names to avoid collisions. */\nconst BRANCH_DB_PREFIX = \"rb_\";\n\n/** `duplicate_database` — the target database name is already taken. */\nconst PG_DUPLICATE_DATABASE = \"42P04\";\n\n/** `object_in_use` — the database still has connections attached. */\nconst PG_OBJECT_IN_USE = \"55006\";\n\n/**\n * Describe a failed branch DDL statement in terms a user can act on.\n *\n * Drizzle reports failures as `Failed query: <sql> params:` and hides the real\n * PostgreSQL error in the `cause` chain, so matching on `err.message` never sees\n * the actual problem. Match on the PG error code instead — it survives wrapping\n * and, unlike the message text, is not locale-dependent.\n */\nfunction describeBranchDdlError(err: unknown, fallbackContext: string): Error {\n const pgError = extractPgError(err);\n\n if (pgError?.code === PG_DUPLICATE_DATABASE) {\n return new Error(`Database \"${fallbackContext}\" already exists on the server. Choose a different branch name.`);\n }\n if (pgError?.code === PG_OBJECT_IN_USE) {\n return new Error(\n `Cannot complete the operation: the database \"${fallbackContext}\" has active connections. ` +\n \"Close other clients or connections and try again.\"\n );\n }\n\n // Unknown failure: surface the real PG message rather than the Drizzle\n // wrapper, which would otherwise show the raw SQL and no reason at all.\n const detail = pgError?.message ?? extractCauseMessage(err);\n if (detail) return new Error(detail);\n return err instanceof Error ? err : new Error(String(err));\n}\n\n/** Fully-qualified metadata table in the rebase schema. */\nconst BRANCHES_TABLE = \"rebase.branches\";\n\n/**\n * Validate that a user-provided identifier only contains safe characters.\n * Throws if the value contains characters outside [a-zA-Z0-9_-].\n */\nfunction validateIdentifier(value: string, label: string): void {\n if (!/^[a-zA-Z0-9_-]+$/.test(value)) {\n throw new Error(`Invalid ${label}: only letters, digits, underscores, and hyphens are allowed.`);\n }\n}\n\n/**\n * Postgres truncates identifiers at NAMEDATALEN-1 = 63 bytes, and does it\n * silently. The prefix comes out of the same budget.\n */\nconst MAX_BRANCH_NAME_LENGTH = 63 - BRANCH_DB_PREFIX.length;\n\n/**\n * Check a user-provided branch name, and otherwise leave it exactly as given.\n *\n * This used to strip everything outside [a-zA-Z0-9_], so `my-feature` was\n * quietly created as `myfeature`: the name you typed was not the name `list`\n * gave back. Nothing ever needed that. Every identifier this service builds is\n * double-quoted (see `CREATE DATABASE` below), which is what makes a hyphen\n * safe, and `validateIdentifier` has always accepted hyphens for the `--from`\n * source database — the two disagreed about the same character class.\n *\n * Refusing a name we cannot represent is better than representing a different\n * one, which is also why the length is checked here rather than left to\n * Postgres, whose answer to an over-long identifier is a silent rename.\n */\nfunction assertValidBranchName(name: string): void {\n validateIdentifier(name, \"branch name\");\n if (name.length > MAX_BRANCH_NAME_LENGTH) {\n throw new Error(\n `Branch name \"${name}\" is too long: ${name.length} characters, maximum ${MAX_BRANCH_NAME_LENGTH}. ` +\n \"Postgres truncates identifiers past 63 bytes, which would give the branch a name you did not choose.\"\n );\n }\n}\n\n/**\n * Convert a user-facing branch name to the actual PostgreSQL database name.\n */\nfunction toBranchDbName(name: string): string {\n assertValidBranchName(name);\n return `${BRANCH_DB_PREFIX}${name}`;\n}\n\nexport class BranchService {\n constructor(\n private db: DrizzleClient,\n private poolManager: DatabasePoolManager\n ) {}\n\n /**\n * Ensure the `rebase.branches` metadata table exists in the default database.\n * Idempotent — safe to call on every startup.\n */\n async ensureBranchMetadataTable(): Promise<void> {\n // Create the rebase schema (idempotent — may already exist from auth/history init)\n await this.db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);\n\n await this.db.execute(sql.raw(`\n CREATE TABLE IF NOT EXISTS ${BRANCHES_TABLE} (\n name TEXT PRIMARY KEY,\n db_name TEXT NOT NULL UNIQUE,\n parent_db TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB DEFAULT '{}'\n );\n `));\n\n // Not a collection, so no RLS — and it names every branch database on\n // this server. The driver's schema-wide grant reaches it, so revoke.\n await this.db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"branches\")));\n }\n\n /**\n * Create a new branch database by templating the source database.\n *\n * Uses `CREATE DATABASE ... TEMPLATE` for an instant, full-fidelity copy\n * of both schema and data.\n *\n * @param name User-facing branch name (e.g., \"feature_auth\")\n * @param options.source Source database to clone; defaults to the main database.\n */\n async createBranch(name: string, options?: { source?: string }): Promise<BranchInfo> {\n if (options?.source) {\n validateIdentifier(options.source, \"source database name\");\n }\n\n const dbName = toBranchDbName(name);\n const sourceDb = options?.source || this.poolManager.defaultDatabaseName;\n\n // Check if branch already exists\n const existing = await this.db.execute(\n sql`SELECT name FROM rebase.branches WHERE name = ${name} OR db_name = ${dbName}`\n );\n if ((existing.rows as unknown[]).length > 0) {\n throw new Error(`Branch \"${name}\" already exists.`);\n }\n\n // Disconnect any idle pools to the source DB so TEMPLATE works.\n // CREATE DATABASE ... TEMPLATE requires no other connections to the template.\n await this.poolManager.disconnectDatabase(sourceDb);\n\n // Create the database using the source as a template.\n // Note: Identifiers must be double-quoted, not parameterized.\n const safeDbName = dbName.replace(/\"/g, '\"\"');\n const safeSourceDb = sourceDb.replace(/\"/g, '\"\"');\n try {\n await this.db.execute(\n sql.raw(`CREATE DATABASE \"${safeDbName}\" TEMPLATE \"${safeSourceDb}\"`)\n );\n } catch (err) {\n const pgError = extractPgError(err);\n if (pgError?.code === PG_OBJECT_IN_USE) {\n // The template — not the new database — is the one still in use.\n throw new Error(\n `Cannot create branch: the source database \"${sourceDb}\" has active connections. ` +\n \"Close other clients or connections and try again.\"\n );\n }\n throw describeBranchDdlError(err, dbName);\n }\n\n // Record metadata in the default database\n const now = new Date();\n await this.db.execute(\n sql`INSERT INTO rebase.branches (name, db_name, parent_db, created_at)\n VALUES (${name}, ${dbName}, ${sourceDb}, ${now.toISOString()})`\n );\n\n return {\n name,\n parentDatabase: sourceDb,\n createdAt: now\n };\n }\n\n /**\n * Delete a branch database and remove its metadata.\n * Cannot delete the main/default database.\n */\n async deleteBranch(name: string): Promise<void> {\n assertValidBranchName(name);\n\n // Safety, first pass: a request that would target the default database\n // is refused before any metadata is read, so the answer costs nothing\n // and cannot depend on what a row happens to say.\n if (toBranchDbName(name) === this.poolManager.defaultDatabaseName) {\n throw new Error(\"Cannot delete the main database.\");\n }\n\n // Verify the branch exists, and take the database name from the row\n // rather than deriving it again. A branch created before names stopped\n // being stripped is recorded as `rb_myfeature` while `my-feature` now\n // derives `rb_my-feature`; re-deriving would drop a database this row\n // never named — or, if that name happened to exist, somebody else's.\n // The stored value is the only one that is true by construction.\n const existing = await this.db.execute(\n sql`SELECT db_name FROM rebase.branches WHERE name = ${name}`\n );\n const existingRows = existing.rows as Record<string, unknown>[];\n if (existingRows.length === 0) {\n throw new Error(`Branch \"${name}\" not found.`);\n }\n const dbName = existingRows[0].db_name as string;\n\n // Safety, second pass: the first checked a name we derived, this checks\n // the one we are about to drop. They differ for any row written under\n // the old scheme, and it is this one that governs.\n if (dbName === this.poolManager.defaultDatabaseName) {\n throw new Error(\"Cannot delete the main database.\");\n }\n\n // Disconnect any pools to this branch before dropping\n await this.poolManager.disconnectDatabase(dbName);\n\n // Drop the database\n const safeDbName = dbName.replace(/\"/g, '\"\"');\n try {\n await this.db.execute(sql.raw(`DROP DATABASE \"${safeDbName}\"`));\n } catch (err) {\n const pgError = extractPgError(err);\n if (pgError?.code === PG_OBJECT_IN_USE) {\n throw new Error(\n `Cannot delete branch \"${name}\": the database has active connections. ` +\n \"Close other clients and try again.\"\n );\n }\n throw describeBranchDdlError(err, dbName);\n }\n\n // Remove metadata\n await this.db.execute(\n sql`DELETE FROM rebase.branches WHERE name = ${name}`\n );\n }\n\n /**\n * List all branches recorded in the metadata table.\n * Optionally fetches database sizes from pg_database.\n */\n async listBranches(): Promise<BranchInfo[]> {\n const result = await this.db.execute(sql.raw(`\n SELECT \n b.name,\n b.parent_db,\n b.created_at,\n pg_database_size(b.db_name) as size_bytes\n FROM ${BRANCHES_TABLE} b\n JOIN pg_database d ON d.datname = b.db_name\n ORDER BY b.created_at DESC\n `));\n\n return (result.rows as Record<string, unknown>[]).map((row) => ({\n name: row.name as string,\n parentDatabase: row.parent_db as string,\n createdAt: new Date(row.created_at as string),\n sizeBytes: row.size_bytes != null ? Number(row.size_bytes) : undefined\n }));\n }\n\n /**\n * Get info about a specific branch.\n */\n async getBranchInfo(name: string): Promise<BranchInfo | undefined> {\n assertValidBranchName(name);\n\n const result = await this.db.execute(sql`\n SELECT\n b.name,\n b.db_name,\n b.parent_db,\n b.created_at\n FROM rebase.branches b\n WHERE b.name = ${name}\n `);\n\n const rows = result.rows as Record<string, unknown>[];\n if (rows.length === 0) return undefined;\n\n const row = rows[0];\n\n // Attempt to get size — may fail if the DB was externally dropped.\n // Same reason as `deleteBranch`: the size of a re-derived name is the\n // size of some other database, or of nothing.\n let sizeBytes: number | undefined;\n try {\n const dbName = row.db_name as string;\n const sizeResult = await this.db.execute(\n sql`SELECT pg_database_size(${dbName}) as size_bytes`\n );\n const sizeRows = sizeResult.rows as Record<string, unknown>[];\n if (sizeRows.length > 0 && sizeRows[0].size_bytes != null) {\n sizeBytes = Number(sizeRows[0].size_bytes);\n }\n } catch {\n // Database might not exist anymore\n }\n\n return {\n name: row.name as string,\n parentDatabase: row.parent_db as string,\n createdAt: new Date(row.created_at as string),\n sizeBytes\n };\n }\n}\n","import { CollectionConfig, NumberProperty, Property, ResolvedRelation, RelationProperty, SecurityOperation, SecurityRule, StringProperty, isPostgresCollectionConfig, DateProperty, ArrayProperty, MapProperty, ReferenceProperty, VectorProperty, BinaryProperty, isManyToMany, type ResolvedManyToMany, type ResolvedBelongsTo, type ResolvedForeignKeyOnTarget, hasForeignKeyOnTarget } from \"@rebasepro/types\";\nimport { getPrimaryKeys } from \"../services/collection-helpers\";\nimport { buildSearchColumnSpec } from \"./search-column\";\nimport { getEnumVarName, getTableName, getTableVarName, resolveCollectionRelations, findRelation, fieldKeyForColumn, securityRuleToConditions, policyToPostgres, getEffectiveSecurityRules, resolveJunctionSpecs, getJunctionSecurityRules, getJunctionCollectionConfig, resolveStringColumnLength, relationalCollections, sortCollectionsBySlug } from \"@rebasepro/common\";\nimport { toSnakeCase, getPolicyNamesForRule } from \"@rebasepro/utils\";\nimport { logger } from \"@rebasepro/server\";\n// --- Helper Functions ---\n\n/**\n * Resolve the SQL column name for a property.\n * Uses the explicit `columnName` when set (e.g. from introspection),\n * falling back to `toSnakeCase(propName)` for manually-authored collections.\n */\nconst JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * A string literal for the generated schema file.\n *\n * Column names, table names and enum values are all written into this file as\n * literals, and none of them is constrained to be quote-free: a Postgres\n * identifier only has to be quoted, and `O'Brien` is an ordinary enum value.\n * Interpolating them raw ended the literal early — for enum values, inside\n * single quotes, where an apostrophe is not an edge case.\n */\nconst quote = (value: string): string => JSON.stringify(value);\n\n/** An object key: verbatim when it is an identifier, quoted otherwise. */\nconst propKey = (name: string): string => (JS_IDENTIFIER.test(name) ? name : quote(name));\n\n/**\n * A property access on a generated table variable.\n *\n * `users.full name` is not an expression; `users[\"full name\"]` is, and Drizzle\n * treats the two identically.\n */\nconst member = (object: string, key: string): string =>\n (JS_IDENTIFIER.test(key) ? `${object}.${key}` : `${object}[${quote(key)}]`);\n\nconst resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\nconst getPrimaryKeyProp = (collection: CollectionConfig): { name: string, type: \"string\" | \"number\", isUuid: boolean } => {\n if (collection.properties) {\n const idPropEntry = Object.entries(collection.properties).find(([_, prop]) => \"isId\" in (prop as unknown as object) && Boolean((prop as unknown as Record<string, unknown>).isId));\n if (idPropEntry) {\n const prop = idPropEntry[1] as unknown as Property;\n const isUuid = prop.type === \"string\" && \"isId\" in prop && (prop as unknown as StringProperty).isId === \"uuid\";\n return { name: idPropEntry[0],\ntype: prop.type === \"number\" ? \"number\" : \"string\",\nisUuid };\n }\n }\n // Fallback\n const idProp = collection.properties?.[\"id\"] as unknown as Property | undefined;\n if (idProp?.type === \"number\") {\n return { name: \"id\",\ntype: \"number\",\nisUuid: false };\n }\n const isUuid = idProp?.type === \"string\" && \"isId\" in idProp && (idProp as unknown as StringProperty).isId === \"uuid\";\n return { name: \"id\",\ntype: \"string\",\nisUuid: isUuid ?? false };\n};\n\n/**\n * Given a raw DB column name (e.g. \"client_id\"), the Drizzle property key that\n * maps to it.\n *\n * One line, because the rule is shared: the Drizzle object key is the wire\n * name, and {@link fieldKeyForColumn} is the one definition of what a column is\n * named on the wire. This used to be a private copy that fell back to the\n * column verbatim, which is how a derived foreign key ended up served as\n * `author_id` beside a hand-authored `displayName`.\n */\nconst resolvePropertyKeyForColumn = (collection: CollectionConfig, column: string): string =>\n fieldKeyForColumn(collection, column);\n\nconst isNumericId = (collection: CollectionConfig): boolean => {\n return getPrimaryKeyProp(collection).type === \"number\";\n};\n\nconst getPrimaryKeyName = (collection: CollectionConfig): string => {\n return getPrimaryKeyProp(collection).name;\n};\n\nconst isIdProperty = (propName: string, prop: Property, collection: CollectionConfig): boolean => {\n if (\"isId\" in prop && Boolean(prop.isId)) return true;\n\n // We only fallback to \"id\" if NO property is explicitly marked with `isId: true` or a generator string\n const hasExplicitId = Object.values(collection.properties ?? {}).some(p => \"isId\" in (p as unknown as object) && Boolean((p as unknown as Record<string, unknown>).isId));\n return !hasExplicitId && propName === \"id\";\n};\n\n/**\n * The Drizzle column declaration a property compiles to, or `null` when the\n * property puts no column on *this* table (an inverse relation, whose column\n * lives on the target). Exported so it can be checked against its DDL twin\n * `getSqlColumnType` directly — the two disagreeing is what left `geopoint`\n * with a database column and no Drizzle key.\n */\nexport const getDrizzleColumn = (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]): string | null => {\n\n const colName = resolveColumnName(propName, prop);\n let columnDefinition: string;\n\n switch (prop.type) {\n case \"string\": {\n const stringProp = prop as unknown as StringProperty;\n if (stringProp.enum) {\n const enumName = getEnumVarName(getTableName(collection), propName);\n columnDefinition = `${enumName}(${quote(colName)})`;\n } else if (\"isId\" in stringProp && stringProp.isId === \"uuid\") {\n columnDefinition = `uuid(${quote(colName)})`;\n } else if (stringProp.columnType === \"uuid\") {\n columnDefinition = `uuid(${quote(colName)})`;\n } else if (stringProp.columnType === \"char\") {\n columnDefinition = `char(${quote(colName)}, { length: ${resolveStringColumnLength(stringProp)} })`;\n } else if (stringProp.columnType === \"varchar\") {\n // The length is not optional decoration: `varchar(\"col\")` with\n // no length is an UNBOUNDED varchar in Postgres, which is what\n // this emitted while the DDL generator emitted VARCHAR(255) for\n // the very same property.\n columnDefinition = `varchar(${quote(colName)}, { length: ${resolveStringColumnLength(stringProp)} })`;\n } else {\n // `text` is the default, and the only length-unbounded choice.\n // Ask for `varchar` explicitly if you want the length constraint.\n columnDefinition = `text(${quote(colName)})`;\n }\n if (isIdProperty(propName, prop, collection)) {\n columnDefinition += \".primaryKey()\";\n }\n if (\"isId\" in stringProp && stringProp.isId !== \"manual\" && stringProp.isId !== true) {\n if (stringProp.isId === \"uuid\") {\n columnDefinition += \".defaultRandom()\";\n } else if (stringProp.isId === \"cuid\") {\n columnDefinition += \".default(sql`cuid()`)\";\n } else if (typeof stringProp.isId === \"string\") {\n const sqlContent = stringProp.isId.startsWith(\"sql`\") && stringProp.isId.endsWith(\"`\")\n ? stringProp.isId.substring(4, stringProp.isId.length - 1)\n : stringProp.isId;\n columnDefinition += `.default(sql\\`${sqlContent}\\`)`;\n }\n }\n if (stringProp.validation?.unique) {\n columnDefinition += \".unique()\";\n }\n break;\n }\n case \"number\": {\n const numProp = prop as unknown as NumberProperty;\n const isId = isIdProperty(propName, prop, collection);\n\n let baseType = (numProp.validation?.integer || isId) ? `integer(${quote(colName)})` : `numeric(${quote(colName)})`;\n if (numProp.columnType) {\n if (numProp.columnType === \"double precision\") baseType = `doublePrecision(${quote(colName)})`;\n // `bigint` and `bigserial` are the only pg-core builders that\n // *require* a config argument: without `mode`, drizzle cannot\n // know whether to hand back a `number` or a `bigint`, and the\n // emitted call does not typecheck.\n //\n // This is why `schema.generated.ts` drifted. Regenerating it\n // produced a file that would not compile, so the bigint lines\n // were hand-patched — and every regeneration after that looked\n // like a large, alarming diff nobody wanted to ship. The file\n // then sat stale for long enough that a security fix to two RLS\n // policies never reached production.\n //\n // `number` rather than `bigint`: these are counters and byte\n // totals that every caller already treats as numbers, and\n // switching the runtime type would be a breaking change to\n // every consumer of the generated schema.\n else if (numProp.columnType === \"bigint\" || numProp.columnType === \"bigserial\") {\n baseType = `${numProp.columnType}(${quote(colName)}, { mode: \"number\" })`;\n }\n else baseType = `${numProp.columnType}(${quote(colName)})`;\n }\n\n if (\"isId\" in numProp && numProp.isId === \"increment\") {\n columnDefinition = `${baseType}.generatedByDefaultAsIdentity()`;\n } else if (\"isId\" in numProp && typeof numProp.isId === \"string\" && numProp.isId !== \"manual\") {\n columnDefinition = baseType;\n const sqlContent = numProp.isId.startsWith(\"sql`\") && numProp.isId.endsWith(\"`\")\n ? numProp.isId.substring(4, numProp.isId.length - 1)\n : numProp.isId;\n columnDefinition += `.default(sql\\`${sqlContent}\\`)`;\n } else {\n columnDefinition = baseType;\n }\n\n if (isId) {\n columnDefinition += \".primaryKey()\";\n }\n if (numProp.validation?.unique) {\n columnDefinition += \".unique()\";\n }\n break;\n }\n case \"boolean\":\n columnDefinition = `boolean(${quote(colName)})`;\n break;\n case \"date\": {\n const dateProp = prop as DateProperty;\n if (dateProp.columnType === \"date\") {\n columnDefinition = `date(${quote(colName)}, { mode: 'string' })`;\n } else if (dateProp.columnType === \"time\") {\n columnDefinition = `time(${quote(colName)})`;\n } else {\n columnDefinition = `timestamp(${quote(colName)}, { withTimezone: true, mode: 'string' })`;\n }\n // autoValue: database-level default for initial value on INSERT\n if (dateProp.autoValue === \"on_create\" || dateProp.autoValue === \"on_update\") {\n columnDefinition += \".default(sql`now()`)\";\n }\n break;\n }\n case \"map\": {\n const mapProp = prop as MapProperty;\n if (mapProp.columnType === \"json\") {\n columnDefinition = `json(${quote(colName)})`;\n } else {\n columnDefinition = `jsonb(${quote(colName)})`;\n }\n break;\n }\n case \"geopoint\": {\n // `{ latitude, longitude }`, which is what the OpenAPI schema, the\n // generated TS type and the admin's field binding all describe.\n // This arm did not exist: `geopoint` fell to `default: return null`\n // and the caller dropped the column, so the DDL generator created a\n // column the Drizzle table had no key for — and every write to it\n // was silently discarded with a 201.\n columnDefinition = `jsonb(${quote(colName)})`;\n break;\n }\n case \"array\": {\n const arrayProp = prop as ArrayProperty;\n let colType = arrayProp.columnType;\n if (!colType && arrayProp.of && !Array.isArray(arrayProp.of)) {\n const ofProp = arrayProp.of as Property;\n if (ofProp.type === \"string\") {\n colType = \"text[]\";\n } else if (ofProp.type === \"number\") {\n colType = ofProp.validation?.integer ? \"integer[]\" : \"numeric[]\";\n } else if (ofProp.type === \"boolean\") {\n colType = \"boolean[]\";\n }\n }\n\n if (colType === \"json\") {\n columnDefinition = `json(${quote(colName)})`;\n } else if (colType === \"text[]\") {\n columnDefinition = `text(${quote(colName)}).array()`;\n } else if (colType === \"integer[]\") {\n columnDefinition = `integer(${quote(colName)}).array()`;\n } else if (colType === \"boolean[]\") {\n columnDefinition = `boolean(${quote(colName)}).array()`;\n } else if (colType === \"numeric[]\") {\n columnDefinition = `numeric(${quote(colName)}).array()`;\n } else {\n columnDefinition = `jsonb(${quote(colName)})`;\n }\n break;\n }\n case \"vector\": {\n const vp = prop as VectorProperty;\n columnDefinition = `vector(${quote(colName)}, { dimensions: ${vp.dimensions} })`;\n break;\n }\n case \"binary\": {\n columnDefinition = `customType({ dataType() { return 'bytea'; } })(${quote(colName)})`;\n break;\n }\n case \"relation\": {\n const refProp = prop as RelationProperty;\n const resolvedRelations = resolveCollectionRelations(collection);\n const relation = findRelation(resolvedRelations, refProp.relation?.relationName ?? propName);\n\n // Only `belongsTo` puts a column on this table; every other kind\n // is a column on the target, a junction row, or a join chain.\n if (!relation || relation.kind !== \"belongsTo\") {\n return null;\n }\n\n // If a property of its own already declares this foreign key, that\n // property emits the column and this relation must not emit a second\n // key for it. Asked of the *field key*, not the column: a property\n // declared `authorId` with `columnName: \"author_id\"` did not answer\n // to `properties[\"author_id\"]`, so both sides emitted and the table\n // carried two Drizzle keys pointing at one column.\n const fkFieldKey = fieldKeyForColumn(collection, relation.localKey);\n if (collection.properties[fkFieldKey] && propName !== fkFieldKey) {\n return null;\n }\n\n let targetCollection: CollectionConfig;\n try {\n targetCollection = relation.target();\n } catch {\n return null; // Cannot resolve target\n }\n\n const fkColumnName = relation.localKey;\n const targetTableVar = getTableVarName(getTableName(targetCollection));\n const pkProp = getPrimaryKeyProp(targetCollection);\n const targetIdField = pkProp.name;\n // `quote`, like every other column literal in this file: a column\n // name only has to be quotable in Postgres, and this one is derived\n // from a relation the author wrote.\n const baseColumn = pkProp.type === \"number\"\n ? `integer(${quote(fkColumnName)})`\n : (pkProp.isUuid ? `uuid(${quote(fkColumnName)})` : `text(${quote(fkColumnName)})`);\n\n const onUpdate = relation.onUpdate ? `onUpdate: \"${relation.onUpdate}\"` : \"\";\n const required = prop.validation?.required;\n const onDeleteVal = relation.onDelete ?? (required ? \"cascade\" : \"set null\");\n const onDelete = `onDelete: \\\"${onDeleteVal}\\\"`;\n\n const refOptionsParts = [onUpdate, onDelete].filter(Boolean);\n const refOptions = refOptionsParts.length > 0 ? `{ ${refOptionsParts.join(\", \")} }` : \"\";\n\n let columnDef = `${baseColumn}.references(() => ${member(targetTableVar, targetIdField)}${refOptions ? `, ${refOptions}` : \"\"})`;\n\n if (required) {\n columnDef += \".notNull()\";\n }\n\n // Key by the wire name, column by `localKey`. They are two different\n // names for one thing and the generated line is where they meet:\n // `authorId: integer(\"author_id\")`.\n return ` ${propKey(fkFieldKey)}: ${columnDef}`;\n }\n case \"reference\": {\n const refProp = prop as ReferenceProperty;\n const targetCollection = collections.find(c => c.slug === refProp.path || getTableName(c) === refProp.path);\n if (!targetCollection) {\n columnDefinition = `text(${quote(colName)})`;\n break;\n }\n\n const pkProp = getPrimaryKeyProp(targetCollection);\n const targetTableVar = getTableVarName(getTableName(targetCollection));\n const targetIdField = pkProp.name;\n const baseColumn = pkProp.type === \"number\" ? `integer(${quote(colName)})` : (pkProp.isUuid ? `uuid(${quote(colName)})` : `text(${quote(colName)})`);\n\n const required = prop.validation?.required;\n const onDelete = required ? \"cascade\" : \"set null\";\n const refOptions = `{ onDelete: \"${onDelete}\" }`;\n\n columnDefinition = `${baseColumn}.references(() => ${member(targetTableVar, targetIdField)}, ${refOptions})`;\n if (required) {\n columnDefinition += \".notNull()\";\n }\n // Skip the standard notNull() handling below because we did it here with references\n return ` ${propKey(propName)}: ${columnDefinition}`;\n }\n default:\n // Not `return null`. A `null` here means \"this property puts no\n // column on this table\", which is true of an inverse relation and\n // false of everything else — and the caller cannot tell the two\n // apart, so a type this switch simply forgot produced a table\n // missing a column while the DDL generator happily created one.\n // That is how `geopoint` stayed unpersistable: writes to it were\n // dropped before the SQL was built, forever, with a 201. A property\n // type nobody mapped is a generator bug, and it says so here rather\n // than at some caller's next INSERT.\n throw new Error(\n `No Postgres column mapping for property '${propName}' of type ` +\n `'${(prop as Property).type}' in collection '${collection.slug}'. ` +\n \"Add a case to `getDrizzleColumn` (and to `getSqlColumnType`, which must agree).\"\n );\n }\n\n if (prop.validation?.required) {\n columnDefinition += \".notNull()\";\n }\n\n return ` ${propKey(propName)}: ${columnDefinition}`;\n};\n\n/**\n * Wraps a compiled SQL clause in a Drizzle `sql\\`...\\`` template literal.\n *\n * The clause is SQL being written into a TypeScript file, so it has to survive\n * being read back as a template literal. Three characters do not:\n *\n * - `` ` `` closes the template early, and the rest of the clause becomes code.\n * - `${` opens an interpolation — the file stops compiling, or worse, compiles\n * against whatever identifier happens to be in scope.\n * - `\\` is an escape, and Drizzle's `sql` tag reads the *cooked* strings, not\n * `.raw`. So a policy written as `email ~ '^admin\\.user@corp\\.com$'` reaches\n * the database as `^admin.user@corp.com$`, where every `\\.` now matches any\n * character. A `USING` clause is a security boundary and that one silently\n * widened it — the SQL file emitted by the DDL generator kept the backslashes\n * while this path dropped them, so the two disagreed about who could read the\n * table.\n *\n * Escaping here rather than in the compiler: the clause is correct SQL, and it\n * is only this destination that has an opinion about backslashes.\n */\nconst wrapSql = (clause: string): string =>\n `sql\\`${clause.replace(/\\\\/g, \"\\\\\\\\\").replace(/`/g, \"\\\\`\").replace(/\\$\\{/g, \"\\\\${\")}\\``;\n\n/**\n * Generates a deterministic hash based on the rule configuration.\n */\n\n/**\n * Generates Drizzle pgPolicy() calls from a declarative SecurityRule definition.\n *\n * Supports the full spectrum:\n * - Convenience shortcuts: ownerField, access, roles\n * - Raw SQL: using, withCheck\n * - Mode: permissive (default) or restrictive\n * - operations[] array: generates one policy per operation\n * - Combinations: roles + ownerField, roles + raw SQL, etc.\n */\ntype ResolveCollection = (slug: string) => CollectionConfig | undefined;\n\nconst generatePolicyCode = (collection: CollectionConfig, rule: SecurityRule, index: number, resolveCollection: ResolveCollection): string => {\n const tableName = getTableName(collection);\n // Resolve operations: operations[] takes precedence over operation (singular)\n const ops: readonly SecurityOperation[] = rule.operations && rule.operations.length > 0\n ? rule.operations\n : [rule.operation ?? \"all\"];\n\n const policyNames = getPolicyNamesForRule(rule, tableName);\n\n // Generate one pgPolicy per operation\n return ops.map((op, opIdx) => {\n return generateSinglePolicyCode(collection, rule, op, policyNames[opIdx], resolveCollection);\n }).join(\"\");\n};\n\n/**\n * Generates a single pgPolicy() call for one specific operation.\n */\nconst generateSinglePolicyCode = (collection: CollectionConfig, rule: SecurityRule, operation: SecurityOperation, policyName: string, resolveCollection: ResolveCollection): string => {\n const mode = rule.mode ?? \"permissive\";\n\n // Determine which clauses this operation needs:\n // SELECT, DELETE → USING only\n // INSERT → WITH CHECK only\n // UPDATE, ALL → both USING and WITH CHECK\n const needsUsing = operation !== \"insert\";\n const needsWithCheck = operation !== \"select\" && operation !== \"delete\";\n\n // Desugar the rule (access / ownerField / roles / structured condition / raw\n // SQL) into the shared PolicyExpression model, then compile to SQL — the same\n // normalization the DDL generator and the client-side evaluator use.\n const { usingExpr, withCheckExpr } = securityRuleToConditions(rule);\n\n let usingClause = needsUsing && usingExpr ? wrapSql(policyToPostgres(usingExpr, collection, { resolveCollection })) : null;\n let withCheckClause = needsWithCheck && withCheckExpr ? wrapSql(policyToPostgres(withCheckExpr, collection, { resolveCollection })) : null;\n\n // Fallback: if we still have no clauses, deny all (safety net)\n if (!usingClause && needsUsing) {\n usingClause = \"sql`false`\";\n }\n if (!withCheckClause && needsWithCheck) {\n withCheckClause = \"sql`false`\";\n }\n\n // Build the policy options object\n const parts: string[] = [];\n parts.push(`as: \"${mode}\"`);\n parts.push(`for: \"${operation}\"`);\n const toRoles = rule.pgRoles ? [...rule.pgRoles].sort() : [\"public\"];\n parts.push(`to: [${toRoles.map(r => `\"${r}\"`).join(\", \")}]`);\n if (usingClause) parts.push(`using: ${usingClause}`);\n if (withCheckClause) parts.push(`withCheck: ${withCheckClause}`);\n\n return ` pgPolicy(${quote(policyName)}, { ${parts.join(\", \")} }),\\n`;\n};\n\n/**\n * Computes a deterministic shared relation name for Drizzle.\n *\n * Drizzle requires both sides of a relation (owning + inverse) to use the\n * exact same `relationName` string so it can pair them. Each collection\n * definition may use a different local `relationName`, so we need a canonical\n * form that both sides can independently compute.\n *\n * Strategy: `{owningTable}_{foreignKey}`\n * - owning side → `{thisTable}_{localKey}` e.g. \"jobs_company_id\"\n * - inverse side → `{targetTable}_{foreignKeyOnTarget}` e.g. \"jobs_company_id\"\n *\n * For M2M with junction tables the owning relation name is already shared via\n * the junction table wiring, so we keep it as-is.\n *\n * Falls back to the local relation name when the counterpart can't be resolved.\n */\nconst computeSharedRelationName = (\n rel: ResolvedRelation,\n sourceCollection: CollectionConfig,\n _collections: CollectionConfig[]\n): string => {\n const fallback = rel.relationName ?? toSnakeCase(rel.target().slug);\n\n // Both sides of a link must derive the same name, so each resolves the\n // column to its Drizzle property key and builds the name from the table\n // that actually owns it.\n if (rel.kind === \"belongsTo\") {\n const normalisedKey = resolvePropertyKeyForColumn(sourceCollection, rel.localKey);\n return `${getTableName(sourceCollection)}_${normalisedKey}`;\n }\n\n if (rel.kind === \"hasMany\" || rel.kind === \"hasOne\") {\n // The owning table is the *target*; the column is foreignKeyOnTarget.\n try {\n const targetCollection = rel.target();\n const normalisedFK = resolvePropertyKeyForColumn(targetCollection, rel.foreignKeyOnTarget);\n return `${getTableName(targetCollection)}_${normalisedFK}`;\n } catch {\n return fallback;\n }\n }\n\n // manyToMany is named through its junction wiring; `via` is not emitted as\n // a Drizzle relation at all. Both keep the local name.\n return fallback;\n};\n\n// --- Main Schema Generation Logic ---\nexport const generateSchema = async (allCollections: CollectionConfig[], stripPolicies = false): Promise<string> => {\n // A Firestore or MongoDB collection has no table to generate, and generating\n // one for it is not merely wasted output: `db push` would create it, and\n // `rebase doctor` would then report the store the collection actually reads\n // from as drift. Non-SQL collections leave the toolchain here, once, rather\n // than being filtered again in each stage below.\n //\n // Sorted here rather than in the writer: the output is order-dependent and\n // `rebase doctor` regenerates it in memory to diff against the file on\n // disk. With the sort in the writer only, a project whose file order\n // differed from its slug order was reported stale forever.\n const collections = sortCollectionsBySlug(relationalCollections(allCollections));\n let schemaContent = \"// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\\n\\n\";\n\n\n const hasUuid = collections.some(c =>\n c.properties && Object.values(c.properties).some(\n (p: Property) => p.type === \"string\" && ((p as unknown as Record<string, unknown>).autoValue === \"uuid\" || (p as unknown as Record<string, unknown>).isId === \"uuid\")\n )\n );\n\n const hasVector = collections.some(c =>\n c.properties && Object.values(c.properties).some(\n (p: Property) => p.type === \"vector\"\n )\n );\n\n const hasBinary = collections.some(c =>\n c.properties && Object.values(c.properties).some(\n (p: Property) => p.type === \"binary\"\n )\n );\n\n // drizzle ships no `tsvector` builder, so an opted-in search column reaches\n // the schema the same way `bytea` does — through `customType`.\n const hasSearch = collections.some(c => buildSearchColumnSpec(c) !== undefined);\n\n // Always import pgPolicy and sql — RLS is enabled on every table (secure by default)\n const pgCoreImports = [\"primaryKey\", \"pgTable\", \"integer\", \"varchar\", \"text\", \"char\", \"boolean\", \"timestamp\", \"date\", \"time\", \"jsonb\", \"json\", \"pgEnum\", \"numeric\", \"real\", \"doublePrecision\", \"bigint\", \"serial\", \"bigserial\", \"pgPolicy\"];\n if (hasUuid) pgCoreImports.push(\"uuid\");\n if (hasVector) pgCoreImports.push(\"vector\");\n if (hasBinary || hasSearch) pgCoreImports.push(\"customType\");\n\n const uniqueSchemas = Array.from(new Set(\n collections.map(c => isPostgresCollectionConfig(c) ? c.schema : undefined).filter(Boolean)\n ));\n if (uniqueSchemas.length > 0) {\n pgCoreImports.push(\"pgSchema\");\n }\n\n schemaContent += `import { ${pgCoreImports.join(\", \")} } from 'drizzle-orm/pg-core';\\n`;\n schemaContent += \"import { relations as drizzleRelations, sql } from 'drizzle-orm';\\n\\n\";\n\n uniqueSchemas.forEach(schema => {\n schemaContent += `export const ${schema}Schema = pgSchema(\"${schema}\");\\n`;\n });\n if (uniqueSchemas.length > 0) {\n schemaContent += \"\\n\";\n }\n\n const exportedTableVars: string[] = [];\n const exportedEnumVars: string[] = [];\n const exportedRelationVars: string[] = [];\n\n const allTablesToGenerate = new Map<string, {\n collection: CollectionConfig,\n isJunction?: boolean,\n relation?: ResolvedRelation,\n sourceCollection?: CollectionConfig\n }>();\n\n // 1. Generate Enums\n collections.forEach(collection => {\n const collectionPath = getTableName(collection);\n Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {\n\n if ((\"enum\" in prop) && (prop.type === \"string\" || prop.type === \"number\") && prop.enum) {\n const enumVarName = getEnumVarName(collectionPath, propName);\n const enumDbName = `${collectionPath}_${resolveColumnName(propName, prop)}`;\n const values = Array.isArray(prop.enum)\n ? (prop.enum as (string | number | { id: string | number })[]).map((v: string | number | { id: string | number }) =>\n String(typeof v === \"object\" && v !== null && \"id\" in v ? v.id : v)\n )\n : Object.keys(prop.enum);\n if (values.length > 0) {\n schemaContent += `export const ${enumVarName} = pgEnum(${quote(enumDbName)}, [${values.map(v => quote(v)).join(\", \")}]);\\n`;\n if (!exportedEnumVars.includes(enumVarName)) exportedEnumVars.push(enumVarName);\n }\n }\n });\n });\n schemaContent += \"\\n\";\n\n // Junction policy derivation needs every declaring side of each junction,\n // not just the first relation that reached it in the walk below.\n const junctionSpecs = resolveJunctionSpecs(collections);\n\n // 2. Identify all tables (collections and junction tables only)\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (tableName) {\n allTablesToGenerate.set(tableName, { collection });\n }\n\n const resolvedRelations = resolveCollectionRelations(collection);\n for (const relation of Object.values(resolvedRelations)) {\n if (isManyToMany(relation)) { // Standard M2M junction table\n const junctionTableName = relation.through.table;\n if (!allTablesToGenerate.has(junctionTableName)) {\n allTablesToGenerate.set(junctionTableName, {\n collection: {\n table: junctionTableName,\n properties: {}\n } as CollectionConfig,\n isJunction: true,\n relation: relation,\n sourceCollection: collection\n });\n }\n }\n // joinPath relations use existing user-controlled tables - no generation needed\n }\n }\n\n // 3. Generate pgTable definitions for all unique tables\n for (const [tableName, {\n collection,\n isJunction,\n relation,\n sourceCollection\n }] of allTablesToGenerate.entries()) {\n const tableVarName = getTableVarName(tableName);\n if (isJunction && relation && sourceCollection && isManyToMany(relation)) {\n const targetCollection = relation.target();\n // Junctions live in `public`, full stop — `resolveJunctionSpecs`\n // hardcodes that, so it is where `planJunctionTables` CREATEs them\n // and where the derived RLS policies are applied. Inheriting the\n // endpoint's schema here (as this used to) put the junction of any\n // m2m onto `users` in `rebase`, while its policies were still\n // created against `public.<junction>` — RLS enabled on one table,\n // rows written to another. The three generators have to agree, and\n // the other two already did.\n const tableCreator = \"pgTable\";\n const baseTableName = tableName.includes(\".\") ? tableName.split(\".\").pop()! : tableName;\n const {\n sourceColumn,\n targetColumn\n } = relation.through;\n\n const onDelete = relation.onDelete ?? \"cascade\";\n const refOptions = `{ onDelete: \\\"${onDelete}\\\" }`;\n\n // `text`, matching the string default: a junction column must have the\n // same type as the primary key it references.\n const sourceColType = isNumericId(sourceCollection) ? \"integer\" : (getPrimaryKeyProp(sourceCollection).isUuid ? \"uuid\" : \"text\");\n const targetColType = isNumericId(targetCollection) ? \"integer\" : (getPrimaryKeyProp(targetCollection).isUuid ? \"uuid\" : \"text\");\n const sourceId = getPrimaryKeyName(sourceCollection);\n const targetId = getPrimaryKeyName(targetCollection);\n\n schemaContent += `export const ${tableVarName} = ${tableCreator}(\\\"${baseTableName}\\\", {\\n`;\n // The junction block was the one place these three helpers were not\n // applied, so a junction column containing a space or a hyphen —\n // both legal in Postgres — produced a file that does not parse.\n schemaContent += ` ${propKey(sourceColumn)}: ${sourceColType}(${quote(sourceColumn)}).notNull().references(() => ${member(getTableVarName(getTableName(sourceCollection)), sourceId)}, ${refOptions}),\\n`;\n schemaContent += ` ${propKey(targetColumn)}: ${targetColType}(${quote(targetColumn)}).notNull().references(() => ${member(getTableVarName(getTableName(targetCollection)), targetId)}, ${refOptions}),\\n`;\n schemaContent += \"}, (table) => ([\\n\";\n schemaContent += ` primaryKey({ columns: [${member(\"table\", sourceColumn)}, ${member(\"table\", targetColumn)}] }),\\n`;\n\n // Junctions are generated tables like any other: locked by default,\n // with derived policies (reads follow the endpoints, writes follow\n // the declaring side's update rules). RLS is enabled regardless of\n // policy stripping — a bare junction must default-deny, not fail open.\n const junctionSpec = junctionSpecs.get(baseTableName);\n if (!stripPolicies && junctionSpec) {\n const junctionCollection = getJunctionCollectionConfig(junctionSpec);\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n getJunctionSecurityRules(junctionSpec).forEach((rule: SecurityRule, idx: number) => {\n schemaContent += generatePolicyCode(junctionCollection, rule, idx, resolveCollection);\n });\n }\n schemaContent += \"])).enableRLS();\\n\\n\";\n } else if (!isJunction) {\n const schema = isPostgresCollectionConfig(collection) ? collection.schema : undefined;\n const tableCreator = schema ? `${schema}Schema.table` : \"pgTable\";\n const baseTableName = tableName.includes(\".\") ? tableName.split(\".\").pop()! : tableName;\n schemaContent += `export const ${tableVarName} = ${tableCreator}(\\\"${baseTableName}\\\", {\\n`;\n const columns = new Set<string>();\n Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {\n const columnString = getDrizzleColumn(propName, prop as Property, collection, collections);\n if (columnString) columns.add(columnString);\n\n });\n\n // The opt-in search column. Declared with its real generation\n // expression rather than as a bare custom type: `schema.generated.ts`\n // is what a developer running drizzle-kit themselves diffs against,\n // and a column declared without its expression reads to drizzle-kit\n // as one it should alter.\n const searchSpec = buildSearchColumnSpec(collection);\n if (searchSpec) {\n columns.add(\n ` ${propKey(searchSpec.column)}: customType({ dataType() { return 'tsvector'; } })(${quote(searchSpec.column)})` +\n `.generatedAlwaysAs(sql\\`${searchSpec.expression}\\`)`\n );\n if (searchSpec.fuzzy) {\n columns.add(\n ` ${propKey(searchSpec.fuzzy.column)}: text(${quote(searchSpec.fuzzy.column)})` +\n `.generatedAlwaysAs(sql\\`${searchSpec.fuzzy.expression}\\`)`\n );\n }\n }\n\n // A collection that declares no primary key gets an implicit one:\n // `id TEXT PRIMARY KEY`. The DDL generator emits the same column,\n // and `derivePrimaryKeys` reads it back.\n const hasIdColumn = Array.from(columns).some(col => col.includes(\".primaryKey()\"));\n if (!hasIdColumn) {\n columns.add(\" id: text(\\\"id\\\").primaryKey()\");\n }\n\n schemaContent += `${Array.from(columns).join(\",\\n\")}`;\n\n const securityRules = getEffectiveSecurityRules(collection);\n if (!stripPolicies && securityRules.length > 0) {\n schemaContent += \"\\n}, (table) => ([\\n\";\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n securityRules.forEach((rule: SecurityRule, idx: number) => {\n schemaContent += generatePolicyCode(collection, rule, idx, resolveCollection);\n });\n schemaContent += \"])).enableRLS();\\n\\n\";\n } else {\n // No explicit policies — RLS enabled with deny-all default (Postgres denies\n // everything when RLS is on and no permissive policies exist).\n schemaContent += \"\\n}).enableRLS();\\n\\n\";\n }\n }\n if (!exportedTableVars.includes(tableVarName)) exportedTableVars.push(tableVarName);\n }\n\n // 4. Generate Drizzle Relations\n for (const [tableName, {\n collection,\n isJunction\n }] of allTablesToGenerate.entries()) {\n const tableVarName = getTableVarName(tableName);\n const tableRelations: string[] = [];\n\n if (isJunction) {\n const relationInfo = Array.from(allTablesToGenerate.values()).find(v => v.isJunction && getTableName(v.collection) === tableName);\n if (relationInfo && relationInfo.relation && relationInfo.sourceCollection && isManyToMany(relationInfo.relation)) {\n const {\n relation,\n sourceCollection\n } = relationInfo;\n const targetCollection = relation.target();\n const sourceTableVar = getTableVarName(getTableName(sourceCollection));\n const targetTableVar = getTableVarName(getTableName(targetCollection));\n const sourceId = getPrimaryKeyName(sourceCollection);\n const targetId = getPrimaryKeyName(targetCollection);\n\n if (!relation?.through)\n throw new Error(\"Internal, the relation should have a through property. Relations passed to this script should sanitized first with sanitizeRelation().\");\n\n // The owning relation's name — used on the source side of the junction\n const owningRelationName = relation.relationName ?? toSnakeCase(getTableName(targetCollection));\n\n // Find the inverse relation name on the target collection (if any)\n // This is needed so the junction's target-side one() can pair with the\n // inverse many() on the target table.\n let inverseRelationName: string | null = null;\n try {\n const targetRelations = resolveCollectionRelations(targetCollection);\n for (const [, targetRel] of Object.entries(targetRelations)) {\n if (targetRel.kind !== \"belongsTo\" &&\n targetRel.cardinality === \"many\" &&\n targetRel.relationName === owningRelationName) {\n inverseRelationName = targetRel.relationName ?? null;\n break;\n }\n }\n } catch {\n // ignore — inverse side may not exist\n }\n\n // Source side one(): pairs with owning table's many(junctionTable, { relationName })\n tableRelations.push(` ${quote(relation.through.sourceColumn)}: one(${sourceTableVar}, {\\n fields: [${member(tableVarName, relation.through.sourceColumn)}],\\n references: [${member(sourceTableVar, sourceId)}],\\n relationName: ${quote(owningRelationName)}\\n })`);\n\n // Target side one(): pairs with inverse table's many(junctionTable, { relationName })\n // Always emit a relationName to avoid collisions with the source-side's owningRelationName.\n // When no inverse relation exists on the target collection, synthesize a unique name.\n const targetRelationName = inverseRelationName\n ? inverseRelationName\n : `${tableName}_${relation.through.targetColumn}`;\n tableRelations.push(` ${quote(relation.through.targetColumn)}: one(${targetTableVar}, {\\n fields: [${member(tableVarName, relation.through.targetColumn)}],\\n references: [${member(targetTableVar, targetId)}],\\n relationName: ${quote(targetRelationName)}\\n })`);\n }\n } else {\n const resolvedRelations = resolveCollectionRelations(collection);\n // Defensive safety net: track emitted `drizzleRelationName` values\n // to prevent duplicate one()/many() entries in the generated schema.\n // The root deduplication happens inside resolveCollectionRelations,\n // but this guards against any future regressions in that utility.\n const emittedRelationNames = new Set<string>();\n for (const [relationKey, rel] of Object.entries(resolvedRelations)) {\n try {\n const target = rel.target();\n const targetTableVar = getTableVarName(getTableName(target));\n\n // Compute a deterministic shared relationName for Drizzle.\n // Both sides of an owning/inverse pair MUST share the same\n // relationName, otherwise Drizzle cannot pair them.\n //\n // Strategy: use \"{ownerTable}_{foreignKey}\" which is\n // computable from either side:\n // - owning side: {thisTable}_{localKey}\n // - inverse side: {targetTable}_{foreignKeyOnTarget}\n const drizzleRelationName = computeSharedRelationName(rel, collection, collections);\n\n // Skip if we've already emitted a relation with this drizzleRelationName\n // for this table — prevents duplicate definitions when\n // resolveCollectionRelations returns alias entries for the same FK.\n const deduplicationKey = `${drizzleRelationName}::${rel.kind}`;\n if (emittedRelationNames.has(deduplicationKey)) continue;\n emittedRelationNames.add(deduplicationKey);\n\n switch (rel.kind) {\n case \"belongsTo\": {\n // `localKey` is a COLUMN name; the generated Drizzle\n // object is keyed by PROPERTY. They differ whenever\n // the property is camelCase — `user_id` is exposed\n // as `userId` — and emitting the column produces a\n // schema that does not compile. The three other\n // emission sites normalise; this one did not.\n const localFieldKey = resolvePropertyKeyForColumn(collection, rel.localKey);\n tableRelations.push(` ${quote(relationKey)}: one(${targetTableVar}, {\\n fields: [${member(tableVarName, localFieldKey)}],\\n references: [${member(targetTableVar, getPrimaryKeyName(target))}],\\n relationName: ${quote(drizzleRelationName)}\\n })`);\n break;\n }\n\n case \"hasOne\":\n // The foreign key lives on the TARGET table. Drizzle pairs\n // the two sides by `relationName` alone — giving\n // `fields`/`references` here is invalid and crashes\n // `normalizeRelation` with \"Cannot read properties of\n // undefined (reading 'referencedTable')\".\n // A `relationName` is authored, and a `\"` in one\n // closed this string literal early — in a file that\n // is compiled and imported by the server.\n tableRelations.push(` ${quote(relationKey)}: one(${targetTableVar}, {\\n relationName: ${quote(drizzleRelationName)}\\n })`);\n break;\n\n case \"hasMany\":\n tableRelations.push(` ${quote(relationKey)}: many(${targetTableVar}, { relationName: ${quote(drizzleRelationName)} })`);\n break;\n\n case \"manyToMany\": {\n // Both sides point at the junction. This used to have a\n // second arm that searched the *target's* relations for an\n // owning many-to-many whose name matched, to borrow its\n // junction table — unnecessary now that each side names\n // its own.\n const junctionTableVar = getTableVarName(rel.through.table);\n tableRelations.push(` ${quote(relationKey)}: many(${junctionTableVar}, { relationName: ${quote(drizzleRelationName)} })`);\n break;\n }\n\n case \"via\":\n // A join chain is resolved at query time, not modelled\n // as a Drizzle relation.\n break;\n }\n } catch (e) {\n logger.warn(`Could not generate relation ${relationKey} for ${collection.name}`, { error: e });\n }\n }\n\n // Synthesize missing reciprocal relations\n for (const otherCollection of collections) {\n if (otherCollection.slug === collection.slug) continue;\n\n const otherRelations = resolveCollectionRelations(otherCollection);\n for (const [otherKey, otherRel] of Object.entries(otherRelations)) {\n if (hasForeignKeyOnTarget(otherRel)) {\n try {\n const otherTarget = otherRel.target();\n if (otherTarget.slug === collection.slug) {\n const drizzleRelationName = computeSharedRelationName(otherRel, otherCollection, collections);\n const deduplicationKey = `${drizzleRelationName}::belongsTo`;\n\n if (!emittedRelationNames.has(deduplicationKey)) {\n const otherTableVar = getTableVarName(getTableName(otherCollection));\n // Resolve foreignKeyOnTarget to the Drizzle property key\n // on THIS collection (the owning table). The raw FK column\n // name (e.g. \"client_id\") may differ from the property key\n // (e.g. \"clientId\") when `columnName` is set.\n const drizzleFieldKey = resolvePropertyKeyForColumn(collection, otherRel.foreignKeyOnTarget);\n // The column the far side points at: its\n // primary key, unless the link names another\n // one with `sourceKey`.\n const referencedKey = otherRel.sourceKey\n ? resolvePropertyKeyForColumn(otherCollection, otherRel.sourceKey)\n : getPrimaryKeyName(otherCollection);\n const synthKey = `_synth_${otherTableVar}_${drizzleFieldKey}`;\n tableRelations.push(` ${quote(synthKey)}: one(${otherTableVar}, {\\n fields: [${member(tableVarName, drizzleFieldKey)}],\\n references: [${member(otherTableVar, referencedKey)}],\\n relationName: ${quote(drizzleRelationName)}\\n })`);\n emittedRelationNames.add(deduplicationKey);\n }\n }\n } catch (e) {\n // ignore\n }\n }\n }\n }\n }\n\n if (tableRelations.length > 0) {\n const relVarName = `${tableVarName}Relations`;\n schemaContent += `export const ${relVarName} = drizzleRelations(${tableVarName}, ({ one, many }) => ({\\n${tableRelations.join(\",\\n\")}\\n}));\\n\\n`;\n if (!exportedRelationVars.includes(relVarName)) exportedRelationVars.push(relVarName);\n }\n }\n\n // <<< ADDED: Final aggregated exports block\n const tablesExport = `export const tables = { ${exportedTableVars.join(\", \")} };\\n`;\n const enumsExport = `export const enums = { ${exportedEnumVars.join(\", \")} };\\n`;\n const relationsExport = `export const relations = { ${exportedRelationVars.join(\", \")} };\\n\\n`;\n schemaContent += tablesExport + enumsExport + relationsExport;\n\n return schemaContent;\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 * ## 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 type {\n CollectionConfig,\n Property,\n SchemaChange,\n SchemaChangeKind,\n SchemaChangeVerdict,\n ClassifiedSchemaChanges\n} from \"@rebasepro/types\";\nimport { getTableName } from \"@rebasepro/common\";\nimport { resolveColumnName } from \"./generate-postgres-ddl-logic\";\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 * A change to any of these alters the physical column, which the ensure path\n * cannot do. Compared as a tuple so a change to *any* of them is caught without\n * this module having to re-derive the SQL type — which is the DDL generator's\n * job, and duplicating it here is how the two would drift.\n */\nconst physicalShapeOf = (prop: Property): string => JSON.stringify([\n prop.type,\n (prop as { columnType?: unknown }).columnType ?? null,\n (prop as { dimensions?: unknown }).dimensions ?? null,\n (prop as { isId?: unknown }).isId ?? null,\n (prop.validation as { max?: unknown } | undefined)?.max ?? null\n]);\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}\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/**\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 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 classifyProperties(previous.get(slug)!, collection, changes, facts);\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 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\nfunction classifyProperties(\n before: CollectionConfig,\n after: CollectionConfig,\n changes: SchemaChange[],\n facts?: SchemaFacts\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 for (const [name, prop] of Object.entries(next)) {\n const old = previous[name];\n\n if (!old) {\n // A NOT NULL is checked against rows that are already there, so\n // whether this is safe is a question about the data, not about the\n // configuration. On an empty table the constraint cannot fail and\n // the ensure path applies it; on a populated one it is withheld and\n // the column arrives nullable, which is a database that disagrees\n // with its own config — still worth refusing, now for a reason the\n // reader can act on.\n if (isRequired(prop) && !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(after)}\" 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 } else {\n const column = resolveColumnName(name, prop);\n changes.push({\n kind: \"add-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: isRequired(prop)\n ? `New required property \"${name}\" — adds column \"${column}\" NOT NULL, ` +\n `which \"${getTableName(after)}\" can take because it holds no rows.`\n : `New optional property \"${name}\" — adds column \"${column}\".`\n });\n }\n continue;\n }\n\n classifyProperty(slug, after, name, old, prop, changes, facts);\n }\n\n for (const [name, prop] of Object.entries(previous)) {\n if (next[name]) continue;\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 column ` +\n `\"${resolveColumnName(name, prop)}\" and its data.`,\n remedy:\n \"The ensure path never drops a column. Remove it in a migration you have read, or \" +\n \"leave the column and stop exposing the property.\"\n });\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\n): void {\n const beforeColumn = resolveColumnName(name, before);\n const afterColumn = resolveColumnName(name, after);\n\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 });\n }\n\n if (isIdProperty(before) !== isIdProperty(after)) {\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.`,\n remedy: \"A primary key change rewrites the table and every foreign key into it. Migration only.\"\n });\n return;\n }\n\n if (physicalShapeOf(before) !== physicalShapeOf(after)) {\n changes.push({\n kind: \"change-property-type\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail: `\"${name}\" changes physical type — ${before.type} to ${after.type}.`,\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 });\n }\n\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, collection, name, before, after, changes, facts);\n}\n\nfunction classifyEnum(\n slug: string,\n collection: CollectionConfig,\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/** 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 { DEFAULT_COMMIT_PATHS, type CollectionConfig, type SchemaCommitPaths } from \"@rebasepro/types\";\nimport {\n generatePostgresDdl,\n generatePostgresPoliciesDdl,\n generatePostgresSearchDdl\n} from \"./generate-postgres-ddl-logic\";\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, 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\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/** 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\n const collections = [...new Set(changes.map(change => change.collection))].sort();\n const added = changes.filter(c => c.kind === \"add-collection\").map(c => c.collection);\n const properties = changes.filter(c => c.kind === \"add-property\");\n\n let subject: string;\n if (added.length === 1 && changes.length === 1) {\n subject = `add the ${added[0]} collection`;\n } else if (properties.length === 1 && changes.length === 1) {\n subject = `add ${properties[0].property} to ${properties[0].collection}`;\n } else if (collections.length === 1) {\n subject = `${changes.length} change(s) to ${collections[0]}`;\n } else {\n subject = `${changes.length} change(s) across ${collections.length} collections`;\n }\n\n const body = changes.map(change => `- ${change.detail}`).join(\"\\n\");\n return `feat(schema): ${subject}\\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 (!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 const [schema, ddl, policies, search] = await Promise.all([\n generateSchema(input.after),\n generatePostgresDdl(input.after),\n Promise.resolve(generatePostgresPoliciesDdl(input.after)),\n Promise.resolve(generatePostgresSearchDdl(input.after))\n ]);\n\n const generated: SchemaCommitFile[] = [\n { path: paths.schemaFile, contents: schema },\n { path: paths.ddlFile, contents: ddl },\n { path: paths.policiesFile, contents: policies },\n { path: paths.searchFile, contents: search }\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` keystroke in the panel.\n const previous = planCollectionSchemaEnsure(input.before, existing, options);\n const next = planCollectionSchemaEnsure(input.after, existing, options);\n const already = new Set(previous.statements);\n\n return {\n files: [...(input.sourceFiles ?? []), ...generated],\n statements: next.statements.filter(statement => !already.has(statement)),\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 { DataService } from \"./services/dataService\";\nimport { BranchService } from \"./services/BranchService\";\nimport { RealtimeService } from \"./services/realtimeService\";\nimport { DatabasePoolManager } from \"./databasePoolManager\";\nimport { DrizzleClient } from \"./interfaces\";\nimport {\n DatabaseAdmin,\n DataDriver,\n DeleteProps,\n CollectionConfig,\n FetchCollectionProps,\n FetchOneProps,\n ListenCollectionProps,\n ListenOneProps,\n RebaseCallContext,\n RebaseClient,\n RebaseData,\n RebaseSdkData,\n RestFetchService,\n SaveManyProps,\n SaveProps,\n StorageSource,\n UpdateManyProps,\n DeleteManyProps,\n EntityValues,\n TableColumnInfo,\n TableForeignKeyInfo,\n TableJunctionInfo,\n TableMetadata,\n TablePolicyInfo,\n User\n} from \"@rebasepro/types\";\nimport { sql as drizzleSql } from \"drizzle-orm\";\nimport { buildPropertyCallbacks, buildSdkData, classifyTable, detectJunctionTables, resolveCollectionRelations, updateDateAutoValues } from \"@rebasepro/common\";\nimport { PostgresCollectionRegistry } from \"./collections/PostgresCollectionRegistry\";\nimport { deriveRowAddress } from \"./services/collection-helpers\";\nimport { HistoryService } from \"./history/HistoryService\";\nimport { mergeDeep } from \"@rebasepro/utils\";\nimport { logger } from \"@rebasepro/server\";\nimport { isRoleSwitchingPermissionError } from \"./utils/pg-error-utils\";\nimport { applyAuthContext } from \"./security/rls-enforcement\";\nimport { generateSchemaCommit } from \"./schema/generate-schema-commit\";\nimport { readSchemaFactsFor, type Queryable } from \"./schema/ensure-collection-tables\";\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 * Exact `\"true\"` on purpose, matching the check this replaced. `=1` and `=yes`\n * silently do nothing, which `docs/audits/80-config-and-env.md` already records\n * as a finding across the env surface; fixing it here alone would make this one\n * variable disagree with the rest.\n */\nexport function isRoleSwitchingOptedOut(): boolean {\n return process.env.DISABLE_DB_ROLE_SWITCHING === \"true\";\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 * 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\nexport class PostgresBackendDriver implements DataDriver {\n key = \"postgres\";\n initialised = 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 public client?: RebaseClient;\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 * 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 this.dataService = new DataService(db, registry);\n this.realtimeService = realtimeService;\n this.historyService = historyService;\n this.user = user;\n this.data = buildSdkData(this);\n\n // Initialize BranchService when adminConnectionString is configured\n if (poolManager) {\n this.branchService = new BranchService(db, poolManager);\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 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 existing: await readSchemaFactsFor(\n this.schemaFactsQueryable(),\n after as CollectionConfig[]\n )\n }),\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 * 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 const result = await this.db.execute(drizzleSql.raw(text));\n const rows = (result as unknown as { rows?: T[] }).rows;\n return { rows: rows ?? (Array.isArray(result) ? (result as T[]) : []) };\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) => {\n const row = await raw.fetchOneForRest(collectionPath, id, include, databaseId);\n if (!row) return row;\n const [masked] = await this.applyAfterReadForRest([row], collectionPath);\n return masked;\n }\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 unknown 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 private buildCallContext(): RebaseCallContext {\n return {\n user: this.user,\n driver: this,\n data: this.data,\n client: this.client as RebaseCallContext[\"client\"],\n storageSource: this.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 (globalCallbacks?.afterRead) {\n out = await globalCallbacks.afterRead({\n collection: resolvedCollection as unknown as CollectionConfig,\n path, row: out, context: contextForCallback\n }) ?? out;\n }\n if (callbacks?.afterRead) {\n out = await callbacks.afterRead({\n collection: resolvedCollection as CollectionConfig,\n path, row: out, context: contextForCallback\n }) ?? out;\n }\n if (propertyCallbacks?.afterRead) {\n out = await propertyCallbacks.afterRead({\n collection: resolvedCollection as unknown as CollectionConfig,\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 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 const targetResolved = this.resolveCollectionCallbacks(undefined, 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\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 path,\n collection,\n filter,\n limit,\n offset,\n startAfter,\n orderBy,\n searchString,\n order,\n vectorSearch\n }: FetchCollectionProps<M>): Promise<Record<string, unknown>[]> {\n\n const rows = await this.dataService.fetchCollection<M>(path, {\n filter,\n orderBy,\n order,\n limit,\n offset,\n startAfter: startAfter as Record<string, unknown> | undefined,\n databaseId: collection?.databaseId,\n searchString,\n vectorSearch\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 return Promise.all(rows.map(async (row) => {\n let fetched = row;\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n fetched = await globalCallbacks.afterRead({\n collection: resolvedCollection as unknown as CollectionConfig,\n path,\n row: fetched,\n context: contextForCallback\n });\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n fetched = await callbacks.afterRead({\n collection: resolvedCollection as CollectionConfig<M>,\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: resolvedCollection as unknown as CollectionConfig,\n path,\n row: fetched,\n context: contextForCallback\n });\n }\n return fetched;\n }));\n }\n\n return rows;\n }\n\n listenCollection<M extends Record<string, unknown>>({\n path,\n collection,\n filter,\n limit,\n offset,\n startAfter,\n orderBy,\n searchString,\n order,\n onUpdate,\n onError\n }: ListenCollectionProps<M>): () => void {\n\n const subscriptionId = this.generateSubscriptionId();\n\n // Type-adapter wrapper: RealtimeService expects a union callback signature\n const callbackWrapper = (rows: Record<string, unknown>[]) => {\n onUpdate(rows);\n };\n\n // Store the subscription in RealtimeService properly using the new public method\n this.realtimeService.registerDataDriverSubscription(subscriptionId, {\n clientId: \"driver\",\n type: \"collection\" as const,\n path,\n collectionRequest: {\n filter,\n orderBy,\n order,\n limit,\n offset,\n startAfter: startAfter as Record<string, unknown> | undefined,\n databaseId: collection?.databaseId,\n searchString\n }\n });\n\n // Store the callback for this subscription\n this.realtimeService.addSubscriptionCallback(subscriptionId, callbackWrapper as (data: Record<string, unknown> | Record<string, unknown>[] | null) => void);\n\n // Send initial data immediately\n this.fetchCollection({\n path: path,\n collection,\n filter,\n limit,\n offset,\n startAfter,\n orderBy,\n searchString,\n order\n }).then(rows => {\n callbackWrapper(rows);\n }).catch(error => {\n if (onError) onError(error);\n });\n\n return () => {\n this.realtimeService.removeSubscriptionCallback(subscriptionId);\n this.realtimeService.subscriptions.delete(subscriptionId);\n };\n }\n\n async fetchOne<M extends Record<string, unknown>>({\n path,\n id,\n databaseId,\n collection\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 );\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 // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n row = await globalCallbacks.afterRead({\n collection: resolvedCollection as unknown as CollectionConfig,\n path,\n row,\n context: contextForCallback\n });\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n row = await callbacks.afterRead({\n collection: resolvedCollection as CollectionConfig<M>,\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: resolvedCollection as unknown as CollectionConfig,\n path,\n row,\n context: contextForCallback\n });\n }\n }\n\n return row;\n }\n\n listenOne<M extends Record<string, unknown>>({\n path,\n id,\n collection,\n onUpdate,\n onError\n }: ListenOneProps<M>): () => void {\n\n const subscriptionId = this.generateSubscriptionId();\n const callbackWrapper = (row: Record<string, unknown> | null) => {\n if (row)\n onUpdate(row);\n };\n\n // Register the subscription with the RealtimeService\n this.realtimeService.registerDataDriverSubscription(subscriptionId, {\n clientId: \"driver\",\n type: \"single\" as const,\n path,\n id\n });\n\n // Store the callback for this subscription\n this.realtimeService.addSubscriptionCallback(subscriptionId, callbackWrapper as (data: Record<string, unknown> | Record<string, unknown>[] | null) => void);\n\n // Fetch initial data\n this.fetchOne({\n path,\n id,\n collection\n })\n .then(row => {\n if (row) onUpdate(row);\n })\n .catch(error => {\n if (onError) onError(error as Error);\n });\n\n // Return the unsubscribe function\n return () => {\n this.realtimeService.removeSubscriptionCallback(subscriptionId);\n this.realtimeService.subscriptions.delete(subscriptionId);\n };\n }\n\n async save<M extends Record<string, unknown>>({\n path,\n id,\n values,\n collection,\n status,\n upsert\n }: SaveProps<M>): Promise<Record<string, unknown>> {\n\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, path);\n\n let updatedValues = values;\n const contextForCallback = this.buildCallContext();\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 try {\n const existing = await this.dataService.getFetchService()\n .fetchOneForRest(path, id, undefined, resolvedCollection?.databaseId);\n if (existing) {\n const { id: _existingId, ...existingValues } = existing;\n previousValuesForHistory = existingValues as Partial<M>;\n }\n } catch (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 if (globalCallbacks?.beforeSave || callbacks?.beforeSave || propertyCallbacks?.beforeSave) {\n // 1. Global callbacks first\n if (globalCallbacks?.beforeSave) {\n const result = await globalCallbacks.beforeSave({\n collection: resolvedCollection as unknown as CollectionConfig,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = mergeDeep(updatedValues, result);\n }\n\n // 2. Collection callbacks second\n if (callbacks?.beforeSave) {\n const result = await callbacks.beforeSave({\n collection: resolvedCollection as CollectionConfig<M>,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = mergeDeep(updatedValues, result);\n }\n\n // 3. Property callbacks third\n if (propertyCallbacks?.beforeSave) {\n const result = await propertyCallbacks.beforeSave({\n collection: resolvedCollection as unknown as CollectionConfig,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = mergeDeep(updatedValues, result);\n }\n\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 }\n\n try {\n let savedRow = await this.dataService.save<M>(\n path,\n updatedValues,\n id,\n resolvedCollection?.databaseId,\n { upsert }\n );\n\n if (savedRow && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n savedRow = await globalCallbacks.afterRead({\n collection: resolvedCollection as unknown as CollectionConfig,\n path,\n row: savedRow,\n context: contextForCallback\n });\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n savedRow = await callbacks.afterRead({\n collection: resolvedCollection as CollectionConfig<M>,\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: resolvedCollection as unknown as CollectionConfig,\n path,\n row: savedRow,\n context: contextForCallback\n });\n }\n }\n\n // The row is exactly its columns, so its address is derived, not read\n // off it: `savedRow.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.\n const savedId = deriveRowAddress(\n savedRow,\n (resolvedCollection ?? collection) as CollectionConfig,\n this.registry\n );\n // `values` are the row's columns — all of them. For an `id`-keyed table\n // that includes `id`, which used to be stripped here because it was the\n // synthesized address rather than the column it now is.\n const savedValues = savedRow;\n\n if (globalCallbacks?.afterSave || callbacks?.afterSave || propertyCallbacks?.afterSave) {\n // 1. Global callbacks first\n if (globalCallbacks?.afterSave) {\n await globalCallbacks.afterSave({\n collection: resolvedCollection as unknown as CollectionConfig,\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: resolvedCollection as CollectionConfig<M>,\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: resolvedCollection as unknown as CollectionConfig,\n path,\n id: savedId,\n values: savedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n }\n }\n\n // Record row history (fire-and-forget, never blocks the save)\n if (this.historyService && resolvedCollection?.history) {\n this.historyService.recordHistory({\n tableName: path,\n id: savedId,\n action: status === \"new\" ? \"create\" : \"update\",\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 if (this._deferNotifications) {\n this._pendingNotifications.push({\n path,\n id: savedId,\n row: savedRow,\n databaseId: resolvedCollection?.databaseId\n });\n } else {\n await this.realtimeService.notifyUpdate(\n path,\n savedId,\n savedRow,\n resolvedCollection?.databaseId\n );\n }\n\n return savedRow;\n } catch (error) {\n if (globalCallbacks?.afterSaveError || callbacks?.afterSaveError || propertyCallbacks?.afterSaveError) {\n // 1. Global callbacks first\n if (globalCallbacks?.afterSaveError) {\n await globalCallbacks.afterSaveError({\n collection: resolvedCollection as unknown as CollectionConfig,\n path,\n id: id || \"unknown\",\n values: updatedValues,\n previousValues: undefined,\n status,\n context: contextForCallback\n });\n }\n // 2. Collection callbacks second\n if (callbacks?.afterSaveError) {\n await callbacks.afterSaveError({\n collection: resolvedCollection as CollectionConfig<M>,\n path,\n id: id || \"unknown\",\n values: updatedValues,\n previousValues: undefined,\n status,\n context: contextForCallback\n });\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterSaveError) {\n await propertyCallbacks.afterSaveError({\n collection: resolvedCollection as unknown as CollectionConfig,\n path,\n id: id || \"unknown\",\n values: updatedValues,\n previousValues: undefined,\n status,\n context: contextForCallback\n });\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 async saveMany<M extends Record<string, unknown>>({\n path,\n rows,\n collection,\n upsert\n }: SaveManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.db.transaction(async (tx) => {\n // Bind the whole batch to the transaction handle. Without this the\n // rows 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 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\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 }));\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.db.transaction(async (tx) => {\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 txDriver.client = this.client;\n txDriver._deferNotifications = this._deferNotifications;\n txDriver._pendingNotifications = this._pendingNotifications;\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 // Read first so a missing row is a 404 rather than a silent\n // no-op. `save` with status \"existing\" would otherwise write\n // an UPDATE that matches nothing and report success.\n const existing = await txDriver.fetchOne({\n path,\n id: String(id),\n collection: collection as CollectionConfig\n });\n if (!existing) {\n throw Object.assign(new Error(`No row with id ${JSON.stringify(id)}`), {\n statusCode: 404,\n code: \"NOT_FOUND\"\n });\n }\n\n saved.push(await txDriver.save<M>({\n path,\n id: String(id),\n values,\n collection,\n status: \"existing\"\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 }: DeleteManyProps<M>): Promise<void> {\n await this.db.transaction(async (tx) => {\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 txDriver.client = this.client;\n txDriver._deferNotifications = this._deferNotifications;\n txDriver._pendingNotifications = this._pendingNotifications;\n\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n try {\n const existing = await txDriver.fetchOne({\n path,\n id: String(id),\n collection: collection as CollectionConfig\n });\n if (!existing) {\n throw Object.assign(new Error(`No row with id ${JSON.stringify(id)}`), {\n statusCode: 404,\n code: \"NOT_FOUND\"\n });\n }\n\n await txDriver.delete<M>({\n row: {\n // The address from the caller, not read back off the\n // row: a row is only its columns, so `existing.id` is\n // undefined for any table not keyed on `id`.\n id: String(id),\n path,\n values: existing as Partial<EntityValues<M>>\n },\n collection\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 async delete<M extends Record<string, unknown>>({\n row,\n collection\n }: DeleteProps<M>): Promise<void> {\n\n const targetPath = row.path;\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.\n const targetRow: Record<string, unknown> = { ...(row.values ?? {}) };\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\n const contextForCallback = this.buildCallContext();\n\n if (globalCallbacks?.beforeDelete || callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {\n let preventDefault = false;\n // 1. Global callbacks first\n if (globalCallbacks?.beforeDelete) {\n const result = await globalCallbacks.beforeDelete({\n collection: resolvedCollection as unknown as CollectionConfig,\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: resolvedCollection as CollectionConfig<M>,\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: resolvedCollection as unknown as CollectionConfig,\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 return;\n }\n }\n\n await this.dataService.delete(\n targetPath,\n row.id,\n resolvedCollection?.databaseId\n );\n\n if (globalCallbacks?.afterDelete || callbacks?.afterDelete || propertyCallbacks?.afterDelete) {\n // 1. Global callbacks first\n if (globalCallbacks?.afterDelete) {\n await globalCallbacks.afterDelete({\n collection: resolvedCollection as unknown as CollectionConfig,\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: resolvedCollection as CollectionConfig<M>,\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: resolvedCollection as unknown as CollectionConfig,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n }\n }\n\n // Record delete history (fire-and-forget)\n if (this.historyService && resolvedCollection?.history) {\n this.historyService.recordHistory({\n tableName: targetPath,\n id: row.id.toString(),\n action: \"delete\",\n values: row.values as Record<string, unknown> ?? {},\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 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 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 }: 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.\n logical,\n searchString,\n vectorSearch\n }\n );\n }\n\n private getTargetDb(databaseName?: string): DrizzleClient {\n if (!databaseName || databaseName === this.poolManager?.defaultDatabaseName) {\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 }): Promise<Record<string, unknown>[]> {\n if (!options?.database && !options?.role) {\n return this.dataService.executeSql(sqlText, options?.params);\n }\n\n const targetDb = this.getTargetDb(options?.database);\n\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 const result = await tx.execute(this.buildStatement(sqlText, options?.params));\n return result.rows as Record<string, unknown>[];\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 const result = await targetDb.execute(this.buildStatement(sqlText, options?.params));\n return result.rows as Record<string, unknown>[];\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 = this.poolManager?.defaultDatabaseName;\n if (currentDb && !databases.includes(currentDb)) {\n databases.unshift(currentDb);\n } else if (currentDb) {\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.poolManager?.defaultDatabaseName;\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 // Also fetch enum values for any USER-DEFINED columns\n const enumColumns = columns.filter((c) => c.data_type === \"USER-DEFINED\");\n if (enumColumns.length > 0) {\n for (const col of enumColumns) {\n try {\n const enumResult = await this.db.execute(drizzleSql`\n SELECT e.enumlabel\n FROM pg_type t\n JOIN pg_enum e ON t.oid = e.enumtypid\n WHERE t.typname = ${col.udt_name as string}\n ORDER BY e.enumsortorder\n `);\n col.enum_values = (enumResult.rows as Record<string, unknown>[]).map(e => e.enumlabel);\n } catch {\n col.enum_values = [];\n }\n }\n }\n // SAFETY: Raw SQL result rows are typed as QueryResultRow[]; the query shape matches TableColumnInfo\n const typedColumns = columns as unknown as TableColumnInfo[];\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 const policiesResult = await this.db.execute(drizzleSql`\n SELECT \n polname as policy_name, \n polcmd as cmd, \n polroles::regrole[]::text[] as roles, \n pg_get_expr(polqual, polrelid) as qual, \n pg_get_expr(polwithcheck, polrelid) as with_check\n FROM pg_policy\n WHERE polrelid = (SELECT oid FROM pg_class WHERE relname = ${safeName} AND relnamespace = 'public'::regnamespace);\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\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\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) => {\n return this.withTransaction(async (delegate) => {\n return delegate.restFetchService.fetchOneForRest(collectionPath, id, include, databaseId);\n }, { accessMode: \"read only\" });\n }\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 const result = await this.delegate.db.transaction(async (tx) => {\n let uid = this.user?.uid;\n if (!uid) {\n logger.warn(\"[DataDriver] User ID (uid) is missing for authenticated delegate. Using 'anonymous'. User object\", { detail: this.user });\n uid = \"anonymous\";\n }\n\n const userRoles = this.user?.roles ?? [];\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 await applyAuthContext(tx, { uid, roles: userRoles }, this.delegate.rlsUserRole);\n\n const txEntityService = new DataService(tx, this.delegate.registry);\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 return await operation(txDelegate);\n }, options);\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 * Injects the authenticated user's context into the most recently\n * registered realtime subscription so RLS-aware polling can apply.\n */\n private injectAuthContext(unsubscribe: () => void): () => void {\n const authContext = {\n uid: this.user?.uid || \"anonymous\",\n roles: this.user?.roles ?? []\n };\n const entries = Array.from(this.delegate.realtimeService.subscriptions.entries());\n const lastEntry = entries[entries.length - 1];\n const lastSub = lastEntry?.[1] as Record<string, unknown> | undefined;\n if (lastSub && lastSub.clientId === \"driver\") {\n lastSub.authContext = authContext;\n }\n return unsubscribe;\n }\n\n listenCollection<M extends Record<string, unknown>>(props: ListenCollectionProps<M>): () => void {\n return this.injectAuthContext(this.delegate.listenCollection(props));\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.injectAuthContext(this.delegate.listenOne(props));\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 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 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 { RealtimeService } from \"./services/realtimeService\";\nimport { PostgresBackendDriver, effectiveSqlRole } from \"./PostgresBackendDriver\";\nimport type { DataDriver, DeleteProps, FetchCollectionProps, FetchOneProps, SaveProps, TableMetadata, BranchInfo, AuthAdapter } from \"@rebasepro/types\";\nimport { ANONYMOUS_USER_ID, isSQLAdmin, isSchemaAdmin, resolveClientListLimit, ListLimitError } from \"@rebasepro/types\";\nimport type { User } from \"@rebasepro/types\";\n\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { Server } from \"http\";\nimport { inspect } from \"util\";\nimport { extractUserFromToken, AccessTokenPayload, safeCompare, resolveRequireAuth, assertWriteRequestValid, ApiError } from \"@rebasepro/server\";\nimport { logger } from \"@rebasepro/server\";\n\n/** Minimal subset of RebaseAuthConfig used by the WebSocket layer. */\ninterface WsAuthConfig {\n requireAuth?: boolean;\n jwtSecret?: string;\n /**\n * Same static server-to-server secret the HTTP middleware accepts. Without\n * it here, a service key authenticates over HTTP but not over the socket —\n * so any SDK client using one (scripts, cron, server-to-server) connects,\n * fails realtime auth with \"jwt malformed\", and silently gets no events.\n */\n serviceKey?: string;\n}\n\n/**\n * Normalized user identity for WebSocket sessions.\n */\ninterface WsUserIdentity {\n uid: string;\n roles: string[];\n isAdmin: boolean;\n}\n\ninterface ClientSession {\n ws: WebSocket;\n user?: WsUserIdentity;\n authenticated: boolean;\n /** Sliding window message counter for rate limiting */\n messageCount: number;\n messageWindowStart: number;\n /** The same window, counted separately for channel frames. */\n channelMessageCount: number;\n channelWindowStart: number;\n}\n\n\n/** Maximum messages per client per window */\nconst WS_RATE_LIMIT = 2000;\n/** Rate limit window in milliseconds (60 seconds) */\nconst WS_RATE_WINDOW_MS = 60_000;\n\n/**\n * Channel frames get their own budget, because they are a different workload.\n *\n * 2000/minute is 33/second, which is generous for queries and subscriptions and\n * an order of magnitude below what the documented channel idiom asks for: the\n * capacity note in `docs/backend/realtime.md` uses 60 fps cursor movement as\n * its worked example, and the presence idiom re-`track()`s on every move, so\n * one client sustaining that sends ~120 frames/second — 7200 a minute. Sharing\n * one counter meant the cursor stream ate the query budget and then froze for\n * the rest of the window.\n *\n * The number is sized to that documented workload and nothing more; it is not\n * a considered product limit (see `docs/channel-authorization.md`).\n */\nconst WS_CHANNEL_RATE_LIMIT = 7200;\n\n/** Frames counted against the channel budget rather than the general one. */\nconst CHANNEL_MESSAGE_TYPES = new Set([\n \"join_channel\",\n \"leave_channel\",\n \"broadcast\",\n \"presence_track\",\n \"presence_untrack\",\n \"presence_state\",\n \"channel_history\"\n]);\n\n/**\n * WebSocket message types that require an admin session.\n *\n * Exported so the test can READ it. It used to be private, and the test that\n * exists to make \"a privileged verb added without a role check\" impossible held\n * a hand-typed copy of the same nine strings — so it agreed with itself, and\n * `FETCH_APPLICATION_ROLES` was added to neither. That verb runs\n * `SELECT DISTINCT unnest(roles)` over the users table through `executeSql`,\n * which is the owner connection and not subject to RLS, so any authenticated\n * non-admin could enumerate every role in the project — and any anonymous\n * socket could, on a deployment with `requireAuth: false`.\n *\n * The list is no longer the whole guarantee. `PUBLIC_TYPES` below is its\n * counterpart, and a test asserts that every `case` this file handles appears\n * in exactly one of the two — so a verb added to neither fails rather than\n * defaulting to reachable.\n */\nexport const ADMIN_ONLY_TYPES = new Set([\n \"EXECUTE_SQL\",\n \"FETCH_DATABASES\",\n \"FETCH_ROLES\",\n \"FETCH_APPLICATION_ROLES\",\n \"FETCH_UNMAPPED_TABLES\",\n \"FETCH_TABLE_METADATA\",\n \"FETCH_CURRENT_DATABASE\",\n \"CREATE_BRANCH\",\n \"DELETE_BRANCH\",\n \"LIST_BRANCHES\"\n]);\n\n/**\n * Message types deliberately reachable by a non-admin session.\n *\n * Data operations, gated per row by RLS and per request by the same write\n * checks the REST layer applies — not by this list. It exists so that \"which\n * bucket is this verb in\" is a question with an answer for every verb, and\n * adding one without answering it is a test failure.\n */\nexport const PUBLIC_TYPES = new Set([\n \"FETCH_COLLECTION\",\n \"FETCH_ONE\",\n \"COUNT\",\n \"SAVE\",\n \"DELETE\",\n \"CHECK_UNIQUE_FIELD\"\n]);\n\n/**\n * Recursively extract the deepest error message from an error's cause chain (e.g., Drizzle wrapping a PG error).\n */\nfunction extractErrorMessage(error: unknown): string {\n if (!error) return \"Unknown error\";\n if (error instanceof Error) {\n if (\"cause\" in error && error.cause) {\n return extractErrorMessage(error.cause);\n }\n return error.message;\n }\n if (typeof error === \"object\" && \"message\" in error && typeof (error as { message: unknown }).message === \"string\") {\n return (error as { message: string }).message;\n }\n return String(error);\n}\n\n/**\n * Check if the current session belongs to an admin user.\n */\nfunction isAdminSession(session: ClientSession | undefined): boolean {\n if (!session?.user) return false;\n // Fast path: new adapter-aware sessions set isAdmin directly\n if (session.user.isAdmin) return true;\n if (!session.user.roles) return false;\n return session.user.roles.some((r) => r === \"admin\");\n}\n\nexport function createPostgresWebSocket(\n server: Server,\n realtimeService: RealtimeService,\n driver: PostgresBackendDriver,\n authConfig?: WsAuthConfig,\n authAdapter?: AuthAdapter\n) {\n // Session map scoped to this factory invocation — prevents stale sessions\n // leaking across hot reloads or multiple factory calls.\n const clientSessions = new Map<string, ClientSession>();\n\n const isProduction = process.env.NODE_ENV === \"production\";\n /** Debug logger that is suppressed in production to prevent PII/data leaks */\n const wsDebug = (...args: unknown[]) => { if (!isProduction) console.debug(...args); };\n const wss = new WebSocketServer({ server });\n\n // Handle errors on the WSS so that EADDRINUSE from the underlying HTTP\n // server doesn't surface as an unhandled 'error' event and crash the\n // process. The dev-mode `listenWithPortRetry` utility handles retry\n // logic on the HTTP server side — we just need the WSS not to throw.\n wss.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n // Silently absorbed — listenWithPortRetry will retry the next port\n return;\n }\n logger.error(\"❌ [WebSocket Server] Error\", { error: err });\n });\n\n // The same predicate the HTTP data routes use, from the same function —\n // this socket is the other enforcement point for one product decision, and\n // while it computed the answer itself it computed a different one. See\n // `resolveRequireAuth` for what its local copy got wrong and why a `false`\n // here grants access rather than skipping a check.\n const requireAuth = !!authAdapter || resolveRequireAuth(authConfig as never);\n\n if (requireAuth && !authAdapter && !authConfig?.jwtSecret && !authConfig?.serviceKey) {\n logger.warn(\n \"🔐 [WebSocket Server] Authentication is required but no adapter, jwtSecret or \" +\n \"serviceKey is configured — no client can complete AUTH, so every realtime \" +\n \"message will be refused with UNAUTHORIZED.\"\n );\n }\n\n wss.on(\"connection\", (ws) => {\n const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n wsDebug(`WebSocket client connected: ${clientId}`);\n\n // Initialize client session\n clientSessions.set(clientId, { ws,\nauthenticated: !requireAuth,\nmessageCount: 0,\nmessageWindowStart: Date.now(),\nchannelMessageCount: 0,\nchannelWindowStart: Date.now() });\n realtimeService.addClient(clientId, ws);\n\n ws.on(\"close\", () => {\n wsDebug(`WebSocket client disconnected: ${clientId}`);\n clientSessions.delete(clientId);\n });\n\n // Route all messages through RealtimeService for unified handling\n ws.on(\"message\", async (message) => {\n let requestId: string | undefined;\n try {\n const {\n type,\n payload,\n requestId: reqId\n } = JSON.parse(message.toString());\n requestId = reqId; // Capture requestId for use in catch block\n\n wsDebug(`[WS] ${clientId} → ${type}`, requestId ? `(${requestId})` : \"\");\n\n // Handle authentication first\n // Helper: send a canonical error frame\n const sendError = (errType: \"ERROR\" | \"AUTH_ERROR\", code: string, msg: string) => {\n ws.send(JSON.stringify({\n type: errType,\n requestId,\n payload: { error: { message: msg,\ncode } }\n }));\n };\n\n if (type === \"AUTHENTICATE\") {\n const { token } = payload || {};\n if (!token) {\n sendError(\"AUTH_ERROR\", \"INVALID_INPUT\", \"Token is required\");\n return;\n }\n\n // Use the auth adapter when available (custom auth, Clerk, etc.)\n // Fall back to JWT extraction otherwise.\n let verifiedUser: WsUserIdentity | null = null;\n\n if (authAdapter) {\n try {\n const adapterUser = authAdapter.verifyToken\n ? await authAdapter.verifyToken(token)\n : await authAdapter.verifyRequest(new Request(\"http://localhost/_ws_auth\", {\n headers: { Authorization: `Bearer ${token}` }\n }));\n\n if (adapterUser) {\n verifiedUser = {\n uid: adapterUser.uid,\n roles: adapterUser.roles,\n isAdmin: adapterUser.isAdmin\n };\n }\n } catch {\n // Adapter threw — treat as invalid token\n }\n } else if (authConfig?.serviceKey && safeCompare(token, authConfig.serviceKey)) {\n // Service key: a static secret, not a JWT. Checked\n // before verification, mirroring the HTTP middleware —\n // verifying it as a JWT can only ever fail.\n verifiedUser = { uid: \"service\", roles: [\"admin\"], isAdmin: true };\n } else {\n // Standard JWT path\n const jwtPayload = extractUserFromToken(token);\n if (jwtPayload) {\n verifiedUser = {\n uid: jwtPayload.uid,\n roles: jwtPayload.roles ?? [],\n isAdmin: (jwtPayload.roles ?? []).some((r: string) => r === \"admin\")\n };\n }\n }\n\n if (verifiedUser) {\n const session = clientSessions.get(clientId);\n if (session) {\n session.user = verifiedUser;\n session.authenticated = true;\n }\n wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);\n ws.send(JSON.stringify({\n type: \"AUTH_SUCCESS\",\n requestId,\n payload: { uid: verifiedUser.uid,\nroles: verifiedUser.roles }\n }));\n wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.uid}`);\n } else {\n wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);\n sendError(\"AUTH_ERROR\", \"INVALID_TOKEN\", \"Invalid or expired token\");\n }\n return;\n }\n\n // Check authentication for protected operations\n if (requireAuth) {\n const session = clientSessions.get(clientId);\n if (!session?.authenticated) {\n sendError(\"ERROR\", \"UNAUTHORIZED\", \"Authentication required\");\n return;\n }\n }\n\n // Rate limiting: reject if client exceeds message limit.\n // Channel frames are counted against their own budget — see\n // WS_CHANNEL_RATE_LIMIT for why one shared counter starved them.\n {\n const session = clientSessions.get(clientId);\n if (session) {\n const now = Date.now();\n const isChannelFrame = CHANNEL_MESSAGE_TYPES.has(type);\n if (isChannelFrame) {\n if (now - session.channelWindowStart > WS_RATE_WINDOW_MS) {\n session.channelMessageCount = 0;\n session.channelWindowStart = now;\n }\n session.channelMessageCount++;\n if (session.channelMessageCount > WS_CHANNEL_RATE_LIMIT) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many channel messages. Please slow down.\");\n return;\n }\n } else {\n if (now - session.messageWindowStart > WS_RATE_WINDOW_MS) {\n session.messageCount = 0;\n session.messageWindowStart = now;\n }\n session.messageCount++;\n if (session.messageCount > WS_RATE_LIMIT) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many requests. Please slow down.\");\n return;\n }\n }\n }\n }\n\n // Admin-only operations require admin role\n if (ADMIN_ONLY_TYPES.has(type)) {\n const session = clientSessions.get(clientId);\n if (!isAdminSession(session)) {\n sendError(\"ERROR\", \"FORBIDDEN\", \"Admin access required for this operation\");\n return;\n }\n }\n\n /**\n * Apply the REST layer's write checks to a socket payload.\n *\n * Silent when the path names no registered collection: the\n * driver decides what a path means, and refusing here would\n * turn \"unknown collection\" into a validation error.\n */\n const assertWriteRequest = (path: string | undefined, values: unknown): void => {\n if (!path || !values || typeof values !== \"object\") return;\n const collection = driver.registry?.getCollectionByPath(path);\n if (!collection) return;\n assertWriteRequestValid(values as Record<string, unknown>, collection);\n };\n\n // Helper to get correctly scoped delegate for the current request\n const getScopedDelegate = async (): Promise<DataDriver> => {\n const session = clientSessions.get(clientId);\n // Check if the driver supports RLS-scoped delegates\n if (typeof driver.withAuth === \"function\") {\n try {\n const userForAuth: User = session?.user\n ? {\n uid: session.user.uid,\n displayName: null,\n email: null,\n photoURL: null,\n providerId: \"websocket\",\n isAnonymous: false,\n roles: session.user.roles ?? []\n }\n : {\n uid: ANONYMOUS_USER_ID,\n displayName: null,\n email: null,\n photoURL: null,\n providerId: \"websocket\",\n isAnonymous: true,\n roles: [\"anon\"]\n };\n return await driver.withAuth(userForAuth);\n } catch (e) {\n logger.error(\"Failed to create RLS scoped delegate for WS request\", { error: e });\n throw new Error(\"Internal authentication error\");\n }\n }\n return driver;\n };\n\n switch (type) {\n case \"FETCH_COLLECTION\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_COLLECTION request\");\n const request: FetchCollectionProps = payload;\n const delegate = await getScopedDelegate();\n // Bound the client-supplied limit with the SAME guarantee\n // the REST ingress and `subscribe_collection` apply\n // (`resolveClientListLimit`). Without it an absent limit\n // reached the driver as `undefined`, which emits no LIMIT\n // clause — one socket frame streamed the whole table, on\n // the one transport that skipped the ceiling every other\n // read path enforces.\n const rows = await delegate.fetchCollection({\n ...request,\n limit: resolveClientListLimit(request.limit, {\n vectorSearch: !!request.vectorSearch\n })\n });\n wsDebug(\"📋 [WebSocket Server] FETCH_COLLECTION result - rows count:\", rows.length);\n const response = {\n type: \"FETCH_COLLECTION_SUCCESS\",\n payload: { rows },\n requestId\n };\n wsDebug(\"📋 [WebSocket Server] Sending FETCH_COLLECTION_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_ONE\": {\n wsDebug(\"📄 [WebSocket Server] Processing FETCH_ENTITY request\");\n const request: FetchOneProps = payload;\n const delegate = await getScopedDelegate();\n const row = await delegate.fetchOne(request);\n wsDebug(\"📄 [WebSocket Server] FETCH_ENTITY result:\", row);\n const response = {\n type: \"FETCH_ONE_SUCCESS\",\n payload: { row: row ?? null },\n requestId\n };\n wsDebug(\"📄 [WebSocket Server] Sending FETCH_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"SAVE\": {\n wsDebug(\"💾 [WebSocket Server] Processing SAVE_ENTITY request\");\n const request: SaveProps = payload;\n wsDebug(\"💾 [WebSocket Server] Saving row with request:\", inspect(request, { depth: null,\ncolors: true }));\n // The same two checks the REST write routes run, on the\n // same input, at the same point. This socket is the\n // other request boundary — the comment on `requireAuth`\n // above says so — and it used to hand the client's\n // payload straight to the driver, so a value the HTTP\n // API answers 400 for was written when it arrived here.\n //\n // The collection comes from the registry by path, never\n // from `request.collection`: that field is client-\n // supplied, and reading the rules out of it would let\n // the caller choose which rules to be checked against.\n assertWriteRequest(request.path, request.values as Record<string, unknown>);\n const delegate = await getScopedDelegate();\n const row = await delegate.save(request);\n wsDebug(\"💾 [WebSocket Server] SAVE_ENTITY result:\", inspect(row, { depth: null,\ncolors: true }));\n const response = {\n type: \"SAVE_SUCCESS\",\n payload: { row },\n requestId\n };\n wsDebug(\"💾 [WebSocket Server] Sending SAVE_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"DELETE\": {\n wsDebug(\"🗑️ [WebSocket Server] Processing DELETE_ENTITY request\");\n const request: DeleteProps = payload;\n wsDebug(\"🗑️ [WebSocket Server] Deleting row:\", request.row);\n const delegate = await getScopedDelegate();\n await delegate.delete(request);\n wsDebug(\"🗑️ [WebSocket Server] DELETE_ENTITY completed successfully\");\n const response = {\n type: \"DELETE_SUCCESS\",\n payload: { success: true },\n requestId\n };\n wsDebug(\"🗑️ [WebSocket Server] Sending DELETE_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"CHECK_UNIQUE_FIELD\": {\n wsDebug(\"🔍 [WebSocket Server] Processing CHECK_UNIQUE_FIELD request\");\n const {\n path,\n name,\n value,\n id,\n collection\n } = payload;\n const delegate = await getScopedDelegate();\n const isUnique = await delegate.checkUniqueField(path, name, value, id, collection);\n wsDebug(\"🔍 [WebSocket Server] CHECK_UNIQUE_FIELD result:\", isUnique);\n const response = {\n type: \"CHECK_UNIQUE_FIELD_SUCCESS\",\n payload: { isUnique },\n requestId\n };\n wsDebug(\"🔍 [WebSocket Server] Sending CHECK_UNIQUE_FIELD_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n\n case \"COUNT\": {\n // Deliberately NOT routed through `resolveClientListLimit`:\n // this answers with a scalar, and the driver drops `limit`\n // on the way to `SELECT count(*)`. Clamping here could only\n // ever make `total` describe fewer rows than the collection\n // holds — the page size is the caller's business, the total\n // is not.\n const request: FetchCollectionProps = payload;\n const delegate = await getScopedDelegate();\n const count = await delegate.count!(request);\n const response = {\n type: \"COUNT_SUCCESS\",\n payload: { count },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"EXECUTE_SQL\": {\n const { sql, options } = payload;\n try {\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n if (!isSQLAdmin(admin)) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"SQL execution is not available for this driver.\");\n break;\n }\n const result = await admin.executeSql(sql, options);\n if (process.env.NODE_ENV !== \"production\") {\n wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : \"non-array\"} rows.`);\n }\n const auditSession = clientSessions.get(clientId);\n // Through `logger`, not `console.log`: this line is\n // emitted in production, and a bare console call\n // has no severity, no timestamp, no JSON envelope\n // and no LOG_LEVEL gate, so it lands in Cloud\n // Logging as unstructured text the queries written\n // for every other line cannot match.\n //\n // The bound values are counted, never written — the\n // statement is the audit signal, the parameters are\n // whatever row the operator was touching. (stdout is\n // not an audit sink either; a real trail belongs in\n // a table with an actor and a retention policy.)\n logger.info(\"[SQL Audit] WebSocket SQL execution\", {\n sql: typeof sql === \"string\" ? sql.substring(0, 500) : String(sql),\n database: options?.database,\n role: options?.role,\n // The role the statement asked for is above;\n // this is the one it ran as. They used to be\n // assumed identical, so an execution that fell\n // back to the owner was audited under the role\n // it had failed to assume. That fallback is now\n // an error everywhere except the documented\n // `DISABLE_DB_ROLE_SWITCHING` opt-out — which is\n // precisely the case this line still has to\n // report honestly.\n effectiveRole: effectiveSqlRole(options?.role),\n paramCount: Array.isArray(options?.params) ? options.params.length : 0,\n resultRows: Array.isArray(result) ? result.length : \"unknown\",\n uid: auditSession?.user?.uid ?? \"unknown\",\n roles: auditSession?.user?.roles ?? [],\n isAdmin: auditSession?.user?.isAdmin ?? false,\n requestId\n });\n const response = {\n type: \"EXECUTE_SQL_SUCCESS\",\n payload: { result },\n requestId\n };\n ws.send(JSON.stringify(response));\n } catch (sqlError: unknown) {\n // This is a query execution error (e.g., syntax error, permission denied).\n // We return it cleanly to the client without logging a server stack trace.\n const errMsg = extractErrorMessage(sqlError);\n sendError(\"ERROR\", \"SQL_ERROR\", errMsg);\n }\n }\n break;\n\n case \"FETCH_DATABASES\": {\n wsDebug(\"📚 [WebSocket Server] Processing FETCH_DATABASES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let databases: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchAvailableDatabases) {\n databases = await admin.fetchAvailableDatabases();\n }\n wsDebug(`📚 [WebSocket Server] Fetched ${databases.length} databases.`);\n const response = {\n type: \"FETCH_DATABASES_SUCCESS\",\n payload: { databases },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_ROLES\": {\n wsDebug(\"👤 [WebSocket Server] Processing FETCH_ROLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let roles: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchAvailableRoles) {\n roles = await admin.fetchAvailableRoles();\n }\n wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} roles.`);\n const response = {\n type: \"FETCH_ROLES_SUCCESS\",\n payload: { roles },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_APPLICATION_ROLES\": {\n wsDebug(\"👤 [WebSocket Server] Processing FETCH_APPLICATION_ROLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let roles: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchApplicationRoles) {\n roles = await admin.fetchApplicationRoles();\n }\n wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} application roles.`);\n const response = {\n type: \"FETCH_APPLICATION_ROLES_SUCCESS\",\n payload: { roles },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_CURRENT_DATABASE\": {\n wsDebug(\"📚 [WebSocket Server] Processing FETCH_CURRENT_DATABASE request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let database: string | undefined = undefined;\n if (isSQLAdmin(admin) && admin.fetchCurrentDatabase) {\n database = await admin.fetchCurrentDatabase();\n }\n const response = {\n type: \"FETCH_CURRENT_DATABASE_SUCCESS\",\n payload: { database },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_UNMAPPED_TABLES\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_UNMAPPED_TABLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let tables: string[] = [];\n if (isSchemaAdmin(admin) && admin.fetchUnmappedTables) {\n tables = await admin.fetchUnmappedTables(payload?.mappedPaths);\n }\n wsDebug(`📋 [WebSocket Server] Fetched ${tables.length} unmapped tables.`);\n const response = {\n type: \"FETCH_UNMAPPED_TABLES_SUCCESS\",\n payload: { tables },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_TABLE_METADATA\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_TABLE_METADATA request\");\n const { tableName } = payload;\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let metadata: TableMetadata | undefined;\n if (isSchemaAdmin(admin) && admin.fetchTableMetadata) {\n metadata = await admin.fetchTableMetadata(tableName) as TableMetadata;\n }\n wsDebug(`📋 [WebSocket Server] Fetched metadata for table '${tableName}'. (${metadata?.columns?.length ?? 0} columns)`);\n const response = {\n type: \"FETCH_TABLE_METADATA_SUCCESS\",\n payload: { metadata },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"CREATE_BRANCH\": {\n wsDebug(\"🌿 [WebSocket Server] Processing CREATE_BRANCH request\");\n const { name, options } = payload;\n const delegate = await getScopedDelegate();\n if (!delegate.admin?.createBranch) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"Database branching is not available. Configure adminConnectionString.\");\n break;\n }\n const branch: BranchInfo = await delegate.admin.createBranch(name, options);\n wsDebug(`🌿 [WebSocket Server] Branch created: ${branch.name}`);\n const response = {\n type: \"CREATE_BRANCH_SUCCESS\",\n payload: { branch },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"DELETE_BRANCH\": {\n wsDebug(\"🗑️ [WebSocket Server] Processing DELETE_BRANCH request\");\n const { name: branchName } = payload;\n const delegate = await getScopedDelegate();\n if (!delegate.admin?.deleteBranch) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"Database branching is not available.\");\n break;\n }\n await delegate.admin.deleteBranch(branchName);\n wsDebug(`🗑️ [WebSocket Server] Branch deleted: ${branchName}`);\n const response = {\n type: \"DELETE_BRANCH_SUCCESS\",\n payload: { success: true },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"LIST_BRANCHES\": {\n wsDebug(\"🌿 [WebSocket Server] Processing LIST_BRANCHES request\");\n const delegate = await getScopedDelegate();\n let branches: BranchInfo[] = [];\n if (delegate.admin?.listBranches) {\n branches = await delegate.admin.listBranches();\n }\n wsDebug(`🌿 [WebSocket Server] Listed ${branches.length} branches.`);\n const response = {\n type: \"LIST_BRANCHES_SUCCESS\",\n payload: { branches },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n // Route subscription messages, broadcast channels, and presence to RealtimeService\n case \"subscribe_collection\":\n case \"subscribe_one\":\n case \"unsubscribe\":\n case \"join_channel\":\n case \"leave_channel\":\n case \"broadcast\":\n case \"presence_track\":\n case \"presence_untrack\":\n case \"presence_state\":\n case \"channel_history\": {\n wsDebug(\"🔄 [WebSocket Server] Routing realtime message to RealtimeService:\", type);\n // Attach auth context from the WS session so RLS-aware refetches work\n const session = clientSessions.get(clientId);\n const authContext = session?.user\n ? { uid: session.user.uid,\nroles: session.user.roles ?? [] }\n : { uid: ANONYMOUS_USER_ID,\nroles: [\"anon\"] };\n // Let RealtimeService handle these messages\n await realtimeService.handleClientMessage(clientId, {\n type,\n payload,\n subscriptionId: payload?.subscriptionId\n }, authContext);\n break;\n }\n\n default:\n logger.error(\"❌ [WebSocket Server] Unknown message type\", { detail: type });\n }\n } catch (error: unknown) {\n // A refused `limit` is the caller's mistake, not a server fault.\n // Left to the generic branch below it answers INTERNAL_ERROR\n // with the message suppressed in production — so the one thing\n // that would tell the caller what to send instead is exactly\n // what gets dropped. Answered here the way\n // `subscribe_collection` already answers it: INVALID_LIMIT,\n // message intact. The text names the ceiling and nothing else.\n if (error instanceof ListLimitError) {\n logger.warn(`[WebSocket Server] Refused a list read: ${error.message}`);\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: { message: error.message,\ncode: \"INVALID_LIMIT\" } }\n }));\n return;\n }\n // A refused write is the caller's mistake, and its message is\n // the only thing that says what to send instead — the same\n // reasoning as `ListLimitError` above. Left to the generic\n // branch it becomes INTERNAL_ERROR with the text dropped in\n // production, so the socket would refuse the write and decline\n // to say why.\n if (error instanceof ApiError || (error as Error)?.name === \"ApiError\") {\n const apiError = error as ApiError;\n logger.warn(`[WebSocket Server] Refused a write: ${apiError.message}`);\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: { message: apiError.message,\ncode: apiError.code } }\n }));\n return;\n }\n logger.error(\"💥 [WebSocket Server] Error handling message\", { error: error });\n if (error instanceof Error) {\n logger.error(\"Stack trace\", { detail: error.stack });\n }\n // Unwrap the cause chain: a Drizzle failure reports itself as\n // \"Failed query: <sql> params:\", which tells the user nothing and\n // echoes the statement back at them. The reason is in the cause.\n const errorMessage = process.env.NODE_ENV === \"production\"\n ? \"An unexpected error occurred\"\n : extractErrorMessage(error);\n const errorResponse = {\n type: \"ERROR\",\n requestId,\n payload: {\n error: {\n message: errorMessage,\n code: \"INTERNAL_ERROR\"\n }\n }\n };\n ws.send(JSON.stringify(errorResponse));\n }\n });\n });\n}\n"],"mappings":"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"}