@classytic/mongokit 3.30.0 → 3.31.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.
@@ -636,7 +636,27 @@ var PaginationEngine = class {
636
636
  hasNext = data.length > sanitizedLimit;
637
637
  if (hasNext) data.pop();
638
638
  } else hasNext = sanitizedPage < totalPages;
639
- const warning = shouldWarnDeepPagination(sanitizedPage, this.config.deepPageThreshold) ? `Deep pagination (page ${sanitizedPage}). Consider getAll({ after, sort, limit }) for better performance.` : void 0;
639
+ /**
640
+ * A CLAMPED limit is reported, not swallowed.
641
+ *
642
+ * `validateLimit` caps silently by design — an over-ask is benign and rejecting it
643
+ * would be worse (that contradiction cost a day: the querystring schema's `maximum`
644
+ * made the parser's documented clamp unreachable, so `?limit=200` 400'd and two UI
645
+ * callers rendered the failure as an empty list).
646
+ *
647
+ * But clamping quietly has its own cost. A caller asking for 1000 and receiving 100
648
+ * gets an arbitrary slice with no signal — which is exactly how a bank-account
649
+ * picker rendered "No accounts found" against 696 accounts, because the rows it
650
+ * wanted were outside the first page. `limit` in the response already carries the
651
+ * effective value; nobody compares it. A warning is the channel that is actually
652
+ * read.
653
+ *
654
+ * Deep-pagination takes precedence when both apply: it is the more actionable of
655
+ * the two, and stacking warnings turns a signal into noise.
656
+ */
657
+ const requestedLimit = Number(limit);
658
+ const wasClamped = Number.isFinite(requestedLimit) && requestedLimit > sanitizedLimit;
659
+ const warning = shouldWarnDeepPagination(sanitizedPage, this.config.deepPageThreshold) ? `Deep pagination (page ${sanitizedPage}). Consider getAll({ after, sort, limit }) for better performance.` : wasClamped ? `Requested limit ${Math.floor(requestedLimit)} exceeds this repository's cap; returning ${sanitizedLimit}. Filter server-side or sort deterministically — the returned page is otherwise an arbitrary slice.` : void 0;
640
660
  return {
641
661
  method: "offset",
642
662
  data,
@@ -1,4 +1,4 @@
1
- import { _ as WithTransactionOptions, a as DeleteManyResult, f as OperationOptions, g as UpdateOptions, h as UpdateManyResult, i as CreateOptions, l as LookupPopulateOptions, m as SessionOptions, o as DeleteResult, p as ReadOptions, r as CacheableOptions, s as FindOneAndUpdateOptions, t as AggregateOptions, u as LookupPopulateResult, v as LookupBuilder, y as LookupOptions } from "./operations-C8IcCHF9.mjs";
1
+ import { _ as WithTransactionOptions, a as DeleteManyResult, f as OperationOptions, g as UpdateOptions, h as UpdateManyResult, i as CreateOptions, l as LookupPopulateOptions, m as SessionOptions, o as DeleteResult, p as ReadOptions, r as CacheableOptions, s as FindOneAndUpdateOptions, t as AggregateOptions, u as LookupPopulateResult, v as LookupBuilder, y as LookupOptions } from "./operations-CuzX9m2Z.mjs";
2
2
  import { a as PopulateSpec, i as ObjectId, l as SortSpec$1, o as ReadPreferenceType, s as SelectSpec } from "./core-62oo3N1C.mjs";
3
3
  import { c as PaginationConfig, r as CollationOptions, t as AggregatePaginationOptions } from "./pagination-jSUdrACK.mjs";
4
4
  import { _ as RepositoryOptions, d as PluginType, f as PrioritizedHook, o as Middleware, p as RepositoryContext } from "./repository-Q16ErTu8.mjs";
@@ -1,8 +1,8 @@
1
1
  import { i as isValidIdForType, t as getSchemaIdType } from "./id-resolution-BqDrk0S2.mjs";
2
- import { a as count, b as LookupBuilder, c as getByQuery, d as deleteById, f as deleteByQuery, h as createMany, l as getOrCreate, m as create, n as update, o as exists, r as updateByQuery, s as getById, t as findOneAndUpdate, x as compileFilterToMongo, y as distinct } from "./update-DHdXdOk_.mjs";
2
+ import { a as count, b as LookupBuilder, c as getByQuery, d as deleteById, f as deleteByQuery, h as createMany, l as getOrCreate, m as create, n as update, o as exists, r as updateByQuery, s as getById, t as findOneAndUpdate, x as compileFilterToMongo, y as distinct } from "./update-w5i-pa_c.mjs";
3
3
  import { n as warn } from "./logger-K7dPmu2U.mjs";
4
4
  import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-BuByZwKU.mjs";
5
- import { t as PaginationEngine } from "./PaginationEngine-CczbvCAq.mjs";
5
+ import { t as PaginationEngine } from "./PaginationEngine-CuONvwCM.mjs";
6
6
  import { hasNearOperator, rewriteNearForCount } from "./query/primitives/geo.mjs";
7
7
  import { RepositoryBase, runChunkedArchive, runChunkedPurge, throwIfAborted, validatePluginOrder, withRetry } from "@classytic/repo-core/repository";
8
8
  import mongoose from "mongoose";
@@ -159,6 +159,7 @@ const MONGOKIT_CAPABILITIES = Object.freeze({
159
159
  percentile: true,
160
160
  stddev: true,
161
161
  topN: true,
162
+ dateBucketTimezone: true,
162
163
  customDateBuckets: true,
163
164
  dateBucketSubMinute: true,
164
165
  cache: true
@@ -173,6 +174,7 @@ const MONGOKIT_CAPABILITIES = Object.freeze({
173
174
  changeStreams: true,
174
175
  lean: true,
175
176
  lookupPopulate: true,
177
+ lookupCoerce: true,
176
178
  streaming: true
177
179
  });
178
180
  /**
@@ -666,8 +668,48 @@ function validateMeasures$1(measures) {
666
668
  * 2. Inside `$project` to flatten `_id.<alias>` into a top-level
667
669
  * column on the output row — matches the cross-kit shape.
668
670
  */
671
+ /**
672
+ * Reject an unknown IANA zone HERE, not at the server.
673
+ *
674
+ * `@classytic/primitives` owns the canonical `isValidTimeZone`, but this is a
675
+ * storage kit and must not take a domain dependency for one validator — so it
676
+ * mirrors the primitive's implementation exactly: construct an `Intl` formatter
677
+ * for the zone and treat the `RangeError` as the answer. ICU is the same source
678
+ * of truth either way, so the two cannot disagree about which zones exist.
679
+ *
680
+ * Without this, a typo (`Asia/Dahka`) travels all the way to Mongo and comes
681
+ * back as a server-side "unrecognized time zone identifier" attached to whatever
682
+ * query happened to run — at request time, far from the aggregation that set
683
+ * it. Failing at compile time names the field and the value.
684
+ */
685
+ const validatedZones = /* @__PURE__ */ new Set(["UTC"]);
686
+ function assertValidTimeZone(zone) {
687
+ if (validatedZones.has(zone)) return;
688
+ try {
689
+ new Intl.DateTimeFormat("en-US", { timeZone: zone });
690
+ } catch {
691
+ throw new Error(`mongokit/aggregate: dateBucket.timezone is not a valid IANA zone — got ${JSON.stringify(zone)}. Use a name like "Asia/Dhaka" or "America/New_York"; omit the field for UTC.`);
692
+ }
693
+ validatedZones.add(zone);
694
+ }
669
695
  function compileDateBucket(bucket) {
670
696
  const dateExpr = { $toDate: `$${bucket.field}` };
697
+ /**
698
+ * Bucket boundaries are drawn in `bucket.timezone`, defaulting to UTC.
699
+ *
700
+ * Every branch below used to hardcode `'UTC'`, which made the portable IR
701
+ * unusable for any business-day rollup: in a UTC+6 deployment the 18:00–
702
+ * midnight rows land on the previous day and, at month-end, in the previous
703
+ * month — silently. Consumers therefore abandoned the IR and hand-rolled
704
+ * `$dateToString` pipelines, where the zone became optional and was
705
+ * forgotten (see the daily profit rollup that bucketed on UTC days).
706
+ *
707
+ * Mongo takes an IANA zone natively on both operators, so honouring it here
708
+ * costs nothing and makes the correct thing expressible. Gated by the
709
+ * `dateBucketTimezone` capability for kits that cannot do it.
710
+ */
711
+ const timezone = bucket.timezone ?? "UTC";
712
+ assertValidTimeZone(timezone);
671
713
  if (typeof bucket.interval === "object") {
672
714
  const { every, unit } = bucket.interval;
673
715
  if (!Number.isInteger(every) || every <= 0) throw new Error(`mongokit/aggregate: dateBucket.interval.every must be a positive integer — got ${String(every)}`);
@@ -675,56 +717,56 @@ function compileDateBucket(bucket) {
675
717
  date: dateExpr,
676
718
  unit,
677
719
  binSize: every,
678
- timezone: "UTC"
720
+ timezone
679
721
  } };
680
722
  return { $dateToString: {
681
723
  format: formatForUnit(unit),
682
724
  date: truncated,
683
- timezone: "UTC"
725
+ timezone
684
726
  } };
685
727
  }
686
728
  switch (bucket.interval) {
687
729
  case "minute": return { $dateToString: {
688
730
  format: "%Y-%m-%dT%H:%M",
689
731
  date: dateExpr,
690
- timezone: "UTC"
732
+ timezone
691
733
  } };
692
734
  case "hour": return { $dateToString: {
693
735
  format: "%Y-%m-%dT%H:00",
694
736
  date: dateExpr,
695
- timezone: "UTC"
737
+ timezone
696
738
  } };
697
739
  case "day": return { $dateToString: {
698
740
  format: "%Y-%m-%d",
699
741
  date: dateExpr,
700
- timezone: "UTC"
742
+ timezone
701
743
  } };
702
744
  case "week": return { $dateToString: {
703
745
  format: "%G-W%V",
704
746
  date: dateExpr,
705
- timezone: "UTC"
747
+ timezone
706
748
  } };
707
749
  case "month": return { $dateToString: {
708
750
  format: "%Y-%m",
709
751
  date: dateExpr,
710
- timezone: "UTC"
752
+ timezone
711
753
  } };
712
754
  case "quarter": return { $concat: [
713
755
  { $dateToString: {
714
756
  format: "%Y",
715
757
  date: dateExpr,
716
- timezone: "UTC"
758
+ timezone
717
759
  } },
718
760
  "-Q",
719
761
  { $toString: { $ceil: { $divide: [{ $month: {
720
762
  date: dateExpr,
721
- timezone: "UTC"
763
+ timezone
722
764
  } }, 3] } } }
723
765
  ] };
724
766
  case "year": return { $dateToString: {
725
767
  format: "%Y",
726
768
  date: dateExpr,
727
- timezone: "UTC"
769
+ timezone
728
770
  } };
729
771
  }
730
772
  }
@@ -4268,9 +4310,9 @@ var Repository = class extends RepositoryBase {
4268
4310
  const appendLookupStages$2 = (pipeline) => appendLookupStages(pipeline, lookups, projection, isKeyset && sort ? this._parseSort(sort) : void 0);
4269
4311
  if (isKeyset && sort) {
4270
4312
  const parsedSort = this._parseSort(sort);
4271
- const { validateKeysetSort } = await import("./PaginationEngine-CczbvCAq.mjs").then((n) => n.n);
4272
- const { encodeCursor, resolveCursorFilter } = await import("./PaginationEngine-CczbvCAq.mjs").then((n) => n.r);
4273
- const { getPrimaryField } = await import("./PaginationEngine-CczbvCAq.mjs").then((n) => n.n);
4313
+ const { validateKeysetSort } = await import("./PaginationEngine-CuONvwCM.mjs").then((n) => n.n);
4314
+ const { encodeCursor, resolveCursorFilter } = await import("./PaginationEngine-CuONvwCM.mjs").then((n) => n.r);
4315
+ const { getPrimaryField } = await import("./PaginationEngine-CuONvwCM.mjs").then((n) => n.n);
4274
4316
  const normalizedSort = validateKeysetSort(parsedSort, this._pagination.config.strictKeysetSortFields);
4275
4317
  const cursorVersion = this._pagination.config.cursorVersion ?? 1;
4276
4318
  const minCursorVersion = this._pagination.config.minCursorVersion ?? 1;
@@ -1,2 +1,2 @@
1
- import { a as create_d_exports, i as delete_d_exports, n as update_d_exports, o as aggregate_d_exports, r as read_d_exports } from "../index-DceInCTX.mjs";
1
+ import { a as create_d_exports, i as delete_d_exports, n as update_d_exports, o as aggregate_d_exports, r as read_d_exports } from "../index-Beyr1r4d.mjs";
2
2
  export { aggregate_d_exports as aggregate, create_d_exports as create, delete_d_exports as deleteActions, read_d_exports as read, update_d_exports as update };
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "../rolldown-runtime-D7D4PA-g.mjs";
2
- import { g as create_exports, i as update_exports, p as delete_exports, u as read_exports, v as aggregate_exports } from "../update-DHdXdOk_.mjs";
2
+ import { g as create_exports, i as update_exports, p as delete_exports, u as read_exports, v as aggregate_exports } from "../update-w5i-pa_c.mjs";
3
3
  //#region src/actions/index.ts
4
4
  var actions_exports = /* @__PURE__ */ __exportAll({
5
5
  aggregate: () => aggregate_exports,
@@ -1,4 +1,4 @@
1
- import { n as Repository } from "../Repository-X7U2d0kU.mjs";
1
+ import { n as Repository } from "../Repository-Cx7BwvDA.mjs";
2
2
  import { n as createMongooseAdapter } from "../adapter-BitHg-CN.mjs";
3
3
  import { pluralizeBetterAuthCollection, resolveBetterAuthCollections } from "@classytic/repo-core/better-auth";
4
4
  //#region src/better-auth/index.ts
@@ -1,5 +1,5 @@
1
- import { n as Repository } from "./Repository-X7U2d0kU.mjs";
2
- import { a as customIdPlugin, f as auditTrailPlugin, i as multiTenantPlugin, m as auditLogPlugin, n as softDeletePlugin, t as timestampPlugin, u as cachePlugin } from "./timestamp.plugin-ChFRcLRJ.mjs";
1
+ import { n as Repository } from "./Repository-Cx7BwvDA.mjs";
2
+ import { a as customIdPlugin, f as auditTrailPlugin, i as multiTenantPlugin, m as auditLogPlugin, n as softDeletePlugin, t as timestampPlugin, u as cachePlugin } from "./timestamp.plugin-CX_kw6TN.mjs";
3
3
  import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-DblM7V6j.mjs";
4
4
  //#region src/create-repository.ts
5
5
  function isLoggerAudit(value) {
@@ -1,7 +1,7 @@
1
1
  import { c as PaginationConfig } from "./pagination-jSUdrACK.mjs";
2
2
  import { _ as RepositoryOptions, d as PluginType } from "./repository-Q16ErTu8.mjs";
3
3
  import { T as AuditTrailOptions, _ as RepositoryCachePluginOptions, i as CustomIdOptions, t as MultiTenantOptions } from "./multi-tenant.plugin-2VtSSm1Y.mjs";
4
- import { n as Repository } from "./Repository-BXHkUtLk.mjs";
4
+ import { n as Repository } from "./Repository-BAzymuOz.mjs";
5
5
  import { i as Logger, o as SoftDeleteOptions } from "./plugin-options-BsFH0sTi.mjs";
6
6
  import { Model } from "mongoose";
7
7
  //#region src/create-repository.d.ts
@@ -1,4 +1,4 @@
1
- import { c as GroupResult, d as MinMaxResult, f as OperationOptions, g as UpdateOptions, h as UpdateManyResult, i as CreateOptions, o as DeleteResult, s as FindOneAndUpdateOptions, y as LookupOptions } from "./operations-C8IcCHF9.mjs";
1
+ import { c as GroupResult, d as MinMaxResult, f as OperationOptions, g as UpdateOptions, h as UpdateManyResult, i as CreateOptions, o as DeleteResult, s as FindOneAndUpdateOptions, y as LookupOptions } from "./operations-CuzX9m2Z.mjs";
2
2
  import { a as PopulateSpec, i as ObjectId, l as SortSpec, o as ReadPreferenceType, s as SelectSpec, t as AnyDocument } from "./core-62oo3N1C.mjs";
3
3
  import { p as UpdateWithValidationResult } from "./type-utils-CLzRFtIA.mjs";
4
4
  import { Model, PipelineStage } from "mongoose";
package/dist/index.d.mts CHANGED
@@ -1,14 +1,14 @@
1
- import { _ as WithTransactionOptions, c as GroupResult, d as MinMaxResult, f as OperationOptions, g as UpdateOptions, h as UpdateManyResult, i as CreateOptions, l as LookupPopulateOptions, m as SessionOptions, n as CacheOperationOptions, o as DeleteResult, p as ReadOptions, r as CacheableOptions, s as FindOneAndUpdateOptions, t as AggregateOptions, u as LookupPopulateResult, v as LookupBuilder, y as LookupOptions } from "./operations-C8IcCHF9.mjs";
1
+ import { _ as WithTransactionOptions, c as GroupResult, d as MinMaxResult, f as OperationOptions, g as UpdateOptions, h as UpdateManyResult, i as CreateOptions, l as LookupPopulateOptions, m as SessionOptions, n as CacheOperationOptions, o as DeleteResult, p as ReadOptions, r as CacheableOptions, s as FindOneAndUpdateOptions, t as AggregateOptions, u as LookupPopulateResult, v as LookupBuilder, y as LookupOptions } from "./operations-CuzX9m2Z.mjs";
2
2
  import { a as PopulateSpec, c as SortDirection, i as ObjectId, l as SortSpec$1, n as AnyModel, o as ReadPreferenceType, s as SelectSpec, t as AnyDocument } from "./core-62oo3N1C.mjs";
3
3
  import { a as DecodedCursor, c as PaginationConfig, i as CursorPayload, l as ValueType, n as BasePaginationOptions, o as KeysetPaginationOptions, r as CollationOptions, s as OffsetPaginationOptions, t as AggregatePaginationOptions } from "./pagination-jSUdrACK.mjs";
4
- import { t as index_d_exports } from "./index-DceInCTX.mjs";
4
+ import { t as index_d_exports } from "./index-Beyr1r4d.mjs";
5
5
  import { a as InferRawDoc, c as NonNullableFields, d as Strict, f as UpdatePatch, i as InferDocument, l as PartialBy, n as DeepPartial, o as KeysOfType, p as UpdateWithValidationResult, r as DocField, s as MongoOperatorUpdate, t as CreateInput, u as RequiredBy } from "./type-utils-CLzRFtIA.mjs";
6
6
  import { S as RepositoryPluginName, _ as RepositoryOptions, a as HookMode, b as MONGOKIT_PLUGIN_NAMES, c as MinimalRepoView, d as PluginType, f as PrioritizedHook, g as RepositoryOperation, h as RepositoryInstance, i as EventPhase, l as Plugin, m as RepositoryEvent, n as EventHandlers, o as Middleware, p as RepositoryContext, r as EventPayload, s as MiddlewareContext, t as AllPluginMethods, u as PluginFunction, v as UserContext, x as MongoKitPluginName, y as WithPlugins } from "./repository-Q16ErTu8.mjs";
7
7
  import { A as ensureAuditTrailReady, C as AuditTrailMethods, D as AuditTrailQueryOptions, E as AuditTrailQuery, O as EnsureAuditTrailReadyOptions, S as AuditQueryResult, T as AuditTrailOptions, _ as RepositoryCachePluginOptions, a as DateSequentialIdOptions, b as AuditOperation, c as SequentialIdOptions, d as getNextSequence, f as prefixedId, g as RepositoryCacheHandle, h as CacheOptions, i as CustomIdOptions, k as auditTrailPlugin, l as customIdPlugin, m as CacheAdapter, n as adminBypass, o as IdGenerator, p as sequentialId, r as multiTenantPlugin, s as PrefixedIdOptions, t as MultiTenantOptions, u as dateSequentialId, v as cachePlugin, w as AuditTrailMode, x as AuditQueryOptions, y as AuditEntry } from "./multi-tenant.plugin-2VtSSm1Y.mjs";
8
8
  import { t as PaginationEngine } from "./PaginationEngine-CBS1XGzp.mjs";
9
- import { a as AggregationPlan, c as SortOrder, i as AggregationBuilder, l as SortSpec, n as Repository, o as GroupSpec, r as TransitionMachine, s as ProjectionSpec, t as HOOK_PRIORITY, u as VectorSearchOptions } from "./Repository-BXHkUtLk.mjs";
9
+ import { a as AggregationPlan, c as SortOrder, i as AggregationBuilder, l as SortSpec, n as Repository, o as GroupSpec, r as TransitionMachine, s as ProjectionSpec, t as HOOK_PRIORITY, u as VectorSearchOptions } from "./Repository-BAzymuOz.mjs";
10
10
  import { a as SoftDeleteFilterMode, c as ValidationChainOptions, i as Logger, l as ValidatorDefinition, n as CascadeRelation, o as SoftDeleteOptions, r as FieldPreset, s as SoftDeleteRepository, t as CascadeOptions } from "./plugin-options-BsFH0sTi.mjs";
11
- import { n as CreateRepositoryConfig, r as createRepository, t as AuditConfig } from "./create-repository-CvD4wzuN.mjs";
11
+ import { n as CreateRepositoryConfig, r as createRepository, t as AuditConfig } from "./create-repository-mEdfH9sz.mjs";
12
12
  import { A as IdempotencyStoreMethods, B as auditLogPlugin, C as LeaseMethods, D as ImmutableStatesPluginOptions, E as ImmutableClaimView, F as elasticSearchPlugin, H as appendOnlyPlugin, I as cascadePlugin, L as BatchOperationsMethods, M as idempotencyStorePlugin, N as fieldFilterPlugin, O as immutableStatesPlugin, P as ElasticSearchOptions, R as BulkWriteResult, S as methodRegistryPlugin, T as leasePlugin, U as AggregateHelpersMethods, V as AppendOnlyPluginOptions, W as aggregateHelpersPlugin, _ as OperationMetric, a as requireField, b as mongoOperationsPlugin, c as timestampPlugin, d as createTenantContext, f as SubdocumentMethods, g as ObservabilityOptions, h as softDeletePlugin, i as immutableField, j as IdempotencyStorePluginOptions, k as IdempotencyClaim, l as TenantContext, m as SoftDeleteMethods, n as autoInject, o as uniqueField, p as subdocumentPlugin, r as blockIf, s as validationChainPlugin, t as OperationType, u as TenantStore, v as observabilityPlugin, w as LeasePluginOptions, y as MongoOperationsMethods, z as batchOperationsPlugin } from "./validation-chain.plugin-BSshLJTA.mjs";
13
13
  import { n as SchemaPathsLike, t as FieldType$1 } from "./coercion-BjdPX3YF.mjs";
14
14
  import { n as SchemaIndexes } from "./indexes-DIjMpYES.mjs";
@@ -118,6 +118,21 @@ type CapabilityResolution = 'declared' | 'observed' | 'unknown';
118
118
  */
119
119
  interface MongoRepoCapabilities extends RepoCapabilities {
120
120
  readonly transactionsResolution: CapabilityResolution;
121
+ /**
122
+ * `LookupOptions.coerce` — join a STRING reference against an `ObjectId` key.
123
+ *
124
+ * Declared HERE and not on repo-core's portable `RepoCapabilities` on
125
+ * purpose: `coerce` is a mongokit-only `LookupOptions` field with no
126
+ * counterpart on repo-core's `LookupSpec`, so a cross-kit caller cannot
127
+ * express it. A portable capability flag for a non-portable option would be
128
+ * decoration — and it would force a repo-core release plus a peer-floor bump
129
+ * on every kit for a field only one of them can honour.
130
+ *
131
+ * It is worth declaring at all because the unsupported case is INVISIBLE: a
132
+ * `$lookup` comparing `string` to `ObjectId` matches nothing and raises no
133
+ * error, so a host cannot tell "not supported here" from "no matches".
134
+ */
135
+ readonly lookupCoerce: boolean;
121
136
  }
122
137
  /**
123
138
  * Read the provenance of a capability descriptor's `transactions` flag.
@@ -585,6 +600,19 @@ declare class QueryParser {
585
600
  * Returns `undefined` if no whitelist is set (all fields allowed).
586
601
  */
587
602
  get allowedSortFields(): string[] | undefined;
603
+ /**
604
+ * The configured page-size cap — EXPOSED so a caller does not apply a second one.
605
+ *
606
+ * A resource that constructs `new QueryParser({ maxLimit: 1000 })` has answered the
607
+ * question "how large may a page be?". Without a way to read it back, every layer
608
+ * downstream applies its own default and the LOWEST silently wins — which is exactly
609
+ * what happened: a resource asked for 1000, a repository was configured for 1000, and
610
+ * arc's `QueryResolver` capped at its own default of 100. Three caps, one winner, no
611
+ * signal, and an account picker that showed 100 of 696 rows.
612
+ *
613
+ * Read this instead of defaulting when a parser is supplied.
614
+ */
615
+ get maxLimit(): number;
588
616
  /**
589
617
  * Get the configured allowed operators.
590
618
  * Returns `undefined` if no whitelist is set (all built-in operators allowed).
package/dist/index.mjs CHANGED
@@ -1,16 +1,16 @@
1
1
  import { n as idVariants } from "./id-resolution-BqDrk0S2.mjs";
2
- import { _ as resetCapabilityProbeCache, a as supportsTransactions, b as transactionResolutionOf, c as ALL_OPERATIONS, d as READ_OPERATIONS, f as operationsByPolicyKey, g as probeMongoCapabilities, h as classifyHelloReply, i as isTransactionUnsupported, l as MUTATING_OPERATIONS, m as capabilitiesForSupport, n as Repository, o as withTransaction, p as MONGOKIT_CAPABILITIES, r as batchTransaction, s as AggregationBuilder, t as HOOK_PRIORITY, u as OP_REGISTRY, v as resolveMongoCapabilities, x as createMongoPurgePort, y as resolveTransactionSupport } from "./Repository-X7U2d0kU.mjs";
3
- import { b as LookupBuilder, x as compileFilterToMongo } from "./update-DHdXdOk_.mjs";
2
+ import { _ as resetCapabilityProbeCache, a as supportsTransactions, b as transactionResolutionOf, c as ALL_OPERATIONS, d as READ_OPERATIONS, f as operationsByPolicyKey, g as probeMongoCapabilities, h as classifyHelloReply, i as isTransactionUnsupported, l as MUTATING_OPERATIONS, m as capabilitiesForSupport, n as Repository, o as withTransaction, p as MONGOKIT_CAPABILITIES, r as batchTransaction, s as AggregationBuilder, t as HOOK_PRIORITY, u as OP_REGISTRY, v as resolveMongoCapabilities, x as createMongoPurgePort, y as resolveTransactionSupport } from "./Repository-Cx7BwvDA.mjs";
3
+ import { b as LookupBuilder, x as compileFilterToMongo } from "./update-w5i-pa_c.mjs";
4
4
  import { n as warn, t as configureLogger } from "./logger-K7dPmu2U.mjs";
5
5
  import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-BuByZwKU.mjs";
6
6
  import { t as actions_exports } from "./actions/index.mjs";
7
- import { a as customIdPlugin, c as prefixedId, d as AuditTrailQuery, f as auditTrailPlugin, i as multiTenantPlugin, l as sequentialId, m as auditLogPlugin, n as softDeletePlugin, o as dateSequentialId, p as ensureAuditTrailReady, r as adminBypass, s as getNextSequence, t as timestampPlugin, u as cachePlugin } from "./timestamp.plugin-ChFRcLRJ.mjs";
7
+ import { a as customIdPlugin, c as prefixedId, d as AuditTrailQuery, f as auditTrailPlugin, i as multiTenantPlugin, l as sequentialId, m as auditLogPlugin, n as softDeletePlugin, o as dateSequentialId, p as ensureAuditTrailReady, r as adminBypass, s as getNextSequence, t as timestampPlugin, u as cachePlugin } from "./timestamp.plugin-CX_kw6TN.mjs";
8
8
  import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-DblM7V6j.mjs";
9
- import { t as PaginationEngine } from "./PaginationEngine-CczbvCAq.mjs";
9
+ import { t as PaginationEngine } from "./PaginationEngine-CuONvwCM.mjs";
10
10
  import { isGeoOperator, parseGeoFilter } from "./query/primitives/geo.mjs";
11
- import { t as createRepository } from "./create-repository-DYe5YEie.mjs";
11
+ import { t as createRepository } from "./create-repository-EWKzpsyM.mjs";
12
12
  import { n as defineModel, r as isModelRegistered, t as ModelCollisionError } from "./model-registry-ClKtWiHa.mjs";
13
- import { _ as cascadePlugin, a as uniqueField, c as subdocumentPlugin, d as mongoOperationsPlugin, f as leasePlugin, g as elasticSearchPlugin, h as fieldFilterPlugin, i as requireField, l as observabilityPlugin, m as idempotencyStorePlugin, n as blockIf, o as validationChainPlugin, p as immutableStatesPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as MONGOKIT_PLUGIN_NAMES, v as appendOnlyPlugin, y as aggregateHelpersPlugin } from "./validation-chain.plugin-BxTl_dYR.mjs";
13
+ import { _ as cascadePlugin, a as uniqueField, c as subdocumentPlugin, d as mongoOperationsPlugin, f as leasePlugin, g as elasticSearchPlugin, h as fieldFilterPlugin, i as requireField, l as observabilityPlugin, m as idempotencyStorePlugin, n as blockIf, o as validationChainPlugin, p as immutableStatesPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as MONGOKIT_PLUGIN_NAMES, v as appendOnlyPlugin, y as aggregateHelpersPlugin } from "./validation-chain.plugin-DbcBOWi3.mjs";
14
14
  import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-FSQFLpsU.mjs";
15
15
  import { buildFieldTypeMap, coerceFieldValue, coerceHeuristic } from "./query/primitives/coercion.mjs";
16
16
  import { extractSchemaIndexes } from "./query/primitives/indexes.mjs";
@@ -1109,10 +1109,29 @@ function buildQuerySchema(rt) {
1109
1109
  },
1110
1110
  limit: {
1111
1111
  type: "integer",
1112
- description: "Number of items per page",
1112
+ /**
1113
+ * The cap is DOCUMENTED, not enforced here — deliberately.
1114
+ *
1115
+ * `QueryParser.parse` clamps an over-large limit to `maxLimit` and says so in
1116
+ * its own comment: "Exceeding maxLimit is CLAMPED, not rejected — capping is not
1117
+ * invalid." Emitting `maximum` into the JSON Schema contradicted that: Fastify
1118
+ * validates the querystring BEFORE the parser runs, so the clamp was unreachable
1119
+ * and `?limit=200` became a hard 400.
1120
+ *
1121
+ * That combination is worse than either policy alone. A caller asking for more
1122
+ * rows than allowed is a benign over-ask with an obvious correct answer (give
1123
+ * them the maximum); turning it into a validation failure meant a UI that passed
1124
+ * a generous page size got NOTHING, and — because the two components doing so
1125
+ * never read the query error — rendered it as an empty list rather than a
1126
+ * failure. A 400 disguised as "no results" is the hardest kind to find.
1127
+ *
1128
+ * If a deployment genuinely wants rejection rather than clamping, that belongs
1129
+ * in the parser's fail-closed policy where both halves can agree, not in a schema
1130
+ * that silently outranks it.
1131
+ */
1132
+ description: `Number of items per page (values above ${rt.options.maxLimit} are capped to ${rt.options.maxLimit})`,
1113
1133
  default: 20,
1114
- minimum: 1,
1115
- maximum: rt.options.maxLimit
1134
+ minimum: 1
1116
1135
  },
1117
1136
  sort: {
1118
1137
  type: "string",
@@ -1338,6 +1357,21 @@ var QueryParser = class {
1338
1357
  return this.rt.options.allowedSortFields;
1339
1358
  }
1340
1359
  /**
1360
+ * The configured page-size cap — EXPOSED so a caller does not apply a second one.
1361
+ *
1362
+ * A resource that constructs `new QueryParser({ maxLimit: 1000 })` has answered the
1363
+ * question "how large may a page be?". Without a way to read it back, every layer
1364
+ * downstream applies its own default and the LOWEST silently wins — which is exactly
1365
+ * what happened: a resource asked for 1000, a repository was configured for 1000, and
1366
+ * arc's `QueryResolver` capped at its own default of 100. Three caps, one winner, no
1367
+ * signal, and an account picker that showed 100 of 696 rows.
1368
+ *
1369
+ * Read this instead of defaulting when a parser is supplied.
1370
+ */
1371
+ get maxLimit() {
1372
+ return this.rt.options.maxLimit;
1373
+ }
1374
+ /**
1341
1375
  * Get the configured allowed operators.
1342
1376
  * Returns `undefined` if no whitelist is set (all built-in operators allowed).
1343
1377
  */
@@ -26,8 +26,39 @@ interface LookupOptions {
26
26
  select?: string | readonly string[] | Record<string, 0 | 1>;
27
27
  /** Additional pipeline to run on the joined collection */
28
28
  pipeline?: PipelineStage[];
29
- /** Optional let variables for pipeline */
30
- let?: Record<string, string>;
29
+ /**
30
+ * Let variables for the pipeline form.
31
+ *
32
+ * Values are aggregation EXPRESSIONS, not just field paths — `'$skuRef'` and
33
+ * `{ $toObjectId: '$skuRef' }` are both valid. This was typed
34
+ * `Record<string, string>`, which made every cross-type correlation
35
+ * inexpressible through the typed API and pushed callers to hand-write raw
36
+ * pipelines or give up on the join entirely.
37
+ */
38
+ let?: Record<string, unknown>;
39
+ /**
40
+ * Coerce `localField` before comparing it to `foreignField`.
41
+ *
42
+ * ## Why this exists
43
+ *
44
+ * A `$lookup` compares values with STRICT BSON type equality. A `string`
45
+ * holding `"6a43c8fd…"` never equals an `ObjectId("6a43c8fd…")`, so the join
46
+ * matches nothing — and a `$lookup` that matches nothing is not an error, it
47
+ * is an empty array. The row renders without its joined data and every count
48
+ * downstream is plausibly, silently wrong.
49
+ *
50
+ * That is not hypothetical: flow stores `skuRef` as an opaque `string` (it
51
+ * must also admit non-ObjectId SKU codes) while `catalog_products._id` is an
52
+ * `ObjectId`. The naive form returned 0 matches for every row; the coerced
53
+ * form returned all of them. The workaround was a second round-trip resolver
54
+ * per request.
55
+ *
56
+ * `'objectId'` compiles to a `$convert` with `onError: null` — NOT
57
+ * `$toObjectId`, which THROWS on the first unconvertible value and would take
58
+ * down the whole query for one legacy SKU code. A row whose ref cannot be an
59
+ * ObjectId simply does not join, which is the same outcome as a genuine miss.
60
+ */
61
+ coerce?: 'objectId';
31
62
  /** Query filter to apply before join (legacy, for aggregate.ts compatibility) */
32
63
  query?: Record<string, unknown>;
33
64
  /** Query options (legacy, for aggregate.ts compatibility) */
@@ -75,6 +106,14 @@ declare class LookupBuilder {
75
106
  * Defaults to the collection name if not specified
76
107
  */
77
108
  as(fieldName: string): this;
109
+ /**
110
+ * Coerce the local field before comparing it to the foreign field.
111
+ *
112
+ * Use when the two sides are stored as different BSON types — most commonly a
113
+ * `string` reference against an `ObjectId` `_id`. Without it the join matches
114
+ * nothing and reports no error. See {@link LookupOptions.coerce}.
115
+ */
116
+ coerce(kind: NonNullable<LookupOptions['coerce']>): this;
78
117
  /**
79
118
  * Mark this lookup as returning a single document
80
119
  * Automatically unwraps the array result to a single object or null
@@ -98,7 +137,7 @@ declare class LookupBuilder {
98
137
  * Set let variables for use in pipeline
99
138
  * Allows referencing local document fields in the pipeline
100
139
  */
101
- let(variables: Record<string, string>): this;
140
+ let(variables: Record<string, unknown>): this;
102
141
  /**
103
142
  * Control pipeline sanitization (default: true)
104
143
  * Set to false for auto-generated pipelines that are known safe
@@ -1,4 +1,4 @@
1
- import { n as Repository } from "../Repository-BXHkUtLk.mjs";
1
+ import { n as Repository } from "../Repository-BAzymuOz.mjs";
2
2
  import { Connection, Model } from "mongoose";
3
3
  //#region src/outbox/index.d.ts
4
4
  /** Delivery states. `dead_letter` is terminal and operator-visible. */
@@ -1,4 +1,4 @@
1
- import { n as Repository } from "../Repository-X7U2d0kU.mjs";
1
+ import { n as Repository } from "../Repository-Cx7BwvDA.mjs";
2
2
  import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "../method-registry.plugin-DblM7V6j.mjs";
3
3
  import { Schema } from "mongoose";
4
4
  //#region src/outbox/index.ts
@@ -1,2 +1,2 @@
1
- import { t as PaginationEngine } from "../PaginationEngine-CczbvCAq.mjs";
1
+ import { t as PaginationEngine } from "../PaginationEngine-CuONvwCM.mjs";
2
2
  export { PaginationEngine };
@@ -1,6 +1,6 @@
1
- import { a as customIdPlugin, c as prefixedId, d as AuditTrailQuery, f as auditTrailPlugin, i as multiTenantPlugin, l as sequentialId, m as auditLogPlugin, n as softDeletePlugin, o as dateSequentialId, p as ensureAuditTrailReady, s as getNextSequence, t as timestampPlugin, u as cachePlugin } from "../timestamp.plugin-ChFRcLRJ.mjs";
1
+ import { a as customIdPlugin, c as prefixedId, d as AuditTrailQuery, f as auditTrailPlugin, i as multiTenantPlugin, l as sequentialId, m as auditLogPlugin, n as softDeletePlugin, o as dateSequentialId, p as ensureAuditTrailReady, s as getNextSequence, t as timestampPlugin, u as cachePlugin } from "../timestamp.plugin-CX_kw6TN.mjs";
2
2
  import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "../method-registry.plugin-DblM7V6j.mjs";
3
- import { _ as cascadePlugin, a as uniqueField, c as subdocumentPlugin, d as mongoOperationsPlugin, f as leasePlugin, g as elasticSearchPlugin, h as fieldFilterPlugin, i as requireField, l as observabilityPlugin, m as idempotencyStorePlugin, n as blockIf, o as validationChainPlugin, p as immutableStatesPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as MONGOKIT_PLUGIN_NAMES, v as appendOnlyPlugin, y as aggregateHelpersPlugin } from "../validation-chain.plugin-BxTl_dYR.mjs";
3
+ import { _ as cascadePlugin, a as uniqueField, c as subdocumentPlugin, d as mongoOperationsPlugin, f as leasePlugin, g as elasticSearchPlugin, h as fieldFilterPlugin, i as requireField, l as observabilityPlugin, m as idempotencyStorePlugin, n as blockIf, o as validationChainPlugin, p as immutableStatesPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as MONGOKIT_PLUGIN_NAMES, v as appendOnlyPlugin, y as aggregateHelpersPlugin } from "../validation-chain.plugin-DbcBOWi3.mjs";
4
4
  //#region src/plugins/change-log.plugin.ts
5
5
  const PLUGIN_NAME = "changeLog";
6
6
  function isSkipped(context) {
@@ -1,5 +1,5 @@
1
- import { n as Repository } from "../Repository-BXHkUtLk.mjs";
2
- import { n as CreateRepositoryConfig } from "../create-repository-CvD4wzuN.mjs";
1
+ import { n as Repository } from "../Repository-BAzymuOz.mjs";
2
+ import { n as CreateRepositoryConfig } from "../create-repository-mEdfH9sz.mjs";
3
3
  import { Connection, Model, Schema } from "mongoose";
4
4
  import { MongoMemoryReplSet, MongoMemoryServer } from "mongodb-memory-server";
5
5
  //#region src/testkit/index.d.ts
@@ -1,4 +1,4 @@
1
- import { t as createRepository } from "../create-repository-DYe5YEie.mjs";
1
+ import { t as createRepository } from "../create-repository-EWKzpsyM.mjs";
2
2
  import { createConnection } from "mongoose";
3
3
  //#region src/testkit/index.ts
4
4
  function idempotentStop(server) {
@@ -1,4 +1,4 @@
1
- import { c as ALL_OPERATIONS, t as HOOK_PRIORITY, u as OP_REGISTRY } from "./Repository-X7U2d0kU.mjs";
1
+ import { c as ALL_OPERATIONS, t as HOOK_PRIORITY, u as OP_REGISTRY } from "./Repository-Cx7BwvDA.mjs";
2
2
  import { n as warn } from "./logger-K7dPmu2U.mjs";
3
3
  import { throwIfAborted, withRetry } from "@classytic/repo-core/repository";
4
4
  import mongoose from "mongoose";
@@ -201,6 +201,25 @@ const DANGEROUS_OPERATORS = [
201
201
  "$accumulator"
202
202
  ];
203
203
  /**
204
+ * The local side of a join correlation, coerced when the two sides are stored
205
+ * as different BSON types.
206
+ *
207
+ * `$convert` with `onError`/`onNull` rather than `$toObjectId`: the latter
208
+ * throws on the first unconvertible value, which would fail the entire
209
+ * aggregation because ONE row held a legacy non-ObjectId reference. Returning
210
+ * null makes that row miss the join, exactly as a genuine non-match would.
211
+ */
212
+ function localCorrelation(localField, coerce) {
213
+ const path = `$${localField}`;
214
+ if (coerce !== "objectId") return path;
215
+ return { $convert: {
216
+ input: path,
217
+ to: "objectId",
218
+ onError: null,
219
+ onNull: null
220
+ } };
221
+ }
222
+ /**
204
223
  * Fluent builder for MongoDB $lookup aggregation stage
205
224
  * Optimized for custom field joins at scale
206
225
  */
@@ -241,6 +260,17 @@ var LookupBuilder = class LookupBuilder {
241
260
  return this;
242
261
  }
243
262
  /**
263
+ * Coerce the local field before comparing it to the foreign field.
264
+ *
265
+ * Use when the two sides are stored as different BSON types — most commonly a
266
+ * `string` reference against an `ObjectId` `_id`. Without it the join matches
267
+ * nothing and reports no error. See {@link LookupOptions.coerce}.
268
+ */
269
+ coerce(kind) {
270
+ this.options.coerce = kind;
271
+ return this;
272
+ }
273
+ /**
244
274
  * Mark this lookup as returning a single document
245
275
  * Automatically unwraps the array result to a single object or null
246
276
  */
@@ -293,11 +323,12 @@ var LookupBuilder = class LookupBuilder {
293
323
  * Otherwise, we use the simpler localField/foreignField form.
294
324
  */
295
325
  build() {
296
- const { from, localField, foreignField, as, single, pipeline, let: letVars } = this.options;
326
+ const { from, localField, foreignField, as, single, pipeline, let: letVars, coerce } = this.options;
297
327
  if (!from) throw new Error("LookupBuilder: \"from\" collection is required");
328
+ if (coerce && (!localField || !foreignField)) throw new Error("LookupBuilder: \"coerce\" needs both localField and foreignField — it describes how to compare them. Without both there is no comparison to coerce.");
298
329
  const outputField = as || from;
299
330
  const stages = [];
300
- const usePipelineForm = pipeline || letVars;
331
+ const usePipelineForm = pipeline || letVars || coerce;
301
332
  let lookupStage;
302
333
  if (usePipelineForm) if (!pipeline || pipeline.length === 0) {
303
334
  if (!localField || !foreignField) throw new Error("LookupBuilder: When using pipeline form without a custom pipeline, both localField and foreignField are required to auto-generate the pipeline");
@@ -305,7 +336,7 @@ var LookupBuilder = class LookupBuilder {
305
336
  lookupStage = { $lookup: {
306
337
  from,
307
338
  let: {
308
- [localField]: `$${localField}`,
339
+ [localField]: localCorrelation(localField, coerce),
309
340
  ...letVars || {}
310
341
  },
311
342
  pipeline: autoPipeline,
@@ -316,7 +347,7 @@ var LookupBuilder = class LookupBuilder {
316
347
  let effectiveLet = letVars;
317
348
  let effectivePipeline = safePipeline;
318
349
  if (localField && foreignField && !letVars) {
319
- effectiveLet = { lookupJoinVal: `$${localField}` };
350
+ effectiveLet = { lookupJoinVal: localCorrelation(localField, coerce) };
320
351
  effectivePipeline = [{ $match: { $expr: { $eq: [`$${foreignField}`, "$$lookupJoinVal"] } } }, ...safePipeline];
321
352
  }
322
353
  lookupStage = { $lookup: {
@@ -371,6 +402,17 @@ var LookupBuilder = class LookupBuilder {
371
402
  const builder = new LookupBuilder(lookup.from).localField(lookup.localField).foreignField(lookup.foreignField);
372
403
  if (lookup.as) builder.as(lookup.as);
373
404
  if (lookup.single) builder.single(lookup.single);
405
+ /**
406
+ * Coercion, for the SIMPLE branch below (no select / pipeline / where):
407
+ * there `build()` auto-generates the correlation and reads this flag.
408
+ *
409
+ * The pipeline branch sets its own `let` explicitly and therefore coerces
410
+ * at THAT assignment instead — see `lookupJoinVal` further down. Both
411
+ * exist because `multiple()` hand-assembles stages rather than deferring
412
+ * to `build()`, so each of its two paths must be covered separately; a
413
+ * fix to one silently leaves the other matching nothing.
414
+ */
415
+ if (lookup.coerce) builder.coerce(lookup.coerce);
374
416
  const stages = [];
375
417
  const appendCallerStages = (raw) => {
376
418
  if (raw.length === 0) return;
@@ -385,7 +427,7 @@ var LookupBuilder = class LookupBuilder {
385
427
  if (lookup.select) stages.push({ $project: compileSelectToProjection(lookup.select) });
386
428
  builder.pipeline(stages);
387
429
  builder.let({
388
- lookupJoinVal: `$${lookup.localField}`,
430
+ lookupJoinVal: localCorrelation(lookup.localField, lookup.coerce),
389
431
  ...lookup.let || {}
390
432
  });
391
433
  builder.sanitize(false);
@@ -413,6 +455,7 @@ var LookupBuilder = class LookupBuilder {
413
455
  if (lookup.single !== void 0) builder.single(lookup.single);
414
456
  if (lookup.pipeline) builder.pipeline(lookup.pipeline);
415
457
  if (lookup.let) builder.let(lookup.let);
458
+ if (lookup.coerce) builder.coerce(lookup.coerce);
416
459
  return builder.build();
417
460
  });
418
461
  }
@@ -1,5 +1,5 @@
1
- import { c as ALL_OPERATIONS, u as OP_REGISTRY } from "./Repository-X7U2d0kU.mjs";
2
- import { _ as upsert } from "./update-DHdXdOk_.mjs";
1
+ import { c as ALL_OPERATIONS, u as OP_REGISTRY } from "./Repository-Cx7BwvDA.mjs";
2
+ import { _ as upsert } from "./update-w5i-pa_c.mjs";
3
3
  import { n as warn } from "./logger-K7dPmu2U.mjs";
4
4
  import { t as createError } from "./error-BuByZwKU.mjs";
5
5
  import { r as getFieldsForUser } from "./field-selection-FSQFLpsU.mjs";
package/package.json CHANGED
@@ -1,193 +1,193 @@
1
- {
2
- "name": "@classytic/mongokit",
3
- "version": "3.30.0",
4
- "description": "Production-grade MongoDB repositories - smart pagination, events, and plugins. Built on @classytic/repo-core.",
5
- "type": "module",
6
- "sideEffects": false,
7
- "main": "./dist/index.mjs",
8
- "module": "./dist/index.mjs",
9
- "types": "./dist/index.d.mts",
10
- "exports": {
11
- ".": {
12
- "types": "./dist/index.d.mts",
13
- "default": "./dist/index.mjs"
14
- },
15
- "./pagination": {
16
- "types": "./dist/pagination/PaginationEngine.d.mts",
17
- "default": "./dist/pagination/PaginationEngine.mjs"
18
- },
19
- "./plugins": {
20
- "types": "./dist/plugins/index.d.mts",
21
- "default": "./dist/plugins/index.mjs"
22
- },
23
- "./utils": {
24
- "types": "./dist/utils/index.d.mts",
25
- "default": "./dist/utils/index.mjs"
26
- },
27
- "./actions": {
28
- "types": "./dist/actions/index.d.mts",
29
- "default": "./dist/actions/index.mjs"
30
- },
31
- "./ai": {
32
- "types": "./dist/ai/index.d.mts",
33
- "default": "./dist/ai/index.mjs"
34
- },
35
- "./adapter": {
36
- "types": "./dist/adapter/index.d.mts",
37
- "default": "./dist/adapter/index.mjs"
38
- },
39
- "./better-auth": {
40
- "types": "./dist/better-auth/index.d.mts",
41
- "default": "./dist/better-auth/index.mjs"
42
- },
43
- "./outbox": {
44
- "types": "./dist/outbox/index.d.mts",
45
- "default": "./dist/outbox/index.mjs"
46
- },
47
- "./lock": {
48
- "types": "./dist/lock/index.d.mts",
49
- "default": "./dist/lock/index.mjs"
50
- },
51
- "./usage": {
52
- "types": "./dist/usage/index.d.mts",
53
- "default": "./dist/usage/index.mjs"
54
- },
55
- "./query/primitives/geo": {
56
- "types": "./dist/query/primitives/geo.d.mts",
57
- "default": "./dist/query/primitives/geo.mjs"
58
- },
59
- "./query/primitives/coercion": {
60
- "types": "./dist/query/primitives/coercion.d.mts",
61
- "default": "./dist/query/primitives/coercion.mjs"
62
- },
63
- "./query/primitives/indexes": {
64
- "types": "./dist/query/primitives/indexes.d.mts",
65
- "default": "./dist/query/primitives/indexes.mjs"
66
- },
67
- "./testkit": {
68
- "types": "./dist/testkit/index.d.mts",
69
- "default": "./dist/testkit/index.mjs"
70
- },
71
- "./kernel-conformance": {
72
- "types": "./dist/kernel-conformance/index.d.mts",
73
- "default": "./dist/kernel-conformance/index.mjs"
74
- }
75
- },
76
- "files": [
77
- "dist",
78
- "README.md",
79
- "LICENSE",
80
- "TRADEMARK.md"
81
- ],
82
- "keywords": [
83
- "mongodb",
84
- "mongoose",
85
- "repository",
86
- "repository-pattern",
87
- "data-access",
88
- "pagination",
89
- "cursor-pagination",
90
- "infinite-scroll",
91
- "offset-pagination",
92
- "keyset-pagination",
93
- "caching",
94
- "redis",
95
- "cache",
96
- "multi-tenant",
97
- "multi-tenancy",
98
- "vector-search",
99
- "embeddings",
100
- "atlas-search",
101
- "observability",
102
- "event-driven",
103
- "plugin-based",
104
- "zero-dependencies",
105
- "express",
106
- "fastify",
107
- "nestjs",
108
- "nextjs",
109
- "typescript"
110
- ],
111
- "author": "Classytic <classytic.dev@gmail.com> (https://github.com/classytic)",
112
- "contributors": [
113
- "Sadman Chowdhury (https://github.com/siam923)"
114
- ],
115
- "license": "MIT",
116
- "repository": {
117
- "type": "git",
118
- "url": "git+https://github.com/classytic/mongokit.git"
119
- },
120
- "bugs": {
121
- "url": "https://github.com/classytic/mongokit/issues"
122
- },
123
- "homepage": "https://github.com/classytic/mongokit#readme",
124
- "peerDependencies": {
125
- "@classytic/repo-core": ">=0.20.0",
126
- "mongodb-memory-server": ">=10.4.3",
127
- "mongoose": ">=9.4.1"
128
- },
129
- "peerDependenciesMeta": {
130
- "mongodb-memory-server": {
131
- "optional": true
132
- }
133
- },
134
- "engines": {
135
- "node": ">=22"
136
- },
137
- "publishConfig": {
138
- "access": "public"
139
- },
140
- "scripts": {
141
- "build": "tsdown",
142
- "dev": "tsdown --watch",
143
- "test": "vitest run --project unit --project integration",
144
- "test:unit": "vitest run --project unit",
145
- "test:integration": "vitest run --project integration",
146
- "test:e2e": "vitest run --project e2e",
147
- "test:all": "vitest run --project unit --project integration --project e2e",
148
- "test:watch": "vitest --project unit --project integration",
149
- "test:coverage": "vitest run --coverage",
150
- "typecheck": "tsc --noEmit",
151
- "typecheck:tests": "tsc --noEmit -p tsconfig.tests.json",
152
- "lint": "biome check src/",
153
- "lint:fix": "biome check src/ --write",
154
- "check:exports": "node scripts/check-exports.mjs",
155
- "lint:package": "npm run check:exports && publint && attw --pack . --profile esm-only",
156
- "format": "biome format src/ --write",
157
- "check": "biome ci src/ --diagnostic-level=error",
158
- "check:dead-code": "knip",
159
- "ci": "npm run check && npm run typecheck && npm run typecheck:tests && npm run build && npm run lint:package && npm run check:dead-code && npm run check:forgotten-exports && npm test",
160
- "push": "classytic-push",
161
- "prepublishOnly": "npm run ci",
162
- "publish:dry": "npm publish --dry-run --access public",
163
- "publish:npm": "npm publish --access public",
164
- "release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
165
- "release": "npm run push -- main && npm run release:tag && npm publish --access public",
166
- "release:patch": "npm version patch && npm run release",
167
- "release:minor": "npm version minor && npm run release",
168
- "release:major": "npm version major && npm run release",
169
- "check:forgotten-exports": "node scripts/check-forgotten-exports.mjs"
170
- },
171
- "devDependencies": {
172
- "@arethetypeswrong/cli": "^0.18.2",
173
- "@better-auth/api-key": "^1.6.22",
174
- "@better-auth/mongo-adapter": "^1.6.22",
175
- "@biomejs/biome": "^2.4.10",
176
- "@classytic/dev-tools": "^0.2.0",
177
- "@classytic/repo-core": "^0.20.0",
178
- "@types/node": "^22.0.0",
179
- "@vitest/coverage-v8": "^4.1.4",
180
- "ajv": "^8.18.0",
181
- "ajv-formats": "^3.0.1",
182
- "better-auth": "^1.6.23",
183
- "dotenv": "^17.4.2",
184
- "knip": "^6.31.0",
185
- "mongodb": "^7.5.0",
186
- "mongodb-memory-server": "^10.4.3",
187
- "mongoose": "^9.9.1",
188
- "publint": "^0.3.18",
189
- "tsdown": "^0.22.14",
190
- "typescript": "~5.9.0",
191
- "vitest": "^4.1.4"
192
- }
193
- }
1
+ {
2
+ "name": "@classytic/mongokit",
3
+ "version": "3.31.1",
4
+ "description": "Production-grade MongoDB repositories - smart pagination, events, and plugins. Built on @classytic/repo-core.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.mjs",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.mts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.mts",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "./pagination": {
16
+ "types": "./dist/pagination/PaginationEngine.d.mts",
17
+ "default": "./dist/pagination/PaginationEngine.mjs"
18
+ },
19
+ "./plugins": {
20
+ "types": "./dist/plugins/index.d.mts",
21
+ "default": "./dist/plugins/index.mjs"
22
+ },
23
+ "./utils": {
24
+ "types": "./dist/utils/index.d.mts",
25
+ "default": "./dist/utils/index.mjs"
26
+ },
27
+ "./actions": {
28
+ "types": "./dist/actions/index.d.mts",
29
+ "default": "./dist/actions/index.mjs"
30
+ },
31
+ "./ai": {
32
+ "types": "./dist/ai/index.d.mts",
33
+ "default": "./dist/ai/index.mjs"
34
+ },
35
+ "./adapter": {
36
+ "types": "./dist/adapter/index.d.mts",
37
+ "default": "./dist/adapter/index.mjs"
38
+ },
39
+ "./better-auth": {
40
+ "types": "./dist/better-auth/index.d.mts",
41
+ "default": "./dist/better-auth/index.mjs"
42
+ },
43
+ "./outbox": {
44
+ "types": "./dist/outbox/index.d.mts",
45
+ "default": "./dist/outbox/index.mjs"
46
+ },
47
+ "./lock": {
48
+ "types": "./dist/lock/index.d.mts",
49
+ "default": "./dist/lock/index.mjs"
50
+ },
51
+ "./usage": {
52
+ "types": "./dist/usage/index.d.mts",
53
+ "default": "./dist/usage/index.mjs"
54
+ },
55
+ "./query/primitives/geo": {
56
+ "types": "./dist/query/primitives/geo.d.mts",
57
+ "default": "./dist/query/primitives/geo.mjs"
58
+ },
59
+ "./query/primitives/coercion": {
60
+ "types": "./dist/query/primitives/coercion.d.mts",
61
+ "default": "./dist/query/primitives/coercion.mjs"
62
+ },
63
+ "./query/primitives/indexes": {
64
+ "types": "./dist/query/primitives/indexes.d.mts",
65
+ "default": "./dist/query/primitives/indexes.mjs"
66
+ },
67
+ "./testkit": {
68
+ "types": "./dist/testkit/index.d.mts",
69
+ "default": "./dist/testkit/index.mjs"
70
+ },
71
+ "./kernel-conformance": {
72
+ "types": "./dist/kernel-conformance/index.d.mts",
73
+ "default": "./dist/kernel-conformance/index.mjs"
74
+ }
75
+ },
76
+ "files": [
77
+ "dist",
78
+ "README.md",
79
+ "LICENSE",
80
+ "TRADEMARK.md"
81
+ ],
82
+ "keywords": [
83
+ "mongodb",
84
+ "mongoose",
85
+ "repository",
86
+ "repository-pattern",
87
+ "data-access",
88
+ "pagination",
89
+ "cursor-pagination",
90
+ "infinite-scroll",
91
+ "offset-pagination",
92
+ "keyset-pagination",
93
+ "caching",
94
+ "redis",
95
+ "cache",
96
+ "multi-tenant",
97
+ "multi-tenancy",
98
+ "vector-search",
99
+ "embeddings",
100
+ "atlas-search",
101
+ "observability",
102
+ "event-driven",
103
+ "plugin-based",
104
+ "zero-dependencies",
105
+ "express",
106
+ "fastify",
107
+ "nestjs",
108
+ "nextjs",
109
+ "typescript"
110
+ ],
111
+ "author": "Classytic <classytic.dev@gmail.com> (https://github.com/classytic)",
112
+ "contributors": [
113
+ "Sadman Chowdhury (https://github.com/siam923)"
114
+ ],
115
+ "license": "MIT",
116
+ "repository": {
117
+ "type": "git",
118
+ "url": "git+https://github.com/classytic/mongokit.git"
119
+ },
120
+ "bugs": {
121
+ "url": "https://github.com/classytic/mongokit/issues"
122
+ },
123
+ "homepage": "https://github.com/classytic/mongokit#readme",
124
+ "peerDependencies": {
125
+ "@classytic/repo-core": ">=0.20.0",
126
+ "mongodb-memory-server": ">=10.4.3",
127
+ "mongoose": ">=9.4.1"
128
+ },
129
+ "peerDependenciesMeta": {
130
+ "mongodb-memory-server": {
131
+ "optional": true
132
+ }
133
+ },
134
+ "engines": {
135
+ "node": ">=22"
136
+ },
137
+ "publishConfig": {
138
+ "access": "public"
139
+ },
140
+ "scripts": {
141
+ "build": "tsdown",
142
+ "dev": "tsdown --watch",
143
+ "test": "vitest run --project unit --project integration",
144
+ "test:unit": "vitest run --project unit",
145
+ "test:integration": "vitest run --project integration",
146
+ "test:e2e": "vitest run --project e2e",
147
+ "test:all": "vitest run --project unit --project integration --project e2e",
148
+ "test:watch": "vitest --project unit --project integration",
149
+ "test:coverage": "vitest run --coverage",
150
+ "typecheck": "tsc --noEmit",
151
+ "typecheck:tests": "tsc --noEmit -p tsconfig.tests.json",
152
+ "lint": "biome check src/",
153
+ "lint:fix": "biome check src/ --write",
154
+ "check:exports": "node scripts/check-exports.mjs",
155
+ "lint:package": "npm run check:exports && publint && attw --pack . --profile esm-only",
156
+ "format": "biome format src/ --write",
157
+ "check": "biome ci src/ --diagnostic-level=error",
158
+ "check:dead-code": "knip",
159
+ "ci": "npm run check && npm run typecheck && npm run typecheck:tests && npm run build && npm run lint:package && npm run check:dead-code && npm run check:forgotten-exports && npm test",
160
+ "push": "classytic-push",
161
+ "prepublishOnly": "npm run ci",
162
+ "publish:dry": "npm publish --dry-run --access public",
163
+ "publish:npm": "npm publish --access public",
164
+ "release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
165
+ "release": "npm run push -- main && npm run release:tag && npm publish --access public",
166
+ "release:patch": "npm version patch && npm run release",
167
+ "release:minor": "npm version minor && npm run release",
168
+ "release:major": "npm version major && npm run release",
169
+ "check:forgotten-exports": "node scripts/check-forgotten-exports.mjs"
170
+ },
171
+ "devDependencies": {
172
+ "@arethetypeswrong/cli": "^0.18.2",
173
+ "@better-auth/api-key": "^1.6.22",
174
+ "@better-auth/mongo-adapter": "^1.6.22",
175
+ "@biomejs/biome": "^2.4.10",
176
+ "@classytic/dev-tools": "^0.2.0",
177
+ "@classytic/repo-core": "^0.21.0",
178
+ "@types/node": "^22.0.0",
179
+ "@vitest/coverage-v8": "^4.1.4",
180
+ "ajv": "^8.18.0",
181
+ "ajv-formats": "^3.0.1",
182
+ "better-auth": "^1.6.23",
183
+ "dotenv": "^17.4.2",
184
+ "knip": "^6.31.0",
185
+ "mongodb": "^7.5.0",
186
+ "mongodb-memory-server": "^10.4.3",
187
+ "mongoose": "^9.9.1",
188
+ "publint": "^0.3.18",
189
+ "tsdown": "^0.22.14",
190
+ "typescript": "~5.9.0",
191
+ "vitest": "^4.1.4"
192
+ }
193
+ }