@cyberskill/shared 3.17.0 → 3.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/cli/index.js.map +1 -1
- package/dist/node/command/command.util.js.map +1 -1
- package/dist/node/mongo/mongo.controller.mongoose.js.map +1 -1
- package/dist/node/mongo/mongo.controller.native.js.map +1 -1
- package/dist/node/mongo/mongo.dynamic-populate.js.map +1 -1
- package/dist/node/mongo/mongo.util.d.ts +1 -0
- package/dist/node/mongo/mongo.util.js +14 -19
- package/dist/node/mongo/mongo.util.js.map +1 -1
- package/dist/node/package/package.util.js.map +1 -1
- package/dist/node/storage/storage.util.js.map +1 -1
- package/dist/node/upload/upload.util.js.map +1 -1
- package/dist/react/storage/storage.hook.js.map +1 -1
- package/dist/react/storage/storage.util.js.map +1 -1
- package/dist/util/string/string.util.js +22 -30
- package/dist/util/string/string.util.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/node/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport process from 'node:process';\nimport { hideBin } from 'yargs/helpers';\nimport yargs from 'yargs/yargs';\n\nimport type { I_IssueEntry } from '../log/index.js';\n\nimport { clearAllErrorLists, getStoredErrorLists, resolveCommands, runCommand } from '../command/index.js';\nimport { addGitIgnoreEntry, pathExistsSync, readFileSync, removeSync, writeFileSync } from '../fs/index.js';\nimport { catchError, E_IssueType, log } from '../log/index.js';\nimport { getPackage, installDependencies } from '../package/index.js';\nimport { AG_KIT_PACKAGE_NAME, command, createGitHooksConfig, CYBERSKILL_CLI, CYBERSKILL_PACKAGE_NAME, DOT_AGENT, PATH, resolve, SIMPLE_GIT_HOOK_JSON } from '../path/index.js';\n\n/**\n * Retrieves the version from the package.json file.\n * This function reads the package.json file and extracts the version number.\n * If the file cannot be read or parsed, it returns a default version of '1.0.0'.\n *\n * @returns The version string from package.json or '1.0.0' as fallback.\n */\nfunction getVersion(): string {\n try {\n const pkg = JSON.parse(\n readFileSync(resolve(import.meta.dirname, '../../../package.json'), 'utf-8'),\n );\n return pkg.version;\n }\n catch {\n /* Intentionally empty — fallback to default version when package.json is unreadable */\n return '1.0.0';\n }\n}\n\n/**\n * Performs TypeScript validation if a TypeScript configuration file exists.\n * Uses `--incremental` mode to cache results — the first run may be slow\n * (especially with large generated files), but subsequent runs are near-instant.\n *\n * @returns A promise that resolves when the TypeScript validation is complete.\n */\nasync function checkTypescript() {\n if (!pathExistsSync(PATH.TS_CONFIG)) {\n log.warn('No TypeScript configuration found. Skipping type check.');\n return;\n }\n\n await runCommand('Performing TypeScript validation', await command.typescriptCheck());\n}\n\n/**\n * Performs ESLint checking with optional auto-fix functionality.\n * This function runs ESLint checks on the codebase and optionally applies\n * automatic fixes to resolve linting issues.\n *\n * @param fix - Whether to apply automatic fixes to linting issues (default: false).\n * @returns A promise that resolves when the ESLint check is complete.\n */\nasync function checkEslint(fix = false) {\n const commandToRun = fix ? await command.eslintFix() : await command.eslintCheck();\n const label = fix ? 'Running ESLint with auto-fix' : 'Running ESLint check';\n\n try {\n await runCommand(label, commandToRun, { timeout: 60000, throwOnError: true });\n }\n catch (error: unknown) {\n const errObj = error as { code?: string; killed?: boolean; signal?: string };\n if (errObj.code === 'ETIMEDOUT' || errObj.killed || errObj.signal === 'SIGTERM') {\n log.warn('Lint check timed out. Retrying with debug mode enabled...');\n process.env['DEBUG'] = 'true';\n await runCommand(`${label} (Debug Mode)`, commandToRun);\n }\n else {\n catchError(error);\n }\n }\n}\n\n/**\n * Prints a formatted list of issues (errors or warnings) to the console.\n * This function displays issues in a boxed format with appropriate color coding\n * based on the issue type. It only displays issues if the list is not empty.\n *\n * @param type - The type of issues to display ('Errors' or 'Warnings').\n * @param list - An array of issue entries to display.\n */\nfunction printIssues(type: 'Errors' | 'Warnings', list: I_IssueEntry[]) {\n if (!list.length) {\n return;\n }\n\n const color = type === 'Errors' ? 'red' : 'yellow';\n log.printBoxedLog(type === 'Errors' ? '✖ Errors' : '⚠ Warnings', list, color);\n}\n\n/**\n * Displays the final check results after all validation processes.\n * This function retrieves stored error lists and displays them in a formatted manner.\n * If no errors or warnings are found, it displays a success message. If errors are found,\n * it exits the process with code 1 to indicate failure.\n *\n * @returns A promise that resolves when the results are displayed.\n */\nasync function showCheckResult() {\n // Allow pending I/O (runCommand writes) to flush before reading results\n await new Promise(resolve => setImmediate(resolve));\n\n const allResults = (await getStoredErrorLists()) || [];\n const errors = allResults.filter(e => e.type === E_IssueType.Error);\n const warnings = allResults.filter(e => e.type === E_IssueType.Warning);\n\n if (!errors.length && !warnings.length) {\n log.printBoxedLog('✔ NO ISSUES FOUND', [], 'green');\n }\n else {\n printIssues('Warnings', warnings);\n printIssues('Errors', errors);\n\n if (errors.length > 0) {\n process.exit(1);\n }\n }\n}\n\n/**\n * Executes lint-staged to check only staged files.\n * This function runs lint-staged which executes linting tools only on files\n * that are staged for commit. It includes building the package if it's the current project\n * and displays the results after completion.\n *\n * @returns A promise that resolves when lint-staged execution is complete.\n */\nasync function lintStaged() {\n await clearAllErrorLists();\n const packageData = await getPackage({ name: CYBERSKILL_PACKAGE_NAME });\n\n if (!packageData.success) {\n log.error('Failed to retrieve package information. Aborting lint-staged.');\n return;\n }\n\n if (packageData.result.isCurrentProject) {\n await runCommand(`Building package: ${CYBERSKILL_PACKAGE_NAME}`, await command.build());\n }\n\n await runCommand('Executing lint-staged', await command.lintStaged());\n await showCheckResult();\n}\n\n/**\n * Inspects the ESLint configuration to show active rules and settings.\n * This function runs ESLint inspection to display the current configuration,\n * including which rules are active and their settings.\n *\n * @returns A promise that resolves when the ESLint inspection is complete.\n */\nasync function inspectLint() {\n await runCommand('Inspecting ESLint configuration', await command.eslintInspect());\n}\n\n/**\n * Performs comprehensive linting checks including TypeScript and ESLint.\n * This function runs both TypeScript validation and ESLint checks in parallel,\n * then displays the combined results.\n *\n * @returns A promise that resolves when all linting checks are complete.\n */\nasync function lintCheck() {\n await clearAllErrorLists();\n await checkTypescript();\n await checkEslint();\n await showCheckResult();\n}\n\n/**\n * Performs comprehensive linting checks with automatic fixes.\n * This function runs both TypeScript validation and ESLint checks with auto-fix\n * in parallel, then displays the combined results.\n *\n * @returns A promise that resolves when all linting checks with fixes are complete.\n */\nasync function lintFix() {\n await clearAllErrorLists();\n await checkTypescript();\n await checkEslint(true);\n await showCheckResult();\n}\n\n/**\n * Validates commit message format using commitlint.\n * This function runs commitlint to check if the current commit message\n * follows the conventional commit format and displays the results.\n *\n * @returns A promise that resolves when commit message validation is complete.\n */\nasync function commitLint() {\n await clearAllErrorLists();\n await runCommand('Validating commit message', await command.commitLint());\n await showCheckResult();\n}\n\n/**\n * Sets up Git hooks for automated code quality checks.\n * This function configures Git hooks using simple-git-hooks, creates the necessary\n * configuration files, and updates .gitignore to exclude hook configuration files.\n * It also sets up the hooks to run linting and commit validation automatically.\n *\n * @returns A promise that resolves when Git hook setup is complete.\n */\nasync function gitHookSetup() {\n await runCommand('Configuring Git hooks', await command.configureGitHook());\n\n removeSync(PATH.GIT_HOOK);\n\n const hooks = await resolveCommands(createGitHooksConfig);\n\n writeFileSync(PATH.SIMPLE_GIT_HOOKS_JSON, JSON.stringify(hooks, null, 4));\n\n addGitIgnoreEntry(PATH.GIT_IGNORE, SIMPLE_GIT_HOOK_JSON);\n\n await runCommand('Setting up simple-git-hooks', await command.simpleGitHooks());\n}\n\n/**\n * Sets up the AI agent environment.\n * This function ensures the ag-kit package is installed globally, initializes\n * or updates the AI agent configuration, and adds the agent directory\n * to .gitignore to exclude it from version control.\n *\n * @returns A promise that resolves when the AI agent setup is complete.\n */\nasync function aiSetup() {\n try {\n await runCommand(`Checking for ${AG_KIT_PACKAGE_NAME}`, `pnpm list -g ${AG_KIT_PACKAGE_NAME}`);\n }\n catch {\n /* Package not found globally — install it */\n await runCommand(`Installing ${AG_KIT_PACKAGE_NAME} globally`, `pnpm i -g ${AG_KIT_PACKAGE_NAME}`);\n }\n\n if (pathExistsSync(PATH.DOT_AGENT)) {\n await runCommand('Updating AI agent', 'echo y | ag-kit update');\n }\n else {\n await runCommand('Initializing AI agent', 'ag-kit init');\n }\n\n addGitIgnoreEntry(PATH.GIT_EXCLUDE, DOT_AGENT);\n}\n\n/**\n * Initializes the project with dependencies and Git hooks.\n * This function installs project dependencies and sets up Git hooks for\n * automated code quality checks. It's typically run when setting up a new project.\n *\n * @returns A promise that resolves when project initialization is complete.\n */\nasync function ready() {\n await installDependencies();\n await gitHookSetup();\n}\n\n/**\n * Resets the project by removing dependencies and reinstalling them.\n * This function removes node_modules and lock files, cleans the package manager cache,\n * reinstalls dependencies, and sets up Git hooks. It's useful for resolving\n * dependency-related issues.\n *\n * @returns A promise that resolves when project reset is complete.\n */\nasync function reset() {\n removeSync(PATH.NODE_MODULES, PATH.PNPM_LOCK_YAML, PATH.SIMPLE_GIT_HOOKS_JSON);\n await runCommand('Pruning pnpm store', await command.pnpmPruneStore());\n await runCommand('Clearing pnpm cache', await command.pnpmCleanCache());\n await installDependencies();\n await gitHookSetup();\n // await aiSetup();\n}\n\n/**\n * Inspects project dependencies to analyze their status.\n * This function runs dependency inspection to check for outdated packages,\n * security vulnerabilities, and other dependency-related issues.\n *\n * @returns A promise that resolves when dependency inspection is complete.\n */\nasync function inspect() {\n await runCommand('Inspecting project dependencies', await command.nodeModulesInspect());\n}\n\n/**\n * Runs the unit test suite.\n * This function executes unit tests using the configured test runner\n * and displays the test results.\n *\n * @returns A promise that resolves when unit tests are complete.\n */\nasync function testUnit() {\n try {\n const cmd = await command.testUnit();\n\n await runCommand('Running unit tests', cmd, { throwOnError: true });\n }\n catch (error) {\n log.error(error);\n process.exit(1);\n }\n}\n\n/**\n * Runs the end-to-end test suite.\n * This function executes end-to-end tests using the configured test runner\n * and displays the test results.\n *\n * @returns A promise that resolves when end-to-end tests are complete.\n */\nasync function testE2E() {\n try {\n const cmd = await command.testE2e();\n\n await runCommand('Running end-to-end tests', cmd, { throwOnError: true });\n }\n catch (error) {\n log.error(error);\n process.exit(1);\n }\n}\n\n/**\n * Creates a new MongoDB migration file.\n * This function creates a new migration file with the specified name\n * for database schema changes.\n *\n * @param migrationName - The name for the new migration file.\n * @returns A promise that resolves when the migration file is created.\n */\nasync function mongoMigrateCreate(migrationName: string) {\n await runCommand('Creating MongoDB migration', await command.mongoMigrateCreate(migrationName));\n}\n\n/**\n * Applies all pending MongoDB migrations.\n * This function runs all pending database migrations to update the database schema\n * to the latest version.\n *\n * @returns A promise that resolves when all migrations are applied.\n */\nasync function mongoMigrateUp() {\n await runCommand('Running MongoDB migrations', await command.mongoMigrateUp());\n}\n\n/**\n * Rolls back the last applied MongoDB migration.\n * This function reverts the most recent database migration, undoing\n * the last schema change.\n *\n * @returns A promise that resolves when the migration is rolled back.\n */\nasync function mongoMigrateDown() {\n await runCommand('Rolling back MongoDB migration', await command.mongoMigrateDown());\n}\n\n/**\n * Starts the Storybook development server.\n * This function runs Storybook in development mode, allowing you to\n * view and interact with your component stories in a browser.\n *\n * @returns A promise that resolves when the Storybook dev server is started.\n */\nasync function storybookDev() {\n await runCommand('Starting Storybook development server', await command.storybookDev());\n}\n\n/**\n * Builds Storybook for production deployment.\n * This function creates a static build of Storybook that can be\n * deployed to a web server or hosting service.\n *\n * @returns A promise that resolves when the Storybook build is complete.\n */\nasync function storybookBuild() {\n await runCommand('Building Storybook', await command.storybookBuild());\n}\n\n(async () => {\n try {\n await yargs(hideBin(process.argv))\n .scriptName(CYBERSKILL_CLI)\n .usage('$0 <command> [options]')\n .command('ai:setup', 'Setup AI agent environment', aiSetup)\n .command('lint', 'Check code for linting issues', lintCheck)\n .command('lint:fix', 'Fix linting issues automatically', lintFix)\n .command('lint:inspect', 'View active ESLint configuration', inspectLint)\n .command('lint-staged', 'Run lint checks on staged files', lintStaged)\n .command('commitlint', 'Validate commit message format', commitLint)\n .command('build', 'Build the package', y =>\n y.option('filter', {\n describe: 'Filter entry points to build',\n type: 'string',\n }), async (argv) => {\n if (argv.filter) {\n process.env['FILTER'] = argv.filter;\n }\n await runCommand('Building package', await command.build(), { throwOnError: true });\n })\n .command('ready', 'Initialize project and dependencies', ready)\n .command('reset', 'Reset the project and reinstall dependencies', reset)\n .command('inspect', 'Analyze installed project dependencies', inspect)\n .command('test:unit', 'Run unit test suite', testUnit)\n .command('test:e2e', 'Run end-to-end test suite', testE2E)\n .command('mongo:migrate:create <name>', 'Create a MongoDB migration', y =>\n y.positional('name', {\n describe: 'Migration name',\n type: 'string',\n }), async (argv) => {\n if (!argv.name) {\n log.error('Migration name is required.');\n\n return;\n }\n\n await mongoMigrateCreate(argv.name);\n })\n .command('mongo:migrate:up', 'Apply all MongoDB migrations', mongoMigrateUp)\n .command('mongo:migrate:down', 'Rollback last MongoDB migration', mongoMigrateDown)\n .command('storybook:dev', 'Start Storybook development server', storybookDev)\n .command('storybook:build', 'Build Storybook for production', storybookBuild)\n .example('$0 lint:fix', 'Automatically block and fix linter errors')\n .example('$0 test:unit', 'Run the unit test suite')\n .example('$0 mongo:migrate:create init_db', 'Create a new migration file named \"init_db\"')\n .demandCommand(1, 'Please specify a valid command.')\n .strict()\n .help()\n .alias('h', 'help')\n .alias('v', 'version')\n .version(getVersion())\n .epilog('💡 Tip: Use \"--help\" with any command to see options\\n')\n .parse();\n }\n catch (error) {\n catchError(error);\n process.exit(1);\n }\n})();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/node/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport process from 'node:process';\nimport { hideBin } from 'yargs/helpers';\nimport yargs from 'yargs/yargs';\n\nimport type { I_IssueEntry } from '../log/index.js';\n\nimport { clearAllErrorLists, getStoredErrorLists, resolveCommands, runCommand } from '../command/index.js';\nimport { addGitIgnoreEntry, pathExistsSync, readFileSync, removeSync, writeFileSync } from '../fs/index.js';\nimport { catchError, E_IssueType, log } from '../log/index.js';\nimport { getPackage, installDependencies } from '../package/index.js';\nimport { AG_KIT_PACKAGE_NAME, command, createGitHooksConfig, CYBERSKILL_CLI, CYBERSKILL_PACKAGE_NAME, DOT_AGENT, PATH, resolve, SIMPLE_GIT_HOOK_JSON } from '../path/index.js';\n\n/**\n * Retrieves the version from the package.json file.\n * This function reads the package.json file and extracts the version number.\n * If the file cannot be read or parsed, it returns a default version of '1.0.0'.\n *\n * @returns The version string from package.json or '1.0.0' as fallback.\n */\nfunction getVersion(): string {\n try {\n const pkg = JSON.parse(\n readFileSync(resolve(import.meta.dirname, '../../../package.json'), 'utf-8'),\n );\n return pkg.version;\n }\n catch {\n /* Intentionally empty — fallback to default version when package.json is unreadable */\n return '1.0.0';\n }\n}\n\n/**\n * Performs TypeScript validation if a TypeScript configuration file exists.\n * Uses `--incremental` mode to cache results — the first run may be slow\n * (especially with large generated files), but subsequent runs are near-instant.\n *\n * @returns A promise that resolves when the TypeScript validation is complete.\n */\nasync function checkTypescript() {\n if (!pathExistsSync(PATH.TS_CONFIG)) {\n log.warn('No TypeScript configuration found. Skipping type check.');\n return;\n }\n\n await runCommand('Performing TypeScript validation', await command.typescriptCheck());\n}\n\n/**\n * Performs ESLint checking with optional auto-fix functionality.\n * This function runs ESLint checks on the codebase and optionally applies\n * automatic fixes to resolve linting issues.\n *\n * @param fix - Whether to apply automatic fixes to linting issues (default: false).\n * @returns A promise that resolves when the ESLint check is complete.\n */\nasync function checkEslint(fix = false) {\n const commandToRun = fix ? await command.eslintFix() : await command.eslintCheck();\n const label = fix ? 'Running ESLint with auto-fix' : 'Running ESLint check';\n\n try {\n await runCommand(label, commandToRun, { timeout: 60000, throwOnError: true });\n }\n catch (error: unknown) {\n const errObj = error as { code?: string; killed?: boolean; signal?: string };\n if (errObj.code === 'ETIMEDOUT' || errObj.killed || errObj.signal === 'SIGTERM') {\n log.warn('Lint check timed out. Retrying with debug mode enabled...');\n process.env['DEBUG'] = 'true';\n await runCommand(`${label} (Debug Mode)`, commandToRun);\n }\n else {\n catchError(error);\n }\n }\n}\n\n/**\n * Prints a formatted list of issues (errors or warnings) to the console.\n * This function displays issues in a boxed format with appropriate color coding\n * based on the issue type. It only displays issues if the list is not empty.\n *\n * @param type - The type of issues to display ('Errors' or 'Warnings').\n * @param list - An array of issue entries to display.\n */\nfunction printIssues(type: 'Errors' | 'Warnings', list: I_IssueEntry[]) {\n if (!list.length) {\n return;\n }\n\n const color = type === 'Errors' ? 'red' : 'yellow';\n log.printBoxedLog(type === 'Errors' ? '✖ Errors' : '⚠ Warnings', list, color);\n}\n\n/**\n * Displays the final check results after all validation processes.\n * This function retrieves stored error lists and displays them in a formatted manner.\n * If no errors or warnings are found, it displays a success message. If errors are found,\n * it exits the process with code 1 to indicate failure.\n *\n * @returns A promise that resolves when the results are displayed.\n */\nasync function showCheckResult() {\n // Allow pending I/O (runCommand writes) to flush before reading results\n await new Promise(resolve => setImmediate(resolve));\n\n const allResults = (await getStoredErrorLists()) || [];\n const errors = allResults.filter(e => e.type === E_IssueType.Error);\n const warnings = allResults.filter(e => e.type === E_IssueType.Warning);\n\n if (!errors.length && !warnings.length) {\n log.printBoxedLog('✔ NO ISSUES FOUND', [], 'green');\n }\n else {\n printIssues('Warnings', warnings);\n printIssues('Errors', errors);\n\n if (errors.length > 0) {\n process.exit(1);\n }\n }\n}\n\n/**\n * Executes lint-staged to check only staged files.\n * This function runs lint-staged which executes linting tools only on files\n * that are staged for commit. It includes building the package if it's the current project\n * and displays the results after completion.\n *\n * @returns A promise that resolves when lint-staged execution is complete.\n */\nasync function lintStaged() {\n await clearAllErrorLists();\n const packageData = await getPackage({ name: CYBERSKILL_PACKAGE_NAME });\n\n if (!packageData.success) {\n log.error('Failed to retrieve package information. Aborting lint-staged.');\n return;\n }\n\n if (packageData.result.isCurrentProject) {\n await runCommand(`Building package: ${CYBERSKILL_PACKAGE_NAME}`, await command.build());\n }\n\n await runCommand('Executing lint-staged', await command.lintStaged());\n await showCheckResult();\n}\n\n/**\n * Inspects the ESLint configuration to show active rules and settings.\n * This function runs ESLint inspection to display the current configuration,\n * including which rules are active and their settings.\n *\n * @returns A promise that resolves when the ESLint inspection is complete.\n */\nasync function inspectLint() {\n await runCommand('Inspecting ESLint configuration', await command.eslintInspect());\n}\n\n/**\n * Performs comprehensive linting checks including TypeScript and ESLint.\n * This function runs both TypeScript validation and ESLint checks in parallel,\n * then displays the combined results.\n *\n * @returns A promise that resolves when all linting checks are complete.\n */\nasync function lintCheck() {\n await clearAllErrorLists();\n await checkTypescript();\n await checkEslint();\n await showCheckResult();\n}\n\n/**\n * Performs comprehensive linting checks with automatic fixes.\n * This function runs both TypeScript validation and ESLint checks with auto-fix\n * in parallel, then displays the combined results.\n *\n * @returns A promise that resolves when all linting checks with fixes are complete.\n */\nasync function lintFix() {\n await clearAllErrorLists();\n await checkTypescript();\n await checkEslint(true);\n await showCheckResult();\n}\n\n/**\n * Validates commit message format using commitlint.\n * This function runs commitlint to check if the current commit message\n * follows the conventional commit format and displays the results.\n *\n * @returns A promise that resolves when commit message validation is complete.\n */\nasync function commitLint() {\n await clearAllErrorLists();\n await runCommand('Validating commit message', await command.commitLint());\n await showCheckResult();\n}\n\n/**\n * Sets up Git hooks for automated code quality checks.\n * This function configures Git hooks using simple-git-hooks, creates the necessary\n * configuration files, and updates .gitignore to exclude hook configuration files.\n * It also sets up the hooks to run linting and commit validation automatically.\n *\n * @returns A promise that resolves when Git hook setup is complete.\n */\nasync function gitHookSetup() {\n await runCommand('Configuring Git hooks', await command.configureGitHook());\n\n removeSync(PATH.GIT_HOOK);\n\n const hooks = await resolveCommands(createGitHooksConfig);\n\n writeFileSync(PATH.SIMPLE_GIT_HOOKS_JSON, JSON.stringify(hooks, null, 4));\n\n addGitIgnoreEntry(PATH.GIT_IGNORE, SIMPLE_GIT_HOOK_JSON);\n\n await runCommand('Setting up simple-git-hooks', await command.simpleGitHooks());\n}\n\n/**\n * Sets up the AI agent environment.\n * This function ensures the ag-kit package is installed globally, initializes\n * or updates the AI agent configuration, and adds the agent directory\n * to .gitignore to exclude it from version control.\n *\n * @returns A promise that resolves when the AI agent setup is complete.\n */\nasync function aiSetup() {\n try {\n await runCommand(`Checking for ${AG_KIT_PACKAGE_NAME}`, `pnpm list -g ${AG_KIT_PACKAGE_NAME}`);\n }\n catch {\n /* Package not found globally — install it */\n await runCommand(`Installing ${AG_KIT_PACKAGE_NAME} globally`, `pnpm i -g ${AG_KIT_PACKAGE_NAME}`);\n }\n\n if (pathExistsSync(PATH.DOT_AGENT)) {\n await runCommand('Updating AI agent', 'echo y | ag-kit update');\n }\n else {\n await runCommand('Initializing AI agent', 'ag-kit init');\n }\n\n addGitIgnoreEntry(PATH.GIT_EXCLUDE, DOT_AGENT);\n}\n\n/**\n * Initializes the project with dependencies and Git hooks.\n * This function installs project dependencies and sets up Git hooks for\n * automated code quality checks. It's typically run when setting up a new project.\n *\n * @returns A promise that resolves when project initialization is complete.\n */\nasync function ready() {\n await installDependencies();\n await gitHookSetup();\n}\n\n/**\n * Resets the project by removing dependencies and reinstalling them.\n * This function removes node_modules and lock files, cleans the package manager cache,\n * reinstalls dependencies, and sets up Git hooks. It's useful for resolving\n * dependency-related issues.\n *\n * @returns A promise that resolves when project reset is complete.\n */\nasync function reset() {\n removeSync(PATH.NODE_MODULES, PATH.PNPM_LOCK_YAML, PATH.SIMPLE_GIT_HOOKS_JSON);\n await runCommand('Pruning pnpm store', await command.pnpmPruneStore());\n await runCommand('Clearing pnpm cache', await command.pnpmCleanCache());\n await installDependencies();\n await gitHookSetup();\n // await aiSetup();\n}\n\n/**\n * Inspects project dependencies to analyze their status.\n * This function runs dependency inspection to check for outdated packages,\n * security vulnerabilities, and other dependency-related issues.\n *\n * @returns A promise that resolves when dependency inspection is complete.\n */\nasync function inspect() {\n await runCommand('Inspecting project dependencies', await command.nodeModulesInspect());\n}\n\n/**\n * Runs the unit test suite.\n * This function executes unit tests using the configured test runner\n * and displays the test results.\n *\n * @returns A promise that resolves when unit tests are complete.\n */\nasync function testUnit() {\n try {\n const cmd = await command.testUnit();\n\n await runCommand('Running unit tests', cmd, { throwOnError: true });\n }\n catch (error: unknown) {\n log.error(error);\n process.exit(1);\n }\n}\n\n/**\n * Runs the end-to-end test suite.\n * This function executes end-to-end tests using the configured test runner\n * and displays the test results.\n *\n * @returns A promise that resolves when end-to-end tests are complete.\n */\nasync function testE2E() {\n try {\n const cmd = await command.testE2e();\n\n await runCommand('Running end-to-end tests', cmd, { throwOnError: true });\n }\n catch (error: unknown) {\n log.error(error);\n process.exit(1);\n }\n}\n\n/**\n * Creates a new MongoDB migration file.\n * This function creates a new migration file with the specified name\n * for database schema changes.\n *\n * @param migrationName - The name for the new migration file.\n * @returns A promise that resolves when the migration file is created.\n */\nasync function mongoMigrateCreate(migrationName: string) {\n await runCommand('Creating MongoDB migration', await command.mongoMigrateCreate(migrationName));\n}\n\n/**\n * Applies all pending MongoDB migrations.\n * This function runs all pending database migrations to update the database schema\n * to the latest version.\n *\n * @returns A promise that resolves when all migrations are applied.\n */\nasync function mongoMigrateUp() {\n await runCommand('Running MongoDB migrations', await command.mongoMigrateUp());\n}\n\n/**\n * Rolls back the last applied MongoDB migration.\n * This function reverts the most recent database migration, undoing\n * the last schema change.\n *\n * @returns A promise that resolves when the migration is rolled back.\n */\nasync function mongoMigrateDown() {\n await runCommand('Rolling back MongoDB migration', await command.mongoMigrateDown());\n}\n\n/**\n * Starts the Storybook development server.\n * This function runs Storybook in development mode, allowing you to\n * view and interact with your component stories in a browser.\n *\n * @returns A promise that resolves when the Storybook dev server is started.\n */\nasync function storybookDev() {\n await runCommand('Starting Storybook development server', await command.storybookDev());\n}\n\n/**\n * Builds Storybook for production deployment.\n * This function creates a static build of Storybook that can be\n * deployed to a web server or hosting service.\n *\n * @returns A promise that resolves when the Storybook build is complete.\n */\nasync function storybookBuild() {\n await runCommand('Building Storybook', await command.storybookBuild());\n}\n\n(async () => {\n try {\n await yargs(hideBin(process.argv))\n .scriptName(CYBERSKILL_CLI)\n .usage('$0 <command> [options]')\n .command('ai:setup', 'Setup AI agent environment', aiSetup)\n .command('lint', 'Check code for linting issues', lintCheck)\n .command('lint:fix', 'Fix linting issues automatically', lintFix)\n .command('lint:inspect', 'View active ESLint configuration', inspectLint)\n .command('lint-staged', 'Run lint checks on staged files', lintStaged)\n .command('commitlint', 'Validate commit message format', commitLint)\n .command('build', 'Build the package', y =>\n y.option('filter', {\n describe: 'Filter entry points to build',\n type: 'string',\n }), async (argv) => {\n if (argv.filter) {\n process.env['FILTER'] = argv.filter;\n }\n await runCommand('Building package', await command.build(), { throwOnError: true });\n })\n .command('ready', 'Initialize project and dependencies', ready)\n .command('reset', 'Reset the project and reinstall dependencies', reset)\n .command('inspect', 'Analyze installed project dependencies', inspect)\n .command('test:unit', 'Run unit test suite', testUnit)\n .command('test:e2e', 'Run end-to-end test suite', testE2E)\n .command('mongo:migrate:create <name>', 'Create a MongoDB migration', y =>\n y.positional('name', {\n describe: 'Migration name',\n type: 'string',\n }), async (argv) => {\n if (!argv.name) {\n log.error('Migration name is required.');\n\n return;\n }\n\n await mongoMigrateCreate(argv.name);\n })\n .command('mongo:migrate:up', 'Apply all MongoDB migrations', mongoMigrateUp)\n .command('mongo:migrate:down', 'Rollback last MongoDB migration', mongoMigrateDown)\n .command('storybook:dev', 'Start Storybook development server', storybookDev)\n .command('storybook:build', 'Build Storybook for production', storybookBuild)\n .example('$0 lint:fix', 'Automatically block and fix linter errors')\n .example('$0 test:unit', 'Run the unit test suite')\n .example('$0 mongo:migrate:create init_db', 'Create a new migration file named \"init_db\"')\n .demandCommand(1, 'Please specify a valid command.')\n .strict()\n .help()\n .alias('h', 'help')\n .alias('v', 'version')\n .version(getVersion())\n .epilog('💡 Tip: Use \"--help\" with any command to see options\\n')\n .parse();\n }\n catch (error: unknown) {\n catchError(error);\n process.exit(1);\n }\n})();\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.util.js","names":[],"sources":["../../../src/node/command/command.util.ts"],"sourcesContent":["import { exec, execFile } from 'node:child_process';\nimport process from 'node:process';\nimport * as util from 'node:util';\n\nimport { getEnv } from '#config/env/index.js';\n\nimport type { I_IssueEntry } from '../log/index.js';\nimport type { I_CommandContext, I_EslintError, T_Command, T_CommandMapInput } from './command.type.js';\n\nimport { catchError, E_IssueType, log } from '../log/index.js';\nimport { getPackage } from '../package/index.js';\nimport { CYBERSKILL_CLI, CYBERSKILL_CLI_PATH, CYBERSKILL_PACKAGE_NAME, PNPM_EXEC_CLI, TSX_CLI } from '../path/index.js';\nimport { storage } from '../storage/index.js';\n\nconst execPromise = util.promisify(exec);\nconst execFilePromise = util.promisify(execFile);\nconst SHELL_METACHARACTERS = /[|&;<>`$(){}[\\]!#~*?]/;\nconst RE_ESLINT_ERROR = /^\\s*(\\d+):(\\d+)\\s+(error|warning)\\s+(\\S+(?:\\s+\\S+)*)\\s+(\\S+)$/;\nconst RE_TS_ERROR = /^(.+?)\\((\\d+),(\\d+)\\):\\s+(error|warning)\\s+TS\\d+:\\s+(\\S.+)$/;\nconst RE_COMMITLINT_ERROR = /^✖\\s+(\\S+(?:\\s+\\S+)*)\\s+\\[([^\\]]*)\\]$/;\nconst RE_WHITESPACE = /\\s+/;\n\n/**\n * Retrieves the package name for the current project.\n * This function attempts to get the package name from the current project's package.json.\n * If the package information cannot be retrieved, it returns a timestamp as a fallback.\n * Results are cached after first successful read to avoid redundant I/O.\n *\n * @returns A promise that resolves to the package name or a timestamp string.\n */\nlet _cachedPackageName: string | null = null;\n/** Returns the cached package name, falling back to a timestamp if unavailable. */\nasync function getPackageName() {\n if (_cachedPackageName) {\n return _cachedPackageName;\n }\n\n const pkg = await getPackage();\n\n if (!pkg.success) {\n return Date.now().toString();\n }\n\n _cachedPackageName = pkg.result.name;\n return _cachedPackageName;\n}\n\n/**\n * Resets the cached package name. For testing only.\n * Call this to force `getPackageName()` to re-read the package.json on the next invocation.\n */\nexport function resetCommandCacheForTesting(): void {\n _cachedPackageName = null;\n}\n\n/**\n * Saves a list of error entries to persistent storage.\n * This function stores error information with the package name as the key,\n * and provides a log link for manual inspection of the stored errors.\n *\n * @param errorList - An array of error entries to be stored.\n * @returns A promise that resolves when the storage operation is complete.\n */\nasync function saveErrorListToStorage(errorList: I_IssueEntry[]): Promise<void> {\n if (errorList.length === 0) {\n return;\n }\n\n const packageName = await getPackageName();\n\n try {\n const existingErrors = await getStoredErrorLists();\n const mergedErrors = [...existingErrors, ...errorList];\n\n await storage.set(packageName, mergedErrors);\n\n const logPath = await storage.getLogLink(packageName);\n if (logPath) {\n log.info(`📂 Open the error list manually: ${logPath}`);\n }\n }\n catch (error) {\n catchError(error);\n }\n}\n\n/**\n * Retrieves all stored error lists from persistent storage.\n * This function fetches error entries that were previously saved using the package name as the key.\n *\n * @returns A promise that resolves to an array of error entries, or an empty array if none are found.\n */\nexport async function getStoredErrorLists(): Promise<I_IssueEntry[]> {\n try {\n const packageName = await getPackageName();\n const allErrors = await storage.get<I_IssueEntry[]>(packageName);\n\n return allErrors ?? [];\n }\n catch (error) {\n return catchError<I_IssueEntry[]>(error, {\n returnValue: [],\n });\n }\n}\n\n/**\n * Clears all stored error lists from persistent storage.\n * This function removes all error entries associated with the current package name.\n *\n * @returns A promise that resolves when the clearing operation is complete.\n */\nexport async function clearAllErrorLists(): Promise<void> {\n try {\n const packageName = await getPackageName();\n await storage.remove(packageName);\n }\n catch (error) {\n catchError(error);\n }\n}\n\n/**\n * Parses text-based error output and converts it to structured error entries.\n * This function processes command output that contains error information in text format,\n * extracting file paths, line numbers, error types, messages, and rule violations.\n * It handles multiple error formats including ESLint, TypeScript, and commitlint errors.\n *\n * @param output - The raw text output from a command execution containing error information.\n */\nasync function parseTextErrors(output: string): Promise<void> {\n const errorList: I_IssueEntry[] = [];\n const unmatchedLines: string[] = [];\n let lastFilePath = '';\n const eslintErrorDetailsRegex = RE_ESLINT_ERROR;\n const tsRegex = RE_TS_ERROR;\n const commitlintRegex = RE_COMMITLINT_ERROR;\n\n output.split('\\n').forEach((line) => {\n const tsMatch = tsRegex.exec(line);\n\n if (tsMatch) {\n errorList.push({\n file: tsMatch?.[1] ?? '',\n position: `${tsMatch[2]}:${tsMatch[3]}`,\n type: tsMatch[4] === E_IssueType.Error ? E_IssueType.Error : E_IssueType.Warning,\n message: tsMatch?.[5]?.trim() ?? '',\n });\n }\n else if (line.startsWith('/')) {\n lastFilePath = line.trim();\n }\n else {\n const eslintMatch = eslintErrorDetailsRegex.exec(line);\n\n if (eslintMatch && lastFilePath) {\n errorList.push({\n file: lastFilePath,\n position: `${eslintMatch[1]}:${eslintMatch[2]}`,\n type: eslintMatch[3] === E_IssueType.Error ? E_IssueType.Error : E_IssueType.Warning,\n message: eslintMatch?.[4]?.trim() ?? '',\n rule: eslintMatch?.[5]?.trim() ?? '',\n });\n }\n else {\n const commitlintMatch = commitlintRegex.exec(line);\n\n if (commitlintMatch) {\n errorList.push({\n file: 'commitlint',\n type: E_IssueType.Error,\n message: commitlintMatch?.[1]?.trim() ?? '',\n rule: commitlintMatch?.[2]?.trim() ?? '',\n });\n }\n else {\n unmatchedLines.push(line.trim());\n }\n }\n }\n });\n\n if (errorList.length) {\n await saveErrorListToStorage(errorList);\n }\n\n if (unmatchedLines.length) {\n log.warn(`Unmatched lines:`);\n unmatchedLines.forEach(line => log.info(` ${line}`));\n }\n}\n\n/**\n * Parses command output that contains structured error information.\n * This function attempts to parse JSON-formatted error output (typically from ESLint)\n * and converts it to structured error entries. If JSON parsing fails, it falls back\n * to text-based parsing.\n *\n * @param output - The command output to parse, expected to be JSON-formatted error data.\n */\nasync function parseCommandOutput(output: string): Promise<void> {\n try {\n const results: I_EslintError[] = JSON.parse(output);\n const errorList: I_IssueEntry[] = [];\n\n results.forEach(({ filePath, messages }) => {\n messages.forEach(({ severity, line, column, ruleId, message }) => {\n errorList.push({\n type: severity === 2 ? E_IssueType.Error : E_IssueType.Warning,\n file: filePath,\n position: `${line}:${column}`,\n rule: ruleId,\n message,\n });\n });\n });\n\n if (errorList.length) {\n await saveErrorListToStorage(errorList);\n }\n }\n catch {\n await parseTextErrors(output);\n }\n}\n\n/**\n * Executes a command and processes its output for errors.\n * This function runs a command with proper signal handling for graceful termination,\n * processes both stdout and stderr for error information, and handles command failures.\n *\n * @param command - The command string to execute, or undefined if no command should be run.\n * @param parser - The function to use for parsing command output (defaults to parseCommandOutput).\n * @returns A promise that resolves when the command execution is complete.\n */\nasync function executeCommand(command: string | void, parser = parseCommandOutput, options: { timeout?: number } = {}): Promise<void> {\n const controller = new AbortController();\n\n const onSigint = () => {\n log.warn('Process interrupted. Terminating...');\n controller.abort();\n process.exit(130);\n };\n\n process.once('SIGINT', onSigint);\n\n try {\n if (typeof command === 'string') {\n const execOptions = {\n maxBuffer: 10 * 1024 * 1024,\n signal: controller.signal,\n timeout: options.timeout,\n };\n\n let result: { stdout: string; stderr: string };\n\n if (SHELL_METACHARACTERS.test(command)) {\n result = await execPromise(command, execOptions);\n }\n else {\n const parts = command.split(RE_WHITESPACE).filter(Boolean);\n result = await execFilePromise(parts[0]!, parts.slice(1), execOptions);\n }\n\n await Promise.all([result.stdout, result.stderr].map(output => output && parser(output)));\n }\n }\n catch (error) {\n const { stdout, stderr, message } = error as {\n stdout?: string;\n stderr?: string;\n message: string;\n };\n\n await Promise.all([stdout, stderr].map(output => output && parser(output)));\n\n if (!stderr && !stdout) {\n log.error(`Command failed: ${message}`);\n }\n\n throw error;\n }\n finally {\n process.removeListener('SIGINT', onSigint);\n }\n}\n\n/**\n * Creates a raw command object that bypasses CLI formatting.\n * This function wraps a command string in an object that indicates it should be executed\n * as-is without any additional CLI formatting or path resolution.\n *\n * @param cmd - The raw command string to be executed directly.\n * @returns An object containing the raw command with a flag indicating it should not be formatted.\n */\nexport function rawCommand(cmd: string) {\n return { raw: true, cmd };\n}\n\n/**\n * Formats a command for CLI execution based on the current project context.\n * This function determines whether to use the current project's CLI path or the global CLI,\n * and formats the command accordingly with the appropriate executable paths.\n *\n * @param command - The command string to format.\n * @param context - Optional context information about the current project.\n * @returns The formatted command string ready for execution.\n */\nfunction formatCLI(command: string, context?: I_CommandContext) {\n if (context?.isCurrentProject) {\n return `${PNPM_EXEC_CLI} ${TSX_CLI} ${CYBERSKILL_CLI_PATH} ${command}`;\n }\n\n return `${PNPM_EXEC_CLI} ${CYBERSKILL_CLI} ${command}`;\n}\n\n/**\n * Formats a command based on its type and context.\n * This function handles different command types:\n * - Function commands: Executes the function with context and formats the result\n * - Raw commands: Returns the command as-is without formatting\n * - String commands: Formats them as CLI commands\n *\n * @param command - The command to format, which can be a string, function, or raw command object.\n * @param context - Optional context information for command execution.\n * @returns The formatted command string ready for execution.\n */\nexport function formatCommand(command: T_Command, context?: I_CommandContext) {\n if (typeof command === 'function') {\n return formatCLI(command(context), context);\n }\n\n if (typeof command === 'object' && command?.raw === true) {\n return command.cmd;\n }\n\n if (typeof command === 'string') {\n return formatCLI(command, context);\n }\n\n return command;\n}\n\n/**\n * Resolves a map of commands by formatting them based on the current project context.\n * This function takes a command map (either static or dynamic) and formats all commands\n * using the appropriate CLI paths based on whether the current project is the Cyberskill package.\n *\n * @param input - The command map to resolve, which can be static or a function that returns a map.\n * @returns A promise that resolves to an object with formatted command strings, or undefined if package info cannot be retrieved.\n */\nexport async function resolveCommands(input: T_CommandMapInput) {\n const packageData = await getPackage({ name: CYBERSKILL_PACKAGE_NAME });\n\n if (packageData.success) {\n const ctx: I_CommandContext = { isCurrentProject: packageData.result.isCurrentProject };\n const commands = typeof input === 'function' ? input(ctx) : input;\n\n return Object.fromEntries(\n Object.entries(commands).map(([key, cmd]) => [key, formatCommand(cmd, ctx)]),\n );\n }\n}\n\n/**\n * Executes a command with proper logging and error handling.\n * This function provides a standardized way to run commands with:\n * - Progress logging with start and success messages\n * - Debug logging of the actual command when DEBUG mode is enabled\n * - Error handling and reporting\n *\n * @param label - A human-readable label describing what the command does.\n * @param command - The command string to execute, or undefined if no command should be run.\n * @returns A promise that resolves when the command execution is complete.\n */\nexport async function runCommand(label: string, command: string | void, options: { timeout?: number; throwOnError?: boolean } = {}) {\n let timer: NodeJS.Timeout | undefined;\n\n try {\n const startTime = Date.now();\n log.start(`${label}`);\n\n if (getEnv().DEBUG) {\n log.info(`→ ${command}`);\n }\n else {\n timer = setInterval(() => {\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n\n if (elapsed > 0) {\n process.stdout.write(`\\r⏳ ${label}... ${elapsed}s`);\n }\n }, 1000);\n }\n\n await executeCommand(command, parseCommandOutput, options);\n\n log.success(`${label} done.`);\n }\n catch (error) {\n if (options.throwOnError) {\n throw error;\n }\n catchError(error);\n }\n finally {\n if (timer) {\n clearInterval(timer);\n process.stdout.write(`\\r\\x1B[K`);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"command.util.js","names":[],"sources":["../../../src/node/command/command.util.ts"],"sourcesContent":["import { exec, execFile } from 'node:child_process';\nimport process from 'node:process';\nimport * as util from 'node:util';\n\nimport { getEnv } from '#config/env/index.js';\n\nimport type { I_IssueEntry } from '../log/index.js';\nimport type { I_CommandContext, I_EslintError, T_Command, T_CommandMapInput } from './command.type.js';\n\nimport { catchError, E_IssueType, log } from '../log/index.js';\nimport { getPackage } from '../package/index.js';\nimport { CYBERSKILL_CLI, CYBERSKILL_CLI_PATH, CYBERSKILL_PACKAGE_NAME, PNPM_EXEC_CLI, TSX_CLI } from '../path/index.js';\nimport { storage } from '../storage/index.js';\n\nconst execPromise = util.promisify(exec);\nconst execFilePromise = util.promisify(execFile);\nconst SHELL_METACHARACTERS = /[|&;<>`$(){}[\\]!#~*?]/;\nconst RE_ESLINT_ERROR = /^\\s*(\\d+):(\\d+)\\s+(error|warning)\\s+(\\S+(?:\\s+\\S+)*)\\s+(\\S+)$/;\nconst RE_TS_ERROR = /^(.+?)\\((\\d+),(\\d+)\\):\\s+(error|warning)\\s+TS\\d+:\\s+(\\S.+)$/;\nconst RE_COMMITLINT_ERROR = /^✖\\s+(\\S+(?:\\s+\\S+)*)\\s+\\[([^\\]]*)\\]$/;\nconst RE_WHITESPACE = /\\s+/;\n\n/**\n * Retrieves the package name for the current project.\n * This function attempts to get the package name from the current project's package.json.\n * If the package information cannot be retrieved, it returns a timestamp as a fallback.\n * Results are cached after first successful read to avoid redundant I/O.\n *\n * @returns A promise that resolves to the package name or a timestamp string.\n */\nlet _cachedPackageName: string | null = null;\n/** Returns the cached package name, falling back to a timestamp if unavailable. */\nasync function getPackageName() {\n if (_cachedPackageName) {\n return _cachedPackageName;\n }\n\n const pkg = await getPackage();\n\n if (!pkg.success) {\n return Date.now().toString();\n }\n\n _cachedPackageName = pkg.result.name;\n return _cachedPackageName;\n}\n\n/**\n * Resets the cached package name. For testing only.\n * Call this to force `getPackageName()` to re-read the package.json on the next invocation.\n */\nexport function resetCommandCacheForTesting(): void {\n _cachedPackageName = null;\n}\n\n/**\n * Saves a list of error entries to persistent storage.\n * This function stores error information with the package name as the key,\n * and provides a log link for manual inspection of the stored errors.\n *\n * @param errorList - An array of error entries to be stored.\n * @returns A promise that resolves when the storage operation is complete.\n */\nasync function saveErrorListToStorage(errorList: I_IssueEntry[]): Promise<void> {\n if (errorList.length === 0) {\n return;\n }\n\n const packageName = await getPackageName();\n\n try {\n const existingErrors = await getStoredErrorLists();\n const mergedErrors = [...existingErrors, ...errorList];\n\n await storage.set(packageName, mergedErrors);\n\n const logPath = await storage.getLogLink(packageName);\n if (logPath) {\n log.info(`📂 Open the error list manually: ${logPath}`);\n }\n }\n catch (error: unknown) {\n catchError(error);\n }\n}\n\n/**\n * Retrieves all stored error lists from persistent storage.\n * This function fetches error entries that were previously saved using the package name as the key.\n *\n * @returns A promise that resolves to an array of error entries, or an empty array if none are found.\n */\nexport async function getStoredErrorLists(): Promise<I_IssueEntry[]> {\n try {\n const packageName = await getPackageName();\n const allErrors = await storage.get<I_IssueEntry[]>(packageName);\n\n return allErrors ?? [];\n }\n catch (error: unknown) {\n return catchError<I_IssueEntry[]>(error, {\n returnValue: [],\n });\n }\n}\n\n/**\n * Clears all stored error lists from persistent storage.\n * This function removes all error entries associated with the current package name.\n *\n * @returns A promise that resolves when the clearing operation is complete.\n */\nexport async function clearAllErrorLists(): Promise<void> {\n try {\n const packageName = await getPackageName();\n await storage.remove(packageName);\n }\n catch (error: unknown) {\n catchError(error);\n }\n}\n\n/**\n * Parses text-based error output and converts it to structured error entries.\n * This function processes command output that contains error information in text format,\n * extracting file paths, line numbers, error types, messages, and rule violations.\n * It handles multiple error formats including ESLint, TypeScript, and commitlint errors.\n *\n * @param output - The raw text output from a command execution containing error information.\n */\nasync function parseTextErrors(output: string): Promise<void> {\n const errorList: I_IssueEntry[] = [];\n const unmatchedLines: string[] = [];\n let lastFilePath = '';\n const eslintErrorDetailsRegex = RE_ESLINT_ERROR;\n const tsRegex = RE_TS_ERROR;\n const commitlintRegex = RE_COMMITLINT_ERROR;\n\n output.split('\\n').forEach((line) => {\n const tsMatch = tsRegex.exec(line);\n\n if (tsMatch) {\n errorList.push({\n file: tsMatch?.[1] ?? '',\n position: `${tsMatch[2]}:${tsMatch[3]}`,\n type: tsMatch[4] === E_IssueType.Error ? E_IssueType.Error : E_IssueType.Warning,\n message: tsMatch?.[5]?.trim() ?? '',\n });\n }\n else if (line.startsWith('/')) {\n lastFilePath = line.trim();\n }\n else {\n const eslintMatch = eslintErrorDetailsRegex.exec(line);\n\n if (eslintMatch && lastFilePath) {\n errorList.push({\n file: lastFilePath,\n position: `${eslintMatch[1]}:${eslintMatch[2]}`,\n type: eslintMatch[3] === E_IssueType.Error ? E_IssueType.Error : E_IssueType.Warning,\n message: eslintMatch?.[4]?.trim() ?? '',\n rule: eslintMatch?.[5]?.trim() ?? '',\n });\n }\n else {\n const commitlintMatch = commitlintRegex.exec(line);\n\n if (commitlintMatch) {\n errorList.push({\n file: 'commitlint',\n type: E_IssueType.Error,\n message: commitlintMatch?.[1]?.trim() ?? '',\n rule: commitlintMatch?.[2]?.trim() ?? '',\n });\n }\n else {\n unmatchedLines.push(line.trim());\n }\n }\n }\n });\n\n if (errorList.length) {\n await saveErrorListToStorage(errorList);\n }\n\n if (unmatchedLines.length) {\n log.warn(`Unmatched lines:`);\n unmatchedLines.forEach(line => log.info(` ${line}`));\n }\n}\n\n/**\n * Parses command output that contains structured error information.\n * This function attempts to parse JSON-formatted error output (typically from ESLint)\n * and converts it to structured error entries. If JSON parsing fails, it falls back\n * to text-based parsing.\n *\n * @param output - The command output to parse, expected to be JSON-formatted error data.\n */\nasync function parseCommandOutput(output: string): Promise<void> {\n try {\n const results: I_EslintError[] = JSON.parse(output);\n const errorList: I_IssueEntry[] = [];\n\n results.forEach(({ filePath, messages }) => {\n messages.forEach(({ severity, line, column, ruleId, message }) => {\n errorList.push({\n type: severity === 2 ? E_IssueType.Error : E_IssueType.Warning,\n file: filePath,\n position: `${line}:${column}`,\n rule: ruleId,\n message,\n });\n });\n });\n\n if (errorList.length) {\n await saveErrorListToStorage(errorList);\n }\n }\n catch {\n await parseTextErrors(output);\n }\n}\n\n/**\n * Executes a command and processes its output for errors.\n * This function runs a command with proper signal handling for graceful termination,\n * processes both stdout and stderr for error information, and handles command failures.\n *\n * @param command - The command string to execute, or undefined if no command should be run.\n * @param parser - The function to use for parsing command output (defaults to parseCommandOutput).\n * @returns A promise that resolves when the command execution is complete.\n */\nasync function executeCommand(command: string | void, parser = parseCommandOutput, options: { timeout?: number } = {}): Promise<void> {\n const controller = new AbortController();\n\n const onSigint = () => {\n log.warn('Process interrupted. Terminating...');\n controller.abort();\n process.exit(130);\n };\n\n process.once('SIGINT', onSigint);\n\n try {\n if (typeof command === 'string') {\n const execOptions = {\n maxBuffer: 10 * 1024 * 1024,\n signal: controller.signal,\n timeout: options.timeout,\n };\n\n let result: { stdout: string; stderr: string };\n\n if (SHELL_METACHARACTERS.test(command)) {\n result = await execPromise(command, execOptions);\n }\n else {\n const parts = command.split(RE_WHITESPACE).filter(Boolean);\n result = await execFilePromise(parts[0]!, parts.slice(1), execOptions);\n }\n\n await Promise.all([result.stdout, result.stderr].map(output => output && parser(output)));\n }\n }\n catch (error: unknown) {\n const { stdout, stderr, message } = error as {\n stdout?: string;\n stderr?: string;\n message: string;\n };\n\n await Promise.all([stdout, stderr].map(output => output && parser(output)));\n\n if (!stderr && !stdout) {\n log.error(`Command failed: ${message}`);\n }\n\n throw error;\n }\n finally {\n process.removeListener('SIGINT', onSigint);\n }\n}\n\n/**\n * Creates a raw command object that bypasses CLI formatting.\n * This function wraps a command string in an object that indicates it should be executed\n * as-is without any additional CLI formatting or path resolution.\n *\n * @param cmd - The raw command string to be executed directly.\n * @returns An object containing the raw command with a flag indicating it should not be formatted.\n */\nexport function rawCommand(cmd: string) {\n return { raw: true, cmd };\n}\n\n/**\n * Formats a command for CLI execution based on the current project context.\n * This function determines whether to use the current project's CLI path or the global CLI,\n * and formats the command accordingly with the appropriate executable paths.\n *\n * @param command - The command string to format.\n * @param context - Optional context information about the current project.\n * @returns The formatted command string ready for execution.\n */\nfunction formatCLI(command: string, context?: I_CommandContext) {\n if (context?.isCurrentProject) {\n return `${PNPM_EXEC_CLI} ${TSX_CLI} ${CYBERSKILL_CLI_PATH} ${command}`;\n }\n\n return `${PNPM_EXEC_CLI} ${CYBERSKILL_CLI} ${command}`;\n}\n\n/**\n * Formats a command based on its type and context.\n * This function handles different command types:\n * - Function commands: Executes the function with context and formats the result\n * - Raw commands: Returns the command as-is without formatting\n * - String commands: Formats them as CLI commands\n *\n * @param command - The command to format, which can be a string, function, or raw command object.\n * @param context - Optional context information for command execution.\n * @returns The formatted command string ready for execution.\n */\nexport function formatCommand(command: T_Command, context?: I_CommandContext) {\n if (typeof command === 'function') {\n return formatCLI(command(context), context);\n }\n\n if (typeof command === 'object' && command?.raw === true) {\n return command.cmd;\n }\n\n if (typeof command === 'string') {\n return formatCLI(command, context);\n }\n\n return command;\n}\n\n/**\n * Resolves a map of commands by formatting them based on the current project context.\n * This function takes a command map (either static or dynamic) and formats all commands\n * using the appropriate CLI paths based on whether the current project is the Cyberskill package.\n *\n * @param input - The command map to resolve, which can be static or a function that returns a map.\n * @returns A promise that resolves to an object with formatted command strings, or undefined if package info cannot be retrieved.\n */\nexport async function resolveCommands(input: T_CommandMapInput) {\n const packageData = await getPackage({ name: CYBERSKILL_PACKAGE_NAME });\n\n if (packageData.success) {\n const ctx: I_CommandContext = { isCurrentProject: packageData.result.isCurrentProject };\n const commands = typeof input === 'function' ? input(ctx) : input;\n\n return Object.fromEntries(\n Object.entries(commands).map(([key, cmd]) => [key, formatCommand(cmd, ctx)]),\n );\n }\n}\n\n/**\n * Executes a command with proper logging and error handling.\n * This function provides a standardized way to run commands with:\n * - Progress logging with start and success messages\n * - Debug logging of the actual command when DEBUG mode is enabled\n * - Error handling and reporting\n *\n * @param label - A human-readable label describing what the command does.\n * @param command - The command string to execute, or undefined if no command should be run.\n * @returns A promise that resolves when the command execution is complete.\n */\nexport async function runCommand(label: string, command: string | void, options: { timeout?: number; throwOnError?: boolean } = {}) {\n let timer: NodeJS.Timeout | undefined;\n\n try {\n const startTime = Date.now();\n log.start(`${label}`);\n\n if (getEnv().DEBUG) {\n log.info(`→ ${command}`);\n }\n else {\n timer = setInterval(() => {\n const elapsed = Math.floor((Date.now() - startTime) / 1000);\n\n if (elapsed > 0) {\n process.stdout.write(`\\r⏳ ${label}... ${elapsed}s`);\n }\n }, 1000);\n }\n\n await executeCommand(command, parseCommandOutput, options);\n\n log.success(`${label} done.`);\n }\n catch (error: unknown) {\n if (options.throwOnError) {\n throw error;\n }\n catchError(error);\n }\n finally {\n if (timer) {\n clearInterval(timer);\n process.stdout.write(`\\r\\x1B[K`);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -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 { 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_MAX_TIME_MS, MONGO_SLUG_MAX_ATTEMPTS } from './mongo.constant.js';\nimport { wrapNotFound } from './mongo.controller.helpers.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 private defaultProjection: T_ProjectionType<T>;\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 * @param options.defaultProjection - Default projection to apply to findOne and findAll queries.\n */\n constructor(private model: I_ExtendedModel<T>, options?: { defaultLimit?: number; defaultProjection?: T_ProjectionType<T> }) {\n this.defaultLimit = options?.defaultLimit ?? 1_000;\n this.defaultProjection = options?.defaultProjection ?? {};\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 appliedProjection = Object.keys(projection).length > 0 ? projection : Object.keys(this.defaultProjection).length > 0 ? this.defaultProjection : projection;\n const query = this.model.findOne(normalizedFilter, appliedProjection, options).maxTimeMS(MONGO_MAX_TIME_MS).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 wrapNotFound(this.getModelName());\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 appliedProjection = Object.keys(projection).length > 0 ? projection : Object.keys(this.defaultProjection).length > 0 ? this.defaultProjection : projection;\n const query = this.model.find(normalizedFilter, appliedProjection, options).maxTimeMS(MONGO_MAX_TIME_MS).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 wrapNotFound(this.getModelName());\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 wrapNotFound(this.getModelName());\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 wrapNotFound('documents');\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_MAX_TIME_MS, MONGO_SLUG_MAX_ATTEMPTS } from './mongo.constant.js';\nimport { wrapNotFound } from './mongo.controller.helpers.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 private defaultProjection: T_ProjectionType<T>;\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 * @param options.defaultProjection - Default projection to apply to findOne and findAll queries.\n */\n constructor(private model: I_ExtendedModel<T>, options?: { defaultLimit?: number; defaultProjection?: T_ProjectionType<T> }) {\n this.defaultLimit = options?.defaultLimit ?? 1_000;\n this.defaultProjection = options?.defaultProjection ?? {};\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 appliedProjection = Object.keys(projection).length > 0 ? projection : Object.keys(this.defaultProjection).length > 0 ? this.defaultProjection : projection;\n const query = this.model.findOne(normalizedFilter, appliedProjection, options).maxTimeMS(MONGO_MAX_TIME_MS).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 wrapNotFound(this.getModelName());\n }\n\n const finalResult = await this.populateDynamicVirtualsForDocument(result, populate);\n\n return { success: true, result: toPlainObject(finalResult) };\n }\n catch (error: unknown) {\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 appliedProjection = Object.keys(projection).length > 0 ? projection : Object.keys(this.defaultProjection).length > 0 ? this.defaultProjection : projection;\n const query = this.model.find(normalizedFilter, appliedProjection, options).maxTimeMS(MONGO_MAX_TIME_MS).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: unknown) {\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: unknown) {\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: unknown) {\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: unknown) {\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: unknown) {\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: unknown) {\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 wrapNotFound(this.getModelName());\n }\n\n return { success: true, result: result?.toObject?.() ?? result };\n }\n catch (error: unknown) {\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: unknown) {\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 wrapNotFound(this.getModelName());\n }\n\n return { success: true, result: result?.toObject?.() ?? result };\n }\n catch (error: unknown) {\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 wrapNotFound('documents');\n }\n\n return { success: true, result };\n }\n catch (error: unknown) {\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: unknown) {\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: unknown) {\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: unknown) {\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: unknown) {\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: unknown) {\n return catchError<unknown[]>(error);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongo.controller.native.js","names":[],"sources":["../../../src/node/mongo/mongo.controller.native.ts"],"sourcesContent":["import type { I_Return } from '#typescript/index.js';\n\nimport type { C_Collection, C_Db, C_Document, T_BulkWriteOperation, T_BulkWriteResult, T_DeleteResult, T_Filter, T_OptionalUnlessRequiredId, T_UpdateResult, T_WithId } from './mongo.type.js';\n\nimport { catchError, log } from '../log/index.js';\nimport { MONGO_MAX_TIME_MS } from './mongo.constant.js';\nimport { wrapError, wrapNotFound } from './mongo.controller.helpers.js';\nimport { mongo } from './mongo.util.js';\n\n/**\n * MongoDB native driver controller for direct database operations.\n * This class provides a simplified interface for MongoDB operations using the native driver,\n * with automatic generic field generation and standardized response formatting.\n *\n * @see I_MongoController for the shared polymorphic interface (use via type assertion when needed).\n */\nexport class MongoController<D extends Partial<C_Document>> {\n private collection: C_Collection<D>;\n private defaultLimit: number;\n private collectionName: string;\n\n /**\n * Creates a new MongoDB controller instance.\n *\n * @param db - The MongoDB database instance.\n * @param collectionName - The name of the collection 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(db: C_Db, collectionName: string, options?: { defaultLimit?: number }) {\n this.collection = db.collection<D>(collectionName);\n this.collectionName = collectionName;\n this.defaultLimit = options?.defaultLimit ?? 1_000;\n }\n\n /**\n * Creates a single document in the collection.\n * This method adds generic fields (id, isDel, timestamps) to the document before insertion.\n *\n * @param document - The document to create, with or without generic fields.\n * @returns A promise that resolves to a standardized response with the created document.\n */\n async createOne(document: D | Partial<D>): Promise<I_Return<D | Partial<D>>> {\n try {\n const finalDocument = {\n ...mongo.createGenericFields(),\n ...document,\n };\n\n const result = await this.collection.insertOne(finalDocument as unknown as T_OptionalUnlessRequiredId<D>);\n\n if (!result.acknowledged) {\n return wrapError('Document creation failed');\n }\n\n return {\n success: true,\n message: 'Document created successfully',\n result: finalDocument,\n };\n }\n catch (error) {\n return catchError<(D | Partial<D>)>(error);\n }\n }\n\n /**\n * Creates multiple documents in the collection.\n * This method adds generic fields to each document before bulk insertion.\n *\n * @param documents - An array of documents to create.\n * @returns A promise that resolves to a standardized response with the created documents.\n */\n async createMany(documents: (D | Partial<D>)[]): Promise<I_Return<(D | Partial<D>)[]>> {\n try {\n const finalDocuments = documents.map(document => ({\n ...mongo.createGenericFields(),\n ...document,\n }));\n\n const result = await this.collection.insertMany(finalDocuments as unknown as T_OptionalUnlessRequiredId<D>[]);\n\n if (result.insertedCount === 0) {\n return wrapError('No documents were inserted');\n }\n\n return {\n success: true,\n message: `${result.insertedCount} documents created successfully`,\n result: finalDocuments,\n };\n }\n catch (error) {\n return catchError<(D | Partial<D>)[]>(error);\n }\n }\n\n /**\n * Finds a single document by filter criteria.\n *\n * @param filter - The filter criteria to find the document.\n * @returns A promise that resolves to a standardized response with the found document.\n */\n async findOne(filter: T_Filter<D>): Promise<I_Return<T_WithId<D>>> {\n try {\n const result = await this.collection.findOne(filter, { maxTimeMS: MONGO_MAX_TIME_MS });\n\n if (!result) {\n return wrapNotFound('Document');\n }\n\n return { success: true, message: 'Document found', result };\n }\n catch (error) {\n return catchError<T_WithId<D>>(error);\n }\n }\n\n /**\n * Finds all documents matching the filter criteria.\n *\n * @param filter - The filter criteria to find documents (defaults to empty object for all documents).\n * @returns A promise that resolves to a standardized response with the found documents.\n */\n async findAll(\n filter: T_Filter<D> = {},\n options?: { skip?: number; limit?: number },\n ): Promise<I_Return<T_WithId<D>[]>> {\n try {\n const limit = options?.limit ?? this.defaultLimit;\n let query = this.collection.find(filter).limit(limit).maxTimeMS(MONGO_MAX_TIME_MS);\n\n if (options?.skip) {\n query = query.skip(options.skip);\n }\n\n const result = await query.toArray();\n\n const truncated = result.length === limit;\n\n if (truncated) {\n log.warn(`[${this.collectionName}] findAll returned exactly ${limit} documents (the limit). Results may be truncated. Consider using pagination or setting an explicit limit.`);\n }\n\n return {\n success: true,\n message: 'Documents retrieved successfully',\n result,\n truncated,\n };\n }\n catch (error) {\n return catchError<T_WithId<D>[]>(error);\n }\n }\n\n /**\n * Counts documents matching the filter criteria.\n *\n * @param filter - The filter criteria to count documents (defaults to empty object for all documents).\n * @returns A promise that resolves to a standardized response with the document count.\n */\n async count(\n filter: T_Filter<D> = {},\n ): Promise<I_Return<number>> {\n try {\n const result = await this.collection.countDocuments(filter);\n\n return {\n success: true,\n message: `${result} documents counted successfully`,\n result,\n };\n }\n catch (error) {\n return catchError<number>(error);\n }\n }\n\n /**\n * Updates a single document matching the filter criteria.\n *\n * @param filter - The filter criteria to find the document to update.\n * @param update - The update data to apply to the document.\n * @returns A promise that resolves to a standardized response with the update result.\n */\n async updateOne(\n filter: T_Filter<D>,\n update: Partial<D>,\n ): Promise<I_Return<T_UpdateResult>> {\n try {\n const result = await this.collection.updateOne(filter, {\n $set: update,\n });\n\n if (result.matchedCount === 0) {\n return wrapNotFound('No documents matched the filter');\n }\n return {\n success: true,\n message: 'Document updated successfully',\n result,\n };\n }\n catch (error) {\n return catchError<T_UpdateResult>(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 to the documents.\n * @returns A promise that resolves to a standardized response with the update result.\n */\n async updateMany(\n filter: T_Filter<D>,\n update: Partial<D>,\n ): Promise<I_Return<T_UpdateResult>> {\n try {\n const result = await this.collection.updateMany(filter, {\n $set: update,\n });\n\n if (result.matchedCount === 0) {\n return wrapNotFound('No documents matched the filter');\n }\n\n return {\n success: true,\n message: 'Documents updated successfully',\n result,\n };\n }\n catch (error) {\n return catchError<T_UpdateResult>(error);\n }\n }\n\n /**\n * Deletes a single document matching the filter criteria.\n *\n * @param filter - The filter criteria to find the document to delete.\n * @returns A promise that resolves to a standardized response with the delete result.\n */\n async deleteOne(\n filter: T_Filter<D>,\n ): Promise<I_Return<T_DeleteResult>> {\n try {\n const result = await this.collection.deleteOne(filter);\n\n if (result.deletedCount === 0) {\n return wrapNotFound('No documents matched the filter');\n }\n return {\n success: true,\n message: 'Document deleted successfully',\n result,\n };\n }\n catch (error) {\n return catchError<T_DeleteResult>(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 * @returns A promise that resolves to a standardized response with the delete result.\n */\n async deleteMany(\n filter: T_Filter<D>,\n ): Promise<I_Return<T_DeleteResult>> {\n try {\n const result = await this.collection.deleteMany(filter);\n\n if (result.deletedCount === 0) {\n return wrapNotFound('No documents matched the filter');\n }\n\n return {\n success: true,\n message: 'Documents deleted successfully',\n result,\n };\n }\n catch (error) {\n return catchError<T_DeleteResult>(error);\n }\n }\n\n /**\n * Executes multiple write operations in a single batch.\n * Use this for high-performance ingestion or batched updates.\n * Note: This method does NOT automatically add generic fields (id, timestamps)\n * to the documents. If required, inject these fields before invoking bulkWrite.\n *\n * @param operations - The array of bulk write operations.\n * @returns A promise that resolves to a standardized response with the bulk write result.\n */\n async bulkWrite(\n operations: T_BulkWriteOperation<D>[],\n ): Promise<I_Return<T_BulkWriteResult>> {\n try {\n if (!operations.length) {\n return wrapError('No bulk write operations provided');\n }\n\n const result = await this.collection.bulkWrite(operations);\n\n return {\n success: true,\n message: 'Bulk write executed successfully',\n result,\n };\n }\n catch (error) {\n return catchError<T_BulkWriteResult>(error);\n }\n }\n}\n\n/**\n * Factory function to create a native MongoController.\n * Use this method to construct controllers in migration scripts or services.\n *\n * @param db - The MongoDB database instance.\n * @param collectionName - The name of the collection.\n * @param options - Controller options.\n * @param options.defaultLimit - Optional default limit for pagination.\n * @returns A new MongoController instance for the given collection.\n * @since 3.13.0\n */\nexport function createMongoController<D extends Partial<C_Document>>(\n db: C_Db,\n collectionName: string,\n options?: { defaultLimit?: number },\n): MongoController<D> {\n return new MongoController<D>(db, collectionName, options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mongo.controller.native.js","names":[],"sources":["../../../src/node/mongo/mongo.controller.native.ts"],"sourcesContent":["import type { I_Return } from '#typescript/index.js';\n\nimport type { C_Collection, C_Db, C_Document, T_BulkWriteOperation, T_BulkWriteResult, T_DeleteResult, T_Filter, T_OptionalUnlessRequiredId, T_UpdateResult, T_WithId } from './mongo.type.js';\n\nimport { catchError, log } from '../log/index.js';\nimport { MONGO_MAX_TIME_MS } from './mongo.constant.js';\nimport { wrapError, wrapNotFound } from './mongo.controller.helpers.js';\nimport { mongo } from './mongo.util.js';\n\n/**\n * MongoDB native driver controller for direct database operations.\n * This class provides a simplified interface for MongoDB operations using the native driver,\n * with automatic generic field generation and standardized response formatting.\n *\n * @see I_MongoController for the shared polymorphic interface (use via type assertion when needed).\n */\nexport class MongoController<D extends Partial<C_Document>> {\n private collection: C_Collection<D>;\n private defaultLimit: number;\n private collectionName: string;\n\n /**\n * Creates a new MongoDB controller instance.\n *\n * @param db - The MongoDB database instance.\n * @param collectionName - The name of the collection 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(db: C_Db, collectionName: string, options?: { defaultLimit?: number }) {\n this.collection = db.collection<D>(collectionName);\n this.collectionName = collectionName;\n this.defaultLimit = options?.defaultLimit ?? 1_000;\n }\n\n /**\n * Creates a single document in the collection.\n * This method adds generic fields (id, isDel, timestamps) to the document before insertion.\n *\n * @param document - The document to create, with or without generic fields.\n * @returns A promise that resolves to a standardized response with the created document.\n */\n async createOne(document: D | Partial<D>): Promise<I_Return<D | Partial<D>>> {\n try {\n const finalDocument = {\n ...mongo.createGenericFields(),\n ...document,\n };\n\n const result = await this.collection.insertOne(finalDocument as unknown as T_OptionalUnlessRequiredId<D>);\n\n if (!result.acknowledged) {\n return wrapError('Document creation failed');\n }\n\n return {\n success: true,\n message: 'Document created successfully',\n result: finalDocument,\n };\n }\n catch (error: unknown) {\n return catchError<(D | Partial<D>)>(error);\n }\n }\n\n /**\n * Creates multiple documents in the collection.\n * This method adds generic fields to each document before bulk insertion.\n *\n * @param documents - An array of documents to create.\n * @returns A promise that resolves to a standardized response with the created documents.\n */\n async createMany(documents: (D | Partial<D>)[]): Promise<I_Return<(D | Partial<D>)[]>> {\n try {\n const finalDocuments = documents.map(document => ({\n ...mongo.createGenericFields(),\n ...document,\n }));\n\n const result = await this.collection.insertMany(finalDocuments as unknown as T_OptionalUnlessRequiredId<D>[]);\n\n if (result.insertedCount === 0) {\n return wrapError('No documents were inserted');\n }\n\n return {\n success: true,\n message: `${result.insertedCount} documents created successfully`,\n result: finalDocuments,\n };\n }\n catch (error: unknown) {\n return catchError<(D | Partial<D>)[]>(error);\n }\n }\n\n /**\n * Finds a single document by filter criteria.\n *\n * @param filter - The filter criteria to find the document.\n * @returns A promise that resolves to a standardized response with the found document.\n */\n async findOne(filter: T_Filter<D>): Promise<I_Return<T_WithId<D>>> {\n try {\n const result = await this.collection.findOne(filter, { maxTimeMS: MONGO_MAX_TIME_MS });\n\n if (!result) {\n return wrapNotFound('Document');\n }\n\n return { success: true, message: 'Document found', result };\n }\n catch (error: unknown) {\n return catchError<T_WithId<D>>(error);\n }\n }\n\n /**\n * Finds all documents matching the filter criteria.\n *\n * @param filter - The filter criteria to find documents (defaults to empty object for all documents).\n * @returns A promise that resolves to a standardized response with the found documents.\n */\n async findAll(\n filter: T_Filter<D> = {},\n options?: { skip?: number; limit?: number },\n ): Promise<I_Return<T_WithId<D>[]>> {\n try {\n const limit = options?.limit ?? this.defaultLimit;\n let query = this.collection.find(filter).limit(limit).maxTimeMS(MONGO_MAX_TIME_MS);\n\n if (options?.skip) {\n query = query.skip(options.skip);\n }\n\n const result = await query.toArray();\n\n const truncated = result.length === limit;\n\n if (truncated) {\n log.warn(`[${this.collectionName}] findAll returned exactly ${limit} documents (the limit). Results may be truncated. Consider using pagination or setting an explicit limit.`);\n }\n\n return {\n success: true,\n message: 'Documents retrieved successfully',\n result,\n truncated,\n };\n }\n catch (error: unknown) {\n return catchError<T_WithId<D>[]>(error);\n }\n }\n\n /**\n * Counts documents matching the filter criteria.\n *\n * @param filter - The filter criteria to count documents (defaults to empty object for all documents).\n * @returns A promise that resolves to a standardized response with the document count.\n */\n async count(\n filter: T_Filter<D> = {},\n ): Promise<I_Return<number>> {\n try {\n const result = await this.collection.countDocuments(filter);\n\n return {\n success: true,\n message: `${result} documents counted successfully`,\n result,\n };\n }\n catch (error: unknown) {\n return catchError<number>(error);\n }\n }\n\n /**\n * Updates a single document matching the filter criteria.\n *\n * @param filter - The filter criteria to find the document to update.\n * @param update - The update data to apply to the document.\n * @returns A promise that resolves to a standardized response with the update result.\n */\n async updateOne(\n filter: T_Filter<D>,\n update: Partial<D>,\n ): Promise<I_Return<T_UpdateResult>> {\n try {\n const result = await this.collection.updateOne(filter, {\n $set: update,\n });\n\n if (result.matchedCount === 0) {\n return wrapNotFound('No documents matched the filter');\n }\n return {\n success: true,\n message: 'Document updated successfully',\n result,\n };\n }\n catch (error: unknown) {\n return catchError<T_UpdateResult>(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 to the documents.\n * @returns A promise that resolves to a standardized response with the update result.\n */\n async updateMany(\n filter: T_Filter<D>,\n update: Partial<D>,\n ): Promise<I_Return<T_UpdateResult>> {\n try {\n const result = await this.collection.updateMany(filter, {\n $set: update,\n });\n\n if (result.matchedCount === 0) {\n return wrapNotFound('No documents matched the filter');\n }\n\n return {\n success: true,\n message: 'Documents updated successfully',\n result,\n };\n }\n catch (error: unknown) {\n return catchError<T_UpdateResult>(error);\n }\n }\n\n /**\n * Deletes a single document matching the filter criteria.\n *\n * @param filter - The filter criteria to find the document to delete.\n * @returns A promise that resolves to a standardized response with the delete result.\n */\n async deleteOne(\n filter: T_Filter<D>,\n ): Promise<I_Return<T_DeleteResult>> {\n try {\n const result = await this.collection.deleteOne(filter);\n\n if (result.deletedCount === 0) {\n return wrapNotFound('No documents matched the filter');\n }\n return {\n success: true,\n message: 'Document deleted successfully',\n result,\n };\n }\n catch (error: unknown) {\n return catchError<T_DeleteResult>(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 * @returns A promise that resolves to a standardized response with the delete result.\n */\n async deleteMany(\n filter: T_Filter<D>,\n ): Promise<I_Return<T_DeleteResult>> {\n try {\n const result = await this.collection.deleteMany(filter);\n\n if (result.deletedCount === 0) {\n return wrapNotFound('No documents matched the filter');\n }\n\n return {\n success: true,\n message: 'Documents deleted successfully',\n result,\n };\n }\n catch (error: unknown) {\n return catchError<T_DeleteResult>(error);\n }\n }\n\n /**\n * Executes multiple write operations in a single batch.\n * Use this for high-performance ingestion or batched updates.\n * Note: This method does NOT automatically add generic fields (id, timestamps)\n * to the documents. If required, inject these fields before invoking bulkWrite.\n *\n * @param operations - The array of bulk write operations.\n * @returns A promise that resolves to a standardized response with the bulk write result.\n */\n async bulkWrite(\n operations: T_BulkWriteOperation<D>[],\n ): Promise<I_Return<T_BulkWriteResult>> {\n try {\n if (!operations.length) {\n return wrapError('No bulk write operations provided');\n }\n\n const result = await this.collection.bulkWrite(operations);\n\n return {\n success: true,\n message: 'Bulk write executed successfully',\n result,\n };\n }\n catch (error: unknown) {\n return catchError<T_BulkWriteResult>(error);\n }\n }\n}\n\n/**\n * Factory function to create a native MongoController.\n * Use this method to construct controllers in migration scripts or services.\n *\n * @param db - The MongoDB database instance.\n * @param collectionName - The name of the collection.\n * @param options - Controller options.\n * @param options.defaultLimit - Optional default limit for pagination.\n * @returns A new MongoController instance for the given collection.\n * @since 3.13.0\n */\nexport function createMongoController<D extends Partial<C_Document>>(\n db: C_Db,\n collectionName: string,\n options?: { defaultLimit?: number },\n): MongoController<D> {\n return new MongoController<D>(db, collectionName, options);\n}\n"],"mappings":"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"}
|
|
@@ -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 * 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: unknown) {\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":";;;;;AAoBA,SAAgB,EACZ,GACA,GAC4B;AAC5B,KAAI,CAAC,KAAY,CAAC,KAAmB,EAAgB,WAAW,EAC5D,QAAO;CAGX,IAAM,IAAsB,IAAI,IAAI,EAAgB,KAAI,MAAK,EAAE,KAAK,CAAC;AAErE,KAAI,MAAM,QAAQ,EAAS,EAAE;EACzB,IAAM,IAAW,EAAS,QAAQ,MAAM;AACpC,OAAI,OAAO,KAAM,SACb,QAAO,CAAC,CAAC,GAAG,EAAoB,CAAC,MAAK,MAClC,MAAM,KAAe,EAAE,WAAW,GAAG,EAAY,GAAG,CACvD;AAGL,OAAI,OAAO,KAAM,YAAY,GAAY;IACrC,IAAM,IAAS,GACT,IAAO,EAAO,QAAQ,EAAO,YAAY;AAE/C,WAAO,CAAC,CAAC,GAAG,EAAoB,CAAC,MAAK,MAClC,MAAS,KAAe,EAAK,WAAW,GAAG,EAAY,GAAG,CAC7D;;AAGL,UAAO;IACT;AAEF,SAAO,EAAS,SAAS,IAAK,IAAgC,KAAA;;AAGlE,KAAI,OAAO,KAAa,SACpB,QAAO,CAAC,GAAG,EAAoB,CAAC,MAAK,MACjC,MAAa,KAAe,EAAS,WAAW,GAAG,EAAY,GAAG,CACrE,GACK,KAAA,IACA;AAGV,KAAI,OAAO,KAAa,YAAY,GAAmB;EACnD,IAAM,IAAS,GACT,IAAO,EAAO,QAAQ,EAAO,YAAY;AAE/C,SAAO,CAAC,GAAG,EAAoB,CAAC,MAAK,MACjC,MAAS,KAAe,EAAK,WAAW,GAAG,EAAY,GAAG,CAC7D,GACK,KAAA,IACA;;AAGV,QAAO;;AAcX,SAAgB,EACZ,GACA,GACA,GACmC;AACnC,KAAI,CAAC,EAAU,UAAU,CAAC,KAAe,CAAC,GAAgB,IACtD,QAAO,EAAE;CAGb,IAAM,oBAAc,IAAI,KAAkB;AA0B1C,QAzBA,EAAU,SAAS,MAAQ;AACvB,MAAI;GACA,IAAM,IAAY,EAAe,IAAI,EAAI;AAEzC,OAAI,KAAyC,KACzC;GAGJ,IAAM,IAAkB,OAAO,KAAc,WAAW,IAAY,OAAO,EAAU;AAErF,OAAI,KAAmB,EAAgB,MAAM,KAAK,IAAI;IAClD,IAAM,IAAqB,EAAuB,EAAgB;AAMlE,IAJK,EAAY,IAAI,EAAmB,IACpC,EAAY,IAAI,GAAoB,EAAE,CAAC,EAG3C,EAAY,IAAI,EAAmB,CAAE,KAAK,EAAI;;WAG/C,GAAgB;AACnB,KAAW,gBAAI,MAAM,4CAA4C,EAAY,KAAK,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM,GAAG,CAAC;;GAElJ,EAEK,MAAM,KAAK,EAAY,SAAS,GAAG,CAAC,GAAO,QAAW;EAAE;EAAO;EAAM,EAAE;;AAQlF,SAAgB,EAAc,GAAqE;AAC/F,QAAuB,OAAO,KAAQ,cAA/B,KAA2C,cAAc,KAAO,OAAQ,EAA8B,YAAa;;AAsB9H,eAAsB,EAClB,GACA,GACA,GACA,GACA,GACA,GACY;AAKZ,KAJI,CAAC,EAAU,UAAU,CAAC,EAAe,UAIrC,CAAC,EACD,QAAO;CAGX,IAAM,IAAoB,EAAe,QAAQ,MAAW;AACxD,MAAI,MAAM,QAAQ,EAAS,CACvB,QAAO,EAAS,SAAS,KAAK,EAAS,MAAM,MAAM;AAC/C,OAAI,OAAO,KAAM,SACb,QAAO,MAAM,EAAO,QAAQ,EAAE,WAAW,GAAG,EAAO,KAAK,GAAG;AAE/D,OAAI,KAAK,OAAO,KAAM,UAAU;IAC5B,IAAM,IAAS,GACT,IAAO,EAAO,QAAQ,EAAO,YAAY;AAE/C,WAAO,MAAS,EAAO,QAAQ,EAAK,WAAW,GAAG,EAAO,KAAK,GAAG;;AAGrE,UAAO;IACT;AAGN,MAAI,OAAO,KAAa,SACpB,QAAO,MAAa,EAAO,QAAQ,EAAS,WAAW,GAAG,EAAO,KAAK,GAAG;AAG7E,MAAI,OAAO,KAAa,YAAY,GAAmB;GACnD,IAAM,IAAS,GACT,IAAO,EAAO,QAAQ,EAAO,YAAY;AAE/C,UAAQ,MAAS,EAAO,QAAS,EAAK,WAAW,GAAG,EAAO,KAAK,GAAG;;AAGvE,SAAO;GACT;AAEF,KAAI,EAAkB,WAAW,EAC7B,QAAO;CAMX,IAAM,IAAkB,EADD,EAAU,KAAI,MAAO,EAAc,EAAI,GAAG,EAAI,UAAU,GAAG,EAAI,CACrC;AAEjD,GAAgB,SAAS,MAAQ;AAC7B,IAAkB,SAAS,EAAE,SAAM,iBAAc;AAC7C,GAAM,KAAQ,MACT,EAAmC,KAAkB,EAAQ,QAAQ,IAAK,EAAQ,UAAU,OAAO,EAAE;IAE5G;GACJ;CAEF,IAAM,oBAAqB,IAAI,KAI3B;AAEJ,MAAK,IAAM,KAAiB,GAAmB;EAC3C,IAAM,EAAE,SAAM,eAAY,GACpB,IAAiB,EAAqB,GAAiB,GAAM,EAAQ;AAE3E,OAAK,IAAM,KAAS,GAAgB;AAChC,GAAK,EAAmB,IAAI,EAAM,MAAM,IACpC,EAAmB,IAAI,EAAM,OAAO;IAChC,UAAU,EAAE;IACZ,gCAAgB,IAAI,KAAK;IACzB,kCAAkB,IAAI,KAAK;IAC9B,CAAC;GAGN,IAAM,IAAa,EAAmB,IAAI,EAAM,MAAM;AAEtD,GAAK,EAAW,SAAS,MAAK,MAAK,EAAE,SAAS,EAAK,KAC/C,EAAW,SAAS,KAAK,EAAc,EACvC,EAAW,eAAe,IAAI,mBAAgB,IAAI,KAAK,CAAC;GAG5D,IAAM,IAAgB,EAAW,eAAe,IAAI,EAAe;AACnE,KAAM,KAAK,SAAS,MAAQ;IACxB,IAAM,IAAY,EAAmC,EAAQ;AAE7D,QAAI,KAAY,MAAM;KAClB,IAAM,IAAS,OAAO,EAAS;AAC/B,OAAc,IAAI,EAAO;KAEzB,IAAI,IAAM,IAEJ,IAAc;AAiBpB,KAfI,EAAY,OAAU,KAAA,IAOjB,EAAY,QAAW,KAAA,MAC5B,IAAM,EAAgB,WAAW,MACX,EAED,KAAQ,YAAY,KAAK,EAAY,KAAQ,YAAY,CAC5E,IAXF,IAAM,EAAgB,WAAW,MACX,EAED,OAAU,EAAY,GACzC,EAUF,MAAQ,OACH,EAAW,iBAAiB,IAAI,EAAO,IACxC,EAAW,iBAAiB,IAAI,GAAQ,EAAE,CAAC,EAE/C,EAAW,iBAAiB,IAAI,EAAO,CAAE,KAAK,EAAI;;KAG5D;;;AA6KV,QAzKA,MAAM,QAAQ,IAAI,MAAM,KAAK,EAAmB,SAAS,EAAE,OAAO,CAAC,GAAW,OAAgB;EAC1F,IAAM,IAAQ,EAAS,OAAO;AAE9B,MAAI,CAAC,EACD;EAGJ,IAAM,oBAAiB,IAAI,KAAa;AAKxC,MAJA,EAAW,eAAe,SAAS,MAAkB;AACjD,KAAc,SAAQ,MAAO,EAAe,IAAI,EAAI,CAAC;IACvD,EAEE,EAAe,SAAS,EACxB;EAGJ,IAAM,IAAgB,CAAC,GAAG,IAAI,IAAI,EAAW,SAAS,KAAI,MAAK,EAAE,QAAQ,aAAa,CAAC,CAAC,EAClF,IAAmB,CAAC,GAAG,EAAe,EACxC;AAEJ,EAII,IAJA,EAAc,WAAW,IACjB,GAAG,OAAO,EAAc,GAAG,GAAG,EAAE,KAAK,GAAkB,EAAE,GAGzD,EAAE,KAAK,EAAc,KAAI,OAAU,GAAG,IAAQ,EAAE,KAAK,GAAkB,EAAE,EAAE,EAAE;EAGzF,IAAM,IAAmB,MAAM,EAAM,KAAK,GAAO,EAAW,CAAC,MAAM;AAEnE,OAAK,IAAM,KAAiB,EAAW,UAAU;GAC7C,IAAM,EAAE,SAAM,eAAY,GACpB,IAAe,EAAiB,QAAQ,MAAS;IACnD,IAAM,IAAc,EAAM,EAAQ;AAElC,WAAO,KAAc,QAAQ,EAAe,IAAI,OAAO,EAAW,CAAC;KACrE;AAEF,OAAI,EAAQ,OAAO;IACf,IAAM,oBAAW,IAAI,KAAqB;AAU1C,IARA,EAAa,SAAS,MAAS;KAC3B,IAAM,IAAO,EAAM,EAAQ,eAAe,UAAU;AAEpD,KAAI,KACA,EAAS,IAAI,IAAM,EAAS,IAAI,EAAI,IAAI,KAAK,EAAE;MAErD,EAEF,EAAW,eAAe,IAAI,EAAe,CAAE,SAAS,MAAe;KACnE,IAAM,IAAO,EAAW,iBAAiB,IAAI,EAAW,IAAI,EAAE,EACxD,IAAQ,EAAS,IAAI,EAAW,IAAI;AAE1C,OAAK,SAAS,MAAQ;MAClB,IAAM,IAAc,EAAgB;AAEpC,MAAI,EAAY,OAAU,KAAA,MACtB,EAAY,KAAQ;OAE1B;MACJ;UAED;IACD,IAAM,oBAAY,IAAI,KAAkB;AAaxC,IAXA,EAAa,SAAS,MAAS;KAC3B,IAAM,IAAO,EAAM,EAAQ,eAAe,UAAU;AAEpD,KAAI,MACK,EAAU,IAAI,EAAI,IACnB,EAAU,IAAI,GAAK,EAAE,CAAC,EAE1B,EAAU,IAAI,EAAI,CAAE,KAAK,EAAK;MAEpC,EAEF,EAAW,eAAe,IAAI,EAAe,CAAE,SAAS,MAAa;KACjE,IAAM,IAAO,EAAW,iBAAiB,IAAI,EAAS,IAAI,EAAE,EACtD,IAAU,EAAU,IAAI,EAAS,IAAI,EAAE,EACvC,IAAQ,EAAQ,UAAW,EAAQ,MAAM,OAAQ;AAEvD,OAAK,SAAS,MAAQ;MAClB,IAAM,IAAc,EAAgB;AACpC,QAAY,KAAQ;OACtB;MACJ;;;GAGZ,CAAC,EAEC,KA6EA,MAAM,EAAoB,GAAU,KA5ET,MAA4C;EACnE,IAAM,IAAU,MAAM,QAAQ,EAAI,GAAG,IAAM,CAAC,EAAI,EAC1C,oBAAU,IAAI,KAAwB,EACtC,IAAyB,EAAE;AAEjC,OAAK,IAAM,KAAS,EAChB,KAAI,OAAO,KAAU,SACjB,KAAI,EAAM,SAAS,IAAI,EAAE;GACrB,IAAM,IAAQ,EAAM,MAAM,IAAI,EACxB,IAAQ,EAAM,MAAM,IACpB,IAAO,EAAM,MAAM,EAAE,CAAC,KAAK,IAAI;AAErC,GAAI,MACK,EAAQ,IAAI,EAAM,IACnB,EAAQ,IAAI,GAAO,EAAE,CAAC,EAEtB,KACA,EAAQ,IAAI,EAAM,CAAE,KAAK,EAAK;QAKtC,GAAY,KAAK,EAAM;WAGtB,KAAS,OAAO,KAAU,UAAU;GACzC,IAAM,IAAM;AAEZ,OAAI,EAAI,QAAQ,EAAI,KAAK,SAAS,IAAI,EAAE;IACpC,IAAM,IAAQ,EAAI,KAAK,MAAM,IAAI,EAC3B,IAAQ,EAAM,MAAM,IACpB,IAAO,EAAM,MAAM,EAAE,CAAC,KAAK,IAAI;AAErC,IAAI,MACK,EAAQ,IAAI,EAAM,IACnB,EAAQ,IAAI,GAAO,EAAE,CAAC,EAEtB,KACA,EAAQ,IAAI,EAAM,CAAE,KAAK,EAAK,EAE9B,EAAI,YACJ,EAAQ,IAAI,EAAM,CAAE,KAAK,EAAI,SAAS;SAK9C,GAAY,KAAK,EAAM;;EAKnC,IAAM,IAAwB,CAAC,GAAG,EAAY;AAoB9C,SAnBA,EAAQ,SAAS,GAAQ,MAAS;GAC9B,IAAM,IAAkB,EAAE;AAE1B,QAAK,IAAM,KAAK,EACZ,EAAI,OAAO,KAAM,YAGR,KAAK,OAAO,KAAM,aAFvB,EAAK,KAAK,EAAE;AAMpB,GACI,EAAW,KADX,EAAK,SAAS,IACE;IAAE,MAAM;IAAM,UAAU;IAAqC,GAG7D,EAAK;IAE3B,EAEK;IAGkC,EAAS,EAEmB,GAA6D,EAAW,EAG9I"}
|
|
@@ -40,6 +40,7 @@ interface I_MongoUtils {
|
|
|
40
40
|
};
|
|
41
41
|
regexify: <T>(filter?: T_QueryFilter<T>, fields?: (keyof T | string)[]) => T_QueryFilter<T>;
|
|
42
42
|
isDynamicVirtual: <T, R extends string>(options?: T_VirtualOptions<T, R>) => options is I_DynamicVirtualOptions<T, R>;
|
|
43
|
+
fetchAllRecords: <T extends I_GenericDocument>(controller: MongoController<T>, filter?: T_Filter<T>, batchSize?: number) => Promise<T_WithId<T>[]>;
|
|
43
44
|
getNewRecords: <T extends I_GenericDocument>(controller: MongoController<T>, recordsToCheck: T[], filterFn: (existingRecord: T_WithId<T>, newRecord: T) => boolean, filter?: T_Filter<T>) => Promise<T[]>;
|
|
44
45
|
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>[]>;
|
|
45
46
|
health: (mongooseInstance: typeof mongooseRaw) => Record<string, unknown>;
|