{"version":3,"sources":["../../src/drain/index.ts","../../src/drain/ActiveConnectionTracker.ts","../../src/state/ProcessHealth.ts","../../src/utils/time.ts","../../src/drain/DrainController.ts"],"sourcesContent":["export { ActiveConnectionTracker } from './ActiveConnectionTracker.js'\nexport { DrainController } from './DrainController.js'\nexport type { ActiveConnectionSnapshot } from './ActiveConnectionTracker.js'\nexport type { DrainControllerOptions, DrainTargetResult } from './DrainController.js'\n","import net from 'node:net'\nimport type { MeshConnectionCounters } from '../core/types.js'\n\nexport interface ActiveConnectionSnapshot {\n  readonly total: MeshConnectionCounters\n  readonly byInstance: Readonly<Record<string, MeshConnectionCounters>>\n}\n\nexport class ActiveConnectionTracker {\n  private readonly http = new Map<string, number>()\n  private readonly sockets = new Map<string, Set<net.Socket>>()\n  private readonly waiters = new Set<() => void>()\n\n  public beginHttp(instanceId: string): () => void {\n    this.http.set(instanceId, (this.http.get(instanceId) ?? 0) + 1)\n    let ended = false\n    return () => {\n      if (ended) return\n      ended = true\n      this.http.set(instanceId, Math.max(0, (this.http.get(instanceId) ?? 0) - 1))\n      this.notifyIfIdle()\n    }\n  }\n\n  public trackSocket(instanceId: string, ...tracked: net.Socket[]): () => void {\n    let set = this.sockets.get(instanceId)\n    if (!set) {\n      set = new Set()\n      this.sockets.set(instanceId, set)\n    }\n    for (const socket of tracked) set.add(socket)\n\n    let ended = false\n    const done = (): void => {\n      if (ended) return\n      ended = true\n      const current = this.sockets.get(instanceId)\n      if (current) {\n        for (const socket of tracked) current.delete(socket)\n        if (current.size === 0) this.sockets.delete(instanceId)\n      }\n      this.notifyIfIdle()\n    }\n\n    for (const socket of tracked) {\n      socket.once('close', done)\n      socket.once('error', done)\n    }\n\n    return done\n  }\n\n  public snapshot(): ActiveConnectionSnapshot {\n    const ids = new Set([...this.http.keys(), ...this.sockets.keys()])\n    const byInstance: Record<string, MeshConnectionCounters> = {}\n    let totalHttp = 0\n    let totalSockets = 0\n\n    for (const id of ids) {\n      const http = this.http.get(id) ?? 0\n      const sockets = this.sockets.get(id)?.size ?? 0\n      byInstance[id] = { http, sockets, total: http + sockets }\n      totalHttp += http\n      totalSockets += sockets\n    }\n\n    return {\n      total: { http: totalHttp, sockets: totalSockets, total: totalHttp + totalSockets },\n      byInstance\n    }\n  }\n\n  public isIdle(): boolean {\n    return this.snapshot().total.total === 0\n  }\n\n  public async waitForIdle(timeoutMs: number): Promise<boolean> {\n    if (this.isIdle()) return true\n    if (timeoutMs <= 0) return false\n\n    return await new Promise<boolean>(resolve => {\n      const timeout = setTimeout(() => {\n        this.waiters.delete(done)\n        resolve(false)\n      }, timeoutMs)\n      timeout.unref?.()\n\n      const done = (): void => {\n        clearTimeout(timeout)\n        this.waiters.delete(done)\n        resolve(true)\n      }\n\n      this.waiters.add(done)\n    })\n  }\n\n  public destroySockets(): void {\n    for (const set of this.sockets.values()) {\n      for (const socket of set) socket.destroy()\n    }\n    this.notifyIfIdle()\n  }\n\n  private notifyIfIdle(): void {\n    if (!this.isIdle()) return\n    for (const waiter of Array.from(this.waiters)) waiter()\n  }\n}\n","import type { MeshInstanceRecord } from '../core/types.js'\n\nexport class ProcessHealth {\n  public isAlive(pid: number | null): boolean {\n    if (pid === null) return false\n    try {\n      process.kill(pid, 0)\n      return true\n    } catch {\n      return false\n    }\n  }\n\n  public refresh(instance: MeshInstanceRecord): MeshInstanceRecord {\n    if (instance.status === 'stopped' || instance.status === 'failed') return instance\n    return this.isAlive(instance.pid)\n      ? { ...instance, status: 'running' }\n      : { ...instance, status: 'unknown' }\n  }\n}\n","export function nowIso(): string {\n  return new Date().toISOString()\n}\n\nexport function formatDurationSince(iso: string): string {\n  const started = new Date(iso).getTime()\n  const diff = Math.max(0, Date.now() - started)\n  const seconds = Math.floor(diff / 1000)\n  if (seconds < 60) return `${seconds}s`\n  const minutes = Math.floor(seconds / 60)\n  if (minutes < 60) return `${minutes}m`\n  const hours = Math.floor(minutes / 60)\n  if (hours < 24) return `${hours}h`\n  const days = Math.floor(hours / 24)\n  return `${days}d`\n}\n\nexport async function sleep(ms: number): Promise<void> {\n  if (ms <= 0) return\n  await new Promise<void>(resolve => setTimeout(resolve, ms))\n}\n","import type { MeshRegistry } from '../registry/types.js'\nimport type { MeshInstanceRecord } from '../core/types.js'\nimport { ProcessHealth } from '../state/ProcessHealth.js'\nimport { sleep } from '../utils/time.js'\n\nexport interface DrainTargetResult {\n  readonly instance: MeshInstanceRecord\n  readonly signalled: boolean\n  readonly killed: boolean\n  readonly unregistered: boolean\n}\n\nexport interface DrainControllerOptions {\n  readonly registry: MeshRegistry\n  readonly drainTimeoutMs: number\n  readonly shutdownTimeoutMs: number\n  readonly killTimeoutMs: number\n  readonly force?: boolean\n}\n\nexport class DrainController {\n  private readonly health = new ProcessHealth()\n\n  public constructor(private readonly options: DrainControllerOptions) {}\n\n  public async drainAndStop(instances: readonly MeshInstanceRecord[]): Promise<readonly DrainTargetResult[]> {\n    for (const instance of instances) {\n      if (!this.options.force) await this.options.registry.markDraining(instance.id).catch(() => undefined)\n    }\n\n    if (!this.options.force && this.options.drainTimeoutMs > 0) {\n      await sleep(this.options.drainTimeoutMs)\n    }\n\n    const results: DrainTargetResult[] = []\n    await Promise.all(instances.map(async instance => {\n      const signalled = this.signal(instance, 'SIGTERM')\n      if (signalled) await this.waitUntilDead(instance.pid, this.options.shutdownTimeoutMs)\n      const killed = this.health.isAlive(instance.pid) ? this.signal(instance, 'SIGKILL') : false\n      if (killed) await this.waitUntilDead(instance.pid, this.options.killTimeoutMs)\n      await this.options.registry.unregister(instance.id).catch(() => undefined)\n      results.push({ instance, signalled, killed, unregistered: true })\n    }))\n\n    return results.sort((a, b) => a.instance.id.localeCompare(b.instance.id))\n  }\n\n  private signal(instance: MeshInstanceRecord, signal: NodeJS.Signals): boolean {\n    if (!this.health.isAlive(instance.pid)) return false\n    try {\n      process.kill(instance.pid!, signal)\n      return true\n    } catch {\n      return false\n    }\n  }\n\n  private async waitUntilDead(pid: number | null, timeoutMs: number): Promise<void> {\n    const started = Date.now()\n    while (this.health.isAlive(pid) && Date.now() - started < timeoutMs) {\n      await sleep(100)\n    }\n  }\n}\n"],"mappings":"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","names":[]}