{"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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"}