everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
142 lines (140 loc) • 4.36 kB
JavaScript
import { Data, Effect } from "effect";
import { createConnection } from "node:net";
//#region src/infra/preflight.ts
var PreflightConnectionError = class extends Data.TaggedError("PreflightConnectionError") {
get message() {
return `Postgres connection failed: ${this.cause instanceof Error ? this.cause.message : String(this.cause)}`;
}
};
const scheduleTimeout = (ms, fn) => setTimeout(fn, ms);
function parseLocalUrl(url) {
try {
const parsed = new URL(url);
const host = parsed.hostname;
if (host !== "localhost" && host !== "127.0.0.1" && host !== "::1") return null;
const port = Number.parseInt(parsed.port, 10);
if (Number.isNaN(port)) return {
host,
port: parsed.protocol === "redis:" ? 6379 : 5432
};
return {
host,
port
};
} catch {
return null;
}
}
function checkTcpReachable(host, port, timeoutMs = 2e3) {
return Effect.callback((resume) => {
const socket = createConnection({
host,
port
});
const timer = scheduleTimeout(timeoutMs, () => {
socket.destroy();
resume(Effect.succeed(false));
});
socket.once("connect", () => {
clearTimeout(timer);
socket.destroy();
resume(Effect.succeed(true));
});
socket.once("error", () => {
clearTimeout(timer);
socket.destroy();
resume(Effect.succeed(false));
});
});
}
function checkPgConnection(url) {
return Effect.gen(function* () {
if (!(yield* checkTcpReachable(new URL(url).hostname, Number(new URL(url).port) || 5432, 4e3))) return false;
return yield* Effect.tryPromise({
try: async () => {
const { Pool } = await import("pg");
const pool = new Pool({
connectionString: url,
connectionTimeoutMillis: 3e3
});
try {
const client = await pool.connect();
try {
await client.query("SELECT 1");
return true;
} finally {
client.release();
}
} finally {
await pool.end().catch(() => {});
}
},
catch: (cause) => new PreflightConnectionError({ cause })
}).pipe(Effect.orElseSucceed(() => false));
});
}
function preflightTargetsFromEnv(env) {
const targets = [];
for (const [secret, value] of Object.entries(env)) {
if (!value) continue;
if (secret.endsWith("_DATABASE_URL")) {
const parsed = parseLocalUrl(value);
if (parsed) targets.push({
secret,
host: parsed.host,
port: parsed.port,
kind: "postgres",
url: value
});
} else if (secret.endsWith("_REDIS_URL")) {
const parsed = parseLocalUrl(value);
if (parsed) targets.push({
secret,
host: parsed.host,
port: parsed.port,
kind: "redis",
url: value
});
}
}
return targets;
}
function preflightLocalInfra(env, overrides) {
const targets = preflightTargetsFromEnv(overrides ? {
...env,
...overrides
} : env);
if (targets.length === 0) return Effect.succeed([]);
return Effect.forEach(targets, (target) => Effect.gen(function* () {
if (target.kind === "postgres") {
if (yield* checkPgConnection(target.url)) return null;
const tcpOk = yield* checkTcpReachable(target.host, target.port);
const pluginContext = target.secret === "API_DATABASE_URL" || target.secret === "AUTH_DATABASE_URL" ? " Run `docker compose up -d --wait` to start local Postgres." : ` 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.`;
if (tcpOk) return {
secret: target.secret,
host: target.host,
port: target.port,
error: `${target.secret} at ${target.host}:${target.port} is reachable but Postgres connection failed. Check credentials and database name.${pluginContext}`,
tcpReachable: true
};
return {
secret: target.secret,
host: target.host,
port: target.port,
error: `${target.secret} points to ${target.host}:${target.port} but nothing is listening.${pluginContext}`,
tcpReachable: false
};
}
if (yield* checkTcpReachable(target.host, target.port)) return null;
return {
secret: target.secret,
host: target.host,
port: target.port,
error: `${target.secret} points to ${target.host}:${target.port} but nothing is listening`,
tcpReachable: false
};
}), { concurrency: "unbounded" }).pipe(Effect.map((results) => results.filter((r) => r !== null)));
}
//#endregion
export { preflightLocalInfra };
//# sourceMappingURL=preflight.mjs.map