UNPKG

apitally

Version:

Simple API monitoring & analytics for REST APIs built with Express, Fastify, NestJS, AdonisJS, Hono, H3, Elysia, Hapi, and Koa.

1 lines 7.59 kB
{"version":3,"sources":["../../src/common/instance.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport {\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst TEMP_DIR = join(tmpdir(), \"apitally\");\nconst MAX_SLOTS = 100;\nconst MAX_LOCK_AGE_MS = 24 * 60 * 60 * 1000;\nconst UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport function getOrCreateInstanceUuid(clientId: string, env: string): string {\n try {\n mkdirSync(TEMP_DIR, { recursive: true });\n } catch {\n return randomUUID();\n }\n\n const hash = getAppEnvHash(clientId, env);\n validateLockFiles(hash);\n\n for (let slot = 0; slot < MAX_SLOTS; slot++) {\n const pidFile = join(TEMP_DIR, `instance_${hash}_${slot}.pid`);\n const uuidFile = join(TEMP_DIR, `instance_${hash}_${slot}.uuid`);\n\n // Try atomic exclusive create of PID file\n try {\n writeFileSync(pidFile, String(process.pid), { flag: \"wx\" });\n return getOrCreateUuid(uuidFile);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") {\n continue;\n }\n }\n\n // PID file exists - check if it's ours (hot reload)\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n if (pid === process.pid) {\n return readFileSync(uuidFile, \"utf-8\").trim();\n }\n } catch {\n // Ignore read error\n }\n }\n\n return randomUUID();\n}\n\nfunction getAppEnvHash(clientId: string, env: string): string {\n return createHash(\"sha256\")\n .update(`${clientId}:${env}`)\n .digest(\"hex\")\n .slice(0, 8);\n}\n\nfunction getOrCreateUuid(uuidFile: string): string {\n try {\n const existingUuid = readFileSync(uuidFile, \"utf-8\").trim();\n if (validateUuid(existingUuid)) {\n return existingUuid;\n }\n } catch {\n // File doesn't exist or read error\n }\n\n const newUuid = randomUUID();\n writeFileSync(uuidFile, newUuid);\n return newUuid;\n}\n\nfunction validateUuid(value: string): boolean {\n return UUID_REGEX.test(value);\n}\n\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction deleteFiles(...paths: string[]) {\n for (const path of paths) {\n try {\n unlinkSync(path);\n } catch {\n // Ignore errors\n }\n }\n}\n\nexport function validateLockFiles(appEnvHash: string) {\n let files: string[];\n try {\n files = readdirSync(TEMP_DIR);\n } catch {\n return;\n }\n\n const prefix = `instance_${appEnvHash}_`;\n const uuidFiles = files\n .filter((f) => f.startsWith(prefix) && f.endsWith(\".uuid\"))\n .sort();\n const pidFiles = files\n .filter((f) => f.startsWith(prefix) && f.endsWith(\".pid\"))\n .sort();\n const seenUuids = new Set<string>();\n const now = Date.now();\n\n // Clean up UUID files\n for (const uuidFileName of uuidFiles) {\n const uuidFile = join(TEMP_DIR, uuidFileName);\n const pidFile = join(TEMP_DIR, uuidFileName.replace(\".uuid\", \".pid\"));\n\n try {\n const stat = statSync(uuidFile);\n\n // Delete if older than 24 hours\n if (now - stat.mtimeMs > MAX_LOCK_AGE_MS) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n\n // Delete if UUID is invalid\n const uuid = readFileSync(uuidFile, \"utf-8\").trim();\n if (!validateUuid(uuid)) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n\n // Delete if UUID is a duplicate\n if (seenUuids.has(uuid)) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n seenUuids.add(uuid);\n } catch {\n // Ignore stat or read error\n }\n }\n\n // Clean up PID files from dead processes\n for (const pidFileName of pidFiles) {\n const pidFile = join(TEMP_DIR, pidFileName);\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n if (!isPidAlive(pid)) {\n deleteFiles(pidFile);\n }\n } catch {\n // Ignore read error\n }\n }\n}\n"],"mappings":"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","names":["TEMP_DIR","join","tmpdir","MAX_SLOTS","MAX_LOCK_AGE_MS","UUID_REGEX","getOrCreateInstanceUuid","clientId","env","mkdirSync","recursive","randomUUID","hash","getAppEnvHash","validateLockFiles","slot","pidFile","uuidFile","writeFileSync","String","process","pid","flag","getOrCreateUuid","err","code","parseInt","readFileSync","trim","createHash","update","digest","slice","existingUuid","validateUuid","newUuid","value","test","isPidAlive","kill","deleteFiles","paths","path","unlinkSync","appEnvHash","files","readdirSync","prefix","uuidFiles","filter","f","startsWith","endsWith","sort","pidFiles","seenUuids","Set","now","Date","uuidFileName","replace","stat","statSync","mtimeMs","uuid","has","add","pidFileName"]}