metal-orm 1.1.11 → 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 (182) hide show
  1. package/README.md +758 -769
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/package.json +100 -96
  5. package/scripts/generate-entities/schema.mjs +122 -122
  6. package/scripts/generate-entities/tree-detection.mjs +92 -92
  7. package/scripts/inflection/pt-br.mjs +468 -468
  8. package/scripts/run-eslint.mjs +33 -34
  9. package/scripts/show-sql-seed.sql +102 -0
  10. package/scripts/show-sql.mjs +321 -325
  11. package/src/codegen/naming-strategy.ts +54 -54
  12. package/src/codegen/typescript.ts +482 -482
  13. package/src/core/ast/adapters.ts +1 -1
  14. package/src/core/ast/builders.ts +123 -123
  15. package/src/core/ast/expression-builders.ts +745 -745
  16. package/src/core/ast/expression-nodes.ts +304 -304
  17. package/src/core/ast/expression-visitor.ts +212 -212
  18. package/src/core/ast/expression.ts +20 -20
  19. package/src/core/ast/helpers.ts +14 -14
  20. package/src/core/ast/join.ts +18 -18
  21. package/src/core/ast/query.ts +200 -200
  22. package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
  23. package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
  24. package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
  25. package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
  26. package/src/core/ddl/dialects/render-reference.test.ts +70 -70
  27. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
  28. package/src/core/ddl/introspect/catalogs/index.ts +5 -5
  29. package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
  30. package/src/core/ddl/introspect/context.ts +15 -15
  31. package/src/core/ddl/introspect/functions/postgres.ts +35 -35
  32. package/src/core/ddl/introspect/registry.ts +40 -40
  33. package/src/core/ddl/introspect/run-select.ts +35 -35
  34. package/src/core/ddl/introspect/utils.ts +56 -56
  35. package/src/core/ddl/naming-strategy.ts +16 -16
  36. package/src/core/ddl/schema-dialect.ts +56 -56
  37. package/src/core/ddl/schema-diff.ts +234 -234
  38. package/src/core/ddl/schema-generator.ts +1 -1
  39. package/src/core/ddl/schema-introspect.ts +25 -25
  40. package/src/core/ddl/sql-writing.ts +171 -171
  41. package/src/core/dialect/abstract.ts +541 -541
  42. package/src/core/dialect/base/cte-compiler.ts +33 -33
  43. package/src/core/dialect/base/function-table-formatter.ts +103 -103
  44. package/src/core/dialect/base/groupby-compiler.ts +21 -21
  45. package/src/core/dialect/base/join-compiler.ts +25 -25
  46. package/src/core/dialect/base/orderby-compiler.ts +35 -35
  47. package/src/core/dialect/base/pagination-strategy.ts +32 -32
  48. package/src/core/dialect/base/returning-strategy.ts +57 -57
  49. package/src/core/dialect/base/sql-dialect.ts +294 -294
  50. package/src/core/dialect/dialect-factory.ts +91 -91
  51. package/src/core/dialect/mssql/functions.ts +120 -120
  52. package/src/core/dialect/mssql/index.ts +317 -317
  53. package/src/core/dialect/mysql/functions.ts +103 -103
  54. package/src/core/dialect/mysql/index.ts +105 -105
  55. package/src/core/dialect/postgres/functions.ts +108 -108
  56. package/src/core/dialect/postgres/index.ts +91 -91
  57. package/src/core/dialect/sqlite/functions.ts +129 -129
  58. package/src/core/dialect/sqlite/index.ts +67 -67
  59. package/src/core/driver/database-driver.ts +19 -19
  60. package/src/core/driver/mssql-driver.ts +23 -23
  61. package/src/core/driver/mysql-driver.ts +23 -23
  62. package/src/core/driver/postgres-driver.ts +23 -23
  63. package/src/core/driver/sqlite-driver.ts +23 -23
  64. package/src/core/execution/db-executor.ts +92 -92
  65. package/src/core/execution/executors/better-sqlite3-executor.ts +90 -90
  66. package/src/core/execution/executors/mssql-executor.ts +184 -184
  67. package/src/core/execution/executors/mysql-executor.ts +88 -88
  68. package/src/core/execution/executors/postgres-executor.ts +26 -26
  69. package/src/core/execution/executors/sqlite-executor.ts +42 -42
  70. package/src/core/execution/pooling/pool-types.ts +30 -30
  71. package/src/core/execution/pooling/pool.ts +275 -275
  72. package/src/core/functions/array.ts +35 -35
  73. package/src/core/functions/control-flow.ts +79 -79
  74. package/src/core/functions/datetime.ts +237 -237
  75. package/src/core/functions/json.ts +70 -70
  76. package/src/core/functions/numeric.ts +297 -297
  77. package/src/core/functions/text.ts +334 -334
  78. package/src/core/functions/types.ts +33 -33
  79. package/src/core/hydration/types.ts +51 -51
  80. package/src/core/sql/sql-operator-config.ts +39 -39
  81. package/src/core/sql/sql.ts +152 -152
  82. package/src/decorators/column-decorator.ts +87 -87
  83. package/src/decorators/decorator-metadata.ts +143 -143
  84. package/src/decorators/entity.ts +93 -93
  85. package/src/decorators/index.ts +39 -39
  86. package/src/decorators/relations.ts +231 -231
  87. package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
  88. package/src/decorators/transformers/transformer-decorators.ts +134 -134
  89. package/src/decorators/transformers/transformer-executor.ts +171 -171
  90. package/src/decorators/transformers/transformer-metadata.ts +73 -73
  91. package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
  92. package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
  93. package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
  94. package/src/decorators/validators/country-validator-registry.ts +64 -64
  95. package/src/decorators/validators/country-validators-decorators.ts +203 -203
  96. package/src/decorators/validators/country-validators.ts +105 -105
  97. package/src/decorators/validators/validator-metadata.ts +59 -59
  98. package/src/dto/dto-types.ts +226 -226
  99. package/src/dto/filter-types.ts +141 -141
  100. package/src/dto/index.ts +95 -95
  101. package/src/dto/openapi/generators/base.ts +29 -29
  102. package/src/dto/openapi/generators/column.ts +37 -37
  103. package/src/dto/openapi/generators/dto.ts +91 -91
  104. package/src/dto/openapi/generators/filter.ts +75 -75
  105. package/src/dto/openapi/generators/nested-dto.ts +618 -618
  106. package/src/dto/openapi/generators/pagination.ts +111 -111
  107. package/src/dto/openapi/generators/relation-filter.ts +228 -228
  108. package/src/dto/openapi/index.ts +12 -12
  109. package/src/dto/openapi/types.ts +101 -101
  110. package/src/dto/openapi/utilities.ts +95 -95
  111. package/src/dto/pagination-utils.ts +150 -150
  112. package/src/dto/transform.ts +197 -197
  113. package/src/index.ts +86 -86
  114. package/src/orm/als.ts +58 -58
  115. package/src/orm/domain-event-bus.ts +119 -119
  116. package/src/orm/entity-context.ts +86 -86
  117. package/src/orm/entity-materializer.ts +159 -159
  118. package/src/orm/entity-meta.ts +97 -97
  119. package/src/orm/entity-registry.ts +39 -39
  120. package/src/orm/entity-relation-cache.ts +39 -39
  121. package/src/orm/execute.ts +194 -194
  122. package/src/orm/execution-context.ts +18 -18
  123. package/src/orm/hydration-context.ts +26 -26
  124. package/src/orm/hydration.ts +112 -112
  125. package/src/orm/identity-map.ts +61 -61
  126. package/src/orm/interceptor-pipeline.ts +16 -16
  127. package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
  128. package/src/orm/lazy-batch/belongs-to.ts +108 -108
  129. package/src/orm/lazy-batch/has-many.ts +69 -69
  130. package/src/orm/lazy-batch/has-one.ts +68 -68
  131. package/src/orm/lazy-batch/shared.ts +125 -125
  132. package/src/orm/orm-session.ts +620 -620
  133. package/src/orm/orm.ts +34 -34
  134. package/src/orm/pooled-executor-factory.ts +121 -121
  135. package/src/orm/query-logger.ts +40 -40
  136. package/src/orm/relation-change-processor.ts +420 -420
  137. package/src/orm/relations/belongs-to.ts +143 -143
  138. package/src/orm/relations/has-many.ts +204 -204
  139. package/src/orm/relations/has-one.ts +174 -174
  140. package/src/orm/relations/many-to-many.ts +288 -288
  141. package/src/orm/relations/morph-many.ts +156 -156
  142. package/src/orm/relations/morph-one.ts +151 -151
  143. package/src/orm/relations/morph-to.ts +162 -162
  144. package/src/orm/runtime-types.ts +92 -92
  145. package/src/orm/save-graph.ts +561 -561
  146. package/src/orm/transaction-runner.ts +24 -24
  147. package/src/orm/unit-of-work.ts +426 -426
  148. package/src/query/index.ts +69 -69
  149. package/src/query/target.ts +46 -46
  150. package/src/query-builder/column-selector.ts +81 -81
  151. package/src/query-builder/delete.ts +141 -141
  152. package/src/query-builder/insert-query-state.ts +149 -149
  153. package/src/query-builder/insert.ts +117 -117
  154. package/src/query-builder/query-ast-service.ts +287 -287
  155. package/src/query-builder/query-resolution.ts +78 -78
  156. package/src/query-builder/raw-column-parser.ts +38 -38
  157. package/src/query-builder/relation-filter-utils.ts +153 -153
  158. package/src/query-builder/relation-join-planner.ts +10 -10
  159. package/src/query-builder/relation-manager.ts +87 -87
  160. package/src/query-builder/relation-projection-helper.ts +102 -102
  161. package/src/query-builder/relation-types.ts +9 -9
  162. package/src/query-builder/relation-utils.ts +15 -15
  163. package/src/query-builder/select/cte-facet.ts +40 -40
  164. package/src/query-builder/select/from-facet.ts +80 -80
  165. package/src/query-builder/select/join-facet.ts +62 -62
  166. package/src/query-builder/select/predicate-facet.ts +104 -104
  167. package/src/query-builder/select/projection-facet.ts +70 -70
  168. package/src/query-builder/select/relation-facet.ts +81 -81
  169. package/src/query-builder/select/setop-facet.ts +36 -36
  170. package/src/query-builder/select-helpers.ts +1 -1
  171. package/src/query-builder/select-query-builder-deps.ts +130 -130
  172. package/src/query-builder/select-query-state.ts +207 -207
  173. package/src/query-builder/update.ts +151 -151
  174. package/src/schema/column-types.ts +322 -322
  175. package/src/schema/table-guards.ts +37 -37
  176. package/src/schema/table.ts +281 -281
  177. package/src/tree/index.ts +13 -13
  178. package/src/tree/nested-set-strategy.ts +545 -545
  179. package/src/tree/tree-decorator.ts +397 -397
  180. package/src/tree/tree-manager.ts +754 -754
  181. package/src/tree/tree-query.ts +427 -427
  182. 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) {