@atscript/db-mongo 0.1.39 → 0.1.41

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