@atscript/mongo 0.1.35 → 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/dist/index.cjs CHANGED
@@ -24,26 +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/logger.ts
30
- const NoopLogger = {
31
- error: () => {},
32
- warn: () => {},
33
- log: () => {},
34
- info: () => {},
35
- debug: () => {}
36
- };
37
-
38
- //#endregion
39
- //#region packages/mongo/src/lib/validate-plugins.ts
40
- const validateMongoIdPlugin = (ctx, def, value) => {
41
- if (ctx.path === "_id" && def.type.tags.has("objectId")) return ctx.validateAnnotatedType(def, value instanceof mongodb.ObjectId ? value.toString() : value);
42
- };
43
-
44
- //#endregion
45
28
  //#region packages/mongo/src/lib/collection-patcher.ts
46
- function _define_property$2(obj, key, value) {
29
+ function _define_property$1(obj, key, value) {
47
30
  if (key in obj) Object.defineProperty(obj, key, {
48
31
  value,
49
32
  enumerable: true,
@@ -53,50 +36,7 @@ function _define_property$2(obj, key, value) {
53
36
  else obj[key] = value;
54
37
  return obj;
55
38
  }
56
- var CollectionPatcher = class CollectionPatcher {
57
- /**
58
- * Build a runtime *Validator* that understands the extended patch payload.
59
- *
60
- * * Adds per‑array *patch* wrappers (the `$replace`, `$insert`, … fields).
61
- * * Honors `db.patch.strategy === "merge"` metadata.
62
- *
63
- * @param collection Target collection wrapper
64
- * @returns Atscript Validator
65
- */ static prepareValidator(context) {
66
- return context.createValidator({
67
- plugins: [validateMongoIdPlugin],
68
- replace: (def, path) => {
69
- if (path === "" && def.type.kind === "object") {
70
- const obj = (0, __atscript_typescript_utils.defineAnnotatedType)("object").copyMetadata(def.metadata);
71
- 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);
72
- return obj.$type;
73
- }
74
- if (def.type.kind === "array" && context.flatMap.get(path)?.metadata.get("db.mongo.__topLevelArray") && !def.metadata.has("db.mongo.__patchArrayValue")) {
75
- const defArray = def;
76
- const mergeStrategy = defArray.metadata.get("db.patch.strategy") === "merge";
77
- function getPatchType() {
78
- const isPrimitive = (0, __atscript_typescript_utils.isAnnotatedTypeOfPrimitive)(defArray.type.of);
79
- if (isPrimitive) return (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(def).copyMetadata(def.metadata).annotate("db.mongo.__patchArrayValue").optional().$type;
80
- if (defArray.type.of.type.kind === "object") {
81
- const objType = defArray.type.of.type;
82
- const t = (0, __atscript_typescript_utils.defineAnnotatedType)("object").copyMetadata(defArray.type.of.metadata);
83
- const keyProps = CollectionPatcher.getKeyProps(defArray);
84
- 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);
85
- else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(val).copyMetadata(def.metadata).optional().$type);
86
- else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(val).copyMetadata(def.metadata).optional(!!val.optional).$type);
87
- return (0, __atscript_typescript_utils.defineAnnotatedType)("array").of(t.$type).copyMetadata(def.metadata).annotate("db.mongo.__patchArrayValue").optional().$type;
88
- }
89
- return undefined;
90
- }
91
- const fullType = (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(def).copyMetadata(def.metadata).annotate("db.mongo.__patchArrayValue").optional().$type;
92
- const patchType = getPatchType();
93
- 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;
94
- }
95
- return def;
96
- },
97
- partial: (def, path) => path !== "" && def.metadata.get("db.patch.strategy") === "merge"
98
- });
99
- }
39
+ var CollectionPatcher = class {
100
40
  /**
101
41
  * Entry point – walk the payload, build `filter`, `update` and `options`.
102
42
  *
@@ -143,8 +83,8 @@ else t.prop(key, (0, __atscript_typescript_utils.defineAnnotatedType)().refTo(va
143
83
  for (const [_key, value] of Object.entries(payload)) {
144
84
  const key = evalKey(_key);
145
85
  const flatType = this.collection.flatMap.get(key);
146
- const topLevelArray = flatType?.metadata?.get("db.mongo.__topLevelArray");
147
- 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);
148
88
  else if (typeof value === "object" && flatType?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
149
89
  else if (key !== "_id") this._set(key, value);
150
90
  }
@@ -208,22 +148,37 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
208
148
  * `$upsert`
209
149
  * - keyed → remove existing matching by key(s) then append candidate
210
150
  * - unique → $setUnion (deep equality)
211
- */ _upsert(key, input, keys, _flatType) {
151
+ */ _upsert(key, input, keys, flatType) {
212
152
  if (!input?.length) return;
213
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
+ }
214
172
  this._set(key, { $reduce: {
215
173
  input,
216
174
  initialValue: { $ifNull: [`$${key}`, []] },
217
175
  in: { $let: {
218
- vars: {
219
- acc: "$$value",
220
- cand: "$$this"
221
- },
176
+ vars,
222
177
  in: { $concatArrays: [{ $filter: {
223
178
  input: "$$acc",
224
179
  as: "el",
225
180
  cond: { $not: this._keysEqual(keys, "$$el", "$$cand") }
226
- } }, ["$$cand"]] }
181
+ } }, [appendExpr]] }
227
182
  } }
228
183
  } });
229
184
  return;
@@ -274,15 +229,15 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
274
229
  else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
275
230
  }
276
231
  constructor(collection, payload) {
277
- _define_property$2(this, "collection", void 0);
278
- _define_property$2(this, "payload", void 0);
232
+ _define_property$1(this, "collection", void 0);
233
+ _define_property$1(this, "payload", void 0);
279
234
  /**
280
235
  * Internal accumulator: filter passed to `updateOne()`.
281
236
  * Filled only with the `_id` field right now.
282
- */ _define_property$2(this, "filterObj", void 0);
283
- /** MongoDB *update* document being built. */ _define_property$2(this, "updatePipeline", void 0);
284
- /** Current `$set` stage being populated. */ _define_property$2(this, "currentSetStage", void 0);
285
- /** Additional *options* (mainly `arrayFilters`). */ _define_property$2(this, "optionsObj", void 0);
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);
286
241
  this.collection = collection;
287
242
  this.payload = payload;
288
243
  this.filterObj = {};
@@ -291,7 +246,7 @@ else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
291
246
  this.optionsObj = {};
292
247
  }
293
248
  };
294
- _define_property$2(CollectionPatcher, "getKeyProps", __atscript_utils_db.getKeyProps);
249
+ _define_property$1(CollectionPatcher, "getKeyProps", __atscript_utils_db.getKeyProps);
295
250
 
296
251
  //#endregion
297
252
  //#region packages/mongo/src/lib/mongo-filter.ts
@@ -320,9 +275,21 @@ function buildMongoFilter(filter) {
320
275
  return (0, __atscript_utils_db.walkFilter)(filter, mongoVisitor) ?? EMPTY;
321
276
  }
322
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
+
323
290
  //#endregion
324
291
  //#region packages/mongo/src/lib/mongo-adapter.ts
325
- function _define_property$1(obj, key, value) {
292
+ function _define_property(obj, key, value) {
326
293
  if (key in obj) Object.defineProperty(obj, key, {
327
294
  value,
328
295
  enumerable: true,
@@ -334,13 +301,14 @@ else obj[key] = value;
334
301
  }
335
302
  const INDEX_PREFIX = "atscript__";
336
303
  const DEFAULT_INDEX_NAME = "DEFAULT";
304
+ const JOINED_PREFIX = "__joined_";
337
305
  function mongoIndexKey(type, name) {
338
306
  const cleanName = name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 127 - INDEX_PREFIX.length - type.length - 2);
339
307
  return `${INDEX_PREFIX}${type}__${cleanName}`;
340
308
  }
341
309
  var MongoAdapter = class MongoAdapter extends __atscript_utils_db.BaseDbAdapter {
342
310
  get _client() {
343
- return this.asMongo?.client;
311
+ return this.client;
344
312
  }
345
313
  async _beginTransaction() {
346
314
  if (this._txDisabled || !this._client) return undefined;
@@ -427,30 +395,260 @@ var MongoAdapter = class MongoAdapter extends __atscript_utils_db.BaseDbAdapter
427
395
  getValidatorPlugins() {
428
396
  return [validateMongoIdPlugin];
429
397
  }
430
- getTopLevelArrayTag() {
431
- return "db.mongo.__topLevelArray";
432
- }
433
398
  getAdapterTableName(type) {
434
399
  return undefined;
435
400
  }
436
- buildInsertValidator(table) {
437
- return table.createValidator({
438
- plugins: this.getValidatorPlugins(),
439
- replace: (type, path) => {
440
- if (path === "_id" && type.type.tags?.has("objectId")) return {
441
- ...type,
442
- optional: true
443
- };
444
- if (table.defaults.has(path)) return {
445
- ...type,
446
- optional: true
447
- };
448
- return type;
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);
449
439
  }
450
- });
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
+ }
471
+ }
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
+ };
451
562
  }
452
- buildPatchValidator(table) {
453
- return CollectionPatcher.prepareValidator(this.getPatcherContext());
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 || "";
454
652
  }
455
653
  /** Returns the context object used by CollectionPatcher. */ getPatcherContext() {
456
654
  return {
@@ -493,8 +691,8 @@ var MongoAdapter = class MongoAdapter extends __atscript_utils_db.BaseDbAdapter
493
691
  });
494
692
  }
495
693
  onFieldScanned(field, type, metadata) {
694
+ if (field === "_id") this._hasExplicitId = true;
496
695
  if (field !== "_id" && metadata.has("meta.id")) {
497
- this._table.removePrimaryKey(field);
498
696
  this._addMongoIndexField("unique", "__pk", field);
499
697
  this._table.addUniqueField(field);
500
698
  }
@@ -519,7 +717,28 @@ var MongoAdapter = class MongoAdapter extends __atscript_utils_db.BaseDbAdapter
519
717
  for (const index of metadata.get("db.mongo.search.filter") || []) this._vectorFilters.set(mongoIndexKey("vector", index.indexName), field);
520
718
  }
521
719
  onAfterFlatten() {
522
- this._table.addPrimaryKey("_id");
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
+ }
523
742
  for (const [key, value] of this._vectorFilters.entries()) {
524
743
  const index = this._mongoIndexes.get(key);
525
744
  if (index && index.type === "vector") index.definition.fields?.push({
@@ -577,7 +796,7 @@ var MongoAdapter = class MongoAdapter extends __atscript_utils_db.BaseDbAdapter
577
796
  /**
578
797
  * Builds a MongoDB `$search` pipeline stage.
579
798
  * Override `buildVectorSearchStage` in subclasses to provide embeddings.
580
- */ buildSearchStage(text, indexName) {
799
+ */ async buildSearchStage(text, indexName) {
581
800
  const index = this.getMongoSearchIndex(indexName);
582
801
  if (!index) return undefined;
583
802
  if (index.type === "vector") return this.buildVectorSearchStage(text, index);
@@ -592,11 +811,11 @@ var MongoAdapter = class MongoAdapter extends __atscript_utils_db.BaseDbAdapter
592
811
  /**
593
812
  * Builds a vector search stage. Override in subclasses to generate embeddings.
594
813
  * Returns `undefined` by default (vector search requires custom implementation).
595
- */ buildVectorSearchStage(text, index) {
814
+ */ async buildVectorSearchStage(text, index) {
596
815
  return undefined;
597
816
  }
598
817
  async search(text, query, indexName) {
599
- const searchStage = this.buildSearchStage(text, indexName);
818
+ const searchStage = await this.buildSearchStage(text, indexName);
600
819
  if (!searchStage) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
601
820
  const filter = buildMongoFilter(query.filter);
602
821
  const controls = query.controls || {};
@@ -610,7 +829,7 @@ else pipeline.push({ $limit: 1e3 });
610
829
  return this.collection.aggregate(pipeline, this._getSessionOpts()).toArray();
611
830
  }
612
831
  async searchWithCount(text, query, indexName) {
613
- const searchStage = this.buildSearchStage(text, indexName);
832
+ const searchStage = await this.buildSearchStage(text, indexName);
614
833
  if (!searchStage) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
615
834
  const filter = buildMongoFilter(query.filter);
616
835
  const controls = query.controls || {};
@@ -654,7 +873,6 @@ else pipeline.push({ $limit: 1e3 });
654
873
  };
655
874
  }
656
875
  async collectionExists() {
657
- if (this.asMongo) return this.asMongo.collectionExists(this._table.tableName);
658
876
  const cols = await this.db.listCollections({ name: this._table.tableName }).toArray();
659
877
  return cols.length > 0;
660
878
  }
@@ -671,39 +889,48 @@ else pipeline.push({ $limit: 1e3 });
671
889
  await this.db.createCollection(this._table.tableName, opts);
672
890
  }
673
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
+ }
908
+ }
674
909
  async insertOne(data) {
675
910
  if (this._incrementFields.size > 0) {
676
911
  const fields = this._fieldsNeedingIncrement(data);
677
912
  if (fields.length > 0) {
678
- const maxValues = await this._getMaxValues(fields);
679
- for (const physical of fields) data[physical] = (maxValues.get(physical) ?? 0) + 1;
913
+ const nextValues = await this._allocateIncrementValues(fields, 1);
914
+ for (const physical of fields) data[physical] = nextValues.get(physical) ?? 1;
680
915
  }
681
916
  }
682
917
  this._log("insertOne", data);
683
- const result = await this.collection.insertOne(data, this._getSessionOpts());
684
- return { insertedId: result.insertedId };
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 };
685
921
  }
686
922
  async insertMany(data) {
687
923
  if (this._incrementFields.size > 0) {
688
924
  const allFields = new Set();
689
925
  for (const item of data) for (const f of this._fieldsNeedingIncrement(item)) allFields.add(f);
690
- if (allFields.size > 0) {
691
- const maxValues = await this._getMaxValues([...allFields]);
692
- for (const item of data) for (const physical of allFields) if (item[physical] === undefined || item[physical] === null) {
693
- const next = (maxValues.get(physical) ?? 0) + 1;
694
- item[physical] = next;
695
- maxValues.set(physical, next);
696
- } else if (typeof item[physical] === "number") {
697
- const current = maxValues.get(physical) ?? 0;
698
- if (item[physical] > current) maxValues.set(physical, item[physical]);
699
- }
700
- }
926
+ if (allFields.size > 0) await this._assignBatchIncrements(data, allFields);
701
927
  }
702
928
  this._log("insertMany", `${data.length} docs`);
703
- const result = await this.collection.insertMany(data, this._getSessionOpts());
929
+ const result = await this._wrapDuplicateKeyError(() => this.collection.insertMany(data, this._getSessionOpts()));
930
+ const metaIdPhysical = this._getMetaIdPhysical();
704
931
  return {
705
932
  insertedCount: result.insertedCount,
706
- insertedIds: Object.values(result.insertedIds)
933
+ insertedIds: metaIdPhysical ? data.map((item, i) => item[metaIdPhysical] ?? result.insertedIds[i]) : Object.values(result.insertedIds)
707
934
  };
708
935
  }
709
936
  async findOne(query) {
@@ -741,7 +968,7 @@ else pipeline.push({ $limit: 1e3 });
741
968
  async replaceOne(filter, data) {
742
969
  const mongoFilter = buildMongoFilter(filter);
743
970
  this._log("replaceOne", mongoFilter, data);
744
- const result = await this.collection.replaceOne(mongoFilter, data, this._getSessionOpts());
971
+ const result = await this._wrapDuplicateKeyError(() => this.collection.replaceOne(mongoFilter, data, this._getSessionOpts()));
745
972
  return {
746
973
  matchedCount: result.matchedCount,
747
974
  modifiedCount: result.modifiedCount
@@ -777,14 +1004,180 @@ else pipeline.push({ $limit: 1e3 });
777
1004
  const result = await this.collection.deleteMany(mongoFilter, this._getSessionOpts());
778
1005
  return { deletedCount: result.deletedCount };
779
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
+ }
780
1020
  async ensureTable() {
1021
+ if (this._table instanceof __atscript_utils_db.AtscriptDbView && !this._table.isExternal) return this._ensureView(this._table);
781
1022
  return this.ensureCollectionExists();
782
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
+ }
783
1101
  async dropTable() {
784
1102
  this._log("drop", this._table.tableName);
785
1103
  await this.collection.drop();
786
1104
  this._collection = undefined;
787
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
+ }
788
1181
  async syncIndexes() {
789
1182
  await this.ensureCollectionExists();
790
1183
  const allIndexes = new Map();
@@ -917,25 +1310,84 @@ else toUpdate.add(remote.name);
917
1310
  }
918
1311
  } catch {}
919
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
+ }
920
1329
  /** Returns physical field names of increment fields that are undefined in the data. */ _fieldsNeedingIncrement(data) {
921
1330
  const result = [];
922
1331
  for (const physical of this._incrementFields) if (data[physical] === undefined || data[physical] === null) result.push(physical);
923
1332
  return result;
924
1333
  }
925
- /** Reads current max value for each field via $group aggregation. */ async _getMaxValues(physicalFields) {
926
- const aliases = physicalFields.map((f) => [`max__${f.replace(/\./g, "__")}`, f]);
927
- const group = { _id: null };
928
- for (const [alias, field] of aliases) group[alias] = { $max: `$${field}` };
929
- const result = await this.collection.aggregate([{ $group: group }], this._getSessionOpts()).toArray();
930
- const maxMap = new Map();
931
- if (result.length > 0) {
932
- const row = result[0];
933
- for (const [alias, field] of aliases) {
934
- const val = row[alias];
935
- maxMap.set(field, typeof val === "number" ? val : 0);
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
+ }
936
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);
937
1390
  }
938
- return maxMap;
939
1391
  }
940
1392
  _buildFindOptions(controls) {
941
1393
  const opts = {};
@@ -987,11 +1439,11 @@ else {
987
1439
  if (analyzer) index.definition.mappings.fields[fieldName].analyzer = analyzer;
988
1440
  }
989
1441
  }
990
- constructor(db, asMongo) {
991
- super(), _define_property$1(this, "db", void 0), _define_property$1(this, "asMongo", void 0), _define_property$1(this, "_collection", void 0), _define_property$1(this, "_mongoIndexes", void 0), _define_property$1(this, "_vectorFilters", void 0), _define_property$1(this, "_searchIndexesMap", void 0), _define_property$1(this, "_incrementFields", void 0), _define_property$1(this, "_cappedOptions", void 0), _define_property$1(this, "_txDisabled", void 0), this.db = db, this.asMongo = asMongo, this._mongoIndexes = new Map(), this._vectorFilters = new Map(), this._incrementFields = new Set(), this._txDisabled = false;
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;
992
1444
  }
993
1445
  };
994
- _define_property$1(MongoAdapter, "_noSession", Object.freeze({}));
1446
+ _define_property(MongoAdapter, "_noSession", Object.freeze({}));
995
1447
  function objMatch(o1, o2) {
996
1448
  const keys1 = Object.keys(o1);
997
1449
  const keys2 = Object.keys(o2);
@@ -1020,51 +1472,15 @@ function vectorFieldsMatch(left, right) {
1020
1472
  return true;
1021
1473
  }
1022
1474
 
1023
- //#endregion
1024
- //#region packages/mongo/src/lib/as-mongo.ts
1025
- function _define_property(obj, key, value) {
1026
- if (key in obj) Object.defineProperty(obj, key, {
1027
- value,
1028
- enumerable: true,
1029
- configurable: true,
1030
- writable: true
1031
- });
1032
- else obj[key] = value;
1033
- return obj;
1034
- }
1035
- var AsMongo = class extends __atscript_utils_db.DbSpace {
1036
- get db() {
1037
- return this.client.db();
1038
- }
1039
- getCollectionsList() {
1040
- if (!this.collectionsList) this.collectionsList = this.db.listCollections().toArray().then((c) => new Set(c.map((c$1) => c$1.name)));
1041
- return this.collectionsList;
1042
- }
1043
- async collectionExists(name) {
1044
- const list = await this.getCollectionsList();
1045
- return list.has(name);
1046
- }
1047
- /**
1048
- * Returns the MongoAdapter for the given type.
1049
- * Convenience accessor for Mongo-specific adapter operations.
1050
- */ getAdapter(type) {
1051
- return super.getAdapter(type);
1052
- }
1053
- constructor(client, logger = NoopLogger) {
1054
- const resolvedClient = typeof client === "string" ? new mongodb.MongoClient(client) : client;
1055
- super(() => new MongoAdapter(this.db, this), logger), _define_property(this, "client", void 0), _define_property(this, "collectionsList", void 0);
1056
- this.client = resolvedClient;
1057
- }
1058
- };
1059
-
1060
1475
  //#endregion
1061
1476
  //#region packages/mongo/src/lib/index.ts
1062
1477
  function createAdapter(connection, _options) {
1063
- return new AsMongo(connection);
1478
+ const client = new mongodb.MongoClient(connection);
1479
+ const db = client.db();
1480
+ return new __atscript_utils_db.DbSpace(() => new MongoAdapter(db, client));
1064
1481
  }
1065
1482
 
1066
1483
  //#endregion
1067
- exports.AsMongo = AsMongo
1068
1484
  exports.CollectionPatcher = CollectionPatcher
1069
1485
  exports.MongoAdapter = MongoAdapter
1070
1486
  exports.buildMongoFilter = buildMongoFilter