@atscript/db-mongo 0.1.39 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,27 +1,57 @@
1
- import { buildMongoFilter } from "./mongo-filter-CL69Yhcm.mjs";
1
+ import { t as buildMongoFilter } from "./mongo-filter-CcQO-sEh.mjs";
2
2
  import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace, computeInsights, getKeyProps } from "@atscript/db";
3
3
  import { MongoClient, MongoServerError, ObjectId } from "mongodb";
4
-
5
- //#region packages/db-mongo/src/lib/collection-patcher.ts
6
- function _define_property$1(obj, key, value) {
7
- if (key in obj) Object.defineProperty(obj, key, {
8
- value,
9
- enumerable: true,
10
- configurable: true,
11
- writable: true
12
- });
13
- else obj[key] = value;
14
- return obj;
15
- }
4
+ //#region src/lib/collection-patcher.ts
5
+ /**
6
+ * CollectionPatcher is a small helper that converts a *patch payload* produced
7
+ * by Atscript into a shape that the official MongoDB driver understands – a
8
+ * triple of `(filter, update, options)` to be fed to `collection.updateOne()`.
9
+ *
10
+ * Supported high‑level operations for *top‑level arrays* (see the attached
11
+ * spreadsheet in the chat):
12
+ *
13
+ * | Payload field | MongoDB operator | Purpose |
14
+ * |-------------- |-------------------------|----------------------------------------|
15
+ * | `$replace` | full `$set` | Replace the whole array. |
16
+ * | `$insert` | `$push` | Append new items (duplicates allowed). |
17
+ * | `$upsert` | custom | Insert or update by *key* (see TODO). |
18
+ * | `$update` | `$set` + `arrayFilters` | Update array elements matched by *key* |
19
+ * | `$remove` | `$pullAll` / `$pull` | Remove by value or by *key*. |
20
+ *
21
+ * The class walks through the incoming payload, detects which of the above
22
+ * operations applies to each top‑level array and builds the corresponding
23
+ * MongoDB update document. Primitive fields are flattened into a regular
24
+ * `$set` map.
25
+ */
16
26
  var CollectionPatcher = class {
27
+ constructor(collection, payload, ops) {
28
+ this.collection = collection;
29
+ this.payload = payload;
30
+ this.ops = ops;
31
+ }
32
+ static getKeyProps = getKeyProps;
33
+ /**
34
+ * Internal accumulator: filter passed to `updateOne()`.
35
+ * Filled only with the `_id` field right now.
36
+ */
37
+ filterObj = {};
38
+ /** MongoDB *update* document being built. */
39
+ updatePipeline = [];
40
+ /** Current `$set` stage being populated. */
41
+ currentSetStage = null;
42
+ /** Additional *options* (mainly `arrayFilters`). */
43
+ optionsObj = {};
17
44
  /**
18
45
  * Entry point – walk the payload, build `filter`, `update` and `options`.
19
46
  *
20
47
  * @returns Helper object exposing both individual parts and
21
48
  * a `.toArgs()` convenience callback.
22
- */ preparePatch() {
49
+ */
50
+ preparePatch() {
23
51
  this.filterObj = { _id: this.collection.prepareId(this.payload._id) };
24
52
  this.flattenPayload(this.payload);
53
+ if (this.ops?.inc) for (const key in this.ops.inc) this._set(key, { $add: [`$${key}`, this.ops.inc[key]] });
54
+ if (this.ops?.mul) for (const key in this.ops.mul) this._set(key, { $multiply: [`$${key}`, this.ops.mul[key]] });
25
55
  const updateFilter = this.updatePipeline;
26
56
  return {
27
57
  toArgs: () => [
@@ -40,7 +70,8 @@ var CollectionPatcher = class {
40
70
  * @param key Fully‑qualified dotted path
41
71
  * @param val Value to be written
42
72
  * @private
43
- */ _set(key, val) {
73
+ */
74
+ _set(key, val) {
44
75
  if (this.currentSetStage && !(key in this.currentSetStage.$set)) {
45
76
  this.currentSetStage.$set[key] = val;
46
77
  return;
@@ -55,15 +86,16 @@ var CollectionPatcher = class {
55
86
  * @param payload Current payload chunk
56
87
  * @param prefix Dotted path accumulated so far
57
88
  * @private
58
- */ flattenPayload(payload, prefix = "") {
89
+ */
90
+ flattenPayload(payload, prefix = "") {
59
91
  const evalKey = (k) => prefix ? `${prefix}.${k}` : k;
60
92
  for (const [_key, value] of Object.entries(payload)) {
61
93
  const key = evalKey(_key);
62
94
  const flatType = this.collection.flatMap.get(key);
63
95
  const topLevelArray = flatType?.metadata?.get("db.__topLevelArray");
64
96
  if (typeof value === "object" && !Array.isArray(value) && topLevelArray && !flatType?.metadata?.has("db.json")) this.parseArrayPatch(key, value, flatType);
65
- else if (typeof value === "object" && flatType?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
66
- else if (key !== "_id") this._set(key, value);
97
+ else if (typeof value === "object" && flatType?.metadata?.get("db.patch.strategy") === "merge") this.flattenPayload(value, key);
98
+ else if (key !== "_id") this._set(key, value);
67
99
  }
68
100
  return this.updatePipeline;
69
101
  }
@@ -74,13 +106,14 @@ else if (key !== "_id") this._set(key, value);
74
106
  * @param key Dotted path to the array field
75
107
  * @param value Payload slice for that field
76
108
  * @private
77
- */ parseArrayPatch(key, value, flatType) {
109
+ */
110
+ parseArrayPatch(key, value, flatType) {
78
111
  const toRemove = value.$remove;
79
112
  const toReplace = value.$replace;
80
113
  const toInsert = value.$insert;
81
114
  const toUpsert = value.$upsert;
82
115
  const toUpdate = value.$update;
83
- const keyProps = flatType.type.kind === "array" ? getKeyProps(flatType) : new Set();
116
+ const keyProps = flatType.type.kind === "array" ? getKeyProps(flatType) : /* @__PURE__ */ new Set();
84
117
  const keys = keyProps.size > 0 ? [...keyProps] : [];
85
118
  this._remove(key, toRemove, keys, flatType);
86
119
  this._replace(key, toReplace);
@@ -98,7 +131,8 @@ else if (key !== "_id") this._set(key, value);
98
131
  * @param keys Ordered list of key property names
99
132
  * @param left Base token for *left* expression (e.g. `"$$el"`)
100
133
  * @param right Base token for *right* expression (e.g. `"$$this"`)
101
- */ _keysEqual(keys, left, right) {
134
+ */
135
+ _keysEqual(keys, left, right) {
102
136
  const eqs = keys.map((k) => ({ $eq: [`${left}.${k}`, `${right}.${k}`] }));
103
137
  return eqs.length === 1 ? eqs[0] : { $and: eqs };
104
138
  }
@@ -108,24 +142,26 @@ else if (key !== "_id") this._set(key, value);
108
142
  * @param key Dotted path to the array
109
143
  * @param input New array value (may be `undefined`)
110
144
  * @private
111
- */ _replace(key, input) {
145
+ */
146
+ _replace(key, input) {
112
147
  if (input) this._set(key, input);
113
148
  }
114
149
  /**
115
150
  * `$insert`
116
151
  * - plain append → $concatArrays
117
152
  * - unique / keyed → delegate to _upsert (insert-or-update)
118
- */ _insert(key, input, keys, flatType) {
153
+ */
154
+ _insert(key, input, keys, flatType) {
119
155
  if (!input?.length) return;
120
- const uniqueItems = flatType.metadata?.has("expect.array.uniqueItems");
121
- if (uniqueItems || keys.length > 0) this._upsert(key, input, keys, flatType);
122
- else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
156
+ if (flatType.metadata?.has("expect.array.uniqueItems") || keys.length > 0) this._upsert(key, input, keys, flatType);
157
+ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
123
158
  }
124
159
  /**
125
160
  * `$upsert`
126
161
  * - keyed → remove existing matching by key(s) then append candidate
127
162
  * - unique → $setUnion (deep equality)
128
- */ _upsert(key, input, keys, flatType) {
163
+ */
164
+ _upsert(key, input, keys, flatType) {
129
165
  if (!input?.length) return;
130
166
  if (keys.length > 0) {
131
167
  const mergeStrategy = flatType.metadata?.get("db.patch.strategy") === "merge";
@@ -166,7 +202,8 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
166
202
  * `$update`
167
203
  * - keyed → map array and merge / replace matching element(s)
168
204
  * - non-keyed → behave like `$addToSet` (insert only when not present)
169
- */ _update(key, input, keys, flatType) {
205
+ */
206
+ _update(key, input, keys, flatType) {
170
207
  if (!input?.length) return;
171
208
  if (keys.length > 0) {
172
209
  const mergeStrategy = flatType.metadata?.get("db.patch.strategy") === "merge";
@@ -189,7 +226,8 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
189
226
  * `$remove`
190
227
  * - keyed → filter out any element whose key set matches a payload item
191
228
  * - non-keyed → deep equality remove (`$setDifference`)
192
- */ _remove(key, input, keys, _flatType) {
229
+ */
230
+ _remove(key, input, keys, _flatType) {
193
231
  if (!input?.length) return;
194
232
  if (keys.length > 0) this._set(key, { $let: {
195
233
  vars: { rem: input },
@@ -203,46 +241,29 @@ else this._set(key, { $concatArrays: [{ $ifNull: [`$${key}`, []] }, input] });
203
241
  } } } }
204
242
  } }
205
243
  } });
206
- else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
207
- }
208
- constructor(collection, payload) {
209
- _define_property$1(this, "collection", void 0);
210
- _define_property$1(this, "payload", void 0);
211
- /**
212
- * Internal accumulator: filter passed to `updateOne()`.
213
- * Filled only with the `_id` field right now.
214
- */ _define_property$1(this, "filterObj", void 0);
215
- /** MongoDB *update* document being built. */ _define_property$1(this, "updatePipeline", void 0);
216
- /** Current `$set` stage being populated. */ _define_property$1(this, "currentSetStage", void 0);
217
- /** Additional *options* (mainly `arrayFilters`). */ _define_property$1(this, "optionsObj", void 0);
218
- this.collection = collection;
219
- this.payload = payload;
220
- this.filterObj = {};
221
- this.updatePipeline = [];
222
- this.currentSetStage = null;
223
- this.optionsObj = {};
244
+ else this._set(key, { $setDifference: [{ $ifNull: [`$${key}`, []] }, input] });
224
245
  }
225
246
  };
226
- _define_property$1(CollectionPatcher, "getKeyProps", getKeyProps);
227
-
228
247
  //#endregion
229
- //#region packages/db-mongo/src/lib/mongo-types.ts
248
+ //#region src/lib/mongo-types.ts
230
249
  const INDEX_PREFIX = "atscript__";
231
250
  const DEFAULT_INDEX_NAME = "DEFAULT";
232
251
  const JOINED_PREFIX = "__joined_";
233
252
  function mongoIndexKey(type, name) {
234
- const cleanName = name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 127 - INDEX_PREFIX.length - type.length - 2);
235
- return `${INDEX_PREFIX}${type}__${cleanName}`;
253
+ return `${INDEX_PREFIX}${type}__${name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 117 - type.length - 2)}`;
236
254
  }
237
-
238
255
  //#endregion
239
- //#region packages/db-mongo/src/lib/mongo-relations.ts
256
+ //#region src/lib/mongo-relations.ts
240
257
  function buildPKKey(primaryKeys, doc) {
241
- if (primaryKeys.length === 1) return String(doc[primaryKeys[0]] ?? "");
258
+ if (primaryKeys.length === 1) {
259
+ const val = doc[primaryKeys[0]];
260
+ return val == null ? "" : `${val}`;
261
+ }
242
262
  let key = "";
243
263
  for (let i = 0; i < primaryKeys.length; i++) {
244
264
  if (i > 0) key += "\0";
245
- key += String(doc[primaryKeys[i]] ?? "");
265
+ const val = doc[primaryKeys[i]];
266
+ key += val == null ? "" : `${val}`;
246
267
  }
247
268
  return key;
248
269
  }
@@ -269,23 +290,23 @@ async function loadRelationsImpl(host, rows, withRelations, relations, foreignKe
269
290
  if (pkMatchFilter) {
270
291
  const pipeline = [{ $match: pkMatchFilter }];
271
292
  for (const meta of relMeta) pipeline.push(...meta.stages);
272
- const results = await host.collection.aggregate(pipeline, host._getSessionOpts()).toArray();
273
- mergeRelationResults(rows, results, primaryKeys, relMeta);
293
+ mergeRelationResults(rows, await host.collection.aggregate(pipeline, host._getSessionOpts()).toArray(), primaryKeys, relMeta);
274
294
  } else for (const row of rows) for (const meta of relMeta) row[meta.name] = meta.isArray ? [] : null;
275
295
  await loadNestedRelations(rows, relMeta, tableResolver);
276
296
  }
277
- /** Builds a $match filter to re-select source rows by PK. */ function buildPKMatchFilter(rows, primaryKeys) {
297
+ /** Builds a $match filter to re-select source rows by PK. */
298
+ function buildPKMatchFilter(rows, primaryKeys) {
278
299
  if (primaryKeys.length === 1) {
279
300
  const pk = primaryKeys[0];
280
- const values = new Set();
301
+ const values = /* @__PURE__ */ new Set();
281
302
  for (const row of rows) {
282
303
  const v = row[pk];
283
- if (v !== null && v !== undefined) values.add(v);
304
+ if (v !== null && v !== void 0) values.add(v);
284
305
  }
285
- if (values.size === 0) return undefined;
306
+ if (values.size === 0) return;
286
307
  return { [pk]: { $in: [...values] } };
287
308
  }
288
- const seen = new Set();
309
+ const seen = /* @__PURE__ */ new Set();
289
310
  const orFilters = [];
290
311
  for (const row of rows) {
291
312
  const key = buildPKKey(primaryKeys, row);
@@ -295,7 +316,7 @@ async function loadRelationsImpl(host, rows, withRelations, relations, foreignKe
295
316
  let valid = true;
296
317
  for (const pk of primaryKeys) {
297
318
  const val = row[pk];
298
- if (val === null || val === undefined) {
319
+ if (val === null || val === void 0) {
299
320
  valid = false;
300
321
  break;
301
322
  }
@@ -303,18 +324,20 @@ async function loadRelationsImpl(host, rows, withRelations, relations, foreignKe
303
324
  }
304
325
  if (valid) orFilters.push(condition);
305
326
  }
306
- if (orFilters.length === 0) return undefined;
327
+ if (orFilters.length === 0) return;
307
328
  return orFilters.length === 1 ? orFilters[0] : { $or: orFilters };
308
329
  }
309
- /** Dispatches to the correct $lookup builder based on relation direction. */ function buildRelationLookup(host, withRel, relation, foreignKeys, tableResolver) {
330
+ /** Dispatches to the correct $lookup builder based on relation direction. */
331
+ function buildRelationLookup(host, withRel, relation, foreignKeys, tableResolver) {
310
332
  switch (relation.direction) {
311
333
  case "to": return buildToLookup(withRel, relation, foreignKeys);
312
334
  case "from": return buildFromLookup(host, withRel, relation, tableResolver);
313
335
  case "via": return buildViaLookup(host, withRel, relation, tableResolver);
314
- default: return undefined;
336
+ default: return;
315
337
  }
316
338
  }
317
- /** Builds `let` variable bindings and the corresponding `$expr` match for `$lookup`. */ function buildLookupJoin(localFields, remoteFields, varPrefix) {
339
+ /** Builds `let` variable bindings and the corresponding `$expr` match for `$lookup`. */
340
+ function buildLookupJoin(localFields, remoteFields, varPrefix) {
318
341
  const letVars = {};
319
342
  for (let i = 0; i < localFields.length; i++) letVars[`${varPrefix}${i}`] = `$${localFields[i]}`;
320
343
  if (remoteFields.length === 1) return {
@@ -328,32 +351,33 @@ async function loadRelationsImpl(host, rows, withRelations, relations, foreignKe
328
351
  exprMatch: { $and: andClauses }
329
352
  };
330
353
  }
331
- /** $lookup for TO relations (FK is on this table → target). Always single-valued. */ function buildToLookup(withRel, relation, foreignKeys) {
354
+ /** $lookup for TO relations (FK is on this table → target). Always single-valued. */
355
+ function buildToLookup(withRel, relation, foreignKeys) {
332
356
  const fk = findFKForRelation(relation, foreignKeys);
333
- if (!fk) return undefined;
357
+ if (!fk) return;
334
358
  const innerPipeline = buildLookupInnerPipeline(withRel, fk.targetFields);
335
359
  const { letVars, exprMatch } = buildLookupJoin(fk.localFields, fk.targetFields, "fk_");
336
- const stages = [{ $lookup: {
337
- from: fk.targetTable,
338
- let: letVars,
339
- pipeline: [{ $match: { $expr: exprMatch } }, ...innerPipeline],
340
- as: withRel.name
341
- } }, { $unwind: {
342
- path: `$${withRel.name}`,
343
- preserveNullAndEmptyArrays: true
344
- } }];
345
360
  return {
346
- stages,
361
+ stages: [{ $lookup: {
362
+ from: fk.targetTable,
363
+ let: letVars,
364
+ pipeline: [{ $match: { $expr: exprMatch } }, ...innerPipeline],
365
+ as: withRel.name
366
+ } }, { $unwind: {
367
+ path: `$${withRel.name}`,
368
+ preserveNullAndEmptyArrays: true
369
+ } }],
347
370
  isArray: false
348
371
  };
349
372
  }
350
- /** $lookup for FROM relations (FK is on target → this table). */ function buildFromLookup(host, withRel, relation, tableResolver) {
373
+ /** $lookup for FROM relations (FK is on target → this table). */
374
+ function buildFromLookup(host, withRel, relation, tableResolver) {
351
375
  const targetType = relation.targetType();
352
- if (!targetType || !tableResolver) return undefined;
376
+ if (!targetType || !tableResolver) return;
353
377
  const targetMeta = tableResolver(targetType);
354
- if (!targetMeta) return undefined;
378
+ if (!targetMeta) return;
355
379
  const remoteFK = findRemoteFK(targetMeta, host._table.tableName, relation.alias);
356
- if (!remoteFK) return undefined;
380
+ if (!remoteFK) return;
357
381
  const targetTableName = resolveRelTargetTableName(relation);
358
382
  const innerPipeline = buildLookupInnerPipeline(withRel, remoteFK.fields);
359
383
  const { letVars, exprMatch } = buildLookupJoin(remoteFK.targetFields, remoteFK.fields, "pk_");
@@ -372,46 +396,47 @@ async function loadRelationsImpl(host, rows, withRelations, relations, foreignKe
372
396
  isArray: relation.isArray
373
397
  };
374
398
  }
375
- /** $lookup for VIA relations (M:N through junction table). Always array. */ function buildViaLookup(host, withRel, relation, tableResolver) {
376
- if (!relation.viaType || !tableResolver) return undefined;
399
+ /** $lookup for VIA relations (M:N through junction table). Always array. */
400
+ function buildViaLookup(host, withRel, relation, tableResolver) {
401
+ if (!relation.viaType || !tableResolver) return;
377
402
  const junctionType = relation.viaType();
378
- if (!junctionType) return undefined;
403
+ if (!junctionType) return;
379
404
  const junctionMeta = tableResolver(junctionType);
380
- if (!junctionMeta) return undefined;
405
+ if (!junctionMeta) return;
381
406
  const junctionTableName = junctionType.metadata?.get("db.table") || junctionType.id || "";
382
407
  const targetTableName = resolveRelTargetTableName(relation);
383
408
  const fkToThis = findRemoteFK(junctionMeta, host._table.tableName);
384
- if (!fkToThis) return undefined;
409
+ if (!fkToThis) return;
385
410
  const fkToTarget = findRemoteFK(junctionMeta, targetTableName);
386
- if (!fkToTarget) return undefined;
411
+ if (!fkToTarget) return;
387
412
  const innerPipeline = buildLookupInnerPipeline(withRel, fkToTarget.targetFields);
388
413
  const { letVars, exprMatch } = buildLookupJoin(fkToThis.targetFields, fkToThis.fields, "pk_");
389
- const stages = [{ $lookup: {
390
- from: junctionTableName,
391
- let: letVars,
392
- pipeline: [
393
- { $match: { $expr: exprMatch } },
394
- { $lookup: {
395
- from: targetTableName,
396
- localField: fkToTarget.fields[0],
397
- foreignField: fkToTarget.targetFields[0],
398
- pipeline: innerPipeline,
399
- as: "__target"
400
- } },
401
- { $unwind: {
402
- path: "$__target",
403
- preserveNullAndEmptyArrays: false
404
- } },
405
- { $replaceRoot: { newRoot: "$__target" } }
406
- ],
407
- as: withRel.name
408
- } }];
409
414
  return {
410
- stages,
415
+ stages: [{ $lookup: {
416
+ from: junctionTableName,
417
+ let: letVars,
418
+ pipeline: [
419
+ { $match: { $expr: exprMatch } },
420
+ { $lookup: {
421
+ from: targetTableName,
422
+ localField: fkToTarget.fields[0],
423
+ foreignField: fkToTarget.targetFields[0],
424
+ pipeline: innerPipeline,
425
+ as: "__target"
426
+ } },
427
+ { $unwind: {
428
+ path: "$__target",
429
+ preserveNullAndEmptyArrays: false
430
+ } },
431
+ { $replaceRoot: { newRoot: "$__target" } }
432
+ ],
433
+ as: withRel.name
434
+ } }],
411
435
  isArray: true
412
436
  };
413
437
  }
414
- /** Builds inner pipeline stages for relation controls ($sort, $limit, $skip, $select, filter). */ function buildLookupInnerPipeline(withRel, requiredFields) {
438
+ /** Builds inner pipeline stages for relation controls ($sort, $limit, $skip, $select, filter). */
439
+ function buildLookupInnerPipeline(withRel, requiredFields) {
415
440
  const pipeline = [];
416
441
  const flatRel = withRel;
417
442
  const nested = withRel.controls || {};
@@ -423,7 +448,7 @@ async function loadRelationsImpl(host, rows, withRelations, relations, foreignKe
423
448
  if (filter && Object.keys(filter).length > 0) pipeline.push({ $match: buildMongoFilter(filter) });
424
449
  if (sort) pipeline.push({ $sort: sort });
425
450
  if (skip) pipeline.push({ $skip: skip });
426
- if (limit !== null && limit !== undefined) pipeline.push({ $limit: limit });
451
+ if (limit !== null && limit !== void 0) pipeline.push({ $limit: limit });
427
452
  if (select) {
428
453
  const projection = {};
429
454
  for (const f of select) projection[f] = 1;
@@ -433,13 +458,14 @@ async function loadRelationsImpl(host, rows, withRelations, relations, foreignKe
433
458
  }
434
459
  return pipeline;
435
460
  }
436
- /** Extracts nested $with from a WithRelation's controls. */ function extractNestedWith(withRel) {
461
+ /** Extracts nested $with from a WithRelation's controls. */
462
+ function extractNestedWith(withRel) {
437
463
  const flatRel = withRel;
438
- const nested = withRel.controls || {};
439
- const nestedWith = nested.$with || flatRel.$with;
440
- return nestedWith && nestedWith.length > 0 ? nestedWith : undefined;
464
+ const nestedWith = (withRel.controls || {}).$with || flatRel.$with;
465
+ return nestedWith && nestedWith.length > 0 ? nestedWith : void 0;
441
466
  }
442
- /** Post-processes nested $with by delegating to the target table's own relation loading. */ async function loadNestedRelations(rows, relMeta, tableResolver) {
467
+ /** Post-processes nested $with by delegating to the target table's own relation loading. */
468
+ async function loadNestedRelations(rows, relMeta, tableResolver) {
443
469
  if (!tableResolver) return;
444
470
  const tasks = [];
445
471
  for (const meta of relMeta) {
@@ -452,26 +478,28 @@ async function loadRelationsImpl(host, rows, withRelations, relations, foreignKe
452
478
  for (const row of rows) {
453
479
  const val = row[meta.name];
454
480
  if (meta.isArray && Array.isArray(val)) for (const item of val) subRows.push(item);
455
- else if (val && typeof val === "object") subRows.push(val);
481
+ else if (val && typeof val === "object") subRows.push(val);
456
482
  }
457
483
  if (subRows.length === 0) continue;
458
484
  tasks.push(targetTable.loadRelations(subRows, meta.nestedWith));
459
485
  }
460
486
  await Promise.all(tasks);
461
487
  }
462
- /** Merges aggregation results back onto the original rows by PK. */ function mergeRelationResults(rows, results, primaryKeys, relMeta) {
463
- const resultIndex = new Map();
488
+ /** Merges aggregation results back onto the original rows by PK. */
489
+ function mergeRelationResults(rows, results, primaryKeys, relMeta) {
490
+ const resultIndex = /* @__PURE__ */ new Map();
464
491
  for (const doc of results) resultIndex.set(buildPKKey(primaryKeys, doc), doc);
465
492
  for (const row of rows) {
466
493
  const enriched = resultIndex.get(buildPKKey(primaryKeys, row));
467
494
  for (const meta of relMeta) if (enriched) {
468
495
  const value = enriched[meta.name];
469
496
  if (!meta.isArray && Array.isArray(value)) row[meta.name] = value[0] ?? null;
470
- else row[meta.name] = value ?? (meta.isArray ? [] : null);
497
+ else row[meta.name] = value ?? (meta.isArray ? [] : null);
471
498
  } else row[meta.name] = meta.isArray ? [] : null;
472
499
  }
473
500
  }
474
- /** Finds FK entry for a TO relation from this table's foreignKeys map. */ function findFKForRelation(relation, foreignKeys) {
501
+ /** Finds FK entry for a TO relation from this table's foreignKeys map. */
502
+ function findFKForRelation(relation, foreignKeys) {
475
503
  const targetTableName = resolveRelTargetTableName(relation);
476
504
  for (const fk of foreignKeys.values()) if (relation.alias) {
477
505
  if (fk.alias === relation.alias) return {
@@ -484,22 +512,22 @@ else row[meta.name] = value ?? (meta.isArray ? [] : null);
484
512
  targetFields: fk.targetFields,
485
513
  targetTable: fk.targetTable
486
514
  };
487
- return undefined;
488
515
  }
489
- /** Finds a FK on a remote table that points back to the given table name. */ function findRemoteFK(target, thisTableName, alias) {
516
+ /** Finds a FK on a remote table that points back to the given table name. */
517
+ function findRemoteFK(target, thisTableName, alias) {
490
518
  for (const fk of target.foreignKeys.values()) {
491
519
  if (alias && fk.alias === alias && fk.targetTable === thisTableName) return fk;
492
520
  if (!alias && fk.targetTable === thisTableName) return fk;
493
521
  }
494
- return undefined;
495
522
  }
496
- /** Resolves the target table/collection name from a relation's target type. */ function resolveRelTargetTableName(relation) {
523
+ /** Resolves the target table/collection name from a relation's target type. */
524
+ function resolveRelTargetTableName(relation) {
497
525
  const targetType = relation.targetType();
498
526
  return targetType?.metadata?.get("db.table") || targetType?.id || "";
499
527
  }
500
-
501
528
  //#endregion
502
- //#region packages/db-mongo/src/lib/mongo-search.ts
529
+ //#region src/lib/mongo-search.ts
530
+ /** Returns available search indexes as generic metadata for UI. */
503
531
  function getSearchIndexesImpl(host) {
504
532
  const result = [];
505
533
  for (const [name, index] of host.getMongoSearchIndexes()) result.push({
@@ -509,40 +537,43 @@ function getSearchIndexesImpl(host) {
509
537
  });
510
538
  return result;
511
539
  }
540
+ /** Checks if any vector search index is available. */
512
541
  function isVectorSearchableImpl(host) {
513
542
  for (const index of host.getMongoSearchIndexes().values()) if (index.type === "vector") return true;
514
543
  return false;
515
544
  }
545
+ /** Text search via $search aggregation stage. */
516
546
  async function searchImpl(host, text, query, indexName) {
517
547
  const stage = buildSearchStage(host, text, indexName);
518
548
  if (!stage) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
519
549
  return runSearchPipeline(host, stage, query, "search");
520
550
  }
551
+ /** Text search with faceted count. */
521
552
  async function searchWithCountImpl(host, text, query, indexName) {
522
553
  const stage = buildSearchStage(host, text, indexName);
523
554
  if (!stage) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
524
555
  return runSearchWithCountPipeline(host, stage, query, "searchWithCount");
525
556
  }
557
+ /** Vector search via $vectorSearch aggregation stage. */
526
558
  async function vectorSearchImpl(host, vector, query, indexName) {
527
559
  const controls = query.controls || {};
528
- const stage = buildVectorSearchStage(host, vector, indexName, controls.$limit);
529
- const threshold = resolveThreshold(host, controls, indexName);
530
- return runSearchPipeline(host, stage, query, "vectorSearch", threshold);
560
+ return runSearchPipeline(host, buildVectorSearchStage(host, vector, indexName, controls.$limit), query, "vectorSearch", resolveThreshold(host, controls, indexName));
531
561
  }
562
+ /** Vector search with faceted count. */
532
563
  async function vectorSearchWithCountImpl(host, vector, query, indexName) {
533
564
  const controls = query.controls || {};
534
- const stage = buildVectorSearchStage(host, vector, indexName, controls.$limit);
535
- const threshold = resolveThreshold(host, controls, indexName);
536
- return runSearchWithCountPipeline(host, stage, query, "vectorSearchWithCount", threshold);
565
+ return runSearchWithCountPipeline(host, buildVectorSearchStage(host, vector, indexName, controls.$limit), query, "vectorSearchWithCount", resolveThreshold(host, controls, indexName));
537
566
  }
538
- /** Resolves the effective threshold: query-time $threshold > schema-level @db.search.vector.threshold. */ function resolveThreshold(host, controls, indexName) {
567
+ /** Resolves the effective threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
568
+ function resolveThreshold(host, controls, indexName) {
539
569
  const queryThreshold = controls.$threshold;
540
- if (queryThreshold !== undefined) return queryThreshold;
570
+ if (queryThreshold !== void 0) return queryThreshold;
541
571
  return host.getVectorThreshold(indexName);
542
572
  }
543
- /** Builds a MongoDB $search pipeline stage for text search. */ function buildSearchStage(host, text, indexName) {
573
+ /** Builds a MongoDB $search pipeline stage for text search. */
574
+ function buildSearchStage(host, text, indexName) {
544
575
  const index = host.getMongoSearchIndex(indexName);
545
- if (!index) return undefined;
576
+ if (!index) return;
546
577
  if (index.type === "vector") throw new Error("Vector indexes cannot be used with text search. Use vectorSearch() instead.");
547
578
  return { $search: {
548
579
  index: index.key,
@@ -552,7 +583,8 @@ async function vectorSearchWithCountImpl(host, vector, query, indexName) {
552
583
  }
553
584
  } };
554
585
  }
555
- /** Builds a $vectorSearch aggregation stage from a pre-computed vector. */ function buildVectorSearchStage(host, vector, indexName, limit) {
586
+ /** Builds a $vectorSearch aggregation stage from a pre-computed vector. */
587
+ function buildVectorSearchStage(host, vector, indexName, limit) {
556
588
  let index;
557
589
  if (indexName) {
558
590
  const found = host.getMongoSearchIndex(indexName);
@@ -579,11 +611,12 @@ async function vectorSearchWithCountImpl(host, vector, query, indexName) {
579
611
  limit: limit || 20
580
612
  } };
581
613
  }
582
- /** Runs a search/vector pipeline and returns results. Shared by search + vectorSearch. */ async function runSearchPipeline(host, stage, query, label, threshold) {
614
+ /** Runs a search/vector pipeline and returns results. Shared by search + vectorSearch. */
615
+ async function runSearchPipeline(host, stage, query, label, threshold) {
583
616
  const filter = buildMongoFilter(query.filter);
584
617
  const controls = query.controls || {};
585
618
  const pipeline = [stage];
586
- if (threshold !== undefined) {
619
+ if (threshold !== void 0) {
587
620
  pipeline.push({ $addFields: { _score: { $meta: "vectorSearchScore" } } });
588
621
  pipeline.push({ $match: { _score: { $gte: threshold } } });
589
622
  }
@@ -591,16 +624,17 @@ async function vectorSearchWithCountImpl(host, vector, query, indexName) {
591
624
  if (controls.$sort) pipeline.push({ $sort: controls.$sort });
592
625
  if (controls.$skip) pipeline.push({ $skip: controls.$skip });
593
626
  if (controls.$limit) pipeline.push({ $limit: controls.$limit });
594
- else pipeline.push({ $limit: 1e3 });
627
+ else pipeline.push({ $limit: 1e3 });
595
628
  if (controls.$select) pipeline.push({ $project: controls.$select.asProjection });
596
629
  host._log(`aggregate (${label})`, pipeline);
597
630
  return host.collection.aggregate(pipeline, host._getSessionOpts()).toArray();
598
631
  }
599
- /** Runs a search/vector pipeline with $facet for count. Shared by searchWithCount + vectorSearchWithCount. */ async function runSearchWithCountPipeline(host, stage, query, label, threshold) {
632
+ /** Runs a search/vector pipeline with $facet for count. Shared by searchWithCount + vectorSearchWithCount. */
633
+ async function runSearchWithCountPipeline(host, stage, query, label, threshold) {
600
634
  const filter = buildMongoFilter(query.filter);
601
635
  const controls = query.controls || {};
602
636
  const preStages = [];
603
- if (threshold !== undefined) {
637
+ if (threshold !== void 0) {
604
638
  preStages.push({ $addFields: { _score: { $meta: "vectorSearchScore" } } });
605
639
  preStages.push({ $match: { _score: { $gte: threshold } } });
606
640
  }
@@ -625,9 +659,8 @@ else pipeline.push({ $limit: 1e3 });
625
659
  count: result[0]?.meta[0]?.count || 0
626
660
  };
627
661
  }
628
-
629
662
  //#endregion
630
- //#region packages/db-mongo/src/lib/mongo-schema-sync.ts
663
+ //#region src/lib/mongo-schema-sync.ts
631
664
  const DESTRUCTIVE_OPTION_KEYS = new Set([
632
665
  "capped",
633
666
  "capped.size",
@@ -645,7 +678,7 @@ function getDesiredTableOptionsImpl(cappedOptions) {
645
678
  key: "capped.size",
646
679
  value: String(cappedOptions.size)
647
680
  }];
648
- if (cappedOptions.max !== undefined) opts.push({
681
+ if (cappedOptions.max !== void 0) opts.push({
649
682
  key: "capped.max",
650
683
  value: String(cappedOptions.max)
651
684
  });
@@ -660,11 +693,11 @@ async function getExistingTableOptionsImpl(host) {
660
693
  key: "capped",
661
694
  value: "true"
662
695
  }];
663
- if (collOpts.size !== undefined) opts.push({
696
+ if (collOpts.size !== void 0) opts.push({
664
697
  key: "capped.size",
665
698
  value: String(collOpts.size)
666
699
  });
667
- if (collOpts.max !== undefined) opts.push({
700
+ if (collOpts.max !== void 0) opts.push({
668
701
  key: "capped.max",
669
702
  value: String(collOpts.max)
670
703
  });
@@ -674,9 +707,9 @@ async function ensureTableImpl(host, table) {
674
707
  if (table instanceof AtscriptDbView && !table.isExternal) return ensureView(host, table);
675
708
  return host.ensureCollectionExists();
676
709
  }
677
- /** Creates a MongoDB view from the AtscriptDbView's view plan. */ async function ensureView(host, view) {
678
- const exists = await host.collectionExists();
679
- if (exists) return;
710
+ /** Creates a MongoDB view from the AtscriptDbView's view plan. */
711
+ async function ensureView(host, view) {
712
+ if (await host.collectionExists()) return;
680
713
  const plan = view.viewPlan;
681
714
  const columns = view.getViewColumnMappings();
682
715
  const pipeline = [];
@@ -698,13 +731,14 @@ async function ensureTableImpl(host, table) {
698
731
  pipeline.push({ $match: matchExpr });
699
732
  }
700
733
  const hasAggregates = columns.some((c) => c.aggFn);
701
- /** Resolves a column to its MongoDB source field path. */ const colSourceField = (col) => col.sourceTable === plan.entryTable ? `$${col.sourceColumn}` : `$${JOINED_PREFIX}${col.sourceTable}.${col.sourceColumn}`;
734
+ /** Resolves a column to its MongoDB source field path. */
735
+ const colSourceField = (col) => col.sourceTable === plan.entryTable ? `$${col.sourceColumn}` : `$${JOINED_PREFIX}${col.sourceTable}.${col.sourceColumn}`;
702
736
  if (hasAggregates) {
703
737
  const group = { _id: {} };
704
738
  const project = { _id: 0 };
705
739
  for (const col of columns) if (col.aggFn) {
706
740
  if (col.aggFn === "count" && col.aggField === "*") group[col.viewColumn] = { $sum: 1 };
707
- else group[col.viewColumn] = { [`$${col.aggFn}`]: colSourceField(col) };
741
+ else group[col.viewColumn] = { [`$${col.aggFn}`]: colSourceField(col) };
708
742
  project[col.viewColumn] = `$${col.viewColumn}`;
709
743
  } else {
710
744
  group._id[col.viewColumn] = colSourceField(col);
@@ -727,11 +761,10 @@ else group[col.viewColumn] = { [`$${col.aggFn}`]: colSourceField(col) };
727
761
  pipeline
728
762
  });
729
763
  }
730
- /** Extracts localField/foreignField from a join condition. */ function resolveJoinFields(condition, entryTable, joinTable) {
731
- const comp = "$and" in condition ? condition.$and[0] : condition;
732
- const c = comp;
733
- const leftTable = c.left.type ? c.left.type()?.metadata?.get("db.table") || "" : entryTable;
734
- if (leftTable === joinTable) return {
764
+ /** Extracts localField/foreignField from a join condition. */
765
+ function resolveJoinFields(condition, entryTable, joinTable) {
766
+ const c = "$and" in condition ? condition.$and[0] : condition;
767
+ if ((c.left.type ? c.left.type()?.metadata?.get("db.table") || "" : entryTable) === joinTable) return {
735
768
  localField: c.right.field,
736
769
  foreignField: c.left.field
737
770
  };
@@ -740,7 +773,8 @@ else group[col.viewColumn] = { [`$${col.aggFn}`]: colSourceField(col) };
740
773
  foreignField: c.right.field
741
774
  };
742
775
  }
743
- /** Translates an AtscriptQueryNode to a MongoDB $match expression. */ function queryNodeToMatch(node, entryTable) {
776
+ /** Translates an AtscriptQueryNode to a MongoDB $match expression. */
777
+ function queryNodeToMatch(node, entryTable) {
744
778
  if ("$and" in node) {
745
779
  const items = node.$and;
746
780
  const result = [];
@@ -764,7 +798,8 @@ else group[col.viewColumn] = { [`$${col.aggFn}`]: colSourceField(col) };
764
798
  if (comp.op === "$ne") return { [fieldPath]: { $ne: comp.right } };
765
799
  return { [fieldPath]: { [comp.op]: comp.right } };
766
800
  }
767
- /** Resolves a field ref to a MongoDB dot path for view pipeline expressions. */ function resolveViewFieldPath(ref, entryTable) {
801
+ /** Resolves a field ref to a MongoDB dot path for view pipeline expressions. */
802
+ function resolveViewFieldPath(ref, entryTable) {
768
803
  if (!ref.type) return ref.field;
769
804
  const table = ref.type()?.metadata?.get("db.table") || "";
770
805
  if (table === entryTable) return ref.field;
@@ -773,7 +808,8 @@ else group[col.viewColumn] = { [`$${col.aggFn}`]: colSourceField(col) };
773
808
  /**
774
809
  * Translates an AtscriptQueryNode to a MongoDB $match for use after $group (HAVING).
775
810
  * After $group, aggregate fields are top-level and dimension fields are under _id.
776
- */ function queryNodeToHaving(node, columns) {
811
+ */
812
+ function queryNodeToHaving(node, columns) {
777
813
  const colMap = new Map(columns.map((c) => [c.viewColumn, c]));
778
814
  const resolveHavingField = (ref) => {
779
815
  if (!ref.type) {
@@ -854,7 +890,7 @@ async function syncColumnsImpl(host, diff) {
854
890
  const setSpec = {};
855
891
  for (const field of diff.added) {
856
892
  const defaultVal = resolveSyncDefault(field);
857
- if (defaultVal !== undefined) setSpec[field.physicalName] = defaultVal;
893
+ if (defaultVal !== void 0) setSpec[field.physicalName] = defaultVal;
858
894
  added.push(field.physicalName);
859
895
  }
860
896
  if (Object.keys(setSpec).length > 0) update.$set = setSpec;
@@ -871,14 +907,14 @@ async function dropColumnsImpl(host, columns) {
871
907
  for (const col of columns) unsetSpec[col] = "";
872
908
  await host.collection.updateMany({}, { $unset: unsetSpec }, host._getSessionOpts());
873
909
  }
874
- /** Resolves a field's default value for bulk $set during column sync. */ function resolveSyncDefault(field) {
875
- if (!field.defaultValue) return field.optional ? null : undefined;
910
+ /** Resolves a field's default value for bulk $set during column sync. */
911
+ function resolveSyncDefault(field) {
912
+ if (!field.defaultValue) return field.optional ? null : void 0;
876
913
  if (field.defaultValue.kind === "value") return field.defaultValue.value;
877
- return undefined;
878
914
  }
879
915
  async function syncIndexesImpl(host) {
880
916
  await host.ensureCollectionExists();
881
- const allIndexes = new Map();
917
+ const allIndexes = /* @__PURE__ */ new Map();
882
918
  for (const [key, index] of host._table.indexes.entries()) {
883
919
  const fields = {};
884
920
  const weights = {};
@@ -911,20 +947,19 @@ async function syncIndexesImpl(host) {
911
947
  const existingIndexes = await host.collection.listIndexes().toArray();
912
948
  const indexesToCreate = new Map(allIndexes);
913
949
  for (const remote of existingIndexes) {
914
- if (!remote.name.startsWith(INDEX_PREFIX)) continue;
950
+ if (!remote.name.startsWith("atscript__")) continue;
915
951
  if (indexesToCreate.has(remote.name)) {
916
952
  const local = indexesToCreate.get(remote.name);
917
953
  switch (local.type) {
918
954
  case "plain":
919
955
  case "unique":
920
- case "text": {
956
+ case "text":
921
957
  if ((local.type === "text" || objMatch(local.fields, remote.key)) && objMatch(local.weights || {}, remote.weights || {})) indexesToCreate.delete(remote.name);
922
- else {
958
+ else {
923
959
  host._log("dropIndex", remote.name);
924
960
  await host.collection.dropIndex(remote.name);
925
961
  }
926
962
  break;
927
- }
928
963
  default:
929
964
  }
930
965
  } else {
@@ -933,13 +968,12 @@ else {
933
968
  }
934
969
  }
935
970
  for (const [key, value] of allIndexes.entries()) switch (value.type) {
936
- case "plain": {
971
+ case "plain":
937
972
  if (!indexesToCreate.has(key)) continue;
938
973
  host._log("createIndex", key, value.fields);
939
974
  await host.collection.createIndex(value.fields, { name: key });
940
975
  break;
941
- }
942
- case "unique": {
976
+ case "unique":
943
977
  if (!indexesToCreate.has(key)) continue;
944
978
  host._log("createIndex (unique)", key, value.fields);
945
979
  await host.collection.createIndex(value.fields, {
@@ -947,8 +981,7 @@ else {
947
981
  unique: true
948
982
  });
949
983
  break;
950
- }
951
- case "text": {
984
+ case "text":
952
985
  if (!indexesToCreate.has(key)) continue;
953
986
  host._log("createIndex (text)", key, value.fields);
954
987
  await host.collection.createIndex(value.fields, {
@@ -956,14 +989,13 @@ else {
956
989
  name: key
957
990
  });
958
991
  break;
959
- }
960
992
  default:
961
993
  }
962
994
  try {
963
- const toUpdate = new Set();
995
+ const toUpdate = /* @__PURE__ */ new Set();
964
996
  const existingSearchIndexes = await host.collection.listSearchIndexes().toArray();
965
997
  for (const remote of existingSearchIndexes) {
966
- if (!remote.name.startsWith(INDEX_PREFIX)) continue;
998
+ if (!remote.name.startsWith("atscript__")) continue;
967
999
  if (indexesToCreate.has(remote.name)) {
968
1000
  const local = indexesToCreate.get(remote.name);
969
1001
  const right = remote.latestDefinition;
@@ -972,14 +1004,13 @@ else {
972
1004
  case "search_text": {
973
1005
  const left = local.definition;
974
1006
  if (left.analyzer === right.analyzer && fieldsMatch(left.mappings.fields || {}, right.mappings.fields || {})) indexesToCreate.delete(remote.name);
975
- else toUpdate.add(remote.name);
1007
+ else toUpdate.add(remote.name);
976
1008
  break;
977
1009
  }
978
- case "vector": {
1010
+ case "vector":
979
1011
  if (vectorFieldsMatch(local.definition.fields || [], right.fields || [])) indexesToCreate.delete(remote.name);
980
- else toUpdate.add(remote.name);
1012
+ else toUpdate.add(remote.name);
981
1013
  break;
982
- }
983
1014
  default:
984
1015
  }
985
1016
  } else if (remote.status !== "DELETING") {
@@ -990,7 +1021,7 @@ else toUpdate.add(remote.name);
990
1021
  for (const [key, value] of indexesToCreate.entries()) switch (value.type) {
991
1022
  case "dynamic_text":
992
1023
  case "search_text":
993
- case "vector": {
1024
+ case "vector":
994
1025
  if (toUpdate.has(key)) {
995
1026
  host._log("updateSearchIndex", key, value.definition);
996
1027
  await host.collection.updateSearchIndex(key, value.definition);
@@ -1003,7 +1034,6 @@ else toUpdate.add(remote.name);
1003
1034
  });
1004
1035
  }
1005
1036
  break;
1006
- }
1007
1037
  default:
1008
1038
  }
1009
1039
  } catch {}
@@ -1028,7 +1058,7 @@ function fieldsMatch(left, right) {
1028
1058
  }
1029
1059
  function vectorFieldsMatch(left, right) {
1030
1060
  if (left.length !== (right || []).length) return false;
1031
- const rightMap = new Map();
1061
+ const rightMap = /* @__PURE__ */ new Map();
1032
1062
  for (const f of right || []) rightMap.set(f.path, f);
1033
1063
  for (const l of left) {
1034
1064
  const r = rightMap.get(l.path);
@@ -1037,35 +1067,52 @@ function vectorFieldsMatch(left, right) {
1037
1067
  }
1038
1068
  return true;
1039
1069
  }
1040
-
1041
1070
  //#endregion
1042
- //#region packages/db-mongo/src/lib/validate-plugins.ts
1071
+ //#region src/lib/validate-plugins.ts
1043
1072
  const validateMongoIdPlugin = (ctx, def, value) => {
1044
1073
  if (def.type.tags?.has("objectId")) {
1045
- if (ctx.path === "_id" && (value === undefined || value === null)) {
1074
+ if (ctx.path === "_id" && (value === void 0 || value === null)) {
1046
1075
  const dbCtx = ctx.context;
1047
1076
  if (dbCtx && (dbCtx.mode === "insert" || dbCtx.mode === "replace")) return true;
1048
1077
  }
1049
1078
  return ctx.validateAnnotatedType(def, value instanceof ObjectId ? value.toString() : value);
1050
1079
  }
1051
1080
  };
1052
-
1053
1081
  //#endregion
1054
- //#region packages/db-mongo/src/lib/mongo-adapter.ts
1055
- function _define_property(obj, key, value) {
1056
- if (key in obj) Object.defineProperty(obj, key, {
1057
- value,
1058
- enumerable: true,
1059
- configurable: true,
1060
- writable: true
1061
- });
1062
- else obj[key] = value;
1063
- return obj;
1064
- }
1082
+ //#region src/lib/mongo-adapter.ts
1065
1083
  var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1084
+ _collection;
1085
+ /** MongoDB-specific indexes (search, vector) — separate from table.indexes. */
1086
+ _mongoIndexes = /* @__PURE__ */ new Map();
1087
+ /** Vector search filter associations built during flattening. */
1088
+ _vectorFilters = /* @__PURE__ */ new Map();
1089
+ /** Default similarity thresholds per vector index (from @db.search.vector.threshold). */
1090
+ _vectorThresholds = /* @__PURE__ */ new Map();
1091
+ /** Cached search index lookup. */
1092
+ _searchIndexesMap;
1093
+ /** Physical field names with @db.default.increment → optional start value. */
1094
+ _incrementFields = /* @__PURE__ */ new Map();
1095
+ /** Physical field names that have a non-binary collation (nocase/unicode). */
1096
+ _collateFields;
1097
+ /** Capped collection options from @db.mongo.capped. */
1098
+ _cappedOptions;
1099
+ /** Whether the schema explicitly defines _id (via @db.mongo.collection or manual _id field). */
1100
+ _hasExplicitId = false;
1101
+ /** Unique fields accumulated during onFieldScanned, returned via getMetadataOverrides. */
1102
+ _pendingUniqueFields = [];
1103
+ constructor(db, client) {
1104
+ super();
1105
+ this.db = db;
1106
+ this.client = client;
1107
+ }
1066
1108
  get _client() {
1067
1109
  return this.client;
1068
1110
  }
1111
+ /**
1112
+ * Per-client cache: whether transactions are unavailable (standalone MongoDB).
1113
+ * Shared across all adapter instances for the same client so topology is probed once.
1114
+ */
1115
+ static _txDisabledClients = /* @__PURE__ */ new WeakSet();
1069
1116
  get _txDisabled() {
1070
1117
  return this.client ? MongoAdapter._txDisabledClients.has(this.client) : true;
1071
1118
  }
@@ -1076,14 +1123,14 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1076
1123
  * Uses MongoDB's Convenient Transaction API (`session.withTransaction()`).
1077
1124
  * This handles txnNumber management and automatic retry for
1078
1125
  * `TransientTransactionError` / `UnknownTransactionCommitResult`.
1079
- */ async withTransaction(fn) {
1126
+ */
1127
+ async withTransaction(fn) {
1080
1128
  if (this._getTransactionState()) return fn();
1081
1129
  if (this._txDisabled || !this._client) return fn();
1082
1130
  try {
1083
1131
  const topology = this._client.topology;
1084
1132
  if (topology) {
1085
- const desc = topology.description ?? topology.s?.description;
1086
- const type = desc?.type;
1133
+ const type = (topology.description ?? topology.s?.description)?.type;
1087
1134
  if (type === "Single" || type === "Unknown") {
1088
1135
  this._txDisabled = true;
1089
1136
  return fn();
@@ -1106,7 +1153,9 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1106
1153
  } catch {}
1107
1154
  }
1108
1155
  }
1109
- /** Returns `{ session }` opts if inside a transaction, empty object otherwise. */ _getSessionOpts() {
1156
+ static _noSession = Object.freeze({});
1157
+ /** Returns `{ session }` opts if inside a transaction, empty object otherwise. */
1158
+ _getSessionOpts() {
1110
1159
  const session = this._getTransactionState();
1111
1160
  return session ? { session } : MongoAdapter._noSession;
1112
1161
  }
@@ -1118,11 +1167,11 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1118
1167
  return this.collection.aggregate(pipeline, this._getSessionOpts());
1119
1168
  }
1120
1169
  async aggregate(query) {
1121
- const { buildAggregatePipeline, buildCountPipeline } = await import("./agg-CUX5Jb_A.mjs");
1170
+ const { buildAggregatePipeline, buildCountPipeline } = await import("./agg.mjs");
1122
1171
  if (query.controls?.$count) {
1123
- const pipeline$1 = buildCountPipeline(query);
1124
- this._log("aggregate (count)", pipeline$1);
1125
- const result = await this.aggregatePipeline(pipeline$1).toArray();
1172
+ const pipeline = buildCountPipeline(query);
1173
+ this._log("aggregate (count)", pipeline);
1174
+ const result = await this.aggregatePipeline(pipeline).toArray();
1126
1175
  return result.length > 0 ? result : [{ count: 0 }];
1127
1176
  }
1128
1177
  const pipeline = buildAggregatePipeline(query);
@@ -1136,19 +1185,20 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1136
1185
  if (idProp?.type.kind === "") return idProp.type.designType;
1137
1186
  return "objectId";
1138
1187
  }
1139
- prepareId(id, fieldType) {
1188
+ prepareId(id, _fieldType) {
1189
+ const fieldType = _fieldType;
1140
1190
  const tags = fieldType.type.tags;
1141
1191
  if (tags?.has("objectId") && tags?.has("mongo")) return id instanceof ObjectId ? id : new ObjectId(id);
1142
1192
  if (fieldType.type.kind === "") {
1143
- const dt = fieldType.type.designType;
1144
- if (dt === "number") return Number(id);
1193
+ if (fieldType.type.designType === "number") return Number(id);
1145
1194
  }
1146
1195
  return String(id);
1147
1196
  }
1148
1197
  /**
1149
1198
  * Convenience method that uses `idType` to transform an ID value.
1150
1199
  * For use in controllers that don't have access to the field type.
1151
- */ prepareIdFromIdType(id) {
1200
+ */
1201
+ prepareIdFromIdType(id) {
1152
1202
  switch (this.idType) {
1153
1203
  case "objectId": return id instanceof ObjectId ? id : new ObjectId(id);
1154
1204
  case "number": return Number(id);
@@ -1165,26 +1215,24 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1165
1215
  getValidatorPlugins() {
1166
1216
  return [validateMongoIdPlugin];
1167
1217
  }
1168
- getAdapterTableName(type) {
1169
- return undefined;
1170
- }
1218
+ getAdapterTableName(_type) {}
1171
1219
  supportsNativeRelations() {
1172
1220
  return true;
1173
1221
  }
1174
1222
  async loadRelations(rows, withRelations, relations, foreignKeys, tableResolver) {
1175
1223
  return loadRelationsImpl(this, rows, withRelations, relations, foreignKeys, tableResolver);
1176
1224
  }
1177
- /** Returns the context object used by CollectionPatcher. */ getPatcherContext() {
1225
+ /** Returns the context object used by CollectionPatcher. */
1226
+ getPatcherContext() {
1178
1227
  return {
1179
1228
  flatMap: this._table.flatMap,
1180
1229
  prepareId: (id) => this.prepareIdFromIdType(id),
1181
1230
  createValidator: (opts) => this._table.createValidator(opts)
1182
1231
  };
1183
1232
  }
1184
- async nativePatch(filter, patch) {
1233
+ async nativePatch(filter, patch, ops) {
1185
1234
  const mongoFilter = buildMongoFilter(filter);
1186
- const patcher = new CollectionPatcher(this.getPatcherContext(), patch);
1187
- const { updateFilter, updateOptions } = patcher.preparePatch();
1235
+ const { updateFilter, updateOptions } = new CollectionPatcher(this.getPatcherContext(), patch, ops).preparePatch();
1188
1236
  this._log("updateOne (patch)", mongoFilter, updateFilter);
1189
1237
  const result = await this.collection.updateOne(mongoFilter, updateFilter, {
1190
1238
  ...updateOptions,
@@ -1195,8 +1243,8 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1195
1243
  modifiedCount: result.modifiedCount
1196
1244
  };
1197
1245
  }
1198
- onBeforeFlatten(type) {
1199
- const typeMeta = type.metadata;
1246
+ onBeforeFlatten(_type) {
1247
+ const typeMeta = _type.metadata;
1200
1248
  const capped = typeMeta.get("db.mongo.capped");
1201
1249
  if (capped) this._cappedOptions = {
1202
1250
  size: capped.size,
@@ -1214,7 +1262,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1214
1262
  text: { fuzzy: { maxEdits: textSearch.fuzzy || 0 } }
1215
1263
  });
1216
1264
  }
1217
- onFieldScanned(field, type, metadata) {
1265
+ onFieldScanned(field, _type, metadata) {
1218
1266
  if (field === "_id") this._hasExplicitId = true;
1219
1267
  if (field !== "_id" && metadata.has("meta.id")) {
1220
1268
  this._addMongoIndexField("unique", "__pk", field);
@@ -1223,7 +1271,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1223
1271
  if (metadata.has("db.default.increment")) {
1224
1272
  const physicalName = metadata.get("db.column") ?? field;
1225
1273
  const startValue = metadata.get("db.default.increment");
1226
- this._incrementFields.set(physicalName, typeof startValue === "number" ? startValue : undefined);
1274
+ this._incrementFields.set(physicalName, typeof startValue === "number" ? startValue : void 0);
1227
1275
  }
1228
1276
  for (const index of metadata.get("db.index.fulltext") || []) {
1229
1277
  const name = typeof index === "object" ? index.name || "" : "";
@@ -1241,7 +1289,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1241
1289
  numDimensions: vectorIndex.dimensions
1242
1290
  }] });
1243
1291
  const threshold = metadata.get("db.search.vector.threshold");
1244
- if (threshold !== undefined) this._vectorThresholds.set(mongoIndexKey("vector", indexName), threshold);
1292
+ if (threshold !== void 0) this._vectorThresholds.set(mongoIndexKey("vector", indexName), threshold);
1245
1293
  }
1246
1294
  for (const indexName of metadata.get("db.search.filter") || []) this._vectorFilters.set(mongoIndexKey("vector", indexName), field);
1247
1295
  }
@@ -1250,7 +1298,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1250
1298
  if (this._hasExplicitId) return {
1251
1299
  addPrimaryKeys: ["_id"],
1252
1300
  removePrimaryKeys: meta.originalMetaIdFields.filter((f) => f !== "_id"),
1253
- addUniqueFields: uniqueFields.length > 0 ? uniqueFields : undefined
1301
+ addUniqueFields: uniqueFields.length > 0 ? uniqueFields : void 0
1254
1302
  };
1255
1303
  uniqueFields.push("_id");
1256
1304
  return {
@@ -1263,7 +1311,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1263
1311
  designType: "string",
1264
1312
  tags: new Set(["objectId", "mongo"])
1265
1313
  },
1266
- metadata: new Map()
1314
+ metadata: /* @__PURE__ */ new Map()
1267
1315
  }
1268
1316
  }],
1269
1317
  addUniqueFields: uniqueFields
@@ -1278,14 +1326,15 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1278
1326
  });
1279
1327
  }
1280
1328
  for (const fd of this._table.fieldDescriptors) if (fd.collate && fd.collate !== "binary") {
1281
- if (!this._collateFields) this._collateFields = new Map();
1329
+ if (!this._collateFields) this._collateFields = /* @__PURE__ */ new Map();
1282
1330
  this._collateFields.set(fd.physicalName, fd.collate);
1283
1331
  }
1284
1332
  }
1285
- /** Returns MongoDB-specific search index map (internal). */ getMongoSearchIndexes() {
1333
+ /** Returns MongoDB-specific search index map (internal). */
1334
+ getMongoSearchIndexes() {
1286
1335
  if (!this._searchIndexesMap) {
1287
1336
  this._table.flatMap;
1288
- this._searchIndexesMap = new Map();
1337
+ this._searchIndexesMap = /* @__PURE__ */ new Map();
1289
1338
  let defaultIndex;
1290
1339
  for (const index of this._table.indexes.values()) if (index.type === "fulltext" && !defaultIndex) defaultIndex = {
1291
1340
  key: index.key,
@@ -1295,34 +1344,32 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1295
1344
  weights: Object.fromEntries(index.fields.filter((f) => f.weight).map((f) => [f.name, f.weight]))
1296
1345
  };
1297
1346
  for (const index of this._mongoIndexes.values()) switch (index.type) {
1298
- case "text": {
1347
+ case "text":
1299
1348
  if (!defaultIndex) defaultIndex = index;
1300
1349
  break;
1301
- }
1302
- case "dynamic_text": {
1350
+ case "dynamic_text":
1303
1351
  defaultIndex = index;
1304
1352
  break;
1305
- }
1306
- case "search_text": {
1353
+ case "search_text":
1307
1354
  if (!defaultIndex || defaultIndex.type === "text") defaultIndex = index;
1308
1355
  this._searchIndexesMap.set(index.name, index);
1309
1356
  break;
1310
- }
1311
- case "vector": {
1357
+ case "vector":
1312
1358
  this._searchIndexesMap.set(index.name, index);
1313
1359
  break;
1314
- }
1315
1360
  default:
1316
1361
  }
1317
- if (defaultIndex && !this._searchIndexesMap.has(DEFAULT_INDEX_NAME)) this._searchIndexesMap.set(DEFAULT_INDEX_NAME, defaultIndex);
1362
+ if (defaultIndex && !this._searchIndexesMap.has("DEFAULT")) this._searchIndexesMap.set(DEFAULT_INDEX_NAME, defaultIndex);
1318
1363
  }
1319
1364
  return this._searchIndexesMap;
1320
1365
  }
1321
- /** Returns a specific MongoDB search index by name. */ getMongoSearchIndex(name = DEFAULT_INDEX_NAME) {
1366
+ /** Returns a specific MongoDB search index by name. */
1367
+ getMongoSearchIndex(name = DEFAULT_INDEX_NAME) {
1322
1368
  return this.getMongoSearchIndexes().get(name);
1323
1369
  }
1324
- /** Returns the default similarity threshold for a vector index (from @db.search.vector.threshold). */ getVectorThreshold(indexName) {
1325
- const key = mongoIndexKey("vector", indexName || DEFAULT_INDEX_NAME);
1370
+ /** Returns the default similarity threshold for a vector index (from @db.search.vector.threshold). */
1371
+ getVectorThreshold(indexName) {
1372
+ const key = mongoIndexKey("vector", indexName || "DEFAULT");
1326
1373
  return this._vectorThresholds.get(key);
1327
1374
  }
1328
1375
  getSearchIndexes() {
@@ -1366,18 +1413,16 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1366
1413
  };
1367
1414
  }
1368
1415
  async collectionExists() {
1369
- const cols = await this.db.listCollections({ name: this._table.tableName }).toArray();
1370
- return cols.length > 0;
1416
+ return (await this.db.listCollections({ name: this._table.tableName }).toArray()).length > 0;
1371
1417
  }
1372
1418
  async ensureCollectionExists() {
1373
- const exists = await this.collectionExists();
1374
- if (!exists) {
1419
+ if (!await this.collectionExists()) {
1375
1420
  this._log("createCollection", this._table.tableName);
1376
1421
  const opts = { comment: "Created by Atscript Mongo Adapter" };
1377
1422
  if (this._cappedOptions) {
1378
1423
  opts.capped = true;
1379
1424
  opts.size = this._cappedOptions.size;
1380
- if (this._cappedOptions.max !== null && this._cappedOptions.max !== undefined) opts.max = this._cappedOptions.max;
1425
+ if (this._cappedOptions.max !== null && this._cappedOptions.max !== void 0) opts.max = this._cappedOptions.max;
1381
1426
  }
1382
1427
  await this.db.createCollection(this._table.tableName, opts);
1383
1428
  }
@@ -1385,17 +1430,15 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1385
1430
  /**
1386
1431
  * Wraps an async operation to catch MongoDB duplicate key errors
1387
1432
  * (code 11000) and rethrow as structured `DbError`.
1388
- */ async _wrapDuplicateKeyError(fn) {
1433
+ */
1434
+ async _wrapDuplicateKeyError(fn) {
1389
1435
  try {
1390
1436
  return await fn();
1391
1437
  } catch (error) {
1392
- if (error instanceof MongoServerError && error.code === 11e3) {
1393
- const field = error.keyPattern ? Object.keys(error.keyPattern)[0] ?? "" : "";
1394
- throw new DbError("CONFLICT", [{
1395
- path: field,
1396
- message: error.message
1397
- }]);
1398
- }
1438
+ if (error instanceof MongoServerError && error.code === 11e3) throw new DbError("CONFLICT", [{
1439
+ path: error.keyPattern ? Object.keys(error.keyPattern)[0] ?? "" : "",
1440
+ message: error.message
1441
+ }]);
1399
1442
  throw error;
1400
1443
  }
1401
1444
  }
@@ -1413,7 +1456,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1413
1456
  }
1414
1457
  async insertMany(data) {
1415
1458
  if (this._incrementFields.size > 0) {
1416
- const allFields = new Set();
1459
+ const allFields = /* @__PURE__ */ new Set();
1417
1460
  for (const item of data) for (const f of this._fieldsNeedingIncrement(item)) allFields.add(f);
1418
1461
  if (allFields.size > 0) await this._assignBatchIncrements(data, allFields);
1419
1462
  }
@@ -1452,10 +1495,11 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1452
1495
  ...this._getSessionOpts()
1453
1496
  });
1454
1497
  }
1455
- async updateOne(filter, data) {
1498
+ async updateOne(filter, data, ops) {
1456
1499
  const mongoFilter = buildMongoFilter(filter);
1457
- this._log("updateOne", mongoFilter, { $set: data });
1458
- const result = await this.collection.updateOne(mongoFilter, { $set: data }, this._getSessionOpts());
1500
+ const updateDoc = buildMongoUpdateDoc(data, ops);
1501
+ this._log("updateOne", mongoFilter, updateDoc);
1502
+ const result = await this.collection.updateOne(mongoFilter, updateDoc, this._getSessionOpts());
1459
1503
  return {
1460
1504
  matchedCount: result.matchedCount,
1461
1505
  modifiedCount: result.modifiedCount
@@ -1473,13 +1517,13 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1473
1517
  async deleteOne(filter) {
1474
1518
  const mongoFilter = buildMongoFilter(filter);
1475
1519
  this._log("deleteOne", mongoFilter);
1476
- const result = await this.collection.deleteOne(mongoFilter, this._getSessionOpts());
1477
- return { deletedCount: result.deletedCount };
1520
+ return { deletedCount: (await this.collection.deleteOne(mongoFilter, this._getSessionOpts())).deletedCount };
1478
1521
  }
1479
- async updateMany(filter, data) {
1522
+ async updateMany(filter, data, ops) {
1480
1523
  const mongoFilter = buildMongoFilter(filter);
1481
- this._log("updateMany", mongoFilter, { $set: data });
1482
- const result = await this.collection.updateMany(mongoFilter, { $set: data }, this._getSessionOpts());
1524
+ const updateDoc = buildMongoUpdateDoc(data, ops);
1525
+ this._log("updateMany", mongoFilter, updateDoc);
1526
+ const result = await this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts());
1483
1527
  return {
1484
1528
  matchedCount: result.matchedCount,
1485
1529
  modifiedCount: result.modifiedCount
@@ -1497,11 +1541,10 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1497
1541
  async deleteMany(filter) {
1498
1542
  const mongoFilter = buildMongoFilter(filter);
1499
1543
  this._log("deleteMany", mongoFilter);
1500
- const result = await this.collection.deleteMany(mongoFilter, this._getSessionOpts());
1501
- return { deletedCount: result.deletedCount };
1544
+ return { deletedCount: (await this.collection.deleteMany(mongoFilter, this._getSessionOpts())).deletedCount };
1502
1545
  }
1503
1546
  clearCollectionCache() {
1504
- this._collection = undefined;
1547
+ this._collection = void 0;
1505
1548
  }
1506
1549
  async tableExists() {
1507
1550
  return tableExistsImpl(this);
@@ -1542,29 +1585,31 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1542
1585
  destructiveOptionKeys() {
1543
1586
  return DESTRUCTIVE_OPTION_KEYS;
1544
1587
  }
1545
- /** Returns the counters collection used for atomic auto-increment. */ get _countersCollection() {
1588
+ /** Returns the counters collection used for atomic auto-increment. */
1589
+ get _countersCollection() {
1546
1590
  return this.db.collection("__atscript_counters");
1547
1591
  }
1548
- /** Returns physical field names of increment fields that are undefined in the data. */ _fieldsNeedingIncrement(data) {
1592
+ /** Returns physical field names of increment fields that are undefined in the data. */
1593
+ _fieldsNeedingIncrement(data) {
1549
1594
  const result = [];
1550
- for (const physical of this._incrementFields.keys()) if (data[physical] === undefined || data[physical] === null) result.push(physical);
1595
+ for (const physical of this._incrementFields.keys()) if (data[physical] === void 0 || data[physical] === null) result.push(physical);
1551
1596
  return result;
1552
1597
  }
1553
1598
  /**
1554
1599
  * Atomically allocates `count` sequential values for each increment field
1555
1600
  * using a counter collection. Returns a map of field → first allocated value.
1556
- */ async _allocateIncrementValues(physicalFields, count) {
1601
+ */
1602
+ async _allocateIncrementValues(physicalFields, count) {
1557
1603
  const counters = this._countersCollection;
1558
1604
  const collectionName = this._table.tableName;
1559
- const result = new Map();
1605
+ const result = /* @__PURE__ */ new Map();
1560
1606
  for (const field of physicalFields) {
1561
1607
  const counterId = `${collectionName}.${field}`;
1562
1608
  const startValue = this._incrementFields.get(field);
1563
- const doc = await counters.findOneAndUpdate({ _id: counterId }, { $inc: { seq: count } }, {
1609
+ const seq = (await counters.findOneAndUpdate({ _id: counterId }, { $inc: { seq: count } }, {
1564
1610
  upsert: true,
1565
1611
  returnDocument: "after"
1566
- });
1567
- const seq = doc?.seq ?? count;
1612
+ }))?.seq ?? count;
1568
1613
  if (seq === count) {
1569
1614
  const currentMax = await this._getCurrentFieldMax(field);
1570
1615
  const minStart = typeof startValue === "number" ? startValue : 1;
@@ -1580,7 +1625,8 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1580
1625
  }
1581
1626
  return result;
1582
1627
  }
1583
- /** Reads current max value for a single field via $group aggregation. */ async _getCurrentFieldMax(field) {
1628
+ /** Reads current max value for a single field via $group aggregation. */
1629
+ async _getCurrentFieldMax(field) {
1584
1630
  const alias = `max__${field.replace(/\./g, "__")}`;
1585
1631
  const agg = await this.collection.aggregate([{ $group: {
1586
1632
  _id: null,
@@ -1592,19 +1638,20 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1592
1638
  }
1593
1639
  return 0;
1594
1640
  }
1595
- /** Allocates increment values for a batch of items, assigning in order. */ async _assignBatchIncrements(data, allFields) {
1596
- const fieldCounts = new Map();
1641
+ /** Allocates increment values for a batch of items, assigning in order. */
1642
+ async _assignBatchIncrements(data, allFields) {
1643
+ const fieldCounts = /* @__PURE__ */ new Map();
1597
1644
  for (const physical of allFields) {
1598
1645
  let count = 0;
1599
- for (const item of data) if (item[physical] === undefined || item[physical] === null) count++;
1646
+ for (const item of data) if (item[physical] === void 0 || item[physical] === null) count++;
1600
1647
  if (count > 0) fieldCounts.set(physical, count);
1601
1648
  }
1602
- const fieldCounters = new Map();
1649
+ const fieldCounters = /* @__PURE__ */ new Map();
1603
1650
  for (const [physical, count] of fieldCounts) {
1604
1651
  const allocated = await this._allocateIncrementValues([physical], count);
1605
1652
  fieldCounters.set(physical, allocated.get(physical) ?? 1);
1606
1653
  }
1607
- for (const item of data) for (const physical of allFields) if (item[physical] === undefined || item[physical] === null) {
1654
+ for (const item of data) for (const physical of allFields) if (item[physical] === void 0 || item[physical] === null) {
1608
1655
  const next = fieldCounters.get(physical) ?? 1;
1609
1656
  item[physical] = next;
1610
1657
  fieldCounters.set(physical, next + 1);
@@ -1623,8 +1670,9 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1623
1670
  * Returns MongoDB collation options if any filter field has a non-binary collation.
1624
1671
  * Uses pre-computed insights when available, falls back to computing them on demand.
1625
1672
  * Maps: nocase → strength 2 (case-insensitive), unicode → strength 1 (case+accent-insensitive).
1626
- */ _getCollationOpts(query) {
1627
- if (!this._collateFields) return undefined;
1673
+ */
1674
+ _getCollationOpts(query) {
1675
+ if (!this._collateFields) return;
1628
1676
  const insights = query.insights ?? computeInsights(query.filter);
1629
1677
  let strength;
1630
1678
  for (const field of insights.keys()) {
@@ -1638,14 +1686,14 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
1638
1686
  return strength ? { collation: {
1639
1687
  locale: "en",
1640
1688
  strength
1641
- } } : undefined;
1689
+ } } : void 0;
1642
1690
  }
1643
1691
  _addMongoIndexField(type, name, field, weight) {
1644
1692
  const key = mongoIndexKey(type, name);
1645
1693
  let index = this._mongoIndexes.get(key);
1646
1694
  const value = type === "text" ? "text" : 1;
1647
1695
  if (index) index.fields[field] = value;
1648
- else {
1696
+ else {
1649
1697
  index = {
1650
1698
  key,
1651
1699
  name,
@@ -1658,16 +1706,16 @@ else {
1658
1706
  if (weight) index.weights[field] = weight;
1659
1707
  }
1660
1708
  _setSearchIndex(type, name, definition) {
1661
- const key = mongoIndexKey(type, name || DEFAULT_INDEX_NAME);
1709
+ const key = mongoIndexKey(type, name || "DEFAULT");
1662
1710
  this._mongoIndexes.set(key, {
1663
1711
  key,
1664
- name: name || DEFAULT_INDEX_NAME,
1712
+ name: name || "DEFAULT",
1665
1713
  type,
1666
1714
  definition
1667
1715
  });
1668
1716
  }
1669
1717
  _addFieldToSearchIndex(type, _name, fieldName, analyzer) {
1670
- const name = _name || DEFAULT_INDEX_NAME;
1718
+ const name = _name || "DEFAULT";
1671
1719
  let index = this._mongoIndexes.get(mongoIndexKey(type, name));
1672
1720
  if (!index && type === "search_text") {
1673
1721
  this._setSearchIndex(type, name, {
@@ -1681,23 +1729,30 @@ else {
1681
1729
  if (analyzer) index.definition.mappings.fields[fieldName].analyzer = analyzer;
1682
1730
  }
1683
1731
  }
1684
- constructor(db, client) {
1685
- 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, "_vectorThresholds", void 0), _define_property(this, "_searchIndexesMap", void 0), _define_property(this, "_incrementFields", void 0), _define_property(this, "_collateFields", void 0), _define_property(this, "_cappedOptions", void 0), _define_property(this, "_hasExplicitId", void 0), _define_property(this, "_pendingUniqueFields", void 0), this.db = db, this.client = client, this._mongoIndexes = new Map(), this._vectorFilters = new Map(), this._vectorThresholds = new Map(), this._incrementFields = new Map(), this._hasExplicitId = false, this._pendingUniqueFields = [];
1686
- }
1687
1732
  };
1688
1733
  /**
1689
- * Per-client cache: whether transactions are unavailable (standalone MongoDB).
1690
- * Shared across all adapter instances for the same client so topology is probed once.
1691
- */ _define_property(MongoAdapter, "_txDisabledClients", new WeakSet());
1692
- _define_property(MongoAdapter, "_noSession", Object.freeze({}));
1693
-
1734
+ * Builds a MongoDB update document from a data object that may contain
1735
+ * field ops (`{ $inc: N }`, `{ $dec: N }`, `{ $mul: N }`).
1736
+ * Regular fields go into `$set`, ops go into `$inc` / `$mul`.
1737
+ */
1738
+ function buildMongoUpdateDoc(data, ops) {
1739
+ const updateDoc = {};
1740
+ let hasData = false;
1741
+ for (const _ in data) {
1742
+ hasData = true;
1743
+ break;
1744
+ }
1745
+ if (hasData) updateDoc.$set = data;
1746
+ if (ops?.inc) updateDoc.$inc = ops.inc;
1747
+ if (ops?.mul) updateDoc.$mul = ops.mul;
1748
+ return updateDoc;
1749
+ }
1694
1750
  //#endregion
1695
- //#region packages/db-mongo/src/lib/index.ts
1751
+ //#region src/lib/index.ts
1696
1752
  function createAdapter(connection, _options) {
1697
1753
  const client = new MongoClient(connection);
1698
1754
  const db = client.db();
1699
1755
  return new DbSpace(() => new MongoAdapter(db, client));
1700
1756
  }
1701
-
1702
1757
  //#endregion
1703
- export { CollectionPatcher, MongoAdapter, buildMongoFilter, createAdapter, validateMongoIdPlugin };
1758
+ export { CollectionPatcher, MongoAdapter, buildMongoFilter, createAdapter, validateMongoIdPlugin };