monsqlize 3.1.14 → 3.3.0

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.
@@ -39,6 +39,14 @@ var ErrorCodes = {
39
39
  INVALID_EXPRESSION: "INVALID_EXPRESSION",
40
40
  INVALID_PAGINATION: "INVALID_PAGINATION",
41
41
  INVALID_OPERATION: "INVALID_OPERATION",
42
+ /** Invalid local Vector Search options before the driver is called. */
43
+ INVALID_VECTOR_SEARCH: "INVALID_VECTOR_SEARCH",
44
+ /** Relation select is outside the supported inclusion-only top-level contract. */
45
+ INVALID_RELATION_SELECT: "INVALID_RELATION_SELECT",
46
+ /** A protected delete found declared Model relations that still reference its target. */
47
+ RELATION_IN_USE: "RELATION_IN_USE",
48
+ /** Relation usage coverage is incomplete, so a protected delete must not continue. */
49
+ RELATION_USAGE_UNAVAILABLE: "RELATION_USAGE_UNAVAILABLE",
42
50
  CACHE_UNAVAILABLE: "CACHE_UNAVAILABLE",
43
51
  MANAGEMENT_OPERATION_FAILED: "MANAGEMENT_OPERATION_FAILED",
44
52
  NOT_CONNECTED: "NOT_CONNECTED",
@@ -484,12 +492,49 @@ function validateRelationConfig(name, config) {
484
492
  if (config.single !== void 0 && typeof config.single !== "boolean") {
485
493
  throw createError(ErrorCodes.INVALID_ARGUMENT, `relations.single must be a boolean`);
486
494
  }
495
+ normalizeRelationSelect(config.select, { relation: name });
496
+ }
497
+ function normalizeRelationSelect(value, context = {}) {
498
+ if (value === void 0) {
499
+ return void 0;
500
+ }
501
+ const invalid2 = (reason) => {
502
+ throw createError(
503
+ ErrorCodes.INVALID_RELATION_SELECT,
504
+ `Relation select must use non-empty top-level inclusion fields: ${reason}`,
505
+ [{ ...context, value, reason }]
506
+ );
507
+ };
508
+ const rawTokens = typeof value === "string" ? [value] : Array.isArray(value) ? value : invalid2("select must be a string or string array");
509
+ const tokens = [];
510
+ for (const rawToken of rawTokens) {
511
+ if (typeof rawToken !== "string") {
512
+ invalid2("select array entries must be strings");
513
+ }
514
+ const parts = rawToken.trim().split(/\s+/).filter(Boolean);
515
+ if (parts.length === 0) {
516
+ invalid2("select must not be empty");
517
+ }
518
+ tokens.push(...parts);
519
+ }
520
+ for (const token of tokens) {
521
+ if (token.startsWith("-") || token.startsWith("+")) {
522
+ invalid2("exclusion and prefix tokens are not supported");
523
+ }
524
+ if (token.includes(".")) {
525
+ invalid2("dot-path fields are not supported");
526
+ }
527
+ }
528
+ return [...new Set(tokens)];
487
529
  }
488
530
  function normalizePopulateConfig(path5) {
489
531
  return typeof path5 === "string" ? { path: path5 } : path5;
490
532
  }
491
533
 
492
534
  // src/capabilities/model/model-registry.ts
535
+ function defineModel(collectionName, definition) {
536
+ return { collectionName, definition };
537
+ }
493
538
  var Model = class {
494
539
  static {
495
540
  // collection name → registered model definition
@@ -504,21 +549,19 @@ var Model = class {
504
549
  /** Collection names that have been redefined (used by the runtime to trigger hot-reload). */
505
550
  this._redefinedNames = /* @__PURE__ */ new Set();
506
551
  }
507
- /**
508
- * Register a new model definition.
509
- * Throws MODEL_ALREADY_EXISTS if a model with the same name already exists.
510
- * Validates the definition and resolves the timestamps option.
511
- */
512
- static define(collectionName, definition) {
552
+ static define(collectionNameOrDescriptor, definition) {
553
+ const collectionName = typeof collectionNameOrDescriptor === "string" ? collectionNameOrDescriptor : collectionNameOrDescriptor.collectionName;
554
+ const modelDefinition = typeof collectionNameOrDescriptor === "string" ? definition : collectionNameOrDescriptor.definition;
513
555
  const normalizedName = validateCollectionName(collectionName);
514
556
  if (this.registry.has(normalizedName)) {
515
557
  throw createError(ErrorCodes.MODEL_ALREADY_EXISTS, `Model '${normalizedName}' is already defined.`);
516
558
  }
517
- validateDefinition(definition);
518
- processTimestamps(definition);
559
+ validateDefinition(modelDefinition);
560
+ const resolvedDefinition = modelDefinition;
561
+ processTimestamps(resolvedDefinition);
519
562
  this.registry.set(normalizedName, {
520
563
  collectionName: normalizedName,
521
- definition
564
+ definition: resolvedDefinition
522
565
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
523
566
  });
524
567
  this.bumpRevision(normalizedName);
@@ -1060,6 +1103,56 @@ function resolveRegisteredCollectionName(registered, fallback) {
1060
1103
  const definition = registered.definition;
1061
1104
  return definition.collection ?? definition.name ?? registered.collectionName;
1062
1105
  }
1106
+ function flattenComparableValues(value) {
1107
+ if (Array.isArray(value)) {
1108
+ return value.flatMap((entry) => flattenComparableValues(entry));
1109
+ }
1110
+ return value === void 0 || value === null ? [] : [value];
1111
+ }
1112
+ function resolveNestedPopulatePaths(value) {
1113
+ if (value === void 0) {
1114
+ return [];
1115
+ }
1116
+ const paths = Array.isArray(value) ? value : [value];
1117
+ for (const path5 of paths) {
1118
+ const valid = typeof path5 === "string" || typeof path5 === "object" && path5 !== null && !Array.isArray(path5) && Boolean(path5.path);
1119
+ if (!valid) {
1120
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "nested populate must be a string, array, or object");
1121
+ }
1122
+ }
1123
+ return paths;
1124
+ }
1125
+ function collectNestedLocalFields(relatedModel, nestedPaths) {
1126
+ if (!relatedModel) {
1127
+ return [];
1128
+ }
1129
+ const relations = relatedModel.getRelations();
1130
+ return unique(
1131
+ nestedPaths.map((path5) => relations[normalizePopulateConfig(path5).path]?.localField).filter((field) => Boolean(field))
1132
+ );
1133
+ }
1134
+ function dedupeDocuments(documents) {
1135
+ const seen = /* @__PURE__ */ new Set();
1136
+ return documents.filter((document) => {
1137
+ if (seen.has(document)) {
1138
+ return false;
1139
+ }
1140
+ seen.add(document);
1141
+ return true;
1142
+ });
1143
+ }
1144
+ function rebuildPopulateMatches(localValue, grouped, sort) {
1145
+ const matches = flattenComparableValues(localValue).flatMap((value) => grouped.get(toKey(value)) ?? []);
1146
+ const deduped = dedupeDocuments(matches);
1147
+ return sort ? applySort(deduped, sort) : deduped;
1148
+ }
1149
+ function projectPopulatedDocument(document, select, nestedPaths) {
1150
+ if (!select) {
1151
+ return document;
1152
+ }
1153
+ const nestedRelationFields = nestedPaths.map((path5) => normalizePopulateConfig(path5).path);
1154
+ return pickFields(document, [.../* @__PURE__ */ new Set([...select, ...nestedRelationFields])]);
1155
+ }
1063
1156
  async function populateModelPath(context, docs, path5, traversalState) {
1064
1157
  const config = normalizePopulateConfig(path5);
1065
1158
  const state = resolvePopulateState(config, traversalState);
@@ -1078,9 +1171,8 @@ async function populateModelPath(context, docs, path5, traversalState) {
1078
1171
  if (!relation) {
1079
1172
  throw createError(ErrorCodes.INVALID_ARGUMENT, `Undefined relation: ${config.path}`);
1080
1173
  }
1081
- const keys = unique(
1082
- docs.map((doc) => getByPath(doc, relation.localField)).filter((value) => value !== void 0 && value !== null)
1083
- );
1174
+ const localValues = docs.map((doc) => getByPath(doc, relation.localField));
1175
+ const keys = unique(localValues.flatMap((value) => flattenComparableValues(value)));
1084
1176
  if (keys.length === 0) {
1085
1177
  for (const doc of docs) {
1086
1178
  doc[config.path] = relation.single ? null : [];
@@ -1095,51 +1187,42 @@ async function populateModelPath(context, docs, path5, traversalState) {
1095
1187
  const relatedCollectionName = resolveRegisteredCollectionName(registered, relation.from);
1096
1188
  const relatedCollection = context.runtime.scopedCollection(relatedCollectionName, scope);
1097
1189
  const relatedModel = Model.has(relation.from) ? context.runtime.scopedModel(relation.from, scope) : null;
1190
+ const nestedPaths = resolveNestedPopulatePaths(config.populate);
1191
+ const effectiveSelect = normalizeRelationSelect(config.select, { path: config.path }) ?? normalizeRelationSelect(relation.select, { relation: config.path });
1192
+ const projectionFields = effectiveSelect ? [.../* @__PURE__ */ new Set([
1193
+ ...effectiveSelect,
1194
+ relation.foreignField,
1195
+ "_id",
1196
+ ...collectNestedLocalFields(relatedModel, nestedPaths)
1197
+ ])] : [];
1098
1198
  const relatedDocs = await relatedCollection.find({
1099
1199
  [relation.foreignField]: { $in: keys },
1100
1200
  ...config.match ?? {}
1101
- });
1201
+ }, effectiveSelect ? {
1202
+ projection: Object.fromEntries(projectionFields.map((field) => [field, 1]))
1203
+ } : void 0);
1102
1204
  let hydrated = relatedModel ? relatedModel.hydrateDocuments(relatedDocs) : relatedDocs.map((item) => ({ ...item }));
1103
- if (config.sort) {
1104
- hydrated = applySort(hydrated, config.sort);
1105
- }
1106
- if (config.select) {
1107
- const select = config.select;
1108
- hydrated = hydrated.map((item) => pickFields(item, select, [relation.foreignField]));
1109
- }
1110
- if (config.populate) {
1111
- const nestedRaw = config.populate;
1112
- const isValidNestedConfig = (value) => {
1113
- if (typeof value === "string" || Array.isArray(value)) return true;
1114
- if (typeof value === "object" && value !== null && value.path) return true;
1115
- return false;
1116
- };
1117
- if (!isValidNestedConfig(nestedRaw)) {
1118
- throw createError(ErrorCodes.INVALID_ARGUMENT, "nested populate must be a string, array, or object");
1119
- }
1120
- if (relatedModel) {
1121
- if (state.depth + 1 > state.maxDepth) {
1122
- throw createError(ErrorCodes.INVALID_ARGUMENT, `populate maxDepth exceeded: ${state.maxDepth}`);
1123
- }
1124
- const nestedPaths = Array.isArray(config.populate) ? config.populate : [config.populate];
1125
- hydrated = await relatedModel.populateDocuments(hydrated, nestedPaths, {
1126
- depth: state.depth + 1,
1127
- maxDepth: state.maxDepth,
1128
- activeConfigs: state.activeConfigs
1129
- });
1205
+ if (nestedPaths.length > 0 && relatedModel) {
1206
+ if (state.depth + 1 > state.maxDepth) {
1207
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `populate maxDepth exceeded: ${state.maxDepth}`);
1130
1208
  }
1209
+ hydrated = await relatedModel.populateDocuments(hydrated, nestedPaths, {
1210
+ depth: state.depth + 1,
1211
+ maxDepth: state.maxDepth,
1212
+ activeConfigs: state.activeConfigs
1213
+ });
1131
1214
  }
1132
1215
  const grouped = groupBy(hydrated, (item) => getByPath(item, relation.foreignField));
1133
- for (const doc of docs) {
1134
- const localValue = getByPath(doc, relation.localField);
1135
- let matches = grouped.get(toKey(localValue)) ?? [];
1216
+ for (const [index, doc] of docs.entries()) {
1217
+ let matches = rebuildPopulateMatches(localValues[index], grouped, config.sort);
1136
1218
  if (config.skip) {
1137
1219
  matches = matches.slice(config.skip);
1138
1220
  }
1139
1221
  if (config.limit !== void 0) {
1140
1222
  matches = matches.slice(0, config.limit);
1141
1223
  }
1142
- doc[config.path] = relation.single ? matches[0] ?? null : [...matches];
1224
+ const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
1225
+ doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
1143
1226
  }
1144
1227
  return docs;
1145
1228
  } finally {
@@ -5557,7 +5640,382 @@ async function orchestrateModelDeleteMany(context, filter, options) {
5557
5640
  return result;
5558
5641
  }
5559
5642
 
5643
+ // src/capabilities/model/relation-usage.ts
5644
+ var DEFAULT_MAX_TARGETS = 100;
5645
+ var DEFAULT_MAX_SAMPLES = 20;
5646
+ function isNonArrayObject(value) {
5647
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5648
+ }
5649
+ function normalizePositiveInteger(value, field, fallback) {
5650
+ if (value === void 0) {
5651
+ return fallback;
5652
+ }
5653
+ if (!Number.isInteger(value) || value <= 0) {
5654
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `${field} must be a positive integer`, [{ field, value }]);
5655
+ }
5656
+ return value;
5657
+ }
5658
+ function normalizeRelationNames(value, field) {
5659
+ if (value === void 0) {
5660
+ return void 0;
5661
+ }
5662
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim().length === 0)) {
5663
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `${field} must be an array of non-empty strings`, [{ field, value }]);
5664
+ }
5665
+ return [...new Set(value.map((entry) => entry.trim()))];
5666
+ }
5667
+ function normalizeRelationUsageOptions(options) {
5668
+ if (options !== void 0 && !isNonArrayObject(options)) {
5669
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "relation usage options must be a non-array object", [{ field: "options", value: options }]);
5670
+ }
5671
+ const value = options ?? {};
5672
+ if (value.includeSoftDeletedReferences !== void 0 && typeof value.includeSoftDeletedReferences !== "boolean") {
5673
+ throw createError(
5674
+ ErrorCodes.INVALID_ARGUMENT,
5675
+ "includeSoftDeletedReferences must be a boolean",
5676
+ [{ field: "includeSoftDeletedReferences", value: value.includeSoftDeletedReferences }]
5677
+ );
5678
+ }
5679
+ return {
5680
+ session: value.session,
5681
+ maxTargets: normalizePositiveInteger(value.maxTargets, "maxTargets", DEFAULT_MAX_TARGETS),
5682
+ maxSamples: normalizePositiveInteger(value.maxSamples, "maxSamples", DEFAULT_MAX_SAMPLES),
5683
+ includeRelations: normalizeRelationNames(value.includeRelations, "includeRelations"),
5684
+ excludeRelations: normalizeRelationNames(value.excludeRelations, "excludeRelations"),
5685
+ includeSoftDeletedReferences: value.includeSoftDeletedReferences !== false
5686
+ };
5687
+ }
5688
+ function resolveRegisteredCollectionName2(registered) {
5689
+ const definition = registered.definition;
5690
+ return definition.collection ?? definition.name ?? registered.collectionName;
5691
+ }
5692
+ function flattenComparableValues2(value) {
5693
+ if (Array.isArray(value)) {
5694
+ return value.flatMap((entry) => flattenComparableValues2(entry));
5695
+ }
5696
+ return value === void 0 || value === null ? [] : [value];
5697
+ }
5698
+ function isRelationSelected(options, sourceModel, relation) {
5699
+ const qualified = `${sourceModel}.${relation}`;
5700
+ const matches = (names) => Boolean(names?.includes(qualified) || names?.includes(relation));
5701
+ if (options.includeRelations && !matches(options.includeRelations)) {
5702
+ return false;
5703
+ }
5704
+ return !matches(options.excludeRelations);
5705
+ }
5706
+ function describeError(error) {
5707
+ return error instanceof Error ? error.message : String(error);
5708
+ }
5709
+ function findDeclaredInboundRelations(targetModelName) {
5710
+ const relations = [];
5711
+ for (const sourceModel of Model.list()) {
5712
+ const registered = Model.get(sourceModel);
5713
+ if (!registered) {
5714
+ continue;
5715
+ }
5716
+ for (const [relation, config] of Object.entries(registered.definition.relations ?? {})) {
5717
+ if (config.from !== targetModelName) {
5718
+ continue;
5719
+ }
5720
+ relations.push({
5721
+ sourceModel,
5722
+ sourceCollection: resolveRegisteredCollectionName2(registered),
5723
+ relation,
5724
+ definition: registered.definition,
5725
+ localField: config.localField,
5726
+ foreignField: config.foreignField
5727
+ });
5728
+ }
5729
+ }
5730
+ return relations;
5731
+ }
5732
+ function buildRelationUsageTargetProjection(targetModelName) {
5733
+ const fields = /* @__PURE__ */ new Set(["_id"]);
5734
+ for (const relation of findDeclaredInboundRelations(targetModelName)) {
5735
+ fields.add(relation.foreignField);
5736
+ }
5737
+ return Object.fromEntries([...fields].map((field) => [field, 1]));
5738
+ }
5739
+ function createUnavailableRelationUsageReport(context, reason) {
5740
+ return {
5741
+ used: false,
5742
+ target: {
5743
+ matchedCount: 0,
5744
+ truncated: false,
5745
+ sampleIds: []
5746
+ },
5747
+ checkedRelations: 0,
5748
+ usages: [],
5749
+ coverage: {
5750
+ mode: "registered-declared",
5751
+ complete: false,
5752
+ registryModels: Model.list(),
5753
+ checkedModels: [],
5754
+ skipped: [{ sourceModel: context.modelName, reason }]
5755
+ }
5756
+ };
5757
+ }
5758
+ async function inspectDeclaredRelationUsage(context, targets, inputOptions, targetTruncated = false) {
5759
+ const options = normalizeRelationUsageOptions(inputOptions);
5760
+ const registryModels = Model.list();
5761
+ const checkedModels = /* @__PURE__ */ new Set();
5762
+ const skipped = [];
5763
+ const usages = [];
5764
+ let checkedRelations = 0;
5765
+ for (const inbound of findDeclaredInboundRelations(context.modelName)) {
5766
+ if (!isRelationSelected(options, inbound.sourceModel, inbound.relation)) {
5767
+ skipped.push({
5768
+ sourceModel: inbound.sourceModel,
5769
+ reason: `relation '${inbound.sourceModel}.${inbound.relation}' was excluded by relation selection`
5770
+ });
5771
+ continue;
5772
+ }
5773
+ checkedRelations += 1;
5774
+ const targetValues = unique(
5775
+ targets.flatMap((target) => flattenComparableValues2(getByPath(target, inbound.foreignField)))
5776
+ );
5777
+ if (targetValues.length === 0) {
5778
+ checkedModels.add(inbound.sourceModel);
5779
+ continue;
5780
+ }
5781
+ const scope = {
5782
+ database: inbound.definition.connection?.database ?? context.dbName,
5783
+ pool: inbound.definition.connection?.pool ?? context.poolName
5784
+ };
5785
+ const baseQuery = {
5786
+ [inbound.localField]: { $in: targetValues }
5787
+ };
5788
+ const sourceSoftDelete = resolveModelSoftDeleteConfig(inbound.definition);
5789
+ const query = !options.includeSoftDeletedReferences && sourceSoftDelete?.enabled ? { $and: [baseQuery, { [sourceSoftDelete.field]: null }] } : baseQuery;
5790
+ try {
5791
+ const collection = context.runtime.scopedCollection(inbound.sourceCollection, scope);
5792
+ if (!collection) {
5793
+ throw new Error("scopedCollection returned no collection");
5794
+ }
5795
+ checkedModels.add(inbound.sourceModel);
5796
+ const count = await collection.count(query, { session: options.session });
5797
+ if (typeof count !== "number" || !Number.isFinite(count) || count < 0) {
5798
+ throw new Error("relation usage count did not return a non-negative number");
5799
+ }
5800
+ if (count === 0) {
5801
+ continue;
5802
+ }
5803
+ const samples = await collection.find(query, {
5804
+ session: options.session,
5805
+ projection: { _id: 1 },
5806
+ limit: options.maxSamples
5807
+ });
5808
+ usages.push({
5809
+ sourceModel: inbound.sourceModel,
5810
+ sourceCollection: inbound.sourceCollection,
5811
+ relation: inbound.relation,
5812
+ localField: inbound.localField,
5813
+ foreignField: inbound.foreignField,
5814
+ count,
5815
+ sampleIds: samples.filter((sample) => isNonArrayObject(sample)).map((sample) => sample._id).filter((id) => id !== void 0).slice(0, options.maxSamples)
5816
+ });
5817
+ } catch (error) {
5818
+ skipped.push({
5819
+ sourceModel: inbound.sourceModel,
5820
+ reason: describeError(error)
5821
+ });
5822
+ }
5823
+ }
5824
+ const targetSampleIds = targets.map((target) => target._id).filter((id) => id !== void 0).slice(0, options.maxSamples);
5825
+ return {
5826
+ used: usages.length > 0,
5827
+ target: {
5828
+ matchedCount: targets.length,
5829
+ truncated: targetTruncated,
5830
+ sampleIds: targetSampleIds
5831
+ },
5832
+ checkedRelations,
5833
+ usages,
5834
+ coverage: {
5835
+ mode: "registered-declared",
5836
+ complete: !targetTruncated && skipped.length === 0,
5837
+ registryModels,
5838
+ checkedModels: [...checkedModels],
5839
+ skipped
5840
+ }
5841
+ };
5842
+ }
5843
+ function stripRelationUsageOptions(options) {
5844
+ if (!isNonArrayObject(options)) {
5845
+ return options;
5846
+ }
5847
+ const {
5848
+ maxTargets: _maxTargets,
5849
+ maxSamples: _maxSamples,
5850
+ includeRelations: _includeRelations,
5851
+ excludeRelations: _excludeRelations,
5852
+ includeSoftDeletedReferences: _includeSoftDeletedReferences,
5853
+ ...deleteOptions
5854
+ } = options;
5855
+ void _maxTargets;
5856
+ void _maxSamples;
5857
+ void _includeRelations;
5858
+ void _excludeRelations;
5859
+ void _includeSoftDeletedReferences;
5860
+ return deleteOptions;
5861
+ }
5862
+
5863
+ // src/capabilities/model/model-p0-operations.ts
5864
+ function relationUsageContext(host) {
5865
+ return {
5866
+ modelName: host.modelName,
5867
+ runtime: host.runtime,
5868
+ dbName: host.dbName,
5869
+ poolName: host.poolName
5870
+ };
5871
+ }
5872
+ function isNonEmptyFilter(filter) {
5873
+ return typeof filter === "object" && filter !== null && !Array.isArray(filter) && Object.keys(filter).length > 0;
5874
+ }
5875
+ function relationTargetReadOptions(options, projection) {
5876
+ const source = options ?? {};
5877
+ const result = { projection };
5878
+ for (const key of ["session", "collation", "hint", "comment", "let", "maxTimeMS", "readConcern", "readPreference"]) {
5879
+ if (source[key] !== void 0) {
5880
+ result[key] = source[key];
5881
+ }
5882
+ }
5883
+ return result;
5884
+ }
5885
+ function hydrateVectorSearchHits(host, hits) {
5886
+ return hits.map((hit) => {
5887
+ const document = host.hydrateDocument(hit.document);
5888
+ if (!document) {
5889
+ throw createError(ErrorCodes.INVALID_OPERATION, "Vector Search returned a non-document result");
5890
+ }
5891
+ return { document, score: hit.score };
5892
+ });
5893
+ }
5894
+ async function executeModelVectorSearch(host, options) {
5895
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
5896
+ const hits2 = await host.collection.vectorSearch(options);
5897
+ return hydrateVectorSearchHits(host, hits2);
5898
+ }
5899
+ const { withDeleted, onlyDeleted, ...collectionOptions } = options;
5900
+ if (withDeleted !== void 0 && typeof withDeleted !== "boolean") {
5901
+ throw createError(
5902
+ ErrorCodes.INVALID_VECTOR_SEARCH,
5903
+ "withDeleted must be a boolean",
5904
+ [{ field: "withDeleted", reason: "boolean required" }]
5905
+ );
5906
+ }
5907
+ if (onlyDeleted !== void 0 && typeof onlyDeleted !== "boolean") {
5908
+ throw createError(
5909
+ ErrorCodes.INVALID_VECTOR_SEARCH,
5910
+ "onlyDeleted must be a boolean",
5911
+ [{ field: "onlyDeleted", reason: "boolean required" }]
5912
+ );
5913
+ }
5914
+ const filter = host.softDeleteConfig?.enabled && !withDeleted ? applyModelSoftDeleteFilter(
5915
+ collectionOptions.filter,
5916
+ { withDeleted, onlyDeleted },
5917
+ host.softDeleteConfig
5918
+ ) : collectionOptions.filter;
5919
+ const hits = await host.collection.vectorSearch({
5920
+ ...collectionOptions,
5921
+ ...filter === void 0 ? {} : { filter }
5922
+ });
5923
+ return hydrateVectorSearchHits(host, hits);
5924
+ }
5925
+ async function executeRelationUsageCheck(host, filter, options) {
5926
+ const normalizedOptions = normalizeRelationUsageOptions(options);
5927
+ const context = relationUsageContext(host);
5928
+ try {
5929
+ const rawTargets = await host.collection.find(
5930
+ applyModelSoftDeleteFilter(filter, void 0, host.softDeleteConfig),
5931
+ {
5932
+ session: normalizedOptions.session,
5933
+ projection: buildRelationUsageTargetProjection(host.modelName),
5934
+ limit: normalizedOptions.maxTargets + 1
5935
+ }
5936
+ );
5937
+ const targets = rawTargets.filter(
5938
+ (target) => typeof target === "object" && target !== null && !Array.isArray(target)
5939
+ );
5940
+ return inspectDeclaredRelationUsage(
5941
+ context,
5942
+ targets.slice(0, normalizedOptions.maxTargets),
5943
+ normalizedOptions,
5944
+ targets.length > normalizedOptions.maxTargets
5945
+ );
5946
+ } catch (error) {
5947
+ return createUnavailableRelationUsageReport(context, error instanceof Error ? error.message : String(error));
5948
+ }
5949
+ }
5950
+ async function executeProtectedRelationDelete(host, filter, options, force) {
5951
+ if (!isNonEmptyFilter(filter)) {
5952
+ throw createError(
5953
+ ErrorCodes.INVALID_ARGUMENT,
5954
+ "protected relation deletion requires a non-empty filter",
5955
+ [{ field: "filter", reason: "non-empty object required" }]
5956
+ );
5957
+ }
5958
+ const rawOptions = options ?? {};
5959
+ if (rawOptions.includeRelations !== void 0 || rawOptions.excludeRelations !== void 0) {
5960
+ throw createError(
5961
+ ErrorCodes.INVALID_ARGUMENT,
5962
+ "protected relation deletion cannot narrow the declared relation scan",
5963
+ [{ field: "includeRelations/excludeRelations", reason: "protected deletion always scans all declared relations" }]
5964
+ );
5965
+ }
5966
+ const usageOptions = normalizeRelationUsageOptions(options);
5967
+ const context = relationUsageContext(host);
5968
+ const deleteOptions = stripRelationUsageOptions(options);
5969
+ let target;
5970
+ try {
5971
+ const targetFilter = force || !host.softDeleteConfig?.enabled ? filter : { ...filter, [host.softDeleteConfig.field]: null };
5972
+ const rawTarget = await host.collection.findOne(
5973
+ targetFilter,
5974
+ relationTargetReadOptions(options, buildRelationUsageTargetProjection(host.modelName))
5975
+ );
5976
+ target = typeof rawTarget === "object" && rawTarget !== null && !Array.isArray(rawTarget) ? rawTarget : null;
5977
+ } catch (error) {
5978
+ const usage2 = createUnavailableRelationUsageReport(
5979
+ context,
5980
+ error instanceof Error ? error.message : String(error)
5981
+ );
5982
+ throw createError(
5983
+ ErrorCodes.RELATION_USAGE_UNAVAILABLE,
5984
+ "Unable to resolve a target for protected relation deletion",
5985
+ [{ coverage: usage2.coverage, reason: usage2.coverage.skipped[0]?.reason, skipped: usage2.coverage.skipped }]
5986
+ );
5987
+ }
5988
+ const usage = await inspectDeclaredRelationUsage(context, target ? [target] : [], usageOptions);
5989
+ if (!usage.coverage.complete) {
5990
+ throw createError(
5991
+ ErrorCodes.RELATION_USAGE_UNAVAILABLE,
5992
+ "Declared relation usage coverage is incomplete; protected deletion was not performed",
5993
+ [{ coverage: usage.coverage, reason: "incomplete relation usage coverage", skipped: usage.coverage.skipped }]
5994
+ );
5995
+ }
5996
+ if (usage.used) {
5997
+ throw createError(
5998
+ ErrorCodes.RELATION_IN_USE,
5999
+ "Protected deletion was blocked because declared relations still reference the target",
6000
+ [{ used: usage.used, usages: usage.usages, coverage: usage.coverage }]
6001
+ );
6002
+ }
6003
+ if (target && target._id === void 0) {
6004
+ throw createError(
6005
+ ErrorCodes.RELATION_USAGE_UNAVAILABLE,
6006
+ "Protected deletion target did not include an _id",
6007
+ [{ coverage: usage.coverage, reason: "target _id missing", skipped: usage.coverage.skipped }]
6008
+ );
6009
+ }
6010
+ const deleteFilter = target ? { _id: target._id } : { _id: { $in: [] } };
6011
+ const result = force ? await host.forceDelete(deleteFilter, deleteOptions) : await host.deleteOne(deleteFilter, deleteOptions);
6012
+ return { result, usage };
6013
+ }
6014
+
5560
6015
  // src/capabilities/model/index.ts
6016
+ function resolveModelRegistryName(definition, fallback) {
6017
+ return Model.list().find((name) => Model.get(name)?.definition === definition) ?? fallback;
6018
+ }
5561
6019
  var ModelInstance = class {
5562
6020
  constructor(collection, runtime, options) {
5563
6021
  this.collection = collection;
@@ -5578,6 +6036,7 @@ var ModelInstance = class {
5578
6036
  this.dbName = options.dbName;
5579
6037
  this.poolName = options.poolName;
5580
6038
  this.definition = options.definition;
6039
+ this.modelName = options.modelName ?? resolveModelRegistryName(options.definition, options.collectionName);
5581
6040
  this.relations = new Map(Object.entries(options.definition.relations ?? {}));
5582
6041
  for (const [name, config] of this.relations) {
5583
6042
  validateRelationConfig(name, config);
@@ -5761,6 +6220,9 @@ var ModelInstance = class {
5761
6220
  };
5762
6221
  });
5763
6222
  }
6223
+ async vectorSearch(options) {
6224
+ return executeModelVectorSearch(this.p0OperationsContext(), options);
6225
+ }
5764
6226
  count(query, options) {
5765
6227
  const filteredQuery = applyModelSoftDeleteFilter(query, options, this._softDeleteConfig);
5766
6228
  return this.runV1HookedOperation("find", [filteredQuery, options], (nextQuery, nextOptions) => this.collection.count(nextQuery, nextOptions));
@@ -5810,6 +6272,19 @@ var ModelInstance = class {
5810
6272
  async deleteMany(filter, options) {
5811
6273
  return this.runModelWrite(() => orchestrateModelDeleteMany(this.mutationContext(), filter, options));
5812
6274
  }
6275
+ /**
6276
+ * Inspects only registered and declared inbound relations. It is read-only;
6277
+ * callers must check coverage before treating an empty result as safe.
6278
+ */
6279
+ async checkRelationUsage(filter, options) {
6280
+ return executeRelationUsageCheck(this.p0OperationsContext(), filter, options);
6281
+ }
6282
+ async deleteOneWithRelations(filter, options) {
6283
+ return executeProtectedRelationDelete(this.p0OperationsContext(), filter, options, false);
6284
+ }
6285
+ async forceDeleteWithRelations(filter, options) {
6286
+ return executeProtectedRelationDelete(this.p0OperationsContext(), filter, options, true);
6287
+ }
5813
6288
  // ── soft-delete extended methods (only meaningful when softDelete is enabled) ──
5814
6289
  findWithDeleted(query, options) {
5815
6290
  return findWithDeletedDocuments(this.softDeleteContext(), query, options);
@@ -5978,6 +6453,19 @@ var ModelInstance = class {
5978
6453
  populateDocument: (document, paths) => this.populateSingle(document, paths)
5979
6454
  }, doc);
5980
6455
  }
6456
+ p0OperationsContext() {
6457
+ return {
6458
+ collection: this.collection,
6459
+ softDeleteConfig: this._softDeleteConfig,
6460
+ modelName: this.modelName,
6461
+ runtime: this.runtime,
6462
+ dbName: this.dbName,
6463
+ poolName: this.poolName,
6464
+ hydrateDocument: (doc) => this.hydrateDocument(doc),
6465
+ deleteOne: (filter, options) => this.deleteOne(filter, options),
6466
+ forceDelete: (filter, options) => this.forceDelete(filter, options)
6467
+ };
6468
+ }
5981
6469
  softDeleteContext() {
5982
6470
  return {
5983
6471
  collection: this.collection,
@@ -7173,6 +7661,7 @@ var CountQueue = class {
7173
7661
  };
7174
7662
 
7175
7663
  // src/capabilities/pool/pool-health-checker.ts
7664
+ var SILENT_HEALTH_CHECKER_LOGGER = {};
7176
7665
  var HealthChecker = class {
7177
7666
  constructor(options = {}) {
7178
7667
  this._healthStatus = /* @__PURE__ */ new Map();
@@ -7182,7 +7671,7 @@ var HealthChecker = class {
7182
7671
  this._inProgress = /* @__PURE__ */ new Set();
7183
7672
  this._started = false;
7184
7673
  this._poolManager = options.poolManager ?? null;
7185
- this._logger = options.logger ?? console;
7674
+ this._logger = options.logger ?? SILENT_HEALTH_CHECKER_LOGGER;
7186
7675
  }
7187
7676
  register(poolNameOrConfig, configOrClient) {
7188
7677
  let poolName;
@@ -7306,6 +7795,7 @@ var HealthChecker = class {
7306
7795
  };
7307
7796
 
7308
7797
  // src/capabilities/pool/pool-selector.ts
7798
+ var SILENT_POOL_SELECTOR_LOGGER = {};
7309
7799
  var POOL_STRATEGY_ALIASES = {
7310
7800
  "round-robin": "roundRobin",
7311
7801
  "least-connections": "leastConnections",
@@ -7319,7 +7809,7 @@ var PoolSelector = class {
7319
7809
  constructor(options = {}) {
7320
7810
  this._roundRobinIndex = /* @__PURE__ */ new Map();
7321
7811
  this._strategy = normalizePoolStrategy(options.strategy);
7322
- this._logger = options.logger ?? console;
7812
+ this._logger = options.logger ?? SILENT_POOL_SELECTOR_LOGGER;
7323
7813
  }
7324
7814
  select(pools, context) {
7325
7815
  if (!pools || pools.length === 0) {
@@ -7450,11 +7940,12 @@ var PoolSelector = class {
7450
7940
  };
7451
7941
 
7452
7942
  // src/capabilities/pool/pool-stats-manager.ts
7943
+ var SILENT_POOL_STATS_LOGGER = {};
7453
7944
  var PoolStatsManager = class {
7454
7945
  constructor(options = {}) {
7455
7946
  this._stats = /* @__PURE__ */ new Map();
7456
7947
  this._buffer = [];
7457
- this._logger = options.logger ?? console;
7948
+ this._logger = options.logger ?? SILENT_POOL_STATS_LOGGER;
7458
7949
  this._batchInterval = setInterval(() => {
7459
7950
  this._flush();
7460
7951
  }, 100);
@@ -7702,9 +8193,9 @@ var ConnectionPoolManager = class {
7702
8193
  this._fallbackConfig = this.fallback;
7703
8194
  this._fallback = this.fallback;
7704
8195
  this._pools = this.pools;
7705
- this._selector = new PoolSelector({ strategy: this.strategy });
7706
- this._healthChecker = new HealthChecker({ poolManager: this });
7707
- this._stats = new PoolStatsManager({ logger: options.logger ?? void 0 });
8196
+ this._selector = new PoolSelector({ strategy: this.strategy, logger: this.logger ?? void 0 });
8197
+ this._healthChecker = new HealthChecker({ poolManager: this, logger: this.logger ?? void 0 });
8198
+ this._stats = new PoolStatsManager({ logger: this.logger ?? void 0 });
7708
8199
  }
7709
8200
  /**
7710
8201
  * Add a connection pool.
@@ -9537,7 +10028,7 @@ async function computeTotals(coll, query, limit, totals, defaults = {}, queryCac
9537
10028
  }
9538
10029
 
9539
10030
  // src/adapters/mongodb/queries/find-page.ts
9540
- function normalizePositiveInteger(value, fallback, field) {
10031
+ function normalizePositiveInteger2(value, fallback, field) {
9541
10032
  if (value === void 0 || value === null) {
9542
10033
  return fallback;
9543
10034
  }
@@ -9585,8 +10076,8 @@ async function executeFindPage(collection, options = {}, defaults = {}, queryCac
9585
10076
  const metaStartTs = Date.now();
9586
10077
  const metaSteps = [];
9587
10078
  const ext = options;
9588
- const page = normalizePositiveInteger(options.page, 1, "page");
9589
- const rawLimit = normalizePositiveInteger(options.limit, DEFAULT_FIND_PAGE_LIMIT, "limit");
10079
+ const page = normalizePositiveInteger2(options.page, 1, "page");
10080
+ const rawLimit = normalizePositiveInteger2(options.limit, DEFAULT_FIND_PAGE_LIMIT, "limit");
9590
10081
  const limit = defaults.findPageMaxLimit !== void 0 && rawLimit > defaults.findPageMaxLimit ? defaults.findPageMaxLimit : rawLimit;
9591
10082
  const sort = normalizeSortShape(options.sort);
9592
10083
  const baseQuery = options.query ?? {};
@@ -10460,6 +10951,138 @@ function explainDocuments(collection, query, options, defaults) {
10460
10951
  return createFindChain(collection, query, options, defaults).explain(options?.explain ?? "queryPlanner");
10461
10952
  }
10462
10953
 
10954
+ // src/adapters/mongodb/queries/vector-search.ts
10955
+ var VECTOR_SEARCH_SCORE_FIELD = "__monsqlize_vector_search_score__";
10956
+ function attachVectorSearchOperationContext(error, namespace) {
10957
+ if (error instanceof Error) {
10958
+ const contextualized = error;
10959
+ const existing = contextualized.monsqlize;
10960
+ try {
10961
+ contextualized.monsqlize = {
10962
+ ...existing && typeof existing === "object" ? existing : {},
10963
+ operation: "vectorSearch",
10964
+ namespace
10965
+ };
10966
+ } catch {
10967
+ }
10968
+ return error;
10969
+ }
10970
+ return createError(
10971
+ ErrorCodes.MONGODB_ERROR,
10972
+ "vectorSearch execution failed",
10973
+ [{ operation: "vectorSearch", namespace, reason: String(error) }]
10974
+ );
10975
+ }
10976
+ function assertNonEmptyString(value, field) {
10977
+ if (typeof value !== "string" || value.trim().length === 0) {
10978
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, `${field} must be a non-empty string`, [{ field, reason: "non-empty string required" }]);
10979
+ }
10980
+ }
10981
+ function assertPositiveInteger(value, field) {
10982
+ if (!Number.isInteger(value) || value <= 0) {
10983
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, `${field} must be a positive integer`, [{ field, reason: "positive integer required" }]);
10984
+ }
10985
+ }
10986
+ function isNonArrayObject2(value) {
10987
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10988
+ }
10989
+ function normalizeVectorProjection(value) {
10990
+ if (value === void 0) return void 0;
10991
+ if (Array.isArray(value)) {
10992
+ if (value.some((field) => typeof field !== "string" || field.length === 0)) {
10993
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "projection array entries must be non-empty strings", [{ field: "projection", reason: "array entries must be non-empty strings" }]);
10994
+ }
10995
+ return normalizeProjection(value);
10996
+ }
10997
+ if (!isNonArrayObject2(value)) {
10998
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "projection must be an object or string array", [{ field: "projection", reason: "object or string array required" }]);
10999
+ }
11000
+ return normalizeProjection(value);
11001
+ }
11002
+ function normalizeAggregateOptions(value) {
11003
+ if (value === void 0) return void 0;
11004
+ if (!isNonArrayObject2(value)) {
11005
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "aggregateOptions must be a non-array object", [{ field: "aggregateOptions", reason: "non-array object required" }]);
11006
+ }
11007
+ for (const key of ["meta", "stream", "explain"]) {
11008
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
11009
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, `aggregateOptions.${key} is not supported by vectorSearch()`, [{ field: `aggregateOptions.${key}`, reason: "reserved aggregate option" }]);
11010
+ }
11011
+ }
11012
+ return { ...value };
11013
+ }
11014
+ function buildVectorSearchPipeline(options) {
11015
+ if (!isNonArrayObject2(options)) {
11016
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "vectorSearch options must be a non-array object", [{ field: "options", reason: "non-array object required" }]);
11017
+ }
11018
+ assertNonEmptyString(options.index, "index");
11019
+ assertNonEmptyString(options.path, "path");
11020
+ assertPositiveInteger(options.limit, "limit");
11021
+ if (!Array.isArray(options.queryVector) || options.queryVector.length === 0) {
11022
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "queryVector must be a non-empty number array", [{ field: "queryVector", reason: "non-empty number array required" }]);
11023
+ }
11024
+ if (options.queryVector.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
11025
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "queryVector values must be finite numbers", [{ field: "queryVector", reason: "finite numbers required" }]);
11026
+ }
11027
+ if (options.exact !== void 0 && typeof options.exact !== "boolean") {
11028
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "exact must be a boolean", [{ field: "exact", reason: "boolean required" }]);
11029
+ }
11030
+ if (options.filter !== void 0 && !isNonArrayObject2(options.filter)) {
11031
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "filter must be a non-array object", [{ field: "filter", reason: "non-array object required" }]);
11032
+ }
11033
+ const isExactSearch = options.exact === true;
11034
+ if (isExactSearch) {
11035
+ if (options.numCandidates !== void 0) {
11036
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "numCandidates is not supported when exact is true", [{ field: "numCandidates", reason: "ANN-only option" }]);
11037
+ }
11038
+ } else {
11039
+ assertPositiveInteger(options.numCandidates, "numCandidates");
11040
+ if (options.numCandidates < options.limit) {
11041
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "numCandidates must be greater than or equal to limit", [{ field: "numCandidates", reason: "must be greater than or equal to limit" }]);
11042
+ }
11043
+ }
11044
+ const vectorSearchStage = {
11045
+ index: options.index.trim(),
11046
+ path: options.path.trim(),
11047
+ queryVector: [...options.queryVector],
11048
+ limit: options.limit,
11049
+ ...isExactSearch ? { exact: true } : { numCandidates: options.numCandidates },
11050
+ ...options.filter === void 0 ? {} : { filter: options.filter }
11051
+ };
11052
+ const projection = normalizeVectorProjection(options.projection);
11053
+ const scoreStage = projection ? {
11054
+ $project: {
11055
+ ...projection,
11056
+ [VECTOR_SEARCH_SCORE_FIELD]: { $meta: "vectorSearchScore" }
11057
+ }
11058
+ } : {
11059
+ $set: {
11060
+ [VECTOR_SEARCH_SCORE_FIELD]: { $meta: "vectorSearchScore" }
11061
+ }
11062
+ };
11063
+ return {
11064
+ pipeline: [
11065
+ { $vectorSearch: vectorSearchStage },
11066
+ scoreStage
11067
+ ],
11068
+ aggregateOptions: normalizeAggregateOptions(options.aggregateOptions)
11069
+ };
11070
+ }
11071
+ function mapVectorSearchRows(rows) {
11072
+ return rows.map((row) => {
11073
+ const score = row[VECTOR_SEARCH_SCORE_FIELD];
11074
+ if (typeof score !== "number" || !Number.isFinite(score)) {
11075
+ throw createError(ErrorCodes.INVALID_OPERATION, "Vector Search result did not contain a finite score");
11076
+ }
11077
+ const document = { ...row };
11078
+ delete document[VECTOR_SEARCH_SCORE_FIELD];
11079
+ return {
11080
+ document,
11081
+ score
11082
+ };
11083
+ });
11084
+ }
11085
+
10463
11086
  // src/adapters/mongodb/writes/write-utils.ts
10464
11087
  function sleep2(ms) {
10465
11088
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -11633,6 +12256,19 @@ var MongoCollectionAccessor = class {
11633
12256
  } : void 0
11634
12257
  );
11635
12258
  }
12259
+ /** Searches a MongoDB Vector Search index through a constrained aggregate pipeline. */
12260
+ async vectorSearch(options) {
12261
+ const { pipeline, aggregateOptions } = buildVectorSearchPipeline(options);
12262
+ try {
12263
+ const rows = await this.aggregate(
12264
+ pipeline,
12265
+ aggregateOptions
12266
+ );
12267
+ return mapVectorSearchRows(rows);
12268
+ } catch (error) {
12269
+ throw attachVectorSearchOperationContext(error, this.getNamespace());
12270
+ }
12271
+ }
11636
12272
  /** Returns an array of distinct values for the given field key. */
11637
12273
  async distinct(key, query, options) {
11638
12274
  const startTs = Date.now();
@@ -12134,10 +12770,10 @@ function assertCompatPoolExists(poolManager, poolName) {
12134
12770
  function createPoolScope(runtime, poolName) {
12135
12771
  return {
12136
12772
  collection: (name) => runtime.scopedCollection(name, { pool: poolName }),
12137
- model: (name) => runtime.scopedModel(name, { pool: poolName }),
12773
+ model: (model) => runtime.scopedModel(model, { pool: poolName }),
12138
12774
  use: (dbName) => ({
12139
12775
  collection: (name) => runtime.scopedCollection(name, { pool: poolName, database: dbName }),
12140
- model: (name) => runtime.scopedModel(name, { pool: poolName, database: dbName })
12776
+ model: (model) => runtime.scopedModel(model, { pool: poolName, database: dbName })
12141
12777
  })
12142
12778
  };
12143
12779
  }
@@ -12167,6 +12803,7 @@ function createCompatModelInstance(config) {
12167
12803
  config.runtime,
12168
12804
  {
12169
12805
  collectionName: config.collectionName,
12806
+ modelName: config.modelName,
12170
12807
  dbName: config.dbName,
12171
12808
  poolName: config.poolName,
12172
12809
  definition: config.definition,
@@ -12204,6 +12841,7 @@ function createRuntimeModelInstance(host, name, scope) {
12204
12841
  host.runtime,
12205
12842
  {
12206
12843
  collectionName: actualCollectionName,
12844
+ modelName: registered.collectionName,
12207
12845
  dbName: databaseName,
12208
12846
  poolName,
12209
12847
  definition: registered.definition,
@@ -16294,7 +16932,7 @@ var MonSQLizeRuntime = class {
16294
16932
  requireCompatDbInstance(this);
16295
16933
  return {
16296
16934
  collection: (collectionName) => this.scopedCollection(collectionName, { database: name }),
16297
- model: (modelName) => this.scopedModel(modelName, { database: name })
16935
+ model: (model) => this.scopedModel(model, { database: name })
16298
16936
  };
16299
16937
  }
16300
16938
  pool(poolName) {
@@ -16326,7 +16964,8 @@ var MonSQLizeRuntime = class {
16326
16964
  });
16327
16965
  }
16328
16966
  // Model accessors ---------------------------------------------------------
16329
- scopedModel(name, options = {}) {
16967
+ scopedModel(model, options = {}) {
16968
+ const name = typeof model === "string" ? model : model.collectionName;
16330
16969
  const dbInstance = requireCompatDbInstance(this);
16331
16970
  if (this._client) {
16332
16971
  return this.createModelInstance(name, options);
@@ -16343,13 +16982,15 @@ var MonSQLizeRuntime = class {
16343
16982
  collection,
16344
16983
  runtime: this,
16345
16984
  collectionName: actualCollectionName,
16985
+ modelName: registered.collectionName,
16346
16986
  dbName: database ?? getRuntimeDatabaseName(this),
16347
16987
  poolName: pool,
16348
16988
  definition: registered.definition,
16349
16989
  schemaEngine: this._schemaDslEngine
16350
16990
  });
16351
16991
  }
16352
- model(name) {
16992
+ model(model) {
16993
+ const name = typeof model === "string" ? model : model.collectionName;
16353
16994
  if (this._client) {
16354
16995
  this.ensureConnected();
16355
16996
  return this.createModelInstance(name, {
@@ -16375,6 +17016,7 @@ var MonSQLizeRuntime = class {
16375
17016
  collection,
16376
17017
  runtime: this,
16377
17018
  collectionName: actualCollectionName,
17019
+ modelName: registered.collectionName,
16378
17020
  dbName: getRuntimeDatabaseName(this),
16379
17021
  definition: registered.definition,
16380
17022
  schemaEngine: this._schemaDslEngine
@@ -18149,6 +18791,7 @@ MonSQLize.PoolSelector = PoolSelector;
18149
18791
  MonSQLize.PoolStats = PoolStatsManager;
18150
18792
  MonSQLize.validatePoolConfig = validatePoolConfig;
18151
18793
  MonSQLize.validatePoolConfigSafe = validatePoolConfigSafe;
18794
+ MonSQLize.defineModel = defineModel;
18152
18795
  MonSQLize.Model = Model;
18153
18796
  MonSQLize.ModelInstance = ModelInstance;
18154
18797
  MonSQLize.expr = expr;
@@ -18238,6 +18881,7 @@ export {
18238
18881
  dataTasks,
18239
18882
  decodeCursor2 as decodeCursor,
18240
18883
  index_default as default,
18884
+ defineModel,
18241
18885
  encodeCursor2 as encodeCursor,
18242
18886
  expr,
18243
18887
  generateQueryHash,