@atscript/db-mongo 0.1.38 → 0.1.40

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