@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/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
+ });
@@ -0,0 +1,4 @@
1
+ require('./relation-helpers-guFL_oRf.cjs');
2
+ const require_relation_loader = require('./relation-loader-CpnDRf9k.cjs');
3
+
4
+ exports.loadRelationsImpl = require_relation_loader.loadRelationsImpl