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.
@@ -56,6 +56,14 @@ var ErrorCodes = {
56
56
  INVALID_EXPRESSION: "INVALID_EXPRESSION",
57
57
  INVALID_PAGINATION: "INVALID_PAGINATION",
58
58
  INVALID_OPERATION: "INVALID_OPERATION",
59
+ /** Invalid local Vector Search options before the driver is called. */
60
+ INVALID_VECTOR_SEARCH: "INVALID_VECTOR_SEARCH",
61
+ /** Relation select is outside the supported inclusion-only top-level contract. */
62
+ INVALID_RELATION_SELECT: "INVALID_RELATION_SELECT",
63
+ /** A protected delete found declared Model relations that still reference its target. */
64
+ RELATION_IN_USE: "RELATION_IN_USE",
65
+ /** Relation usage coverage is incomplete, so a protected delete must not continue. */
66
+ RELATION_USAGE_UNAVAILABLE: "RELATION_USAGE_UNAVAILABLE",
59
67
  CACHE_UNAVAILABLE: "CACHE_UNAVAILABLE",
60
68
  MANAGEMENT_OPERATION_FAILED: "MANAGEMENT_OPERATION_FAILED",
61
69
  NOT_CONNECTED: "NOT_CONNECTED",
@@ -501,12 +509,49 @@ function validateRelationConfig(name, config) {
501
509
  if (config.single !== void 0 && typeof config.single !== "boolean") {
502
510
  throw createError(ErrorCodes.INVALID_ARGUMENT, `relations.single must be a boolean`);
503
511
  }
512
+ normalizeRelationSelect(config.select, { relation: name });
513
+ }
514
+ function normalizeRelationSelect(value, context = {}) {
515
+ if (value === void 0) {
516
+ return void 0;
517
+ }
518
+ const invalid2 = (reason) => {
519
+ throw createError(
520
+ ErrorCodes.INVALID_RELATION_SELECT,
521
+ `Relation select must use non-empty top-level inclusion fields: ${reason}`,
522
+ [{ ...context, value, reason }]
523
+ );
524
+ };
525
+ const rawTokens = typeof value === "string" ? [value] : Array.isArray(value) ? value : invalid2("select must be a string or string array");
526
+ const tokens = [];
527
+ for (const rawToken of rawTokens) {
528
+ if (typeof rawToken !== "string") {
529
+ invalid2("select array entries must be strings");
530
+ }
531
+ const parts = rawToken.trim().split(/\s+/).filter(Boolean);
532
+ if (parts.length === 0) {
533
+ invalid2("select must not be empty");
534
+ }
535
+ tokens.push(...parts);
536
+ }
537
+ for (const token of tokens) {
538
+ if (token.startsWith("-") || token.startsWith("+")) {
539
+ invalid2("exclusion and prefix tokens are not supported");
540
+ }
541
+ if (token.includes(".")) {
542
+ invalid2("dot-path fields are not supported");
543
+ }
544
+ }
545
+ return [...new Set(tokens)];
504
546
  }
505
547
  function normalizePopulateConfig(path5) {
506
548
  return typeof path5 === "string" ? { path: path5 } : path5;
507
549
  }
508
550
 
509
551
  // src/capabilities/model/model-registry.ts
552
+ function defineModel(collectionName, definition) {
553
+ return { collectionName, definition };
554
+ }
510
555
  var Model = class {
511
556
  static {
512
557
  // collection name → registered model definition
@@ -521,21 +566,19 @@ var Model = class {
521
566
  /** Collection names that have been redefined (used by the runtime to trigger hot-reload). */
522
567
  this._redefinedNames = /* @__PURE__ */ new Set();
523
568
  }
524
- /**
525
- * Register a new model definition.
526
- * Throws MODEL_ALREADY_EXISTS if a model with the same name already exists.
527
- * Validates the definition and resolves the timestamps option.
528
- */
529
- static define(collectionName, definition) {
569
+ static define(collectionNameOrDescriptor, definition) {
570
+ const collectionName = typeof collectionNameOrDescriptor === "string" ? collectionNameOrDescriptor : collectionNameOrDescriptor.collectionName;
571
+ const modelDefinition = typeof collectionNameOrDescriptor === "string" ? definition : collectionNameOrDescriptor.definition;
530
572
  const normalizedName = validateCollectionName(collectionName);
531
573
  if (this.registry.has(normalizedName)) {
532
574
  throw createError(ErrorCodes.MODEL_ALREADY_EXISTS, `Model '${normalizedName}' is already defined.`);
533
575
  }
534
- validateDefinition(definition);
535
- processTimestamps(definition);
576
+ validateDefinition(modelDefinition);
577
+ const resolvedDefinition = modelDefinition;
578
+ processTimestamps(resolvedDefinition);
536
579
  this.registry.set(normalizedName, {
537
580
  collectionName: normalizedName,
538
- definition
581
+ definition: resolvedDefinition
539
582
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
540
583
  });
541
584
  this.bumpRevision(normalizedName);
@@ -1077,6 +1120,56 @@ function resolveRegisteredCollectionName(registered, fallback) {
1077
1120
  const definition = registered.definition;
1078
1121
  return definition.collection ?? definition.name ?? registered.collectionName;
1079
1122
  }
1123
+ function flattenComparableValues(value) {
1124
+ if (Array.isArray(value)) {
1125
+ return value.flatMap((entry) => flattenComparableValues(entry));
1126
+ }
1127
+ return value === void 0 || value === null ? [] : [value];
1128
+ }
1129
+ function resolveNestedPopulatePaths(value) {
1130
+ if (value === void 0) {
1131
+ return [];
1132
+ }
1133
+ const paths = Array.isArray(value) ? value : [value];
1134
+ for (const path5 of paths) {
1135
+ const valid = typeof path5 === "string" || typeof path5 === "object" && path5 !== null && !Array.isArray(path5) && Boolean(path5.path);
1136
+ if (!valid) {
1137
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "nested populate must be a string, array, or object");
1138
+ }
1139
+ }
1140
+ return paths;
1141
+ }
1142
+ function collectNestedLocalFields(relatedModel, nestedPaths) {
1143
+ if (!relatedModel) {
1144
+ return [];
1145
+ }
1146
+ const relations = relatedModel.getRelations();
1147
+ return unique(
1148
+ nestedPaths.map((path5) => relations[normalizePopulateConfig(path5).path]?.localField).filter((field) => Boolean(field))
1149
+ );
1150
+ }
1151
+ function dedupeDocuments(documents) {
1152
+ const seen = /* @__PURE__ */ new Set();
1153
+ return documents.filter((document) => {
1154
+ if (seen.has(document)) {
1155
+ return false;
1156
+ }
1157
+ seen.add(document);
1158
+ return true;
1159
+ });
1160
+ }
1161
+ function rebuildPopulateMatches(localValue, grouped, sort) {
1162
+ const matches = flattenComparableValues(localValue).flatMap((value) => grouped.get(toKey(value)) ?? []);
1163
+ const deduped = dedupeDocuments(matches);
1164
+ return sort ? applySort(deduped, sort) : deduped;
1165
+ }
1166
+ function projectPopulatedDocument(document, select, nestedPaths) {
1167
+ if (!select) {
1168
+ return document;
1169
+ }
1170
+ const nestedRelationFields = nestedPaths.map((path5) => normalizePopulateConfig(path5).path);
1171
+ return pickFields(document, [.../* @__PURE__ */ new Set([...select, ...nestedRelationFields])]);
1172
+ }
1080
1173
  async function populateModelPath(context, docs, path5, traversalState) {
1081
1174
  const config = normalizePopulateConfig(path5);
1082
1175
  const state = resolvePopulateState(config, traversalState);
@@ -1095,9 +1188,8 @@ async function populateModelPath(context, docs, path5, traversalState) {
1095
1188
  if (!relation) {
1096
1189
  throw createError(ErrorCodes.INVALID_ARGUMENT, `Undefined relation: ${config.path}`);
1097
1190
  }
1098
- const keys = unique(
1099
- docs.map((doc) => getByPath(doc, relation.localField)).filter((value) => value !== void 0 && value !== null)
1100
- );
1191
+ const localValues = docs.map((doc) => getByPath(doc, relation.localField));
1192
+ const keys = unique(localValues.flatMap((value) => flattenComparableValues(value)));
1101
1193
  if (keys.length === 0) {
1102
1194
  for (const doc of docs) {
1103
1195
  doc[config.path] = relation.single ? null : [];
@@ -1112,51 +1204,42 @@ async function populateModelPath(context, docs, path5, traversalState) {
1112
1204
  const relatedCollectionName = resolveRegisteredCollectionName(registered, relation.from);
1113
1205
  const relatedCollection = context.runtime.scopedCollection(relatedCollectionName, scope);
1114
1206
  const relatedModel = Model.has(relation.from) ? context.runtime.scopedModel(relation.from, scope) : null;
1207
+ const nestedPaths = resolveNestedPopulatePaths(config.populate);
1208
+ const effectiveSelect = normalizeRelationSelect(config.select, { path: config.path }) ?? normalizeRelationSelect(relation.select, { relation: config.path });
1209
+ const projectionFields = effectiveSelect ? [.../* @__PURE__ */ new Set([
1210
+ ...effectiveSelect,
1211
+ relation.foreignField,
1212
+ "_id",
1213
+ ...collectNestedLocalFields(relatedModel, nestedPaths)
1214
+ ])] : [];
1115
1215
  const relatedDocs = await relatedCollection.find({
1116
1216
  [relation.foreignField]: { $in: keys },
1117
1217
  ...config.match ?? {}
1118
- });
1218
+ }, effectiveSelect ? {
1219
+ projection: Object.fromEntries(projectionFields.map((field) => [field, 1]))
1220
+ } : void 0);
1119
1221
  let hydrated = relatedModel ? relatedModel.hydrateDocuments(relatedDocs) : relatedDocs.map((item) => ({ ...item }));
1120
- if (config.sort) {
1121
- hydrated = applySort(hydrated, config.sort);
1122
- }
1123
- if (config.select) {
1124
- const select = config.select;
1125
- hydrated = hydrated.map((item) => pickFields(item, select, [relation.foreignField]));
1126
- }
1127
- if (config.populate) {
1128
- const nestedRaw = config.populate;
1129
- const isValidNestedConfig = (value) => {
1130
- if (typeof value === "string" || Array.isArray(value)) return true;
1131
- if (typeof value === "object" && value !== null && value.path) return true;
1132
- return false;
1133
- };
1134
- if (!isValidNestedConfig(nestedRaw)) {
1135
- throw createError(ErrorCodes.INVALID_ARGUMENT, "nested populate must be a string, array, or object");
1136
- }
1137
- if (relatedModel) {
1138
- if (state.depth + 1 > state.maxDepth) {
1139
- throw createError(ErrorCodes.INVALID_ARGUMENT, `populate maxDepth exceeded: ${state.maxDepth}`);
1140
- }
1141
- const nestedPaths = Array.isArray(config.populate) ? config.populate : [config.populate];
1142
- hydrated = await relatedModel.populateDocuments(hydrated, nestedPaths, {
1143
- depth: state.depth + 1,
1144
- maxDepth: state.maxDepth,
1145
- activeConfigs: state.activeConfigs
1146
- });
1222
+ if (nestedPaths.length > 0 && relatedModel) {
1223
+ if (state.depth + 1 > state.maxDepth) {
1224
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `populate maxDepth exceeded: ${state.maxDepth}`);
1147
1225
  }
1226
+ hydrated = await relatedModel.populateDocuments(hydrated, nestedPaths, {
1227
+ depth: state.depth + 1,
1228
+ maxDepth: state.maxDepth,
1229
+ activeConfigs: state.activeConfigs
1230
+ });
1148
1231
  }
1149
1232
  const grouped = groupBy(hydrated, (item) => getByPath(item, relation.foreignField));
1150
- for (const doc of docs) {
1151
- const localValue = getByPath(doc, relation.localField);
1152
- let matches = grouped.get(toKey(localValue)) ?? [];
1233
+ for (const [index, doc] of docs.entries()) {
1234
+ let matches = rebuildPopulateMatches(localValues[index], grouped, config.sort);
1153
1235
  if (config.skip) {
1154
1236
  matches = matches.slice(config.skip);
1155
1237
  }
1156
1238
  if (config.limit !== void 0) {
1157
1239
  matches = matches.slice(0, config.limit);
1158
1240
  }
1159
- doc[config.path] = relation.single ? matches[0] ?? null : [...matches];
1241
+ const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
1242
+ doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
1160
1243
  }
1161
1244
  return docs;
1162
1245
  } finally {
@@ -5574,7 +5657,382 @@ async function orchestrateModelDeleteMany(context, filter, options) {
5574
5657
  return result;
5575
5658
  }
5576
5659
 
5660
+ // src/capabilities/model/relation-usage.ts
5661
+ var DEFAULT_MAX_TARGETS = 100;
5662
+ var DEFAULT_MAX_SAMPLES = 20;
5663
+ function isNonArrayObject(value) {
5664
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5665
+ }
5666
+ function normalizePositiveInteger(value, field, fallback) {
5667
+ if (value === void 0) {
5668
+ return fallback;
5669
+ }
5670
+ if (!Number.isInteger(value) || value <= 0) {
5671
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `${field} must be a positive integer`, [{ field, value }]);
5672
+ }
5673
+ return value;
5674
+ }
5675
+ function normalizeRelationNames(value, field) {
5676
+ if (value === void 0) {
5677
+ return void 0;
5678
+ }
5679
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim().length === 0)) {
5680
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `${field} must be an array of non-empty strings`, [{ field, value }]);
5681
+ }
5682
+ return [...new Set(value.map((entry) => entry.trim()))];
5683
+ }
5684
+ function normalizeRelationUsageOptions(options) {
5685
+ if (options !== void 0 && !isNonArrayObject(options)) {
5686
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "relation usage options must be a non-array object", [{ field: "options", value: options }]);
5687
+ }
5688
+ const value = options ?? {};
5689
+ if (value.includeSoftDeletedReferences !== void 0 && typeof value.includeSoftDeletedReferences !== "boolean") {
5690
+ throw createError(
5691
+ ErrorCodes.INVALID_ARGUMENT,
5692
+ "includeSoftDeletedReferences must be a boolean",
5693
+ [{ field: "includeSoftDeletedReferences", value: value.includeSoftDeletedReferences }]
5694
+ );
5695
+ }
5696
+ return {
5697
+ session: value.session,
5698
+ maxTargets: normalizePositiveInteger(value.maxTargets, "maxTargets", DEFAULT_MAX_TARGETS),
5699
+ maxSamples: normalizePositiveInteger(value.maxSamples, "maxSamples", DEFAULT_MAX_SAMPLES),
5700
+ includeRelations: normalizeRelationNames(value.includeRelations, "includeRelations"),
5701
+ excludeRelations: normalizeRelationNames(value.excludeRelations, "excludeRelations"),
5702
+ includeSoftDeletedReferences: value.includeSoftDeletedReferences !== false
5703
+ };
5704
+ }
5705
+ function resolveRegisteredCollectionName2(registered) {
5706
+ const definition = registered.definition;
5707
+ return definition.collection ?? definition.name ?? registered.collectionName;
5708
+ }
5709
+ function flattenComparableValues2(value) {
5710
+ if (Array.isArray(value)) {
5711
+ return value.flatMap((entry) => flattenComparableValues2(entry));
5712
+ }
5713
+ return value === void 0 || value === null ? [] : [value];
5714
+ }
5715
+ function isRelationSelected(options, sourceModel, relation) {
5716
+ const qualified = `${sourceModel}.${relation}`;
5717
+ const matches = (names) => Boolean(names?.includes(qualified) || names?.includes(relation));
5718
+ if (options.includeRelations && !matches(options.includeRelations)) {
5719
+ return false;
5720
+ }
5721
+ return !matches(options.excludeRelations);
5722
+ }
5723
+ function describeError(error) {
5724
+ return error instanceof Error ? error.message : String(error);
5725
+ }
5726
+ function findDeclaredInboundRelations(targetModelName) {
5727
+ const relations = [];
5728
+ for (const sourceModel of Model.list()) {
5729
+ const registered = Model.get(sourceModel);
5730
+ if (!registered) {
5731
+ continue;
5732
+ }
5733
+ for (const [relation, config] of Object.entries(registered.definition.relations ?? {})) {
5734
+ if (config.from !== targetModelName) {
5735
+ continue;
5736
+ }
5737
+ relations.push({
5738
+ sourceModel,
5739
+ sourceCollection: resolveRegisteredCollectionName2(registered),
5740
+ relation,
5741
+ definition: registered.definition,
5742
+ localField: config.localField,
5743
+ foreignField: config.foreignField
5744
+ });
5745
+ }
5746
+ }
5747
+ return relations;
5748
+ }
5749
+ function buildRelationUsageTargetProjection(targetModelName) {
5750
+ const fields = /* @__PURE__ */ new Set(["_id"]);
5751
+ for (const relation of findDeclaredInboundRelations(targetModelName)) {
5752
+ fields.add(relation.foreignField);
5753
+ }
5754
+ return Object.fromEntries([...fields].map((field) => [field, 1]));
5755
+ }
5756
+ function createUnavailableRelationUsageReport(context, reason) {
5757
+ return {
5758
+ used: false,
5759
+ target: {
5760
+ matchedCount: 0,
5761
+ truncated: false,
5762
+ sampleIds: []
5763
+ },
5764
+ checkedRelations: 0,
5765
+ usages: [],
5766
+ coverage: {
5767
+ mode: "registered-declared",
5768
+ complete: false,
5769
+ registryModels: Model.list(),
5770
+ checkedModels: [],
5771
+ skipped: [{ sourceModel: context.modelName, reason }]
5772
+ }
5773
+ };
5774
+ }
5775
+ async function inspectDeclaredRelationUsage(context, targets, inputOptions, targetTruncated = false) {
5776
+ const options = normalizeRelationUsageOptions(inputOptions);
5777
+ const registryModels = Model.list();
5778
+ const checkedModels = /* @__PURE__ */ new Set();
5779
+ const skipped = [];
5780
+ const usages = [];
5781
+ let checkedRelations = 0;
5782
+ for (const inbound of findDeclaredInboundRelations(context.modelName)) {
5783
+ if (!isRelationSelected(options, inbound.sourceModel, inbound.relation)) {
5784
+ skipped.push({
5785
+ sourceModel: inbound.sourceModel,
5786
+ reason: `relation '${inbound.sourceModel}.${inbound.relation}' was excluded by relation selection`
5787
+ });
5788
+ continue;
5789
+ }
5790
+ checkedRelations += 1;
5791
+ const targetValues = unique(
5792
+ targets.flatMap((target) => flattenComparableValues2(getByPath(target, inbound.foreignField)))
5793
+ );
5794
+ if (targetValues.length === 0) {
5795
+ checkedModels.add(inbound.sourceModel);
5796
+ continue;
5797
+ }
5798
+ const scope = {
5799
+ database: inbound.definition.connection?.database ?? context.dbName,
5800
+ pool: inbound.definition.connection?.pool ?? context.poolName
5801
+ };
5802
+ const baseQuery = {
5803
+ [inbound.localField]: { $in: targetValues }
5804
+ };
5805
+ const sourceSoftDelete = resolveModelSoftDeleteConfig(inbound.definition);
5806
+ const query = !options.includeSoftDeletedReferences && sourceSoftDelete?.enabled ? { $and: [baseQuery, { [sourceSoftDelete.field]: null }] } : baseQuery;
5807
+ try {
5808
+ const collection = context.runtime.scopedCollection(inbound.sourceCollection, scope);
5809
+ if (!collection) {
5810
+ throw new Error("scopedCollection returned no collection");
5811
+ }
5812
+ checkedModels.add(inbound.sourceModel);
5813
+ const count = await collection.count(query, { session: options.session });
5814
+ if (typeof count !== "number" || !Number.isFinite(count) || count < 0) {
5815
+ throw new Error("relation usage count did not return a non-negative number");
5816
+ }
5817
+ if (count === 0) {
5818
+ continue;
5819
+ }
5820
+ const samples = await collection.find(query, {
5821
+ session: options.session,
5822
+ projection: { _id: 1 },
5823
+ limit: options.maxSamples
5824
+ });
5825
+ usages.push({
5826
+ sourceModel: inbound.sourceModel,
5827
+ sourceCollection: inbound.sourceCollection,
5828
+ relation: inbound.relation,
5829
+ localField: inbound.localField,
5830
+ foreignField: inbound.foreignField,
5831
+ count,
5832
+ sampleIds: samples.filter((sample) => isNonArrayObject(sample)).map((sample) => sample._id).filter((id) => id !== void 0).slice(0, options.maxSamples)
5833
+ });
5834
+ } catch (error) {
5835
+ skipped.push({
5836
+ sourceModel: inbound.sourceModel,
5837
+ reason: describeError(error)
5838
+ });
5839
+ }
5840
+ }
5841
+ const targetSampleIds = targets.map((target) => target._id).filter((id) => id !== void 0).slice(0, options.maxSamples);
5842
+ return {
5843
+ used: usages.length > 0,
5844
+ target: {
5845
+ matchedCount: targets.length,
5846
+ truncated: targetTruncated,
5847
+ sampleIds: targetSampleIds
5848
+ },
5849
+ checkedRelations,
5850
+ usages,
5851
+ coverage: {
5852
+ mode: "registered-declared",
5853
+ complete: !targetTruncated && skipped.length === 0,
5854
+ registryModels,
5855
+ checkedModels: [...checkedModels],
5856
+ skipped
5857
+ }
5858
+ };
5859
+ }
5860
+ function stripRelationUsageOptions(options) {
5861
+ if (!isNonArrayObject(options)) {
5862
+ return options;
5863
+ }
5864
+ const {
5865
+ maxTargets: _maxTargets,
5866
+ maxSamples: _maxSamples,
5867
+ includeRelations: _includeRelations,
5868
+ excludeRelations: _excludeRelations,
5869
+ includeSoftDeletedReferences: _includeSoftDeletedReferences,
5870
+ ...deleteOptions
5871
+ } = options;
5872
+ void _maxTargets;
5873
+ void _maxSamples;
5874
+ void _includeRelations;
5875
+ void _excludeRelations;
5876
+ void _includeSoftDeletedReferences;
5877
+ return deleteOptions;
5878
+ }
5879
+
5880
+ // src/capabilities/model/model-p0-operations.ts
5881
+ function relationUsageContext(host) {
5882
+ return {
5883
+ modelName: host.modelName,
5884
+ runtime: host.runtime,
5885
+ dbName: host.dbName,
5886
+ poolName: host.poolName
5887
+ };
5888
+ }
5889
+ function isNonEmptyFilter(filter) {
5890
+ return typeof filter === "object" && filter !== null && !Array.isArray(filter) && Object.keys(filter).length > 0;
5891
+ }
5892
+ function relationTargetReadOptions(options, projection) {
5893
+ const source = options ?? {};
5894
+ const result = { projection };
5895
+ for (const key of ["session", "collation", "hint", "comment", "let", "maxTimeMS", "readConcern", "readPreference"]) {
5896
+ if (source[key] !== void 0) {
5897
+ result[key] = source[key];
5898
+ }
5899
+ }
5900
+ return result;
5901
+ }
5902
+ function hydrateVectorSearchHits(host, hits) {
5903
+ return hits.map((hit) => {
5904
+ const document = host.hydrateDocument(hit.document);
5905
+ if (!document) {
5906
+ throw createError(ErrorCodes.INVALID_OPERATION, "Vector Search returned a non-document result");
5907
+ }
5908
+ return { document, score: hit.score };
5909
+ });
5910
+ }
5911
+ async function executeModelVectorSearch(host, options) {
5912
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
5913
+ const hits2 = await host.collection.vectorSearch(options);
5914
+ return hydrateVectorSearchHits(host, hits2);
5915
+ }
5916
+ const { withDeleted, onlyDeleted, ...collectionOptions } = options;
5917
+ if (withDeleted !== void 0 && typeof withDeleted !== "boolean") {
5918
+ throw createError(
5919
+ ErrorCodes.INVALID_VECTOR_SEARCH,
5920
+ "withDeleted must be a boolean",
5921
+ [{ field: "withDeleted", reason: "boolean required" }]
5922
+ );
5923
+ }
5924
+ if (onlyDeleted !== void 0 && typeof onlyDeleted !== "boolean") {
5925
+ throw createError(
5926
+ ErrorCodes.INVALID_VECTOR_SEARCH,
5927
+ "onlyDeleted must be a boolean",
5928
+ [{ field: "onlyDeleted", reason: "boolean required" }]
5929
+ );
5930
+ }
5931
+ const filter = host.softDeleteConfig?.enabled && !withDeleted ? applyModelSoftDeleteFilter(
5932
+ collectionOptions.filter,
5933
+ { withDeleted, onlyDeleted },
5934
+ host.softDeleteConfig
5935
+ ) : collectionOptions.filter;
5936
+ const hits = await host.collection.vectorSearch({
5937
+ ...collectionOptions,
5938
+ ...filter === void 0 ? {} : { filter }
5939
+ });
5940
+ return hydrateVectorSearchHits(host, hits);
5941
+ }
5942
+ async function executeRelationUsageCheck(host, filter, options) {
5943
+ const normalizedOptions = normalizeRelationUsageOptions(options);
5944
+ const context = relationUsageContext(host);
5945
+ try {
5946
+ const rawTargets = await host.collection.find(
5947
+ applyModelSoftDeleteFilter(filter, void 0, host.softDeleteConfig),
5948
+ {
5949
+ session: normalizedOptions.session,
5950
+ projection: buildRelationUsageTargetProjection(host.modelName),
5951
+ limit: normalizedOptions.maxTargets + 1
5952
+ }
5953
+ );
5954
+ const targets = rawTargets.filter(
5955
+ (target) => typeof target === "object" && target !== null && !Array.isArray(target)
5956
+ );
5957
+ return inspectDeclaredRelationUsage(
5958
+ context,
5959
+ targets.slice(0, normalizedOptions.maxTargets),
5960
+ normalizedOptions,
5961
+ targets.length > normalizedOptions.maxTargets
5962
+ );
5963
+ } catch (error) {
5964
+ return createUnavailableRelationUsageReport(context, error instanceof Error ? error.message : String(error));
5965
+ }
5966
+ }
5967
+ async function executeProtectedRelationDelete(host, filter, options, force) {
5968
+ if (!isNonEmptyFilter(filter)) {
5969
+ throw createError(
5970
+ ErrorCodes.INVALID_ARGUMENT,
5971
+ "protected relation deletion requires a non-empty filter",
5972
+ [{ field: "filter", reason: "non-empty object required" }]
5973
+ );
5974
+ }
5975
+ const rawOptions = options ?? {};
5976
+ if (rawOptions.includeRelations !== void 0 || rawOptions.excludeRelations !== void 0) {
5977
+ throw createError(
5978
+ ErrorCodes.INVALID_ARGUMENT,
5979
+ "protected relation deletion cannot narrow the declared relation scan",
5980
+ [{ field: "includeRelations/excludeRelations", reason: "protected deletion always scans all declared relations" }]
5981
+ );
5982
+ }
5983
+ const usageOptions = normalizeRelationUsageOptions(options);
5984
+ const context = relationUsageContext(host);
5985
+ const deleteOptions = stripRelationUsageOptions(options);
5986
+ let target;
5987
+ try {
5988
+ const targetFilter = force || !host.softDeleteConfig?.enabled ? filter : { ...filter, [host.softDeleteConfig.field]: null };
5989
+ const rawTarget = await host.collection.findOne(
5990
+ targetFilter,
5991
+ relationTargetReadOptions(options, buildRelationUsageTargetProjection(host.modelName))
5992
+ );
5993
+ target = typeof rawTarget === "object" && rawTarget !== null && !Array.isArray(rawTarget) ? rawTarget : null;
5994
+ } catch (error) {
5995
+ const usage2 = createUnavailableRelationUsageReport(
5996
+ context,
5997
+ error instanceof Error ? error.message : String(error)
5998
+ );
5999
+ throw createError(
6000
+ ErrorCodes.RELATION_USAGE_UNAVAILABLE,
6001
+ "Unable to resolve a target for protected relation deletion",
6002
+ [{ coverage: usage2.coverage, reason: usage2.coverage.skipped[0]?.reason, skipped: usage2.coverage.skipped }]
6003
+ );
6004
+ }
6005
+ const usage = await inspectDeclaredRelationUsage(context, target ? [target] : [], usageOptions);
6006
+ if (!usage.coverage.complete) {
6007
+ throw createError(
6008
+ ErrorCodes.RELATION_USAGE_UNAVAILABLE,
6009
+ "Declared relation usage coverage is incomplete; protected deletion was not performed",
6010
+ [{ coverage: usage.coverage, reason: "incomplete relation usage coverage", skipped: usage.coverage.skipped }]
6011
+ );
6012
+ }
6013
+ if (usage.used) {
6014
+ throw createError(
6015
+ ErrorCodes.RELATION_IN_USE,
6016
+ "Protected deletion was blocked because declared relations still reference the target",
6017
+ [{ used: usage.used, usages: usage.usages, coverage: usage.coverage }]
6018
+ );
6019
+ }
6020
+ if (target && target._id === void 0) {
6021
+ throw createError(
6022
+ ErrorCodes.RELATION_USAGE_UNAVAILABLE,
6023
+ "Protected deletion target did not include an _id",
6024
+ [{ coverage: usage.coverage, reason: "target _id missing", skipped: usage.coverage.skipped }]
6025
+ );
6026
+ }
6027
+ const deleteFilter = target ? { _id: target._id } : { _id: { $in: [] } };
6028
+ const result = force ? await host.forceDelete(deleteFilter, deleteOptions) : await host.deleteOne(deleteFilter, deleteOptions);
6029
+ return { result, usage };
6030
+ }
6031
+
5577
6032
  // src/capabilities/model/index.ts
6033
+ function resolveModelRegistryName(definition, fallback) {
6034
+ return Model.list().find((name) => Model.get(name)?.definition === definition) ?? fallback;
6035
+ }
5578
6036
  var ModelInstance = class {
5579
6037
  constructor(collection, runtime, options) {
5580
6038
  this.collection = collection;
@@ -5595,6 +6053,7 @@ var ModelInstance = class {
5595
6053
  this.dbName = options.dbName;
5596
6054
  this.poolName = options.poolName;
5597
6055
  this.definition = options.definition;
6056
+ this.modelName = options.modelName ?? resolveModelRegistryName(options.definition, options.collectionName);
5598
6057
  this.relations = new Map(Object.entries(options.definition.relations ?? {}));
5599
6058
  for (const [name, config] of this.relations) {
5600
6059
  validateRelationConfig(name, config);
@@ -5778,6 +6237,9 @@ var ModelInstance = class {
5778
6237
  };
5779
6238
  });
5780
6239
  }
6240
+ async vectorSearch(options) {
6241
+ return executeModelVectorSearch(this.p0OperationsContext(), options);
6242
+ }
5781
6243
  count(query, options) {
5782
6244
  const filteredQuery = applyModelSoftDeleteFilter(query, options, this._softDeleteConfig);
5783
6245
  return this.runV1HookedOperation("find", [filteredQuery, options], (nextQuery, nextOptions) => this.collection.count(nextQuery, nextOptions));
@@ -5827,6 +6289,19 @@ var ModelInstance = class {
5827
6289
  async deleteMany(filter, options) {
5828
6290
  return this.runModelWrite(() => orchestrateModelDeleteMany(this.mutationContext(), filter, options));
5829
6291
  }
6292
+ /**
6293
+ * Inspects only registered and declared inbound relations. It is read-only;
6294
+ * callers must check coverage before treating an empty result as safe.
6295
+ */
6296
+ async checkRelationUsage(filter, options) {
6297
+ return executeRelationUsageCheck(this.p0OperationsContext(), filter, options);
6298
+ }
6299
+ async deleteOneWithRelations(filter, options) {
6300
+ return executeProtectedRelationDelete(this.p0OperationsContext(), filter, options, false);
6301
+ }
6302
+ async forceDeleteWithRelations(filter, options) {
6303
+ return executeProtectedRelationDelete(this.p0OperationsContext(), filter, options, true);
6304
+ }
5830
6305
  // ── soft-delete extended methods (only meaningful when softDelete is enabled) ──
5831
6306
  findWithDeleted(query, options) {
5832
6307
  return findWithDeletedDocuments(this.softDeleteContext(), query, options);
@@ -5995,6 +6470,19 @@ var ModelInstance = class {
5995
6470
  populateDocument: (document, paths) => this.populateSingle(document, paths)
5996
6471
  }, doc);
5997
6472
  }
6473
+ p0OperationsContext() {
6474
+ return {
6475
+ collection: this.collection,
6476
+ softDeleteConfig: this._softDeleteConfig,
6477
+ modelName: this.modelName,
6478
+ runtime: this.runtime,
6479
+ dbName: this.dbName,
6480
+ poolName: this.poolName,
6481
+ hydrateDocument: (doc) => this.hydrateDocument(doc),
6482
+ deleteOne: (filter, options) => this.deleteOne(filter, options),
6483
+ forceDelete: (filter, options) => this.forceDelete(filter, options)
6484
+ };
6485
+ }
5998
6486
  softDeleteContext() {
5999
6487
  return {
6000
6488
  collection: this.collection,
@@ -7190,6 +7678,7 @@ var CountQueue = class {
7190
7678
  };
7191
7679
 
7192
7680
  // src/capabilities/pool/pool-health-checker.ts
7681
+ var SILENT_HEALTH_CHECKER_LOGGER = {};
7193
7682
  var HealthChecker = class {
7194
7683
  constructor(options = {}) {
7195
7684
  this._healthStatus = /* @__PURE__ */ new Map();
@@ -7199,7 +7688,7 @@ var HealthChecker = class {
7199
7688
  this._inProgress = /* @__PURE__ */ new Set();
7200
7689
  this._started = false;
7201
7690
  this._poolManager = options.poolManager ?? null;
7202
- this._logger = options.logger ?? console;
7691
+ this._logger = options.logger ?? SILENT_HEALTH_CHECKER_LOGGER;
7203
7692
  }
7204
7693
  register(poolNameOrConfig, configOrClient) {
7205
7694
  let poolName;
@@ -7323,6 +7812,7 @@ var HealthChecker = class {
7323
7812
  };
7324
7813
 
7325
7814
  // src/capabilities/pool/pool-selector.ts
7815
+ var SILENT_POOL_SELECTOR_LOGGER = {};
7326
7816
  var POOL_STRATEGY_ALIASES = {
7327
7817
  "round-robin": "roundRobin",
7328
7818
  "least-connections": "leastConnections",
@@ -7336,7 +7826,7 @@ var PoolSelector = class {
7336
7826
  constructor(options = {}) {
7337
7827
  this._roundRobinIndex = /* @__PURE__ */ new Map();
7338
7828
  this._strategy = normalizePoolStrategy(options.strategy);
7339
- this._logger = options.logger ?? console;
7829
+ this._logger = options.logger ?? SILENT_POOL_SELECTOR_LOGGER;
7340
7830
  }
7341
7831
  select(pools, context) {
7342
7832
  if (!pools || pools.length === 0) {
@@ -7467,11 +7957,12 @@ var PoolSelector = class {
7467
7957
  };
7468
7958
 
7469
7959
  // src/capabilities/pool/pool-stats-manager.ts
7960
+ var SILENT_POOL_STATS_LOGGER = {};
7470
7961
  var PoolStatsManager = class {
7471
7962
  constructor(options = {}) {
7472
7963
  this._stats = /* @__PURE__ */ new Map();
7473
7964
  this._buffer = [];
7474
- this._logger = options.logger ?? console;
7965
+ this._logger = options.logger ?? SILENT_POOL_STATS_LOGGER;
7475
7966
  this._batchInterval = setInterval(() => {
7476
7967
  this._flush();
7477
7968
  }, 100);
@@ -7719,9 +8210,9 @@ var ConnectionPoolManager = class {
7719
8210
  this._fallbackConfig = this.fallback;
7720
8211
  this._fallback = this.fallback;
7721
8212
  this._pools = this.pools;
7722
- this._selector = new PoolSelector({ strategy: this.strategy });
7723
- this._healthChecker = new HealthChecker({ poolManager: this });
7724
- this._stats = new PoolStatsManager({ logger: options.logger ?? void 0 });
8213
+ this._selector = new PoolSelector({ strategy: this.strategy, logger: this.logger ?? void 0 });
8214
+ this._healthChecker = new HealthChecker({ poolManager: this, logger: this.logger ?? void 0 });
8215
+ this._stats = new PoolStatsManager({ logger: this.logger ?? void 0 });
7725
8216
  }
7726
8217
  /**
7727
8218
  * Add a connection pool.
@@ -9554,7 +10045,7 @@ async function computeTotals(coll, query, limit, totals, defaults = {}, queryCac
9554
10045
  }
9555
10046
 
9556
10047
  // src/adapters/mongodb/queries/find-page.ts
9557
- function normalizePositiveInteger(value, fallback, field) {
10048
+ function normalizePositiveInteger2(value, fallback, field) {
9558
10049
  if (value === void 0 || value === null) {
9559
10050
  return fallback;
9560
10051
  }
@@ -9602,8 +10093,8 @@ async function executeFindPage(collection, options = {}, defaults = {}, queryCac
9602
10093
  const metaStartTs = Date.now();
9603
10094
  const metaSteps = [];
9604
10095
  const ext = options;
9605
- const page = normalizePositiveInteger(options.page, 1, "page");
9606
- const rawLimit = normalizePositiveInteger(options.limit, DEFAULT_FIND_PAGE_LIMIT, "limit");
10096
+ const page = normalizePositiveInteger2(options.page, 1, "page");
10097
+ const rawLimit = normalizePositiveInteger2(options.limit, DEFAULT_FIND_PAGE_LIMIT, "limit");
9607
10098
  const limit = defaults.findPageMaxLimit !== void 0 && rawLimit > defaults.findPageMaxLimit ? defaults.findPageMaxLimit : rawLimit;
9608
10099
  const sort = normalizeSortShape(options.sort);
9609
10100
  const baseQuery = options.query ?? {};
@@ -10477,6 +10968,138 @@ function explainDocuments(collection, query, options, defaults) {
10477
10968
  return createFindChain(collection, query, options, defaults).explain(options?.explain ?? "queryPlanner");
10478
10969
  }
10479
10970
 
10971
+ // src/adapters/mongodb/queries/vector-search.ts
10972
+ var VECTOR_SEARCH_SCORE_FIELD = "__monsqlize_vector_search_score__";
10973
+ function attachVectorSearchOperationContext(error, namespace) {
10974
+ if (error instanceof Error) {
10975
+ const contextualized = error;
10976
+ const existing = contextualized.monsqlize;
10977
+ try {
10978
+ contextualized.monsqlize = {
10979
+ ...existing && typeof existing === "object" ? existing : {},
10980
+ operation: "vectorSearch",
10981
+ namespace
10982
+ };
10983
+ } catch {
10984
+ }
10985
+ return error;
10986
+ }
10987
+ return createError(
10988
+ ErrorCodes.MONGODB_ERROR,
10989
+ "vectorSearch execution failed",
10990
+ [{ operation: "vectorSearch", namespace, reason: String(error) }]
10991
+ );
10992
+ }
10993
+ function assertNonEmptyString(value, field) {
10994
+ if (typeof value !== "string" || value.trim().length === 0) {
10995
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, `${field} must be a non-empty string`, [{ field, reason: "non-empty string required" }]);
10996
+ }
10997
+ }
10998
+ function assertPositiveInteger(value, field) {
10999
+ if (!Number.isInteger(value) || value <= 0) {
11000
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, `${field} must be a positive integer`, [{ field, reason: "positive integer required" }]);
11001
+ }
11002
+ }
11003
+ function isNonArrayObject2(value) {
11004
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11005
+ }
11006
+ function normalizeVectorProjection(value) {
11007
+ if (value === void 0) return void 0;
11008
+ if (Array.isArray(value)) {
11009
+ if (value.some((field) => typeof field !== "string" || field.length === 0)) {
11010
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "projection array entries must be non-empty strings", [{ field: "projection", reason: "array entries must be non-empty strings" }]);
11011
+ }
11012
+ return normalizeProjection(value);
11013
+ }
11014
+ if (!isNonArrayObject2(value)) {
11015
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "projection must be an object or string array", [{ field: "projection", reason: "object or string array required" }]);
11016
+ }
11017
+ return normalizeProjection(value);
11018
+ }
11019
+ function normalizeAggregateOptions(value) {
11020
+ if (value === void 0) return void 0;
11021
+ if (!isNonArrayObject2(value)) {
11022
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "aggregateOptions must be a non-array object", [{ field: "aggregateOptions", reason: "non-array object required" }]);
11023
+ }
11024
+ for (const key of ["meta", "stream", "explain"]) {
11025
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
11026
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, `aggregateOptions.${key} is not supported by vectorSearch()`, [{ field: `aggregateOptions.${key}`, reason: "reserved aggregate option" }]);
11027
+ }
11028
+ }
11029
+ return { ...value };
11030
+ }
11031
+ function buildVectorSearchPipeline(options) {
11032
+ if (!isNonArrayObject2(options)) {
11033
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "vectorSearch options must be a non-array object", [{ field: "options", reason: "non-array object required" }]);
11034
+ }
11035
+ assertNonEmptyString(options.index, "index");
11036
+ assertNonEmptyString(options.path, "path");
11037
+ assertPositiveInteger(options.limit, "limit");
11038
+ if (!Array.isArray(options.queryVector) || options.queryVector.length === 0) {
11039
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "queryVector must be a non-empty number array", [{ field: "queryVector", reason: "non-empty number array required" }]);
11040
+ }
11041
+ if (options.queryVector.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
11042
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "queryVector values must be finite numbers", [{ field: "queryVector", reason: "finite numbers required" }]);
11043
+ }
11044
+ if (options.exact !== void 0 && typeof options.exact !== "boolean") {
11045
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "exact must be a boolean", [{ field: "exact", reason: "boolean required" }]);
11046
+ }
11047
+ if (options.filter !== void 0 && !isNonArrayObject2(options.filter)) {
11048
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "filter must be a non-array object", [{ field: "filter", reason: "non-array object required" }]);
11049
+ }
11050
+ const isExactSearch = options.exact === true;
11051
+ if (isExactSearch) {
11052
+ if (options.numCandidates !== void 0) {
11053
+ throw createError(ErrorCodes.INVALID_VECTOR_SEARCH, "numCandidates is not supported when exact is true", [{ field: "numCandidates", reason: "ANN-only option" }]);
11054
+ }
11055
+ } else {
11056
+ assertPositiveInteger(options.numCandidates, "numCandidates");
11057
+ if (options.numCandidates < options.limit) {
11058
+ 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" }]);
11059
+ }
11060
+ }
11061
+ const vectorSearchStage = {
11062
+ index: options.index.trim(),
11063
+ path: options.path.trim(),
11064
+ queryVector: [...options.queryVector],
11065
+ limit: options.limit,
11066
+ ...isExactSearch ? { exact: true } : { numCandidates: options.numCandidates },
11067
+ ...options.filter === void 0 ? {} : { filter: options.filter }
11068
+ };
11069
+ const projection = normalizeVectorProjection(options.projection);
11070
+ const scoreStage = projection ? {
11071
+ $project: {
11072
+ ...projection,
11073
+ [VECTOR_SEARCH_SCORE_FIELD]: { $meta: "vectorSearchScore" }
11074
+ }
11075
+ } : {
11076
+ $set: {
11077
+ [VECTOR_SEARCH_SCORE_FIELD]: { $meta: "vectorSearchScore" }
11078
+ }
11079
+ };
11080
+ return {
11081
+ pipeline: [
11082
+ { $vectorSearch: vectorSearchStage },
11083
+ scoreStage
11084
+ ],
11085
+ aggregateOptions: normalizeAggregateOptions(options.aggregateOptions)
11086
+ };
11087
+ }
11088
+ function mapVectorSearchRows(rows) {
11089
+ return rows.map((row) => {
11090
+ const score = row[VECTOR_SEARCH_SCORE_FIELD];
11091
+ if (typeof score !== "number" || !Number.isFinite(score)) {
11092
+ throw createError(ErrorCodes.INVALID_OPERATION, "Vector Search result did not contain a finite score");
11093
+ }
11094
+ const document = { ...row };
11095
+ delete document[VECTOR_SEARCH_SCORE_FIELD];
11096
+ return {
11097
+ document,
11098
+ score
11099
+ };
11100
+ });
11101
+ }
11102
+
10480
11103
  // src/adapters/mongodb/writes/write-utils.ts
10481
11104
  function sleep2(ms) {
10482
11105
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -11650,6 +12273,19 @@ var MongoCollectionAccessor = class {
11650
12273
  } : void 0
11651
12274
  );
11652
12275
  }
12276
+ /** Searches a MongoDB Vector Search index through a constrained aggregate pipeline. */
12277
+ async vectorSearch(options) {
12278
+ const { pipeline, aggregateOptions } = buildVectorSearchPipeline(options);
12279
+ try {
12280
+ const rows = await this.aggregate(
12281
+ pipeline,
12282
+ aggregateOptions
12283
+ );
12284
+ return mapVectorSearchRows(rows);
12285
+ } catch (error) {
12286
+ throw attachVectorSearchOperationContext(error, this.getNamespace());
12287
+ }
12288
+ }
11653
12289
  /** Returns an array of distinct values for the given field key. */
11654
12290
  async distinct(key, query, options) {
11655
12291
  const startTs = Date.now();
@@ -12151,10 +12787,10 @@ function assertCompatPoolExists(poolManager, poolName) {
12151
12787
  function createPoolScope(runtime, poolName) {
12152
12788
  return {
12153
12789
  collection: (name) => runtime.scopedCollection(name, { pool: poolName }),
12154
- model: (name) => runtime.scopedModel(name, { pool: poolName }),
12790
+ model: (model) => runtime.scopedModel(model, { pool: poolName }),
12155
12791
  use: (dbName) => ({
12156
12792
  collection: (name) => runtime.scopedCollection(name, { pool: poolName, database: dbName }),
12157
- model: (name) => runtime.scopedModel(name, { pool: poolName, database: dbName })
12793
+ model: (model) => runtime.scopedModel(model, { pool: poolName, database: dbName })
12158
12794
  })
12159
12795
  };
12160
12796
  }
@@ -12184,6 +12820,7 @@ function createCompatModelInstance(config) {
12184
12820
  config.runtime,
12185
12821
  {
12186
12822
  collectionName: config.collectionName,
12823
+ modelName: config.modelName,
12187
12824
  dbName: config.dbName,
12188
12825
  poolName: config.poolName,
12189
12826
  definition: config.definition,
@@ -12221,6 +12858,7 @@ function createRuntimeModelInstance(host, name, scope) {
12221
12858
  host.runtime,
12222
12859
  {
12223
12860
  collectionName: actualCollectionName,
12861
+ modelName: registered.collectionName,
12224
12862
  dbName: databaseName,
12225
12863
  poolName,
12226
12864
  definition: registered.definition,
@@ -16308,7 +16946,7 @@ var MonSQLizeRuntime = class {
16308
16946
  requireCompatDbInstance(this);
16309
16947
  return {
16310
16948
  collection: (collectionName) => this.scopedCollection(collectionName, { database: name }),
16311
- model: (modelName) => this.scopedModel(modelName, { database: name })
16949
+ model: (model) => this.scopedModel(model, { database: name })
16312
16950
  };
16313
16951
  }
16314
16952
  pool(poolName) {
@@ -16340,7 +16978,8 @@ var MonSQLizeRuntime = class {
16340
16978
  });
16341
16979
  }
16342
16980
  // Model accessors ---------------------------------------------------------
16343
- scopedModel(name, options = {}) {
16981
+ scopedModel(model, options = {}) {
16982
+ const name = typeof model === "string" ? model : model.collectionName;
16344
16983
  const dbInstance = requireCompatDbInstance(this);
16345
16984
  if (this._client) {
16346
16985
  return this.createModelInstance(name, options);
@@ -16357,13 +16996,15 @@ var MonSQLizeRuntime = class {
16357
16996
  collection,
16358
16997
  runtime: this,
16359
16998
  collectionName: actualCollectionName,
16999
+ modelName: registered.collectionName,
16360
17000
  dbName: database ?? getRuntimeDatabaseName(this),
16361
17001
  poolName: pool,
16362
17002
  definition: registered.definition,
16363
17003
  schemaEngine: this._schemaDslEngine
16364
17004
  });
16365
17005
  }
16366
- model(name) {
17006
+ model(model) {
17007
+ const name = typeof model === "string" ? model : model.collectionName;
16367
17008
  if (this._client) {
16368
17009
  this.ensureConnected();
16369
17010
  return this.createModelInstance(name, {
@@ -16389,6 +17030,7 @@ var MonSQLizeRuntime = class {
16389
17030
  collection,
16390
17031
  runtime: this,
16391
17032
  collectionName: actualCollectionName,
17033
+ modelName: registered.collectionName,
16392
17034
  dbName: getRuntimeDatabaseName(this),
16393
17035
  definition: registered.definition,
16394
17036
  schemaEngine: this._schemaDslEngine
@@ -18163,6 +18805,7 @@ MonSQLize.PoolSelector = PoolSelector;
18163
18805
  MonSQLize.PoolStats = PoolStatsManager;
18164
18806
  MonSQLize.validatePoolConfig = validatePoolConfig;
18165
18807
  MonSQLize.validatePoolConfigSafe = validatePoolConfigSafe;
18808
+ MonSQLize.defineModel = defineModel;
18166
18809
  MonSQLize.Model = Model;
18167
18810
  MonSQLize.expr = expr;
18168
18811
  MonSQLize.createExpression = createExpression;