@atscript/db 0.1.38
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/LICENSE +21 -0
- package/README.md +343 -0
- package/dist/agg-BJFJ3dFQ.mjs +8 -0
- package/dist/agg-DnUWAOK8.cjs +14 -0
- package/dist/agg.cjs +3 -0
- package/dist/agg.d.ts +13 -0
- package/dist/agg.mjs +3 -0
- package/dist/chunk-CrpGerW8.cjs +31 -0
- package/dist/control_as-BFPERAF_.cjs +28 -0
- package/dist/control_as-bjmwe24C.mjs +26 -0
- package/dist/index.cjs +2887 -0
- package/dist/index.d.ts +1706 -0
- package/dist/index.mjs +2846 -0
- package/dist/logger-B7oxCfLQ.mjs +12 -0
- package/dist/logger-Dt2v_-wb.cjs +18 -0
- package/dist/nested-writer-BkqL7cp3.cjs +667 -0
- package/dist/nested-writer-NEN51mnR.mjs +576 -0
- package/dist/plugin.cjs +993 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.mjs +989 -0
- package/dist/rel.cjs +20 -0
- package/dist/rel.d.ts +1305 -0
- package/dist/rel.mjs +5 -0
- package/dist/relation-helpers-DyBIlQnB.mjs +29 -0
- package/dist/relation-helpers-guFL_oRf.cjs +47 -0
- package/dist/relation-loader-CpnDRf9k.cjs +415 -0
- package/dist/relation-loader-D4mTw6yH.cjs +4 -0
- package/dist/relation-loader-Dv7qXYq7.mjs +409 -0
- package/dist/relation-loader-Ggy1ujwR.mjs +4 -0
- package/dist/shared.cjs +13 -0
- package/dist/shared.d.ts +70 -0
- package/dist/shared.mjs +3 -0
- package/dist/sync.cjs +1205 -0
- package/dist/sync.d.ts +1878 -0
- package/dist/sync.mjs +1186 -0
- package/dist/validation-utils-DEoCMmEb.cjs +304 -0
- package/dist/validation-utils-DhR_mtKa.mjs +237 -0
- package/package.json +81 -0
package/dist/rel.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, preValidateNestedFrom, validateBatch } from "./nested-writer-NEN51mnR.mjs";
|
|
2
|
+
import { findFKForRelation, findRemoteFK, resolveRelationTargetTable } from "./relation-helpers-DyBIlQnB.mjs";
|
|
3
|
+
import { loadRelationsImpl } from "./relation-loader-Dv7qXYq7.mjs";
|
|
4
|
+
|
|
5
|
+
export { batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
//#region packages/db/src/rel/relation-helpers.ts
|
|
3
|
+
function findFKForRelation(relation, foreignKeys) {
|
|
4
|
+
const targetTable = resolveRelationTargetTable(relation);
|
|
5
|
+
for (const fk of foreignKeys.values()) if (relation.alias) {
|
|
6
|
+
if (fk.alias === relation.alias) return {
|
|
7
|
+
localFields: fk.fields,
|
|
8
|
+
targetFields: fk.targetFields
|
|
9
|
+
};
|
|
10
|
+
} else if (fk.targetTable === targetTable) return {
|
|
11
|
+
localFields: fk.fields,
|
|
12
|
+
targetFields: fk.targetFields
|
|
13
|
+
};
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
function findRemoteFK(targetTable, thisTableName, alias) {
|
|
17
|
+
for (const fk of targetTable.foreignKeys.values()) {
|
|
18
|
+
if (alias && fk.alias === alias && fk.targetTable === thisTableName) return fk;
|
|
19
|
+
if (!alias && fk.targetTable === thisTableName) return fk;
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
function resolveRelationTargetTable(relation) {
|
|
24
|
+
const targetType = relation.targetType();
|
|
25
|
+
return targetType?.metadata?.get("db.table") || targetType?.id || "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { findFKForRelation, findRemoteFK, resolveRelationTargetTable };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
//#region packages/db/src/rel/relation-helpers.ts
|
|
4
|
+
function findFKForRelation(relation, foreignKeys) {
|
|
5
|
+
const targetTable = resolveRelationTargetTable(relation);
|
|
6
|
+
for (const fk of foreignKeys.values()) if (relation.alias) {
|
|
7
|
+
if (fk.alias === relation.alias) return {
|
|
8
|
+
localFields: fk.fields,
|
|
9
|
+
targetFields: fk.targetFields
|
|
10
|
+
};
|
|
11
|
+
} else if (fk.targetTable === targetTable) return {
|
|
12
|
+
localFields: fk.fields,
|
|
13
|
+
targetFields: fk.targetFields
|
|
14
|
+
};
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
function findRemoteFK(targetTable, thisTableName, alias) {
|
|
18
|
+
for (const fk of targetTable.foreignKeys.values()) {
|
|
19
|
+
if (alias && fk.alias === alias && fk.targetTable === thisTableName) return fk;
|
|
20
|
+
if (!alias && fk.targetTable === thisTableName) return fk;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
function resolveRelationTargetTable(relation) {
|
|
25
|
+
const targetType = relation.targetType();
|
|
26
|
+
return targetType?.metadata?.get("db.table") || targetType?.id || "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
Object.defineProperty(exports, 'findFKForRelation', {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () {
|
|
33
|
+
return findFKForRelation;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(exports, 'findRemoteFK', {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return findRemoteFK;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(exports, 'resolveRelationTargetTable', {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function () {
|
|
45
|
+
return resolveRelationTargetTable;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_relation_helpers = require('./relation-helpers-guFL_oRf.cjs');
|
|
3
|
+
|
|
4
|
+
//#region packages/db/src/rel/relation-loader.ts
|
|
5
|
+
async function loadRelationsImpl(rows, withRelations, host) {
|
|
6
|
+
if (rows.length === 0 || withRelations.length === 0) return;
|
|
7
|
+
if (host.adapter.supportsNativeRelations()) return host.adapter.loadRelations(rows, withRelations, host._meta.relations, host._meta.foreignKeys, host._tableResolver);
|
|
8
|
+
if (!host._tableResolver) return;
|
|
9
|
+
const tasks = [];
|
|
10
|
+
for (const withRel of withRelations) {
|
|
11
|
+
const relName = withRel.name;
|
|
12
|
+
if (relName.includes(".")) continue;
|
|
13
|
+
const relation = host._meta.relations.get(relName);
|
|
14
|
+
if (!relation) throw new Error(`Unknown relation "${relName}" in $with. Available relations: ${[...host._meta.relations.keys()].join(", ") || "(none)"}`);
|
|
15
|
+
const targetType = relation.targetType();
|
|
16
|
+
if (!targetType) continue;
|
|
17
|
+
const targetTable = host._tableResolver(targetType);
|
|
18
|
+
if (!targetTable) {
|
|
19
|
+
host.logger.warn(`Could not resolve table for relation "${relName}" — skipping`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const filter = withRel.filter && Object.keys(withRel.filter).length > 0 ? withRel.filter : undefined;
|
|
23
|
+
const flatRel = withRel;
|
|
24
|
+
const nested = withRel.controls || {};
|
|
25
|
+
const controls = { ...nested };
|
|
26
|
+
if (flatRel.$sort && !controls.$sort) controls.$sort = flatRel.$sort;
|
|
27
|
+
if (flatRel.$limit !== null && flatRel.$limit !== undefined && (controls.$limit === null || controls.$limit === undefined)) controls.$limit = flatRel.$limit;
|
|
28
|
+
if (flatRel.$skip !== null && flatRel.$skip !== undefined && (controls.$skip === null || controls.$skip === undefined)) controls.$skip = flatRel.$skip;
|
|
29
|
+
if (flatRel.$select && !controls.$select) controls.$select = flatRel.$select;
|
|
30
|
+
if (flatRel.$with && !controls.$with) controls.$with = flatRel.$with;
|
|
31
|
+
const relQuery = {
|
|
32
|
+
filter,
|
|
33
|
+
controls
|
|
34
|
+
};
|
|
35
|
+
if (relation.direction === "to") tasks.push(loadToRelation(rows, {
|
|
36
|
+
relName,
|
|
37
|
+
relation,
|
|
38
|
+
targetTable,
|
|
39
|
+
relQuery
|
|
40
|
+
}, host));
|
|
41
|
+
else if (relation.direction === "via") tasks.push(loadViaRelation(rows, {
|
|
42
|
+
relName,
|
|
43
|
+
relation,
|
|
44
|
+
targetTable,
|
|
45
|
+
relQuery
|
|
46
|
+
}, host));
|
|
47
|
+
else tasks.push(loadFromRelation(rows, {
|
|
48
|
+
relName,
|
|
49
|
+
relation,
|
|
50
|
+
targetTable,
|
|
51
|
+
relQuery
|
|
52
|
+
}, host));
|
|
53
|
+
}
|
|
54
|
+
await Promise.all(tasks);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Loads a `@db.rel.to` relation (FK is on this table).
|
|
58
|
+
*/ async function loadToRelation(rows, opts, host) {
|
|
59
|
+
const { relName, relation, targetTable, relQuery } = opts;
|
|
60
|
+
const fkEntry = require_relation_helpers.findFKForRelation(relation, host._meta.foreignKeys);
|
|
61
|
+
if (!fkEntry) return;
|
|
62
|
+
const { localFields, targetFields } = fkEntry;
|
|
63
|
+
if (localFields.length === 1) {
|
|
64
|
+
const localField = localFields[0];
|
|
65
|
+
const targetField = targetFields[0];
|
|
66
|
+
const fkValues = collectUniqueValues(rows, localField);
|
|
67
|
+
if (fkValues.length === 0) {
|
|
68
|
+
for (const row of rows) row[relName] = null;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const inFilter = { [targetField]: { $in: fkValues } };
|
|
72
|
+
const targetFilter = relQuery.filter ? { $and: [inFilter, relQuery.filter] } : inFilter;
|
|
73
|
+
const controls = ensureSelectIncludesFields(relQuery.controls, targetFields);
|
|
74
|
+
const related = await targetTable.findMany({
|
|
75
|
+
filter: targetFilter,
|
|
76
|
+
controls
|
|
77
|
+
});
|
|
78
|
+
assignSingle({
|
|
79
|
+
rows,
|
|
80
|
+
related,
|
|
81
|
+
localField,
|
|
82
|
+
remoteField: targetField,
|
|
83
|
+
relName
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
const related = await queryCompositeFK(rows, {
|
|
87
|
+
localFields,
|
|
88
|
+
targetFields,
|
|
89
|
+
targetTable,
|
|
90
|
+
relQuery
|
|
91
|
+
});
|
|
92
|
+
const index = new Map();
|
|
93
|
+
for (const item of related) index.set(compositeKey(targetFields, item), item);
|
|
94
|
+
for (const row of rows) row[relName] = index.get(compositeKey(localFields, row)) ?? null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Loads a `@db.rel.from` relation (FK is on the target table).
|
|
99
|
+
*/ async function loadFromRelation(rows, opts, host) {
|
|
100
|
+
const { relName, relation, targetTable, relQuery } = opts;
|
|
101
|
+
const remoteFK = require_relation_helpers.findRemoteFK(targetTable, host.tableName, relation.alias);
|
|
102
|
+
if (!remoteFK) {
|
|
103
|
+
host.logger.warn(`Could not find FK on target table for relation "${relName}"`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const localFields = remoteFK.targetFields;
|
|
107
|
+
const remoteFields = remoteFK.fields;
|
|
108
|
+
if (localFields.length === 1) {
|
|
109
|
+
const localField = localFields[0];
|
|
110
|
+
const remoteField = remoteFields[0];
|
|
111
|
+
const pkValues = collectUniqueValues(rows, localField);
|
|
112
|
+
if (pkValues.length === 0) return;
|
|
113
|
+
const inFilter = { [remoteField]: { $in: pkValues } };
|
|
114
|
+
const targetFilter = relQuery.filter ? { $and: [inFilter, relQuery.filter] } : inFilter;
|
|
115
|
+
const controls = ensureSelectIncludesFields(relQuery.controls, remoteFields);
|
|
116
|
+
const related = await targetTable.findMany({
|
|
117
|
+
filter: targetFilter,
|
|
118
|
+
controls
|
|
119
|
+
});
|
|
120
|
+
if (relation.isArray) assignGrouped({
|
|
121
|
+
rows,
|
|
122
|
+
related,
|
|
123
|
+
localField,
|
|
124
|
+
remoteField,
|
|
125
|
+
relName
|
|
126
|
+
});
|
|
127
|
+
else assignSingle({
|
|
128
|
+
rows,
|
|
129
|
+
related,
|
|
130
|
+
localField,
|
|
131
|
+
remoteField,
|
|
132
|
+
relName
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
const related = await queryCompositeFK(rows, {
|
|
136
|
+
localFields,
|
|
137
|
+
targetFields: remoteFields,
|
|
138
|
+
targetTable,
|
|
139
|
+
relQuery
|
|
140
|
+
});
|
|
141
|
+
if (relation.isArray) {
|
|
142
|
+
const groups = new Map();
|
|
143
|
+
for (const item of related) {
|
|
144
|
+
const key = compositeKey(remoteFields, item);
|
|
145
|
+
let group = groups.get(key);
|
|
146
|
+
if (!group) {
|
|
147
|
+
group = [];
|
|
148
|
+
groups.set(key, group);
|
|
149
|
+
}
|
|
150
|
+
group.push(item);
|
|
151
|
+
}
|
|
152
|
+
for (const row of rows) row[relName] = groups.get(compositeKey(localFields, row)) ?? [];
|
|
153
|
+
} else {
|
|
154
|
+
const index = new Map();
|
|
155
|
+
for (const item of related) {
|
|
156
|
+
const key = compositeKey(remoteFields, item);
|
|
157
|
+
if (!index.has(key)) index.set(key, item);
|
|
158
|
+
}
|
|
159
|
+
for (const row of rows) row[relName] = index.get(compositeKey(localFields, row)) ?? null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Loads a `@db.rel.via` relation (M:N through a junction table).
|
|
165
|
+
*/ async function loadViaRelation(rows, opts, host) {
|
|
166
|
+
const { relName, relation, targetTable, relQuery } = opts;
|
|
167
|
+
if (!relation.viaType || !host._tableResolver) return;
|
|
168
|
+
const junctionType = relation.viaType();
|
|
169
|
+
if (!junctionType) return;
|
|
170
|
+
const junctionTable = host._tableResolver(junctionType);
|
|
171
|
+
if (!junctionTable) {
|
|
172
|
+
host.logger.warn(`Could not resolve junction table for via relation "${relName}"`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const fkToThis = require_relation_helpers.findRemoteFK(junctionTable, host.tableName);
|
|
176
|
+
if (!fkToThis) {
|
|
177
|
+
host.logger.warn(`Could not find FK on junction table pointing to "${host.tableName}" for via relation "${relName}"`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const targetTableName = require_relation_helpers.resolveRelationTargetTable(relation);
|
|
181
|
+
const fkToTarget = require_relation_helpers.findRemoteFK(junctionTable, targetTableName);
|
|
182
|
+
if (!fkToTarget) {
|
|
183
|
+
host.logger.warn(`Could not find FK on junction table pointing to target "${targetTableName}" for via relation "${relName}"`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const localPKFields = fkToThis.targetFields;
|
|
187
|
+
const junctionLocalFields = fkToThis.fields;
|
|
188
|
+
const targetPKFields = fkToTarget.targetFields;
|
|
189
|
+
const junctionTargetFields = fkToTarget.fields;
|
|
190
|
+
if (localPKFields.length === 1) await loadViaSingleKey(rows, {
|
|
191
|
+
relName,
|
|
192
|
+
relation,
|
|
193
|
+
targetTable,
|
|
194
|
+
relQuery,
|
|
195
|
+
localPKFields,
|
|
196
|
+
junctionLocalFields,
|
|
197
|
+
targetPKFields,
|
|
198
|
+
junctionTargetFields,
|
|
199
|
+
junctionTable
|
|
200
|
+
});
|
|
201
|
+
else await loadViaCompositeKey(rows, {
|
|
202
|
+
relName,
|
|
203
|
+
relation,
|
|
204
|
+
targetTable,
|
|
205
|
+
relQuery,
|
|
206
|
+
localPKFields,
|
|
207
|
+
junctionLocalFields,
|
|
208
|
+
targetPKFields,
|
|
209
|
+
junctionTargetFields,
|
|
210
|
+
junctionTable
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async function loadViaSingleKey(rows, opts) {
|
|
214
|
+
const { relName, relation, targetTable, relQuery, localPKFields, junctionLocalFields, targetPKFields, junctionTargetFields, junctionTable } = opts;
|
|
215
|
+
const localField = localPKFields[0];
|
|
216
|
+
const junctionLocalField = junctionLocalFields[0];
|
|
217
|
+
const junctionTargetField = junctionTargetFields[0];
|
|
218
|
+
const targetPKField = targetPKFields[0];
|
|
219
|
+
const pkValues = collectUniqueValues(rows, localField);
|
|
220
|
+
if (pkValues.length === 0) {
|
|
221
|
+
for (const row of rows) row[relName] = [];
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const junctionFilter = { [junctionLocalField]: { $in: pkValues } };
|
|
225
|
+
const junctionRows = await junctionTable.findMany({
|
|
226
|
+
filter: junctionFilter,
|
|
227
|
+
controls: { $select: [junctionLocalField, junctionTargetField] }
|
|
228
|
+
});
|
|
229
|
+
if (junctionRows.length === 0) {
|
|
230
|
+
for (const row of rows) row[relName] = relation.isArray ? [] : null;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const targetFKValues = collectUniqueValues(junctionRows, junctionTargetField);
|
|
234
|
+
const inFilter = { [targetPKField]: { $in: targetFKValues } };
|
|
235
|
+
const targetFilter = relQuery.filter ? { $and: [inFilter, relQuery.filter] } : inFilter;
|
|
236
|
+
const controls = ensureSelectIncludesFields(relQuery.controls, targetPKFields);
|
|
237
|
+
const targetRows = await targetTable.findMany({
|
|
238
|
+
filter: targetFilter,
|
|
239
|
+
controls
|
|
240
|
+
});
|
|
241
|
+
const targetIndex = new Map();
|
|
242
|
+
for (const item of targetRows) targetIndex.set(String(item[targetPKField]), item);
|
|
243
|
+
const groups = new Map();
|
|
244
|
+
for (const jRow of junctionRows) {
|
|
245
|
+
const localKey = String(jRow[junctionLocalField]);
|
|
246
|
+
const targetKey = String(jRow[junctionTargetField]);
|
|
247
|
+
const target = targetIndex.get(targetKey);
|
|
248
|
+
if (!target) continue;
|
|
249
|
+
let group = groups.get(localKey);
|
|
250
|
+
if (!group) {
|
|
251
|
+
group = [];
|
|
252
|
+
groups.set(localKey, group);
|
|
253
|
+
}
|
|
254
|
+
group.push(target);
|
|
255
|
+
}
|
|
256
|
+
for (const row of rows) {
|
|
257
|
+
const key = String(row[localField]);
|
|
258
|
+
row[relName] = relation.isArray ? groups.get(key) ?? [] : groups.get(key)?.[0] ?? null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async function loadViaCompositeKey(rows, opts) {
|
|
262
|
+
const { relName, relation, targetTable, relQuery, localPKFields, junctionLocalFields, targetPKFields, junctionTargetFields, junctionTable } = opts;
|
|
263
|
+
const orFilters = [];
|
|
264
|
+
for (const row of rows) {
|
|
265
|
+
const condition = {};
|
|
266
|
+
let valid = true;
|
|
267
|
+
for (let i = 0; i < localPKFields.length; i++) {
|
|
268
|
+
const val = row[localPKFields[i]];
|
|
269
|
+
if (val === null || val === undefined) {
|
|
270
|
+
valid = false;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
condition[junctionLocalFields[i]] = val;
|
|
274
|
+
}
|
|
275
|
+
if (valid) orFilters.push(condition);
|
|
276
|
+
}
|
|
277
|
+
if (orFilters.length === 0) {
|
|
278
|
+
for (const row of rows) row[relName] = relation.isArray ? [] : null;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const junctionFilter = orFilters.length === 1 ? orFilters[0] : { $or: orFilters };
|
|
282
|
+
const junctionRows = await junctionTable.findMany({
|
|
283
|
+
filter: junctionFilter,
|
|
284
|
+
controls: { $select: [...junctionLocalFields, ...junctionTargetFields] }
|
|
285
|
+
});
|
|
286
|
+
if (junctionRows.length === 0) {
|
|
287
|
+
for (const row of rows) row[relName] = relation.isArray ? [] : null;
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const targetOrFilters = [];
|
|
291
|
+
const seenTargets = new Set();
|
|
292
|
+
for (const jRow of junctionRows) {
|
|
293
|
+
const key = compositeKey(junctionTargetFields, jRow);
|
|
294
|
+
if (seenTargets.has(key)) continue;
|
|
295
|
+
seenTargets.add(key);
|
|
296
|
+
const condition = {};
|
|
297
|
+
for (let i = 0; i < junctionTargetFields.length; i++) condition[targetPKFields[i]] = jRow[junctionTargetFields[i]];
|
|
298
|
+
targetOrFilters.push(condition);
|
|
299
|
+
}
|
|
300
|
+
const targetBaseFilter = targetOrFilters.length === 1 ? targetOrFilters[0] : { $or: targetOrFilters };
|
|
301
|
+
const finalFilter = relQuery.filter ? { $and: [targetBaseFilter, relQuery.filter] } : targetBaseFilter;
|
|
302
|
+
const targetRows = await targetTable.findMany({
|
|
303
|
+
filter: finalFilter,
|
|
304
|
+
controls: relQuery.controls
|
|
305
|
+
});
|
|
306
|
+
const targetIndex = new Map();
|
|
307
|
+
for (const item of targetRows) targetIndex.set(compositeKey(targetPKFields, item), item);
|
|
308
|
+
const groups = new Map();
|
|
309
|
+
for (const jRow of junctionRows) {
|
|
310
|
+
const localKey = compositeKey(junctionLocalFields, jRow);
|
|
311
|
+
const targetKey = compositeKey(junctionTargetFields, jRow);
|
|
312
|
+
const target = targetIndex.get(targetKey);
|
|
313
|
+
if (!target) continue;
|
|
314
|
+
let group = groups.get(localKey);
|
|
315
|
+
if (!group) {
|
|
316
|
+
group = [];
|
|
317
|
+
groups.set(localKey, group);
|
|
318
|
+
}
|
|
319
|
+
group.push(target);
|
|
320
|
+
}
|
|
321
|
+
for (const row of rows) {
|
|
322
|
+
const key = compositeKey(localPKFields, row);
|
|
323
|
+
row[relName] = relation.isArray ? groups.get(key) ?? [] : groups.get(key)?.[0] ?? null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* If controls include an array-style $select, ensure the given join fields
|
|
328
|
+
* are present so that FK matching works after the query returns.
|
|
329
|
+
*/ function ensureSelectIncludesFields(controls, fields) {
|
|
330
|
+
if (!controls) return controls;
|
|
331
|
+
const sel = controls.$select;
|
|
332
|
+
if (!Array.isArray(sel)) return controls;
|
|
333
|
+
const augmented = [...sel];
|
|
334
|
+
for (const f of fields) if (!augmented.includes(f)) augmented.push(f);
|
|
335
|
+
return {
|
|
336
|
+
...controls,
|
|
337
|
+
$select: augmented
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function compositeKey(fields, obj) {
|
|
341
|
+
let key = "";
|
|
342
|
+
for (let i = 0; i < fields.length; i++) {
|
|
343
|
+
if (i > 0) key += "\0\0";
|
|
344
|
+
const v = obj[fields[i]];
|
|
345
|
+
key += v === null || v === undefined ? "\0" : String(v);
|
|
346
|
+
}
|
|
347
|
+
return key;
|
|
348
|
+
}
|
|
349
|
+
/** Collects unique non-null values for a field across rows. */ function collectUniqueValues(rows, field) {
|
|
350
|
+
const set = new Set();
|
|
351
|
+
for (const row of rows) {
|
|
352
|
+
const v = row[field];
|
|
353
|
+
if (v !== null && v !== undefined) set.add(v);
|
|
354
|
+
}
|
|
355
|
+
return [...set];
|
|
356
|
+
}
|
|
357
|
+
/** Assigns related items grouped by FK value (one-to-many). */ function assignGrouped(opts) {
|
|
358
|
+
const { rows, related, localField, remoteField, relName } = opts;
|
|
359
|
+
const groups = new Map();
|
|
360
|
+
for (const item of related) {
|
|
361
|
+
const key = item[remoteField];
|
|
362
|
+
let group = groups.get(key);
|
|
363
|
+
if (!group) {
|
|
364
|
+
group = [];
|
|
365
|
+
groups.set(key, group);
|
|
366
|
+
}
|
|
367
|
+
group.push(item);
|
|
368
|
+
}
|
|
369
|
+
for (const row of rows) row[relName] = groups.get(row[localField]) ?? [];
|
|
370
|
+
}
|
|
371
|
+
/** Assigns related items by FK value (many-to-one / one-to-one). */ function assignSingle(opts) {
|
|
372
|
+
const { rows, related, localField, remoteField, relName } = opts;
|
|
373
|
+
const index = new Map();
|
|
374
|
+
for (const item of related) {
|
|
375
|
+
const key = item[remoteField];
|
|
376
|
+
if (!index.has(key)) index.set(key, item);
|
|
377
|
+
}
|
|
378
|
+
for (const row of rows) row[relName] = index.get(row[localField]) ?? null;
|
|
379
|
+
}
|
|
380
|
+
/** Batch query for composite FK. */ function queryCompositeFK(rows, opts) {
|
|
381
|
+
const { localFields, targetFields, targetTable, relQuery } = opts;
|
|
382
|
+
const seen = new Set();
|
|
383
|
+
const orFilters = [];
|
|
384
|
+
for (const row of rows) {
|
|
385
|
+
const key = compositeKey(localFields, row);
|
|
386
|
+
if (seen.has(key)) continue;
|
|
387
|
+
seen.add(key);
|
|
388
|
+
const condition = {};
|
|
389
|
+
let valid = true;
|
|
390
|
+
for (let i = 0; i < localFields.length; i++) {
|
|
391
|
+
const val = row[localFields[i]];
|
|
392
|
+
if (val === null || val === undefined) {
|
|
393
|
+
valid = false;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
condition[targetFields[i]] = val;
|
|
397
|
+
}
|
|
398
|
+
if (valid) orFilters.push(condition);
|
|
399
|
+
}
|
|
400
|
+
if (orFilters.length === 0) return Promise.resolve([]);
|
|
401
|
+
const baseFilter = orFilters.length === 1 ? orFilters[0] : { $or: orFilters };
|
|
402
|
+
const targetFilter = relQuery.filter ? { $and: [baseFilter, relQuery.filter] } : baseFilter;
|
|
403
|
+
return targetTable.findMany({
|
|
404
|
+
filter: targetFilter,
|
|
405
|
+
controls: relQuery.controls
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
//#endregion
|
|
410
|
+
Object.defineProperty(exports, 'loadRelationsImpl', {
|
|
411
|
+
enumerable: true,
|
|
412
|
+
get: function () {
|
|
413
|
+
return loadRelationsImpl;
|
|
414
|
+
}
|
|
415
|
+
});
|