// src/health/effects.ts
import { Effect } from "effect";

// src/health/types.ts
var HealthError = class extends Error {
  _tag = "HealthError";
  cause;
  constructor(message, cause) {
    super(message);
    this.name = "HealthError";
    this.cause = cause;
  }
};

// src/health/effects.ts
function evaluateEffect(evaluator) {
  return Effect.tryPromise({
    try: () => evaluator(),
    catch: (err) => new HealthError("health evaluation failed", err)
  });
}

// src/health/handler.ts
function createWebHandler(source) {
  return async (_req) => {
    try {
      const score = await source.evaluate();
      return new Response(JSON.stringify(score), {
        status: 200,
        headers: {
          "Content-Type": "application/json",
          // Cache-Control: never cache. Even 1-second cache on a
          // CDN edge would mask a fast-moving health collapse.
          "Cache-Control": "no-store, no-cache, must-revalidate"
        }
      });
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      return new Response(
        JSON.stringify({
          error: "health_evaluator_failed",
          message
        }),
        {
          status: 500,
          headers: {
            "Content-Type": "application/json",
            "Cache-Control": "no-store, no-cache, must-revalidate"
          }
        }
      );
    }
  };
}
function createNodeHandler(source) {
  return async (_req, res) => {
    try {
      const score = await source.evaluate();
      res.statusCode = 200;
      res.setHeader("Content-Type", "application/json");
      res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
      res.end(JSON.stringify(score));
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      res.statusCode = 500;
      res.setHeader("Content-Type", "application/json");
      res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
      res.end(JSON.stringify({ error: "health_evaluator_failed", message }));
    }
  };
}

// src/health/scoring.ts
function clamp01(x) {
  if (!Number.isFinite(x)) return 0;
  if (x < 0) return 0;
  if (x > 1) return 1;
  return x;
}
var weightedProduct = (readings) => {
  if (readings.length === 0) return 1;
  const active = [];
  for (const { signal, reading } of readings) {
    const w = signal.weight;
    if (!Number.isFinite(w) || w <= 0) continue;
    if (reading.unknown === true) continue;
    active.push({
      factor: clamp01(reading.healthFactor),
      weight: w
    });
  }
  if (active.length === 0) return 1;
  const totalWeight = active.reduce((sum, a) => sum + a.weight, 0);
  if (totalWeight <= 0 || !Number.isFinite(totalWeight)) return 1;
  let logSum = 0;
  for (const { factor, weight } of active) {
    const normalisedWeight = weight / totalWeight;
    if (factor <= 0) {
      return 0;
    }
    logSum += normalisedWeight * Math.log(factor);
  }
  const score = Math.exp(logSum);
  return clamp01(score);
};
function defaultScoringStrategy() {
  return weightedProduct;
}

// src/health/signals/event-loop-lag.ts
import { monitorEventLoopDelay } from "perf_hooks";
function eventLoopLagSignal(opts = {}) {
  const degradedMs = opts.degradedMs ?? 5e3;
  const deadMs = opts.deadMs ?? 3e4;
  const resolutionMs = opts.resolutionMs ?? 10;
  const weight = opts.weight ?? 0.4;
  if (!Number.isFinite(degradedMs) || degradedMs < 0) {
    throw new Error(`eventLoopLagSignal: degradedMs must be >= 0, got ${degradedMs}`);
  }
  if (!Number.isFinite(deadMs) || deadMs <= degradedMs) {
    throw new Error(
      `eventLoopLagSignal: deadMs must be > degradedMs (${degradedMs}), got ${deadMs}`
    );
  }
  if (!Number.isFinite(resolutionMs) || resolutionMs < 1) {
    throw new Error(`eventLoopLagSignal: resolutionMs must be >= 1, got ${resolutionMs}`);
  }
  const monitor = opts.monitor ?? monitorEventLoopDelay({ resolution: resolutionMs });
  monitor.enable();
  return {
    name: "eventLoopLagMs",
    weight,
    read() {
      const maxNs = monitor.max;
      if (!Number.isFinite(maxNs) || maxNs <= 0 || maxNs >= Number.MAX_SAFE_INTEGER) {
        monitor.reset();
        return { value: 0, healthFactor: 1, unknown: true };
      }
      const observedMs = maxNs / 1e6;
      monitor.reset();
      let factor;
      if (observedMs <= degradedMs) factor = 1;
      else if (observedMs >= deadMs) factor = 0;
      else {
        const span = deadMs - degradedMs;
        factor = 1 - (observedMs - degradedMs) / span;
      }
      return {
        value: Math.round(observedMs),
        healthFactor: factor
      };
    },
    dispose() {
      monitor.disable();
    }
  };
}

// src/health/unix-socket-server.ts
import { unlinkSync } from "fs";
function startUnixSocketServer(source, opts = {}) {
  const bun = opts.bunRuntime !== void 0 ? opts.bunRuntime : globalThis.Bun;
  if (bun === null || bun === void 0 || typeof bun.serve !== "function") {
    throw new Error(
      "startUnixSocketServer: Bun.serve is unavailable \u2014 Unix-socket transport requires a Bun runtime"
    );
  }
  const path = opts.path ?? "/var/run/sylphx/health.sock";
  const unlinkOnShutdown = opts.unlinkOnShutdown ?? true;
  try {
    unlinkSync(path);
  } catch {
  }
  const handler = createWebHandler(source);
  const server = bun.serve({
    unix: path,
    fetch: async (req) => handler(req)
  });
  const handle = {
    path,
    server,
    async shutdown() {
      try {
        server.stop(true);
      } catch {
      }
      if (unlinkOnShutdown) {
        try {
          unlinkSync(path);
        } catch {
        }
      }
    }
  };
  return handle;
}

// src/health/signals/error-rate.ts
function parseWindow(w) {
  if (typeof w === "number") {
    if (!Number.isFinite(w) || w <= 0) {
      throw new Error(`errorRateSignal: window must be > 0, got ${w}`);
    }
    return w;
  }
  const m = /^(\d+)(s|m)$/.exec(w);
  if (m === null) {
    throw new Error(`errorRateSignal: invalid window '${w}', expected '5s' / '1m' / number`);
  }
  const n = Number.parseInt(m[1], 10);
  const unit = m[2];
  const ms = unit === "s" ? n * 1e3 : n * 6e4;
  if (ms <= 0) {
    throw new Error(`errorRateSignal: parsed window ${ms}ms must be > 0`);
  }
  return ms;
}
function errorRateSignal(opts) {
  const windowMs = parseWindow(opts.window);
  const degradedRate = opts.degradedRate ?? 0.05;
  const deadRate = opts.deadRate ?? 0.5;
  const minSamples = opts.minSamples ?? 10;
  const weight = opts.weight ?? 0.2;
  const now = opts.now ?? Date.now;
  if (degradedRate < 0 || degradedRate > 1) {
    throw new Error(`errorRateSignal: degradedRate must be in [0, 1], got ${degradedRate}`);
  }
  if (deadRate <= degradedRate || deadRate > 1) {
    throw new Error(`errorRateSignal: deadRate must be in (degradedRate, 1], got ${deadRate}`);
  }
  if (!Number.isFinite(minSamples) || minSamples < 1) {
    throw new Error(`errorRateSignal: minSamples must be >= 1, got ${minSamples}`);
  }
  const samples = [];
  function pruneExpired(t) {
    const cutoff = t - windowMs;
    while (samples.length > 0 && samples[0].t < cutoff) {
      samples.shift();
    }
  }
  return {
    name: `recent${Math.round(windowMs / 1e3)}sErrorRate`,
    weight,
    recordSuccess() {
      samples.push({ t: now(), e: false });
    },
    recordError() {
      samples.push({ t: now(), e: true });
    },
    reset() {
      samples.length = 0;
    },
    read() {
      const t = now();
      pruneExpired(t);
      if (samples.length === 0 || samples.length < minSamples) {
        return { value: 0, healthFactor: 1 };
      }
      let errors = 0;
      for (const s of samples) {
        if (s.e) errors++;
      }
      const rate = errors / samples.length;
      let factor;
      if (rate <= degradedRate) factor = 1;
      else if (rate >= deadRate) factor = 0;
      else {
        const span = deadRate - degradedRate;
        factor = 1 - (rate - degradedRate) / span;
      }
      const valueRounded = Math.round(rate * 1e4) / 1e4;
      return {
        value: valueRounded,
        healthFactor: factor
      };
    }
  };
}

// src/health/signals/memory-pressure.ts
import { readFileSync } from "fs";
var CGROUP_V1_UNLIMITED_FLOOR = 1e18;
function tryRead(reader, path) {
  try {
    return reader(path);
  } catch {
    return null;
  }
}
function readContainerMemoryLimit(reader, v2Path, v1Path) {
  const v2 = tryRead(reader, v2Path);
  if (v2 !== null) {
    const trimmed2 = v2.trim();
    if (trimmed2 === "max" || trimmed2 === "") return null;
    const n2 = Number.parseInt(trimmed2, 10);
    if (!Number.isFinite(n2) || n2 <= 0) return null;
    if (n2 > CGROUP_V1_UNLIMITED_FLOOR) return null;
    return n2;
  }
  const v1 = tryRead(reader, v1Path);
  if (v1 === null) return null;
  const trimmed = v1.trim();
  const n = Number.parseInt(trimmed, 10);
  if (!Number.isFinite(n) || n <= 0) return null;
  if (n > CGROUP_V1_UNLIMITED_FLOOR) return null;
  return n;
}
function memoryPressureSignal(opts = {}) {
  const degradedRatio = opts.degradedRatio ?? 0.85;
  const deadRatio = opts.deadRatio ?? 0.95;
  const v2Path = opts.cgroupV2Path ?? "/sys/fs/cgroup/memory.max";
  const v1Path = opts.cgroupV1Path ?? "/sys/fs/cgroup/memory/memory.limit_in_bytes";
  const weight = opts.weight ?? 0.2;
  const memoryUsage = opts.memoryUsage ?? (() => {
    const m = process.memoryUsage();
    return { rss: m.rss };
  });
  const readFile = opts.readFile ?? ((p) => readFileSync(p, "utf8"));
  if (degradedRatio <= 0 || degradedRatio >= 1) {
    throw new Error(`memoryPressureSignal: degradedRatio must be in (0, 1), got ${degradedRatio}`);
  }
  if (deadRatio <= degradedRatio || deadRatio > 1) {
    throw new Error(
      `memoryPressureSignal: deadRatio must be in (degradedRatio, 1], got ${deadRatio}`
    );
  }
  let cachedLimit;
  function getLimit() {
    if (cachedLimit === void 0) {
      cachedLimit = readContainerMemoryLimit(readFile, v2Path, v1Path);
    }
    return cachedLimit;
  }
  return {
    name: "memoryPressure",
    weight,
    read() {
      const limit = getLimit();
      if (limit === null) {
        return { value: 0, healthFactor: 1, unknown: true };
      }
      let rss;
      try {
        rss = memoryUsage().rss;
      } catch {
        return { value: 0, healthFactor: 1, unknown: true };
      }
      if (!Number.isFinite(rss) || rss < 0) {
        return { value: 0, healthFactor: 1, unknown: true };
      }
      const ratio = rss / limit;
      let factor;
      if (ratio <= degradedRatio) factor = 1;
      else if (ratio >= deadRatio) factor = 0;
      else {
        const span = deadRatio - degradedRatio;
        factor = 1 - (ratio - degradedRatio) / span;
      }
      const valueRounded = Math.round(ratio * 1e3) / 1e3;
      return { value: valueRounded, healthFactor: factor };
    }
  };
}

// src/health/signals/queue-depth.ts
function queueDepthSignal(opts) {
  if (typeof opts.getter !== "function") {
    throw new Error("queueDepthSignal: getter must be a function");
  }
  if (!Number.isFinite(opts.fullThreshold) || opts.fullThreshold <= 0) {
    throw new Error(`queueDepthSignal: fullThreshold must be > 0, got ${opts.fullThreshold}`);
  }
  const healthyBelow = opts.healthyBelow ?? 0;
  if (!Number.isFinite(healthyBelow) || healthyBelow < 0 || healthyBelow >= opts.fullThreshold) {
    throw new Error(
      `queueDepthSignal: healthyBelow must be in [0, fullThreshold), got ${healthyBelow}`
    );
  }
  const weight = opts.weight ?? 0.2;
  return {
    name: opts.name ?? "queueDepth",
    weight,
    async read() {
      let raw;
      try {
        raw = await opts.getter();
      } catch {
        return { value: 0, healthFactor: 1, unknown: true };
      }
      if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) {
        return { value: 0, healthFactor: 1, unknown: true };
      }
      const depth = raw;
      let factor;
      if (depth <= healthyBelow) factor = 1;
      else if (depth >= opts.fullThreshold) factor = 0;
      else {
        const span = opts.fullThreshold - healthyBelow;
        factor = 1 - (depth - healthyBelow) / span;
      }
      return {
        value: depth,
        healthFactor: factor
      };
    }
  };
}

// src/health/index.ts
function sylphxHealth(opts = {}) {
  const signals = opts.signals && opts.signals.length > 0 ? [...opts.signals] : [eventLoopLagSignal({ degradedMs: 5e3, deadMs: 3e4, weight: 1 })];
  const scoringStrategy = opts.scoringStrategy ?? defaultScoringStrategy();
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
  let disposed = false;
  const evaluate = async () => {
    if (disposed) {
      throw new Error("sylphxHealth: instance disposed");
    }
    const readings = await Promise.all(
      signals.map(async (signal) => ({
        signal,
        reading: await signal.read()
      }))
    );
    const score = scoringStrategy(readings);
    const signalsMap = {};
    for (const { signal, reading } of readings) {
      signalsMap[signal.name] = reading.value;
    }
    return {
      score,
      signals: signalsMap,
      lastTickAt: now().toISOString()
    };
  };
  const evaluator = { evaluate };
  const evaluateEffect2 = evaluateEffect(evaluate);
  return {
    signals,
    evaluate,
    evaluateEffect: evaluateEffect2,
    handler() {
      return createWebHandler(evaluator);
    },
    nodeHandler() {
      return createNodeHandler(evaluator);
    },
    serveUnixSocket(unixOpts) {
      return startUnixSocketServer(evaluator, unixOpts);
    },
    dispose() {
      if (disposed) return;
      disposed = true;
      for (const s of signals) {
        try {
          s.dispose?.();
        } catch {
        }
      }
    }
  };
}
export {
  HealthError,
  createNodeHandler,
  createWebHandler,
  defaultScoringStrategy,
  errorRateSignal,
  eventLoopLagSignal,
  memoryPressureSignal,
  queueDepthSignal,
  sylphxHealth,
  weightedProduct
};
//# sourceMappingURL=index.mjs.map