@classytic/mongokit 3.18.0 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +66 -0
  2. package/dist/{PaginationEngine-BcvaXBQ-.mjs → PaginationEngine-BT1ywu9k.mjs} +327 -6
  3. package/dist/PaginationEngine-DYpOD2wY.d.mts +102 -0
  4. package/dist/{Repository-B01VW_fU.mjs → Repository-dbbgOGjK.mjs} +85 -11
  5. package/dist/actions/index.d.mts +1 -1
  6. package/dist/actions/index.mjs +2 -2
  7. package/dist/adapter/index.d.mts +10 -2
  8. package/dist/adapter/index.mjs +1 -1
  9. package/dist/{adapter-DYEMkLwK.mjs → adapter-CW1EL0fF.mjs} +10 -1
  10. package/dist/ai/index.d.mts +1 -2
  11. package/dist/ai/index.mjs +3 -2
  12. package/dist/better-auth/index.d.mts +0 -1
  13. package/dist/better-auth/index.mjs +2 -2
  14. package/dist/create-repository-0TkQhcv-.d.mts +1757 -0
  15. package/dist/create-repository-CJATiAKX.mjs +29 -0
  16. package/dist/{id-resolution-CnN0YA8t.mjs → id-resolution-P2OzcyRl.mjs} +10 -5
  17. package/dist/{index-fi91ycQc.d.mts → index-Cg3SWugF.d.mts} +1 -3
  18. package/dist/index.d.mts +11 -1666
  19. package/dist/index.mjs +51 -37
  20. package/dist/lock/index.d.mts +0 -1
  21. package/dist/{mongooseToJsonSchema-BdQJU39w.d.mts → mongooseToJsonSchema-D2CyAMIC.d.mts} +9 -5
  22. package/dist/{mongooseToJsonSchema-CBEQmeEa.mjs → mongooseToJsonSchema-khdzVo8x.mjs} +1 -1
  23. package/dist/multi-tenant.plugin-CTsWz0XK.d.mts +486 -0
  24. package/dist/pagination/PaginationEngine.d.mts +1 -102
  25. package/dist/pagination/PaginationEngine.mjs +1 -1
  26. package/dist/plugins/index.d.mts +3 -3
  27. package/dist/plugins/index.mjs +2 -1
  28. package/dist/query/primitives/coercion.d.mts +1 -1
  29. package/dist/query/primitives/coercion.mjs +1 -1
  30. package/dist/query/primitives/geo.d.mts +1 -1
  31. package/dist/query/primitives/indexes.d.mts +1 -1
  32. package/dist/testkit/index.d.mts +128 -0
  33. package/dist/testkit/index.mjs +139 -0
  34. package/dist/{validation-chain.plugin-DJtC0KJ-.mjs → timestamp.plugin-nG9Z1JEk.mjs} +5 -1056
  35. package/dist/{types-CU3SEDbD.d.mts → types-DNFMofHy.d.mts} +51 -22
  36. package/dist/{update-CvCMv5Cl.mjs → update-1Lq9OMD2.mjs} +5 -5
  37. package/dist/utils/index.d.mts +1 -1
  38. package/dist/utils/index.mjs +5 -5
  39. package/dist/validation-chain.plugin-Dn_wZYLA.mjs +1056 -0
  40. package/dist/{validation-chain.plugin-BpMfy18N.d.mts → validation-chain.plugin-buy3uvyk.d.mts} +2 -487
  41. package/package.json +16 -7
  42. package/dist/cursor-_SbWVAn-.mjs +0 -254
  43. package/dist/sort-Ca1BPfvE.mjs +0 -72
  44. /package/dist/{coercion-TRMmR49h.d.mts → coercion-BjdPX3YF.d.mts} +0 -0
  45. /package/dist/{error-BTeepSNL.mjs → error-BuByZwKU.mjs} +0 -0
  46. /package/dist/{field-selection-CQW3WBiK.mjs → field-selection-FSQFLpsU.mjs} +0 -0
  47. /package/dist/{indexes-CgOF7Wjd.d.mts → indexes-DIjMpYES.d.mts} +0 -0
  48. /package/dist/{logger-B8wf-Mtb.mjs → logger-K7dPmu2U.mjs} +0 -0
  49. /package/dist/{chunk-CfYAbeIz.mjs → rolldown-runtime-D7D4PA-g.mjs} +0 -0
package/dist/index.mjs CHANGED
@@ -1,16 +1,18 @@
1
- import { n as idVariants } from "./id-resolution-CnN0YA8t.mjs";
2
- import { a as withTransaction, c as MUTATING_OPERATIONS, d as operationsByPolicyKey, f as MONGOKIT_CAPABILITIES, i as isTransactionUnsupported, l as OP_REGISTRY, n as Repository, o as AggregationBuilder, p as createMongoPurgePort, r as batchTransaction, s as ALL_OPERATIONS, t as HOOK_PRIORITY, u as READ_OPERATIONS } from "./Repository-B01VW_fU.mjs";
3
- import { b as LookupBuilder, x as compileFilterToMongo } from "./update-CvCMv5Cl.mjs";
4
- import { n as warn, t as configureLogger } from "./logger-B8wf-Mtb.mjs";
5
- import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-BTeepSNL.mjs";
1
+ import { n as idVariants } from "./id-resolution-P2OzcyRl.mjs";
2
+ import { a as withTransaction, c as MUTATING_OPERATIONS, d as operationsByPolicyKey, f as MONGOKIT_CAPABILITIES, i as isTransactionUnsupported, l as OP_REGISTRY, n as Repository, o as AggregationBuilder, p as createMongoPurgePort, r as batchTransaction, s as ALL_OPERATIONS, t as HOOK_PRIORITY, u as READ_OPERATIONS } from "./Repository-dbbgOGjK.mjs";
3
+ import { b as LookupBuilder, x as compileFilterToMongo } from "./update-1Lq9OMD2.mjs";
4
+ import { n as warn, t as configureLogger } from "./logger-K7dPmu2U.mjs";
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 auditLogPlugin, C as getNextSequence, D as batchOperationsPlugin, E as cachePlugin, O as AuditTrailQuery, S as dateSequentialId, T as sequentialId, _ as softDeletePlugin, a as uniqueField, b as methodRegistryPlugin, c as subdocumentPlugin, d as leasePlugin, f as fieldFilterPlugin, g as timestampPlugin, h as aggregateHelpersPlugin, i as requireField, k as auditTrailPlugin, l as observabilityPlugin, m as cascadePlugin, n as blockIf, o as validationChainPlugin, p as elasticSearchPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as mongoOperationsPlugin, v as adminBypass, w as prefixedId, x as customIdPlugin, y as multiTenantPlugin } from "./validation-chain.plugin-DJtC0KJ-.mjs";
8
- import { t as PaginationEngine } from "./PaginationEngine-BcvaXBQ-.mjs";
7
+ import { a as methodRegistryPlugin, c as getNextSequence, d as cachePlugin, f as batchOperationsPlugin, h as auditLogPlugin, i as multiTenantPlugin, l as prefixedId, m as auditTrailPlugin, n as softDeletePlugin, o as customIdPlugin, p as AuditTrailQuery, r as adminBypass, s as dateSequentialId, t as timestampPlugin, u as sequentialId } from "./timestamp.plugin-nG9Z1JEk.mjs";
8
+ import { t as PaginationEngine } from "./PaginationEngine-BT1ywu9k.mjs";
9
9
  import { isGeoOperator, parseGeoFilter } from "./query/primitives/geo.mjs";
10
- import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-CQW3WBiK.mjs";
10
+ import { t as createRepository } from "./create-repository-CJATiAKX.mjs";
11
+ import { a as uniqueField, c as subdocumentPlugin, d as leasePlugin, f as fieldFilterPlugin, h as aggregateHelpersPlugin, i as requireField, l as observabilityPlugin, m as cascadePlugin, n as blockIf, o as validationChainPlugin, p as elasticSearchPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as mongoOperationsPlugin } from "./validation-chain.plugin-Dn_wZYLA.mjs";
12
+ import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-FSQFLpsU.mjs";
11
13
  import { buildFieldTypeMap, coerceFieldValue, coerceHeuristic } from "./query/primitives/coercion.mjs";
12
14
  import { extractSchemaIndexes } from "./query/primitives/indexes.mjs";
13
- import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-CBEQmeEa.mjs";
15
+ import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-khdzVo8x.mjs";
14
16
  import { runChunkedPurge } from "@classytic/repo-core/repository";
15
17
  import { isControlParam } from "@classytic/repo-core/query-parser";
16
18
  //#region src/actions/cascade-references.ts
@@ -142,32 +144,6 @@ async function cascadePurgeReferences(options) {
142
144
  return out;
143
145
  }
144
146
  //#endregion
145
- //#region src/create-repository.ts
146
- function isLoggerAudit(value) {
147
- const v = value;
148
- return typeof v.info === "function" || typeof v.warn === "function" || typeof v.error === "function" || typeof v.debug === "function";
149
- }
150
- /**
151
- * Build a `Repository` from a declarative config. See module JSDoc for
152
- * ordering guarantees and an example.
153
- */
154
- function createRepository(model, config = {}) {
155
- const { tenant, softDelete, timestamps, batch, cache, audit, customId, plugins = [], pagination = {}, ...repositoryOptions } = config;
156
- const stack = [methodRegistryPlugin()];
157
- if (tenant) stack.push(multiTenantPlugin(tenant));
158
- if (softDelete) stack.push(softDeletePlugin(softDelete === true ? {} : softDelete));
159
- if (timestamps) stack.push(timestampPlugin());
160
- if (customId) stack.push(customIdPlugin(customId));
161
- if (cache) stack.push(cachePlugin(cache));
162
- if (audit) stack.push(isLoggerAudit(audit) ? auditLogPlugin(audit) : auditTrailPlugin(audit));
163
- if (batch) stack.push(batchOperationsPlugin());
164
- stack.push(...plugins);
165
- return new Repository(model, stack, pagination, {
166
- pluginOrderChecks: repositoryOptions.pluginOrderChecks ?? "throw",
167
- ...repositoryOptions
168
- });
169
- }
170
- //#endregion
171
147
  //#region src/query/QueryParser.ts
172
148
  /**
173
149
  * Modern Query Parser - URL to MongoDB Query Transpiler
@@ -250,7 +226,7 @@ function createRepository(model, config = {}) {
250
226
  * without this guard. Kept as a Set for O(1) lookup; values mirror the
251
227
  * reserved-key list at the top of `_parseFilters`.
252
228
  */
253
- const RESERVED_PAGINATION_KEYS = new Set([
229
+ const RESERVED_PAGINATION_KEYS = /* @__PURE__ */ new Set([
254
230
  "page",
255
231
  "limit",
256
232
  "sort",
@@ -1067,7 +1043,7 @@ var QueryParser = class {
1067
1043
  return {};
1068
1044
  }
1069
1045
  const sanitized = {};
1070
- const logicalArrayOps = new Set([
1046
+ const logicalArrayOps = /* @__PURE__ */ new Set([
1071
1047
  "$or",
1072
1048
  "$and",
1073
1049
  "$nor"
@@ -1445,6 +1421,44 @@ function systemContext(extra) {
1445
1421
  }
1446
1422
  //#endregion
1447
1423
  //#region src/index.ts
1424
+ /**
1425
+ * MongoKit - Event-driven repository pattern for MongoDB
1426
+ *
1427
+ * Production-grade MongoDB repositories with zero dependencies -
1428
+ * smart pagination, events, and plugins.
1429
+ *
1430
+ * @module @classytic/mongokit
1431
+ * @author Classytic (https://github.com/classytic)
1432
+ * @license MIT
1433
+ *
1434
+ * @example
1435
+ * ```typescript
1436
+ * import { createRepository } from '@classytic/mongokit';
1437
+ *
1438
+ * // Create repository with declarative config (plugins composed in the
1439
+ * // canonical safe order automatically)
1440
+ * const userRepo = createRepository(UserModel, {
1441
+ * timestamps: true,
1442
+ * softDelete: true,
1443
+ * });
1444
+ *
1445
+ * // Create
1446
+ * const user = await userRepo.create({ name: 'John', email: 'john@example.com' });
1447
+ *
1448
+ * // Read with pagination (auto-detects offset vs keyset)
1449
+ * const users = await userRepo.getAll({ page: 1, limit: 20 });
1450
+ *
1451
+ * // Keyset pagination for infinite scroll
1452
+ * const stream = await userRepo.getAll({ sort: { createdAt: -1 }, limit: 50 });
1453
+ * const nextStream = await userRepo.getAll({ after: stream.next, sort: { createdAt: -1 } });
1454
+ *
1455
+ * // Update
1456
+ * await userRepo.update(user._id, { name: 'John Doe' });
1457
+ *
1458
+ * // Delete
1459
+ * await userRepo.delete(user._id);
1460
+ * ```
1461
+ */
1448
1462
  var src_default = Repository;
1449
1463
  //#endregion
1450
1464
  export { ALL_OPERATIONS, AggregationBuilder, AuditTrailQuery, HOOK_PRIORITY, LookupBuilder, MONGOKIT_CAPABILITIES, 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, cascadePurgeReferences, compileFilterToMongo, configureLogger, createError, createFieldPreset, createMemoryCache, createOptionsExtractor, createRepository, createTenantContext, customIdPlugin, dateSequentialId, src_default as default, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getMongooseProjection, getNextSequence, idVariants, immutableField, isDuplicateKeyError, isTransactionUnsupported, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, operationsByPolicyKey, parseDuplicateKeyError, prefixedId, repoOptionsFromCtx, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, systemContext, timestampPlugin, uniqueField, validationChainPlugin, withTransaction };
@@ -1,6 +1,5 @@
1
1
  import { Connection } from "mongoose";
2
2
  import { BaseLockAdapterOptions, LockAdapter } from "@classytic/repo-core/lock";
3
-
4
3
  //#region src/lock/index.d.ts
5
4
  /** Adapter-construction options. */
6
5
  interface MongoLockAdapterOptions extends BaseLockAdapterOptions {
@@ -1,9 +1,8 @@
1
- import { C as FieldPreset, gt as UserContext } from "./types-CU3SEDbD.mjs";
1
+ import { C as FieldPreset, gt as UserContext } from "./types-DNFMofHy.mjs";
2
2
  import mongoose, { Schema } from "mongoose";
3
3
  import { CacheAdapter } from "@classytic/repo-core/cache";
4
4
  import { CrudSchemas, SchemaBuilderOptions } from "@classytic/repo-core/schema";
5
5
  import { HttpError } from "@classytic/repo-core/errors";
6
-
7
6
  //#region src/utils/error.d.ts
8
7
  /** Optional structured fields for createError. */
9
8
  interface CreateErrorOptions {
@@ -170,9 +169,10 @@ interface SchemaLike {
170
169
  }>;
171
170
  }
172
171
  /**
173
- * Detect the _id path type from a Mongoose schema.
172
+ * Detect an id path's type from a Mongoose schema. Defaults to the `_id`
173
+ * path; pass `field` to resolve a custom id field (Repository's `idField`).
174
174
  *
175
- * Returns 'objectid' as the safe default when:
175
+ * For `_id`, returns 'objectid' as the safe default when:
176
176
  * - No schema is provided (mock models, test harnesses)
177
177
  * - The schema has no `paths._id` (auto-generated ObjectId)
178
178
  * - The instance string is unrecognized
@@ -180,8 +180,12 @@ interface SchemaLike {
180
180
  * This safe-default means existing callers that don't set _id explicitly
181
181
  * get the same ObjectId validation they always had — the behavioral change
182
182
  * is strictly opt-in via schema declaration.
183
+ *
184
+ * For a custom `field` with no resolvable path, returns 'unknown' instead —
185
+ * custom id fields carry no ObjectId convention, so validation stays
186
+ * permissive and the DB remains the authority.
183
187
  */
184
- declare function getSchemaIdType(schema?: SchemaLike | null): IdType;
188
+ declare function getSchemaIdType(schema?: SchemaLike | null, field?: string): IdType;
185
189
  /**
186
190
  * Validate an id value against a known _id type — WITHOUT hitting the DB.
187
191
  *
@@ -1,4 +1,4 @@
1
- import { r as isObjectIdInstance } from "./id-resolution-CnN0YA8t.mjs";
1
+ import { r as isObjectIdInstance } from "./id-resolution-P2OzcyRl.mjs";
2
2
  import mongoose from "mongoose";
3
3
  import { applyFieldRules, collectFieldsToOmit } from "@classytic/repo-core/schema";
4
4
  //#region src/utils/memory-cache.ts
@@ -0,0 +1,486 @@
1
+ import { B as ObjectId, G as Plugin, Q as RepositoryContext } from "./types-DNFMofHy.mjs";
2
+ import mongoose from "mongoose";
3
+ import { CacheAdapter as CacheAdapter$1, CacheOptions, RepositoryCacheHandle, RepositoryCachePluginOptions, cachePlugin } from "@classytic/repo-core/cache";
4
+ import { TenantConfig } from "@classytic/repo-core/tenant";
5
+ //#region src/plugins/audit-trail.plugin.d.ts
6
+ interface AuditTrailOptions {
7
+ /** Operations to track (default: ['create', 'update', 'delete']) */
8
+ operations?: AuditOperation[];
9
+ /** Store field-level before/after diff on updates (default: true) */
10
+ trackChanges?: boolean;
11
+ /** Store full document snapshot on create (default: false — can be heavy) */
12
+ trackDocument?: boolean;
13
+ /** Auto-purge after N days via MongoDB TTL index (default: undefined — keep forever) */
14
+ ttlDays?: number;
15
+ /** MongoDB collection name (default: 'audit_trails') */
16
+ collectionName?: string;
17
+ /**
18
+ * Extract custom metadata from the repository context.
19
+ * Returned object is stored on the audit entry as `metadata`.
20
+ */
21
+ metadata?: (context: RepositoryContext) => Record<string, unknown>;
22
+ /**
23
+ * Fields to exclude from change tracking (e.g., passwords, tokens).
24
+ * These fields are redacted in the `changes` diff.
25
+ */
26
+ excludeFields?: string[];
27
+ /**
28
+ * Custom callback fired when an audit write fails. Receives the error
29
+ * and the entry that failed to land. Default: `console.warn` only.
30
+ *
31
+ * **Use this for compliance-grade audit trails (SOC 2 / PCI / HIPAA)
32
+ * where missing entries must be surfaced upstream** — e.g. forward to
33
+ * a dead-letter queue, page on-call, or short-circuit the request.
34
+ * The default `warn` log is appropriate for development and
35
+ * eventually-consistent observability use cases but is invisible to
36
+ * compliance review.
37
+ *
38
+ * The callback runs on the same fire-and-forget microtask that wrote
39
+ * the entry, so throwing from here will surface as an unhandled
40
+ * rejection — handle (or rethrow into your error reporter) inside.
41
+ */
42
+ onWriteError?: (error: Error, entry: Omit<AuditEntry, 'timestamp'>) => void;
43
+ }
44
+ type AuditOperation = 'create' | 'update' | 'delete' | 'findOneAndUpdate';
45
+ interface AuditEntry {
46
+ model: string;
47
+ operation: AuditOperation;
48
+ documentId: unknown;
49
+ userId?: unknown;
50
+ orgId?: unknown;
51
+ changes?: Record<string, {
52
+ from: unknown;
53
+ to: unknown;
54
+ }>;
55
+ document?: Record<string, unknown>;
56
+ metadata?: Record<string, unknown>;
57
+ timestamp: Date;
58
+ }
59
+ declare function auditTrailPlugin(options?: AuditTrailOptions): Plugin;
60
+ interface AuditQueryOptions {
61
+ model?: string;
62
+ documentId?: string | ObjectId;
63
+ userId?: string | ObjectId;
64
+ orgId?: string | ObjectId;
65
+ operation?: AuditOperation;
66
+ from?: Date;
67
+ to?: Date;
68
+ page?: number;
69
+ limit?: number;
70
+ }
71
+ interface AuditQueryResult {
72
+ data: AuditEntry[];
73
+ page: number;
74
+ limit: number;
75
+ total: number;
76
+ pages: number;
77
+ hasNext: boolean;
78
+ hasPrev: boolean;
79
+ }
80
+ /**
81
+ * Standalone audit trail query utility.
82
+ * Use this to query audits across all models — e.g., admin dashboards, audit APIs.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * import { AuditTrailQuery } from '@classytic/mongokit';
87
+ *
88
+ * const auditQuery = new AuditTrailQuery(); // defaults to 'audit_trails' collection
89
+ *
90
+ * // All audits for an org
91
+ * const orgAudits = await auditQuery.query({ orgId: '...' });
92
+ *
93
+ * // All updates by a user
94
+ * const userUpdates = await auditQuery.query({
95
+ * userId: '...',
96
+ * operation: 'update',
97
+ * });
98
+ *
99
+ * // All audits for a specific document
100
+ * const docHistory = await auditQuery.query({
101
+ * model: 'Job',
102
+ * documentId: '...',
103
+ * });
104
+ *
105
+ * // Date range
106
+ * const recent = await auditQuery.query({
107
+ * from: new Date('2025-01-01'),
108
+ * to: new Date(),
109
+ * page: 1,
110
+ * limit: 50,
111
+ * });
112
+ *
113
+ * // Direct model access for custom queries
114
+ * const model = auditQuery.getModel();
115
+ * const count = await model.countDocuments({ operation: 'delete' });
116
+ * ```
117
+ */
118
+ declare class AuditTrailQuery {
119
+ private model;
120
+ constructor(collectionName?: string, ttlDays?: number);
121
+ /**
122
+ * Get the underlying Mongoose model for custom queries
123
+ */
124
+ getModel(): mongoose.Model<AuditEntry>;
125
+ /**
126
+ * Query audit entries with filters and pagination
127
+ */
128
+ query(options?: AuditQueryOptions): Promise<AuditQueryResult>;
129
+ /**
130
+ * Get audit trail for a specific document
131
+ */
132
+ getDocumentTrail(model: string, documentId: string | ObjectId, options?: {
133
+ page?: number;
134
+ limit?: number;
135
+ operation?: AuditOperation;
136
+ }): Promise<AuditQueryResult>;
137
+ /**
138
+ * Get all audits for a user
139
+ */
140
+ getUserTrail(userId: string | ObjectId, options?: {
141
+ page?: number;
142
+ limit?: number;
143
+ operation?: AuditOperation;
144
+ orgId?: string | ObjectId;
145
+ }): Promise<AuditQueryResult>;
146
+ /**
147
+ * Get all audits for an organization
148
+ */
149
+ getOrgTrail(orgId: string | ObjectId, options?: {
150
+ page?: number;
151
+ limit?: number;
152
+ operation?: AuditOperation;
153
+ model?: string;
154
+ }): Promise<AuditQueryResult>;
155
+ }
156
+ interface AuditTrailMethods {
157
+ /**
158
+ * Get paginated audit trail for a document
159
+ */
160
+ getAuditTrail(documentId: string | ObjectId, options?: {
161
+ page?: number;
162
+ limit?: number;
163
+ operation?: AuditOperation;
164
+ }): Promise<AuditQueryResult>;
165
+ }
166
+ //#endregion
167
+ //#region src/plugins/custom-id.plugin.d.ts
168
+ /**
169
+ * Generator function that produces a unique ID.
170
+ * Receives the full repository context for conditional logic.
171
+ */
172
+ type IdGenerator = (context: RepositoryContext) => string | Promise<string>;
173
+ interface CustomIdOptions {
174
+ /** Field to store the custom ID (default: 'customId') */
175
+ field?: string;
176
+ /** Function to generate the ID. Can be async. */
177
+ generator: IdGenerator;
178
+ /** Only generate if the field is missing/empty (default: true) */
179
+ generateOnlyIfEmpty?: boolean;
180
+ }
181
+ /**
182
+ * Atomically increment and return the next sequence value for a given key.
183
+ * Uses `findOneAndUpdate` with `upsert` + `$inc` — fully atomic even under
184
+ * heavy concurrency.
185
+ *
186
+ * @param counterKey - Unique key identifying this counter (e.g., "Invoice" or "Invoice:2026-02")
187
+ * @param increment - Value to increment by (default: 1)
188
+ * @param connection - Mongoose connection to use (defaults to the default connection)
189
+ * @param session - Optional client session. When provided, the counter bump
190
+ * participates in the caller's transaction — if the surrounding transaction
191
+ * aborts, the counter does NOT advance. Without a session, the bump commits
192
+ * immediately and is not rolled back if a sibling write fails.
193
+ * @returns The next sequence number (after increment)
194
+ *
195
+ * @example
196
+ * const seq = await getNextSequence('invoices');
197
+ * // First call → 1, second → 2, ...
198
+ *
199
+ * @example Batch increment for createMany
200
+ * const startSeq = await getNextSequence('invoices', 5);
201
+ * // If current was 10, returns 15 (you use 11, 12, 13, 14, 15)
202
+ *
203
+ * @example Transactional counter bump
204
+ * await withTransaction(conn, async (session) => {
205
+ * const seq = await getNextSequence('invoices', 1, conn, session);
206
+ * await invoiceRepo.create({ invoiceNumber: `INV-${seq}` }, { session });
207
+ * // if this throws, the counter is rolled back with the insert
208
+ * });
209
+ */
210
+ declare function getNextSequence(counterKey: string, increment?: number, connection?: mongoose.Connection, session?: mongoose.ClientSession): Promise<number>;
211
+ interface SequentialIdOptions {
212
+ /** Prefix string (e.g., 'INV', 'ORD') */
213
+ prefix: string;
214
+ /** Mongoose model — used to derive the counter key from model name */
215
+ model: mongoose.Model<any>;
216
+ /** Number of digits to pad to (default: 4 → "0001") */
217
+ padding?: number;
218
+ /** Separator between prefix and number (default: '-') */
219
+ separator?: string;
220
+ /** Custom counter key override (default: model.modelName) */
221
+ counterKey?: string;
222
+ }
223
+ /**
224
+ * Generator: Simple sequential counter.
225
+ * Produces IDs like `INV-0001`, `INV-0002`, etc.
226
+ *
227
+ * Uses atomic MongoDB counters — safe under concurrency.
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * customIdPlugin({
232
+ * field: 'invoiceNumber',
233
+ * generator: sequentialId({ prefix: 'INV', model: InvoiceModel }),
234
+ * })
235
+ * ```
236
+ */
237
+ declare function sequentialId(options: SequentialIdOptions): IdGenerator;
238
+ interface DateSequentialIdOptions {
239
+ /** Prefix string (e.g., 'BILL', 'INV') */
240
+ prefix: string;
241
+ /** Mongoose model — used to derive the counter key */
242
+ model: mongoose.Model<any>;
243
+ /**
244
+ * Partition granularity — counter resets each period.
245
+ * - 'yearly' → BILL-2026-0001, resets every January
246
+ * - 'monthly' → BILL-2026-02-0001, resets every month
247
+ * - 'daily' → BILL-2026-02-20-0001, resets every day
248
+ */
249
+ partition?: 'yearly' | 'monthly' | 'daily';
250
+ /** Number of digits to pad to (default: 4) */
251
+ padding?: number;
252
+ /** Separator (default: '-') */
253
+ separator?: string;
254
+ }
255
+ /**
256
+ * Generator: Date-partitioned sequential counter.
257
+ * Counter resets per period — great for invoice/bill numbering.
258
+ *
259
+ * Produces IDs like:
260
+ * - yearly: `BILL-2026-0001`
261
+ * - monthly: `BILL-2026-02-0001`
262
+ * - daily: `BILL-2026-02-20-0001`
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * customIdPlugin({
267
+ * field: 'billNumber',
268
+ * generator: dateSequentialId({
269
+ * prefix: 'BILL',
270
+ * model: BillModel,
271
+ * partition: 'monthly',
272
+ * }),
273
+ * })
274
+ * ```
275
+ */
276
+ declare function dateSequentialId(options: DateSequentialIdOptions): IdGenerator;
277
+ interface PrefixedIdOptions {
278
+ /** Prefix string (e.g., 'USR', 'TXN') */
279
+ prefix: string;
280
+ /** Separator (default: '_') */
281
+ separator?: string;
282
+ /** Length of the random suffix (default: 12) */
283
+ length?: number;
284
+ }
285
+ /**
286
+ * Generator: Prefix + random alphanumeric suffix.
287
+ * Does NOT require a database round-trip — purely in-memory.
288
+ *
289
+ * Produces IDs like: `USR_a7b3xk9m2p1q`
290
+ *
291
+ * Good for: user-facing IDs where ordering doesn't matter.
292
+ * Not suitable for sequential numbering.
293
+ *
294
+ * @example
295
+ * ```typescript
296
+ * customIdPlugin({
297
+ * field: 'publicId',
298
+ * generator: prefixedId({ prefix: 'USR', length: 10 }),
299
+ * })
300
+ * ```
301
+ */
302
+ declare function prefixedId(options: PrefixedIdOptions): IdGenerator;
303
+ /**
304
+ * Custom ID plugin — injects generated IDs into documents before creation.
305
+ *
306
+ * @param options - Configuration for ID generation
307
+ * @returns Plugin instance
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * import { Repository, customIdPlugin, sequentialId } from '@classytic/mongokit';
312
+ *
313
+ * const invoiceRepo = new Repository(InvoiceModel, [
314
+ * customIdPlugin({
315
+ * field: 'invoiceNumber',
316
+ * generator: sequentialId({ prefix: 'INV', model: InvoiceModel }),
317
+ * }),
318
+ * ]);
319
+ *
320
+ * const inv = await invoiceRepo.create({ amount: 100 });
321
+ * console.log(inv.invoiceNumber); // "INV-0001"
322
+ * ```
323
+ */
324
+ declare function customIdPlugin(options: CustomIdOptions): Plugin;
325
+ //#endregion
326
+ //#region src/plugins/multi-tenant.plugin.d.ts
327
+ /**
328
+ * Static tenant fields are picked from `@classytic/repo-core/tenant` so the
329
+ * vocabulary (`tenantField`, `contextKey`, `required`, `fieldType`) stays
330
+ * locked to the org-wide canonical source. Mongokit-specific runtime
331
+ * callbacks (`skipWhen`, `resolveContext`, `skipOperations`,
332
+ * `allowDataInjection`) extend on top — those genuinely differ from
333
+ * sqlitekit's runtime shape because mongokit's `RepositoryContext` carries
334
+ * Mongoose-specific bits the resolver may inspect.
335
+ *
336
+ * Mongokit defaults `fieldType` to `'string'` (not repo-core's `'objectId'`)
337
+ * for back-compat — pre-3.x stored tenant ids as raw strings; flipping the
338
+ * default would silently change cast behavior for existing apps.
339
+ */
340
+ interface MultiTenantOptions extends Pick<TenantConfig, 'tenantField' | 'contextKey' | 'required' | 'fieldType'> {
341
+ /** Operations to skip tenant injection (e.g., for admin/system queries) */
342
+ skipOperations?: string[];
343
+ /**
344
+ * Dynamic skip — receives the context and operation name, returns true to
345
+ * bypass tenant scoping for this call. Use for role-based bypass (e.g.,
346
+ * super admin) without needing a separate repo instance.
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * skipWhen: (context) => context.role === 'superadmin'
351
+ * ```
352
+ */
353
+ skipWhen?: (context: RepositoryContext, operation: string) => boolean;
354
+ /**
355
+ * Resolve tenant ID from external source (e.g., AsyncLocalStorage, CLS).
356
+ * Called when tenant ID is not found in context. If it returns a value,
357
+ * that value is used as the tenant ID without requiring it in context.
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * resolveContext: () => asyncLocalStorage.getStore()?.tenantId
362
+ * ```
363
+ */
364
+ resolveContext?: () => string | undefined;
365
+ /**
366
+ * When `true`, bypass the `required` throw if the operation's policy
367
+ * target already carries the tenant field. This lets hosts that stamp
368
+ * the tenant into the payload themselves (e.g., `data[tenantField]`
369
+ * on writes, `filters[tenantField]` on paginated reads) use the plugin
370
+ * without having to hand-roll a `skipWhen` that duplicates this check.
371
+ *
372
+ * **Default: `false` (secure / fail-closed).** Tenant scope MUST come
373
+ * from `context` or `resolveContext`, otherwise the plugin throws
374
+ * (when `required: true`). This is the right posture for a security-
375
+ * sensitive boundary: caller-supplied scope on the payload cannot be
376
+ * trusted as authentication. If a host control-plane has already
377
+ * authenticated the tenant out-of-band and stamps it onto every
378
+ * write/read, set this to `true` explicitly — making the trust model
379
+ * visible at the call site.
380
+ *
381
+ * **Migration from < 4.0:** the prior default was `true`. To preserve
382
+ * the old behavior verbatim, set `allowDataInjection: true`. To get
383
+ * the new secure default, leave it unset.
384
+ *
385
+ * Composition: a user-supplied `skipWhen` still runs first and can
386
+ * always opt out. This option controls the behavior after `skipWhen`
387
+ * and `resolveContext` have had their turn.
388
+ *
389
+ * Safety: when the payload supplies the tenant AND this is `true`,
390
+ * the plugin skips its own injection (it does not overwrite). Cross-
391
+ * tenant isolation is therefore only as strong as the caller's own
392
+ * stamping — same guarantee as a hand-rolled `skipWhen`.
393
+ */
394
+ allowDataInjection?: boolean;
395
+ /**
396
+ * What to do when the call's filter/payload ALREADY carries
397
+ * `tenantField` with a value that does NOT match the resolved tenant
398
+ * scope (a caller-supplied cross-tenant value — fat-fingered org id,
399
+ * stale client state, or an injection attempt).
400
+ *
401
+ * - `'throw'` (default, fail-closed): reject the call with a clear
402
+ * error. A mismatch is never legitimate on a scoped call — callers
403
+ * who genuinely need cross-tenant access use `bypassTenant: true`
404
+ * (per-call) or `skipWhen` (plugin-level), both of which emit the
405
+ * `after:tenant-bypass` audit event. Operator-shaped values
406
+ * (`{ $in: [...] }`) on the tenant field can't be verified against
407
+ * the scope and are rejected too — multi-value tenant scoping is
408
+ * deliberately not supported (see CLAUDE.md); use an explicit
409
+ * bypass + manual filter.
410
+ * - `'overwrite'` (legacy, pre-3.16 behavior): silently replace the
411
+ * supplied value with the resolved scope. Still leak-safe (the
412
+ * resolved scope always wins) but hides caller bugs.
413
+ *
414
+ * Matching values (same tenant, any representation — string vs
415
+ * ObjectId) are never a mismatch; the plugin normalizes them to the
416
+ * configured `fieldType`.
417
+ */
418
+ onMismatch?: 'throw' | 'overwrite';
419
+ }
420
+ declare function multiTenantPlugin(options?: MultiTenantOptions): Plugin;
421
+ /**
422
+ * Build the canonical `skipWhen` callback for role-based tenant
423
+ * bypass — replaces the hand-rolled
424
+ * `(ctx) => ctx.role === 'superadmin'` callback that ~6 packages
425
+ * across the classytic codebase carry as boilerplate. Eliminates the
426
+ * "I forgot to include 'platform_admin'" drift class — the role
427
+ * vocabulary lives in one place.
428
+ *
429
+ * **Composes with `bypassTenant: true` per-call**, doesn't replace
430
+ * it. `bypassTenant` is the deliberate per-call form for migration
431
+ * scripts and one-off admin queries; `adminBypass` is the
432
+ * always-on role-based bypass for users whose JWT / session carries
433
+ * a privileged role.
434
+ *
435
+ * **NOT a substitute for authentication or RBAC.** This factory only
436
+ * answers "should the tenant scope hook fire for this call?" — it
437
+ * does NOT validate that the caller is authenticated, that their
438
+ * role claim is genuine, or that the requested operation is
439
+ * permitted. Always run auth + RBAC before the call reaches the
440
+ * repository layer; this is the LAST gate, not the only one.
441
+ *
442
+ * @example Single role
443
+ * ```ts
444
+ * import { adminBypass, multiTenantPlugin } from '@classytic/mongokit';
445
+ *
446
+ * const repo = new Repository(InvoiceModel, [
447
+ * multiTenantPlugin({
448
+ * tenantField: 'organizationId',
449
+ * skipWhen: adminBypass({ adminRoles: ['superadmin'] }),
450
+ * }),
451
+ * ]);
452
+ *
453
+ * await repo.getAll({}, { role: 'superadmin' }); // sees all orgs
454
+ * await repo.getAll({}, { role: 'user', organizationId: 'org-1' }); // scoped
455
+ * ```
456
+ *
457
+ * @example Multiple admin roles + custom field name
458
+ * ```ts
459
+ * skipWhen: adminBypass({
460
+ * roleField: 'principalRole', // ctx.principalRole, not ctx.role
461
+ * adminRoles: ['superadmin', 'platform_admin', 'support'],
462
+ * }),
463
+ * ```
464
+ *
465
+ * @example Composing with custom logic
466
+ * ```ts
467
+ * // adminBypass for the role check, then your own additional logic:
468
+ * const adminCheck = adminBypass({ adminRoles: ['superadmin'] });
469
+ * skipWhen: (ctx, op) => adminCheck(ctx, op) || ctx.internalSystemFlag === true,
470
+ * ```
471
+ *
472
+ * @param options.roleField - Path on the context to read the role
473
+ * from. Defaults to `'role'`. Top-level keys only — for nested
474
+ * shapes (e.g. `ctx.user.role`), wrap your own `(ctx) => …` callback
475
+ * instead.
476
+ * @param options.adminRoles - Role values that grant the bypass. The
477
+ * factory does an exact-match `includes` check — case-sensitive,
478
+ * no fuzzy matching. Lowercase your role vocabulary upstream.
479
+ * @returns A `skipWhen`-compatible callback `(ctx, op) → boolean`.
480
+ */
481
+ declare function adminBypass(options: {
482
+ roleField?: string;
483
+ adminRoles: readonly string[];
484
+ }): (context: RepositoryContext, operation: string) => boolean;
485
+ //#endregion
486
+ export { AuditTrailMethods as C, auditTrailPlugin as E, AuditQueryResult as S, AuditTrailQuery as T, RepositoryCachePluginOptions as _, DateSequentialIdOptions as a, AuditOperation as b, SequentialIdOptions as c, getNextSequence as d, prefixedId as f, RepositoryCacheHandle as g, CacheOptions as h, CustomIdOptions as i, customIdPlugin as l, CacheAdapter$1 as m, adminBypass as n, IdGenerator as o, sequentialId as p, multiTenantPlugin as r, PrefixedIdOptions as s, MultiTenantOptions as t, dateSequentialId as u, cachePlugin as v, AuditTrailOptions as w, AuditQueryOptions as x, AuditEntry as y };