@classytic/mongokit 3.13.0 → 3.13.2

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.
package/README.md CHANGED
@@ -284,6 +284,58 @@ Each plugin is tree-shakeable. Import only what you use.
284
284
 
285
285
  ---
286
286
 
287
+ ## Composing with mongoose-level plugins
288
+
289
+ mongokit deliberately doesn't reabsorb mongoose plugins that already work via `schema.plugin(...)`. They compose cleanly: the mongoose plugin attaches to the schema before the model lands at mongokit, both layers fire on every write.
290
+
291
+ Canonical examples — install + wire as documented by the upstream package, then hand the model to mongokit:
292
+
293
+ ```ts
294
+ import mongoose from 'mongoose';
295
+ import { Repository, multiTenantPlugin } from '@classytic/mongokit';
296
+ import timelineAuditPlugin from 'mongoose-timeline-audit';
297
+ import slugPlugin from 'mongoose-slug-plugin';
298
+
299
+ const orderSchema = new mongoose.Schema({
300
+ customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer' },
301
+ status: String,
302
+ title: String,
303
+ });
304
+
305
+ // 1. Wire mongoose-level plugins on the schema
306
+ orderSchema.plugin(timelineAuditPlugin, {
307
+ ownerField: 'customerId',
308
+ eventLimits: { 'order.updated': 20 },
309
+ });
310
+ orderSchema.plugin(slugPlugin, { tmpl: '<%=title%>' });
311
+
312
+ // 2. Build the model
313
+ const Order = mongoose.model('Order', orderSchema);
314
+
315
+ // 3. Hand the model to mongokit — kit plugins layer on top
316
+ const orderRepo = new Repository(Order, [
317
+ multiTenantPlugin({ tenantField: 'organizationId' }),
318
+ ]);
319
+
320
+ // Now every write fires BOTH layers:
321
+ // - mongoose pre('save') / post('save') (timeline-audit, slug)
322
+ // - mongokit before:create / after:create (multi-tenant, audit, cache)
323
+ await orderRepo.create({ customerId, status: 'pending', title: 'New order' });
324
+ ```
325
+
326
+ **Why this composition over absorbing into mongokit:** mongoose plugins hook `schema.add()` + `pre('save')`; mongokit plugins hook `before:create` / `after:update`. Different event models, different concerns. Wrapping a mongoose plugin as a mongokit plugin would be a fork, not a wrapper — and the mongoose plugin already works without mongokit being involved at all. Keeping the layers separate also lets each package version independently.
327
+
328
+ **When to use which audit surface:**
329
+
330
+ | Need | Reach for |
331
+ |---|---|
332
+ | Per-doc UX timeline visible to end users (`order.timeline = [{event, actor, at}, ...]`) | `mongoose-timeline-audit` (mongoose plugin) — embedded array on the doc |
333
+ | Compliance audit ledger across models, immutable, queryable | `auditTrailPlugin` (mongokit, ships in this package) — separate collection |
334
+
335
+ The two are complementary, not alternatives. A typical app wires both — timeline for the customer-facing order page, auditTrail for the compliance officer's quarterly export.
336
+
337
+ ---
338
+
287
339
  ## QueryParser (URL → filter)
288
340
 
289
341
  Turn Express / Fastify query strings into sanitized Mongo filters + sort + pagination.
@@ -1,4 +1,4 @@
1
- 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-DLcSSa5b.mjs";
1
+ 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-C-3viiw1.mjs";
2
2
  import { n as warn } from "./logger-DprijP5h.mjs";
3
3
  import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-B00pI7dV.mjs";
4
4
  import { t as PaginationEngine } from "./PaginationEngine-CfQRM1yz.mjs";
@@ -2934,8 +2934,11 @@ var Repository = class extends RepositoryBase {
2934
2934
  * `context.filter`, which is merged into the pre-aggregate `$match`
2935
2935
  * before compilation.
2936
2936
  */
2937
- async aggregate(req) {
2938
- const context = await this._buildContext("aggregate", { aggRequest: req });
2937
+ async aggregate(req, options = {}) {
2938
+ const context = await this._buildContext("aggregate", {
2939
+ aggRequest: req,
2940
+ ...options
2941
+ });
2939
2942
  const cached = this._cachedValue(context);
2940
2943
  if (cached !== void 0) {
2941
2944
  await this._emitHook("after:aggregate", {
@@ -2988,8 +2991,11 @@ var Repository = class extends RepositoryBase {
2988
2991
  * `after`. Scales to arbitrary group counts because the planner
2989
2992
  * uses `(sort_keys) > (cursor)` instead of `OFFSET N`.
2990
2993
  */
2991
- async aggregatePaginate(req) {
2992
- const context = await this._buildContext("aggregatePaginate", { aggRequest: req });
2994
+ async aggregatePaginate(req, options = {}) {
2995
+ const context = await this._buildContext("aggregatePaginate", {
2996
+ aggRequest: req,
2997
+ ...options
2998
+ });
2993
2999
  const limit = Math.max(1, Math.min(req.limit ?? 20, 1e3));
2994
3000
  const session = context.session;
2995
3001
  const useKeyset = isKeysetMode(req);
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "../chunk-CfYAbeIz.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-DLcSSa5b.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-C-3viiw1.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-BY40uhcv.mjs";
1
+ import { n as Repository } from "../Repository-DwJFh3XA.mjs";
2
2
  import { n as createMongooseAdapter } from "../adapter-DTDMnUOH.mjs";
3
3
  import { pluralizeBetterAuthCollection, resolveBetterAuthCollections } from "@classytic/repo-core/better-auth";
4
4
  //#region src/better-auth/index.ts
package/dist/index.d.mts CHANGED
@@ -7,11 +7,34 @@ import { n as SchemaIndexes } from "./indexes-CgOF7Wjd.mjs";
7
7
  import { a as createFieldPreset, c as getMongooseProjection, d as parseDuplicateKeyError, i as configureLogger, l as createError, n as buildCrudSchemasFromMongooseSchema, o as filterResponseData, r as createMemoryCache, s as getFieldsForUser, t as buildCrudSchemasFromModel, u as isDuplicateKeyError } from "./mongooseToJsonSchema-BgRAaXX-.mjs";
8
8
  import * as _$mongoose from "mongoose";
9
9
  import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
10
- import { AggPaginationRequest, AggRequest, AggResult, KeysetAggPaginationResult, RepositoryBase } from "@classytic/repo-core/repository";
10
+ import { AggPaginationRequest, AggRequest, AggResult, KeysetAggPaginationResult, QueryOptions, RepositoryBase } from "@classytic/repo-core/repository";
11
11
  import { UpdateInput } from "@classytic/repo-core/update";
12
12
  import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult, OffsetPaginationResultCore } from "@classytic/repo-core/pagination";
13
13
  import { HttpError } from "@classytic/repo-core/errors";
14
14
 
15
+ //#region src/filter/compile.d.ts
16
+ /**
17
+ * Filter IR → MongoDB query compiler.
18
+ *
19
+ * Mirrors sqlitekit's `compileFilterToDrizzle`: takes a repo-core
20
+ * `Filter` tree and emits a MongoDB query object. Used by `groupBy` /
21
+ * `groupByPaginate` to translate the portable AggRequest's `filter`
22
+ * + `having` into `$match` stages.
23
+ *
24
+ * Scope: the same operator set sqlitekit supports (`eq`, `ne`, `gt`,
25
+ * `gte`, `lt`, `lte`, `in`, `nin`, `exists`, `like`, `regex`, `and`,
26
+ * `or`, `not`, `raw`). Anything else throws so we fail loud instead
27
+ * of silently dropping predicates.
28
+ *
29
+ * Why only this op set here and not everywhere in mongokit? Historic
30
+ * reason: mongokit pre-dates repo-core's Filter IR, so its existing
31
+ * policy plugins (multi-tenant, soft-delete) speak MongoDB query
32
+ * language directly. Converting mongokit's entire filter pipeline
33
+ * to Filter IR is out-of-scope for this change — we introduce the
34
+ * compiler only where the new portable `groupBy` IR needs it.
35
+ */
36
+ declare function compileFilterToMongo(input: unknown): Record<string, unknown>;
37
+ //#endregion
15
38
  //#region src/operations.d.ts
16
39
  /**
17
40
  * Where a plugin should inject its scoping filter (multi-tenant scope,
@@ -1616,7 +1639,7 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
1616
1639
  * `context.filter`, which is merged into the pre-aggregate `$match`
1617
1640
  * before compilation.
1618
1641
  */
1619
- aggregate<TRow extends Record<string, unknown> = Record<string, unknown>>(req: AggRequest): Promise<AggResult<TRow>>;
1642
+ aggregate<TRow extends Record<string, unknown> = Record<string, unknown>>(req: AggRequest, options?: QueryOptions): Promise<AggResult<TRow>>;
1620
1643
  /**
1621
1644
  * Invalidate every aggregate-cache entry tagged with ANY of the
1622
1645
  * given tags. Delegates to the unified cache plugin's handle
@@ -1647,7 +1670,7 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
1647
1670
  * `after`. Scales to arbitrary group counts because the planner
1648
1671
  * uses `(sort_keys) > (cursor)` instead of `OFFSET N`.
1649
1672
  */
1650
- aggregatePaginate<TRow extends Record<string, unknown> = Record<string, unknown>>(req: AggPaginationRequest): Promise<OffsetPaginationResultCore<TRow> | KeysetAggPaginationResult<TRow>>;
1673
+ aggregatePaginate<TRow extends Record<string, unknown> = Record<string, unknown>>(req: AggPaginationRequest, options?: QueryOptions): Promise<OffsetPaginationResultCore<TRow> | KeysetAggPaginationResult<TRow>>;
1651
1674
  /**
1652
1675
  * Internal: the actual aggregate-paginate execution body. Extracted
1653
1676
  * so `aggregatePaginate` can wrap it in the cache layer without
@@ -2114,4 +2137,4 @@ declare function createOptionsExtractor<TCtx extends Record<string, unknown>>(fi
2114
2137
  */
2115
2138
  declare function createRepository<TDoc>(Model: _$mongoose.Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions): Repository<TDoc>;
2116
2139
  //#endregion
2117
- export { ALL_OPERATIONS, type AggregateHelpersMethods, type AggregateOptions, type AggregatePaginationOptions, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BasePaginationOptions, type BatchOperationsMethods, type BulkWriteResult, type CacheAdapter, type CacheOperationOptions, type CacheOptions, type CacheableOptions, type CascadeOptions, type CascadeRelation, type CollationOptions, type CreateInput, type CreateOptions, type CursorPayload, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type DocField, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldType, type FilterQuery, type FindOneAndUpdateOptions, type GroupResult, HOOK_PRIORITY, type HookMode, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type KeysOfType, type KeysetPaginationOptions, type LeaseMethods, type LeasePluginOptions, type Logger, LookupBuilder, type LookupOptions, type LookupPopulateOptions, type LookupPopulateResult, MUTATING_OPERATIONS, type Middleware, type MiddlewareContext, type MinMaxResult, type MinimalRepoView, type MongoOperationsMethods, type MongoOperatorUpdate, type MultiTenantOptions, type NonNullableFields, OP_REGISTRY, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OperationDescriptor, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PolicyKey, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, type PrioritizedHook, QueryParser, type QueryParserOptions, READ_OPERATIONS, type ReadOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryCacheHandle, type RepositoryCachePluginOptions, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaLike, type SearchMode, type SelectSpec, type SequentialIdOptions, type SessionOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type TenantContext, type TenantStore, type UpdateManyResult, type UpdateOptions, type UpdatePatch, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidatorDefinition, type ValueType, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, adminBypass, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, batchTransaction, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createOptionsExtractor, createRepository, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getMongooseProjection, getNextSequence, immutableField, isDuplicateKeyError, isTransactionUnsupported, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, operationsByPolicyKey, parseDuplicateKeyError, prefixedId, repoOptionsFromCtx, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin, withTransaction };
2140
+ export { ALL_OPERATIONS, type AggregateHelpersMethods, type AggregateOptions, type AggregatePaginationOptions, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BasePaginationOptions, type BatchOperationsMethods, type BulkWriteResult, type CacheAdapter, type CacheOperationOptions, type CacheOptions, type CacheableOptions, type CascadeOptions, type CascadeRelation, type CollationOptions, type CreateInput, type CreateOptions, type CursorPayload, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type DocField, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldType, type FilterQuery, type FindOneAndUpdateOptions, type GroupResult, HOOK_PRIORITY, type HookMode, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type KeysOfType, type KeysetPaginationOptions, type LeaseMethods, type LeasePluginOptions, type Logger, LookupBuilder, type LookupOptions, type LookupPopulateOptions, type LookupPopulateResult, MUTATING_OPERATIONS, type Middleware, type MiddlewareContext, type MinMaxResult, type MinimalRepoView, type MongoOperationsMethods, type MongoOperatorUpdate, type MultiTenantOptions, type NonNullableFields, OP_REGISTRY, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OperationDescriptor, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PolicyKey, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, type PrioritizedHook, QueryParser, type QueryParserOptions, READ_OPERATIONS, type ReadOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryCacheHandle, type RepositoryCachePluginOptions, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaLike, type SearchMode, type SelectSpec, type SequentialIdOptions, type SessionOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type TenantContext, type TenantStore, type UpdateManyResult, type UpdateOptions, type UpdatePatch, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidatorDefinition, type ValueType, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, adminBypass, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, batchTransaction, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, compileFilterToMongo, configureLogger, createError, createFieldPreset, createMemoryCache, createOptionsExtractor, createRepository, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getMongooseProjection, getNextSequence, immutableField, isDuplicateKeyError, isTransactionUnsupported, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, operationsByPolicyKey, parseDuplicateKeyError, prefixedId, repoOptionsFromCtx, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin, withTransaction };
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
- import { b as LookupBuilder } from "./update-DLcSSa5b.mjs";
1
+ import { b as LookupBuilder, x as compileFilterToMongo } from "./update-C-3viiw1.mjs";
2
2
  import { n as warn, t as configureLogger } from "./logger-DprijP5h.mjs";
3
3
  import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-B00pI7dV.mjs";
4
4
  import { t as actions_exports } from "./actions/index.mjs";
5
- import { a as withTransaction, c as MUTATING_OPERATIONS, d as operationsByPolicyKey, i as isTransactionUnsupported, l as OP_REGISTRY, n as Repository, o as AggregationBuilder, r as batchTransaction, s as ALL_OPERATIONS, t as HOOK_PRIORITY, u as READ_OPERATIONS } from "./Repository-BY40uhcv.mjs";
5
+ import { a as withTransaction, c as MUTATING_OPERATIONS, d as operationsByPolicyKey, i as isTransactionUnsupported, l as OP_REGISTRY, n as Repository, o as AggregationBuilder, r as batchTransaction, s as ALL_OPERATIONS, t as HOOK_PRIORITY, u as READ_OPERATIONS } from "./Repository-DwJFh3XA.mjs";
6
6
  import { t as PaginationEngine } from "./PaginationEngine-CfQRM1yz.mjs";
7
- import { A as aggregateHelpersPlugin, C as sequentialId, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as prefixedId, T as cachePlugin, _ as fieldFilterPlugin, a as uniqueField, b as dateSequentialId, c as createTenantContext, d as observabilityPlugin, f as adminBypass, g as leasePlugin, h as methodRegistryPlugin, i as requireField, k as auditLogPlugin, l as subdocumentPlugin, m as mongoOperationsPlugin, n as blockIf, o as validationChainPlugin, p as multiTenantPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as softDeletePlugin, v as elasticSearchPlugin, w as cascadePlugin, x as getNextSequence, y as customIdPlugin } from "./validation-chain.plugin-_-WuNml2.mjs";
7
+ import { A as aggregateHelpersPlugin, C as sequentialId, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as prefixedId, T as cachePlugin, _ as fieldFilterPlugin, a as uniqueField, b as dateSequentialId, c as createTenantContext, d as observabilityPlugin, f as adminBypass, g as leasePlugin, h as methodRegistryPlugin, i as requireField, k as auditLogPlugin, l as subdocumentPlugin, m as mongoOperationsPlugin, n as blockIf, o as validationChainPlugin, p as multiTenantPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as softDeletePlugin, v as elasticSearchPlugin, w as cascadePlugin, x as getNextSequence, y as customIdPlugin } from "./validation-chain.plugin-Pj2aD17U.mjs";
8
8
  import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-Br3WPOEb.mjs";
9
9
  import { isGeoOperator, parseGeoFilter } from "./query/primitives/geo.mjs";
10
10
  import { buildFieldTypeMap, coerceFieldValue, coerceHeuristic } from "./query/primitives/coercion.mjs";
@@ -1224,4 +1224,4 @@ function createRepository(Model, plugins = [], paginationConfig = {}, options =
1224
1224
  }
1225
1225
  var src_default = Repository;
1226
1226
  //#endregion
1227
- export { ALL_OPERATIONS, AggregationBuilder, AuditTrailQuery, HOOK_PRIORITY, LookupBuilder, MUTATING_OPERATIONS, OP_REGISTRY, PaginationEngine, QueryParser, READ_OPERATIONS, Repository, actions_exports as actions, adminBypass, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, batchTransaction, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createOptionsExtractor, createRepository, createTenantContext, customIdPlugin, dateSequentialId, src_default as default, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getMongooseProjection, getNextSequence, immutableField, isDuplicateKeyError, isTransactionUnsupported, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, operationsByPolicyKey, parseDuplicateKeyError, prefixedId, repoOptionsFromCtx, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin, withTransaction };
1227
+ export { ALL_OPERATIONS, AggregationBuilder, AuditTrailQuery, HOOK_PRIORITY, LookupBuilder, MUTATING_OPERATIONS, OP_REGISTRY, PaginationEngine, QueryParser, READ_OPERATIONS, Repository, actions_exports as actions, adminBypass, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, batchTransaction, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, compileFilterToMongo, configureLogger, createError, createFieldPreset, createMemoryCache, createOptionsExtractor, createRepository, createTenantContext, customIdPlugin, dateSequentialId, src_default as default, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getMongooseProjection, getNextSequence, immutableField, isDuplicateKeyError, isTransactionUnsupported, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, operationsByPolicyKey, parseDuplicateKeyError, prefixedId, repoOptionsFromCtx, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin, withTransaction };
@@ -0,0 +1,28 @@
1
+ import { Connection } from "mongoose";
2
+ import { BaseLockAdapterOptions, LockAdapter } from "@classytic/repo-core/lock";
3
+
4
+ //#region src/lock/index.d.ts
5
+ /** Adapter-construction options. */
6
+ interface MongoLockAdapterOptions extends BaseLockAdapterOptions {
7
+ /**
8
+ * Mongoose connection. Defaults to `mongoose.connection` (the
9
+ * default global connection) — most apps connect once via
10
+ * `mongoose.connect()` and want every primitive to share that
11
+ * handle. Apps using a named connection
12
+ * (`mongoose.createConnection(...)`) pass it explicitly.
13
+ */
14
+ connection?: Connection;
15
+ /** Collection name. Default `mongo_locks`. */
16
+ collectionName?: string;
17
+ /** Override the Mongoose model name. Default `MongoLock`. */
18
+ modelName?: string;
19
+ }
20
+ /**
21
+ * Build a Mongo-backed lock adapter. Idempotent — the underlying
22
+ * model is registered once per `(connection, modelName)` pair, so
23
+ * calling this multiple times with the same options shares the same
24
+ * collection (no duplicate-model errors).
25
+ */
26
+ declare function createMongoLockAdapter(options?: MongoLockAdapterOptions): LockAdapter;
27
+ //#endregion
28
+ export { MongoLockAdapterOptions, createMongoLockAdapter };
@@ -0,0 +1,92 @@
1
+ import mongoose, { Schema } from "mongoose";
2
+ //#region src/lock/index.ts
3
+ /**
4
+ * Build a Mongo-backed lock adapter. Idempotent — the underlying
5
+ * model is registered once per `(connection, modelName)` pair, so
6
+ * calling this multiple times with the same options shares the same
7
+ * collection (no duplicate-model errors).
8
+ */
9
+ function createMongoLockAdapter(options = {}) {
10
+ const { connection, collectionName = "mongo_locks", modelName = "MongoLock", defaultLeaseMs = 3e4 } = options;
11
+ const model = getOrCreateModel(connection ?? mongoose, modelName, collectionName);
12
+ return {
13
+ async tryAcquire(name, holderId, leaseMs) {
14
+ const ms = leaseMs > 0 ? leaseMs : defaultLeaseMs;
15
+ const now = /* @__PURE__ */ new Date();
16
+ const expiresAt = new Date(now.getTime() + ms);
17
+ try {
18
+ const result = await model.findOneAndUpdate({
19
+ _id: name,
20
+ $or: [{ expiresAt: { $lt: now } }, { holder: holderId }]
21
+ }, {
22
+ $set: {
23
+ holder: holderId,
24
+ expiresAt
25
+ },
26
+ $setOnInsert: { acquiredAt: now }
27
+ }, {
28
+ upsert: true,
29
+ returnDocument: "after",
30
+ setDefaultsOnInsert: true
31
+ }).lean();
32
+ return !!result && result.holder === holderId;
33
+ } catch (err) {
34
+ if (err?.code === 11e3) return false;
35
+ throw err;
36
+ }
37
+ },
38
+ async release(name, holderId) {
39
+ return !!await model.findOneAndDelete({
40
+ _id: name,
41
+ holder: holderId
42
+ }).lean();
43
+ },
44
+ async inspect(name) {
45
+ const doc = await model.findById(name).lean();
46
+ if (!doc) return null;
47
+ if (doc.expiresAt.getTime() <= Date.now()) return null;
48
+ return {
49
+ name,
50
+ holder: doc.holder,
51
+ expiresAt: doc.expiresAt,
52
+ acquiredAt: doc.acquiredAt
53
+ };
54
+ }
55
+ };
56
+ }
57
+ /**
58
+ * Idempotent model registration. Mongoose throws OverwriteModelError
59
+ * if `model('Name', schema)` is called twice with the same name on
60
+ * the same connection — this guard makes the adapter safe to
61
+ * construct multiple times in tests / hot-reload setups.
62
+ */
63
+ function getOrCreateModel(conn, modelName, collectionName) {
64
+ const existing = "models" in conn ? conn.models[modelName] : void 0;
65
+ if (existing) return existing;
66
+ const schema = new Schema({
67
+ _id: {
68
+ type: String,
69
+ required: true
70
+ },
71
+ holder: {
72
+ type: String,
73
+ required: true
74
+ },
75
+ expiresAt: {
76
+ type: Date,
77
+ required: true
78
+ },
79
+ acquiredAt: {
80
+ type: Date,
81
+ required: true
82
+ }
83
+ }, {
84
+ versionKey: false,
85
+ timestamps: false,
86
+ _id: false
87
+ });
88
+ schema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
89
+ return conn.model(modelName, schema, collectionName);
90
+ }
91
+ //#endregion
92
+ export { createMongoLockAdapter };
@@ -1,2 +1,2 @@
1
- import { A as aggregateHelpersPlugin, C as sequentialId, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as prefixedId, T as cachePlugin, _ as fieldFilterPlugin, a as uniqueField, b as dateSequentialId, c as createTenantContext, d as observabilityPlugin, g as leasePlugin, h as methodRegistryPlugin, i as requireField, k as auditLogPlugin, l as subdocumentPlugin, m as mongoOperationsPlugin, n as blockIf, o as validationChainPlugin, p as multiTenantPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as softDeletePlugin, v as elasticSearchPlugin, w as cascadePlugin, x as getNextSequence, y as customIdPlugin } from "../validation-chain.plugin-_-WuNml2.mjs";
1
+ import { A as aggregateHelpersPlugin, C as sequentialId, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as prefixedId, T as cachePlugin, _ as fieldFilterPlugin, a as uniqueField, b as dateSequentialId, c as createTenantContext, d as observabilityPlugin, g as leasePlugin, h as methodRegistryPlugin, i as requireField, k as auditLogPlugin, l as subdocumentPlugin, m as mongoOperationsPlugin, n as blockIf, o as validationChainPlugin, p as multiTenantPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as softDeletePlugin, v as elasticSearchPlugin, w as cascadePlugin, x as getNextSequence, y as customIdPlugin } from "../validation-chain.plugin-Pj2aD17U.mjs";
2
2
  export { AuditTrailQuery, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
@@ -13,9 +13,79 @@ import { isFilter } from "@classytic/repo-core/filter";
13
13
  * appropriately. This keeps the portable API ergonomic for apps that
14
14
  * write Mongo queries directly today.
15
15
  */
16
+ /**
17
+ * Operator shorthands arc propagates from bracket-syntax URL params.
18
+ * Fastify parses `?createdAt[gte]=...&createdAt[lte]=...` into
19
+ * `{ createdAt: { gte: '...', lte: '...' } }` — without `$` prefixes.
20
+ * Arc intentionally forwards these as-is ("the kit's filter compiler
21
+ * handles them"). This set covers every operator the arc aggregation
22
+ * guard accepts in `parseDateRange` and `hasFilterOnField`.
23
+ */
24
+ const SHORTHAND_OPS = new Set([
25
+ "eq",
26
+ "ne",
27
+ "gt",
28
+ "gte",
29
+ "lt",
30
+ "lte",
31
+ "in",
32
+ "nin",
33
+ "exists",
34
+ "regex",
35
+ "mod"
36
+ ]);
37
+ /** Range operators whose string values should be coerced to Date. */
38
+ const RANGE_OPS = new Set([
39
+ "gt",
40
+ "gte",
41
+ "lt",
42
+ "lte"
43
+ ]);
44
+ /** ISO-8601 date-only or datetime string pattern. */
45
+ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}/;
46
+ /**
47
+ * Coerce a string value to a Date when it looks like an ISO-8601 date.
48
+ * Needed because MongoDB won't match a BSON Date field against a string
49
+ * value even when using range operators — BSON type comparison treats
50
+ * Date (type 9) and String (type 2) as distinct, so `{ createdAt: {
51
+ * $gte: "2026-05-01" } }` never matches a Date-typed field.
52
+ */
53
+ function tryCoerceDate(value) {
54
+ if (typeof value === "string" && ISO_DATE_RE.test(value)) {
55
+ const d = new Date(value);
56
+ return Number.isNaN(d.getTime()) ? value : d;
57
+ }
58
+ return value;
59
+ }
60
+ /**
61
+ * Expand operator shorthand values in a plain Mongo-style query object.
62
+ * Only expands nested objects whose keys are ALL in `SHORTHAND_OPS` — so
63
+ * real nested documents (e.g. `address: { city: 'Dhaka' }`) pass through
64
+ * unchanged, while `createdAt: { gte: '2026-04-01', lte: '2026-05-01' }`
65
+ * becomes `createdAt: { $gte: Date(2026-04-01), $lte: Date(2026-05-01) }`.
66
+ * String values on range operators are coerced to Date when they match the
67
+ * ISO-8601 pattern, so BSON Date fields in MongoDB are compared correctly.
68
+ */
69
+ function expandShorthands(obj) {
70
+ const out = {};
71
+ for (const [key, value] of Object.entries(obj)) {
72
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
73
+ const nested = value;
74
+ const keys = Object.keys(nested);
75
+ if (keys.length > 0 && keys.every((k) => !k.startsWith("$") && SHORTHAND_OPS.has(k))) {
76
+ const expanded = {};
77
+ for (const [op, opVal] of Object.entries(nested)) expanded[`$${op}`] = RANGE_OPS.has(op) ? tryCoerceDate(opVal) : opVal;
78
+ out[key] = expanded;
79
+ continue;
80
+ }
81
+ }
82
+ out[key] = value;
83
+ }
84
+ return out;
85
+ }
16
86
  function compileFilterToMongo(input) {
17
87
  if (!input) return {};
18
- if (!isFilter(input)) return input;
88
+ if (!isFilter(input)) return expandShorthands(input);
19
89
  return compile(input);
20
90
  }
21
91
  function compile(filter) {
@@ -1,7 +1,7 @@
1
- import { _ as upsert } from "./update-DLcSSa5b.mjs";
1
+ import { _ as upsert } from "./update-C-3viiw1.mjs";
2
2
  import { n as warn } from "./logger-DprijP5h.mjs";
3
3
  import { t as createError } from "./error-B00pI7dV.mjs";
4
- import { l as OP_REGISTRY, s as ALL_OPERATIONS, t as HOOK_PRIORITY } from "./Repository-BY40uhcv.mjs";
4
+ import { l as OP_REGISTRY, s as ALL_OPERATIONS, t as HOOK_PRIORITY } from "./Repository-DwJFh3XA.mjs";
5
5
  import { r as getFieldsForUser } from "./field-selection-Br3WPOEb.mjs";
6
6
  import mongoose from "mongoose";
7
7
  import { cachePlugin } from "@classytic/repo-core/cache";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/mongokit",
3
- "version": "3.13.0",
3
+ "version": "3.13.2",
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,
@@ -40,6 +40,10 @@
40
40
  "types": "./dist/better-auth/index.d.mts",
41
41
  "default": "./dist/better-auth/index.mjs"
42
42
  },
43
+ "./lock": {
44
+ "types": "./dist/lock/index.d.mts",
45
+ "default": "./dist/lock/index.mjs"
46
+ },
43
47
  "./query/primitives/geo": {
44
48
  "types": "./dist/query/primitives/geo.d.mts",
45
49
  "default": "./dist/query/primitives/geo.mjs"