metal-orm 1.1.10 → 1.1.16

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 (192) hide show
  1. package/README.md +758 -769
  2. package/dist/index.cjs +106 -43
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +5 -5
  5. package/dist/index.d.ts +5 -5
  6. package/dist/index.js +106 -43
  7. package/dist/index.js.map +1 -1
  8. package/package.json +100 -96
  9. package/scripts/generate-entities/render.mjs +21 -12
  10. package/scripts/generate-entities/schema.mjs +209 -195
  11. package/scripts/generate-entities/tree-detection.mjs +159 -153
  12. package/scripts/inflection/pt-br.mjs +468 -468
  13. package/scripts/run-eslint.mjs +33 -34
  14. package/scripts/show-sql-seed.sql +102 -0
  15. package/scripts/show-sql.mjs +321 -325
  16. package/src/bulk/bulk-delete-executor.ts +3 -5
  17. package/src/bulk/bulk-insert-executor.ts +7 -7
  18. package/src/bulk/bulk-update-executor.ts +2 -2
  19. package/src/bulk/bulk-upsert-executor.ts +5 -7
  20. package/src/codegen/naming-strategy.ts +54 -54
  21. package/src/codegen/typescript.ts +482 -482
  22. package/src/core/ast/adapters.ts +1 -1
  23. package/src/core/ast/builders.ts +123 -123
  24. package/src/core/ast/expression-builders.ts +745 -745
  25. package/src/core/ast/expression-nodes.ts +304 -304
  26. package/src/core/ast/expression-visitor.ts +212 -212
  27. package/src/core/ast/expression.ts +20 -20
  28. package/src/core/ast/helpers.ts +14 -14
  29. package/src/core/ast/join.ts +18 -18
  30. package/src/core/ast/query.ts +200 -200
  31. package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
  32. package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
  33. package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
  34. package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
  35. package/src/core/ddl/dialects/render-reference.test.ts +70 -70
  36. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
  37. package/src/core/ddl/introspect/catalogs/index.ts +5 -5
  38. package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
  39. package/src/core/ddl/introspect/context.ts +15 -15
  40. package/src/core/ddl/introspect/functions/postgres.ts +35 -35
  41. package/src/core/ddl/introspect/mysql.ts +113 -36
  42. package/src/core/ddl/introspect/registry.ts +40 -40
  43. package/src/core/ddl/introspect/run-select.ts +35 -35
  44. package/src/core/ddl/introspect/utils.ts +56 -56
  45. package/src/core/ddl/naming-strategy.ts +16 -16
  46. package/src/core/ddl/schema-dialect.ts +56 -56
  47. package/src/core/ddl/schema-diff.ts +234 -234
  48. package/src/core/ddl/schema-generator.ts +1 -1
  49. package/src/core/ddl/schema-introspect.ts +25 -25
  50. package/src/core/ddl/sql-writing.ts +171 -171
  51. package/src/core/dialect/abstract.ts +541 -541
  52. package/src/core/dialect/base/cte-compiler.ts +33 -33
  53. package/src/core/dialect/base/function-table-formatter.ts +103 -103
  54. package/src/core/dialect/base/groupby-compiler.ts +21 -21
  55. package/src/core/dialect/base/join-compiler.ts +25 -25
  56. package/src/core/dialect/base/orderby-compiler.ts +35 -35
  57. package/src/core/dialect/base/pagination-strategy.ts +32 -32
  58. package/src/core/dialect/base/returning-strategy.ts +57 -57
  59. package/src/core/dialect/base/sql-dialect.ts +294 -294
  60. package/src/core/dialect/dialect-factory.ts +91 -91
  61. package/src/core/dialect/mssql/functions.ts +120 -120
  62. package/src/core/dialect/mssql/index.ts +317 -317
  63. package/src/core/dialect/mysql/functions.ts +103 -103
  64. package/src/core/dialect/mysql/index.ts +105 -105
  65. package/src/core/dialect/postgres/functions.ts +108 -108
  66. package/src/core/dialect/postgres/index.ts +91 -91
  67. package/src/core/dialect/sqlite/functions.ts +129 -129
  68. package/src/core/dialect/sqlite/index.ts +67 -67
  69. package/src/core/driver/database-driver.ts +19 -19
  70. package/src/core/driver/mssql-driver.ts +23 -23
  71. package/src/core/driver/mysql-driver.ts +23 -23
  72. package/src/core/driver/postgres-driver.ts +23 -23
  73. package/src/core/driver/sqlite-driver.ts +23 -23
  74. package/src/core/execution/db-executor.ts +92 -92
  75. package/src/core/execution/executors/better-sqlite3-executor.ts +94 -94
  76. package/src/core/execution/executors/mssql-executor.ts +184 -184
  77. package/src/core/execution/executors/mysql-executor.ts +88 -88
  78. package/src/core/execution/executors/postgres-executor.ts +26 -26
  79. package/src/core/execution/executors/sqlite-executor.ts +42 -42
  80. package/src/core/execution/pooling/pool-types.ts +30 -30
  81. package/src/core/execution/pooling/pool.ts +275 -275
  82. package/src/core/functions/array.ts +35 -35
  83. package/src/core/functions/control-flow.ts +79 -79
  84. package/src/core/functions/datetime.ts +237 -237
  85. package/src/core/functions/json.ts +70 -70
  86. package/src/core/functions/numeric.ts +297 -297
  87. package/src/core/functions/text.ts +334 -334
  88. package/src/core/functions/types.ts +33 -33
  89. package/src/core/hydration/types.ts +51 -51
  90. package/src/core/sql/sql-operator-config.ts +39 -39
  91. package/src/core/sql/sql.ts +152 -152
  92. package/src/decorators/column-decorator.ts +87 -87
  93. package/src/decorators/decorator-metadata.ts +143 -143
  94. package/src/decorators/entity.ts +93 -93
  95. package/src/decorators/index.ts +39 -39
  96. package/src/decorators/relations.ts +231 -231
  97. package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
  98. package/src/decorators/transformers/transformer-decorators.ts +134 -134
  99. package/src/decorators/transformers/transformer-executor.ts +171 -171
  100. package/src/decorators/transformers/transformer-metadata.ts +73 -73
  101. package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
  102. package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
  103. package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
  104. package/src/decorators/validators/country-validator-registry.ts +64 -64
  105. package/src/decorators/validators/country-validators-decorators.ts +203 -203
  106. package/src/decorators/validators/country-validators.ts +105 -105
  107. package/src/decorators/validators/validator-metadata.ts +59 -59
  108. package/src/dto/dto-types.ts +226 -226
  109. package/src/dto/filter-types.ts +141 -141
  110. package/src/dto/index.ts +95 -95
  111. package/src/dto/openapi/generators/base.ts +29 -29
  112. package/src/dto/openapi/generators/column.ts +37 -37
  113. package/src/dto/openapi/generators/dto.ts +91 -91
  114. package/src/dto/openapi/generators/filter.ts +75 -75
  115. package/src/dto/openapi/generators/nested-dto.ts +618 -618
  116. package/src/dto/openapi/generators/pagination.ts +111 -111
  117. package/src/dto/openapi/generators/relation-filter.ts +228 -228
  118. package/src/dto/openapi/index.ts +12 -12
  119. package/src/dto/openapi/types.ts +101 -101
  120. package/src/dto/openapi/utilities.ts +95 -95
  121. package/src/dto/pagination-utils.ts +150 -150
  122. package/src/dto/transform.ts +197 -197
  123. package/src/index.ts +86 -86
  124. package/src/orm/als.ts +58 -58
  125. package/src/orm/domain-event-bus.ts +119 -119
  126. package/src/orm/entity-context.ts +86 -86
  127. package/src/orm/entity-materializer.ts +159 -159
  128. package/src/orm/entity-meta.ts +97 -97
  129. package/src/orm/entity-registry.ts +39 -39
  130. package/src/orm/entity-relation-cache.ts +39 -39
  131. package/src/orm/execute.ts +194 -194
  132. package/src/orm/execution-context.ts +18 -18
  133. package/src/orm/hydration-context.ts +26 -26
  134. package/src/orm/hydration.ts +112 -112
  135. package/src/orm/identity-map.ts +61 -61
  136. package/src/orm/interceptor-pipeline.ts +16 -16
  137. package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
  138. package/src/orm/lazy-batch/belongs-to.ts +108 -108
  139. package/src/orm/lazy-batch/has-many.ts +69 -69
  140. package/src/orm/lazy-batch/has-one.ts +68 -68
  141. package/src/orm/lazy-batch/shared.ts +125 -125
  142. package/src/orm/orm-session.ts +620 -620
  143. package/src/orm/orm.ts +34 -34
  144. package/src/orm/pooled-executor-factory.ts +121 -121
  145. package/src/orm/query-logger.ts +40 -40
  146. package/src/orm/relation-change-processor.ts +420 -420
  147. package/src/orm/relations/belongs-to.ts +143 -143
  148. package/src/orm/relations/has-many.ts +204 -204
  149. package/src/orm/relations/has-one.ts +174 -174
  150. package/src/orm/relations/many-to-many.ts +288 -288
  151. package/src/orm/relations/morph-many.ts +156 -156
  152. package/src/orm/relations/morph-one.ts +151 -151
  153. package/src/orm/relations/morph-to.ts +162 -162
  154. package/src/orm/runtime-types.ts +92 -92
  155. package/src/orm/save-graph.ts +561 -561
  156. package/src/orm/transaction-runner.ts +24 -24
  157. package/src/orm/unit-of-work.ts +426 -426
  158. package/src/query/index.ts +69 -69
  159. package/src/query/target.ts +46 -46
  160. package/src/query-builder/column-selector.ts +81 -81
  161. package/src/query-builder/delete.ts +141 -141
  162. package/src/query-builder/insert-query-state.ts +149 -149
  163. package/src/query-builder/insert.ts +117 -117
  164. package/src/query-builder/query-ast-service.ts +287 -287
  165. package/src/query-builder/query-resolution.ts +78 -78
  166. package/src/query-builder/raw-column-parser.ts +38 -38
  167. package/src/query-builder/relation-filter-utils.ts +153 -153
  168. package/src/query-builder/relation-join-planner.ts +10 -10
  169. package/src/query-builder/relation-manager.ts +87 -87
  170. package/src/query-builder/relation-projection-helper.ts +102 -102
  171. package/src/query-builder/relation-types.ts +9 -9
  172. package/src/query-builder/relation-utils.ts +15 -15
  173. package/src/query-builder/select/cte-facet.ts +40 -40
  174. package/src/query-builder/select/from-facet.ts +80 -80
  175. package/src/query-builder/select/join-facet.ts +62 -62
  176. package/src/query-builder/select/predicate-facet.ts +104 -104
  177. package/src/query-builder/select/projection-facet.ts +70 -70
  178. package/src/query-builder/select/relation-facet.ts +81 -81
  179. package/src/query-builder/select/setop-facet.ts +36 -36
  180. package/src/query-builder/select-helpers.ts +1 -1
  181. package/src/query-builder/select-query-builder-deps.ts +130 -130
  182. package/src/query-builder/select-query-state.ts +207 -207
  183. package/src/query-builder/update.ts +151 -151
  184. package/src/schema/column-types.ts +322 -322
  185. package/src/schema/table-guards.ts +37 -37
  186. package/src/schema/table.ts +281 -281
  187. package/src/tree/index.ts +13 -13
  188. package/src/tree/nested-set-strategy.ts +545 -545
  189. package/src/tree/tree-decorator.ts +397 -397
  190. package/src/tree/tree-manager.ts +754 -754
  191. package/src/tree/tree-query.ts +427 -427
  192. package/src/tree/tree-types.ts +299 -299
@@ -1,105 +1,105 @@
1
- import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
2
- import { RelationKinds } from '../schema/relation.js';
3
- import { isRelationAlias, makeRelationAlias } from '../query-builder/relation-alias.js';
4
-
5
- /**
6
- * Hydrates query results according to a hydration plan
7
- * @param rows - Raw database rows
8
-
9
- /**
10
- * Hydrates query results according to a hydration plan
11
- * @param rows - Raw database rows
12
- * @param plan - Hydration plan
13
- * @returns Hydrated result objects with nested relations
14
- */
15
- export const hydrateRows = (rows: Record<string, unknown>[], plan?: HydrationPlan): Record<string, unknown>[] => {
16
- if (!plan || !rows.length) return rows;
17
-
18
- const rootMap = new Map<unknown, Record<string, unknown>>();
19
- const relationIndex = new Map<unknown, Record<string, Set<unknown>>>();
20
-
21
- const getOrCreateParent = (row: Record<string, unknown>) => {
22
- const rootId = row[plan.rootPrimaryKey];
23
- if (rootId === undefined) return undefined;
24
-
25
- if (!rootMap.has(rootId)) {
26
- rootMap.set(rootId, createBaseRow(row, plan));
27
- }
28
-
29
- return rootMap.get(rootId);
30
- };
31
-
32
- const getRelationSeenSet = (rootId: unknown, relationName: string): Set<unknown> => {
33
- let byRelation = relationIndex.get(rootId);
34
- if (!byRelation) {
35
- byRelation = {};
36
- relationIndex.set(rootId, byRelation);
37
- }
38
-
39
- let seen = byRelation[relationName];
40
- if (!seen) {
41
- seen = new Set<unknown>();
42
- byRelation[relationName] = seen;
43
- }
44
-
45
- return seen;
46
- };
47
-
48
- const hasRelations = plan.relations.length > 0;
49
-
50
- for (const row of rows) {
51
- const rootId = row[plan.rootPrimaryKey];
52
-
53
- if (rootId === undefined || rootId === null) {
54
- if (!hasRelations) {
55
- rootMap.set(Symbol(), createBaseRow(row, plan));
56
- }
57
- continue;
58
- }
59
-
60
- const parent = getOrCreateParent(row);
61
- if (!parent) continue;
62
-
63
- for (const rel of plan.relations) {
64
- const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
65
- const childPk = row[childPkKey];
66
- if (childPk === null || childPk === undefined) continue;
67
-
68
- const seen = getRelationSeenSet(rootId, rel.name);
69
- if (seen.has(childPk)) continue;
70
- seen.add(childPk);
71
-
72
- if (rel.type === RelationKinds.HasOne) {
73
- if (!parent[rel.name]) {
74
- parent[rel.name] = buildChild(row, rel);
75
- }
76
- continue;
77
- }
78
-
79
- const bucket = parent[rel.name] as unknown[];
80
- bucket.push(buildChild(row, rel));
81
- }
82
- }
83
-
84
- return Array.from(rootMap.values());
85
- };
86
-
87
- const createBaseRow = (row: Record<string, unknown>, plan: HydrationPlan): Record<string, unknown> => {
88
- const base: Record<string, unknown> = {};
89
- const baseKeys = plan.rootColumns.length
90
- ? plan.rootColumns
91
- : Object.keys(row).filter(k => !isRelationAlias(k));
92
-
93
- for (const key of baseKeys) {
94
- base[key] = row[key];
95
- }
96
-
97
- for (const rel of plan.relations) {
98
- base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
99
- }
100
- return base;
101
- };
102
-
1
+ import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
2
+ import { RelationKinds } from '../schema/relation.js';
3
+ import { isRelationAlias, makeRelationAlias } from '../query-builder/relation-alias.js';
4
+
5
+ /**
6
+ * Hydrates query results according to a hydration plan
7
+ * @param rows - Raw database rows
8
+
9
+ /**
10
+ * Hydrates query results according to a hydration plan
11
+ * @param rows - Raw database rows
12
+ * @param plan - Hydration plan
13
+ * @returns Hydrated result objects with nested relations
14
+ */
15
+ export const hydrateRows = (rows: Record<string, unknown>[], plan?: HydrationPlan): Record<string, unknown>[] => {
16
+ if (!plan || !rows.length) return rows;
17
+
18
+ const rootMap = new Map<unknown, Record<string, unknown>>();
19
+ const relationIndex = new Map<unknown, Record<string, Set<unknown>>>();
20
+
21
+ const getOrCreateParent = (row: Record<string, unknown>) => {
22
+ const rootId = row[plan.rootPrimaryKey];
23
+ if (rootId === undefined) return undefined;
24
+
25
+ if (!rootMap.has(rootId)) {
26
+ rootMap.set(rootId, createBaseRow(row, plan));
27
+ }
28
+
29
+ return rootMap.get(rootId);
30
+ };
31
+
32
+ const getRelationSeenSet = (rootId: unknown, relationName: string): Set<unknown> => {
33
+ let byRelation = relationIndex.get(rootId);
34
+ if (!byRelation) {
35
+ byRelation = {};
36
+ relationIndex.set(rootId, byRelation);
37
+ }
38
+
39
+ let seen = byRelation[relationName];
40
+ if (!seen) {
41
+ seen = new Set<unknown>();
42
+ byRelation[relationName] = seen;
43
+ }
44
+
45
+ return seen;
46
+ };
47
+
48
+ const hasRelations = plan.relations.length > 0;
49
+
50
+ for (const row of rows) {
51
+ const rootId = row[plan.rootPrimaryKey];
52
+
53
+ if (rootId === undefined || rootId === null) {
54
+ if (!hasRelations) {
55
+ rootMap.set(Symbol(), createBaseRow(row, plan));
56
+ }
57
+ continue;
58
+ }
59
+
60
+ const parent = getOrCreateParent(row);
61
+ if (!parent) continue;
62
+
63
+ for (const rel of plan.relations) {
64
+ const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
65
+ const childPk = row[childPkKey];
66
+ if (childPk === null || childPk === undefined) continue;
67
+
68
+ const seen = getRelationSeenSet(rootId, rel.name);
69
+ if (seen.has(childPk)) continue;
70
+ seen.add(childPk);
71
+
72
+ if (rel.type === RelationKinds.HasOne) {
73
+ if (!parent[rel.name]) {
74
+ parent[rel.name] = buildChild(row, rel);
75
+ }
76
+ continue;
77
+ }
78
+
79
+ const bucket = parent[rel.name] as unknown[];
80
+ bucket.push(buildChild(row, rel));
81
+ }
82
+ }
83
+
84
+ return Array.from(rootMap.values());
85
+ };
86
+
87
+ const createBaseRow = (row: Record<string, unknown>, plan: HydrationPlan): Record<string, unknown> => {
88
+ const base: Record<string, unknown> = {};
89
+ const baseKeys = plan.rootColumns.length
90
+ ? plan.rootColumns
91
+ : Object.keys(row).filter(k => !isRelationAlias(k));
92
+
93
+ for (const key of baseKeys) {
94
+ base[key] = row[key];
95
+ }
96
+
97
+ for (const rel of plan.relations) {
98
+ base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
99
+ }
100
+ return base;
101
+ };
102
+
103
103
  const buildChild = (row: Record<string, unknown>, rel: HydrationRelationPlan): Record<string, unknown> => {
104
104
  const child: Record<string, unknown> = {};
105
105
  for (const col of rel.columns) {
@@ -127,13 +127,13 @@ const mergePivotIntoChild = (child: Record<string, unknown>, pivot: Record<strin
127
127
 
128
128
  const buildPivot = (row: Record<string, unknown>, rel: HydrationRelationPlan): Record<string, unknown> | undefined => {
129
129
  if (!rel.pivot) return undefined;
130
-
131
- const pivot: Record<string, unknown> = {};
132
- for (const col of rel.pivot.columns) {
133
- const key = makeRelationAlias(rel.pivot.aliasPrefix, col);
134
- pivot[col] = row[key];
135
- }
136
-
137
- const hasValue = Object.values(pivot).some(v => v !== null && v !== undefined);
138
- return hasValue ? pivot : undefined;
139
- };
130
+
131
+ const pivot: Record<string, unknown> = {};
132
+ for (const col of rel.pivot.columns) {
133
+ const key = makeRelationAlias(rel.pivot.aliasPrefix, col);
134
+ pivot[col] = row[key];
135
+ }
136
+
137
+ const hasValue = Object.values(pivot).some(v => v !== null && v !== undefined);
138
+ return hasValue ? pivot : undefined;
139
+ };
@@ -1,61 +1,61 @@
1
- import type { TableDef } from '../schema/table.js';
2
- import type { TrackedEntity } from './runtime-types.js';
3
- import type { PrimaryKey } from './entity-context.js';
4
-
5
- /**
6
- * Simple identity map for tracking entities within a session.
7
- * Ensures that the same database record is represented by a single entity instance.
8
- */
9
- export class IdentityMap {
10
- private readonly buckets = new Map<string, Map<string, TrackedEntity>>();
11
-
12
- get bucketsMap(): Map<string, Map<string, TrackedEntity>> {
13
- return this.buckets;
14
- }
15
-
16
- /**
17
- * Retrieves an entity from the identity map if it exists.
18
- * @param table The table definition of the entity.
19
- * @param pk The primary key value.
20
- * @returns The entity instance if found, undefined otherwise.
21
- */
22
- getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
23
- const bucket = this.buckets.get(table.name);
24
- return bucket?.get(this.toIdentityKey(pk))?.entity;
25
- }
26
-
27
- /**
28
- * Registers a tracked entity in the identity map.
29
- * @param tracked The tracked entity metadata and instance.
30
- */
31
- register(tracked: TrackedEntity): void {
32
- if (tracked.pk == null) return;
33
- const bucket = this.buckets.get(tracked.table.name) ?? new Map<string, TrackedEntity>();
34
- bucket.set(this.toIdentityKey(tracked.pk), tracked);
35
- this.buckets.set(tracked.table.name, bucket);
36
- }
37
-
38
- remove(tracked: TrackedEntity): void {
39
- if (tracked.pk == null) return;
40
- const bucket = this.buckets.get(tracked.table.name);
41
- bucket?.delete(this.toIdentityKey(tracked.pk));
42
- }
43
-
44
- /**
45
- * Returns all tracked entities for a specific table.
46
- * @param table The table definition.
47
- * @returns Array of tracked entities.
48
- */
49
- getEntitiesForTable(table: TableDef): TrackedEntity[] {
50
- const bucket = this.buckets.get(table.name);
51
- return bucket ? Array.from(bucket.values()) : [];
52
- }
53
-
54
- clear(): void {
55
- this.buckets.clear();
56
- }
57
-
58
- private toIdentityKey(pk: PrimaryKey): string {
59
- return String(pk);
60
- }
61
- }
1
+ import type { TableDef } from '../schema/table.js';
2
+ import type { TrackedEntity } from './runtime-types.js';
3
+ import type { PrimaryKey } from './entity-context.js';
4
+
5
+ /**
6
+ * Simple identity map for tracking entities within a session.
7
+ * Ensures that the same database record is represented by a single entity instance.
8
+ */
9
+ export class IdentityMap {
10
+ private readonly buckets = new Map<string, Map<string, TrackedEntity>>();
11
+
12
+ get bucketsMap(): Map<string, Map<string, TrackedEntity>> {
13
+ return this.buckets;
14
+ }
15
+
16
+ /**
17
+ * Retrieves an entity from the identity map if it exists.
18
+ * @param table The table definition of the entity.
19
+ * @param pk The primary key value.
20
+ * @returns The entity instance if found, undefined otherwise.
21
+ */
22
+ getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
23
+ const bucket = this.buckets.get(table.name);
24
+ return bucket?.get(this.toIdentityKey(pk))?.entity;
25
+ }
26
+
27
+ /**
28
+ * Registers a tracked entity in the identity map.
29
+ * @param tracked The tracked entity metadata and instance.
30
+ */
31
+ register(tracked: TrackedEntity): void {
32
+ if (tracked.pk == null) return;
33
+ const bucket = this.buckets.get(tracked.table.name) ?? new Map<string, TrackedEntity>();
34
+ bucket.set(this.toIdentityKey(tracked.pk), tracked);
35
+ this.buckets.set(tracked.table.name, bucket);
36
+ }
37
+
38
+ remove(tracked: TrackedEntity): void {
39
+ if (tracked.pk == null) return;
40
+ const bucket = this.buckets.get(tracked.table.name);
41
+ bucket?.delete(this.toIdentityKey(tracked.pk));
42
+ }
43
+
44
+ /**
45
+ * Returns all tracked entities for a specific table.
46
+ * @param table The table definition.
47
+ * @returns Array of tracked entities.
48
+ */
49
+ getEntitiesForTable(table: TableDef): TrackedEntity[] {
50
+ const bucket = this.buckets.get(table.name);
51
+ return bucket ? Array.from(bucket.values()) : [];
52
+ }
53
+
54
+ clear(): void {
55
+ this.buckets.clear();
56
+ }
57
+
58
+ private toIdentityKey(pk: PrimaryKey): string {
59
+ return String(pk);
60
+ }
61
+ }
@@ -1,28 +1,28 @@
1
1
  import type { DbExecutor, ExecutionPayload } from '../core/execution/db-executor.js';
2
2
  import { toExecutionPayload } from '../core/execution/db-executor.js';
3
-
3
+
4
4
  export interface QueryContext {
5
- sql: string;
6
- params: unknown[];
7
- // maybe metadata like entity type, operation type, etc.
5
+ sql: string;
6
+ params: unknown[];
7
+ // maybe metadata like entity type, operation type, etc.
8
8
  }
9
9
 
10
10
  export type QueryInterceptor = (
11
11
  ctx: QueryContext,
12
12
  next: () => Promise<ExecutionPayload>
13
13
  ) => Promise<ExecutionPayload>;
14
-
15
- /**
16
- * Pipeline for query interceptors.
17
- * Interceptors can wrap query execution to add logging, tracing, caching, etc.
18
- */
19
- export class InterceptorPipeline {
20
- private interceptors: QueryInterceptor[] = [];
21
-
22
- use(interceptor: QueryInterceptor) {
23
- this.interceptors.push(interceptor);
24
- }
25
-
14
+
15
+ /**
16
+ * Pipeline for query interceptors.
17
+ * Interceptors can wrap query execution to add logging, tracing, caching, etc.
18
+ */
19
+ export class InterceptorPipeline {
20
+ private interceptors: QueryInterceptor[] = [];
21
+
22
+ use(interceptor: QueryInterceptor) {
23
+ this.interceptors.push(interceptor);
24
+ }
25
+
26
26
  async run(ctx: QueryContext, executor: DbExecutor): Promise<ExecutionPayload> {
27
27
  let i = 0;
28
28
  const dispatch = async (): Promise<ExecutionPayload> => {
@@ -1,122 +1,122 @@
1
- import { TableDef } from '../../schema/table.js';
2
- import { BelongsToManyRelation } from '../../schema/relation.js';
3
- import { findPrimaryKey } from '../../query-builder/hydration-planner.js';
4
- import { RelationIncludeOptions } from '../../query-builder/relation-types.js';
5
- import { buildDefaultPivotColumns } from '../../query-builder/relation-utils.js';
6
- import { EntityContext } from '../entity-context.js';
7
- import {
8
- buildColumnSelection,
9
- collectKeysFromRoots,
10
- fetchRowsForKeys,
11
- filterRow,
12
- groupRowsByUnique,
13
- hasColumns,
14
- toKey,
15
- Rows
16
- } from './shared.js';
17
-
18
- /**
19
- * Loads related entities for a belongs-to-many relation in batch, including pivot data.
20
- * @param ctx - The entity context.
21
- * @param rootTable - The root table of the relation.
22
- * @param _relationName - The name of the relation (unused).
23
- * @param relation - The belongs-to-many relation definition.
24
- * @returns A promise resolving to a map of root keys to arrays of related rows with pivot data.
25
- */
26
- export const loadBelongsToManyRelation = async (
27
- ctx: EntityContext,
28
- rootTable: TableDef,
29
- relationName: string,
30
- relation: BelongsToManyRelation,
31
- options?: RelationIncludeOptions
32
- ): Promise<Map<string, Rows>> => {
33
- const rootKey = relation.localKey || findPrimaryKey(rootTable);
34
- const roots = ctx.getEntitiesForTable(rootTable);
35
- const rootIds = collectKeysFromRoots(roots, rootKey);
36
-
37
- if (!rootIds.size) {
38
- return new Map();
39
- }
40
-
41
- const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
42
- if (!pivotColumn) return new Map();
43
-
44
- const pivotColumnsRequested = hasColumns(options?.pivot?.columns) ? [...options!.pivot!.columns] : undefined;
45
- const useIncludeDefaults = options !== undefined;
46
- let pivotSelectedColumns: string[];
47
- if (pivotColumnsRequested) {
48
- pivotSelectedColumns = [...pivotColumnsRequested];
49
- } else if (useIncludeDefaults) {
50
- const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
51
- pivotSelectedColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
52
- } else {
53
- pivotSelectedColumns = Object.keys(relation.pivotTable.columns);
54
- }
55
-
56
- const pivotQueryColumns = new Set(pivotSelectedColumns);
57
- pivotQueryColumns.add(relation.pivotForeignKeyToRoot);
58
- pivotQueryColumns.add(relation.pivotForeignKeyToTarget);
59
-
60
- const pivotSelection = buildColumnSelection(
61
- relation.pivotTable,
62
- Array.from(pivotQueryColumns),
63
- column => `Column '${column}' not found on pivot table '${relation.pivotTable.name}'`
64
- );
65
-
66
- const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds, pivotSelection);
67
- const rootLookup = new Map<string, { targetId: unknown; pivot: Record<string, unknown> }[]>();
68
- const targetIds = new Set<unknown>();
69
- const pivotVisibleColumns = new Set(pivotSelectedColumns);
70
-
71
- for (const pivot of pivotRows) {
72
- const rootValue = pivot[relation.pivotForeignKeyToRoot];
73
- const targetValue = pivot[relation.pivotForeignKeyToTarget];
74
- if (rootValue === null || rootValue === undefined || targetValue === null || targetValue === undefined) {
75
- continue;
76
- }
77
- const bucket = rootLookup.get(toKey(rootValue)) ?? [];
78
- bucket.push({
79
- targetId: targetValue,
80
- pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
81
- });
82
- rootLookup.set(toKey(rootValue), bucket);
83
- targetIds.add(targetValue);
84
- }
85
-
86
- if (!targetIds.size) {
87
- return new Map();
88
- }
89
-
90
- const targetKey = relation.targetKey || findPrimaryKey(relation.target);
91
- const targetPkColumn = relation.target.columns[targetKey];
92
- if (!targetPkColumn) return new Map();
93
-
94
- const targetRequestedColumns = hasColumns(options?.columns) ? [...options!.columns] : undefined;
95
- const targetSelectedColumns = targetRequestedColumns
96
- ? [...targetRequestedColumns]
97
- : Object.keys(relation.target.columns);
98
- if (!targetSelectedColumns.includes(targetKey)) {
99
- targetSelectedColumns.push(targetKey);
100
- }
101
-
102
- const targetSelection = buildColumnSelection(
103
- relation.target,
104
- targetSelectedColumns,
105
- column => `Column '${column}' not found on relation '${relationName}'`
106
- );
107
-
108
- const targetRows = await fetchRowsForKeys(
109
- ctx,
110
- relation.target,
111
- targetPkColumn,
112
- targetIds,
113
- targetSelection,
114
- options?.filter
115
- );
116
- const targetMap = groupRowsByUnique(targetRows, targetKey);
117
- const targetVisibleColumns = new Set(targetSelectedColumns);
118
- const result = new Map<string, Rows>();
119
-
1
+ import { TableDef } from '../../schema/table.js';
2
+ import { BelongsToManyRelation } from '../../schema/relation.js';
3
+ import { findPrimaryKey } from '../../query-builder/hydration-planner.js';
4
+ import { RelationIncludeOptions } from '../../query-builder/relation-types.js';
5
+ import { buildDefaultPivotColumns } from '../../query-builder/relation-utils.js';
6
+ import { EntityContext } from '../entity-context.js';
7
+ import {
8
+ buildColumnSelection,
9
+ collectKeysFromRoots,
10
+ fetchRowsForKeys,
11
+ filterRow,
12
+ groupRowsByUnique,
13
+ hasColumns,
14
+ toKey,
15
+ Rows
16
+ } from './shared.js';
17
+
18
+ /**
19
+ * Loads related entities for a belongs-to-many relation in batch, including pivot data.
20
+ * @param ctx - The entity context.
21
+ * @param rootTable - The root table of the relation.
22
+ * @param _relationName - The name of the relation (unused).
23
+ * @param relation - The belongs-to-many relation definition.
24
+ * @returns A promise resolving to a map of root keys to arrays of related rows with pivot data.
25
+ */
26
+ export const loadBelongsToManyRelation = async (
27
+ ctx: EntityContext,
28
+ rootTable: TableDef,
29
+ relationName: string,
30
+ relation: BelongsToManyRelation,
31
+ options?: RelationIncludeOptions
32
+ ): Promise<Map<string, Rows>> => {
33
+ const rootKey = relation.localKey || findPrimaryKey(rootTable);
34
+ const roots = ctx.getEntitiesForTable(rootTable);
35
+ const rootIds = collectKeysFromRoots(roots, rootKey);
36
+
37
+ if (!rootIds.size) {
38
+ return new Map();
39
+ }
40
+
41
+ const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
42
+ if (!pivotColumn) return new Map();
43
+
44
+ const pivotColumnsRequested = hasColumns(options?.pivot?.columns) ? [...options!.pivot!.columns] : undefined;
45
+ const useIncludeDefaults = options !== undefined;
46
+ let pivotSelectedColumns: string[];
47
+ if (pivotColumnsRequested) {
48
+ pivotSelectedColumns = [...pivotColumnsRequested];
49
+ } else if (useIncludeDefaults) {
50
+ const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
51
+ pivotSelectedColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
52
+ } else {
53
+ pivotSelectedColumns = Object.keys(relation.pivotTable.columns);
54
+ }
55
+
56
+ const pivotQueryColumns = new Set(pivotSelectedColumns);
57
+ pivotQueryColumns.add(relation.pivotForeignKeyToRoot);
58
+ pivotQueryColumns.add(relation.pivotForeignKeyToTarget);
59
+
60
+ const pivotSelection = buildColumnSelection(
61
+ relation.pivotTable,
62
+ Array.from(pivotQueryColumns),
63
+ column => `Column '${column}' not found on pivot table '${relation.pivotTable.name}'`
64
+ );
65
+
66
+ const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds, pivotSelection);
67
+ const rootLookup = new Map<string, { targetId: unknown; pivot: Record<string, unknown> }[]>();
68
+ const targetIds = new Set<unknown>();
69
+ const pivotVisibleColumns = new Set(pivotSelectedColumns);
70
+
71
+ for (const pivot of pivotRows) {
72
+ const rootValue = pivot[relation.pivotForeignKeyToRoot];
73
+ const targetValue = pivot[relation.pivotForeignKeyToTarget];
74
+ if (rootValue === null || rootValue === undefined || targetValue === null || targetValue === undefined) {
75
+ continue;
76
+ }
77
+ const bucket = rootLookup.get(toKey(rootValue)) ?? [];
78
+ bucket.push({
79
+ targetId: targetValue,
80
+ pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
81
+ });
82
+ rootLookup.set(toKey(rootValue), bucket);
83
+ targetIds.add(targetValue);
84
+ }
85
+
86
+ if (!targetIds.size) {
87
+ return new Map();
88
+ }
89
+
90
+ const targetKey = relation.targetKey || findPrimaryKey(relation.target);
91
+ const targetPkColumn = relation.target.columns[targetKey];
92
+ if (!targetPkColumn) return new Map();
93
+
94
+ const targetRequestedColumns = hasColumns(options?.columns) ? [...options!.columns] : undefined;
95
+ const targetSelectedColumns = targetRequestedColumns
96
+ ? [...targetRequestedColumns]
97
+ : Object.keys(relation.target.columns);
98
+ if (!targetSelectedColumns.includes(targetKey)) {
99
+ targetSelectedColumns.push(targetKey);
100
+ }
101
+
102
+ const targetSelection = buildColumnSelection(
103
+ relation.target,
104
+ targetSelectedColumns,
105
+ column => `Column '${column}' not found on relation '${relationName}'`
106
+ );
107
+
108
+ const targetRows = await fetchRowsForKeys(
109
+ ctx,
110
+ relation.target,
111
+ targetPkColumn,
112
+ targetIds,
113
+ targetSelection,
114
+ options?.filter
115
+ );
116
+ const targetMap = groupRowsByUnique(targetRows, targetKey);
117
+ const targetVisibleColumns = new Set(targetSelectedColumns);
118
+ const result = new Map<string, Rows>();
119
+
120
120
  for (const [rootId, entries] of rootLookup.entries()) {
121
121
  const bucket: Rows = [];
122
122
  for (const entry of entries) {