@classytic/mongokit 3.3.1 → 3.4.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.
package/dist/index.d.mts CHANGED
@@ -1,10 +1,9 @@
1
- import { $ as SchemaBuilderOptions, A as KeysetPaginationOptions, B as PaginationResult, C as GroupResult, D as InferRawDoc, E as InferDocument, F as ObjectId, G as PopulateSpec, H as Plugin, I as OffsetPaginationOptions, J as RepositoryEvent, K as ReadPreferenceType, L as OffsetPaginationResult, M as Logger, N as MinMaxResult, O as JsonSchema, P as NonNullableFields, Q as RequiredBy, R as OperationOptions, S as FieldRules, St as LookupOptions, T as HttpError, U as PluginFunction, V as PartialBy, W as PluginType, X as RepositoryOperation, Y as RepositoryInstance, Z as RepositoryOptions, _ as DeleteResult, _t as IController, a as AnyModel, at as SortSpec, b as EventPhase, bt as IResponseFormatter, c as CacheOptions, ct as UpdateManyResult, d as CascadeRelation, dt as UserContext, et as SelectSpec, f as CreateInput, ft as ValidationChainOptions, g as DeepPartial, gt as WithTransactionOptions, h as DecodedCursor, ht as WithPlugins, i as AnyDocument, it as SortDirection, j as KeysetPaginationResult, k as KeysOfType, l as CacheStats, lt as UpdateOptions, m as CrudSchemas, mt as ValidatorDefinition, n as AggregatePaginationResult, nt as SoftDeleteOptions, o as CacheAdapter, ot as Strict, p as CreateOptions, pt as ValidationResult, q as RepositoryContext, r as AllPluginMethods, rt as SoftDeleteRepository, s as CacheOperationOptions, st as UpdateInput, t as AggregatePaginationOptions, tt as SoftDeleteFilterMode, u as CascadeOptions, ut as UpdateWithValidationResult, v as EventHandlers, vt as IControllerResponse, w as HookMode, x as FieldPreset, xt as LookupBuilder, y as EventPayload, yt as IRequestContext, z as PaginationConfig } from "./types-pVY0w1Pp.mjs";
2
- import "./aggregate-BkOG9qwr.mjs";
3
- import { t as index_d_exports } from "./actions/index.mjs";
1
+ import { $ as SchemaBuilderOptions, A as KeysetPaginationOptions, B as PaginationResult, C as GroupResult, D as InferRawDoc, E as InferDocument, F as ObjectId, G as PopulateSpec, H as Plugin, I as OffsetPaginationOptions, J as RepositoryEvent, K as ReadPreferenceType, L as OffsetPaginationResult, M as Logger, N as MinMaxResult, O as JsonSchema, P as NonNullableFields, Q as RequiredBy, R as OperationOptions, S as FieldRules, St as LookupOptions, T as HttpError, U as PluginFunction, V as PartialBy, W as PluginType, X as RepositoryOperation, Y as RepositoryInstance, Z as RepositoryOptions, _ as DeleteResult, _t as IController, a as AnyModel, at as SortSpec, b as EventPhase, bt as IResponseFormatter, c as CacheOptions, ct as UpdateManyResult, d as CascadeRelation, dt as UserContext, et as SelectSpec, f as CreateInput, ft as ValidationChainOptions, g as DeepPartial, gt as WithTransactionOptions, h as DecodedCursor, ht as WithPlugins, i as AnyDocument, it as SortDirection, j as KeysetPaginationResult, k as KeysOfType, l as CacheStats, lt as UpdateOptions, m as CrudSchemas, mt as ValidatorDefinition, n as AggregatePaginationResult, nt as SoftDeleteOptions, o as CacheAdapter, ot as Strict, p as CreateOptions, pt as ValidationResult, q as RepositoryContext, r as AllPluginMethods, rt as SoftDeleteRepository, s as CacheOperationOptions, st as UpdateInput, t as AggregatePaginationOptions, tt as SoftDeleteFilterMode, u as CascadeOptions, ut as UpdateWithValidationResult, v as EventHandlers, vt as IControllerResponse, w as HookMode, x as FieldPreset, xt as LookupBuilder, y as EventPayload, yt as IRequestContext, z as PaginationConfig } from "./types-BlCwDszq.mjs";
2
+ import { t as index_d_exports } from "./index-Df3ernpC.mjs";
4
3
  import { PaginationEngine } from "./pagination/PaginationEngine.mjs";
5
- import { A as subdocumentPlugin, B as immutableField, C as observabilityPlugin, D as CacheMethods, E as cascadePlugin, F as batchOperationsPlugin, G as methodRegistryPlugin, H as uniqueField, I as MongoOperationsMethods, J as auditLogPlugin, K as SoftDeleteMethods, L as mongoOperationsPlugin, M as aggregateHelpersPlugin, N as BatchOperationsMethods, O as cachePlugin, P as BulkWriteResult, R as autoInject, S as OperationMetric, T as multiTenantPlugin, U as validationChainPlugin, V as requireField, X as fieldFilterPlugin, Y as timestampPlugin, _ as AuditTrailMethods, a as SequentialIdOptions, b as auditTrailPlugin, c as getNextSequence, d as ElasticSearchOptions, f as elasticSearchPlugin, g as AuditQueryResult, h as AuditQueryOptions, i as PrefixedIdOptions, j as AggregateHelpersMethods, k as SubdocumentMethods, l as prefixedId, m as AuditOperation, n as DateSequentialIdOptions, o as customIdPlugin, p as AuditEntry, q as softDeletePlugin, r as IdGenerator, s as dateSequentialId, t as CustomIdOptions, u as sequentialId, v as AuditTrailOptions, w as MultiTenantOptions, x as ObservabilityOptions, y as AuditTrailQuery, z as blockIf } from "./custom-id.plugin-BJ3FSnzt.mjs";
6
- import { a as isFieldUpdateAllowed, c as configureLogger, d as filterResponseData, f as getFieldsForUser, i as getSystemManagedFields, l as createError, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as getMongooseProjection, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as createFieldPreset } from "./mongooseToJsonSchema-B6O2ED3n.mjs";
7
- import * as mongoose$1 from "mongoose";
4
+ import { A as dateSequentialId, B as AuditEntry, C as elasticSearchPlugin, D as PrefixedIdOptions, E as IdGenerator, F as CacheMethods, G as AuditTrailOptions, H as AuditQueryOptions, I as cachePlugin, J as auditLogPlugin, K as AuditTrailQuery, L as BatchOperationsMethods, M as prefixedId, N as sequentialId, O as SequentialIdOptions, P as cascadePlugin, R as BulkWriteResult, S as ElasticSearchOptions, T as DateSequentialIdOptions, U as AuditQueryResult, V as AuditOperation, W as AuditTrailMethods, X as aggregateHelpersPlugin, Y as AggregateHelpersMethods, _ as MongoOperationsMethods, a as uniqueField, b as methodRegistryPlugin, c as SubdocumentMethods, d as softDeletePlugin, f as ObservabilityOptions, g as multiTenantPlugin, h as MultiTenantOptions, i as requireField, j as getNextSequence, k as customIdPlugin, l as subdocumentPlugin, m as observabilityPlugin, n as blockIf, o as validationChainPlugin, p as OperationMetric, q as auditTrailPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as SoftDeleteMethods, v as mongoOperationsPlugin, w as CustomIdOptions, x as fieldFilterPlugin, z as batchOperationsPlugin } from "./validation-chain.plugin-DxqiHv-E.mjs";
5
+ import { a as isFieldUpdateAllowed, c as configureLogger, d as getFieldsForUser, f as getMongooseProjection, i as getSystemManagedFields, l as createFieldPreset, m as parseDuplicateKeyError, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as createError, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as filterResponseData } from "./mongooseToJsonSchema-BqgVOlrR.mjs";
6
+ import * as _$mongoose from "mongoose";
8
7
  import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
9
8
 
10
9
  //#region src/query/AggregationBuilder.d.ts
@@ -67,7 +66,7 @@ declare class AggregationBuilder {
67
66
  * .exec(MyModel);
68
67
  * ```
69
68
  */
70
- exec<T = unknown>(model: Model<any>, session?: mongoose$1.ClientSession): Promise<T[]>;
69
+ exec<T = unknown>(model: Model<any>, session?: _$mongoose.ClientSession): Promise<T[]>;
71
70
  /**
72
71
  * Reset the pipeline
73
72
  */
@@ -428,135 +427,435 @@ declare class AggregationBuilder {
428
427
  static startWith(query: Record<string, unknown>): AggregationBuilder;
429
428
  }
430
429
  //#endregion
431
- //#region src/Repository.d.ts
432
- type HookListener = (data: any) => void | Promise<void>;
433
- /** Hook with priority for phase ordering */
434
- interface PrioritizedHook {
435
- listener: HookListener;
436
- priority: number;
437
- }
438
- /**
439
- * Plugin phase priorities (lower = runs first)
440
- * Policy hooks (multi-tenant, soft-delete, validation) MUST run before cache
441
- * to ensure filters are injected before cache keys are computed.
442
- */
443
- declare const HOOK_PRIORITY: {
444
- /** Policy enforcement: tenant isolation, soft-delete filtering, validation */readonly POLICY: 100; /** Caching: lookup/store after policy filters are applied */
445
- readonly CACHE: 200; /** Observability: audit logging, metrics, telemetry */
446
- readonly OBSERVABILITY: 300; /** Default priority for user-registered hooks */
447
- readonly DEFAULT: 500;
448
- };
430
+ //#region src/query/QueryParser.d.ts
431
+ type SortSpec$1 = Record<string, 1 | -1>;
432
+ type FilterQuery = Record<string, unknown>;
449
433
  /**
450
- * Production-grade repository for MongoDB
451
- * Event-driven, plugin-based, with smart pagination
434
+ * Mongoose-compatible populate option
435
+ * Supports advanced populate with select, match, limit, sort, and nested populate
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * // URL: ?populate[author][select]=name,email&populate[author][match][active]=true
440
+ * // Generates: { path: 'author', select: 'name email', match: { active: true } }
441
+ * ```
452
442
  */
453
- declare class Repository<TDoc = any> {
454
- readonly Model: Model<TDoc>;
455
- readonly model: string;
456
- readonly _hooks: Map<string, PrioritizedHook[]>;
457
- readonly _pagination: PaginationEngine<TDoc>;
458
- private readonly _hookMode;
459
- [key: string]: unknown;
460
- private _hasTextIndex;
461
- constructor(Model: Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions);
443
+ interface PopulateOption {
444
+ /** Field path to populate */
445
+ path: string;
446
+ /** Fields to select (space-separated) */
447
+ select?: string;
448
+ /** Filter conditions for populated documents */
449
+ match?: Record<string, unknown>;
450
+ /** Query options (limit, sort, skip) */
451
+ options?: {
452
+ limit?: number;
453
+ sort?: SortSpec$1;
454
+ skip?: number;
455
+ };
456
+ /** Nested populate configuration */
457
+ populate?: PopulateOption;
458
+ }
459
+ /** Parsed query result with optional lookup configuration */
460
+ interface ParsedQuery {
461
+ /** MongoDB filter query */
462
+ filters: FilterQuery;
463
+ /** Sort specification */
464
+ sort?: SortSpec$1;
465
+ /** Fields to populate (simple comma-separated string) */
466
+ populate?: string;
462
467
  /**
463
- * Register a plugin
468
+ * Advanced populate options (Mongoose-compatible)
469
+ * When this is set, `populate` will be undefined
470
+ * @example [{ path: 'author', select: 'name email' }]
464
471
  */
465
- use(plugin: PluginType): this;
472
+ populateOptions?: PopulateOption[];
473
+ /** Page number for offset pagination */
474
+ page?: number;
475
+ /** Cursor for keyset pagination */
476
+ after?: string;
477
+ /** Limit */
478
+ limit: number;
479
+ /** Full-text search query */
480
+ search?: string;
481
+ /** Lookup configurations for custom field joins */
482
+ lookups?: LookupOptions[];
483
+ /** Aggregation pipeline stages (advanced) */
484
+ aggregation?: PipelineStage[];
485
+ /** Select/project fields */
486
+ select?: Record<string, 0 | 1>;
487
+ }
488
+ /** Search mode for query parser */
489
+ type SearchMode = 'text' | 'regex';
490
+ interface QueryParserOptions {
491
+ /** Maximum allowed regex pattern length (default: 500) */
492
+ maxRegexLength?: number;
493
+ /** Maximum allowed text search query length (default: 200) */
494
+ maxSearchLength?: number;
495
+ /** Maximum allowed filter depth (default: 10) */
496
+ maxFilterDepth?: number;
497
+ /** Maximum allowed limit value (default: 1000) */
498
+ maxLimit?: number;
499
+ /** Additional operators to block */
500
+ additionalDangerousOperators?: string[];
501
+ /** Enable lookup parsing (default: true) */
502
+ enableLookups?: boolean;
503
+ /** Enable aggregation parsing (default: false - requires explicit opt-in) */
504
+ enableAggregations?: boolean;
466
505
  /**
467
- * Register event listener with optional priority for phase ordering.
468
- *
469
- * @param event - Event name (e.g. 'before:getAll')
470
- * @param listener - Hook function
471
- * @param options - Optional { priority } — use HOOK_PRIORITY constants.
472
- * Lower priority numbers run first.
473
- * Default: HOOK_PRIORITY.DEFAULT (500)
506
+ * Search mode (default: 'text')
507
+ * - 'text': Uses MongoDB $text search (requires text index)
508
+ * - 'regex': Uses $or with $regex across searchFields (no index required)
474
509
  */
475
- on(event: string, listener: HookListener, options?: {
476
- priority?: number;
477
- }): this;
510
+ searchMode?: SearchMode;
478
511
  /**
479
- * Remove a specific event listener
512
+ * Fields to search when searchMode is 'regex'
513
+ * Required when searchMode is 'regex'
514
+ * @example ['name', 'description', 'sku', 'tags']
480
515
  */
481
- off(event: string, listener: HookListener): this;
516
+ searchFields?: string[];
482
517
  /**
483
- * Remove all listeners for an event, or all listeners entirely
518
+ * Whitelist of collection names allowed in lookups.
519
+ * When set, only these collections can be used in $lookup stages.
520
+ * When undefined, all collection names are allowed.
521
+ * @example ['departments', 'categories', 'users']
484
522
  */
485
- removeAllListeners(event?: string): this;
523
+ allowedLookupCollections?: string[];
524
+ /** Allowed fields for filtering. If set, ignores unknown fields. */
525
+ allowedFilterFields?: string[];
526
+ /** Allowed fields for sorting. If set, ignores unknown fields. */
527
+ allowedSortFields?: string[];
486
528
  /**
487
- * Emit event (sync - for backwards compatibility)
529
+ * Whitelist of allowed filter operators.
530
+ * When set, only these operators can be used in filters.
531
+ * When undefined, all built-in operators are allowed.
532
+ * Values are human-readable keys: 'eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin',
533
+ * 'like', 'contains', 'regex', 'exists', 'size', 'type'
534
+ * @example ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in']
488
535
  */
489
- emit(event: string, data: unknown): void;
536
+ allowedOperators?: string[];
537
+ }
538
+ /**
539
+ * Modern Query Parser
540
+ * Converts URL parameters to MongoDB queries with $lookup support
541
+ */
542
+ declare class QueryParser {
543
+ private readonly options;
544
+ private readonly operators;
545
+ private readonly dangerousOperators;
490
546
  /**
491
- * Emit event and await all async handlers (sorted by priority)
547
+ * Regex patterns that can cause catastrophic backtracking (ReDoS attacks)
548
+ * Detects:
549
+ * - Quantifiers: {n,m}
550
+ * - Possessive quantifiers: *+, ++, ?+
551
+ * - Nested quantifiers: (a+)+, (a*)*
552
+ * - Backreferences: \1, \2, etc.
553
+ * - Complex character classes: [...]...[...]
492
554
  */
493
- emitAsync(event: string, data: unknown): Promise<void>;
494
- private _emitHook;
495
- private _emitErrorHook;
555
+ private readonly dangerousRegexPatterns;
556
+ constructor(options?: QueryParserOptions);
496
557
  /**
497
- * Create single document
558
+ * Parse URL query parameters into MongoDB query format
559
+ *
560
+ * @example
561
+ * ```typescript
562
+ * // URL: ?status=active&lookup[department][foreignField]=slug&sort=-createdAt&page=1
563
+ * const query = parser.parse(req.query);
564
+ * // Returns: { filters: {...}, lookups: [...], sort: {...}, page: 1 }
565
+ * ```
498
566
  */
499
- create(data: Record<string, unknown>, options?: {
500
- session?: ClientSession;
501
- }): Promise<TDoc>;
567
+ parse(query: Record<string, unknown> | null | undefined): ParsedQuery;
502
568
  /**
503
- * Create multiple documents
569
+ * Generate OpenAPI-compatible JSON Schema for query parameters.
570
+ * Arc's defineResource() auto-detects this method and uses it
571
+ * to document list endpoint query parameters in OpenAPI/Swagger.
572
+ *
573
+ * The schema respects parser configuration:
574
+ * - `allowedOperators`: only documents allowed operators
575
+ * - `allowedFilterFields`: generates explicit field[op] entries
576
+ * - `enableLookups` / `enableAggregations`: includes/excludes lookup/aggregate params
577
+ * - `maxLimit` / `maxSearchLength`: reflected in schema constraints
504
578
  */
505
- createMany(dataArray: Record<string, unknown>[], options?: {
506
- session?: ClientSession;
507
- ordered?: boolean;
508
- }): Promise<TDoc[]>;
579
+ getQuerySchema(): {
580
+ type: 'object';
581
+ properties: Record<string, unknown>;
582
+ required?: string[];
583
+ };
509
584
  /**
510
- * Get document by ID
585
+ * Get the query schema with OpenAPI extensions (x-internal metadata).
586
+ * Use this when generating OpenAPI/Swagger docs — it includes a documentary
587
+ * `_filterOperators` property describing available filter operators.
588
+ * For validation-only schemas, use `getQuerySchema()` instead.
511
589
  */
512
- getById(id: string | ObjectId, options?: {
513
- select?: SelectSpec;
514
- populate?: PopulateSpec;
515
- populateOptions?: PopulateOptions[];
516
- lean?: boolean;
517
- session?: ClientSession;
518
- throwOnNotFound?: boolean;
519
- skipCache?: boolean;
520
- cacheTtl?: number;
521
- readPreference?: ReadPreferenceType;
522
- }): Promise<TDoc | null>;
590
+ getOpenAPIQuerySchema(): {
591
+ type: 'object';
592
+ properties: Record<string, unknown>;
593
+ };
523
594
  /**
524
- * Get single document by query
595
+ * Get the JSON Schema type for a filter operator
525
596
  */
526
- getByQuery(query: Record<string, unknown>, options?: {
527
- select?: SelectSpec;
528
- populate?: PopulateSpec;
529
- populateOptions?: PopulateOptions[];
530
- lean?: boolean;
531
- session?: ClientSession;
532
- throwOnNotFound?: boolean;
533
- skipCache?: boolean;
534
- cacheTtl?: number;
535
- readPreference?: ReadPreferenceType;
536
- }): Promise<TDoc | null>;
597
+ private _getOperatorSchemaType;
537
598
  /**
538
- * Unified pagination - auto-detects offset vs keyset based on params
539
- *
540
- * Auto-detection logic:
541
- * - If params has 'cursor' or 'after' → uses keyset pagination (stream)
542
- * - If params has 'pagination' or 'page' uses offset pagination (paginate)
543
- * - Else → defaults to offset pagination with page=1
599
+ * Get a human-readable description for a filter operator
600
+ */
601
+ private _getOperatorDescription;
602
+ /**
603
+ * Build a summary description of all available filter operators
604
+ */
605
+ private _buildOperatorDescription;
606
+ /**
607
+ * Parse lookup configurations from URL parameters
544
608
  *
545
- * @example
546
- * // Offset pagination (page-based)
547
- * await repo.getAll({ page: 1, limit: 50, filters: { status: 'active' } });
548
- * await repo.getAll({ pagination: { page: 2, limit: 20 } });
609
+ * Supported formats:
610
+ * 1. Simple: ?lookup[department]=slug
611
+ * Join with 'departments' collection on slug field
549
612
  *
550
- * // Keyset pagination (cursor-based)
551
- * await repo.getAll({ cursor: 'eyJ2Ij...', limit: 50 });
552
- * await repo.getAll({ after: 'eyJ2Ij...', sort: { createdAt: -1 } });
613
+ * 2. Detailed: ?lookup[department][localField]=deptSlug&lookup[department][foreignField]=slug
614
+ * Full control over join configuration
553
615
  *
554
- * // Simple query (defaults to page 1)
555
- * await repo.getAll({ filters: { status: 'active' } });
616
+ * 3. Multiple: ?lookup[department]=slug&lookup[category]=categorySlug
617
+ * Multiple lookups
556
618
  *
557
- * // Skip cache for fresh data
558
- * await repo.getAll({ filters: { status: 'active' } }, { skipCache: true });
559
- */
619
+ * @example
620
+ * ```typescript
621
+ * // URL: ?lookup[department][localField]=deptSlug&lookup[department][foreignField]=slug&lookup[department][single]=true
622
+ * const lookups = parser._parseLookups({
623
+ * department: { localField: 'deptSlug', foreignField: 'slug', single: 'true' }
624
+ * });
625
+ * // Returns: [{ from: 'departments', localField: 'deptSlug', foreignField: 'slug', single: true }]
626
+ * ```
627
+ */
628
+ private _parseLookups;
629
+ /**
630
+ * Parse a single lookup configuration
631
+ */
632
+ private _parseSingleLookup;
633
+ /**
634
+ * Parse aggregation pipeline from URL (advanced feature)
635
+ *
636
+ * @example
637
+ * ```typescript
638
+ * // URL: ?aggregate[group][_id]=$status&aggregate[group][count]=$sum:1
639
+ * const pipeline = parser._parseAggregation({
640
+ * group: { _id: '$status', count: '$sum:1' }
641
+ * });
642
+ * ```
643
+ */
644
+ private _parseAggregation;
645
+ /**
646
+ * Parse select/project fields
647
+ *
648
+ * @example
649
+ * ```typescript
650
+ * // URL: ?select=name,email,-password
651
+ * // Returns: { name: 1, email: 1, password: 0 }
652
+ * ```
653
+ */
654
+ private _parseSelect;
655
+ /**
656
+ * Parse populate parameter - handles both simple string and advanced object format
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * // Simple: ?populate=author,category
661
+ * // Returns: { simplePopulate: 'author,category', populateOptions: undefined }
662
+ *
663
+ * // Advanced: ?populate[author][select]=name,email
664
+ * // Returns: { simplePopulate: undefined, populateOptions: [{ path: 'author', select: 'name email' }] }
665
+ * ```
666
+ */
667
+ private _parsePopulate;
668
+ /**
669
+ * Parse a single populate configuration
670
+ */
671
+ private _parseSinglePopulate;
672
+ /**
673
+ * Convert populate match values (handles boolean strings, etc.)
674
+ */
675
+ private _convertPopulateMatch;
676
+ /**
677
+ * Parse filter parameters
678
+ */
679
+ private _parseFilters;
680
+ /**
681
+ * Handle operator syntax: field[operator]=value
682
+ */
683
+ private _handleOperatorSyntax;
684
+ /**
685
+ * Handle bracket syntax with object value
686
+ */
687
+ private _handleBracketSyntax;
688
+ private _parseSort;
689
+ private _toMongoOperator;
690
+ private _createSafeRegex;
691
+ private _escapeRegex;
692
+ /**
693
+ * Sanitize $match configuration to prevent dangerous operators
694
+ * Recursively filters out operators like $where, $function, $accumulator
695
+ */
696
+ private _sanitizeMatchConfig;
697
+ /**
698
+ * Sanitize pipeline stages for use in $lookup.
699
+ * Blocks dangerous stages ($out, $merge, etc.) and recursively sanitizes
700
+ * operator expressions within $match, $addFields, and $set stages.
701
+ */
702
+ private _sanitizePipeline;
703
+ /**
704
+ * Recursively sanitize expression objects, blocking dangerous operators
705
+ * like $where, $function, $accumulator inside $addFields/$set stages.
706
+ */
707
+ private _sanitizeExpressions;
708
+ private _sanitizeSearch;
709
+ /**
710
+ * Build regex-based multi-field search filters
711
+ * Creates an $or query with case-insensitive regex across all searchFields
712
+ *
713
+ * @example
714
+ * // searchFields: ['name', 'description', 'sku']
715
+ * // search: 'azure'
716
+ * // Returns: [
717
+ * // { name: { $regex: /azure/i } },
718
+ * // { description: { $regex: /azure/i } },
719
+ * // { sku: { $regex: /azure/i } }
720
+ * // ]
721
+ */
722
+ private _buildRegexSearch;
723
+ private _convertValue;
724
+ private _parseOr;
725
+ private _enhanceWithBetween;
726
+ private _pluralize;
727
+ private _capitalize;
728
+ }
729
+ //#endregion
730
+ //#region src/Repository.d.ts
731
+ type HookListener = (data: any) => void | Promise<void>;
732
+ /** Hook with priority for phase ordering */
733
+ interface PrioritizedHook {
734
+ listener: HookListener;
735
+ priority: number;
736
+ }
737
+ /**
738
+ * Plugin phase priorities (lower = runs first)
739
+ * Policy hooks (multi-tenant, soft-delete, validation) MUST run before cache
740
+ * to ensure filters are injected before cache keys are computed.
741
+ */
742
+ declare const HOOK_PRIORITY: {
743
+ /** Policy enforcement: tenant isolation, soft-delete filtering, validation */readonly POLICY: 100; /** Caching: lookup/store after policy filters are applied */
744
+ readonly CACHE: 200; /** Observability: audit logging, metrics, telemetry */
745
+ readonly OBSERVABILITY: 300; /** Default priority for user-registered hooks */
746
+ readonly DEFAULT: 500;
747
+ };
748
+ /**
749
+ * Production-grade repository for MongoDB
750
+ * Event-driven, plugin-based, with smart pagination
751
+ */
752
+ declare class Repository<TDoc = any> {
753
+ readonly Model: Model<TDoc>;
754
+ readonly model: string;
755
+ readonly _hooks: Map<string, PrioritizedHook[]>;
756
+ readonly _pagination: PaginationEngine<TDoc>;
757
+ private readonly _hookMode;
758
+ [key: string]: unknown;
759
+ private _hasTextIndex;
760
+ constructor(Model: Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions);
761
+ /**
762
+ * Register a plugin
763
+ */
764
+ use(plugin: PluginType): this;
765
+ /**
766
+ * Register event listener with optional priority for phase ordering.
767
+ *
768
+ * @param event - Event name (e.g. 'before:getAll')
769
+ * @param listener - Hook function
770
+ * @param options - Optional { priority } — use HOOK_PRIORITY constants.
771
+ * Lower priority numbers run first.
772
+ * Default: HOOK_PRIORITY.DEFAULT (500)
773
+ */
774
+ on(event: string, listener: HookListener, options?: {
775
+ priority?: number;
776
+ }): this;
777
+ /**
778
+ * Remove a specific event listener
779
+ */
780
+ off(event: string, listener: HookListener): this;
781
+ /**
782
+ * Remove all listeners for an event, or all listeners entirely
783
+ */
784
+ removeAllListeners(event?: string): this;
785
+ /**
786
+ * Emit event (sync - for backwards compatibility)
787
+ */
788
+ emit(event: string, data: unknown): void;
789
+ /**
790
+ * Emit event and await all async handlers (sorted by priority)
791
+ */
792
+ emitAsync(event: string, data: unknown): Promise<void>;
793
+ private _emitHook;
794
+ private _emitErrorHook;
795
+ /**
796
+ * Create single document
797
+ */
798
+ create(data: Record<string, unknown>, options?: {
799
+ session?: ClientSession;
800
+ }): Promise<TDoc>;
801
+ /**
802
+ * Create multiple documents
803
+ */
804
+ createMany(dataArray: Record<string, unknown>[], options?: {
805
+ session?: ClientSession;
806
+ ordered?: boolean;
807
+ }): Promise<TDoc[]>;
808
+ /**
809
+ * Get document by ID
810
+ */
811
+ getById(id: string | ObjectId, options?: {
812
+ select?: SelectSpec;
813
+ populate?: PopulateSpec;
814
+ populateOptions?: PopulateOptions[];
815
+ lean?: boolean;
816
+ session?: ClientSession;
817
+ throwOnNotFound?: boolean;
818
+ skipCache?: boolean;
819
+ cacheTtl?: number;
820
+ readPreference?: ReadPreferenceType;
821
+ }): Promise<TDoc | null>;
822
+ /**
823
+ * Get single document by query
824
+ */
825
+ getByQuery(query: Record<string, unknown>, options?: {
826
+ select?: SelectSpec;
827
+ populate?: PopulateSpec;
828
+ populateOptions?: PopulateOptions[];
829
+ lean?: boolean;
830
+ session?: ClientSession;
831
+ throwOnNotFound?: boolean;
832
+ skipCache?: boolean;
833
+ cacheTtl?: number;
834
+ readPreference?: ReadPreferenceType;
835
+ }): Promise<TDoc | null>;
836
+ /**
837
+ * Unified pagination - auto-detects offset vs keyset based on params
838
+ *
839
+ * Auto-detection logic:
840
+ * - If params has 'cursor' or 'after' → uses keyset pagination (stream)
841
+ * - If params has 'pagination' or 'page' → uses offset pagination (paginate)
842
+ * - Else → defaults to offset pagination with page=1
843
+ *
844
+ * @example
845
+ * // Offset pagination (page-based)
846
+ * await repo.getAll({ page: 1, limit: 50, filters: { status: 'active' } });
847
+ * await repo.getAll({ pagination: { page: 2, limit: 20 } });
848
+ *
849
+ * // Keyset pagination (cursor-based)
850
+ * await repo.getAll({ cursor: 'eyJ2Ij...', limit: 50 });
851
+ * await repo.getAll({ after: 'eyJ2Ij...', sort: { createdAt: -1 } });
852
+ *
853
+ * // Simple query (defaults to page 1)
854
+ * await repo.getAll({ filters: { status: 'active' } });
855
+ *
856
+ * // Skip cache for fresh data
857
+ * await repo.getAll({ filters: { status: 'active' } }, { skipCache: true });
858
+ */
560
859
  getAll(params?: {
561
860
  filters?: Record<string, unknown>;
562
861
  sort?: SortSpec | string;
@@ -569,12 +868,13 @@ declare class Repository<TDoc = any> {
569
868
  };
570
869
  limit?: number;
571
870
  search?: string;
572
- mode?: "offset" | "keyset";
871
+ mode?: 'offset' | 'keyset';
573
872
  hint?: string | Record<string, 1 | -1>;
574
873
  maxTimeMS?: number;
575
- countStrategy?: "exact" | "estimated" | "none";
874
+ countStrategy?: 'exact' | 'estimated' | 'none';
576
875
  readPreference?: ReadPreferenceType; /** Advanced populate options (from QueryParser or Arc's BaseController) */
577
- populateOptions?: PopulateOptions[];
876
+ populateOptions?: PopulateOptions[]; /** Lookup configurations for $lookup joins (from QueryParser or manual) */
877
+ lookups?: LookupOptions[];
578
878
  }, options?: {
579
879
  select?: SelectSpec;
580
880
  populate?: PopulateSpec;
@@ -602,483 +902,193 @@ declare class Repository<TDoc = any> {
602
902
  }): Promise<number>;
603
903
  /**
604
904
  * Check if document exists
605
- * Routes through hook system for policy enforcement (multi-tenant, soft-delete)
606
- */
607
- exists(query: Record<string, unknown>, options?: {
608
- session?: ClientSession;
609
- readPreference?: ReadPreferenceType;
610
- }): Promise<{
611
- _id: unknown;
612
- } | null>;
613
- /**
614
- * Update document by ID
615
- */
616
- update(id: string | ObjectId, data: Record<string, unknown>, options?: UpdateOptions): Promise<TDoc>;
617
- /**
618
- * Delete document by ID
619
- */
620
- delete(id: string | ObjectId, options?: {
621
- session?: ClientSession;
622
- }): Promise<DeleteResult>;
623
- /**
624
- * Execute aggregation pipeline
625
- * Routes through hook system for policy enforcement (multi-tenant, soft-delete)
626
- *
627
- * @param pipeline - Aggregation pipeline stages
628
- * @param options - Aggregation options including governance controls
629
- */
630
- aggregate<TResult = unknown>(pipeline: PipelineStage[], options?: {
631
- session?: ClientSession;
632
- allowDiskUse?: boolean;
633
- comment?: string;
634
- readPreference?: ReadPreferenceType;
635
- maxTimeMS?: number;
636
- readConcern?: {
637
- level: string;
638
- };
639
- collation?: Record<string, unknown>;
640
- maxPipelineStages?: number;
641
- }): Promise<TResult[]>;
642
- /**
643
- * Aggregate pipeline with pagination
644
- * Best for: Complex queries, grouping, joins
645
- *
646
- * Policy hooks (multi-tenant, soft-delete) inject context.filters which are
647
- * prepended as a $match stage to the pipeline, ensuring tenant isolation.
648
- */
649
- aggregatePaginate(options?: AggregatePaginationOptions): Promise<AggregatePaginationResult<TDoc>>;
650
- /**
651
- * Get distinct values
652
- * Routes through hook system for policy enforcement (multi-tenant, soft-delete)
653
- */
654
- distinct<T = unknown>(field: string, query?: Record<string, unknown>, options?: {
655
- session?: ClientSession;
656
- readPreference?: ReadPreferenceType;
657
- }): Promise<T[]>;
658
- /**
659
- * Query with custom field lookups ($lookup)
660
- * Best for: Joins on slugs, SKUs, codes, or other indexed custom fields
661
- *
662
- * @example
663
- * ```typescript
664
- * // Join employees with departments using slug instead of ObjectId
665
- * const employees = await employeeRepo.lookupPopulate({
666
- * filters: { status: 'active' },
667
- * lookups: [
668
- * {
669
- * from: 'departments',
670
- * localField: 'departmentSlug',
671
- * foreignField: 'slug',
672
- * as: 'department',
673
- * single: true
674
- * }
675
- * ],
676
- * sort: '-createdAt',
677
- * page: 1,
678
- * limit: 50
679
- * });
680
- * ```
681
- */
682
- lookupPopulate(options: {
683
- filters?: Record<string, unknown>;
684
- lookups: LookupOptions[];
685
- sort?: SortSpec | string;
686
- page?: number;
687
- limit?: number;
688
- select?: SelectSpec;
689
- session?: ClientSession;
690
- readPreference?: ReadPreferenceType;
691
- }): Promise<{
692
- data: TDoc[];
693
- total?: number;
694
- page?: number;
695
- limit?: number;
696
- }>;
697
- /**
698
- * Create an aggregation builder for this model
699
- * Useful for building complex custom aggregations
700
- *
701
- * @example
702
- * ```typescript
703
- * const pipeline = repo.buildAggregation()
704
- * .match({ status: 'active' })
705
- * .lookup('departments', 'deptSlug', 'slug', 'department', true)
706
- * .group({ _id: '$department', count: { $sum: 1 } })
707
- * .sort({ count: -1 })
708
- * .build();
709
- *
710
- * const results = await repo.Model.aggregate(pipeline);
711
- * ```
712
- */
713
- buildAggregation(): AggregationBuilder;
714
- /**
715
- * Create a lookup builder
716
- * Useful for building $lookup stages independently
717
- *
718
- * @example
719
- * ```typescript
720
- * const lookupStages = repo.buildLookup('departments')
721
- * .localField('deptSlug')
722
- * .foreignField('slug')
723
- * .as('department')
724
- * .single()
725
- * .build();
726
- *
727
- * const pipeline = [
728
- * { $match: { status: 'active' } },
729
- * ...lookupStages
730
- * ];
731
- * ```
732
- */
733
- buildLookup(from?: string): LookupBuilder;
734
- /**
735
- * Execute callback within a transaction with automatic retry on transient failures.
736
- *
737
- * Uses the MongoDB driver's `session.withTransaction()` which automatically retries
738
- * on `TransientTransactionError` and `UnknownTransactionCommitResult`.
739
- *
740
- * The callback always receives a `ClientSession`. When `allowFallback` is true
741
- * and the MongoDB deployment doesn't support transactions (e.g., standalone),
742
- * the callback runs without a transaction on the same session.
743
- *
744
- * @param callback - Receives a `ClientSession` to pass to repository operations
745
- * @param options.allowFallback - Run without transaction on standalone MongoDB (default: false)
746
- * @param options.onFallback - Called when falling back to non-transactional execution
747
- * @param options.transactionOptions - MongoDB driver transaction options (readConcern, writeConcern, etc.)
748
- *
749
- * @example
750
- * ```typescript
751
- * const result = await repo.withTransaction(async (session) => {
752
- * const order = await repo.create({ total: 100 }, { session });
753
- * await paymentRepo.create({ orderId: order._id }, { session });
754
- * return order;
755
- * });
756
- *
757
- * // With fallback for standalone/dev environments
758
- * await repo.withTransaction(callback, {
759
- * allowFallback: true,
760
- * onFallback: (err) => logger.warn('Running without transaction', err),
761
- * });
762
- * ```
763
- */
764
- withTransaction<T>(callback: (session: ClientSession) => Promise<T>, options?: WithTransactionOptions): Promise<T>;
765
- private _isTransactionUnsupported;
766
- /**
767
- * Execute custom query with event emission
768
- */
769
- _executeQuery<T>(buildQuery: (Model: Model<TDoc>) => Promise<T>): Promise<T>;
770
- /**
771
- * Build operation context and run before hooks (sorted by priority).
772
- *
773
- * Hook execution order is deterministic:
774
- * 1. POLICY (100) — tenant isolation, soft-delete filtering, validation
775
- * 2. CACHE (200) — cache lookup (after policy filters are injected)
776
- * 3. OBSERVABILITY (300) — audit logging, metrics
777
- * 4. DEFAULT (500) — user-registered hooks
778
- */
779
- _buildContext(operation: string, options: Record<string, unknown>): Promise<RepositoryContext>;
780
- /**
781
- * Parse sort string or object
782
- */
783
- _parseSort(sort: SortSpec | string | undefined): SortSpec;
784
- /**
785
- * Parse populate specification
786
- */
787
- _parsePopulate(populate: PopulateSpec | undefined): string[] | PopulateOptions[];
788
- /**
789
- * Handle errors with proper HTTP status codes
790
- */
791
- _handleError(error: Error): HttpError;
792
- }
793
- //#endregion
794
- //#region src/query/QueryParser.d.ts
795
- type SortSpec$1 = Record<string, 1 | -1>;
796
- type FilterQuery = Record<string, unknown>;
797
- /**
798
- * Mongoose-compatible populate option
799
- * Supports advanced populate with select, match, limit, sort, and nested populate
800
- *
801
- * @example
802
- * ```typescript
803
- * // URL: ?populate[author][select]=name,email&populate[author][match][active]=true
804
- * // Generates: { path: 'author', select: 'name email', match: { active: true } }
805
- * ```
806
- */
807
- interface PopulateOption {
808
- /** Field path to populate */
809
- path: string;
810
- /** Fields to select (space-separated) */
811
- select?: string;
812
- /** Filter conditions for populated documents */
813
- match?: Record<string, unknown>;
814
- /** Query options (limit, sort, skip) */
815
- options?: {
816
- limit?: number;
817
- sort?: SortSpec$1;
818
- skip?: number;
819
- };
820
- /** Nested populate configuration */
821
- populate?: PopulateOption;
822
- }
823
- /** Parsed query result with optional lookup configuration */
824
- interface ParsedQuery {
825
- /** MongoDB filter query */
826
- filters: FilterQuery;
827
- /** Sort specification */
828
- sort?: SortSpec$1;
829
- /** Fields to populate (simple comma-separated string) */
830
- populate?: string;
831
- /**
832
- * Advanced populate options (Mongoose-compatible)
833
- * When this is set, `populate` will be undefined
834
- * @example [{ path: 'author', select: 'name email' }]
835
- */
836
- populateOptions?: PopulateOption[];
837
- /** Page number for offset pagination */
838
- page?: number;
839
- /** Cursor for keyset pagination */
840
- after?: string;
841
- /** Limit */
842
- limit: number;
843
- /** Full-text search query */
844
- search?: string;
845
- /** Lookup configurations for custom field joins */
846
- lookups?: LookupOptions[];
847
- /** Aggregation pipeline stages (advanced) */
848
- aggregation?: PipelineStage[];
849
- /** Select/project fields */
850
- select?: Record<string, 0 | 1>;
851
- }
852
- /** Search mode for query parser */
853
- type SearchMode = "text" | "regex";
854
- interface QueryParserOptions {
855
- /** Maximum allowed regex pattern length (default: 500) */
856
- maxRegexLength?: number;
857
- /** Maximum allowed text search query length (default: 200) */
858
- maxSearchLength?: number;
859
- /** Maximum allowed filter depth (default: 10) */
860
- maxFilterDepth?: number;
861
- /** Maximum allowed limit value (default: 1000) */
862
- maxLimit?: number;
863
- /** Additional operators to block */
864
- additionalDangerousOperators?: string[];
865
- /** Enable lookup parsing (default: true) */
866
- enableLookups?: boolean;
867
- /** Enable aggregation parsing (default: false - requires explicit opt-in) */
868
- enableAggregations?: boolean;
869
- /**
870
- * Search mode (default: 'text')
871
- * - 'text': Uses MongoDB $text search (requires text index)
872
- * - 'regex': Uses $or with $regex across searchFields (no index required)
873
- */
874
- searchMode?: SearchMode;
875
- /**
876
- * Fields to search when searchMode is 'regex'
877
- * Required when searchMode is 'regex'
878
- * @example ['name', 'description', 'sku', 'tags']
879
- */
880
- searchFields?: string[];
881
- /**
882
- * Whitelist of collection names allowed in lookups.
883
- * When set, only these collections can be used in $lookup stages.
884
- * When undefined, all collection names are allowed.
885
- * @example ['departments', 'categories', 'users']
886
- */
887
- allowedLookupCollections?: string[];
888
- /** Allowed fields for filtering. If set, ignores unknown fields. */
889
- allowedFilterFields?: string[];
890
- /** Allowed fields for sorting. If set, ignores unknown fields. */
891
- allowedSortFields?: string[];
892
- /**
893
- * Whitelist of allowed filter operators.
894
- * When set, only these operators can be used in filters.
895
- * When undefined, all built-in operators are allowed.
896
- * Values are human-readable keys: 'eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin',
897
- * 'like', 'contains', 'regex', 'exists', 'size', 'type'
898
- * @example ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in']
899
- */
900
- allowedOperators?: string[];
901
- }
902
- /**
903
- * Modern Query Parser
904
- * Converts URL parameters to MongoDB queries with $lookup support
905
- */
906
- declare class QueryParser {
907
- private readonly options;
908
- private readonly operators;
909
- private readonly dangerousOperators;
910
- /**
911
- * Regex patterns that can cause catastrophic backtracking (ReDoS attacks)
912
- * Detects:
913
- * - Quantifiers: {n,m}
914
- * - Possessive quantifiers: *+, ++, ?+
915
- * - Nested quantifiers: (a+)+, (a*)*
916
- * - Backreferences: \1, \2, etc.
917
- * - Complex character classes: [...]...[...]
905
+ * Routes through hook system for policy enforcement (multi-tenant, soft-delete)
918
906
  */
919
- private readonly dangerousRegexPatterns;
920
- constructor(options?: QueryParserOptions);
907
+ exists(query: Record<string, unknown>, options?: {
908
+ session?: ClientSession;
909
+ readPreference?: ReadPreferenceType;
910
+ }): Promise<{
911
+ _id: unknown;
912
+ } | null>;
921
913
  /**
922
- * Parse URL query parameters into MongoDB query format
923
- *
924
- * @example
925
- * ```typescript
926
- * // URL: ?status=active&lookup[department][foreignField]=slug&sort=-createdAt&page=1
927
- * const query = parser.parse(req.query);
928
- * // Returns: { filters: {...}, lookups: [...], sort: {...}, page: 1 }
929
- * ```
914
+ * Update document by ID
930
915
  */
931
- parse(query: Record<string, unknown> | null | undefined): ParsedQuery;
916
+ update(id: string | ObjectId, data: Record<string, unknown>, options?: UpdateOptions): Promise<TDoc>;
932
917
  /**
933
- * Generate OpenAPI-compatible JSON Schema for query parameters.
934
- * Arc's defineResource() auto-detects this method and uses it
935
- * to document list endpoint query parameters in OpenAPI/Swagger.
936
- *
937
- * The schema respects parser configuration:
938
- * - `allowedOperators`: only documents allowed operators
939
- * - `allowedFilterFields`: generates explicit field[op] entries
940
- * - `enableLookups` / `enableAggregations`: includes/excludes lookup/aggregate params
941
- * - `maxLimit` / `maxSearchLength`: reflected in schema constraints
918
+ * Delete document by ID
942
919
  */
943
- getQuerySchema(): {
944
- type: "object";
945
- properties: Record<string, unknown>;
946
- required?: string[];
947
- };
920
+ delete(id: string | ObjectId, options?: {
921
+ session?: ClientSession;
922
+ }): Promise<DeleteResult>;
948
923
  /**
949
- * Get the JSON Schema type for a filter operator
924
+ * Execute aggregation pipeline
925
+ * Routes through hook system for policy enforcement (multi-tenant, soft-delete)
926
+ *
927
+ * @param pipeline - Aggregation pipeline stages
928
+ * @param options - Aggregation options including governance controls
950
929
  */
951
- private _getOperatorSchemaType;
930
+ aggregate<TResult = unknown>(pipeline: PipelineStage[], options?: {
931
+ session?: ClientSession;
932
+ allowDiskUse?: boolean;
933
+ comment?: string;
934
+ readPreference?: ReadPreferenceType;
935
+ maxTimeMS?: number;
936
+ readConcern?: {
937
+ level: string;
938
+ };
939
+ collation?: Record<string, unknown>;
940
+ maxPipelineStages?: number;
941
+ }): Promise<TResult[]>;
952
942
  /**
953
- * Get a human-readable description for a filter operator
943
+ * Aggregate pipeline with pagination
944
+ * Best for: Complex queries, grouping, joins
945
+ *
946
+ * Policy hooks (multi-tenant, soft-delete) inject context.filters which are
947
+ * prepended as a $match stage to the pipeline, ensuring tenant isolation.
954
948
  */
955
- private _getOperatorDescription;
949
+ aggregatePaginate(options?: AggregatePaginationOptions): Promise<AggregatePaginationResult<TDoc>>;
956
950
  /**
957
- * Build a summary description of all available filter operators
951
+ * Get distinct values
952
+ * Routes through hook system for policy enforcement (multi-tenant, soft-delete)
958
953
  */
959
- private _buildOperatorDescription;
954
+ distinct<T = unknown>(field: string, query?: Record<string, unknown>, options?: {
955
+ session?: ClientSession;
956
+ readPreference?: ReadPreferenceType;
957
+ }): Promise<T[]>;
960
958
  /**
961
- * Parse lookup configurations from URL parameters
962
- *
963
- * Supported formats:
964
- * 1. Simple: ?lookup[department]=slug
965
- * → Join with 'departments' collection on slug field
966
- *
967
- * 2. Detailed: ?lookup[department][localField]=deptSlug&lookup[department][foreignField]=slug
968
- * → Full control over join configuration
969
- *
970
- * 3. Multiple: ?lookup[department]=slug&lookup[category]=categorySlug
971
- * → Multiple lookups
959
+ * Query with custom field lookups ($lookup)
960
+ * Best for: Joins on slugs, SKUs, codes, or other indexed custom fields
972
961
  *
973
962
  * @example
974
963
  * ```typescript
975
- * // URL: ?lookup[department][localField]=deptSlug&lookup[department][foreignField]=slug&lookup[department][single]=true
976
- * const lookups = parser._parseLookups({
977
- * department: { localField: 'deptSlug', foreignField: 'slug', single: 'true' }
964
+ * // Join employees with departments using slug instead of ObjectId
965
+ * const employees = await employeeRepo.lookupPopulate({
966
+ * filters: { status: 'active' },
967
+ * lookups: [
968
+ * {
969
+ * from: 'departments',
970
+ * localField: 'departmentSlug',
971
+ * foreignField: 'slug',
972
+ * as: 'department',
973
+ * single: true
974
+ * }
975
+ * ],
976
+ * sort: '-createdAt',
977
+ * page: 1,
978
+ * limit: 50
978
979
  * });
979
- * // Returns: [{ from: 'departments', localField: 'deptSlug', foreignField: 'slug', single: true }]
980
980
  * ```
981
981
  */
982
- private _parseLookups;
983
- /**
984
- * Parse a single lookup configuration
985
- */
986
- private _parseSingleLookup;
982
+ lookupPopulate(options: {
983
+ filters?: Record<string, unknown>;
984
+ lookups: LookupOptions[];
985
+ sort?: SortSpec | string;
986
+ page?: number;
987
+ limit?: number;
988
+ select?: SelectSpec;
989
+ session?: ClientSession;
990
+ readPreference?: ReadPreferenceType;
991
+ }): Promise<{
992
+ data: TDoc[];
993
+ total?: number;
994
+ page?: number;
995
+ limit?: number;
996
+ }>;
987
997
  /**
988
- * Parse aggregation pipeline from URL (advanced feature)
998
+ * Create an aggregation builder for this model
999
+ * Useful for building complex custom aggregations
989
1000
  *
990
1001
  * @example
991
1002
  * ```typescript
992
- * // URL: ?aggregate[group][_id]=$status&aggregate[group][count]=$sum:1
993
- * const pipeline = parser._parseAggregation({
994
- * group: { _id: '$status', count: '$sum:1' }
995
- * });
1003
+ * const pipeline = repo.buildAggregation()
1004
+ * .match({ status: 'active' })
1005
+ * .lookup('departments', 'deptSlug', 'slug', 'department', true)
1006
+ * .group({ _id: '$department', count: { $sum: 1 } })
1007
+ * .sort({ count: -1 })
1008
+ * .build();
1009
+ *
1010
+ * const results = await repo.Model.aggregate(pipeline);
996
1011
  * ```
997
1012
  */
998
- private _parseAggregation;
1013
+ buildAggregation(): AggregationBuilder;
999
1014
  /**
1000
- * Parse select/project fields
1015
+ * Create a lookup builder
1016
+ * Useful for building $lookup stages independently
1001
1017
  *
1002
1018
  * @example
1003
1019
  * ```typescript
1004
- * // URL: ?select=name,email,-password
1005
- * // Returns: { name: 1, email: 1, password: 0 }
1020
+ * const lookupStages = repo.buildLookup('departments')
1021
+ * .localField('deptSlug')
1022
+ * .foreignField('slug')
1023
+ * .as('department')
1024
+ * .single()
1025
+ * .build();
1026
+ *
1027
+ * const pipeline = [
1028
+ * { $match: { status: 'active' } },
1029
+ * ...lookupStages
1030
+ * ];
1006
1031
  * ```
1007
1032
  */
1008
- private _parseSelect;
1033
+ buildLookup(from?: string): LookupBuilder;
1009
1034
  /**
1010
- * Parse populate parameter - handles both simple string and advanced object format
1035
+ * Execute callback within a transaction with automatic retry on transient failures.
1036
+ *
1037
+ * Uses the MongoDB driver's `session.withTransaction()` which automatically retries
1038
+ * on `TransientTransactionError` and `UnknownTransactionCommitResult`.
1039
+ *
1040
+ * The callback always receives a `ClientSession`. When `allowFallback` is true
1041
+ * and the MongoDB deployment doesn't support transactions (e.g., standalone),
1042
+ * the callback runs without a transaction on the same session.
1043
+ *
1044
+ * @param callback - Receives a `ClientSession` to pass to repository operations
1045
+ * @param options.allowFallback - Run without transaction on standalone MongoDB (default: false)
1046
+ * @param options.onFallback - Called when falling back to non-transactional execution
1047
+ * @param options.transactionOptions - MongoDB driver transaction options (readConcern, writeConcern, etc.)
1011
1048
  *
1012
1049
  * @example
1013
1050
  * ```typescript
1014
- * // Simple: ?populate=author,category
1015
- * // Returns: { simplePopulate: 'author,category', populateOptions: undefined }
1051
+ * const result = await repo.withTransaction(async (session) => {
1052
+ * const order = await repo.create({ total: 100 }, { session });
1053
+ * await paymentRepo.create({ orderId: order._id }, { session });
1054
+ * return order;
1055
+ * });
1016
1056
  *
1017
- * // Advanced: ?populate[author][select]=name,email
1018
- * // Returns: { simplePopulate: undefined, populateOptions: [{ path: 'author', select: 'name email' }] }
1057
+ * // With fallback for standalone/dev environments
1058
+ * await repo.withTransaction(callback, {
1059
+ * allowFallback: true,
1060
+ * onFallback: (err) => logger.warn('Running without transaction', err),
1061
+ * });
1019
1062
  * ```
1020
1063
  */
1021
- private _parsePopulate;
1022
- /**
1023
- * Parse a single populate configuration
1024
- */
1025
- private _parseSinglePopulate;
1026
- /**
1027
- * Convert populate match values (handles boolean strings, etc.)
1028
- */
1029
- private _convertPopulateMatch;
1030
- /**
1031
- * Parse filter parameters
1032
- */
1033
- private _parseFilters;
1034
- /**
1035
- * Handle operator syntax: field[operator]=value
1036
- */
1037
- private _handleOperatorSyntax;
1064
+ withTransaction<T>(callback: (session: ClientSession) => Promise<T>, options?: WithTransactionOptions): Promise<T>;
1065
+ private _isTransactionUnsupported;
1038
1066
  /**
1039
- * Handle bracket syntax with object value
1067
+ * Execute custom query with event emission
1040
1068
  */
1041
- private _handleBracketSyntax;
1042
- private _parseSort;
1043
- private _toMongoOperator;
1044
- private _createSafeRegex;
1045
- private _escapeRegex;
1069
+ _executeQuery<T>(buildQuery: (Model: Model<TDoc>) => Promise<T>): Promise<T>;
1046
1070
  /**
1047
- * Sanitize $match configuration to prevent dangerous operators
1048
- * Recursively filters out operators like $where, $function, $accumulator
1071
+ * Build operation context and run before hooks (sorted by priority).
1072
+ *
1073
+ * Hook execution order is deterministic:
1074
+ * 1. POLICY (100) — tenant isolation, soft-delete filtering, validation
1075
+ * 2. CACHE (200) — cache lookup (after policy filters are injected)
1076
+ * 3. OBSERVABILITY (300) — audit logging, metrics
1077
+ * 4. DEFAULT (500) — user-registered hooks
1049
1078
  */
1050
- private _sanitizeMatchConfig;
1079
+ _buildContext(operation: string, options: Record<string, unknown>): Promise<RepositoryContext>;
1051
1080
  /**
1052
- * Sanitize pipeline stages for use in $lookup.
1053
- * Blocks dangerous stages ($out, $merge, etc.) and recursively sanitizes
1054
- * operator expressions within $match, $addFields, and $set stages.
1081
+ * Parse sort string or object
1055
1082
  */
1056
- private _sanitizePipeline;
1083
+ _parseSort(sort: SortSpec | string | undefined): SortSpec;
1057
1084
  /**
1058
- * Recursively sanitize expression objects, blocking dangerous operators
1059
- * like $where, $function, $accumulator inside $addFields/$set stages.
1085
+ * Parse populate specification
1060
1086
  */
1061
- private _sanitizeExpressions;
1062
- private _sanitizeSearch;
1087
+ _parsePopulate(populate: PopulateSpec | undefined): string[] | PopulateOptions[];
1063
1088
  /**
1064
- * Build regex-based multi-field search filters
1065
- * Creates an $or query with case-insensitive regex across all searchFields
1066
- *
1067
- * @example
1068
- * // searchFields: ['name', 'description', 'sku']
1069
- * // search: 'azure'
1070
- * // Returns: [
1071
- * // { name: { $regex: /azure/i } },
1072
- * // { description: { $regex: /azure/i } },
1073
- * // { sku: { $regex: /azure/i } }
1074
- * // ]
1089
+ * Handle errors with proper HTTP status codes
1075
1090
  */
1076
- private _buildRegexSearch;
1077
- private _convertValue;
1078
- private _parseOr;
1079
- private _enhanceWithBetween;
1080
- private _pluralize;
1081
- private _capitalize;
1091
+ _handleError(error: Error): HttpError;
1082
1092
  }
1083
1093
  //#endregion
1084
1094
  //#region src/index.d.ts
@@ -1092,6 +1102,6 @@ declare class QueryParser {
1092
1102
  * @example
1093
1103
  * const userRepo = createRepository(UserModel, [timestampPlugin()]);
1094
1104
  */
1095
- declare function createRepository<TDoc>(Model: mongoose$1.Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions): Repository<TDoc>;
1105
+ declare function createRepository<TDoc>(Model: _$mongoose.Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions): Repository<TDoc>;
1096
1106
  //#endregion
1097
- export { type AggregateHelpersMethods, type AggregatePaginationOptions, type AggregatePaginationResult, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type BulkWriteResult, type CacheAdapter, type CacheMethods, type CacheOperationOptions, type CacheOptions, type CacheStats, type CascadeOptions, type CascadeRelation, type CreateInput, type CreateOptions, type CrudSchemas, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldRules, type FilterQuery, type GroupResult, HOOK_PRIORITY, type HookMode, type HttpError, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type JsonSchema, type KeysOfType, type KeysetPaginationOptions, type KeysetPaginationResult, type Logger, LookupBuilder, type LookupOptions, type MinMaxResult, type MongoOperationsMethods, type MultiTenantOptions, type NonNullableFields, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OffsetPaginationResult, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type PaginationResult, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, QueryParser, type QueryParserOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaBuilderOptions, type SearchMode, type SelectSpec, type SequentialIdOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type UpdateInput, type UpdateManyResult, type UpdateOptions, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidationResult, type ValidatorDefinition, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };
1107
+ export { type AggregateHelpersMethods, type AggregatePaginationOptions, type AggregatePaginationResult, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type BulkWriteResult, type CacheAdapter, type CacheMethods, type CacheOperationOptions, type CacheOptions, type CacheStats, type CascadeOptions, type CascadeRelation, type CreateInput, type CreateOptions, type CrudSchemas, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldRules, type FilterQuery, type GroupResult, HOOK_PRIORITY, type HookMode, type HttpError, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type JsonSchema, type KeysOfType, type KeysetPaginationOptions, type KeysetPaginationResult, type Logger, LookupBuilder, type LookupOptions, type MinMaxResult, type MongoOperationsMethods, type MultiTenantOptions, type NonNullableFields, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OffsetPaginationResult, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type PaginationResult, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, QueryParser, type QueryParserOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaBuilderOptions, type SearchMode, type SelectSpec, type SequentialIdOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type UpdateInput, type UpdateManyResult, type UpdateOptions, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidationResult, type ValidatorDefinition, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, parseDuplicateKeyError, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };