@socketsecurity/lib 2.10.4 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +29 -24
  2. package/README.md +231 -40
  3. package/dist/constants/platform.js +1 -1
  4. package/dist/constants/platform.js.map +3 -3
  5. package/dist/cover/code.js +1 -1
  6. package/dist/cover/code.js.map +3 -3
  7. package/dist/debug.js +2 -2
  8. package/dist/debug.js.map +3 -3
  9. package/dist/dlx-binary.d.ts +29 -6
  10. package/dist/dlx-binary.js +6 -6
  11. package/dist/dlx-binary.js.map +3 -3
  12. package/dist/dlx-package.d.ts +16 -1
  13. package/dist/dlx-package.js +6 -6
  14. package/dist/dlx-package.js.map +3 -3
  15. package/dist/dlx.js +1 -1
  16. package/dist/dlx.js.map +3 -3
  17. package/dist/env/rewire.js +1 -1
  18. package/dist/env/rewire.js.map +3 -3
  19. package/dist/external/yoctocolors-cjs.d.ts +14 -0
  20. package/dist/fs.d.ts +2 -2
  21. package/dist/fs.js.map +1 -1
  22. package/dist/git.js +1 -1
  23. package/dist/git.js.map +3 -3
  24. package/dist/http-request.js +1 -1
  25. package/dist/http-request.js.map +3 -3
  26. package/dist/ipc.js +1 -1
  27. package/dist/ipc.js.map +3 -3
  28. package/dist/links/index.d.ts +65 -0
  29. package/dist/links/index.js +3 -0
  30. package/dist/links/index.js.map +7 -0
  31. package/dist/logger.d.ts +21 -18
  32. package/dist/logger.js +1 -1
  33. package/dist/logger.js.map +3 -3
  34. package/dist/packages/isolation.js +1 -1
  35. package/dist/packages/isolation.js.map +3 -3
  36. package/dist/paths.js +1 -1
  37. package/dist/paths.js.map +2 -2
  38. package/dist/process-lock.js +2 -2
  39. package/dist/process-lock.js.map +3 -3
  40. package/dist/promises.d.ts +6 -21
  41. package/dist/promises.js +1 -1
  42. package/dist/promises.js.map +2 -2
  43. package/dist/prompts/index.d.ts +115 -0
  44. package/dist/prompts/index.js +3 -0
  45. package/dist/prompts/index.js.map +7 -0
  46. package/dist/spinner.d.ts +33 -23
  47. package/dist/spinner.js +1 -1
  48. package/dist/spinner.js.map +3 -3
  49. package/dist/stdio/mask.d.ts +2 -2
  50. package/dist/stdio/mask.js +4 -4
  51. package/dist/stdio/mask.js.map +3 -3
  52. package/dist/stdio/stdout.js +1 -1
  53. package/dist/stdio/stdout.js.map +3 -3
  54. package/dist/themes/context.d.ts +80 -0
  55. package/dist/themes/context.js +3 -0
  56. package/dist/themes/context.js.map +7 -0
  57. package/dist/themes/index.d.ts +53 -0
  58. package/dist/themes/index.js +3 -0
  59. package/dist/themes/index.js.map +7 -0
  60. package/dist/themes/themes.d.ts +49 -0
  61. package/dist/themes/themes.js +3 -0
  62. package/dist/themes/themes.js.map +7 -0
  63. package/dist/themes/types.d.ts +92 -0
  64. package/dist/themes/types.js +3 -0
  65. package/dist/themes/types.js.map +7 -0
  66. package/dist/themes/utils.d.ts +78 -0
  67. package/dist/themes/utils.js +3 -0
  68. package/dist/themes/utils.js.map +7 -0
  69. package/package.json +39 -7
  70. package/dist/download-lock.d.ts +0 -49
  71. package/dist/download-lock.js +0 -10
  72. package/dist/download-lock.js.map +0 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/packages/isolation.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Package isolation utilities for testing.\n * Provides tools to set up isolated test environments for packages.\n */\n\nimport { existsSync, promises as fs } from 'node:fs'\n\nimport { WIN32 } from '#constants/platform'\n\nimport type { PackageJson } from '../packages'\nimport { isAbsolute, isPath, trimLeadingDotSlash } from '../path'\nimport { readPackageJson } from './operations'\nimport { getOsTmpDir } from '#lib/paths'\n\nlet _path: typeof import('node:path') | undefined\n\n/*@__NO_SIDE_EFFECTS__*/\nfunction getPath() {\n if (_path === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _path = /*@__PURE__*/ require('node:path')\n }\n return _path as typeof import('path')\n}\n\n/**\n * Copy options for fs.cp with cross-platform retry support.\n */\nconst FS_CP_OPTIONS = {\n dereference: true,\n errorOnExist: false,\n filter: (src: string) =>\n !src.includes('node_modules') && !src.endsWith('.DS_Store'),\n force: true,\n recursive: true,\n ...(WIN32 ? { maxRetries: 3, retryDelay: 100 } : {}),\n}\n\n/**\n * Resolve a path to its real location, handling symlinks.\n */\nasync function resolveRealPath(pathStr: string): Promise<string> {\n const path = getPath()\n return await fs.realpath(pathStr).catch(() => path.resolve(pathStr))\n}\n\n/**\n * Merge and write package.json with original and new values.\n */\nasync function mergePackageJson(\n pkgJsonPath: string,\n originalPkgJson: PackageJson | undefined,\n): Promise<PackageJson> {\n const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'))\n const mergedPkgJson = originalPkgJson\n ? { ...originalPkgJson, ...pkgJson }\n : pkgJson\n return mergedPkgJson\n}\n\nexport type IsolatePackageOptions = {\n imports?: Record<string, string> | undefined\n install?: ((cwd: string) => Promise<void>) | undefined\n onPackageJson?:\n | ((pkgJson: PackageJson) => PackageJson | Promise<PackageJson>)\n | undefined\n sourcePath?: string | undefined\n}\n\nexport type IsolatePackageResult = {\n exports?: Record<string, unknown> | undefined\n tmpdir: string\n}\n\n/**\n * Isolates a package in a temporary test environment.\n *\n * Supports multiple input types:\n * 1. File system path (absolute or relative)\n * 2. Package name with optional version spec\n * 3. npm package spec (parsed via npm-package-arg)\n *\n * @throws {Error} When package installation or setup fails.\n */\nexport async function isolatePackage(\n packageSpec: string,\n options?: IsolatePackageOptions | undefined,\n): Promise<IsolatePackageResult> {\n const path = getPath()\n const opts = { __proto__: null, ...options } as IsolatePackageOptions\n const { imports, install, onPackageJson, sourcePath: optSourcePath } = opts\n\n let sourcePath = optSourcePath\n let packageName: string | undefined\n let spec: string | undefined\n\n // Determine if this is a path or package spec.\n if (isPath(packageSpec)) {\n // File system path.\n // Handle edge case on Windows where path.relative() returns an absolute path\n // when paths are on different drives, and the test prepends './' to it.\n // Example: './C:\\path\\to\\file' should be treated as 'C:\\path\\to\\file'.\n const trimmedPath = trimLeadingDotSlash(packageSpec)\n const pathToResolve = isAbsolute(trimmedPath) ? trimmedPath : packageSpec\n sourcePath = path.resolve(pathToResolve)\n\n if (!existsSync(sourcePath)) {\n throw new Error(`Source path does not exist: ${sourcePath}`)\n }\n\n // Read package.json to get the name.\n const pkgJson = await readPackageJson(sourcePath, { normalize: true })\n if (!pkgJson) {\n throw new Error(`Could not read package.json from: ${sourcePath}`)\n }\n packageName = pkgJson.name as string\n } else {\n // Parse as npm package spec.\n const npa = /*@__PURE__*/ require('../external/npm-package-arg')\n const parsed = npa(packageSpec)\n\n packageName = parsed.name\n\n if (parsed.type === 'directory' || parsed.type === 'file') {\n sourcePath = parsed.fetchSpec\n if (!sourcePath || !existsSync(sourcePath)) {\n throw new Error(`Source path does not exist: ${sourcePath}`)\n }\n // If package name not provided by parser, read from package.json.\n if (!packageName) {\n const pkgJson = await readPackageJson(sourcePath, { normalize: true })\n if (!pkgJson) {\n throw new Error(`Could not read package.json from: ${sourcePath}`)\n }\n packageName = pkgJson.name as string\n }\n } else {\n // Registry package.\n spec = parsed.fetchSpec || parsed.rawSpec\n }\n }\n\n if (!packageName) {\n throw new Error(`Could not determine package name from: ${packageSpec}`)\n }\n\n // Create temp directory for this package.\n const sanitizedName = packageName.replace(/[@/]/g, '-')\n const tempDir = await fs.mkdtemp(\n path.join(getOsTmpDir(), `socket-test-${sanitizedName}-`),\n )\n const packageTempDir = path.join(tempDir, sanitizedName)\n await fs.mkdir(packageTempDir, { recursive: true })\n\n let installedPath: string\n let originalPackageJson: PackageJson | undefined\n\n if (spec) {\n // Installing from registry first, then copying source on top if provided.\n await fs.writeFile(\n path.join(packageTempDir, 'package.json'),\n JSON.stringify(\n {\n name: 'test-temp',\n private: true,\n version: '1.0.0',\n },\n null,\n 2,\n ),\n )\n\n // Use custom install function or default pnpm install.\n if (install) {\n await install(packageTempDir)\n } else {\n const { spawn } = /*@__PURE__*/ require('../spawn')\n const WIN32 = require('../constants/platform').WIN32\n const packageInstallSpec = spec.startsWith('https://')\n ? spec\n : `${packageName}@${spec}`\n\n await spawn('pnpm', ['add', packageInstallSpec], {\n cwd: packageTempDir,\n shell: WIN32,\n stdio: 'pipe',\n })\n }\n\n installedPath = path.join(packageTempDir, 'node_modules', packageName)\n\n // Save original package.json before copying source.\n originalPackageJson = await readPackageJson(installedPath, {\n normalize: true,\n })\n\n // Copy source files on top if provided.\n if (sourcePath) {\n // Check if source and destination are the same (symlinked).\n const realInstalledPath = await resolveRealPath(installedPath)\n const realSourcePath = await resolveRealPath(sourcePath)\n\n if (realSourcePath !== realInstalledPath) {\n await fs.cp(sourcePath, installedPath, FS_CP_OPTIONS)\n }\n }\n } else {\n // Just copying local package, no registry install.\n if (!sourcePath) {\n throw new Error('sourcePath is required when no version spec provided')\n }\n\n const scopedPath = packageName.startsWith('@')\n ? path.join(\n packageTempDir,\n 'node_modules',\n packageName.split('/')[0] ?? '',\n )\n : path.join(packageTempDir, 'node_modules')\n\n await fs.mkdir(scopedPath, { recursive: true })\n installedPath = path.join(packageTempDir, 'node_modules', packageName)\n\n await fs.cp(sourcePath, installedPath, FS_CP_OPTIONS)\n }\n\n // Prepare package.json if callback provided or if we need to merge with original.\n if (onPackageJson || originalPackageJson) {\n const pkgJsonPath = path.join(installedPath, 'package.json')\n const mergedPkgJson = await mergePackageJson(\n pkgJsonPath,\n originalPackageJson,\n )\n\n const finalPkgJson = onPackageJson\n ? await onPackageJson(mergedPkgJson)\n : mergedPkgJson\n\n await fs.writeFile(pkgJsonPath, JSON.stringify(finalPkgJson, null, 2))\n }\n\n // Install dependencies.\n if (install) {\n await install(installedPath)\n } else {\n const { spawn } = /*@__PURE__*/ require('../spawn')\n const WIN32 = require('../constants/platform').WIN32\n await spawn('pnpm', ['install'], {\n cwd: installedPath,\n shell: WIN32,\n stdio: 'pipe',\n })\n }\n\n // Load module exports if imports provided.\n const exports: Record<string, unknown> = imports\n ? { __proto__: null }\n : (undefined as unknown as Record<string, unknown>)\n\n if (imports) {\n for (const { 0: key, 1: specifier } of Object.entries(imports)) {\n const fullPath = path.join(installedPath, specifier)\n exports[key] = require(fullPath)\n }\n }\n\n return {\n exports,\n tmpdir: installedPath,\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["isolation_exports", "__export", "isolatePackage", "__toCommonJS", "import_node_fs", "import_platform", "import_path", "import_operations", "import_paths", "_path", "getPath", "FS_CP_OPTIONS", "src", "resolveRealPath", "pathStr", "path", "fs", "mergePackageJson", "pkgJsonPath", "originalPkgJson", "pkgJson", "packageSpec", "options", "opts", "imports", "install", "onPackageJson", "optSourcePath", "sourcePath", "packageName", "spec", "trimmedPath", "pathToResolve", "parsed", "sanitizedName", "tempDir", "packageTempDir", "installedPath", "originalPackageJson", "spawn", "WIN32", "packageInstallSpec", "realInstalledPath", "scopedPath", "mergedPkgJson", "finalPkgJson", "exports", "key", "specifier", "fullPath"]
4
+ "sourcesContent": ["/**\n * @fileoverview Package isolation utilities for testing.\n * Provides tools to set up isolated test environments for packages.\n */\n\nimport { existsSync, promises as fs } from 'fs'\n\nimport { WIN32 } from '#constants/platform'\n\nimport type { PackageJson } from '../packages'\nimport { isAbsolute, isPath, trimLeadingDotSlash } from '../path'\nimport { readPackageJson } from './operations'\nimport { getOsTmpDir } from '#lib/paths'\n\nlet _path: typeof import('node:path') | undefined\n\n/*@__NO_SIDE_EFFECTS__*/\nfunction getPath() {\n if (_path === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _path = /*@__PURE__*/ require('node:path')\n }\n return _path as typeof import('path')\n}\n\n/**\n * Copy options for fs.cp with cross-platform retry support.\n */\nconst FS_CP_OPTIONS = {\n dereference: true,\n errorOnExist: false,\n filter: (src: string) =>\n !src.includes('node_modules') && !src.endsWith('.DS_Store'),\n force: true,\n recursive: true,\n ...(WIN32 ? { maxRetries: 3, retryDelay: 100 } : {}),\n}\n\n/**\n * Resolve a path to its real location, handling symlinks.\n */\nasync function resolveRealPath(pathStr: string): Promise<string> {\n const path = getPath()\n return await fs.realpath(pathStr).catch(() => path.resolve(pathStr))\n}\n\n/**\n * Merge and write package.json with original and new values.\n */\nasync function mergePackageJson(\n pkgJsonPath: string,\n originalPkgJson: PackageJson | undefined,\n): Promise<PackageJson> {\n const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'))\n const mergedPkgJson = originalPkgJson\n ? { ...originalPkgJson, ...pkgJson }\n : pkgJson\n return mergedPkgJson\n}\n\nexport type IsolatePackageOptions = {\n imports?: Record<string, string> | undefined\n install?: ((cwd: string) => Promise<void>) | undefined\n onPackageJson?:\n | ((pkgJson: PackageJson) => PackageJson | Promise<PackageJson>)\n | undefined\n sourcePath?: string | undefined\n}\n\nexport type IsolatePackageResult = {\n exports?: Record<string, unknown> | undefined\n tmpdir: string\n}\n\n/**\n * Isolates a package in a temporary test environment.\n *\n * Supports multiple input types:\n * 1. File system path (absolute or relative)\n * 2. Package name with optional version spec\n * 3. npm package spec (parsed via npm-package-arg)\n *\n * @throws {Error} When package installation or setup fails.\n */\nexport async function isolatePackage(\n packageSpec: string,\n options?: IsolatePackageOptions | undefined,\n): Promise<IsolatePackageResult> {\n const path = getPath()\n const opts = { __proto__: null, ...options } as IsolatePackageOptions\n const { imports, install, onPackageJson, sourcePath: optSourcePath } = opts\n\n let sourcePath = optSourcePath\n let packageName: string | undefined\n let spec: string | undefined\n\n // Determine if this is a path or package spec.\n if (isPath(packageSpec)) {\n // File system path.\n // Handle edge case on Windows where path.relative() returns an absolute path\n // when paths are on different drives, and the test prepends './' to it.\n // Example: './C:\\path\\to\\file' should be treated as 'C:\\path\\to\\file'.\n const trimmedPath = trimLeadingDotSlash(packageSpec)\n const pathToResolve = isAbsolute(trimmedPath) ? trimmedPath : packageSpec\n sourcePath = path.resolve(pathToResolve)\n\n if (!existsSync(sourcePath)) {\n throw new Error(`Source path does not exist: ${sourcePath}`)\n }\n\n // Read package.json to get the name.\n const pkgJson = await readPackageJson(sourcePath, { normalize: true })\n if (!pkgJson) {\n throw new Error(`Could not read package.json from: ${sourcePath}`)\n }\n packageName = pkgJson.name as string\n } else {\n // Parse as npm package spec.\n const npa = /*@__PURE__*/ require('../external/npm-package-arg')\n const parsed = npa(packageSpec)\n\n packageName = parsed.name\n\n if (parsed.type === 'directory' || parsed.type === 'file') {\n sourcePath = parsed.fetchSpec\n if (!sourcePath || !existsSync(sourcePath)) {\n throw new Error(`Source path does not exist: ${sourcePath}`)\n }\n // If package name not provided by parser, read from package.json.\n if (!packageName) {\n const pkgJson = await readPackageJson(sourcePath, { normalize: true })\n if (!pkgJson) {\n throw new Error(`Could not read package.json from: ${sourcePath}`)\n }\n packageName = pkgJson.name as string\n }\n } else {\n // Registry package.\n spec = parsed.fetchSpec || parsed.rawSpec\n }\n }\n\n if (!packageName) {\n throw new Error(`Could not determine package name from: ${packageSpec}`)\n }\n\n // Create temp directory for this package.\n const sanitizedName = packageName.replace(/[@/]/g, '-')\n const tempDir = await fs.mkdtemp(\n path.join(getOsTmpDir(), `socket-test-${sanitizedName}-`),\n )\n const packageTempDir = path.join(tempDir, sanitizedName)\n await fs.mkdir(packageTempDir, { recursive: true })\n\n let installedPath: string\n let originalPackageJson: PackageJson | undefined\n\n if (spec) {\n // Installing from registry first, then copying source on top if provided.\n await fs.writeFile(\n path.join(packageTempDir, 'package.json'),\n JSON.stringify(\n {\n name: 'test-temp',\n private: true,\n version: '1.0.0',\n },\n null,\n 2,\n ),\n )\n\n // Use custom install function or default pnpm install.\n if (install) {\n await install(packageTempDir)\n } else {\n const { spawn } = /*@__PURE__*/ require('../spawn')\n const WIN32 = require('../constants/platform').WIN32\n const packageInstallSpec = spec.startsWith('https://')\n ? spec\n : `${packageName}@${spec}`\n\n await spawn('pnpm', ['add', packageInstallSpec], {\n cwd: packageTempDir,\n shell: WIN32,\n stdio: 'pipe',\n })\n }\n\n installedPath = path.join(packageTempDir, 'node_modules', packageName)\n\n // Save original package.json before copying source.\n originalPackageJson = await readPackageJson(installedPath, {\n normalize: true,\n })\n\n // Copy source files on top if provided.\n if (sourcePath) {\n // Check if source and destination are the same (symlinked).\n const realInstalledPath = await resolveRealPath(installedPath)\n const realSourcePath = await resolveRealPath(sourcePath)\n\n if (realSourcePath !== realInstalledPath) {\n await fs.cp(sourcePath, installedPath, FS_CP_OPTIONS)\n }\n }\n } else {\n // Just copying local package, no registry install.\n if (!sourcePath) {\n throw new Error('sourcePath is required when no version spec provided')\n }\n\n const scopedPath = packageName.startsWith('@')\n ? path.join(\n packageTempDir,\n 'node_modules',\n packageName.split('/')[0] ?? '',\n )\n : path.join(packageTempDir, 'node_modules')\n\n await fs.mkdir(scopedPath, { recursive: true })\n installedPath = path.join(packageTempDir, 'node_modules', packageName)\n\n await fs.cp(sourcePath, installedPath, FS_CP_OPTIONS)\n }\n\n // Prepare package.json if callback provided or if we need to merge with original.\n if (onPackageJson || originalPackageJson) {\n const pkgJsonPath = path.join(installedPath, 'package.json')\n const mergedPkgJson = await mergePackageJson(\n pkgJsonPath,\n originalPackageJson,\n )\n\n const finalPkgJson = onPackageJson\n ? await onPackageJson(mergedPkgJson)\n : mergedPkgJson\n\n await fs.writeFile(pkgJsonPath, JSON.stringify(finalPkgJson, null, 2))\n }\n\n // Install dependencies.\n if (install) {\n await install(installedPath)\n } else {\n const { spawn } = /*@__PURE__*/ require('../spawn')\n const WIN32 = require('../constants/platform').WIN32\n await spawn('pnpm', ['install'], {\n cwd: installedPath,\n shell: WIN32,\n stdio: 'pipe',\n })\n }\n\n // Load module exports if imports provided.\n const exports: Record<string, unknown> = imports\n ? { __proto__: null }\n : (undefined as unknown as Record<string, unknown>)\n\n if (imports) {\n for (const { 0: key, 1: specifier } of Object.entries(imports)) {\n const fullPath = path.join(installedPath, specifier)\n exports[key] = require(fullPath)\n }\n }\n\n return {\n exports,\n tmpdir: installedPath,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["isolation_exports", "__export", "isolatePackage", "__toCommonJS", "import_fs", "import_platform", "import_path", "import_operations", "import_paths", "_path", "getPath", "FS_CP_OPTIONS", "src", "resolveRealPath", "pathStr", "path", "fs", "mergePackageJson", "pkgJsonPath", "originalPkgJson", "pkgJson", "packageSpec", "options", "opts", "imports", "install", "onPackageJson", "optSourcePath", "sourcePath", "packageName", "spec", "trimmedPath", "pathToResolve", "parsed", "sanitizedName", "tempDir", "packageTempDir", "installedPath", "originalPackageJson", "spawn", "WIN32", "packageInstallSpec", "realInstalledPath", "scopedPath", "mergedPkgJson", "finalPkgJson", "exports", "key", "specifier", "fullPath"]
7
7
  }
package/dist/paths.js CHANGED
@@ -1,3 +1,3 @@
1
1
  /* Socket Lib - Built with esbuild */
2
- var T=Object.create;var f=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var R=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var q=(e,r)=>{for(var i in r)f(e,i,{get:r[i],enumerable:!0})},m=(e,r,i,_)=>{if(r&&typeof r=="object"||typeof r=="function")for(let c of l(r))!I.call(e,c)&&c!==i&&f(e,c,{get:()=>r[c],enumerable:!(_=k(r,c))||_.enumerable});return e};var C=(e,r,i)=>(i=e!=null?T(R(e)):{},m(r||!e||!e.__esModule?f(i,"default",{value:e,enumerable:!0}):i,e)),O=e=>m(f({},"__esModule",{value:!0}),e);var M={};q(M,{getOsHomeDir:()=>d,getOsTmpDir:()=>H,getSocketAppCacheDir:()=>P,getSocketAppCacheTtlDir:()=>h,getSocketAppDir:()=>p,getSocketCacacheDir:()=>j,getSocketCliDir:()=>U,getSocketDlxDir:()=>v,getSocketHomePath:()=>K,getSocketRegistryDir:()=>$,getSocketRegistryGithubCacheDir:()=>G,getSocketUserDir:()=>s,getUserHomeDir:()=>A,invalidateCache:()=>x});module.exports=O(M);var D=C(require("node:os")),n=C(require("node:path")),S=require("#env/home"),o=require("#env/socket"),E=require("#env/windows"),t=require("./path"),g=require("./paths/rewire");function d(){return(0,g.getPathValue)("homedir",()=>D.homedir())}function H(){return(0,g.getPathValue)("tmpdir",()=>D.tmpdir())}function K(){return s()}let a;function s(){return a===void 0&&(a=(0,t.normalizePath)(n.join(A(),require("#constants/paths").DOT_SOCKET_DIR))),a}function p(e){return(0,t.normalizePath)(n.join(s(),`${require("#constants/socket").SOCKET_APP_PREFIX}${e}`))}let u;function j(){return u===void 0&&((0,o.getSocketCacacheDir)()?u=(0,t.normalizePath)((0,o.getSocketCacacheDir)()):u=(0,t.normalizePath)(n.join(s(),`${require("#constants/socket").SOCKET_APP_PREFIX}cacache`))),u}function v(){return(0,o.getSocketDlxDirEnv)()?(0,t.normalizePath)((0,o.getSocketDlxDirEnv)()):(0,t.normalizePath)(n.join(s(),`${require("#constants/socket").SOCKET_APP_PREFIX}${require("#constants/socket").SOCKET_DLX_APP_NAME}`))}function P(e){return(0,t.normalizePath)(n.join(p(e),require("#constants/paths").CACHE_DIR))}function h(e){return(0,t.normalizePath)(n.join(P(e),require("#constants/paths").CACHE_TTL_DIR))}function U(){return p(require("#constants/socket").SOCKET_CLI_APP_NAME)}function $(){return p(require("#constants/socket").SOCKET_REGISTRY_APP_NAME)}function G(){return(0,t.normalizePath)(n.join(h(require("#constants/socket").SOCKET_REGISTRY_APP_NAME),require("#constants/github").CACHE_GITHUB_DIR))}function A(){const e=(0,S.getHome)();if(e)return e;const r=(0,E.getUserprofile)();return r||d()}function x(){a=void 0,u=void 0}(0,g.registerCacheInvalidation)(x);0&&(module.exports={getOsHomeDir,getOsTmpDir,getSocketAppCacheDir,getSocketAppCacheTtlDir,getSocketAppDir,getSocketCacacheDir,getSocketCliDir,getSocketDlxDir,getSocketHomePath,getSocketRegistryDir,getSocketRegistryGithubCacheDir,getSocketUserDir,getUserHomeDir,invalidateCache});
2
+ var T=Object.create;var f=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var R=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var q=(e,r)=>{for(var i in r)f(e,i,{get:r[i],enumerable:!0})},m=(e,r,i,_)=>{if(r&&typeof r=="object"||typeof r=="function")for(let c of l(r))!I.call(e,c)&&c!==i&&f(e,c,{get:()=>r[c],enumerable:!(_=k(r,c))||_.enumerable});return e};var C=(e,r,i)=>(i=e!=null?T(R(e)):{},m(r||!e||!e.__esModule?f(i,"default",{value:e,enumerable:!0}):i,e)),O=e=>m(f({},"__esModule",{value:!0}),e);var M={};q(M,{getOsHomeDir:()=>d,getOsTmpDir:()=>H,getSocketAppCacheDir:()=>P,getSocketAppCacheTtlDir:()=>h,getSocketAppDir:()=>p,getSocketCacacheDir:()=>j,getSocketCliDir:()=>U,getSocketDlxDir:()=>v,getSocketHomePath:()=>K,getSocketRegistryDir:()=>$,getSocketRegistryGithubCacheDir:()=>G,getSocketUserDir:()=>s,getUserHomeDir:()=>A,invalidateCache:()=>x});module.exports=O(M);var D=C(require("os")),n=C(require("path")),S=require("#env/home"),o=require("#env/socket"),E=require("#env/windows"),t=require("./path"),g=require("./paths/rewire");function d(){return(0,g.getPathValue)("homedir",()=>D.homedir())}function H(){return(0,g.getPathValue)("tmpdir",()=>D.tmpdir())}function K(){return s()}let a;function s(){return a===void 0&&(a=(0,t.normalizePath)(n.join(A(),require("#constants/paths").DOT_SOCKET_DIR))),a}function p(e){return(0,t.normalizePath)(n.join(s(),`${require("#constants/socket").SOCKET_APP_PREFIX}${e}`))}let u;function j(){return u===void 0&&((0,o.getSocketCacacheDir)()?u=(0,t.normalizePath)((0,o.getSocketCacacheDir)()):u=(0,t.normalizePath)(n.join(s(),`${require("#constants/socket").SOCKET_APP_PREFIX}cacache`))),u}function v(){return(0,o.getSocketDlxDirEnv)()?(0,t.normalizePath)((0,o.getSocketDlxDirEnv)()):(0,t.normalizePath)(n.join(s(),`${require("#constants/socket").SOCKET_APP_PREFIX}${require("#constants/socket").SOCKET_DLX_APP_NAME}`))}function P(e){return(0,t.normalizePath)(n.join(p(e),require("#constants/paths").CACHE_DIR))}function h(e){return(0,t.normalizePath)(n.join(P(e),require("#constants/paths").CACHE_TTL_DIR))}function U(){return p(require("#constants/socket").SOCKET_CLI_APP_NAME)}function $(){return p(require("#constants/socket").SOCKET_REGISTRY_APP_NAME)}function G(){return(0,t.normalizePath)(n.join(h(require("#constants/socket").SOCKET_REGISTRY_APP_NAME),require("#constants/github").CACHE_GITHUB_DIR))}function A(){const e=(0,S.getHome)();if(e)return e;const r=(0,E.getUserprofile)();return r||d()}function x(){a=void 0,u=void 0}(0,g.registerCacheInvalidation)(x);0&&(module.exports={getOsHomeDir,getOsTmpDir,getSocketAppCacheDir,getSocketAppCacheTtlDir,getSocketAppDir,getSocketCacacheDir,getSocketCliDir,getSocketDlxDir,getSocketHomePath,getSocketRegistryDir,getSocketRegistryGithubCacheDir,getSocketUserDir,getUserHomeDir,invalidateCache});
3
3
  //# sourceMappingURL=paths.js.map
package/dist/paths.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/paths.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Path utilities for Socket ecosystem directories.\n * Provides platform-aware path resolution for Socket tools' shared directory structure.\n *\n * Directory Structure:\n * ~/.socket/\n * \u251C\u2500\u2500 _cacache/ # Content-addressable cache for npm packages\n * \u251C\u2500\u2500 _dlx/ # DLX installations (content-addressed by hash)\n * \u2502 \u251C\u2500\u2500 <hash>/ # npm package installs (dlx-package)\n * \u2502 \u2514\u2500\u2500 <hash>/ # binary downloads (dlx-binary)\n * \u251C\u2500\u2500 _socket/ # Socket CLI app directory\n * \u251C\u2500\u2500 _registry/ # Socket Registry app directory\n * \u2514\u2500\u2500 _sfw/ # Socket Firewall app directory\n */\n\nimport * as os from 'node:os'\nimport * as path from 'node:path'\n\nimport { getHome } from '#env/home'\nimport {\n getSocketCacacheDir as getSocketCacacheDirEnv,\n getSocketDlxDirEnv,\n} from '#env/socket'\nimport { getUserprofile } from '#env/windows'\n\nimport { normalizePath } from './path'\nimport { getPathValue, registerCacheInvalidation } from './paths/rewire'\n\n/**\n * Get the OS home directory.\n * Can be overridden in tests using setPath('homedir', ...) from paths/rewire.\n */\nexport function getOsHomeDir(): string {\n // Always check for overrides - don't cache when using rewire\n return getPathValue('homedir', () => os.homedir())\n}\n\n/**\n * Get the OS temporary directory.\n * Can be overridden in tests using setPath('tmpdir', ...) from paths/rewire.\n */\nexport function getOsTmpDir(): string {\n // Always check for overrides - don't cache when using rewire\n return getPathValue('tmpdir', () => os.tmpdir())\n}\n\n/**\n * Get the Socket home directory (~/.socket).\n * Alias for getSocketUserDir() for consistency across Socket projects.\n */\nexport function getSocketHomePath(): string {\n return getSocketUserDir()\n}\n\nlet _cachedSocketUserDir: string | undefined\n\n/**\n * Get the Socket user directory (~/.socket).\n * Result is memoized for performance.\n */\nexport function getSocketUserDir(): string {\n if (_cachedSocketUserDir === undefined) {\n _cachedSocketUserDir = normalizePath(\n path.join(\n getUserHomeDir(),\n /*@__INLINE__*/ require('#constants/paths').DOT_SOCKET_DIR,\n ),\n )\n }\n return _cachedSocketUserDir\n}\n\n/**\n * Get a Socket app directory (~/.socket/_<appName>).\n */\nexport function getSocketAppDir(appName: string): string {\n return normalizePath(\n path.join(\n getSocketUserDir(),\n `${/*@__INLINE__*/ require('#constants/socket').SOCKET_APP_PREFIX}${appName}`,\n ),\n )\n}\n\nlet _cachedSocketCacacheDir: string | undefined\n\n/**\n * Get the Socket cacache directory (~/.socket/_cacache).\n * Can be overridden with SOCKET_CACACHE_DIR environment variable for testing.\n * Result is memoized for performance.\n */\nexport function getSocketCacacheDir(): string {\n if (_cachedSocketCacacheDir === undefined) {\n if (getSocketCacacheDirEnv()) {\n _cachedSocketCacacheDir = normalizePath(\n getSocketCacacheDirEnv() as string,\n )\n } else {\n _cachedSocketCacacheDir = normalizePath(\n path.join(\n getSocketUserDir(),\n `${/*@__INLINE__*/ require('#constants/socket').SOCKET_APP_PREFIX}cacache`,\n ),\n )\n }\n }\n return _cachedSocketCacacheDir\n}\n\n/**\n * Get the Socket DLX directory (~/.socket/_dlx).\n * Can be overridden with SOCKET_DLX_DIR environment variable for testing.\n */\nexport function getSocketDlxDir(): string {\n if (getSocketDlxDirEnv()) {\n return normalizePath(getSocketDlxDirEnv() as string)\n }\n return normalizePath(\n path.join(\n getSocketUserDir(),\n `${/*@__INLINE__*/ require('#constants/socket').SOCKET_APP_PREFIX}${/*@__INLINE__*/ require('#constants/socket').SOCKET_DLX_APP_NAME}`,\n ),\n )\n}\n\n/**\n * Get a Socket app cache directory (~/.socket/_<appName>/cache).\n */\nexport function getSocketAppCacheDir(appName: string): string {\n return normalizePath(\n path.join(\n getSocketAppDir(appName),\n /*@__INLINE__*/ require('#constants/paths').CACHE_DIR,\n ),\n )\n}\n\n/**\n * Get a Socket app TTL cache directory (~/.socket/_<appName>/cache/ttl).\n */\nexport function getSocketAppCacheTtlDir(appName: string): string {\n return normalizePath(\n path.join(\n getSocketAppCacheDir(appName),\n /*@__INLINE__*/ require('#constants/paths').CACHE_TTL_DIR,\n ),\n )\n}\n\n/**\n * Get the Socket CLI directory (~/.socket/_socket).\n */\nexport function getSocketCliDir(): string {\n return getSocketAppDir(\n /*@__INLINE__*/ require('#constants/socket').SOCKET_CLI_APP_NAME,\n )\n}\n\n/**\n * Get the Socket Registry directory (~/.socket/_registry).\n */\nexport function getSocketRegistryDir(): string {\n return getSocketAppDir(\n /*@__INLINE__*/ require('#constants/socket').SOCKET_REGISTRY_APP_NAME,\n )\n}\n\n/**\n * Get the Socket Registry GitHub cache directory (~/.socket/_registry/cache/ttl/github).\n */\nexport function getSocketRegistryGithubCacheDir(): string {\n return normalizePath(\n path.join(\n getSocketAppCacheTtlDir(\n /*@__INLINE__*/ require('#constants/socket').SOCKET_REGISTRY_APP_NAME,\n ),\n /*@__INLINE__*/ require('#constants/github').CACHE_GITHUB_DIR,\n ),\n )\n}\n\n/**\n * Get the user's home directory.\n * Uses environment variables directly to support test mocking.\n * Falls back to os.homedir() if env vars not set.\n */\nexport function getUserHomeDir(): string {\n // Try HOME first (Unix)\n const home = getHome()\n if (home) {\n return home\n }\n // Try USERPROFILE (Windows)\n const userProfile = getUserprofile()\n if (userProfile) {\n return userProfile\n }\n // Fallback to os.homedir()\n return getOsHomeDir()\n}\n\n/**\n * Invalidate all cached path values.\n * Called automatically by the paths/rewire module when setPath/clearPath/resetPaths are used.\n *\n * @internal Used for test rewiring\n */\nexport function invalidateCache(): void {\n _cachedSocketUserDir = undefined\n _cachedSocketCacacheDir = undefined\n}\n\n// Register cache invalidation with the rewire module\nregisterCacheInvalidation(invalidateCache)\n"],
5
- "mappings": ";6iBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,kBAAAE,EAAA,gBAAAC,EAAA,yBAAAC,EAAA,4BAAAC,EAAA,oBAAAC,EAAA,wBAAAC,EAAA,oBAAAC,EAAA,oBAAAC,EAAA,sBAAAC,EAAA,yBAAAC,EAAA,oCAAAC,EAAA,qBAAAC,EAAA,mBAAAC,EAAA,oBAAAC,IAAA,eAAAC,EAAAhB,GAeA,IAAAiB,EAAoB,sBACpBC,EAAsB,wBAEtBC,EAAwB,qBACxBC,EAGO,uBACPC,EAA+B,wBAE/BC,EAA8B,kBAC9BC,EAAwD,0BAMjD,SAASrB,GAAuB,CAErC,SAAO,gBAAa,UAAW,IAAMe,EAAG,QAAQ,CAAC,CACnD,CAMO,SAASd,GAAsB,CAEpC,SAAO,gBAAa,SAAU,IAAMc,EAAG,OAAO,CAAC,CACjD,CAMO,SAASP,GAA4B,CAC1C,OAAOG,EAAiB,CAC1B,CAEA,IAAIW,EAMG,SAASX,GAA2B,CACzC,OAAIW,IAAyB,SAC3BA,KAAuB,iBACrBN,EAAK,KACHJ,EAAe,EACC,QAAQ,kBAAkB,EAAE,cAC9C,CACF,GAEKU,CACT,CAKO,SAASlB,EAAgBmB,EAAyB,CACvD,SAAO,iBACLP,EAAK,KACHL,EAAiB,EACjB,GAAmB,QAAQ,mBAAmB,EAAE,iBAAiB,GAAGY,CAAO,EAC7E,CACF,CACF,CAEA,IAAIC,EAOG,SAASnB,GAA8B,CAC5C,OAAImB,IAA4B,YAC1B,EAAAC,qBAAuB,EACzBD,KAA0B,oBACxB,EAAAC,qBAAuB,CACzB,EAEAD,KAA0B,iBACxBR,EAAK,KACHL,EAAiB,EACjB,GAAmB,QAAQ,mBAAmB,EAAE,iBAAiB,SACnE,CACF,GAGGa,CACT,CAMO,SAASjB,GAA0B,CACxC,SAAI,sBAAmB,KACd,oBAAc,sBAAmB,CAAW,KAE9C,iBACLS,EAAK,KACHL,EAAiB,EACjB,GAAmB,QAAQ,mBAAmB,EAAE,iBAAiB,GAAmB,QAAQ,mBAAmB,EAAE,mBAAmB,EACtI,CACF,CACF,CAKO,SAAST,EAAqBqB,EAAyB,CAC5D,SAAO,iBACLP,EAAK,KACHZ,EAAgBmB,CAAO,EACP,QAAQ,kBAAkB,EAAE,SAC9C,CACF,CACF,CAKO,SAASpB,EAAwBoB,EAAyB,CAC/D,SAAO,iBACLP,EAAK,KACHd,EAAqBqB,CAAO,EACZ,QAAQ,kBAAkB,EAAE,aAC9C,CACF,CACF,CAKO,SAASjB,GAA0B,CACxC,OAAOF,EACW,QAAQ,mBAAmB,EAAE,mBAC/C,CACF,CAKO,SAASK,GAA+B,CAC7C,OAAOL,EACW,QAAQ,mBAAmB,EAAE,wBAC/C,CACF,CAKO,SAASM,GAA0C,CACxD,SAAO,iBACLM,EAAK,KACHb,EACkB,QAAQ,mBAAmB,EAAE,wBAC/C,EACgB,QAAQ,mBAAmB,EAAE,gBAC/C,CACF,CACF,CAOO,SAASS,GAAyB,CAEvC,MAAMc,KAAO,WAAQ,EACrB,GAAIA,EACF,OAAOA,EAGT,MAAMC,KAAc,kBAAe,EACnC,OAAIA,GAIG3B,EAAa,CACtB,CAQO,SAASa,GAAwB,CACtCS,EAAuB,OACvBE,EAA0B,MAC5B,IAGA,6BAA0BX,CAAe",
4
+ "sourcesContent": ["/**\n * @fileoverview Path utilities for Socket ecosystem directories.\n * Provides platform-aware path resolution for Socket tools' shared directory structure.\n *\n * Directory Structure:\n * ~/.socket/\n * \u251C\u2500\u2500 _cacache/ # Content-addressable cache for npm packages\n * \u251C\u2500\u2500 _dlx/ # DLX installations (content-addressed by hash)\n * \u2502 \u251C\u2500\u2500 <hash>/ # npm package installs (dlx-package)\n * \u2502 \u2514\u2500\u2500 <hash>/ # binary downloads (dlx-binary)\n * \u251C\u2500\u2500 _socket/ # Socket CLI app directory\n * \u251C\u2500\u2500 _registry/ # Socket Registry app directory\n * \u2514\u2500\u2500 _sfw/ # Socket Firewall app directory\n */\n\nimport * as os from 'os'\nimport * as path from 'path'\n\nimport { getHome } from '#env/home'\nimport {\n getSocketCacacheDir as getSocketCacacheDirEnv,\n getSocketDlxDirEnv,\n} from '#env/socket'\nimport { getUserprofile } from '#env/windows'\n\nimport { normalizePath } from './path'\nimport { getPathValue, registerCacheInvalidation } from './paths/rewire'\n\n/**\n * Get the OS home directory.\n * Can be overridden in tests using setPath('homedir', ...) from paths/rewire.\n */\nexport function getOsHomeDir(): string {\n // Always check for overrides - don't cache when using rewire\n return getPathValue('homedir', () => os.homedir())\n}\n\n/**\n * Get the OS temporary directory.\n * Can be overridden in tests using setPath('tmpdir', ...) from paths/rewire.\n */\nexport function getOsTmpDir(): string {\n // Always check for overrides - don't cache when using rewire\n return getPathValue('tmpdir', () => os.tmpdir())\n}\n\n/**\n * Get the Socket home directory (~/.socket).\n * Alias for getSocketUserDir() for consistency across Socket projects.\n */\nexport function getSocketHomePath(): string {\n return getSocketUserDir()\n}\n\nlet _cachedSocketUserDir: string | undefined\n\n/**\n * Get the Socket user directory (~/.socket).\n * Result is memoized for performance.\n */\nexport function getSocketUserDir(): string {\n if (_cachedSocketUserDir === undefined) {\n _cachedSocketUserDir = normalizePath(\n path.join(\n getUserHomeDir(),\n /*@__INLINE__*/ require('#constants/paths').DOT_SOCKET_DIR,\n ),\n )\n }\n return _cachedSocketUserDir\n}\n\n/**\n * Get a Socket app directory (~/.socket/_<appName>).\n */\nexport function getSocketAppDir(appName: string): string {\n return normalizePath(\n path.join(\n getSocketUserDir(),\n `${/*@__INLINE__*/ require('#constants/socket').SOCKET_APP_PREFIX}${appName}`,\n ),\n )\n}\n\nlet _cachedSocketCacacheDir: string | undefined\n\n/**\n * Get the Socket cacache directory (~/.socket/_cacache).\n * Can be overridden with SOCKET_CACACHE_DIR environment variable for testing.\n * Result is memoized for performance.\n */\nexport function getSocketCacacheDir(): string {\n if (_cachedSocketCacacheDir === undefined) {\n if (getSocketCacacheDirEnv()) {\n _cachedSocketCacacheDir = normalizePath(\n getSocketCacacheDirEnv() as string,\n )\n } else {\n _cachedSocketCacacheDir = normalizePath(\n path.join(\n getSocketUserDir(),\n `${/*@__INLINE__*/ require('#constants/socket').SOCKET_APP_PREFIX}cacache`,\n ),\n )\n }\n }\n return _cachedSocketCacacheDir\n}\n\n/**\n * Get the Socket DLX directory (~/.socket/_dlx).\n * Can be overridden with SOCKET_DLX_DIR environment variable for testing.\n */\nexport function getSocketDlxDir(): string {\n if (getSocketDlxDirEnv()) {\n return normalizePath(getSocketDlxDirEnv() as string)\n }\n return normalizePath(\n path.join(\n getSocketUserDir(),\n `${/*@__INLINE__*/ require('#constants/socket').SOCKET_APP_PREFIX}${/*@__INLINE__*/ require('#constants/socket').SOCKET_DLX_APP_NAME}`,\n ),\n )\n}\n\n/**\n * Get a Socket app cache directory (~/.socket/_<appName>/cache).\n */\nexport function getSocketAppCacheDir(appName: string): string {\n return normalizePath(\n path.join(\n getSocketAppDir(appName),\n /*@__INLINE__*/ require('#constants/paths').CACHE_DIR,\n ),\n )\n}\n\n/**\n * Get a Socket app TTL cache directory (~/.socket/_<appName>/cache/ttl).\n */\nexport function getSocketAppCacheTtlDir(appName: string): string {\n return normalizePath(\n path.join(\n getSocketAppCacheDir(appName),\n /*@__INLINE__*/ require('#constants/paths').CACHE_TTL_DIR,\n ),\n )\n}\n\n/**\n * Get the Socket CLI directory (~/.socket/_socket).\n */\nexport function getSocketCliDir(): string {\n return getSocketAppDir(\n /*@__INLINE__*/ require('#constants/socket').SOCKET_CLI_APP_NAME,\n )\n}\n\n/**\n * Get the Socket Registry directory (~/.socket/_registry).\n */\nexport function getSocketRegistryDir(): string {\n return getSocketAppDir(\n /*@__INLINE__*/ require('#constants/socket').SOCKET_REGISTRY_APP_NAME,\n )\n}\n\n/**\n * Get the Socket Registry GitHub cache directory (~/.socket/_registry/cache/ttl/github).\n */\nexport function getSocketRegistryGithubCacheDir(): string {\n return normalizePath(\n path.join(\n getSocketAppCacheTtlDir(\n /*@__INLINE__*/ require('#constants/socket').SOCKET_REGISTRY_APP_NAME,\n ),\n /*@__INLINE__*/ require('#constants/github').CACHE_GITHUB_DIR,\n ),\n )\n}\n\n/**\n * Get the user's home directory.\n * Uses environment variables directly to support test mocking.\n * Falls back to os.homedir() if env vars not set.\n */\nexport function getUserHomeDir(): string {\n // Try HOME first (Unix)\n const home = getHome()\n if (home) {\n return home\n }\n // Try USERPROFILE (Windows)\n const userProfile = getUserprofile()\n if (userProfile) {\n return userProfile\n }\n // Fallback to os.homedir()\n return getOsHomeDir()\n}\n\n/**\n * Invalidate all cached path values.\n * Called automatically by the paths/rewire module when setPath/clearPath/resetPaths are used.\n *\n * @internal Used for test rewiring\n */\nexport function invalidateCache(): void {\n _cachedSocketUserDir = undefined\n _cachedSocketCacacheDir = undefined\n}\n\n// Register cache invalidation with the rewire module\nregisterCacheInvalidation(invalidateCache)\n"],
5
+ "mappings": ";6iBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,kBAAAE,EAAA,gBAAAC,EAAA,yBAAAC,EAAA,4BAAAC,EAAA,oBAAAC,EAAA,wBAAAC,EAAA,oBAAAC,EAAA,oBAAAC,EAAA,sBAAAC,EAAA,yBAAAC,EAAA,oCAAAC,EAAA,qBAAAC,EAAA,mBAAAC,EAAA,oBAAAC,IAAA,eAAAC,EAAAhB,GAeA,IAAAiB,EAAoB,iBACpBC,EAAsB,mBAEtBC,EAAwB,qBACxBC,EAGO,uBACPC,EAA+B,wBAE/BC,EAA8B,kBAC9BC,EAAwD,0BAMjD,SAASrB,GAAuB,CAErC,SAAO,gBAAa,UAAW,IAAMe,EAAG,QAAQ,CAAC,CACnD,CAMO,SAASd,GAAsB,CAEpC,SAAO,gBAAa,SAAU,IAAMc,EAAG,OAAO,CAAC,CACjD,CAMO,SAASP,GAA4B,CAC1C,OAAOG,EAAiB,CAC1B,CAEA,IAAIW,EAMG,SAASX,GAA2B,CACzC,OAAIW,IAAyB,SAC3BA,KAAuB,iBACrBN,EAAK,KACHJ,EAAe,EACC,QAAQ,kBAAkB,EAAE,cAC9C,CACF,GAEKU,CACT,CAKO,SAASlB,EAAgBmB,EAAyB,CACvD,SAAO,iBACLP,EAAK,KACHL,EAAiB,EACjB,GAAmB,QAAQ,mBAAmB,EAAE,iBAAiB,GAAGY,CAAO,EAC7E,CACF,CACF,CAEA,IAAIC,EAOG,SAASnB,GAA8B,CAC5C,OAAImB,IAA4B,YAC1B,EAAAC,qBAAuB,EACzBD,KAA0B,oBACxB,EAAAC,qBAAuB,CACzB,EAEAD,KAA0B,iBACxBR,EAAK,KACHL,EAAiB,EACjB,GAAmB,QAAQ,mBAAmB,EAAE,iBAAiB,SACnE,CACF,GAGGa,CACT,CAMO,SAASjB,GAA0B,CACxC,SAAI,sBAAmB,KACd,oBAAc,sBAAmB,CAAW,KAE9C,iBACLS,EAAK,KACHL,EAAiB,EACjB,GAAmB,QAAQ,mBAAmB,EAAE,iBAAiB,GAAmB,QAAQ,mBAAmB,EAAE,mBAAmB,EACtI,CACF,CACF,CAKO,SAAST,EAAqBqB,EAAyB,CAC5D,SAAO,iBACLP,EAAK,KACHZ,EAAgBmB,CAAO,EACP,QAAQ,kBAAkB,EAAE,SAC9C,CACF,CACF,CAKO,SAASpB,EAAwBoB,EAAyB,CAC/D,SAAO,iBACLP,EAAK,KACHd,EAAqBqB,CAAO,EACZ,QAAQ,kBAAkB,EAAE,aAC9C,CACF,CACF,CAKO,SAASjB,GAA0B,CACxC,OAAOF,EACW,QAAQ,mBAAmB,EAAE,mBAC/C,CACF,CAKO,SAASK,GAA+B,CAC7C,OAAOL,EACW,QAAQ,mBAAmB,EAAE,wBAC/C,CACF,CAKO,SAASM,GAA0C,CACxD,SAAO,iBACLM,EAAK,KACHb,EACkB,QAAQ,mBAAmB,EAAE,wBAC/C,EACgB,QAAQ,mBAAmB,EAAE,gBAC/C,CACF,CACF,CAOO,SAASS,GAAyB,CAEvC,MAAMc,KAAO,WAAQ,EACrB,GAAIA,EACF,OAAOA,EAGT,MAAMC,KAAc,kBAAe,EACnC,OAAIA,GAIG3B,EAAa,CACtB,CAQO,SAASa,GAAwB,CACtCS,EAAuB,OACvBE,EAA0B,MAC5B,IAGA,6BAA0BX,CAAe",
6
6
  "names": ["paths_exports", "__export", "getOsHomeDir", "getOsTmpDir", "getSocketAppCacheDir", "getSocketAppCacheTtlDir", "getSocketAppDir", "getSocketCacacheDir", "getSocketCliDir", "getSocketDlxDir", "getSocketHomePath", "getSocketRegistryDir", "getSocketRegistryGithubCacheDir", "getSocketUserDir", "getUserHomeDir", "invalidateCache", "__toCommonJS", "os", "path", "import_home", "import_socket", "import_windows", "import_path", "import_rewire", "_cachedSocketUserDir", "appName", "_cachedSocketCacacheDir", "getSocketCacacheDirEnv", "home", "userProfile"]
7
7
  }
@@ -1,5 +1,5 @@
1
1
  /* Socket Lib - Built with esbuild */
2
- var f=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var g=(n,e)=>{for(var r in e)f(n,r,{get:e[r],enumerable:!0})},T=(n,e,r,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of v(e))!E.call(n,s)&&s!==r&&f(n,s,{get:()=>e[s],enumerable:!(i=w(e,s))||i.enumerable});return n};var x=n=>T(f({},"__esModule",{value:!0}),n);var L={};g(L,{processLock:()=>$});module.exports=x(L);var t=require("node:fs"),u=require("./fs"),d=require("./logger"),p=require("./promises"),y=require("./signal-exit");class S{activeLocks=new Set;touchTimers=new Map;exitHandlerRegistered=!1;ensureExitHandler(){this.exitHandlerRegistered||((0,y.onExit)(()=>{for(const e of this.touchTimers.values())clearInterval(e);this.touchTimers.clear();for(const e of this.activeLocks)try{(0,t.existsSync)(e)&&(0,u.safeDeleteSync)(e,{recursive:!0})}catch{}}),this.exitHandlerRegistered=!0)}touchLock(e){try{if((0,t.existsSync)(e)){const r=new Date;(0,t.utimesSync)(e,r,r)}}catch(r){d.logger.warn(`Failed to touch lock ${e}: ${r instanceof Error?r.message:String(r)}`)}}startTouchTimer(e,r){if(r<=0||this.touchTimers.has(e))return;const i=setInterval(()=>{this.touchLock(e)},r);i.unref(),this.touchTimers.set(e,i)}stopTouchTimer(e){const r=this.touchTimers.get(e);r&&(clearInterval(r),this.touchTimers.delete(e))}isStale(e,r){try{if(!(0,t.existsSync)(e))return!1;const i=(0,t.statSync)(e),s=Math.floor((Date.now()-i.mtime.getTime())/1e3),m=Math.floor(r/1e3);return s>m}catch{return!1}}async acquire(e,r={}){const{baseDelayMs:i=100,maxDelayMs:s=1e3,retries:m=3,staleMs:l=5e3,touchIntervalMs:h=2e3}=r;return this.ensureExitHandler(),await(0,p.pRetry)(async()=>{try{if((0,t.existsSync)(e)&&this.isStale(e,l)){d.logger.log(`Removing stale lock: ${e}`);try{(0,u.safeDeleteSync)(e,{recursive:!0})}catch{}}if((0,t.existsSync)(e))throw new Error(`Lock already exists: ${e}`);return(0,t.mkdirSync)(e,{recursive:!0}),this.activeLocks.add(e),this.startTouchTimer(e,h),()=>this.release(e)}catch(o){const a=o.code;if(a==="EEXIST")throw this.isStale(e,l)?new Error(`Stale lock detected: ${e}`):new Error(`Lock already exists: ${e}`);if(a==="EACCES"||a==="EPERM")throw new Error(`Permission denied creating lock: ${e}. Check directory permissions or run with appropriate access.`,{cause:o});if(a==="EROFS")throw new Error(`Cannot create lock on read-only filesystem: ${e}`,{cause:o});if(a==="ENOTDIR"){const c=e.slice(0,e.lastIndexOf("/"));throw new Error(`Cannot create lock directory: ${e}
2
+ var m=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var T=(n,e)=>{for(var r in e)m(n,r,{get:e[r],enumerable:!0})},x=(n,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of g(e))!E.call(n,i)&&i!==r&&m(n,i,{get:()=>e[i],enumerable:!(s=v(e,i))||s.enumerable});return n};var S=n=>x(m({},"__esModule",{value:!0}),n);var M={};T(M,{processLock:()=>L});module.exports=S(M);var t=require("fs"),u=require("./fs"),p=require("./logger"),y=require("./promises"),h=require("./signal-exit");const f=(0,p.getDefaultLogger)();class ${activeLocks=new Set;touchTimers=new Map;exitHandlerRegistered=!1;ensureExitHandler(){this.exitHandlerRegistered||((0,h.onExit)(()=>{for(const e of this.touchTimers.values())clearInterval(e);this.touchTimers.clear();for(const e of this.activeLocks)try{(0,t.existsSync)(e)&&(0,u.safeDeleteSync)(e,{recursive:!0})}catch{}}),this.exitHandlerRegistered=!0)}touchLock(e){try{if((0,t.existsSync)(e)){const r=new Date;(0,t.utimesSync)(e,r,r)}}catch(r){f.warn(`Failed to touch lock ${e}: ${r instanceof Error?r.message:String(r)}`)}}startTouchTimer(e,r){if(r<=0||this.touchTimers.has(e))return;const s=setInterval(()=>{this.touchLock(e)},r);s.unref(),this.touchTimers.set(e,s)}stopTouchTimer(e){const r=this.touchTimers.get(e);r&&(clearInterval(r),this.touchTimers.delete(e))}isStale(e,r){try{if(!(0,t.existsSync)(e))return!1;const s=(0,t.statSync)(e),i=Math.floor((Date.now()-s.mtime.getTime())/1e3),d=Math.floor(r/1e3);return i>d}catch{return!1}}async acquire(e,r={}){const{baseDelayMs:s=100,maxDelayMs:i=1e3,retries:d=3,staleMs:l=5e3,touchIntervalMs:w=2e3}=r;return this.ensureExitHandler(),await(0,y.pRetry)(async()=>{try{if((0,t.existsSync)(e)&&this.isStale(e,l)){f.log(`Removing stale lock: ${e}`);try{(0,u.safeDeleteSync)(e,{recursive:!0})}catch{}}if((0,t.existsSync)(e))throw new Error(`Lock already exists: ${e}`);return(0,t.mkdirSync)(e,{recursive:!0}),this.activeLocks.add(e),this.startTouchTimer(e,w),()=>this.release(e)}catch(o){const a=o.code;if(a==="EEXIST")throw this.isStale(e,l)?new Error(`Stale lock detected: ${e}`):new Error(`Lock already exists: ${e}`);if(a==="EACCES"||a==="EPERM")throw new Error(`Permission denied creating lock: ${e}. Check directory permissions or run with appropriate access.`,{cause:o});if(a==="EROFS")throw new Error(`Cannot create lock on read-only filesystem: ${e}`,{cause:o});if(a==="ENOTDIR"){const c=e.slice(0,e.lastIndexOf("/"));throw new Error(`Cannot create lock directory: ${e}
3
3
  A path component is a file when it should be a directory.
4
4
  Parent path: ${c}
5
5
  To resolve:
@@ -10,5 +10,5 @@ Parent directory does not exist: ${c}
10
10
  To resolve:
11
11
  1. Ensure the parent directory "${c}" exists
12
12
  2. Create the directory structure: mkdir -p "${c}"
13
- 3. Check filesystem permissions allow directory creation`,{cause:o})}throw new Error(`Failed to acquire lock: ${e}`,{cause:o})}},{retries:m,baseDelayMs:i,maxDelayMs:s,jitter:!0})}release(e){this.stopTouchTimer(e);try{(0,t.existsSync)(e)&&(0,u.safeDeleteSync)(e,{recursive:!0}),this.activeLocks.delete(e)}catch(r){d.logger.warn(`Failed to release lock ${e}: ${r instanceof Error?r.message:String(r)}`)}}async withLock(e,r,i){const s=await this.acquire(e,i);try{return await r()}finally{s()}}}const $=new S;0&&(module.exports={processLock});
13
+ 3. Check filesystem permissions allow directory creation`,{cause:o})}throw new Error(`Failed to acquire lock: ${e}`,{cause:o})}},{retries:d,baseDelayMs:s,maxDelayMs:i,jitter:!0})}release(e){this.stopTouchTimer(e);try{(0,t.existsSync)(e)&&(0,u.safeDeleteSync)(e,{recursive:!0}),this.activeLocks.delete(e)}catch(r){f.warn(`Failed to release lock ${e}: ${r instanceof Error?r.message:String(r)}`)}}async withLock(e,r,s){const i=await this.acquire(e,s);try{return await r()}finally{i()}}}const L=new $;0&&(module.exports={processLock});
14
14
  //# sourceMappingURL=process-lock.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/process-lock.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Process locking utilities with stale detection and exit cleanup.\n * Provides cross-platform inter-process synchronization using directory-based locks.\n * Aligned with npm's npx locking strategy (5-second stale timeout, periodic touching).\n *\n * ## Why directories instead of files?\n *\n * This implementation uses `mkdir()` to create lock directories (not files) because:\n *\n * 1. **Atomic guarantee**: `mkdir()` is guaranteed atomic across ALL filesystems,\n * including NFS. Only ONE process can successfully create the directory. If it\n * exists, `mkdir()` fails with EEXIST instantly with no race conditions.\n *\n * 2. **File-based locking issues**:\n * - `writeFile()` with `flag: 'wx'` - atomicity can fail on NFS\n * - `open()` with `O_EXCL` - not guaranteed atomic on older NFS\n * - Traditional lockfiles - can have race conditions on network filesystems\n *\n * 3. **Simplicity**: No need to write/read file content, track PIDs, or manage\n * file descriptors. Just create/delete directory and check mtime.\n *\n * 4. **Historical precedent**: Well-known Unix locking pattern used by package\n * managers for decades. Git uses similar approach for `.git/index.lock`.\n *\n * ## The mtime trick\n *\n * We periodically update the lock directory's mtime (modification time) by\n * \"touching\" it to signal \"I'm still actively working\". This prevents other\n * processes from treating the lock as stale and removing it.\n *\n * **The lock directory remains empty** - it's just a sentinel that signals\n * \"locked\". The mtime is the only data needed to track lock freshness.\n *\n * ## npm npx compatibility\n *\n * This implementation matches npm npx's concurrency.lock approach:\n * - Lock created via `mkdir(path.join(installDir, 'concurrency.lock'))`\n * - 5-second stale timeout (if mtime is older than 5s, lock is stale)\n * - 2-second touching interval (updates mtime every 2s to keep lock fresh)\n * - Automatic cleanup on process exit\n */\n\nimport { existsSync, mkdirSync, statSync, utimesSync } from 'node:fs'\n\nimport { safeDeleteSync } from './fs'\nimport { logger } from './logger'\nimport { pRetry } from './promises'\nimport { onExit } from './signal-exit'\n\n/**\n * Lock acquisition options.\n */\nexport interface ProcessLockOptions {\n /**\n * Maximum number of retry attempts.\n * @default 3\n */\n retries?: number | undefined\n\n /**\n * Base delay between retries in milliseconds.\n * @default 100\n */\n baseDelayMs?: number | undefined\n\n /**\n * Maximum delay between retries in milliseconds.\n * @default 1000\n */\n maxDelayMs?: number | undefined\n\n /**\n * Stale lock timeout in milliseconds.\n * Locks older than this are considered abandoned and can be reclaimed.\n * Aligned with npm's npx locking strategy (5 seconds).\n * @default 5000 (5 seconds)\n */\n staleMs?: number | undefined\n\n /**\n * Interval for touching lock file to keep it fresh in milliseconds.\n * Set to 0 to disable periodic touching.\n * @default 2000 (2 seconds)\n */\n touchIntervalMs?: number | undefined\n}\n\n/**\n * Process lock manager with stale detection and exit cleanup.\n * Provides cross-platform inter-process synchronization using file-system\n * based locks.\n */\nclass ProcessLockManager {\n private activeLocks = new Set<string>()\n private touchTimers = new Map<string, NodeJS.Timeout>()\n private exitHandlerRegistered = false\n\n /**\n * Ensure process exit handler is registered for cleanup.\n * Registers a handler that cleans up all active locks when the process exits.\n */\n private ensureExitHandler() {\n if (this.exitHandlerRegistered) {\n return\n }\n\n onExit(() => {\n // Clear all touch timers.\n for (const timer of this.touchTimers.values()) {\n clearInterval(timer)\n }\n this.touchTimers.clear()\n\n // Clean up all active locks.\n for (const lockPath of this.activeLocks) {\n try {\n if (existsSync(lockPath)) {\n safeDeleteSync(lockPath, { recursive: true })\n }\n } catch {\n // Ignore cleanup errors during exit.\n }\n }\n })\n\n this.exitHandlerRegistered = true\n }\n\n /**\n * Touch a lock file to update its mtime.\n * This prevents the lock from being detected as stale during long operations.\n *\n * @param lockPath - Path to the lock directory\n */\n private touchLock(lockPath: string): void {\n try {\n if (existsSync(lockPath)) {\n const now = new Date()\n utimesSync(lockPath, now, now)\n }\n } catch (error) {\n logger.warn(\n `Failed to touch lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n /**\n * Start periodic touching of a lock file.\n * Aligned with npm npx strategy to prevent false stale detection.\n *\n * @param lockPath - Path to the lock directory\n * @param intervalMs - Touch interval in milliseconds\n */\n private startTouchTimer(lockPath: string, intervalMs: number): void {\n if (intervalMs <= 0 || this.touchTimers.has(lockPath)) {\n return\n }\n\n const timer = setInterval(() => {\n this.touchLock(lockPath)\n }, intervalMs)\n\n // Prevent timer from keeping process alive.\n timer.unref()\n\n this.touchTimers.set(lockPath, timer)\n }\n\n /**\n * Stop periodic touching of a lock file.\n *\n * @param lockPath - Path to the lock directory\n */\n private stopTouchTimer(lockPath: string): void {\n const timer = this.touchTimers.get(lockPath)\n if (timer) {\n clearInterval(timer)\n this.touchTimers.delete(lockPath)\n }\n }\n\n /**\n * Check if a lock is stale based on mtime.\n * Uses second-level granularity to avoid APFS floating-point precision issues.\n * Aligned with npm's npx locking strategy.\n *\n * @param lockPath - Path to the lock directory\n * @param staleMs - Stale timeout in milliseconds\n * @returns True if lock exists and is stale\n */\n private isStale(lockPath: string, staleMs: number): boolean {\n try {\n if (!existsSync(lockPath)) {\n return false\n }\n\n const stats = statSync(lockPath)\n // Use second-level granularity to avoid APFS issues.\n const ageSeconds = Math.floor((Date.now() - stats.mtime.getTime()) / 1000)\n const staleSeconds = Math.floor(staleMs / 1000)\n return ageSeconds > staleSeconds\n } catch {\n return false\n }\n }\n\n /**\n * Acquire a lock using mkdir for atomic operation.\n * Handles stale locks and includes exit cleanup.\n *\n * This method attempts to create a lock directory atomically. If the lock\n * already exists, it checks if it's stale and removes it before retrying.\n * Uses exponential backoff with jitter for retry attempts.\n *\n * @param lockPath - Path to the lock directory\n * @param options - Lock acquisition options\n * @returns Release function to unlock\n * @throws Error if lock cannot be acquired after all retries\n *\n * @example\n * ```typescript\n * const release = await processLock.acquire('/tmp/my-lock')\n * try {\n * // Critical section\n * } finally {\n * release()\n * }\n * ```\n */\n async acquire(\n lockPath: string,\n options: ProcessLockOptions = {},\n ): Promise<() => void> {\n const {\n baseDelayMs = 100,\n maxDelayMs = 1000,\n retries = 3,\n staleMs = 5000,\n touchIntervalMs = 2000,\n } = options\n\n // Ensure exit handler is registered before any lock acquisition.\n this.ensureExitHandler()\n\n return await pRetry(\n async () => {\n try {\n // Check for stale lock and remove if necessary.\n if (existsSync(lockPath) && this.isStale(lockPath, staleMs)) {\n logger.log(`Removing stale lock: ${lockPath}`)\n try {\n safeDeleteSync(lockPath, { recursive: true })\n } catch {\n // Ignore errors removing stale lock - will retry.\n }\n }\n\n // Check if lock already exists before creating.\n if (existsSync(lockPath)) {\n throw new Error(`Lock already exists: ${lockPath}`)\n }\n\n // Atomic lock acquisition via mkdir with recursive to create parent dirs.\n mkdirSync(lockPath, { recursive: true })\n\n // Track lock for cleanup.\n this.activeLocks.add(lockPath)\n\n // Start periodic touching to prevent stale detection.\n this.startTouchTimer(lockPath, touchIntervalMs)\n\n // Return release function.\n return () => this.release(lockPath)\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code\n\n // Handle lock contention - lock already exists.\n if (code === 'EEXIST') {\n if (this.isStale(lockPath, staleMs)) {\n throw new Error(`Stale lock detected: ${lockPath}`)\n }\n throw new Error(`Lock already exists: ${lockPath}`)\n }\n\n // Handle permission errors - not retryable.\n if (code === 'EACCES' || code === 'EPERM') {\n throw new Error(\n `Permission denied creating lock: ${lockPath}. ` +\n 'Check directory permissions or run with appropriate access.',\n { cause: error },\n )\n }\n\n // Handle read-only filesystem - not retryable.\n if (code === 'EROFS') {\n throw new Error(\n `Cannot create lock on read-only filesystem: ${lockPath}`,\n { cause: error },\n )\n }\n\n // Handle parent path issues - not retryable.\n if (code === 'ENOTDIR') {\n const parentDir = lockPath.slice(0, lockPath.lastIndexOf('/'))\n throw new Error(\n `Cannot create lock directory: ${lockPath}\\n` +\n 'A path component is a file when it should be a directory.\\n' +\n `Parent path: ${parentDir}\\n` +\n 'To resolve:\\n' +\n ` 1. Check if \"${parentDir}\" contains a file instead of a directory\\n` +\n ' 2. Remove any conflicting files in the path\\n' +\n ' 3. Ensure the full parent directory structure exists',\n { cause: error },\n )\n }\n\n if (code === 'ENOENT') {\n const parentDir = lockPath.slice(0, lockPath.lastIndexOf('/'))\n throw new Error(\n `Cannot create lock directory: ${lockPath}\\n` +\n `Parent directory does not exist: ${parentDir}\\n` +\n 'To resolve:\\n' +\n ` 1. Ensure the parent directory \"${parentDir}\" exists\\n` +\n ` 2. Create the directory structure: mkdir -p \"${parentDir}\"\\n` +\n ' 3. Check filesystem permissions allow directory creation',\n { cause: error },\n )\n }\n\n // Re-throw other errors with context.\n throw new Error(`Failed to acquire lock: ${lockPath}`, {\n cause: error,\n })\n }\n },\n {\n retries,\n baseDelayMs,\n maxDelayMs,\n jitter: true,\n },\n )\n }\n\n /**\n * Release a lock and remove from tracking.\n * Stops periodic touching and removes the lock directory.\n *\n * @param lockPath - Path to the lock directory\n *\n * @example\n * ```typescript\n * processLock.release('/tmp/my-lock')\n * ```\n */\n release(lockPath: string): void {\n // Stop periodic touching.\n this.stopTouchTimer(lockPath)\n\n try {\n if (existsSync(lockPath)) {\n safeDeleteSync(lockPath, { recursive: true })\n }\n this.activeLocks.delete(lockPath)\n } catch (error) {\n logger.warn(\n `Failed to release lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n /**\n * Execute a function with exclusive lock protection.\n * Automatically handles lock acquisition, execution, and cleanup.\n *\n * This is the recommended way to use process locks, as it guarantees\n * cleanup even if the callback throws an error.\n *\n * @param lockPath - Path to the lock directory\n * @param fn - Function to execute while holding the lock\n * @param options - Lock acquisition options\n * @returns Result of the callback function\n * @throws Error from callback or lock acquisition failure\n *\n * @example\n * ```typescript\n * const result = await processLock.withLock('/tmp/my-lock', async () => {\n * // Critical section\n * return someValue\n * })\n * ```\n */\n async withLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options?: ProcessLockOptions,\n ): Promise<T> {\n const release = await this.acquire(lockPath, options)\n try {\n return await fn()\n } finally {\n release()\n }\n }\n}\n\n// Export singleton instance.\nexport const processLock = new ProcessLockManager()\n"],
5
- "mappings": "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",
6
- "names": ["process_lock_exports", "__export", "processLock", "__toCommonJS", "import_node_fs", "import_fs", "import_logger", "import_promises", "import_signal_exit", "ProcessLockManager", "timer", "lockPath", "now", "error", "intervalMs", "staleMs", "stats", "ageSeconds", "staleSeconds", "options", "baseDelayMs", "maxDelayMs", "retries", "touchIntervalMs", "code", "parentDir", "fn", "release"]
4
+ "sourcesContent": ["/**\n * @fileoverview Process locking utilities with stale detection and exit cleanup.\n * Provides cross-platform inter-process synchronization using directory-based locks.\n * Aligned with npm's npx locking strategy (5-second stale timeout, periodic touching).\n *\n * ## Why directories instead of files?\n *\n * This implementation uses `mkdir()` to create lock directories (not files) because:\n *\n * 1. **Atomic guarantee**: `mkdir()` is guaranteed atomic across ALL filesystems,\n * including NFS. Only ONE process can successfully create the directory. If it\n * exists, `mkdir()` fails with EEXIST instantly with no race conditions.\n *\n * 2. **File-based locking issues**:\n * - `writeFile()` with `flag: 'wx'` - atomicity can fail on NFS\n * - `open()` with `O_EXCL` - not guaranteed atomic on older NFS\n * - Traditional lockfiles - can have race conditions on network filesystems\n *\n * 3. **Simplicity**: No need to write/read file content, track PIDs, or manage\n * file descriptors. Just create/delete directory and check mtime.\n *\n * 4. **Historical precedent**: Well-known Unix locking pattern used by package\n * managers for decades. Git uses similar approach for `.git/index.lock`.\n *\n * ## The mtime trick\n *\n * We periodically update the lock directory's mtime (modification time) by\n * \"touching\" it to signal \"I'm still actively working\". This prevents other\n * processes from treating the lock as stale and removing it.\n *\n * **The lock directory remains empty** - it's just a sentinel that signals\n * \"locked\". The mtime is the only data needed to track lock freshness.\n *\n * ## npm npx compatibility\n *\n * This implementation matches npm npx's concurrency.lock approach:\n * - Lock created via `mkdir(path.join(installDir, 'concurrency.lock'))`\n * - 5-second stale timeout (if mtime is older than 5s, lock is stale)\n * - 2-second touching interval (updates mtime every 2s to keep lock fresh)\n * - Automatic cleanup on process exit\n */\n\nimport { existsSync, mkdirSync, statSync, utimesSync } from 'fs'\n\nimport { safeDeleteSync } from './fs'\nimport { getDefaultLogger } from './logger'\nimport { pRetry } from './promises'\nimport { onExit } from './signal-exit'\n\nconst logger = getDefaultLogger()\n\n/**\n * Lock acquisition options.\n */\nexport interface ProcessLockOptions {\n /**\n * Maximum number of retry attempts.\n * @default 3\n */\n retries?: number | undefined\n\n /**\n * Base delay between retries in milliseconds.\n * @default 100\n */\n baseDelayMs?: number | undefined\n\n /**\n * Maximum delay between retries in milliseconds.\n * @default 1000\n */\n maxDelayMs?: number | undefined\n\n /**\n * Stale lock timeout in milliseconds.\n * Locks older than this are considered abandoned and can be reclaimed.\n * Aligned with npm's npx locking strategy (5 seconds).\n * @default 5000 (5 seconds)\n */\n staleMs?: number | undefined\n\n /**\n * Interval for touching lock file to keep it fresh in milliseconds.\n * Set to 0 to disable periodic touching.\n * @default 2000 (2 seconds)\n */\n touchIntervalMs?: number | undefined\n}\n\n/**\n * Process lock manager with stale detection and exit cleanup.\n * Provides cross-platform inter-process synchronization using file-system\n * based locks.\n */\nclass ProcessLockManager {\n private activeLocks = new Set<string>()\n private touchTimers = new Map<string, NodeJS.Timeout>()\n private exitHandlerRegistered = false\n\n /**\n * Ensure process exit handler is registered for cleanup.\n * Registers a handler that cleans up all active locks when the process exits.\n */\n private ensureExitHandler() {\n if (this.exitHandlerRegistered) {\n return\n }\n\n onExit(() => {\n // Clear all touch timers.\n for (const timer of this.touchTimers.values()) {\n clearInterval(timer)\n }\n this.touchTimers.clear()\n\n // Clean up all active locks.\n for (const lockPath of this.activeLocks) {\n try {\n if (existsSync(lockPath)) {\n safeDeleteSync(lockPath, { recursive: true })\n }\n } catch {\n // Ignore cleanup errors during exit.\n }\n }\n })\n\n this.exitHandlerRegistered = true\n }\n\n /**\n * Touch a lock file to update its mtime.\n * This prevents the lock from being detected as stale during long operations.\n *\n * @param lockPath - Path to the lock directory\n */\n private touchLock(lockPath: string): void {\n try {\n if (existsSync(lockPath)) {\n const now = new Date()\n utimesSync(lockPath, now, now)\n }\n } catch (error) {\n logger.warn(\n `Failed to touch lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n /**\n * Start periodic touching of a lock file.\n * Aligned with npm npx strategy to prevent false stale detection.\n *\n * @param lockPath - Path to the lock directory\n * @param intervalMs - Touch interval in milliseconds\n */\n private startTouchTimer(lockPath: string, intervalMs: number): void {\n if (intervalMs <= 0 || this.touchTimers.has(lockPath)) {\n return\n }\n\n const timer = setInterval(() => {\n this.touchLock(lockPath)\n }, intervalMs)\n\n // Prevent timer from keeping process alive.\n timer.unref()\n\n this.touchTimers.set(lockPath, timer)\n }\n\n /**\n * Stop periodic touching of a lock file.\n *\n * @param lockPath - Path to the lock directory\n */\n private stopTouchTimer(lockPath: string): void {\n const timer = this.touchTimers.get(lockPath)\n if (timer) {\n clearInterval(timer)\n this.touchTimers.delete(lockPath)\n }\n }\n\n /**\n * Check if a lock is stale based on mtime.\n * Uses second-level granularity to avoid APFS floating-point precision issues.\n * Aligned with npm's npx locking strategy.\n *\n * @param lockPath - Path to the lock directory\n * @param staleMs - Stale timeout in milliseconds\n * @returns True if lock exists and is stale\n */\n private isStale(lockPath: string, staleMs: number): boolean {\n try {\n if (!existsSync(lockPath)) {\n return false\n }\n\n const stats = statSync(lockPath)\n // Use second-level granularity to avoid APFS issues.\n const ageSeconds = Math.floor((Date.now() - stats.mtime.getTime()) / 1000)\n const staleSeconds = Math.floor(staleMs / 1000)\n return ageSeconds > staleSeconds\n } catch {\n return false\n }\n }\n\n /**\n * Acquire a lock using mkdir for atomic operation.\n * Handles stale locks and includes exit cleanup.\n *\n * This method attempts to create a lock directory atomically. If the lock\n * already exists, it checks if it's stale and removes it before retrying.\n * Uses exponential backoff with jitter for retry attempts.\n *\n * @param lockPath - Path to the lock directory\n * @param options - Lock acquisition options\n * @returns Release function to unlock\n * @throws Error if lock cannot be acquired after all retries\n *\n * @example\n * ```typescript\n * const release = await processLock.acquire('/tmp/my-lock')\n * try {\n * // Critical section\n * } finally {\n * release()\n * }\n * ```\n */\n async acquire(\n lockPath: string,\n options: ProcessLockOptions = {},\n ): Promise<() => void> {\n const {\n baseDelayMs = 100,\n maxDelayMs = 1000,\n retries = 3,\n staleMs = 5000,\n touchIntervalMs = 2000,\n } = options\n\n // Ensure exit handler is registered before any lock acquisition.\n this.ensureExitHandler()\n\n return await pRetry(\n async () => {\n try {\n // Check for stale lock and remove if necessary.\n if (existsSync(lockPath) && this.isStale(lockPath, staleMs)) {\n logger.log(`Removing stale lock: ${lockPath}`)\n try {\n safeDeleteSync(lockPath, { recursive: true })\n } catch {\n // Ignore errors removing stale lock - will retry.\n }\n }\n\n // Check if lock already exists before creating.\n if (existsSync(lockPath)) {\n throw new Error(`Lock already exists: ${lockPath}`)\n }\n\n // Atomic lock acquisition via mkdir with recursive to create parent dirs.\n mkdirSync(lockPath, { recursive: true })\n\n // Track lock for cleanup.\n this.activeLocks.add(lockPath)\n\n // Start periodic touching to prevent stale detection.\n this.startTouchTimer(lockPath, touchIntervalMs)\n\n // Return release function.\n return () => this.release(lockPath)\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code\n\n // Handle lock contention - lock already exists.\n if (code === 'EEXIST') {\n if (this.isStale(lockPath, staleMs)) {\n throw new Error(`Stale lock detected: ${lockPath}`)\n }\n throw new Error(`Lock already exists: ${lockPath}`)\n }\n\n // Handle permission errors - not retryable.\n if (code === 'EACCES' || code === 'EPERM') {\n throw new Error(\n `Permission denied creating lock: ${lockPath}. ` +\n 'Check directory permissions or run with appropriate access.',\n { cause: error },\n )\n }\n\n // Handle read-only filesystem - not retryable.\n if (code === 'EROFS') {\n throw new Error(\n `Cannot create lock on read-only filesystem: ${lockPath}`,\n { cause: error },\n )\n }\n\n // Handle parent path issues - not retryable.\n if (code === 'ENOTDIR') {\n const parentDir = lockPath.slice(0, lockPath.lastIndexOf('/'))\n throw new Error(\n `Cannot create lock directory: ${lockPath}\\n` +\n 'A path component is a file when it should be a directory.\\n' +\n `Parent path: ${parentDir}\\n` +\n 'To resolve:\\n' +\n ` 1. Check if \"${parentDir}\" contains a file instead of a directory\\n` +\n ' 2. Remove any conflicting files in the path\\n' +\n ' 3. Ensure the full parent directory structure exists',\n { cause: error },\n )\n }\n\n if (code === 'ENOENT') {\n const parentDir = lockPath.slice(0, lockPath.lastIndexOf('/'))\n throw new Error(\n `Cannot create lock directory: ${lockPath}\\n` +\n `Parent directory does not exist: ${parentDir}\\n` +\n 'To resolve:\\n' +\n ` 1. Ensure the parent directory \"${parentDir}\" exists\\n` +\n ` 2. Create the directory structure: mkdir -p \"${parentDir}\"\\n` +\n ' 3. Check filesystem permissions allow directory creation',\n { cause: error },\n )\n }\n\n // Re-throw other errors with context.\n throw new Error(`Failed to acquire lock: ${lockPath}`, {\n cause: error,\n })\n }\n },\n {\n retries,\n baseDelayMs,\n maxDelayMs,\n jitter: true,\n },\n )\n }\n\n /**\n * Release a lock and remove from tracking.\n * Stops periodic touching and removes the lock directory.\n *\n * @param lockPath - Path to the lock directory\n *\n * @example\n * ```typescript\n * processLock.release('/tmp/my-lock')\n * ```\n */\n release(lockPath: string): void {\n // Stop periodic touching.\n this.stopTouchTimer(lockPath)\n\n try {\n if (existsSync(lockPath)) {\n safeDeleteSync(lockPath, { recursive: true })\n }\n this.activeLocks.delete(lockPath)\n } catch (error) {\n logger.warn(\n `Failed to release lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n /**\n * Execute a function with exclusive lock protection.\n * Automatically handles lock acquisition, execution, and cleanup.\n *\n * This is the recommended way to use process locks, as it guarantees\n * cleanup even if the callback throws an error.\n *\n * @param lockPath - Path to the lock directory\n * @param fn - Function to execute while holding the lock\n * @param options - Lock acquisition options\n * @returns Result of the callback function\n * @throws Error from callback or lock acquisition failure\n *\n * @example\n * ```typescript\n * const result = await processLock.withLock('/tmp/my-lock', async () => {\n * // Critical section\n * return someValue\n * })\n * ```\n */\n async withLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options?: ProcessLockOptions,\n ): Promise<T> {\n const release = await this.acquire(lockPath, options)\n try {\n return await fn()\n } finally {\n release()\n }\n }\n}\n\n// Export singleton instance.\nexport const processLock = new ProcessLockManager()\n"],
5
+ "mappings": "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",
6
+ "names": ["process_lock_exports", "__export", "processLock", "__toCommonJS", "import_fs", "import_logger", "import_promises", "import_signal_exit", "logger", "ProcessLockManager", "timer", "lockPath", "now", "error", "intervalMs", "staleMs", "stats", "ageSeconds", "staleSeconds", "options", "baseDelayMs", "maxDelayMs", "retries", "touchIntervalMs", "code", "parentDir", "fn", "release"]
7
7
  }
@@ -30,13 +30,8 @@ export interface RetryOptions {
30
30
  * @default 200
31
31
  */
32
32
  baseDelayMs?: number | undefined;
33
- /**
34
- * Legacy alias for `backoffFactor`. Use `backoffFactor` instead.
35
- *
36
- * @deprecated Use `backoffFactor` instead
37
- * @default 2
38
- */
39
- factor?: number | undefined;
33
+ // REMOVED: Deprecated `factor` option
34
+ // Migration: Use `backoffFactor` instead
40
35
  /**
41
36
  * Whether to apply randomness to spread out retries and avoid thundering herd.
42
37
  * When `true`, adds random delay between 0 and current delay value.
@@ -54,20 +49,10 @@ export interface RetryOptions {
54
49
  * @default 10000
55
50
  */
56
51
  maxDelayMs?: number | undefined;
57
- /**
58
- * Legacy alias for `maxDelayMs`. Use `maxDelayMs` instead.
59
- *
60
- * @deprecated Use `maxDelayMs` instead
61
- * @default 10000
62
- */
63
- maxTimeout?: number | undefined;
64
- /**
65
- * Legacy alias for `baseDelayMs`. Use `baseDelayMs` instead.
66
- *
67
- * @deprecated Use `baseDelayMs` instead
68
- * @default 200
69
- */
70
- minTimeout?: number | undefined;
52
+ // REMOVED: Deprecated `maxTimeout` option
53
+ // Migration: Use `maxDelayMs` instead
54
+ // REMOVED: Deprecated `minTimeout` option
55
+ // Migration: Use `baseDelayMs` instead
71
56
  /**
72
57
  * Callback invoked on each retry attempt.
73
58
  * Can observe errors, customize delays, or cancel retries.
package/dist/promises.js CHANGED
@@ -1,3 +1,3 @@
1
1
  /* Socket Lib - Built with esbuild */
2
- var g=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var I=(n,e)=>{for(var r in e)g(n,r,{get:e[r],enumerable:!0})},v=(n,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of S(e))!z.call(n,t)&&t!==r&&g(n,t,{get:()=>e[t],enumerable:!(o=F(e,t))||o.enumerable});return n};var A=n=>v(g({},"__esModule",{value:!0}),n);var K={};I(K,{normalizeIterationOptions:()=>w,normalizeRetryOptions:()=>y,pEach:()=>j,pEachChunk:()=>q,pFilter:()=>N,pFilterChunk:()=>C,pRetry:()=>h,resolveRetryOptions:()=>x});module.exports=A(K);var T=require("#constants/core"),M=require("#constants/process"),O=require("./arrays");const P=(0,M.getAbortSignal)();let k;function E(){return k===void 0&&(k=require("node:timers/promises")),k}function w(n){const e=typeof n=="number"?{concurrency:n}:n,{concurrency:r=1,retries:o,signal:t=P}={__proto__:null,...e};return{__proto__:null,concurrency:Math.max(1,r),retries:y({signal:t,...x(o)}),signal:t}}function y(n){const e=x(n),{args:r=[],backoffFactor:o=e.factor||2,baseDelayMs:t=e.minTimeout||200,jitter:u=!0,maxDelayMs:i=e.maxTimeout||1e4,onRetry:s,onRetryCancelOnFalse:c=!1,onRetryRethrow:f=!1,retries:m=e.retries||0,signal:a=P}=e;return{args:r,backoffFactor:o,baseDelayMs:t,jitter:u,maxDelayMs:i,minTimeout:t,maxTimeout:i,onRetry:s,onRetryCancelOnFalse:c,onRetryRethrow:f,retries:m,signal:a}}function x(n){const e={__proto__:null,retries:0,minTimeout:200,maxTimeout:1e4,factor:2};return typeof n=="number"?{...e,retries:n}:n?{...e,...n}:e}async function j(n,e,r){const o=w(r),{concurrency:t,retries:u,signal:i}=o,s=(0,O.arrayChunk)(n,t);for(const c of s){if(i?.aborted)return;await Promise.all(c.map(f=>h((...m)=>e(m[0]),{...u,args:[f],signal:i})))}}async function N(n,e,r){const o=w(r);return(await C((0,O.arrayChunk)(n,o.concurrency),e,o.retries)).flat()}async function q(n,e,r){const{chunkSize:o=100,...t}=r||{},u=(0,O.arrayChunk)(n,o),i=y(t),{signal:s}=i;for(const c of u){if(s?.aborted)return;await h((...f)=>e(f[0]),{...i,args:[c]})}}async function C(n,e,r){const o=y(r),{signal:t}=o,{length:u}=n,i=Array(u);for(let s=0;s<u;s+=1)if(t?.aborted)i[s]=[];else{const c=n[s],f=await Promise.all(c.map(m=>h((...a)=>e(a[0]),{...o,args:[m]})));i[s]=c.filter((m,a)=>f[a])}return i}async function h(n,e){const{args:r,backoffFactor:o,baseDelayMs:t,jitter:u,maxDelayMs:i,onRetry:s,onRetryCancelOnFalse:c,onRetryRethrow:f,retries:m,signal:a}=y(e);if(a?.aborted)return;if(m===0)return await n(...r||[],{signal:a});const D=E();let R=m,p=t,b=T.UNDEFINED_TOKEN;for(;R-->=0;){if(a?.aborted)return;try{return await n(...r||[],{signal:a})}catch(_){if(b===T.UNDEFINED_TOKEN&&(b=_),R<0)break;let d=p;if(u&&(d+=Math.floor(Math.random()*p)),d=Math.min(d,i),typeof s=="function")try{const l=s(m-R,_,d);if(l===!1&&c)break;typeof l=="number"&&l>=0&&(d=Math.min(l,i))}catch(l){if(f)throw l}try{await D.setTimeout(d,void 0,{signal:a})}catch{return}if(a?.aborted)return;p=Math.min(p*o,i)}}if(b!==T.UNDEFINED_TOKEN)throw b}0&&(module.exports={normalizeIterationOptions,normalizeRetryOptions,pEach,pEachChunk,pFilter,pFilterChunk,pRetry,resolveRetryOptions});
2
+ var T=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var I=(e,n)=>{for(var r in n)T(e,r,{get:n[r],enumerable:!0})},v=(e,n,r,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of S(n))!z.call(e,o)&&o!==r&&T(e,o,{get:()=>n[o],enumerable:!(t=F(n,o))||t.enumerable});return e};var A=e=>v(T({},"__esModule",{value:!0}),e);var K={};I(K,{normalizeIterationOptions:()=>w,normalizeRetryOptions:()=>y,pEach:()=>j,pEachChunk:()=>q,pFilter:()=>N,pFilterChunk:()=>D,pRetry:()=>R,resolveRetryOptions:()=>_});module.exports=A(K);var O=require("#constants/core"),M=require("#constants/process"),h=require("./arrays");const P=(0,M.getAbortSignal)();let k;function E(){return k===void 0&&(k=require("node:timers/promises")),k}function w(e){const n=typeof e=="number"?{concurrency:e}:e,{concurrency:r=1,retries:t,signal:o=P}={__proto__:null,...n};return{__proto__:null,concurrency:Math.max(1,r),retries:y({signal:o,..._(t)}),signal:o}}function y(e){const n=_(e),{args:r=[],backoffFactor:t=2,baseDelayMs:o=200,jitter:u=!0,maxDelayMs:i=1e4,onRetry:s,onRetryCancelOnFalse:c=!1,onRetryRethrow:d=!1,retries:f=0,signal:a=P}=n;return{args:r,backoffFactor:t,baseDelayMs:o,jitter:u,maxDelayMs:i,onRetry:s,onRetryCancelOnFalse:c,onRetryRethrow:d,retries:f,signal:a}}function _(e){const n={__proto__:null,retries:0,baseDelayMs:200,maxDelayMs:1e4,backoffFactor:2};return typeof e=="number"?{...n,retries:e}:e?{...n,...e}:n}async function j(e,n,r){const t=w(r),{concurrency:o,retries:u,signal:i}=t,s=(0,h.arrayChunk)(e,o);for(const c of s){if(i?.aborted)return;await Promise.all(c.map(d=>R((...f)=>n(f[0]),{...u,args:[d],signal:i})))}}async function N(e,n,r){const t=w(r);return(await D((0,h.arrayChunk)(e,t.concurrency),n,t.retries)).flat()}async function q(e,n,r){const{chunkSize:t=100,...o}=r||{},u=(0,h.arrayChunk)(e,t),i=y(o),{signal:s}=i;for(const c of u){if(s?.aborted)return;await R((...d)=>n(d[0]),{...i,args:[c]})}}async function D(e,n,r){const t=y(r),{signal:o}=t,{length:u}=e,i=Array(u);for(let s=0;s<u;s+=1)if(o?.aborted)i[s]=[];else{const c=e[s],d=await Promise.all(c.map(f=>R((...a)=>n(a[0]),{...t,args:[f]})));i[s]=c.filter((f,a)=>d[a])}return i}async function R(e,n){const{args:r,backoffFactor:t,baseDelayMs:o,jitter:u,maxDelayMs:i,onRetry:s,onRetryCancelOnFalse:c,onRetryRethrow:d,retries:f,signal:a}=y(n);if(a?.aborted)return;if(f===0)return await e(...r||[],{signal:a});const C=E();let g=f,p=o,b=O.UNDEFINED_TOKEN;for(;g-->=0;){if(a?.aborted)return;try{return await e(...r||[],{signal:a})}catch(x){if(b===O.UNDEFINED_TOKEN&&(b=x),g<0)break;let m=p;if(u&&(m+=Math.floor(Math.random()*p)),m=Math.min(m,i),typeof s=="function")try{const l=s(f-g,x,m);if(l===!1&&c)break;typeof l=="number"&&l>=0&&(m=Math.min(l,i))}catch(l){if(d)throw l}try{await C.setTimeout(m,void 0,{signal:a})}catch{return}if(a?.aborted)return;p=Math.min(p*t,i)}}if(b!==O.UNDEFINED_TOKEN)throw b}0&&(module.exports={normalizeIterationOptions,normalizeRetryOptions,pEach,pEachChunk,pFilter,pFilterChunk,pRetry,resolveRetryOptions});
3
3
  //# sourceMappingURL=promises.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/promises.ts"],
4
- "sourcesContent": ["/**\n * @fileoverview Promise utilities including chunked iteration and timers.\n * Provides async control flow helpers and promise-based timing functions.\n */\n\nimport { UNDEFINED_TOKEN } from '#constants/core'\nimport { getAbortSignal } from '#constants/process'\n\nimport { arrayChunk } from './arrays'\n\nconst abortSignal = getAbortSignal()\n\n/**\n * Configuration options for retry behavior with exponential backoff.\n *\n * Controls how failed operations are retried, including timing, backoff strategy,\n * and callback hooks for observing or modifying retry behavior.\n */\nexport interface RetryOptions {\n /**\n * Arguments to pass to the callback function on each attempt.\n *\n * @default []\n */\n args?: unknown[] | undefined\n\n /**\n * Multiplier for exponential backoff (e.g., 2 doubles delay each retry).\n * Each retry waits `baseDelayMs * (backoffFactor ** attemptNumber)`.\n *\n * @default 2\n * @example\n * // With backoffFactor: 2, baseDelayMs: 100\n * // Retry 1: 100ms\n * // Retry 2: 200ms\n * // Retry 3: 400ms\n */\n backoffFactor?: number | undefined\n\n /**\n * Initial delay before the first retry (in milliseconds).\n * This is the base value for exponential backoff calculations.\n *\n * @default 200\n */\n baseDelayMs?: number | undefined\n\n /**\n * Legacy alias for `backoffFactor`. Use `backoffFactor` instead.\n *\n * @deprecated Use `backoffFactor` instead\n * @default 2\n */\n factor?: number | undefined\n\n /**\n * Whether to apply randomness to spread out retries and avoid thundering herd.\n * When `true`, adds random delay between 0 and current delay value.\n *\n * @default true\n * @example\n * // With jitter: true, delay: 100ms\n * // Actual wait: 100ms + random(0-100ms) = 100-200ms\n */\n jitter?: boolean | undefined\n\n /**\n * Upper limit for any backoff delay (in milliseconds).\n * Prevents exponential backoff from growing unbounded.\n *\n * @default 10000\n */\n maxDelayMs?: number | undefined\n\n /**\n * Legacy alias for `maxDelayMs`. Use `maxDelayMs` instead.\n *\n * @deprecated Use `maxDelayMs` instead\n * @default 10000\n */\n maxTimeout?: number | undefined\n\n /**\n * Legacy alias for `baseDelayMs`. Use `baseDelayMs` instead.\n *\n * @deprecated Use `baseDelayMs` instead\n * @default 200\n */\n minTimeout?: number | undefined\n\n /**\n * Callback invoked on each retry attempt.\n * Can observe errors, customize delays, or cancel retries.\n *\n * @param attempt - The current attempt number (1-based: 1, 2, 3, ...)\n * @param error - The error that triggered this retry\n * @param delay - The calculated delay in milliseconds before next retry\n * @returns `false` to cancel retries (if `onRetryCancelOnFalse` is `true`),\n * a number to override the delay, or `undefined` to use calculated delay\n *\n * @example\n * // Log each retry\n * onRetry: (attempt, error, delay) => {\n * console.log(`Retry ${attempt} after ${delay}ms: ${error}`)\n * }\n *\n * @example\n * // Cancel retries for specific errors\n * onRetry: (attempt, error) => {\n * if (error instanceof ValidationError) return false\n * }\n *\n * @example\n * // Use custom delay\n * onRetry: (attempt) => attempt * 1000 // 1s, 2s, 3s, ...\n */\n onRetry?:\n | ((\n attempt: number,\n error: unknown,\n delay: number,\n ) => boolean | number | undefined)\n | undefined\n\n /**\n * Whether `onRetry` can cancel retries by returning `false`.\n * When `true`, returning `false` from `onRetry` stops retry attempts.\n *\n * @default false\n */\n onRetryCancelOnFalse?: boolean | undefined\n\n /**\n * Whether errors thrown by `onRetry` should propagate.\n * When `true`, exceptions in `onRetry` terminate the retry loop.\n * When `false`, exceptions in `onRetry` are silently caught.\n *\n * @default false\n */\n onRetryRethrow?: boolean | undefined\n\n /**\n * Number of retry attempts (0 = no retries, only initial attempt).\n * The callback is executed `retries + 1` times total (initial + retries).\n *\n * @default 0\n * @example\n * // retries: 0 -> 1 total attempt (no retries)\n * // retries: 3 -> 4 total attempts (1 initial + 3 retries)\n */\n retries?: number | undefined\n\n /**\n * AbortSignal to support cancellation of retry operations.\n * When aborted, immediately stops retrying and returns `undefined`.\n *\n * @default process abort signal\n * @example\n * const controller = new AbortController()\n * pRetry(fn, { signal: controller.signal })\n * // Later: controller.abort() to cancel\n */\n signal?: AbortSignal | undefined\n}\n\n/**\n * Configuration options for iteration functions with concurrency control.\n *\n * Controls how array operations are parallelized and retried.\n */\nexport interface IterationOptions {\n /**\n * The number of concurrent executions performed at one time.\n * Higher values increase parallelism but may overwhelm resources.\n *\n * @default 1\n * @example\n * // Process 5 items at a time\n * await pEach(items, processItem, { concurrency: 5 })\n */\n concurrency?: number | undefined\n\n /**\n * Retry configuration as a number (retry count) or full options object.\n * Applied to each individual item's callback execution.\n *\n * @default 0 (no retries)\n * @example\n * // Simple: retry each item up to 3 times\n * await pEach(items, fetchItem, { retries: 3 })\n *\n * @example\n * // Advanced: custom backoff for each item\n * await pEach(items, fetchItem, {\n * retries: {\n * retries: 3,\n * baseDelayMs: 1000,\n * backoffFactor: 2\n * }\n * })\n */\n retries?: number | RetryOptions | undefined\n\n /**\n * AbortSignal to support cancellation of the entire iteration.\n * When aborted, stops processing remaining items.\n *\n * @default process abort signal\n */\n signal?: AbortSignal | undefined\n}\n\nlet _timers: typeof import('node:timers/promises') | undefined\n/**\n * Get the timers/promises module.\n * Uses lazy loading to avoid Webpack bundling issues.\n *\n * @private\n * @returns The Node.js timers/promises module\n */\n/*@__NO_SIDE_EFFECTS__*/\nfunction getTimers() {\n if (_timers === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _timers = /*@__PURE__*/ require('node:timers/promises')\n }\n return _timers as typeof import('node:timers/promises')\n}\n\n/**\n * Normalize options for iteration functions.\n *\n * Converts various option formats into a consistent structure with defaults applied.\n * Handles number shorthand for concurrency and ensures minimum values.\n *\n * @param options - Concurrency as number, or full options object, or undefined\n * @returns Normalized options with concurrency, retries, and signal\n *\n * @example\n * // Number shorthand for concurrency\n * normalizeIterationOptions(5)\n * // => { concurrency: 5, retries: {...}, signal: AbortSignal }\n *\n * @example\n * // Full options\n * normalizeIterationOptions({ concurrency: 3, retries: 2 })\n * // => { concurrency: 3, retries: {...}, signal: AbortSignal }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function normalizeIterationOptions(\n options?: number | IterationOptions | undefined,\n): { concurrency: number; retries: RetryOptions; signal: AbortSignal } {\n // Handle number as concurrency shorthand\n const opts = typeof options === 'number' ? { concurrency: options } : options\n\n const {\n // The number of concurrent executions performed at one time.\n concurrency = 1,\n // Retries as a number or options object.\n retries,\n // AbortSignal used to support cancellation.\n signal = abortSignal,\n } = { __proto__: null, ...opts } as IterationOptions\n\n // Ensure concurrency is at least 1\n const normalizedConcurrency = Math.max(1, concurrency)\n const retryOpts = resolveRetryOptions(retries)\n return {\n __proto__: null,\n concurrency: normalizedConcurrency,\n retries: normalizeRetryOptions({ signal, ...retryOpts }),\n signal,\n } as { concurrency: number; retries: RetryOptions; signal: AbortSignal }\n}\n\n/**\n * Normalize options for retry functionality.\n *\n * Converts various retry option formats into a complete configuration with all defaults.\n * Handles legacy property names (`factor`, `minTimeout`, `maxTimeout`) and merges them\n * with modern equivalents.\n *\n * @param options - Retry count as number, or full options object, or undefined\n * @returns Normalized retry options with all properties set\n *\n * @example\n * // Number shorthand\n * normalizeRetryOptions(3)\n * // => { retries: 3, baseDelayMs: 200, backoffFactor: 2, ... }\n *\n * @example\n * // Full options with defaults filled in\n * normalizeRetryOptions({ retries: 5, baseDelayMs: 500 })\n * // => { retries: 5, baseDelayMs: 500, backoffFactor: 2, jitter: true, ... }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function normalizeRetryOptions(\n options?: number | RetryOptions | undefined,\n): RetryOptions {\n const resolved = resolveRetryOptions(options)\n const {\n // Arguments to pass to the callback function.\n args = [],\n // Multiplier for exponential backoff (e.g., 2 doubles delay each retry).\n backoffFactor = resolved.factor || 2,\n // Initial delay before the first retry (in milliseconds).\n baseDelayMs = resolved.minTimeout || 200,\n // Whether to apply randomness to spread out retries.\n jitter = true,\n // Upper limit for any backoff delay (in milliseconds).\n maxDelayMs = resolved.maxTimeout || 10_000,\n // Optional callback invoked on each retry attempt:\n // (attempt: number, error: unknown, delay: number) => void\n onRetry,\n // Whether onRetry can cancel retries by returning `false`.\n onRetryCancelOnFalse = false,\n // Whether onRetry will rethrow errors.\n onRetryRethrow = false,\n // Number of retry attempts (0 = no retries, only initial attempt).\n retries = resolved.retries || 0,\n // AbortSignal used to support cancellation.\n signal = abortSignal,\n } = resolved\n return {\n args,\n backoffFactor,\n baseDelayMs,\n jitter,\n maxDelayMs,\n minTimeout: baseDelayMs,\n maxTimeout: maxDelayMs,\n onRetry,\n onRetryCancelOnFalse,\n onRetryRethrow,\n retries,\n signal,\n } as RetryOptions\n}\n\n/**\n * Resolve retry options from various input formats.\n *\n * Converts shorthand and partial options into a base configuration that can be\n * further normalized. This is an internal helper for option processing.\n *\n * @param options - Retry count as number, or partial options object, or undefined\n * @returns Resolved retry options with defaults for basic properties\n *\n * @example\n * resolveRetryOptions(3)\n * // => { retries: 3, minTimeout: 200, maxTimeout: 10000, factor: 2 }\n *\n * @example\n * resolveRetryOptions({ retries: 5, maxTimeout: 5000 })\n * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function resolveRetryOptions(\n options?: number | RetryOptions | undefined,\n): RetryOptions {\n const defaults = {\n __proto__: null,\n retries: 0,\n minTimeout: 200,\n maxTimeout: 10_000,\n factor: 2,\n }\n\n if (typeof options === 'number') {\n return { ...defaults, retries: options }\n }\n\n return options ? { ...defaults, ...options } : defaults\n}\n\n/**\n * Execute an async function for each array element with concurrency control.\n *\n * Processes array items in parallel batches (chunks) with configurable concurrency.\n * Each item's callback can be retried independently on failure. Similar to\n * `Promise.all(array.map(fn))` but with controlled parallelism.\n *\n * @template T - The type of array elements\n * @param array - The array to iterate over\n * @param callbackFn - Async function to execute for each item\n * @param options - Concurrency as number, or full iteration options, or undefined\n * @returns Promise that resolves when all items are processed\n *\n * @example\n * // Process items serially (concurrency: 1)\n * await pEach(urls, async (url) => {\n * await fetch(url)\n * })\n *\n * @example\n * // Process 5 items at a time\n * await pEach(files, async (file) => {\n * await processFile(file)\n * }, 5)\n *\n * @example\n * // With retries and cancellation\n * const controller = new AbortController()\n * await pEach(tasks, async (task) => {\n * await executeTask(task)\n * }, {\n * concurrency: 3,\n * retries: 2,\n * signal: controller.signal\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pEach<T>(\n array: T[],\n callbackFn: (item: T) => Promise<unknown>,\n options?: number | IterationOptions | undefined,\n): Promise<void> {\n const iterOpts = normalizeIterationOptions(options)\n const { concurrency, retries, signal } = iterOpts\n\n // Process items with concurrency control.\n const chunks = arrayChunk(array, concurrency)\n for (const chunk of chunks) {\n if (signal?.aborted) {\n return\n }\n // Process each item in the chunk concurrently.\n // eslint-disable-next-line no-await-in-loop\n await Promise.all(\n chunk.map((item: T) =>\n pRetry((...args: unknown[]) => callbackFn(args[0] as T), {\n ...retries,\n args: [item],\n signal,\n }),\n ),\n )\n }\n}\n\n/**\n * Filter an array asynchronously with concurrency control.\n *\n * Tests each element with an async predicate function, processing items in parallel\n * batches. Returns a new array with only items that pass the test. Similar to\n * `array.filter()` but for async predicates with controlled concurrency.\n *\n * @template T - The type of array elements\n * @param array - The array to filter\n * @param callbackFn - Async predicate function returning true to keep item\n * @param options - Concurrency as number, or full iteration options, or undefined\n * @returns Promise resolving to filtered array\n *\n * @example\n * // Filter serially\n * const activeUsers = await pFilter(users, async (user) => {\n * return await isUserActive(user.id)\n * })\n *\n * @example\n * // Filter with concurrency\n * const validFiles = await pFilter(filePaths, async (path) => {\n * try {\n * await fs.access(path)\n * return true\n * } catch {\n * return false\n * }\n * }, 10)\n *\n * @example\n * // With retries for flaky checks\n * const reachable = await pFilter(endpoints, async (url) => {\n * const response = await fetch(url)\n * return response.ok\n * }, {\n * concurrency: 5,\n * retries: 2\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pFilter<T>(\n array: T[],\n callbackFn: (item: T) => Promise<boolean>,\n options?: number | IterationOptions | undefined,\n): Promise<T[]> {\n const iterOpts = normalizeIterationOptions(options)\n return (\n await pFilterChunk(\n arrayChunk(array, iterOpts.concurrency),\n callbackFn,\n iterOpts.retries,\n )\n ).flat()\n}\n\n/**\n * Process array in chunks with an async callback.\n *\n * Divides the array into fixed-size chunks and processes each chunk sequentially\n * with the callback. Useful for batch operations like bulk database inserts or\n * API calls with payload size limits.\n *\n * @template T - The type of array elements\n * @param array - The array to process in chunks\n * @param callbackFn - Async function to execute for each chunk\n * @param options - Chunk size and retry options\n * @returns Promise that resolves when all chunks are processed\n *\n * @example\n * // Insert records in batches of 100\n * await pEachChunk(records, async (chunk) => {\n * await db.batchInsert(chunk)\n * }, { chunkSize: 100 })\n *\n * @example\n * // Upload files in batches with retries\n * await pEachChunk(files, async (batch) => {\n * await uploadBatch(batch)\n * }, {\n * chunkSize: 50,\n * retries: 3,\n * baseDelayMs: 1000\n * })\n *\n * @example\n * // Process with cancellation support\n * const controller = new AbortController()\n * await pEachChunk(items, async (chunk) => {\n * await processChunk(chunk)\n * }, {\n * chunkSize: 25,\n * signal: controller.signal\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pEachChunk<T>(\n array: T[],\n callbackFn: (chunk: T[]) => Promise<unknown>,\n options?: (RetryOptions & { chunkSize?: number | undefined }) | undefined,\n): Promise<void> {\n const { chunkSize = 100, ...retryOpts } = options || {}\n const chunks = arrayChunk(array, chunkSize)\n const normalizedRetryOpts = normalizeRetryOptions(retryOpts)\n const { signal } = normalizedRetryOpts\n for (const chunk of chunks) {\n if (signal?.aborted) {\n return\n }\n // eslint-disable-next-line no-await-in-loop\n await pRetry((...args: unknown[]) => callbackFn(args[0] as T[]), {\n ...normalizedRetryOpts,\n args: [chunk],\n })\n }\n}\n\n/**\n * Filter chunked arrays with an async predicate.\n *\n * Internal helper for `pFilter`. Processes pre-chunked arrays, applying the\n * predicate to each element within each chunk with retry support.\n *\n * @template T - The type of array elements\n * @param chunks - Pre-chunked array (array of arrays)\n * @param callbackFn - Async predicate function\n * @param options - Retry count as number, or full retry options, or undefined\n * @returns Promise resolving to array of filtered chunks\n *\n * @example\n * const chunks = [[1, 2], [3, 4], [5, 6]]\n * const filtered = await pFilterChunk(chunks, async (n) => n % 2 === 0)\n * // => [[2], [4], [6]]\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pFilterChunk<T>(\n chunks: T[][],\n callbackFn: (value: T) => Promise<boolean>,\n options?: number | RetryOptions | undefined,\n): Promise<T[][]> {\n const retryOpts = normalizeRetryOptions(options)\n const { signal } = retryOpts\n const { length } = chunks\n const filteredChunks = Array(length)\n for (let i = 0; i < length; i += 1) {\n // Process each chunk, filtering based on the callback function.\n if (signal?.aborted) {\n filteredChunks[i] = []\n } else {\n const chunk = chunks[i] as T[]\n // eslint-disable-next-line no-await-in-loop\n const predicateResults = await Promise.all(\n chunk.map(value =>\n pRetry((...args: unknown[]) => callbackFn(args[0] as T), {\n ...retryOpts,\n args: [value],\n }),\n ),\n )\n filteredChunks[i] = chunk.filter((_v, i) => predicateResults[i])\n }\n }\n return filteredChunks\n}\n\n/**\n * Retry an async function with exponential backoff.\n *\n * Attempts to execute a function multiple times with increasing delays between attempts.\n * Implements exponential backoff with optional jitter to prevent thundering herd problems.\n * Supports custom retry logic via `onRetry` callback.\n *\n * The delay calculation follows: `min(baseDelayMs * (backoffFactor ** attempt), maxDelayMs)`\n * With jitter: adds random value between 0 and calculated delay.\n *\n * @template T - The return type of the callback function\n * @param callbackFn - Async function to retry\n * @param options - Retry count as number, or full retry options, or undefined\n * @returns Promise resolving to callback result, or `undefined` if aborted\n *\n * @throws {Error} The last error if all retry attempts fail\n *\n * @example\n * // Simple retry: 3 attempts with default backoff\n * const data = await pRetry(async () => {\n * return await fetchData()\n * }, 3)\n *\n * @example\n * // Custom backoff strategy\n * const result = await pRetry(async () => {\n * return await unreliableOperation()\n * }, {\n * retries: 5,\n * baseDelayMs: 1000, // Start at 1 second\n * backoffFactor: 2, // Double each time\n * maxDelayMs: 30000, // Cap at 30 seconds\n * jitter: true // Add randomness\n * })\n * // Delays: ~1s, ~2s, ~4s, ~8s, ~16s (each \u00B1 random jitter)\n *\n * @example\n * // With custom retry logic\n * const data = await pRetry(async () => {\n * return await apiCall()\n * }, {\n * retries: 3,\n * onRetry: (attempt, error, delay) => {\n * console.log(`Attempt ${attempt} failed: ${error}`)\n * console.log(`Waiting ${delay}ms before retry...`)\n *\n * // Cancel retries for client errors (4xx)\n * if (error.statusCode >= 400 && error.statusCode < 500) {\n * return false\n * }\n *\n * // Use longer delay for rate limit errors\n * if (error.statusCode === 429) {\n * return 60000 // Wait 1 minute\n * }\n * },\n * onRetryCancelOnFalse: true\n * })\n *\n * @example\n * // With cancellation support\n * const controller = new AbortController()\n * setTimeout(() => controller.abort(), 5000) // Cancel after 5s\n *\n * const result = await pRetry(async ({ signal }) => {\n * return await longRunningTask(signal)\n * }, {\n * retries: 10,\n * signal: controller.signal\n * })\n * // Returns undefined if aborted\n *\n * @example\n * // Pass arguments to callback\n * const result = await pRetry(\n * async (url, options) => {\n * return await fetch(url, options)\n * },\n * {\n * retries: 3,\n * args: ['https://api.example.com', { method: 'POST' }]\n * }\n * )\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pRetry<T>(\n callbackFn: (...args: unknown[]) => Promise<T>,\n options?: number | RetryOptions | undefined,\n): Promise<T | undefined> {\n const {\n args,\n backoffFactor,\n baseDelayMs,\n jitter,\n maxDelayMs,\n onRetry,\n onRetryCancelOnFalse,\n onRetryRethrow,\n retries,\n signal,\n } = normalizeRetryOptions(options)\n if (signal?.aborted) {\n return undefined\n }\n if (retries === 0) {\n return await callbackFn(...(args || []), { signal })\n }\n\n const timers = getTimers()\n\n let attempts = retries as number\n let delay = baseDelayMs as number\n let error: unknown = UNDEFINED_TOKEN\n\n while (attempts-- >= 0) {\n // Check abort before attempt.\n if (signal?.aborted) {\n return undefined\n }\n\n try {\n // eslint-disable-next-line no-await-in-loop\n return await callbackFn(...(args || []), { signal })\n } catch (e) {\n if (error === UNDEFINED_TOKEN) {\n error = e\n }\n if (attempts < 0) {\n break\n }\n let waitTime = delay\n if (jitter) {\n // Add randomness: Pick a value between 0 and `delay`.\n waitTime += Math.floor(Math.random() * delay)\n }\n // Clamp wait time to max delay.\n waitTime = Math.min(waitTime, maxDelayMs as number)\n if (typeof onRetry === 'function') {\n try {\n const result = onRetry((retries as number) - attempts, e, waitTime)\n if (result === false && onRetryCancelOnFalse) {\n break\n }\n // If onRetry returns a number, use it as the custom delay.\n if (typeof result === 'number' && result >= 0) {\n waitTime = Math.min(result, maxDelayMs as number)\n }\n } catch (e) {\n if (onRetryRethrow) {\n throw e\n }\n }\n }\n\n try {\n // eslint-disable-next-line no-await-in-loop\n await timers.setTimeout(waitTime, undefined, { signal })\n } catch {\n // setTimeout was aborted.\n return undefined\n }\n\n // Check abort again after delay.\n if (signal?.aborted) {\n return undefined\n }\n\n // Exponentially increase the delay for the next attempt, capping at maxDelayMs.\n delay = Math.min(delay * (backoffFactor as number), maxDelayMs as number)\n }\n }\n if (error !== UNDEFINED_TOKEN) {\n throw error\n }\n return undefined\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @fileoverview Promise utilities including chunked iteration and timers.\n * Provides async control flow helpers and promise-based timing functions.\n */\n\nimport { UNDEFINED_TOKEN } from '#constants/core'\nimport { getAbortSignal } from '#constants/process'\n\nimport { arrayChunk } from './arrays'\n\nconst abortSignal = getAbortSignal()\n\n/**\n * Configuration options for retry behavior with exponential backoff.\n *\n * Controls how failed operations are retried, including timing, backoff strategy,\n * and callback hooks for observing or modifying retry behavior.\n */\nexport interface RetryOptions {\n /**\n * Arguments to pass to the callback function on each attempt.\n *\n * @default []\n */\n args?: unknown[] | undefined\n\n /**\n * Multiplier for exponential backoff (e.g., 2 doubles delay each retry).\n * Each retry waits `baseDelayMs * (backoffFactor ** attemptNumber)`.\n *\n * @default 2\n * @example\n * // With backoffFactor: 2, baseDelayMs: 100\n * // Retry 1: 100ms\n * // Retry 2: 200ms\n * // Retry 3: 400ms\n */\n backoffFactor?: number | undefined\n\n /**\n * Initial delay before the first retry (in milliseconds).\n * This is the base value for exponential backoff calculations.\n *\n * @default 200\n */\n baseDelayMs?: number | undefined\n\n // REMOVED: Deprecated `factor` option\n // Migration: Use `backoffFactor` instead\n\n /**\n * Whether to apply randomness to spread out retries and avoid thundering herd.\n * When `true`, adds random delay between 0 and current delay value.\n *\n * @default true\n * @example\n * // With jitter: true, delay: 100ms\n * // Actual wait: 100ms + random(0-100ms) = 100-200ms\n */\n jitter?: boolean | undefined\n\n /**\n * Upper limit for any backoff delay (in milliseconds).\n * Prevents exponential backoff from growing unbounded.\n *\n * @default 10000\n */\n maxDelayMs?: number | undefined\n\n // REMOVED: Deprecated `maxTimeout` option\n // Migration: Use `maxDelayMs` instead\n\n // REMOVED: Deprecated `minTimeout` option\n // Migration: Use `baseDelayMs` instead\n\n /**\n * Callback invoked on each retry attempt.\n * Can observe errors, customize delays, or cancel retries.\n *\n * @param attempt - The current attempt number (1-based: 1, 2, 3, ...)\n * @param error - The error that triggered this retry\n * @param delay - The calculated delay in milliseconds before next retry\n * @returns `false` to cancel retries (if `onRetryCancelOnFalse` is `true`),\n * a number to override the delay, or `undefined` to use calculated delay\n *\n * @example\n * // Log each retry\n * onRetry: (attempt, error, delay) => {\n * console.log(`Retry ${attempt} after ${delay}ms: ${error}`)\n * }\n *\n * @example\n * // Cancel retries for specific errors\n * onRetry: (attempt, error) => {\n * if (error instanceof ValidationError) return false\n * }\n *\n * @example\n * // Use custom delay\n * onRetry: (attempt) => attempt * 1000 // 1s, 2s, 3s, ...\n */\n onRetry?:\n | ((\n attempt: number,\n error: unknown,\n delay: number,\n ) => boolean | number | undefined)\n | undefined\n\n /**\n * Whether `onRetry` can cancel retries by returning `false`.\n * When `true`, returning `false` from `onRetry` stops retry attempts.\n *\n * @default false\n */\n onRetryCancelOnFalse?: boolean | undefined\n\n /**\n * Whether errors thrown by `onRetry` should propagate.\n * When `true`, exceptions in `onRetry` terminate the retry loop.\n * When `false`, exceptions in `onRetry` are silently caught.\n *\n * @default false\n */\n onRetryRethrow?: boolean | undefined\n\n /**\n * Number of retry attempts (0 = no retries, only initial attempt).\n * The callback is executed `retries + 1` times total (initial + retries).\n *\n * @default 0\n * @example\n * // retries: 0 -> 1 total attempt (no retries)\n * // retries: 3 -> 4 total attempts (1 initial + 3 retries)\n */\n retries?: number | undefined\n\n /**\n * AbortSignal to support cancellation of retry operations.\n * When aborted, immediately stops retrying and returns `undefined`.\n *\n * @default process abort signal\n * @example\n * const controller = new AbortController()\n * pRetry(fn, { signal: controller.signal })\n * // Later: controller.abort() to cancel\n */\n signal?: AbortSignal | undefined\n}\n\n/**\n * Configuration options for iteration functions with concurrency control.\n *\n * Controls how array operations are parallelized and retried.\n */\nexport interface IterationOptions {\n /**\n * The number of concurrent executions performed at one time.\n * Higher values increase parallelism but may overwhelm resources.\n *\n * @default 1\n * @example\n * // Process 5 items at a time\n * await pEach(items, processItem, { concurrency: 5 })\n */\n concurrency?: number | undefined\n\n /**\n * Retry configuration as a number (retry count) or full options object.\n * Applied to each individual item's callback execution.\n *\n * @default 0 (no retries)\n * @example\n * // Simple: retry each item up to 3 times\n * await pEach(items, fetchItem, { retries: 3 })\n *\n * @example\n * // Advanced: custom backoff for each item\n * await pEach(items, fetchItem, {\n * retries: {\n * retries: 3,\n * baseDelayMs: 1000,\n * backoffFactor: 2\n * }\n * })\n */\n retries?: number | RetryOptions | undefined\n\n /**\n * AbortSignal to support cancellation of the entire iteration.\n * When aborted, stops processing remaining items.\n *\n * @default process abort signal\n */\n signal?: AbortSignal | undefined\n}\n\nlet _timers: typeof import('node:timers/promises') | undefined\n/**\n * Get the timers/promises module.\n * Uses lazy loading to avoid Webpack bundling issues.\n *\n * @private\n * @returns The Node.js timers/promises module\n */\n/*@__NO_SIDE_EFFECTS__*/\nfunction getTimers() {\n if (_timers === undefined) {\n // Use non-'node:' prefixed require to avoid Webpack errors.\n\n _timers = /*@__PURE__*/ require('node:timers/promises')\n }\n return _timers as typeof import('node:timers/promises')\n}\n\n/**\n * Normalize options for iteration functions.\n *\n * Converts various option formats into a consistent structure with defaults applied.\n * Handles number shorthand for concurrency and ensures minimum values.\n *\n * @param options - Concurrency as number, or full options object, or undefined\n * @returns Normalized options with concurrency, retries, and signal\n *\n * @example\n * // Number shorthand for concurrency\n * normalizeIterationOptions(5)\n * // => { concurrency: 5, retries: {...}, signal: AbortSignal }\n *\n * @example\n * // Full options\n * normalizeIterationOptions({ concurrency: 3, retries: 2 })\n * // => { concurrency: 3, retries: {...}, signal: AbortSignal }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function normalizeIterationOptions(\n options?: number | IterationOptions | undefined,\n): { concurrency: number; retries: RetryOptions; signal: AbortSignal } {\n // Handle number as concurrency shorthand\n const opts = typeof options === 'number' ? { concurrency: options } : options\n\n const {\n // The number of concurrent executions performed at one time.\n concurrency = 1,\n // Retries as a number or options object.\n retries,\n // AbortSignal used to support cancellation.\n signal = abortSignal,\n } = { __proto__: null, ...opts } as IterationOptions\n\n // Ensure concurrency is at least 1\n const normalizedConcurrency = Math.max(1, concurrency)\n const retryOpts = resolveRetryOptions(retries)\n return {\n __proto__: null,\n concurrency: normalizedConcurrency,\n retries: normalizeRetryOptions({ signal, ...retryOpts }),\n signal,\n } as { concurrency: number; retries: RetryOptions; signal: AbortSignal }\n}\n\n/**\n * Normalize options for retry functionality.\n *\n * Converts various retry option formats into a complete configuration with all defaults.\n * Handles legacy property names (`factor`, `minTimeout`, `maxTimeout`) and merges them\n * with modern equivalents.\n *\n * @param options - Retry count as number, or full options object, or undefined\n * @returns Normalized retry options with all properties set\n *\n * @example\n * // Number shorthand\n * normalizeRetryOptions(3)\n * // => { retries: 3, baseDelayMs: 200, backoffFactor: 2, ... }\n *\n * @example\n * // Full options with defaults filled in\n * normalizeRetryOptions({ retries: 5, baseDelayMs: 500 })\n * // => { retries: 5, baseDelayMs: 500, backoffFactor: 2, jitter: true, ... }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function normalizeRetryOptions(\n options?: number | RetryOptions | undefined,\n): RetryOptions {\n const resolved = resolveRetryOptions(options)\n const {\n // Arguments to pass to the callback function.\n args = [],\n // Multiplier for exponential backoff (e.g., 2 doubles delay each retry).\n backoffFactor = 2,\n // Initial delay before the first retry (in milliseconds).\n baseDelayMs = 200,\n // Whether to apply randomness to spread out retries.\n jitter = true,\n // Upper limit for any backoff delay (in milliseconds).\n maxDelayMs = 10_000,\n // Optional callback invoked on each retry attempt:\n // (attempt: number, error: unknown, delay: number) => void\n onRetry,\n // Whether onRetry can cancel retries by returning `false`.\n onRetryCancelOnFalse = false,\n // Whether onRetry will rethrow errors.\n onRetryRethrow = false,\n // Number of retry attempts (0 = no retries, only initial attempt).\n retries = 0,\n // AbortSignal used to support cancellation.\n signal = abortSignal,\n } = resolved\n return {\n args,\n backoffFactor,\n baseDelayMs,\n jitter,\n maxDelayMs,\n onRetry,\n onRetryCancelOnFalse,\n onRetryRethrow,\n retries,\n signal,\n } as RetryOptions\n}\n\n/**\n * Resolve retry options from various input formats.\n *\n * Converts shorthand and partial options into a base configuration that can be\n * further normalized. This is an internal helper for option processing.\n *\n * @param options - Retry count as number, or partial options object, or undefined\n * @returns Resolved retry options with defaults for basic properties\n *\n * @example\n * resolveRetryOptions(3)\n * // => { retries: 3, minTimeout: 200, maxTimeout: 10000, factor: 2 }\n *\n * @example\n * resolveRetryOptions({ retries: 5, maxTimeout: 5000 })\n * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 }\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function resolveRetryOptions(\n options?: number | RetryOptions | undefined,\n): RetryOptions {\n const defaults = {\n __proto__: null,\n retries: 0,\n baseDelayMs: 200,\n maxDelayMs: 10_000,\n backoffFactor: 2,\n }\n\n if (typeof options === 'number') {\n return { ...defaults, retries: options }\n }\n\n return options ? { ...defaults, ...options } : defaults\n}\n\n/**\n * Execute an async function for each array element with concurrency control.\n *\n * Processes array items in parallel batches (chunks) with configurable concurrency.\n * Each item's callback can be retried independently on failure. Similar to\n * `Promise.all(array.map(fn))` but with controlled parallelism.\n *\n * @template T - The type of array elements\n * @param array - The array to iterate over\n * @param callbackFn - Async function to execute for each item\n * @param options - Concurrency as number, or full iteration options, or undefined\n * @returns Promise that resolves when all items are processed\n *\n * @example\n * // Process items serially (concurrency: 1)\n * await pEach(urls, async (url) => {\n * await fetch(url)\n * })\n *\n * @example\n * // Process 5 items at a time\n * await pEach(files, async (file) => {\n * await processFile(file)\n * }, 5)\n *\n * @example\n * // With retries and cancellation\n * const controller = new AbortController()\n * await pEach(tasks, async (task) => {\n * await executeTask(task)\n * }, {\n * concurrency: 3,\n * retries: 2,\n * signal: controller.signal\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pEach<T>(\n array: T[],\n callbackFn: (item: T) => Promise<unknown>,\n options?: number | IterationOptions | undefined,\n): Promise<void> {\n const iterOpts = normalizeIterationOptions(options)\n const { concurrency, retries, signal } = iterOpts\n\n // Process items with concurrency control.\n const chunks = arrayChunk(array, concurrency)\n for (const chunk of chunks) {\n if (signal?.aborted) {\n return\n }\n // Process each item in the chunk concurrently.\n // eslint-disable-next-line no-await-in-loop\n await Promise.all(\n chunk.map((item: T) =>\n pRetry((...args: unknown[]) => callbackFn(args[0] as T), {\n ...retries,\n args: [item],\n signal,\n }),\n ),\n )\n }\n}\n\n/**\n * Filter an array asynchronously with concurrency control.\n *\n * Tests each element with an async predicate function, processing items in parallel\n * batches. Returns a new array with only items that pass the test. Similar to\n * `array.filter()` but for async predicates with controlled concurrency.\n *\n * @template T - The type of array elements\n * @param array - The array to filter\n * @param callbackFn - Async predicate function returning true to keep item\n * @param options - Concurrency as number, or full iteration options, or undefined\n * @returns Promise resolving to filtered array\n *\n * @example\n * // Filter serially\n * const activeUsers = await pFilter(users, async (user) => {\n * return await isUserActive(user.id)\n * })\n *\n * @example\n * // Filter with concurrency\n * const validFiles = await pFilter(filePaths, async (path) => {\n * try {\n * await fs.access(path)\n * return true\n * } catch {\n * return false\n * }\n * }, 10)\n *\n * @example\n * // With retries for flaky checks\n * const reachable = await pFilter(endpoints, async (url) => {\n * const response = await fetch(url)\n * return response.ok\n * }, {\n * concurrency: 5,\n * retries: 2\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pFilter<T>(\n array: T[],\n callbackFn: (item: T) => Promise<boolean>,\n options?: number | IterationOptions | undefined,\n): Promise<T[]> {\n const iterOpts = normalizeIterationOptions(options)\n return (\n await pFilterChunk(\n arrayChunk(array, iterOpts.concurrency),\n callbackFn,\n iterOpts.retries,\n )\n ).flat()\n}\n\n/**\n * Process array in chunks with an async callback.\n *\n * Divides the array into fixed-size chunks and processes each chunk sequentially\n * with the callback. Useful for batch operations like bulk database inserts or\n * API calls with payload size limits.\n *\n * @template T - The type of array elements\n * @param array - The array to process in chunks\n * @param callbackFn - Async function to execute for each chunk\n * @param options - Chunk size and retry options\n * @returns Promise that resolves when all chunks are processed\n *\n * @example\n * // Insert records in batches of 100\n * await pEachChunk(records, async (chunk) => {\n * await db.batchInsert(chunk)\n * }, { chunkSize: 100 })\n *\n * @example\n * // Upload files in batches with retries\n * await pEachChunk(files, async (batch) => {\n * await uploadBatch(batch)\n * }, {\n * chunkSize: 50,\n * retries: 3,\n * baseDelayMs: 1000\n * })\n *\n * @example\n * // Process with cancellation support\n * const controller = new AbortController()\n * await pEachChunk(items, async (chunk) => {\n * await processChunk(chunk)\n * }, {\n * chunkSize: 25,\n * signal: controller.signal\n * })\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pEachChunk<T>(\n array: T[],\n callbackFn: (chunk: T[]) => Promise<unknown>,\n options?: (RetryOptions & { chunkSize?: number | undefined }) | undefined,\n): Promise<void> {\n const { chunkSize = 100, ...retryOpts } = options || {}\n const chunks = arrayChunk(array, chunkSize)\n const normalizedRetryOpts = normalizeRetryOptions(retryOpts)\n const { signal } = normalizedRetryOpts\n for (const chunk of chunks) {\n if (signal?.aborted) {\n return\n }\n // eslint-disable-next-line no-await-in-loop\n await pRetry((...args: unknown[]) => callbackFn(args[0] as T[]), {\n ...normalizedRetryOpts,\n args: [chunk],\n })\n }\n}\n\n/**\n * Filter chunked arrays with an async predicate.\n *\n * Internal helper for `pFilter`. Processes pre-chunked arrays, applying the\n * predicate to each element within each chunk with retry support.\n *\n * @template T - The type of array elements\n * @param chunks - Pre-chunked array (array of arrays)\n * @param callbackFn - Async predicate function\n * @param options - Retry count as number, or full retry options, or undefined\n * @returns Promise resolving to array of filtered chunks\n *\n * @example\n * const chunks = [[1, 2], [3, 4], [5, 6]]\n * const filtered = await pFilterChunk(chunks, async (n) => n % 2 === 0)\n * // => [[2], [4], [6]]\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pFilterChunk<T>(\n chunks: T[][],\n callbackFn: (value: T) => Promise<boolean>,\n options?: number | RetryOptions | undefined,\n): Promise<T[][]> {\n const retryOpts = normalizeRetryOptions(options)\n const { signal } = retryOpts\n const { length } = chunks\n const filteredChunks = Array(length)\n for (let i = 0; i < length; i += 1) {\n // Process each chunk, filtering based on the callback function.\n if (signal?.aborted) {\n filteredChunks[i] = []\n } else {\n const chunk = chunks[i] as T[]\n // eslint-disable-next-line no-await-in-loop\n const predicateResults = await Promise.all(\n chunk.map(value =>\n pRetry((...args: unknown[]) => callbackFn(args[0] as T), {\n ...retryOpts,\n args: [value],\n }),\n ),\n )\n filteredChunks[i] = chunk.filter((_v, i) => predicateResults[i])\n }\n }\n return filteredChunks\n}\n\n/**\n * Retry an async function with exponential backoff.\n *\n * Attempts to execute a function multiple times with increasing delays between attempts.\n * Implements exponential backoff with optional jitter to prevent thundering herd problems.\n * Supports custom retry logic via `onRetry` callback.\n *\n * The delay calculation follows: `min(baseDelayMs * (backoffFactor ** attempt), maxDelayMs)`\n * With jitter: adds random value between 0 and calculated delay.\n *\n * @template T - The return type of the callback function\n * @param callbackFn - Async function to retry\n * @param options - Retry count as number, or full retry options, or undefined\n * @returns Promise resolving to callback result, or `undefined` if aborted\n *\n * @throws {Error} The last error if all retry attempts fail\n *\n * @example\n * // Simple retry: 3 attempts with default backoff\n * const data = await pRetry(async () => {\n * return await fetchData()\n * }, 3)\n *\n * @example\n * // Custom backoff strategy\n * const result = await pRetry(async () => {\n * return await unreliableOperation()\n * }, {\n * retries: 5,\n * baseDelayMs: 1000, // Start at 1 second\n * backoffFactor: 2, // Double each time\n * maxDelayMs: 30000, // Cap at 30 seconds\n * jitter: true // Add randomness\n * })\n * // Delays: ~1s, ~2s, ~4s, ~8s, ~16s (each \u00B1 random jitter)\n *\n * @example\n * // With custom retry logic\n * const data = await pRetry(async () => {\n * return await apiCall()\n * }, {\n * retries: 3,\n * onRetry: (attempt, error, delay) => {\n * console.log(`Attempt ${attempt} failed: ${error}`)\n * console.log(`Waiting ${delay}ms before retry...`)\n *\n * // Cancel retries for client errors (4xx)\n * if (error.statusCode >= 400 && error.statusCode < 500) {\n * return false\n * }\n *\n * // Use longer delay for rate limit errors\n * if (error.statusCode === 429) {\n * return 60000 // Wait 1 minute\n * }\n * },\n * onRetryCancelOnFalse: true\n * })\n *\n * @example\n * // With cancellation support\n * const controller = new AbortController()\n * setTimeout(() => controller.abort(), 5000) // Cancel after 5s\n *\n * const result = await pRetry(async ({ signal }) => {\n * return await longRunningTask(signal)\n * }, {\n * retries: 10,\n * signal: controller.signal\n * })\n * // Returns undefined if aborted\n *\n * @example\n * // Pass arguments to callback\n * const result = await pRetry(\n * async (url, options) => {\n * return await fetch(url, options)\n * },\n * {\n * retries: 3,\n * args: ['https://api.example.com', { method: 'POST' }]\n * }\n * )\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport async function pRetry<T>(\n callbackFn: (...args: unknown[]) => Promise<T>,\n options?: number | RetryOptions | undefined,\n): Promise<T | undefined> {\n const {\n args,\n backoffFactor,\n baseDelayMs,\n jitter,\n maxDelayMs,\n onRetry,\n onRetryCancelOnFalse,\n onRetryRethrow,\n retries,\n signal,\n } = normalizeRetryOptions(options)\n if (signal?.aborted) {\n return undefined\n }\n if (retries === 0) {\n return await callbackFn(...(args || []), { signal })\n }\n\n const timers = getTimers()\n\n let attempts = retries as number\n let delay = baseDelayMs as number\n let error: unknown = UNDEFINED_TOKEN\n\n while (attempts-- >= 0) {\n // Check abort before attempt.\n if (signal?.aborted) {\n return undefined\n }\n\n try {\n // eslint-disable-next-line no-await-in-loop\n return await callbackFn(...(args || []), { signal })\n } catch (e) {\n if (error === UNDEFINED_TOKEN) {\n error = e\n }\n if (attempts < 0) {\n break\n }\n let waitTime = delay\n if (jitter) {\n // Add randomness: Pick a value between 0 and `delay`.\n waitTime += Math.floor(Math.random() * delay)\n }\n // Clamp wait time to max delay.\n waitTime = Math.min(waitTime, maxDelayMs as number)\n if (typeof onRetry === 'function') {\n try {\n const result = onRetry((retries as number) - attempts, e, waitTime)\n if (result === false && onRetryCancelOnFalse) {\n break\n }\n // If onRetry returns a number, use it as the custom delay.\n if (typeof result === 'number' && result >= 0) {\n waitTime = Math.min(result, maxDelayMs as number)\n }\n } catch (e) {\n if (onRetryRethrow) {\n throw e\n }\n }\n }\n\n try {\n // eslint-disable-next-line no-await-in-loop\n await timers.setTimeout(waitTime, undefined, { signal })\n } catch {\n // setTimeout was aborted.\n return undefined\n }\n\n // Check abort again after delay.\n if (signal?.aborted) {\n return undefined\n }\n\n // Exponentially increase the delay for the next attempt, capping at maxDelayMs.\n delay = Math.min(delay * (backoffFactor as number), maxDelayMs as number)\n }\n }\n if (error !== UNDEFINED_TOKEN) {\n throw error\n }\n return undefined\n}\n"],
5
+ "mappings": ";4ZAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,+BAAAE,EAAA,0BAAAC,EAAA,UAAAC,EAAA,eAAAC,EAAA,YAAAC,EAAA,iBAAAC,EAAA,WAAAC,EAAA,wBAAAC,IAAA,eAAAC,EAAAV,GAKA,IAAAW,EAAgC,2BAChCC,EAA+B,8BAE/BC,EAA2B,oBAE3B,MAAMC,KAAc,kBAAe,EA2LnC,IAAIC,EASJ,SAASC,GAAY,CACnB,OAAID,IAAY,SAGdA,EAAwB,QAAQ,sBAAsB,GAEjDA,CACT,CAsBO,SAASb,EACde,EACqE,CAErE,MAAMC,EAAO,OAAOD,GAAY,SAAW,CAAE,YAAaA,CAAQ,EAAIA,EAEhE,CAEJ,YAAAE,EAAc,EAEd,QAAAC,EAEA,OAAAC,EAASP,CACX,EAAI,CAAE,UAAW,KAAM,GAAGI,CAAK,EAK/B,MAAO,CACL,UAAW,KACX,YAJ4B,KAAK,IAAI,EAAGC,CAAW,EAKnD,QAAShB,EAAsB,CAAE,OAAAkB,EAAQ,GAJzBZ,EAAoBW,CAAO,CAIW,CAAC,EACvD,OAAAC,CACF,CACF,CAuBO,SAASlB,EACdc,EACc,CACd,MAAMK,EAAWb,EAAoBQ,CAAO,EACtC,CAEJ,KAAAM,EAAO,CAAC,EAER,cAAAC,EAAgB,EAEhB,YAAAC,EAAc,IAEd,OAAAC,EAAS,GAET,WAAAC,EAAa,IAGb,QAAAC,EAEA,qBAAAC,EAAuB,GAEvB,eAAAC,EAAiB,GAEjB,QAAAV,EAAU,EAEV,OAAAC,EAASP,CACX,EAAIQ,EACJ,MAAO,CACL,KAAAC,EACA,cAAAC,EACA,YAAAC,EACA,OAAAC,EACA,WAAAC,EACA,QAAAC,EACA,qBAAAC,EACA,eAAAC,EACA,QAAAV,EACA,OAAAC,CACF,CACF,CAoBO,SAASZ,EACdQ,EACc,CACd,MAAMc,EAAW,CACf,UAAW,KACX,QAAS,EACT,YAAa,IACb,WAAY,IACZ,cAAe,CACjB,EAEA,OAAI,OAAOd,GAAY,SACd,CAAE,GAAGc,EAAU,QAASd,CAAQ,EAGlCA,EAAU,CAAE,GAAGc,EAAU,GAAGd,CAAQ,EAAIc,CACjD,CAuCA,eAAsB3B,EACpB4B,EACAC,EACAhB,EACe,CACf,MAAMiB,EAAWhC,EAA0Be,CAAO,EAC5C,CAAE,YAAAE,EAAa,QAAAC,EAAS,OAAAC,CAAO,EAAIa,EAGnCC,KAAS,cAAWH,EAAOb,CAAW,EAC5C,UAAWiB,KAASD,EAAQ,CAC1B,GAAId,GAAQ,QACV,OAIF,MAAM,QAAQ,IACZe,EAAM,IAAKC,GACT7B,EAAO,IAAIe,IAAoBU,EAAWV,EAAK,CAAC,CAAM,EAAG,CACvD,GAAGH,EACH,KAAM,CAACiB,CAAI,EACX,OAAAhB,CACF,CAAC,CACH,CACF,CACF,CACF,CA2CA,eAAsBf,EACpB0B,EACAC,EACAhB,EACc,CACd,MAAMiB,EAAWhC,EAA0Be,CAAO,EAClD,OACE,MAAMV,KACJ,cAAWyB,EAAOE,EAAS,WAAW,EACtCD,EACAC,EAAS,OACX,GACA,KAAK,CACT,CA0CA,eAAsB7B,EACpB2B,EACAC,EACAhB,EACe,CACf,KAAM,CAAE,UAAAqB,EAAY,IAAK,GAAGC,CAAU,EAAItB,GAAW,CAAC,EAChDkB,KAAS,cAAWH,EAAOM,CAAS,EACpCE,EAAsBrC,EAAsBoC,CAAS,EACrD,CAAE,OAAAlB,CAAO,EAAImB,EACnB,UAAWJ,KAASD,EAAQ,CAC1B,GAAId,GAAQ,QACV,OAGF,MAAMb,EAAO,IAAIe,IAAoBU,EAAWV,EAAK,CAAC,CAAQ,EAAG,CAC/D,GAAGiB,EACH,KAAM,CAACJ,CAAK,CACd,CAAC,CACH,CACF,CAoBA,eAAsB7B,EACpB4B,EACAF,EACAhB,EACgB,CAChB,MAAMsB,EAAYpC,EAAsBc,CAAO,EACzC,CAAE,OAAAI,CAAO,EAAIkB,EACb,CAAE,OAAAE,CAAO,EAAIN,EACbO,EAAiB,MAAMD,CAAM,EACnC,QAASE,EAAI,EAAGA,EAAIF,EAAQE,GAAK,EAE/B,GAAItB,GAAQ,QACVqB,EAAeC,CAAC,EAAI,CAAC,MAChB,CACL,MAAMP,EAAQD,EAAOQ,CAAC,EAEhBC,EAAmB,MAAM,QAAQ,IACrCR,EAAM,IAAIS,GACRrC,EAAO,IAAIe,IAAoBU,EAAWV,EAAK,CAAC,CAAM,EAAG,CACvD,GAAGgB,EACH,KAAM,CAACM,CAAK,CACd,CAAC,CACH,CACF,EACAH,EAAeC,CAAC,EAAIP,EAAM,OAAO,CAACU,EAAIH,IAAMC,EAAiBD,CAAC,CAAC,CACjE,CAEF,OAAOD,CACT,CAuFA,eAAsBlC,EACpByB,EACAhB,EACwB,CACxB,KAAM,CACJ,KAAAM,EACA,cAAAC,EACA,YAAAC,EACA,OAAAC,EACA,WAAAC,EACA,QAAAC,EACA,qBAAAC,EACA,eAAAC,EACA,QAAAV,EACA,OAAAC,CACF,EAAIlB,EAAsBc,CAAO,EACjC,GAAII,GAAQ,QACV,OAEF,GAAID,IAAY,EACd,OAAO,MAAMa,EAAW,GAAIV,GAAQ,CAAC,EAAI,CAAE,OAAAF,CAAO,CAAC,EAGrD,MAAM0B,EAAS/B,EAAU,EAEzB,IAAIgC,EAAW5B,EACX6B,EAAQxB,EACRyB,EAAiB,kBAErB,KAAOF,KAAc,GAAG,CAEtB,GAAI3B,GAAQ,QACV,OAGF,GAAI,CAEF,OAAO,MAAMY,EAAW,GAAIV,GAAQ,CAAC,EAAI,CAAE,OAAAF,CAAO,CAAC,CACrD,OAAS8B,EAAG,CAIV,GAHID,IAAU,oBACZA,EAAQC,GAENH,EAAW,EACb,MAEF,IAAII,EAAWH,EAOf,GANIvB,IAEF0B,GAAY,KAAK,MAAM,KAAK,OAAO,EAAIH,CAAK,GAG9CG,EAAW,KAAK,IAAIA,EAAUzB,CAAoB,EAC9C,OAAOC,GAAY,WACrB,GAAI,CACF,MAAMyB,EAASzB,EAASR,EAAqB4B,EAAUG,EAAGC,CAAQ,EAClE,GAAIC,IAAW,IAASxB,EACtB,MAGE,OAAOwB,GAAW,UAAYA,GAAU,IAC1CD,EAAW,KAAK,IAAIC,EAAQ1B,CAAoB,EAEpD,OAASwB,EAAG,CACV,GAAIrB,EACF,MAAMqB,CAEV,CAGF,GAAI,CAEF,MAAMJ,EAAO,WAAWK,EAAU,OAAW,CAAE,OAAA/B,CAAO,CAAC,CACzD,MAAQ,CAEN,MACF,CAGA,GAAIA,GAAQ,QACV,OAIF4B,EAAQ,KAAK,IAAIA,EAASzB,EAA0BG,CAAoB,CAC1E,CACF,CACA,GAAIuB,IAAU,kBACZ,MAAMA,CAGV",
6
6
  "names": ["promises_exports", "__export", "normalizeIterationOptions", "normalizeRetryOptions", "pEach", "pEachChunk", "pFilter", "pFilterChunk", "pRetry", "resolveRetryOptions", "__toCommonJS", "import_core", "import_process", "import_arrays", "abortSignal", "_timers", "getTimers", "options", "opts", "concurrency", "retries", "signal", "resolved", "args", "backoffFactor", "baseDelayMs", "jitter", "maxDelayMs", "onRetry", "onRetryCancelOnFalse", "onRetryRethrow", "defaults", "array", "callbackFn", "iterOpts", "chunks", "chunk", "item", "chunkSize", "retryOpts", "normalizedRetryOpts", "length", "filteredChunks", "i", "predicateResults", "value", "_v", "timers", "attempts", "delay", "error", "e", "waitTime", "result"]
7
7
  }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @fileoverview Themed interactive prompts for terminal input.
3
+ * Provides type definitions and utilities for themed prompt interactions.
4
+ *
5
+ * Note: This module provides the theme-aware API structure.
6
+ * Actual prompt implementations should be added based on project needs.
7
+ */
8
+ import type { Theme } from '../themes/types';
9
+ import type { ThemeName } from '../themes/themes';
10
+ /**
11
+ * Base options for all prompts.
12
+ */
13
+ export type PromptBaseOptions = {
14
+ /** Prompt message to display */
15
+ message: string;
16
+ /** Theme to use (overrides global) */
17
+ theme?: Theme | ThemeName | undefined;
18
+ };
19
+ /**
20
+ * Options for text input prompts.
21
+ */
22
+ export type InputPromptOptions = PromptBaseOptions & {
23
+ /** Default value */
24
+ default?: string | undefined;
25
+ /** Validation function */
26
+ validate?: ((value: string) => boolean | string) | undefined;
27
+ /** Placeholder text */
28
+ placeholder?: string | undefined;
29
+ };
30
+ /**
31
+ * Options for confirmation prompts.
32
+ */
33
+ export type ConfirmPromptOptions = PromptBaseOptions & {
34
+ /** Default value */
35
+ default?: boolean | undefined;
36
+ };
37
+ /**
38
+ * Choice option for select prompts.
39
+ */
40
+ export type Choice<T = string> = {
41
+ /** Display label */
42
+ label: string;
43
+ /** Value to return */
44
+ value: T;
45
+ /** Optional description */
46
+ description?: string | undefined;
47
+ /** Whether this choice is disabled */
48
+ disabled?: boolean | undefined;
49
+ };
50
+ /**
51
+ * Options for selection prompts.
52
+ */
53
+ export type SelectPromptOptions<T = string> = PromptBaseOptions & {
54
+ /** Array of choices */
55
+ choices: Array<Choice<T>>;
56
+ /** Default selected value */
57
+ default?: T | undefined;
58
+ };
59
+ /**
60
+ * Text input prompt (themed).
61
+ *
62
+ * @param options - Input prompt configuration
63
+ * @returns Promise resolving to user input
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * import { input } from '@socketsecurity/lib/prompts'
68
+ *
69
+ * const name = await input({
70
+ * message: 'Enter your name:',
71
+ * default: 'User',
72
+ * validate: (v) => v.length > 0 || 'Name required'
73
+ * })
74
+ * ```
75
+ */
76
+ export declare function input(_options: InputPromptOptions): Promise<string>;
77
+ /**
78
+ * Confirmation prompt (themed).
79
+ *
80
+ * @param options - Confirm prompt configuration
81
+ * @returns Promise resolving to boolean
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * import { confirm } from '@socketsecurity/lib/prompts'
86
+ *
87
+ * const proceed = await confirm({
88
+ * message: 'Continue with installation?',
89
+ * default: true
90
+ * })
91
+ * ```
92
+ */
93
+ export declare function confirm(_options: ConfirmPromptOptions): Promise<boolean>;
94
+ /**
95
+ * Selection prompt (themed).
96
+ *
97
+ * @template T - Type of choice values
98
+ * @param options - Select prompt configuration
99
+ * @returns Promise resolving to selected value
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * import { select } from '@socketsecurity/lib/prompts'
104
+ *
105
+ * const choice = await select({
106
+ * message: 'Select environment:',
107
+ * choices: [
108
+ * { label: 'Development', value: 'dev' },
109
+ * { label: 'Staging', value: 'staging' },
110
+ * { label: 'Production', value: 'prod' }
111
+ * ]
112
+ * })
113
+ * ```
114
+ */
115
+ export declare function select<T = string>(_options: SelectPromptOptions<T>): Promise<T>;
@@ -0,0 +1,3 @@
1
+ /* Socket Lib - Built with esbuild */
2
+ var r=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var s=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var a=(e,t)=>{for(var o in t)r(e,o,{get:t[o],enumerable:!0})},d=(e,t,o,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of s(t))!m.call(e,n)&&n!==o&&r(e,n,{get:()=>t[n],enumerable:!(i=p(t,n))||i.enumerable});return e};var l=e=>d(r({},"__esModule",{value:!0}),e);var y={};a(y,{confirm:()=>u,input:()=>f,select:()=>c});module.exports=l(y);async function f(e){throw new Error("input() not yet implemented - add prompt library integration")}async function u(e){throw new Error("confirm() not yet implemented - add prompt library integration")}async function c(e){throw new Error("select() not yet implemented - add prompt library integration")}0&&(module.exports={confirm,input,select});
3
+ //# sourceMappingURL=index.js.map