metal-orm 1.0.8 → 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 +12 -1
  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,87 +1,87 @@
1
- import { TableDef } from '../schema/table';
2
- import { RelationDef } from '../schema/relation';
3
- import { SelectQueryNode, HydrationPlan } from '../ast/query';
4
- import { HydrationPlanner } from './hydration-planner';
5
- import { SelectQueryState, ProjectionNode } from './select-query-state';
6
-
7
- /**
8
- * Manages hydration planning for query results
9
- */
10
- export class HydrationManager {
11
- /**
12
- * Creates a new HydrationManager instance
13
- * @param table - Table definition
14
- * @param planner - Hydration planner
15
- */
16
- constructor(
17
- private readonly table: TableDef,
18
- private readonly planner: HydrationPlanner
19
- ) {}
20
-
21
- /**
22
- * Creates a new HydrationManager with updated planner
23
- * @param nextPlanner - Updated hydration planner
24
- * @returns New HydrationManager instance
25
- */
26
- private clone(nextPlanner: HydrationPlanner): HydrationManager {
27
- return new HydrationManager(this.table, nextPlanner);
28
- }
29
-
30
- /**
31
- * Handles column selection for hydration planning
32
- * @param state - Current query state
33
- * @param newColumns - Newly selected columns
34
- * @returns Updated HydrationManager with captured columns
35
- */
36
- onColumnsSelected(state: SelectQueryState, newColumns: ProjectionNode[]): HydrationManager {
37
- const updated = this.planner.captureRootColumns(newColumns);
38
- return this.clone(updated);
39
- }
40
-
41
- /**
42
- * Handles relation inclusion for hydration planning
43
- * @param state - Current query state
44
- * @param relation - Relation definition
45
- * @param relationName - Name of the relation
46
- * @param aliasPrefix - Alias prefix for the relation
47
- * @param targetColumns - Target columns to include
48
- * @returns Updated HydrationManager with included relation
49
- */
50
- onRelationIncluded(
51
- state: SelectQueryState,
52
- relation: RelationDef,
53
- relationName: string,
54
- aliasPrefix: string,
55
- targetColumns: string[],
56
- pivot?: { aliasPrefix: string; columns: string[] }
57
- ): HydrationManager {
58
- const withRoots = this.planner.captureRootColumns(state.ast.columns);
59
- const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
60
- return this.clone(next);
61
- }
62
-
63
- /**
64
- * Applies hydration plan to the AST
65
- * @param ast - Query AST to modify
66
- * @returns AST with hydration metadata
67
- */
68
- applyToAst(ast: SelectQueryNode): SelectQueryNode {
69
- const plan = this.planner.getPlan();
70
- if (!plan) return ast;
71
- return {
72
- ...ast,
73
- meta: {
74
- ...(ast.meta || {}),
75
- hydration: plan
76
- }
77
- };
78
- }
79
-
80
- /**
81
- * Gets the current hydration plan
82
- * @returns Hydration plan or undefined if none exists
83
- */
84
- getPlan(): HydrationPlan | undefined {
85
- return this.planner.getPlan();
86
- }
87
- }
1
+ import { TableDef } from '../schema/table.js';
2
+ import { RelationDef } from '../schema/relation.js';
3
+ import { SelectQueryNode, HydrationPlan } from '../core/ast/query.js';
4
+ import { HydrationPlanner } from './hydration-planner.js';
5
+ import { SelectQueryState, ProjectionNode } from './select-query-state.js';
6
+
7
+ /**
8
+ * Manages hydration planning for query results
9
+ */
10
+ export class HydrationManager {
11
+ /**
12
+ * Creates a new HydrationManager instance
13
+ * @param table - Table definition
14
+ * @param planner - Hydration planner
15
+ */
16
+ constructor(
17
+ private readonly table: TableDef,
18
+ private readonly planner: HydrationPlanner
19
+ ) {}
20
+
21
+ /**
22
+ * Creates a new HydrationManager with updated planner
23
+ * @param nextPlanner - Updated hydration planner
24
+ * @returns New HydrationManager instance
25
+ */
26
+ private clone(nextPlanner: HydrationPlanner): HydrationManager {
27
+ return new HydrationManager(this.table, nextPlanner);
28
+ }
29
+
30
+ /**
31
+ * Handles column selection for hydration planning
32
+ * @param state - Current query state
33
+ * @param newColumns - Newly selected columns
34
+ * @returns Updated HydrationManager with captured columns
35
+ */
36
+ onColumnsSelected(state: SelectQueryState, newColumns: ProjectionNode[]): HydrationManager {
37
+ const updated = this.planner.captureRootColumns(newColumns);
38
+ return this.clone(updated);
39
+ }
40
+
41
+ /**
42
+ * Handles relation inclusion for hydration planning
43
+ * @param state - Current query state
44
+ * @param relation - Relation definition
45
+ * @param relationName - Name of the relation
46
+ * @param aliasPrefix - Alias prefix for the relation
47
+ * @param targetColumns - Target columns to include
48
+ * @returns Updated HydrationManager with included relation
49
+ */
50
+ onRelationIncluded(
51
+ state: SelectQueryState,
52
+ relation: RelationDef,
53
+ relationName: string,
54
+ aliasPrefix: string,
55
+ targetColumns: string[],
56
+ pivot?: { aliasPrefix: string; columns: string[] }
57
+ ): HydrationManager {
58
+ const withRoots = this.planner.captureRootColumns(state.ast.columns);
59
+ const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
60
+ return this.clone(next);
61
+ }
62
+
63
+ /**
64
+ * Applies hydration plan to the AST
65
+ * @param ast - Query AST to modify
66
+ * @returns AST with hydration metadata
67
+ */
68
+ applyToAst(ast: SelectQueryNode): SelectQueryNode {
69
+ const plan = this.planner.getPlan();
70
+ if (!plan) return ast;
71
+ return {
72
+ ...ast,
73
+ meta: {
74
+ ...(ast.meta || {}),
75
+ hydration: plan
76
+ }
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Gets the current hydration plan
82
+ * @returns Hydration plan or undefined if none exists
83
+ */
84
+ getPlan(): HydrationPlan | undefined {
85
+ return this.planner.getPlan();
86
+ }
87
+ }
@@ -1,182 +1,182 @@
1
- import { TableDef } from '../schema/table';
2
- import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation';
3
- import { ProjectionNode } from './select-query-state';
4
- import { HydrationPlan, HydrationRelationPlan } from '../ast/query';
5
- import { isRelationAlias } from '../utils/relation-alias';
6
- import { buildDefaultPivotColumns } from './relation-utils';
7
-
8
- /**
9
- * Finds the primary key column name for a table
10
- * @param table - Table definition
11
- * @returns Name of the primary key column, defaults to 'id'
12
- */
13
- export const findPrimaryKey = (table: TableDef): string => {
14
- const pk = Object.values(table.columns).find(c => c.primary);
15
- return pk?.name || 'id';
16
- };
17
-
18
- /**
19
- * Manages hydration planning for query results
20
- */
21
- export class HydrationPlanner {
22
- /**
23
- * Creates a new HydrationPlanner instance
24
- * @param table - Table definition
25
- * @param plan - Optional existing hydration plan
26
- */
27
- constructor(private readonly table: TableDef, private readonly plan?: HydrationPlan) { }
28
-
29
- /**
30
- * Captures root table columns for hydration planning
31
- * @param columns - Columns to capture
32
- * @returns Updated HydrationPlanner with captured columns
33
- */
34
- captureRootColumns(columns: ProjectionNode[]): HydrationPlanner {
35
- const currentPlan = this.getPlanOrDefault();
36
- const rootCols = new Set(currentPlan.rootColumns);
37
- let changed = false;
38
-
39
- columns.forEach(node => {
40
- if (node.type !== 'Column') return;
41
- if (node.table !== this.table.name) return;
42
-
43
- const alias = node.alias || node.name;
44
- if (isRelationAlias(alias)) return;
45
- if (!rootCols.has(alias)) {
46
- rootCols.add(alias);
47
- changed = true;
48
- }
49
- });
50
-
51
- if (!changed) return this;
52
- return new HydrationPlanner(this.table, {
53
- ...currentPlan,
54
- rootColumns: Array.from(rootCols)
55
- });
56
- }
57
-
58
- /**
59
- * Includes a relation in the hydration plan
60
- * @param rel - Relation definition
61
- * @param relationName - Name of the relation
62
- * @param aliasPrefix - Alias prefix for relation columns
63
- * @param columns - Columns to include from the relation
64
- * @returns Updated HydrationPlanner with included relation
65
- */
66
- includeRelation(
67
- rel: RelationDef,
68
- relationName: string,
69
- aliasPrefix: string,
70
- columns: string[],
71
- pivot?: { aliasPrefix: string; columns: string[] }
72
- ): HydrationPlanner {
73
- const currentPlan = this.getPlanOrDefault();
74
- const relations = currentPlan.relations.filter(r => r.name !== relationName);
75
- relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
76
- return new HydrationPlanner(this.table, {
77
- ...currentPlan,
78
- relations
79
- });
80
- }
81
-
82
- /**
83
- * Gets the current hydration plan
84
- * @returns Current hydration plan or undefined
85
- */
86
- getPlan(): HydrationPlan | undefined {
87
- return this.plan;
88
- }
89
-
90
- /**
91
- * Gets the current hydration plan or creates a default one
92
- * @returns Current hydration plan or default plan
93
- */
94
- private getPlanOrDefault(): HydrationPlan {
95
- return this.plan ?? buildDefaultHydrationPlan(this.table);
96
- }
97
-
98
- /**
99
- * Builds a relation plan for hydration
100
- * @param rel - Relation definition
101
- * @param relationName - Name of the relation
102
- * @param aliasPrefix - Alias prefix for relation columns
103
- * @param columns - Columns to include from the relation
104
- * @returns Hydration relation plan
105
- */
106
- private buildRelationPlan(
107
- rel: RelationDef,
108
- relationName: string,
109
- aliasPrefix: string,
110
- columns: string[],
111
- pivot?: { aliasPrefix: string; columns: string[] }
112
- ): HydrationRelationPlan {
113
- switch (rel.type) {
114
- case RelationKinds.HasMany: {
115
- const localKey = rel.localKey || findPrimaryKey(this.table);
116
- return {
117
- name: relationName,
118
- aliasPrefix,
119
- type: rel.type,
120
- targetTable: rel.target.name,
121
- targetPrimaryKey: findPrimaryKey(rel.target),
122
- foreignKey: rel.foreignKey,
123
- localKey,
124
- columns
125
- };
126
- }
127
- case RelationKinds.BelongsTo: {
128
- const localKey = rel.localKey || findPrimaryKey(rel.target);
129
- return {
130
- name: relationName,
131
- aliasPrefix,
132
- type: rel.type,
133
- targetTable: rel.target.name,
134
- targetPrimaryKey: findPrimaryKey(rel.target),
135
- foreignKey: rel.foreignKey,
136
- localKey,
137
- columns
138
- };
139
- }
140
- case RelationKinds.BelongsToMany: {
141
- const many = rel as BelongsToManyRelation;
142
- const localKey = many.localKey || findPrimaryKey(this.table);
143
- const targetPk = many.targetKey || findPrimaryKey(many.target);
144
- const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
145
- const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
146
- const pivotColumns =
147
- pivot?.columns ??
148
- many.defaultPivotColumns ??
149
- buildDefaultPivotColumns(many, pivotPk);
150
-
151
- return {
152
- name: relationName,
153
- aliasPrefix,
154
- type: rel.type,
155
- targetTable: many.target.name,
156
- targetPrimaryKey: targetPk,
157
- foreignKey: many.pivotForeignKeyToRoot,
158
- localKey,
159
- columns,
160
- pivot: {
161
- table: many.pivotTable.name,
162
- primaryKey: pivotPk,
163
- aliasPrefix: pivotAliasPrefix,
164
- columns: pivotColumns
165
- }
166
- };
167
- }
168
- }
169
- }
170
- }
171
-
172
- /**
173
- * Builds a default hydration plan for a table
174
- * @param table - Table definition
175
- * @returns Default hydration plan
176
- */
177
- const buildDefaultHydrationPlan = (table: TableDef): HydrationPlan => ({
178
- rootTable: table.name,
179
- rootPrimaryKey: findPrimaryKey(table),
180
- rootColumns: [],
181
- relations: []
182
- });
1
+ import { TableDef } from '../schema/table.js';
2
+ import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation.js';
3
+ import { ProjectionNode } from './select-query-state.js';
4
+ import { HydrationPlan, HydrationRelationPlan } from '../core/ast/query.js';
5
+ import { isRelationAlias } from './relation-alias.js';
6
+ import { buildDefaultPivotColumns } from './relation-utils.js';
7
+
8
+ /**
9
+ * Finds the primary key column name for a table
10
+ * @param table - Table definition
11
+ * @returns Name of the primary key column, defaults to 'id'
12
+ */
13
+ export const findPrimaryKey = (table: TableDef): string => {
14
+ const pk = Object.values(table.columns).find(c => c.primary);
15
+ return pk?.name || 'id';
16
+ };
17
+
18
+ /**
19
+ * Manages hydration planning for query results
20
+ */
21
+ export class HydrationPlanner {
22
+ /**
23
+ * Creates a new HydrationPlanner instance
24
+ * @param table - Table definition
25
+ * @param plan - Optional existing hydration plan
26
+ */
27
+ constructor(private readonly table: TableDef, private readonly plan?: HydrationPlan) { }
28
+
29
+ /**
30
+ * Captures root table columns for hydration planning
31
+ * @param columns - Columns to capture
32
+ * @returns Updated HydrationPlanner with captured columns
33
+ */
34
+ captureRootColumns(columns: ProjectionNode[]): HydrationPlanner {
35
+ const currentPlan = this.getPlanOrDefault();
36
+ const rootCols = new Set(currentPlan.rootColumns);
37
+ let changed = false;
38
+
39
+ columns.forEach(node => {
40
+ if (node.type !== 'Column') return;
41
+ if (node.table !== this.table.name) return;
42
+
43
+ const alias = node.alias || node.name;
44
+ if (isRelationAlias(alias)) return;
45
+ if (!rootCols.has(alias)) {
46
+ rootCols.add(alias);
47
+ changed = true;
48
+ }
49
+ });
50
+
51
+ if (!changed) return this;
52
+ return new HydrationPlanner(this.table, {
53
+ ...currentPlan,
54
+ rootColumns: Array.from(rootCols)
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Includes a relation in the hydration plan
60
+ * @param rel - Relation definition
61
+ * @param relationName - Name of the relation
62
+ * @param aliasPrefix - Alias prefix for relation columns
63
+ * @param columns - Columns to include from the relation
64
+ * @returns Updated HydrationPlanner with included relation
65
+ */
66
+ includeRelation(
67
+ rel: RelationDef,
68
+ relationName: string,
69
+ aliasPrefix: string,
70
+ columns: string[],
71
+ pivot?: { aliasPrefix: string; columns: string[] }
72
+ ): HydrationPlanner {
73
+ const currentPlan = this.getPlanOrDefault();
74
+ const relations = currentPlan.relations.filter(r => r.name !== relationName);
75
+ relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
76
+ return new HydrationPlanner(this.table, {
77
+ ...currentPlan,
78
+ relations
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Gets the current hydration plan
84
+ * @returns Current hydration plan or undefined
85
+ */
86
+ getPlan(): HydrationPlan | undefined {
87
+ return this.plan;
88
+ }
89
+
90
+ /**
91
+ * Gets the current hydration plan or creates a default one
92
+ * @returns Current hydration plan or default plan
93
+ */
94
+ private getPlanOrDefault(): HydrationPlan {
95
+ return this.plan ?? buildDefaultHydrationPlan(this.table);
96
+ }
97
+
98
+ /**
99
+ * Builds a relation plan for hydration
100
+ * @param rel - Relation definition
101
+ * @param relationName - Name of the relation
102
+ * @param aliasPrefix - Alias prefix for relation columns
103
+ * @param columns - Columns to include from the relation
104
+ * @returns Hydration relation plan
105
+ */
106
+ private buildRelationPlan(
107
+ rel: RelationDef,
108
+ relationName: string,
109
+ aliasPrefix: string,
110
+ columns: string[],
111
+ pivot?: { aliasPrefix: string; columns: string[] }
112
+ ): HydrationRelationPlan {
113
+ switch (rel.type) {
114
+ case RelationKinds.HasMany: {
115
+ const localKey = rel.localKey || findPrimaryKey(this.table);
116
+ return {
117
+ name: relationName,
118
+ aliasPrefix,
119
+ type: rel.type,
120
+ targetTable: rel.target.name,
121
+ targetPrimaryKey: findPrimaryKey(rel.target),
122
+ foreignKey: rel.foreignKey,
123
+ localKey,
124
+ columns
125
+ };
126
+ }
127
+ case RelationKinds.BelongsTo: {
128
+ const localKey = rel.localKey || findPrimaryKey(rel.target);
129
+ return {
130
+ name: relationName,
131
+ aliasPrefix,
132
+ type: rel.type,
133
+ targetTable: rel.target.name,
134
+ targetPrimaryKey: findPrimaryKey(rel.target),
135
+ foreignKey: rel.foreignKey,
136
+ localKey,
137
+ columns
138
+ };
139
+ }
140
+ case RelationKinds.BelongsToMany: {
141
+ const many = rel as BelongsToManyRelation;
142
+ const localKey = many.localKey || findPrimaryKey(this.table);
143
+ const targetPk = many.targetKey || findPrimaryKey(many.target);
144
+ const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
145
+ const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
146
+ const pivotColumns =
147
+ pivot?.columns ??
148
+ many.defaultPivotColumns ??
149
+ buildDefaultPivotColumns(many, pivotPk);
150
+
151
+ return {
152
+ name: relationName,
153
+ aliasPrefix,
154
+ type: rel.type,
155
+ targetTable: many.target.name,
156
+ targetPrimaryKey: targetPk,
157
+ foreignKey: many.pivotForeignKeyToRoot,
158
+ localKey,
159
+ columns,
160
+ pivot: {
161
+ table: many.pivotTable.name,
162
+ primaryKey: pivotPk,
163
+ aliasPrefix: pivotAliasPrefix,
164
+ columns: pivotColumns
165
+ }
166
+ };
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Builds a default hydration plan for a table
174
+ * @param table - Table definition
175
+ * @returns Default hydration plan
176
+ */
177
+ const buildDefaultHydrationPlan = (table: TableDef): HydrationPlan => ({
178
+ rootTable: table.name,
179
+ rootPrimaryKey: findPrimaryKey(table),
180
+ rootColumns: [],
181
+ relations: []
182
+ });
@@ -1,62 +1,51 @@
1
- import { TableDef } from '../schema/table';
2
- import { InsertQueryNode, TableNode } from '../ast/query';
3
- import { ColumnNode, OperandNode, valueToOperand } from '../ast/expression';
4
-
5
- const createTableNode = (table: TableDef): TableNode => ({
6
- type: 'Table',
7
- name: table.name
8
- });
9
-
10
- const buildColumnNodes = (table: TableDef, names: string[]): ColumnNode[] =>
11
- names.map(name => ({
12
- type: 'Column',
13
- table: table.name,
14
- name
15
- }));
16
-
17
- /**
18
- * Maintains immutable state for building INSERT queries
19
- */
20
- export class InsertQueryState {
21
- public readonly table: TableDef;
22
- public readonly ast: InsertQueryNode;
23
-
24
- constructor(table: TableDef, ast?: InsertQueryNode) {
25
- this.table = table;
26
- this.ast = ast ?? {
27
- type: 'InsertQuery',
28
- into: createTableNode(table),
29
- columns: [],
30
- values: []
31
- };
32
- }
33
-
34
- private clone(nextAst: InsertQueryNode): InsertQueryState {
35
- return new InsertQueryState(this.table, nextAst);
36
- }
37
-
38
- withValues(rows: Record<string, unknown>[]): InsertQueryState {
39
- if (!rows.length) return this;
40
-
41
- const definedColumns = this.ast.columns.length
42
- ? this.ast.columns
43
- : buildColumnNodes(this.table, Object.keys(rows[0]));
44
-
45
- const newRows: OperandNode[][] = rows.map(row =>
46
- definedColumns.map(column => valueToOperand(row[column.name]))
47
- );
48
-
49
- return this.clone({
50
- ...this.ast,
51
- columns: definedColumns,
52
- values: [...this.ast.values, ...newRows]
53
- });
54
- }
55
-
56
- withReturning(columns: ColumnNode[]): InsertQueryState {
57
- return this.clone({
58
- ...this.ast,
59
- returning: [...columns]
60
- });
61
- }
62
- }
1
+ import { TableDef } from '../schema/table.js';
2
+ import { InsertQueryNode, TableNode } from '../core/ast/query.js';
3
+ import { ColumnNode, OperandNode, valueToOperand } from '../core/ast/expression.js';
4
+ import { buildColumnNodes, createTableNode } from '../core/ast/builders.js';
5
+
6
+ /**
7
+ * Maintains immutable state for building INSERT queries
8
+ */
9
+ export class InsertQueryState {
10
+ public readonly table: TableDef;
11
+ public readonly ast: InsertQueryNode;
12
+
13
+ constructor(table: TableDef, ast?: InsertQueryNode) {
14
+ this.table = table;
15
+ this.ast = ast ?? {
16
+ type: 'InsertQuery',
17
+ into: createTableNode(table),
18
+ columns: [],
19
+ values: []
20
+ };
21
+ }
22
+
23
+ private clone(nextAst: InsertQueryNode): InsertQueryState {
24
+ return new InsertQueryState(this.table, nextAst);
25
+ }
26
+
27
+ withValues(rows: Record<string, unknown>[]): InsertQueryState {
28
+ if (!rows.length) return this;
29
+
30
+ const definedColumns = this.ast.columns.length
31
+ ? this.ast.columns
32
+ : buildColumnNodes(this.table, Object.keys(rows[0]));
33
+
34
+ const newRows: OperandNode[][] = rows.map(row =>
35
+ definedColumns.map(column => valueToOperand(row[column.name]))
36
+ );
37
+
38
+ return this.clone({
39
+ ...this.ast,
40
+ columns: definedColumns,
41
+ values: [...this.ast.values, ...newRows]
42
+ });
43
+ }
44
+
45
+ withReturning(columns: ColumnNode[]): InsertQueryState {
46
+ return this.clone({
47
+ ...this.ast,
48
+ returning: [...columns]
49
+ });
50
+ }
51
+ }