{"version":3,"file":"preflight.cjs","names":["Effect"],"sources":["../../src/infra/preflight.ts"],"sourcesContent":["import { createConnection } from \"node:net\";\nimport { Effect } from \"effect\";\n\nexport interface PreflightTarget {\n  secret: string;\n  host: string;\n  port: number;\n  kind: \"postgres\" | \"redis\";\n  url: string;\n}\n\nexport interface PreflightFailure {\n  secret: string;\n  host: string;\n  port: number;\n  error: string;\n}\n\nfunction parseLocalUrl(url: string): { host: string; port: number } | null {\n  try {\n    const match = url.match(/:\\/\\/([^:/]+):(\\d+)/);\n    if (!match) return null;\n    const host = match[1];\n    const port = Number.parseInt(match[2], 10);\n    if (Number.isNaN(port)) return null;\n    if (host !== \"localhost\" && host !== \"127.0.0.1\") return null;\n    return { host, port };\n  } catch {\n    return null;\n  }\n}\n\nfunction checkTcpReachable(host: string, port: number, timeoutMs = 2000): Effect.Effect<boolean> {\n  return Effect.async<boolean>((resume) => {\n    const socket = createConnection({ host, port });\n    const timer = setTimeout(() => {\n      socket.destroy();\n      resume(Effect.succeed(false));\n    }, timeoutMs);\n\n    socket.once(\"connect\", () => {\n      clearTimeout(timer);\n      socket.destroy();\n      resume(Effect.succeed(true));\n    });\n\n    socket.once(\"error\", () => {\n      clearTimeout(timer);\n      socket.destroy();\n      resume(Effect.succeed(false));\n    });\n  });\n}\n\nfunction checkPgConnection(url: string): Effect.Effect<boolean> {\n  return Effect.gen(function* () {\n    const reachable = yield* checkTcpReachable(\n      new URL(url).hostname,\n      Number(new URL(url).port) || 5432,\n      4000,\n    );\n    if (!reachable) return false;\n\n    try {\n      const { Pool } = yield* Effect.promise(() => import(\"pg\"));\n      const pool = new Pool({ connectionString: url, connectionTimeoutMillis: 3000 });\n      const client = yield* Effect.promise(() => pool.connect().then((c) => ({ client: c, pool })));\n      try {\n        yield* Effect.promise(() => client.client.query(\"SELECT 1\"));\n        return true;\n      } finally {\n        client.client.release();\n        yield* Effect.promise(() => client.pool.end().catch(() => {}));\n      }\n    } catch {\n      return false;\n    }\n  });\n}\n\nfunction preflightTargetsFromEnv(env: Record<string, string>): PreflightTarget[] {\n  const targets: PreflightTarget[] = [];\n  for (const [secret, value] of Object.entries(env)) {\n    if (!value) continue;\n    if (secret.endsWith(\"_DATABASE_URL\")) {\n      const parsed = parseLocalUrl(value);\n      if (parsed) {\n        targets.push({\n          secret,\n          host: parsed.host,\n          port: parsed.port,\n          kind: \"postgres\",\n          url: value,\n        });\n      }\n    } else if (secret.endsWith(\"_REDIS_URL\")) {\n      const parsed = parseLocalUrl(value);\n      if (parsed) {\n        targets.push({ secret, host: parsed.host, port: parsed.port, kind: \"redis\", url: value });\n      }\n    }\n  }\n  return targets;\n}\n\nexport function preflightLocalInfra(\n  env: Record<string, string>,\n  overrides?: Record<string, string>,\n): Effect.Effect<PreflightFailure[], never> {\n  const merged = overrides ? { ...env, ...overrides } : env;\n  const targets = preflightTargetsFromEnv(merged);\n  if (targets.length === 0) return Effect.succeed([]);\n\n  return Effect.forEach(\n    targets,\n    (target) =>\n      Effect.gen(function* () {\n        if (target.kind === \"postgres\") {\n          const ok = yield* checkPgConnection(target.url);\n          if (ok) return null;\n          const tcpOk = yield* checkTcpReachable(target.host, target.port);\n          const pluginContext = target.secret.endsWith(\"_DATABASE_URL\")\n            ? ` The plugin for ${target.secret} runs inside the local host process, so this DB must be reachable. Run \\`docker compose up -d --wait\\` to start local Postgres/Redis.`\n            : \"\";\n          if (tcpOk) {\n            return {\n              secret: target.secret,\n              host: target.host,\n              port: target.port,\n              error: `${target.secret} at ${target.host}:${target.port} is reachable but Postgres connection failed. Check credentials and database name.${pluginContext}`,\n            } satisfies PreflightFailure;\n          }\n          return {\n            secret: target.secret,\n            host: target.host,\n            port: target.port,\n            error: `${target.secret} points to ${target.host}:${target.port} but nothing is listening.${pluginContext}`,\n          } satisfies PreflightFailure;\n        }\n\n        const reachable = yield* checkTcpReachable(target.host, target.port);\n        if (reachable) return null;\n        return {\n          secret: target.secret,\n          host: target.host,\n          port: target.port,\n          error: `${target.secret} points to ${target.host}:${target.port} but nothing is listening`,\n        } satisfies PreflightFailure;\n      }),\n    { concurrency: \"unbounded\" },\n  ).pipe(Effect.map((results) => results.filter((r): r is PreflightFailure => r !== null)));\n}\n"],"mappings":"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"}