metal-orm 1.0.8 → 1.0.10

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 +341 -146
  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
@@ -1,284 +1,284 @@
1
- import { TableDef } from '../schema/table';
2
- import { ColumnDef } from '../schema/column';
3
- import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation';
4
- import { SelectQueryNode } from '../ast/query';
5
- import {
6
- ColumnNode,
7
- ExpressionNode,
8
- and
9
- } from '../ast/expression';
10
- import { SelectQueryState } from './select-query-state';
11
- import { HydrationManager } from './hydration-manager';
12
- import { QueryAstService } from './query-ast-service';
13
- import { findPrimaryKey } from './hydration-planner';
14
- import { RelationProjectionHelper } from './relation-projection-helper';
15
- import type { RelationResult } from './relation-projection-helper';
16
- import {
17
- buildRelationJoinCondition,
18
- buildRelationCorrelation,
19
- buildBelongsToManyJoins
20
- } from './relation-conditions';
21
- import { JoinKind, JOIN_KINDS } from '../constants/sql';
22
- import { RelationIncludeOptions } from './relation-types';
23
- import { createJoinNode } from '../utils/join-node';
24
- import { makeRelationAlias } from '../utils/relation-alias';
25
- import { buildDefaultPivotColumns } from './relation-utils';
26
-
27
- /**
28
- * Service for handling relation operations (joins, includes, etc.)
29
- */
30
- export class RelationService {
31
- private readonly projectionHelper: RelationProjectionHelper;
32
-
33
- /**
34
- * Creates a new RelationService instance
35
- * @param table - Table definition
36
- * @param state - Current query state
37
- * @param hydration - Hydration manager
38
- */
39
- constructor(
40
- private readonly table: TableDef,
41
- private readonly state: SelectQueryState,
42
- private readonly hydration: HydrationManager,
43
- private readonly createQueryAstService: (table: TableDef, state: SelectQueryState) => QueryAstService
44
- ) {
45
- this.projectionHelper = new RelationProjectionHelper(table, (state, hydration, columns) =>
46
- this.selectColumns(state, hydration, columns)
47
- );
48
- }
49
-
50
- /**
51
- * Joins a relation to the query
52
- * @param relationName - Name of the relation to join
53
- * @param joinKind - Type of join to use
54
- * @param extraCondition - Additional join condition
55
- * @returns Relation result with updated state and hydration
56
- */
57
- joinRelation(
58
- relationName: string,
59
- joinKind: JoinKind,
60
- extraCondition?: ExpressionNode
61
- ): RelationResult {
62
- const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
63
- return { state: nextState, hydration: this.hydration };
64
- }
65
-
66
- /**
67
- * Matches records based on a relation with an optional predicate
68
- * @param relationName - Name of the relation to match
69
- * @param predicate - Optional predicate expression
70
- * @returns Relation result with updated state and hydration
71
- */
72
- match(
73
- relationName: string,
74
- predicate?: ExpressionNode
75
- ): RelationResult {
76
- const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
77
- const pk = findPrimaryKey(this.table);
78
- const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.table.name, name: pk }];
79
- const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
80
- const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
81
- return { state: nextState, hydration: joined.hydration };
82
- }
83
-
84
- /**
85
- * Includes a relation in the query result
86
- * @param relationName - Name of the relation to include
87
- * @param options - Options for relation inclusion
88
- * @returns Relation result with updated state and hydration
89
- */
90
- include(relationName: string, options?: RelationIncludeOptions): RelationResult {
91
- let state = this.state;
92
- let hydration = this.hydration;
93
-
94
- const relation = this.getRelation(relationName);
95
- const aliasPrefix = options?.aliasPrefix ?? relationName;
96
- const alreadyJoined = state.ast.joins.some(j => j.relationName === relationName);
97
-
98
- if (!alreadyJoined) {
99
- const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
100
- state = joined.state;
101
- }
102
-
103
- const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
104
- state = projectionResult.state;
105
- hydration = projectionResult.hydration;
106
-
107
- const targetColumns = options?.columns?.length
108
- ? options.columns
109
- : Object.keys(relation.target.columns);
110
-
111
- const buildTypedSelection = (
112
- columns: Record<string, ColumnDef>,
113
- prefix: string,
114
- keys: string[],
115
- missingMsg: (col: string) => string
116
- ) : Record<string, ColumnDef> => {
117
- return keys.reduce((acc, key) => {
118
- const def = columns[key];
119
- if (!def) {
120
- throw new Error(missingMsg(key));
121
- }
122
- acc[makeRelationAlias(prefix, key)] = def;
123
- return acc;
124
- }, {} as Record<string, ColumnDef>);
125
- };
126
-
127
- const targetSelection = buildTypedSelection(
128
- relation.target.columns as Record<string, ColumnDef>,
129
- aliasPrefix,
130
- targetColumns,
131
- key => `Column '${key}' not found on relation '${relationName}'`
132
- );
133
-
134
- if (relation.type !== RelationKinds.BelongsToMany) {
135
- const relationSelectionResult = this.selectColumns(state, hydration, targetSelection);
136
- state = relationSelectionResult.state;
137
- hydration = relationSelectionResult.hydration;
138
-
139
- hydration = hydration.onRelationIncluded(
140
- state,
141
- relation,
142
- relationName,
143
- aliasPrefix,
144
- targetColumns
145
- );
146
-
147
- return { state, hydration };
148
- }
149
-
150
- const many = relation as BelongsToManyRelation;
151
- const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
152
- const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
153
- const pivotColumns =
154
- options?.pivot?.columns ??
155
- many.defaultPivotColumns ??
156
- buildDefaultPivotColumns(many, pivotPk);
157
-
158
- const pivotSelection = buildTypedSelection(
159
- many.pivotTable.columns as Record<string, ColumnDef>,
160
- pivotAliasPrefix,
161
- pivotColumns,
162
- key => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
163
- );
164
-
165
- const combinedSelection = {
166
- ...targetSelection,
167
- ...pivotSelection
168
- };
169
-
170
- const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
171
- state = relationSelectionResult.state;
172
- hydration = relationSelectionResult.hydration;
173
-
174
- hydration = hydration.onRelationIncluded(
175
- state,
176
- relation,
177
- relationName,
178
- aliasPrefix,
179
- targetColumns,
180
- { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
181
- );
182
-
183
- return { state, hydration };
184
- }
185
-
186
- /**
187
- * Applies relation correlation to a query AST
188
- * @param relationName - Name of the relation
189
- * @param ast - Query AST to modify
190
- * @returns Modified query AST with relation correlation
191
- */
192
- applyRelationCorrelation(
193
- relationName: string,
194
- ast: SelectQueryNode
195
- ): SelectQueryNode {
196
- const relation = this.getRelation(relationName);
197
- const correlation = buildRelationCorrelation(this.table, relation);
198
- const whereInSubquery = ast.where
199
- ? and(correlation, ast.where)
200
- : correlation;
201
-
202
- return {
203
- ...ast,
204
- where: whereInSubquery
205
- };
206
- }
207
-
208
- /**
209
- * Creates a join node for a relation
210
- * @param state - Current query state
211
- * @param relationName - Name of the relation
212
- * @param joinKind - Type of join to use
213
- * @param extraCondition - Additional join condition
214
- * @returns Updated query state with join
215
- */
216
- private withJoin(
217
- state: SelectQueryState,
218
- relationName: string,
219
- joinKind: JoinKind,
220
- extraCondition?: ExpressionNode
221
- ): SelectQueryState {
222
- const relation = this.getRelation(relationName);
223
- if (relation.type === RelationKinds.BelongsToMany) {
224
- const joins = buildBelongsToManyJoins(
225
- this.table,
226
- relationName,
227
- relation as BelongsToManyRelation,
228
- joinKind,
229
- extraCondition
230
- );
231
- return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
232
- }
233
-
234
- const condition = buildRelationJoinCondition(this.table, relation, extraCondition);
235
- const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
236
-
237
- return this.astService(state).withJoin(joinNode);
238
- }
239
-
240
- /**
241
- * Selects columns for a relation
242
- * @param state - Current query state
243
- * @param hydration - Hydration manager
244
- * @param columns - Columns to select
245
- * @returns Relation result with updated state and hydration
246
- */
247
- private selectColumns(
248
- state: SelectQueryState,
249
- hydration: HydrationManager,
250
- columns: Record<string, ColumnDef>
251
- ): RelationResult {
252
- const { state: nextState, addedColumns } = this.astService(state).select(columns);
253
- return {
254
- state: nextState,
255
- hydration: hydration.onColumnsSelected(nextState, addedColumns)
256
- };
257
- }
258
-
259
- /**
260
- * Gets a relation definition by name
261
- * @param relationName - Name of the relation
262
- * @returns Relation definition
263
- * @throws Error if relation is not found
264
- */
265
- private getRelation(relationName: string): RelationDef {
266
- const relation = this.table.relations[relationName];
267
- if (!relation) {
268
- throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
269
- }
270
-
271
- return relation;
272
- }
273
-
274
- /**
275
- * Creates a QueryAstService instance
276
- * @param state - Current query state
277
- * @returns QueryAstService instance
278
- */
279
- private astService(state: SelectQueryState = this.state): QueryAstService {
280
- return this.createQueryAstService(this.table, state);
281
- }
282
- }
283
-
284
- export type { RelationResult } from './relation-projection-helper';
1
+ import { TableDef } from '../schema/table.js';
2
+ import { ColumnDef } from '../schema/column.js';
3
+ import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation.js';
4
+ import { SelectQueryNode } from '../core/ast/query.js';
5
+ import {
6
+ ColumnNode,
7
+ ExpressionNode,
8
+ and
9
+ } from '../core/ast/expression.js';
10
+ import { SelectQueryState } from './select-query-state.js';
11
+ import { HydrationManager } from './hydration-manager.js';
12
+ import { QueryAstService } from './query-ast-service.js';
13
+ import { findPrimaryKey } from './hydration-planner.js';
14
+ import { RelationProjectionHelper } from './relation-projection-helper.js';
15
+ import type { RelationResult } from './relation-projection-helper.js';
16
+ import {
17
+ buildRelationJoinCondition,
18
+ buildRelationCorrelation,
19
+ buildBelongsToManyJoins
20
+ } from './relation-conditions.js';
21
+ import { JoinKind, JOIN_KINDS } from '../core/sql/sql.js';
22
+ import { RelationIncludeOptions } from './relation-types.js';
23
+ import { createJoinNode } from '../core/ast/join-node.js';
24
+ import { makeRelationAlias } from './relation-alias.js';
25
+ import { buildDefaultPivotColumns } from './relation-utils.js';
26
+
27
+ /**
28
+ * Service for handling relation operations (joins, includes, etc.)
29
+ */
30
+ export class RelationService {
31
+ private readonly projectionHelper: RelationProjectionHelper;
32
+
33
+ /**
34
+ * Creates a new RelationService instance
35
+ * @param table - Table definition
36
+ * @param state - Current query state
37
+ * @param hydration - Hydration manager
38
+ */
39
+ constructor(
40
+ private readonly table: TableDef,
41
+ private readonly state: SelectQueryState,
42
+ private readonly hydration: HydrationManager,
43
+ private readonly createQueryAstService: (table: TableDef, state: SelectQueryState) => QueryAstService
44
+ ) {
45
+ this.projectionHelper = new RelationProjectionHelper(table, (state, hydration, columns) =>
46
+ this.selectColumns(state, hydration, columns)
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Joins a relation to the query
52
+ * @param relationName - Name of the relation to join
53
+ * @param joinKind - Type of join to use
54
+ * @param extraCondition - Additional join condition
55
+ * @returns Relation result with updated state and hydration
56
+ */
57
+ joinRelation(
58
+ relationName: string,
59
+ joinKind: JoinKind,
60
+ extraCondition?: ExpressionNode
61
+ ): RelationResult {
62
+ const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
63
+ return { state: nextState, hydration: this.hydration };
64
+ }
65
+
66
+ /**
67
+ * Matches records based on a relation with an optional predicate
68
+ * @param relationName - Name of the relation to match
69
+ * @param predicate - Optional predicate expression
70
+ * @returns Relation result with updated state and hydration
71
+ */
72
+ match(
73
+ relationName: string,
74
+ predicate?: ExpressionNode
75
+ ): RelationResult {
76
+ const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
77
+ const pk = findPrimaryKey(this.table);
78
+ const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.table.name, name: pk }];
79
+ const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
80
+ const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
81
+ return { state: nextState, hydration: joined.hydration };
82
+ }
83
+
84
+ /**
85
+ * Includes a relation in the query result
86
+ * @param relationName - Name of the relation to include
87
+ * @param options - Options for relation inclusion
88
+ * @returns Relation result with updated state and hydration
89
+ */
90
+ include(relationName: string, options?: RelationIncludeOptions): RelationResult {
91
+ let state = this.state;
92
+ let hydration = this.hydration;
93
+
94
+ const relation = this.getRelation(relationName);
95
+ const aliasPrefix = options?.aliasPrefix ?? relationName;
96
+ const alreadyJoined = state.ast.joins.some(j => j.relationName === relationName);
97
+
98
+ if (!alreadyJoined) {
99
+ const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
100
+ state = joined.state;
101
+ }
102
+
103
+ const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
104
+ state = projectionResult.state;
105
+ hydration = projectionResult.hydration;
106
+
107
+ const targetColumns = options?.columns?.length
108
+ ? options.columns
109
+ : Object.keys(relation.target.columns);
110
+
111
+ const buildTypedSelection = (
112
+ columns: Record<string, ColumnDef>,
113
+ prefix: string,
114
+ keys: string[],
115
+ missingMsg: (col: string) => string
116
+ ) : Record<string, ColumnDef> => {
117
+ return keys.reduce((acc, key) => {
118
+ const def = columns[key];
119
+ if (!def) {
120
+ throw new Error(missingMsg(key));
121
+ }
122
+ acc[makeRelationAlias(prefix, key)] = def;
123
+ return acc;
124
+ }, {} as Record<string, ColumnDef>);
125
+ };
126
+
127
+ const targetSelection = buildTypedSelection(
128
+ relation.target.columns as Record<string, ColumnDef>,
129
+ aliasPrefix,
130
+ targetColumns,
131
+ key => `Column '${key}' not found on relation '${relationName}'`
132
+ );
133
+
134
+ if (relation.type !== RelationKinds.BelongsToMany) {
135
+ const relationSelectionResult = this.selectColumns(state, hydration, targetSelection);
136
+ state = relationSelectionResult.state;
137
+ hydration = relationSelectionResult.hydration;
138
+
139
+ hydration = hydration.onRelationIncluded(
140
+ state,
141
+ relation,
142
+ relationName,
143
+ aliasPrefix,
144
+ targetColumns
145
+ );
146
+
147
+ return { state, hydration };
148
+ }
149
+
150
+ const many = relation as BelongsToManyRelation;
151
+ const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
152
+ const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
153
+ const pivotColumns =
154
+ options?.pivot?.columns ??
155
+ many.defaultPivotColumns ??
156
+ buildDefaultPivotColumns(many, pivotPk);
157
+
158
+ const pivotSelection = buildTypedSelection(
159
+ many.pivotTable.columns as Record<string, ColumnDef>,
160
+ pivotAliasPrefix,
161
+ pivotColumns,
162
+ key => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
163
+ );
164
+
165
+ const combinedSelection = {
166
+ ...targetSelection,
167
+ ...pivotSelection
168
+ };
169
+
170
+ const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
171
+ state = relationSelectionResult.state;
172
+ hydration = relationSelectionResult.hydration;
173
+
174
+ hydration = hydration.onRelationIncluded(
175
+ state,
176
+ relation,
177
+ relationName,
178
+ aliasPrefix,
179
+ targetColumns,
180
+ { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
181
+ );
182
+
183
+ return { state, hydration };
184
+ }
185
+
186
+ /**
187
+ * Applies relation correlation to a query AST
188
+ * @param relationName - Name of the relation
189
+ * @param ast - Query AST to modify
190
+ * @returns Modified query AST with relation correlation
191
+ */
192
+ applyRelationCorrelation(
193
+ relationName: string,
194
+ ast: SelectQueryNode
195
+ ): SelectQueryNode {
196
+ const relation = this.getRelation(relationName);
197
+ const correlation = buildRelationCorrelation(this.table, relation);
198
+ const whereInSubquery = ast.where
199
+ ? and(correlation, ast.where)
200
+ : correlation;
201
+
202
+ return {
203
+ ...ast,
204
+ where: whereInSubquery
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Creates a join node for a relation
210
+ * @param state - Current query state
211
+ * @param relationName - Name of the relation
212
+ * @param joinKind - Type of join to use
213
+ * @param extraCondition - Additional join condition
214
+ * @returns Updated query state with join
215
+ */
216
+ private withJoin(
217
+ state: SelectQueryState,
218
+ relationName: string,
219
+ joinKind: JoinKind,
220
+ extraCondition?: ExpressionNode
221
+ ): SelectQueryState {
222
+ const relation = this.getRelation(relationName);
223
+ if (relation.type === RelationKinds.BelongsToMany) {
224
+ const joins = buildBelongsToManyJoins(
225
+ this.table,
226
+ relationName,
227
+ relation as BelongsToManyRelation,
228
+ joinKind,
229
+ extraCondition
230
+ );
231
+ return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
232
+ }
233
+
234
+ const condition = buildRelationJoinCondition(this.table, relation, extraCondition);
235
+ const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
236
+
237
+ return this.astService(state).withJoin(joinNode);
238
+ }
239
+
240
+ /**
241
+ * Selects columns for a relation
242
+ * @param state - Current query state
243
+ * @param hydration - Hydration manager
244
+ * @param columns - Columns to select
245
+ * @returns Relation result with updated state and hydration
246
+ */
247
+ private selectColumns(
248
+ state: SelectQueryState,
249
+ hydration: HydrationManager,
250
+ columns: Record<string, ColumnDef>
251
+ ): RelationResult {
252
+ const { state: nextState, addedColumns } = this.astService(state).select(columns);
253
+ return {
254
+ state: nextState,
255
+ hydration: hydration.onColumnsSelected(nextState, addedColumns)
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Gets a relation definition by name
261
+ * @param relationName - Name of the relation
262
+ * @returns Relation definition
263
+ * @throws Error if relation is not found
264
+ */
265
+ private getRelation(relationName: string): RelationDef {
266
+ const relation = this.table.relations[relationName];
267
+ if (!relation) {
268
+ throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
269
+ }
270
+
271
+ return relation;
272
+ }
273
+
274
+ /**
275
+ * Creates a QueryAstService instance
276
+ * @param state - Current query state
277
+ * @returns QueryAstService instance
278
+ */
279
+ private astService(state: SelectQueryState = this.state): QueryAstService {
280
+ return this.createQueryAstService(this.table, state);
281
+ }
282
+ }
283
+
284
+ export type { RelationResult } from './relation-projection-helper.js';
@@ -1,21 +1,21 @@
1
- import { ExpressionNode } from '../ast/expression';
2
- import { JOIN_KINDS } from '../constants/sql';
3
-
4
- /**
5
- * Join kinds allowed when including a relation using `.include(...)`.
6
- */
7
- export type RelationIncludeJoinKind = typeof JOIN_KINDS.LEFT | typeof JOIN_KINDS.INNER;
8
-
9
- /**
10
- * Options for including a relation in a query
11
- */
12
- export interface RelationIncludeOptions {
13
- columns?: string[];
14
- aliasPrefix?: string;
15
- filter?: ExpressionNode;
16
- joinKind?: RelationIncludeJoinKind;
17
- pivot?: {
18
- columns?: string[];
19
- aliasPrefix?: string;
20
- };
21
- }
1
+ import { ExpressionNode } from '../core/ast/expression.js';
2
+ import { JOIN_KINDS } from '../core/sql/sql.js';
3
+
4
+ /**
5
+ * Join kinds allowed when including a relation using `.include(...)`.
6
+ */
7
+ export type RelationIncludeJoinKind = typeof JOIN_KINDS.LEFT | typeof JOIN_KINDS.INNER;
8
+
9
+ /**
10
+ * Options for including a relation in a query
11
+ */
12
+ export interface RelationIncludeOptions {
13
+ columns?: string[];
14
+ aliasPrefix?: string;
15
+ filter?: ExpressionNode;
16
+ joinKind?: RelationIncludeJoinKind;
17
+ pivot?: {
18
+ columns?: string[];
19
+ aliasPrefix?: string;
20
+ };
21
+ }
@@ -1,12 +1,12 @@
1
- import { BelongsToManyRelation } from '../schema/relation';
2
-
3
- /**
4
- * Builds a default set of pivot columns, excluding keys used for joins.
5
- */
6
- export const buildDefaultPivotColumns = (
7
- rel: BelongsToManyRelation,
8
- pivotPk: string
9
- ): string[] => {
10
- const excluded = new Set([pivotPk, rel.pivotForeignKeyToRoot, rel.pivotForeignKeyToTarget]);
11
- return Object.keys(rel.pivotTable.columns).filter(col => !excluded.has(col));
12
- };
1
+ import { BelongsToManyRelation } from '../schema/relation.js';
2
+
3
+ /**
4
+ * Builds a default set of pivot columns, excluding keys used for joins.
5
+ */
6
+ export const buildDefaultPivotColumns = (
7
+ rel: BelongsToManyRelation,
8
+ pivotPk: string
9
+ ): string[] => {
10
+ const excluded = new Set([pivotPk, rel.pivotForeignKeyToRoot, rel.pivotForeignKeyToTarget]);
11
+ return Object.keys(rel.pivotTable.columns).filter(col => !excluded.has(col));
12
+ };