@common-stack/store-mongo 7.1.1-alpha.8 → 7.1.1-alpha.9

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.
@@ -0,0 +1,460 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /**
3
+ * @file IBaseServiceMixin.ts
4
+ * @description Defines the interface and types for the BaseServiceMixin functionality.
5
+ * This file documents the contract and behavior of the BaseServiceMixin without implementation details.
6
+ *
7
+ * The BaseServiceMixin provides a way to add standardized service methods to Moleculer service schemas.
8
+ * It implements the IBaseService interface and connects it to Moleculer's service framework,
9
+ * creating action handlers for all standard service operations.
10
+ *
11
+ * Architecture Context:
12
+ * - Part of the service layer in a microservices architecture
13
+ * - Bridges domain services with the Moleculer microservice framework
14
+ * - Follows a consistent pattern for exposing domain operations as microservice actions
15
+ * - Implements an event-driven architecture by emitting events on data changes
16
+ * - Provides automatic parameter validation through Moleculer's built-in validator
17
+ *
18
+ * Key features:
19
+ * - Automatic action creation for all IBaseService methods
20
+ * - Event emission for data changes (create, update, delete)
21
+ * - Parameter validation for all actions
22
+ * - Consistent error handling
23
+ * - Type safety through generics
24
+ *
25
+ * Usage Patterns:
26
+ * - Include this mixin in any Moleculer service that needs standard CRUD operations
27
+ * - Customize event emission by providing a custom events array
28
+ * - Extend with additional service-specific actions
29
+ * - Use with any implementation of IBaseService (typically a concrete service class)
30
+ *
31
+ * @see IBaseService - The interface this mixin implements
32
+ * @see IBaseMongoRepository - The repository interface used for data access
33
+ * @see BaseServiceCommands - Enum of standard service commands used as action names
34
+ */
35
+
36
+ import type { ServiceBroker, ServiceSchema } from 'moleculer';
37
+ import { BaseServiceCommands } from 'common';
38
+ import { IBaseService } from './IBaseService';
39
+
40
+
41
+ /**
42
+ * Interface for the BaseServiceMixin function
43
+ * This function creates a Moleculer service mixin that implements the IBaseService interface
44
+ *
45
+ * @param service - Implementation of IBaseService that will handle the actual business logic
46
+ * @param broker - Moleculer service broker for event emission
47
+ * @param name - Service name used for event naming
48
+ * @param events - List of operations that should emit events
49
+ * @returns A partial ServiceSchema that can be merged with other service definitions
50
+ *
51
+ * @example
52
+ * // Create a user service with the base service mixin
53
+ * const UserService: ServiceSchema = {
54
+ * name: "users",
55
+ * mixins: [
56
+ * BaseServiceMixin(userService, broker, "users")
57
+ * ],
58
+ * // Additional service-specific actions and methods
59
+ * actions: {
60
+ * changePassword: { ... }
61
+ * }
62
+ * };
63
+ *
64
+ * // Example of customizing event emission
65
+ * const ProductService: ServiceSchema = {
66
+ * name: "products",
67
+ * mixins: [
68
+ * // Only emit events for create and update operations
69
+ * BaseServiceMixin(
70
+ * productService,
71
+ * broker,
72
+ * "products",
73
+ * [BaseServiceCommands.Create, BaseServiceCommands.Update]
74
+ * )
75
+ * ]
76
+ * };
77
+ */
78
+ export interface IBaseServiceMixinFunction {
79
+ <T>(
80
+ service: IBaseService<T>,
81
+ broker?: ServiceBroker,
82
+ name?: string,
83
+ events?: BaseServiceCommands[],
84
+ ): Partial<ServiceSchema>;
85
+ }
86
+
87
+ /**
88
+ * Interface describing the actions created by the BaseServiceMixin
89
+ * Each action corresponds to a method in the IBaseService interface
90
+ */
91
+ export interface IBaseServiceMixinActions {
92
+ /**
93
+ * Get a single document by ID
94
+ *
95
+ * This action retrieves a single entity by its unique identifier.
96
+ * It maps to the IBaseService.get method and handles parameter validation.
97
+ *
98
+ * @param id - The unique identifier of the document to retrieve
99
+ * @returns The domain entity or null if not found
100
+ *
101
+ * @example
102
+ * // Call from another service
103
+ * const user = await broker.call('users.get', { id: '123' });
104
+ *
105
+ * // Call from API
106
+ * app.get('/api/users/:id', async (req, res) => {
107
+ * const user = await broker.call('users.get', { id: req.params.id });
108
+ * res.json(user);
109
+ * });
110
+ */
111
+ [BaseServiceCommands.Get]: {
112
+ params: {
113
+ id: string;
114
+ };
115
+ };
116
+
117
+ /**
118
+ * Count documents matching criteria
119
+ *
120
+ * This action counts entities matching the provided filter criteria.
121
+ * It maps to the IBaseService.count method and handles parameter validation.
122
+ *
123
+ * @param criteria - Optional filter criteria to count specific documents
124
+ * @returns The count of matching documents
125
+ *
126
+ * @example
127
+ * // Count active users
128
+ * const count = await broker.call('users.count', {
129
+ * criteria: { active: true }
130
+ * });
131
+ */
132
+ [BaseServiceCommands.Count]: {
133
+ params: {
134
+ criteria?: Record<string, any>;
135
+ };
136
+ };
137
+
138
+ /**
139
+ * Create multiple documents
140
+ *
141
+ * This action creates multiple entities in a single operation.
142
+ * It maps to the IBaseService.bulkCreate method and handles parameter validation.
143
+ * It also emits events for the created entities if configured.
144
+ *
145
+ * @param data - Array of data objects for the new documents
146
+ * @returns Array of created domain entities
147
+ *
148
+ * @example
149
+ * // Create multiple users
150
+ * const users = await broker.call('users.bulkCreate', {
151
+ * data: [
152
+ * { name: 'John', email: 'john@example.com' },
153
+ * { name: 'Jane', email: 'jane@example.com' }
154
+ * ]
155
+ * });
156
+ */
157
+ [BaseServiceCommands.BulkCreate]: {
158
+ params: {
159
+ data: any[];
160
+ };
161
+ };
162
+
163
+ /**
164
+ * Create a single document
165
+ *
166
+ * This action creates a single entity.
167
+ * It maps to the IBaseService.create method and handles parameter validation.
168
+ * It also emits an event for the created entity if configured.
169
+ *
170
+ * @param data - Data object for the new document
171
+ * @returns The created domain entity
172
+ *
173
+ * @example
174
+ * // Create a new user
175
+ * const user = await broker.call('users.create', {
176
+ * data: { name: 'John', email: 'john@example.com' }
177
+ * });
178
+ */
179
+ [BaseServiceCommands.Create]: {
180
+ params: {
181
+ data: any;
182
+ };
183
+ };
184
+
185
+ /**
186
+ * Delete a document by ID
187
+ *
188
+ * This action deletes a single entity by its unique identifier.
189
+ * It maps to the IBaseService.delete method and handles parameter validation.
190
+ * It also emits an event for the deleted entity if configured.
191
+ *
192
+ * @param id - The unique identifier of the document to delete
193
+ * @returns Boolean indicating success
194
+ *
195
+ * @example
196
+ * // Delete a user
197
+ * const success = await broker.call('users.delete', { id: '123' });
198
+ */
199
+ [BaseServiceCommands.Delete]: {
200
+ params: {
201
+ id: string;
202
+ };
203
+ };
204
+
205
+ /**
206
+ * Get documents with pagination
207
+ *
208
+ * This action retrieves multiple entities with filtering, sorting, and pagination.
209
+ * It maps to the IBaseService.getAll method and handles parameter validation.
210
+ *
211
+ * @param criteria - Optional filter criteria
212
+ * @param sort - Optional sorting configuration
213
+ * @param skip - Optional number of documents to skip (for pagination)
214
+ * @param limit - Optional maximum number of documents to return
215
+ * @param selectedFields - Optional space-separated list of fields to include
216
+ * @returns Array of domain entities
217
+ *
218
+ * @example
219
+ * // Get active users sorted by creation date
220
+ * const users = await broker.call('users.getAll', {
221
+ * criteria: { active: true },
222
+ * sort: { createdAt: -1 },
223
+ * skip: 0,
224
+ * limit: 10
225
+ * });
226
+ */
227
+ [BaseServiceCommands.GetAll]: {
228
+ params: {
229
+ criteria?: Record<string, any>;
230
+ sort?: Record<string, 1 | -1>;
231
+ skip?: number;
232
+ limit?: number;
233
+ selectedFields?: string;
234
+ };
235
+ };
236
+
237
+ /**
238
+ * Get documents with pagination and total count
239
+ *
240
+ * This action retrieves multiple entities with filtering, sorting, pagination,
241
+ * and includes the total count of matching documents (useful for UI pagination).
242
+ * It maps to the IBaseService.getAllWithCount method and handles parameter validation.
243
+ *
244
+ * @param criteria - Optional filter criteria
245
+ * @param sort - Optional sorting configuration
246
+ * @param skip - Optional number of documents to skip (for pagination)
247
+ * @param limit - Optional maximum number of documents to return
248
+ * @param selectedFields - Optional space-separated list of fields to include
249
+ * @returns Object with data array and total count
250
+ *
251
+ * @example
252
+ * // Get paginated users with total count for UI pagination
253
+ * const { data, totalCount } = await broker.call('users.getAllWithCount', {
254
+ * criteria: { role: 'user' },
255
+ * skip: 20,
256
+ * limit: 10,
257
+ * sort: { createdAt: -1 }
258
+ * });
259
+ */
260
+ [BaseServiceCommands.GetAllWithCount]: {
261
+ params: {
262
+ criteria?: Record<string, any>;
263
+ sort?: Record<string, 1 | -1>;
264
+ skip?: number;
265
+ limit?: number;
266
+ selectedFields?: string;
267
+ };
268
+ };
269
+
270
+ /**
271
+ * Create or update a document
272
+ *
273
+ * This action creates a new entity or updates an existing one if an ID is provided.
274
+ * It maps to the IBaseService.insert method and handles parameter validation.
275
+ * It also emits an event for the created or updated entity if configured.
276
+ *
277
+ * @param data - Data object for the new or updated document
278
+ * @param overwrite - Optional flag to completely replace the document instead of merging
279
+ * @returns The created or updated domain entity
280
+ *
281
+ * @example
282
+ * // Create a new product
283
+ * const newProduct = await broker.call('products.insert', {
284
+ * data: { name: 'New Product', price: 99.99 }
285
+ * });
286
+ *
287
+ * // Update an existing product
288
+ * const updatedProduct = await broker.call('products.insert', {
289
+ * data: { id: 'product123', name: 'Updated Product', price: 129.99 }
290
+ * });
291
+ */
292
+ [BaseServiceCommands.Insert]: {
293
+ params: {
294
+ data: any & { id?: string };
295
+ overwrite?: boolean;
296
+ };
297
+ };
298
+
299
+ /**
300
+ * Update a document by ID
301
+ *
302
+ * This action updates an existing entity by its unique identifier.
303
+ * It maps to the IBaseService.update method and handles parameter validation.
304
+ * It also emits an event for the updated entity if configured.
305
+ *
306
+ * @param id - The unique identifier of the document to update
307
+ * @param data - Data object with fields to update
308
+ * @param overwrite - Optional flag to completely replace the document instead of merging
309
+ * @returns The updated domain entity
310
+ *
311
+ * @example
312
+ * // Update a user's role
313
+ * const updatedUser = await broker.call('users.update', {
314
+ * id: 'user123',
315
+ * data: { role: 'admin', active: true }
316
+ * });
317
+ *
318
+ * // Replace a user document entirely
319
+ * const replacedUser = await broker.call('users.update', {
320
+ * id: 'user123',
321
+ * data: { name: 'New Name', email: 'new@example.com', role: 'user' },
322
+ * overwrite: true
323
+ * });
324
+ */
325
+ [BaseServiceCommands.Update]: {
326
+ params: {
327
+ id: string;
328
+ data: any;
329
+ overwrite?: boolean;
330
+ };
331
+ };
332
+
333
+ /**
334
+ * Delete multiple documents matching criteria
335
+ *
336
+ * This action deletes multiple entities matching the provided filter criteria.
337
+ * It maps to the IBaseService.delete method and handles parameter validation.
338
+ * It also emits an event with the deletion criteria if configured.
339
+ *
340
+ * @param criteria - Filter criteria to find documents to delete
341
+ * @returns Number of deleted documents
342
+ *
343
+ * @example
344
+ * // Delete all inactive users
345
+ * const deletedCount = await broker.call('users.deleteMany', {
346
+ * criteria: { active: false }
347
+ * });
348
+ *
349
+ * // Delete users with a specific role
350
+ * const deletedCount = await broker.call('users.deleteMany', {
351
+ * criteria: { role: 'guest' }
352
+ * });
353
+ */
354
+ [BaseServiceCommands.DeleteMany]: {
355
+ params: {
356
+ criteria: Record<string, any>;
357
+ };
358
+ };
359
+ }
360
+
361
+ /**
362
+ * Interface describing the events emitted by the BaseServiceMixin
363
+ * Events follow a consistent naming pattern: `${serviceName}.on${CommandName}`
364
+ */
365
+ export interface IBaseServiceMixinEvents {
366
+ /**
367
+ * Event emitted when a document is created
368
+ * Event name format: `${serviceName}.onCreate`
369
+ * Payload: The created domain entity
370
+ *
371
+ * @example
372
+ * // Listen for user creation events
373
+ * broker.createService({
374
+ * name: 'notification',
375
+ * events: {
376
+ * 'users.onCreate': function(payload) {
377
+ * // Send welcome email to new user
378
+ * this.sendWelcomeEmail(payload.email);
379
+ * }
380
+ * }
381
+ * });
382
+ */
383
+ onCreate: any;
384
+
385
+ /**
386
+ * Event emitted when multiple documents are created
387
+ * Event name format: `${serviceName}.onBulkCreate`
388
+ * Payload: Array of created domain entities
389
+ *
390
+ * @example
391
+ * // Listen for bulk user creation events
392
+ * broker.createService({
393
+ * name: 'notification',
394
+ * events: {
395
+ * 'users.onBulkCreate': function(payload) {
396
+ * // Send welcome emails to all new users
397
+ * payload.forEach(user => this.sendWelcomeEmail(user.email));
398
+ * }
399
+ * }
400
+ * });
401
+ */
402
+ onBulkCreate: any[];
403
+
404
+ /**
405
+ * Event emitted when a document is updated
406
+ * Event name format: `${serviceName}.onUpdate`
407
+ * Payload: The updated domain entity
408
+ *
409
+ * @example
410
+ * // Listen for user update events
411
+ * broker.createService({
412
+ * name: 'audit',
413
+ * events: {
414
+ * 'users.onUpdate': function(payload) {
415
+ * // Log user update for audit trail
416
+ * this.logAuditEvent('user_updated', payload.id);
417
+ * }
418
+ * }
419
+ * });
420
+ */
421
+ onUpdate: any;
422
+
423
+ /**
424
+ * Event emitted when a document is deleted
425
+ * Event name format: `${serviceName}.onDelete`
426
+ * Payload: Object containing the ID of the deleted entity
427
+ *
428
+ * @example
429
+ * // Listen for user deletion events
430
+ * broker.createService({
431
+ * name: 'cleanup',
432
+ * events: {
433
+ * 'users.onDelete': function(payload) {
434
+ * // Clean up related user data
435
+ * this.cleanupUserData(payload.id);
436
+ * }
437
+ * }
438
+ * });
439
+ */
440
+ onDelete: { id: string };
441
+
442
+ /**
443
+ * Event emitted when multiple documents are deleted
444
+ * Event name format: `${serviceName}.onDeleteMany`
445
+ * Payload: The criteria used for deletion
446
+ *
447
+ * @example
448
+ * // Listen for bulk user deletion events
449
+ * broker.createService({
450
+ * name: 'cleanup',
451
+ * events: {
452
+ * 'users.onDeleteMany': function(payload) {
453
+ * // Clean up related data for deleted users
454
+ * this.cleanupBulkUserData(payload.criteria);
455
+ * }
456
+ * }
457
+ * });
458
+ */
459
+ onDeleteMany: { criteria: Record<string, any> };
460
+ }
@@ -0,0 +1,211 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /**
3
+ * @file IDataLoader.ts
4
+ * @description Defines interfaces for implementing the DataLoader pattern to efficiently batch and cache database queries.
5
+ *
6
+ * The DataLoader pattern helps prevent the N+1 query problem by consolidating multiple individual requests
7
+ * into a single batch request. This significantly improves performance for nested queries and relationship
8
+ * traversal in GraphQL or REST APIs.
9
+ *
10
+ * Architecture Context:
11
+ * - Part of the data access optimization layer
12
+ * - Sits between repositories and services/resolvers
13
+ * - Implements request batching and caching for efficient data access
14
+ * - Particularly valuable for GraphQL implementations to solve the N+1 query problem
15
+ * - Works with MongoDB collections through Mongoose
16
+ *
17
+ * Key features:
18
+ * - Request batching: Combines multiple individual requests into a single database query
19
+ * - In-memory caching: Prevents duplicate requests for the same entity
20
+ * - Custom loading options: Supports flexible query criteria beyond simple ID lookups
21
+ * - Clear cache methods: Allows for cache invalidation when data changes
22
+ *
23
+ * Usage Patterns:
24
+ * - Create a DataLoader instance for each entity type in your application
25
+ * - Use the load method for single entity retrieval by ID
26
+ * - Use loadMany for retrieving multiple entities by their IDs
27
+ * - Use withOptions for more complex queries with custom criteria
28
+ * - Clear cache entries when entities are modified
29
+ *
30
+ * Performance Impact:
31
+ * - Reduces database round trips by batching requests
32
+ * - Minimizes redundant queries through caching
33
+ * - Particularly effective for nested relationship queries in GraphQL
34
+ * - Can dramatically improve response times for complex data graphs
35
+ *
36
+ * @see IBaseMongoRepository - Repository layer that provides the underlying data access
37
+ * @see AsDomainType - Type transformation for domain objects
38
+ */
39
+
40
+ import DataLoader from 'dataloader';
41
+ import type { FilterQuery } from 'mongoose';
42
+ import { AsDomainType } from './dbCommonTypes';
43
+
44
+ /**
45
+ * Options for configuring how entities are loaded through the DataLoader
46
+ *
47
+ * @property id - Unique identifier for the request (used for caching)
48
+ * @property searchKey - The field to search by (typically 'id' or another indexed field)
49
+ * @property comparator - Optional custom function to determine if an item matches the search criteria
50
+ * @property criteria - Optional additional filter criteria to apply to the query
51
+ *
52
+ * @example
53
+ * // Basic options for loading by ID
54
+ * const options: DataLoaderOptions<UserSchema> = {
55
+ * id: '123',
56
+ * searchKey: 'id'
57
+ * };
58
+ *
59
+ * // Advanced options with custom criteria
60
+ * const options: DataLoaderOptions<UserSchema> = {
61
+ * id: 'active-admins',
62
+ * searchKey: 'role',
63
+ * criteria: { active: true, role: 'admin' },
64
+ * comparator: (opt, item) => item.role === 'admin' && item.active === true
65
+ * };
66
+ */
67
+ export interface DataLoaderOptions<SchemaType> {
68
+ /** Unique identifier for the request (used for caching) */
69
+ id: string;
70
+
71
+ /** The field to search by (typically 'id' or another indexed field) */
72
+ searchKey: keyof AsDomainType<SchemaType> | string;
73
+
74
+ /** Optional custom function to determine if an item matches the search criteria */
75
+ comparator?: (option: DataLoaderOptions<SchemaType>, item: AsDomainType<SchemaType>) => boolean;
76
+
77
+ /** Optional additional filter criteria to apply to the query */
78
+ criteria?: FilterQuery<SchemaType>;
79
+
80
+ /** Additional custom properties can be added as needed */
81
+ [key: string]: any;
82
+ }
83
+
84
+ /**
85
+ * Interface for implementing the DataLoader pattern to efficiently batch and cache database queries
86
+ *
87
+ * The IDataLoader interface provides methods for loading single or multiple entities by ID,
88
+ * clearing cache entries, and loading entities with custom options. This implementation
89
+ * supports MongoDB collections through Mongoose.
90
+ *
91
+ * @example
92
+ * // Create a user data loader
93
+ * const userLoader = new MongoDataLoader(UserModel);
94
+ *
95
+ * // Load a user by ID (batched and cached)
96
+ * const user1 = await userLoader.load('user1');
97
+ * const user2 = await userLoader.load('user2');
98
+ *
99
+ * // Load multiple users by ID in a single batch
100
+ * const users = await userLoader.loadMany(['user3', 'user4', 'user5']);
101
+ *
102
+ * // Load users with custom criteria
103
+ * const activeAdmins = await userLoader.withOptions.load({
104
+ * id: 'active-admins',
105
+ * searchKey: 'role',
106
+ * criteria: { active: true, role: 'admin' }
107
+ * });
108
+ *
109
+ * // Clear cache when a user is updated
110
+ * userLoader.clear('user1');
111
+ */
112
+ export interface IDataLoader<SchemaType> {
113
+ /**
114
+ * Load a single entity by ID
115
+ *
116
+ * This method retrieves a single entity by its unique identifier.
117
+ * Requests are batched with other load calls in the same tick of the event loop
118
+ * and results are cached to prevent duplicate database queries.
119
+ *
120
+ * @param id - The unique identifier of the entity to load
121
+ * @returns Promise resolving to the domain entity or null if not found
122
+ *
123
+ * @example
124
+ * // Load a user by ID
125
+ * const user = await userLoader.load('user123');
126
+ *
127
+ * // Handle case where entity might not exist
128
+ * const product = await productLoader.load('product456');
129
+ * if (product) {
130
+ * // Product exists, use it
131
+ * } else {
132
+ * // Product not found
133
+ * }
134
+ */
135
+ load: (id: string) => Promise<AsDomainType<SchemaType> | null>;
136
+
137
+ /**
138
+ * Load multiple entities by their IDs
139
+ *
140
+ * This method retrieves multiple entities by their unique identifiers in a single batch.
141
+ * Results are cached to prevent duplicate database queries.
142
+ *
143
+ * @param ids - Array of unique identifiers of the entities to load
144
+ * @returns Promise resolving to an array of domain entities, nulls, or errors
145
+ *
146
+ * @example
147
+ * // Load multiple users by their IDs
148
+ * const users = await userLoader.loadMany(['user1', 'user2', 'user3']);
149
+ *
150
+ * // Handle results which may include nulls for not found entities
151
+ * const validUsers = users.filter(user => user !== null && !(user instanceof Error));
152
+ */
153
+ loadMany: (ids: string[]) => Promise<(AsDomainType<SchemaType> | null | Error)[]>;
154
+
155
+ /**
156
+ * Clear a single entity from the cache
157
+ *
158
+ * This method removes a specific entity from the cache, forcing the next load
159
+ * request for this ID to query the database. Use this when an entity is updated
160
+ * or deleted to ensure fresh data is loaded.
161
+ *
162
+ * @param id - The unique identifier of the entity to remove from cache
163
+ * @returns The DataLoader instance for method chaining
164
+ *
165
+ * @example
166
+ * // Update a user and clear the cache
167
+ * await userService.update('user123', { name: 'New Name' });
168
+ * userLoader.clear('user123');
169
+ */
170
+ clear: (id: string) => DataLoader<string, AsDomainType<SchemaType> | null>;
171
+
172
+ /**
173
+ * Clear all entities from the cache
174
+ *
175
+ * This method removes all entities from the cache, forcing the next load
176
+ * requests to query the database. Use this when multiple entities are updated
177
+ * or when you need to ensure all data is fresh.
178
+ *
179
+ * @returns The DataLoader instance for method chaining
180
+ *
181
+ * @example
182
+ * // After a bulk update operation, clear the entire cache
183
+ * await userService.bulkUpdate({ role: 'user' }, { active: true });
184
+ * userLoader.clearAll();
185
+ */
186
+ clearAll: () => DataLoader<string, AsDomainType<SchemaType> | null>;
187
+
188
+ /**
189
+ * DataLoader for loading entities with custom options
190
+ *
191
+ * This property provides a DataLoader instance that supports more complex
192
+ * loading scenarios beyond simple ID lookups. Use this when you need to
193
+ * load entities based on other criteria or with additional filtering.
194
+ *
195
+ * @example
196
+ * // Load active users with the admin role
197
+ * const adminUsers = await userLoader.withOptions.load({
198
+ * id: 'active-admins', // Unique cache key
199
+ * searchKey: 'role',
200
+ * criteria: { active: true, role: 'admin' }
201
+ * });
202
+ *
203
+ * // Load products in a specific price range
204
+ * const affordableProducts = await productLoader.withOptions.load({
205
+ * id: 'affordable-products',
206
+ * searchKey: 'price',
207
+ * criteria: { price: { $lt: 100 } }
208
+ * });
209
+ */
210
+ withOptions: DataLoader<DataLoaderOptions<SchemaType>, AsDomainType<SchemaType>[]>;
211
+ }