@classytic/mongokit 3.23.0 → 3.25.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 (40) hide show
  1. package/README.md +11 -2
  2. package/dist/{PaginationEngine-BnZ24ehC.d.mts → PaginationEngine-CBS1XGzp.d.mts} +2 -1
  3. package/dist/{PaginationEngine-BT1ywu9k.mjs → PaginationEngine-CczbvCAq.mjs} +64 -3
  4. package/dist/{Repository-CLQA3sEz.mjs → Repository-KKR8jxfD.mjs} +74 -45
  5. package/dist/actions/index.d.mts +1 -1
  6. package/dist/adapter/index.d.mts +1 -1
  7. package/dist/adapter/index.mjs +1 -1
  8. package/dist/{adapter-BWgJ_Jdc.mjs → adapter-CLvUHiTW.mjs} +1 -1
  9. package/dist/ai/index.d.mts +1 -1
  10. package/dist/better-auth/index.mjs +2 -2
  11. package/dist/core-62oo3N1C.d.mts +50 -0
  12. package/dist/{create-repository-Bx1PpcdF.mjs → create-repository-DA5VJh-3.mjs} +2 -2
  13. package/dist/{create-repository-Cn1LkvL7.d.mts → create-repository-HjMD9h9_.d.mts} +18 -5
  14. package/dist/{id-resolution-P2OzcyRl.mjs → id-resolution-BqDrk0S2.mjs} +12 -1
  15. package/dist/{index-DLQ6pjal.d.mts → index-DceInCTX.d.mts} +3 -1
  16. package/dist/index.d.mts +53 -213
  17. package/dist/index.mjs +1072 -923
  18. package/dist/{mongooseToJsonSchema-CVnrKFDq.mjs → mongooseToJsonSchema-CByt1VhQ.mjs} +1 -1
  19. package/dist/{multi-tenant.plugin-BjXXuc_g.d.mts → multi-tenant.plugin-DVyVGREb.d.mts} +117 -18
  20. package/dist/operations-C8IcCHF9.d.mts +463 -0
  21. package/dist/pagination/PaginationEngine.d.mts +1 -1
  22. package/dist/pagination/PaginationEngine.mjs +1 -1
  23. package/dist/pagination-jSUdrACK.d.mts +158 -0
  24. package/dist/plugin-options-vFufQZMZ.d.mts +125 -0
  25. package/dist/plugins/index.d.mts +4 -4
  26. package/dist/plugins/index.mjs +3 -3
  27. package/dist/query/primitives/coercion.mjs +1 -1
  28. package/dist/query/primitives/geo.d.mts +1 -1
  29. package/dist/repository-DCHRWRXw.d.mts +511 -0
  30. package/dist/testkit/index.d.mts +1 -1
  31. package/dist/testkit/index.mjs +1 -1
  32. package/dist/{timestamp.plugin-BnuuIDqJ.mjs → timestamp.plugin-B8252GuQ.mjs} +192 -46
  33. package/dist/{to-plain-g8-gsVxZ.d.mts → to-plain-DkF5px0R.d.mts} +12 -16
  34. package/dist/type-utils-CLzRFtIA.d.mts +163 -0
  35. package/dist/utils/index.d.mts +2 -2
  36. package/dist/utils/index.mjs +3 -3
  37. package/dist/{validation-chain.plugin-ZFDHxXLn.mjs → validation-chain.plugin-C3Vwp-Rk.mjs} +1 -1
  38. package/dist/{validation-chain.plugin-CFJbr964.d.mts → validation-chain.plugin-DxD15h-2.d.mts} +3 -1
  39. package/package.json +5 -3
  40. package/dist/types-BtMuhoX5.d.mts +0 -1830
package/README.md CHANGED
@@ -212,12 +212,21 @@ const parser = new QueryParser({
212
212
  searchMode: 'auto',
213
213
  });
214
214
 
215
- const parsed = parser.parse(req.query);
215
+ const parsed = parser.parse(req.query); // throws HttpError(400) on invalid/blocked input
216
216
  const result = await invoiceRepo.getAll(parsed);
217
217
  ```
218
218
 
219
219
  Supports common URL operators such as `_gt`, `_gte`, `_lt`, `_lte`, `_in`, `_nin`, `_regex`, geo filters, populate, and schema-aware coercion.
220
220
 
221
+ The parser is **fail-closed by default** (`invalidInput: 'throw'`): blocked
222
+ operators, disallowed fields, malformed values, and pathological regex input
223
+ raise HTTP 400 (`code: 'INVALID_QUERY_INPUT'`) instead of being silently
224
+ dropped — dropping a request's *only* filter would broaden the query to every
225
+ record in tenant scope. Literal-semantics input (`search`, `like`/`contains`)
226
+ always escapes rather than rejects, so `?search=c++` never 400s. Trusted
227
+ migration/compat tooling can opt into the legacy warn-and-drop behavior with
228
+ `invalidInput: 'drop'`.
229
+
221
230
  ## Built-In Plugins
222
231
 
223
232
  | Plugin | Purpose |
@@ -228,7 +237,7 @@ Supports common URL operators such as `_gt`, `_gte`, `_lt`, `_lte`, `_in`, `_nin
228
237
  | `timestampPlugin` | `createdAt` / `updatedAt` management |
229
238
  | `batchOperationsPlugin` | `bulkWrite` and batch helpers |
230
239
  | `cachePlugin` | Pluggable read-through cache |
231
- | `auditLogPlugin` / `auditTrailPlugin` | Operational and compliance audit trails |
240
+ | `auditLogPlugin` / `auditTrailPlugin` | Audit trails — `auditTrailPlugin({ mode: 'transactional' })` gives session-joined entries that are atomic inside `withTransaction` (compliance-grade); the default `'best-effort'` mode is fire-and-forget observability. Call `ensureAuditTrailReady()` at boot so collection/index creation doesn't happen inside the first transaction |
232
241
  | `observabilityPlugin` | Metrics hook points |
233
242
  | `cascadePlugin` | Hook-routed cascade delete |
234
243
  | `customIdPlugin` | Prefixed or sequential public IDs |
@@ -1,4 +1,5 @@
1
- import { A as KeysetPaginationOptions, U as PaginationConfig, V as OffsetPaginationOptions, i as AnyDocument, n as AggregatePaginationOptions } from "./types-BtMuhoX5.mjs";
1
+ import { t as AnyDocument } from "./core-62oo3N1C.mjs";
2
+ import { c as PaginationConfig, o as KeysetPaginationOptions, s as OffsetPaginationOptions, t as AggregatePaginationOptions } from "./pagination-jSUdrACK.mjs";
2
3
  import { Model } from "mongoose";
3
4
  import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
4
5
  //#region src/pagination/PaginationEngine.d.ts
@@ -255,6 +255,59 @@ function resolveCursorFilter(after, sort, cursorVersion, baseFilters = {}, minCu
255
255
  //#endregion
256
256
  //#region src/pagination/utils/index-hint.ts
257
257
  /**
258
+ * The only operators that give the query planner a *bounded equality* it can
259
+ * lead an index with. Everything else (`$ne`, `$nin`, `$gt(e)`, `$lt(e)`,
260
+ * `$all`, `$elemMatch`, `$regex`, `$exists`, …) is a range predicate and, per ESR,
261
+ * must come AFTER the sort keys — never in the leading prefix.
262
+ *
263
+ * `$all` is intentionally excluded: although it combines equality matches,
264
+ * it targets multikey data, whose bounded index prefix cannot generally
265
+ * provide a subsequent index sort. Treating it as residual avoids claiming
266
+ * that a multikey-prefix compound index satisfies the sort.
267
+ */
268
+ const EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["$eq", "$in"]);
269
+ /**
270
+ * Is a top-level filter value a RANGE predicate (as opposed to equality)?
271
+ *
272
+ * Only a plain operator object — one whose own keys start with `$` — can be a
273
+ * range. Scalars, `Date`, `ObjectId`, arrays, and embedded-doc equality
274
+ * matches (`{ nested: { a: 1 } }`) are all equality for index-leading
275
+ * purposes. An operator object is equality iff every `$`-operator it uses is
276
+ * in {@link EQUALITY_OPERATORS}.
277
+ */
278
+ function isRangePredicate(value) {
279
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
280
+ const operatorKeys = Object.keys(value).filter((k) => k.startsWith("$"));
281
+ if (operatorKeys.length === 0) return false;
282
+ return operatorKeys.some((k) => !EQUALITY_OPERATORS.has(k));
283
+ }
284
+ /**
285
+ * Split top-level filter fields into ESR equality vs range predicates.
286
+ *
287
+ * The keyset index check follows MongoDB's ESR rule (Equality → Sort →
288
+ * Range). Only equality predicates belong in an index's leading prefix; a
289
+ * range predicate placed there stops the planner from using any later key for
290
+ * bounds OR for satisfying the sort. So the compatibility check and the
291
+ * recommended-index shape must treat the two classes differently — a field
292
+ * filtered by a range should TRAIL the sort keys, not lead them.
293
+ *
294
+ * `$`-prefixed top-level keys (`$and`/`$or`/…) are ignored (the caller decides
295
+ * how to handle logical combinators).
296
+ */
297
+ function classifyFilterFields(filters) {
298
+ const equality = [];
299
+ const range = [];
300
+ for (const [key, value] of Object.entries(filters)) {
301
+ if (key.startsWith("$")) continue;
302
+ if (isRangePredicate(value)) range.push(key);
303
+ else equality.push(key);
304
+ }
305
+ return {
306
+ equality,
307
+ range
308
+ };
309
+ }
310
+ /**
258
311
  * Read a Mongoose schema's declared indexes, defensively.
259
312
  * Returns an empty array on any introspection failure.
260
313
  */
@@ -626,9 +679,17 @@ var PaginationEngine = class {
626
679
  const sortWithoutIdTail = stripTrailingIdTiebreaker(normalizedSort);
627
680
  const filterKeys = Object.keys(filters).filter((k) => !k.startsWith("$"));
628
681
  const effectiveSortFields = Object.keys(sortWithoutIdTail);
629
- if (process.env.NODE_ENV !== "test" && filterKeys.length > 0 && effectiveSortFields.length > 0 && !hasCompatibleKeysetIndex(this._getSchemaIndexes(), filterKeys, sortWithoutIdTail)) {
630
- const indexFields = [...filterKeys.map((f) => `${f}: 1`), ...effectiveSortFields.map((f) => `${f}: ${sortWithoutIdTail[f]}`)];
631
- warn(`[mongokit] Keyset pagination with filters [${filterKeys.join(", ")}] and sort [${effectiveSortFields.join(", ")}] has no matching schema-declared compound index. For O(1) performance, declare: { ${indexFields.join(", ")} }. (Collection-level indexes created outside the schema are not visible here.)`);
682
+ if (process.env.NODE_ENV !== "test" && filterKeys.length > 0 && effectiveSortFields.length > 0) {
683
+ const indexes = this._getSchemaIndexes();
684
+ const { equality, range } = classifyFilterFields(filters);
685
+ if (!(hasCompatibleKeysetIndex(indexes, filterKeys, sortWithoutIdTail) || equality.length !== filterKeys.length && hasCompatibleKeysetIndex(indexes, equality, sortWithoutIdTail))) {
686
+ const indexFields = [
687
+ ...equality.map((f) => `${f}: 1`),
688
+ ...effectiveSortFields.map((f) => `${f}: ${sortWithoutIdTail[f]}`),
689
+ ...range.map((f) => `${f}: 1`)
690
+ ];
691
+ warn(`[mongokit] Keyset pagination with filters [${filterKeys.join(", ")}] and sort [${effectiveSortFields.join(", ")}] has no matching schema-declared compound index. For O(1) performance, declare: { ${indexFields.join(", ")} }. (Collection-level indexes created outside the schema are not visible here.)`);
692
+ }
632
693
  }
633
694
  let query = { ...filters };
634
695
  if (after) query = resolveCursorFilter(after, normalizedSort, this.config.cursorVersion, query, this.config.minCursorVersion);
@@ -1,8 +1,8 @@
1
- import { i as isValidIdForType, t as getSchemaIdType } from "./id-resolution-P2OzcyRl.mjs";
1
+ import { i as isValidIdForType, t as getSchemaIdType } from "./id-resolution-BqDrk0S2.mjs";
2
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-1Lq9OMD2.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-BT1ywu9k.mjs";
5
+ import { t as PaginationEngine } from "./PaginationEngine-CczbvCAq.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";
@@ -543,7 +543,7 @@ function buildAggPipeline(req) {
543
543
  const match = compileFilterToMongo(baseFilter);
544
544
  if (Object.keys(match).length > 0) stages.push({ $match: match });
545
545
  }
546
- if (req.lookups && req.lookups.length > 0) appendLookupStages(stages, req.lookups);
546
+ if (req.lookups && req.lookups.length > 0) appendLookupStages$1(stages, req.lookups);
547
547
  if (joinedFilter !== void 0) {
548
548
  const match = compileFilterToMongo(joinedFilter);
549
549
  if (Object.keys(match).length > 0) stages.push({ $match: match });
@@ -698,7 +698,7 @@ function validateBucketAliases(bucketAliases, groupCols, measures) {
698
698
  function safeIdKey(field) {
699
699
  return field.includes(".") ? field.replace(/\./g, "__") : field;
700
700
  }
701
- function appendLookupStages(stages, lookups) {
701
+ function appendLookupStages$1(stages, lookups) {
702
702
  const asLookupOptions = lookups;
703
703
  stages.push(...LookupBuilder.multiple(asLookupOptions));
704
704
  for (const lookup of lookups) if (lookup.single) {
@@ -1470,6 +1470,59 @@ var AggregationBuilder = class AggregationBuilder {
1470
1470
  * ```
1471
1471
  */
1472
1472
  //#endregion
1473
+ //#region src/repository/lookup-populate.ts
1474
+ /**
1475
+ * Inclusion projections must carry the keyset sort fields (plus `_id`) or
1476
+ * cursor encoding has no values to serialize. No-op for exclusion
1477
+ * projections and cursor-less calls.
1478
+ */
1479
+ function ensureLookupProjectionIncludesCursorFields(projection, sort) {
1480
+ if (!projection || !sort) return projection;
1481
+ if (!Object.values(projection).some((value) => value === 1)) return projection;
1482
+ const nextProjection = { ...projection };
1483
+ for (const field of [...Object.keys(sort), "_id"]) nextProjection[field] = 1;
1484
+ return nextProjection;
1485
+ }
1486
+ /**
1487
+ * Build the `$project` map from a caller's select spec (string / array /
1488
+ * inclusion-exclusion record). For inclusion projections, every lookup's
1489
+ * `as` field is auto-included so `$project` doesn't strip the joined data.
1490
+ * Returns a fresh object (callers may add fields for cursor encoding).
1491
+ */
1492
+ function buildLookupProjection(selectSpec, lookups) {
1493
+ if (!selectSpec) return void 0;
1494
+ let projection;
1495
+ if (typeof selectSpec === "string") {
1496
+ projection = {};
1497
+ for (const field of selectSpec.split(",").map((f) => f.trim())) if (field.startsWith("-")) projection[field.substring(1)] = 0;
1498
+ else projection[field] = 1;
1499
+ } else if (Array.isArray(selectSpec)) {
1500
+ projection = {};
1501
+ for (const field of selectSpec) if (field.startsWith("-")) projection[field.substring(1)] = 0;
1502
+ else projection[field] = 1;
1503
+ } else projection = { ...selectSpec };
1504
+ if (Object.values(projection).some((v) => v === 1)) for (const lookup of lookups) {
1505
+ const asField = lookup.as || lookup.from;
1506
+ if (!(asField in projection)) projection[asField] = 1;
1507
+ }
1508
+ return projection;
1509
+ }
1510
+ /**
1511
+ * Append the shared pipeline tail: `$lookup` stages, `$ifNull` coalescing
1512
+ * for `single` lookups, and the final `$project` (with cursor fields kept
1513
+ * when a keyset sort is active). Mutates `pipeline` in place — it is the
1514
+ * tail-builder both the keyset and offset modes share.
1515
+ */
1516
+ function appendLookupStages(pipeline, lookups, projection, keysetSort) {
1517
+ pipeline.push(...LookupBuilder.multiple(lookups));
1518
+ for (const lookup of lookups) if (lookup.single) {
1519
+ const asField = lookup.as || lookup.from;
1520
+ pipeline.push({ $addFields: { [asField]: { $ifNull: [`$${asField}`, null] } } });
1521
+ }
1522
+ const finalProjection = ensureLookupProjectionIncludesCursorFields(projection, keysetSort);
1523
+ if (finalProjection) pipeline.push({ $project: finalProjection });
1524
+ }
1525
+ //#endregion
1473
1526
  //#region src/tx-bound.ts
1474
1527
  /**
1475
1528
  * Map of session-aware method name → positional index of the `{ session }`
@@ -1680,13 +1733,6 @@ function isTransactionUnsupported(error) {
1680
1733
  }
1681
1734
  //#endregion
1682
1735
  //#region src/Repository.ts
1683
- function ensureLookupProjectionIncludesCursorFields(projection, sort) {
1684
- if (!projection || !sort) return projection;
1685
- if (!Object.values(projection).some((value) => value === 1)) return projection;
1686
- const nextProjection = { ...projection };
1687
- for (const field of [...Object.keys(sort), "_id"]) nextProjection[field] = 1;
1688
- return nextProjection;
1689
- }
1690
1736
  /**
1691
1737
  * Validates that an update patch is homogeneous — either all top-level
1692
1738
  * keys are `$`-prefixed Mongo operators OR none are. Mixed shapes (e.g.
@@ -3423,6 +3469,15 @@ var Repository = class extends RepositoryBase {
3423
3469
  * Routes through the hook system for policy enforcement
3424
3470
  * (multi-tenant, soft-delete).
3425
3471
  *
3472
+ * The pipeline parameter also accepts `readonly unknown[]` (3.25.0):
3473
+ * real-world stage arrays are assembled from exported fragment builders
3474
+ * and spreads whose element type degrades to `unknown` — mongoose's
3475
+ * `PipelineStage` union can't be satisfied without an `as` at every
3476
+ * such call site. Runtime validation is MongoDB's job either way; the
3477
+ * single documented widening lives HERE instead of N host-side casts
3478
+ * (spine PACKAGE_RULES §7.1). Literal arrays still get full
3479
+ * `PipelineStage` contextual checking via the first union member.
3480
+ *
3426
3481
  * @param pipeline - MongoDB aggregation stage array
3427
3482
  * @param options - Aggregation options including governance controls
3428
3483
  */
@@ -3748,39 +3803,13 @@ var Repository = class extends RepositoryBase {
3748
3803
  const pageFromContext = context.page ?? options.page;
3749
3804
  const isKeyset = !!after || !pageFromContext && !!sort;
3750
3805
  const countStrategy = context.countStrategy ?? options.countStrategy ?? "exact";
3751
- const selectSpec = context.select ?? options.select;
3752
- let projection;
3753
- if (selectSpec) {
3754
- if (typeof selectSpec === "string") {
3755
- projection = {};
3756
- for (const field of selectSpec.split(",").map((f) => f.trim())) if (field.startsWith("-")) projection[field.substring(1)] = 0;
3757
- else projection[field] = 1;
3758
- } else if (Array.isArray(selectSpec)) {
3759
- projection = {};
3760
- for (const field of selectSpec) if (field.startsWith("-")) projection[field.substring(1)] = 0;
3761
- else projection[field] = 1;
3762
- } else projection = { ...selectSpec };
3763
- if (projection) {
3764
- if (Object.values(projection).some((v) => v === 1)) for (const lookup of lookups) {
3765
- const asField = lookup.as || lookup.from;
3766
- if (!(asField in projection)) projection[asField] = 1;
3767
- }
3768
- }
3769
- }
3770
- const appendLookupStages = (pipeline) => {
3771
- pipeline.push(...LookupBuilder.multiple(lookups));
3772
- for (const lookup of lookups) if (lookup.single) {
3773
- const asField = lookup.as || lookup.from;
3774
- pipeline.push({ $addFields: { [asField]: { $ifNull: [`$${asField}`, null] } } });
3775
- }
3776
- const finalProjection = ensureLookupProjectionIncludesCursorFields(projection, isKeyset && sort ? this._parseSort(sort) : void 0);
3777
- if (finalProjection) pipeline.push({ $project: finalProjection });
3778
- };
3806
+ const projection = buildLookupProjection(context.select ?? options.select, lookups);
3807
+ const appendLookupStages$2 = (pipeline) => appendLookupStages(pipeline, lookups, projection, isKeyset && sort ? this._parseSort(sort) : void 0);
3779
3808
  if (isKeyset && sort) {
3780
3809
  const parsedSort = this._parseSort(sort);
3781
- const { validateKeysetSort } = await import("./PaginationEngine-BT1ywu9k.mjs").then((n) => n.n);
3782
- const { encodeCursor, resolveCursorFilter } = await import("./PaginationEngine-BT1ywu9k.mjs").then((n) => n.r);
3783
- const { getPrimaryField } = await import("./PaginationEngine-BT1ywu9k.mjs").then((n) => n.n);
3810
+ const { validateKeysetSort } = await import("./PaginationEngine-CczbvCAq.mjs").then((n) => n.n);
3811
+ const { encodeCursor, resolveCursorFilter } = await import("./PaginationEngine-CczbvCAq.mjs").then((n) => n.r);
3812
+ const { getPrimaryField } = await import("./PaginationEngine-CczbvCAq.mjs").then((n) => n.n);
3784
3813
  const normalizedSort = validateKeysetSort(parsedSort, this._pagination.config.strictKeysetSortFields);
3785
3814
  const cursorVersion = this._pagination.config.cursorVersion ?? 1;
3786
3815
  const minCursorVersion = this._pagination.config.minCursorVersion ?? 1;
@@ -3794,7 +3823,7 @@ var Repository = class extends RepositoryBase {
3794
3823
  if (Object.keys(matchFilters).length > 0) pipeline.push({ $match: matchFilters });
3795
3824
  pipeline.push({ $sort: normalizedSort });
3796
3825
  pipeline.push({ $limit: limit + 1 });
3797
- appendLookupStages(pipeline);
3826
+ appendLookupStages$2(pipeline);
3798
3827
  const aggregation = this.Model.aggregate(pipeline).session(session || null);
3799
3828
  if (collation) aggregation.collation(collation);
3800
3829
  if (readPref) aggregation.read(readPref);
@@ -3823,7 +3852,7 @@ var Repository = class extends RepositoryBase {
3823
3852
  if (sort) dataPipeline.push({ $sort: this._parseSort(sort) });
3824
3853
  if (countStrategy === "none") {
3825
3854
  dataPipeline.push({ $skip: skip }, { $limit: limit + 1 });
3826
- appendLookupStages(dataPipeline);
3855
+ appendLookupStages$2(dataPipeline);
3827
3856
  const aggregation = this.Model.aggregate(dataPipeline).session(session || null);
3828
3857
  if (collation) aggregation.collation(collation);
3829
3858
  if (readPref) aggregation.read(readPref);
@@ -3847,7 +3876,7 @@ var Repository = class extends RepositoryBase {
3847
3876
  return result;
3848
3877
  }
3849
3878
  dataPipeline.push({ $skip: skip }, { $limit: limit });
3850
- appendLookupStages(dataPipeline);
3879
+ appendLookupStages$2(dataPipeline);
3851
3880
  const countPipeline = [];
3852
3881
  if (filters && Object.keys(filters).length > 0) countPipeline.push({ $match: filters });
3853
3882
  countPipeline.push({ $count: "total" });
@@ -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-DLQ6pjal.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-DceInCTX.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 };
@@ -59,7 +59,7 @@ interface MongooseAdapterOptions<TDoc = unknown> {
59
59
  * `@classytic/repo-core/adapter`.
60
60
  */
61
61
  declare class MongooseAdapter<TDoc = unknown> implements DataAdapter<TDoc> {
62
- readonly type: 'mongoose';
62
+ readonly type: "mongoose";
63
63
  readonly name: string;
64
64
  readonly model: Model<TDoc>;
65
65
  readonly repository: RepositoryLike<TDoc>;
@@ -1,2 +1,2 @@
1
- import { n as createMongooseAdapter, r as isMongooseModel, t as MongooseAdapter } from "../adapter-BWgJ_Jdc.mjs";
1
+ import { n as createMongooseAdapter, r as isMongooseModel, t as MongooseAdapter } from "../adapter-CLvUHiTW.mjs";
2
2
  export { MongooseAdapter, createMongooseAdapter, isMongooseModel };
@@ -1,5 +1,5 @@
1
1
  import { n as warn } from "./logger-K7dPmu2U.mjs";
2
- import { t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-CVnrKFDq.mjs";
2
+ import { t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-CByt1VhQ.mjs";
3
3
  import { matchesRecordFilter } from "@classytic/repo-core/filter";
4
4
  import { mergeFieldRuleConstraints } from "@classytic/repo-core/schema";
5
5
  import { asRepositoryLike, isRepository } from "@classytic/repo-core/adapter";
@@ -1,4 +1,4 @@
1
- import { G as Plugin } from "../types-BtMuhoX5.mjs";
1
+ import { l as Plugin } from "../repository-DCHRWRXw.mjs";
2
2
  import { PipelineStage } from "mongoose";
3
3
  //#region src/ai/types.d.ts
4
4
  /** Supported similarity metrics for vector search */
@@ -1,5 +1,5 @@
1
- import { n as Repository } from "../Repository-CLQA3sEz.mjs";
2
- import { n as createMongooseAdapter } from "../adapter-BWgJ_Jdc.mjs";
1
+ import { n as Repository } from "../Repository-KKR8jxfD.mjs";
2
+ import { n as createMongooseAdapter } from "../adapter-CLvUHiTW.mjs";
3
3
  import { pluralizeBetterAuthCollection, resolveBetterAuthCollections } from "@classytic/repo-core/better-auth";
4
4
  //#region src/better-auth/index.ts
5
5
  /**
@@ -0,0 +1,50 @@
1
+ import { Document, Model, PopulateOptions, Types } from "mongoose";
2
+ //#region src/types/core.d.ts
3
+ /**
4
+ * Core query/document primitives shared across every mongokit module —
5
+ * sort/select/populate/filter specs and the mongoose-facing aliases.
6
+ *
7
+ * Also the home of the `softRequired` Mongoose declaration merge (this file
8
+ * is loaded by every module, so the augmentation is always in the program).
9
+ */
10
+ /**
11
+ * Global declaration merge: adds `softRequired` to Mongoose's SchemaTypeOptions
12
+ * so consumers can annotate paths type-safely without importing anything.
13
+ *
14
+ * A path with `softRequired: true` keeps its DB-level `required: true`
15
+ * invariant but is excluded from the auto-generated CRUD body `required[]`
16
+ * array (see buildCrudSchemasFromModel).
17
+ */
18
+ declare module 'mongoose' {
19
+ interface SchemaTypeOptions<T, EnforcedDocType = any, THydratedDocumentType = HydratedDocument<EnforcedDocType>> {
20
+ /** mongokit: omit from auto-generated CRUD body `required` array. */
21
+ softRequired?: boolean;
22
+ }
23
+ }
24
+ /** Read Preference Type for replica sets */
25
+ type ReadPreferenceType = 'primary' | 'primaryPreferred' | 'secondary' | 'secondaryPreferred' | 'nearest' | (string & {});
26
+ /** Re-export mongoose ObjectId */
27
+ type ObjectId = Types.ObjectId;
28
+ /** Generic document type */
29
+ type AnyDocument = Document & Record<string, unknown>;
30
+ /** Generic model type */
31
+ type AnyModel = Model<AnyDocument>;
32
+ /** Sort direction */
33
+ type SortDirection = 1 | -1;
34
+ /** Sort specification for MongoDB queries */
35
+ type SortSpec = Record<string, SortDirection>;
36
+ /** Populate specification */
37
+ type PopulateSpec = string | string[] | PopulateOptions | PopulateOptions[];
38
+ /**
39
+ * Select specification. `readonly string[]` widens the legacy `string[]`
40
+ * form to also accept the immutable variant repo-core's contract uses
41
+ * (`LookupPopulateOptions.select: readonly string[] | Record<string, 0
42
+ * | 1>`), so `Repository<TDoc>` assigns to `StandardRepo<TDoc>` without
43
+ * TS2322 on the `select` field. Any existing mutable-array caller stays
44
+ * compatible — `string[]` is assignable to `readonly string[]`.
45
+ */
46
+ type SelectSpec = string | readonly string[] | Record<string, 0 | 1>;
47
+ /** Filter query type for MongoDB queries (compatible with Mongoose 8 & 9) */
48
+ type FilterQuery<_T = unknown> = Record<string, unknown>;
49
+ //#endregion
50
+ export { PopulateSpec as a, SortDirection as c, ObjectId as i, SortSpec as l, AnyModel as n, ReadPreferenceType as o, FilterQuery as r, SelectSpec as s, AnyDocument as t };
@@ -1,5 +1,5 @@
1
- import { n as Repository } from "./Repository-CLQA3sEz.mjs";
2
- import { a as methodRegistryPlugin, d as cachePlugin, f as batchOperationsPlugin, h as auditLogPlugin, i as multiTenantPlugin, m as auditTrailPlugin, n as softDeletePlugin, o as customIdPlugin, t as timestampPlugin } from "./timestamp.plugin-BnuuIDqJ.mjs";
1
+ import { n as Repository } from "./Repository-KKR8jxfD.mjs";
2
+ import { a as methodRegistryPlugin, d as cachePlugin, f as batchOperationsPlugin, g as auditLogPlugin, i as multiTenantPlugin, m as auditTrailPlugin, n as softDeletePlugin, o as customIdPlugin, t as timestampPlugin } from "./timestamp.plugin-B8252GuQ.mjs";
3
3
  //#region src/create-repository.ts
4
4
  function isLoggerAudit(value) {
5
5
  const v = value;
@@ -1,12 +1,16 @@
1
- import { At as LookupOptions, B as ObjectId, H as OperationOptions, J as PopulateSpec, M as LookupPopulateOptions, N as LookupPopulateResult, P as Middleware, Q as RepositoryContext, U as PaginationConfig, X as ReadOptions, Y as PrioritizedHook, Z as ReadPreferenceType, _ as DeleteManyResult, at as SessionOptions, c as CacheableOptions, d as CollationOptions, ft as UpdateManyResult, it as SelectSpec, j as Logger, kt as LookupBuilder, n as AggregatePaginationOptions, nt as RepositoryOptions, p as CreateOptions, pt as UpdateOptions, q as PluginType, st as SoftDeleteOptions, t as AggregateOptions, ut as SortSpec$1, v as DeleteResult, w as FindOneAndUpdateOptions, xt as WithTransactionOptions } from "./types-BtMuhoX5.mjs";
2
- import { _ as RepositoryCachePluginOptions, i as CustomIdOptions, t as MultiTenantOptions, w as AuditTrailOptions } from "./multi-tenant.plugin-BjXXuc_g.mjs";
3
- import { t as PaginationEngine } from "./PaginationEngine-BnZ24ehC.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-C8IcCHF9.mjs";
2
+ import { a as PopulateSpec, i as ObjectId, l as SortSpec$1, o as ReadPreferenceType, s as SelectSpec } from "./core-62oo3N1C.mjs";
3
+ import { c as PaginationConfig, r as CollationOptions, t as AggregatePaginationOptions } from "./pagination-jSUdrACK.mjs";
4
+ import { _ as RepositoryOptions, d as PluginType, f as PrioritizedHook, o as Middleware, p as RepositoryContext } from "./repository-DCHRWRXw.mjs";
5
+ import { T as AuditTrailOptions, _ as RepositoryCachePluginOptions, i as CustomIdOptions, t as MultiTenantOptions } from "./multi-tenant.plugin-DVyVGREb.mjs";
6
+ import { t as PaginationEngine } from "./PaginationEngine-CBS1XGzp.mjs";
7
+ import { i as Logger, o as SoftDeleteOptions } from "./plugin-options-vFufQZMZ.mjs";
4
8
  import { AggPaginationRequest, AggRequest, AggResult, ArchiveOptions, ArchiveResult, ArchiveSink, ChangeEvent, FilterInput, KeysetAggPaginationResult, QueryOptions, RepoCapabilities, RepositoryBase, TenantPurgeOptions, TenantPurgeResult, TenantPurgeStrategy, WatchOptions } from "@classytic/repo-core/repository";
5
9
  import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
6
10
  import { Filter } from "@classytic/repo-core/filter";
7
11
  import { UpdateInput } from "@classytic/repo-core/update";
8
- import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult, OffsetPaginationResultCore } from "@classytic/repo-core/pagination";
9
12
  import { HttpError } from "@classytic/repo-core/errors";
13
+ import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult, OffsetPaginationResultCore } from "@classytic/repo-core/pagination";
10
14
  //#region src/query/AggregationBuilder.d.ts
11
15
  /** Options for $vectorSearch stage (Atlas only) */
12
16
  interface VectorSearchOptions {
@@ -1486,10 +1490,19 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
1486
1490
  * Routes through the hook system for policy enforcement
1487
1491
  * (multi-tenant, soft-delete).
1488
1492
  *
1493
+ * The pipeline parameter also accepts `readonly unknown[]` (3.25.0):
1494
+ * real-world stage arrays are assembled from exported fragment builders
1495
+ * and spreads whose element type degrades to `unknown` — mongoose's
1496
+ * `PipelineStage` union can't be satisfied without an `as` at every
1497
+ * such call site. Runtime validation is MongoDB's job either way; the
1498
+ * single documented widening lives HERE instead of N host-side casts
1499
+ * (spine PACKAGE_RULES §7.1). Literal arrays still get full
1500
+ * `PipelineStage` contextual checking via the first union member.
1501
+ *
1489
1502
  * @param pipeline - MongoDB aggregation stage array
1490
1503
  * @param options - Aggregation options including governance controls
1491
1504
  */
1492
- aggregatePipeline<TResult = unknown>(pipeline: PipelineStage[], options?: AggregateOptions): Promise<TResult[]>;
1505
+ aggregatePipeline<TResult = unknown>(pipeline: PipelineStage[] | readonly unknown[], options?: AggregateOptions): Promise<TResult[]>;
1493
1506
  /**
1494
1507
  * Paginated kit-native aggregation pipeline. Same mongo-specific
1495
1508
  * scope as `aggregatePipeline` — reach for this when you need
@@ -15,6 +15,17 @@ import mongoose from "mongoose";
15
15
  * Design: no `this`, no Model, no DB calls. Only reads `schema.paths._id.instance`.
16
16
  */
17
17
  /**
18
+ * Null-tolerant `id → ObjectId` coercion — the single seam for optional
19
+ * reference/audit-stamp fields (`approvedBy`, `submittedBy`, …) where an
20
+ * absent actor is a legitimate `null`, not an error. Invalid non-empty
21
+ * strings still throw (mongoose's own constructor validation) — this helper
22
+ * only absorbs the absent case, never bad input. Hosts were hand-rolling
23
+ * this identically at every approval-hook write; import it instead.
24
+ */
25
+ function toObjectId(id) {
26
+ return id ? new mongoose.Types.ObjectId(id) : null;
27
+ }
28
+ /**
18
29
  * Check if a Mongoose SchemaType `instance` string is an ObjectId variant.
19
30
  * Mongoose 9.x uses `'ObjectId'`, older versions use `'ObjectID'`. This
20
31
  * is the single source of truth — use it instead of inline
@@ -103,4 +114,4 @@ function idVariants(id) {
103
114
  return [id];
104
115
  }
105
116
  //#endregion
106
- export { isValidIdForType as i, idVariants as n, isObjectIdInstance as r, getSchemaIdType as t };
117
+ export { toObjectId as a, isValidIdForType as i, idVariants as n, isObjectIdInstance as r, getSchemaIdType as t };
@@ -1,4 +1,6 @@
1
- import { At as LookupOptions, B as ObjectId, H as OperationOptions, I as MinMaxResult, J as PopulateSpec, T as GroupResult, Z as ReadPreferenceType, ft as UpdateManyResult, ht as UpdateWithValidationResult, i as AnyDocument, it as SelectSpec, p as CreateOptions, pt as UpdateOptions, ut as SortSpec, v as DeleteResult, w as FindOneAndUpdateOptions } from "./types-BtMuhoX5.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-C8IcCHF9.mjs";
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
+ import { p as UpdateWithValidationResult } from "./type-utils-CLzRFtIA.mjs";
2
4
  import { Model, PipelineStage } from "mongoose";
3
5
  declare namespace aggregate_d_exports {
4
6
  export { aggregate, aggregatePaginate, average, countBy, distinct, facet, groupBy, lookup, minMax, sum, unwind };