metal-orm 1.0.7 → 1.0.9

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 (153) hide show
  1. package/README.md +133 -121
  2. package/dist/decorators/index.cjs +2564 -0
  3. package/dist/decorators/index.cjs.map +1 -0
  4. package/dist/decorators/index.d.cts +53 -0
  5. package/dist/decorators/index.d.ts +53 -0
  6. package/dist/decorators/index.js +2530 -0
  7. package/dist/decorators/index.js.map +1 -0
  8. package/dist/index.cjs +4227 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +701 -0
  11. package/dist/index.d.ts +701 -0
  12. package/dist/index.js +4131 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/select-654m4qy8.d.cts +1522 -0
  15. package/dist/select-654m4qy8.d.ts +1522 -0
  16. package/package.json +27 -20
  17. package/src/codegen/typescript.ts +405 -393
  18. package/src/core/ast/aggregate-functions.ts +30 -0
  19. package/src/core/ast/builders.ts +43 -0
  20. package/src/core/ast/expression-builders.ts +310 -0
  21. package/src/core/ast/expression-nodes.ts +211 -0
  22. package/src/core/ast/expression-visitor.ts +99 -0
  23. package/src/core/ast/expression.ts +5 -0
  24. package/src/{utils → core/ast}/join-node.ts +20 -20
  25. package/src/{ast → core/ast}/join.ts +18 -18
  26. package/src/{ast → core/ast}/query.ts +113 -113
  27. package/src/core/ast/window-functions.ts +140 -0
  28. package/src/{dialect → core/dialect}/abstract.ts +94 -94
  29. package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
  30. package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
  31. package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
  32. package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
  33. package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
  34. package/src/decorators/bootstrap.ts +126 -0
  35. package/src/decorators/column.ts +78 -0
  36. package/src/decorators/entity.ts +36 -0
  37. package/src/decorators/index.ts +4 -0
  38. package/src/decorators/relations.ts +107 -0
  39. package/src/global.d.ts +1 -0
  40. package/src/index.ts +22 -22
  41. package/src/orm/db-executor.ts +11 -0
  42. package/src/orm/domain-event-bus.ts +52 -0
  43. package/src/{runtime → orm}/entity-meta.ts +52 -52
  44. package/src/orm/entity-metadata.ts +140 -0
  45. package/src/{runtime → orm}/entity.ts +252 -252
  46. package/src/{runtime → orm}/execute.ts +36 -36
  47. package/src/{runtime → orm}/hydration.ts +103 -103
  48. package/src/orm/identity-map.ts +37 -0
  49. package/src/{runtime → orm}/lazy-batch.ts +205 -205
  50. package/src/orm/orm-context.ts +154 -0
  51. package/src/orm/relation-change-processor.ts +140 -0
  52. package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
  53. package/src/{runtime → orm}/relations/has-many.ts +111 -111
  54. package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
  55. package/src/orm/runtime-types.ts +39 -0
  56. package/src/orm/transaction-runner.ts +17 -0
  57. package/src/orm/unit-of-work.ts +232 -0
  58. package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
  59. package/src/{builder → query-builder}/delete-query-state.ts +38 -42
  60. package/src/{builder → query-builder}/delete.ts +46 -57
  61. package/src/{builder → query-builder}/hydration-manager.ts +87 -87
  62. package/src/{builder → query-builder}/hydration-planner.ts +182 -182
  63. package/src/{builder → query-builder}/insert-query-state.ts +51 -62
  64. package/src/{builder → query-builder}/insert.ts +48 -59
  65. package/src/{builder → query-builder}/query-ast-service.ts +208 -226
  66. package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
  67. package/src/{builder → query-builder}/relation-conditions.ts +112 -112
  68. package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
  69. package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
  70. package/src/{builder → query-builder}/relation-service.ts +284 -284
  71. package/src/{builder → query-builder}/relation-types.ts +21 -21
  72. package/src/{builder → query-builder}/relation-utils.ts +12 -12
  73. package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
  74. package/src/{builder → query-builder}/select-query-state.ts +179 -179
  75. package/src/{builder → query-builder}/select.ts +78 -69
  76. package/src/{builder → query-builder}/update-query-state.ts +55 -59
  77. package/src/{builder → query-builder}/update.ts +50 -61
  78. package/src/schema/column.ts +25 -25
  79. package/src/schema/relation.ts +116 -116
  80. package/src/schema/table.ts +34 -34
  81. package/src/schema/types.ts +76 -76
  82. package/.github/workflows/publish-metal-orm.yml +0 -38
  83. package/ROADMAP.md +0 -125
  84. package/docs/CHANGES.md +0 -104
  85. package/docs/advanced-features.md +0 -176
  86. package/docs/api-reference.md +0 -31
  87. package/docs/dml-operations.md +0 -156
  88. package/docs/getting-started.md +0 -171
  89. package/docs/hydration.md +0 -115
  90. package/docs/index.md +0 -36
  91. package/docs/multi-dialect-support.md +0 -59
  92. package/docs/query-builder.md +0 -135
  93. package/docs/runtime.md +0 -105
  94. package/docs/schema-definition.md +0 -112
  95. package/metadata.json +0 -5
  96. package/playground/api/playground-api.ts +0 -94
  97. package/playground/index.html +0 -15
  98. package/playground/src/App.css +0 -1
  99. package/playground/src/App.tsx +0 -114
  100. package/playground/src/components/CodeDisplay.tsx +0 -43
  101. package/playground/src/components/QueryExecutor.tsx +0 -189
  102. package/playground/src/components/ResultsTable.tsx +0 -67
  103. package/playground/src/components/ResultsTabs.tsx +0 -105
  104. package/playground/src/components/ScenarioList.tsx +0 -56
  105. package/playground/src/components/logo.svg +0 -45
  106. package/playground/src/data/scenarios.ts +0 -2
  107. package/playground/src/main.tsx +0 -9
  108. package/playground/src/services/PlaygroundApiService.ts +0 -60
  109. package/postcss.config.cjs +0 -5
  110. package/sql_sql-ansi-cheatsheet-2025.md +0 -264
  111. package/src/ast/expression.ts +0 -658
  112. package/src/builder/operations/cte-manager.ts +0 -34
  113. package/src/builder/operations/filter-manager.ts +0 -68
  114. package/src/builder/operations/join-manager.ts +0 -36
  115. package/src/builder/operations/pagination-manager.ts +0 -36
  116. package/src/playground/features/playground/api/types.ts +0 -16
  117. package/src/playground/features/playground/clients/MockClient.ts +0 -17
  118. package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
  119. package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
  120. package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
  121. package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
  122. package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
  123. package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
  124. package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
  125. package/src/playground/features/playground/data/scenarios/index.ts +0 -29
  126. package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
  127. package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
  128. package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
  129. package/src/playground/features/playground/data/scenarios/types.ts +0 -70
  130. package/src/playground/features/playground/data/schema.ts +0 -91
  131. package/src/playground/features/playground/data/seed.ts +0 -104
  132. package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
  133. package/src/runtime/orm-context.ts +0 -539
  134. package/tests/belongs-to-many.test.ts +0 -57
  135. package/tests/between.test.ts +0 -43
  136. package/tests/case-expression.test.ts +0 -58
  137. package/tests/complex-exists.test.ts +0 -230
  138. package/tests/cte.test.ts +0 -118
  139. package/tests/dml.test.ts +0 -206
  140. package/tests/exists.test.ts +0 -127
  141. package/tests/like.test.ts +0 -33
  142. package/tests/orm-runtime.test.ts +0 -254
  143. package/tests/postgres.test.ts +0 -30
  144. package/tests/right-join.test.ts +0 -89
  145. package/tests/subquery-having.test.ts +0 -193
  146. package/tests/window-function.test.ts +0 -151
  147. package/tsconfig.json +0 -30
  148. package/tsup.config.ts +0 -10
  149. package/vite.config.ts +0 -22
  150. package/vitest.config.ts +0 -14
  151. /package/src/{constants → core/sql}/sql.ts +0 -0
  152. /package/src/{runtime → orm}/als.ts +0 -0
  153. /package/src/{utils → query-builder}/relation-alias.ts +0 -0
@@ -0,0 +1,2530 @@
1
+ // src/schema/table.ts
2
+ var defineTable = (name, columns, relations = {}, hooks) => {
3
+ const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
4
+ acc[key] = { ...def, name: key, table: name };
5
+ return acc;
6
+ }, {});
7
+ return { name, columns: colsWithNames, relations, hooks };
8
+ };
9
+
10
+ // src/orm/entity-metadata.ts
11
+ var metadataMap = /* @__PURE__ */ new Map();
12
+ var ensureEntityMetadata = (target) => {
13
+ let meta = metadataMap.get(target);
14
+ if (!meta) {
15
+ meta = {
16
+ target,
17
+ tableName: target.name || "unknown",
18
+ columns: {},
19
+ relations: {}
20
+ };
21
+ metadataMap.set(target, meta);
22
+ }
23
+ return meta;
24
+ };
25
+ var getEntityMetadata = (target) => {
26
+ return metadataMap.get(target);
27
+ };
28
+ var getAllEntityMetadata = () => {
29
+ return Array.from(metadataMap.values());
30
+ };
31
+ var addColumnMetadata = (target, propertyKey, column) => {
32
+ const meta = ensureEntityMetadata(target);
33
+ meta.columns[propertyKey] = { ...column };
34
+ };
35
+ var addRelationMetadata = (target, propertyKey, relation) => {
36
+ const meta = ensureEntityMetadata(target);
37
+ meta.relations[propertyKey] = relation;
38
+ };
39
+ var setEntityTableName = (target, tableName, hooks) => {
40
+ const meta = ensureEntityMetadata(target);
41
+ if (tableName && tableName.length > 0) {
42
+ meta.tableName = tableName;
43
+ }
44
+ if (hooks) {
45
+ meta.hooks = hooks;
46
+ }
47
+ };
48
+ var buildTableDef = (meta) => {
49
+ if (meta.table) {
50
+ return meta.table;
51
+ }
52
+ const columns = Object.entries(meta.columns).reduce((acc, [key, def]) => {
53
+ acc[key] = {
54
+ ...def,
55
+ name: key,
56
+ table: meta.tableName
57
+ };
58
+ return acc;
59
+ }, {});
60
+ const table = defineTable(meta.tableName, columns, {}, meta.hooks);
61
+ meta.table = table;
62
+ return table;
63
+ };
64
+
65
+ // src/decorators/entity.ts
66
+ var toSnakeCase = (value) => {
67
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
68
+ };
69
+ var deriveTableNameFromConstructor = (ctor) => {
70
+ const fallback = "unknown";
71
+ const rawName = ctor.name || fallback;
72
+ const strippedName = rawName.replace(/Entity$/i, "");
73
+ const normalized = toSnakeCase(strippedName || rawName);
74
+ if (!normalized) {
75
+ return fallback;
76
+ }
77
+ return normalized.endsWith("s") ? normalized : `${normalized}s`;
78
+ };
79
+ function Entity(options = {}) {
80
+ const decorator = (value) => {
81
+ const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
82
+ setEntityTableName(value, tableName, options.hooks);
83
+ return value;
84
+ };
85
+ return decorator;
86
+ }
87
+
88
+ // src/decorators/column.ts
89
+ var normalizeColumnInput = (input) => {
90
+ const column = {
91
+ type: input.type ?? input.type,
92
+ args: input.args ?? input.args,
93
+ notNull: input.notNull ?? input.notNull,
94
+ primary: input.primary ?? input.primary
95
+ };
96
+ if (!column.type) {
97
+ throw new Error("Column decorator requires a column type");
98
+ }
99
+ return column;
100
+ };
101
+ var normalizePropertyName = (name) => {
102
+ if (typeof name === "symbol") {
103
+ return name.description ?? name.toString();
104
+ }
105
+ return name;
106
+ };
107
+ var resolveConstructor = (target) => {
108
+ if (typeof target === "function") {
109
+ return target;
110
+ }
111
+ if (target && typeof target.constructor === "function") {
112
+ return target.constructor;
113
+ }
114
+ return void 0;
115
+ };
116
+ var registerColumn = (ctor, propertyName, column) => {
117
+ const meta = ensureEntityMetadata(ctor);
118
+ if (meta.columns[propertyName]) {
119
+ return;
120
+ }
121
+ addColumnMetadata(ctor, propertyName, column);
122
+ };
123
+ function Column(definition) {
124
+ const normalized = normalizeColumnInput(definition);
125
+ const decorator = (target, propertyKey) => {
126
+ const propertyName = normalizePropertyName(propertyKey);
127
+ const ctor = resolveConstructor(target);
128
+ if (!ctor) {
129
+ throw new Error("Unable to resolve constructor when registering column metadata");
130
+ }
131
+ registerColumn(ctor, propertyName, { ...normalized });
132
+ };
133
+ return decorator;
134
+ }
135
+ function PrimaryKey(definition) {
136
+ const normalized = normalizeColumnInput(definition);
137
+ normalized.primary = true;
138
+ return Column(normalized);
139
+ }
140
+
141
+ // src/schema/relation.ts
142
+ var RelationKinds = {
143
+ /** One-to-many relationship */
144
+ HasMany: "HAS_MANY",
145
+ /** Many-to-one relationship */
146
+ BelongsTo: "BELONGS_TO",
147
+ /** Many-to-many relationship with pivot metadata */
148
+ BelongsToMany: "BELONGS_TO_MANY"
149
+ };
150
+ var hasMany = (target, foreignKey, localKey, cascade) => ({
151
+ type: RelationKinds.HasMany,
152
+ target,
153
+ foreignKey,
154
+ localKey,
155
+ cascade
156
+ });
157
+ var belongsTo = (target, foreignKey, localKey, cascade) => ({
158
+ type: RelationKinds.BelongsTo,
159
+ target,
160
+ foreignKey,
161
+ localKey,
162
+ cascade
163
+ });
164
+ var belongsToMany = (target, pivotTable, options) => ({
165
+ type: RelationKinds.BelongsToMany,
166
+ target,
167
+ pivotTable,
168
+ pivotForeignKeyToRoot: options.pivotForeignKeyToRoot,
169
+ pivotForeignKeyToTarget: options.pivotForeignKeyToTarget,
170
+ localKey: options.localKey,
171
+ targetKey: options.targetKey,
172
+ pivotPrimaryKey: options.pivotPrimaryKey,
173
+ defaultPivotColumns: options.defaultPivotColumns,
174
+ cascade: options.cascade
175
+ });
176
+
177
+ // src/decorators/relations.ts
178
+ var normalizePropertyName2 = (name) => {
179
+ if (typeof name === "symbol") {
180
+ return name.description ?? name.toString();
181
+ }
182
+ return name;
183
+ };
184
+ var resolveConstructor2 = (instanceOrCtor) => {
185
+ if (typeof instanceOrCtor === "function") {
186
+ return instanceOrCtor;
187
+ }
188
+ if (instanceOrCtor && typeof instanceOrCtor.constructor === "function") {
189
+ return instanceOrCtor.constructor;
190
+ }
191
+ return void 0;
192
+ };
193
+ var registerRelation = (ctor, propertyName, metadata) => {
194
+ addRelationMetadata(ctor, propertyName, metadata);
195
+ };
196
+ var createFieldDecorator = (metadataFactory) => {
197
+ const decorator = (target, propertyKey) => {
198
+ const propertyName = normalizePropertyName2(propertyKey);
199
+ const ctor = resolveConstructor2(target);
200
+ if (!ctor) {
201
+ throw new Error("Unable to resolve constructor when registering relation metadata");
202
+ }
203
+ registerRelation(ctor, propertyName, metadataFactory(propertyName));
204
+ };
205
+ return decorator;
206
+ };
207
+ function HasMany(options) {
208
+ return createFieldDecorator((propertyName) => ({
209
+ kind: RelationKinds.HasMany,
210
+ propertyKey: propertyName,
211
+ target: options.target,
212
+ foreignKey: options.foreignKey,
213
+ localKey: options.localKey,
214
+ cascade: options.cascade
215
+ }));
216
+ }
217
+ function BelongsTo(options) {
218
+ return createFieldDecorator((propertyName) => ({
219
+ kind: RelationKinds.BelongsTo,
220
+ propertyKey: propertyName,
221
+ target: options.target,
222
+ foreignKey: options.foreignKey,
223
+ localKey: options.localKey,
224
+ cascade: options.cascade
225
+ }));
226
+ }
227
+ function BelongsToMany(options) {
228
+ return createFieldDecorator((propertyName) => ({
229
+ kind: RelationKinds.BelongsToMany,
230
+ propertyKey: propertyName,
231
+ target: options.target,
232
+ pivotTable: options.pivotTable,
233
+ pivotForeignKeyToRoot: options.pivotForeignKeyToRoot,
234
+ pivotForeignKeyToTarget: options.pivotForeignKeyToTarget,
235
+ localKey: options.localKey,
236
+ targetKey: options.targetKey,
237
+ pivotPrimaryKey: options.pivotPrimaryKey,
238
+ defaultPivotColumns: options.defaultPivotColumns,
239
+ cascade: options.cascade
240
+ }));
241
+ }
242
+
243
+ // src/core/ast/expression-nodes.ts
244
+ var operandTypes = /* @__PURE__ */ new Set([
245
+ "Column",
246
+ "Literal",
247
+ "Function",
248
+ "JsonPath",
249
+ "ScalarSubquery",
250
+ "CaseExpression",
251
+ "WindowFunction"
252
+ ]);
253
+ var isOperandNode = (node) => node && operandTypes.has(node.type);
254
+ var isFunctionNode = (node) => node?.type === "Function";
255
+ var isCaseExpressionNode = (node) => node?.type === "CaseExpression";
256
+ var isWindowFunctionNode = (node) => node?.type === "WindowFunction";
257
+ var isExpressionSelectionNode = (node) => isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
258
+
259
+ // src/core/ast/expression-builders.ts
260
+ var toNode = (col) => {
261
+ if (isOperandNode(col)) return col;
262
+ const def = col;
263
+ return { type: "Column", table: def.table || "unknown", name: def.name };
264
+ };
265
+ var toLiteralNode = (value) => ({
266
+ type: "Literal",
267
+ value
268
+ });
269
+ var toOperand = (val) => {
270
+ if (val === null) return { type: "Literal", value: null };
271
+ if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
272
+ return { type: "Literal", value: val };
273
+ }
274
+ return toNode(val);
275
+ };
276
+ var columnOperand = (col) => toNode(col);
277
+ var createBinaryExpression = (operator, left, right, escape) => {
278
+ const node = {
279
+ type: "BinaryExpression",
280
+ left: toNode(left),
281
+ operator,
282
+ right: toOperand(right)
283
+ };
284
+ if (escape !== void 0) {
285
+ node.escape = toLiteralNode(escape);
286
+ }
287
+ return node;
288
+ };
289
+ var eq = (left, right) => createBinaryExpression("=", left, right);
290
+ var and = (...operands) => ({
291
+ type: "LogicalExpression",
292
+ operator: "AND",
293
+ operands
294
+ });
295
+ var createInExpression = (operator, left, values) => ({
296
+ type: "InExpression",
297
+ left: toNode(left),
298
+ operator,
299
+ right: values.map((v) => toOperand(v))
300
+ });
301
+ var inList = (left, values) => createInExpression("IN", left, values);
302
+ var exists = (subquery) => ({
303
+ type: "ExistsExpression",
304
+ operator: "EXISTS",
305
+ subquery
306
+ });
307
+ var notExists = (subquery) => ({
308
+ type: "ExistsExpression",
309
+ operator: "NOT EXISTS",
310
+ subquery
311
+ });
312
+
313
+ // src/core/ast/aggregate-functions.ts
314
+ var buildAggregate = (name) => (col) => ({
315
+ type: "Function",
316
+ name,
317
+ args: [columnOperand(col)]
318
+ });
319
+ var count = buildAggregate("COUNT");
320
+ var sum = buildAggregate("SUM");
321
+ var avg = buildAggregate("AVG");
322
+
323
+ // src/query-builder/select-query-state.ts
324
+ var SelectQueryState = class _SelectQueryState {
325
+ /**
326
+ * Creates a new SelectQueryState instance
327
+ * @param table - Table definition
328
+ * @param ast - Optional existing AST
329
+ */
330
+ constructor(table, ast) {
331
+ this.table = table;
332
+ this.ast = ast ?? {
333
+ type: "SelectQuery",
334
+ from: { type: "Table", name: table.name },
335
+ columns: [],
336
+ joins: []
337
+ };
338
+ }
339
+ /**
340
+ * Creates a new SelectQueryState with updated AST
341
+ * @param nextAst - Updated AST
342
+ * @returns New SelectQueryState instance
343
+ */
344
+ clone(nextAst) {
345
+ return new _SelectQueryState(this.table, nextAst);
346
+ }
347
+ /**
348
+ * Adds columns to the query
349
+ * @param newCols - Columns to add
350
+ * @returns New SelectQueryState with added columns
351
+ */
352
+ withColumns(newCols) {
353
+ return this.clone({
354
+ ...this.ast,
355
+ columns: [...this.ast.columns ?? [], ...newCols]
356
+ });
357
+ }
358
+ /**
359
+ * Adds a join to the query
360
+ * @param join - Join node to add
361
+ * @returns New SelectQueryState with added join
362
+ */
363
+ withJoin(join) {
364
+ return this.clone({
365
+ ...this.ast,
366
+ joins: [...this.ast.joins ?? [], join]
367
+ });
368
+ }
369
+ /**
370
+ * Adds a WHERE clause to the query
371
+ * @param predicate - WHERE predicate expression
372
+ * @returns New SelectQueryState with WHERE clause
373
+ */
374
+ withWhere(predicate) {
375
+ return this.clone({
376
+ ...this.ast,
377
+ where: predicate
378
+ });
379
+ }
380
+ /**
381
+ * Adds a HAVING clause to the query
382
+ * @param predicate - HAVING predicate expression
383
+ * @returns New SelectQueryState with HAVING clause
384
+ */
385
+ withHaving(predicate) {
386
+ return this.clone({
387
+ ...this.ast,
388
+ having: predicate
389
+ });
390
+ }
391
+ /**
392
+ * Adds GROUP BY columns to the query
393
+ * @param columns - Columns to group by
394
+ * @returns New SelectQueryState with GROUP BY clause
395
+ */
396
+ withGroupBy(columns) {
397
+ return this.clone({
398
+ ...this.ast,
399
+ groupBy: [...this.ast.groupBy ?? [], ...columns]
400
+ });
401
+ }
402
+ /**
403
+ * Adds ORDER BY clauses to the query
404
+ * @param orderBy - ORDER BY nodes
405
+ * @returns New SelectQueryState with ORDER BY clause
406
+ */
407
+ withOrderBy(orderBy) {
408
+ return this.clone({
409
+ ...this.ast,
410
+ orderBy: [...this.ast.orderBy ?? [], ...orderBy]
411
+ });
412
+ }
413
+ /**
414
+ * Adds DISTINCT columns to the query
415
+ * @param columns - Columns to make distinct
416
+ * @returns New SelectQueryState with DISTINCT clause
417
+ */
418
+ withDistinct(columns) {
419
+ return this.clone({
420
+ ...this.ast,
421
+ distinct: [...this.ast.distinct ?? [], ...columns]
422
+ });
423
+ }
424
+ /**
425
+ * Adds a LIMIT clause to the query
426
+ * @param limit - Maximum number of rows to return
427
+ * @returns New SelectQueryState with LIMIT clause
428
+ */
429
+ withLimit(limit) {
430
+ return this.clone({
431
+ ...this.ast,
432
+ limit
433
+ });
434
+ }
435
+ /**
436
+ * Adds an OFFSET clause to the query
437
+ * @param offset - Number of rows to skip
438
+ * @returns New SelectQueryState with OFFSET clause
439
+ */
440
+ withOffset(offset) {
441
+ return this.clone({
442
+ ...this.ast,
443
+ offset
444
+ });
445
+ }
446
+ /**
447
+ * Adds a Common Table Expression (CTE) to the query
448
+ * @param cte - CTE node to add
449
+ * @returns New SelectQueryState with CTE
450
+ */
451
+ withCte(cte) {
452
+ return this.clone({
453
+ ...this.ast,
454
+ ctes: [...this.ast.ctes ?? [], cte]
455
+ });
456
+ }
457
+ };
458
+
459
+ // src/query-builder/hydration-manager.ts
460
+ var HydrationManager = class _HydrationManager {
461
+ /**
462
+ * Creates a new HydrationManager instance
463
+ * @param table - Table definition
464
+ * @param planner - Hydration planner
465
+ */
466
+ constructor(table, planner) {
467
+ this.table = table;
468
+ this.planner = planner;
469
+ }
470
+ /**
471
+ * Creates a new HydrationManager with updated planner
472
+ * @param nextPlanner - Updated hydration planner
473
+ * @returns New HydrationManager instance
474
+ */
475
+ clone(nextPlanner) {
476
+ return new _HydrationManager(this.table, nextPlanner);
477
+ }
478
+ /**
479
+ * Handles column selection for hydration planning
480
+ * @param state - Current query state
481
+ * @param newColumns - Newly selected columns
482
+ * @returns Updated HydrationManager with captured columns
483
+ */
484
+ onColumnsSelected(state, newColumns) {
485
+ const updated = this.planner.captureRootColumns(newColumns);
486
+ return this.clone(updated);
487
+ }
488
+ /**
489
+ * Handles relation inclusion for hydration planning
490
+ * @param state - Current query state
491
+ * @param relation - Relation definition
492
+ * @param relationName - Name of the relation
493
+ * @param aliasPrefix - Alias prefix for the relation
494
+ * @param targetColumns - Target columns to include
495
+ * @returns Updated HydrationManager with included relation
496
+ */
497
+ onRelationIncluded(state, relation, relationName, aliasPrefix, targetColumns, pivot) {
498
+ const withRoots = this.planner.captureRootColumns(state.ast.columns);
499
+ const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
500
+ return this.clone(next);
501
+ }
502
+ /**
503
+ * Applies hydration plan to the AST
504
+ * @param ast - Query AST to modify
505
+ * @returns AST with hydration metadata
506
+ */
507
+ applyToAst(ast) {
508
+ const plan = this.planner.getPlan();
509
+ if (!plan) return ast;
510
+ return {
511
+ ...ast,
512
+ meta: {
513
+ ...ast.meta || {},
514
+ hydration: plan
515
+ }
516
+ };
517
+ }
518
+ /**
519
+ * Gets the current hydration plan
520
+ * @returns Hydration plan or undefined if none exists
521
+ */
522
+ getPlan() {
523
+ return this.planner.getPlan();
524
+ }
525
+ };
526
+
527
+ // src/query-builder/relation-alias.ts
528
+ var RELATION_SEPARATOR = "__";
529
+ var makeRelationAlias = (relationName, columnName) => `${relationName}${RELATION_SEPARATOR}${columnName}`;
530
+ var isRelationAlias = (alias) => !!alias && alias.includes(RELATION_SEPARATOR);
531
+
532
+ // src/query-builder/relation-utils.ts
533
+ var buildDefaultPivotColumns = (rel, pivotPk) => {
534
+ const excluded = /* @__PURE__ */ new Set([pivotPk, rel.pivotForeignKeyToRoot, rel.pivotForeignKeyToTarget]);
535
+ return Object.keys(rel.pivotTable.columns).filter((col) => !excluded.has(col));
536
+ };
537
+
538
+ // src/query-builder/hydration-planner.ts
539
+ var findPrimaryKey = (table) => {
540
+ const pk = Object.values(table.columns).find((c) => c.primary);
541
+ return pk?.name || "id";
542
+ };
543
+ var HydrationPlanner = class _HydrationPlanner {
544
+ /**
545
+ * Creates a new HydrationPlanner instance
546
+ * @param table - Table definition
547
+ * @param plan - Optional existing hydration plan
548
+ */
549
+ constructor(table, plan) {
550
+ this.table = table;
551
+ this.plan = plan;
552
+ }
553
+ /**
554
+ * Captures root table columns for hydration planning
555
+ * @param columns - Columns to capture
556
+ * @returns Updated HydrationPlanner with captured columns
557
+ */
558
+ captureRootColumns(columns) {
559
+ const currentPlan = this.getPlanOrDefault();
560
+ const rootCols = new Set(currentPlan.rootColumns);
561
+ let changed = false;
562
+ columns.forEach((node) => {
563
+ if (node.type !== "Column") return;
564
+ if (node.table !== this.table.name) return;
565
+ const alias = node.alias || node.name;
566
+ if (isRelationAlias(alias)) return;
567
+ if (!rootCols.has(alias)) {
568
+ rootCols.add(alias);
569
+ changed = true;
570
+ }
571
+ });
572
+ if (!changed) return this;
573
+ return new _HydrationPlanner(this.table, {
574
+ ...currentPlan,
575
+ rootColumns: Array.from(rootCols)
576
+ });
577
+ }
578
+ /**
579
+ * Includes a relation in the hydration plan
580
+ * @param rel - Relation definition
581
+ * @param relationName - Name of the relation
582
+ * @param aliasPrefix - Alias prefix for relation columns
583
+ * @param columns - Columns to include from the relation
584
+ * @returns Updated HydrationPlanner with included relation
585
+ */
586
+ includeRelation(rel, relationName, aliasPrefix, columns, pivot) {
587
+ const currentPlan = this.getPlanOrDefault();
588
+ const relations = currentPlan.relations.filter((r) => r.name !== relationName);
589
+ relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
590
+ return new _HydrationPlanner(this.table, {
591
+ ...currentPlan,
592
+ relations
593
+ });
594
+ }
595
+ /**
596
+ * Gets the current hydration plan
597
+ * @returns Current hydration plan or undefined
598
+ */
599
+ getPlan() {
600
+ return this.plan;
601
+ }
602
+ /**
603
+ * Gets the current hydration plan or creates a default one
604
+ * @returns Current hydration plan or default plan
605
+ */
606
+ getPlanOrDefault() {
607
+ return this.plan ?? buildDefaultHydrationPlan(this.table);
608
+ }
609
+ /**
610
+ * Builds a relation plan for hydration
611
+ * @param rel - Relation definition
612
+ * @param relationName - Name of the relation
613
+ * @param aliasPrefix - Alias prefix for relation columns
614
+ * @param columns - Columns to include from the relation
615
+ * @returns Hydration relation plan
616
+ */
617
+ buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot) {
618
+ switch (rel.type) {
619
+ case RelationKinds.HasMany: {
620
+ const localKey = rel.localKey || findPrimaryKey(this.table);
621
+ return {
622
+ name: relationName,
623
+ aliasPrefix,
624
+ type: rel.type,
625
+ targetTable: rel.target.name,
626
+ targetPrimaryKey: findPrimaryKey(rel.target),
627
+ foreignKey: rel.foreignKey,
628
+ localKey,
629
+ columns
630
+ };
631
+ }
632
+ case RelationKinds.BelongsTo: {
633
+ const localKey = rel.localKey || findPrimaryKey(rel.target);
634
+ return {
635
+ name: relationName,
636
+ aliasPrefix,
637
+ type: rel.type,
638
+ targetTable: rel.target.name,
639
+ targetPrimaryKey: findPrimaryKey(rel.target),
640
+ foreignKey: rel.foreignKey,
641
+ localKey,
642
+ columns
643
+ };
644
+ }
645
+ case RelationKinds.BelongsToMany: {
646
+ const many = rel;
647
+ const localKey = many.localKey || findPrimaryKey(this.table);
648
+ const targetPk = many.targetKey || findPrimaryKey(many.target);
649
+ const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
650
+ const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
651
+ const pivotColumns = pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
652
+ return {
653
+ name: relationName,
654
+ aliasPrefix,
655
+ type: rel.type,
656
+ targetTable: many.target.name,
657
+ targetPrimaryKey: targetPk,
658
+ foreignKey: many.pivotForeignKeyToRoot,
659
+ localKey,
660
+ columns,
661
+ pivot: {
662
+ table: many.pivotTable.name,
663
+ primaryKey: pivotPk,
664
+ aliasPrefix: pivotAliasPrefix,
665
+ columns: pivotColumns
666
+ }
667
+ };
668
+ }
669
+ }
670
+ }
671
+ };
672
+ var buildDefaultHydrationPlan = (table) => ({
673
+ rootTable: table.name,
674
+ rootPrimaryKey: findPrimaryKey(table),
675
+ rootColumns: [],
676
+ relations: []
677
+ });
678
+
679
+ // src/core/ast/builders.ts
680
+ var buildColumnNode = (table, column) => {
681
+ if (column.type === "Column") {
682
+ return column;
683
+ }
684
+ const def = column;
685
+ return {
686
+ type: "Column",
687
+ table: def.table || table.name,
688
+ name: def.name
689
+ };
690
+ };
691
+
692
+ // src/query-builder/raw-column-parser.ts
693
+ var parseRawColumn = (col, tableName, ctes) => {
694
+ if (col.includes("(")) {
695
+ const [fn, rest] = col.split("(");
696
+ const colName = rest.replace(")", "");
697
+ const [table, name] = colName.includes(".") ? colName.split(".") : [tableName, colName];
698
+ return { type: "Column", table, name, alias: col };
699
+ }
700
+ if (col.includes(".")) {
701
+ const [potentialCteName, columnName] = col.split(".");
702
+ const hasCte = ctes?.some((cte) => cte.name === potentialCteName);
703
+ if (hasCte) {
704
+ return { type: "Column", table: tableName, name: col };
705
+ }
706
+ return { type: "Column", table: potentialCteName, name: columnName };
707
+ }
708
+ return { type: "Column", table: tableName, name: col };
709
+ };
710
+
711
+ // src/query-builder/query-ast-service.ts
712
+ var QueryAstService = class {
713
+ /**
714
+ * Creates a new QueryAstService instance
715
+ * @param table - Table definition
716
+ * @param state - Current query state
717
+ */
718
+ constructor(table, state) {
719
+ this.table = table;
720
+ this.state = state;
721
+ }
722
+ /**
723
+ * Selects columns for the query
724
+ * @param columns - Columns to select (key: alias, value: column definition or expression)
725
+ * @returns Column selection result with updated state and added columns
726
+ */
727
+ select(columns) {
728
+ const existingAliases = new Set(
729
+ this.state.ast.columns.map((c) => c.alias || c.name)
730
+ );
731
+ const newCols = Object.entries(columns).reduce((acc, [alias, val]) => {
732
+ if (existingAliases.has(alias)) return acc;
733
+ if (isExpressionSelectionNode(val)) {
734
+ acc.push({ ...val, alias });
735
+ return acc;
736
+ }
737
+ const colDef = val;
738
+ acc.push({
739
+ type: "Column",
740
+ table: colDef.table || this.table.name,
741
+ name: colDef.name,
742
+ alias
743
+ });
744
+ return acc;
745
+ }, []);
746
+ const nextState = this.state.withColumns(newCols);
747
+ return { state: nextState, addedColumns: newCols };
748
+ }
749
+ /**
750
+ * Selects raw column expressions (best-effort parser for simple references/functions)
751
+ * @param cols - Raw column expressions
752
+ * @returns Column selection result with updated state and added columns
753
+ */
754
+ selectRaw(cols) {
755
+ const newCols = cols.map((col) => parseRawColumn(col, this.table.name, this.state.ast.ctes));
756
+ const nextState = this.state.withColumns(newCols);
757
+ return { state: nextState, addedColumns: newCols };
758
+ }
759
+ /**
760
+ * Adds a Common Table Expression (CTE) to the query
761
+ * @param name - Name of the CTE
762
+ * @param query - Query for the CTE
763
+ * @param columns - Optional column names for the CTE
764
+ * @param recursive - Whether the CTE is recursive
765
+ * @returns Updated query state with CTE
766
+ */
767
+ withCte(name, query, columns, recursive = false) {
768
+ const cte = {
769
+ type: "CommonTableExpression",
770
+ name,
771
+ query,
772
+ columns,
773
+ recursive
774
+ };
775
+ return this.state.withCte(cte);
776
+ }
777
+ /**
778
+ * Selects a subquery as a column
779
+ * @param alias - Alias for the subquery
780
+ * @param query - Subquery to select
781
+ * @returns Updated query state with subquery selection
782
+ */
783
+ selectSubquery(alias, query) {
784
+ const node = { type: "ScalarSubquery", query, alias };
785
+ return this.state.withColumns([node]);
786
+ }
787
+ /**
788
+ * Adds a JOIN clause to the query
789
+ * @param join - Join node to add
790
+ * @returns Updated query state with JOIN
791
+ */
792
+ withJoin(join) {
793
+ return this.state.withJoin(join);
794
+ }
795
+ /**
796
+ * Adds a WHERE clause to the query
797
+ * @param expr - Expression for the WHERE clause
798
+ * @returns Updated query state with WHERE clause
799
+ */
800
+ withWhere(expr) {
801
+ const combined = this.combineExpressions(this.state.ast.where, expr);
802
+ return this.state.withWhere(combined);
803
+ }
804
+ /**
805
+ * Adds a GROUP BY clause to the query
806
+ * @param col - Column to group by
807
+ * @returns Updated query state with GROUP BY clause
808
+ */
809
+ withGroupBy(col) {
810
+ const node = buildColumnNode(this.table, col);
811
+ return this.state.withGroupBy([node]);
812
+ }
813
+ /**
814
+ * Adds a HAVING clause to the query
815
+ * @param expr - Expression for the HAVING clause
816
+ * @returns Updated query state with HAVING clause
817
+ */
818
+ withHaving(expr) {
819
+ const combined = this.combineExpressions(this.state.ast.having, expr);
820
+ return this.state.withHaving(combined);
821
+ }
822
+ /**
823
+ * Adds an ORDER BY clause to the query
824
+ * @param col - Column to order by
825
+ * @param direction - Order direction (ASC/DESC)
826
+ * @returns Updated query state with ORDER BY clause
827
+ */
828
+ withOrderBy(col, direction) {
829
+ const node = buildColumnNode(this.table, col);
830
+ return this.state.withOrderBy([{ type: "OrderBy", column: node, direction }]);
831
+ }
832
+ /**
833
+ * Adds a DISTINCT clause to the query
834
+ * @param cols - Columns to make distinct
835
+ * @returns Updated query state with DISTINCT clause
836
+ */
837
+ withDistinct(cols) {
838
+ return this.state.withDistinct(cols);
839
+ }
840
+ /**
841
+ * Adds a LIMIT clause to the query
842
+ * @param limit - Maximum number of rows to return
843
+ * @returns Updated query state with LIMIT clause
844
+ */
845
+ withLimit(limit) {
846
+ return this.state.withLimit(limit);
847
+ }
848
+ /**
849
+ * Adds an OFFSET clause to the query
850
+ * @param offset - Number of rows to skip
851
+ * @returns Updated query state with OFFSET clause
852
+ */
853
+ withOffset(offset) {
854
+ return this.state.withOffset(offset);
855
+ }
856
+ /**
857
+ * Combines expressions with AND operator
858
+ * @param existing - Existing expression
859
+ * @param next - New expression to combine
860
+ * @returns Combined expression
861
+ */
862
+ combineExpressions(existing, next) {
863
+ return existing ? and(existing, next) : next;
864
+ }
865
+ };
866
+
867
+ // src/query-builder/relation-projection-helper.ts
868
+ var RelationProjectionHelper = class {
869
+ /**
870
+ * Creates a new RelationProjectionHelper instance
871
+ * @param table - Table definition
872
+ * @param selectColumns - Callback for selecting columns
873
+ */
874
+ constructor(table, selectColumns) {
875
+ this.table = table;
876
+ this.selectColumns = selectColumns;
877
+ }
878
+ /**
879
+ * Ensures base projection is included in the query
880
+ * @param state - Current query state
881
+ * @param hydration - Hydration manager
882
+ * @returns Relation result with updated state and hydration
883
+ */
884
+ ensureBaseProjection(state, hydration) {
885
+ const primaryKey = findPrimaryKey(this.table);
886
+ if (!this.hasBaseProjection(state)) {
887
+ return this.selectColumns(state, hydration, this.getBaseColumns());
888
+ }
889
+ if (primaryKey && !this.hasPrimarySelected(state, primaryKey) && this.table.columns[primaryKey]) {
890
+ return this.selectColumns(state, hydration, {
891
+ [primaryKey]: this.table.columns[primaryKey]
892
+ });
893
+ }
894
+ return { state, hydration };
895
+ }
896
+ /**
897
+ * Checks if base projection exists in the query
898
+ * @param state - Current query state
899
+ * @returns True if base projection exists
900
+ */
901
+ hasBaseProjection(state) {
902
+ return state.ast.columns.some((col) => !isRelationAlias(col.alias));
903
+ }
904
+ /**
905
+ * Checks if primary key is selected in the query
906
+ * @param state - Current query state
907
+ * @param primaryKey - Primary key name
908
+ * @returns True if primary key is selected
909
+ */
910
+ hasPrimarySelected(state, primaryKey) {
911
+ return state.ast.columns.some((col) => {
912
+ const alias = col.alias;
913
+ const name = alias || col.name;
914
+ return !isRelationAlias(alias) && name === primaryKey;
915
+ });
916
+ }
917
+ /**
918
+ * Gets all base columns for the table
919
+ * @returns Record of all table columns
920
+ */
921
+ getBaseColumns() {
922
+ return Object.keys(this.table.columns).reduce((acc, key) => {
923
+ acc[key] = this.table.columns[key];
924
+ return acc;
925
+ }, {});
926
+ }
927
+ };
928
+
929
+ // src/core/ast/join-node.ts
930
+ var createJoinNode = (kind, tableName, condition, relationName) => ({
931
+ type: "Join",
932
+ kind,
933
+ table: { type: "Table", name: tableName },
934
+ condition,
935
+ relationName
936
+ });
937
+
938
+ // src/query-builder/relation-conditions.ts
939
+ var assertNever = (value) => {
940
+ throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
941
+ };
942
+ var baseRelationCondition = (root, relation) => {
943
+ const defaultLocalKey = relation.type === RelationKinds.HasMany ? findPrimaryKey(root) : findPrimaryKey(relation.target);
944
+ const localKey = relation.localKey || defaultLocalKey;
945
+ switch (relation.type) {
946
+ case RelationKinds.HasMany:
947
+ return eq(
948
+ { type: "Column", table: relation.target.name, name: relation.foreignKey },
949
+ { type: "Column", table: root.name, name: localKey }
950
+ );
951
+ case RelationKinds.BelongsTo:
952
+ return eq(
953
+ { type: "Column", table: relation.target.name, name: localKey },
954
+ { type: "Column", table: root.name, name: relation.foreignKey }
955
+ );
956
+ case RelationKinds.BelongsToMany:
957
+ throw new Error("BelongsToMany relations do not support the standard join condition builder");
958
+ default:
959
+ return assertNever(relation);
960
+ }
961
+ };
962
+ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra) => {
963
+ const rootKey = relation.localKey || findPrimaryKey(root);
964
+ const targetKey = relation.targetKey || findPrimaryKey(relation.target);
965
+ const pivotCondition = eq(
966
+ { type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
967
+ { type: "Column", table: root.name, name: rootKey }
968
+ );
969
+ const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
970
+ let targetCondition = eq(
971
+ { type: "Column", table: relation.target.name, name: targetKey },
972
+ { type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
973
+ );
974
+ if (extra) {
975
+ targetCondition = and(targetCondition, extra);
976
+ }
977
+ const targetJoin = createJoinNode(
978
+ joinKind,
979
+ relation.target.name,
980
+ targetCondition,
981
+ relationName
982
+ );
983
+ return [pivotJoin, targetJoin];
984
+ };
985
+ var buildRelationJoinCondition = (root, relation, extra) => {
986
+ const base = baseRelationCondition(root, relation);
987
+ return extra ? and(base, extra) : base;
988
+ };
989
+ var buildRelationCorrelation = (root, relation) => {
990
+ return baseRelationCondition(root, relation);
991
+ };
992
+
993
+ // src/core/sql/sql.ts
994
+ var JOIN_KINDS = {
995
+ /** INNER JOIN type */
996
+ INNER: "INNER",
997
+ /** LEFT JOIN type */
998
+ LEFT: "LEFT",
999
+ /** RIGHT JOIN type */
1000
+ RIGHT: "RIGHT",
1001
+ /** CROSS JOIN type */
1002
+ CROSS: "CROSS"
1003
+ };
1004
+ var ORDER_DIRECTIONS = {
1005
+ /** Ascending order */
1006
+ ASC: "ASC",
1007
+ /** Descending order */
1008
+ DESC: "DESC"
1009
+ };
1010
+
1011
+ // src/query-builder/relation-service.ts
1012
+ var RelationService = class {
1013
+ /**
1014
+ * Creates a new RelationService instance
1015
+ * @param table - Table definition
1016
+ * @param state - Current query state
1017
+ * @param hydration - Hydration manager
1018
+ */
1019
+ constructor(table, state, hydration, createQueryAstService) {
1020
+ this.table = table;
1021
+ this.state = state;
1022
+ this.hydration = hydration;
1023
+ this.createQueryAstService = createQueryAstService;
1024
+ this.projectionHelper = new RelationProjectionHelper(
1025
+ table,
1026
+ (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
1027
+ );
1028
+ }
1029
+ /**
1030
+ * Joins a relation to the query
1031
+ * @param relationName - Name of the relation to join
1032
+ * @param joinKind - Type of join to use
1033
+ * @param extraCondition - Additional join condition
1034
+ * @returns Relation result with updated state and hydration
1035
+ */
1036
+ joinRelation(relationName, joinKind, extraCondition) {
1037
+ const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
1038
+ return { state: nextState, hydration: this.hydration };
1039
+ }
1040
+ /**
1041
+ * Matches records based on a relation with an optional predicate
1042
+ * @param relationName - Name of the relation to match
1043
+ * @param predicate - Optional predicate expression
1044
+ * @returns Relation result with updated state and hydration
1045
+ */
1046
+ match(relationName, predicate) {
1047
+ const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
1048
+ const pk = findPrimaryKey(this.table);
1049
+ const distinctCols = [{ type: "Column", table: this.table.name, name: pk }];
1050
+ const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
1051
+ const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
1052
+ return { state: nextState, hydration: joined.hydration };
1053
+ }
1054
+ /**
1055
+ * Includes a relation in the query result
1056
+ * @param relationName - Name of the relation to include
1057
+ * @param options - Options for relation inclusion
1058
+ * @returns Relation result with updated state and hydration
1059
+ */
1060
+ include(relationName, options) {
1061
+ let state = this.state;
1062
+ let hydration = this.hydration;
1063
+ const relation = this.getRelation(relationName);
1064
+ const aliasPrefix = options?.aliasPrefix ?? relationName;
1065
+ const alreadyJoined = state.ast.joins.some((j) => j.relationName === relationName);
1066
+ if (!alreadyJoined) {
1067
+ const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
1068
+ state = joined.state;
1069
+ }
1070
+ const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
1071
+ state = projectionResult.state;
1072
+ hydration = projectionResult.hydration;
1073
+ const targetColumns = options?.columns?.length ? options.columns : Object.keys(relation.target.columns);
1074
+ const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
1075
+ return keys.reduce((acc, key) => {
1076
+ const def = columns[key];
1077
+ if (!def) {
1078
+ throw new Error(missingMsg(key));
1079
+ }
1080
+ acc[makeRelationAlias(prefix, key)] = def;
1081
+ return acc;
1082
+ }, {});
1083
+ };
1084
+ const targetSelection = buildTypedSelection(
1085
+ relation.target.columns,
1086
+ aliasPrefix,
1087
+ targetColumns,
1088
+ (key) => `Column '${key}' not found on relation '${relationName}'`
1089
+ );
1090
+ if (relation.type !== RelationKinds.BelongsToMany) {
1091
+ const relationSelectionResult2 = this.selectColumns(state, hydration, targetSelection);
1092
+ state = relationSelectionResult2.state;
1093
+ hydration = relationSelectionResult2.hydration;
1094
+ hydration = hydration.onRelationIncluded(
1095
+ state,
1096
+ relation,
1097
+ relationName,
1098
+ aliasPrefix,
1099
+ targetColumns
1100
+ );
1101
+ return { state, hydration };
1102
+ }
1103
+ const many = relation;
1104
+ const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
1105
+ const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
1106
+ const pivotColumns = options?.pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
1107
+ const pivotSelection = buildTypedSelection(
1108
+ many.pivotTable.columns,
1109
+ pivotAliasPrefix,
1110
+ pivotColumns,
1111
+ (key) => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
1112
+ );
1113
+ const combinedSelection = {
1114
+ ...targetSelection,
1115
+ ...pivotSelection
1116
+ };
1117
+ const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
1118
+ state = relationSelectionResult.state;
1119
+ hydration = relationSelectionResult.hydration;
1120
+ hydration = hydration.onRelationIncluded(
1121
+ state,
1122
+ relation,
1123
+ relationName,
1124
+ aliasPrefix,
1125
+ targetColumns,
1126
+ { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
1127
+ );
1128
+ return { state, hydration };
1129
+ }
1130
+ /**
1131
+ * Applies relation correlation to a query AST
1132
+ * @param relationName - Name of the relation
1133
+ * @param ast - Query AST to modify
1134
+ * @returns Modified query AST with relation correlation
1135
+ */
1136
+ applyRelationCorrelation(relationName, ast) {
1137
+ const relation = this.getRelation(relationName);
1138
+ const correlation = buildRelationCorrelation(this.table, relation);
1139
+ const whereInSubquery = ast.where ? and(correlation, ast.where) : correlation;
1140
+ return {
1141
+ ...ast,
1142
+ where: whereInSubquery
1143
+ };
1144
+ }
1145
+ /**
1146
+ * Creates a join node for a relation
1147
+ * @param state - Current query state
1148
+ * @param relationName - Name of the relation
1149
+ * @param joinKind - Type of join to use
1150
+ * @param extraCondition - Additional join condition
1151
+ * @returns Updated query state with join
1152
+ */
1153
+ withJoin(state, relationName, joinKind, extraCondition) {
1154
+ const relation = this.getRelation(relationName);
1155
+ if (relation.type === RelationKinds.BelongsToMany) {
1156
+ const joins = buildBelongsToManyJoins(
1157
+ this.table,
1158
+ relationName,
1159
+ relation,
1160
+ joinKind,
1161
+ extraCondition
1162
+ );
1163
+ return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
1164
+ }
1165
+ const condition = buildRelationJoinCondition(this.table, relation, extraCondition);
1166
+ const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
1167
+ return this.astService(state).withJoin(joinNode);
1168
+ }
1169
+ /**
1170
+ * Selects columns for a relation
1171
+ * @param state - Current query state
1172
+ * @param hydration - Hydration manager
1173
+ * @param columns - Columns to select
1174
+ * @returns Relation result with updated state and hydration
1175
+ */
1176
+ selectColumns(state, hydration, columns) {
1177
+ const { state: nextState, addedColumns } = this.astService(state).select(columns);
1178
+ return {
1179
+ state: nextState,
1180
+ hydration: hydration.onColumnsSelected(nextState, addedColumns)
1181
+ };
1182
+ }
1183
+ /**
1184
+ * Gets a relation definition by name
1185
+ * @param relationName - Name of the relation
1186
+ * @returns Relation definition
1187
+ * @throws Error if relation is not found
1188
+ */
1189
+ getRelation(relationName) {
1190
+ const relation = this.table.relations[relationName];
1191
+ if (!relation) {
1192
+ throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
1193
+ }
1194
+ return relation;
1195
+ }
1196
+ /**
1197
+ * Creates a QueryAstService instance
1198
+ * @param state - Current query state
1199
+ * @returns QueryAstService instance
1200
+ */
1201
+ astService(state = this.state) {
1202
+ return this.createQueryAstService(this.table, state);
1203
+ }
1204
+ };
1205
+
1206
+ // src/query-builder/select-query-builder-deps.ts
1207
+ var defaultCreateQueryAstService = (table, state) => new QueryAstService(table, state);
1208
+ var defaultCreateHydrationPlanner = (table) => new HydrationPlanner(table);
1209
+ var defaultCreateHydration = (table, plannerFactory) => new HydrationManager(table, plannerFactory(table));
1210
+ var resolveSelectQueryBuilderDependencies = (overrides = {}) => {
1211
+ const createQueryAstService = overrides.createQueryAstService ?? defaultCreateQueryAstService;
1212
+ const createHydrationPlanner = overrides.createHydrationPlanner ?? defaultCreateHydrationPlanner;
1213
+ const createHydration = overrides.createHydration ?? ((table) => defaultCreateHydration(table, createHydrationPlanner));
1214
+ const createRelationService = overrides.createRelationService ?? ((table, state, hydration) => new RelationService(table, state, hydration, createQueryAstService));
1215
+ return {
1216
+ createState: overrides.createState ?? ((table) => new SelectQueryState(table)),
1217
+ createHydration,
1218
+ createHydrationPlanner,
1219
+ createQueryAstService,
1220
+ createRelationService
1221
+ };
1222
+ };
1223
+ var defaultSelectQueryBuilderDependencies = resolveSelectQueryBuilderDependencies();
1224
+
1225
+ // src/query-builder/column-selector.ts
1226
+ var ColumnSelector = class {
1227
+ /**
1228
+ * Creates a new ColumnSelector instance
1229
+ * @param env - Query builder environment
1230
+ */
1231
+ constructor(env) {
1232
+ this.env = env;
1233
+ }
1234
+ /**
1235
+ * Selects columns for the query
1236
+ * @param context - Current query context
1237
+ * @param columns - Columns to select
1238
+ * @returns Updated query context with selected columns
1239
+ */
1240
+ select(context, columns) {
1241
+ const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
1242
+ const { state: nextState, addedColumns } = astService.select(columns);
1243
+ return {
1244
+ state: nextState,
1245
+ hydration: context.hydration.onColumnsSelected(nextState, addedColumns)
1246
+ };
1247
+ }
1248
+ /**
1249
+ * Selects raw column expressions
1250
+ * @param context - Current query context
1251
+ * @param columns - Raw column expressions
1252
+ * @returns Updated query context with raw column selections
1253
+ */
1254
+ selectRaw(context, columns) {
1255
+ const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
1256
+ const nextState = astService.selectRaw(columns).state;
1257
+ return { state: nextState, hydration: context.hydration };
1258
+ }
1259
+ /**
1260
+ * Selects a subquery as a column
1261
+ * @param context - Current query context
1262
+ * @param alias - Alias for the subquery
1263
+ * @param query - Subquery to select
1264
+ * @returns Updated query context with subquery selection
1265
+ */
1266
+ selectSubquery(context, alias, query) {
1267
+ const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
1268
+ const nextState = astService.selectSubquery(alias, query);
1269
+ return { state: nextState, hydration: context.hydration };
1270
+ }
1271
+ /**
1272
+ * Adds DISTINCT clause to the query
1273
+ * @param context - Current query context
1274
+ * @param columns - Columns to make distinct
1275
+ * @returns Updated query context with DISTINCT clause
1276
+ */
1277
+ distinct(context, columns) {
1278
+ const nodes = columns.map((col) => buildColumnNode(this.env.table, col));
1279
+ const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
1280
+ const nextState = astService.withDistinct(nodes);
1281
+ return { state: nextState, hydration: context.hydration };
1282
+ }
1283
+ };
1284
+
1285
+ // src/query-builder/relation-manager.ts
1286
+ var RelationManager = class {
1287
+ /**
1288
+ * Creates a new RelationManager instance
1289
+ * @param env - Query builder environment
1290
+ */
1291
+ constructor(env) {
1292
+ this.env = env;
1293
+ }
1294
+ /**
1295
+ * Matches records based on a relation with an optional predicate
1296
+ * @param context - Current query context
1297
+ * @param relationName - Name of the relation to match
1298
+ * @param predicate - Optional predicate expression
1299
+ * @returns Updated query context with relation match
1300
+ */
1301
+ match(context, relationName, predicate) {
1302
+ const result = this.createService(context).match(relationName, predicate);
1303
+ return { state: result.state, hydration: result.hydration };
1304
+ }
1305
+ /**
1306
+ * Joins a relation to the query
1307
+ * @param context - Current query context
1308
+ * @param relationName - Name of the relation to join
1309
+ * @param joinKind - Type of join to use
1310
+ * @param extraCondition - Additional join condition
1311
+ * @returns Updated query context with relation join
1312
+ */
1313
+ joinRelation(context, relationName, joinKind, extraCondition) {
1314
+ const result = this.createService(context).joinRelation(relationName, joinKind, extraCondition);
1315
+ return { state: result.state, hydration: result.hydration };
1316
+ }
1317
+ /**
1318
+ * Includes a relation in the query result
1319
+ * @param context - Current query context
1320
+ * @param relationName - Name of the relation to include
1321
+ * @param options - Options for relation inclusion
1322
+ * @returns Updated query context with included relation
1323
+ */
1324
+ include(context, relationName, options) {
1325
+ const result = this.createService(context).include(relationName, options);
1326
+ return { state: result.state, hydration: result.hydration };
1327
+ }
1328
+ /**
1329
+ * Applies relation correlation to a query AST
1330
+ * @param context - Current query context
1331
+ * @param relationName - Name of the relation
1332
+ * @param ast - Query AST to modify
1333
+ * @returns Modified query AST with relation correlation
1334
+ */
1335
+ applyRelationCorrelation(context, relationName, ast) {
1336
+ return this.createService(context).applyRelationCorrelation(relationName, ast);
1337
+ }
1338
+ /**
1339
+ * Creates a relation service instance
1340
+ * @param context - Current query context
1341
+ * @returns Relation service instance
1342
+ */
1343
+ createService(context) {
1344
+ return this.env.deps.createRelationService(this.env.table, context.state, context.hydration);
1345
+ }
1346
+ };
1347
+
1348
+ // src/orm/hydration.ts
1349
+ var hydrateRows = (rows, plan) => {
1350
+ if (!plan || !rows.length) return rows;
1351
+ const rootMap = /* @__PURE__ */ new Map();
1352
+ const relationIndex = /* @__PURE__ */ new Map();
1353
+ const getOrCreateParent = (row) => {
1354
+ const rootId = row[plan.rootPrimaryKey];
1355
+ if (rootId === void 0) return void 0;
1356
+ if (!rootMap.has(rootId)) {
1357
+ rootMap.set(rootId, createBaseRow(row, plan));
1358
+ }
1359
+ return rootMap.get(rootId);
1360
+ };
1361
+ const getRelationSeenSet = (rootId, relationName) => {
1362
+ let byRelation = relationIndex.get(rootId);
1363
+ if (!byRelation) {
1364
+ byRelation = {};
1365
+ relationIndex.set(rootId, byRelation);
1366
+ }
1367
+ let seen = byRelation[relationName];
1368
+ if (!seen) {
1369
+ seen = /* @__PURE__ */ new Set();
1370
+ byRelation[relationName] = seen;
1371
+ }
1372
+ return seen;
1373
+ };
1374
+ for (const row of rows) {
1375
+ const rootId = row[plan.rootPrimaryKey];
1376
+ if (rootId === void 0) continue;
1377
+ const parent = getOrCreateParent(row);
1378
+ if (!parent) continue;
1379
+ for (const rel of plan.relations) {
1380
+ const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
1381
+ const childPk = row[childPkKey];
1382
+ if (childPk === null || childPk === void 0) continue;
1383
+ const seen = getRelationSeenSet(rootId, rel.name);
1384
+ if (seen.has(childPk)) continue;
1385
+ seen.add(childPk);
1386
+ const bucket = parent[rel.name];
1387
+ bucket.push(buildChild(row, rel));
1388
+ }
1389
+ }
1390
+ return Array.from(rootMap.values());
1391
+ };
1392
+ var createBaseRow = (row, plan) => {
1393
+ const base = {};
1394
+ const baseKeys = plan.rootColumns.length ? plan.rootColumns : Object.keys(row).filter((k) => !isRelationAlias(k));
1395
+ for (const key of baseKeys) {
1396
+ base[key] = row[key];
1397
+ }
1398
+ for (const rel of plan.relations) {
1399
+ base[rel.name] = [];
1400
+ }
1401
+ return base;
1402
+ };
1403
+ var buildChild = (row, rel) => {
1404
+ const child = {};
1405
+ for (const col of rel.columns) {
1406
+ const key = makeRelationAlias(rel.aliasPrefix, col);
1407
+ child[col] = row[key];
1408
+ }
1409
+ const pivot = buildPivot(row, rel);
1410
+ if (pivot) {
1411
+ child._pivot = pivot;
1412
+ }
1413
+ return child;
1414
+ };
1415
+ var buildPivot = (row, rel) => {
1416
+ if (!rel.pivot) return void 0;
1417
+ const pivot = {};
1418
+ for (const col of rel.pivot.columns) {
1419
+ const key = makeRelationAlias(rel.pivot.aliasPrefix, col);
1420
+ pivot[col] = row[key];
1421
+ }
1422
+ const hasValue = Object.values(pivot).some((v) => v !== null && v !== void 0);
1423
+ return hasValue ? pivot : void 0;
1424
+ };
1425
+
1426
+ // src/orm/entity-meta.ts
1427
+ var ENTITY_META = Symbol("EntityMeta");
1428
+ var toKey = (value) => value === null || value === void 0 ? "" : String(value);
1429
+ var getHydrationRows = (meta, relationName, key) => {
1430
+ const map = meta.relationHydration.get(relationName);
1431
+ if (!map) return void 0;
1432
+ const rows = map.get(toKey(key));
1433
+ if (!rows) return void 0;
1434
+ return Array.isArray(rows) ? rows : void 0;
1435
+ };
1436
+ var getHydrationRecord = (meta, relationName, key) => {
1437
+ const map = meta.relationHydration.get(relationName);
1438
+ if (!map) return void 0;
1439
+ const value = map.get(toKey(key));
1440
+ if (!value) return void 0;
1441
+ if (Array.isArray(value)) {
1442
+ return value[0];
1443
+ }
1444
+ return value;
1445
+ };
1446
+ var getEntityMeta = (entity) => {
1447
+ if (!entity || typeof entity !== "object") return void 0;
1448
+ return entity[ENTITY_META];
1449
+ };
1450
+ var hasEntityMeta = (entity) => {
1451
+ return Boolean(getEntityMeta(entity));
1452
+ };
1453
+
1454
+ // src/orm/relations/has-many.ts
1455
+ var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
1456
+ var DefaultHasManyCollection = class {
1457
+ constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
1458
+ this.ctx = ctx;
1459
+ this.meta = meta;
1460
+ this.root = root;
1461
+ this.relationName = relationName;
1462
+ this.relation = relation;
1463
+ this.rootTable = rootTable;
1464
+ this.loader = loader;
1465
+ this.createEntity = createEntity;
1466
+ this.localKey = localKey;
1467
+ this.loaded = false;
1468
+ this.items = [];
1469
+ this.added = /* @__PURE__ */ new Set();
1470
+ this.removed = /* @__PURE__ */ new Set();
1471
+ this.hydrateFromCache();
1472
+ }
1473
+ async load() {
1474
+ if (this.loaded) return this.items;
1475
+ const map = await this.loader();
1476
+ const key = toKey2(this.root[this.localKey]);
1477
+ const rows = map.get(key) ?? [];
1478
+ this.items = rows.map((row) => this.createEntity(row));
1479
+ this.loaded = true;
1480
+ return this.items;
1481
+ }
1482
+ getItems() {
1483
+ return this.items;
1484
+ }
1485
+ add(data) {
1486
+ const keyValue = this.root[this.localKey];
1487
+ const childRow = {
1488
+ ...data,
1489
+ [this.relation.foreignKey]: keyValue
1490
+ };
1491
+ const entity = this.createEntity(childRow);
1492
+ this.added.add(entity);
1493
+ this.items.push(entity);
1494
+ this.ctx.registerRelationChange(
1495
+ this.root,
1496
+ this.relationKey,
1497
+ this.rootTable,
1498
+ this.relationName,
1499
+ this.relation,
1500
+ { kind: "add", entity }
1501
+ );
1502
+ return entity;
1503
+ }
1504
+ attach(entity) {
1505
+ const keyValue = this.root[this.localKey];
1506
+ entity[this.relation.foreignKey] = keyValue;
1507
+ this.ctx.markDirty(entity);
1508
+ this.items.push(entity);
1509
+ this.ctx.registerRelationChange(
1510
+ this.root,
1511
+ this.relationKey,
1512
+ this.rootTable,
1513
+ this.relationName,
1514
+ this.relation,
1515
+ { kind: "attach", entity }
1516
+ );
1517
+ }
1518
+ remove(entity) {
1519
+ this.items = this.items.filter((item) => item !== entity);
1520
+ this.removed.add(entity);
1521
+ this.ctx.registerRelationChange(
1522
+ this.root,
1523
+ this.relationKey,
1524
+ this.rootTable,
1525
+ this.relationName,
1526
+ this.relation,
1527
+ { kind: "remove", entity }
1528
+ );
1529
+ }
1530
+ clear() {
1531
+ for (const entity of [...this.items]) {
1532
+ this.remove(entity);
1533
+ }
1534
+ }
1535
+ get relationKey() {
1536
+ return `${this.rootTable.name}.${this.relationName}`;
1537
+ }
1538
+ hydrateFromCache() {
1539
+ const keyValue = this.root[this.localKey];
1540
+ if (keyValue === void 0 || keyValue === null) return;
1541
+ const rows = getHydrationRows(this.meta, this.relationName, keyValue);
1542
+ if (!rows?.length) return;
1543
+ this.items = rows.map((row) => this.createEntity(row));
1544
+ this.loaded = true;
1545
+ }
1546
+ };
1547
+
1548
+ // src/orm/relations/belongs-to.ts
1549
+ var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
1550
+ var DefaultBelongsToReference = class {
1551
+ constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
1552
+ this.ctx = ctx;
1553
+ this.meta = meta;
1554
+ this.root = root;
1555
+ this.relationName = relationName;
1556
+ this.relation = relation;
1557
+ this.rootTable = rootTable;
1558
+ this.loader = loader;
1559
+ this.createEntity = createEntity;
1560
+ this.targetKey = targetKey;
1561
+ this.loaded = false;
1562
+ this.current = null;
1563
+ this.populateFromHydrationCache();
1564
+ }
1565
+ async load() {
1566
+ if (this.loaded) return this.current;
1567
+ const map = await this.loader();
1568
+ const fkValue = this.root[this.relation.foreignKey];
1569
+ if (fkValue === null || fkValue === void 0) {
1570
+ this.current = null;
1571
+ } else {
1572
+ const row = map.get(toKey3(fkValue));
1573
+ this.current = row ? this.createEntity(row) : null;
1574
+ }
1575
+ this.loaded = true;
1576
+ return this.current;
1577
+ }
1578
+ get() {
1579
+ return this.current;
1580
+ }
1581
+ set(data) {
1582
+ if (data === null) {
1583
+ const previous = this.current;
1584
+ this.root[this.relation.foreignKey] = null;
1585
+ this.current = null;
1586
+ this.ctx.registerRelationChange(
1587
+ this.root,
1588
+ this.relationKey,
1589
+ this.rootTable,
1590
+ this.relationName,
1591
+ this.relation,
1592
+ { kind: "remove", entity: previous }
1593
+ );
1594
+ return null;
1595
+ }
1596
+ const entity = hasEntityMeta(data) ? data : this.createEntity(data);
1597
+ const pkValue = entity[this.targetKey];
1598
+ if (pkValue !== void 0) {
1599
+ this.root[this.relation.foreignKey] = pkValue;
1600
+ }
1601
+ this.current = entity;
1602
+ this.ctx.registerRelationChange(
1603
+ this.root,
1604
+ this.relationKey,
1605
+ this.rootTable,
1606
+ this.relationName,
1607
+ this.relation,
1608
+ { kind: "attach", entity }
1609
+ );
1610
+ return entity;
1611
+ }
1612
+ get relationKey() {
1613
+ return `${this.rootTable.name}.${this.relationName}`;
1614
+ }
1615
+ populateFromHydrationCache() {
1616
+ const fkValue = this.root[this.relation.foreignKey];
1617
+ if (fkValue === void 0 || fkValue === null) return;
1618
+ const row = getHydrationRecord(this.meta, this.relationName, fkValue);
1619
+ if (!row) return;
1620
+ this.current = this.createEntity(row);
1621
+ this.loaded = true;
1622
+ }
1623
+ };
1624
+
1625
+ // src/orm/relations/many-to-many.ts
1626
+ var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
1627
+ var DefaultManyToManyCollection = class {
1628
+ constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
1629
+ this.ctx = ctx;
1630
+ this.meta = meta;
1631
+ this.root = root;
1632
+ this.relationName = relationName;
1633
+ this.relation = relation;
1634
+ this.rootTable = rootTable;
1635
+ this.loader = loader;
1636
+ this.createEntity = createEntity;
1637
+ this.localKey = localKey;
1638
+ this.loaded = false;
1639
+ this.items = [];
1640
+ this.hydrateFromCache();
1641
+ }
1642
+ async load() {
1643
+ if (this.loaded) return this.items;
1644
+ const map = await this.loader();
1645
+ const key = toKey4(this.root[this.localKey]);
1646
+ const rows = map.get(key) ?? [];
1647
+ this.items = rows.map((row) => {
1648
+ const entity = this.createEntity(row);
1649
+ if (row._pivot) {
1650
+ entity._pivot = row._pivot;
1651
+ }
1652
+ return entity;
1653
+ });
1654
+ this.loaded = true;
1655
+ return this.items;
1656
+ }
1657
+ getItems() {
1658
+ return this.items;
1659
+ }
1660
+ attach(target) {
1661
+ const entity = this.ensureEntity(target);
1662
+ const id = this.extractId(entity);
1663
+ if (id == null) return;
1664
+ if (this.items.some((item) => this.extractId(item) === id)) {
1665
+ return;
1666
+ }
1667
+ this.items.push(entity);
1668
+ this.ctx.registerRelationChange(
1669
+ this.root,
1670
+ this.relationKey,
1671
+ this.rootTable,
1672
+ this.relationName,
1673
+ this.relation,
1674
+ { kind: "attach", entity }
1675
+ );
1676
+ }
1677
+ detach(target) {
1678
+ const id = typeof target === "number" || typeof target === "string" ? target : this.extractId(target);
1679
+ if (id == null) return;
1680
+ const existing = this.items.find((item) => this.extractId(item) === id);
1681
+ if (!existing) return;
1682
+ this.items = this.items.filter((item) => this.extractId(item) !== id);
1683
+ this.ctx.registerRelationChange(
1684
+ this.root,
1685
+ this.relationKey,
1686
+ this.rootTable,
1687
+ this.relationName,
1688
+ this.relation,
1689
+ { kind: "detach", entity: existing }
1690
+ );
1691
+ }
1692
+ async syncByIds(ids) {
1693
+ await this.load();
1694
+ const targetKey = this.relation.targetKey || findPrimaryKey(this.relation.target);
1695
+ const normalized = new Set(ids.map((id) => toKey4(id)));
1696
+ const currentIds = new Set(this.items.map((item) => toKey4(this.extractId(item))));
1697
+ for (const id of normalized) {
1698
+ if (!currentIds.has(id)) {
1699
+ this.attach(id);
1700
+ }
1701
+ }
1702
+ for (const item of [...this.items]) {
1703
+ const itemId = toKey4(this.extractId(item));
1704
+ if (!normalized.has(itemId)) {
1705
+ this.detach(item);
1706
+ }
1707
+ }
1708
+ }
1709
+ ensureEntity(target) {
1710
+ if (typeof target === "number" || typeof target === "string") {
1711
+ const stub = {
1712
+ [this.targetKey]: target
1713
+ };
1714
+ return this.createEntity(stub);
1715
+ }
1716
+ return target;
1717
+ }
1718
+ extractId(entity) {
1719
+ if (entity === null || entity === void 0) return null;
1720
+ if (typeof entity === "number" || typeof entity === "string") {
1721
+ return entity;
1722
+ }
1723
+ return entity[this.targetKey] ?? null;
1724
+ }
1725
+ get relationKey() {
1726
+ return `${this.rootTable.name}.${this.relationName}`;
1727
+ }
1728
+ get targetKey() {
1729
+ return this.relation.targetKey || findPrimaryKey(this.relation.target);
1730
+ }
1731
+ hydrateFromCache() {
1732
+ const keyValue = this.root[this.localKey];
1733
+ if (keyValue === void 0 || keyValue === null) return;
1734
+ const rows = getHydrationRows(this.meta, this.relationName, keyValue);
1735
+ if (!rows?.length) return;
1736
+ this.items = rows.map((row) => {
1737
+ const entity = this.createEntity(row);
1738
+ if (row._pivot) {
1739
+ entity._pivot = row._pivot;
1740
+ }
1741
+ return entity;
1742
+ });
1743
+ this.loaded = true;
1744
+ }
1745
+ };
1746
+
1747
+ // src/orm/lazy-batch.ts
1748
+ var selectAllColumns = (table) => Object.entries(table.columns).reduce((acc, [name, def]) => {
1749
+ acc[name] = def;
1750
+ return acc;
1751
+ }, {});
1752
+ var rowsFromResults = (results) => {
1753
+ const rows = [];
1754
+ for (const result of results) {
1755
+ const { columns, values } = result;
1756
+ for (const valueRow of values) {
1757
+ const row = {};
1758
+ columns.forEach((column, idx) => {
1759
+ row[column] = valueRow[idx];
1760
+ });
1761
+ rows.push(row);
1762
+ }
1763
+ }
1764
+ return rows;
1765
+ };
1766
+ var executeQuery = async (ctx, qb) => {
1767
+ const compiled = ctx.dialect.compileSelect(qb.getAST());
1768
+ const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
1769
+ return rowsFromResults(results);
1770
+ };
1771
+ var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
1772
+ var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
1773
+ const localKey = relation.localKey || findPrimaryKey(rootTable);
1774
+ const roots = ctx.getEntitiesForTable(rootTable);
1775
+ const keys = /* @__PURE__ */ new Set();
1776
+ for (const tracked of roots) {
1777
+ const value = tracked.entity[localKey];
1778
+ if (value !== null && value !== void 0) {
1779
+ keys.add(value);
1780
+ }
1781
+ }
1782
+ if (!keys.size) {
1783
+ return /* @__PURE__ */ new Map();
1784
+ }
1785
+ const selectMap = selectAllColumns(relation.target);
1786
+ const fb = new SelectQueryBuilder(relation.target).select(selectMap);
1787
+ const fkColumn = relation.target.columns[relation.foreignKey];
1788
+ if (!fkColumn) return /* @__PURE__ */ new Map();
1789
+ fb.where(inList(fkColumn, Array.from(keys)));
1790
+ const rows = await executeQuery(ctx, fb);
1791
+ const grouped = /* @__PURE__ */ new Map();
1792
+ for (const row of rows) {
1793
+ const fkValue = row[relation.foreignKey];
1794
+ if (fkValue === null || fkValue === void 0) continue;
1795
+ const key = toKey5(fkValue);
1796
+ const bucket = grouped.get(key) ?? [];
1797
+ bucket.push(row);
1798
+ grouped.set(key, bucket);
1799
+ }
1800
+ return grouped;
1801
+ };
1802
+ var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
1803
+ const roots = ctx.getEntitiesForTable(rootTable);
1804
+ const foreignKeys = /* @__PURE__ */ new Set();
1805
+ for (const tracked of roots) {
1806
+ const value = tracked.entity[relation.foreignKey];
1807
+ if (value !== null && value !== void 0) {
1808
+ foreignKeys.add(value);
1809
+ }
1810
+ }
1811
+ if (!foreignKeys.size) {
1812
+ return /* @__PURE__ */ new Map();
1813
+ }
1814
+ const selectMap = selectAllColumns(relation.target);
1815
+ const qb = new SelectQueryBuilder(relation.target).select(selectMap);
1816
+ const targetKey = relation.localKey || findPrimaryKey(relation.target);
1817
+ const pkColumn = relation.target.columns[targetKey];
1818
+ if (!pkColumn) return /* @__PURE__ */ new Map();
1819
+ qb.where(inList(pkColumn, Array.from(foreignKeys)));
1820
+ const rows = await executeQuery(ctx, qb);
1821
+ const map = /* @__PURE__ */ new Map();
1822
+ for (const row of rows) {
1823
+ const keyValue = row[targetKey];
1824
+ if (keyValue === null || keyValue === void 0) continue;
1825
+ map.set(toKey5(keyValue), row);
1826
+ }
1827
+ return map;
1828
+ };
1829
+ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation) => {
1830
+ const rootKey = relation.localKey || findPrimaryKey(rootTable);
1831
+ const roots = ctx.getEntitiesForTable(rootTable);
1832
+ const rootIds = /* @__PURE__ */ new Set();
1833
+ for (const tracked of roots) {
1834
+ const value = tracked.entity[rootKey];
1835
+ if (value !== null && value !== void 0) {
1836
+ rootIds.add(value);
1837
+ }
1838
+ }
1839
+ if (!rootIds.size) {
1840
+ return /* @__PURE__ */ new Map();
1841
+ }
1842
+ const pivotSelect = selectAllColumns(relation.pivotTable);
1843
+ const pivotQb = new SelectQueryBuilder(relation.pivotTable).select(pivotSelect);
1844
+ const pivotFkCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
1845
+ if (!pivotFkCol) return /* @__PURE__ */ new Map();
1846
+ pivotQb.where(inList(pivotFkCol, Array.from(rootIds)));
1847
+ const pivotRows = await executeQuery(ctx, pivotQb);
1848
+ const rootLookup = /* @__PURE__ */ new Map();
1849
+ const targetIds = /* @__PURE__ */ new Set();
1850
+ for (const pivot of pivotRows) {
1851
+ const rootValue = pivot[relation.pivotForeignKeyToRoot];
1852
+ const targetValue = pivot[relation.pivotForeignKeyToTarget];
1853
+ if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
1854
+ continue;
1855
+ }
1856
+ const bucket = rootLookup.get(toKey5(rootValue)) ?? [];
1857
+ bucket.push({
1858
+ targetId: targetValue,
1859
+ pivot: { ...pivot }
1860
+ });
1861
+ rootLookup.set(toKey5(rootValue), bucket);
1862
+ targetIds.add(targetValue);
1863
+ }
1864
+ if (!targetIds.size) {
1865
+ return /* @__PURE__ */ new Map();
1866
+ }
1867
+ const targetSelect = selectAllColumns(relation.target);
1868
+ const targetKey = relation.targetKey || findPrimaryKey(relation.target);
1869
+ const targetPkColumn = relation.target.columns[targetKey];
1870
+ if (!targetPkColumn) return /* @__PURE__ */ new Map();
1871
+ const targetQb = new SelectQueryBuilder(relation.target).select(targetSelect);
1872
+ targetQb.where(inList(targetPkColumn, Array.from(targetIds)));
1873
+ const targetRows = await executeQuery(ctx, targetQb);
1874
+ const targetMap = /* @__PURE__ */ new Map();
1875
+ for (const row of targetRows) {
1876
+ const pkValue = row[targetKey];
1877
+ if (pkValue === null || pkValue === void 0) continue;
1878
+ targetMap.set(toKey5(pkValue), row);
1879
+ }
1880
+ const result = /* @__PURE__ */ new Map();
1881
+ for (const [rootId, entries] of rootLookup.entries()) {
1882
+ const bucket = [];
1883
+ for (const entry of entries) {
1884
+ const targetRow = targetMap.get(toKey5(entry.targetId));
1885
+ if (!targetRow) continue;
1886
+ bucket.push({
1887
+ ...targetRow,
1888
+ _pivot: entry.pivot
1889
+ });
1890
+ }
1891
+ result.set(rootId, bucket);
1892
+ }
1893
+ return result;
1894
+ };
1895
+
1896
+ // src/orm/entity.ts
1897
+ var relationLoaderCache = (meta, relationName, factory) => {
1898
+ if (meta.relationCache.has(relationName)) {
1899
+ return meta.relationCache.get(relationName);
1900
+ }
1901
+ const promise = factory().then((value) => {
1902
+ for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
1903
+ const otherMeta = getEntityMeta(tracked.entity);
1904
+ if (!otherMeta) continue;
1905
+ otherMeta.relationHydration.set(relationName, value);
1906
+ }
1907
+ return value;
1908
+ });
1909
+ meta.relationCache.set(relationName, promise);
1910
+ for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
1911
+ const otherMeta = getEntityMeta(tracked.entity);
1912
+ if (!otherMeta) continue;
1913
+ otherMeta.relationCache.set(relationName, promise);
1914
+ }
1915
+ return promise;
1916
+ };
1917
+ var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
1918
+ const target = { ...row };
1919
+ const meta = {
1920
+ ctx,
1921
+ table,
1922
+ lazyRelations: [...lazyRelations],
1923
+ relationCache: /* @__PURE__ */ new Map(),
1924
+ relationHydration: /* @__PURE__ */ new Map(),
1925
+ relationWrappers: /* @__PURE__ */ new Map()
1926
+ };
1927
+ Object.defineProperty(target, ENTITY_META, {
1928
+ value: meta,
1929
+ enumerable: false,
1930
+ writable: false
1931
+ });
1932
+ let proxy;
1933
+ const handler = {
1934
+ get(targetObj, prop, receiver) {
1935
+ if (prop === ENTITY_META) {
1936
+ return meta;
1937
+ }
1938
+ if (prop === "$load") {
1939
+ return async (relationName) => {
1940
+ const wrapper = getRelationWrapper(meta, relationName, proxy);
1941
+ if (wrapper && typeof wrapper.load === "function") {
1942
+ return wrapper.load();
1943
+ }
1944
+ return void 0;
1945
+ };
1946
+ }
1947
+ if (typeof prop === "string" && table.relations[prop]) {
1948
+ return getRelationWrapper(meta, prop, proxy);
1949
+ }
1950
+ return Reflect.get(targetObj, prop, receiver);
1951
+ },
1952
+ set(targetObj, prop, value, receiver) {
1953
+ const result = Reflect.set(targetObj, prop, value, receiver);
1954
+ if (typeof prop === "string" && table.columns[prop]) {
1955
+ ctx.markDirty(proxy);
1956
+ }
1957
+ return result;
1958
+ }
1959
+ };
1960
+ proxy = new Proxy(target, handler);
1961
+ populateHydrationCache(proxy, row, meta);
1962
+ return proxy;
1963
+ };
1964
+ var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
1965
+ const pkName = findPrimaryKey(table);
1966
+ const pkValue = row[pkName];
1967
+ if (pkValue !== void 0 && pkValue !== null) {
1968
+ const tracked = ctx.getEntity(table, pkValue);
1969
+ if (tracked) return tracked;
1970
+ }
1971
+ const entity = createEntityProxy(ctx, table, row, lazyRelations);
1972
+ if (pkValue !== void 0 && pkValue !== null) {
1973
+ ctx.trackManaged(table, pkValue, entity);
1974
+ } else {
1975
+ ctx.trackNew(table, entity);
1976
+ }
1977
+ return entity;
1978
+ };
1979
+ var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
1980
+ var populateHydrationCache = (entity, row, meta) => {
1981
+ for (const relationName of Object.keys(meta.table.relations)) {
1982
+ const relation = meta.table.relations[relationName];
1983
+ const data = row[relationName];
1984
+ if (!Array.isArray(data)) continue;
1985
+ if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
1986
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
1987
+ const rootValue = entity[localKey];
1988
+ if (rootValue === void 0 || rootValue === null) continue;
1989
+ const cache = /* @__PURE__ */ new Map();
1990
+ cache.set(toKey6(rootValue), data);
1991
+ meta.relationHydration.set(relationName, cache);
1992
+ meta.relationCache.set(relationName, Promise.resolve(cache));
1993
+ continue;
1994
+ }
1995
+ if (relation.type === RelationKinds.BelongsTo) {
1996
+ const targetKey = relation.localKey || findPrimaryKey(relation.target);
1997
+ const cache = /* @__PURE__ */ new Map();
1998
+ for (const item of data) {
1999
+ const pkValue = item[targetKey];
2000
+ if (pkValue === void 0 || pkValue === null) continue;
2001
+ cache.set(toKey6(pkValue), item);
2002
+ }
2003
+ if (cache.size) {
2004
+ meta.relationHydration.set(relationName, cache);
2005
+ meta.relationCache.set(relationName, Promise.resolve(cache));
2006
+ }
2007
+ }
2008
+ }
2009
+ };
2010
+ var getRelationWrapper = (meta, relationName, owner) => {
2011
+ if (meta.relationWrappers.has(relationName)) {
2012
+ return meta.relationWrappers.get(relationName);
2013
+ }
2014
+ const relation = meta.table.relations[relationName];
2015
+ if (!relation) return void 0;
2016
+ const wrapper = instantiateWrapper(meta, relationName, relation, owner);
2017
+ if (wrapper) {
2018
+ meta.relationWrappers.set(relationName, wrapper);
2019
+ }
2020
+ return wrapper;
2021
+ };
2022
+ var instantiateWrapper = (meta, relationName, relation, owner) => {
2023
+ switch (relation.type) {
2024
+ case RelationKinds.HasMany: {
2025
+ const hasMany2 = relation;
2026
+ const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
2027
+ const loader = () => relationLoaderCache(
2028
+ meta,
2029
+ relationName,
2030
+ () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2)
2031
+ );
2032
+ return new DefaultHasManyCollection(
2033
+ meta.ctx,
2034
+ meta,
2035
+ owner,
2036
+ relationName,
2037
+ hasMany2,
2038
+ meta.table,
2039
+ loader,
2040
+ (row) => createEntityFromRow(meta.ctx, relation.target, row),
2041
+ localKey
2042
+ );
2043
+ }
2044
+ case RelationKinds.BelongsTo: {
2045
+ const belongsTo2 = relation;
2046
+ const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
2047
+ const loader = () => relationLoaderCache(
2048
+ meta,
2049
+ relationName,
2050
+ () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2)
2051
+ );
2052
+ return new DefaultBelongsToReference(
2053
+ meta.ctx,
2054
+ meta,
2055
+ owner,
2056
+ relationName,
2057
+ belongsTo2,
2058
+ meta.table,
2059
+ loader,
2060
+ (row) => createEntityFromRow(meta.ctx, relation.target, row),
2061
+ targetKey
2062
+ );
2063
+ }
2064
+ case RelationKinds.BelongsToMany: {
2065
+ const many = relation;
2066
+ const localKey = many.localKey || findPrimaryKey(meta.table);
2067
+ const loader = () => relationLoaderCache(
2068
+ meta,
2069
+ relationName,
2070
+ () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
2071
+ );
2072
+ return new DefaultManyToManyCollection(
2073
+ meta.ctx,
2074
+ meta,
2075
+ owner,
2076
+ relationName,
2077
+ many,
2078
+ meta.table,
2079
+ loader,
2080
+ (row) => createEntityFromRow(meta.ctx, relation.target, row),
2081
+ localKey
2082
+ );
2083
+ }
2084
+ default:
2085
+ return void 0;
2086
+ }
2087
+ };
2088
+
2089
+ // src/orm/execute.ts
2090
+ var flattenResults = (results) => {
2091
+ const rows = [];
2092
+ for (const result of results) {
2093
+ const { columns, values } = result;
2094
+ for (const valueRow of values) {
2095
+ const row = {};
2096
+ columns.forEach((column, idx) => {
2097
+ row[column] = valueRow[idx];
2098
+ });
2099
+ rows.push(row);
2100
+ }
2101
+ }
2102
+ return rows;
2103
+ };
2104
+ async function executeHydrated(ctx, qb) {
2105
+ const compiled = ctx.dialect.compileSelect(qb.getAST());
2106
+ const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
2107
+ const rows = flattenResults(executed);
2108
+ const hydrated = hydrateRows(rows, qb.getHydrationPlan());
2109
+ return hydrated.map(
2110
+ (row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
2111
+ );
2112
+ }
2113
+
2114
+ // src/query-builder/select.ts
2115
+ var SelectQueryBuilder = class _SelectQueryBuilder {
2116
+ /**
2117
+ * Creates a new SelectQueryBuilder instance
2118
+ * @param table - Table definition to query
2119
+ * @param state - Optional initial query state
2120
+ * @param hydration - Optional hydration manager
2121
+ * @param dependencies - Optional query builder dependencies
2122
+ */
2123
+ constructor(table, state, hydration, dependencies, lazyRelations) {
2124
+ const deps = resolveSelectQueryBuilderDependencies(dependencies);
2125
+ this.env = { table, deps };
2126
+ const initialState = state ?? deps.createState(table);
2127
+ const initialHydration = hydration ?? deps.createHydration(table);
2128
+ this.context = {
2129
+ state: initialState,
2130
+ hydration: initialHydration
2131
+ };
2132
+ this.lazyRelations = new Set(lazyRelations ?? []);
2133
+ this.columnSelector = new ColumnSelector(this.env);
2134
+ this.relationManager = new RelationManager(this.env);
2135
+ }
2136
+ clone(context = this.context, lazyRelations = new Set(this.lazyRelations)) {
2137
+ return new _SelectQueryBuilder(this.env.table, context.state, context.hydration, this.env.deps, lazyRelations);
2138
+ }
2139
+ resolveQueryNode(query) {
2140
+ return typeof query.getAST === "function" ? query.getAST() : query;
2141
+ }
2142
+ createChildBuilder(table) {
2143
+ return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
2144
+ }
2145
+ applyAst(context, mutator) {
2146
+ const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
2147
+ const nextState = mutator(astService);
2148
+ return { state: nextState, hydration: context.hydration };
2149
+ }
2150
+ applyJoin(context, table, condition, kind) {
2151
+ const joinNode = createJoinNode(kind, table.name, condition);
2152
+ return this.applyAst(context, (service) => service.withJoin(joinNode));
2153
+ }
2154
+ /**
2155
+ * Selects specific columns for the query
2156
+ * @param columns - Record of column definitions, function nodes, case expressions, or window functions
2157
+ * @returns New query builder instance with selected columns
2158
+ */
2159
+ select(columns) {
2160
+ return this.clone(this.columnSelector.select(this.context, columns));
2161
+ }
2162
+ /**
2163
+ * Selects raw column expressions
2164
+ * @param cols - Column expressions as strings
2165
+ * @returns New query builder instance with raw column selections
2166
+ */
2167
+ selectRaw(...cols) {
2168
+ return this.clone(this.columnSelector.selectRaw(this.context, cols));
2169
+ }
2170
+ /**
2171
+ * Adds a Common Table Expression (CTE) to the query
2172
+ * @param name - Name of the CTE
2173
+ * @param query - Query builder or query node for the CTE
2174
+ * @param columns - Optional column names for the CTE
2175
+ * @returns New query builder instance with the CTE
2176
+ */
2177
+ with(name, query, columns) {
2178
+ const subAst = this.resolveQueryNode(query);
2179
+ const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
2180
+ return this.clone(nextContext);
2181
+ }
2182
+ /**
2183
+ * Adds a recursive Common Table Expression (CTE) to the query
2184
+ * @param name - Name of the CTE
2185
+ * @param query - Query builder or query node for the CTE
2186
+ * @param columns - Optional column names for the CTE
2187
+ * @returns New query builder instance with the recursive CTE
2188
+ */
2189
+ withRecursive(name, query, columns) {
2190
+ const subAst = this.resolveQueryNode(query);
2191
+ const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
2192
+ return this.clone(nextContext);
2193
+ }
2194
+ /**
2195
+ * Selects a subquery as a column
2196
+ * @param alias - Alias for the subquery column
2197
+ * @param sub - Query builder or query node for the subquery
2198
+ * @returns New query builder instance with the subquery selection
2199
+ */
2200
+ selectSubquery(alias, sub) {
2201
+ const query = this.resolveQueryNode(sub);
2202
+ return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
2203
+ }
2204
+ /**
2205
+ * Adds an INNER JOIN to the query
2206
+ * @param table - Table to join
2207
+ * @param condition - Join condition expression
2208
+ * @returns New query builder instance with the INNER JOIN
2209
+ */
2210
+ innerJoin(table, condition) {
2211
+ const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
2212
+ return this.clone(nextContext);
2213
+ }
2214
+ /**
2215
+ * Adds a LEFT JOIN to the query
2216
+ * @param table - Table to join
2217
+ * @param condition - Join condition expression
2218
+ * @returns New query builder instance with the LEFT JOIN
2219
+ */
2220
+ leftJoin(table, condition) {
2221
+ const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
2222
+ return this.clone(nextContext);
2223
+ }
2224
+ /**
2225
+ * Adds a RIGHT JOIN to the query
2226
+ * @param table - Table to join
2227
+ * @param condition - Join condition expression
2228
+ * @returns New query builder instance with the RIGHT JOIN
2229
+ */
2230
+ rightJoin(table, condition) {
2231
+ const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
2232
+ return this.clone(nextContext);
2233
+ }
2234
+ /**
2235
+ * Matches records based on a relationship
2236
+ * @param relationName - Name of the relationship to match
2237
+ * @param predicate - Optional predicate expression
2238
+ * @returns New query builder instance with the relationship match
2239
+ */
2240
+ match(relationName, predicate) {
2241
+ const nextContext = this.relationManager.match(this.context, relationName, predicate);
2242
+ return this.clone(nextContext);
2243
+ }
2244
+ /**
2245
+ * Joins a related table
2246
+ * @param relationName - Name of the relationship to join
2247
+ * @param joinKind - Type of join (defaults to INNER)
2248
+ * @param extraCondition - Optional additional join condition
2249
+ * @returns New query builder instance with the relationship join
2250
+ */
2251
+ joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
2252
+ const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
2253
+ return this.clone(nextContext);
2254
+ }
2255
+ /**
2256
+ * Includes related data in the query results
2257
+ * @param relationName - Name of the relationship to include
2258
+ * @param options - Optional include options
2259
+ * @returns New query builder instance with the relationship inclusion
2260
+ */
2261
+ include(relationName, options) {
2262
+ const nextContext = this.relationManager.include(this.context, relationName, options);
2263
+ return this.clone(nextContext);
2264
+ }
2265
+ includeLazy(relationName) {
2266
+ const nextLazy = new Set(this.lazyRelations);
2267
+ nextLazy.add(relationName);
2268
+ return this.clone(this.context, nextLazy);
2269
+ }
2270
+ getLazyRelations() {
2271
+ return Array.from(this.lazyRelations);
2272
+ }
2273
+ getTable() {
2274
+ return this.env.table;
2275
+ }
2276
+ async execute(ctx) {
2277
+ return executeHydrated(ctx, this);
2278
+ }
2279
+ /**
2280
+ * Adds a WHERE condition to the query
2281
+ * @param expr - Expression for the WHERE clause
2282
+ * @returns New query builder instance with the WHERE condition
2283
+ */
2284
+ where(expr) {
2285
+ const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
2286
+ return this.clone(nextContext);
2287
+ }
2288
+ /**
2289
+ * Adds a GROUP BY clause to the query
2290
+ * @param col - Column definition or column node to group by
2291
+ * @returns New query builder instance with the GROUP BY clause
2292
+ */
2293
+ groupBy(col) {
2294
+ const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(col));
2295
+ return this.clone(nextContext);
2296
+ }
2297
+ /**
2298
+ * Adds a HAVING condition to the query
2299
+ * @param expr - Expression for the HAVING clause
2300
+ * @returns New query builder instance with the HAVING condition
2301
+ */
2302
+ having(expr) {
2303
+ const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
2304
+ return this.clone(nextContext);
2305
+ }
2306
+ /**
2307
+ * Adds an ORDER BY clause to the query
2308
+ * @param col - Column definition or column node to order by
2309
+ * @param direction - Order direction (defaults to ASC)
2310
+ * @returns New query builder instance with the ORDER BY clause
2311
+ */
2312
+ orderBy(col, direction = ORDER_DIRECTIONS.ASC) {
2313
+ const nextContext = this.applyAst(this.context, (service) => service.withOrderBy(col, direction));
2314
+ return this.clone(nextContext);
2315
+ }
2316
+ /**
2317
+ * Adds a DISTINCT clause to the query
2318
+ * @param cols - Columns to make distinct
2319
+ * @returns New query builder instance with the DISTINCT clause
2320
+ */
2321
+ distinct(...cols) {
2322
+ return this.clone(this.columnSelector.distinct(this.context, cols));
2323
+ }
2324
+ /**
2325
+ * Adds a LIMIT clause to the query
2326
+ * @param n - Maximum number of rows to return
2327
+ * @returns New query builder instance with the LIMIT clause
2328
+ */
2329
+ limit(n) {
2330
+ const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
2331
+ return this.clone(nextContext);
2332
+ }
2333
+ /**
2334
+ * Adds an OFFSET clause to the query
2335
+ * @param n - Number of rows to skip
2336
+ * @returns New query builder instance with the OFFSET clause
2337
+ */
2338
+ offset(n) {
2339
+ const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
2340
+ return this.clone(nextContext);
2341
+ }
2342
+ /**
2343
+ * Adds a WHERE EXISTS condition to the query
2344
+ * @param subquery - Subquery to check for existence
2345
+ * @returns New query builder instance with the WHERE EXISTS condition
2346
+ */
2347
+ whereExists(subquery) {
2348
+ const subAst = this.resolveQueryNode(subquery);
2349
+ return this.where(exists(subAst));
2350
+ }
2351
+ /**
2352
+ * Adds a WHERE NOT EXISTS condition to the query
2353
+ * @param subquery - Subquery to check for non-existence
2354
+ * @returns New query builder instance with the WHERE NOT EXISTS condition
2355
+ */
2356
+ whereNotExists(subquery) {
2357
+ const subAst = this.resolveQueryNode(subquery);
2358
+ return this.where(notExists(subAst));
2359
+ }
2360
+ /**
2361
+ * Adds a WHERE EXISTS condition based on a relationship
2362
+ * @param relationName - Name of the relationship to check
2363
+ * @param callback - Optional callback to modify the relationship query
2364
+ * @returns New query builder instance with the relationship existence check
2365
+ */
2366
+ whereHas(relationName, callback) {
2367
+ const relation = this.env.table.relations[relationName];
2368
+ if (!relation) {
2369
+ throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
2370
+ }
2371
+ let subQb = this.createChildBuilder(relation.target);
2372
+ if (callback) {
2373
+ subQb = callback(subQb);
2374
+ }
2375
+ const subAst = subQb.getAST();
2376
+ const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
2377
+ return this.where(exists(finalSubAst));
2378
+ }
2379
+ /**
2380
+ * Adds a WHERE NOT EXISTS condition based on a relationship
2381
+ * @param relationName - Name of the relationship to check
2382
+ * @param callback - Optional callback to modify the relationship query
2383
+ * @returns New query builder instance with the relationship non-existence check
2384
+ */
2385
+ whereHasNot(relationName, callback) {
2386
+ const relation = this.env.table.relations[relationName];
2387
+ if (!relation) {
2388
+ throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
2389
+ }
2390
+ let subQb = this.createChildBuilder(relation.target);
2391
+ if (callback) {
2392
+ subQb = callback(subQb);
2393
+ }
2394
+ const subAst = subQb.getAST();
2395
+ const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
2396
+ return this.where(notExists(finalSubAst));
2397
+ }
2398
+ /**
2399
+ * Compiles the query to SQL for a specific dialect
2400
+ * @param dialect - Database dialect to compile for
2401
+ * @returns Compiled query with SQL and parameters
2402
+ */
2403
+ compile(dialect) {
2404
+ return dialect.compileSelect(this.context.state.ast);
2405
+ }
2406
+ /**
2407
+ * Converts the query to SQL string for a specific dialect
2408
+ * @param dialect - Database dialect to generate SQL for
2409
+ * @returns SQL string representation of the query
2410
+ */
2411
+ toSql(dialect) {
2412
+ return this.compile(dialect).sql;
2413
+ }
2414
+ /**
2415
+ * Gets the hydration plan for the query
2416
+ * @returns Hydration plan or undefined if none exists
2417
+ */
2418
+ getHydrationPlan() {
2419
+ return this.context.hydration.getPlan();
2420
+ }
2421
+ /**
2422
+ * Gets the Abstract Syntax Tree (AST) representation of the query
2423
+ * @returns Query AST with hydration applied
2424
+ */
2425
+ getAST() {
2426
+ return this.context.hydration.applyToAst(this.context.state.ast);
2427
+ }
2428
+ };
2429
+
2430
+ // src/decorators/bootstrap.ts
2431
+ var isTableDef = (value) => {
2432
+ return typeof value === "object" && value !== null && "columns" in value;
2433
+ };
2434
+ var unwrapTarget = (target) => {
2435
+ if (typeof target === "function" && target.prototype === void 0) {
2436
+ return target();
2437
+ }
2438
+ return target;
2439
+ };
2440
+ var resolveTableTarget = (target, tableMap) => {
2441
+ const resolved = unwrapTarget(target);
2442
+ if (isTableDef(resolved)) {
2443
+ return resolved;
2444
+ }
2445
+ const table = tableMap.get(resolved);
2446
+ if (!table) {
2447
+ throw new Error(`Entity '${resolved.name}' is not registered with decorators`);
2448
+ }
2449
+ return table;
2450
+ };
2451
+ var buildRelationDefinitions = (meta, tableMap) => {
2452
+ const relations = {};
2453
+ for (const [name, relation] of Object.entries(meta.relations)) {
2454
+ switch (relation.kind) {
2455
+ case RelationKinds.HasMany: {
2456
+ relations[name] = hasMany(
2457
+ resolveTableTarget(relation.target, tableMap),
2458
+ relation.foreignKey,
2459
+ relation.localKey,
2460
+ relation.cascade
2461
+ );
2462
+ break;
2463
+ }
2464
+ case RelationKinds.BelongsTo: {
2465
+ relations[name] = belongsTo(
2466
+ resolveTableTarget(relation.target, tableMap),
2467
+ relation.foreignKey,
2468
+ relation.localKey,
2469
+ relation.cascade
2470
+ );
2471
+ break;
2472
+ }
2473
+ case RelationKinds.BelongsToMany: {
2474
+ relations[name] = belongsToMany(
2475
+ resolveTableTarget(relation.target, tableMap),
2476
+ resolveTableTarget(relation.pivotTable, tableMap),
2477
+ {
2478
+ pivotForeignKeyToRoot: relation.pivotForeignKeyToRoot,
2479
+ pivotForeignKeyToTarget: relation.pivotForeignKeyToTarget,
2480
+ localKey: relation.localKey,
2481
+ targetKey: relation.targetKey,
2482
+ pivotPrimaryKey: relation.pivotPrimaryKey,
2483
+ defaultPivotColumns: relation.defaultPivotColumns,
2484
+ cascade: relation.cascade
2485
+ }
2486
+ );
2487
+ break;
2488
+ }
2489
+ }
2490
+ }
2491
+ return relations;
2492
+ };
2493
+ var bootstrapEntities = () => {
2494
+ const metas = getAllEntityMetadata();
2495
+ const tableMap = /* @__PURE__ */ new Map();
2496
+ for (const meta of metas) {
2497
+ const table = buildTableDef(meta);
2498
+ tableMap.set(meta.target, table);
2499
+ }
2500
+ for (const meta of metas) {
2501
+ const table = meta.table;
2502
+ const relations = buildRelationDefinitions(meta, tableMap);
2503
+ table.relations = relations;
2504
+ }
2505
+ return metas.map((meta) => meta.table);
2506
+ };
2507
+ var getTableDefFromEntity = (ctor) => {
2508
+ const meta = getEntityMetadata(ctor);
2509
+ if (!meta) return void 0;
2510
+ return meta.table;
2511
+ };
2512
+ var selectFromEntity = (ctor) => {
2513
+ const table = getTableDefFromEntity(ctor);
2514
+ if (!table) {
2515
+ throw new Error("Entity metadata has not been bootstrapped");
2516
+ }
2517
+ return new SelectQueryBuilder(table);
2518
+ };
2519
+ export {
2520
+ BelongsTo,
2521
+ BelongsToMany,
2522
+ Column,
2523
+ Entity,
2524
+ HasMany,
2525
+ PrimaryKey,
2526
+ bootstrapEntities,
2527
+ getTableDefFromEntity,
2528
+ selectFromEntity
2529
+ };
2530
+ //# sourceMappingURL=index.js.map