@atscript/db 0.1.38 → 0.1.40
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 +42 -303
- package/dist/agg.cjs +8 -3
- package/dist/agg.d.cts +7 -0
- package/dist/agg.d.mts +7 -0
- package/dist/agg.mjs +7 -3
- package/dist/control-DRgryKeg.cjs +14 -0
- package/dist/{control_as-bjmwe24C.mjs → control-IANbnfjG.mjs} +6 -18
- package/dist/db-readable-BQQzfguJ.d.cts +1249 -0
- package/dist/db-readable-Bbr4CjMb.d.mts +1249 -0
- package/dist/db-space-BUrQ5BFm.d.mts +309 -0
- package/dist/db-space-Vxpcnyt5.d.cts +309 -0
- package/dist/db-validator-plugin-07kDiis2.d.cts +22 -0
- package/dist/db-validator-plugin-CiqsHTI_.d.mts +22 -0
- package/dist/db-view-BntnAmXO.cjs +3071 -0
- package/dist/db-view-ZsoN91-q.mjs +2970 -0
- package/dist/index.cjs +95 -2801
- package/dist/index.d.cts +137 -0
- package/dist/index.d.mts +137 -0
- package/dist/index.mjs +55 -2761
- package/dist/{nested-writer-BkqL7cp3.cjs → nested-writer-BDXsDMPP.cjs} +196 -150
- package/dist/{nested-writer-NEN51mnR.mjs → nested-writer-Dmm1gbZV.mjs} +118 -70
- package/dist/ops-BdRAFLKY.d.mts +67 -0
- package/dist/ops-DXJ4Zw0P.d.cts +67 -0
- package/dist/ops.cjs +123 -0
- package/dist/ops.d.cts +2 -0
- package/dist/ops.d.mts +2 -0
- package/dist/ops.mjs +112 -0
- package/dist/plugin.cjs +90 -109
- package/dist/plugin.d.cts +6 -0
- package/dist/plugin.d.mts +6 -0
- package/dist/plugin.mjs +29 -49
- package/dist/rel.cjs +20 -20
- package/dist/rel.d.cts +119 -0
- package/dist/rel.d.mts +119 -0
- package/dist/rel.mjs +4 -5
- package/dist/{relation-helpers-guFL_oRf.cjs → relation-helpers-BYvsE1tR.cjs} +26 -22
- package/dist/{relation-helpers-DyBIlQnB.mjs → relation-helpers-CLasawQq.mjs} +11 -6
- package/dist/{relation-loader-Dv7qXYq7.mjs → relation-loader-BEOTXNcq.mjs} +63 -43
- package/dist/{relation-loader-CpnDRf9k.cjs → relation-loader-CRC5LcqM.cjs} +74 -49
- package/dist/shared.cjs +13 -13
- package/dist/{shared.d.ts → shared.d.cts} +14 -13
- package/dist/shared.d.mts +71 -0
- package/dist/shared.mjs +2 -3
- package/dist/sync.cjs +300 -252
- package/dist/sync.d.cts +369 -0
- package/dist/sync.d.mts +369 -0
- package/dist/sync.mjs +284 -233
- package/dist/{validation-utils-DEoCMmEb.cjs → validation-utils-DVJDijnB.cjs} +141 -109
- package/dist/{validation-utils-DhR_mtKa.mjs → validation-utils-DhjIjP1-.mjs} +71 -37
- package/package.json +31 -30
- package/LICENSE +0 -21
- package/dist/agg-BJFJ3dFQ.mjs +0 -8
- package/dist/agg-DnUWAOK8.cjs +0 -14
- package/dist/agg.d.ts +0 -13
- package/dist/chunk-CrpGerW8.cjs +0 -31
- package/dist/control_as-BFPERAF_.cjs +0 -28
- package/dist/index.d.ts +0 -1706
- package/dist/logger-B7oxCfLQ.mjs +0 -12
- package/dist/logger-Dt2v_-wb.cjs +0 -18
- package/dist/plugin.d.ts +0 -5
- package/dist/rel.d.ts +0 -1305
- package/dist/relation-loader-D4mTw6yH.cjs +0 -4
- package/dist/relation-loader-Ggy1ujwR.mjs +0 -4
- package/dist/sync.d.ts +0 -1878
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
import { resolveRelationTargetTable } from "./relation-helpers-
|
|
1
|
+
import { r as resolveRelationTargetTable } from "./relation-helpers-CLasawQq.mjs";
|
|
2
2
|
import { ValidatorError } from "@atscript/typescript/utils";
|
|
3
|
-
|
|
4
|
-
//#region packages/db/src/db-error.ts
|
|
5
|
-
function _define_property(obj, key, value) {
|
|
6
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
7
|
-
value,
|
|
8
|
-
enumerable: true,
|
|
9
|
-
configurable: true,
|
|
10
|
-
writable: true
|
|
11
|
-
});
|
|
12
|
-
else obj[key] = value;
|
|
13
|
-
return obj;
|
|
14
|
-
}
|
|
3
|
+
//#region src/db-error.ts
|
|
15
4
|
var DbError = class extends Error {
|
|
5
|
+
name = "DbError";
|
|
16
6
|
constructor(code, errors, message) {
|
|
17
|
-
super(message ?? errors[0]?.message ?? "Database error")
|
|
18
|
-
this.
|
|
7
|
+
super(message ?? errors[0]?.message ?? "Database error");
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.errors = errors;
|
|
10
|
+
this.stack = void 0;
|
|
19
11
|
}
|
|
20
12
|
};
|
|
21
|
-
|
|
22
13
|
//#endregion
|
|
23
|
-
//#region
|
|
14
|
+
//#region src/table/error-utils.ts
|
|
15
|
+
/**
|
|
16
|
+
* Prefixes error paths with a nav field context.
|
|
17
|
+
* Ensures errors from child table operations (e.g., FK violations on a comment)
|
|
18
|
+
* get paths like `comments[0].authorId` instead of just `authorId`.
|
|
19
|
+
*/
|
|
24
20
|
function prefixErrorPaths(errors, prefix) {
|
|
25
21
|
return errors.map((err) => ({
|
|
26
22
|
...err,
|
|
27
23
|
path: err.path ? `${prefix}.${err.path}` : prefix
|
|
28
24
|
}));
|
|
29
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Wraps an async nested operation and prefixes error paths with the nav field context.
|
|
28
|
+
*/
|
|
30
29
|
async function wrapNestedError(navField, fn) {
|
|
31
30
|
try {
|
|
32
31
|
return await fn();
|
|
@@ -36,6 +35,11 @@ async function wrapNestedError(navField, fn) {
|
|
|
36
35
|
throw error;
|
|
37
36
|
}
|
|
38
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Catches `DbError('FK_VIOLATION')` with empty paths (from adapters that
|
|
40
|
+
* enforce FKs natively but can't report which field failed) and enriches
|
|
41
|
+
* the error with all FK field names from table metadata.
|
|
42
|
+
*/
|
|
39
43
|
async function enrichFkViolation(meta, fn) {
|
|
40
44
|
try {
|
|
41
45
|
return await fn();
|
|
@@ -52,6 +56,10 @@ async function enrichFkViolation(meta, fn) {
|
|
|
52
56
|
throw error;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Wraps a delete operation: catches native `FK_VIOLATION` errors (e.g. SQLite
|
|
61
|
+
* RESTRICT) and re-throws as `CONFLICT` (409) with a descriptive message.
|
|
62
|
+
*/
|
|
55
63
|
async function remapDeleteFkViolation(tableName, fn) {
|
|
56
64
|
try {
|
|
57
65
|
return await fn();
|
|
@@ -63,13 +71,20 @@ async function remapDeleteFkViolation(tableName, fn) {
|
|
|
63
71
|
throw error;
|
|
64
72
|
}
|
|
65
73
|
}
|
|
66
|
-
|
|
67
74
|
//#endregion
|
|
68
|
-
//#region
|
|
75
|
+
//#region src/rel/nested-writer.ts
|
|
76
|
+
/**
|
|
77
|
+
* Checks if any payload contains navigational data that would be silently
|
|
78
|
+
* dropped because maxDepth is 0.
|
|
79
|
+
*/
|
|
69
80
|
function checkDepthOverflow(payloads, maxDepth, meta) {
|
|
70
81
|
if (meta.navFields.size === 0) return;
|
|
71
|
-
for (const payload of payloads) for (const navField of meta.navFields) if (payload[navField] !==
|
|
82
|
+
for (const payload of payloads) for (const navField of meta.navFields) if (payload[navField] !== void 0) throw new Error(`Nested data in '${navField}' exceeds maxDepth (${maxDepth}). Increase maxDepth or strip nested data before writing.`);
|
|
72
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Validates a batch of items using the given validator and context.
|
|
86
|
+
* Wraps per-item validation errors with array index paths for batch operations.
|
|
87
|
+
*/
|
|
73
88
|
function validateBatch(validator, items, ctx) {
|
|
74
89
|
for (let i = 0; i < items.length; i++) try {
|
|
75
90
|
validator.validate(items[i], false, ctx);
|
|
@@ -81,6 +96,10 @@ function validateBatch(validator, items, ctx) {
|
|
|
81
96
|
throw error;
|
|
82
97
|
}
|
|
83
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Pre-validates FROM children (type + FK constraints) before the main insert.
|
|
101
|
+
* Catches errors early before the parent record is committed.
|
|
102
|
+
*/
|
|
84
103
|
async function preValidateNestedFrom(host, originals) {
|
|
85
104
|
for (const [navField, relation] of host._meta.relations) {
|
|
86
105
|
if (relation.direction !== "from") continue;
|
|
@@ -104,6 +123,9 @@ async function preValidateNestedFrom(host, originals) {
|
|
|
104
123
|
await wrapNestedError(navField, () => targetTable.preValidateItems(allChildren, { excludeFkTargetTable: host.tableName }));
|
|
105
124
|
}
|
|
106
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Batch-creates TO dependencies before the main insert.
|
|
128
|
+
*/
|
|
107
129
|
async function batchInsertNestedTo(host, items, maxDepth, depth) {
|
|
108
130
|
for (const [navField, relation] of host._meta.relations) {
|
|
109
131
|
if (relation.direction !== "to") continue;
|
|
@@ -128,6 +150,9 @@ async function batchInsertNestedTo(host, items, maxDepth, depth) {
|
|
|
128
150
|
for (let j = 0; j < sourceIndices.length; j++) if (fk.localFields.length === 1) items[sourceIndices[j]][fk.localFields[0]] = result.insertedIds[j];
|
|
129
151
|
}
|
|
130
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Batch-creates FROM dependents after the main insert.
|
|
155
|
+
*/
|
|
131
156
|
async function batchInsertNestedFrom(host, originals, parentIds, maxDepth, depth) {
|
|
132
157
|
for (const [navField, relation] of host._meta.relations) {
|
|
133
158
|
if (relation.direction !== "from") continue;
|
|
@@ -152,6 +177,9 @@ async function batchInsertNestedFrom(host, originals, parentIds, maxDepth, depth
|
|
|
152
177
|
}));
|
|
153
178
|
}
|
|
154
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Batch-creates VIA (M:N) targets and junction entries after the main insert.
|
|
182
|
+
*/
|
|
155
183
|
async function batchInsertNestedVia(host, originals, parentIds, maxDepth, depth) {
|
|
156
184
|
for (const [navField, relation] of host._meta.relations) {
|
|
157
185
|
if (relation.direction !== "via" || !relation.viaType) continue;
|
|
@@ -170,14 +198,14 @@ async function batchInsertNestedVia(host, originals, parentIds, maxDepth, depth)
|
|
|
170
198
|
const targets = originals[i][navField];
|
|
171
199
|
if (!Array.isArray(targets) || targets.length === 0) continue;
|
|
172
200
|
const parentPK = parentIds[i];
|
|
173
|
-
if (parentPK ===
|
|
201
|
+
if (parentPK === void 0) continue;
|
|
174
202
|
const newTargets = [];
|
|
175
203
|
const existingIds = [];
|
|
176
204
|
for (const t of targets) {
|
|
177
205
|
const rec = t;
|
|
178
206
|
const pk = rec[targetPKField];
|
|
179
|
-
if (pk !==
|
|
180
|
-
else newTargets.push({ ...rec });
|
|
207
|
+
if (pk !== void 0 && pk !== null) existingIds.push(pk);
|
|
208
|
+
else newTargets.push({ ...rec });
|
|
181
209
|
}
|
|
182
210
|
const allTargetIds = [...existingIds];
|
|
183
211
|
if (newTargets.length > 0) {
|
|
@@ -197,6 +225,9 @@ else newTargets.push({ ...rec });
|
|
|
197
225
|
}
|
|
198
226
|
}
|
|
199
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Batch-replaces TO dependencies before the main replace.
|
|
230
|
+
*/
|
|
200
231
|
async function batchReplaceNestedTo(host, items, maxDepth, depth) {
|
|
201
232
|
for (const [navField, relation] of host._meta.relations) {
|
|
202
233
|
if (relation.direction !== "to") continue;
|
|
@@ -221,6 +252,9 @@ async function batchReplaceNestedTo(host, items, maxDepth, depth) {
|
|
|
221
252
|
for (let j = 0; j < sourceIndices.length; j++) if (fk.localFields.length === 1 && fk.targetFields.length === 1) items[sourceIndices[j]][fk.localFields[0]] = parents[j][fk.targetFields[0]];
|
|
222
253
|
}
|
|
223
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Batch-replaces FROM dependents after the main replace.
|
|
257
|
+
*/
|
|
224
258
|
async function batchReplaceNestedFrom(host, originals, maxDepth, depth) {
|
|
225
259
|
for (const [navField, relation] of host._meta.relations) {
|
|
226
260
|
if (relation.direction !== "from") continue;
|
|
@@ -232,12 +266,15 @@ async function batchReplaceNestedFrom(host, originals, maxDepth, depth) {
|
|
|
232
266
|
for (const original of originals) {
|
|
233
267
|
const children = original[navField];
|
|
234
268
|
if (!Array.isArray(children)) continue;
|
|
235
|
-
const parentPK = host._meta.primaryKeys.length === 1 ? original[host._meta.primaryKeys[0]] :
|
|
236
|
-
if (parentPK ===
|
|
269
|
+
const parentPK = host._meta.primaryKeys.length === 1 ? original[host._meta.primaryKeys[0]] : void 0;
|
|
270
|
+
if (parentPK === void 0 || remoteFK.fields.length !== 1) continue;
|
|
237
271
|
await fromReplace(targetTable, children, parentPK, remoteFK.fields[0], childPKs, navField, maxDepth, depth);
|
|
238
272
|
}
|
|
239
273
|
}
|
|
240
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Handles VIA (M:N) relations during replace.
|
|
277
|
+
*/
|
|
241
278
|
async function batchReplaceNestedVia(host, originals, maxDepth, depth) {
|
|
242
279
|
for (const [navField, relation] of host._meta.relations) {
|
|
243
280
|
if (relation.direction !== "via" || !relation.viaType) continue;
|
|
@@ -255,12 +292,16 @@ async function batchReplaceNestedVia(host, originals, maxDepth, depth) {
|
|
|
255
292
|
for (const original of originals) {
|
|
256
293
|
const targets = original[navField];
|
|
257
294
|
if (!Array.isArray(targets)) continue;
|
|
258
|
-
const parentPK = host._meta.primaryKeys.length === 1 ? original[host._meta.primaryKeys[0]] :
|
|
259
|
-
if (parentPK ===
|
|
295
|
+
const parentPK = host._meta.primaryKeys.length === 1 ? original[host._meta.primaryKeys[0]] : void 0;
|
|
296
|
+
if (parentPK === void 0) continue;
|
|
260
297
|
await viaReplace(targetTable, junctionTable, targets, parentPK, targetPKField, fkToThis.fields[0], fkToTarget.fields[0], maxDepth, depth);
|
|
261
298
|
}
|
|
262
299
|
}
|
|
263
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Batch-patches TO dependencies before the main patch.
|
|
303
|
+
* Reads FK values from DB if not present in the payload.
|
|
304
|
+
*/
|
|
264
305
|
async function batchPatchNestedTo(host, items, maxDepth, depth) {
|
|
265
306
|
for (const [navField, relation] of host._meta.relations) {
|
|
266
307
|
if (relation.direction !== "to") continue;
|
|
@@ -273,8 +314,8 @@ async function batchPatchNestedTo(host, items, maxDepth, depth) {
|
|
|
273
314
|
const nested = item[navField];
|
|
274
315
|
if (!nested || typeof nested !== "object" || Array.isArray(nested)) continue;
|
|
275
316
|
const patch = { ...nested };
|
|
276
|
-
let fkValue = fk.localFields.length === 1 ? item[fk.localFields[0]] :
|
|
277
|
-
if (fkValue ===
|
|
317
|
+
let fkValue = fk.localFields.length === 1 ? item[fk.localFields[0]] : void 0;
|
|
318
|
+
if (fkValue === void 0) {
|
|
278
319
|
const pkFilter = host._extractPrimaryKeyFilter(item);
|
|
279
320
|
const current = await host.findOne({
|
|
280
321
|
filter: pkFilter,
|
|
@@ -284,9 +325,9 @@ async function batchPatchNestedTo(host, items, maxDepth, depth) {
|
|
|
284
325
|
path: navField,
|
|
285
326
|
message: `Cannot patch relation '${navField}' — source record not found`
|
|
286
327
|
}]);
|
|
287
|
-
fkValue = fk.localFields.length === 1 ? current[fk.localFields[0]] :
|
|
328
|
+
fkValue = fk.localFields.length === 1 ? current[fk.localFields[0]] : void 0;
|
|
288
329
|
}
|
|
289
|
-
if (fkValue === null || fkValue ===
|
|
330
|
+
if (fkValue === null || fkValue === void 0) throw new DbError("FK_VIOLATION", [{
|
|
290
331
|
path: fk.localFields[0],
|
|
291
332
|
message: `Cannot patch relation '${navField}' — foreign key '${fk.localFields[0]}' is null`
|
|
292
333
|
}]);
|
|
@@ -300,6 +341,10 @@ async function batchPatchNestedTo(host, items, maxDepth, depth) {
|
|
|
300
341
|
});
|
|
301
342
|
}
|
|
302
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Batch-patches FROM (1:N) dependencies after the main patch.
|
|
346
|
+
* Supports patch operators: $replace, $insert, $remove, $update, $upsert.
|
|
347
|
+
*/
|
|
303
348
|
async function batchPatchNestedFrom(host, originals, maxDepth, depth) {
|
|
304
349
|
for (const [navField, relation] of host._meta.relations) {
|
|
305
350
|
if (relation.direction !== "from") continue;
|
|
@@ -310,9 +355,9 @@ async function batchPatchNestedFrom(host, originals, maxDepth, depth) {
|
|
|
310
355
|
const childPKs = [...targetTable.primaryKeys];
|
|
311
356
|
for (const original of originals) {
|
|
312
357
|
const navValue = original[navField];
|
|
313
|
-
if (navValue ===
|
|
314
|
-
const parentPK = host._meta.primaryKeys.length === 1 ? original[host._meta.primaryKeys[0]] :
|
|
315
|
-
if (parentPK ===
|
|
358
|
+
if (navValue === void 0 || navValue === null) continue;
|
|
359
|
+
const parentPK = host._meta.primaryKeys.length === 1 ? original[host._meta.primaryKeys[0]] : void 0;
|
|
360
|
+
if (parentPK === void 0 || remoteFK.fields.length !== 1) continue;
|
|
316
361
|
const fkField = remoteFK.fields[0];
|
|
317
362
|
const ops = extractNavPatchOps(navValue);
|
|
318
363
|
if (ops.replace) await fromReplace(targetTable, ops.replace, parentPK, fkField, childPKs, navField, maxDepth, depth);
|
|
@@ -325,7 +370,7 @@ async function batchPatchNestedFrom(host, originals, maxDepth, depth) {
|
|
|
325
370
|
return f;
|
|
326
371
|
});
|
|
327
372
|
if (removeFilters.length === 1) await targetTable.deleteMany(removeFilters[0]);
|
|
328
|
-
else await targetTable.deleteMany({ $or: removeFilters });
|
|
373
|
+
else await targetTable.deleteMany({ $or: removeFilters });
|
|
329
374
|
}
|
|
330
375
|
if (ops.update && ops.update.length > 0) {
|
|
331
376
|
const items = ops.update.map((child) => {
|
|
@@ -344,9 +389,8 @@ else await targetTable.deleteMany({ $or: removeFilters });
|
|
|
344
389
|
for (const child of ops.upsert) {
|
|
345
390
|
const rec = { ...child };
|
|
346
391
|
rec[fkField] = parentPK;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
else toInsert.push(rec);
|
|
392
|
+
if (childPKs.length > 0 && childPKs.every((pk) => rec[pk] !== void 0)) toUpdate.push(rec);
|
|
393
|
+
else toInsert.push(rec);
|
|
350
394
|
}
|
|
351
395
|
if (toUpdate.length > 0) await wrapNestedError(navField, () => targetTable.bulkUpdate(toUpdate, {
|
|
352
396
|
maxDepth,
|
|
@@ -358,11 +402,12 @@ else toInsert.push(rec);
|
|
|
358
402
|
}));
|
|
359
403
|
}
|
|
360
404
|
if (ops.insert && ops.insert.length > 0) {
|
|
361
|
-
const items =
|
|
362
|
-
|
|
405
|
+
const items = [];
|
|
406
|
+
for (let i = 0; i < ops.insert.length; i++) {
|
|
407
|
+
const rec = { ...ops.insert[i] };
|
|
363
408
|
rec[fkField] = parentPK;
|
|
364
|
-
|
|
365
|
-
}
|
|
409
|
+
items.push(rec);
|
|
410
|
+
}
|
|
366
411
|
await wrapNestedError(navField, () => targetTable.insertMany(items, {
|
|
367
412
|
maxDepth,
|
|
368
413
|
_depth: depth + 1
|
|
@@ -371,6 +416,10 @@ else toInsert.push(rec);
|
|
|
371
416
|
}
|
|
372
417
|
}
|
|
373
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Batch-patches VIA (M:N) dependencies after the main patch.
|
|
421
|
+
* Supports patch operators: $replace, $insert, $remove, $update, $upsert.
|
|
422
|
+
*/
|
|
374
423
|
async function batchPatchNestedVia(host, originals, maxDepth, depth) {
|
|
375
424
|
for (const [navField, relation] of host._meta.relations) {
|
|
376
425
|
if (relation.direction !== "via" || !relation.viaType) continue;
|
|
@@ -387,18 +436,18 @@ async function batchPatchNestedVia(host, originals, maxDepth, depth) {
|
|
|
387
436
|
if (!targetPKField || fkToTarget.fields.length !== 1 || fkToThis.fields.length !== 1) continue;
|
|
388
437
|
for (const original of originals) {
|
|
389
438
|
const navValue = original[navField];
|
|
390
|
-
if (navValue ===
|
|
391
|
-
const parentPK = host._meta.primaryKeys.length === 1 ? original[host._meta.primaryKeys[0]] :
|
|
392
|
-
if (parentPK ===
|
|
439
|
+
if (navValue === void 0 || navValue === null) continue;
|
|
440
|
+
const parentPK = host._meta.primaryKeys.length === 1 ? original[host._meta.primaryKeys[0]] : void 0;
|
|
441
|
+
if (parentPK === void 0) continue;
|
|
393
442
|
const ops = extractNavPatchOps(navValue);
|
|
394
443
|
if (ops.replace) await viaReplace(targetTable, junctionTable, ops.replace, parentPK, targetPKField, fkToThis.fields[0], fkToTarget.fields[0], maxDepth, depth);
|
|
395
444
|
if (ops.remove && ops.remove.length > 0) {
|
|
396
|
-
const targetPKs = ops.remove.map((t) => t[targetPKField]).filter((pk) => pk !==
|
|
445
|
+
const targetPKs = ops.remove.map((t) => t[targetPKField]).filter((pk) => pk !== void 0 && pk !== null);
|
|
397
446
|
if (targetPKs.length === 1) await junctionTable.deleteMany({
|
|
398
447
|
[fkToThis.fields[0]]: parentPK,
|
|
399
448
|
[fkToTarget.fields[0]]: targetPKs[0]
|
|
400
449
|
});
|
|
401
|
-
else if (targetPKs.length > 1) await junctionTable.deleteMany({
|
|
450
|
+
else if (targetPKs.length > 1) await junctionTable.deleteMany({
|
|
402
451
|
[fkToThis.fields[0]]: parentPK,
|
|
403
452
|
[fkToTarget.fields[0]]: { $in: targetPKs }
|
|
404
453
|
});
|
|
@@ -414,7 +463,7 @@ else if (targetPKs.length > 1) await junctionTable.deleteMany({
|
|
|
414
463
|
for (const target of ops.upsert) {
|
|
415
464
|
const rec = { ...target };
|
|
416
465
|
const pk = rec[targetPKField];
|
|
417
|
-
if (pk !==
|
|
466
|
+
if (pk !== void 0 && pk !== null) {
|
|
418
467
|
toUpdate.push(rec);
|
|
419
468
|
existingPKs.push(pk);
|
|
420
469
|
} else toInsert.push(rec);
|
|
@@ -442,11 +491,10 @@ else if (targetPKs.length > 1) await junctionTable.deleteMany({
|
|
|
442
491
|
}
|
|
443
492
|
}
|
|
444
493
|
if (toInsert.length > 0) {
|
|
445
|
-
const
|
|
494
|
+
const junctionRows = (await targetTable.insertMany(toInsert, {
|
|
446
495
|
maxDepth,
|
|
447
496
|
_depth: depth + 1
|
|
448
|
-
})
|
|
449
|
-
const junctionRows = insertResult.insertedIds.map((newId) => ({
|
|
497
|
+
})).insertedIds.map((newId) => ({
|
|
450
498
|
[fkToThis.fields[0]]: parentPK,
|
|
451
499
|
[fkToTarget.fields[0]]: newId
|
|
452
500
|
}));
|
|
@@ -459,8 +507,8 @@ else if (targetPKs.length > 1) await junctionTable.deleteMany({
|
|
|
459
507
|
for (const target of ops.insert) {
|
|
460
508
|
const rec = { ...target };
|
|
461
509
|
const pk = rec[targetPKField];
|
|
462
|
-
if (pk !==
|
|
463
|
-
else toInsert.push(rec);
|
|
510
|
+
if (pk !== void 0 && pk !== null) existingIds.push(pk);
|
|
511
|
+
else toInsert.push(rec);
|
|
464
512
|
}
|
|
465
513
|
const allIds = [...existingIds];
|
|
466
514
|
if (toInsert.length > 0) {
|
|
@@ -484,29 +532,30 @@ else toInsert.push(rec);
|
|
|
484
532
|
/**
|
|
485
533
|
* Extracts patch operations from a nav field value.
|
|
486
534
|
* Plain array → $replace. Object with $insert, $remove, etc. → individual ops.
|
|
487
|
-
*/
|
|
535
|
+
*/
|
|
536
|
+
function extractNavPatchOps(navValue) {
|
|
488
537
|
if (Array.isArray(navValue)) return { replace: navValue };
|
|
489
538
|
if (typeof navValue !== "object" || navValue === null) return {};
|
|
490
539
|
const obj = navValue;
|
|
491
540
|
return {
|
|
492
|
-
replace: obj.$replace !==
|
|
493
|
-
insert: obj.$insert !==
|
|
494
|
-
remove: obj.$remove !==
|
|
495
|
-
update: obj.$update !==
|
|
496
|
-
upsert: obj.$upsert !==
|
|
541
|
+
replace: obj.$replace !== void 0 ? obj.$replace : void 0,
|
|
542
|
+
insert: obj.$insert !== void 0 ? obj.$insert : void 0,
|
|
543
|
+
remove: obj.$remove !== void 0 ? obj.$remove : void 0,
|
|
544
|
+
update: obj.$update !== void 0 ? obj.$update : void 0,
|
|
545
|
+
upsert: obj.$upsert !== void 0 ? obj.$upsert : void 0
|
|
497
546
|
};
|
|
498
547
|
}
|
|
499
548
|
/**
|
|
500
549
|
* FROM $replace helper: delete orphans, replace existing, insert new.
|
|
501
|
-
*/
|
|
550
|
+
*/
|
|
551
|
+
async function fromReplace(targetTable, children, parentPK, fkField, childPKs, navField, maxDepth, depth) {
|
|
502
552
|
const toReplace = [];
|
|
503
553
|
const toInsert = [];
|
|
504
|
-
const newPKSet = new Set();
|
|
554
|
+
const newPKSet = /* @__PURE__ */ new Set();
|
|
505
555
|
for (const child of children) {
|
|
506
556
|
const childData = { ...child };
|
|
507
557
|
childData[fkField] = parentPK;
|
|
508
|
-
|
|
509
|
-
if (hasPK) {
|
|
558
|
+
if (childPKs.length > 0 && childPKs.every((pk) => childData[pk] !== void 0)) {
|
|
510
559
|
newPKSet.add(childPKs.map((pk) => String(childData[pk])).join("\0"));
|
|
511
560
|
toReplace.push(childData);
|
|
512
561
|
} else toInsert.push(childData);
|
|
@@ -525,7 +574,7 @@ else toInsert.push(rec);
|
|
|
525
574
|
}
|
|
526
575
|
}
|
|
527
576
|
if (orphanFilters.length === 1) await targetTable.deleteMany(orphanFilters[0]);
|
|
528
|
-
else if (orphanFilters.length > 1) await targetTable.deleteMany({ $or: orphanFilters });
|
|
577
|
+
else if (orphanFilters.length > 1) await targetTable.deleteMany({ $or: orphanFilters });
|
|
529
578
|
if (toReplace.length > 0) await wrapNestedError(navField, () => targetTable.bulkReplace(toReplace, {
|
|
530
579
|
maxDepth,
|
|
531
580
|
_depth: depth + 1
|
|
@@ -537,7 +586,8 @@ else if (orphanFilters.length > 1) await targetTable.deleteMany({ $or: orphanFil
|
|
|
537
586
|
}
|
|
538
587
|
/**
|
|
539
588
|
* VIA $replace helper: clear junctions, replace/insert targets, rebuild junctions.
|
|
540
|
-
*/
|
|
589
|
+
*/
|
|
590
|
+
async function viaReplace(targetTable, junctionTable, targets, parentPK, targetPKField, fkToThisField, fkToTargetField, maxDepth, depth) {
|
|
541
591
|
await junctionTable.deleteMany({ [fkToThisField]: parentPK });
|
|
542
592
|
const toReplace = [];
|
|
543
593
|
const toInsert = [];
|
|
@@ -545,9 +595,8 @@ else if (orphanFilters.length > 1) await targetTable.deleteMany({ $or: orphanFil
|
|
|
545
595
|
for (const t of targets) {
|
|
546
596
|
const rec = t;
|
|
547
597
|
const pk = rec[targetPKField];
|
|
548
|
-
if (pk !==
|
|
549
|
-
|
|
550
|
-
if (keys.length > 0) toReplace.push({ ...rec });
|
|
598
|
+
if (pk !== void 0 && pk !== null) {
|
|
599
|
+
if (Object.keys(rec).filter((k) => k !== targetPKField).length > 0) toReplace.push({ ...rec });
|
|
551
600
|
existingIds.push(pk);
|
|
552
601
|
} else toInsert.push({ ...rec });
|
|
553
602
|
}
|
|
@@ -571,6 +620,5 @@ else if (orphanFilters.length > 1) await targetTable.deleteMany({ $or: orphanFil
|
|
|
571
620
|
await junctionTable.insertMany(junctionRows, { maxDepth: 0 });
|
|
572
621
|
}
|
|
573
622
|
}
|
|
574
|
-
|
|
575
623
|
//#endregion
|
|
576
|
-
export {
|
|
624
|
+
export { batchPatchNestedTo as a, batchReplaceNestedTo as c, preValidateNestedFrom as d, validateBatch as f, DbError as h, batchPatchNestedFrom as i, batchReplaceNestedVia as l, remapDeleteFkViolation as m, batchInsertNestedTo as n, batchPatchNestedVia as o, enrichFkViolation as p, batchInsertNestedVia as r, batchReplaceNestedFrom as s, batchInsertNestedFrom as t, checkDepthOverflow as u };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/ops.d.ts
|
|
2
|
+
/** A numeric field operation (increment, decrement, or multiply). */
|
|
3
|
+
interface TDbFieldOp {
|
|
4
|
+
$inc?: number;
|
|
5
|
+
$dec?: number;
|
|
6
|
+
$mul?: number;
|
|
7
|
+
}
|
|
8
|
+
/** Increment a numeric field by `value` (default 1). */
|
|
9
|
+
declare function $inc(value?: number): TDbFieldOp;
|
|
10
|
+
/** Decrement a numeric field by `value` (default 1). */
|
|
11
|
+
declare function $dec(value?: number): TDbFieldOp;
|
|
12
|
+
/** Multiply a numeric field by `value`. */
|
|
13
|
+
declare function $mul(value: number): TDbFieldOp;
|
|
14
|
+
/** Replace the entire array. */
|
|
15
|
+
declare function $replace<T>(items: T[]): {
|
|
16
|
+
$replace: T[];
|
|
17
|
+
};
|
|
18
|
+
/** Append items to an array. */
|
|
19
|
+
declare function $insert<T>(items: T[]): {
|
|
20
|
+
$insert: T[];
|
|
21
|
+
};
|
|
22
|
+
/** Insert-or-update items by key. */
|
|
23
|
+
declare function $upsert<T>(items: T[]): {
|
|
24
|
+
$upsert: T[];
|
|
25
|
+
};
|
|
26
|
+
/** Update existing items matched by key. */
|
|
27
|
+
declare function $update<T>(items: Partial<T>[]): {
|
|
28
|
+
$update: Partial<T>[];
|
|
29
|
+
};
|
|
30
|
+
/** Remove items matched by key or value. */
|
|
31
|
+
declare function $remove<T>(items: Partial<T>[]): {
|
|
32
|
+
$remove: Partial<T>[];
|
|
33
|
+
};
|
|
34
|
+
/** Pre-separated field operations, ready for adapters. */
|
|
35
|
+
interface TFieldOps {
|
|
36
|
+
inc?: Record<string, number>;
|
|
37
|
+
mul?: Record<string, number>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns `true` when `value` is a field operation object
|
|
41
|
+
* (`{ $inc: N }`, `{ $dec: N }`, or `{ $mul: N }`).
|
|
42
|
+
*/
|
|
43
|
+
declare function isDbFieldOp(value: unknown): value is TDbFieldOp;
|
|
44
|
+
/**
|
|
45
|
+
* Extracts the normalized operation from a field op value.
|
|
46
|
+
* Returns `undefined` when `value` is not a field op.
|
|
47
|
+
*
|
|
48
|
+
* `$dec` is normalized to `{ op: 'inc', value: -N }` so consumers
|
|
49
|
+
* only need to handle `inc` and `mul`.
|
|
50
|
+
*/
|
|
51
|
+
declare function getDbFieldOp(value: unknown): {
|
|
52
|
+
op: "inc" | "mul";
|
|
53
|
+
value: number;
|
|
54
|
+
} | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Separates field operations from a data payload.
|
|
57
|
+
*
|
|
58
|
+
* Mutates `data` in-place (removes op entries) and returns the separated ops.
|
|
59
|
+
* When no ops are found, returns `undefined` — zero allocation for the
|
|
60
|
+
* common non-op case.
|
|
61
|
+
*
|
|
62
|
+
* Hot path: uses `for...in` (no array allocation), inlines detection to
|
|
63
|
+
* avoid intermediate `{ op, value }` objects, and short-circuits on typeof.
|
|
64
|
+
*/
|
|
65
|
+
declare function separateFieldOps(data: Record<string, unknown>): TFieldOps | undefined;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { $remove as a, $upsert as c, getDbFieldOp as d, isDbFieldOp as f, $mul as i, TDbFieldOp as l, $inc as n, $replace as o, separateFieldOps as p, $insert as r, $update as s, $dec as t, TFieldOps as u };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/ops.d.ts
|
|
2
|
+
/** A numeric field operation (increment, decrement, or multiply). */
|
|
3
|
+
interface TDbFieldOp {
|
|
4
|
+
$inc?: number;
|
|
5
|
+
$dec?: number;
|
|
6
|
+
$mul?: number;
|
|
7
|
+
}
|
|
8
|
+
/** Increment a numeric field by `value` (default 1). */
|
|
9
|
+
declare function $inc(value?: number): TDbFieldOp;
|
|
10
|
+
/** Decrement a numeric field by `value` (default 1). */
|
|
11
|
+
declare function $dec(value?: number): TDbFieldOp;
|
|
12
|
+
/** Multiply a numeric field by `value`. */
|
|
13
|
+
declare function $mul(value: number): TDbFieldOp;
|
|
14
|
+
/** Replace the entire array. */
|
|
15
|
+
declare function $replace<T>(items: T[]): {
|
|
16
|
+
$replace: T[];
|
|
17
|
+
};
|
|
18
|
+
/** Append items to an array. */
|
|
19
|
+
declare function $insert<T>(items: T[]): {
|
|
20
|
+
$insert: T[];
|
|
21
|
+
};
|
|
22
|
+
/** Insert-or-update items by key. */
|
|
23
|
+
declare function $upsert<T>(items: T[]): {
|
|
24
|
+
$upsert: T[];
|
|
25
|
+
};
|
|
26
|
+
/** Update existing items matched by key. */
|
|
27
|
+
declare function $update<T>(items: Partial<T>[]): {
|
|
28
|
+
$update: Partial<T>[];
|
|
29
|
+
};
|
|
30
|
+
/** Remove items matched by key or value. */
|
|
31
|
+
declare function $remove<T>(items: Partial<T>[]): {
|
|
32
|
+
$remove: Partial<T>[];
|
|
33
|
+
};
|
|
34
|
+
/** Pre-separated field operations, ready for adapters. */
|
|
35
|
+
interface TFieldOps {
|
|
36
|
+
inc?: Record<string, number>;
|
|
37
|
+
mul?: Record<string, number>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns `true` when `value` is a field operation object
|
|
41
|
+
* (`{ $inc: N }`, `{ $dec: N }`, or `{ $mul: N }`).
|
|
42
|
+
*/
|
|
43
|
+
declare function isDbFieldOp(value: unknown): value is TDbFieldOp;
|
|
44
|
+
/**
|
|
45
|
+
* Extracts the normalized operation from a field op value.
|
|
46
|
+
* Returns `undefined` when `value` is not a field op.
|
|
47
|
+
*
|
|
48
|
+
* `$dec` is normalized to `{ op: 'inc', value: -N }` so consumers
|
|
49
|
+
* only need to handle `inc` and `mul`.
|
|
50
|
+
*/
|
|
51
|
+
declare function getDbFieldOp(value: unknown): {
|
|
52
|
+
op: "inc" | "mul";
|
|
53
|
+
value: number;
|
|
54
|
+
} | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Separates field operations from a data payload.
|
|
57
|
+
*
|
|
58
|
+
* Mutates `data` in-place (removes op entries) and returns the separated ops.
|
|
59
|
+
* When no ops are found, returns `undefined` — zero allocation for the
|
|
60
|
+
* common non-op case.
|
|
61
|
+
*
|
|
62
|
+
* Hot path: uses `for...in` (no array allocation), inlines detection to
|
|
63
|
+
* avoid intermediate `{ op, value }` objects, and short-circuits on typeof.
|
|
64
|
+
*/
|
|
65
|
+
declare function separateFieldOps(data: Record<string, unknown>): TFieldOps | undefined;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { $remove as a, $upsert as c, getDbFieldOp as d, isDbFieldOp as f, $mul as i, TDbFieldOp as l, $inc as n, $replace as o, separateFieldOps as p, $insert as r, $update as s, $dec as t, TFieldOps as u };
|