@classytic/mongokit 3.19.0 → 3.21.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 (49) hide show
  1. package/dist/{PaginationEngine-BcvaXBQ-.mjs → PaginationEngine-BT1ywu9k.mjs} +327 -6
  2. package/dist/{PaginationEngine-4SjzfBQE.d.mts → PaginationEngine-DYpOD2wY.d.mts} +1 -2
  3. package/dist/{Repository-B01VW_fU.mjs → Repository-dbbgOGjK.mjs} +85 -11
  4. package/dist/actions/index.d.mts +1 -1
  5. package/dist/actions/index.mjs +2 -2
  6. package/dist/adapter/index.d.mts +27 -12
  7. package/dist/adapter/index.mjs +1 -1
  8. package/dist/{adapter-BSxhT0Ip.mjs → adapter-rTPLUrAV.mjs} +12 -2
  9. package/dist/ai/index.d.mts +1 -2
  10. package/dist/ai/index.mjs +3 -2
  11. package/dist/better-auth/index.d.mts +0 -1
  12. package/dist/better-auth/index.mjs +2 -2
  13. package/dist/{create-repository-K0yft2ht.d.mts → create-repository-0TkQhcv-.d.mts} +107 -13
  14. package/dist/{create-repository-D5YX0wGs.mjs → create-repository-CJATiAKX.mjs} +2 -2
  15. package/dist/{id-resolution-CnN0YA8t.mjs → id-resolution-P2OzcyRl.mjs} +10 -5
  16. package/dist/{index-BgSBwlWu.d.mts → index-Cg3SWugF.d.mts} +1 -3
  17. package/dist/index.d.mts +9 -10
  18. package/dist/index.mjs +52 -13
  19. package/dist/lock/index.d.mts +0 -1
  20. package/dist/memory-cache-DI1jb4dz.mjs +67 -0
  21. package/dist/{mongooseToJsonSchema-ChtXSrLT.mjs → mongooseToJsonSchema-CVnrKFDq.mjs} +2 -68
  22. package/dist/{mongooseToJsonSchema-Bz47aMfH.d.mts → mongooseToJsonSchema-D2CyAMIC.d.mts} +9 -5
  23. package/dist/{multi-tenant.plugin-D6lw3BsI.d.mts → multi-tenant.plugin-CTsWz0XK.d.mts} +1 -2
  24. package/dist/pagination/PaginationEngine.d.mts +1 -1
  25. package/dist/pagination/PaginationEngine.mjs +1 -1
  26. package/dist/plugins/index.d.mts +3 -4
  27. package/dist/plugins/index.mjs +2 -2
  28. package/dist/query/primitives/coercion.d.mts +1 -1
  29. package/dist/query/primitives/coercion.mjs +1 -1
  30. package/dist/query/primitives/geo.d.mts +1 -1
  31. package/dist/query/primitives/indexes.d.mts +1 -1
  32. package/dist/testkit/index.d.mts +1 -2
  33. package/dist/testkit/index.mjs +1 -1
  34. package/dist/{timestamp.plugin-B1A9U-P7.mjs → timestamp.plugin-nG9Z1JEk.mjs} +4 -4
  35. package/dist/{types-BA8oXjFe.d.mts → types-DNFMofHy.d.mts} +51 -22
  36. package/dist/{update-CvCMv5Cl.mjs → update-1Lq9OMD2.mjs} +5 -5
  37. package/dist/utils/index.d.mts +1 -1
  38. package/dist/utils/index.mjs +6 -5
  39. package/dist/{validation-chain.plugin-DHZbpb4t.mjs → validation-chain.plugin-Dn_wZYLA.mjs} +6 -6
  40. package/dist/{validation-chain.plugin-CYOPfFGF.d.mts → validation-chain.plugin-buy3uvyk.d.mts} +1 -2
  41. package/package.json +8 -7
  42. package/dist/cursor-_SbWVAn-.mjs +0 -254
  43. package/dist/sort-Ca1BPfvE.mjs +0 -72
  44. /package/dist/{coercion-BxxHyLjP.d.mts → coercion-BjdPX3YF.d.mts} +0 -0
  45. /package/dist/{error-BTeepSNL.mjs → error-BuByZwKU.mjs} +0 -0
  46. /package/dist/{field-selection-d1qdRiMr.mjs → field-selection-FSQFLpsU.mjs} +0 -0
  47. /package/dist/{indexes-3eHBWNZ2.d.mts → indexes-DIjMpYES.d.mts} +0 -0
  48. /package/dist/{logger-B8wf-Mtb.mjs → logger-K7dPmu2U.mjs} +0 -0
  49. /package/dist/{chunk-CfYAbeIz.mjs → rolldown-runtime-D7D4PA-g.mjs} +0 -0
@@ -1,7 +1,6 @@
1
- import { a as Repository, n as CreateRepositoryConfig } from "../create-repository-K0yft2ht.mjs";
1
+ import { a as Repository, n as CreateRepositoryConfig } from "../create-repository-0TkQhcv-.mjs";
2
2
  import { Connection, Model, Schema } from "mongoose";
3
3
  import { MongoMemoryReplSet, MongoMemoryServer } from "mongodb-memory-server";
4
-
5
4
  //#region src/testkit/index.d.ts
6
5
  interface MongoMemoryOptions {
7
6
  /**
@@ -1,4 +1,4 @@
1
- import { t as createRepository } from "../create-repository-D5YX0wGs.mjs";
1
+ import { t as createRepository } from "../create-repository-CJATiAKX.mjs";
2
2
  import { createConnection } from "mongoose";
3
3
  //#region src/testkit/index.ts
4
4
  function idempotentStop(server) {
@@ -1,6 +1,6 @@
1
- import { l as OP_REGISTRY, s as ALL_OPERATIONS, t as HOOK_PRIORITY } from "./Repository-B01VW_fU.mjs";
2
- import { n as warn } from "./logger-B8wf-Mtb.mjs";
3
- import { t as createError } from "./error-BTeepSNL.mjs";
1
+ import { l as OP_REGISTRY, s as ALL_OPERATIONS, t as HOOK_PRIORITY } from "./Repository-dbbgOGjK.mjs";
2
+ import { n as warn } from "./logger-K7dPmu2U.mjs";
3
+ import { t as createError } from "./error-BuByZwKU.mjs";
4
4
  import { throwIfAborted, withRetry } from "@classytic/repo-core/repository";
5
5
  import mongoose from "mongoose";
6
6
  import { cachePlugin } from "@classytic/repo-core/cache";
@@ -1233,7 +1233,7 @@ function softDeletePlugin(options = {}) {
1233
1233
  };
1234
1234
  }
1235
1235
  };
1236
- const SOFT_DELETE_SPECIAL = new Set([
1236
+ const SOFT_DELETE_SPECIAL = /* @__PURE__ */ new Set([
1237
1237
  "updateMany",
1238
1238
  "deleteMany",
1239
1239
  "bulkWrite"
@@ -1,16 +1,9 @@
1
- import * as _$_classytic_repo_core_repository0 from "@classytic/repo-core/repository";
2
1
  import { DeleteManyResult, DeleteResult, LookupPopulateResult, RepositoryBase, UpdateManyResult } from "@classytic/repo-core/repository";
3
- import * as _$mongoose from "mongoose";
4
2
  import { Document, Model, PipelineStage, PopulateOptions, Types } from "mongoose";
5
- import * as _$_classytic_repo_core_filter0 from "@classytic/repo-core/filter";
6
3
  import { Filter } from "@classytic/repo-core/filter";
7
- import * as _$_classytic_repo_core_cache0 from "@classytic/repo-core/cache";
8
- import * as _$_classytic_repo_core_schema0 from "@classytic/repo-core/schema";
9
4
  import { ValidationResult } from "@classytic/repo-core/schema";
10
- import * as _$_classytic_repo_core_events0 from "@classytic/repo-core/events";
11
5
  import { AnyPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
12
6
  import { HttpError } from "@classytic/repo-core/errors";
13
-
14
7
  //#region src/query/LookupBuilder.d.ts
15
8
  interface LookupOptions {
16
9
  /** Collection to join with */
@@ -172,6 +165,42 @@ declare class LookupBuilder {
172
165
  */
173
166
  private static _sanitizeDeep;
174
167
  }
168
+ /**
169
+ * Performance Guidelines for $lookup at Scale:
170
+ *
171
+ * 1. **Index Requirements** (Critical for millions of records):
172
+ * - localField should be indexed on source collection
173
+ * - foreignField should be indexed on target collection (unique index preferred)
174
+ *
175
+ * Example:
176
+ * ```typescript
177
+ * // Employee collection
178
+ * employeeSchema.index({ departmentSlug: 1 });
179
+ *
180
+ * // Department collection
181
+ * departmentSchema.index({ slug: 1 }, { unique: true });
182
+ * ```
183
+ *
184
+ * 2. **Query Performance**:
185
+ * - With proper indexes: O(log n) per lookup
186
+ * - Without indexes: O(n * m) - AVOID THIS!
187
+ * - Use explain() to verify index usage: IXSCAN (good) vs COLLSCAN (bad)
188
+ *
189
+ * 3. **Pipeline Optimization**:
190
+ * - Place $match stages as early as possible
191
+ * - Use $project to reduce field size before lookups
192
+ * - Limit joined results with pipeline: [{ $match: {...} }, { $limit: n }]
193
+ *
194
+ * 4. **Memory Considerations**:
195
+ * - Each lookup creates a new field in memory
196
+ * - Use $project after lookup to remove unnecessary fields
197
+ * - Consider allowDiskUse: true for very large datasets
198
+ *
199
+ * 5. **Alternative Approaches**:
200
+ * - For 1:1 relationships with high read frequency: Consider storing ObjectId + slug
201
+ * - For read-heavy workloads: Consider caching or materialized views
202
+ * - For real-time dashboards: Consider separate aggregation collections
203
+ */
175
204
  //#endregion
176
205
  //#region src/types/controller.types.d.ts
177
206
  /**
@@ -561,20 +590,20 @@ type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
561
590
  * @example
562
591
  * type StringFields = KeysOfType<User, string>; // 'name' | 'email'
563
592
  */
564
- type KeysOfType<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
593
+ type KeysOfType<T, V> = { [K in keyof T]: T[K] extends V ? K : never; }[keyof T];
565
594
  /**
566
595
  * Deep partial - makes all nested properties optional
567
596
  */
568
- type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
597
+ type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]>; } : T;
569
598
  /**
570
599
  * Strict object type - prevents excess properties
571
600
  * Use with `satisfies` for compile-time validation
572
601
  */
573
- type Strict<T> = T & { [K in Exclude<string, keyof T>]?: never };
602
+ type Strict<T> = T & { [K in Exclude<string, keyof T>]?: never; };
574
603
  /**
575
604
  * NonNullable fields extractor
576
605
  */
577
- type NonNullableFields<T> = { [K in keyof T]: NonNullable<T[K]> };
606
+ type NonNullableFields<T> = { [K in keyof T]: NonNullable<T[K]>; };
578
607
  /**
579
608
  * Create/Update input types from document
580
609
  */
@@ -869,20 +898,20 @@ interface RepositoryOptions {
869
898
  * structured `validationErrors`. Validator output replaces the
870
899
  * payload, so schema-declared coercions/defaults flow into the write.
871
900
  */
872
- schema?: _$_classytic_repo_core_schema0.StandardSchemaV1;
901
+ schema?: import('@classytic/repo-core/schema').StandardSchemaV1;
873
902
  /**
874
903
  * Standard Schema validator for `update` payloads. Separate slot
875
904
  * because updates are partial — derive one explicitly (e.g.
876
905
  * `schema.partial()` in Zod).
877
906
  */
878
- updateSchema?: _$_classytic_repo_core_schema0.StandardSchemaV1;
907
+ updateSchema?: import('@classytic/repo-core/schema').StandardSchemaV1;
879
908
  /**
880
909
  * Domain-event emission. Pass any arc / `@classytic/primitives`-
881
910
  * compatible transport and every mutating op publishes
882
911
  * `<resource>.<verb>` events (`user.created`, `user.updated`, ...).
883
912
  * Omit and the wiring is inert. See `@classytic/repo-core/events`.
884
913
  */
885
- events?: _$_classytic_repo_core_events0.RepositoryEventsOptions;
914
+ events?: import('@classytic/repo-core/events').RepositoryEventsOptions;
886
915
  }
887
916
  /** Pagination configuration */
888
917
  interface PaginationConfig {
@@ -1105,7 +1134,7 @@ interface OperationOptions extends ReadOptions {
1105
1134
  */
1106
1135
  interface CacheableOptions extends OperationOptions {
1107
1136
  /** Per-call cache override forwarded to the unified cache plugin. */
1108
- cache?: _$_classytic_repo_core_cache0.CacheOptions;
1137
+ cache?: import('@classytic/repo-core/cache').CacheOptions;
1109
1138
  }
1110
1139
  /** withTransaction options */
1111
1140
  interface WithTransactionOptions {
@@ -1114,7 +1143,7 @@ interface WithTransactionOptions {
1114
1143
  /** Optional hook to observe fallback triggers */
1115
1144
  onFallback?: (error: Error) => void;
1116
1145
  /** MongoDB transaction options (readConcern, writeConcern, readPreference, maxCommitTimeMS) */
1117
- transactionOptions?: _$mongoose.mongo.TransactionOptions;
1146
+ transactionOptions?: import('mongoose').mongo.TransactionOptions;
1118
1147
  }
1119
1148
  /** Create operation options */
1120
1149
  interface CreateOptions extends SessionOptions {
@@ -1200,13 +1229,13 @@ interface LookupPopulateOptions<TBase = unknown> {
1200
1229
  * controllers produce when typed against `StandardRepo<TDoc>`), or
1201
1230
  * a plain `Record<string, unknown>` for ad-hoc callers.
1202
1231
  */
1203
- filters?: _$_classytic_repo_core_filter0.Filter | (Partial<TBase> & Record<string, unknown>) | Record<string, unknown>;
1232
+ filters?: import('@classytic/repo-core/filter').Filter | (Partial<TBase> & Record<string, unknown>) | Record<string, unknown>;
1204
1233
  /**
1205
1234
  * Portable `LookupSpec[]` (cross-kit) or mongokit-native
1206
1235
  * `LookupOptions[]` (with `pipeline` / `let` / `sanitize`). The
1207
1236
  * portable shape is a structural subset, so callers can mix.
1208
1237
  */
1209
- lookups: readonly _$_classytic_repo_core_repository0.LookupSpec[] | LookupOptions[];
1238
+ lookups: readonly import('@classytic/repo-core/repository').LookupSpec[] | LookupOptions[];
1210
1239
  /** Sort specification */
1211
1240
  sort?: SortSpec | string;
1212
1241
  /** Page number (offset mode, 1-indexed) */
@@ -1338,7 +1367,7 @@ interface RepositoryContext {
1338
1367
  * `options.cache` onto the context so the plugin's `before:<op>`
1339
1368
  * hook can read it.
1340
1369
  */
1341
- cache?: _$_classytic_repo_core_cache0.CacheOptions;
1370
+ cache?: import('@classytic/repo-core/cache').CacheOptions;
1342
1371
  /** Whether result was served from cache (internal — set by cache plugin) */
1343
1372
  _cacheHit?: boolean;
1344
1373
  /** Cached result (internal — set by cache plugin) */
@@ -1440,7 +1469,7 @@ type EventHandlers<TDoc = unknown> = { [K in RepositoryEvent]?: K extends `befor
1440
1469
  error: Error;
1441
1470
  }) => void | Promise<void> : (payload: {
1442
1471
  context: RepositoryContext;
1443
- }) => void | Promise<void> };
1472
+ }) => void | Promise<void>; };
1444
1473
  /** Event payload */
1445
1474
  interface EventPayload {
1446
1475
  context: RepositoryContext;
@@ -1584,7 +1613,7 @@ interface MinMaxResult {
1584
1613
  */
1585
1614
  interface CacheOperationOptions {
1586
1615
  /** Per-call cache override forwarded to the unified cache plugin. */
1587
- cache?: _$_classytic_repo_core_cache0.CacheOptions;
1616
+ cache?: import('@classytic/repo-core/cache').CacheOptions;
1588
1617
  }
1589
1618
  /** Cascade relation definition */
1590
1619
  interface CascadeRelation {
@@ -1721,7 +1750,7 @@ type AllPluginMethods<TDoc> = {
1721
1750
  * `CacheEngine` plus convenience invalidation helpers. `undefined` when
1722
1751
  * no cache plugin is wired.
1723
1752
  */
1724
- cache?: _$_classytic_repo_core_cache0.RepositoryCacheHandle;
1753
+ cache?: import('@classytic/repo-core/cache').RepositoryCacheHandle;
1725
1754
  };
1726
1755
  /**
1727
1756
  * Helper type to add all plugin methods to a repository class
@@ -1,6 +1,6 @@
1
- import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
- import { n as warn } from "./logger-B8wf-Mtb.mjs";
3
- import { t as createError } from "./error-BTeepSNL.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { n as warn } from "./logger-K7dPmu2U.mjs";
3
+ import { t as createError } from "./error-BuByZwKU.mjs";
4
4
  import { isFilter } from "@classytic/repo-core/filter";
5
5
  //#region src/filter/compile.ts
6
6
  /**
@@ -21,7 +21,7 @@ import { isFilter } from "@classytic/repo-core/filter";
21
21
  * handles them"). This set covers every operator the arc aggregation
22
22
  * guard accepts in `parseDateRange` and `hasFilterOnField`.
23
23
  */
24
- const SHORTHAND_OPS = new Set([
24
+ const SHORTHAND_OPS = /* @__PURE__ */ new Set([
25
25
  "eq",
26
26
  "ne",
27
27
  "gt",
@@ -35,7 +35,7 @@ const SHORTHAND_OPS = new Set([
35
35
  "mod"
36
36
  ]);
37
37
  /** Range operators whose string values should be coerced to Date. */
38
- const RANGE_OPS = new Set([
38
+ const RANGE_OPS = /* @__PURE__ */ new Set([
39
39
  "gt",
40
40
  "gte",
41
41
  "lt",
@@ -1,2 +1,2 @@
1
- import { a as IdType, c as isObjectIdInstance, d as filterResponseData, f as getFieldsForUser, i as configureLogger, l as isValidIdForType, m as createError, n as buildCrudSchemasFromMongooseSchema, o as getSchemaIdType, p as getMongooseProjection, r as createMemoryCache, t as buildCrudSchemasFromModel, u as createFieldPreset } from "../mongooseToJsonSchema-Bz47aMfH.mjs";
1
+ import { a as IdType, c as isObjectIdInstance, d as filterResponseData, f as getFieldsForUser, i as configureLogger, l as isValidIdForType, m as createError, n as buildCrudSchemasFromMongooseSchema, o as getSchemaIdType, p as getMongooseProjection, r as createMemoryCache, t as buildCrudSchemasFromModel, u as createFieldPreset } from "../mongooseToJsonSchema-D2CyAMIC.mjs";
2
2
  export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType };
@@ -1,6 +1,7 @@
1
- import { i as isValidIdForType, r as isObjectIdInstance, t as getSchemaIdType } from "../id-resolution-CnN0YA8t.mjs";
2
- import { t as configureLogger } from "../logger-B8wf-Mtb.mjs";
3
- import { t as createError } from "../error-BTeepSNL.mjs";
4
- import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "../field-selection-d1qdRiMr.mjs";
5
- import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-ChtXSrLT.mjs";
1
+ import { i as isValidIdForType, r as isObjectIdInstance, t as getSchemaIdType } from "../id-resolution-P2OzcyRl.mjs";
2
+ import { t as configureLogger } from "../logger-K7dPmu2U.mjs";
3
+ import { t as createError } from "../error-BuByZwKU.mjs";
4
+ import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "../field-selection-FSQFLpsU.mjs";
5
+ import { t as createMemoryCache } from "../memory-cache-DI1jb4dz.mjs";
6
+ import { n as buildCrudSchemasFromMongooseSchema, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-CVnrKFDq.mjs";
6
7
  export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType };
@@ -1,8 +1,8 @@
1
- import { s as ALL_OPERATIONS } from "./Repository-B01VW_fU.mjs";
2
- import { _ as upsert } from "./update-CvCMv5Cl.mjs";
3
- import { n as warn } from "./logger-B8wf-Mtb.mjs";
4
- import { t as createError } from "./error-BTeepSNL.mjs";
5
- import { r as getFieldsForUser } from "./field-selection-d1qdRiMr.mjs";
1
+ import { s as ALL_OPERATIONS } from "./Repository-dbbgOGjK.mjs";
2
+ import { _ as upsert } from "./update-1Lq9OMD2.mjs";
3
+ import { n as warn } from "./logger-K7dPmu2U.mjs";
4
+ import { t as createError } from "./error-BuByZwKU.mjs";
5
+ import { r as getFieldsForUser } from "./field-selection-FSQFLpsU.mjs";
6
6
  import mongoose from "mongoose";
7
7
  import { AsyncLocalStorage } from "node:async_hooks";
8
8
  //#region src/plugins/aggregate-helpers.plugin.ts
@@ -681,7 +681,7 @@ function mongoOperationsPlugin() {
681
681
  * }, { arrayFilters: [{ 'elem._id': itemId }] });
682
682
  */
683
683
  repo.registerMethod("atomicUpdate", async function(id, operators, options = {}) {
684
- const validOperators = new Set([
684
+ const validOperators = /* @__PURE__ */ new Set([
685
685
  "$inc",
686
686
  "$set",
687
687
  "$unset",
@@ -1,8 +1,7 @@
1
- import { B as ObjectId, C as FieldPreset, G as Plugin, J as PopulateSpec, Q as RepositoryContext, _t as ValidationChainOptions, et as RepositoryInstance, it as SelectSpec, j as Logger, l as CascadeOptions, st as SoftDeleteOptions, ut as SortSpec, vt as ValidatorDefinition } from "./types-BA8oXjFe.mjs";
1
+ import { B as ObjectId, C as FieldPreset, G as Plugin, J as PopulateSpec, Q as RepositoryContext, _t as ValidationChainOptions, et as RepositoryInstance, it as SelectSpec, j as Logger, l as CascadeOptions, st as SoftDeleteOptions, ut as SortSpec, vt as ValidatorDefinition } from "./types-DNFMofHy.mjs";
2
2
  import { BulkWriteResult } from "@classytic/repo-core/repository";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { OffsetPaginationResult } from "@classytic/repo-core/pagination";
5
-
6
5
  //#region src/plugins/aggregate-helpers.plugin.d.ts
7
6
  /**
8
7
  * Aggregate helpers plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/mongokit",
3
- "version": "3.19.0",
3
+ "version": "3.21.0",
4
4
  "description": "Production-grade MongoDB repositories - smart pagination, events, and plugins. Built on @classytic/repo-core.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -109,7 +109,7 @@
109
109
  },
110
110
  "homepage": "https://github.com/classytic/mongokit#readme",
111
111
  "peerDependencies": {
112
- "@classytic/repo-core": ">=0.7.0",
112
+ "@classytic/repo-core": ">=0.8.0",
113
113
  "better-auth": ">=1.6.0",
114
114
  "mongodb-memory-server": ">=10.0.0",
115
115
  "mongoose": ">=9.4.1"
@@ -142,10 +142,11 @@
142
142
  "typecheck:tests": "tsc --noEmit -p tsconfig.tests.json",
143
143
  "lint": "biome check src/",
144
144
  "lint:fix": "biome check src/ --write",
145
+ "lint:package": "publint && attw --pack . --profile esm-only",
145
146
  "format": "biome format src/ --write",
146
147
  "check": "biome ci src/ --diagnostic-level=error",
147
148
  "push": "classytic-push",
148
- "prepublishOnly": "npm run check && npm run build && npm run typecheck && npm run typecheck:tests && npm test",
149
+ "prepublishOnly": "npm run check && npm run build && npm run lint:package && npm run typecheck && npm run typecheck:tests && npm test",
149
150
  "publish:dry": "npm publish --dry-run --access public",
150
151
  "publish:npm": "npm publish --access public",
151
152
  "release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
@@ -160,20 +161,20 @@
160
161
  "@better-auth/mongo-adapter": "^1.6.22",
161
162
  "@biomejs/biome": "^2.4.10",
162
163
  "@classytic/dev-tools": "^0.2.0",
163
- "@classytic/repo-core": "^0.7.0",
164
+ "@classytic/repo-core": "^0.9.0",
164
165
  "@types/node": "^22.0.0",
165
166
  "@vitest/coverage-v8": "^4.1.4",
166
167
  "ajv": "^8.18.0",
167
168
  "ajv-formats": "^3.0.1",
168
- "better-auth": "^1.6.22",
169
+ "better-auth": "^1.6.23",
169
170
  "dotenv": "^17.4.2",
170
171
  "knip": "^6.3.0",
171
172
  "mongodb": "7.2.0",
172
173
  "mongodb-memory-server": "^10.4.3",
173
174
  "mongoose": "^9.4.1",
174
175
  "publint": "^0.3.18",
175
- "tsdown": "^0.21.8",
176
- "typescript": "^6.0.3",
176
+ "tsdown": "^0.22.5",
177
+ "typescript": "^7.0.2",
177
178
  "vitest": "^4.1.4"
178
179
  },
179
180
  "overrides": {
@@ -1,254 +0,0 @@
1
- import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
- import mongoose from "mongoose";
3
- //#region src/pagination/utils/filter.ts
4
- /**
5
- * Builds MongoDB filter for keyset pagination
6
- * Creates compound $or condition for proper cursor-based filtering
7
- *
8
- * @param baseFilters - Existing query filters
9
- * @param sort - Normalized sort specification
10
- * @param cursorValue - Primary field value from cursor
11
- * @param cursorId - _id value from cursor
12
- * @returns MongoDB filter with keyset condition
13
- *
14
- * @example
15
- * buildKeysetFilter(
16
- * { status: 'active' },
17
- * { createdAt: -1, _id: -1 },
18
- * new Date('2024-01-01'),
19
- * new ObjectId('...')
20
- * )
21
- * // Returns:
22
- * // {
23
- * // status: 'active',
24
- * // $or: [
25
- * // { createdAt: { $lt: Date('2024-01-01') } },
26
- * // { createdAt: Date('2024-01-01'), _id: { $lt: ObjectId('...') } }
27
- * // ]
28
- * // }
29
- */
30
- function buildKeysetFilter(baseFilters, sort, cursorValue, cursorId, cursorValues) {
31
- const sortFields = Object.keys(sort).filter((k) => k !== "_id");
32
- if (sortFields.length <= 1 && !cursorValues) {
33
- const primaryField = sortFields[0] || "_id";
34
- const direction = sort[primaryField];
35
- const operator = direction === 1 ? "$gt" : "$lt";
36
- if (cursorValue === null || cursorValue === void 0) if (direction === 1) return {
37
- ...baseFilters,
38
- $or: [{
39
- [primaryField]: null,
40
- _id: { $gt: cursorId }
41
- }, { [primaryField]: { $ne: null } }]
42
- };
43
- else return {
44
- ...baseFilters,
45
- [primaryField]: null,
46
- _id: { $lt: cursorId }
47
- };
48
- return {
49
- ...baseFilters,
50
- $or: [{ [primaryField]: { [operator]: cursorValue } }, {
51
- [primaryField]: cursorValue,
52
- _id: { [operator]: cursorId }
53
- }]
54
- };
55
- }
56
- const values = cursorValues || { [sortFields[0]]: cursorValue };
57
- const allFields = [...sortFields, "_id"];
58
- const allValues = {
59
- ...values,
60
- _id: cursorId
61
- };
62
- const orConditions = [];
63
- for (let i = 0; i < allFields.length; i++) {
64
- const field = allFields[i];
65
- const operator = (sort[field] ?? sort[sortFields[0]]) === 1 ? "$gt" : "$lt";
66
- const condition = {};
67
- for (let j = 0; j < i; j++) condition[allFields[j]] = allValues[allFields[j]];
68
- condition[field] = { [operator]: allValues[field] };
69
- orConditions.push(condition);
70
- }
71
- return {
72
- ...baseFilters,
73
- $or: orConditions
74
- };
75
- }
76
- //#endregion
77
- //#region src/pagination/utils/cursor.ts
78
- /**
79
- * Cursor Utilities
80
- *
81
- * Encoding and decoding of cursor tokens for keyset pagination.
82
- * Cursors are base64-encoded JSON containing position data and metadata.
83
- */
84
- var cursor_exports = /* @__PURE__ */ __exportAll({
85
- decodeCursor: () => decodeCursor,
86
- encodeCursor: () => encodeCursor,
87
- resolveCursorFilter: () => resolveCursorFilter,
88
- validateCursorSort: () => validateCursorSort,
89
- validateCursorVersion: () => validateCursorVersion
90
- });
91
- /**
92
- * Encodes document values and sort metadata into a base64 cursor token
93
- *
94
- * @param doc - Document to extract cursor values from
95
- * @param primaryField - Primary sort field name
96
- * @param sort - Normalized sort specification
97
- * @param version - Cursor version for forward compatibility
98
- * @returns Base64-encoded cursor token
99
- */
100
- function encodeCursor(doc, primaryField, sort, version = 1) {
101
- const primaryValue = doc[primaryField];
102
- const idValue = doc._id;
103
- const sortFields = Object.keys(sort).filter((k) => k !== "_id");
104
- const vals = {};
105
- const types = {};
106
- for (const field of sortFields) {
107
- vals[field] = serializeValue(doc[field]);
108
- types[field] = getValueType(doc[field]);
109
- }
110
- const payload = {
111
- v: serializeValue(primaryValue),
112
- t: getValueType(primaryValue),
113
- id: serializeValue(idValue),
114
- idType: getValueType(idValue),
115
- sort,
116
- ver: version,
117
- ...sortFields.length > 1 && {
118
- vals,
119
- types
120
- }
121
- };
122
- return Buffer.from(JSON.stringify(payload)).toString("base64");
123
- }
124
- /**
125
- * Decodes a cursor token back into document values and sort metadata
126
- *
127
- * @param token - Base64-encoded cursor token
128
- * @returns Decoded cursor data
129
- * @throws Error if token is invalid or malformed
130
- */
131
- function decodeCursor(token) {
132
- let json;
133
- try {
134
- json = Buffer.from(token, "base64").toString("utf-8");
135
- } catch {
136
- throw new Error("Invalid cursor token: not valid base64");
137
- }
138
- let payload;
139
- try {
140
- payload = JSON.parse(json);
141
- } catch {
142
- throw new Error("Invalid cursor token: not valid JSON");
143
- }
144
- if (!payload || typeof payload !== "object" || !("v" in payload) || !("t" in payload) || !("id" in payload) || !("idType" in payload) || !payload.sort || typeof payload.sort !== "object" || typeof payload.ver !== "number") throw new Error("Invalid cursor token: malformed payload structure");
145
- const VALID_TYPES = [
146
- "date",
147
- "objectid",
148
- "boolean",
149
- "number",
150
- "string",
151
- "null",
152
- "unknown"
153
- ];
154
- if (!VALID_TYPES.includes(payload.t) || !VALID_TYPES.includes(payload.idType)) throw new Error("Invalid cursor token: unrecognized value type");
155
- try {
156
- let values;
157
- if (payload.vals && payload.types) {
158
- values = {};
159
- for (const [field, serialized] of Object.entries(payload.vals)) values[field] = rehydrateValue(serialized, payload.types[field]);
160
- }
161
- return {
162
- value: rehydrateValue(payload.v, payload.t),
163
- id: rehydrateValue(payload.id, payload.idType),
164
- sort: payload.sort,
165
- version: payload.ver,
166
- ...values && { values }
167
- };
168
- } catch {
169
- throw new Error("Invalid cursor token: failed to rehydrate values");
170
- }
171
- }
172
- /**
173
- * Validates that cursor sort matches current query sort
174
- *
175
- * @param cursorSort - Sort specification from cursor
176
- * @param currentSort - Sort specification from query
177
- * @throws Error if sorts don't match
178
- */
179
- function validateCursorSort(cursorSort, currentSort) {
180
- if (JSON.stringify(cursorSort) !== JSON.stringify(currentSort)) throw new Error("Cursor sort does not match current query sort");
181
- }
182
- /**
183
- * Validates cursor version against the server's expected range.
184
- *
185
- * - Cursors newer than `expectedVersion` are rejected (client ahead of server).
186
- * - Cursors older than `minVersion` are rejected (client cached a cursor from
187
- * a pre-breaking-change deploy). Default `minVersion = 1` keeps the legacy
188
- * "accept anything <= expected" behavior; bump it when you ship a breaking
189
- * cursor format change so old clients restart pagination cleanly instead of
190
- * silently paginating from the wrong position.
191
- */
192
- function validateCursorVersion(cursorVersion, expectedVersion, minVersion = 1) {
193
- if (cursorVersion > expectedVersion) throw new Error(`Cursor version ${cursorVersion} is newer than expected version ${expectedVersion}. Please upgrade.`);
194
- if (cursorVersion < minVersion) throw new Error(`Cursor version ${cursorVersion} is older than minimum supported ${minVersion}. Pagination must restart.`);
195
- }
196
- /**
197
- * Serializes a value for cursor storage
198
- */
199
- function serializeValue(value) {
200
- if (value === null || value === void 0) return null;
201
- if (value instanceof Date) return value.toISOString();
202
- if (value instanceof mongoose.Types.ObjectId) return value.toString();
203
- return value;
204
- }
205
- /**
206
- * Gets the type identifier for a value
207
- */
208
- function getValueType(value) {
209
- if (value === null || value === void 0) return "null";
210
- if (value instanceof Date) return "date";
211
- if (value instanceof mongoose.Types.ObjectId) return "objectid";
212
- if (typeof value === "boolean") return "boolean";
213
- if (typeof value === "number") return "number";
214
- if (typeof value === "string") return "string";
215
- return "unknown";
216
- }
217
- /**
218
- * Rehydrates a serialized value back to its original type
219
- */
220
- function rehydrateValue(serialized, type) {
221
- if (type === "null" || serialized === null) return null;
222
- switch (type) {
223
- case "date": return new Date(serialized);
224
- case "objectid": return new mongoose.Types.ObjectId(serialized);
225
- case "boolean": return serialized === true || serialized === "true";
226
- case "number": return Number(serialized);
227
- default: return serialized;
228
- }
229
- }
230
- /**
231
- * Resolves cursor token into MongoDB query filters.
232
- * Shared by PaginationEngine.stream() and Repository.lookupPopulate() keyset path.
233
- *
234
- * Handles:
235
- * - Plain 24-char hex ObjectId strings (fallback cursor)
236
- * - Base64-encoded cursor tokens (standard cursor)
237
- * - Cursor version and sort validation
238
- */
239
- function resolveCursorFilter(after, sort, cursorVersion, baseFilters = {}, minCursorVersion = 1) {
240
- if (/^[a-f0-9]{24}$/i.test(after)) {
241
- const objectId = new mongoose.Types.ObjectId(after);
242
- const idOperator = (sort._id || -1) === 1 ? "$gt" : "$lt";
243
- return {
244
- ...baseFilters,
245
- _id: { [idOperator]: objectId }
246
- };
247
- }
248
- const cursor = decodeCursor(after);
249
- validateCursorVersion(cursor.version, cursorVersion, minCursorVersion);
250
- validateCursorSort(cursor.sort, sort);
251
- return buildKeysetFilter(baseFilters, sort, cursor.value, cursor.id, cursor.values);
252
- }
253
- //#endregion
254
- export { encodeCursor as n, resolveCursorFilter as r, cursor_exports as t };