@atscript/mongo 0.1.34 → 0.1.36
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 +1 -1
- package/dist/index.cjs +725 -196
- package/dist/index.d.ts +114 -56
- package/dist/index.mjs +727 -198
- package/dist/plugin.cjs +16 -0
- package/dist/plugin.mjs +16 -0
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -24,16 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
//#endregion
|
|
25
25
|
const __atscript_utils_db = __toESM(require("@atscript/utils-db"));
|
|
26
26
|
const mongodb = __toESM(require("mongodb"));
|
|
27
|
-
const __atscript_typescript_utils = __toESM(require("@atscript/typescript/utils"));
|
|
28
27
|
|
|
29
|
-
//#region packages/mongo/src/lib/validate-plugins.ts
|
|
30
|
-
const validateMongoIdPlugin = (ctx, def, value) => {
|
|
31
|
-
if (ctx.path === "_id" && def.type.tags.has("objectId")) return ctx.validateAnnotatedType(def, value instanceof mongodb.ObjectId ? value.toString() : value);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
//#endregion
|
|
35
28
|
//#region packages/mongo/src/lib/collection-patcher.ts
|
|
36
|
-
function _define_property$
|
|
29
|
+
function _define_property$1(obj, key, value) {
|
|
37
30
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
38
31
|
value,
|
|
39
32
|
enumerable: true,
|
|
@@ -43,50 +36,7 @@ function _define_property$2(obj, key, value) {
|
|
|
43
36
|
else obj[key] = value;
|
|
44
37
|
return obj;
|
|
45
38
|
}
|
|
46
|
-
var CollectionPatcher = class
|
|
47
|
-
/**
|
|
48
|
-
* Build a runtime *Validator* that understands the extended patch payload.
|
|
49
|
-
*
|
|
50
|
-
* * Adds per‑array *patch* wrappers (the `$replace`, `$insert`, … fields).
|
|
51
|
-
* * Honors `db.patch.strategy === "merge"` metadata.
|
|
52
|
-
*
|
|
53
|
-
* @param collection Target collection wrapper
|
|
54
|
-
* @returns Atscript Validator
|
|
55
|
-
*/ static prepareValidator(context) {
|
|
56
|
-
return context.createValidator({
|
|
57
|
-
plugins: [validateMongoIdPlugin],
|
|
58
|
-
replace: (def, path) => {
|
|
59
|
-
if (path === "" && def.type.kind === "object") {
|
|
60
|
-
const obj = (0, __atscript_typescript_utils.defineAnnotatedType)("object").copyMetadata(def.metadata);
|
|
61
|
-
for (const [prop, type] of def.type.props.entries()) obj.prop(prop, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(type).copyMetadata(type.metadata).optional(prop !== "_id").$type);
|
|
62
|
-
return obj.$type;
|
|
63
|
-
}
|
|
64
|
-
if (def.type.kind === "array" && context.flatMap.get(path)?.metadata.get("db.mongo.__topLevelArray") && !def.metadata.has("db.mongo.__patchArrayValue")) {
|
|
65
|
-
const defArray = def;
|
|
66
|
-
const mergeStrategy = defArray.metadata.get("db.patch.strategy") === "merge";
|
|
67
|
-
function getPatchType() {
|
|
68
|
-
const isPrimitive = (0, __atscript_typescript_utils.isAnnotatedTypeOfPrimitive)(defArray.type.of);
|
|
69
|
-
if (isPrimitive) return (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(def).copyMetadata(def.metadata).annotate("db.mongo.__patchArrayValue").optional().$type;
|
|
70
|
-
if (defArray.type.of.type.kind === "object") {
|
|
71
|
-
const objType = defArray.type.of.type;
|
|
72
|
-
const t = (0, __atscript_typescript_utils.defineAnnotatedType)("object").copyMetadata(defArray.type.of.metadata);
|
|
73
|
-
const keyProps = CollectionPatcher.getKeyProps(defArray);
|
|
74
|
-
for (const [key, val] of objType.props.entries()) if (keyProps.size > 0) if (keyProps.has(key)) t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(val).copyMetadata(def.metadata).$type);
|
|
75
|
-
else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(val).copyMetadata(def.metadata).optional().$type);
|
|
76
|
-
else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(val).copyMetadata(def.metadata).optional(!!val.optional).$type);
|
|
77
|
-
return (0, __atscript_typescript_utils.defineAnnotatedType)("array").of(t.$type).copyMetadata(def.metadata).annotate("db.mongo.__patchArrayValue").optional().$type;
|
|
78
|
-
}
|
|
79
|
-
return undefined;
|
|
80
|
-
}
|
|
81
|
-
const fullType = (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(def).copyMetadata(def.metadata).annotate("db.mongo.__patchArrayValue").optional().$type;
|
|
82
|
-
const patchType = getPatchType();
|
|
83
|
-
return patchType ? (0, __atscript_typescript_utils.defineAnnotatedType)("object").prop("$replace", fullType).prop("$insert", fullType).prop("$upsert", fullType).prop("$update", mergeStrategy ? patchType : fullType).prop("$remove", patchType).optional().$type : (0, __atscript_typescript_utils.defineAnnotatedType)("object").prop("$replace", fullType).prop("$insert", fullType).optional().$type;
|
|
84
|
-
}
|
|
85
|
-
return def;
|
|
86
|
-
},
|
|
87
|
-
partial: (def, path) => path !== "" && def.metadata.get("db.patch.strategy") === "merge"
|
|
88
|
-
});
|
|
89
|
-
}
|
|
39
|
+
var CollectionPatcher = class {
|
|
90
40
|
/**
|
|
91
41
|
* Entry point – walk the payload, build `filter`, `update` and `options`.
|
|
92
42
|
*
|
|
@@ -133,8 +83,8 @@ else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(va
|
|
|
133
83
|
for (const [_key, value] of Object.entries(payload)) {
|
|
134
84
|
const key = evalKey(_key);
|
|
135
85
|
const flatType = this.collection.flatMap.get(key);
|
|
136
|
-
const topLevelArray = flatType?.metadata?.get("db.
|
|
137
|
-
if (typeof value === "object" && topLevelArray) this.parseArrayPatch(key, value, flatType);
|
|
86
|
+
const topLevelArray = flatType?.metadata?.get("db.__topLevelArray");
|
|
87
|
+
if (typeof value === "object" && !Array.isArray(value) && topLevelArray && !flatType?.metadata?.has("db.json")) this.parseArrayPatch(key, value, flatType);
|
|
138
88
|
else if (typeof value === "object" && flatType?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
|
|
139
89
|
else if (key !== "_id") this._set(key, value);
|
|
140
90
|
}
|
|
@@ -198,22 +148,37 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
|
|
|
198
148
|
* `$upsert`
|
|
199
149
|
* - keyed → remove existing matching by key(s) then append candidate
|
|
200
150
|
* - unique → $setUnion (deep equality)
|
|
201
|
-
*/ _upsert(key, input, keys,
|
|
151
|
+
*/ _upsert(key, input, keys, flatType) {
|
|
202
152
|
if (!input?.length) return;
|
|
203
153
|
if (keys.length > 0) {
|
|
154
|
+
const mergeStrategy = flatType.metadata?.get("db.patch.strategy") === "merge";
|
|
155
|
+
const vars = {
|
|
156
|
+
acc: "$$value",
|
|
157
|
+
cand: "$$this"
|
|
158
|
+
};
|
|
159
|
+
let appendExpr = "$$cand";
|
|
160
|
+
if (mergeStrategy) {
|
|
161
|
+
vars.existing = { $arrayElemAt: [{ $filter: {
|
|
162
|
+
input: "$$value",
|
|
163
|
+
as: "el",
|
|
164
|
+
cond: this._keysEqual(keys, "$$el", "$$this")
|
|
165
|
+
} }, 0] };
|
|
166
|
+
appendExpr = { $cond: [
|
|
167
|
+
{ $ifNull: ["$$existing", false] },
|
|
168
|
+
{ $mergeObjects: ["$$existing", "$$cand"] },
|
|
169
|
+
"$$cand"
|
|
170
|
+
] };
|
|
171
|
+
}
|
|
204
172
|
this._set(key, { $reduce: {
|
|
205
173
|
input,
|
|
206
174
|
initialValue: { $ifNull: [`$${key}`, []] },
|
|
207
175
|
in: { $let: {
|
|
208
|
-
vars
|
|
209
|
-
acc: "$$value",
|
|
210
|
-
cand: "$$this"
|
|
211
|
-
},
|
|
176
|
+
vars,
|
|
212
177
|
in: { $concatArrays: [{ $filter: {
|
|
213
178
|
input: "$$acc",
|
|
214
179
|
as: "el",
|
|
215
180
|
cond: { $not: this._keysEqual(keys, "$$el", "$$cand") }
|
|
216
|
-
} }, [
|
|
181
|
+
} }, [appendExpr]] }
|
|
217
182
|
} }
|
|
218
183
|
} });
|
|
219
184
|
return;
|
|
@@ -264,15 +229,15 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
|
|
|
264
229
|
else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
|
|
265
230
|
}
|
|
266
231
|
constructor(collection, payload) {
|
|
267
|
-
_define_property$
|
|
268
|
-
_define_property$
|
|
232
|
+
_define_property$1(this, "collection", void 0);
|
|
233
|
+
_define_property$1(this, "payload", void 0);
|
|
269
234
|
/**
|
|
270
235
|
* Internal accumulator: filter passed to `updateOne()`.
|
|
271
236
|
* Filled only with the `_id` field right now.
|
|
272
|
-
*/ _define_property$
|
|
273
|
-
/** MongoDB *update* document being built. */ _define_property$
|
|
274
|
-
/** Current `$set` stage being populated. */ _define_property$
|
|
275
|
-
/** Additional *options* (mainly `arrayFilters`). */ _define_property$
|
|
237
|
+
*/ _define_property$1(this, "filterObj", void 0);
|
|
238
|
+
/** MongoDB *update* document being built. */ _define_property$1(this, "updatePipeline", void 0);
|
|
239
|
+
/** Current `$set` stage being populated. */ _define_property$1(this, "currentSetStage", void 0);
|
|
240
|
+
/** Additional *options* (mainly `arrayFilters`). */ _define_property$1(this, "optionsObj", void 0);
|
|
276
241
|
this.collection = collection;
|
|
277
242
|
this.payload = payload;
|
|
278
243
|
this.filterObj = {};
|
|
@@ -281,7 +246,7 @@ else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
|
|
|
281
246
|
this.optionsObj = {};
|
|
282
247
|
}
|
|
283
248
|
};
|
|
284
|
-
_define_property$
|
|
249
|
+
_define_property$1(CollectionPatcher, "getKeyProps", __atscript_utils_db.getKeyProps);
|
|
285
250
|
|
|
286
251
|
//#endregion
|
|
287
252
|
//#region packages/mongo/src/lib/mongo-filter.ts
|
|
@@ -307,12 +272,24 @@ const mongoVisitor = {
|
|
|
307
272
|
};
|
|
308
273
|
function buildMongoFilter(filter) {
|
|
309
274
|
if (!filter || Object.keys(filter).length === 0) return EMPTY;
|
|
310
|
-
return (0, __atscript_utils_db.walkFilter)(filter, mongoVisitor);
|
|
275
|
+
return (0, __atscript_utils_db.walkFilter)(filter, mongoVisitor) ?? EMPTY;
|
|
311
276
|
}
|
|
312
277
|
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region packages/mongo/src/lib/validate-plugins.ts
|
|
280
|
+
const validateMongoIdPlugin = (ctx, def, value) => {
|
|
281
|
+
if (def.type.tags?.has("objectId")) {
|
|
282
|
+
if (ctx.path === "_id" && (value === undefined || value === null)) {
|
|
283
|
+
const dbCtx = ctx.context;
|
|
284
|
+
if (dbCtx && (dbCtx.mode === "insert" || dbCtx.mode === "replace")) return true;
|
|
285
|
+
}
|
|
286
|
+
return ctx.validateAnnotatedType(def, value instanceof mongodb.ObjectId ? value.toString() : value);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
313
290
|
//#endregion
|
|
314
291
|
//#region packages/mongo/src/lib/mongo-adapter.ts
|
|
315
|
-
function _define_property
|
|
292
|
+
function _define_property(obj, key, value) {
|
|
316
293
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
317
294
|
value,
|
|
318
295
|
enumerable: true,
|
|
@@ -324,17 +301,63 @@ else obj[key] = value;
|
|
|
324
301
|
}
|
|
325
302
|
const INDEX_PREFIX = "atscript__";
|
|
326
303
|
const DEFAULT_INDEX_NAME = "DEFAULT";
|
|
304
|
+
const JOINED_PREFIX = "__joined_";
|
|
327
305
|
function mongoIndexKey(type, name) {
|
|
328
306
|
const cleanName = name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 127 - INDEX_PREFIX.length - type.length - 2);
|
|
329
307
|
return `${INDEX_PREFIX}${type}__${cleanName}`;
|
|
330
308
|
}
|
|
331
|
-
var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
309
|
+
var MongoAdapter = class MongoAdapter extends __atscript_utils_db.BaseDbAdapter {
|
|
310
|
+
get _client() {
|
|
311
|
+
return this.client;
|
|
312
|
+
}
|
|
313
|
+
async _beginTransaction() {
|
|
314
|
+
if (this._txDisabled || !this._client) return undefined;
|
|
315
|
+
try {
|
|
316
|
+
const topology = this._client.topology;
|
|
317
|
+
if (topology) {
|
|
318
|
+
const desc = topology.description ?? topology.s?.description;
|
|
319
|
+
const type = desc?.type;
|
|
320
|
+
if (type === "Single" || type === "Unknown") {
|
|
321
|
+
this._txDisabled = true;
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const session = this._client.startSession();
|
|
326
|
+
session.startTransaction();
|
|
327
|
+
return session;
|
|
328
|
+
} catch {
|
|
329
|
+
this._txDisabled = true;
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async _commitTransaction(state) {
|
|
334
|
+
if (!state) return;
|
|
335
|
+
const session = state;
|
|
336
|
+
try {
|
|
337
|
+
await session.commitTransaction();
|
|
338
|
+
} finally {
|
|
339
|
+
session.endSession();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async _rollbackTransaction(state) {
|
|
343
|
+
if (!state) return;
|
|
344
|
+
const session = state;
|
|
345
|
+
try {
|
|
346
|
+
await session.abortTransaction();
|
|
347
|
+
} finally {
|
|
348
|
+
session.endSession();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */ _getSessionOpts() {
|
|
352
|
+
const session = this._getTransactionState();
|
|
353
|
+
return session ? { session } : MongoAdapter._noSession;
|
|
354
|
+
}
|
|
332
355
|
get collection() {
|
|
333
356
|
if (!this._collection) this._collection = this.db.collection(this.resolveTableName(false));
|
|
334
357
|
return this._collection;
|
|
335
358
|
}
|
|
336
359
|
aggregate(pipeline) {
|
|
337
|
-
return this.collection.aggregate(pipeline);
|
|
360
|
+
return this.collection.aggregate(pipeline, this._getSessionOpts());
|
|
338
361
|
}
|
|
339
362
|
get idType() {
|
|
340
363
|
const idProp = this._table.type.type.props.get("_id");
|
|
@@ -372,30 +395,260 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
372
395
|
getValidatorPlugins() {
|
|
373
396
|
return [validateMongoIdPlugin];
|
|
374
397
|
}
|
|
375
|
-
getTopLevelArrayTag() {
|
|
376
|
-
return "db.mongo.__topLevelArray";
|
|
377
|
-
}
|
|
378
398
|
getAdapterTableName(type) {
|
|
379
399
|
return undefined;
|
|
380
400
|
}
|
|
381
|
-
|
|
382
|
-
return
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
401
|
+
supportsNativeRelations() {
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
async loadRelations(rows, withRelations, relations, foreignKeys, tableResolver) {
|
|
405
|
+
if (rows.length === 0 || withRelations.length === 0) return;
|
|
406
|
+
const primaryKeys = this._table.primaryKeys;
|
|
407
|
+
const relMeta = [];
|
|
408
|
+
for (const withRel of withRelations) {
|
|
409
|
+
if (withRel.name.includes(".")) continue;
|
|
410
|
+
const relation = relations.get(withRel.name);
|
|
411
|
+
if (!relation) throw new Error(`Unknown relation "${withRel.name}" in $with. Available relations: ${[...relations.keys()].join(", ") || "(none)"}`);
|
|
412
|
+
const lookupResult = this._buildRelationLookup(withRel, relation, foreignKeys, tableResolver);
|
|
413
|
+
if (!lookupResult) continue;
|
|
414
|
+
relMeta.push({
|
|
415
|
+
name: withRel.name,
|
|
416
|
+
isArray: lookupResult.isArray,
|
|
417
|
+
relation,
|
|
418
|
+
nestedWith: this._extractNestedWith(withRel),
|
|
419
|
+
stages: lookupResult.stages
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
if (relMeta.length === 0) return;
|
|
423
|
+
const pkMatchFilter = this._buildPKMatchFilter(rows, primaryKeys);
|
|
424
|
+
if (pkMatchFilter) {
|
|
425
|
+
const pipeline = [{ $match: pkMatchFilter }];
|
|
426
|
+
for (const meta of relMeta) pipeline.push(...meta.stages);
|
|
427
|
+
const results = await this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
428
|
+
this._mergeRelationResults(rows, results, primaryKeys, relMeta);
|
|
429
|
+
} else for (const row of rows) for (const meta of relMeta) row[meta.name] = meta.isArray ? [] : null;
|
|
430
|
+
await this._loadNestedRelations(rows, relMeta, tableResolver);
|
|
431
|
+
}
|
|
432
|
+
/** Builds a $match filter to re-select source rows by PK. */ _buildPKMatchFilter(rows, primaryKeys) {
|
|
433
|
+
if (primaryKeys.length === 1) {
|
|
434
|
+
const pk = primaryKeys[0];
|
|
435
|
+
const values = new Set();
|
|
436
|
+
for (const row of rows) {
|
|
437
|
+
const v = row[pk];
|
|
438
|
+
if (v !== null && v !== undefined) values.add(v);
|
|
394
439
|
}
|
|
395
|
-
|
|
440
|
+
if (values.size === 0) return undefined;
|
|
441
|
+
return { [pk]: { $in: [...values] } };
|
|
442
|
+
}
|
|
443
|
+
const seen = new Set();
|
|
444
|
+
const orFilters = [];
|
|
445
|
+
for (const row of rows) {
|
|
446
|
+
const key = primaryKeys.map((pk) => String(row[pk] ?? "")).join("\0");
|
|
447
|
+
if (seen.has(key)) continue;
|
|
448
|
+
seen.add(key);
|
|
449
|
+
const condition = {};
|
|
450
|
+
let valid = true;
|
|
451
|
+
for (const pk of primaryKeys) {
|
|
452
|
+
const val = row[pk];
|
|
453
|
+
if (val === null || val === undefined) {
|
|
454
|
+
valid = false;
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
condition[pk] = val;
|
|
458
|
+
}
|
|
459
|
+
if (valid) orFilters.push(condition);
|
|
460
|
+
}
|
|
461
|
+
if (orFilters.length === 0) return undefined;
|
|
462
|
+
return orFilters.length === 1 ? orFilters[0] : { $or: orFilters };
|
|
463
|
+
}
|
|
464
|
+
/** Dispatches to the correct $lookup builder based on relation direction. */ _buildRelationLookup(withRel, relation, foreignKeys, tableResolver) {
|
|
465
|
+
switch (relation.direction) {
|
|
466
|
+
case "to": return this._buildToLookup(withRel, relation, foreignKeys);
|
|
467
|
+
case "from": return this._buildFromLookup(withRel, relation, tableResolver);
|
|
468
|
+
case "via": return this._buildViaLookup(withRel, relation, tableResolver);
|
|
469
|
+
default: return undefined;
|
|
470
|
+
}
|
|
396
471
|
}
|
|
397
|
-
|
|
398
|
-
|
|
472
|
+
/** Builds `let` variable bindings and the corresponding `$expr` match for `$lookup`. */ _buildLookupJoin(localFields, remoteFields, varPrefix) {
|
|
473
|
+
const letVars = Object.fromEntries(localFields.map((f, i) => [`${varPrefix}${i}`, `$${f}`]));
|
|
474
|
+
const exprMatch = remoteFields.length === 1 ? { $eq: [`$${remoteFields[0]}`, `$$${varPrefix}0`] } : { $and: remoteFields.map((rf, i) => ({ $eq: [`$${rf}`, `$$${varPrefix}${i}`] })) };
|
|
475
|
+
return {
|
|
476
|
+
letVars,
|
|
477
|
+
exprMatch
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
/** $lookup for TO relations (FK is on this table → target). Always single-valued. */ _buildToLookup(withRel, relation, foreignKeys) {
|
|
481
|
+
const fk = this._findFKForRelationLookup(relation, foreignKeys);
|
|
482
|
+
if (!fk) return undefined;
|
|
483
|
+
const innerPipeline = this._buildLookupInnerPipeline(withRel, fk.targetFields);
|
|
484
|
+
const { letVars, exprMatch } = this._buildLookupJoin(fk.localFields, fk.targetFields, "fk_");
|
|
485
|
+
const stages = [{ $lookup: {
|
|
486
|
+
from: fk.targetTable,
|
|
487
|
+
let: letVars,
|
|
488
|
+
pipeline: [{ $match: { $expr: exprMatch } }, ...innerPipeline],
|
|
489
|
+
as: withRel.name
|
|
490
|
+
} }, { $unwind: {
|
|
491
|
+
path: `$${withRel.name}`,
|
|
492
|
+
preserveNullAndEmptyArrays: true
|
|
493
|
+
} }];
|
|
494
|
+
return {
|
|
495
|
+
stages,
|
|
496
|
+
isArray: false
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/** $lookup for FROM relations (FK is on target → this table). */ _buildFromLookup(withRel, relation, tableResolver) {
|
|
500
|
+
const targetType = relation.targetType();
|
|
501
|
+
if (!targetType || !tableResolver) return undefined;
|
|
502
|
+
const targetMeta = tableResolver(targetType);
|
|
503
|
+
if (!targetMeta) return undefined;
|
|
504
|
+
const remoteFK = this._findRemoteFKFromMeta(targetMeta, this._table.tableName, relation.alias);
|
|
505
|
+
if (!remoteFK) return undefined;
|
|
506
|
+
const targetTableName = this._resolveRelTargetTableName(relation);
|
|
507
|
+
const innerPipeline = this._buildLookupInnerPipeline(withRel, remoteFK.fields);
|
|
508
|
+
const { letVars, exprMatch } = this._buildLookupJoin(remoteFK.targetFields, remoteFK.fields, "pk_");
|
|
509
|
+
const stages = [{ $lookup: {
|
|
510
|
+
from: targetTableName,
|
|
511
|
+
let: letVars,
|
|
512
|
+
pipeline: [{ $match: { $expr: exprMatch } }, ...innerPipeline],
|
|
513
|
+
as: withRel.name
|
|
514
|
+
} }];
|
|
515
|
+
if (!relation.isArray) stages.push({ $unwind: {
|
|
516
|
+
path: `$${withRel.name}`,
|
|
517
|
+
preserveNullAndEmptyArrays: true
|
|
518
|
+
} });
|
|
519
|
+
return {
|
|
520
|
+
stages,
|
|
521
|
+
isArray: relation.isArray
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/** $lookup for VIA relations (M:N through junction table). Always array. */ _buildViaLookup(withRel, relation, tableResolver) {
|
|
525
|
+
if (!relation.viaType || !tableResolver) return undefined;
|
|
526
|
+
const junctionType = relation.viaType();
|
|
527
|
+
if (!junctionType) return undefined;
|
|
528
|
+
const junctionMeta = tableResolver(junctionType);
|
|
529
|
+
if (!junctionMeta) return undefined;
|
|
530
|
+
const junctionTableName = junctionType.metadata?.get("db.table") || junctionType.id || "";
|
|
531
|
+
const targetTableName = this._resolveRelTargetTableName(relation);
|
|
532
|
+
const fkToThis = this._findRemoteFKFromMeta(junctionMeta, this._table.tableName);
|
|
533
|
+
if (!fkToThis) return undefined;
|
|
534
|
+
const fkToTarget = this._findRemoteFKFromMeta(junctionMeta, targetTableName);
|
|
535
|
+
if (!fkToTarget) return undefined;
|
|
536
|
+
const innerPipeline = this._buildLookupInnerPipeline(withRel, fkToTarget.targetFields);
|
|
537
|
+
const { letVars, exprMatch } = this._buildLookupJoin(fkToThis.targetFields, fkToThis.fields, "pk_");
|
|
538
|
+
const stages = [{ $lookup: {
|
|
539
|
+
from: junctionTableName,
|
|
540
|
+
let: letVars,
|
|
541
|
+
pipeline: [
|
|
542
|
+
{ $match: { $expr: exprMatch } },
|
|
543
|
+
{ $lookup: {
|
|
544
|
+
from: targetTableName,
|
|
545
|
+
localField: fkToTarget.fields[0],
|
|
546
|
+
foreignField: fkToTarget.targetFields[0],
|
|
547
|
+
pipeline: innerPipeline,
|
|
548
|
+
as: "__target"
|
|
549
|
+
} },
|
|
550
|
+
{ $unwind: {
|
|
551
|
+
path: "$__target",
|
|
552
|
+
preserveNullAndEmptyArrays: false
|
|
553
|
+
} },
|
|
554
|
+
{ $replaceRoot: { newRoot: "$__target" } }
|
|
555
|
+
],
|
|
556
|
+
as: withRel.name
|
|
557
|
+
} }];
|
|
558
|
+
return {
|
|
559
|
+
stages,
|
|
560
|
+
isArray: true
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
/** Builds inner pipeline stages for relation controls ($sort, $limit, $skip, $select, filter). */ _buildLookupInnerPipeline(withRel, requiredFields) {
|
|
564
|
+
const pipeline = [];
|
|
565
|
+
const flatRel = withRel;
|
|
566
|
+
const nested = withRel.controls || {};
|
|
567
|
+
const filter = withRel.filter;
|
|
568
|
+
const sort = nested.$sort || flatRel.$sort;
|
|
569
|
+
const limit = nested.$limit ?? flatRel.$limit;
|
|
570
|
+
const skip = nested.$skip ?? flatRel.$skip;
|
|
571
|
+
const select = nested.$select || flatRel.$select;
|
|
572
|
+
if (filter && Object.keys(filter).length > 0) pipeline.push({ $match: buildMongoFilter(filter) });
|
|
573
|
+
if (sort) pipeline.push({ $sort: sort });
|
|
574
|
+
if (skip) pipeline.push({ $skip: skip });
|
|
575
|
+
if (limit !== null && limit !== undefined) pipeline.push({ $limit: limit });
|
|
576
|
+
if (select) {
|
|
577
|
+
const projection = {};
|
|
578
|
+
for (const f of select) projection[f] = 1;
|
|
579
|
+
for (const f of requiredFields) projection[f] = 1;
|
|
580
|
+
if (!select.includes("_id") && !requiredFields.includes("_id")) projection["_id"] = 0;
|
|
581
|
+
pipeline.push({ $project: projection });
|
|
582
|
+
}
|
|
583
|
+
return pipeline;
|
|
584
|
+
}
|
|
585
|
+
/** Extracts nested $with from a WithRelation's controls. */ _extractNestedWith(withRel) {
|
|
586
|
+
const flatRel = withRel;
|
|
587
|
+
const nested = withRel.controls || {};
|
|
588
|
+
const nestedWith = nested.$with || flatRel.$with;
|
|
589
|
+
return nestedWith && nestedWith.length > 0 ? nestedWith : undefined;
|
|
590
|
+
}
|
|
591
|
+
/** Post-processes nested $with by delegating to the target table's own relation loading. */ async _loadNestedRelations(rows, relMeta, tableResolver) {
|
|
592
|
+
if (!tableResolver) return;
|
|
593
|
+
const tasks = [];
|
|
594
|
+
for (const meta of relMeta) {
|
|
595
|
+
if (!meta.nestedWith || meta.nestedWith.length === 0) continue;
|
|
596
|
+
const targetType = meta.relation.targetType();
|
|
597
|
+
if (!targetType) continue;
|
|
598
|
+
const targetTable = tableResolver(targetType);
|
|
599
|
+
if (!targetTable) continue;
|
|
600
|
+
const subRows = [];
|
|
601
|
+
for (const row of rows) {
|
|
602
|
+
const val = row[meta.name];
|
|
603
|
+
if (meta.isArray && Array.isArray(val)) for (const item of val) subRows.push(item);
|
|
604
|
+
else if (val && typeof val === "object") subRows.push(val);
|
|
605
|
+
}
|
|
606
|
+
if (subRows.length === 0) continue;
|
|
607
|
+
tasks.push(targetTable.loadRelations(subRows, meta.nestedWith));
|
|
608
|
+
}
|
|
609
|
+
await Promise.all(tasks);
|
|
610
|
+
}
|
|
611
|
+
/** Merges aggregation results back onto the original rows by PK. */ _mergeRelationResults(rows, results, primaryKeys, relMeta) {
|
|
612
|
+
const resultIndex = new Map();
|
|
613
|
+
for (const doc of results) {
|
|
614
|
+
const key = primaryKeys.map((pk) => String(doc[pk] ?? "")).join("\0");
|
|
615
|
+
resultIndex.set(key, doc);
|
|
616
|
+
}
|
|
617
|
+
for (const row of rows) {
|
|
618
|
+
const key = primaryKeys.map((pk) => String(row[pk] ?? "")).join("\0");
|
|
619
|
+
const enriched = resultIndex.get(key);
|
|
620
|
+
for (const meta of relMeta) if (enriched) {
|
|
621
|
+
const value = enriched[meta.name];
|
|
622
|
+
if (!meta.isArray && Array.isArray(value)) row[meta.name] = value[0] ?? null;
|
|
623
|
+
else row[meta.name] = value ?? (meta.isArray ? [] : null);
|
|
624
|
+
} else row[meta.name] = meta.isArray ? [] : null;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/** Finds FK entry for a TO relation from this table's foreignKeys map. */ _findFKForRelationLookup(relation, foreignKeys) {
|
|
628
|
+
const targetTableName = this._resolveRelTargetTableName(relation);
|
|
629
|
+
for (const fk of foreignKeys.values()) if (relation.alias) {
|
|
630
|
+
if (fk.alias === relation.alias) return {
|
|
631
|
+
localFields: fk.fields,
|
|
632
|
+
targetFields: fk.targetFields,
|
|
633
|
+
targetTable: fk.targetTable
|
|
634
|
+
};
|
|
635
|
+
} else if (fk.targetTable === targetTableName) return {
|
|
636
|
+
localFields: fk.fields,
|
|
637
|
+
targetFields: fk.targetFields,
|
|
638
|
+
targetTable: fk.targetTable
|
|
639
|
+
};
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
/** Finds a FK on a remote table that points back to the given table name. */ _findRemoteFKFromMeta(target, thisTableName, alias) {
|
|
643
|
+
for (const fk of target.foreignKeys.values()) {
|
|
644
|
+
if (alias && fk.alias === alias && fk.targetTable === thisTableName) return fk;
|
|
645
|
+
if (!alias && fk.targetTable === thisTableName) return fk;
|
|
646
|
+
}
|
|
647
|
+
return undefined;
|
|
648
|
+
}
|
|
649
|
+
/** Resolves the target table/collection name from a relation's target type. */ _resolveRelTargetTableName(relation) {
|
|
650
|
+
const targetType = relation.targetType();
|
|
651
|
+
return targetType?.metadata?.get("db.table") || targetType?.id || "";
|
|
399
652
|
}
|
|
400
653
|
/** Returns the context object used by CollectionPatcher. */ getPatcherContext() {
|
|
401
654
|
return {
|
|
@@ -408,7 +661,11 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
408
661
|
const mongoFilter = buildMongoFilter(filter);
|
|
409
662
|
const patcher = new CollectionPatcher(this.getPatcherContext(), patch);
|
|
410
663
|
const { updateFilter, updateOptions } = patcher.preparePatch();
|
|
411
|
-
|
|
664
|
+
this._log("updateOne (patch)", mongoFilter, updateFilter);
|
|
665
|
+
const result = await this.collection.updateOne(mongoFilter, updateFilter, {
|
|
666
|
+
...updateOptions,
|
|
667
|
+
...this._getSessionOpts()
|
|
668
|
+
});
|
|
412
669
|
return {
|
|
413
670
|
matchedCount: result.matchedCount,
|
|
414
671
|
modifiedCount: result.modifiedCount
|
|
@@ -416,6 +673,11 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
416
673
|
}
|
|
417
674
|
onBeforeFlatten(type) {
|
|
418
675
|
const typeMeta = type.metadata;
|
|
676
|
+
const capped = typeMeta.get("db.mongo.capped");
|
|
677
|
+
if (capped) this._cappedOptions = {
|
|
678
|
+
size: capped.size,
|
|
679
|
+
max: capped.max
|
|
680
|
+
};
|
|
419
681
|
const dynamicText = typeMeta.get("db.mongo.search.dynamic");
|
|
420
682
|
if (dynamicText) this._setSearchIndex("dynamic_text", "_", {
|
|
421
683
|
mappings: { dynamic: true },
|
|
@@ -429,8 +691,8 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
429
691
|
});
|
|
430
692
|
}
|
|
431
693
|
onFieldScanned(field, type, metadata) {
|
|
694
|
+
if (field === "_id") this._hasExplicitId = true;
|
|
432
695
|
if (field !== "_id" && metadata.has("meta.id")) {
|
|
433
|
-
this._table.removePrimaryKey(field);
|
|
434
696
|
this._addMongoIndexField("unique", "__pk", field);
|
|
435
697
|
this._table.addUniqueField(field);
|
|
436
698
|
}
|
|
@@ -455,7 +717,28 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
455
717
|
for (const index of metadata.get("db.mongo.search.filter") || []) this._vectorFilters.set(mongoIndexKey("vector", index.indexName), field);
|
|
456
718
|
}
|
|
457
719
|
onAfterFlatten() {
|
|
458
|
-
this.
|
|
720
|
+
if (this._hasExplicitId) {
|
|
721
|
+
this._table.addPrimaryKey("_id");
|
|
722
|
+
for (const field of this._table.originalMetaIdFields) if (field !== "_id") this._table.removePrimaryKey(field);
|
|
723
|
+
} else {
|
|
724
|
+
this._table.flatMap.set("_id", {
|
|
725
|
+
__is_atscript_annotated_type: true,
|
|
726
|
+
type: {
|
|
727
|
+
kind: "",
|
|
728
|
+
designType: "string",
|
|
729
|
+
tags: new Set(["objectId", "mongo"])
|
|
730
|
+
},
|
|
731
|
+
metadata: new Map()
|
|
732
|
+
});
|
|
733
|
+
this._table.addUniqueField("_id");
|
|
734
|
+
}
|
|
735
|
+
if (this._table.navFields.size > 0) {
|
|
736
|
+
const isUnderNav = (path) => {
|
|
737
|
+
for (const nav of this._table.navFields) if (path.startsWith(`${nav}.`)) return true;
|
|
738
|
+
return false;
|
|
739
|
+
};
|
|
740
|
+
for (const field of this._incrementFields) if (isUnderNav(field)) this._incrementFields.delete(field);
|
|
741
|
+
}
|
|
459
742
|
for (const [key, value] of this._vectorFilters.entries()) {
|
|
460
743
|
const index = this._mongoIndexes.get(key);
|
|
461
744
|
if (index && index.type === "vector") index.definition.fields?.push({
|
|
@@ -513,7 +796,7 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
513
796
|
/**
|
|
514
797
|
* Builds a MongoDB `$search` pipeline stage.
|
|
515
798
|
* Override `buildVectorSearchStage` in subclasses to provide embeddings.
|
|
516
|
-
*/ buildSearchStage(text, indexName) {
|
|
799
|
+
*/ async buildSearchStage(text, indexName) {
|
|
517
800
|
const index = this.getMongoSearchIndex(indexName);
|
|
518
801
|
if (!index) return undefined;
|
|
519
802
|
if (index.type === "vector") return this.buildVectorSearchStage(text, index);
|
|
@@ -528,11 +811,11 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
528
811
|
/**
|
|
529
812
|
* Builds a vector search stage. Override in subclasses to generate embeddings.
|
|
530
813
|
* Returns `undefined` by default (vector search requires custom implementation).
|
|
531
|
-
*/ buildVectorSearchStage(text, index) {
|
|
814
|
+
*/ async buildVectorSearchStage(text, index) {
|
|
532
815
|
return undefined;
|
|
533
816
|
}
|
|
534
817
|
async search(text, query, indexName) {
|
|
535
|
-
const searchStage = this.buildSearchStage(text, indexName);
|
|
818
|
+
const searchStage = await this.buildSearchStage(text, indexName);
|
|
536
819
|
if (!searchStage) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
|
|
537
820
|
const filter = buildMongoFilter(query.filter);
|
|
538
821
|
const controls = query.controls || {};
|
|
@@ -542,10 +825,11 @@ var MongoAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
542
825
|
if (controls.$limit) pipeline.push({ $limit: controls.$limit });
|
|
543
826
|
else pipeline.push({ $limit: 1e3 });
|
|
544
827
|
if (controls.$select) pipeline.push({ $project: controls.$select.asProjection });
|
|
545
|
-
|
|
828
|
+
this._log("aggregate (search)", pipeline);
|
|
829
|
+
return this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
546
830
|
}
|
|
547
831
|
async searchWithCount(text, query, indexName) {
|
|
548
|
-
const searchStage = this.buildSearchStage(text, indexName);
|
|
832
|
+
const searchStage = await this.buildSearchStage(text, indexName);
|
|
549
833
|
if (!searchStage) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
|
|
550
834
|
const filter = buildMongoFilter(query.filter);
|
|
551
835
|
const controls = query.controls || {};
|
|
@@ -562,7 +846,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
562
846
|
meta: [{ $count: "count" }]
|
|
563
847
|
} }
|
|
564
848
|
];
|
|
565
|
-
|
|
849
|
+
this._log("aggregate (searchWithCount)", pipeline);
|
|
850
|
+
const result = await this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
566
851
|
return {
|
|
567
852
|
data: result[0]?.data || [],
|
|
568
853
|
count: result[0]?.meta[0]?.count || 0
|
|
@@ -580,71 +865,101 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
580
865
|
].filter(Boolean),
|
|
581
866
|
meta: [{ $count: "count" }]
|
|
582
867
|
} }];
|
|
583
|
-
|
|
868
|
+
this._log("aggregate (findManyWithCount)", pipeline);
|
|
869
|
+
const result = await this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
|
|
584
870
|
return {
|
|
585
871
|
data: result[0]?.data || [],
|
|
586
872
|
count: result[0]?.meta[0]?.count || 0
|
|
587
873
|
};
|
|
588
874
|
}
|
|
589
875
|
async collectionExists() {
|
|
590
|
-
if (this.asMongo) return this.asMongo.collectionExists(this._table.tableName);
|
|
591
876
|
const cols = await this.db.listCollections({ name: this._table.tableName }).toArray();
|
|
592
877
|
return cols.length > 0;
|
|
593
878
|
}
|
|
594
879
|
async ensureCollectionExists() {
|
|
595
880
|
const exists = await this.collectionExists();
|
|
596
|
-
if (!exists)
|
|
881
|
+
if (!exists) {
|
|
882
|
+
this._log("createCollection", this._table.tableName);
|
|
883
|
+
const opts = { comment: "Created by Atscript Mongo Adapter" };
|
|
884
|
+
if (this._cappedOptions) {
|
|
885
|
+
opts.capped = true;
|
|
886
|
+
opts.size = this._cappedOptions.size;
|
|
887
|
+
if (this._cappedOptions.max !== null && this._cappedOptions.max !== undefined) opts.max = this._cappedOptions.max;
|
|
888
|
+
}
|
|
889
|
+
await this.db.createCollection(this._table.tableName, opts);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Wraps an async operation to catch MongoDB duplicate key errors
|
|
894
|
+
* (code 11000) and rethrow as structured `DbError`.
|
|
895
|
+
*/ async _wrapDuplicateKeyError(fn) {
|
|
896
|
+
try {
|
|
897
|
+
return await fn();
|
|
898
|
+
} catch (e) {
|
|
899
|
+
if (e instanceof mongodb.MongoServerError && e.code === 11e3) {
|
|
900
|
+
const field = e.keyPattern ? Object.keys(e.keyPattern)[0] ?? "" : "";
|
|
901
|
+
throw new __atscript_utils_db.DbError("CONFLICT", [{
|
|
902
|
+
path: field,
|
|
903
|
+
message: e.message
|
|
904
|
+
}]);
|
|
905
|
+
}
|
|
906
|
+
throw e;
|
|
907
|
+
}
|
|
597
908
|
}
|
|
598
909
|
async insertOne(data) {
|
|
599
910
|
if (this._incrementFields.size > 0) {
|
|
600
911
|
const fields = this._fieldsNeedingIncrement(data);
|
|
601
912
|
if (fields.length > 0) {
|
|
602
|
-
const
|
|
603
|
-
for (const physical of fields) data[physical] =
|
|
913
|
+
const nextValues = await this._allocateIncrementValues(fields, 1);
|
|
914
|
+
for (const physical of fields) data[physical] = nextValues.get(physical) ?? 1;
|
|
604
915
|
}
|
|
605
916
|
}
|
|
606
|
-
|
|
607
|
-
|
|
917
|
+
this._log("insertOne", data);
|
|
918
|
+
const result = await this._wrapDuplicateKeyError(() => this.collection.insertOne(data, this._getSessionOpts()));
|
|
919
|
+
const metaIdPhysical = this._getMetaIdPhysical();
|
|
920
|
+
return { insertedId: metaIdPhysical ? data[metaIdPhysical] ?? result.insertedId : result.insertedId };
|
|
608
921
|
}
|
|
609
922
|
async insertMany(data) {
|
|
610
923
|
if (this._incrementFields.size > 0) {
|
|
611
924
|
const allFields = new Set();
|
|
612
925
|
for (const item of data) for (const f of this._fieldsNeedingIncrement(item)) allFields.add(f);
|
|
613
|
-
if (allFields.size > 0)
|
|
614
|
-
const maxValues = await this._getMaxValues([...allFields]);
|
|
615
|
-
for (const item of data) for (const physical of allFields) if (item[physical] === undefined || item[physical] === null) {
|
|
616
|
-
const next = (maxValues.get(physical) ?? 0) + 1;
|
|
617
|
-
item[physical] = next;
|
|
618
|
-
maxValues.set(physical, next);
|
|
619
|
-
} else if (typeof item[physical] === "number") {
|
|
620
|
-
const current = maxValues.get(physical) ?? 0;
|
|
621
|
-
if (item[physical] > current) maxValues.set(physical, item[physical]);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
926
|
+
if (allFields.size > 0) await this._assignBatchIncrements(data, allFields);
|
|
624
927
|
}
|
|
625
|
-
|
|
928
|
+
this._log("insertMany", `${data.length} docs`);
|
|
929
|
+
const result = await this._wrapDuplicateKeyError(() => this.collection.insertMany(data, this._getSessionOpts()));
|
|
930
|
+
const metaIdPhysical = this._getMetaIdPhysical();
|
|
626
931
|
return {
|
|
627
932
|
insertedCount: result.insertedCount,
|
|
628
|
-
insertedIds: Object.values(result.insertedIds)
|
|
933
|
+
insertedIds: metaIdPhysical ? data.map((item, i) => item[metaIdPhysical] ?? result.insertedIds[i]) : Object.values(result.insertedIds)
|
|
629
934
|
};
|
|
630
935
|
}
|
|
631
936
|
async findOne(query) {
|
|
632
937
|
const filter = buildMongoFilter(query.filter);
|
|
633
938
|
const opts = this._buildFindOptions(query.controls);
|
|
634
|
-
|
|
939
|
+
this._log("findOne", filter, opts);
|
|
940
|
+
return this.collection.findOne(filter, {
|
|
941
|
+
...opts,
|
|
942
|
+
...this._getSessionOpts()
|
|
943
|
+
});
|
|
635
944
|
}
|
|
636
945
|
async findMany(query) {
|
|
637
946
|
const filter = buildMongoFilter(query.filter);
|
|
638
947
|
const opts = this._buildFindOptions(query.controls);
|
|
639
|
-
|
|
948
|
+
this._log("findMany", filter, opts);
|
|
949
|
+
return this.collection.find(filter, {
|
|
950
|
+
...opts,
|
|
951
|
+
...this._getSessionOpts()
|
|
952
|
+
}).toArray();
|
|
640
953
|
}
|
|
641
954
|
async count(query) {
|
|
642
955
|
const filter = buildMongoFilter(query.filter);
|
|
643
|
-
|
|
956
|
+
this._log("countDocuments", filter);
|
|
957
|
+
return this.collection.countDocuments(filter, this._getSessionOpts());
|
|
644
958
|
}
|
|
645
959
|
async updateOne(filter, data) {
|
|
646
960
|
const mongoFilter = buildMongoFilter(filter);
|
|
647
|
-
|
|
961
|
+
this._log("updateOne", mongoFilter, { $set: data });
|
|
962
|
+
const result = await this.collection.updateOne(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
648
963
|
return {
|
|
649
964
|
matchedCount: result.matchedCount,
|
|
650
965
|
modifiedCount: result.modifiedCount
|
|
@@ -652,7 +967,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
652
967
|
}
|
|
653
968
|
async replaceOne(filter, data) {
|
|
654
969
|
const mongoFilter = buildMongoFilter(filter);
|
|
655
|
-
|
|
970
|
+
this._log("replaceOne", mongoFilter, data);
|
|
971
|
+
const result = await this._wrapDuplicateKeyError(() => this.collection.replaceOne(mongoFilter, data, this._getSessionOpts()));
|
|
656
972
|
return {
|
|
657
973
|
matchedCount: result.matchedCount,
|
|
658
974
|
modifiedCount: result.modifiedCount
|
|
@@ -660,12 +976,14 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
660
976
|
}
|
|
661
977
|
async deleteOne(filter) {
|
|
662
978
|
const mongoFilter = buildMongoFilter(filter);
|
|
663
|
-
|
|
979
|
+
this._log("deleteOne", mongoFilter);
|
|
980
|
+
const result = await this.collection.deleteOne(mongoFilter, this._getSessionOpts());
|
|
664
981
|
return { deletedCount: result.deletedCount };
|
|
665
982
|
}
|
|
666
983
|
async updateMany(filter, data) {
|
|
667
984
|
const mongoFilter = buildMongoFilter(filter);
|
|
668
|
-
|
|
985
|
+
this._log("updateMany", mongoFilter, { $set: data });
|
|
986
|
+
const result = await this.collection.updateMany(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
669
987
|
return {
|
|
670
988
|
matchedCount: result.matchedCount,
|
|
671
989
|
modifiedCount: result.modifiedCount
|
|
@@ -673,7 +991,8 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
673
991
|
}
|
|
674
992
|
async replaceMany(filter, data) {
|
|
675
993
|
const mongoFilter = buildMongoFilter(filter);
|
|
676
|
-
|
|
994
|
+
this._log("replaceMany", mongoFilter, { $set: data });
|
|
995
|
+
const result = await this.collection.updateMany(mongoFilter, { $set: data }, this._getSessionOpts());
|
|
677
996
|
return {
|
|
678
997
|
matchedCount: result.matchedCount,
|
|
679
998
|
modifiedCount: result.modifiedCount
|
|
@@ -681,12 +1000,184 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
681
1000
|
}
|
|
682
1001
|
async deleteMany(filter) {
|
|
683
1002
|
const mongoFilter = buildMongoFilter(filter);
|
|
684
|
-
|
|
1003
|
+
this._log("deleteMany", mongoFilter);
|
|
1004
|
+
const result = await this.collection.deleteMany(mongoFilter, this._getSessionOpts());
|
|
685
1005
|
return { deletedCount: result.deletedCount };
|
|
686
1006
|
}
|
|
1007
|
+
async tableExists() {
|
|
1008
|
+
return this.collectionExists();
|
|
1009
|
+
}
|
|
1010
|
+
async detectTableOptionDrift() {
|
|
1011
|
+
if (!this._cappedOptions) return false;
|
|
1012
|
+
const cols = await this.db.listCollections({ name: this._table.tableName }, { nameOnly: false }).toArray();
|
|
1013
|
+
if (cols.length === 0) return false;
|
|
1014
|
+
const opts = cols[0].options;
|
|
1015
|
+
if (!opts?.capped) return true;
|
|
1016
|
+
if (opts.size !== this._cappedOptions.size) return true;
|
|
1017
|
+
if ((opts.max ?? undefined) !== (this._cappedOptions.max ?? undefined)) return true;
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
687
1020
|
async ensureTable() {
|
|
1021
|
+
if (this._table instanceof __atscript_utils_db.AtscriptDbView && !this._table.isExternal) return this._ensureView(this._table);
|
|
688
1022
|
return this.ensureCollectionExists();
|
|
689
1023
|
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Creates a MongoDB view from the AtscriptDbView's view plan.
|
|
1026
|
+
* Translates joins → $lookup/$unwind, filter → $match, columns → $project.
|
|
1027
|
+
*/ async _ensureView(view) {
|
|
1028
|
+
const exists = await this.collectionExists();
|
|
1029
|
+
if (exists) return;
|
|
1030
|
+
const plan = view.viewPlan;
|
|
1031
|
+
const columns = view.getViewColumnMappings();
|
|
1032
|
+
const pipeline = [];
|
|
1033
|
+
for (const join of plan.joins) {
|
|
1034
|
+
const { localField, foreignField } = this._resolveJoinFields(join.condition, plan.entryTable, join.targetTable);
|
|
1035
|
+
pipeline.push({ $lookup: {
|
|
1036
|
+
from: join.targetTable,
|
|
1037
|
+
localField,
|
|
1038
|
+
foreignField,
|
|
1039
|
+
as: `${JOINED_PREFIX}${join.targetTable}`
|
|
1040
|
+
} });
|
|
1041
|
+
pipeline.push({ $unwind: {
|
|
1042
|
+
path: `$__joined_${join.targetTable}`,
|
|
1043
|
+
preserveNullAndEmptyArrays: true
|
|
1044
|
+
} });
|
|
1045
|
+
}
|
|
1046
|
+
if (plan.filter) {
|
|
1047
|
+
const matchExpr = this._queryNodeToMatch(plan.filter, plan.entryTable);
|
|
1048
|
+
pipeline.push({ $match: matchExpr });
|
|
1049
|
+
}
|
|
1050
|
+
const project = { _id: 0 };
|
|
1051
|
+
for (const col of columns) if (col.sourceTable === plan.entryTable) project[col.viewColumn] = `$${col.sourceColumn}`;
|
|
1052
|
+
else project[col.viewColumn] = `$${JOINED_PREFIX}${col.sourceTable}.${col.sourceColumn}`;
|
|
1053
|
+
pipeline.push({ $project: project });
|
|
1054
|
+
this._log("createView", this._table.tableName, plan.entryTable, pipeline);
|
|
1055
|
+
await this.db.createCollection(this._table.tableName, {
|
|
1056
|
+
viewOn: plan.entryTable,
|
|
1057
|
+
pipeline
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Extracts localField/foreignField from a join condition like `User.id = Task.assigneeId`.
|
|
1062
|
+
* The condition is a comparison node with two field refs.
|
|
1063
|
+
*/ _resolveJoinFields(condition, entryTable, joinTable) {
|
|
1064
|
+
const comp = "$and" in condition ? condition.$and[0] : condition;
|
|
1065
|
+
const c = comp;
|
|
1066
|
+
const leftTable = c.left.type ? c.left.type()?.metadata?.get("db.table") || "" : entryTable;
|
|
1067
|
+
if (leftTable === joinTable) return {
|
|
1068
|
+
localField: c.right.field,
|
|
1069
|
+
foreignField: c.left.field
|
|
1070
|
+
};
|
|
1071
|
+
return {
|
|
1072
|
+
localField: c.left.field,
|
|
1073
|
+
foreignField: c.right.field
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Translates an AtscriptQueryNode to a MongoDB $match expression.
|
|
1078
|
+
* Field refs are resolved to dot-path references (joined fields use JOINED_PREFIX).
|
|
1079
|
+
*/ _queryNodeToMatch(node, entryTable) {
|
|
1080
|
+
if ("$and" in node) return { $and: node.$and.map((n) => this._queryNodeToMatch(n, entryTable)) };
|
|
1081
|
+
if ("$or" in node) return { $or: node.$or.map((n) => this._queryNodeToMatch(n, entryTable)) };
|
|
1082
|
+
if ("$not" in node) return { $not: this._queryNodeToMatch(node.$not, entryTable) };
|
|
1083
|
+
const comp = node;
|
|
1084
|
+
const fieldPath = this._resolveViewFieldPath(comp.left, entryTable);
|
|
1085
|
+
if (comp.right && typeof comp.right === "object" && "field" in comp.right) {
|
|
1086
|
+
const rightPath = this._resolveViewFieldPath(comp.right, entryTable);
|
|
1087
|
+
return { $expr: { [comp.op]: [`$${fieldPath}`, `$${rightPath}`] } };
|
|
1088
|
+
}
|
|
1089
|
+
if (comp.op === "$eq") return { [fieldPath]: comp.right };
|
|
1090
|
+
if (comp.op === "$ne") return { [fieldPath]: { $ne: comp.right } };
|
|
1091
|
+
return { [fieldPath]: { [comp.op]: comp.right } };
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Resolves a field ref to a MongoDB dot path for view pipeline expressions.
|
|
1095
|
+
*/ _resolveViewFieldPath(ref, entryTable) {
|
|
1096
|
+
if (!ref.type) return ref.field;
|
|
1097
|
+
const table = ref.type()?.metadata?.get("db.table") || "";
|
|
1098
|
+
if (table === entryTable) return ref.field;
|
|
1099
|
+
return `${JOINED_PREFIX}${table}.${ref.field}`;
|
|
1100
|
+
}
|
|
1101
|
+
async dropTable() {
|
|
1102
|
+
this._log("drop", this._table.tableName);
|
|
1103
|
+
await this.collection.drop();
|
|
1104
|
+
this._collection = undefined;
|
|
1105
|
+
}
|
|
1106
|
+
async dropViewByName(viewName) {
|
|
1107
|
+
this._log("dropView", viewName);
|
|
1108
|
+
try {
|
|
1109
|
+
await this.db.collection(viewName).drop();
|
|
1110
|
+
} catch {}
|
|
1111
|
+
}
|
|
1112
|
+
async dropTableByName(tableName) {
|
|
1113
|
+
this._log("dropByName", tableName);
|
|
1114
|
+
try {
|
|
1115
|
+
await this.db.collection(tableName).drop();
|
|
1116
|
+
} catch {}
|
|
1117
|
+
}
|
|
1118
|
+
async recreateTable() {
|
|
1119
|
+
const tableName = this._table.tableName;
|
|
1120
|
+
this._log("recreateTable", tableName);
|
|
1121
|
+
const tempName = `${tableName}__tmp_${Date.now()}`;
|
|
1122
|
+
const source = this.db.collection(tableName);
|
|
1123
|
+
const count = await source.countDocuments();
|
|
1124
|
+
if (count > 0) await source.aggregate([{ $out: tempName }]).toArray();
|
|
1125
|
+
await this.collection.drop();
|
|
1126
|
+
this._collection = undefined;
|
|
1127
|
+
await this.ensureCollectionExists();
|
|
1128
|
+
if (count > 0) {
|
|
1129
|
+
const temp = this.db.collection(tempName);
|
|
1130
|
+
await temp.aggregate([{ $merge: { into: tableName } }]).toArray();
|
|
1131
|
+
await temp.drop();
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
async syncColumns(diff) {
|
|
1135
|
+
const renamed = [];
|
|
1136
|
+
const added = [];
|
|
1137
|
+
const update = {};
|
|
1138
|
+
if (diff.renamed.length > 0) {
|
|
1139
|
+
const renameSpec = {};
|
|
1140
|
+
for (const r of diff.renamed) {
|
|
1141
|
+
renameSpec[r.oldName] = r.field.physicalName;
|
|
1142
|
+
renamed.push(r.field.physicalName);
|
|
1143
|
+
}
|
|
1144
|
+
update.$rename = renameSpec;
|
|
1145
|
+
}
|
|
1146
|
+
if (diff.added.length > 0) {
|
|
1147
|
+
const setSpec = {};
|
|
1148
|
+
for (const field of diff.added) {
|
|
1149
|
+
const defaultVal = this._resolveSyncDefault(field);
|
|
1150
|
+
if (defaultVal !== undefined) setSpec[field.physicalName] = defaultVal;
|
|
1151
|
+
added.push(field.physicalName);
|
|
1152
|
+
}
|
|
1153
|
+
if (Object.keys(setSpec).length > 0) update.$set = setSpec;
|
|
1154
|
+
}
|
|
1155
|
+
if (Object.keys(update).length > 0) await this.collection.updateMany({}, update, this._getSessionOpts());
|
|
1156
|
+
return {
|
|
1157
|
+
added,
|
|
1158
|
+
renamed
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
async dropColumns(columns) {
|
|
1162
|
+
if (columns.length === 0) return;
|
|
1163
|
+
const unsetSpec = {};
|
|
1164
|
+
for (const col of columns) unsetSpec[col] = "";
|
|
1165
|
+
await this.collection.updateMany({}, { $unset: unsetSpec }, this._getSessionOpts());
|
|
1166
|
+
}
|
|
1167
|
+
async renameTable(oldName) {
|
|
1168
|
+
const newName = this.resolveTableName(false);
|
|
1169
|
+
this._log("renameTable", oldName, "→", newName);
|
|
1170
|
+
await this.db.renameCollection(oldName, newName);
|
|
1171
|
+
this._collection = undefined;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Resolves a field's default value for bulk $set during column sync.
|
|
1175
|
+
* Returns `undefined` if no concrete default can be determined.
|
|
1176
|
+
*/ _resolveSyncDefault(field) {
|
|
1177
|
+
if (!field.defaultValue) return field.optional ? null : undefined;
|
|
1178
|
+
if (field.defaultValue.kind === "value") return field.defaultValue.value;
|
|
1179
|
+
return undefined;
|
|
1180
|
+
}
|
|
690
1181
|
async syncIndexes() {
|
|
691
1182
|
await this.ensureCollectionExists();
|
|
692
1183
|
const allIndexes = new Map();
|
|
@@ -730,21 +1221,29 @@ else pipeline.push({ $limit: 1e3 });
|
|
|
730
1221
|
case "unique":
|
|
731
1222
|
case "text": {
|
|
732
1223
|
if ((local.type === "text" || objMatch(local.fields, remote.key)) && objMatch(local.weights || {}, remote.weights || {})) indexesToCreate.delete(remote.name);
|
|
733
|
-
else
|
|
1224
|
+
else {
|
|
1225
|
+
this._log("dropIndex", remote.name);
|
|
1226
|
+
await this.collection.dropIndex(remote.name);
|
|
1227
|
+
}
|
|
734
1228
|
break;
|
|
735
1229
|
}
|
|
736
1230
|
default:
|
|
737
1231
|
}
|
|
738
|
-
} else
|
|
1232
|
+
} else {
|
|
1233
|
+
this._log("dropIndex", remote.name);
|
|
1234
|
+
await this.collection.dropIndex(remote.name);
|
|
1235
|
+
}
|
|
739
1236
|
}
|
|
740
1237
|
for (const [key, value] of allIndexes.entries()) switch (value.type) {
|
|
741
1238
|
case "plain": {
|
|
742
1239
|
if (!indexesToCreate.has(key)) continue;
|
|
1240
|
+
this._log("createIndex", key, value.fields);
|
|
743
1241
|
await this.collection.createIndex(value.fields, { name: key });
|
|
744
1242
|
break;
|
|
745
1243
|
}
|
|
746
1244
|
case "unique": {
|
|
747
1245
|
if (!indexesToCreate.has(key)) continue;
|
|
1246
|
+
this._log("createIndex (unique)", key, value.fields);
|
|
748
1247
|
await this.collection.createIndex(value.fields, {
|
|
749
1248
|
name: key,
|
|
750
1249
|
unique: true
|
|
@@ -753,6 +1252,7 @@ else await this.collection.dropIndex(remote.name);
|
|
|
753
1252
|
}
|
|
754
1253
|
case "text": {
|
|
755
1254
|
if (!indexesToCreate.has(key)) continue;
|
|
1255
|
+
this._log("createIndex (text)", key, value.fields);
|
|
756
1256
|
await this.collection.createIndex(value.fields, {
|
|
757
1257
|
weights: value.weights,
|
|
758
1258
|
name: key
|
|
@@ -784,43 +1284,110 @@ else toUpdate.add(remote.name);
|
|
|
784
1284
|
}
|
|
785
1285
|
default:
|
|
786
1286
|
}
|
|
787
|
-
} else if (remote.status !== "DELETING")
|
|
1287
|
+
} else if (remote.status !== "DELETING") {
|
|
1288
|
+
this._log("dropSearchIndex", remote.name);
|
|
1289
|
+
await this.collection.dropSearchIndex(remote.name);
|
|
1290
|
+
}
|
|
788
1291
|
}
|
|
789
1292
|
for (const [key, value] of indexesToCreate.entries()) switch (value.type) {
|
|
790
1293
|
case "dynamic_text":
|
|
791
1294
|
case "search_text":
|
|
792
1295
|
case "vector": {
|
|
793
|
-
if (toUpdate.has(key))
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1296
|
+
if (toUpdate.has(key)) {
|
|
1297
|
+
this._log("updateSearchIndex", key, value.definition);
|
|
1298
|
+
await this.collection.updateSearchIndex(key, value.definition);
|
|
1299
|
+
} else {
|
|
1300
|
+
this._log("createSearchIndex", key, value.type);
|
|
1301
|
+
await this.collection.createSearchIndex({
|
|
1302
|
+
name: key,
|
|
1303
|
+
type: value.type === "vector" ? "vectorSearch" : "search",
|
|
1304
|
+
definition: value.definition
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
799
1307
|
break;
|
|
800
1308
|
}
|
|
801
1309
|
default:
|
|
802
1310
|
}
|
|
803
1311
|
} catch {}
|
|
804
1312
|
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Returns the physical column name of the single @meta.id field (if any).
|
|
1315
|
+
* Used to return the user's logical ID instead of MongoDB's _id on insert.
|
|
1316
|
+
*/ _getMetaIdPhysical() {
|
|
1317
|
+
if (this._metaIdPhysical === undefined) {
|
|
1318
|
+
const fields = this._table.originalMetaIdFields;
|
|
1319
|
+
if (fields.length === 1) {
|
|
1320
|
+
const field = fields[0];
|
|
1321
|
+
this._metaIdPhysical = this._table.columnMap.get(field) ?? field;
|
|
1322
|
+
} else this._metaIdPhysical = null;
|
|
1323
|
+
}
|
|
1324
|
+
return this._metaIdPhysical;
|
|
1325
|
+
}
|
|
1326
|
+
/** Returns the counters collection used for atomic auto-increment. */ get _countersCollection() {
|
|
1327
|
+
return this.db.collection("__atscript_counters");
|
|
1328
|
+
}
|
|
805
1329
|
/** Returns physical field names of increment fields that are undefined in the data. */ _fieldsNeedingIncrement(data) {
|
|
806
1330
|
const result = [];
|
|
807
1331
|
for (const physical of this._incrementFields) if (data[physical] === undefined || data[physical] === null) result.push(physical);
|
|
808
1332
|
return result;
|
|
809
1333
|
}
|
|
810
|
-
/**
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1334
|
+
/**
|
|
1335
|
+
* Atomically allocates `count` sequential values for each increment field
|
|
1336
|
+
* using a counter collection. Returns a map of field → first allocated value.
|
|
1337
|
+
*/ async _allocateIncrementValues(physicalFields, count) {
|
|
1338
|
+
const counters = this._countersCollection;
|
|
1339
|
+
const collectionName = this._table.tableName;
|
|
1340
|
+
const result = new Map();
|
|
1341
|
+
for (const field of physicalFields) {
|
|
1342
|
+
const counterId = `${collectionName}.${field}`;
|
|
1343
|
+
const doc = await counters.findOneAndUpdate({ _id: counterId }, { $inc: { seq: count } }, {
|
|
1344
|
+
upsert: true,
|
|
1345
|
+
returnDocument: "after",
|
|
1346
|
+
...this._getSessionOpts()
|
|
1347
|
+
});
|
|
1348
|
+
const seq = doc?.seq ?? count;
|
|
1349
|
+
if (seq === count) {
|
|
1350
|
+
const currentMax = await this._getCurrentFieldMax(field);
|
|
1351
|
+
if (currentMax >= seq) {
|
|
1352
|
+
const adjusted = currentMax + count;
|
|
1353
|
+
await counters.updateOne({ _id: counterId }, { $max: { seq: adjusted } }, this._getSessionOpts());
|
|
1354
|
+
result.set(field, currentMax + 1);
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
821
1357
|
}
|
|
1358
|
+
result.set(field, seq - count + 1);
|
|
1359
|
+
}
|
|
1360
|
+
return result;
|
|
1361
|
+
}
|
|
1362
|
+
/** Reads current max value for a single field via $group aggregation. */ async _getCurrentFieldMax(field) {
|
|
1363
|
+
const alias = `max__${field.replace(/\./g, "__")}`;
|
|
1364
|
+
const agg = await this.collection.aggregate([{ $group: {
|
|
1365
|
+
_id: null,
|
|
1366
|
+
[alias]: { $max: `$${field}` }
|
|
1367
|
+
} }], this._getSessionOpts()).toArray();
|
|
1368
|
+
if (agg.length > 0) {
|
|
1369
|
+
const val = agg[0][alias];
|
|
1370
|
+
if (typeof val === "number") return val;
|
|
1371
|
+
}
|
|
1372
|
+
return 0;
|
|
1373
|
+
}
|
|
1374
|
+
/** Allocates increment values for a batch of items, assigning in order. */ async _assignBatchIncrements(data, allFields) {
|
|
1375
|
+
const fieldCounts = new Map();
|
|
1376
|
+
for (const physical of allFields) {
|
|
1377
|
+
let count = 0;
|
|
1378
|
+
for (const item of data) if (item[physical] === undefined || item[physical] === null) count++;
|
|
1379
|
+
if (count > 0) fieldCounts.set(physical, count);
|
|
1380
|
+
}
|
|
1381
|
+
const fieldCounters = new Map();
|
|
1382
|
+
for (const [physical, count] of fieldCounts) {
|
|
1383
|
+
const allocated = await this._allocateIncrementValues([physical], count);
|
|
1384
|
+
fieldCounters.set(physical, allocated.get(physical) ?? 1);
|
|
1385
|
+
}
|
|
1386
|
+
for (const item of data) for (const physical of allFields) if (item[physical] === undefined || item[physical] === null) {
|
|
1387
|
+
const next = fieldCounters.get(physical) ?? 1;
|
|
1388
|
+
item[physical] = next;
|
|
1389
|
+
fieldCounters.set(physical, next + 1);
|
|
822
1390
|
}
|
|
823
|
-
return maxMap;
|
|
824
1391
|
}
|
|
825
1392
|
_buildFindOptions(controls) {
|
|
826
1393
|
const opts = {};
|
|
@@ -872,10 +1439,11 @@ else {
|
|
|
872
1439
|
if (analyzer) index.definition.mappings.fields[fieldName].analyzer = analyzer;
|
|
873
1440
|
}
|
|
874
1441
|
}
|
|
875
|
-
constructor(db,
|
|
876
|
-
super(), _define_property
|
|
1442
|
+
constructor(db, client) {
|
|
1443
|
+
super(), _define_property(this, "db", void 0), _define_property(this, "client", void 0), _define_property(this, "_collection", void 0), _define_property(this, "_mongoIndexes", void 0), _define_property(this, "_vectorFilters", void 0), _define_property(this, "_searchIndexesMap", void 0), _define_property(this, "_incrementFields", void 0), _define_property(this, "_cappedOptions", void 0), _define_property(this, "_hasExplicitId", void 0), _define_property(this, "_txDisabled", void 0), _define_property(this, "_metaIdPhysical", void 0), this.db = db, this.client = client, this._mongoIndexes = new Map(), this._vectorFilters = new Map(), this._incrementFields = new Set(), this._hasExplicitId = false, this._txDisabled = false;
|
|
877
1444
|
}
|
|
878
1445
|
};
|
|
1446
|
+
_define_property(MongoAdapter, "_noSession", Object.freeze({}));
|
|
879
1447
|
function objMatch(o1, o2) {
|
|
880
1448
|
const keys1 = Object.keys(o1);
|
|
881
1449
|
const keys2 = Object.keys(o2);
|
|
@@ -905,55 +1473,16 @@ function vectorFieldsMatch(left, right) {
|
|
|
905
1473
|
}
|
|
906
1474
|
|
|
907
1475
|
//#endregion
|
|
908
|
-
//#region packages/mongo/src/lib/
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
info: () => {},
|
|
914
|
-
debug: () => {}
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
//#endregion
|
|
918
|
-
//#region packages/mongo/src/lib/as-mongo.ts
|
|
919
|
-
function _define_property(obj, key, value) {
|
|
920
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
921
|
-
value,
|
|
922
|
-
enumerable: true,
|
|
923
|
-
configurable: true,
|
|
924
|
-
writable: true
|
|
925
|
-
});
|
|
926
|
-
else obj[key] = value;
|
|
927
|
-
return obj;
|
|
1476
|
+
//#region packages/mongo/src/lib/index.ts
|
|
1477
|
+
function createAdapter(connection, _options) {
|
|
1478
|
+
const client = new mongodb.MongoClient(connection);
|
|
1479
|
+
const db = client.db();
|
|
1480
|
+
return new __atscript_utils_db.DbSpace(() => new MongoAdapter(db, client));
|
|
928
1481
|
}
|
|
929
|
-
var AsMongo = class extends __atscript_utils_db.DbSpace {
|
|
930
|
-
get db() {
|
|
931
|
-
return this.client.db();
|
|
932
|
-
}
|
|
933
|
-
getCollectionsList() {
|
|
934
|
-
if (!this.collectionsList) this.collectionsList = this.db.listCollections().toArray().then((c) => new Set(c.map((c$1) => c$1.name)));
|
|
935
|
-
return this.collectionsList;
|
|
936
|
-
}
|
|
937
|
-
async collectionExists(name) {
|
|
938
|
-
const list = await this.getCollectionsList();
|
|
939
|
-
return list.has(name);
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Returns the MongoAdapter for the given type.
|
|
943
|
-
* Convenience accessor for Mongo-specific adapter operations.
|
|
944
|
-
*/ getAdapter(type) {
|
|
945
|
-
return super.getAdapter(type);
|
|
946
|
-
}
|
|
947
|
-
constructor(client, logger = NoopLogger) {
|
|
948
|
-
const resolvedClient = typeof client === "string" ? new mongodb.MongoClient(client) : client;
|
|
949
|
-
super(() => new MongoAdapter(this.db, this), logger), _define_property(this, "client", void 0), _define_property(this, "collectionsList", void 0);
|
|
950
|
-
this.client = resolvedClient;
|
|
951
|
-
}
|
|
952
|
-
};
|
|
953
1482
|
|
|
954
1483
|
//#endregion
|
|
955
|
-
exports.AsMongo = AsMongo
|
|
956
1484
|
exports.CollectionPatcher = CollectionPatcher
|
|
957
1485
|
exports.MongoAdapter = MongoAdapter
|
|
958
1486
|
exports.buildMongoFilter = buildMongoFilter
|
|
1487
|
+
exports.createAdapter = createAdapter
|
|
959
1488
|
exports.validateMongoIdPlugin = validateMongoIdPlugin
|