@classytic/mongokit 3.39.0 → 3.40.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.
Files changed (35) hide show
  1. package/dist/{PaginationEngine-DQ7beTCR.mjs → PaginationEngine-Dvl8jcRY.mjs} +33 -17
  2. package/dist/{Repository-D7fDpyFx.mjs → Repository-BMgPSkqZ.mjs} +279 -625
  3. package/dist/{Repository-IxI983_m.d.mts → Repository-CR8Y9g2E.d.mts} +57 -32
  4. package/dist/actions/index.mjs +1 -1
  5. package/dist/ai/index.d.mts +1 -1
  6. package/dist/better-auth/index.mjs +1 -1
  7. package/dist/{create-repository-DOe9E_5F.mjs → create-repository-BzBUMvd1.mjs} +2 -2
  8. package/dist/{create-repository-CRXjDa3N.d.mts → create-repository-CAle1MSU.d.mts} +4 -4
  9. package/dist/idempotency/index.d.mts +23 -0
  10. package/dist/idempotency/index.mjs +134 -0
  11. package/dist/index.d.mts +58 -32
  12. package/dist/index.mjs +285 -675
  13. package/dist/indexes/verify-indexes.d.mts +14 -1
  14. package/dist/indexes/verify-indexes.mjs +54 -7
  15. package/dist/{multi-tenant.plugin-B-tzNeYL.d.mts → multi-tenant.plugin-CBgCo9im.d.mts} +18 -2
  16. package/dist/outbox/index.d.mts +1 -1
  17. package/dist/outbox/index.mjs +6 -4
  18. package/dist/pagination/PaginationEngine.mjs +1 -1
  19. package/dist/{plugin-options-BuvtOSZf.d.mts → plugin-options-Zk46lD6F.d.mts} +15 -1
  20. package/dist/plugins/index.d.mts +8 -3
  21. package/dist/plugins/index.mjs +98 -25
  22. package/dist/{repository-ZbLtL7Nk.d.mts → repository-odl03582.d.mts} +20 -2
  23. package/dist/sync/index.d.mts +40 -0
  24. package/dist/sync/index.mjs +334 -0
  25. package/dist/tenant/index.mjs +2 -0
  26. package/dist/testkit/index.d.mts +2 -2
  27. package/dist/testkit/index.mjs +1 -1
  28. package/dist/{timestamp.plugin-B5eiaItQ.mjs → timestamp.plugin-DF0gJB6p.mjs} +86 -2
  29. package/dist/{to-plain-B8ufk3Qz.d.mts → to-plain-k7SjNlSS.d.mts} +2 -2
  30. package/dist/transaction-B5L9glO0.mjs +533 -0
  31. package/dist/{update-c_01A0if.mjs → update-DG0fCwRJ.mjs} +168 -11
  32. package/dist/utils/index.d.mts +1 -1
  33. package/dist/{validation-chain.plugin-Bju1D1Sn.mjs → validation-chain.plugin-BLY2gLEk.mjs} +192 -279
  34. package/dist/{validation-chain.plugin-DvrLJIbJ.d.mts → validation-chain.plugin-DYp9E7K2.d.mts} +25 -3
  35. package/package.json +13 -4
@@ -602,6 +602,20 @@ function classifyFilterFields(filters) {
602
602
  };
603
603
  }
604
604
  /**
605
+ * The ESR index to recommend for a keyset query: equality → sort (incl. the `_id`
606
+ * tiebreaker) → range, each field ONCE. A filtered field that is also a sort key
607
+ * takes its sort position — Mongo rejects an index naming a key twice.
608
+ */
609
+ function recommendKeysetIndex(filters, sort) {
610
+ const { equality, range } = classifyFilterFields(filters);
611
+ const notSorted = (f) => !Object.hasOwn(sort, f);
612
+ return [
613
+ ...equality.filter(notSorted).map((f) => [f, 1]),
614
+ ...Object.entries(sort),
615
+ ...range.filter(notSorted).map((f) => [f, 1])
616
+ ];
617
+ }
618
+ /**
605
619
  * Read a Mongoose schema's declared indexes, defensively.
606
620
  * Returns an empty array on any introspection failure.
607
621
  */
@@ -641,8 +655,9 @@ function readSchemaIndexes(Model) {
641
655
  function hasCompatibleKeysetIndex(indexes, filterFields, sort) {
642
656
  const sortKeys = Object.keys(sort);
643
657
  if (sortKeys.length === 0) return true;
644
- const filterSet = new Set(filterFields);
645
- const prefixLen = filterFields.length;
658
+ const prefixFields = filterFields.filter((f) => !Object.hasOwn(sort, f));
659
+ const filterSet = new Set(prefixFields);
660
+ const prefixLen = prefixFields.length;
646
661
  for (const [spec] of indexes) {
647
662
  if (!spec || typeof spec !== "object") continue;
648
663
  const entries = Object.entries(spec);
@@ -909,9 +924,9 @@ var PaginationEngine = class {
909
924
  if (readPreference) query = query.read(readPreference);
910
925
  const hasFilters = Object.keys(filters).length > 0;
911
926
  const useEstimated = this.config.useEstimatedCount && !hasFilters;
912
- let countPromise;
913
- if ((countStrategy === "estimated" || useEstimated) && !hasFilters) countPromise = this.Model.estimatedDocumentCount();
914
- else if (countStrategy === "none") countPromise = Promise.resolve(0);
927
+ let runCount;
928
+ if ((countStrategy === "estimated" || useEstimated) && !hasFilters && !session) runCount = () => Promise.resolve(this.Model.estimatedDocumentCount());
929
+ else if (countStrategy === "none") runCount = () => Promise.resolve(0);
915
930
  else if (countStrategy === "capped") {
916
931
  /**
917
932
  * `.limit(n)` on a count is MongoDB's own ceiling — the server stops
@@ -921,19 +936,21 @@ var PaginationEngine = class {
921
936
  */
922
937
  const cappedTarget = countFilters ?? filters;
923
938
  const cappedQuery = this.Model.countDocuments(cappedTarget).limit(resolveCountLimit(countLimit)).session(session ?? null);
939
+ if (collation) cappedQuery.collation(collation);
924
940
  if (hint) cappedQuery.hint(hint);
925
941
  if (maxTimeMS) cappedQuery.maxTimeMS(maxTimeMS);
926
942
  if (readPreference) cappedQuery.read(readPreference);
927
- countPromise = cappedQuery.exec();
943
+ runCount = () => cappedQuery.exec();
928
944
  } else {
929
945
  const countTarget = countFilters ?? filters;
930
946
  const countQuery = this.Model.countDocuments(countTarget).session(session ?? null);
947
+ if (collation) countQuery.collation(collation);
931
948
  if (hint) countQuery.hint(hint);
932
949
  if (maxTimeMS) countQuery.maxTimeMS(maxTimeMS);
933
950
  if (readPreference) countQuery.read(readPreference);
934
- countPromise = countQuery.exec();
951
+ runCount = () => countQuery.exec();
935
952
  }
936
- const [data, total] = await Promise.all([query.exec(), countPromise]);
953
+ const [data, total] = session ? [await query.exec(), await runCount()] : await Promise.all([query.exec(), runCount()]);
937
954
  const totalPages = countStrategy === "none" ? 0 : calculateTotalPages(total, sanitizedLimit);
938
955
  /**
939
956
  * A capped count that came back AT its ceiling is a floor, not a total.
@@ -1025,13 +1042,9 @@ var PaginationEngine = class {
1025
1042
  const effectiveSortFields = sortFieldsForMessage(normalizedSort);
1026
1043
  if (process.env.NODE_ENV !== "test" && filterKeys.length > 0 && effectiveSortFields.length > 0) {
1027
1044
  const indexes = this._getSchemaIndexes();
1028
- const { equality, range } = classifyFilterFields(filters);
1045
+ const { equality } = classifyFilterFields(filters);
1029
1046
  if (!(hasCompatibleKeysetIndex(indexes, filterKeys, normalizedSort) || equality.length !== filterKeys.length && hasCompatibleKeysetIndex(indexes, equality, normalizedSort))) {
1030
- const indexFields = [
1031
- ...equality.map((f) => `${f}: 1`),
1032
- ...Object.keys(normalizedSort).map((f) => `${f}: ${normalizedSort[f]}`),
1033
- ...range.map((f) => `${f}: 1`)
1034
- ];
1047
+ const indexFields = recommendKeysetIndex(filters, normalizedSort).map(([f, dir]) => `${f}: ${dir}`);
1035
1048
  warn(`[mongokit] Keyset pagination with filters [${filterKeys.join(", ")}] and sort [${effectiveSortFields.join(", ")}] has no matching schema-declared compound index. Without one, the tiebreaker forces a BLOCKING SORT over every matching row, on every page. Declare: { ${indexFields.join(", ")} }. (Collection-level indexes created outside the schema are not visible here.)`);
1036
1049
  }
1037
1050
  }
@@ -1084,7 +1097,8 @@ var PaginationEngine = class {
1084
1097
  hasMore,
1085
1098
  next: hasMore ? mint(data[data.length - 1]) : null,
1086
1099
  prev: hasPrev ? mint(data[0]) : null,
1087
- hasPrev
1100
+ hasPrev,
1101
+ end: mint(data[data.length - 1])
1088
1102
  };
1089
1103
  }
1090
1104
  /**
@@ -1149,11 +1163,13 @@ var PaginationEngine = class {
1149
1163
  { $limit: resolveCountLimit(countLimit) },
1150
1164
  { $count: "count" }
1151
1165
  ] : [...pipeline, { $count: "count" }];
1152
- const [dataRows, countRows] = await Promise.all([run([
1166
+ const runData = () => run([
1153
1167
  ...pipeline,
1154
1168
  { $skip: skip },
1155
1169
  { $limit: fetchLimit }
1156
- ]), countStrategy === "none" ? Promise.resolve([]) : run(countStages)]);
1170
+ ]);
1171
+ const runCount = () => countStrategy === "none" ? Promise.resolve([]) : run(countStages);
1172
+ const [dataRows, countRows] = session ? [await runData(), await runCount()] : await Promise.all([runData(), runCount()]);
1157
1173
  const data = dataRows;
1158
1174
  const total = countRows[0]?.count || 0;
1159
1175
  const totalPages = countStrategy === "none" ? 0 : calculateTotalPages(total, sanitizedLimit);