@rebasepro/server-postgresql 0.0.1-canary.4d4fb3e

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 (147) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +106 -0
  3. package/build-errors.txt +37 -0
  4. package/dist/common/src/collections/CollectionRegistry.d.ts +48 -0
  5. package/dist/common/src/collections/index.d.ts +1 -0
  6. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  7. package/dist/common/src/index.d.ts +3 -0
  8. package/dist/common/src/util/builders.d.ts +57 -0
  9. package/dist/common/src/util/callbacks.d.ts +6 -0
  10. package/dist/common/src/util/collections.d.ts +11 -0
  11. package/dist/common/src/util/common.d.ts +2 -0
  12. package/dist/common/src/util/conditions.d.ts +26 -0
  13. package/dist/common/src/util/entities.d.ts +36 -0
  14. package/dist/common/src/util/enums.d.ts +3 -0
  15. package/dist/common/src/util/index.d.ts +16 -0
  16. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  17. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  18. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  19. package/dist/common/src/util/paths.d.ts +14 -0
  20. package/dist/common/src/util/permissions.d.ts +5 -0
  21. package/dist/common/src/util/references.d.ts +2 -0
  22. package/dist/common/src/util/relations.d.ts +12 -0
  23. package/dist/common/src/util/resolutions.d.ts +72 -0
  24. package/dist/common/src/util/storage.d.ts +24 -0
  25. package/dist/index.es.js +10635 -0
  26. package/dist/index.es.js.map +1 -0
  27. package/dist/index.umd.js +10643 -0
  28. package/dist/index.umd.js.map +1 -0
  29. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +112 -0
  30. package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +40 -0
  31. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +6 -0
  32. package/dist/server-postgresql/src/auth/services.d.ts +188 -0
  33. package/dist/server-postgresql/src/cli.d.ts +1 -0
  34. package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +43 -0
  35. package/dist/server-postgresql/src/connection.d.ts +7 -0
  36. package/dist/server-postgresql/src/data-transformer.d.ts +36 -0
  37. package/dist/server-postgresql/src/databasePoolManager.d.ts +20 -0
  38. package/dist/server-postgresql/src/history/HistoryService.d.ts +71 -0
  39. package/dist/server-postgresql/src/history/ensure-history-table.d.ts +7 -0
  40. package/dist/server-postgresql/src/index.d.ts +13 -0
  41. package/dist/server-postgresql/src/interfaces.d.ts +18 -0
  42. package/dist/server-postgresql/src/schema/auth-schema.d.ts +767 -0
  43. package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +2 -0
  44. package/dist/server-postgresql/src/schema/generate-drizzle-schema.d.ts +1 -0
  45. package/dist/server-postgresql/src/services/BranchService.d.ts +47 -0
  46. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +195 -0
  47. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +41 -0
  48. package/dist/server-postgresql/src/services/RelationService.d.ts +92 -0
  49. package/dist/server-postgresql/src/services/entity-helpers.d.ts +24 -0
  50. package/dist/server-postgresql/src/services/entityService.d.ts +102 -0
  51. package/dist/server-postgresql/src/services/index.d.ts +4 -0
  52. package/dist/server-postgresql/src/services/realtimeService.d.ts +186 -0
  53. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +116 -0
  54. package/dist/server-postgresql/src/websocket.d.ts +5 -0
  55. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  56. package/dist/types/src/controllers/auth.d.ts +117 -0
  57. package/dist/types/src/controllers/client.d.ts +58 -0
  58. package/dist/types/src/controllers/collection_registry.d.ts +44 -0
  59. package/dist/types/src/controllers/customization_controller.d.ts +54 -0
  60. package/dist/types/src/controllers/data.d.ts +141 -0
  61. package/dist/types/src/controllers/data_driver.d.ts +168 -0
  62. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  63. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  64. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  65. package/dist/types/src/controllers/index.d.ts +17 -0
  66. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  67. package/dist/types/src/controllers/navigation.d.ts +213 -0
  68. package/dist/types/src/controllers/registry.d.ts +51 -0
  69. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  70. package/dist/types/src/controllers/side_entity_controller.d.ts +89 -0
  71. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  72. package/dist/types/src/controllers/storage.d.ts +173 -0
  73. package/dist/types/src/index.d.ts +4 -0
  74. package/dist/types/src/rebase_context.d.ts +101 -0
  75. package/dist/types/src/types/backend.d.ts +533 -0
  76. package/dist/types/src/types/builders.d.ts +14 -0
  77. package/dist/types/src/types/chips.d.ts +5 -0
  78. package/dist/types/src/types/collections.d.ts +812 -0
  79. package/dist/types/src/types/data_source.d.ts +64 -0
  80. package/dist/types/src/types/entities.d.ts +145 -0
  81. package/dist/types/src/types/entity_actions.d.ts +98 -0
  82. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  83. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  84. package/dist/types/src/types/entity_overrides.d.ts +9 -0
  85. package/dist/types/src/types/entity_views.d.ts +61 -0
  86. package/dist/types/src/types/export_import.d.ts +21 -0
  87. package/dist/types/src/types/index.d.ts +22 -0
  88. package/dist/types/src/types/locales.d.ts +4 -0
  89. package/dist/types/src/types/modify_collections.d.ts +5 -0
  90. package/dist/types/src/types/plugins.d.ts +225 -0
  91. package/dist/types/src/types/properties.d.ts +1091 -0
  92. package/dist/types/src/types/property_config.d.ts +70 -0
  93. package/dist/types/src/types/relations.d.ts +336 -0
  94. package/dist/types/src/types/slots.d.ts +228 -0
  95. package/dist/types/src/types/translations.d.ts +826 -0
  96. package/dist/types/src/types/user_management_delegate.d.ts +120 -0
  97. package/dist/types/src/types/websockets.d.ts +78 -0
  98. package/dist/types/src/users/index.d.ts +2 -0
  99. package/dist/types/src/users/roles.d.ts +22 -0
  100. package/dist/types/src/users/user.d.ts +46 -0
  101. package/jest-all.log +3128 -0
  102. package/jest.log +49 -0
  103. package/package.json +93 -0
  104. package/src/PostgresBackendDriver.ts +1024 -0
  105. package/src/PostgresBootstrapper.ts +232 -0
  106. package/src/auth/ensure-tables.ts +309 -0
  107. package/src/auth/services.ts +740 -0
  108. package/src/cli.ts +347 -0
  109. package/src/collections/PostgresCollectionRegistry.ts +96 -0
  110. package/src/connection.ts +62 -0
  111. package/src/data-transformer.ts +569 -0
  112. package/src/databasePoolManager.ts +84 -0
  113. package/src/history/HistoryService.ts +257 -0
  114. package/src/history/ensure-history-table.ts +45 -0
  115. package/src/index.ts +13 -0
  116. package/src/interfaces.ts +60 -0
  117. package/src/schema/auth-schema.ts +146 -0
  118. package/src/schema/generate-drizzle-schema-logic.ts +618 -0
  119. package/src/schema/generate-drizzle-schema.ts +151 -0
  120. package/src/services/BranchService.ts +237 -0
  121. package/src/services/EntityFetchService.ts +1447 -0
  122. package/src/services/EntityPersistService.ts +351 -0
  123. package/src/services/RelationService.ts +1012 -0
  124. package/src/services/entity-helpers.ts +121 -0
  125. package/src/services/entityService.ts +209 -0
  126. package/src/services/index.ts +13 -0
  127. package/src/services/realtimeService.ts +1005 -0
  128. package/src/utils/drizzle-conditions.ts +999 -0
  129. package/src/websocket.ts +487 -0
  130. package/test/auth-services.test.ts +569 -0
  131. package/test/branchService.test.ts +357 -0
  132. package/test/drizzle-conditions.test.ts +895 -0
  133. package/test/entityService.errors.test.ts +352 -0
  134. package/test/entityService.relations.test.ts +912 -0
  135. package/test/entityService.subcollection-search.test.ts +516 -0
  136. package/test/entityService.test.ts +977 -0
  137. package/test/generate-drizzle-schema.test.ts +795 -0
  138. package/test/historyService.test.ts +126 -0
  139. package/test/postgresDataDriver.test.ts +556 -0
  140. package/test/realtimeService.test.ts +276 -0
  141. package/test/relations.test.ts +662 -0
  142. package/test_drizzle_mock.js +3 -0
  143. package/test_find_changed.mjs +30 -0
  144. package/test_output.txt +3145 -0
  145. package/tsconfig.json +49 -0
  146. package/tsconfig.prod.json +20 -0
  147. package/vite.config.ts +82 -0
@@ -0,0 +1,999 @@
1
+ import { and, eq, or, sql, SQL, ilike, inArray } from "drizzle-orm";
2
+ import { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
3
+ import { FilterValues, WhereFilterOp, Relation, JoinStep } from "@rebasepro/types";
4
+ import { getColumnName, resolveCollectionRelations } from "@rebasepro/common";
5
+ import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
6
+ import { ConditionBuilderStatic } from "../interfaces";
7
+
8
+ /** Drizzle dynamic query builder — accepts innerJoin + where chaining */
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export interface DrizzleDynamicQuery {
11
+ innerJoin(table: PgTable<any>, condition: SQL): this;
12
+ where(condition: SQL | undefined): this;
13
+ limit(limit: number): this;
14
+ }
15
+
16
+ /**
17
+ * Unified condition builder for Drizzle/PostgreSQL queries.
18
+ *
19
+ * This class uses static methods and satisfies the ConditionBuilderStatic<SQL> type.
20
+ * It translates Rebase filter conditions to Drizzle SQL conditions.
21
+ *
22
+ * @example
23
+ * const builder: ConditionBuilderStatic<SQL> = DrizzleConditionBuilder;
24
+ */
25
+ export class DrizzleConditionBuilder {
26
+
27
+ /**
28
+ * Build filter conditions from FilterValues
29
+ */
30
+ static buildFilterConditions<M extends Record<string, any>>(
31
+ filter: FilterValues<Extract<keyof M, string>>,
32
+ table: PgTable<any>,
33
+ collectionPath: string
34
+ ): SQL[] {
35
+ const conditions: SQL[] = [];
36
+
37
+ for (const [field, filterParam] of Object.entries(filter)) {
38
+ if (!filterParam) continue;
39
+
40
+ const [op, value] = filterParam as [WhereFilterOp, any];
41
+ const fieldColumn = table[field as keyof typeof table] as AnyPgColumn;
42
+
43
+ if (!fieldColumn) {
44
+ console.warn(`Filtering by field '${field}', but it does not exist in table for collection '${collectionPath}'`);
45
+ continue;
46
+ }
47
+
48
+ const condition = this.buildSingleFilterCondition(fieldColumn, op, value);
49
+ if (condition) {
50
+ conditions.push(condition);
51
+ }
52
+ }
53
+
54
+ return conditions;
55
+ }
56
+
57
+ /**
58
+ * Build a single filter condition for a specific operator and value
59
+ */
60
+ static buildSingleFilterCondition(
61
+ column: AnyPgColumn,
62
+ op: WhereFilterOp,
63
+ value: unknown
64
+ ): SQL | null {
65
+ switch (op) {
66
+ case "==":
67
+ return eq(column, value);
68
+ case "!=":
69
+ return sql`${column} != ${value}`;
70
+ case ">":
71
+ return sql`${column} > ${value}`;
72
+ case ">=":
73
+ return sql`${column} >= ${value}`;
74
+ case "<":
75
+ return sql`${column} < ${value}`;
76
+ case "<=":
77
+ return sql`${column} <= ${value}`;
78
+ case "in":
79
+ if (Array.isArray(value) && value.length > 0) {
80
+ return inArray(column, value);
81
+ }
82
+ return null;
83
+ case "array-contains":
84
+ // For JSONB arrays
85
+ return sql`${column} @> ${JSON.stringify([value])}`;
86
+ default:
87
+ console.warn(`Unsupported filter operation: ${op}`);
88
+ return null;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Build relation-based conditions for different relation types
94
+ */
95
+ static buildRelationConditions(
96
+ relation: Relation,
97
+ parentEntityId: string | number | (string | number)[],
98
+ targetTable: PgTable<any>,
99
+ parentTable: PgTable<any>,
100
+ parentIdColumn: AnyPgColumn,
101
+ targetIdColumn: AnyPgColumn,
102
+ registry: PostgresCollectionRegistry
103
+ ): {
104
+ joinConditions: { table: PgTable<any>; condition: SQL }[];
105
+ whereConditions: SQL[];
106
+ } {
107
+ console.debug(`🔍 [buildRelationConditions] Building conditions for relation:`, {
108
+ relationName: relation.relationName,
109
+ cardinality: relation.cardinality,
110
+ direction: relation.direction,
111
+ hasThrough: !!relation.through,
112
+ hasForeignKeyOnTarget: !!relation.foreignKeyOnTarget,
113
+ inverseRelationName: relation.inverseRelationName,
114
+ parentEntityId: parentEntityId
115
+ });
116
+
117
+ const joinConditions: { table: PgTable<any>; condition: SQL }[] = [];
118
+ const whereConditions: SQL[] = [];
119
+
120
+ if (relation.joinPath && relation.joinPath.length > 0) {
121
+ console.debug(`🔍 [buildRelationConditions] Using joinPath logic`);
122
+ // Handle join path relations
123
+ const {
124
+ joins,
125
+ finalCondition
126
+ } = this.buildJoinPathConditions(
127
+ relation.joinPath,
128
+ targetTable,
129
+ parentTable,
130
+ parentIdColumn,
131
+ parentEntityId,
132
+ registry
133
+ );
134
+ joinConditions.push(...joins);
135
+ whereConditions.push(finalCondition);
136
+
137
+ } else if (relation.through && relation.cardinality === "many" && relation.direction === "owning") {
138
+ console.debug(`🔍 [buildRelationConditions] Using owning many-to-many with explicit through`);
139
+ // Handle many-to-many relations with junction table
140
+ const junctionResult = this.buildJunctionTableConditions(
141
+ relation.through,
142
+ targetIdColumn,
143
+ parentEntityId,
144
+ registry
145
+ );
146
+ joinConditions.push(junctionResult.join);
147
+ whereConditions.push(junctionResult.condition);
148
+
149
+ } else if (relation.through && relation.cardinality === "many" && relation.direction === "inverse") {
150
+ console.debug(`🔍 [buildRelationConditions] Using inverse many-to-many with explicit through`);
151
+ // Handle inverse many-to-many relations with junction table
152
+ const junctionResult = this.buildInverseJunctionTableConditions(
153
+ relation.through,
154
+ targetIdColumn,
155
+ parentEntityId,
156
+ registry
157
+ );
158
+ joinConditions.push(junctionResult.join);
159
+ whereConditions.push(junctionResult.condition);
160
+
161
+ } else if (relation.cardinality === "many" && relation.direction === "inverse" && !relation.through) {
162
+ console.debug(`🔍 [buildRelationConditions] Handling inverse many relationship without explicit through`);
163
+
164
+ // First, try to find a junction table (for many-to-many relationships)
165
+ const junctionInfo = this.findCorrespondingJunctionTable(relation, registry);
166
+ if (junctionInfo) {
167
+ console.debug(`🔍 [buildRelationConditions] Found junction info for inverse many-to-many, building junction conditions`);
168
+ const junctionResult = this.buildInverseJunctionTableConditions(
169
+ junctionInfo,
170
+ targetIdColumn,
171
+ parentEntityId,
172
+ registry
173
+ );
174
+ joinConditions.push(junctionResult.join);
175
+ whereConditions.push(junctionResult.condition);
176
+ } else if (relation.foreignKeyOnTarget) {
177
+ console.debug(`🔍 [buildRelationConditions] No junction table found, treating as inverse one-to-many with foreign key on target`);
178
+ // This is a true inverse one-to-many relationship
179
+ const simpleCondition = this.buildSimpleRelationCondition(
180
+ relation,
181
+ targetTable,
182
+ parentTable,
183
+ parentEntityId
184
+ );
185
+ whereConditions.push(simpleCondition);
186
+ } else {
187
+ console.error(`🔍 [buildRelationConditions] Failed to find junction table info and no foreign key specified`);
188
+ throw new Error(`Cannot resolve inverse many relation '${relation.relationName}'. Either specify 'through' property, ensure corresponding owning relation exists with junction table configuration, or specify 'foreignKeyOnTarget' for one-to-many relationships.`);
189
+ }
190
+ } else {
191
+ console.debug(`🔍 [buildRelationConditions] Using simple relation logic - THIS IS WHERE THE ERROR MIGHT OCCUR`);
192
+ // Handle simple relations
193
+ const simpleCondition = this.buildSimpleRelationCondition(
194
+ relation,
195
+ targetTable,
196
+ parentTable,
197
+ parentEntityId
198
+ );
199
+ whereConditions.push(simpleCondition);
200
+ }
201
+
202
+ console.debug(`🔍 [buildRelationConditions] Final result:`, {
203
+ joinConditionsCount: joinConditions.length,
204
+ whereConditionsCount: whereConditions.length
205
+ });
206
+
207
+ return {
208
+ joinConditions,
209
+ whereConditions
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Build conditions for join path relations
215
+ */
216
+ private static buildJoinPathConditions(
217
+ joinPath: JoinStep[],
218
+ targetTable: PgTable<any>,
219
+ parentTable: PgTable<any>,
220
+ parentIdColumn: AnyPgColumn,
221
+ parentEntityId: string | number | (string | number)[],
222
+ registry: PostgresCollectionRegistry
223
+ ): {
224
+ joins: { table: PgTable<any>; condition: SQL }[];
225
+ finalCondition: SQL;
226
+ } {
227
+ const joins: { table: PgTable<any>; condition: SQL }[] = [];
228
+ let currentTable = targetTable;
229
+
230
+ // Process join steps in reverse order to build path back to parent
231
+ for (const joinStep of [...joinPath].reverse()) {
232
+ const fromTableName = this.getTableNamesFromColumns(joinStep.on.from)[0];
233
+ const toTableName = this.getTableNamesFromColumns(joinStep.on.to)[0];
234
+ const fromColName = this.getColumnNamesFromColumns(joinStep.on.from)[0];
235
+ const toColName = this.getColumnNamesFromColumns(joinStep.on.to)[0];
236
+
237
+ const fromTable = registry.getTable(fromTableName);
238
+ const toTable = registry.getTable(toTableName);
239
+
240
+ if (!fromTable || !toTable) {
241
+ throw new Error(`Join tables not found for step: from ${fromTableName} to ${toTableName}`);
242
+ }
243
+
244
+ const {
245
+ joinTable,
246
+ condition,
247
+ additionalJoins
248
+ } = this.buildSingleJoinCondition(
249
+ currentTable,
250
+ fromTable,
251
+ toTable,
252
+ fromColName,
253
+ toColName,
254
+ fromTableName,
255
+ toTableName,
256
+ registry
257
+ );
258
+
259
+ joins.push({
260
+ table: joinTable,
261
+ condition
262
+ });
263
+ currentTable = joinTable;
264
+
265
+ // Add any additional joins needed for many-to-many relationships
266
+ if (additionalJoins && additionalJoins.length > 0) {
267
+ joins.push(...additionalJoins);
268
+ }
269
+ }
270
+
271
+ // Ensure we've connected back to the parent table
272
+ // For junction tables, we might end up at the junction table instead of the parent table
273
+ if (currentTable !== parentTable) {
274
+ // Try to get table names from the Drizzle table objects
275
+ let currentTableName = "unknown";
276
+ let parentTableName = "unknown";
277
+
278
+ // Try multiple ways to extract table names from Drizzle objects
279
+ if (currentTable && typeof currentTable === "object") {
280
+ // Check common Drizzle table name properties
281
+ currentTableName = (currentTable as unknown as Record<string | symbol, unknown>)[Symbol.for("drizzle:Name")] as string ||
282
+ ((currentTable as unknown as Record<string, unknown>)._ as Record<string, unknown>)?.name as string ||
283
+ (currentTable as unknown as Record<string, unknown>).tableName as string ||
284
+ (currentTable as unknown as Record<string, unknown>).name as string ||
285
+ "unknown";
286
+ }
287
+
288
+ if (parentTable && typeof parentTable === "object") {
289
+ parentTableName = (parentTable as unknown as Record<string | symbol, unknown>)[Symbol.for("drizzle:Name")] as string ||
290
+ ((parentTable as unknown as Record<string, unknown>)._ as Record<string, unknown>)?.name as string ||
291
+ (parentTable as unknown as Record<string, unknown>).tableName as string ||
292
+ (parentTable as unknown as Record<string, unknown>).name as string ||
293
+ "unknown";
294
+ }
295
+
296
+ // For junction table scenarios, be more lenient with validation
297
+ // If we can't determine table names reliably, or if this looks like a junction table scenario,
298
+ // we'll allow it and let the SQL execution validate the correctness
299
+ const couldBeJunctionScenario = currentTableName.includes("_") ||
300
+ currentTableName === "unknown" ||
301
+ parentTableName === "unknown";
302
+
303
+ if (!couldBeJunctionScenario) {
304
+ throw new Error(`Join path did not result in connecting to parent table. Current: ${currentTableName}, Parent: ${parentTableName}`);
305
+ }
306
+ }
307
+
308
+ // Handle both single ID and array of IDs
309
+ const finalCondition = Array.isArray(parentEntityId)
310
+ ? inArray(parentIdColumn, parentEntityId)
311
+ : eq(parentIdColumn, parentEntityId);
312
+
313
+ return {
314
+ joins,
315
+ finalCondition
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Build a single join condition between tables
321
+ */
322
+ private static buildSingleJoinCondition(
323
+ currentTable: PgTable<any>,
324
+ fromTable: PgTable<any>,
325
+ toTable: PgTable<any>,
326
+ fromColName: string,
327
+ toColName: string,
328
+ fromTableName: string,
329
+ toTableName: string,
330
+ registry?: PostgresCollectionRegistry
331
+ ): { joinTable: PgTable<any>; condition: SQL; additionalJoins?: { table: PgTable<any>; condition: SQL }[] } {
332
+ let joinTable: PgTable<any>;
333
+ let condition: SQL;
334
+ const additionalJoins: { table: PgTable<any>; condition: SQL }[] = [];
335
+
336
+ if (currentTable === toTable) {
337
+ // current -> toTable, so join the fromTable
338
+ const left = fromTable[fromColName as keyof typeof fromTable] as AnyPgColumn;
339
+ const right = (currentTable as unknown as Record<string, unknown>)[toColName] as AnyPgColumn;
340
+
341
+ if (!left || !right) {
342
+ // Check if this might be a many-to-many relationship requiring a junction table
343
+ if (registry) {
344
+ const junctionResult = this.tryBuildJunctionJoin(
345
+ currentTable,
346
+ fromTable,
347
+ fromColName,
348
+ toColName,
349
+ fromTableName,
350
+ toTableName,
351
+ registry
352
+ );
353
+ if (junctionResult) {
354
+ return junctionResult;
355
+ }
356
+ }
357
+ throw new Error(`Join columns not found: ${fromTableName}.${fromColName} = ${toTableName}.${toColName}`);
358
+ }
359
+
360
+ joinTable = fromTable;
361
+ condition = eq(left, right);
362
+ } else if (currentTable === fromTable) {
363
+ // current -> fromTable, so join the toTable
364
+ const left = toTable[toColName as keyof typeof toTable] as AnyPgColumn;
365
+ const right = (currentTable as unknown as Record<string, unknown>)[fromColName] as AnyPgColumn;
366
+
367
+ if (!left || !right) {
368
+ // Check if this might be a many-to-many relationship requiring a junction table
369
+ if (registry) {
370
+ const junctionResult = this.tryBuildJunctionJoin(
371
+ currentTable,
372
+ toTable,
373
+ fromColName,
374
+ toColName,
375
+ fromTableName,
376
+ toTableName,
377
+ registry
378
+ );
379
+ if (junctionResult) {
380
+ return junctionResult;
381
+ }
382
+ }
383
+ throw new Error(`Join columns not found: ${toTableName}.${toColName} = ${fromTableName}.${fromColName}`);
384
+ }
385
+
386
+ joinTable = toTable;
387
+ condition = eq(left, right);
388
+ } else {
389
+ throw new Error(`Join step does not match current table. Current table does not match from: ${fromTableName} or to: ${toTableName}`);
390
+ }
391
+
392
+ return {
393
+ joinTable,
394
+ condition,
395
+ additionalJoins
396
+ };
397
+ }
398
+
399
+ /**
400
+ * Try to build a junction table join when direct foreign key relationship is not found
401
+ */
402
+ private static tryBuildJunctionJoin(
403
+ currentTable: PgTable<any>,
404
+ targetTable: PgTable<any>,
405
+ fromColName: string,
406
+ toColName: string,
407
+ fromTableName: string,
408
+ toTableName: string,
409
+ registry: PostgresCollectionRegistry
410
+ ): { joinTable: PgTable<any>; condition: SQL; additionalJoins: { table: PgTable<any>; condition: SQL }[] } | null {
411
+ // Try to find a junction table that connects these two tables
412
+ // Common naming patterns: table1_table2, table1Table2, etc.
413
+ const possibleJunctionNames = [
414
+ `${fromTableName}_${toTableName}`,
415
+ `${toTableName}_${fromTableName}`,
416
+ `${fromTableName}${toTableName.charAt(0).toUpperCase() + toTableName.slice(1)}`,
417
+ `${toTableName}${fromTableName.charAt(0).toUpperCase() + fromTableName.slice(1)}`
418
+ ];
419
+
420
+ for (const junctionName of possibleJunctionNames) {
421
+ const junctionTable = registry.getTable(junctionName);
422
+ if (junctionTable) {
423
+ // Try to find the appropriate columns in the junction table
424
+ const sourceColName = `${fromTableName.slice(0, -1)}_id`; // Remove 's' and add '_id'
425
+ const targetColName = `${toTableName.slice(0, -1)}_id`;
426
+
427
+ const junctionSourceCol = junctionTable[sourceColName as keyof typeof junctionTable] as AnyPgColumn;
428
+ const junctionTargetCol = junctionTable[targetColName as keyof typeof junctionTable] as AnyPgColumn;
429
+
430
+ if (junctionSourceCol && junctionTargetCol) {
431
+ // Found a valid junction table setup
432
+ const currentTableIdCol = Object.values(currentTable).find((col: Record<string, unknown>) => col.primary) as AnyPgColumn;
433
+ const targetTableIdCol = Object.values(targetTable).find((col: Record<string, unknown>) => col.primary) as AnyPgColumn;
434
+
435
+ if (!currentTableIdCol || !targetTableIdCol) {
436
+ continue; // Skip if we can't find primary keys
437
+ }
438
+
439
+ // Determine which direction to join
440
+ if (currentTable === targetTable) {
441
+ // We're joining through junction to reach the other table
442
+ return {
443
+ joinTable: targetTable,
444
+ condition: eq(targetTableIdCol, junctionTargetCol),
445
+ additionalJoins: [
446
+ {
447
+ table: junctionTable,
448
+ condition: eq(currentTableIdCol, junctionSourceCol)
449
+ }
450
+ ]
451
+ };
452
+ } else {
453
+ // Standard junction join
454
+ return {
455
+ joinTable: junctionTable,
456
+ condition: eq(currentTableIdCol, junctionSourceCol),
457
+ additionalJoins: [
458
+ {
459
+ table: targetTable,
460
+ condition: eq(targetTableIdCol, junctionTargetCol)
461
+ }
462
+ ]
463
+ };
464
+ }
465
+ }
466
+ }
467
+ }
468
+
469
+ return null; // No junction table found
470
+ }
471
+
472
+ /**
473
+ * Build conditions for junction table (many-to-many) relations
474
+ */
475
+ private static buildJunctionTableConditions(
476
+ through: { table: string; sourceColumn: string; targetColumn: string },
477
+ targetIdColumn: AnyPgColumn,
478
+ parentEntityId: string | number | (string | number)[],
479
+ registry: PostgresCollectionRegistry
480
+ ): { join: { table: PgTable<any>; condition: SQL }; condition: SQL } {
481
+ const junctionTable = registry.getTable(through.table);
482
+ if (!junctionTable) {
483
+ throw new Error(`Junction table not found: ${through.table}`);
484
+ }
485
+
486
+ const junctionSourceCol = junctionTable[through.sourceColumn as keyof typeof junctionTable] as AnyPgColumn;
487
+ const junctionTargetCol = junctionTable[through.targetColumn as keyof typeof junctionTable] as AnyPgColumn;
488
+
489
+ if (!junctionSourceCol) {
490
+ throw new Error(`Source column '${through.sourceColumn}' not found in junction table '${through.table}'`);
491
+ }
492
+ if (!junctionTargetCol) {
493
+ throw new Error(`Target column '${through.targetColumn}' not found in junction table '${through.table}'`);
494
+ }
495
+
496
+ // Handle both single ID and array of IDs
497
+ const condition = Array.isArray(parentEntityId)
498
+ ? inArray(junctionSourceCol, parentEntityId)
499
+ : eq(junctionSourceCol, parentEntityId);
500
+
501
+ return {
502
+ join: {
503
+ table: junctionTable,
504
+ condition: eq(targetIdColumn, junctionTargetCol)
505
+ },
506
+ condition
507
+ };
508
+ }
509
+
510
+ /**
511
+ * Build conditions for inverse junction table (many-to-many) relations
512
+ */
513
+ private static buildInverseJunctionTableConditions(
514
+ through: { table: string; sourceColumn: string; targetColumn: string },
515
+ targetIdColumn: AnyPgColumn,
516
+ parentEntityId: string | number | (string | number)[],
517
+ registry: PostgresCollectionRegistry
518
+ ): { join: { table: PgTable<any>; condition: SQL }; condition: SQL } {
519
+ const junctionTable = registry.getTable(through.table);
520
+ if (!junctionTable) {
521
+ throw new Error(`Junction table not found: ${through.table}`);
522
+ }
523
+
524
+ const junctionSourceCol = junctionTable[through.sourceColumn as keyof typeof junctionTable] as AnyPgColumn;
525
+ const junctionTargetCol = junctionTable[through.targetColumn as keyof typeof junctionTable] as AnyPgColumn;
526
+
527
+ if (!junctionSourceCol) {
528
+ throw new Error(`Source column '${through.sourceColumn}' not found in junction table '${through.table}'`);
529
+ }
530
+ if (!junctionTargetCol) {
531
+ throw new Error(`Target column '${through.targetColumn}' not found in junction table '${through.table}'`);
532
+ }
533
+
534
+ // For inverse relations, the parentEntityId (tag ID) should match the sourceColumn (tag_id)
535
+ // and we want to find target entities (posts) through the targetColumn (post_id)
536
+ const condition = Array.isArray(parentEntityId)
537
+ ? inArray(junctionSourceCol, parentEntityId)
538
+ : eq(junctionSourceCol, parentEntityId);
539
+
540
+ return {
541
+ join: {
542
+ table: junctionTable,
543
+ condition: eq(targetIdColumn, junctionTargetCol)
544
+ },
545
+ condition
546
+ };
547
+ }
548
+
549
+ /**
550
+ * Build conditions for simple relations (owning/inverse without join paths)
551
+ */
552
+ private static buildSimpleRelationCondition(
553
+ relation: Relation,
554
+ targetTable: PgTable<any>,
555
+ parentTable: PgTable<any>,
556
+ parentEntityId: string | number | (string | number)[]
557
+ ): SQL {
558
+ if (relation.direction === "owning" && relation.localKey) {
559
+ // For owning relations, the parentEntityId is actually the foreign key value
560
+ // that should match the target table's primary key
561
+ const targetIdCol = Object.values(targetTable).find((col: Record<string, unknown>) => col.primary) as AnyPgColumn;
562
+ if (!targetIdCol) {
563
+ // Fallback to looking for an "id" column by name
564
+ const idCol = Object.values(targetTable).find((col: Record<string, unknown>) => col.name === "id") as AnyPgColumn;
565
+ if (!idCol) {
566
+ throw new Error("No primary key or \"id\" column found in target table");
567
+ }
568
+ return Array.isArray(parentEntityId)
569
+ ? inArray(idCol, parentEntityId)
570
+ : eq(idCol, parentEntityId);
571
+ }
572
+ return Array.isArray(parentEntityId)
573
+ ? inArray(targetIdCol, parentEntityId)
574
+ : eq(targetIdCol, parentEntityId);
575
+
576
+ } else if (relation.direction === "inverse" && relation.foreignKeyOnTarget) {
577
+ // Inverse relation: use foreign key on target table
578
+ const foreignKeyCol = targetTable[relation.foreignKeyOnTarget as keyof typeof targetTable] as AnyPgColumn;
579
+ if (!foreignKeyCol) {
580
+ // This could be a many-to-many relationship where foreignKeyOnTarget was set by sanitizeRelation
581
+ // but the column doesn't actually exist. In this case, we should suggest using junction tables.
582
+ throw new Error(`Foreign key column '${relation.foreignKeyOnTarget}' not found in target table. This might be a many-to-many relationship that requires a junction table. Consider using 'through' property or ensure the corresponding owning relation exists with junction table configuration.`);
583
+ }
584
+ return Array.isArray(parentEntityId)
585
+ ? inArray(foreignKeyCol, parentEntityId)
586
+ : eq(foreignKeyCol, parentEntityId);
587
+
588
+ } else if (relation.direction === "inverse" && relation.cardinality === "many" && relation.inverseRelationName) {
589
+ // For inverse many-to-many relations, this should not be called directly
590
+ // The buildRelationConditions method should handle finding the junction table
591
+ // If we reach here, it means the junction table lookup failed
592
+ throw new Error(`Inverse many-to-many relation '${relation.relationName}' requires a junction table. Either specify 'through' property or ensure the corresponding owning relation exists with junction table configuration.`);
593
+
594
+ } else if (relation.direction === "inverse" && relation.cardinality === "one" && relation.inverseRelationName) {
595
+ // Auto-infer foreign key column for inverse one-to-one relations
596
+ // Pattern: {inverseRelationName}_id (e.g., "author" -> "author_id")
597
+ const inferredForeignKeyName = `${relation.inverseRelationName}_id`;
598
+ const foreignKeyCol = targetTable[inferredForeignKeyName as keyof typeof targetTable] as AnyPgColumn;
599
+
600
+ if (!foreignKeyCol) {
601
+ throw new Error(`Auto-inferred foreign key column '${inferredForeignKeyName}' not found in target table for inverse relation '${relation.relationName}'. Please specify 'foreignKeyOnTarget' explicitly.`);
602
+ }
603
+
604
+ console.debug(`🔍 [DrizzleConditionBuilder] Auto-inferred foreign key '${inferredForeignKeyName}' for inverse relation '${relation.relationName}'`);
605
+
606
+ return Array.isArray(parentEntityId)
607
+ ? inArray(foreignKeyCol, parentEntityId)
608
+ : eq(foreignKeyCol, parentEntityId);
609
+
610
+ } else {
611
+ throw new Error(`Relation '${relation.relationName}' lacks proper configuration. For many-to-many relations, use 'through' property. For simple relations, use 'localKey' or 'foreignKeyOnTarget'.`);
612
+ }
613
+ }
614
+
615
+ /**
616
+ * Combine multiple conditions with AND operator
617
+ */
618
+ static combineConditionsWithAnd(conditions: SQL[]): SQL | undefined {
619
+ if (conditions.length === 0) return undefined;
620
+ if (conditions.length === 1) return conditions[0];
621
+ return and(...conditions);
622
+ }
623
+
624
+ /**
625
+ * Combine multiple conditions with OR operator
626
+ */
627
+ static combineConditionsWithOr(conditions: SQL[]): SQL | undefined {
628
+ if (conditions.length === 0) return undefined;
629
+ if (conditions.length === 1) return conditions[0];
630
+ return or(...conditions);
631
+ }
632
+
633
+ /**
634
+ * Build search conditions for text fields
635
+ */
636
+ static buildSearchConditions(
637
+ searchString: string,
638
+ properties: Record<string, any>,
639
+ table: PgTable<any>
640
+ ): SQL[] {
641
+ const searchConditions: SQL[] = [];
642
+
643
+ for (const [key, prop] of Object.entries(properties)) {
644
+ // Only include string properties that don't have enum defined
645
+ // PostgreSQL enum and uuid columns don't support ILIKE, so we skip them
646
+ if (prop.type === "string" && !prop.enum && prop.isId !== "uuid") {
647
+ const fieldColumn = table[key as keyof typeof table] as AnyPgColumn;
648
+ if (fieldColumn) {
649
+ searchConditions.push(ilike(fieldColumn, `%${searchString}%`));
650
+ }
651
+ }
652
+ }
653
+
654
+ return searchConditions;
655
+ }
656
+
657
+ /**
658
+ * Build a unique field check condition
659
+ */
660
+ static buildUniqueFieldCondition(
661
+ fieldColumn: AnyPgColumn,
662
+ value: unknown,
663
+ idColumn?: AnyPgColumn,
664
+ excludeId?: string | number
665
+ ): SQL[] {
666
+ const conditions: SQL[] = [eq(fieldColumn, value)];
667
+
668
+ if (excludeId && idColumn) {
669
+ conditions.push(sql`${idColumn} != ${excludeId}`);
670
+ }
671
+
672
+ return conditions;
673
+ }
674
+
675
+ /**
676
+ * Build relation-based query with joins and conditions
677
+ */
678
+ static buildRelationQuery<T extends DrizzleDynamicQuery>(
679
+ baseQuery: T,
680
+ relation: Relation,
681
+ parentEntityId: string | number | (string | number)[],
682
+ targetTable: PgTable<any>,
683
+ parentTable: PgTable<any>,
684
+ parentIdColumn: AnyPgColumn,
685
+ targetIdColumn: AnyPgColumn,
686
+ registry: PostgresCollectionRegistry,
687
+ additionalFilters?: SQL[]
688
+ ): T {
689
+ const { joinConditions, whereConditions } = this.buildRelationConditions(
690
+ relation,
691
+ parentEntityId,
692
+ targetTable,
693
+ parentTable,
694
+ parentIdColumn,
695
+ targetIdColumn,
696
+ registry
697
+ );
698
+
699
+ let query = baseQuery;
700
+
701
+ // Apply joins
702
+ for (const { table, condition } of joinConditions) {
703
+ query = query.innerJoin(table, condition);
704
+ }
705
+
706
+ // Combine all conditions
707
+ const allConditions = [...whereConditions];
708
+ if (additionalFilters) {
709
+ allConditions.push(...additionalFilters);
710
+ }
711
+
712
+ // Apply where conditions
713
+ if (allConditions.length > 0) {
714
+ query = query.where(and(...allConditions));
715
+ }
716
+
717
+ return query;
718
+ }
719
+
720
+ /**
721
+ * Build count query for relations with proper joins and conditions
722
+ */
723
+ static buildRelationCountQuery<T extends DrizzleDynamicQuery>(
724
+ baseCountQuery: T,
725
+ relation: Relation,
726
+ parentEntityId: string | number,
727
+ targetTable: PgTable<any>,
728
+ parentTable: PgTable<any>,
729
+ parentIdColumn: AnyPgColumn,
730
+ targetIdColumn: AnyPgColumn,
731
+ registry: PostgresCollectionRegistry,
732
+ additionalFilters?: SQL[]
733
+ ): T {
734
+ // For count queries, we need to handle joins differently to avoid duplicates
735
+ if (relation.joinPath && relation.joinPath.length > 0) {
736
+ return this.buildJoinPathCountQuery(
737
+ baseCountQuery,
738
+ relation.joinPath,
739
+ targetTable,
740
+ parentTable,
741
+ parentIdColumn,
742
+ parentEntityId,
743
+ registry,
744
+ additionalFilters
745
+ );
746
+ } else if (relation.through && relation.cardinality === "many" && relation.direction === "owning") {
747
+ return this.buildJunctionCountQuery(
748
+ baseCountQuery,
749
+ relation.through,
750
+ targetIdColumn,
751
+ parentEntityId,
752
+ registry,
753
+ additionalFilters
754
+ );
755
+ } else if (relation.through && relation.cardinality === "many" && relation.direction === "inverse") {
756
+ return this.buildInverseJunctionCountQuery(
757
+ baseCountQuery,
758
+ relation.through,
759
+ targetIdColumn,
760
+ parentEntityId,
761
+ registry,
762
+ additionalFilters
763
+ );
764
+ } else {
765
+ // Simple relations
766
+ const simpleCondition = this.buildSimpleRelationCondition(
767
+ relation,
768
+ targetTable,
769
+ parentTable,
770
+ parentEntityId
771
+ );
772
+
773
+ const allConditions = [simpleCondition];
774
+ if (additionalFilters) {
775
+ allConditions.push(...additionalFilters);
776
+ }
777
+
778
+ return baseCountQuery.where(and(...allConditions));
779
+ }
780
+ }
781
+
782
+ /**
783
+ * Build join path conditions for count queries
784
+ */
785
+ private static buildJoinPathCountQuery<T extends DrizzleDynamicQuery>(
786
+ baseCountQuery: T,
787
+ joinPath: JoinStep[],
788
+ targetTable: PgTable<any>,
789
+ parentTable: PgTable<any>,
790
+ parentIdColumn: AnyPgColumn,
791
+ parentEntityId: string | number,
792
+ registry: PostgresCollectionRegistry,
793
+ additionalFilters?: SQL[]
794
+ ): T {
795
+ let query = baseCountQuery;
796
+ let currentTable = targetTable;
797
+
798
+ // Process join steps in reverse order
799
+ for (const joinStep of [...joinPath].reverse()) {
800
+ const fromTableName = this.getTableNamesFromColumns(joinStep.on.from)[0];
801
+ const toTableName = this.getTableNamesFromColumns(joinStep.on.to)[0];
802
+ const fromColName = this.getColumnNamesFromColumns(joinStep.on.from)[0];
803
+ const toColName = this.getColumnNamesFromColumns(joinStep.on.to)[0];
804
+
805
+ const fromTable = registry.getTable(fromTableName);
806
+ const toTable = registry.getTable(toTableName);
807
+
808
+ if (!fromTable || !toTable) {
809
+ throw new Error(`Join tables not found for step: from ${fromTableName} to ${toTableName}`);
810
+ }
811
+
812
+ const { joinTable, condition } = this.buildSingleJoinCondition(
813
+ currentTable,
814
+ fromTable,
815
+ toTable,
816
+ fromColName,
817
+ toColName,
818
+ fromTableName,
819
+ toTableName
820
+ );
821
+
822
+ query = query.innerJoin(joinTable, condition);
823
+ currentTable = joinTable;
824
+ }
825
+
826
+ if (currentTable !== parentTable) {
827
+ throw new Error("Join path did not result in connecting to parent table");
828
+ }
829
+
830
+ const allConditions = [eq(parentIdColumn, parentEntityId)];
831
+ if (additionalFilters) {
832
+ allConditions.push(...additionalFilters);
833
+ }
834
+
835
+ return query.where(and(...allConditions));
836
+ }
837
+
838
+ /**
839
+ * Build junction table conditions for count queries
840
+ */
841
+ private static buildJunctionCountQuery<T extends DrizzleDynamicQuery>(
842
+ baseCountQuery: T,
843
+ through: { table: string; sourceColumn: string; targetColumn: string },
844
+ targetIdColumn: AnyPgColumn,
845
+ parentEntityId: string | number,
846
+ registry: PostgresCollectionRegistry,
847
+ additionalFilters?: SQL[]
848
+ ): T {
849
+ const junctionTable = registry.getTable(through.table);
850
+ if (!junctionTable) {
851
+ throw new Error(`Junction table not found: ${through.table}`);
852
+ }
853
+
854
+ const junctionSourceCol = junctionTable[through.sourceColumn as keyof typeof junctionTable] as AnyPgColumn;
855
+ const junctionTargetCol = junctionTable[through.targetColumn as keyof typeof junctionTable] as AnyPgColumn;
856
+
857
+ if (!junctionSourceCol) {
858
+ throw new Error(`Source column '${through.sourceColumn}' not found in junction table '${through.table}'`);
859
+ }
860
+ if (!junctionTargetCol) {
861
+ throw new Error(`Target column '${through.targetColumn}' not found in junction table '${through.table}'`);
862
+ }
863
+
864
+ const baseConditions = [eq(junctionSourceCol, parentEntityId)];
865
+ if (additionalFilters && additionalFilters.length > 0) {
866
+ baseConditions.push(...additionalFilters);
867
+ }
868
+
869
+ return baseCountQuery
870
+ .innerJoin(junctionTable, eq(targetIdColumn, junctionTargetCol))
871
+ .where(and(...baseConditions));
872
+ }
873
+
874
+ /**
875
+ * Build inverse junction table conditions for count queries
876
+ */
877
+ private static buildInverseJunctionCountQuery<T extends DrizzleDynamicQuery>(
878
+ baseCountQuery: T,
879
+ through: { table: string; sourceColumn: string; targetColumn: string },
880
+ targetIdColumn: AnyPgColumn,
881
+ parentEntityId: string | number,
882
+ registry: PostgresCollectionRegistry,
883
+ additionalFilters?: SQL[]
884
+ ): T {
885
+ const junctionTable = registry.getTable(through.table);
886
+ if (!junctionTable) {
887
+ throw new Error(`Junction table not found: ${through.table}`);
888
+ }
889
+
890
+ const junctionSourceCol = junctionTable[through.sourceColumn as keyof typeof junctionTable] as AnyPgColumn;
891
+ const junctionTargetCol = junctionTable[through.targetColumn as keyof typeof junctionTable] as AnyPgColumn;
892
+
893
+ if (!junctionSourceCol) {
894
+ throw new Error(`Source column '${through.sourceColumn}' not found in junction table '${through.table}'`);
895
+ }
896
+ if (!junctionTargetCol) {
897
+ throw new Error(`Target column '${through.targetColumn}' not found in junction table '${through.table}'`);
898
+ }
899
+
900
+ const baseConditions = [eq(junctionSourceCol, parentEntityId)];
901
+ if (additionalFilters && additionalFilters.length > 0) {
902
+ baseConditions.push(...additionalFilters);
903
+ }
904
+
905
+ return baseCountQuery
906
+ .innerJoin(junctionTable, eq(targetIdColumn, junctionTargetCol))
907
+ .where(and(...baseConditions));
908
+ }
909
+
910
+ /**
911
+ * Helper method to extract table names from columns
912
+ */
913
+ static getTableNamesFromColumns(columns: string | string[]): string[] {
914
+ if (Array.isArray(columns)) {
915
+ return columns.map(col => col.includes(".") ? col.split(".")[0] : "");
916
+ }
917
+ return [columns.includes(".") ? columns.split(".")[0] : ""];
918
+ }
919
+
920
+ /**
921
+ * Helper method to extract column names from columns
922
+ */
923
+ static getColumnNamesFromColumns(columns: string | string[]): string[] {
924
+ if (Array.isArray(columns)) {
925
+ return columns.map(col => getColumnName(col));
926
+ }
927
+ return [getColumnName(columns)];
928
+ }
929
+
930
+ /**
931
+ * Find the corresponding junction table for an inverse many-to-many relation
932
+ */
933
+ private static findCorrespondingJunctionTable(
934
+ relation: Relation,
935
+ registry: PostgresCollectionRegistry
936
+ ): { table: string; sourceColumn: string; targetColumn: string } | null {
937
+ try {
938
+ console.debug(`🔍 [findCorrespondingJunctionTable] Looking for junction table for inverse relation '${relation.relationName}' with inverseRelationName '${relation.inverseRelationName}'`);
939
+
940
+ if (!relation.inverseRelationName) {
941
+ console.debug(`🔍 [findCorrespondingJunctionTable] No inverseRelationName specified`);
942
+ return null;
943
+ }
944
+
945
+ // Get the target collection of the inverse relation
946
+ const targetCollection = relation.target();
947
+ console.debug(`🔍 [findCorrespondingJunctionTable] Target collection: ${targetCollection.slug}`);
948
+
949
+ // Find the corresponding owning relation on the target collection
950
+ const targetCollectionRelations = resolveCollectionRelations(targetCollection as import("@rebasepro/types").PostgresCollection<any, any>);
951
+ console.debug(`🔍 [findCorrespondingJunctionTable] Target collection relations:`, Object.keys(targetCollectionRelations));
952
+
953
+ // Look for the owning many-to-many relation that matches our inverseRelationName
954
+ const correspondingRelation = targetCollectionRelations[relation.inverseRelationName];
955
+
956
+ if (!correspondingRelation) {
957
+ console.debug(`🔍 [findCorrespondingJunctionTable] No relation found with key '${relation.inverseRelationName}' on target collection`);
958
+ return null;
959
+ }
960
+
961
+ console.debug(`🔍 [findCorrespondingJunctionTable] Found relation:`, {
962
+ relationName: correspondingRelation.relationName,
963
+ cardinality: correspondingRelation.cardinality,
964
+ direction: correspondingRelation.direction,
965
+ hasThrough: !!correspondingRelation.through
966
+ });
967
+
968
+ // Verify it's an owning many-to-many relation with junction table
969
+ if (correspondingRelation.cardinality !== "many" ||
970
+ correspondingRelation.direction !== "owning" ||
971
+ !correspondingRelation.through) {
972
+ console.debug(`🔍 [findCorrespondingJunctionTable] Relation is not an owning many-to-many with junction table`);
973
+ return null;
974
+ }
975
+
976
+ console.debug(`🔍 [findCorrespondingJunctionTable] Found matching owning relation with junction table!`);
977
+
978
+ // For inverse relation, we need to swap source and target columns
979
+ const through = correspondingRelation.through;
980
+ const result = {
981
+ table: through.table,
982
+ sourceColumn: through.targetColumn, // Swapped for inverse relation
983
+ targetColumn: through.sourceColumn // Swapped for inverse relation
984
+ };
985
+
986
+ console.debug(`🔍 [findCorrespondingJunctionTable] Returning junction info:`, result);
987
+ return result;
988
+ } catch (error) {
989
+ console.error(`🔍 [findCorrespondingJunctionTable] Error finding corresponding junction table for relation '${relation.relationName}':`, error);
990
+ return null;
991
+ }
992
+ }
993
+ }
994
+
995
+ /**
996
+ * Alias for DrizzleConditionBuilder for consistent naming with other database implementations.
997
+ * This allows code to use PostgresConditionBuilder alongside future MongoConditionBuilder, etc.
998
+ */
999
+ export const PostgresConditionBuilder = DrizzleConditionBuilder;