{"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,IACvB;AAOA,MAAM,oBAAoB,OACxB,kBACA,qBACG;CAEH,MAAM,OAAqB;EACzB,SAASA,oCAAY;EACrB,WAAW;CACb;CAEA,IAAI;EAEF,yDAAoB,gBAAgB,GAAG,EAAE,WAAW,KAAK,CAAC;EAE1D,sCAAgB,kBAAkB,KAAK,UAAU,IAAI,GAAG,EAAE,MAAM,KAAK,CAAC;CACxE,SAAS,KAAU;EACjB,IAAI,IAAI,SAAS,UAEf;EAGF,IAAI,IAAI,SAAS,UACf,IAAI;GACF,yDAAoB,gBAAgB,GAAG,EAAE,WAAW,KAAK,CAAC;GAC1D,sCAAgB,kBAAkB,KAAK,UAAU,IAAI,GAAG,EAAE,MAAM,KAAK,CAAC;GACtE;EACF,SAAS,UAAe;GACtB,IAAI,SAAS,SAAS,UAAU;EAClC;EAEF,MAAM;CACR;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,UAAU,OACrB,kBACA,UACA,YACG;CACH,MAAM,EAAE,YAAY,gBAAgB,aAAa;EAC/C,GAAG;EACH,GAAI,WAAW,CAAC;CAClB;CACA,MAAM,mBAAmB,KAAK,IAAI;CAElC,IAAI;EAGF,MAAM,cAAc,oBAAmB,iCADN,gBAAgB,GACI,MAAM,QAAQ;EAGnE,IAAI,gBAAgB,QAAQ,QAAQ,KAAK,cAAc;EAEvD,IAAI,CAAC,eACH,IAAI;GACF,MAAM,MAAM,qCAAe,kBAAkB,MAAM;GACnD,IAAI;GACJ,IAAI;IAEF,gBADe,KAAK,MAAM,GACL,EAAE;GACzB,QAAQ;IAEN,gBAAgB;GAClB;GAEA,IAAI,CAAC,iBAAiB,kBAAkBA,oCAAY,SAClD,gBAAgB;EAEpB,QAAQ;GAEN,gBAAgB;EAClB;EAGF,IAAI,eACF,IAAI;GACF,mCAAa,gBAAgB;EAC/B,QAAQ,CAAC;OAEJ;GACL,MAAM,aAAa;GAEnB;EACF;CACF,SAAS,KAAU;EACjB,IAAI,IAAI,SAAS,UAAU,CAE3B,OACE,MAAM;CAEV;CAIA,MAAM,kBAAkB,kBAAkB,gBAAgB;CAE1D,IAAI;EACF,MAAM,SAAS;EAIf,MAAM,kBAAkB,kBAAkB,gBAAgB;CAC5D,QAAQ;EACN,IAAI;GACF,mCAAa,gBAAgB;EAC/B,QAAQ,CAAC;CACX;AACF"}