@warlock.js/cascade 4.0.92 → 4.0.93

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 (269) hide show
  1. package/cjs/contracts/database-driver.contract.d.ts +118 -0
  2. package/cjs/contracts/database-driver.contract.d.ts.map +1 -1
  3. package/cjs/contracts/migration-driver.contract.d.ts +14 -0
  4. package/cjs/contracts/migration-driver.contract.d.ts.map +1 -1
  5. package/cjs/contracts/query-builder.contract.d.ts +410 -1
  6. package/cjs/contracts/query-builder.contract.d.ts.map +1 -1
  7. package/cjs/data-source/data-source-registry.d.ts +4 -0
  8. package/cjs/data-source/data-source-registry.d.ts.map +1 -1
  9. package/cjs/data-source/data-source-registry.js +7 -0
  10. package/cjs/data-source/data-source-registry.js.map +1 -1
  11. package/cjs/drivers/mongodb/mongodb-blueprint.d.ts.map +1 -0
  12. package/cjs/drivers/mongodb/mongodb-blueprint.js.map +1 -0
  13. package/{esm/drivers/mongo → cjs/drivers/mongodb}/mongodb-driver.d.ts +49 -0
  14. package/cjs/drivers/mongodb/mongodb-driver.d.ts.map +1 -0
  15. package/cjs/drivers/{mongo → mongodb}/mongodb-driver.js +125 -8
  16. package/cjs/drivers/mongodb/mongodb-driver.js.map +1 -0
  17. package/cjs/drivers/{mongo/mongo-id-generator.d.ts → mongodb/mongodb-id-generator.d.ts} +1 -1
  18. package/cjs/drivers/mongodb/mongodb-id-generator.d.ts.map +1 -0
  19. package/cjs/drivers/{mongo/mongo-id-generator.js → mongodb/mongodb-id-generator.js} +1 -1
  20. package/cjs/drivers/mongodb/mongodb-id-generator.js.map +1 -0
  21. package/cjs/drivers/{mongo/mongo-migration-driver.d.ts → mongodb/mongodb-migration-driver.d.ts} +10 -1
  22. package/cjs/drivers/mongodb/mongodb-migration-driver.d.ts.map +1 -0
  23. package/cjs/drivers/{mongo/mongo-migration-driver.js → mongodb/mongodb-migration-driver.js} +18 -1
  24. package/cjs/drivers/mongodb/mongodb-migration-driver.js.map +1 -0
  25. package/cjs/drivers/{mongo/mongo-query-builder.d.ts → mongodb/mongodb-query-builder.d.ts} +172 -4
  26. package/cjs/drivers/mongodb/mongodb-query-builder.d.ts.map +1 -0
  27. package/cjs/drivers/{mongo/mongo-query-builder.js → mongodb/mongodb-query-builder.js} +220 -14
  28. package/cjs/drivers/mongodb/mongodb-query-builder.js.map +1 -0
  29. package/{esm/drivers/mongo/mongo-query-operations.d.ts → cjs/drivers/mongodb/mongodb-query-operations.d.ts} +16 -16
  30. package/cjs/drivers/mongodb/mongodb-query-operations.d.ts.map +1 -0
  31. package/cjs/drivers/{mongo/mongo-query-operations.js → mongodb/mongodb-query-operations.js} +22 -22
  32. package/cjs/drivers/mongodb/mongodb-query-operations.js.map +1 -0
  33. package/cjs/drivers/{mongo/mongo-query-parser.d.ts → mongodb/mongodb-query-parser.d.ts} +2 -2
  34. package/cjs/drivers/mongodb/mongodb-query-parser.d.ts.map +1 -0
  35. package/cjs/drivers/{mongo/mongo-query-parser.js → mongodb/mongodb-query-parser.js} +87 -87
  36. package/cjs/drivers/mongodb/mongodb-query-parser.js.map +1 -0
  37. package/cjs/drivers/{mongo/mongo-sync-adapter.d.ts → mongodb/mongodb-sync-adapter.d.ts} +2 -2
  38. package/cjs/drivers/mongodb/mongodb-sync-adapter.d.ts.map +1 -0
  39. package/cjs/drivers/{mongo/mongo-sync-adapter.js → mongodb/mongodb-sync-adapter.js} +2 -2
  40. package/cjs/drivers/mongodb/mongodb-sync-adapter.js.map +1 -0
  41. package/{esm/drivers/mongo → cjs/drivers/mongodb}/types.d.ts +2 -2
  42. package/cjs/drivers/mongodb/types.d.ts.map +1 -0
  43. package/cjs/drivers/postgres/index.d.ts +16 -0
  44. package/cjs/drivers/postgres/index.d.ts.map +1 -0
  45. package/cjs/drivers/postgres/postgres-blueprint.d.ts +64 -0
  46. package/cjs/drivers/postgres/postgres-blueprint.d.ts.map +1 -0
  47. package/cjs/drivers/postgres/postgres-blueprint.js +121 -0
  48. package/cjs/drivers/postgres/postgres-blueprint.js.map +1 -0
  49. package/cjs/drivers/postgres/postgres-dialect.d.ts +135 -0
  50. package/cjs/drivers/postgres/postgres-dialect.d.ts.map +1 -0
  51. package/cjs/drivers/postgres/postgres-dialect.js +245 -0
  52. package/cjs/drivers/postgres/postgres-dialect.js.map +1 -0
  53. package/cjs/drivers/postgres/postgres-driver.d.ts +360 -0
  54. package/cjs/drivers/postgres/postgres-driver.d.ts.map +1 -0
  55. package/cjs/drivers/postgres/postgres-driver.js +763 -0
  56. package/cjs/drivers/postgres/postgres-driver.js.map +1 -0
  57. package/cjs/drivers/postgres/postgres-migration-driver.d.ts +297 -0
  58. package/cjs/drivers/postgres/postgres-migration-driver.d.ts.map +1 -0
  59. package/cjs/drivers/postgres/postgres-migration-driver.js +578 -0
  60. package/cjs/drivers/postgres/postgres-migration-driver.js.map +1 -0
  61. package/cjs/drivers/postgres/postgres-query-builder.d.ts +824 -0
  62. package/cjs/drivers/postgres/postgres-query-builder.d.ts.map +1 -0
  63. package/cjs/drivers/postgres/postgres-query-builder.js +1800 -0
  64. package/cjs/drivers/postgres/postgres-query-builder.js.map +1 -0
  65. package/cjs/drivers/postgres/postgres-query-parser.d.ts +308 -0
  66. package/cjs/drivers/postgres/postgres-query-parser.d.ts.map +1 -0
  67. package/cjs/drivers/postgres/postgres-query-parser.js +706 -0
  68. package/cjs/drivers/postgres/postgres-query-parser.js.map +1 -0
  69. package/cjs/drivers/postgres/postgres-sync-adapter.d.ts +83 -0
  70. package/cjs/drivers/postgres/postgres-sync-adapter.d.ts.map +1 -0
  71. package/cjs/drivers/postgres/postgres-sync-adapter.js +197 -0
  72. package/cjs/drivers/postgres/postgres-sync-adapter.js.map +1 -0
  73. package/cjs/drivers/postgres/types.d.ts +142 -0
  74. package/cjs/drivers/postgres/types.d.ts.map +1 -0
  75. package/cjs/drivers/sql/index.d.ts +10 -0
  76. package/cjs/drivers/sql/index.d.ts.map +1 -0
  77. package/cjs/drivers/sql/sql-dialect.contract.d.ts +203 -0
  78. package/cjs/drivers/sql/sql-dialect.contract.d.ts.map +1 -0
  79. package/cjs/drivers/sql/sql-types.d.ts +202 -0
  80. package/cjs/drivers/sql/sql-types.d.ts.map +1 -0
  81. package/cjs/index.d.ts +9 -6
  82. package/cjs/index.d.ts.map +1 -1
  83. package/cjs/index.js +1 -1
  84. package/cjs/migration/migration-runner.d.ts.map +1 -1
  85. package/cjs/migration/migration-runner.js +3 -0
  86. package/cjs/migration/migration-runner.js.map +1 -1
  87. package/cjs/model/model.d.ts +236 -1
  88. package/cjs/model/model.d.ts.map +1 -1
  89. package/cjs/model/model.js +203 -4
  90. package/cjs/model/model.js.map +1 -1
  91. package/cjs/relations/helpers.d.ts +156 -0
  92. package/cjs/relations/helpers.d.ts.map +1 -0
  93. package/cjs/relations/helpers.js +197 -0
  94. package/cjs/relations/helpers.js.map +1 -0
  95. package/cjs/relations/index.d.ts +33 -0
  96. package/cjs/relations/index.d.ts.map +1 -0
  97. package/cjs/relations/pivot-operations.d.ts +160 -0
  98. package/cjs/relations/pivot-operations.d.ts.map +1 -0
  99. package/cjs/relations/pivot-operations.js +293 -0
  100. package/cjs/relations/pivot-operations.js.map +1 -0
  101. package/cjs/relations/relation-loader.d.ts +194 -0
  102. package/cjs/relations/relation-loader.d.ts.map +1 -0
  103. package/cjs/relations/relation-loader.js +466 -0
  104. package/cjs/relations/relation-loader.js.map +1 -0
  105. package/cjs/relations/types.d.ts +280 -0
  106. package/cjs/relations/types.d.ts.map +1 -0
  107. package/cjs/sync/model-sync-operation.js +1 -1
  108. package/cjs/sync/model-sync-operation.js.map +1 -1
  109. package/cjs/utils/connect-to-database.d.ts.map +1 -1
  110. package/cjs/utils/connect-to-database.js +15 -3
  111. package/cjs/utils/connect-to-database.js.map +1 -1
  112. package/cjs/utils/define-model.d.ts +51 -29
  113. package/cjs/utils/define-model.d.ts.map +1 -1
  114. package/cjs/validation/rules/database-model-rule.js +1 -1
  115. package/cjs/validation/rules/database-model-rule.js.map +1 -1
  116. package/esm/contracts/database-driver.contract.d.ts +118 -0
  117. package/esm/contracts/database-driver.contract.d.ts.map +1 -1
  118. package/esm/contracts/migration-driver.contract.d.ts +14 -0
  119. package/esm/contracts/migration-driver.contract.d.ts.map +1 -1
  120. package/esm/contracts/query-builder.contract.d.ts +410 -1
  121. package/esm/contracts/query-builder.contract.d.ts.map +1 -1
  122. package/esm/data-source/data-source-registry.d.ts +4 -0
  123. package/esm/data-source/data-source-registry.d.ts.map +1 -1
  124. package/esm/data-source/data-source-registry.js +7 -0
  125. package/esm/data-source/data-source-registry.js.map +1 -1
  126. package/esm/drivers/mongodb/mongodb-blueprint.d.ts.map +1 -0
  127. package/esm/drivers/mongodb/mongodb-blueprint.js.map +1 -0
  128. package/{cjs/drivers/mongo → esm/drivers/mongodb}/mongodb-driver.d.ts +49 -0
  129. package/esm/drivers/mongodb/mongodb-driver.d.ts.map +1 -0
  130. package/esm/drivers/{mongo → mongodb}/mongodb-driver.js +121 -4
  131. package/esm/drivers/mongodb/mongodb-driver.js.map +1 -0
  132. package/esm/drivers/{mongo/mongo-id-generator.d.ts → mongodb/mongodb-id-generator.d.ts} +1 -1
  133. package/esm/drivers/mongodb/mongodb-id-generator.d.ts.map +1 -0
  134. package/esm/drivers/{mongo/mongo-id-generator.js → mongodb/mongodb-id-generator.js} +1 -1
  135. package/esm/drivers/mongodb/mongodb-id-generator.js.map +1 -0
  136. package/esm/drivers/{mongo/mongo-migration-driver.d.ts → mongodb/mongodb-migration-driver.d.ts} +10 -1
  137. package/esm/drivers/mongodb/mongodb-migration-driver.d.ts.map +1 -0
  138. package/esm/drivers/{mongo/mongo-migration-driver.js → mongodb/mongodb-migration-driver.js} +18 -1
  139. package/esm/drivers/mongodb/mongodb-migration-driver.js.map +1 -0
  140. package/esm/drivers/{mongo/mongo-query-builder.d.ts → mongodb/mongodb-query-builder.d.ts} +172 -4
  141. package/esm/drivers/mongodb/mongodb-query-builder.d.ts.map +1 -0
  142. package/esm/drivers/{mongo/mongo-query-builder.js → mongodb/mongodb-query-builder.js} +218 -12
  143. package/esm/drivers/mongodb/mongodb-query-builder.js.map +1 -0
  144. package/{cjs/drivers/mongo/mongo-query-operations.d.ts → esm/drivers/mongodb/mongodb-query-operations.d.ts} +16 -16
  145. package/esm/drivers/mongodb/mongodb-query-operations.d.ts.map +1 -0
  146. package/esm/drivers/{mongo/mongo-query-operations.js → mongodb/mongodb-query-operations.js} +22 -22
  147. package/esm/drivers/mongodb/mongodb-query-operations.js.map +1 -0
  148. package/esm/drivers/{mongo/mongo-query-parser.d.ts → mongodb/mongodb-query-parser.d.ts} +2 -2
  149. package/esm/drivers/mongodb/mongodb-query-parser.d.ts.map +1 -0
  150. package/esm/drivers/{mongo/mongo-query-parser.js → mongodb/mongodb-query-parser.js} +87 -87
  151. package/esm/drivers/mongodb/mongodb-query-parser.js.map +1 -0
  152. package/esm/drivers/{mongo/mongo-sync-adapter.d.ts → mongodb/mongodb-sync-adapter.d.ts} +2 -2
  153. package/esm/drivers/mongodb/mongodb-sync-adapter.d.ts.map +1 -0
  154. package/esm/drivers/{mongo/mongo-sync-adapter.js → mongodb/mongodb-sync-adapter.js} +2 -2
  155. package/esm/drivers/mongodb/mongodb-sync-adapter.js.map +1 -0
  156. package/{cjs/drivers/mongo → esm/drivers/mongodb}/types.d.ts +2 -2
  157. package/esm/drivers/mongodb/types.d.ts.map +1 -0
  158. package/esm/drivers/postgres/index.d.ts +16 -0
  159. package/esm/drivers/postgres/index.d.ts.map +1 -0
  160. package/esm/drivers/postgres/postgres-blueprint.d.ts +64 -0
  161. package/esm/drivers/postgres/postgres-blueprint.d.ts.map +1 -0
  162. package/esm/drivers/postgres/postgres-blueprint.js +121 -0
  163. package/esm/drivers/postgres/postgres-blueprint.js.map +1 -0
  164. package/esm/drivers/postgres/postgres-dialect.d.ts +135 -0
  165. package/esm/drivers/postgres/postgres-dialect.d.ts.map +1 -0
  166. package/esm/drivers/postgres/postgres-dialect.js +245 -0
  167. package/esm/drivers/postgres/postgres-dialect.js.map +1 -0
  168. package/esm/drivers/postgres/postgres-driver.d.ts +360 -0
  169. package/esm/drivers/postgres/postgres-driver.d.ts.map +1 -0
  170. package/esm/drivers/postgres/postgres-driver.js +763 -0
  171. package/esm/drivers/postgres/postgres-driver.js.map +1 -0
  172. package/esm/drivers/postgres/postgres-migration-driver.d.ts +297 -0
  173. package/esm/drivers/postgres/postgres-migration-driver.d.ts.map +1 -0
  174. package/esm/drivers/postgres/postgres-migration-driver.js +578 -0
  175. package/esm/drivers/postgres/postgres-migration-driver.js.map +1 -0
  176. package/esm/drivers/postgres/postgres-query-builder.d.ts +824 -0
  177. package/esm/drivers/postgres/postgres-query-builder.d.ts.map +1 -0
  178. package/esm/drivers/postgres/postgres-query-builder.js +1800 -0
  179. package/esm/drivers/postgres/postgres-query-builder.js.map +1 -0
  180. package/esm/drivers/postgres/postgres-query-parser.d.ts +308 -0
  181. package/esm/drivers/postgres/postgres-query-parser.d.ts.map +1 -0
  182. package/esm/drivers/postgres/postgres-query-parser.js +706 -0
  183. package/esm/drivers/postgres/postgres-query-parser.js.map +1 -0
  184. package/esm/drivers/postgres/postgres-sync-adapter.d.ts +83 -0
  185. package/esm/drivers/postgres/postgres-sync-adapter.d.ts.map +1 -0
  186. package/esm/drivers/postgres/postgres-sync-adapter.js +197 -0
  187. package/esm/drivers/postgres/postgres-sync-adapter.js.map +1 -0
  188. package/esm/drivers/postgres/types.d.ts +142 -0
  189. package/esm/drivers/postgres/types.d.ts.map +1 -0
  190. package/esm/drivers/sql/index.d.ts +10 -0
  191. package/esm/drivers/sql/index.d.ts.map +1 -0
  192. package/esm/drivers/sql/sql-dialect.contract.d.ts +203 -0
  193. package/esm/drivers/sql/sql-dialect.contract.d.ts.map +1 -0
  194. package/esm/drivers/sql/sql-types.d.ts +202 -0
  195. package/esm/drivers/sql/sql-types.d.ts.map +1 -0
  196. package/esm/index.d.ts +9 -6
  197. package/esm/index.d.ts.map +1 -1
  198. package/esm/index.js +1 -1
  199. package/esm/migration/migration-runner.d.ts.map +1 -1
  200. package/esm/migration/migration-runner.js +3 -0
  201. package/esm/migration/migration-runner.js.map +1 -1
  202. package/esm/model/model.d.ts +236 -1
  203. package/esm/model/model.d.ts.map +1 -1
  204. package/esm/model/model.js +203 -4
  205. package/esm/model/model.js.map +1 -1
  206. package/esm/relations/helpers.d.ts +156 -0
  207. package/esm/relations/helpers.d.ts.map +1 -0
  208. package/esm/relations/helpers.js +197 -0
  209. package/esm/relations/helpers.js.map +1 -0
  210. package/esm/relations/index.d.ts +33 -0
  211. package/esm/relations/index.d.ts.map +1 -0
  212. package/esm/relations/pivot-operations.d.ts +160 -0
  213. package/esm/relations/pivot-operations.d.ts.map +1 -0
  214. package/esm/relations/pivot-operations.js +293 -0
  215. package/esm/relations/pivot-operations.js.map +1 -0
  216. package/esm/relations/relation-loader.d.ts +194 -0
  217. package/esm/relations/relation-loader.d.ts.map +1 -0
  218. package/esm/relations/relation-loader.js +466 -0
  219. package/esm/relations/relation-loader.js.map +1 -0
  220. package/esm/relations/types.d.ts +280 -0
  221. package/esm/relations/types.d.ts.map +1 -0
  222. package/esm/sync/model-sync-operation.js +1 -1
  223. package/esm/sync/model-sync-operation.js.map +1 -1
  224. package/esm/utils/connect-to-database.d.ts.map +1 -1
  225. package/esm/utils/connect-to-database.js +15 -3
  226. package/esm/utils/connect-to-database.js.map +1 -1
  227. package/esm/utils/define-model.d.ts +51 -29
  228. package/esm/utils/define-model.d.ts.map +1 -1
  229. package/esm/validation/rules/database-model-rule.js +1 -1
  230. package/esm/validation/rules/database-model-rule.js.map +1 -1
  231. package/package.json +4 -4
  232. package/cjs/drivers/mongo/mongo-id-generator.d.ts.map +0 -1
  233. package/cjs/drivers/mongo/mongo-id-generator.js.map +0 -1
  234. package/cjs/drivers/mongo/mongo-migration-driver.d.ts.map +0 -1
  235. package/cjs/drivers/mongo/mongo-migration-driver.js.map +0 -1
  236. package/cjs/drivers/mongo/mongo-query-builder.d.ts.map +0 -1
  237. package/cjs/drivers/mongo/mongo-query-builder.js.map +0 -1
  238. package/cjs/drivers/mongo/mongo-query-operations.d.ts.map +0 -1
  239. package/cjs/drivers/mongo/mongo-query-operations.js.map +0 -1
  240. package/cjs/drivers/mongo/mongo-query-parser.d.ts.map +0 -1
  241. package/cjs/drivers/mongo/mongo-query-parser.js.map +0 -1
  242. package/cjs/drivers/mongo/mongo-sync-adapter.d.ts.map +0 -1
  243. package/cjs/drivers/mongo/mongo-sync-adapter.js.map +0 -1
  244. package/cjs/drivers/mongo/mongodb-blueprint.d.ts.map +0 -1
  245. package/cjs/drivers/mongo/mongodb-blueprint.js.map +0 -1
  246. package/cjs/drivers/mongo/mongodb-driver.d.ts.map +0 -1
  247. package/cjs/drivers/mongo/mongodb-driver.js.map +0 -1
  248. package/cjs/drivers/mongo/types.d.ts.map +0 -1
  249. package/esm/drivers/mongo/mongo-id-generator.d.ts.map +0 -1
  250. package/esm/drivers/mongo/mongo-id-generator.js.map +0 -1
  251. package/esm/drivers/mongo/mongo-migration-driver.d.ts.map +0 -1
  252. package/esm/drivers/mongo/mongo-migration-driver.js.map +0 -1
  253. package/esm/drivers/mongo/mongo-query-builder.d.ts.map +0 -1
  254. package/esm/drivers/mongo/mongo-query-builder.js.map +0 -1
  255. package/esm/drivers/mongo/mongo-query-operations.d.ts.map +0 -1
  256. package/esm/drivers/mongo/mongo-query-operations.js.map +0 -1
  257. package/esm/drivers/mongo/mongo-query-parser.d.ts.map +0 -1
  258. package/esm/drivers/mongo/mongo-query-parser.js.map +0 -1
  259. package/esm/drivers/mongo/mongo-sync-adapter.d.ts.map +0 -1
  260. package/esm/drivers/mongo/mongo-sync-adapter.js.map +0 -1
  261. package/esm/drivers/mongo/mongodb-blueprint.d.ts.map +0 -1
  262. package/esm/drivers/mongo/mongodb-blueprint.js.map +0 -1
  263. package/esm/drivers/mongo/mongodb-driver.d.ts.map +0 -1
  264. package/esm/drivers/mongo/mongodb-driver.js.map +0 -1
  265. package/esm/drivers/mongo/types.d.ts.map +0 -1
  266. /package/cjs/drivers/{mongo → mongodb}/mongodb-blueprint.d.ts +0 -0
  267. /package/cjs/drivers/{mongo → mongodb}/mongodb-blueprint.js +0 -0
  268. /package/esm/drivers/{mongo → mongodb}/mongodb-blueprint.d.ts +0 -0
  269. /package/esm/drivers/{mongo → mongodb}/mongodb-blueprint.js +0 -0
@@ -0,0 +1,1800 @@
1
+ import {dataSourceRegistry}from'../../data-source/data-source-registry.js';import {getModelFromRegistry}from'../../model/register-model.js';import {PostgresQueryParser}from'./postgres-query-parser.js';/**
2
+ * PostgreSQL Query Builder
3
+ *
4
+ * Implements the QueryBuilderContract for PostgreSQL databases.
5
+ * Provides a fluent API for building SQL queries with proper
6
+ * parameter handling and type safety.
7
+ *
8
+ * @module cascade/drivers/postgres
9
+ */
10
+ /**
11
+ * PostgreSQL Query Builder.
12
+ *
13
+ * Implements the Cascade QueryBuilderContract for PostgreSQL.
14
+ * Collects query operations and delegates to PostgresQueryParser
15
+ * for SQL generation.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const users = await queryBuilder('users')
20
+ * .select(['id', 'name', 'email'])
21
+ * .where('status', 'active')
22
+ * .where('age', '>', 18)
23
+ * .orderBy('createdAt', 'desc')
24
+ * .limit(10)
25
+ * .get();
26
+ * ```
27
+ */
28
+ class PostgresQueryBuilder {
29
+ table;
30
+ /**
31
+ * Collected operations to be parsed into SQL.
32
+ */
33
+ operations = [];
34
+ /**
35
+ * Data source instance.
36
+ */
37
+ dataSource;
38
+ /**
39
+ * Hydrate callback for transforming results.
40
+ */
41
+ hydrateCallback;
42
+ /**
43
+ * Callback invoked before query execution.
44
+ */
45
+ fetchingCallback;
46
+ /**
47
+ * Callback invoked after records fetched but before hydration.
48
+ */
49
+ hydratingCallback;
50
+ /**
51
+ * Callback invoked after records fetched and hydrated.
52
+ */
53
+ fetchedCallback;
54
+ /**
55
+ * Pending global scopes.
56
+ */
57
+ pendingGlobalScopes;
58
+ /**
59
+ * Available local scopes.
60
+ */
61
+ availableLocalScopes;
62
+ /**
63
+ * Disabled global scope names.
64
+ */
65
+ disabledGlobalScopes = new Set();
66
+ /**
67
+ * Whether scopes have been applied.
68
+ */
69
+ scopesApplied = false;
70
+ /**
71
+ * Create a new query builder.
72
+ *
73
+ * @param table - Target table name
74
+ * @param dataSource - Optional data source (uses default if not provided)
75
+ */
76
+ constructor(table, dataSource) {
77
+ this.table = table;
78
+ this.dataSource = dataSource ?? dataSourceRegistry.get();
79
+ }
80
+ /**
81
+ * Get the PostgreSQL driver instance.
82
+ */
83
+ get driver() {
84
+ return this.dataSource.driver;
85
+ }
86
+ /**
87
+ * Add an operation to the operations list.
88
+ *
89
+ * @param type - Operation type
90
+ * @param data - Operation data
91
+ */
92
+ addOperation(type, data) {
93
+ this.operations.push({ type, data });
94
+ }
95
+ /**
96
+ * Clone this query builder with all current operations.
97
+ *
98
+ * @returns New query builder instance
99
+ */
100
+ clone() {
101
+ const cloned = new PostgresQueryBuilder(this.table, this.dataSource);
102
+ cloned.operations = [...this.operations];
103
+ cloned.hydrateCallback = this.hydrateCallback;
104
+ cloned.pendingGlobalScopes = this.pendingGlobalScopes;
105
+ cloned.availableLocalScopes = this.availableLocalScopes;
106
+ cloned.disabledGlobalScopes = new Set(this.disabledGlobalScopes);
107
+ cloned.scopesApplied = this.scopesApplied;
108
+ return cloned;
109
+ }
110
+ // ============================================================================
111
+ // HYDRATION
112
+ // ============================================================================
113
+ /**
114
+ * Set a hydration callback to transform each result row.
115
+ *
116
+ * @param callback - Transform function
117
+ * @returns This builder for chaining
118
+ */
119
+ hydrate(callback) {
120
+ this.hydrateCallback = callback;
121
+ return this;
122
+ }
123
+ /**
124
+ * Register callback invoked before query execution.
125
+ *
126
+ * @param callback - Callback function
127
+ * @returns Unsubscribe function
128
+ */
129
+ onFetching(callback) {
130
+ this.fetchingCallback = callback;
131
+ return () => {
132
+ this.fetchingCallback = undefined;
133
+ };
134
+ }
135
+ /**
136
+ * Register callback invoked after fetch but before hydration.
137
+ *
138
+ * @param callback - Callback function
139
+ * @returns Unsubscribe function
140
+ */
141
+ onHydrating(callback) {
142
+ this.hydratingCallback = callback;
143
+ return () => {
144
+ this.hydratingCallback = undefined;
145
+ };
146
+ }
147
+ /**
148
+ * Register callback invoked after fetch and hydration.
149
+ *
150
+ * @param callback - Callback function
151
+ * @returns Unsubscribe function
152
+ */
153
+ onFetched(callback) {
154
+ this.fetchedCallback = callback;
155
+ return () => {
156
+ this.fetchedCallback = undefined;
157
+ };
158
+ }
159
+ // ============================================================================
160
+ // SCOPES
161
+ // ============================================================================
162
+ /**
163
+ * Disable one or more global scopes for this query.
164
+ *
165
+ * @param scopeNames - Scope names to disable
166
+ * @returns This builder for chaining
167
+ */
168
+ withoutGlobalScope(...scopeNames) {
169
+ scopeNames.forEach((name) => this.disabledGlobalScopes.add(name));
170
+ return this;
171
+ }
172
+ /**
173
+ * Disable all global scopes for this query.
174
+ *
175
+ * @returns This builder for chaining
176
+ */
177
+ withoutGlobalScopes() {
178
+ if (this.pendingGlobalScopes) {
179
+ this.pendingGlobalScopes.forEach((_, name) => {
180
+ this.disabledGlobalScopes.add(name);
181
+ });
182
+ }
183
+ return this;
184
+ }
185
+ /**
186
+ * Apply a local scope to this query.
187
+ *
188
+ * @param scopeName - Name of the local scope
189
+ * @returns This builder for chaining
190
+ */
191
+ scope(scopeName) {
192
+ if (!this.availableLocalScopes) {
193
+ throw new Error("No local scopes available");
194
+ }
195
+ const scopeCallback = this.availableLocalScopes.get(scopeName);
196
+ if (!scopeCallback) {
197
+ throw new Error(`Local scope "${scopeName}" not found`);
198
+ }
199
+ scopeCallback(this);
200
+ return this;
201
+ }
202
+ /**
203
+ * Apply pending global scopes before query execution.
204
+ */
205
+ applyPendingScopes() {
206
+ if (!this.pendingGlobalScopes || this.scopesApplied) {
207
+ return;
208
+ }
209
+ const beforeOps = [];
210
+ const afterOps = [];
211
+ for (const [name, { callback, timing }] of this.pendingGlobalScopes) {
212
+ if (this.disabledGlobalScopes.has(name)) {
213
+ continue;
214
+ }
215
+ const tempBuilder = new PostgresQueryBuilder(this.table, this.dataSource);
216
+ callback(tempBuilder);
217
+ if (timing === "before") {
218
+ beforeOps.push(...tempBuilder.operations);
219
+ }
220
+ else {
221
+ afterOps.push(...tempBuilder.operations);
222
+ }
223
+ }
224
+ this.operations = [...beforeOps, ...this.operations, ...afterOps];
225
+ this.scopesApplied = true;
226
+ }
227
+ where(...args) {
228
+ if (args.length === 1) {
229
+ if (typeof args[0] === "function") {
230
+ // Callback for nested conditions
231
+ const tempBuilder = new PostgresQueryBuilder(this.table, this.dataSource);
232
+ args[0](tempBuilder);
233
+ // Wrap nested operations
234
+ this.addOperation("where", { nested: tempBuilder.operations });
235
+ }
236
+ else if (typeof args[0] === "object") {
237
+ // Object conditions
238
+ for (const [key, value] of Object.entries(args[0])) {
239
+ this.addOperation("where", { field: key, operator: "=", value });
240
+ }
241
+ }
242
+ }
243
+ else if (args.length === 2) {
244
+ this.addOperation("where", { field: args[0], operator: "=", value: args[1] });
245
+ }
246
+ else if (args.length === 3) {
247
+ this.addOperation("where", { field: args[0], operator: args[1], value: args[2] });
248
+ }
249
+ return this;
250
+ }
251
+ orWhere(...args) {
252
+ if (args.length === 2) {
253
+ this.addOperation("orWhere", { field: args[0], operator: "=", value: args[1] });
254
+ }
255
+ else if (args.length === 3) {
256
+ this.addOperation("orWhere", { field: args[0], operator: args[1], value: args[2] });
257
+ }
258
+ return this;
259
+ }
260
+ /**
261
+ * Add a raw WHERE clause.
262
+ */
263
+ whereRaw(expression, bindings) {
264
+ this.addOperation("whereRaw", { expression, bindings });
265
+ return this;
266
+ }
267
+ /**
268
+ * Add a raw OR WHERE clause.
269
+ */
270
+ orWhereRaw(expression, bindings) {
271
+ this.addOperation("orWhereRaw", { expression, bindings });
272
+ return this;
273
+ }
274
+ // ============================================================================
275
+ // WHERE CLAUSES - COLUMN COMPARISONS
276
+ // ============================================================================
277
+ /**
278
+ * Compare two columns.
279
+ */
280
+ whereColumn(first, operator, second) {
281
+ this.addOperation("whereColumn", { first, operator, second });
282
+ return this;
283
+ }
284
+ /**
285
+ * Compare two columns with OR.
286
+ */
287
+ orWhereColumn(first, operator, second) {
288
+ this.addOperation("orWhereColumn", { first, operator, second });
289
+ return this;
290
+ }
291
+ /**
292
+ * Compare multiple column pairs.
293
+ */
294
+ whereColumns(comparisons) {
295
+ for (const [left, operator, right] of comparisons) {
296
+ this.whereColumn(left, operator, right);
297
+ }
298
+ return this;
299
+ }
300
+ /**
301
+ * Check if field is between two columns.
302
+ */
303
+ whereBetweenColumns(field, lowerColumn, upperColumn) {
304
+ this.addOperation("whereBetween", { field, lowerColumn, upperColumn, useColumns: true });
305
+ return this;
306
+ }
307
+ // ============================================================================
308
+ // WHERE CLAUSES - DATE OPERATIONS
309
+ // ============================================================================
310
+ /**
311
+ * Filter by date (ignoring time).
312
+ */
313
+ whereDate(field, value) {
314
+ this.addOperation("whereDate", { field, value });
315
+ return this;
316
+ }
317
+ /**
318
+ * Alias for whereDate.
319
+ */
320
+ whereDateEquals(field, value) {
321
+ return this.whereDate(field, value);
322
+ }
323
+ /**
324
+ * Filter for dates before a value.
325
+ */
326
+ whereDateBefore(field, value) {
327
+ this.addOperation("whereDateBefore", { field, value });
328
+ return this;
329
+ }
330
+ /**
331
+ * Filter for dates after a value.
332
+ */
333
+ whereDateAfter(field, value) {
334
+ this.addOperation("whereDateAfter", { field, value });
335
+ return this;
336
+ }
337
+ /**
338
+ * Filter by time.
339
+ */
340
+ whereTime(field, value) {
341
+ this.addOperation("where", { field, operator: "=", value, timeOnly: true });
342
+ return this;
343
+ }
344
+ /**
345
+ * Filter by day of month.
346
+ */
347
+ whereDay(field, value) {
348
+ this.addOperation("whereRaw", {
349
+ expression: `EXTRACT(DAY FROM ${field}) = ?`,
350
+ bindings: [value],
351
+ });
352
+ return this;
353
+ }
354
+ /**
355
+ * Filter by month.
356
+ */
357
+ whereMonth(field, value) {
358
+ this.addOperation("whereRaw", {
359
+ expression: `EXTRACT(MONTH FROM ${field}) = ?`,
360
+ bindings: [value],
361
+ });
362
+ return this;
363
+ }
364
+ /**
365
+ * Filter by year.
366
+ */
367
+ whereYear(field, value) {
368
+ this.addOperation("whereRaw", {
369
+ expression: `EXTRACT(YEAR FROM ${field}) = ?`,
370
+ bindings: [value],
371
+ });
372
+ return this;
373
+ }
374
+ // ============================================================================
375
+ // WHERE CLAUSES - JSON OPERATIONS
376
+ // ============================================================================
377
+ /**
378
+ * Check if JSON contains value.
379
+ */
380
+ whereJsonContains(path, value) {
381
+ this.addOperation("whereJsonContains", { path, value });
382
+ return this;
383
+ }
384
+ /**
385
+ * Check if JSON doesn't contain value.
386
+ */
387
+ whereJsonDoesntContain(path, value) {
388
+ this.addOperation("whereJsonDoesntContain", { path, value });
389
+ return this;
390
+ }
391
+ /**
392
+ * Check if JSON contains key.
393
+ */
394
+ whereJsonContainsKey(path) {
395
+ this.addOperation("whereRaw", {
396
+ expression: `${path} IS NOT NULL`,
397
+ bindings: [],
398
+ });
399
+ return this;
400
+ }
401
+ /**
402
+ * Check JSON array/string length.
403
+ */
404
+ whereJsonLength(path, operator, value) {
405
+ this.addOperation("whereRaw", {
406
+ expression: `jsonb_array_length(${path}) ${operator} ?`,
407
+ bindings: [value],
408
+ });
409
+ return this;
410
+ }
411
+ /**
412
+ * Check if JSON is array.
413
+ */
414
+ whereJsonIsArray(path) {
415
+ this.addOperation("whereRaw", {
416
+ expression: `jsonb_typeof(${path}) = 'array'`,
417
+ bindings: [],
418
+ });
419
+ return this;
420
+ }
421
+ /**
422
+ * Check if JSON is object.
423
+ */
424
+ whereJsonIsObject(path) {
425
+ this.addOperation("whereRaw", {
426
+ expression: `jsonb_typeof(${path}) = 'object'`,
427
+ bindings: [],
428
+ });
429
+ return this;
430
+ }
431
+ /**
432
+ * Check array length.
433
+ */
434
+ whereArrayLength(field, operator, value) {
435
+ this.addOperation("whereRaw", {
436
+ expression: `array_length(${field}, 1) ${operator} ?`,
437
+ bindings: [value],
438
+ });
439
+ return this;
440
+ }
441
+ // ============================================================================
442
+ // WHERE CLAUSES - CONVENIENCE METHODS
443
+ // ============================================================================
444
+ /**
445
+ * Filter by ID.
446
+ */
447
+ whereId(value) {
448
+ return this.where("id", value);
449
+ }
450
+ /**
451
+ * Filter by multiple IDs.
452
+ */
453
+ whereIds(values) {
454
+ return this.whereIn("id", values);
455
+ }
456
+ /**
457
+ * Filter by UUID.
458
+ */
459
+ whereUuid(value) {
460
+ return this.where("uuid", value);
461
+ }
462
+ /**
463
+ * Filter by ULID.
464
+ */
465
+ whereUlid(value) {
466
+ return this.where("ulid", value);
467
+ }
468
+ /**
469
+ * Full-text search.
470
+ */
471
+ whereFullText(fields, query) {
472
+ const fieldList = Array.isArray(fields) ? fields : [fields];
473
+ this.addOperation("whereFullText", { fields: fieldList, query });
474
+ return this;
475
+ }
476
+ /**
477
+ * Full-text search with OR.
478
+ */
479
+ orWhereFullText(fields, query) {
480
+ // TODO: Handle OR full-text
481
+ return this.whereFullText(fields, query);
482
+ }
483
+ /**
484
+ * Alias for whereFullText.
485
+ */
486
+ whereSearch(field, query) {
487
+ return this.whereFullText(field, query);
488
+ }
489
+ /**
490
+ * Negate conditions.
491
+ */
492
+ whereNot(callback) {
493
+ // TODO: Implement NOT wrapper
494
+ return this;
495
+ }
496
+ /**
497
+ * Negate conditions with OR.
498
+ */
499
+ orWhereNot(callback) {
500
+ // TODO: Implement OR NOT wrapper
501
+ return this;
502
+ }
503
+ // ============================================================================
504
+ // WHERE CLAUSES - COMPARISON OPERATORS
505
+ // ============================================================================
506
+ /**
507
+ * Filter by value in array.
508
+ */
509
+ whereIn(field, values) {
510
+ this.addOperation("whereIn", { field, values });
511
+ return this;
512
+ }
513
+ /**
514
+ * Filter by value not in array.
515
+ */
516
+ whereNotIn(field, values) {
517
+ this.addOperation("whereNotIn", { field, values });
518
+ return this;
519
+ }
520
+ /**
521
+ * Filter by NULL value.
522
+ */
523
+ whereNull(field) {
524
+ this.addOperation("whereNull", { field });
525
+ return this;
526
+ }
527
+ /**
528
+ * Filter by NOT NULL value.
529
+ */
530
+ whereNotNull(field) {
531
+ this.addOperation("whereNotNull", { field });
532
+ return this;
533
+ }
534
+ /**
535
+ * Filter by range (inclusive).
536
+ */
537
+ whereBetween(field, range) {
538
+ this.addOperation("whereBetween", { field, range });
539
+ return this;
540
+ }
541
+ /**
542
+ * Filter by not in range.
543
+ */
544
+ whereNotBetween(field, range) {
545
+ this.addOperation("whereNotBetween", { field, range });
546
+ return this;
547
+ }
548
+ // ============================================================================
549
+ // WHERE CLAUSES - PATTERN MATCHING
550
+ // ============================================================================
551
+ /**
552
+ * Filter by LIKE pattern (case-insensitive).
553
+ */
554
+ whereLike(field, pattern) {
555
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
556
+ this.addOperation("whereLike", { field, pattern: patternStr });
557
+ return this;
558
+ }
559
+ /**
560
+ * Filter by NOT LIKE pattern.
561
+ */
562
+ whereNotLike(field, pattern) {
563
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
564
+ this.addOperation("whereNotLike", { field, pattern: patternStr });
565
+ return this;
566
+ }
567
+ /**
568
+ * Filter by prefix.
569
+ */
570
+ whereStartsWith(field, value) {
571
+ return this.whereLike(field, `${value}%`);
572
+ }
573
+ /**
574
+ * Filter by not starting with prefix.
575
+ */
576
+ whereNotStartsWith(field, value) {
577
+ return this.whereNotLike(field, `${value}%`);
578
+ }
579
+ /**
580
+ * Filter by suffix.
581
+ */
582
+ whereEndsWith(field, value) {
583
+ return this.whereLike(field, `%${value}`);
584
+ }
585
+ /**
586
+ * Filter by not ending with suffix.
587
+ */
588
+ whereNotEndsWith(field, value) {
589
+ return this.whereNotLike(field, `%${value}`);
590
+ }
591
+ /**
592
+ * Filter by date range.
593
+ */
594
+ whereDateBetween(field, range) {
595
+ this.addOperation("whereDateBetween", { field, range });
596
+ return this;
597
+ }
598
+ /**
599
+ * Filter by not in date range.
600
+ */
601
+ whereDateNotBetween(field, range) {
602
+ // Use NOT BETWEEN
603
+ this.addOperation("whereNotBetween", { field, range });
604
+ return this;
605
+ }
606
+ whereExists(param) {
607
+ if (typeof param === "function") {
608
+ // Subquery exists
609
+ const tempBuilder = new PostgresQueryBuilder(this.table, this.dataSource);
610
+ param(tempBuilder);
611
+ this.addOperation("whereExists", { subquery: tempBuilder.operations });
612
+ }
613
+ else {
614
+ this.addOperation("whereNotNull", { field: param });
615
+ }
616
+ return this;
617
+ }
618
+ whereNotExists(param) {
619
+ if (typeof param === "function") {
620
+ const tempBuilder = new PostgresQueryBuilder(this.table, this.dataSource);
621
+ param(tempBuilder);
622
+ this.addOperation("whereNotExists", { subquery: tempBuilder.operations });
623
+ }
624
+ else {
625
+ this.addOperation("whereNull", { field: param });
626
+ }
627
+ return this;
628
+ }
629
+ whereSize(field, ...args) {
630
+ const operator = args.length === 2 ? args[0] : "=";
631
+ const size = args.length === 2 ? args[1] : args[0];
632
+ return this.whereArrayLength(field, operator, size);
633
+ }
634
+ /**
635
+ * Perform a full-text search.
636
+ */
637
+ textSearch(query, filters) {
638
+ // Apply filters if provided
639
+ if (filters) {
640
+ for (const [key, value] of Object.entries(filters)) {
641
+ this.where(key, value);
642
+ }
643
+ }
644
+ // Full-text search would need to know which columns to search
645
+ // For now, this is a placeholder - users should use whereFullText directly
646
+ return this;
647
+ }
648
+ /**
649
+ * Constrain an array field to contain the given value.
650
+ */
651
+ whereArrayContains(field, value, key) {
652
+ if (key) {
653
+ // Array of objects - use JSON containment check
654
+ this.addOperation("whereRaw", {
655
+ expression: `${this.driver.dialect.quoteIdentifier(field)} @> ?::jsonb`,
656
+ bindings: [JSON.stringify([{ [key]: value }])],
657
+ });
658
+ }
659
+ else {
660
+ // Simple array - check if value is in array
661
+ this.addOperation("whereRaw", {
662
+ expression: `? = ANY(${this.driver.dialect.quoteIdentifier(field)})`,
663
+ bindings: [value],
664
+ });
665
+ }
666
+ return this;
667
+ }
668
+ /**
669
+ * Constrain an array field to not contain the given value.
670
+ */
671
+ whereArrayNotContains(field, value, key) {
672
+ if (key) {
673
+ this.addOperation("whereRaw", {
674
+ expression: `NOT (${this.driver.dialect.quoteIdentifier(field)} @> ?::jsonb)`,
675
+ bindings: [JSON.stringify([{ [key]: value }])],
676
+ });
677
+ }
678
+ else {
679
+ this.addOperation("whereRaw", {
680
+ expression: `NOT (? = ANY(${this.driver.dialect.quoteIdentifier(field)}))`,
681
+ bindings: [value],
682
+ });
683
+ }
684
+ return this;
685
+ }
686
+ /**
687
+ * Constrain an array field to contain the value OR be empty.
688
+ */
689
+ whereArrayHasOrEmpty(field, value, key) {
690
+ const quotedField = this.driver.dialect.quoteIdentifier(field);
691
+ if (key) {
692
+ this.addOperation("whereRaw", {
693
+ expression: `(${quotedField} @> ?::jsonb OR ${quotedField} = '[]'::jsonb OR ${quotedField} IS NULL)`,
694
+ bindings: [JSON.stringify([{ [key]: value }])],
695
+ });
696
+ }
697
+ else {
698
+ this.addOperation("whereRaw", {
699
+ expression: `(? = ANY(${quotedField}) OR array_length(${quotedField}, 1) IS NULL)`,
700
+ bindings: [value],
701
+ });
702
+ }
703
+ return this;
704
+ }
705
+ /**
706
+ * Constrain an array field to not contain the value OR be empty.
707
+ */
708
+ whereArrayNotHaveOrEmpty(field, value, key) {
709
+ const quotedField = this.driver.dialect.quoteIdentifier(field);
710
+ if (key) {
711
+ this.addOperation("whereRaw", {
712
+ expression: `(NOT (${quotedField} @> ?::jsonb) OR ${quotedField} = '[]'::jsonb OR ${quotedField} IS NULL)`,
713
+ bindings: [JSON.stringify([{ [key]: value }])],
714
+ });
715
+ }
716
+ else {
717
+ this.addOperation("whereRaw", {
718
+ expression: `(NOT (? = ANY(${quotedField})) OR array_length(${quotedField}, 1) IS NULL)`,
719
+ bindings: [value],
720
+ });
721
+ }
722
+ return this;
723
+ }
724
+ join(...args) {
725
+ if (args.length === 3) {
726
+ this.addOperation("join", {
727
+ table: args[0],
728
+ localField: args[1],
729
+ foreignField: args[2],
730
+ });
731
+ }
732
+ else {
733
+ this.addOperation("join", args[0]);
734
+ }
735
+ return this;
736
+ }
737
+ leftJoin(...args) {
738
+ if (args.length === 3) {
739
+ this.addOperation("leftJoin", {
740
+ table: args[0],
741
+ localField: args[1],
742
+ foreignField: args[2],
743
+ });
744
+ }
745
+ else {
746
+ this.addOperation("leftJoin", args[0]);
747
+ }
748
+ return this;
749
+ }
750
+ rightJoin(...args) {
751
+ if (args.length === 3) {
752
+ this.addOperation("rightJoin", {
753
+ table: args[0],
754
+ localField: args[1],
755
+ foreignField: args[2],
756
+ });
757
+ }
758
+ else {
759
+ this.addOperation("rightJoin", args[0]);
760
+ }
761
+ return this;
762
+ }
763
+ innerJoin(...args) {
764
+ if (args.length === 3) {
765
+ this.addOperation("innerJoin", {
766
+ table: args[0],
767
+ localField: args[1],
768
+ foreignField: args[2],
769
+ });
770
+ }
771
+ else {
772
+ this.addOperation("innerJoin", args[0]);
773
+ }
774
+ return this;
775
+ }
776
+ fullJoin(...args) {
777
+ if (args.length === 3) {
778
+ this.addOperation("fullJoin", {
779
+ table: args[0],
780
+ localField: args[1],
781
+ foreignField: args[2],
782
+ });
783
+ }
784
+ else {
785
+ this.addOperation("fullJoin", args[0]);
786
+ }
787
+ return this;
788
+ }
789
+ /**
790
+ * Add a CROSS JOIN clause.
791
+ */
792
+ crossJoin(table) {
793
+ this.addOperation("crossJoin", { table });
794
+ return this;
795
+ }
796
+ /**
797
+ * Add a raw JOIN clause.
798
+ */
799
+ joinRaw(expression, bindings) {
800
+ this.addOperation("joinRaw", { expression, bindings });
801
+ return this;
802
+ }
803
+ select(...args) {
804
+ // Handle single array argument
805
+ if (args.length === 1 && Array.isArray(args[0])) {
806
+ this.addOperation("select", { fields: args[0] });
807
+ }
808
+ // Handle Record<string, 0 | 1 | boolean> (projection map)
809
+ else if (args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0])) {
810
+ this.addOperation("select", { fields: args[0] });
811
+ }
812
+ // Handle rest params (...fields)
813
+ else {
814
+ const flatFields = args.flat();
815
+ this.addOperation("select", { fields: flatFields });
816
+ }
817
+ return this;
818
+ }
819
+ /**
820
+ * Select a field with alias.
821
+ */
822
+ selectAs(field, alias) {
823
+ // Use select operation with alias format (Record<field, alias>)
824
+ this.addOperation("select", { fields: { [field]: alias } });
825
+ return this;
826
+ }
827
+ /**
828
+ * Select raw expression.
829
+ */
830
+ selectRaw(expression, bindings) {
831
+ this.addOperation("selectRaw", { expression, bindings });
832
+ return this;
833
+ }
834
+ /**
835
+ * Select multiple raw expressions.
836
+ */
837
+ selectRawMany(definitions) {
838
+ for (const def of definitions) {
839
+ this.selectRaw({ [def.alias]: def.expression }, def.bindings);
840
+ }
841
+ return this;
842
+ }
843
+ /**
844
+ * Select subquery.
845
+ */
846
+ selectSub(expression, alias) {
847
+ this.addOperation("selectRaw", { expression: { [alias]: expression } });
848
+ return this;
849
+ }
850
+ /**
851
+ * Add subquery to existing selection.
852
+ */
853
+ addSelectSub(expression, alias) {
854
+ return this.selectSub(expression, alias);
855
+ }
856
+ /**
857
+ * Select aggregate value.
858
+ */
859
+ selectAggregate(field, aggregate, alias) {
860
+ const expr = `${aggregate.toUpperCase()}(${field})`;
861
+ return this.selectRaw({ [alias]: expr });
862
+ }
863
+ /**
864
+ * Select existence check.
865
+ */
866
+ selectExists(field, alias) {
867
+ return this.selectRaw({ [alias]: `${field} IS NOT NULL` });
868
+ }
869
+ /**
870
+ * Select count.
871
+ */
872
+ selectCount(field, alias) {
873
+ return this.selectAggregate(field, "count", alias);
874
+ }
875
+ /**
876
+ * Select CASE expression.
877
+ */
878
+ selectCase(cases, otherwise, alias) {
879
+ const caseExpr = cases.map((c) => `WHEN ${c.when} THEN ${c.then}`).join(" ");
880
+ return this.selectRaw({ [alias]: `CASE ${caseExpr} ELSE ${otherwise} END` });
881
+ }
882
+ /**
883
+ * Select conditional (IF/ELSE).
884
+ */
885
+ selectWhen(condition, thenValue, elseValue, alias) {
886
+ return this.selectRaw({
887
+ [alias]: `CASE WHEN ${condition} THEN ${thenValue} ELSE ${elseValue} END`,
888
+ });
889
+ }
890
+ /**
891
+ * Direct projection manipulation.
892
+ */
893
+ selectDriverProjection(callback) {
894
+ // PostgreSQL doesn't have direct projection manipulation like MongoDB
895
+ return this;
896
+ }
897
+ /**
898
+ * Select JSON path.
899
+ */
900
+ selectJson(path, alias) {
901
+ const parts = path.split("->");
902
+ const column = parts[0];
903
+ const jsonPath = parts.slice(1).join("->");
904
+ const expr = jsonPath ? `${column}->>'${jsonPath}'` : column;
905
+ return alias ? this.selectAs(expr, alias) : this.selectRaw(expr);
906
+ }
907
+ /**
908
+ * Select JSON path with raw expression.
909
+ */
910
+ selectJsonRaw(path, expression, alias) {
911
+ return this.selectRaw({ [alias]: expression });
912
+ }
913
+ /**
914
+ * Exclude JSON path.
915
+ */
916
+ deselectJson(path) {
917
+ return this.deselect([path]);
918
+ }
919
+ /**
920
+ * Concatenate fields.
921
+ */
922
+ selectConcat(fields, alias) {
923
+ const expr = fields.join(" || ");
924
+ return this.selectRaw({ [alias]: expr });
925
+ }
926
+ /**
927
+ * Coalesce values.
928
+ */
929
+ selectCoalesce(fields, alias) {
930
+ const expr = `COALESCE(${fields.join(", ")})`;
931
+ return this.selectRaw({ [alias]: expr });
932
+ }
933
+ /**
934
+ * Window function.
935
+ */
936
+ selectWindow(spec) {
937
+ this.addOperation("selectRaw", { expression: spec });
938
+ return this;
939
+ }
940
+ /**
941
+ * Exclude columns from projection.
942
+ */
943
+ deselect(fields) {
944
+ this.addOperation("deselect", { fields });
945
+ return this;
946
+ }
947
+ /**
948
+ * Clear selection.
949
+ */
950
+ clearSelect() {
951
+ this.operations = this.operations.filter((op) => !op.type.startsWith("select") && op.type !== "deselect");
952
+ return this;
953
+ }
954
+ /**
955
+ * Select all columns.
956
+ */
957
+ selectAll() {
958
+ return this.clearSelect();
959
+ }
960
+ /**
961
+ * Restore default projection.
962
+ */
963
+ selectDefault() {
964
+ return this.clearSelect();
965
+ }
966
+ /**
967
+ * Select distinct values.
968
+ */
969
+ distinctValues(fields) {
970
+ this.addOperation("distinct", {});
971
+ if (fields) {
972
+ this.select(Array.isArray(fields) ? fields : [fields]);
973
+ }
974
+ return this;
975
+ }
976
+ /**
977
+ * Add additional select fields.
978
+ */
979
+ addSelect(fields) {
980
+ this.addOperation("select", { fields, add: true });
981
+ return this;
982
+ }
983
+ orderBy(...args) {
984
+ if (typeof args[0] === "string") {
985
+ this.addOperation("orderBy", { field: args[0], direction: args[1] ?? "asc" });
986
+ }
987
+ else {
988
+ for (const [field, direction] of Object.entries(args[0])) {
989
+ this.addOperation("orderBy", { field, direction });
990
+ }
991
+ }
992
+ return this;
993
+ }
994
+ /**
995
+ * Order descending.
996
+ */
997
+ orderByDesc(field) {
998
+ return this.orderBy(field, "desc");
999
+ }
1000
+ /**
1001
+ * Order with raw expression.
1002
+ */
1003
+ orderByRaw(expression, bindings) {
1004
+ this.addOperation("orderByRaw", { expression, bindings });
1005
+ return this;
1006
+ }
1007
+ /**
1008
+ * Order randomly.
1009
+ */
1010
+ orderByRandom(limit) {
1011
+ this.addOperation("orderByRaw", { expression: "RANDOM()" });
1012
+ return this.limit(limit);
1013
+ }
1014
+ /**
1015
+ * Get latest records.
1016
+ */
1017
+ async latest(column = "createdAt") {
1018
+ return this.orderBy(column, "desc").get();
1019
+ }
1020
+ /**
1021
+ * Get oldest records.
1022
+ */
1023
+ oldest(column = "createdAt") {
1024
+ return this.orderBy(column, "asc");
1025
+ }
1026
+ // ============================================================================
1027
+ // LIMITING / PAGINATION
1028
+ // ============================================================================
1029
+ /**
1030
+ * Limit results.
1031
+ */
1032
+ limit(value) {
1033
+ this.addOperation("limit", { value });
1034
+ return this;
1035
+ }
1036
+ /**
1037
+ * Skip results.
1038
+ */
1039
+ skip(value) {
1040
+ this.addOperation("offset", { value });
1041
+ return this;
1042
+ }
1043
+ /**
1044
+ * Offset results.
1045
+ */
1046
+ offset(value) {
1047
+ return this.skip(value);
1048
+ }
1049
+ /**
1050
+ * Take first N results.
1051
+ */
1052
+ take(value) {
1053
+ return this.limit(value);
1054
+ }
1055
+ /**
1056
+ * Apply cursor pagination hints.
1057
+ */
1058
+ cursor(after, before) {
1059
+ // Store cursor hints for cursorPaginate
1060
+ return this;
1061
+ }
1062
+ // ============================================================================
1063
+ // GROUPING / AGGREGATION
1064
+ // ============================================================================
1065
+ /**
1066
+ * Group results.
1067
+ */
1068
+ groupBy(input) {
1069
+ const fields = Array.isArray(input) ? input : [input];
1070
+ this.addOperation("groupBy", { fields });
1071
+ return this;
1072
+ }
1073
+ /**
1074
+ * Raw GROUP BY.
1075
+ */
1076
+ groupByRaw(expression, bindings) {
1077
+ this.addOperation("groupBy", { expression, bindings });
1078
+ return this;
1079
+ }
1080
+ having(...args) {
1081
+ if (args.length === 1) {
1082
+ const input = args[0];
1083
+ if (Array.isArray(input)) {
1084
+ if (input.length === 2) {
1085
+ this.addOperation("having", { field: input[0], operator: "=", value: input[1] });
1086
+ }
1087
+ else {
1088
+ this.addOperation("having", { field: input[0], operator: input[1], value: input[2] });
1089
+ }
1090
+ }
1091
+ else {
1092
+ for (const [key, value] of Object.entries(input)) {
1093
+ this.addOperation("having", { field: key, operator: "=", value });
1094
+ }
1095
+ }
1096
+ }
1097
+ else if (args.length === 2) {
1098
+ this.addOperation("having", { field: args[0], operator: "=", value: args[1] });
1099
+ }
1100
+ else if (args.length === 3) {
1101
+ this.addOperation("having", { field: args[0], operator: args[1], value: args[2] });
1102
+ }
1103
+ return this;
1104
+ }
1105
+ /**
1106
+ * Raw HAVING clause.
1107
+ */
1108
+ havingRaw(expression, bindings) {
1109
+ this.addOperation("havingRaw", { expression, bindings });
1110
+ return this;
1111
+ }
1112
+ // ============================================================================
1113
+ // EXECUTION METHODS
1114
+ // ============================================================================
1115
+ /**
1116
+ * Execute query and get all results.
1117
+ */
1118
+ async get() {
1119
+ this.applyPendingScopes();
1120
+ // Apply JOIN operations for joinWith() relations
1121
+ this.applyJoinRelations();
1122
+ if (this.fetchingCallback) {
1123
+ await this.fetchingCallback(this);
1124
+ }
1125
+ const parser = new PostgresQueryParser({
1126
+ table: this.table,
1127
+ operations: this.operations,
1128
+ });
1129
+ const { sql, params } = parser.parse();
1130
+ try {
1131
+ const result = await this.driver.query(sql, params);
1132
+ let records = result.rows;
1133
+ // Extract joined relation data before hydration
1134
+ const joinedData = this.extractJoinedRelationData(records);
1135
+ if (this.hydratingCallback) {
1136
+ await this.hydratingCallback(records, {});
1137
+ }
1138
+ if (this.hydrateCallback) {
1139
+ records = records.map((row, index) => this.hydrateCallback(row, index));
1140
+ }
1141
+ // Attach joined relations to hydrated models
1142
+ this.attachJoinedRelations(records, joinedData);
1143
+ if (this.fetchedCallback) {
1144
+ await this.fetchedCallback(records, {});
1145
+ }
1146
+ // Cleanup
1147
+ this.operations = [];
1148
+ return records;
1149
+ }
1150
+ catch (error) {
1151
+ console.log("Error while executing:", sql, params);
1152
+ console.log("Query Builder Error:", error);
1153
+ throw error;
1154
+ }
1155
+ }
1156
+ /**
1157
+ * Apply JOIN operations for joinWith() relations.
1158
+ */
1159
+ applyJoinRelations() {
1160
+ if (this.joinRelations.size === 0 || !this.relationDefinitions)
1161
+ return;
1162
+ for (const [relationName, config] of this.joinRelations) {
1163
+ const relationDef = this.relationDefinitions[relationName];
1164
+ if (!relationDef)
1165
+ continue;
1166
+ // Resolve the related model class
1167
+ const RelatedModel = typeof relationDef.model === "string"
1168
+ ? getModelFromRegistry(relationDef.model)
1169
+ : relationDef.model;
1170
+ if (!RelatedModel)
1171
+ continue;
1172
+ const relatedTable = RelatedModel.table;
1173
+ const alias = config.alias;
1174
+ // Determine join keys based on relation type
1175
+ let localField;
1176
+ let foreignField;
1177
+ if (relationDef.type === "belongsTo") {
1178
+ localField = relationDef.foreignKey || `${relationName}Id`;
1179
+ foreignField = relationDef.ownerKey || "id";
1180
+ }
1181
+ else {
1182
+ // hasOne, hasMany
1183
+ localField = relationDef.localKey || "id";
1184
+ foreignField = relationDef.foreignKey || `${this.table.slice(0, -1)}Id`;
1185
+ }
1186
+ // Add LEFT JOIN operation
1187
+ this.addOperation("leftJoin", {
1188
+ table: relatedTable,
1189
+ alias,
1190
+ localField,
1191
+ foreignField,
1192
+ });
1193
+ // Add SELECT for related columns with prefix
1194
+ this.addOperation("selectRelatedColumns", {
1195
+ alias,
1196
+ relationName,
1197
+ table: relatedTable,
1198
+ });
1199
+ }
1200
+ }
1201
+ /**
1202
+ * Extract joined relation data from raw records.
1203
+ * Returns a map of record index to relation data.
1204
+ */
1205
+ extractJoinedRelationData(records) {
1206
+ const result = new Map();
1207
+ if (this.joinRelations.size === 0)
1208
+ return result;
1209
+ records.forEach((record, index) => {
1210
+ const relationData = {};
1211
+ for (const [relationName, config] of this.joinRelations) {
1212
+ const columnName = config.alias; // e.g., "_rel_author"
1213
+ // Get the JSON object from the row_to_json column
1214
+ const relatedData = record[columnName];
1215
+ // Remove from main record so it doesn't appear in model.data
1216
+ delete record[columnName];
1217
+ // If null or all values are null, set to null
1218
+ if (relatedData === null ||
1219
+ (typeof relatedData === "object" && Object.values(relatedData).every((v) => v === null))) {
1220
+ relationData[relationName] = null;
1221
+ }
1222
+ else {
1223
+ relationData[relationName] = relatedData;
1224
+ }
1225
+ }
1226
+ result.set(index, relationData);
1227
+ });
1228
+ return result;
1229
+ }
1230
+ /**
1231
+ * Attach joined relations to hydrated models.
1232
+ */
1233
+ attachJoinedRelations(records, joinedData) {
1234
+ if (this.joinRelations.size === 0 || !this.relationDefinitions)
1235
+ return;
1236
+ records.forEach((model, index) => {
1237
+ const relationData = joinedData.get(index);
1238
+ if (!relationData)
1239
+ return;
1240
+ for (const [relationName, data] of Object.entries(relationData)) {
1241
+ if (data === null) {
1242
+ // No related record
1243
+ model[relationName] = null;
1244
+ if (model.loadedRelations) {
1245
+ model.loadedRelations.set(relationName, null);
1246
+ }
1247
+ continue;
1248
+ }
1249
+ const relationDef = this.relationDefinitions[relationName];
1250
+ if (!relationDef)
1251
+ continue;
1252
+ // Resolve and hydrate the related model
1253
+ const RelatedModel = typeof relationDef.model === "string"
1254
+ ? getModelFromRegistry(relationDef.model)
1255
+ : relationDef.model;
1256
+ if (RelatedModel) {
1257
+ const relatedInstance = new RelatedModel(data);
1258
+ relatedInstance.isNew = false;
1259
+ model[relationName] = relatedInstance;
1260
+ if (model.loadedRelations) {
1261
+ model.loadedRelations.set(relationName, relatedInstance);
1262
+ }
1263
+ }
1264
+ }
1265
+ });
1266
+ }
1267
+ /**
1268
+ * Get first result.
1269
+ */
1270
+ async first() {
1271
+ const results = await this.limit(1).get();
1272
+ return results[0] ?? null;
1273
+ }
1274
+ /**
1275
+ * Get last result.
1276
+ */
1277
+ async last() {
1278
+ const results = await this.orderByDesc("id").limit(1).get();
1279
+ return results[0] ?? null;
1280
+ }
1281
+ /**
1282
+ * Get random results.
1283
+ */
1284
+ async random(limit) {
1285
+ this.orderByRaw("RANDOM()");
1286
+ if (limit) {
1287
+ this.limit(limit);
1288
+ }
1289
+ return this.get();
1290
+ }
1291
+ /**
1292
+ * Get first or throw.
1293
+ */
1294
+ async firstOrFail() {
1295
+ const result = await this.first();
1296
+ if (!result) {
1297
+ throw new Error("No records found");
1298
+ }
1299
+ return result;
1300
+ }
1301
+ /**
1302
+ * Get first or call callback.
1303
+ */
1304
+ async firstOr(callback) {
1305
+ const result = await this.first();
1306
+ return result ?? (await callback());
1307
+ }
1308
+ /**
1309
+ * Get first or return default.
1310
+ */
1311
+ async firstOrNull() {
1312
+ return this.first();
1313
+ }
1314
+ /**
1315
+ * Get first or create new.
1316
+ */
1317
+ async firstOrNew(defaults) {
1318
+ const result = await this.first();
1319
+ return result ?? defaults;
1320
+ }
1321
+ /**
1322
+ * Find by ID.
1323
+ */
1324
+ async find(id) {
1325
+ return this.where("id", id).first();
1326
+ }
1327
+ /**
1328
+ * Count results.
1329
+ */
1330
+ async count() {
1331
+ this.applyPendingScopes();
1332
+ // Build count query using selectRaw to avoid quoting COUNT(*) as a column
1333
+ const countOps = [
1334
+ ...this.operations.filter((op) => op.type.includes("where") || op.type.includes("join")),
1335
+ { type: "selectRaw", data: { expression: 'COUNT(*) AS "count"' } },
1336
+ ];
1337
+ const parser = new PostgresQueryParser({
1338
+ table: this.table,
1339
+ operations: countOps,
1340
+ });
1341
+ const { sql, params } = parser.parse();
1342
+ const result = await this.driver.query(sql, params);
1343
+ return parseInt(result.rows[0]?.count ?? "0", 10);
1344
+ }
1345
+ /**
1346
+ * Sum of field values.
1347
+ */
1348
+ async sum(field) {
1349
+ this.applyPendingScopes();
1350
+ const result = await this.selectRaw(`SUM(${field}) as sum`).first();
1351
+ return parseFloat(result?.sum ?? "0");
1352
+ }
1353
+ /**
1354
+ * Average of field values.
1355
+ */
1356
+ async avg(field) {
1357
+ this.applyPendingScopes();
1358
+ const result = await this.selectRaw(`AVG(${field}) as avg`).first();
1359
+ return parseFloat(result?.avg ?? "0");
1360
+ }
1361
+ /**
1362
+ * Minimum field value.
1363
+ */
1364
+ async min(field) {
1365
+ this.applyPendingScopes();
1366
+ const result = await this.selectRaw(`MIN(${field}) as min`).first();
1367
+ return parseFloat(result?.min ?? "0");
1368
+ }
1369
+ /**
1370
+ * Maximum field value.
1371
+ */
1372
+ async max(field) {
1373
+ this.applyPendingScopes();
1374
+ const result = await this.selectRaw(`MAX(${field}) as max`).first();
1375
+ return parseFloat(result?.max ?? "0");
1376
+ }
1377
+ /**
1378
+ * Get distinct values.
1379
+ */
1380
+ async distinct(field) {
1381
+ this.distinctValues(field);
1382
+ const results = await this.get();
1383
+ return results.map((row) => row[field]);
1384
+ }
1385
+ /**
1386
+ * Get array of values for field.
1387
+ */
1388
+ async pluck(field) {
1389
+ const results = await this.select([field]).get();
1390
+ return results.map((row) => row[field]);
1391
+ }
1392
+ /**
1393
+ * Get single scalar value.
1394
+ */
1395
+ async value(field) {
1396
+ const result = await this.select([field]).first();
1397
+ return result?.[field] ?? null;
1398
+ }
1399
+ /**
1400
+ * Check if records exist.
1401
+ */
1402
+ async exists() {
1403
+ const count = await this.limit(1).count();
1404
+ return count > 0;
1405
+ }
1406
+ /**
1407
+ * Check if no records exist.
1408
+ */
1409
+ async notExists() {
1410
+ return !(await this.exists());
1411
+ }
1412
+ /**
1413
+ * Count distinct values.
1414
+ */
1415
+ async countDistinct(field) {
1416
+ const result = await this.selectRaw(`COUNT(DISTINCT ${field}) as count`).first();
1417
+ return parseInt(result?.count ?? "0", 10);
1418
+ }
1419
+ /**
1420
+ * Increment field value.
1421
+ */
1422
+ async increment(field, amount = 1) {
1423
+ this.applyPendingScopes();
1424
+ const { sql, params } = this.buildFilter();
1425
+ const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${this.driver.dialect.quoteIdentifier(field)} = COALESCE(${this.driver.dialect.quoteIdentifier(field)}, 0) + $1 WHERE ${sql.replace("WHERE ", "")} RETURNING ${this.driver.dialect.quoteIdentifier(field)}`;
1426
+ const result = await this.driver.query(updateSql, [amount, ...params]);
1427
+ return result.rows[0]?.[field] ?? 0;
1428
+ }
1429
+ /**
1430
+ * Decrement field value.
1431
+ */
1432
+ async decrement(field, amount = 1) {
1433
+ return this.increment(field, -amount);
1434
+ }
1435
+ /**
1436
+ * Increment for all matching.
1437
+ */
1438
+ async incrementMany(field, amount = 1) {
1439
+ this.applyPendingScopes();
1440
+ const { sql, params } = this.buildFilter();
1441
+ const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${this.driver.dialect.quoteIdentifier(field)} = COALESCE(${this.driver.dialect.quoteIdentifier(field)}, 0) + $1 WHERE ${sql.replace("WHERE ", "")}`;
1442
+ const result = await this.driver.query(updateSql, [amount, ...params]);
1443
+ return result.rowCount ?? 0;
1444
+ }
1445
+ /**
1446
+ * Decrement for all matching.
1447
+ */
1448
+ async decrementMany(field, amount = 1) {
1449
+ return this.incrementMany(field, -amount);
1450
+ }
1451
+ // ============================================================================
1452
+ // CHUNKING / PAGINATION
1453
+ // ============================================================================
1454
+ /**
1455
+ * Process results in chunks.
1456
+ */
1457
+ async chunk(size, callback) {
1458
+ let chunkIndex = 0;
1459
+ let hasMore = true;
1460
+ while (hasMore) {
1461
+ const chunk = await this.clone()
1462
+ .skip(chunkIndex * size)
1463
+ .limit(size)
1464
+ .get();
1465
+ if (chunk.length === 0) {
1466
+ break;
1467
+ }
1468
+ const shouldContinue = await callback(chunk, chunkIndex);
1469
+ if (shouldContinue === false) {
1470
+ break;
1471
+ }
1472
+ hasMore = chunk.length === size;
1473
+ chunkIndex++;
1474
+ }
1475
+ }
1476
+ /**
1477
+ * Page-based pagination.
1478
+ */
1479
+ async paginate(options) {
1480
+ const page = options.page ?? 1;
1481
+ const limit = options.limit ?? 10;
1482
+ const skip = (page - 1) * limit;
1483
+ const [data, total] = await Promise.all([
1484
+ this.clone().skip(skip).limit(limit).get(),
1485
+ this.count(),
1486
+ ]);
1487
+ return {
1488
+ data,
1489
+ pagination: {
1490
+ total,
1491
+ page,
1492
+ limit,
1493
+ pages: Math.ceil(total / limit),
1494
+ },
1495
+ };
1496
+ }
1497
+ /**
1498
+ * Cursor-based pagination.
1499
+ */
1500
+ async cursorPaginate(options) {
1501
+ const { limit, cursor, column = "id", direction = "next" } = options;
1502
+ if (cursor) {
1503
+ const operator = direction === "next" ? ">" : "<";
1504
+ this.where(column, operator, cursor);
1505
+ }
1506
+ const sortOrder = direction === "next" ? "asc" : "desc";
1507
+ this.orderBy(column, sortOrder);
1508
+ const results = await this.limit(limit + 1).get();
1509
+ const hasMore = results.length > limit;
1510
+ let data = hasMore ? results.slice(0, limit) : results;
1511
+ if (direction === "prev") {
1512
+ data = data.reverse();
1513
+ }
1514
+ let nextCursor;
1515
+ let prevCursor;
1516
+ let hasPrev = false;
1517
+ if (data.length > 0) {
1518
+ const firstItem = data[0][column];
1519
+ const lastItem = data[data.length - 1][column];
1520
+ if (direction === "next") {
1521
+ nextCursor = hasMore ? lastItem : undefined;
1522
+ if (cursor) {
1523
+ hasPrev = true;
1524
+ prevCursor = firstItem;
1525
+ }
1526
+ }
1527
+ else {
1528
+ prevCursor = hasMore ? firstItem : undefined;
1529
+ hasPrev = hasMore;
1530
+ if (cursor) {
1531
+ nextCursor = lastItem;
1532
+ }
1533
+ }
1534
+ }
1535
+ return {
1536
+ data,
1537
+ pagination: {
1538
+ hasMore,
1539
+ hasPrev,
1540
+ nextCursor,
1541
+ prevCursor,
1542
+ },
1543
+ };
1544
+ }
1545
+ // ============================================================================
1546
+ // MUTATION METHODS
1547
+ // ============================================================================
1548
+ /**
1549
+ * Delete matching records.
1550
+ */
1551
+ async delete() {
1552
+ this.applyPendingScopes();
1553
+ const { sql, params } = this.buildFilter();
1554
+ const deleteSql = `DELETE FROM ${this.driver.dialect.quoteIdentifier(this.table)} ${sql}`;
1555
+ const result = await this.driver.query(deleteSql, params);
1556
+ return result.rowCount ?? 0;
1557
+ }
1558
+ /**
1559
+ * Delete first matching record.
1560
+ */
1561
+ async deleteOne() {
1562
+ return this.limit(1).delete();
1563
+ }
1564
+ /**
1565
+ * Update matching records.
1566
+ */
1567
+ async update(fields) {
1568
+ this.applyPendingScopes();
1569
+ const result = await this.driver.updateMany(this.table, {}, { $set: fields });
1570
+ return result.modifiedCount;
1571
+ }
1572
+ /**
1573
+ * Unset fields from matching records.
1574
+ */
1575
+ async unset(...fields) {
1576
+ this.applyPendingScopes();
1577
+ const updateObj = {};
1578
+ for (const field of fields) {
1579
+ updateObj[field] = 1;
1580
+ }
1581
+ const result = await this.driver.updateMany(this.table, {}, { $unset: updateObj });
1582
+ return result.modifiedCount;
1583
+ }
1584
+ // ============================================================================
1585
+ // INSPECTION / DEBUGGING
1586
+ // ============================================================================
1587
+ /**
1588
+ * Get the raw SQL query.
1589
+ */
1590
+ parse() {
1591
+ this.applyPendingScopes();
1592
+ const parser = new PostgresQueryParser({
1593
+ table: this.table,
1594
+ operations: this.operations,
1595
+ });
1596
+ return parser.parse();
1597
+ }
1598
+ /**
1599
+ * Get formatted SQL string.
1600
+ */
1601
+ pretty() {
1602
+ const { sql, params } = this.parse();
1603
+ return `${sql}\n-- Parameters: ${JSON.stringify(params)}`;
1604
+ }
1605
+ /**
1606
+ * Get query execution plan.
1607
+ */
1608
+ async explain() {
1609
+ const { sql, params } = this.parse();
1610
+ const explainSql = `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${sql}`;
1611
+ const result = await this.driver.query(explainSql, params);
1612
+ return result.rows;
1613
+ }
1614
+ // ============================================================================
1615
+ // UTILITY METHODS
1616
+ // ============================================================================
1617
+ /**
1618
+ * Add driver-specific raw modifications to the query.
1619
+ */
1620
+ raw(builder) {
1621
+ // For PostgreSQL, the native object would be the operations array
1622
+ builder(this.operations);
1623
+ return this;
1624
+ }
1625
+ /**
1626
+ * Extend the query builder with driver-specific extensions.
1627
+ */
1628
+ extend(extension, ..._args) {
1629
+ // PostgreSQL doesn't have driver-specific extensions like MongoDB
1630
+ // Return undefined as R for now - specific extensions can be added later
1631
+ throw new Error(`Extension "${extension}" is not supported by PostgresQueryBuilder`);
1632
+ }
1633
+ /**
1634
+ * Tap into the query builder for side-effects.
1635
+ */
1636
+ tap(callback) {
1637
+ callback(this);
1638
+ return this;
1639
+ }
1640
+ /**
1641
+ * Conditionally apply query modifications.
1642
+ */
1643
+ when(condition, callback, otherwise) {
1644
+ if (condition) {
1645
+ callback(this, condition);
1646
+ }
1647
+ else if (otherwise) {
1648
+ otherwise(this);
1649
+ }
1650
+ return this;
1651
+ }
1652
+ // ============================================================================
1653
+ // INTERNAL HELPERS
1654
+ // ============================================================================
1655
+ /**
1656
+ * Build WHERE clause from current operations.
1657
+ */
1658
+ buildFilter() {
1659
+ const whereOps = this.operations.filter((op) => op.type.includes("where") || op.type.includes("Where"));
1660
+ if (whereOps.length === 0) {
1661
+ return { sql: "", params: [] };
1662
+ }
1663
+ const parser = new PostgresQueryParser({
1664
+ table: this.table,
1665
+ operations: whereOps,
1666
+ });
1667
+ const { sql, params } = parser.parse();
1668
+ const whereMatch = sql.match(/WHERE .+$/);
1669
+ return {
1670
+ sql: whereMatch ? whereMatch[0] : "",
1671
+ params,
1672
+ };
1673
+ }
1674
+ // ============================================================================
1675
+ // RELATIONS / EAGER LOADING (Stubs)
1676
+ // ============================================================================
1677
+ /**
1678
+ * Relations to eagerly load.
1679
+ */
1680
+ eagerLoadRelations = new Map();
1681
+ /**
1682
+ * Relations to count.
1683
+ */
1684
+ countRelations = [];
1685
+ /**
1686
+ * Relations to load via JOIN (single query).
1687
+ */
1688
+ joinRelations = new Map();
1689
+ /**
1690
+ * Relation definitions from the model.
1691
+ */
1692
+ relationDefinitions;
1693
+ /**
1694
+ * Model class reference.
1695
+ */
1696
+ modelClass;
1697
+ /**
1698
+ * Load relations using database JOINs in a single query.
1699
+ *
1700
+ * Unlike `with()` which uses separate queries, `joinWith()` uses
1701
+ * LEFT JOIN to fetch related data in a single query.
1702
+ *
1703
+ * @param relations - Relation names to load via JOIN
1704
+ * @returns This builder for chaining
1705
+ */
1706
+ joinWith(...relations) {
1707
+ for (const relation of relations) {
1708
+ const def = this.relationDefinitions?.[relation];
1709
+ if (def) {
1710
+ this.joinRelations.set(relation, {
1711
+ alias: `_rel_${relation}`,
1712
+ type: def.type,
1713
+ });
1714
+ }
1715
+ }
1716
+ return this;
1717
+ }
1718
+ /**
1719
+ * Eagerly load one or more relations.
1720
+ *
1721
+ * Supported patterns:
1722
+ * - `with("posts")` - Load relation
1723
+ * - `with("posts", "comments")` - Load multiple relations
1724
+ * - `with("posts", callback)` - Load relation with constraint
1725
+ * - `with({ posts: true, comments: callback })` - Object configuration
1726
+ *
1727
+ * @param args - Relation name(s), callbacks, or configuration object
1728
+ */
1729
+ with(...args) {
1730
+ for (let i = 0; i < args.length; i++) {
1731
+ const arg = args[i];
1732
+ if (typeof arg === "string") {
1733
+ // Check if next argument is a callback for this relation
1734
+ const nextArg = args[i + 1];
1735
+ if (typeof nextArg === "function") {
1736
+ this.eagerLoadRelations.set(arg, nextArg);
1737
+ i++; // Skip the callback in next iteration
1738
+ }
1739
+ else {
1740
+ this.eagerLoadRelations.set(arg, true);
1741
+ }
1742
+ }
1743
+ else if (typeof arg === "object" && arg !== null) {
1744
+ for (const [key, value] of Object.entries(arg)) {
1745
+ this.eagerLoadRelations.set(key, value);
1746
+ }
1747
+ }
1748
+ // Functions not preceded by a string are ignored (invalid usage)
1749
+ }
1750
+ return this;
1751
+ }
1752
+ /**
1753
+ * Add a count of related models as a virtual field.
1754
+ * @param relations - Relation name(s) to count
1755
+ */
1756
+ withCount(...relations) {
1757
+ this.countRelations.push(...relations);
1758
+ return this;
1759
+ }
1760
+ /**
1761
+ * Filter results to only those that have related models.
1762
+ * @param relation - Relation name
1763
+ * @param operator - Optional comparison operator
1764
+ * @param count - Optional count to compare against
1765
+ */
1766
+ has(relation, operator, count) {
1767
+ // TODO: Implement has() using EXISTS subquery
1768
+ this.addOperation("has", { relation, operator, count });
1769
+ return this;
1770
+ }
1771
+ /**
1772
+ * Filter results that have related models matching specific conditions.
1773
+ * @param relation - Relation name
1774
+ * @param callback - Callback to define conditions
1775
+ */
1776
+ whereHas(relation, callback) {
1777
+ // TODO: Implement whereHas() using EXISTS subquery with conditions
1778
+ this.addOperation("whereHas", { relation, callback });
1779
+ return this;
1780
+ }
1781
+ /**
1782
+ * Filter results that don't have any related models.
1783
+ * @param relation - Relation name
1784
+ */
1785
+ doesntHave(relation) {
1786
+ // TODO: Implement doesntHave() using NOT EXISTS subquery
1787
+ this.addOperation("doesntHave", { relation });
1788
+ return this;
1789
+ }
1790
+ /**
1791
+ * Filter results that don't have related models matching specific conditions.
1792
+ * @param relation - Relation name
1793
+ * @param callback - Callback to define conditions
1794
+ */
1795
+ whereDoesntHave(relation, callback) {
1796
+ // TODO: Implement whereDoesntHave() using NOT EXISTS subquery with conditions
1797
+ this.addOperation("whereDoesntHave", { relation, callback });
1798
+ return this;
1799
+ }
1800
+ }export{PostgresQueryBuilder};//# sourceMappingURL=postgres-query-builder.js.map