@classytic/mongokit 3.12.0 → 3.13.1

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 (38) hide show
  1. package/README.md +126 -4
  2. package/dist/{PaginationEngine-BDaAsyPn.mjs → PaginationEngine-CfQRM1yz.mjs} +16 -16
  3. package/dist/Repository-d5gWSROf.mjs +3603 -0
  4. package/dist/actions/index.d.mts +1 -1
  5. package/dist/actions/index.mjs +1 -1
  6. package/dist/adapter/index.d.mts +190 -0
  7. package/dist/adapter/index.mjs +2 -0
  8. package/dist/adapter-DTDMnUOH.mjs +158 -0
  9. package/dist/ai/index.d.mts +1 -1
  10. package/dist/ai/index.mjs +1 -1
  11. package/dist/better-auth/index.d.mts +149 -0
  12. package/dist/better-auth/index.mjs +81 -0
  13. package/dist/field-selection-Br3WPOEb.mjs +99 -0
  14. package/dist/{index-D9cORR-o.d.mts → index-BbHaKwi5.d.mts} +33 -13
  15. package/dist/index.d.mts +690 -30
  16. package/dist/index.mjs +240 -17
  17. package/dist/{logger-8-VS1za6.mjs → logger-DprijP5h.mjs} +1 -5
  18. package/dist/{mongooseToJsonSchema-B187ufOP.mjs → mongooseToJsonSchema-BYbG894y.mjs} +1 -1
  19. package/dist/{mongooseToJsonSchema-rtkdbBhf.d.mts → mongooseToJsonSchema-BgRAaXX-.d.mts} +2 -1
  20. package/dist/pagination/PaginationEngine.d.mts +2 -2
  21. package/dist/pagination/PaginationEngine.mjs +1 -1
  22. package/dist/plugins/index.d.mts +2 -2
  23. package/dist/plugins/index.mjs +2 -2
  24. package/dist/query/primitives/coercion.d.mts +1 -1
  25. package/dist/query/primitives/coercion.mjs +1 -1
  26. package/dist/query/primitives/indexes.d.mts +1 -1
  27. package/dist/{types-DXscZnLu.d.mts → types-BbaPZleN.d.mts} +371 -112
  28. package/dist/{update-CDxPtpsk.mjs → update-C-3viiw1.mjs} +112 -28
  29. package/dist/utils/index.d.mts +2 -91
  30. package/dist/utils/index.mjs +5 -5
  31. package/dist/validation-chain.plugin-BLVp_Pw2.mjs +2399 -0
  32. package/dist/{validation-chain.plugin-CH-xPoKM.d.mts → validation-chain.plugin-CAPYCj_T.d.mts} +212 -80
  33. package/package.json +20 -3
  34. package/dist/field-selection-dI0m4RBs.mjs +0 -221
  35. package/dist/validation-chain.plugin-BicIgzzt.mjs +0 -4970
  36. /package/dist/{coercion-oM8G4b6m.d.mts → coercion-TRMmR49h.d.mts} +0 -0
  37. /package/dist/{id-resolution-e4BT9zAV.mjs → id-resolution-B0Odvvou.mjs} +0 -0
  38. /package/dist/{indexes-C26GNZRU.d.mts → indexes-CgOF7Wjd.d.mts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
- import { r as warn } from "./logger-8-VS1za6.mjs";
2
+ import { n as warn } from "./logger-DprijP5h.mjs";
3
3
  import { t as createError } from "./error-B00pI7dV.mjs";
4
4
  import { isFilter } from "@classytic/repo-core/filter";
5
5
  //#region src/filter/compile.ts
@@ -13,9 +13,79 @@ import { isFilter } from "@classytic/repo-core/filter";
13
13
  * appropriately. This keeps the portable API ergonomic for apps that
14
14
  * write Mongo queries directly today.
15
15
  */
16
+ /**
17
+ * Operator shorthands arc propagates from bracket-syntax URL params.
18
+ * Fastify parses `?createdAt[gte]=...&createdAt[lte]=...` into
19
+ * `{ createdAt: { gte: '...', lte: '...' } }` — without `$` prefixes.
20
+ * Arc intentionally forwards these as-is ("the kit's filter compiler
21
+ * handles them"). This set covers every operator the arc aggregation
22
+ * guard accepts in `parseDateRange` and `hasFilterOnField`.
23
+ */
24
+ const SHORTHAND_OPS = new Set([
25
+ "eq",
26
+ "ne",
27
+ "gt",
28
+ "gte",
29
+ "lt",
30
+ "lte",
31
+ "in",
32
+ "nin",
33
+ "exists",
34
+ "regex",
35
+ "mod"
36
+ ]);
37
+ /** Range operators whose string values should be coerced to Date. */
38
+ const RANGE_OPS = new Set([
39
+ "gt",
40
+ "gte",
41
+ "lt",
42
+ "lte"
43
+ ]);
44
+ /** ISO-8601 date-only or datetime string pattern. */
45
+ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}/;
46
+ /**
47
+ * Coerce a string value to a Date when it looks like an ISO-8601 date.
48
+ * Needed because MongoDB won't match a BSON Date field against a string
49
+ * value even when using range operators — BSON type comparison treats
50
+ * Date (type 9) and String (type 2) as distinct, so `{ createdAt: {
51
+ * $gte: "2026-05-01" } }` never matches a Date-typed field.
52
+ */
53
+ function tryCoerceDate(value) {
54
+ if (typeof value === "string" && ISO_DATE_RE.test(value)) {
55
+ const d = new Date(value);
56
+ return Number.isNaN(d.getTime()) ? value : d;
57
+ }
58
+ return value;
59
+ }
60
+ /**
61
+ * Expand operator shorthand values in a plain Mongo-style query object.
62
+ * Only expands nested objects whose keys are ALL in `SHORTHAND_OPS` — so
63
+ * real nested documents (e.g. `address: { city: 'Dhaka' }`) pass through
64
+ * unchanged, while `createdAt: { gte: '2026-04-01', lte: '2026-05-01' }`
65
+ * becomes `createdAt: { $gte: Date(2026-04-01), $lte: Date(2026-05-01) }`.
66
+ * String values on range operators are coerced to Date when they match the
67
+ * ISO-8601 pattern, so BSON Date fields in MongoDB are compared correctly.
68
+ */
69
+ function expandShorthands(obj) {
70
+ const out = {};
71
+ for (const [key, value] of Object.entries(obj)) {
72
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
73
+ const nested = value;
74
+ const keys = Object.keys(nested);
75
+ if (keys.length > 0 && keys.every((k) => !k.startsWith("$") && SHORTHAND_OPS.has(k))) {
76
+ const expanded = {};
77
+ for (const [op, opVal] of Object.entries(nested)) expanded[`$${op}`] = RANGE_OPS.has(op) ? tryCoerceDate(opVal) : opVal;
78
+ out[key] = expanded;
79
+ continue;
80
+ }
81
+ }
82
+ out[key] = value;
83
+ }
84
+ return out;
85
+ }
16
86
  function compileFilterToMongo(input) {
17
87
  if (!input) return {};
18
- if (!isFilter(input)) return input;
88
+ if (!isFilter(input)) return expandShorthands(input);
19
89
  return compile(input);
20
90
  }
21
91
  function compile(filter) {
@@ -475,17 +545,17 @@ async function aggregatePaginate(Model, pipeline, options = {}) {
475
545
  const skip = (page - 1) * limit;
476
546
  if (limit > 1e3) warn(`[mongokit] Large aggregation limit (${limit}). $facet results must be <16MB. Consider using Repository.aggregatePaginate() for safer handling of large datasets.`);
477
547
  const facetPipeline = [...pipeline, { $facet: {
478
- docs: [{ $skip: skip }, { $limit: limit }],
548
+ data: [{ $skip: skip }, { $limit: limit }],
479
549
  total: [{ $count: "count" }]
480
550
  } }];
481
551
  const aggregation = Model.aggregate(facetPipeline);
482
552
  if (options.session) aggregation.session(options.session);
483
553
  const [result] = await aggregation.exec();
484
- const docs = result.docs || [];
554
+ const data = result.data || [];
485
555
  const total = result.total[0]?.count || 0;
486
556
  const pages = Math.ceil(total / limit);
487
557
  return {
488
- docs,
558
+ data,
489
559
  total,
490
560
  page,
491
561
  limit,
@@ -662,54 +732,49 @@ var delete_exports = /* @__PURE__ */ __exportAll({
662
732
  softDelete: () => softDelete
663
733
  });
664
734
  /**
665
- * Delete by ID
735
+ * Delete by ID. Returns `null` on miss (matches `update()` convention) —
736
+ * a second delete on the same id is a no-op, not an error.
666
737
  */
667
738
  async function deleteById(Model, id, options = {}) {
668
739
  const query = {
669
740
  _id: id,
670
741
  ...options.query
671
742
  };
672
- if (!await Model.findOneAndDelete(query).session(options.session ?? null)) return {
673
- success: false,
674
- message: "Document not found",
675
- id: String(id)
676
- };
743
+ if (!await Model.findOneAndDelete(query).session(options.session ?? null)) return null;
677
744
  return {
678
- success: true,
679
745
  message: "Deleted successfully",
680
746
  id: String(id)
681
747
  };
682
748
  }
683
749
  /**
684
- * Delete many documents
750
+ * Delete many documents. Always returns a {@link DeleteResult} (never
751
+ * null) — a deleteMany with zero matches is a successful op with
752
+ * `count: 0`, not a miss.
685
753
  */
686
754
  async function deleteMany(Model, query, options = {}) {
687
755
  return {
688
- success: true,
689
756
  count: (await Model.deleteMany(query).session(options.session ?? null)).deletedCount,
690
757
  message: "Deleted successfully"
691
758
  };
692
759
  }
693
760
  /**
694
- * Delete by query
761
+ * Delete by query. Returns `null` on miss unless `throwOnNotFound: true`.
695
762
  */
696
763
  async function deleteByQuery(Model, query, options = {}) {
697
764
  const document = await Model.findOneAndDelete(query).session(options.session ?? null);
698
765
  if (!document) {
699
766
  if (options.throwOnNotFound === true) throw createError(404, "Document not found");
700
- return {
701
- success: false,
702
- message: "Document not found"
703
- };
767
+ return null;
704
768
  }
705
769
  return {
706
- success: true,
707
770
  message: "Deleted successfully",
708
771
  id: String(document._id)
709
772
  };
710
773
  }
711
774
  /**
712
- * Soft delete (set deleted flag)
775
+ * Soft delete (set deleted flag). Returns `null` on miss for parity with
776
+ * `deleteById`; callers that prefer a throw can wrap or use `throwOnNotFound`
777
+ * pattern at the call site.
713
778
  */
714
779
  async function softDelete(Model, id, options = {}) {
715
780
  if (!await Model.findByIdAndUpdate(id, {
@@ -719,16 +784,15 @@ async function softDelete(Model, id, options = {}) {
719
784
  }, {
720
785
  returnDocument: "after",
721
786
  session: options.session
722
- })) throw createError(404, "Document not found");
787
+ })) return null;
723
788
  return {
724
- success: true,
725
789
  message: "Soft deleted successfully",
726
790
  id: String(id),
727
791
  soft: true
728
792
  };
729
793
  }
730
794
  /**
731
- * Restore soft deleted document
795
+ * Restore soft-deleted document. Returns `null` on miss.
732
796
  */
733
797
  async function restore(Model, id, options = {}) {
734
798
  if (!await Model.findByIdAndUpdate(id, {
@@ -738,9 +802,8 @@ async function restore(Model, id, options = {}) {
738
802
  }, {
739
803
  returnDocument: "after",
740
804
  session: options.session
741
- })) throw createError(404, "Document not found");
805
+ })) return null;
742
806
  return {
743
- success: true,
744
807
  message: "Restored successfully",
745
808
  id: String(id)
746
809
  };
@@ -834,16 +897,37 @@ async function getAll(Model, query = {}, options = {}) {
834
897
  return mongoQuery.exec();
835
898
  }
836
899
  /**
837
- * Get or create document (upsert)
900
+ * Get or create document (atomic upsert).
901
+ *
902
+ * Returns `{ doc, created }` so callers can disambiguate the two
903
+ * outcomes without a follow-up read — load-bearing for race-detection
904
+ * in idempotency stores, lock acquisition, and "ensure-exists" flows.
905
+ *
906
+ * `created: true` ⇔ this caller's `createData` won the race and was
907
+ * just inserted; `created: false` ⇔ a doc matching `query` already
908
+ * existed and was returned unchanged.
909
+ *
910
+ * Mongo: `includeResultMetadata: true` returns the raw `ModifyResult`
911
+ * (`{ value, lastErrorObject, ok }`). `lastErrorObject.upserted` is
912
+ * the `_id` of a freshly-inserted row — present iff this call inserted.
838
913
  */
839
914
  async function getOrCreate(Model, query, createData, options = {}) {
840
- return Model.findOneAndUpdate(query, { $setOnInsert: createData }, {
915
+ const result = await Model.findOneAndUpdate(query, { $setOnInsert: createData }, {
841
916
  upsert: true,
842
917
  returnDocument: "after",
843
918
  runValidators: true,
919
+ includeResultMetadata: true,
844
920
  session: options.session,
845
921
  ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
846
922
  });
923
+ if (result && typeof result === "object" && "value" in result) return {
924
+ doc: result.value,
925
+ created: result.lastErrorObject?.upserted != null
926
+ };
927
+ return {
928
+ doc: result,
929
+ created: false
930
+ };
847
931
  }
848
932
  /**
849
933
  * Count documents matching query
@@ -1,94 +1,5 @@
1
- import { lt as SortSpec, q as PopulateSpec, rt as SelectSpec } from "../types-DXscZnLu.mjs";
2
- import { a as createFieldPreset, c as getMongooseProjection, i as configureLogger, l as createError, n as buildCrudSchemasFromMongooseSchema, o as filterResponseData, r as createMemoryCache, s as getFieldsForUser, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-rtkdbBhf.mjs";
1
+ import { a as createFieldPreset, c as getMongooseProjection, i as configureLogger, l as createError, n as buildCrudSchemasFromMongooseSchema, o as filterResponseData, r as createMemoryCache, s as getFieldsForUser, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-BgRAaXX-.mjs";
3
2
 
4
- //#region src/utils/cache-keys.d.ts
5
- /**
6
- * Generate cache key for getById operations
7
- *
8
- * Includes select/populate/lean in the key so different query shapes
9
- * (e.g., plain vs populated, lean vs hydrated) never collide.
10
- *
11
- * Format: {prefix}:id:{model}:{documentId} (no options)
12
- * Format: {prefix}:id:{model}:{documentId}:{optionsHash} (with options)
13
- *
14
- * @example
15
- * byIdKey('mk', 'User', '507f1f77bcf86cd799439011')
16
- * // => 'mk:id:User:507f1f77bcf86cd799439011'
17
- * byIdKey('mk', 'User', '507f1f77bcf86cd799439011', { select: 'name email' })
18
- * // => 'mk:id:User:507f1f77bcf86cd799439011:a1b2c3d4'
19
- */
20
- declare function byIdKey(prefix: string, model: string, id: string, options?: {
21
- select?: SelectSpec;
22
- populate?: PopulateSpec;
23
- lean?: boolean;
24
- }): string;
25
- /**
26
- * Generate cache key for single-document queries
27
- *
28
- * Format: {prefix}:one:{model}:{queryHash}
29
- *
30
- * @example
31
- * byQueryKey('mk', 'User', { email: 'john@example.com' })
32
- * // => 'mk:one:User:a1b2c3d4'
33
- */
34
- declare function byQueryKey(prefix: string, model: string, version: number, query: Record<string, unknown>, options?: {
35
- select?: SelectSpec;
36
- populate?: PopulateSpec;
37
- }): string;
38
- /**
39
- * Generate cache key for paginated list queries
40
- *
41
- * Format: {prefix}:list:{model}:{version}:{queryHash}
42
- *
43
- * The version component enables efficient bulk invalidation:
44
- * - On any mutation, bump the version
45
- * - All list cache keys become invalid without scanning/deleting each
46
- *
47
- * @example
48
- * listQueryKey('mk', 'User', 1, { filters: { status: 'active' }, page: 1, limit: 20 })
49
- * // => 'mk:list:User:1:e5f6g7h8'
50
- */
51
- declare function listQueryKey(prefix: string, model: string, version: number, params: {
52
- filters?: Record<string, unknown>;
53
- sort?: SortSpec;
54
- page?: number;
55
- limit?: number;
56
- after?: string;
57
- select?: SelectSpec;
58
- populate?: PopulateSpec;
59
- search?: string;
60
- mode?: string;
61
- lean?: boolean;
62
- readPreference?: string;
63
- hint?: string | Record<string, unknown>;
64
- maxTimeMS?: number;
65
- countStrategy?: string;
66
- }): string;
67
- /**
68
- * Generate cache key for collection version tag
69
- *
70
- * Format: {prefix}:ver:{model}
71
- *
72
- * Used to track mutation version for list invalidation
73
- */
74
- declare function versionKey(prefix: string, model: string): string;
75
- /**
76
- * Generate pattern for clearing all cache keys for a model
77
- *
78
- * Format: {prefix}:*:{model}:*
79
- *
80
- * @example
81
- * modelPattern('mk', 'User')
82
- * // => 'mk:*:User:*'
83
- */
84
- declare function modelPattern(prefix: string, model: string): string;
85
- /**
86
- * Generate pattern for clearing all list cache keys for a model
87
- *
88
- * Format: {prefix}:list:{model}:*
89
- */
90
- declare function listPattern(prefix: string, model: string): string;
91
- //#endregion
92
3
  //#region src/utils/id-resolution.d.ts
93
4
  /**
94
5
  * ID Resolution Primitive
@@ -146,4 +57,4 @@ declare function getSchemaIdType(schema?: SchemaLike | null): IdType;
146
57
  */
147
58
  declare function isValidIdForType(id: unknown, idType: IdType): boolean;
148
59
  //#endregion
149
- export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, versionKey };
60
+ export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType };
@@ -1,6 +1,6 @@
1
- import { t as configureLogger } from "../logger-8-VS1za6.mjs";
1
+ import { t as configureLogger } from "../logger-DprijP5h.mjs";
2
2
  import { t as createError } from "../error-B00pI7dV.mjs";
3
- import { n as isObjectIdInstance, r as isValidIdForType, t as getSchemaIdType } from "../id-resolution-e4BT9zAV.mjs";
4
- import { a as byIdKey, c as listQueryKey, i as getMongooseProjection, l as modelPattern, n as filterResponseData, o as byQueryKey, r as getFieldsForUser, s as listPattern, t as createFieldPreset, u as versionKey } from "../field-selection-dI0m4RBs.mjs";
5
- import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-B187ufOP.mjs";
6
- export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, versionKey };
3
+ import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "../field-selection-Br3WPOEb.mjs";
4
+ import { n as isObjectIdInstance, r as isValidIdForType, t as getSchemaIdType } from "../id-resolution-B0Odvvou.mjs";
5
+ import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-BYbG894y.mjs";
6
+ export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType };