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/README.md +723 -720
- package/dist/index.cjs +18 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/hydration/types.ts +57 -57
- package/src/orm/entity-relations.ts +19 -19
- package/src/orm/lazy-batch/belongs-to-many.ts +134 -134
- package/src/orm/lazy-batch/belongs-to.ts +108 -108
- package/src/orm/lazy-batch/has-many.ts +69 -69
- package/src/orm/lazy-batch/has-one.ts +68 -68
- package/src/orm/lazy-batch/shared.ts +125 -125
- package/src/orm/save-graph.ts +48 -48
- package/src/query-builder/column-selector.ts +9 -9
- package/src/query-builder/hydration-manager.ts +353 -353
- package/src/query-builder/hydration-planner.ts +22 -22
- package/src/query-builder/relation-conditions.ts +80 -80
- package/src/query-builder/select/projection-facet.ts +23 -23
- package/src/query-builder/select-query-state.ts +213 -213
- package/src/query-builder/select.ts +1155 -1133
- package/src/schema/relation.ts +22 -22
package/package.json
CHANGED
|
@@ -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
|
+
};
|