@classytic/mongokit 3.5.4 → 3.5.6

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.
Files changed (29) hide show
  1. package/README.md +228 -9
  2. package/dist/{PaginationEngine-DCs-zKwZ.mjs → PaginationEngine-CpsQl471.mjs} +5 -3
  3. package/dist/actions/index.d.mts +1 -1
  4. package/dist/ai/index.d.mts +1 -1
  5. package/dist/coercion-B19MD38s.d.mts +106 -0
  6. package/dist/id-resolution-LZ-cCwR5.mjs +81 -0
  7. package/dist/{index-BNpVzw_-.d.mts → index-DmVbUzDL.d.mts} +1 -1
  8. package/dist/index.d.mts +84 -6
  9. package/dist/index.mjs +101 -36
  10. package/dist/indexes-qIBCZ3mY.d.mts +41 -0
  11. package/dist/{mongooseToJsonSchema-DpjhFfaO.mjs → mongooseToJsonSchema-8Cyu3NFJ.mjs} +50 -45
  12. package/dist/{mongooseToJsonSchema-Dsx3YAAx.d.mts → mongooseToJsonSchema-DQMU0Dlv.d.mts} +4 -3
  13. package/dist/pagination/PaginationEngine.d.mts +1 -1
  14. package/dist/pagination/PaginationEngine.mjs +1 -1
  15. package/dist/plugins/index.d.mts +1 -1
  16. package/dist/plugins/index.mjs +1 -1
  17. package/dist/query/primitives/coercion.d.mts +2 -0
  18. package/dist/query/primitives/coercion.mjs +148 -0
  19. package/dist/query/primitives/geo.d.mts +116 -0
  20. package/dist/query/primitives/geo.mjs +211 -0
  21. package/dist/query/primitives/indexes.d.mts +2 -0
  22. package/dist/query/primitives/indexes.mjs +46 -0
  23. package/dist/{types-CkE3mOaW.d.mts → types-BwO4u3JN.d.mts} +25 -0
  24. package/dist/utils/index.d.mts +60 -3
  25. package/dist/utils/index.mjs +4 -3
  26. package/dist/{validation-chain.plugin-BYMZB8dN.d.mts → validation-chain.plugin-BtmT-FFB.d.mts} +1 -1
  27. package/dist/{validation-chain.plugin-KjPV_6_N.mjs → validation-chain.plugin-DCYjnPau.mjs} +81 -20
  28. package/package.json +15 -3
  29. /package/dist/{field-selection-reyDRzXf.mjs → field-selection-C5o7jyyA.mjs} +0 -0
@@ -1,4 +1,4 @@
1
- import { I as Logger, J as Plugin, St as ValidatorDefinition, T as FieldPreset, U as OffsetPaginationResult, V as ObjectId, Z as PopulateSpec, bt as ValidationChainOptions, et as RepositoryContext, f as CascadeOptions, l as CacheOptions, nt as RepositoryInstance, pt as SortSpec, st as SelectSpec, u as CacheStats, ut as SoftDeleteOptions } from "./types-CkE3mOaW.mjs";
1
+ import { I as Logger, J as Plugin, St as ValidatorDefinition, T as FieldPreset, U as OffsetPaginationResult, V as ObjectId, Z as PopulateSpec, bt as ValidationChainOptions, et as RepositoryContext, f as CascadeOptions, l as CacheOptions, nt as RepositoryInstance, pt as SortSpec, st as SelectSpec, u as CacheStats, ut as SoftDeleteOptions } from "./types-BwO4u3JN.mjs";
2
2
  import mongoose, { ClientSession } from "mongoose";
3
3
 
4
4
  //#region src/plugins/aggregate-helpers.plugin.d.ts
@@ -1,7 +1,9 @@
1
1
  import { a as warn, i as debug, n as parseDuplicateKeyError, t as createError } from "./error-Bpbi_NKo.mjs";
2
2
  import { a as exists, c as getOrCreate, d as deleteByQuery, g as upsert, i as count, m as createMany, n as updateByQuery, o as getById, p as create, s as getByQuery, t as update, u as deleteById, v as distinct, y as LookupBuilder } from "./update-AVfKWNGt.mjs";
3
- import { t as PaginationEngine } from "./PaginationEngine-DCs-zKwZ.mjs";
4
- import { a as byIdKey, c as listQueryKey, l as modelPattern, o as byQueryKey, r as getFieldsForUser, u as versionKey } from "./field-selection-reyDRzXf.mjs";
3
+ import { t as PaginationEngine } from "./PaginationEngine-CpsQl471.mjs";
4
+ import { hasNearOperator, rewriteNearForCount } from "./query/primitives/geo.mjs";
5
+ import { r as isValidIdForType, t as getSchemaIdType } from "./id-resolution-LZ-cCwR5.mjs";
6
+ import { a as byIdKey, c as listQueryKey, l as modelPattern, o as byQueryKey, r as getFieldsForUser, u as versionKey } from "./field-selection-C5o7jyyA.mjs";
5
7
  import mongoose from "mongoose";
6
8
  //#region src/plugins/aggregate-helpers.plugin.ts
7
9
  /**
@@ -1182,6 +1184,8 @@ var Repository = class {
1182
1184
  _pagination;
1183
1185
  _hookMode;
1184
1186
  idField;
1187
+ searchMode;
1188
+ searchFields;
1185
1189
  _hasTextIndex = null;
1186
1190
  constructor(Model, plugins = [], paginationConfig = {}, options = {}) {
1187
1191
  this.Model = Model;
@@ -1190,6 +1194,9 @@ var Repository = class {
1190
1194
  this._pagination = new PaginationEngine(Model, paginationConfig);
1191
1195
  this._hookMode = options.hooks ?? "async";
1192
1196
  this.idField = options.idField ?? "_id";
1197
+ this.searchMode = options.searchMode ?? "text";
1198
+ this.searchFields = options.searchFields;
1199
+ if (this.searchMode === "regex" && (!this.searchFields || this.searchFields.length === 0)) warn(`[mongokit] Repository "${this.model}" configured with searchMode: 'regex' but no searchFields provided. getAll({ search }) will throw until searchFields is set.`);
1193
1200
  plugins.forEach((plugin) => {
1194
1201
  this.use(plugin);
1195
1202
  });
@@ -1364,7 +1371,7 @@ var Repository = class {
1364
1371
  });
1365
1372
  return result;
1366
1373
  }
1367
- if (typeof id === "string" && !mongoose.Types.ObjectId.isValid(id)) {
1374
+ if (!isValidIdForType(id, getSchemaIdType(this.Model.schema))) {
1368
1375
  if (context.throwOnNotFound === false || options.throwOnNotFound === false) return null;
1369
1376
  throw createError(404, "Document not found");
1370
1377
  }
@@ -1542,25 +1549,19 @@ var Repository = class {
1542
1549
  sort: context.sort ?? params.sort
1543
1550
  });
1544
1551
  const filters = context.filters ?? params.filters ?? {};
1545
- const search = context.search ?? params.search;
1546
- const sort = context.sort ?? params.sort ?? "-createdAt";
1552
+ const search = context.search;
1553
+ const isNearQuery = hasNearOperator(filters);
1554
+ const sort = this._resolveSort(context, params, isNearQuery);
1547
1555
  const limit = context.limit ?? params.limit ?? params.pagination?.limit ?? this._pagination.config.defaultLimit;
1548
1556
  const page = context.page ?? params.pagination?.page ?? params.page;
1549
1557
  const after = context.after ?? params.cursor ?? params.after;
1550
1558
  const mode = context.mode ?? params.mode;
1551
- let useKeyset = false;
1552
- if (mode) useKeyset = mode === "keyset";
1553
- else useKeyset = !page && !!(after || sort !== "-createdAt" && (context.sort ?? params.sort));
1554
- const query = { ...filters };
1555
- if (search) {
1556
- if (this._hasTextIndex === null) this._hasTextIndex = this.Model.schema.indexes().some((idx) => idx[0] && Object.values(idx[0]).includes("text"));
1557
- if (this._hasTextIndex) query.$text = { $search: search };
1558
- else throw createError(400, `No text index found for ${this.model}. Cannot perform text search.`);
1559
- }
1559
+ const useKeyset = this._detectPaginationMode(mode, page, after, sort, context, params);
1560
+ const query = this._buildSearchQuery(filters, search);
1560
1561
  const populateSpec = options.populateOptions || params.populateOptions || context.populate || options.populate;
1561
1562
  const paginationOptions = {
1562
1563
  filters: query,
1563
- sort: this._parseSort(sort),
1564
+ sort: isNearQuery ? void 0 : this._parseSort(sort),
1564
1565
  limit,
1565
1566
  populate: this._parsePopulate(populateSpec),
1566
1567
  select: context.select || options.select,
@@ -1630,11 +1631,21 @@ var Repository = class {
1630
1631
  sort: paginationOptions.sort,
1631
1632
  after
1632
1633
  });
1633
- else result = await this._pagination.paginate({
1634
- ...paginationOptions,
1635
- page: page || 1,
1636
- countStrategy: context.countStrategy ?? params.countStrategy
1637
- });
1634
+ else {
1635
+ let countFilters;
1636
+ let forcedCountStrategy;
1637
+ if (isNearQuery) {
1638
+ const rewritten = rewriteNearForCount(query);
1639
+ if (rewritten) countFilters = rewritten;
1640
+ else forcedCountStrategy = "none";
1641
+ }
1642
+ result = await this._pagination.paginate({
1643
+ ...paginationOptions,
1644
+ page: page || 1,
1645
+ countFilters,
1646
+ countStrategy: context.countStrategy ?? params.countStrategy ?? forcedCountStrategy
1647
+ });
1648
+ }
1638
1649
  await this._emitHook("after:getAll", {
1639
1650
  context,
1640
1651
  result
@@ -2201,6 +2212,56 @@ var Repository = class {
2201
2212
  return context;
2202
2213
  }
2203
2214
  /**
2215
+ * Resolve the effective sort for getAll. Handles $near conflict detection
2216
+ * (warns + drops explicit sort when $near is in filters).
2217
+ */
2218
+ _resolveSort(context, params, isNearQuery) {
2219
+ const explicitSort = context.sort ?? params.sort;
2220
+ if (isNearQuery) {
2221
+ if (explicitSort !== void 0) warn(`[mongokit] Repository "${this.model}" dropping explicit sort (${JSON.stringify(explicitSort)}) because the filter contains $near / $nearSphere. MongoDB forbids explicit sort with $near. Use [withinRadius] instead of [near] if you need a custom sort.`);
2222
+ return;
2223
+ }
2224
+ return explicitSort ?? "-createdAt";
2225
+ }
2226
+ /**
2227
+ * Detect whether to use keyset (cursor) or offset (page) pagination.
2228
+ */
2229
+ _detectPaginationMode(mode, page, after, sort, context, params) {
2230
+ if (mode) return mode === "keyset";
2231
+ return !page && !!(after || sort !== "-createdAt" && (context.sort ?? params.sort));
2232
+ }
2233
+ /**
2234
+ * Build the MongoDB query filter with search merged in. Handles text
2235
+ * search ($text), regex search ($or of $regex), and the search-resolver
2236
+ * plugin contract (search already cleared by a before:getAll hook).
2237
+ */
2238
+ _buildSearchQuery(filters, search) {
2239
+ const query = { ...filters };
2240
+ if (!search) return query;
2241
+ if (this._hasTextIndex === null) this._hasTextIndex = this.Model.schema.indexes().some((idx) => idx[0] && Object.values(idx[0]).includes("text"));
2242
+ let effectiveMode = this.searchMode === "regex" ? "regex" : "text";
2243
+ if (this.searchMode === "auto") effectiveMode = this._hasTextIndex ? "text" : "regex";
2244
+ if (effectiveMode === "regex") {
2245
+ if (!this.searchFields || this.searchFields.length === 0) throw createError(400, `Repository "${this.model}" configured with searchMode: '${this.searchMode}' but no searchFields provided.`);
2246
+ const escaped = String(search).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2247
+ const orConds = this.searchFields.map((field) => ({ [field]: {
2248
+ $regex: escaped,
2249
+ $options: "i"
2250
+ } }));
2251
+ if (Array.isArray(query.$or)) {
2252
+ const existingOr = query.$or;
2253
+ delete query.$or;
2254
+ query.$and = [
2255
+ ...Array.isArray(query.$and) ? query.$and : [],
2256
+ { $or: existingOr },
2257
+ { $or: orConds }
2258
+ ];
2259
+ } else query.$or = orConds;
2260
+ } else if (this._hasTextIndex) query.$text = { $search: search };
2261
+ else throw createError(400, `No text index found for ${this.model}. Cannot perform text search. Configure Repository with searchMode: 'regex' (and searchFields) or 'auto' to enable index-free search.`);
2262
+ return query;
2263
+ }
2264
+ /**
2204
2265
  * Parse sort string or object
2205
2266
  */
2206
2267
  _parseSort(sort) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/mongokit",
3
- "version": "3.5.4",
3
+ "version": "3.5.6",
4
4
  "description": "Production-grade MongoDB repositories with zero dependencies - smart pagination, events, and plugins",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -31,6 +31,18 @@
31
31
  "./ai": {
32
32
  "types": "./dist/ai/index.d.mts",
33
33
  "default": "./dist/ai/index.mjs"
34
+ },
35
+ "./query/primitives/geo": {
36
+ "types": "./dist/query/primitives/geo.d.mts",
37
+ "default": "./dist/query/primitives/geo.mjs"
38
+ },
39
+ "./query/primitives/coercion": {
40
+ "types": "./dist/query/primitives/coercion.d.mts",
41
+ "default": "./dist/query/primitives/coercion.mjs"
42
+ },
43
+ "./query/primitives/indexes": {
44
+ "types": "./dist/query/primitives/indexes.d.mts",
45
+ "default": "./dist/query/primitives/indexes.mjs"
34
46
  }
35
47
  },
36
48
  "files": [
@@ -81,7 +93,7 @@
81
93
  },
82
94
  "homepage": "https://github.com/classytic/mongokit#readme",
83
95
  "peerDependencies": {
84
- "mongoose": "^9.0.0"
96
+ "mongoose": ">=9.4.1"
85
97
  },
86
98
  "engines": {
87
99
  "node": ">=22"
@@ -111,7 +123,7 @@
111
123
  "@vitest/coverage-v8": "^3.2.4",
112
124
  "knip": "^6.3.0",
113
125
  "mongodb-memory-server": "^10.4.3",
114
- "mongoose": "^9.1.3",
126
+ "mongoose": "^9.4.1",
115
127
  "tsdown": "^0.21.7",
116
128
  "typescript": "^5.7.0",
117
129
  "vitest": "^3.2.4"