UNPKG

@intlayer/chokidar

Version:

Scans and builds Intlayer declaration files into dictionaries based on Intlayer configuration.

1 lines 6.43 kB
{"version":3,"file":"runOnce.cjs","names":["packageJson"],"sources":["../../../src/utils/runOnce.ts"],"sourcesContent":["import { mkdir, readFile, stat, unlink, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport packageJson from '@intlayer/core/package.json' with { type: 'json' };\n\ntype RunOnceOptions = {\n /**\n * The function to execute when the sentinel is not found or is older than the cache timeout.\n */\n onIsCached?: () => void | Promise<void>;\n /**\n * The time window in milliseconds during which the sentinel is considered valid.\n *\n * @default 60000 = 1 minute\n */\n cacheTimeoutMs?: number;\n /**\n * If true, the callback will always run. If undefined, the callback will run only if the sentinel is older than the cache timeout.\n *\n * @default false\n */\n forceRun?: boolean;\n};\n\nconst DEFAULT_RUN_ONCE_OPTIONS = {\n cacheTimeoutMs: 60 * 1000, // 1 minute in milliseconds,\n} satisfies RunOnceOptions;\n\ntype SentinelData = {\n version: string;\n timestamp: number;\n};\n\nconst writeSentinelFile = async (\n sentinelFilePath: string,\n currentTimestamp: number\n) => {\n // O_EXCL ensures only the *first* process can create the file\n const data: SentinelData = {\n version: packageJson.version,\n timestamp: currentTimestamp,\n };\n\n try {\n // Ensure the directory exists before writing the file\n await mkdir(dirname(sentinelFilePath), { recursive: true });\n\n await writeFile(sentinelFilePath, JSON.stringify(data), { flag: 'wx' });\n } catch (err: any) {\n if (err.code === 'EEXIST') {\n // Another process already created it → we're done\n return;\n }\n // Optimization: If ENOENT occurs on write despite mkdir (race condition with external deletion), retry once.\n if (err.code === 'ENOENT') {\n try {\n await mkdir(dirname(sentinelFilePath), { recursive: true });\n await writeFile(sentinelFilePath, JSON.stringify(data), { flag: 'wx' });\n return;\n } catch (retryErr: any) {\n if (retryErr.code === 'EEXIST') return;\n }\n }\n throw err; // unexpected FS error\n }\n};\n\n/**\n * Ensures a callback function runs only once within a specified time window across multiple processes.\n * Uses a sentinel file to coordinate execution and prevent duplicate work.\n *\n * @param sentinelFilePath - Path to the sentinel file used for coordination\n * @param callback - The function to execute (should be async)\n * @param options - The options for the runOnce function\n *\n * @example\n * ```typescript\n * await runPrepareIntlayerOnce(\n * '/tmp/intlayer-sentinel',\n * async () => {\n * // Your initialization logic here\n * await prepareIntlayer();\n * },\n * 30 * 1000 // 30 seconds cache\n * );\n * ```\n *\n * @throws {Error} When there are unexpected filesystem errors\n */\nexport const runOnce = async (\n sentinelFilePath: string,\n callback: () => void | Promise<void>,\n options?: RunOnceOptions\n) => {\n const { onIsCached, cacheTimeoutMs, forceRun } = {\n ...DEFAULT_RUN_ONCE_OPTIONS,\n ...(options ?? {}),\n };\n const currentTimestamp = Date.now();\n\n try {\n // Check if sentinel file exists and get its stats\n const sentinelStats = await stat(sentinelFilePath);\n const sentinelAge = currentTimestamp - sentinelStats.mtime.getTime();\n\n // Determine if we should rebuild based on cache age, force flag, or version mismatch\n let shouldRebuild = Boolean(forceRun) || sentinelAge > cacheTimeoutMs!;\n\n if (!shouldRebuild) {\n try {\n const raw = await readFile(sentinelFilePath, 'utf8');\n let cachedVersion: string | undefined;\n try {\n const parsed = JSON.parse(raw) as Partial<SentinelData>;\n cachedVersion = parsed.version;\n } catch {\n // Legacy format (timestamp only). Force a rebuild once to write versioned sentinel.\n cachedVersion = undefined;\n }\n\n if (!cachedVersion || cachedVersion !== packageJson.version) {\n shouldRebuild = true;\n }\n } catch {\n // If we cannot read the file, err on the safe side and rebuild\n shouldRebuild = true;\n }\n }\n\n if (shouldRebuild) {\n try {\n await unlink(sentinelFilePath);\n } catch {}\n // Fall through to create new sentinel and rebuild\n } else {\n await onIsCached?.();\n // Sentinel is recent and versions match, no need to rebuild\n return;\n }\n } catch (err: any) {\n if (err.code === 'ENOENT') {\n // File doesn't exist, continue to create it\n } else {\n throw err; // unexpected FS error\n }\n }\n\n // Write sentinel file before to block parallel processes\n // Added await here\n await writeSentinelFile(sentinelFilePath, currentTimestamp);\n\n try {\n await callback();\n\n // Write sentinel file after to ensure the first one has not been removed with cleanOutputDir\n // Added await here\n await writeSentinelFile(sentinelFilePath, currentTimestamp);\n } catch {\n try {\n await unlink(sentinelFilePath); // Remove sentinel file if an error occurs\n } catch {}\n }\n};\n"],"mappings":";;;;;;;;AAuBA,MAAM,2BAA2B,EAC/B,gBAAgB,KAAK,KACtB;AAOD,MAAM,oBAAoB,OACxB,kBACA,qBACG;CAEH,MAAM,OAAqB;EACzB,SAASA,oCAAY;EACrB,WAAW;EACZ;AAED,KAAI;AAEF,2DAAoB,iBAAiB,EAAE,EAAE,WAAW,MAAM,CAAC;AAE3D,wCAAgB,kBAAkB,KAAK,UAAU,KAAK,EAAE,EAAE,MAAM,MAAM,CAAC;UAChE,KAAU;AACjB,MAAI,IAAI,SAAS,SAEf;AAGF,MAAI,IAAI,SAAS,SACf,KAAI;AACF,4DAAoB,iBAAiB,EAAE,EAAE,WAAW,MAAM,CAAC;AAC3D,yCAAgB,kBAAkB,KAAK,UAAU,KAAK,EAAE,EAAE,MAAM,MAAM,CAAC;AACvE;WACO,UAAe;AACtB,OAAI,SAAS,SAAS,SAAU;;AAGpC,QAAM;;;;;;;;;;;;;;;;;;;;;;;;;AA0BV,MAAa,UAAU,OACrB,kBACA,UACA,YACG;CACH,MAAM,EAAE,YAAY,gBAAgB,aAAa;EAC/C,GAAG;EACH,GAAI,WAAW,EAAE;EAClB;CACD,MAAM,mBAAmB,KAAK,KAAK;AAEnC,KAAI;EAGF,MAAM,cAAc,oBAAmB,iCADN,iBAAiB,EACG,MAAM,SAAS;EAGpE,IAAI,gBAAgB,QAAQ,SAAS,IAAI,cAAc;AAEvD,MAAI,CAAC,cACH,KAAI;GACF,MAAM,MAAM,qCAAe,kBAAkB,OAAO;GACpD,IAAI;AACJ,OAAI;AAEF,oBADe,KAAK,MAAM,IACJ,CAAC;WACjB;AAEN,oBAAgB;;AAGlB,OAAI,CAAC,iBAAiB,kBAAkBA,oCAAY,QAClD,iBAAgB;UAEZ;AAEN,mBAAgB;;AAIpB,MAAI,cACF,KAAI;AACF,sCAAa,iBAAiB;UACxB;OAEH;AACL,SAAM,cAAc;AAEpB;;UAEK,KAAU;AACjB,MAAI,IAAI,SAAS,UAAU,OAGzB,OAAM;;AAMV,OAAM,kBAAkB,kBAAkB,iBAAiB;AAE3D,KAAI;AACF,QAAM,UAAU;AAIhB,QAAM,kBAAkB,kBAAkB,iBAAiB;SACrD;AACN,MAAI;AACF,sCAAa,iBAAiB;UACxB"}