metal-orm 1.0.97 → 1.0.99

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.97",
3
+ "version": "1.0.99",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -1,57 +1,57 @@
1
- import { RelationType } from '../../schema/relation.js';
2
-
3
- /**
4
- * Plan describing pivot columns needed for hydration
5
- */
6
- export interface HydrationPivotPlan {
7
- table: string;
8
- primaryKey: string;
9
- aliasPrefix: string;
10
- columns: string[];
11
- }
12
-
13
- /**
14
- * Plan for hydrating relationship data
15
- */
16
- export interface HydrationRelationPlan {
17
- /** Name of the relationship */
18
- name: string;
19
- /** Alias prefix for the relationship */
20
- aliasPrefix: string;
21
- /** Type of relationship */
22
- type: RelationType;
23
- /** Target table name */
24
- targetTable: string;
25
- /** Target table primary key */
26
- targetPrimaryKey: string;
27
- /** Foreign key column */
28
- foreignKey: string;
29
- /** Local key column */
30
- localKey: string;
31
- /** Columns to include */
32
- columns: string[];
33
- /** Optional pivot plan for many-to-many relationships */
34
- pivot?: HydrationPivotPlan;
35
- }
36
-
37
- /**
38
- * Complete hydration plan for a query
39
- */
40
- export interface HydrationPlan {
41
- /** Root table name */
42
- rootTable: string;
43
- /** Root table primary key */
44
- rootPrimaryKey: string;
45
- /** Root table columns */
46
- rootColumns: string[];
47
- /** Relationship hydration plans */
48
- relations: HydrationRelationPlan[];
49
- }
50
-
51
- /**
52
- * Metadata bag for attaching hydration to query ASTs without coupling AST types.
53
- */
54
- export interface HydrationMetadata {
55
- hydration?: HydrationPlan;
56
- [key: string]: unknown;
57
- }
1
+ import { RelationType } from '../../schema/relation.js';
2
+
3
+ /**
4
+ * Plan describing pivot columns needed for hydration
5
+ */
6
+ export interface HydrationPivotPlan {
7
+ table: string;
8
+ primaryKey: string;
9
+ aliasPrefix: string;
10
+ columns: string[];
11
+ }
12
+
13
+ /**
14
+ * Plan for hydrating relationship data
15
+ */
16
+ export interface HydrationRelationPlan {
17
+ /** Name of the relationship */
18
+ name: string;
19
+ /** Alias prefix for the relationship */
20
+ aliasPrefix: string;
21
+ /** Type of relationship */
22
+ type: RelationType;
23
+ /** Target table name */
24
+ targetTable: string;
25
+ /** Target table primary key */
26
+ targetPrimaryKey: string;
27
+ /** Foreign key column */
28
+ foreignKey: string;
29
+ /** Local key column */
30
+ localKey: string;
31
+ /** Columns to include */
32
+ columns: string[];
33
+ /** Optional pivot plan for many-to-many relationships */
34
+ pivot?: HydrationPivotPlan;
35
+ }
36
+
37
+ /**
38
+ * Complete hydration plan for a query
39
+ */
40
+ export interface HydrationPlan {
41
+ /** Root table name */
42
+ rootTable: string;
43
+ /** Root table primary key */
44
+ rootPrimaryKey: string;
45
+ /** Root table columns */
46
+ rootColumns: string[];
47
+ /** Relationship hydration plans */
48
+ relations: HydrationRelationPlan[];
49
+ }
50
+
51
+ /**
52
+ * Metadata bag for attaching hydration to query ASTs without coupling AST types.
53
+ */
54
+ export interface HydrationMetadata {
55
+ hydration?: HydrationPlan;
56
+ [key: string]: unknown;
57
+ }
@@ -125,16 +125,16 @@ const instantiateWrapper = <TTable extends TableDef>(
125
125
  createEntity: RelationEntityFactory
126
126
  ): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
127
127
  const metaBase = meta as unknown as EntityMeta<TableDef>;
128
- const loadCached = <T extends Map<string, unknown>>(factory: () => Promise<T>) =>
129
- relationLoaderCache(metaBase, relationName, factory);
130
- const resolveOptions = () => meta.lazyRelationOptions.get(relationName);
128
+ const loadCached = <T extends Map<string, unknown>>(factory: () => Promise<T>) =>
129
+ relationLoaderCache(metaBase, relationName, factory);
130
+ const resolveOptions = () => meta.lazyRelationOptions.get(relationName);
131
131
  switch (relation.type) {
132
132
  case RelationKinds.HasOne: {
133
133
  const hasOne = relation as HasOneRelation;
134
- const localKey = hasOne.localKey || findPrimaryKey(meta.table);
135
- const loader = () => loadCached(() =>
136
- loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, resolveOptions())
137
- );
134
+ const localKey = hasOne.localKey || findPrimaryKey(meta.table);
135
+ const loader = () => loadCached(() =>
136
+ loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, resolveOptions())
137
+ );
138
138
  return new DefaultHasOneReference(
139
139
  meta.ctx,
140
140
  metaBase,
@@ -149,10 +149,10 @@ const instantiateWrapper = <TTable extends TableDef>(
149
149
  }
150
150
  case RelationKinds.HasMany: {
151
151
  const hasMany = relation as HasManyRelation;
152
- const localKey = hasMany.localKey || findPrimaryKey(meta.table);
153
- const loader = () => loadCached(() =>
154
- loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, resolveOptions())
155
- );
152
+ const localKey = hasMany.localKey || findPrimaryKey(meta.table);
153
+ const loader = () => loadCached(() =>
154
+ loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, resolveOptions())
155
+ );
156
156
  return new DefaultHasManyCollection(
157
157
  meta.ctx,
158
158
  metaBase,
@@ -167,10 +167,10 @@ const instantiateWrapper = <TTable extends TableDef>(
167
167
  }
168
168
  case RelationKinds.BelongsTo: {
169
169
  const belongsTo = relation as BelongsToRelation;
170
- const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
171
- const loader = () => loadCached(() =>
172
- loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, resolveOptions())
173
- );
170
+ const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
171
+ const loader = () => loadCached(() =>
172
+ loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, resolveOptions())
173
+ );
174
174
  return new DefaultBelongsToReference(
175
175
  meta.ctx,
176
176
  metaBase,
@@ -185,10 +185,10 @@ const instantiateWrapper = <TTable extends TableDef>(
185
185
  }
186
186
  case RelationKinds.BelongsToMany: {
187
187
  const many = relation as BelongsToManyRelation;
188
- const localKey = many.localKey || findPrimaryKey(meta.table);
189
- const loader = () => loadCached(() =>
190
- loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, resolveOptions())
191
- );
188
+ const localKey = many.localKey || findPrimaryKey(meta.table);
189
+ const loader = () => loadCached(() =>
190
+ loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, resolveOptions())
191
+ );
192
192
  return new DefaultManyToManyCollection(
193
193
  meta.ctx,
194
194
  metaBase,
@@ -1,134 +1,134 @@
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
- for (const [rootId, entries] of rootLookup.entries()) {
121
- const bucket: Rows = [];
122
- for (const entry of entries) {
123
- const targetRow = targetMap.get(toKey(entry.targetId));
124
- if (!targetRow) continue;
125
- bucket.push({
126
- ...(targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow),
127
- _pivot: entry.pivot
128
- });
129
- }
130
- result.set(rootId, bucket);
131
- }
132
-
133
- return result;
134
- };
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
+ for (const [rootId, entries] of rootLookup.entries()) {
121
+ const bucket: Rows = [];
122
+ for (const entry of entries) {
123
+ const targetRow = targetMap.get(toKey(entry.targetId));
124
+ if (!targetRow) continue;
125
+ bucket.push({
126
+ ...(targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow),
127
+ _pivot: entry.pivot
128
+ });
129
+ }
130
+ result.set(rootId, bucket);
131
+ }
132
+
133
+ return result;
134
+ };