@cyberskill/shared 3.9.0 → 3.10.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 (23) hide show
  1. package/dist/config/lint-staged/index.js +1 -1
  2. package/dist/config/lint-staged/index.js.map +1 -1
  3. package/dist/config/vitest/vitest.e2e.js +1 -1
  4. package/dist/config/vitest/vitest.unit.js +1 -1
  5. package/dist/node/express/express.util.js +3 -4
  6. package/dist/node/express/express.util.js.map +1 -1
  7. package/dist/node/fs/fs.util.js +14 -14
  8. package/dist/node/mongo/index.js +5 -5
  9. package/dist/node/mongo/mongo.controller.mongoose.js +86 -85
  10. package/dist/node/mongo/mongo.controller.mongoose.js.map +1 -1
  11. package/dist/node/mongo/mongo.dynamic-populate.d.ts +0 -4
  12. package/dist/node/mongo/mongo.dynamic-populate.js +14 -17
  13. package/dist/node/mongo/mongo.dynamic-populate.js.map +1 -1
  14. package/dist/node/mongo/mongo.util.js +2 -0
  15. package/dist/node/mongo/mongo.util.js.map +1 -1
  16. package/dist/node/storage/storage.util.js +41 -35
  17. package/dist/node/storage/storage.util.js.map +1 -1
  18. package/dist/node/upload/upload.util.js +34 -28
  19. package/dist/node/upload/upload.util.js.map +1 -1
  20. package/dist/node_modules/.pnpm/vitest@4.1.0_@types_node@25.5.0_jsdom@29.0.0_@noble_hashes@1.8.0__vite@8.0.1_@types_nod_36acd00c2670b611b011192023dc5bd9/node_modules/vitest/dist/config.js +8 -0
  21. package/dist/node_modules/.pnpm/{vitest@4.1.0_@types_node@25.5.0_jsdom@29.0.0_@noble_hashes@1.8.0__vite@8.0.0_@types_nod_53aa4254f295b3c40bb8f17b6ab226b5 → vitest@4.1.0_@types_node@25.5.0_jsdom@29.0.0_@noble_hashes@1.8.0__vite@8.0.1_@types_nod_36acd00c2670b611b011192023dc5bd9}/node_modules/vitest/dist/config.js.map +1 -1
  22. package/package.json +8 -8
  23. package/dist/node_modules/.pnpm/vitest@4.1.0_@types_node@25.5.0_jsdom@29.0.0_@noble_hashes@1.8.0__vite@8.0.0_@types_nod_53aa4254f295b3c40bb8f17b6ab226b5/node_modules/vitest/dist/config.js +0 -8
@@ -1 +1 @@
1
- {"version":3,"file":"mongo.controller.mongoose.js","names":[],"sources":["../../../src/node/mongo/mongo.controller.mongoose.ts"],"sourcesContent":["import type { I_Return } from '#typescript/index.js';\n\nimport { RESPONSE_STATUS } from '#constant/index.js';\nimport { normalizeMongoFilter } from '#util/index.js';\nimport { generateRandomString, generateShortId, generateSlug } from '#util/string/index.js';\n\nimport type { C_Document, I_DeleteOptionsExtended, I_DynamicVirtualConfig, I_ExtendedModel, I_Input_CheckSlug, I_Input_CreateSlug, I_Input_GenerateSlug, I_PaginateOptionsWithPopulate, I_UpdateOptionsExtended, T_AggregatePaginateResult, T_DeleteResult, T_Input_Populate, T_InsertManyOptions, T_PaginateResult, T_PipelineStage, T_PopulateOptions, T_ProjectionType, T_QueryFilter, T_QueryOptions, T_UpdateQuery, T_UpdateResult } from './mongo.type.js';\n\nimport { catchError, log } from '../log/index.js';\nimport { MONGO_SLUG_MAX_ATTEMPTS } from './mongo.constant.js';\nimport { filterDynamicVirtualsFromPopulate, isObject, populateDynamicVirtuals } from './mongo.dynamic-populate.js';\n\n/**\n * Converts a Mongoose document to a plain object, handling the case where\n * the document may already be a plain object (e.g., from `.lean()`).\n *\n * @param doc - The document or plain object.\n * @returns The plain object representation.\n */\nfunction toPlainObject<T>(doc: T): T {\n return (doc as T & { toObject?: () => T })?.toObject?.() ?? doc;\n}\n\n/** Internal shape of a single virtual config stored on the model. */\ninterface I_VirtualConfig {\n name: string;\n options?: { ref?: unknown };\n}\n\n/**\n * Mongoose controller for database operations with advanced features.\n * This class provides a comprehensive interface for Mongoose operations including\n * pagination, aggregation, slug generation, and short ID creation.\n */\nexport class MongooseController<T extends Partial<C_Document>> {\n private defaultLimit: number;\n\n /**\n * Creates a new Mongoose controller instance.\n *\n * @param model - The Mongoose model to operate on.\n * @param options - Optional configuration for the controller.\n * @param options.defaultLimit - Maximum documents returned by findAll when no limit is specified (default: 1,000).\n */\n constructor(private model: I_ExtendedModel<T>, options?: { defaultLimit?: number }) {\n this.defaultLimit = options?.defaultLimit ?? 1_000;\n }\n\n /**\n * Gets the model name for logging and error messages.\n *\n * @returns The name of the model.\n */\n private getModelName(): string {\n return this.model.modelName;\n }\n\n /**\n * Gets the dynamic virtuals configuration from the model instance.\n *\n * @returns Array of dynamic virtual configurations or undefined if none exist.\n */\n private getDynamicVirtuals(): I_DynamicVirtualConfig<T>[] | undefined {\n const model = this.model as I_ExtendedModel<T> & { _virtualConfigs?: I_VirtualConfig[] };\n\n if (model._virtualConfigs) {\n const dynamicOnly = model._virtualConfigs.filter(\n v => typeof v.options?.ref === 'function',\n ) as unknown as I_DynamicVirtualConfig<T>[];\n\n if (dynamicOnly.length > 0) {\n return dynamicOnly;\n }\n }\n\n const schemaStatics = this.model.schema.statics as { [key: string]: unknown };\n\n return schemaStatics['_dynamicVirtuals'] as I_DynamicVirtualConfig<T>[] | undefined;\n }\n\n /**\n * Populates dynamic virtuals for a single document.\n *\n * @param result - The document to populate dynamic virtuals for.\n * @param populate - The populate options to determine which virtuals to populate.\n * @returns The document with dynamic virtuals populated.\n */\n private async populateDynamicVirtualsForDocument(result: T, populate?: T_Input_Populate): Promise<T> {\n const populated = await this.populateDynamic([result], populate);\n return populated[0] ?? result;\n }\n\n /**\n * Populates dynamic virtuals for an array of documents.\n *\n * @param results - The documents to populate dynamic virtuals for.\n * @param populate - The populate options to determine which virtuals to populate.\n * @returns The documents with dynamic virtuals populated.\n */\n private async populateDynamicVirtualsForDocuments(results: T[], populate?: T_Input_Populate): Promise<T[]> {\n return this.populateDynamic(results, populate);\n }\n\n /**\n * Internal helper that populates dynamic virtuals for an array of documents.\n * Shared implementation used by both single-document and multi-document methods.\n */\n private async populateDynamic(results: T[], populate?: T_Input_Populate): Promise<T[]> {\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n if (dynamicVirtuals && dynamicVirtuals.length > 0 && results.length > 0) {\n return await populateDynamicVirtuals(this.model.base, results, dynamicVirtuals, populate, undefined, this.model) as T[];\n }\n\n return results;\n }\n\n /**\n * Finds a single document with optional population and projection.\n * Automatically handles dynamic virtual population if configured.\n *\n * @param filter - The filter criteria to find the document.\n * @param projection - The fields to include/exclude in the result.\n * @param options - Query options for the operation.\n * @param populate - Population configuration for related documents.\n * @returns A promise that resolves to a standardized response with the found document.\n */\n async findOne(\n filter: T_QueryFilter<T> = {},\n projection: T_ProjectionType<T> = {},\n options: T_QueryOptions<T> = {},\n populate?: T_Input_Populate,\n ): Promise<I_Return<T>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const query = this.model.findOne(normalizedFilter, projection, options).maxTimeMS(30_000).lean();\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n const regularPopulate = filterDynamicVirtualsFromPopulate(populate, dynamicVirtuals);\n\n if (regularPopulate) {\n query.populate(regularPopulate as T_PopulateOptions);\n }\n\n const result = await query.exec();\n\n if (!result) {\n return {\n success: false,\n message: `No ${this.getModelName()} found.`,\n code: RESPONSE_STATUS.NOT_FOUND.CODE,\n };\n }\n\n const finalResult = await this.populateDynamicVirtualsForDocument(result, populate);\n\n return { success: true, result: toPlainObject(finalResult) };\n }\n catch (error) {\n return catchError<T>(error);\n }\n }\n\n /**\n * Finds all documents with optional population and projection.\n * Automatically handles dynamic virtual population if configured.\n *\n * @param filter - The filter criteria to find documents.\n * @param projection - The fields to include/exclude in the result.\n * @param options - Query options for the operation.\n * @param populate - Population configuration for related documents.\n * @returns A promise that resolves to a standardized response with the found documents.\n */\n async findAll(\n filter: T_QueryFilter<T> = {},\n projection: T_ProjectionType<T> = {},\n options: T_QueryOptions<T> = {},\n populate?: T_Input_Populate,\n ): Promise<I_Return<T[]>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const query = this.model.find(normalizedFilter, projection, options).maxTimeMS(30_000).lean();\n\n if (!options.limit) {\n query.limit(this.defaultLimit);\n }\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n const regularPopulate = filterDynamicVirtualsFromPopulate(populate, dynamicVirtuals);\n\n if (regularPopulate) {\n query.populate(regularPopulate as T_PopulateOptions);\n }\n\n const result = await query.exec();\n\n const finalResult = await this.populateDynamicVirtualsForDocuments(result, populate);\n\n const truncated = finalResult.length === this.defaultLimit && !options.limit;\n\n if (truncated) {\n log.warn(`[${this.getModelName()}] findAll returned exactly ${this.defaultLimit} documents (the default limit). Results may be truncated. Consider using pagination or setting an explicit limit.`);\n }\n\n return { success: true, result: finalResult.map(item => toPlainObject(item)), truncated };\n }\n catch (error) {\n return catchError<T[]>(error);\n }\n }\n\n /**\n * Finds documents with pagination support.\n * Automatically handles dynamic virtual population if configured.\n *\n * @param filter - The filter criteria to find documents.\n * @param options - Pagination options including page, limit, and population.\n * @returns A promise that resolves to a standardized response with paginated results.\n */\n async findPaging(\n filter: T_QueryFilter<T> = {},\n options: I_PaginateOptionsWithPopulate = {},\n ): Promise<I_Return<T_PaginateResult<T>>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n const filteredOptions = { ...options };\n\n if (options.populate) {\n filteredOptions.populate = filterDynamicVirtualsFromPopulate(options.populate, dynamicVirtuals);\n }\n\n const result = await this.model.paginate(normalizedFilter, filteredOptions);\n\n if (dynamicVirtuals && dynamicVirtuals.length > 0) {\n const populatedDocs = await this.populateDynamicVirtualsForDocuments(result.docs, options.populate);\n\n return { success: true, result: { ...result, docs: populatedDocs.map(item => toPlainObject(item)) } };\n }\n\n return { success: true, result: { ...result, docs: result.docs.map(item => toPlainObject(item)) } };\n }\n catch (error) {\n return catchError<T_PaginateResult<T>>(error);\n }\n }\n\n /**\n * Performs aggregation with pagination support.\n *\n * @param pipeline - The aggregation pipeline stages.\n * @param options - Pagination options for the aggregation result.\n * @returns A promise that resolves to a standardized response with paginated aggregation results.\n */\n async findPagingAggregate(\n pipeline: T_PipelineStage[],\n options: I_PaginateOptionsWithPopulate = {},\n ): Promise<I_Return<T_AggregatePaginateResult<T>>> {\n try {\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n const filteredOptions = { ...options };\n\n if (options.populate) {\n filteredOptions.populate = filterDynamicVirtualsFromPopulate(options.populate, dynamicVirtuals);\n }\n\n const result = await this.model.aggregatePaginate(\n this.model.aggregate(pipeline),\n filteredOptions,\n );\n\n const finalDocs = await this.populateDynamicVirtualsForDocuments(result.docs, options.populate);\n\n return { success: true, result: { ...result, docs: finalDocs.map(item => toPlainObject(item)) } };\n }\n catch (error) {\n return catchError<T_AggregatePaginateResult<T>>(error);\n }\n }\n\n /**\n * Counts documents matching the filter criteria.\n *\n * @param filter - The filter criteria to count documents.\n * @returns A promise that resolves to a standardized response with the document count.\n */\n async count(filter: T_QueryFilter<T> = {}): Promise<I_Return<number>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model.countDocuments(normalizedFilter);\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<number>(error);\n }\n }\n\n /**\n * Creates a single document.\n *\n * @param doc - The document to create.\n * @returns A promise that resolves to a standardized response with the created document.\n */\n async createOne(doc: T | Partial<T>): Promise<I_Return<T>> {\n try {\n const result = await this.model.create(doc as unknown as Parameters<typeof this.model.create>[0]);\n\n return { success: true, result: (result as T)?.toObject?.() ?? result };\n }\n catch (error) {\n return catchError<T>(error);\n }\n }\n\n /**\n * Creates multiple documents with bulk insertion.\n *\n * @param docs - An array of documents to create.\n * @param options - Options for the bulk insertion operation.\n * @returns A promise that resolves to a standardized response with the created documents.\n */\n async createMany(\n docs: (T | Partial<T>)[],\n options: T_InsertManyOptions = {},\n ): Promise<I_Return<T[]>> {\n try {\n const createdDocuments = await this.model.insertMany(docs, options);\n\n return { success: true, result: createdDocuments.map(item => item?.toObject?.() ?? item) as T[] };\n }\n catch (error) {\n return catchError<T[]>(error);\n }\n }\n\n /**\n * Updates a single document and returns the updated version.\n *\n * @param filter - The filter criteria to find the document to update.\n * @param update - The update data to apply.\n * @param options - Options for the update operation.\n * @returns A promise that resolves to a standardized response with the updated document.\n */\n async updateOne(\n filter: T_QueryFilter<T> = {},\n update: T_UpdateQuery<T> = {},\n options: I_UpdateOptionsExtended = {},\n ): Promise<I_Return<T>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model\n .findOneAndUpdate(normalizedFilter, update, {\n new: true,\n ...options,\n })\n .exec();\n\n if (!result) {\n return {\n success: false,\n message: `Failed to update ${this.getModelName()}.`,\n code: RESPONSE_STATUS.NOT_FOUND.CODE,\n };\n }\n\n return { success: true, result: result?.toObject?.() ?? result };\n }\n catch (error) {\n return catchError<T>(error);\n }\n }\n\n /**\n * Updates multiple documents matching the filter criteria.\n *\n * @param filter - The filter criteria to find documents to update.\n * @param update - The update data to apply.\n * @param options - Options for the update operation.\n * @returns A promise that resolves to a standardized response with the update result.\n */\n async updateMany(\n filter: T_QueryFilter<T> = {},\n update: T_UpdateQuery<T> = {},\n options: I_UpdateOptionsExtended = {},\n ): Promise<I_Return<T_UpdateResult>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model\n .updateMany(normalizedFilter, update, options)\n .exec();\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<T_UpdateResult>(error);\n }\n }\n\n /**\n * Deletes a single document and returns the deleted version.\n *\n * @param filter - The filter criteria to find the document to delete.\n * @param options - Options for the delete operation.\n * @returns A promise that resolves to a standardized response with the deleted document.\n */\n async deleteOne(\n filter: T_QueryFilter<T> = {},\n options: I_DeleteOptionsExtended = {},\n ): Promise<I_Return<T>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model\n .findOneAndDelete(normalizedFilter, options)\n .exec();\n\n if (!result) {\n return {\n success: false,\n message: `No ${this.getModelName()} found to delete.`,\n code: RESPONSE_STATUS.NOT_FOUND.CODE,\n };\n }\n\n return { success: true, result: result?.toObject?.() ?? result };\n }\n catch (error) {\n return catchError<T>(error);\n }\n }\n\n /**\n * Deletes multiple documents matching the filter criteria.\n *\n * @param filter - The filter criteria to find documents to delete.\n * @param options - Options for the delete operation.\n * @returns A promise that resolves to a standardized response with the delete result.\n */\n async deleteMany(\n filter: T_QueryFilter<T> = {},\n options: I_DeleteOptionsExtended = {},\n ): Promise<I_Return<T_DeleteResult>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model.deleteMany(normalizedFilter, options).exec();\n\n if (result.deletedCount === 0) {\n return {\n success: false,\n message: `No documents found to delete.`,\n code: RESPONSE_STATUS.NOT_FOUND.CODE,\n };\n }\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<T_DeleteResult>(error);\n }\n }\n\n /**\n * Creates a unique short ID based on a given ID.\n * This method generates multiple short IDs with increasing lengths and finds the first available one.\n *\n * @param id - The base ID to generate short IDs from.\n * @param length - The initial length for short ID generation (default: 4).\n * @returns A promise that resolves to a standardized response with the unique short ID.\n */\n async createShortId(id: string, length = 4): Promise<I_Return<string>> {\n try {\n const maxRetries = 10;\n const shortIds = Array.from({ length: maxRetries }, (_, index) =>\n generateShortId(id, index + length));\n\n // Use a single $in query instead of 10 parallel exists() calls\n const existingDocs = await this.model\n .find({ shortId: { $in: shortIds } })\n .select('shortId')\n .lean();\n\n const existingShortIds = new Set(\n existingDocs.map((d: Record<string, unknown>) => d['shortId'] as string),\n );\n\n const availableShortId = shortIds.find(s => !existingShortIds.has(s));\n\n if (availableShortId) {\n return { success: true, result: availableShortId };\n }\n\n return {\n success: false,\n message: 'Failed to create a unique shortId',\n code: RESPONSE_STATUS.INTERNAL_SERVER_ERROR.CODE,\n };\n }\n catch (error) {\n return catchError<string>(error);\n }\n }\n\n /**\n * Creates a query for slug existence checking.\n * This method generates a query that checks for slug existence in both current and historical slug fields.\n *\n * @param options - Configuration for slug query generation including slug, field, and filter.\n * @param options.slug - The slug string to check for existence.\n * @param options.field - The field name for object-based slug checking.\n * @param options.isObject - Whether the slug is stored as an object with nested fields.\n * @param options.haveHistory - Whether to check historical slug fields for existence.\n * @param options.filter - Additional filter conditions to apply to the query.\n * @returns A MongoDB query object for checking slug existence.\n */\n createSlugQuery({ slug, field, isObject, haveHistory = false, filter }: I_Input_GenerateSlug<T>) {\n const baseFilter = { ...(filter ?? {}) };\n\n return isObject\n ? {\n ...baseFilter,\n $or: [\n { [`slug.${field}`]: slug },\n ...(haveHistory ? [{ slugHistory: { $elemMatch: { [`slug.${field}`]: slug } } }] : []),\n ],\n }\n : {\n ...baseFilter,\n $or: [\n { slug },\n ...(haveHistory ? [{ slugHistory: slug }] : []),\n ],\n };\n }\n\n /**\n * Creates a unique slug based on a given string.\n * This method generates multiple slug variations and finds the first available one.\n *\n * @param options - Configuration for slug generation including slug, field, and filter.\n * @param options.slug - The base slug string to make unique.\n * @param options.field - The field name for object-based slug checking.\n * @param options.isObject - Whether the slug is stored as an object with nested fields.\n * @param options.haveHistory - Whether to check historical slug fields for uniqueness.\n * @param options.filter - Additional filter conditions to apply when checking slug existence.\n * @returns A promise that resolves to a unique slug string.\n */\n async createUniqueSlug({ slug, field, isObject, haveHistory, filter }: I_Input_GenerateSlug<T>): Promise<string> {\n if (!slug || typeof slug !== 'string') {\n throw new Error('Invalid slug provided: must be a non-empty string');\n }\n\n const baseSlug = generateSlug(slug);\n\n const baseExists = await this.model.exists(\n this.createSlugQuery({ slug: baseSlug, field, isObject, haveHistory, filter }),\n );\n\n if (!baseExists) {\n return baseSlug;\n }\n\n // Batch query: check all slug variations in a single database call instead of N+1 sequential queries\n const variants = Array.from(\n { length: MONGO_SLUG_MAX_ATTEMPTS },\n (_, i) => `${baseSlug}-${i + 1}`,\n );\n\n const slugQueries = variants.map(s =>\n this.createSlugQuery({ slug: s, field, isObject, haveHistory, filter }),\n );\n\n const slugField = isObject ? `slug.${field as string}` : 'slug';\n const existingDocs = await this.model\n .find({ $or: slugQueries.map(q => q.$or).flat() })\n .select(slugField)\n .lean();\n\n const existingSlugs = new Set(\n existingDocs.map((d: Record<string, unknown>) => {\n if (isObject) {\n const slug = d['slug'] as Record<string, unknown> | undefined;\n return slug?.[field as string];\n }\n return d['slug'];\n }),\n );\n\n const available = variants.find(s => !existingSlugs.has(s));\n\n if (available) {\n return available;\n }\n\n const timestamp = Date.now();\n const randomSuffix = generateRandomString(6);\n\n return `${baseSlug}-${timestamp}-${randomSuffix}`;\n }\n\n /**\n * Creates a slug for a document field.\n * This method handles both simple string fields and object fields with nested slug generation.\n *\n * @param options - Configuration for slug creation including field, source document, and filter.\n * @param options.field - The field name to create a slug for.\n * @param options.from - The source document containing the field value.\n * @param options.haveHistory - Whether to check historical slug fields for uniqueness.\n * @param options.filter - Additional filter conditions to apply when checking slug existence.\n * @returns A promise that resolves to a standardized response with the created slug(s).\n */\n async createSlug<R = string>({ field, from, filter, haveHistory }: I_Input_CreateSlug<T>): Promise<I_Return<R>> {\n try {\n const fieldValue = from[field as keyof T];\n const isObjectValue = isObject(fieldValue);\n\n if (isObjectValue) {\n const uniqueSlug = Object.fromEntries(\n await Promise.all(\n Object.entries(fieldValue).map(async ([key, value]) => {\n const uniqueSlugForKey = await this.createUniqueSlug({\n slug: value as string,\n field: key,\n isObject: true,\n haveHistory,\n filter,\n });\n return [key, uniqueSlugForKey];\n }),\n ),\n );\n\n return { success: true, result: uniqueSlug as R };\n }\n\n const uniqueSlug = await this.createUniqueSlug({\n slug: fieldValue as string,\n field,\n isObject: false,\n haveHistory,\n filter,\n });\n\n return { success: true, result: uniqueSlug as R };\n }\n catch (error) {\n return catchError<R>(error);\n }\n }\n\n /**\n * Checks if a slug already exists in the collection.\n * This method verifies slug existence in both current and historical slug fields.\n *\n * @param options - Configuration for slug checking including slug, field, source document, and filter.\n * @param options.slug - The slug string to check for existence.\n * @param options.field - The field name for object-based slug checking.\n * @param options.from - The source document containing the field value.\n * @param options.haveHistory - Whether to check historical slug fields for existence.\n * @param options.filter - Additional filter conditions to apply to the query.\n * @returns A promise that resolves to a standardized response indicating whether the slug exists.\n */\n async checkSlug({ slug, field, from, filter, haveHistory }: I_Input_CheckSlug<T>): Promise<I_Return<boolean>> {\n try {\n const fieldValue = from[field as keyof T];\n const isObjectValue = isObject(fieldValue);\n\n if (isObjectValue) {\n const values = Object.values(fieldValue);\n const nestedSlugs = values.map(value => generateSlug(value as string));\n\n const existenceChecks = await Promise.all(\n nestedSlugs.map(nestedSlug =>\n this.model.exists(this.createSlugQuery({\n slug: nestedSlug,\n field,\n isObject: true,\n haveHistory,\n filter,\n })),\n ),\n );\n\n if (existenceChecks.some(exists => exists)) {\n return { success: true, result: true };\n }\n\n return { success: true, result: false };\n }\n\n const baseSlug = generateSlug(slug);\n const exists = await this.model.exists(this.createSlugQuery({\n slug: baseSlug,\n field,\n isObject: false,\n filter,\n }));\n\n return { success: true, result: exists !== null };\n }\n catch (error) {\n return catchError<boolean>(error);\n }\n }\n\n /**\n * Performs aggregation operations on the collection.\n *\n * @param pipeline - The aggregation pipeline stages to execute.\n * @returns A promise that resolves to a standardized response with the aggregation results.\n */\n async aggregate(pipeline: T_PipelineStage[]): Promise<I_Return<T[]>> {\n try {\n const result = await this.model.aggregate<T>(pipeline);\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<T[]>(error);\n }\n }\n\n /**\n * Retrieves distinct values for the specified key from the collection.\n *\n * @param key - The field for which to return distinct values.\n * @param filter - The filter query to apply (optional).\n * @param options - Additional options for the distinct operation (optional).\n * @returns A promise that resolves to a standardized response with the array of distinct values.\n */\n async distinct(\n key: string,\n filter: T_QueryFilter<T> = {},\n options: T_QueryOptions<T> = {},\n ): Promise<I_Return<unknown[]>> {\n try {\n const result = await this.model.distinct(key, filter, options);\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<unknown[]>(error);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mongo.controller.mongoose.js","names":[],"sources":["../../../src/node/mongo/mongo.controller.mongoose.ts"],"sourcesContent":["import type { I_Return } from '#typescript/index.js';\n\nimport { RESPONSE_STATUS } from '#constant/index.js';\nimport { isObject } from '#util/common/index.js';\nimport { normalizeMongoFilter } from '#util/index.js';\nimport { generateRandomString, generateShortId, generateSlug } from '#util/string/index.js';\n\nimport type { C_Document, I_DeleteOptionsExtended, I_DynamicVirtualConfig, I_ExtendedModel, I_Input_CheckSlug, I_Input_CreateSlug, I_Input_GenerateSlug, I_PaginateOptionsWithPopulate, I_UpdateOptionsExtended, T_AggregatePaginateResult, T_DeleteResult, T_Input_Populate, T_InsertManyOptions, T_PaginateResult, T_PipelineStage, T_PopulateOptions, T_ProjectionType, T_QueryFilter, T_QueryOptions, T_UpdateQuery, T_UpdateResult } from './mongo.type.js';\n\nimport { catchError, log } from '../log/index.js';\nimport { MONGO_SLUG_MAX_ATTEMPTS } from './mongo.constant.js';\nimport { filterDynamicVirtualsFromPopulate, populateDynamicVirtuals } from './mongo.dynamic-populate.js';\n/**\n * Converts a Mongoose document to a plain object, handling the case where\n * the document may already be a plain object (e.g., from `.lean()`).\n *\n * @param doc - The document or plain object.\n * @returns The plain object representation.\n */\nfunction toPlainObject<T>(doc: T): T {\n return (doc as T & { toObject?: () => T })?.toObject?.() ?? doc;\n}\n\n/** Internal shape of a single virtual config stored on the model. */\ninterface I_VirtualConfig {\n name: string;\n options?: { ref?: unknown };\n}\n\n/**\n * Mongoose controller for database operations with advanced features.\n * This class provides a comprehensive interface for Mongoose operations including\n * pagination, aggregation, slug generation, and short ID creation.\n */\nexport class MongooseController<T extends Partial<C_Document>> {\n private defaultLimit: number;\n\n /**\n * Creates a new Mongoose controller instance.\n *\n * @param model - The Mongoose model to operate on.\n * @param options - Optional configuration for the controller.\n * @param options.defaultLimit - Maximum documents returned by findAll when no limit is specified (default: 1,000).\n */\n constructor(private model: I_ExtendedModel<T>, options?: { defaultLimit?: number }) {\n this.defaultLimit = options?.defaultLimit ?? 1_000;\n }\n\n /**\n * Gets the model name for logging and error messages.\n *\n * @returns The name of the model.\n */\n private getModelName(): string {\n return this.model.modelName;\n }\n\n /**\n * Gets the dynamic virtuals configuration from the model instance.\n *\n * @returns Array of dynamic virtual configurations or undefined if none exist.\n */\n private getDynamicVirtuals(): I_DynamicVirtualConfig<T>[] | undefined {\n const model = this.model as I_ExtendedModel<T> & { _virtualConfigs?: I_VirtualConfig[] };\n\n if (model._virtualConfigs) {\n const dynamicOnly = model._virtualConfigs.filter(\n v => typeof v.options?.ref === 'function',\n ) as unknown as I_DynamicVirtualConfig<T>[];\n\n if (dynamicOnly.length > 0) {\n return dynamicOnly;\n }\n }\n\n const schemaStatics = this.model.schema.statics as { [key: string]: unknown };\n\n return schemaStatics['_dynamicVirtuals'] as I_DynamicVirtualConfig<T>[] | undefined;\n }\n\n /**\n * Populates dynamic virtuals for a single document.\n *\n * @param result - The document to populate dynamic virtuals for.\n * @param populate - The populate options to determine which virtuals to populate.\n * @returns The document with dynamic virtuals populated.\n */\n private async populateDynamicVirtualsForDocument(result: T, populate?: T_Input_Populate): Promise<T> {\n const populated = await this.populateDynamic([result], populate);\n return populated[0] ?? result;\n }\n\n /**\n * Populates dynamic virtuals for an array of documents.\n *\n * @param results - The documents to populate dynamic virtuals for.\n * @param populate - The populate options to determine which virtuals to populate.\n * @returns The documents with dynamic virtuals populated.\n */\n private async populateDynamicVirtualsForDocuments(results: T[], populate?: T_Input_Populate): Promise<T[]> {\n return this.populateDynamic(results, populate);\n }\n\n /**\n * Internal helper that populates dynamic virtuals for an array of documents.\n * Shared implementation used by both single-document and multi-document methods.\n */\n private async populateDynamic(results: T[], populate?: T_Input_Populate): Promise<T[]> {\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n if (dynamicVirtuals && dynamicVirtuals.length > 0 && results.length > 0) {\n return await populateDynamicVirtuals(this.model.base, results, dynamicVirtuals, populate, undefined, this.model) as T[];\n }\n\n return results;\n }\n\n /**\n * Finds a single document with optional population and projection.\n * Automatically handles dynamic virtual population if configured.\n *\n * @param filter - The filter criteria to find the document.\n * @param projection - The fields to include/exclude in the result.\n * @param options - Query options for the operation.\n * @param populate - Population configuration for related documents.\n * @returns A promise that resolves to a standardized response with the found document.\n */\n async findOne(\n filter: T_QueryFilter<T> = {},\n projection: T_ProjectionType<T> = {},\n options: T_QueryOptions<T> = {},\n populate?: T_Input_Populate,\n ): Promise<I_Return<T>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const query = this.model.findOne(normalizedFilter, projection, options).maxTimeMS(30_000).lean();\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n const regularPopulate = filterDynamicVirtualsFromPopulate(populate, dynamicVirtuals);\n\n if (regularPopulate) {\n query.populate(regularPopulate as T_PopulateOptions);\n }\n\n const result = await query.exec();\n\n if (!result) {\n return {\n success: false,\n message: `No ${this.getModelName()} found.`,\n code: RESPONSE_STATUS.NOT_FOUND.CODE,\n };\n }\n\n const finalResult = await this.populateDynamicVirtualsForDocument(result, populate);\n\n return { success: true, result: toPlainObject(finalResult) };\n }\n catch (error) {\n return catchError<T>(error);\n }\n }\n\n /**\n * Finds all documents with optional population and projection.\n * Automatically handles dynamic virtual population if configured.\n *\n * @param filter - The filter criteria to find documents.\n * @param projection - The fields to include/exclude in the result.\n * @param options - Query options for the operation.\n * @param populate - Population configuration for related documents.\n * @returns A promise that resolves to a standardized response with the found documents.\n */\n async findAll(\n filter: T_QueryFilter<T> = {},\n projection: T_ProjectionType<T> = {},\n options: T_QueryOptions<T> = {},\n populate?: T_Input_Populate,\n ): Promise<I_Return<T[]>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const query = this.model.find(normalizedFilter, projection, options).maxTimeMS(30_000).lean();\n\n if (!options.limit) {\n query.limit(this.defaultLimit);\n }\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n const regularPopulate = filterDynamicVirtualsFromPopulate(populate, dynamicVirtuals);\n\n if (regularPopulate) {\n query.populate(regularPopulate as T_PopulateOptions);\n }\n\n const result = await query.exec();\n\n const finalResult = await this.populateDynamicVirtualsForDocuments(result, populate);\n\n const truncated = finalResult.length === this.defaultLimit && !options.limit;\n\n if (truncated) {\n log.warn(`[${this.getModelName()}] findAll returned exactly ${this.defaultLimit} documents (the default limit). Results may be truncated. Consider using pagination or setting an explicit limit.`);\n }\n\n return { success: true, result: finalResult.map(item => toPlainObject(item)), truncated };\n }\n catch (error) {\n return catchError<T[]>(error);\n }\n }\n\n /**\n * Finds documents with pagination support.\n * Automatically handles dynamic virtual population if configured.\n *\n * @param filter - The filter criteria to find documents.\n * @param options - Pagination options including page, limit, and population.\n * @returns A promise that resolves to a standardized response with paginated results.\n */\n async findPaging(\n filter: T_QueryFilter<T> = {},\n options: I_PaginateOptionsWithPopulate = {},\n ): Promise<I_Return<T_PaginateResult<T>>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n const filteredOptions = { ...options };\n\n if (options.populate) {\n filteredOptions.populate = filterDynamicVirtualsFromPopulate(options.populate, dynamicVirtuals);\n }\n\n const result = await this.model.paginate(normalizedFilter, filteredOptions);\n\n if (dynamicVirtuals && dynamicVirtuals.length > 0) {\n const populatedDocs = await this.populateDynamicVirtualsForDocuments(result.docs, options.populate);\n\n return { success: true, result: { ...result, docs: populatedDocs.map(item => toPlainObject(item)) } };\n }\n\n return { success: true, result: { ...result, docs: result.docs.map(item => toPlainObject(item)) } };\n }\n catch (error) {\n return catchError<T_PaginateResult<T>>(error);\n }\n }\n\n /**\n * Performs aggregation with pagination support.\n *\n * @param pipeline - The aggregation pipeline stages.\n * @param options - Pagination options for the aggregation result.\n * @returns A promise that resolves to a standardized response with paginated aggregation results.\n */\n async findPagingAggregate(\n pipeline: T_PipelineStage[],\n options: I_PaginateOptionsWithPopulate = {},\n ): Promise<I_Return<T_AggregatePaginateResult<T>>> {\n try {\n const dynamicVirtuals = this.getDynamicVirtuals();\n\n const filteredOptions = { ...options };\n\n if (options.populate) {\n filteredOptions.populate = filterDynamicVirtualsFromPopulate(options.populate, dynamicVirtuals);\n }\n\n const result = await this.model.aggregatePaginate(\n this.model.aggregate(pipeline),\n filteredOptions,\n );\n\n const finalDocs = await this.populateDynamicVirtualsForDocuments(result.docs, options.populate);\n\n return { success: true, result: { ...result, docs: finalDocs.map(item => toPlainObject(item)) } };\n }\n catch (error) {\n return catchError<T_AggregatePaginateResult<T>>(error);\n }\n }\n\n /**\n * Counts documents matching the filter criteria.\n *\n * @param filter - The filter criteria to count documents.\n * @returns A promise that resolves to a standardized response with the document count.\n */\n async count(filter: T_QueryFilter<T> = {}): Promise<I_Return<number>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model.countDocuments(normalizedFilter);\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<number>(error);\n }\n }\n\n /**\n * Creates a single document.\n *\n * @param doc - The document to create.\n * @returns A promise that resolves to a standardized response with the created document.\n */\n async createOne(doc: T | Partial<T>): Promise<I_Return<T>> {\n try {\n const result = await this.model.create(doc as unknown as Parameters<typeof this.model.create>[0]);\n\n return { success: true, result: (result as T)?.toObject?.() ?? result };\n }\n catch (error) {\n return catchError<T>(error);\n }\n }\n\n /**\n * Creates multiple documents with bulk insertion.\n *\n * @param docs - An array of documents to create.\n * @param options - Options for the bulk insertion operation.\n * @returns A promise that resolves to a standardized response with the created documents.\n */\n async createMany(\n docs: (T | Partial<T>)[],\n options: T_InsertManyOptions = {},\n ): Promise<I_Return<T[]>> {\n try {\n const createdDocuments = await this.model.insertMany(docs, options);\n\n return { success: true, result: createdDocuments.map(item => item?.toObject?.() ?? item) as T[] };\n }\n catch (error) {\n return catchError<T[]>(error);\n }\n }\n\n /**\n * Updates a single document and returns the updated version.\n *\n * @param filter - The filter criteria to find the document to update.\n * @param update - The update data to apply.\n * @param options - Options for the update operation.\n * @returns A promise that resolves to a standardized response with the updated document.\n */\n async updateOne(\n filter: T_QueryFilter<T> = {},\n update: T_UpdateQuery<T> = {},\n options: I_UpdateOptionsExtended = {},\n ): Promise<I_Return<T>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model\n .findOneAndUpdate(normalizedFilter, update, {\n new: true,\n ...options,\n })\n .exec();\n\n if (!result) {\n return {\n success: false,\n message: `Failed to update ${this.getModelName()}.`,\n code: RESPONSE_STATUS.NOT_FOUND.CODE,\n };\n }\n\n return { success: true, result: result?.toObject?.() ?? result };\n }\n catch (error) {\n return catchError<T>(error);\n }\n }\n\n /**\n * Updates multiple documents matching the filter criteria.\n *\n * @param filter - The filter criteria to find documents to update.\n * @param update - The update data to apply.\n * @param options - Options for the update operation.\n * @returns A promise that resolves to a standardized response with the update result.\n */\n async updateMany(\n filter: T_QueryFilter<T> = {},\n update: T_UpdateQuery<T> = {},\n options: I_UpdateOptionsExtended = {},\n ): Promise<I_Return<T_UpdateResult>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model\n .updateMany(normalizedFilter, update, options)\n .exec();\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<T_UpdateResult>(error);\n }\n }\n\n /**\n * Deletes a single document and returns the deleted version.\n *\n * @param filter - The filter criteria to find the document to delete.\n * @param options - Options for the delete operation.\n * @returns A promise that resolves to a standardized response with the deleted document.\n */\n async deleteOne(\n filter: T_QueryFilter<T> = {},\n options: I_DeleteOptionsExtended = {},\n ): Promise<I_Return<T>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model\n .findOneAndDelete(normalizedFilter, options)\n .exec();\n\n if (!result) {\n return {\n success: false,\n message: `No ${this.getModelName()} found to delete.`,\n code: RESPONSE_STATUS.NOT_FOUND.CODE,\n };\n }\n\n return { success: true, result: result?.toObject?.() ?? result };\n }\n catch (error) {\n return catchError<T>(error);\n }\n }\n\n /**\n * Deletes multiple documents matching the filter criteria.\n *\n * @param filter - The filter criteria to find documents to delete.\n * @param options - Options for the delete operation.\n * @returns A promise that resolves to a standardized response with the delete result.\n */\n async deleteMany(\n filter: T_QueryFilter<T> = {},\n options: I_DeleteOptionsExtended = {},\n ): Promise<I_Return<T_DeleteResult>> {\n try {\n const normalizedFilter = normalizeMongoFilter(filter);\n const result = await this.model.deleteMany(normalizedFilter, options).exec();\n\n if (result.deletedCount === 0) {\n return {\n success: false,\n message: `No documents found to delete.`,\n code: RESPONSE_STATUS.NOT_FOUND.CODE,\n };\n }\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<T_DeleteResult>(error);\n }\n }\n\n /**\n * Creates a unique short ID based on a given ID.\n * This method generates multiple short IDs with increasing lengths and finds the first available one.\n *\n * @param id - The base ID to generate short IDs from.\n * @param length - The initial length for short ID generation (default: 4).\n * @returns A promise that resolves to a standardized response with the unique short ID.\n */\n async createShortId(id: string, length = 4): Promise<I_Return<string>> {\n try {\n const maxRetries = 10;\n const shortIds = Array.from({ length: maxRetries }, (_, index) =>\n generateShortId(id, index + length));\n\n // Use a single $in query instead of 10 parallel exists() calls\n const existingDocs = await this.model\n .find({ shortId: { $in: shortIds } })\n .select('shortId')\n .lean();\n\n const existingShortIds = new Set(\n existingDocs.map((d: Record<string, unknown>) => d['shortId'] as string),\n );\n\n const availableShortId = shortIds.find(s => !existingShortIds.has(s));\n\n if (availableShortId) {\n return { success: true, result: availableShortId };\n }\n\n return {\n success: false,\n message: 'Failed to create a unique shortId',\n code: RESPONSE_STATUS.INTERNAL_SERVER_ERROR.CODE,\n };\n }\n catch (error) {\n return catchError<string>(error);\n }\n }\n\n /**\n * Creates a query for slug existence checking.\n * This method generates a query that checks for slug existence in both current and historical slug fields.\n *\n * @param options - Configuration for slug query generation including slug, field, and filter.\n * @param options.slug - The slug string to check for existence.\n * @param options.field - The field name for object-based slug checking.\n * @param options.isObject - Whether the slug is stored as an object with nested fields.\n * @param options.haveHistory - Whether to check historical slug fields for existence.\n * @param options.filter - Additional filter conditions to apply to the query.\n * @returns A MongoDB query object for checking slug existence.\n */\n createSlugQuery({ slug, field, isObject, haveHistory = false, filter }: I_Input_GenerateSlug<T>) {\n const baseFilter = { ...(filter ?? {}) };\n\n return isObject\n ? {\n ...baseFilter,\n $or: [\n { [`slug.${field}`]: slug },\n ...(haveHistory ? [{ slugHistory: { $elemMatch: { [`slug.${field}`]: slug } } }] : []),\n ],\n }\n : {\n ...baseFilter,\n $or: [\n { slug },\n ...(haveHistory ? [{ slugHistory: slug }] : []),\n ],\n };\n }\n\n /**\n * Creates a unique slug based on a given string.\n * This method generates multiple slug variations and finds the first available one.\n *\n * @param options - Configuration for slug generation including slug, field, and filter.\n * @param options.slug - The base slug string to make unique.\n * @param options.field - The field name for object-based slug checking.\n * @param options.isObject - Whether the slug is stored as an object with nested fields.\n * @param options.haveHistory - Whether to check historical slug fields for uniqueness.\n * @param options.filter - Additional filter conditions to apply when checking slug existence.\n * @returns A promise that resolves to a unique slug string.\n */\n async createUniqueSlug({ slug, field, isObject, haveHistory, filter }: I_Input_GenerateSlug<T>): Promise<string> {\n if (!slug || typeof slug !== 'string') {\n throw new Error('Invalid slug provided: must be a non-empty string');\n }\n\n const baseSlug = generateSlug(slug);\n\n const baseExists = await this.model.exists(\n this.createSlugQuery({ slug: baseSlug, field, isObject, haveHistory, filter }),\n );\n\n if (!baseExists) {\n return baseSlug;\n }\n\n // Batch query: check all slug variations in a single database call instead of N+1 sequential queries\n const variants = Array.from(\n { length: MONGO_SLUG_MAX_ATTEMPTS },\n (_, i) => `${baseSlug}-${i + 1}`,\n );\n\n const slugQueries = variants.map(s =>\n this.createSlugQuery({ slug: s, field, isObject, haveHistory, filter }),\n );\n\n const slugField = isObject ? `slug.${field as string}` : 'slug';\n const existingDocs = await this.model\n .find({ $or: slugQueries.map(q => q.$or).flat() })\n .select(slugField)\n .lean();\n\n const existingSlugs = new Set(\n existingDocs.map((d: Record<string, unknown>) => {\n if (isObject) {\n const slug = d['slug'] as Record<string, unknown> | undefined;\n return slug?.[field as string];\n }\n return d['slug'];\n }),\n );\n\n const available = variants.find(s => !existingSlugs.has(s));\n\n if (available) {\n return available;\n }\n\n const timestamp = Date.now();\n const randomSuffix = generateRandomString(6);\n\n return `${baseSlug}-${timestamp}-${randomSuffix}`;\n }\n\n /**\n * Creates a slug for a document field.\n * This method handles both simple string fields and object fields with nested slug generation.\n *\n * @param options - Configuration for slug creation including field, source document, and filter.\n * @param options.field - The field name to create a slug for.\n * @param options.from - The source document containing the field value.\n * @param options.haveHistory - Whether to check historical slug fields for uniqueness.\n * @param options.filter - Additional filter conditions to apply when checking slug existence.\n * @returns A promise that resolves to a standardized response with the created slug(s).\n */\n async createSlug<R = string>({ field, from, filter, haveHistory }: I_Input_CreateSlug<T>): Promise<I_Return<R>> {\n try {\n const fieldValue = from[field as keyof T];\n const isObjectValue = isObject(fieldValue);\n\n if (isObjectValue) {\n const uniqueSlug = Object.fromEntries(\n await Promise.all(\n Object.entries(fieldValue).map(async ([key, value]) => {\n const uniqueSlugForKey = await this.createUniqueSlug({\n slug: value as string,\n field: key,\n isObject: true,\n haveHistory,\n filter,\n });\n return [key, uniqueSlugForKey];\n }),\n ),\n );\n\n return { success: true, result: uniqueSlug as R };\n }\n\n const uniqueSlug = await this.createUniqueSlug({\n slug: fieldValue as string,\n field,\n isObject: false,\n haveHistory,\n filter,\n });\n\n return { success: true, result: uniqueSlug as R };\n }\n catch (error) {\n return catchError<R>(error);\n }\n }\n\n /**\n * Checks if a slug already exists in the collection.\n * This method verifies slug existence in both current and historical slug fields.\n *\n * @param options - Configuration for slug checking including slug, field, source document, and filter.\n * @param options.slug - The slug string to check for existence.\n * @param options.field - The field name for object-based slug checking.\n * @param options.from - The source document containing the field value.\n * @param options.haveHistory - Whether to check historical slug fields for existence.\n * @param options.filter - Additional filter conditions to apply to the query.\n * @returns A promise that resolves to a standardized response indicating whether the slug exists.\n */\n async checkSlug({ slug, field, from, filter, haveHistory }: I_Input_CheckSlug<T>): Promise<I_Return<boolean>> {\n try {\n const fieldValue = from[field as keyof T];\n const isObjectValue = isObject(fieldValue);\n\n if (isObjectValue) {\n const values = Object.values(fieldValue);\n const nestedSlugs = values.map(value => generateSlug(value as string));\n\n const existenceChecks = await Promise.all(\n nestedSlugs.map(nestedSlug =>\n this.model.exists(this.createSlugQuery({\n slug: nestedSlug,\n field,\n isObject: true,\n haveHistory,\n filter,\n })),\n ),\n );\n\n if (existenceChecks.some(exists => exists)) {\n return { success: true, result: true };\n }\n\n return { success: true, result: false };\n }\n\n const baseSlug = generateSlug(slug);\n const exists = await this.model.exists(this.createSlugQuery({\n slug: baseSlug,\n field,\n isObject: false,\n filter,\n }));\n\n return { success: true, result: exists !== null };\n }\n catch (error) {\n return catchError<boolean>(error);\n }\n }\n\n /**\n * Performs aggregation operations on the collection.\n *\n * @param pipeline - The aggregation pipeline stages to execute.\n * @returns A promise that resolves to a standardized response with the aggregation results.\n */\n async aggregate(pipeline: T_PipelineStage[]): Promise<I_Return<T[]>> {\n try {\n const result = await this.model.aggregate<T>(pipeline);\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<T[]>(error);\n }\n }\n\n /**\n * Retrieves distinct values for the specified key from the collection.\n *\n * @param key - The field for which to return distinct values.\n * @param filter - The filter query to apply (optional).\n * @param options - Additional options for the distinct operation (optional).\n * @returns A promise that resolves to a standardized response with the array of distinct values.\n */\n async distinct(\n key: string,\n filter: T_QueryFilter<T> = {},\n options: T_QueryOptions<T> = {},\n ): Promise<I_Return<unknown[]>> {\n try {\n const result = await this.model.distinct(key, filter, options);\n\n return { success: true, result };\n }\n catch (error) {\n return catchError<unknown[]>(error);\n }\n }\n}\n"],"mappings":"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"}
@@ -1,10 +1,6 @@
1
1
  import { default as mongooseRaw } from 'mongoose';
2
2
  import { I_ModelWithSchema } from './mongo.internal-types.js';
3
3
  import { I_DynamicVirtualConfig, I_DynamicVirtualOptions, T_Input_Populate } from './mongo.type.js';
4
- /**
5
- * Checks if value is object-like (e.g., objects, arrays, etc.), not null.
6
- */
7
- export declare function isObject(value: unknown): value is object;
8
4
  /**
9
5
  * Filters out dynamic virtuals from populate options to prevent Mongoose from trying to populate them.
10
6
  * This function creates a new populate configuration that only includes regular virtuals.
@@ -3,10 +3,7 @@ import { catchError as t } from "../log/log.util.js";
3
3
  import { convertEnumToModelName as n } from "./mongo.util.js";
4
4
  import { applyNestedPopulate as r } from "./mongo.populate.js";
5
5
  //#region src/node/mongo/mongo.dynamic-populate.ts
6
- function i(e) {
7
- return typeof e == "object" && !!e;
8
- }
9
- function a(e, t) {
6
+ function i(e, t) {
10
7
  if (!e || !t || t.length === 0) return e;
11
8
  let n = new Set(t.map((e) => e.name));
12
9
  if (Array.isArray(e)) {
@@ -27,7 +24,7 @@ function a(e, t) {
27
24
  }
28
25
  return e;
29
26
  }
30
- function o(e, r, i) {
27
+ function a(e, r, i) {
31
28
  if (!e.length || !r || !i?.ref) return [];
32
29
  let a = /* @__PURE__ */ new Map();
33
30
  return e.forEach((e) => {
@@ -47,13 +44,13 @@ function o(e, r, i) {
47
44
  docs: t
48
45
  }));
49
46
  }
50
- function s(e) {
47
+ function o(e) {
51
48
  return typeof e == "object" && !!e && "toObject" in e && typeof e.toObject == "function";
52
49
  }
53
- async function c(t, n, i, a, c, l) {
54
- if (!n.length || !i.length || !a) return n;
50
+ async function s(t, n, i, s, c, l) {
51
+ if (!n.length || !i.length || !s) return n;
55
52
  let u = i.filter((e) => {
56
- if (Array.isArray(a)) return a.length > 0 && a.some((t) => {
53
+ if (Array.isArray(s)) return s.length > 0 && s.some((t) => {
57
54
  if (typeof t == "string") return t === e.name || t.startsWith(`${e.name}.`);
58
55
  if (t && typeof t == "object") {
59
56
  let n = t, r = n.path || n.populate || "";
@@ -61,15 +58,15 @@ async function c(t, n, i, a, c, l) {
61
58
  }
62
59
  return !1;
63
60
  });
64
- if (typeof a == "string") return a === e.name || a.startsWith(`${e.name}.`);
65
- if (typeof a == "object" && a) {
66
- let t = a, n = t.path || t.populate || "";
61
+ if (typeof s == "string") return s === e.name || s.startsWith(`${e.name}.`);
62
+ if (typeof s == "object" && s) {
63
+ let t = s, n = t.path || t.populate || "";
67
64
  return n === e.name || n.startsWith(`${e.name}.`);
68
65
  }
69
66
  return !1;
70
67
  });
71
68
  if (u.length === 0) return n;
72
- let d = e(n.map((e) => s(e) ? e.toObject() : e));
69
+ let d = e(n.map((e) => o(e) ? e.toObject() : e));
73
70
  d.forEach((e) => {
74
71
  u.forEach(({ name: t, options: n }) => {
75
72
  t in e || (e[t] = n.count ? 0 : n.justOne ? null : []);
@@ -77,7 +74,7 @@ async function c(t, n, i, a, c, l) {
77
74
  });
78
75
  let f = /* @__PURE__ */ new Map();
79
76
  for (let e of u) {
80
- let { name: t, options: n } = e, r = o(d, t, n);
77
+ let { name: t, options: n } = e, r = a(d, t, n);
81
78
  for (let i of r) {
82
79
  f.has(i.model) || f.set(i.model, {
83
80
  virtuals: [],
@@ -139,7 +136,7 @@ async function c(t, n, i, a, c, l) {
139
136
  });
140
137
  }
141
138
  }
142
- })), a && await r(t, d, ((e) => {
139
+ })), s && await r(t, d, ((e) => {
143
140
  let t = Array.isArray(e) ? e : [e], n = /* @__PURE__ */ new Map(), r = [];
144
141
  for (let e of t) if (typeof e == "string") if (e.includes(".")) {
145
142
  let t = e.split("."), r = t[0] || "", i = t.slice(1).join(".");
@@ -161,9 +158,9 @@ async function c(t, n, i, a, c, l) {
161
158
  populate: n
162
159
  } : t);
163
160
  }), i;
164
- })(a), i, l), d;
161
+ })(s), i, l), d;
165
162
  }
166
163
  //#endregion
167
- export { a as filterDynamicVirtualsFromPopulate, s as isMongooseDoc, i as isObject, c as populateDynamicVirtuals, o as remapDynamicPopulate };
164
+ export { i as filterDynamicVirtualsFromPopulate, o as isMongooseDoc, s as populateDynamicVirtuals, a as remapDynamicPopulate };
168
165
 
169
166
  //# sourceMappingURL=mongo.dynamic-populate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mongo.dynamic-populate.js","names":[],"sources":["../../../src/node/mongo/mongo.dynamic-populate.ts"],"sourcesContent":["import type mongooseRaw from 'mongoose';\n\nimport { deepClone } from '#util/index.js';\n\nimport type { I_ModelWithSchema } from './mongo.internal-types.js';\nimport type { I_DynamicVirtualConfig, I_DynamicVirtualOptions, T_Input_Populate } from './mongo.type.js';\n\nimport { catchError } from '../log/index.js';\nimport { applyNestedPopulate } from './mongo.populate.js';\nimport { convertEnumToModelName } from './mongo.util.js';\n\n/**\n * Checks if value is object-like (e.g., objects, arrays, etc.), not null.\n */\nexport function isObject(value: unknown): value is object {\n return value != null && typeof value === 'object';\n}\n\n/**\n * Filters out dynamic virtuals from populate options to prevent Mongoose from trying to populate them.\n * This function creates a new populate configuration that only includes regular virtuals.\n *\n * @template T - The document type\n * @param populate - The original populate options\n * @param dynamicVirtuals - Array of dynamic virtual configurations\n * @returns Filtered populate options excluding dynamic virtuals\n */\nexport function filterDynamicVirtualsFromPopulate<T>(\n populate: T_Input_Populate | undefined,\n dynamicVirtuals: I_DynamicVirtualConfig<T>[] | undefined,\n): T_Input_Populate | undefined {\n if (!populate || !dynamicVirtuals || dynamicVirtuals.length === 0) {\n return populate;\n }\n\n const dynamicVirtualNames = new Set(dynamicVirtuals.map(v => v.name));\n\n if (Array.isArray(populate)) {\n const filtered = populate.filter((p) => {\n if (typeof p === 'string') {\n return ![...dynamicVirtualNames].some(virtualName =>\n p === virtualName || p.startsWith(`${virtualName}.`),\n );\n }\n\n if (typeof p === 'object' && p !== null) {\n const popObj = p as { path?: string; populate?: string };\n const path = popObj.path || popObj.populate || '';\n\n return ![...dynamicVirtualNames].some(virtualName =>\n path === virtualName || path.startsWith(`${virtualName}.`),\n );\n }\n\n return true;\n });\n\n return filtered.length > 0 ? (filtered as T_Input_Populate) : undefined;\n }\n\n if (typeof populate === 'string') {\n return [...dynamicVirtualNames].some(virtualName =>\n populate === virtualName || populate.startsWith(`${virtualName}.`),\n )\n ? undefined\n : populate;\n }\n\n if (typeof populate === 'object' && populate !== null) {\n const popObj = populate as { path?: string; populate?: string };\n const path = popObj.path || popObj.populate || '';\n\n return [...dynamicVirtualNames].some(virtualName =>\n path === virtualName || path.startsWith(`${virtualName}.`),\n )\n ? undefined\n : populate;\n }\n\n return populate;\n}\n\n/**\n * Groups documents by the resolved model name for a dynamic virtual field.\n * Used to batch population queries for dynamic virtuals.\n *\n * @template T - The document type\n * @template R - The model name type (usually string or enum)\n * @param {T[]} documents - The array of documents to process\n * @param {string} virtualName - The name of the dynamic virtual field\n * @param {I_DynamicVirtualOptions<T, R>} virtualOptions - The dynamic virtual options (must include a ref function)\n * @returns {Array<{ model: string; docs: T[] }>} An array of groups, each with a model name and the docs for that model\n */\nexport function remapDynamicPopulate<T, R extends string = string>(\n documents: T[],\n virtualName: string,\n virtualOptions: I_DynamicVirtualOptions<T, R>,\n): Array<{ model: string; docs: T[] }> {\n if (!documents.length || !virtualName || !virtualOptions?.ref) {\n return [];\n }\n\n const modelGroups = new Map<string, T[]>();\n documents.forEach((doc) => {\n try {\n const modelName = virtualOptions.ref(doc);\n\n if (modelName === undefined || modelName === null) {\n return;\n }\n\n const modelNameString = typeof modelName === 'string' ? modelName : String(modelName);\n\n if (modelNameString && modelNameString.trim() !== '') {\n const convertedModelName = convertEnumToModelName(modelNameString);\n\n if (!modelGroups.has(convertedModelName)) {\n modelGroups.set(convertedModelName, []);\n }\n\n modelGroups.get(convertedModelName)!.push(doc);\n }\n }\n catch (error) {\n catchError(new Error(`Dynamic ref function failed for virtual \"${virtualName}\": ${error instanceof Error ? error.message : String(error)}`));\n }\n });\n\n return Array.from(modelGroups.entries(), ([model, docs]) => ({ model, docs }));\n}\n\n/**\n * Type guard to check if an object is a Mongoose Document (has toObject method).\n * @param obj - The object to check\n * @returns True if obj has a toObject function\n */\nexport function isMongooseDoc(obj: unknown): obj is { toObject: () => { [key: string]: unknown } } {\n return obj !== null && typeof obj === 'object' && 'toObject' in obj && typeof (obj as { toObject: unknown }).toObject === 'function';\n}\n\n/**\n * Optimized batch population for dynamic virtuals.\n *\n * - Groups documents by model and batches queries for each model.\n * - Populates all dynamic virtuals in parallel for maximum performance.\n * - Uses direct assignment for plain objects and skips already populated fields.\n * - Supports optional projection for population queries.\n * - Only populates virtuals that are explicitly requested in populate options.\n * - Reminder: Ensure indexes exist on foreignField in referenced collections for best performance.\n *\n * @template T - The document type (must be an object)\n * @template R - The model name type (usually string or enum)\n * @param {typeof mongooseRaw} mongoose - The Mongoose instance\n * @param {T[]} documents - The array of documents to populate\n * @param {I_DynamicVirtualConfig<T, R>[]} virtualConfigs - The dynamic virtual configurations to process\n * @param {T_Input_Populate} [populate] - Population options to determine which virtuals to populate\n * @param {Record<string, 0 | 1>} [projection] - Optional projection for population queries\n * @returns {Promise<object[]>} The array of documents with dynamic virtuals populated\n */\nexport async function populateDynamicVirtuals<T extends object, R extends string = string>(\n mongoose: typeof mongooseRaw,\n documents: T[],\n virtualConfigs: I_DynamicVirtualConfig<T, R>[],\n populate?: T_Input_Populate,\n projection?: Record<string, 0 | 1>,\n startModel?: I_ModelWithSchema,\n): Promise<T[]> {\n if (!documents.length || !virtualConfigs.length) {\n return documents;\n }\n\n if (!populate) {\n return documents;\n }\n\n const requestedVirtuals = virtualConfigs.filter((config) => {\n if (Array.isArray(populate)) {\n return populate.length > 0 && populate.some((p) => {\n if (typeof p === 'string') {\n return p === config.name || p.startsWith(`${config.name}.`);\n }\n if (p && typeof p === 'object') {\n const popObj = p as { path?: string; populate?: string };\n const path = popObj.path || popObj.populate || '';\n\n return path === config.name || path.startsWith(`${config.name}.`);\n }\n\n return false;\n });\n }\n\n if (typeof populate === 'string') {\n return populate === config.name || populate.startsWith(`${config.name}.`);\n }\n\n if (typeof populate === 'object' && populate !== null) {\n const popObj = populate as { path?: string; populate?: string };\n const path = popObj.path || popObj.populate || '';\n\n return (path === config.name) || path.startsWith(`${config.name}.`);\n }\n\n return false;\n });\n\n if (requestedVirtuals.length === 0) {\n return documents;\n }\n\n // toObject() already creates plain copies for Mongoose docs; deepClone the\n // plain array to avoid mutating the caller's objects.\n const plainDocuments = documents.map(doc => isMongooseDoc(doc) ? doc.toObject() : doc);\n const clonedDocuments = deepClone(plainDocuments) as T[];\n\n clonedDocuments.forEach((doc) => {\n requestedVirtuals.forEach(({ name, options }) => {\n if (!(name in doc)) {\n (doc as { [key: string]: unknown })[name as string] = options.count ? 0 : (options.justOne ? null : []);\n }\n });\n });\n\n const modelProcessingMap = new Map<string, {\n virtuals: I_DynamicVirtualConfig<T, R>[];\n localValueSets: Map<string, Set<string>>;\n docsByLocalValue: Map<string, number[]>;\n }>();\n\n for (const virtualConfig of requestedVirtuals) {\n const { name, options } = virtualConfig;\n const populateGroups = remapDynamicPopulate(clonedDocuments, name, options);\n\n for (const group of populateGroups) {\n if (!modelProcessingMap.has(group.model)) {\n modelProcessingMap.set(group.model, {\n virtuals: [],\n localValueSets: new Map(),\n docsByLocalValue: new Map(),\n });\n }\n\n const processing = modelProcessingMap.get(group.model)!;\n\n if (!processing.virtuals.some(v => v.name === name)) {\n processing.virtuals.push(virtualConfig);\n processing.localValueSets.set(name as string, new Set());\n }\n\n const localValueSet = processing.localValueSets.get(name as string)!;\n group.docs.forEach((doc) => {\n const localVal = (doc as { [key: string]: unknown })[options.localField];\n\n if (localVal != null) {\n const strVal = String(localVal);\n localValueSet.add(strVal);\n\n let idx = -1;\n\n const docWithKeys = doc as { [key: string]: unknown };\n\n if (docWithKeys['id'] !== undefined) {\n idx = clonedDocuments.findIndex((d) => {\n const dWithKeys = d as { [key: string]: unknown };\n\n return dWithKeys['id'] === docWithKeys['id'];\n });\n }\n else if (docWithKeys['_id'] !== undefined) {\n idx = clonedDocuments.findIndex((d) => {\n const dWithKeys = d as { [key: string]: unknown };\n\n return dWithKeys['_id']?.toString?.() === docWithKeys['_id']?.toString?.();\n });\n }\n\n if (idx !== -1) {\n if (!processing.docsByLocalValue.has(strVal)) {\n processing.docsByLocalValue.set(strVal, []);\n }\n processing.docsByLocalValue.get(strVal)!.push(idx);\n }\n }\n });\n }\n }\n\n await Promise.all(Array.from(modelProcessingMap.entries(), async ([modelName, processing]) => {\n const Model = mongoose.models[modelName];\n\n if (!Model) {\n return;\n }\n\n const allLocalValues = new Set<string>();\n processing.localValueSets.forEach((localValueSet) => {\n localValueSet.forEach(val => allLocalValues.add(val));\n });\n\n if (allLocalValues.size === 0) {\n return;\n }\n\n const foreignFields = [...new Set(processing.virtuals.map(v => v.options.foreignField))];\n const localValuesArray = [...allLocalValues];\n let query;\n\n if (foreignFields.length === 1) {\n query = { [String(foreignFields[0])]: { $in: localValuesArray } };\n }\n else {\n query = { $or: foreignFields.map(field => ({ [field]: { $in: localValuesArray } })) };\n }\n\n const allPopulatedData = await Model.find(query, projection).lean();\n\n for (const virtualConfig of processing.virtuals) {\n const { name, options } = virtualConfig;\n const relevantData = allPopulatedData.filter((item) => {\n const foreignVal = (item)[options.foreignField];\n\n return foreignVal != null && allLocalValues.has(String(foreignVal));\n });\n\n if (options.count) {\n const countMap = new Map<string, number>();\n\n relevantData.forEach((item) => {\n const key = (item)[options.foreignField]?.toString();\n\n if (key) {\n countMap.set(key, (countMap.get(key) || 0) + 1);\n }\n });\n\n processing.localValueSets.get(name as string)!.forEach((localValue) => {\n const docs = processing.docsByLocalValue.get(localValue) || [];\n const count = countMap.get(localValue) || 0;\n\n docs.forEach((idx) => {\n const docToUpdate = clonedDocuments[idx] as { [key: string]: unknown };\n\n if (docToUpdate[name] === undefined) {\n docToUpdate[name] = count;\n }\n });\n });\n }\n else {\n const resultMap = new Map<string, T[]>();\n\n relevantData.forEach((item) => {\n const key = (item)[options.foreignField]?.toString();\n\n if (key) {\n if (!resultMap.has(key)) {\n resultMap.set(key, []);\n }\n resultMap.get(key)!.push(item);\n }\n });\n\n processing.localValueSets.get(name as string)!.forEach((localVal) => {\n const docs = processing.docsByLocalValue.get(localVal) || [];\n const results = resultMap.get(localVal) || [];\n const value = options.justOne ? (results[0] || null) : results;\n\n docs.forEach((idx) => {\n const docToUpdate = clonedDocuments[idx] as { [key: string]: unknown };\n docToUpdate[name] = value;\n });\n });\n }\n }\n }));\n\n if (populate) {\n const normalizePopulate = (pop: T_Input_Populate): T_Input_Populate => {\n const asArray = Array.isArray(pop) ? pop : [pop];\n const grouped = new Map<string, unknown[]>();\n const passthrough: unknown[] = [];\n\n for (const entry of asArray) {\n if (typeof entry === 'string') {\n if (entry.includes('.')) {\n const parts = entry.split('.');\n const first = parts[0] || '';\n const rest = parts.slice(1).join('.');\n\n if (first) {\n if (!grouped.has(first)) {\n grouped.set(first, []);\n }\n if (rest) {\n grouped.get(first)!.push(rest);\n }\n }\n }\n else {\n passthrough.push(entry);\n }\n }\n else if (entry && typeof entry === 'object') {\n const obj = entry as { path?: string; populate?: T_Input_Populate };\n\n if (obj.path && obj.path.includes('.')) {\n const parts = obj.path.split('.');\n const first = parts[0] || '';\n const rest = parts.slice(1).join('.');\n\n if (first) {\n if (!grouped.has(first)) {\n grouped.set(first, []);\n }\n if (rest) {\n grouped.get(first)!.push(rest);\n }\n if (obj.populate) {\n grouped.get(first)!.push(obj.populate);\n }\n }\n }\n else {\n passthrough.push(entry);\n }\n }\n }\n\n const normalized: unknown[] = [...passthrough];\n grouped.forEach((nested, root) => {\n const flat: unknown[] = [];\n\n for (const n of nested) {\n if (typeof n === 'string') {\n flat.push(n);\n }\n else if (n && typeof n === 'object') {\n flat.push(n);\n }\n }\n if (flat.length > 0) {\n normalized.push({ path: root, populate: flat as unknown as T_Input_Populate });\n }\n else {\n normalized.push(root);\n }\n });\n\n return normalized as unknown as T_Input_Populate;\n };\n\n const normalizedPopulate = normalizePopulate(populate);\n\n await applyNestedPopulate(mongoose, clonedDocuments, normalizedPopulate, virtualConfigs as I_DynamicVirtualConfig<unknown, string>[], startModel);\n }\n\n return clonedDocuments;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mongo.dynamic-populate.js","names":[],"sources":["../../../src/node/mongo/mongo.dynamic-populate.ts"],"sourcesContent":["import type mongooseRaw from 'mongoose';\n\nimport { deepClone } from '#util/index.js';\n\nimport type { I_ModelWithSchema } from './mongo.internal-types.js';\nimport type { I_DynamicVirtualConfig, I_DynamicVirtualOptions, T_Input_Populate } from './mongo.type.js';\n\nimport { catchError } from '../log/index.js';\nimport { applyNestedPopulate } from './mongo.populate.js';\nimport { convertEnumToModelName } from './mongo.util.js';\n\n/**\n * Filters out dynamic virtuals from populate options to prevent Mongoose from trying to populate them.\n * This function creates a new populate configuration that only includes regular virtuals.\n *\n * @template T - The document type\n * @param populate - The original populate options\n * @param dynamicVirtuals - Array of dynamic virtual configurations\n * @returns Filtered populate options excluding dynamic virtuals\n */\nexport function filterDynamicVirtualsFromPopulate<T>(\n populate: T_Input_Populate | undefined,\n dynamicVirtuals: I_DynamicVirtualConfig<T>[] | undefined,\n): T_Input_Populate | undefined {\n if (!populate || !dynamicVirtuals || dynamicVirtuals.length === 0) {\n return populate;\n }\n\n const dynamicVirtualNames = new Set(dynamicVirtuals.map(v => v.name));\n\n if (Array.isArray(populate)) {\n const filtered = populate.filter((p) => {\n if (typeof p === 'string') {\n return ![...dynamicVirtualNames].some(virtualName =>\n p === virtualName || p.startsWith(`${virtualName}.`),\n );\n }\n\n if (typeof p === 'object' && p !== null) {\n const popObj = p as { path?: string; populate?: string };\n const path = popObj.path || popObj.populate || '';\n\n return ![...dynamicVirtualNames].some(virtualName =>\n path === virtualName || path.startsWith(`${virtualName}.`),\n );\n }\n\n return true;\n });\n\n return filtered.length > 0 ? (filtered as T_Input_Populate) : undefined;\n }\n\n if (typeof populate === 'string') {\n return [...dynamicVirtualNames].some(virtualName =>\n populate === virtualName || populate.startsWith(`${virtualName}.`),\n )\n ? undefined\n : populate;\n }\n\n if (typeof populate === 'object' && populate !== null) {\n const popObj = populate as { path?: string; populate?: string };\n const path = popObj.path || popObj.populate || '';\n\n return [...dynamicVirtualNames].some(virtualName =>\n path === virtualName || path.startsWith(`${virtualName}.`),\n )\n ? undefined\n : populate;\n }\n\n return populate;\n}\n\n/**\n * Groups documents by the resolved model name for a dynamic virtual field.\n * Used to batch population queries for dynamic virtuals.\n *\n * @template T - The document type\n * @template R - The model name type (usually string or enum)\n * @param {T[]} documents - The array of documents to process\n * @param {string} virtualName - The name of the dynamic virtual field\n * @param {I_DynamicVirtualOptions<T, R>} virtualOptions - The dynamic virtual options (must include a ref function)\n * @returns {Array<{ model: string; docs: T[] }>} An array of groups, each with a model name and the docs for that model\n */\nexport function remapDynamicPopulate<T, R extends string = string>(\n documents: T[],\n virtualName: string,\n virtualOptions: I_DynamicVirtualOptions<T, R>,\n): Array<{ model: string; docs: T[] }> {\n if (!documents.length || !virtualName || !virtualOptions?.ref) {\n return [];\n }\n\n const modelGroups = new Map<string, T[]>();\n documents.forEach((doc) => {\n try {\n const modelName = virtualOptions.ref(doc);\n\n if (modelName === undefined || modelName === null) {\n return;\n }\n\n const modelNameString = typeof modelName === 'string' ? modelName : String(modelName);\n\n if (modelNameString && modelNameString.trim() !== '') {\n const convertedModelName = convertEnumToModelName(modelNameString);\n\n if (!modelGroups.has(convertedModelName)) {\n modelGroups.set(convertedModelName, []);\n }\n\n modelGroups.get(convertedModelName)!.push(doc);\n }\n }\n catch (error) {\n catchError(new Error(`Dynamic ref function failed for virtual \"${virtualName}\": ${error instanceof Error ? error.message : String(error)}`));\n }\n });\n\n return Array.from(modelGroups.entries(), ([model, docs]) => ({ model, docs }));\n}\n\n/**\n * Type guard to check if an object is a Mongoose Document (has toObject method).\n * @param obj - The object to check\n * @returns True if obj has a toObject function\n */\nexport function isMongooseDoc(obj: unknown): obj is { toObject: () => { [key: string]: unknown } } {\n return obj !== null && typeof obj === 'object' && 'toObject' in obj && typeof (obj as { toObject: unknown }).toObject === 'function';\n}\n\n/**\n * Optimized batch population for dynamic virtuals.\n *\n * - Groups documents by model and batches queries for each model.\n * - Populates all dynamic virtuals in parallel for maximum performance.\n * - Uses direct assignment for plain objects and skips already populated fields.\n * - Supports optional projection for population queries.\n * - Only populates virtuals that are explicitly requested in populate options.\n * - Reminder: Ensure indexes exist on foreignField in referenced collections for best performance.\n *\n * @template T - The document type (must be an object)\n * @template R - The model name type (usually string or enum)\n * @param {typeof mongooseRaw} mongoose - The Mongoose instance\n * @param {T[]} documents - The array of documents to populate\n * @param {I_DynamicVirtualConfig<T, R>[]} virtualConfigs - The dynamic virtual configurations to process\n * @param {T_Input_Populate} [populate] - Population options to determine which virtuals to populate\n * @param {Record<string, 0 | 1>} [projection] - Optional projection for population queries\n * @returns {Promise<object[]>} The array of documents with dynamic virtuals populated\n */\nexport async function populateDynamicVirtuals<T extends object, R extends string = string>(\n mongoose: typeof mongooseRaw,\n documents: T[],\n virtualConfigs: I_DynamicVirtualConfig<T, R>[],\n populate?: T_Input_Populate,\n projection?: Record<string, 0 | 1>,\n startModel?: I_ModelWithSchema,\n): Promise<T[]> {\n if (!documents.length || !virtualConfigs.length) {\n return documents;\n }\n\n if (!populate) {\n return documents;\n }\n\n const requestedVirtuals = virtualConfigs.filter((config) => {\n if (Array.isArray(populate)) {\n return populate.length > 0 && populate.some((p) => {\n if (typeof p === 'string') {\n return p === config.name || p.startsWith(`${config.name}.`);\n }\n if (p && typeof p === 'object') {\n const popObj = p as { path?: string; populate?: string };\n const path = popObj.path || popObj.populate || '';\n\n return path === config.name || path.startsWith(`${config.name}.`);\n }\n\n return false;\n });\n }\n\n if (typeof populate === 'string') {\n return populate === config.name || populate.startsWith(`${config.name}.`);\n }\n\n if (typeof populate === 'object' && populate !== null) {\n const popObj = populate as { path?: string; populate?: string };\n const path = popObj.path || popObj.populate || '';\n\n return (path === config.name) || path.startsWith(`${config.name}.`);\n }\n\n return false;\n });\n\n if (requestedVirtuals.length === 0) {\n return documents;\n }\n\n // toObject() already creates plain copies for Mongoose docs; deepClone the\n // plain array to avoid mutating the caller's objects.\n const plainDocuments = documents.map(doc => isMongooseDoc(doc) ? doc.toObject() : doc);\n const clonedDocuments = deepClone(plainDocuments) as T[];\n\n clonedDocuments.forEach((doc) => {\n requestedVirtuals.forEach(({ name, options }) => {\n if (!(name in doc)) {\n (doc as { [key: string]: unknown })[name as string] = options.count ? 0 : (options.justOne ? null : []);\n }\n });\n });\n\n const modelProcessingMap = new Map<string, {\n virtuals: I_DynamicVirtualConfig<T, R>[];\n localValueSets: Map<string, Set<string>>;\n docsByLocalValue: Map<string, number[]>;\n }>();\n\n for (const virtualConfig of requestedVirtuals) {\n const { name, options } = virtualConfig;\n const populateGroups = remapDynamicPopulate(clonedDocuments, name, options);\n\n for (const group of populateGroups) {\n if (!modelProcessingMap.has(group.model)) {\n modelProcessingMap.set(group.model, {\n virtuals: [],\n localValueSets: new Map(),\n docsByLocalValue: new Map(),\n });\n }\n\n const processing = modelProcessingMap.get(group.model)!;\n\n if (!processing.virtuals.some(v => v.name === name)) {\n processing.virtuals.push(virtualConfig);\n processing.localValueSets.set(name as string, new Set());\n }\n\n const localValueSet = processing.localValueSets.get(name as string)!;\n group.docs.forEach((doc) => {\n const localVal = (doc as { [key: string]: unknown })[options.localField];\n\n if (localVal != null) {\n const strVal = String(localVal);\n localValueSet.add(strVal);\n\n let idx = -1;\n\n const docWithKeys = doc as { [key: string]: unknown };\n\n if (docWithKeys['id'] !== undefined) {\n idx = clonedDocuments.findIndex((d) => {\n const dWithKeys = d as { [key: string]: unknown };\n\n return dWithKeys['id'] === docWithKeys['id'];\n });\n }\n else if (docWithKeys['_id'] !== undefined) {\n idx = clonedDocuments.findIndex((d) => {\n const dWithKeys = d as { [key: string]: unknown };\n\n return dWithKeys['_id']?.toString?.() === docWithKeys['_id']?.toString?.();\n });\n }\n\n if (idx !== -1) {\n if (!processing.docsByLocalValue.has(strVal)) {\n processing.docsByLocalValue.set(strVal, []);\n }\n processing.docsByLocalValue.get(strVal)!.push(idx);\n }\n }\n });\n }\n }\n\n await Promise.all(Array.from(modelProcessingMap.entries(), async ([modelName, processing]) => {\n const Model = mongoose.models[modelName];\n\n if (!Model) {\n return;\n }\n\n const allLocalValues = new Set<string>();\n processing.localValueSets.forEach((localValueSet) => {\n localValueSet.forEach(val => allLocalValues.add(val));\n });\n\n if (allLocalValues.size === 0) {\n return;\n }\n\n const foreignFields = [...new Set(processing.virtuals.map(v => v.options.foreignField))];\n const localValuesArray = [...allLocalValues];\n let query;\n\n if (foreignFields.length === 1) {\n query = { [String(foreignFields[0])]: { $in: localValuesArray } };\n }\n else {\n query = { $or: foreignFields.map(field => ({ [field]: { $in: localValuesArray } })) };\n }\n\n const allPopulatedData = await Model.find(query, projection).lean();\n\n for (const virtualConfig of processing.virtuals) {\n const { name, options } = virtualConfig;\n const relevantData = allPopulatedData.filter((item) => {\n const foreignVal = (item)[options.foreignField];\n\n return foreignVal != null && allLocalValues.has(String(foreignVal));\n });\n\n if (options.count) {\n const countMap = new Map<string, number>();\n\n relevantData.forEach((item) => {\n const key = (item)[options.foreignField]?.toString();\n\n if (key) {\n countMap.set(key, (countMap.get(key) || 0) + 1);\n }\n });\n\n processing.localValueSets.get(name as string)!.forEach((localValue) => {\n const docs = processing.docsByLocalValue.get(localValue) || [];\n const count = countMap.get(localValue) || 0;\n\n docs.forEach((idx) => {\n const docToUpdate = clonedDocuments[idx] as { [key: string]: unknown };\n\n if (docToUpdate[name] === undefined) {\n docToUpdate[name] = count;\n }\n });\n });\n }\n else {\n const resultMap = new Map<string, T[]>();\n\n relevantData.forEach((item) => {\n const key = (item)[options.foreignField]?.toString();\n\n if (key) {\n if (!resultMap.has(key)) {\n resultMap.set(key, []);\n }\n resultMap.get(key)!.push(item);\n }\n });\n\n processing.localValueSets.get(name as string)!.forEach((localVal) => {\n const docs = processing.docsByLocalValue.get(localVal) || [];\n const results = resultMap.get(localVal) || [];\n const value = options.justOne ? (results[0] || null) : results;\n\n docs.forEach((idx) => {\n const docToUpdate = clonedDocuments[idx] as { [key: string]: unknown };\n docToUpdate[name] = value;\n });\n });\n }\n }\n }));\n\n if (populate) {\n const normalizePopulate = (pop: T_Input_Populate): T_Input_Populate => {\n const asArray = Array.isArray(pop) ? pop : [pop];\n const grouped = new Map<string, unknown[]>();\n const passthrough: unknown[] = [];\n\n for (const entry of asArray) {\n if (typeof entry === 'string') {\n if (entry.includes('.')) {\n const parts = entry.split('.');\n const first = parts[0] || '';\n const rest = parts.slice(1).join('.');\n\n if (first) {\n if (!grouped.has(first)) {\n grouped.set(first, []);\n }\n if (rest) {\n grouped.get(first)!.push(rest);\n }\n }\n }\n else {\n passthrough.push(entry);\n }\n }\n else if (entry && typeof entry === 'object') {\n const obj = entry as { path?: string; populate?: T_Input_Populate };\n\n if (obj.path && obj.path.includes('.')) {\n const parts = obj.path.split('.');\n const first = parts[0] || '';\n const rest = parts.slice(1).join('.');\n\n if (first) {\n if (!grouped.has(first)) {\n grouped.set(first, []);\n }\n if (rest) {\n grouped.get(first)!.push(rest);\n }\n if (obj.populate) {\n grouped.get(first)!.push(obj.populate);\n }\n }\n }\n else {\n passthrough.push(entry);\n }\n }\n }\n\n const normalized: unknown[] = [...passthrough];\n grouped.forEach((nested, root) => {\n const flat: unknown[] = [];\n\n for (const n of nested) {\n if (typeof n === 'string') {\n flat.push(n);\n }\n else if (n && typeof n === 'object') {\n flat.push(n);\n }\n }\n if (flat.length > 0) {\n normalized.push({ path: root, populate: flat as unknown as T_Input_Populate });\n }\n else {\n normalized.push(root);\n }\n });\n\n return normalized as unknown as T_Input_Populate;\n };\n\n const normalizedPopulate = normalizePopulate(populate);\n\n await applyNestedPopulate(mongoose, clonedDocuments, normalizedPopulate, virtualConfigs as I_DynamicVirtualConfig<unknown, string>[], startModel);\n }\n\n return clonedDocuments;\n}\n"],"mappings":"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"}
@@ -124,11 +124,13 @@ var f = {
124
124
  async getNewRecords(e, t, n, r = {}) {
125
125
  let i = await e.findAll(r);
126
126
  if (!i.success) throw Error(`Failed to query existing records: ${i.message}`);
127
+ if ("truncated" in i && i.truncated) throw Error("getNewRecords: Results were truncated by the default limit. Use pagination or set an explicit limit to ensure complete data.");
127
128
  return t.filter((e) => !i.result.some((t) => n(t, e)));
128
129
  },
129
130
  async getExistingRecords(e, t, n, r = {}) {
130
131
  let i = await e.findAll(r);
131
132
  if (!i.success) throw Error(`Failed to query existing records: ${i.message}`);
133
+ if ("truncated" in i && i.truncated) throw Error("getExistingRecords: Results were truncated by the default limit. Use pagination or set an explicit limit to ensure complete data.");
132
134
  return i.result.filter((e) => t.some((t) => n(e, t)));
133
135
  }
134
136
  };
@@ -1 +1 @@
1
- {"version":3,"file":"mongo.util.js","names":[],"sources":["../../../src/node/mongo/mongo.util.ts"],"sourcesContent":["import type migrate from 'migrate-mongo';\nimport type mongooseRaw from 'mongoose';\n\nimport aggregatePaginate from 'mongoose-aggregate-paginate-v2';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport { randomUUID } from 'node:crypto';\n\nimport { getNestedValue, regexSearchMapper, setNestedValue } from '#util/index.js';\nimport { validate } from '#util/validate/index.js';\n\nimport type { MongoController } from './mongo.controller.js';\nimport type { C_Document, I_CreateModelOptions, I_CreateSchemaOptions, I_DynamicVirtualConfig, I_DynamicVirtualOptions, I_ExtendedModel, I_GenericDocument, I_MongooseModelMiddleware, T_Filter, T_MongoosePlugin, T_MongooseSchema, T_QueryFilter, T_VirtualOptions, T_WithId } from './mongo.type.js';\n\nimport { addGitIgnoreEntry, writeFileSync } from '../fs/index.js';\nimport { MIGRATE_MONGO_CONFIG, PATH } from '../path/index.js';\n\n/**\n * Converts enum values to proper model names.\n * Handles common naming conventions like converting 'USER' to 'User'.\n *\n * @param enumValue - The enum value to convert\n * @returns The converted model name\n */\nexport function convertEnumToModelName(enumValue: string): string {\n if (enumValue === enumValue.toUpperCase()) {\n return enumValue.charAt(0).toUpperCase() + enumValue.slice(1).toLowerCase();\n }\n\n return enumValue;\n}\n\n/**\n * Interface for the MongoDB utility object to enable explicit type annotation.\n * Required to avoid TS7056 (inferred type exceeds maximum serialization length).\n */\ninterface I_MongoUtils {\n createGenericFields: () => I_GenericDocument;\n applyPlugins: <T>(schema: T_MongooseSchema<T>, plugins: Array<T_MongoosePlugin | false>) => void;\n applyMiddlewares: <T extends Partial<C_Document>>(schema: T_MongooseSchema<T>, middlewares: I_MongooseModelMiddleware<T>[]) => void;\n createGenericSchema: (mongoose: typeof mongooseRaw) => T_MongooseSchema<I_GenericDocument>;\n createSchema: <T, R extends string = string>(options: I_CreateSchemaOptions<T, R>) => T_MongooseSchema<T>;\n createModel: <T extends Partial<C_Document>, R extends string = string>(options: I_CreateModelOptions<T, R>) => I_ExtendedModel<T>;\n validator: {\n isRequired: <T>() => (this: T, value: unknown) => Promise<boolean>;\n isUnique: <T extends { constructor: { exists: (query: { [key: string]: unknown }) => Promise<unknown> } }>(fields: string[]) => (this: T, value: unknown) => Promise<boolean>;\n matchesRegex: (regexArray: RegExp[]) => (value: string) => Promise<boolean>;\n };\n migrate: {\n getModule: () => Promise<typeof migrate>;\n setConfig: (options: Partial<migrate.config.Config> & { moduleSystem?: 'commonjs' | 'esm' }) => void;\n };\n regexify: <T>(filter?: T_QueryFilter<T>, fields?: (keyof T | string)[]) => T_QueryFilter<T>;\n isDynamicVirtual: <T, R extends string>(options?: T_VirtualOptions<T, R>) => options is I_DynamicVirtualOptions<T, R>;\n getNewRecords: <T extends I_GenericDocument>(controller: MongoController<T>, recordsToCheck: T[], filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean, filter?: T_Filter<T>) => Promise<T[]>;\n getExistingRecords: <T extends I_GenericDocument>(controller: MongoController<T>, recordsToCheck: T[], filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean, filter?: T_Filter<T>) => Promise<T_WithId<T>[]>;\n}\n\n/**\n * MongoDB utility object providing comprehensive database operations and utilities.\n * This object contains methods for creating generic fields, applying plugins and middlewares,\n * creating schemas and models, validation functions, migration utilities, and regex filtering.\n */\nexport const mongo: I_MongoUtils = {\n /**\n * Creates generic fields that are commonly used across MongoDB documents.\n * This function generates standard fields including a UUID, deletion flag, and timestamps\n * that can be applied to any document schema.\n *\n * @returns An object containing generic document fields (id, isDel, createdAt, updatedAt).\n */\n createGenericFields(): I_GenericDocument {\n return {\n id: randomUUID(),\n isDel: false,\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n },\n /**\n * Applies plugins to a Mongoose schema.\n * This function filters out falsy plugins and applies the remaining valid plugins\n * to the provided schema.\n *\n * @param schema - The Mongoose schema to apply plugins to.\n * @param plugins - An array of plugin functions or false values to filter and apply.\n */\n applyPlugins<T>(schema: T_MongooseSchema<T>, plugins: Array<T_MongoosePlugin | false>) {\n plugins\n .filter((plugin): plugin is T_MongoosePlugin => typeof plugin === 'function')\n .forEach(plugin => schema.plugin(plugin));\n },\n /**\n * Applies middleware functions to a Mongoose schema.\n * This function configures pre and post middleware for specified methods on the schema.\n *\n * @param schema - The Mongoose schema to apply middleware to.\n * @param middlewares - An array of middleware configurations with method, pre, and post functions.\n */\n applyMiddlewares<T extends Partial<C_Document>>(\n schema: T_MongooseSchema<T>,\n middlewares: I_MongooseModelMiddleware<T>[],\n ) {\n middlewares.forEach(({ method, pre, post }) => {\n if (method && pre) {\n schema.pre(method as RegExp, pre);\n }\n\n if (method && post) {\n schema.post(method as RegExp, post);\n }\n });\n },\n /**\n * Creates a generic Mongoose schema with common fields.\n * This function creates a base schema with UUID field and deletion flag,\n * configured with automatic timestamps.\n *\n * @param mongoose - The Mongoose instance to create the schema with.\n * @returns A Mongoose schema with generic document fields.\n */\n createGenericSchema(mongoose: typeof mongooseRaw) {\n return new mongoose.Schema<I_GenericDocument>(\n {\n id: { type: String, default: () => randomUUID(), unique: true },\n isDel: { type: Boolean, default: false },\n },\n { timestamps: true },\n );\n },\n /**\n * Creates a Mongoose schema with optional virtual fields and generic fields.\n * This function creates a new Mongoose schema from the provided schema definition,\n * optionally adds virtual fields, and includes generic fields (unless standalone is true).\n *\n * @param options - Configuration options including mongoose instance, schema definition, virtuals, and standalone flag.\n * @param options.mongoose - The Mongoose instance to use for schema creation.\n * @param options.schema - The schema definition object.\n * @param options.virtuals - Optional array of virtual field configurations.\n * @param options.standalone - Whether to exclude generic fields (default: false).\n * @returns A configured Mongoose schema.\n */\n createSchema<T, R extends string = string>({\n mongoose,\n schema,\n virtuals = [],\n standalone = false,\n }: I_CreateSchemaOptions<T, R>): T_MongooseSchema<T> {\n const createdSchema = new mongoose.Schema<T>(schema, {\n toJSON: { virtuals: true }, // So `res.json()` and other `JSON.stringify()` functions include virtuals\n toObject: { virtuals: true }, // So `console.log()` and other functions that use `toObject()` include virtuals\n });\n\n virtuals.forEach(({ name, options, get }) => {\n if (mongo.isDynamicVirtual<T, R>(options)) {\n const schemaStatics = createdSchema.statics as Record<string, unknown>;\n\n if (!schemaStatics['_dynamicVirtuals']) {\n schemaStatics['_dynamicVirtuals'] = [];\n }\n\n (schemaStatics['_dynamicVirtuals'] as I_DynamicVirtualConfig<T>[]).push({\n name: name as string,\n options,\n });\n\n const virtualInstance = createdSchema.virtual(name as string);\n\n if (get) {\n virtualInstance.get(get);\n }\n else {\n virtualInstance.get(function (this: T & { _populated?: { [key: string]: unknown } }) {\n return this._populated?.[name as string] || (options?.count ? 0 : (options?.justOne ? null : []));\n });\n }\n }\n else {\n const virtualInstance = createdSchema.virtual(name as string, options);\n\n if (get) {\n virtualInstance.get(get);\n }\n }\n });\n\n if (!standalone) {\n createdSchema.add(mongo.createGenericSchema(mongoose));\n }\n\n return createdSchema;\n },\n /**\n * Creates a Mongoose model with plugins, middleware, and pagination support.\n * This function creates a model from a schema with optional pagination and aggregation plugins,\n * and applies any specified middleware. If a model with the same name already exists, it returns the existing model.\n *\n * @param options - Configuration options including mongoose instance, model name, schema, and feature flags.\n * @param options.mongoose - The Mongoose instance to use for model creation.\n * @param options.name - The name of the model to create.\n * @param options.schema - The schema definition for the model.\n * @param options.pagination - Whether to enable pagination plugin (default: false).\n * @param options.aggregate - Whether to enable aggregation pagination plugin (default: false).\n * @param options.virtuals - Optional array of virtual field configurations.\n * @param options.middlewares - Optional array of middleware configurations.\n * @returns A configured Mongoose model with extended functionality.\n * @throws {Error} When the model name is not provided.\n */\n createModel<T extends Partial<C_Document>, R extends string = string>({\n mongoose: currentMongooseInstance,\n name,\n schema,\n virtuals = [],\n pagination = true,\n aggregate = true,\n middlewares = [],\n }: I_CreateModelOptions<T, R>): I_ExtendedModel<T> {\n if (!name) {\n throw new Error('Model name is required.');\n }\n\n if (currentMongooseInstance.models[name]) {\n return currentMongooseInstance.models[name] as I_ExtendedModel<T>;\n }\n\n const createdSchema = mongo.createSchema({ mongoose: currentMongooseInstance, schema, virtuals });\n\n if (pagination || aggregate) {\n mongo.applyPlugins<T>(createdSchema, [\n pagination && mongoosePaginate,\n aggregate && aggregatePaginate,\n ]);\n }\n\n mongo.applyMiddlewares<T>(createdSchema, middlewares);\n\n const model = currentMongooseInstance.model<T>(name, createdSchema) as I_ExtendedModel<T>;\n\n if (virtuals.length > 0) {\n // Mongoose Model has no typed _virtualConfigs; used by MongooseController.getDynamicVirtuals()\n (model as any)._virtualConfigs = virtuals;\n }\n\n return model;\n },\n /**\n * Validation utilities for Mongoose schemas.\n * This object provides common validation functions that can be used in Mongoose schema definitions.\n */\n validator: {\n /**\n * Creates a required field validator.\n * This function returns a validator that checks if a field value is not empty\n * using the validate.isEmpty utility.\n *\n * @returns A validation function that returns true if the field is not empty.\n */\n isRequired<T>(): (this: T, value: unknown) => Promise<boolean> {\n return async function (this: T, value: unknown): Promise<boolean> {\n return !validate.isEmpty(value);\n };\n },\n /**\n * Creates a unique field validator.\n * This function returns a validator that checks if a field value is unique\n * across the specified fields in the collection.\n *\n * @param fields - An array of field names to check for uniqueness.\n * @returns A validation function that returns true if the value is unique across the specified fields.\n * @throws {Error} When fields is not a non-empty array of strings.\n */\n isUnique<T extends { constructor: { exists: (query: { [key: string]: unknown }) => Promise<unknown> } }>(fields: string[]) {\n return async function (this: T, value: unknown): Promise<boolean> {\n if (!Array.isArray(fields) || fields.length === 0) {\n throw new Error('Fields must be a non-empty array of strings.');\n }\n\n const query = { $or: fields.map(field => ({ [field]: value })) };\n const existingDocument = await this.constructor.exists(query);\n\n return !existingDocument;\n };\n },\n /**\n * Creates a regex pattern validator.\n * This function returns a validator that checks if a string value matches\n * all provided regular expressions.\n *\n * @param regexArray - An array of regular expressions to test against the value.\n * @returns A validation function that returns true if the value matches all regex patterns.\n * @throws {Error} When regexArray is not an array of valid RegExp objects.\n */\n matchesRegex(regexArray: RegExp[]): (value: string) => Promise<boolean> {\n return async (value: string): Promise<boolean> => {\n if (!Array.isArray(regexArray) || regexArray.some(r => !(r instanceof RegExp))) {\n throw new Error('regexArray must be an array of valid RegExp objects.');\n }\n\n return regexArray.every(regex => regex.test(value));\n };\n },\n },\n /**\n * Migration utilities for MongoDB.\n * This object extends the migrate-mongo library with additional configuration utilities.\n */\n migrate: {\n /**\n * Lazily loads the migrate-mongo module to avoid eager import overhead.\n * Use this to access migrate-mongo methods (up, down, status, create) programmatically.\n *\n * @returns A promise resolving to the migrate-mongo module.\n */\n async getModule(): Promise<typeof migrate> {\n return (await import('migrate-mongo')).default;\n },\n /**\n * Sets the migration configuration and updates .gitignore.\n * This function creates a migration configuration file and ensures it's properly\n * excluded from version control.\n *\n * @param options - Migration configuration options to write to the config file.\n */\n setConfig: (options: Partial<migrate.config.Config> & { moduleSystem?: 'commonjs' | 'esm' }) => {\n const optionsJS = `// This file is automatically generated by the Cyberskill CLI.\\nmodule.exports = ${JSON.stringify(options, null, 4)}`;\n\n writeFileSync(PATH.MIGRATE_MONGO_CONFIG, optionsJS);\n\n addGitIgnoreEntry(PATH.GIT_IGNORE, MIGRATE_MONGO_CONFIG);\n },\n },\n /**\n * Converts string values in a filter to regex patterns for case-insensitive search.\n * This function recursively processes a filter object and converts string values in specified fields\n * to MongoDB regex patterns that support accented character matching.\n *\n * @remarks\n * **Performance guard:** Input strings are capped at 200 characters to mitigate ReDoS risk.\n * The generated regex patterns include accented character alternation groups (e.g., `(a|à|á|...)`)\n * which can be polynomially complex for very long inputs. For production search on large collections,\n * consider using MongoDB `$text` search indexes instead of `$regex` for better performance.\n *\n * @param filter - The filter object to process.\n * @param fields - An array of field names to convert to regex patterns.\n * @returns A new filter object with string values converted to regex patterns.\n */\n regexify<T>(filter?: T_QueryFilter<T>, fields?: (keyof T | string)[]): T_QueryFilter<T> {\n if (!filter) {\n return {} as T_QueryFilter<T>;\n }\n\n let newFilter = { ...filter };\n\n if (!fields || fields.length === 0) {\n return newFilter;\n }\n\n const MAX_REGEX_INPUT_LENGTH = 200;\n\n for (const field of fields) {\n const path = field.toString().split('.');\n const value = getNestedValue(newFilter, path);\n\n if (typeof value === 'string' && value.length > 0 && value.length <= MAX_REGEX_INPUT_LENGTH) {\n const regexValue = {\n $regex: `.*${regexSearchMapper(value)}.*`,\n $options: 'i',\n };\n\n newFilter = setNestedValue(newFilter, path, regexValue);\n }\n }\n\n return newFilter;\n },\n /**\n * Checks if a virtual options object has a dynamic ref function.\n *\n * @param options - The virtual options to check.\n * @returns True if the options contain a dynamic ref function.\n */\n isDynamicVirtual<T, R extends string = string>(options?: T_VirtualOptions<T, R>): options is I_DynamicVirtualOptions<T, R> {\n return Boolean(options && typeof options.ref === 'function');\n },\n\n /**\n * Generic utility function to get new records from the database\n * @param controller - MongoController instance\n * @param recordsToCheck - Array of records to check\n * @param filterFn - Function to determine if a record already exists\n * @returns Array of records that don't exist in the database\n */\n async getNewRecords<T extends I_GenericDocument>(\n controller: MongoController<T>,\n recordsToCheck: T[],\n filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean,\n filter: T_Filter<T> = {} as T_Filter<T>,\n ): Promise<T[]> {\n const existingRecords = await controller.findAll(filter);\n\n if (!existingRecords.success) {\n throw new Error(`Failed to query existing records: ${existingRecords.message}`);\n }\n\n const filteredRecords = recordsToCheck.filter(newRecord =>\n !existingRecords.result.some((existingRecord: T_WithId<T>) =>\n filterFn(existingRecord, newRecord),\n ),\n );\n\n return filteredRecords;\n },\n\n /**\n * Generic utility function to get existing records that match the filter criteria\n * @param controller - MongoController instance\n * @param recordsToCheck - Array of records to check\n * @param filterFn - Function to determine if a record exists\n * @returns Array of existing records that match the filter criteria\n * @throws {Error} When the database query fails — prevents silent data inconsistency.\n */\n async getExistingRecords<T extends I_GenericDocument>(\n controller: MongoController<T>,\n recordsToCheck: T[],\n filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean,\n filter: T_Filter<T> = {} as T_Filter<T>,\n ): Promise<T_WithId<T>[]> {\n const existingRecords = await controller.findAll(filter);\n\n if (!existingRecords.success) {\n throw new Error(`Failed to query existing records: ${existingRecords.message}`);\n }\n\n const foundRecords = existingRecords.result.filter((existingRecord: T_WithId<T>) =>\n recordsToCheck.some((newRecord: T) =>\n filterFn(existingRecord, newRecord),\n ),\n );\n\n return foundRecords;\n },\n};\n\nexport { applyNestedPopulate } from './mongo.populate.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"mongo.util.js","names":[],"sources":["../../../src/node/mongo/mongo.util.ts"],"sourcesContent":["import type migrate from 'migrate-mongo';\nimport type mongooseRaw from 'mongoose';\n\nimport aggregatePaginate from 'mongoose-aggregate-paginate-v2';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport { randomUUID } from 'node:crypto';\n\nimport { getNestedValue, regexSearchMapper, setNestedValue } from '#util/index.js';\nimport { validate } from '#util/validate/index.js';\n\nimport type { MongoController } from './mongo.controller.js';\nimport type { C_Document, I_CreateModelOptions, I_CreateSchemaOptions, I_DynamicVirtualConfig, I_DynamicVirtualOptions, I_ExtendedModel, I_GenericDocument, I_MongooseModelMiddleware, T_Filter, T_MongoosePlugin, T_MongooseSchema, T_QueryFilter, T_VirtualOptions, T_WithId } from './mongo.type.js';\n\nimport { addGitIgnoreEntry, writeFileSync } from '../fs/index.js';\nimport { MIGRATE_MONGO_CONFIG, PATH } from '../path/index.js';\n\n/**\n * Converts enum values to proper model names.\n * Handles common naming conventions like converting 'USER' to 'User'.\n *\n * @param enumValue - The enum value to convert\n * @returns The converted model name\n */\nexport function convertEnumToModelName(enumValue: string): string {\n if (enumValue === enumValue.toUpperCase()) {\n return enumValue.charAt(0).toUpperCase() + enumValue.slice(1).toLowerCase();\n }\n\n return enumValue;\n}\n\n/**\n * Interface for the MongoDB utility object to enable explicit type annotation.\n * Required to avoid TS7056 (inferred type exceeds maximum serialization length).\n */\ninterface I_MongoUtils {\n createGenericFields: () => I_GenericDocument;\n applyPlugins: <T>(schema: T_MongooseSchema<T>, plugins: Array<T_MongoosePlugin | false>) => void;\n applyMiddlewares: <T extends Partial<C_Document>>(schema: T_MongooseSchema<T>, middlewares: I_MongooseModelMiddleware<T>[]) => void;\n createGenericSchema: (mongoose: typeof mongooseRaw) => T_MongooseSchema<I_GenericDocument>;\n createSchema: <T, R extends string = string>(options: I_CreateSchemaOptions<T, R>) => T_MongooseSchema<T>;\n createModel: <T extends Partial<C_Document>, R extends string = string>(options: I_CreateModelOptions<T, R>) => I_ExtendedModel<T>;\n validator: {\n isRequired: <T>() => (this: T, value: unknown) => Promise<boolean>;\n isUnique: <T extends { constructor: { exists: (query: { [key: string]: unknown }) => Promise<unknown> } }>(fields: string[]) => (this: T, value: unknown) => Promise<boolean>;\n matchesRegex: (regexArray: RegExp[]) => (value: string) => Promise<boolean>;\n };\n migrate: {\n getModule: () => Promise<typeof migrate>;\n setConfig: (options: Partial<migrate.config.Config> & { moduleSystem?: 'commonjs' | 'esm' }) => void;\n };\n regexify: <T>(filter?: T_QueryFilter<T>, fields?: (keyof T | string)[]) => T_QueryFilter<T>;\n isDynamicVirtual: <T, R extends string>(options?: T_VirtualOptions<T, R>) => options is I_DynamicVirtualOptions<T, R>;\n getNewRecords: <T extends I_GenericDocument>(controller: MongoController<T>, recordsToCheck: T[], filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean, filter?: T_Filter<T>) => Promise<T[]>;\n getExistingRecords: <T extends I_GenericDocument>(controller: MongoController<T>, recordsToCheck: T[], filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean, filter?: T_Filter<T>) => Promise<T_WithId<T>[]>;\n}\n\n/**\n * MongoDB utility object providing comprehensive database operations and utilities.\n * This object contains methods for creating generic fields, applying plugins and middlewares,\n * creating schemas and models, validation functions, migration utilities, and regex filtering.\n */\nexport const mongo: I_MongoUtils = {\n /**\n * Creates generic fields that are commonly used across MongoDB documents.\n * This function generates standard fields including a UUID, deletion flag, and timestamps\n * that can be applied to any document schema.\n *\n * @returns An object containing generic document fields (id, isDel, createdAt, updatedAt).\n */\n createGenericFields(): I_GenericDocument {\n return {\n id: randomUUID(),\n isDel: false,\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n },\n /**\n * Applies plugins to a Mongoose schema.\n * This function filters out falsy plugins and applies the remaining valid plugins\n * to the provided schema.\n *\n * @param schema - The Mongoose schema to apply plugins to.\n * @param plugins - An array of plugin functions or false values to filter and apply.\n */\n applyPlugins<T>(schema: T_MongooseSchema<T>, plugins: Array<T_MongoosePlugin | false>) {\n plugins\n .filter((plugin): plugin is T_MongoosePlugin => typeof plugin === 'function')\n .forEach(plugin => schema.plugin(plugin));\n },\n /**\n * Applies middleware functions to a Mongoose schema.\n * This function configures pre and post middleware for specified methods on the schema.\n *\n * @param schema - The Mongoose schema to apply middleware to.\n * @param middlewares - An array of middleware configurations with method, pre, and post functions.\n */\n applyMiddlewares<T extends Partial<C_Document>>(\n schema: T_MongooseSchema<T>,\n middlewares: I_MongooseModelMiddleware<T>[],\n ) {\n middlewares.forEach(({ method, pre, post }) => {\n if (method && pre) {\n schema.pre(method as RegExp, pre);\n }\n\n if (method && post) {\n schema.post(method as RegExp, post);\n }\n });\n },\n /**\n * Creates a generic Mongoose schema with common fields.\n * This function creates a base schema with UUID field and deletion flag,\n * configured with automatic timestamps.\n *\n * @param mongoose - The Mongoose instance to create the schema with.\n * @returns A Mongoose schema with generic document fields.\n */\n createGenericSchema(mongoose: typeof mongooseRaw) {\n return new mongoose.Schema<I_GenericDocument>(\n {\n id: { type: String, default: () => randomUUID(), unique: true },\n isDel: { type: Boolean, default: false },\n },\n { timestamps: true },\n );\n },\n /**\n * Creates a Mongoose schema with optional virtual fields and generic fields.\n * This function creates a new Mongoose schema from the provided schema definition,\n * optionally adds virtual fields, and includes generic fields (unless standalone is true).\n *\n * @param options - Configuration options including mongoose instance, schema definition, virtuals, and standalone flag.\n * @param options.mongoose - The Mongoose instance to use for schema creation.\n * @param options.schema - The schema definition object.\n * @param options.virtuals - Optional array of virtual field configurations.\n * @param options.standalone - Whether to exclude generic fields (default: false).\n * @returns A configured Mongoose schema.\n */\n createSchema<T, R extends string = string>({\n mongoose,\n schema,\n virtuals = [],\n standalone = false,\n }: I_CreateSchemaOptions<T, R>): T_MongooseSchema<T> {\n const createdSchema = new mongoose.Schema<T>(schema, {\n toJSON: { virtuals: true }, // So `res.json()` and other `JSON.stringify()` functions include virtuals\n toObject: { virtuals: true }, // So `console.log()` and other functions that use `toObject()` include virtuals\n });\n\n virtuals.forEach(({ name, options, get }) => {\n if (mongo.isDynamicVirtual<T, R>(options)) {\n const schemaStatics = createdSchema.statics as Record<string, unknown>;\n\n if (!schemaStatics['_dynamicVirtuals']) {\n schemaStatics['_dynamicVirtuals'] = [];\n }\n\n (schemaStatics['_dynamicVirtuals'] as I_DynamicVirtualConfig<T>[]).push({\n name: name as string,\n options,\n });\n\n const virtualInstance = createdSchema.virtual(name as string);\n\n if (get) {\n virtualInstance.get(get);\n }\n else {\n virtualInstance.get(function (this: T & { _populated?: { [key: string]: unknown } }) {\n return this._populated?.[name as string] || (options?.count ? 0 : (options?.justOne ? null : []));\n });\n }\n }\n else {\n const virtualInstance = createdSchema.virtual(name as string, options);\n\n if (get) {\n virtualInstance.get(get);\n }\n }\n });\n\n if (!standalone) {\n createdSchema.add(mongo.createGenericSchema(mongoose));\n }\n\n return createdSchema;\n },\n /**\n * Creates a Mongoose model with plugins, middleware, and pagination support.\n * This function creates a model from a schema with optional pagination and aggregation plugins,\n * and applies any specified middleware. If a model with the same name already exists, it returns the existing model.\n *\n * @param options - Configuration options including mongoose instance, model name, schema, and feature flags.\n * @param options.mongoose - The Mongoose instance to use for model creation.\n * @param options.name - The name of the model to create.\n * @param options.schema - The schema definition for the model.\n * @param options.pagination - Whether to enable pagination plugin (default: false).\n * @param options.aggregate - Whether to enable aggregation pagination plugin (default: false).\n * @param options.virtuals - Optional array of virtual field configurations.\n * @param options.middlewares - Optional array of middleware configurations.\n * @returns A configured Mongoose model with extended functionality.\n * @throws {Error} When the model name is not provided.\n */\n createModel<T extends Partial<C_Document>, R extends string = string>({\n mongoose: currentMongooseInstance,\n name,\n schema,\n virtuals = [],\n pagination = true,\n aggregate = true,\n middlewares = [],\n }: I_CreateModelOptions<T, R>): I_ExtendedModel<T> {\n if (!name) {\n throw new Error('Model name is required.');\n }\n\n if (currentMongooseInstance.models[name]) {\n return currentMongooseInstance.models[name] as I_ExtendedModel<T>;\n }\n\n const createdSchema = mongo.createSchema({ mongoose: currentMongooseInstance, schema, virtuals });\n\n if (pagination || aggregate) {\n mongo.applyPlugins<T>(createdSchema, [\n pagination && mongoosePaginate,\n aggregate && aggregatePaginate,\n ]);\n }\n\n mongo.applyMiddlewares<T>(createdSchema, middlewares);\n\n const model = currentMongooseInstance.model<T>(name, createdSchema) as I_ExtendedModel<T>;\n\n if (virtuals.length > 0) {\n // Mongoose Model has no typed _virtualConfigs; used by MongooseController.getDynamicVirtuals()\n (model as any)._virtualConfigs = virtuals;\n }\n\n return model;\n },\n /**\n * Validation utilities for Mongoose schemas.\n * This object provides common validation functions that can be used in Mongoose schema definitions.\n */\n validator: {\n /**\n * Creates a required field validator.\n * This function returns a validator that checks if a field value is not empty\n * using the validate.isEmpty utility.\n *\n * @returns A validation function that returns true if the field is not empty.\n */\n isRequired<T>(): (this: T, value: unknown) => Promise<boolean> {\n return async function (this: T, value: unknown): Promise<boolean> {\n return !validate.isEmpty(value);\n };\n },\n /**\n * Creates a unique field validator.\n * This function returns a validator that checks if a field value is unique\n * across the specified fields in the collection.\n *\n * @param fields - An array of field names to check for uniqueness.\n * @returns A validation function that returns true if the value is unique across the specified fields.\n * @throws {Error} When fields is not a non-empty array of strings.\n */\n isUnique<T extends { constructor: { exists: (query: { [key: string]: unknown }) => Promise<unknown> } }>(fields: string[]) {\n return async function (this: T, value: unknown): Promise<boolean> {\n if (!Array.isArray(fields) || fields.length === 0) {\n throw new Error('Fields must be a non-empty array of strings.');\n }\n\n const query = { $or: fields.map(field => ({ [field]: value })) };\n const existingDocument = await this.constructor.exists(query);\n\n return !existingDocument;\n };\n },\n /**\n * Creates a regex pattern validator.\n * This function returns a validator that checks if a string value matches\n * all provided regular expressions.\n *\n * @param regexArray - An array of regular expressions to test against the value.\n * @returns A validation function that returns true if the value matches all regex patterns.\n * @throws {Error} When regexArray is not an array of valid RegExp objects.\n */\n matchesRegex(regexArray: RegExp[]): (value: string) => Promise<boolean> {\n return async (value: string): Promise<boolean> => {\n if (!Array.isArray(regexArray) || regexArray.some(r => !(r instanceof RegExp))) {\n throw new Error('regexArray must be an array of valid RegExp objects.');\n }\n\n return regexArray.every(regex => regex.test(value));\n };\n },\n },\n /**\n * Migration utilities for MongoDB.\n * This object extends the migrate-mongo library with additional configuration utilities.\n */\n migrate: {\n /**\n * Lazily loads the migrate-mongo module to avoid eager import overhead.\n * Use this to access migrate-mongo methods (up, down, status, create) programmatically.\n *\n * @returns A promise resolving to the migrate-mongo module.\n */\n async getModule(): Promise<typeof migrate> {\n return (await import('migrate-mongo')).default;\n },\n /**\n * Sets the migration configuration and updates .gitignore.\n * This function creates a migration configuration file and ensures it's properly\n * excluded from version control.\n *\n * @param options - Migration configuration options to write to the config file.\n */\n setConfig: (options: Partial<migrate.config.Config> & { moduleSystem?: 'commonjs' | 'esm' }) => {\n const optionsJS = `// This file is automatically generated by the Cyberskill CLI.\\nmodule.exports = ${JSON.stringify(options, null, 4)}`;\n\n writeFileSync(PATH.MIGRATE_MONGO_CONFIG, optionsJS);\n\n addGitIgnoreEntry(PATH.GIT_IGNORE, MIGRATE_MONGO_CONFIG);\n },\n },\n /**\n * Converts string values in a filter to regex patterns for case-insensitive search.\n * This function recursively processes a filter object and converts string values in specified fields\n * to MongoDB regex patterns that support accented character matching.\n *\n * @remarks\n * **Performance guard:** Input strings are capped at 200 characters to mitigate ReDoS risk.\n * The generated regex patterns include accented character alternation groups (e.g., `(a|à|á|...)`)\n * which can be polynomially complex for very long inputs. For production search on large collections,\n * consider using MongoDB `$text` search indexes instead of `$regex` for better performance.\n *\n * @param filter - The filter object to process.\n * @param fields - An array of field names to convert to regex patterns.\n * @returns A new filter object with string values converted to regex patterns.\n */\n regexify<T>(filter?: T_QueryFilter<T>, fields?: (keyof T | string)[]): T_QueryFilter<T> {\n if (!filter) {\n return {} as T_QueryFilter<T>;\n }\n\n let newFilter = { ...filter };\n\n if (!fields || fields.length === 0) {\n return newFilter;\n }\n\n const MAX_REGEX_INPUT_LENGTH = 200;\n\n for (const field of fields) {\n const path = field.toString().split('.');\n const value = getNestedValue(newFilter, path);\n\n if (typeof value === 'string' && value.length > 0 && value.length <= MAX_REGEX_INPUT_LENGTH) {\n const regexValue = {\n $regex: `.*${regexSearchMapper(value)}.*`,\n $options: 'i',\n };\n\n newFilter = setNestedValue(newFilter, path, regexValue);\n }\n }\n\n return newFilter;\n },\n /**\n * Checks if a virtual options object has a dynamic ref function.\n *\n * @param options - The virtual options to check.\n * @returns True if the options contain a dynamic ref function.\n */\n isDynamicVirtual<T, R extends string = string>(options?: T_VirtualOptions<T, R>): options is I_DynamicVirtualOptions<T, R> {\n return Boolean(options && typeof options.ref === 'function');\n },\n\n /**\n * Generic utility function to get new records from the database\n * @param controller - MongoController instance\n * @param recordsToCheck - Array of records to check\n * @param filterFn - Function to determine if a record already exists\n * @returns Array of records that don't exist in the database\n */\n async getNewRecords<T extends I_GenericDocument>(\n controller: MongoController<T>,\n recordsToCheck: T[],\n filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean,\n filter: T_Filter<T> = {} as T_Filter<T>,\n ): Promise<T[]> {\n const existingRecords = await controller.findAll(filter);\n\n if (!existingRecords.success) {\n throw new Error(`Failed to query existing records: ${existingRecords.message}`);\n }\n\n if ('truncated' in existingRecords && existingRecords.truncated) {\n throw new Error('getNewRecords: Results were truncated by the default limit. Use pagination or set an explicit limit to ensure complete data.');\n }\n\n const filteredRecords = recordsToCheck.filter(newRecord =>\n !existingRecords.result.some((existingRecord: T_WithId<T>) =>\n filterFn(existingRecord, newRecord),\n ),\n );\n\n return filteredRecords;\n },\n\n /**\n * Generic utility function to get existing records that match the filter criteria\n * @param controller - MongoController instance\n * @param recordsToCheck - Array of records to check\n * @param filterFn - Function to determine if a record exists\n * @returns Array of existing records that match the filter criteria\n * @throws {Error} When the database query fails — prevents silent data inconsistency.\n */\n async getExistingRecords<T extends I_GenericDocument>(\n controller: MongoController<T>,\n recordsToCheck: T[],\n filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean,\n filter: T_Filter<T> = {} as T_Filter<T>,\n ): Promise<T_WithId<T>[]> {\n const existingRecords = await controller.findAll(filter);\n\n if (!existingRecords.success) {\n throw new Error(`Failed to query existing records: ${existingRecords.message}`);\n }\n\n if ('truncated' in existingRecords && existingRecords.truncated) {\n throw new Error('getExistingRecords: Results were truncated by the default limit. Use pagination or set an explicit limit to ensure complete data.');\n }\n\n const foundRecords = existingRecords.result.filter((existingRecord: T_WithId<T>) =>\n recordsToCheck.some((newRecord: T) =>\n filterFn(existingRecord, newRecord),\n ),\n );\n\n return foundRecords;\n },\n};\n\nexport { applyNestedPopulate } from './mongo.populate.js';\n"],"mappings":"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"}