lambda-pool 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +291 -0
  3. package/dist/adapters/health.d.ts +30 -0
  4. package/dist/adapters/health.d.ts.map +1 -0
  5. package/dist/adapters/health.js +36 -0
  6. package/dist/adapters/health.js.map +1 -0
  7. package/dist/adapters/mysql.d.ts +40 -0
  8. package/dist/adapters/mysql.d.ts.map +1 -0
  9. package/dist/adapters/mysql.js +34 -0
  10. package/dist/adapters/mysql.js.map +1 -0
  11. package/dist/adapters/pg.d.ts +46 -0
  12. package/dist/adapters/pg.d.ts.map +1 -0
  13. package/dist/adapters/pg.js +34 -0
  14. package/dist/adapters/pg.js.map +1 -0
  15. package/dist/application/diagnostics.d.ts +33 -0
  16. package/dist/application/diagnostics.d.ts.map +1 -0
  17. package/dist/application/diagnostics.js +94 -0
  18. package/dist/application/diagnostics.js.map +1 -0
  19. package/dist/application/inspect.d.ts +20 -0
  20. package/dist/application/inspect.d.ts.map +1 -0
  21. package/dist/application/inspect.js +31 -0
  22. package/dist/application/inspect.js.map +1 -0
  23. package/dist/application/preflight.d.ts +28 -0
  24. package/dist/application/preflight.d.ts.map +1 -0
  25. package/dist/application/preflight.js +49 -0
  26. package/dist/application/preflight.js.map +1 -0
  27. package/dist/application/recommend.d.ts +22 -0
  28. package/dist/application/recommend.d.ts.map +1 -0
  29. package/dist/application/recommend.js +30 -0
  30. package/dist/application/recommend.js.map +1 -0
  31. package/dist/core/budget.d.ts +35 -0
  32. package/dist/core/budget.d.ts.map +1 -0
  33. package/dist/core/budget.js +48 -0
  34. package/dist/core/budget.js.map +1 -0
  35. package/dist/core/config.d.ts +24 -0
  36. package/dist/core/config.d.ts.map +1 -0
  37. package/dist/core/config.js +32 -0
  38. package/dist/core/config.js.map +1 -0
  39. package/dist/core/dsn.d.ts +20 -0
  40. package/dist/core/dsn.d.ts.map +1 -0
  41. package/dist/core/dsn.js +38 -0
  42. package/dist/core/dsn.js.map +1 -0
  43. package/dist/core/env.d.ts +27 -0
  44. package/dist/core/env.d.ts.map +1 -0
  45. package/dist/core/env.js +52 -0
  46. package/dist/core/env.js.map +1 -0
  47. package/dist/core/providers.d.ts +29 -0
  48. package/dist/core/providers.d.ts.map +1 -0
  49. package/dist/core/providers.js +121 -0
  50. package/dist/core/providers.js.map +1 -0
  51. package/dist/core/redact.d.ts +20 -0
  52. package/dist/core/redact.d.ts.map +1 -0
  53. package/dist/core/redact.js +63 -0
  54. package/dist/core/redact.js.map +1 -0
  55. package/dist/core/result.d.ts +16 -0
  56. package/dist/core/result.d.ts.map +1 -0
  57. package/dist/core/result.js +32 -0
  58. package/dist/core/result.js.map +1 -0
  59. package/dist/core/retry.d.ts +31 -0
  60. package/dist/core/retry.d.ts.map +1 -0
  61. package/dist/core/retry.js +67 -0
  62. package/dist/core/retry.js.map +1 -0
  63. package/dist/core/url.d.ts +38 -0
  64. package/dist/core/url.d.ts.map +1 -0
  65. package/dist/core/url.js +88 -0
  66. package/dist/core/url.js.map +1 -0
  67. package/dist/index.d.ts +17 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +35 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/presentation/bin.d.ts +3 -0
  72. package/dist/presentation/bin.d.ts.map +1 -0
  73. package/dist/presentation/bin.js +11 -0
  74. package/dist/presentation/bin.js.map +1 -0
  75. package/dist/presentation/cli.d.ts +10 -0
  76. package/dist/presentation/cli.d.ts.map +1 -0
  77. package/dist/presentation/cli.js +124 -0
  78. package/dist/presentation/cli.js.map +1 -0
  79. package/package.json +133 -0
@@ -0,0 +1,94 @@
1
+ // Diagnostics: inspect a connection config and surface actionable warnings.
2
+ //
3
+ // Composes the pure modules (url, providers, budget) into a single advisory
4
+ // report. Returns data — it never logs or throws — so callers decide how to
5
+ // present it (CLI, startup log, test assertion). This is the "lint your DB
6
+ // connection for serverless" feature.
7
+ import { recommendPoolLimit } from "../core/budget.js";
8
+ import { detectProvider, isPooledEndpoint } from "../core/providers.js";
9
+ import { parseConnectionString, redactUrl, urlRequestsSsl } from "../core/url.js";
10
+ const DEFAULT_EXPECTED_INSTANCES = 20;
11
+ /**
12
+ * Analyze a connection config for serverless safety. Pure: returns a report,
13
+ * performs no I/O.
14
+ */
15
+ export function diagnose(input) {
16
+ const diagnostics = [];
17
+ const safeUrl = redactUrl(input.url);
18
+ let parsed;
19
+ try {
20
+ parsed = parseConnectionString(input.url);
21
+ }
22
+ catch (e) {
23
+ diagnostics.push({
24
+ severity: "error",
25
+ code: "INVALID_URL",
26
+ message: e.message,
27
+ });
28
+ return { safeUrl, provider: "unknown", diagnostics, ok: false };
29
+ }
30
+ const preset = detectProvider(parsed.host);
31
+ const pooled = isPooledEndpoint(parsed.host);
32
+ const instances = input.expectedInstances ?? DEFAULT_EXPECTED_INSTANCES;
33
+ // 1) Pool size sanity against the provider's typical budget.
34
+ const budget = recommendPoolLimit({
35
+ maxConnections: preset.typicalMaxConnections,
36
+ expectedInstances: instances,
37
+ });
38
+ if (!pooled && input.poolLimit > budget.recommendedPoolLimit) {
39
+ diagnostics.push({
40
+ severity: "warning",
41
+ code: "POOL_TOO_LARGE",
42
+ message: `poolLimit ${input.poolLimit} may exceed ${preset.label}'s budget: ` +
43
+ `${budget.rationale} Lower poolLimit or use a pooler.`,
44
+ });
45
+ }
46
+ // 2) Provider has a pooler but you're on the direct host.
47
+ if (preset.hasPooledEndpoint && !pooled && input.poolLimit > 1) {
48
+ diagnostics.push({
49
+ severity: "info",
50
+ code: "POOLER_AVAILABLE",
51
+ message: `${preset.label} offers a pooled endpoint. ${preset.note}`,
52
+ });
53
+ }
54
+ // 3) SSL requested in the URL but no CA supplied → likely silent fallback.
55
+ if (urlRequestsSsl(input.url) && !input.hasCaCert) {
56
+ diagnostics.push({
57
+ severity: "warning",
58
+ code: "SSL_WITHOUT_CA",
59
+ message: "URL requests SSL but no CA cert was provided (DATABASE_SSL_CA_BASE64). " +
60
+ "mysql2/pg may not verify the server as you expect; pass the CA explicitly.",
61
+ });
62
+ }
63
+ // 4) A pool larger than 1 with no pooler on a tiny-budget provider.
64
+ if (!preset.hasPooledEndpoint &&
65
+ input.poolLimit > preset.safeDirectPoolLimit &&
66
+ preset.typicalMaxConnections <= 30) {
67
+ diagnostics.push({
68
+ severity: "warning",
69
+ code: "SMALL_MAX_CONNECTIONS",
70
+ message: `${preset.label} typically caps max_connections around ${preset.typicalMaxConnections}; ` +
71
+ `keep the per-instance pool at ${preset.safeDirectPoolLimit}.`,
72
+ });
73
+ }
74
+ // 5) Friendly confirmation when everything looks right.
75
+ if (diagnostics.length === 0) {
76
+ diagnostics.push({
77
+ severity: "info",
78
+ code: "OK",
79
+ message: `Configuration looks serverless-safe for ${preset.label}.`,
80
+ });
81
+ }
82
+ const ok = !diagnostics.some((d) => d.severity === "error");
83
+ return { safeUrl, provider: preset.id, diagnostics, ok };
84
+ }
85
+ /** Format a report as plain text lines (for CLI / startup logs). */
86
+ export function formatReport(report) {
87
+ const head = `lambda-pool diagnostics for ${report.safeUrl} [${report.provider}]`;
88
+ const lines = report.diagnostics.map((d) => ` ${symbolFor(d.severity)} ${d.code}: ${d.message}`);
89
+ return [head, ...lines].join("\n");
90
+ }
91
+ function symbolFor(s) {
92
+ return s === "error" ? "✖" : s === "warning" ? "⚠" : "ℹ";
93
+ }
94
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/application/diagnostics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type DiagnoseReport } from "./diagnostics.ts";
2
+ import { type Env } from "../core/env.ts";
3
+ export interface InspectEnv extends Env {
4
+ DATABASE_URL?: string;
5
+ MYSQL_URL?: string;
6
+ POSTGRES_URL?: string;
7
+ PG_URL?: string;
8
+ DATABASE_POOL_LIMIT?: string;
9
+ DATABASE_SSL_CA_BASE64?: string;
10
+ /** Optional hint for the budget check (peak warm instances). */
11
+ EXPECTED_INSTANCES?: string;
12
+ }
13
+ /**
14
+ * Build a diagnostics report from environment variables.
15
+ *
16
+ * Throws only when no connection URL is present at all — everything else is
17
+ * surfaced as diagnostics, never thrown.
18
+ */
19
+ export declare function inspectEnv(env: InspectEnv): DiagnoseReport;
20
+ //# sourceMappingURL=inspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/application/inspect.ts"],"names":[],"mappings":"AAMA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAA8B,KAAK,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAEtE,MAAM,WAAW,UAAW,SAAQ,GAAG;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gEAAgE;IAChE,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,cAAc,CA0B1D"}
@@ -0,0 +1,31 @@
1
+ // Convenience: run diagnostics straight from an env bag.
2
+ //
3
+ // Bridges the env-shaped world (process.env) to the pure `diagnose()` core, so
4
+ // apps can do a one-line startup check without re-deriving the URL/pool/CA
5
+ // values themselves. Kept separate from the builders so each module has one job.
6
+ import { diagnose } from "./diagnostics.js";
7
+ import { resolvePoolLimit, firstEnv } from "../core/env.js";
8
+ /**
9
+ * Build a diagnostics report from environment variables.
10
+ *
11
+ * Throws only when no connection URL is present at all — everything else is
12
+ * surfaced as diagnostics, never thrown.
13
+ */
14
+ export function inspectEnv(env) {
15
+ const url = firstEnv(env, "DATABASE_URL", "MYSQL_URL", "POSTGRES_URL", "PG_URL");
16
+ if (!url) {
17
+ throw new Error("lambda-pool: no connection URL in env (DATABASE_URL / MYSQL_URL / POSTGRES_URL / PG_URL).");
18
+ }
19
+ const expected = env.EXPECTED_INSTANCES
20
+ ? Number(env.EXPECTED_INSTANCES)
21
+ : undefined;
22
+ return diagnose({
23
+ url,
24
+ poolLimit: resolvePoolLimit(env.DATABASE_POOL_LIMIT),
25
+ hasCaCert: Boolean(env.DATABASE_SSL_CA_BASE64),
26
+ ...(expected && Number.isFinite(expected) && expected > 0
27
+ ? { expectedInstances: Math.floor(expected) }
28
+ : {}),
29
+ });
30
+ }
31
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../src/application/inspect.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,iFAAiF;AAEjF,OAAO,EAAE,QAAQ,EAAuB,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAY,MAAM,gBAAgB,CAAC;AAatE;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAe;IACxC,MAAM,GAAG,GAAG,QAAQ,CAClB,GAAG,EACH,cAAc,EACd,WAAW,EACX,cAAc,EACd,QAAQ,CACT,CAAC;IACF,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,CAAC,kBAAkB;QACrC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAChC,CAAC,CAAC,SAAS,CAAC;IAEd,OAAO,QAAQ,CAAC;QACd,GAAG;QACH,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,mBAAmB,CAAC;QACpD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;QAC9C,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;YACvD,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE;YAC7C,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { type Diagnostic, type DiagnoseReport } from "./diagnostics.ts";
2
+ import { type Env } from "../core/env.ts";
3
+ export interface PreflightOptions {
4
+ /**
5
+ * Optional live reachability probe. Return true if the DB answered. Inject
6
+ * the adapter's `isReachable(pool)` here when you want a real connection test;
7
+ * omit it for a static (no-I/O) preflight.
8
+ */
9
+ probe?: () => Promise<boolean>;
10
+ /** Peak warm instances, forwarded to the budget checks. */
11
+ expectedInstances?: number;
12
+ }
13
+ export interface PreflightResult {
14
+ /** Overall pass: no `error` diagnostics and (if probed) the DB was reachable. */
15
+ ok: boolean;
16
+ safeUrl: string;
17
+ report: DiagnoseReport;
18
+ /** Present only when a probe was supplied. */
19
+ reachable?: boolean;
20
+ diagnostics: Diagnostic[];
21
+ }
22
+ /**
23
+ * Run a preflight check over the connection in `env`. Never throws for a
24
+ * reachability failure — the probe result is folded into `ok`. Throws only when
25
+ * `env` has no connection URL at all (a programming/config error).
26
+ */
27
+ export declare function preflight(env: Env, options?: PreflightOptions): Promise<PreflightResult>;
28
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/application/preflight.ts"],"names":[],"mappings":"AAUA,OAAO,EAAY,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAqC1B"}
@@ -0,0 +1,49 @@
1
+ // Preflight: a single startup gate combining config + diagnostics, with an
2
+ // optional live reachability probe.
3
+ //
4
+ // Lives in the application layer: it composes core (config) and application
5
+ // (diagnostics). The live probe is *injected* as a function rather than
6
+ // imported from adapters, so this module keeps the dependency rule intact
7
+ // (application must not import adapters) while still supporting a real check.
8
+ import { loadPoolConfig } from "../core/config.js";
9
+ import { redactUrl } from "../core/url.js";
10
+ import { diagnose } from "./diagnostics.js";
11
+ import {} from "../core/env.js";
12
+ /**
13
+ * Run a preflight check over the connection in `env`. Never throws for a
14
+ * reachability failure — the probe result is folded into `ok`. Throws only when
15
+ * `env` has no connection URL at all (a programming/config error).
16
+ */
17
+ export async function preflight(env, options = {}) {
18
+ const config = loadPoolConfig(env); // throws if no URL — intentional
19
+ const report = diagnose({
20
+ url: config.url,
21
+ poolLimit: config.poolLimit,
22
+ hasCaCert: config.hasTls,
23
+ ...(options.expectedInstances
24
+ ? { expectedInstances: options.expectedInstances }
25
+ : {}),
26
+ });
27
+ const diagnostics = [...report.diagnostics];
28
+ let reachable;
29
+ if (options.probe) {
30
+ reachable = await options.probe().catch(() => false);
31
+ if (!reachable) {
32
+ diagnostics.push({
33
+ severity: "error",
34
+ code: "UNREACHABLE",
35
+ message: "Database did not respond to the reachability probe.",
36
+ });
37
+ }
38
+ }
39
+ const ok = !diagnostics.some((d) => d.severity === "error") &&
40
+ (reachable ?? true);
41
+ return {
42
+ ok,
43
+ safeUrl: redactUrl(config.url),
44
+ report,
45
+ ...(reachable === undefined ? {} : { reachable }),
46
+ diagnostics,
47
+ };
48
+ }
49
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/application/preflight.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,oCAAoC;AACpC,EAAE;AACF,4EAA4E;AAC5E,wEAAwE;AACxE,0EAA0E;AAC1E,8EAA8E;AAE9E,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAwC,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAY,MAAM,gBAAgB,CAAC;AAuB1C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAAQ,EACR,UAA4B,EAAE;IAE9B,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,iCAAiC;IAErE,MAAM,MAAM,GAAG,QAAQ,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS,EAAE,MAAM,CAAC,MAAM;QACxB,GAAG,CAAC,OAAO,CAAC,iBAAiB;YAC3B,CAAC,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,EAAE;YAClD,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,SAA8B,CAAC;IAEnC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,SAAS,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QACrD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,WAAW,CAAC,IAAI,CAAC;gBACf,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,qDAAqD;aAC/D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GACN,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;QAChD,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;IAEtB,OAAO;QACL,EAAE;QACF,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC;QAC9B,MAAM;QACN,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;QACjD,WAAW;KACZ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { type BudgetResult } from "../core/budget.ts";
2
+ import { type ProviderId } from "../core/providers.ts";
3
+ export interface RecommendInput {
4
+ /** Connection URI. */
5
+ url: string;
6
+ /** Peak warm serverless instances. Default 20. */
7
+ expectedInstances?: number;
8
+ /** Override the provider's assumed max_connections (e.g. you know your plan). */
9
+ maxConnections?: number;
10
+ }
11
+ export interface Recommendation extends BudgetResult {
12
+ provider: ProviderId;
13
+ /** True when the URL already targets the provider's pooled endpoint. */
14
+ usingPooledEndpoint: boolean;
15
+ /** True when a pooler is recommended (budget exceeded and one is available). */
16
+ poolerAdvised: boolean;
17
+ /** The max_connections value the recommendation was computed against. */
18
+ assumedMaxConnections: number;
19
+ }
20
+ /** Recommend a pool size for the database behind `url`. Pure. */
21
+ export declare function recommendForUrl(input: RecommendInput): Recommendation;
22
+ //# sourceMappingURL=recommend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend.d.ts","sourceRoot":"","sources":["../../src/application/recommend.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGzF,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAe,SAAQ,YAAY;IAClD,QAAQ,EAAE,UAAU,CAAC;IACrB,wEAAwE;IACxE,mBAAmB,EAAE,OAAO,CAAC;IAC7B,gFAAgF;IAChF,aAAa,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAID,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,CAsBrE"}
@@ -0,0 +1,30 @@
1
+ // Use-case: end-to-end pool recommendation from a connection URL.
2
+ //
3
+ // Composes provider detection (core/providers) with the budget math
4
+ // (core/budget): given just a URL and an expected instance count, identify the
5
+ // provider, use its typical max_connections as the budget, and recommend a
6
+ // per-instance pool size — plus whether a pooler is advisable. No I/O.
7
+ import { recommendPoolLimit } from "../core/budget.js";
8
+ import { detectProvider, isPooledEndpoint } from "../core/providers.js";
9
+ import { parseConnectionString } from "../core/url.js";
10
+ const DEFAULT_EXPECTED_INSTANCES = 20;
11
+ /** Recommend a pool size for the database behind `url`. Pure. */
12
+ export function recommendForUrl(input) {
13
+ const { host } = parseConnectionString(input.url);
14
+ const preset = detectProvider(host);
15
+ const usingPooledEndpoint = isPooledEndpoint(host);
16
+ const assumedMaxConnections = input.maxConnections ?? preset.typicalMaxConnections;
17
+ const expectedInstances = input.expectedInstances ?? DEFAULT_EXPECTED_INSTANCES;
18
+ const budget = recommendPoolLimit({
19
+ maxConnections: assumedMaxConnections,
20
+ expectedInstances,
21
+ });
22
+ return {
23
+ ...budget,
24
+ provider: preset.id,
25
+ usingPooledEndpoint,
26
+ assumedMaxConnections,
27
+ poolerAdvised: budget.exceedsBudget && preset.hasPooledEndpoint && !usingPooledEndpoint,
28
+ };
29
+ }
30
+ //# sourceMappingURL=recommend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend.js","sourceRoot":"","sources":["../../src/application/recommend.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,oEAAoE;AACpE,+EAA+E;AAC/E,2EAA2E;AAC3E,uEAAuE;AAEvE,OAAO,EAAE,kBAAkB,EAAqB,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAmB,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAqBvD,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAEtC,iEAAiE;AACjE,MAAM,UAAU,eAAe,CAAC,KAAqB;IACnD,MAAM,EAAE,IAAI,EAAE,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAEnD,MAAM,qBAAqB,GACzB,KAAK,CAAC,cAAc,IAAI,MAAM,CAAC,qBAAqB,CAAC;IACvD,MAAM,iBAAiB,GAAG,KAAK,CAAC,iBAAiB,IAAI,0BAA0B,CAAC;IAEhF,MAAM,MAAM,GAAG,kBAAkB,CAAC;QAChC,cAAc,EAAE,qBAAqB;QACrC,iBAAiB;KAClB,CAAC,CAAC;IAEH,OAAO;QACL,GAAG,MAAM;QACT,QAAQ,EAAE,MAAM,CAAC,EAAE;QACnB,mBAAmB;QACnB,qBAAqB;QACrB,aAAa,EACX,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,iBAAiB,IAAI,CAAC,mBAAmB;KAC3E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,35 @@
1
+ export interface BudgetInput {
2
+ /** The database server's `max_connections`. */
3
+ maxConnections: number;
4
+ /** Peak number of warm instances the platform may run concurrently. */
5
+ expectedInstances: number;
6
+ /**
7
+ * Connections to hold back for migrations, admin tools, the DB's own
8
+ * superuser reserve, cron jobs, etc. Defaults to a sensible floor.
9
+ */
10
+ reserved?: number;
11
+ /** Other long-lived clients (a separate worker, a BI tool) using the DB. */
12
+ otherClients?: number;
13
+ }
14
+ export interface BudgetResult {
15
+ /** Recommended per-instance pool size (>= 1). */
16
+ recommendedPoolLimit: number;
17
+ /** Connections available to serverless instances after reserves. */
18
+ usableConnections: number;
19
+ /** Projected peak usage at the recommended pool size. */
20
+ projectedPeakUsage: number;
21
+ /** True when even a pool of 1 across all instances exceeds the budget. */
22
+ exceedsBudget: boolean;
23
+ /** Human-readable explanation of how the number was derived. */
24
+ rationale: string;
25
+ }
26
+ /**
27
+ * Recommend a per-instance pool size from a connection budget.
28
+ *
29
+ * Formula: `floor((maxConnections - reserved - otherClients) / expectedInstances)`,
30
+ * clamped to at least 1. When even 1×instances exceeds the usable budget, the
31
+ * result still recommends 1 but flags `exceedsBudget` so the caller can warn
32
+ * that a pooler is needed.
33
+ */
34
+ export declare function recommendPoolLimit(input: BudgetInput): BudgetResult;
35
+ //# sourceMappingURL=budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/core/budget.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,aAAa,EAAE,OAAO,CAAC;IACvB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;CACnB;AAYD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAgCnE"}
@@ -0,0 +1,48 @@
1
+ // Connection-budget math.
2
+ //
3
+ // The core insight of this package as a pure function: given the database's
4
+ // `max_connections`, how many connections may a single warm instance safely
5
+ // pool, accounting for the number of instances the platform may keep warm and a
6
+ // reserve for migrations / admin / other clients?
7
+ //
8
+ // Pure and dependency-free. No env, no I/O — just arithmetic you can unit-test.
9
+ /** Postgres reserves superuser slots; default reserve reflects that + admin. */
10
+ const DEFAULT_RESERVED = 3;
11
+ function asPositiveInt(n, name) {
12
+ if (!Number.isFinite(n) || n <= 0) {
13
+ throw new Error(`lambda-pool: ${name} must be a positive number (got ${n}).`);
14
+ }
15
+ return Math.floor(n);
16
+ }
17
+ /**
18
+ * Recommend a per-instance pool size from a connection budget.
19
+ *
20
+ * Formula: `floor((maxConnections - reserved - otherClients) / expectedInstances)`,
21
+ * clamped to at least 1. When even 1×instances exceeds the usable budget, the
22
+ * result still recommends 1 but flags `exceedsBudget` so the caller can warn
23
+ * that a pooler is needed.
24
+ */
25
+ export function recommendPoolLimit(input) {
26
+ const maxConnections = asPositiveInt(input.maxConnections, "maxConnections");
27
+ const expectedInstances = asPositiveInt(input.expectedInstances, "expectedInstances");
28
+ const reserved = Math.max(0, Math.floor(input.reserved ?? DEFAULT_RESERVED));
29
+ const otherClients = Math.max(0, Math.floor(input.otherClients ?? 0));
30
+ const usableConnections = Math.max(0, maxConnections - reserved - otherClients);
31
+ const raw = Math.floor(usableConnections / expectedInstances);
32
+ const recommendedPoolLimit = Math.max(1, raw);
33
+ const projectedPeakUsage = recommendedPoolLimit * expectedInstances;
34
+ const exceedsBudget = projectedPeakUsage > usableConnections;
35
+ const rationale = exceedsBudget
36
+ ? `Even 1 connection × ${expectedInstances} instances (${expectedInstances}) exceeds the ${usableConnections} usable connections ` +
37
+ `(${maxConnections} max − ${reserved} reserved − ${otherClients} other). Use a connection pooler (PgBouncer / RDS Proxy / Neon pooled endpoint).`
38
+ : `${usableConnections} usable connections (${maxConnections} max − ${reserved} reserved − ${otherClients} other) ` +
39
+ `÷ ${expectedInstances} instances → pool of ${recommendedPoolLimit} per instance (peak ${projectedPeakUsage}).`;
40
+ return {
41
+ recommendedPoolLimit,
42
+ usableConnections,
43
+ projectedPeakUsage,
44
+ exceedsBudget,
45
+ rationale,
46
+ };
47
+ }
48
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/core/budget.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,gFAAgF;AAChF,kDAAkD;AAClD,EAAE;AACF,gFAAgF;AA6BhF,gFAAgF;AAChF,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B,SAAS,aAAa,CAAC,CAAS,EAAE,IAAY;IAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,mCAAmC,CAAC,IAAI,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAkB;IACnD,MAAM,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAC7E,MAAM,iBAAiB,GAAG,aAAa,CACrC,KAAK,CAAC,iBAAiB,EACvB,mBAAmB,CACpB,CAAC;IACF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,IAAI,gBAAgB,CAAC,CAAC,CAAC;IAC7E,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC;IAEtE,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAChC,CAAC,EACD,cAAc,GAAG,QAAQ,GAAG,YAAY,CACzC,CAAC;IAEF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,GAAG,iBAAiB,CAAC,CAAC;IAC9D,MAAM,oBAAoB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9C,MAAM,kBAAkB,GAAG,oBAAoB,GAAG,iBAAiB,CAAC;IACpE,MAAM,aAAa,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;IAE7D,MAAM,SAAS,GAAG,aAAa;QAC7B,CAAC,CAAC,uBAAuB,iBAAiB,eAAe,iBAAiB,iBAAiB,iBAAiB,sBAAsB;YAChI,IAAI,cAAc,UAAU,QAAQ,eAAe,YAAY,kFAAkF;QACnJ,CAAC,CAAC,GAAG,iBAAiB,wBAAwB,cAAc,UAAU,QAAQ,eAAe,YAAY,UAAU;YACjH,KAAK,iBAAiB,wBAAwB,oBAAoB,uBAAuB,kBAAkB,IAAI,CAAC;IAEpH,OAAO;QACL,oBAAoB;QACpB,iBAAiB;QACjB,kBAAkB;QAClB,aAAa;QACb,SAAS;KACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type Env } from "./env.ts";
2
+ import { type Engine } from "./url.ts";
3
+ export interface PoolConfig {
4
+ /** The resolved connection URI. */
5
+ url: string;
6
+ /** Engine inferred from the URI scheme. */
7
+ engine: Engine;
8
+ /** Host, for provider detection. */
9
+ host: string;
10
+ /** Per-instance pool size (default 1). */
11
+ poolLimit: number;
12
+ /** Decoded CA PEM, when DATABASE_SSL_CA_BASE64 was provided. */
13
+ caCert?: string;
14
+ /** Whether TLS material was supplied. */
15
+ hasTls: boolean;
16
+ }
17
+ /**
18
+ * Read and normalize pool configuration from an env bag.
19
+ *
20
+ * Throws a single clear error when no connection URL is present; everything
21
+ * else has a defined default.
22
+ */
23
+ export declare function loadPoolConfig(env: Env): PoolConfig;
24
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAOA,OAAO,EAA8C,KAAK,GAAG,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAE9D,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,MAAM,EAAE,OAAO,CAAC;CACjB;AAID;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAmBnD"}
@@ -0,0 +1,32 @@
1
+ // Normalized pool configuration from an environment bag.
2
+ //
3
+ // One place that reads the (many) accepted env var names and produces a single
4
+ // typed, engine-resolved config. The adapters and use-cases consume this
5
+ // instead of re-deriving URL / pool-limit / CA from raw env each time (DRY,
6
+ // Single Source of Truth). Pure: takes an env object, returns data.
7
+ import { decodeCaBase64, firstEnv, resolvePoolLimit } from "./env.js";
8
+ import { parseConnectionString } from "./url.js";
9
+ const URL_KEYS = ["DATABASE_URL", "MYSQL_URL", "POSTGRES_URL", "PG_URL"];
10
+ /**
11
+ * Read and normalize pool configuration from an env bag.
12
+ *
13
+ * Throws a single clear error when no connection URL is present; everything
14
+ * else has a defined default.
15
+ */
16
+ export function loadPoolConfig(env) {
17
+ const url = firstEnv(env, ...URL_KEYS);
18
+ if (!url) {
19
+ throw new Error(`lambda-pool: no connection URL in env (one of: ${URL_KEYS.join(", ")}).`);
20
+ }
21
+ const { engine, host } = parseConnectionString(url);
22
+ const tls = decodeCaBase64(env.DATABASE_SSL_CA_BASE64);
23
+ return {
24
+ url,
25
+ engine,
26
+ host,
27
+ poolLimit: resolvePoolLimit(env.DATABASE_POOL_LIMIT),
28
+ ...(tls ? { caCert: tls.ca } : {}),
29
+ hasTls: Boolean(tls),
30
+ };
31
+ }
32
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,+EAA+E;AAC/E,yEAAyE;AACzE,4EAA4E;AAC5E,oEAAoE;AAEpE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAY,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAe,MAAM,UAAU,CAAC;AAiB9D,MAAM,QAAQ,GAAG,CAAC,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,CAAU,CAAC;AAElF;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,GAAQ;IACrC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,kDAAkD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC1E,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IAEvD,OAAO;QACL,GAAG;QACH,MAAM;QACN,IAAI;QACJ,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,mBAAmB,CAAC;QACpD,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC;KACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type Engine } from "./url.ts";
2
+ export interface DsnParts {
3
+ engine: Engine;
4
+ host: string;
5
+ /** Defaults to the engine's standard port when omitted. */
6
+ port?: number;
7
+ user: string;
8
+ password?: string;
9
+ database: string;
10
+ /** Extra query params, e.g. `{ sslmode: "require" }`. */
11
+ params?: Record<string, string>;
12
+ }
13
+ /**
14
+ * Assemble a valid connection URI from typed parts.
15
+ *
16
+ * Throws when a required field (host, user, database) is missing, so a
17
+ * misconfigured environment fails loudly at build time rather than at connect.
18
+ */
19
+ export declare function buildDsn(parts: DsnParts): string;
20
+ //# sourceMappingURL=dsn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsn.d.ts","sourceRoot":"","sources":["../../src/core/dsn.ts"],"names":[],"mappings":"AAOA,OAAO,EAAe,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAEpD,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAOD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAkBhD"}
@@ -0,0 +1,38 @@
1
+ // Build a connection string from parts — the inverse of parseConnectionString.
2
+ //
3
+ // Useful when credentials arrive as discrete env vars (DB_HOST, DB_USER, …)
4
+ // rather than a single URL, which is common on RDS / self-hosted setups. Pure
5
+ // and dependency-free; encodes each component so special characters in a
6
+ // password don't corrupt the URI.
7
+ import { defaultPort } from "./url.js";
8
+ const PROTOCOL = {
9
+ mysql: "mysql:",
10
+ postgres: "postgres:",
11
+ };
12
+ /**
13
+ * Assemble a valid connection URI from typed parts.
14
+ *
15
+ * Throws when a required field (host, user, database) is missing, so a
16
+ * misconfigured environment fails loudly at build time rather than at connect.
17
+ */
18
+ export function buildDsn(parts) {
19
+ if (!parts.host)
20
+ throw new Error("lambda-pool: buildDsn requires a host.");
21
+ if (!parts.user)
22
+ throw new Error("lambda-pool: buildDsn requires a user.");
23
+ if (!parts.database) {
24
+ throw new Error("lambda-pool: buildDsn requires a database.");
25
+ }
26
+ const url = new URL(`${PROTOCOL[parts.engine]}//placeholder`);
27
+ url.hostname = parts.host;
28
+ url.port = String(parts.port ?? defaultPort(parts.engine));
29
+ url.username = encodeURIComponent(parts.user);
30
+ if (parts.password)
31
+ url.password = encodeURIComponent(parts.password);
32
+ url.pathname = `/${encodeURIComponent(parts.database)}`;
33
+ for (const [k, v] of Object.entries(parts.params ?? {})) {
34
+ url.searchParams.set(k, v);
35
+ }
36
+ return url.toString();
37
+ }
38
+ //# sourceMappingURL=dsn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsn.js","sourceRoot":"","sources":["../../src/core/dsn.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,yEAAyE;AACzE,kCAAkC;AAElC,OAAO,EAAE,WAAW,EAAe,MAAM,UAAU,CAAC;AAcpD,MAAM,QAAQ,GAA2B;IACvC,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,WAAW;CACtB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAe;IACtC,IAAI,CAAC,KAAK,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC3E,IAAI,CAAC,KAAK,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC3E,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAC9D,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC;IAC1B,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,GAAG,CAAC,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,KAAK,CAAC,QAAQ;QAAE,GAAG,CAAC,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACtE,GAAG,CAAC,QAAQ,GAAG,IAAI,kBAAkB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;IACxD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,27 @@
1
+ /** A bag of env vars (e.g. `process.env`). */
2
+ export type Env = Record<string, string | undefined>;
3
+ /** TLS material once decoded, ready to hand to a driver. */
4
+ export interface DecodedTls {
5
+ ca: string;
6
+ rejectUnauthorized: boolean;
7
+ }
8
+ /**
9
+ * Resolve the per-instance pool size.
10
+ *
11
+ * Defaults to 1 (see file header). Any non-positive / non-finite override is
12
+ * ignored and falls back to the default so a bad env var can never widen the
13
+ * pool by accident.
14
+ */
15
+ export declare function resolvePoolLimit(raw: string | undefined, fallback?: number): number;
16
+ /**
17
+ * Decode a base64-encoded CA certificate into TLS options.
18
+ *
19
+ * Managed providers hand you a CA bundle but the connection URI's
20
+ * `?ssl-mode=REQUIRED` / `?sslmode=require` param is NOT understood by mysql2
21
+ * or pg in the way people expect, so we pass the CA explicitly and verify the
22
+ * server. Returns `undefined` when no CA is configured (driver defaults apply).
23
+ */
24
+ export declare function decodeCaBase64(caBase64: string | undefined, rejectUnauthorized?: boolean): DecodedTls | undefined;
25
+ /** Read the first defined value among the given env keys. */
26
+ export declare function firstEnv(env: Env, ...keys: string[]): string | undefined;
27
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/core/env.ts"],"names":[],"mappings":"AAcA,8CAA8C;AAC9C,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAErD,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,SAAI,GACX,MAAM,CAIR;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,kBAAkB,UAAO,GACxB,UAAU,GAAG,SAAS,CAMxB;AAED,6DAA6D;AAC7D,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAMxE"}
@@ -0,0 +1,52 @@
1
+ // Shared, dependency-free helpers used by both the MySQL and Postgres adapters.
2
+ //
3
+ // Why this package exists
4
+ // -----------------------
5
+ // On Vercel/Lambda every *warm* function instance keeps its own connection
6
+ // pool. If each pool opens N connections and the platform keeps M instances
7
+ // warm, the database sees up to N*M connections. Managed Postgres/MySQL on
8
+ // small plans (Aiven free, Neon, RDS micro, PlanetScale) cap `max_connections`
9
+ // very low (often 10-25), so a seemingly innocent `connectionLimit: 10` will
10
+ // throw `ER_CON_COUNT_ERROR` / `too many clients already` under modest traffic.
11
+ //
12
+ // The fix is counter-intuitive: make each pool TINY (default 1). The platform's
13
+ // horizontal scaling becomes your concurrency; the database stops melting.
14
+ /**
15
+ * Resolve the per-instance pool size.
16
+ *
17
+ * Defaults to 1 (see file header). Any non-positive / non-finite override is
18
+ * ignored and falls back to the default so a bad env var can never widen the
19
+ * pool by accident.
20
+ */
21
+ export function resolvePoolLimit(raw, fallback = 1) {
22
+ if (raw == null || raw === "")
23
+ return fallback;
24
+ const n = Number(raw);
25
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
26
+ }
27
+ /**
28
+ * Decode a base64-encoded CA certificate into TLS options.
29
+ *
30
+ * Managed providers hand you a CA bundle but the connection URI's
31
+ * `?ssl-mode=REQUIRED` / `?sslmode=require` param is NOT understood by mysql2
32
+ * or pg in the way people expect, so we pass the CA explicitly and verify the
33
+ * server. Returns `undefined` when no CA is configured (driver defaults apply).
34
+ */
35
+ export function decodeCaBase64(caBase64, rejectUnauthorized = true) {
36
+ if (!caBase64)
37
+ return undefined;
38
+ return {
39
+ ca: Buffer.from(caBase64, "base64").toString("utf8"),
40
+ rejectUnauthorized,
41
+ };
42
+ }
43
+ /** Read the first defined value among the given env keys. */
44
+ export function firstEnv(env, ...keys) {
45
+ for (const k of keys) {
46
+ const v = env[k];
47
+ if (v != null && v !== "")
48
+ return v;
49
+ }
50
+ return undefined;
51
+ }
52
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/core/env.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,0BAA0B;AAC1B,0BAA0B;AAC1B,2EAA2E;AAC3E,4EAA4E;AAC5E,2EAA2E;AAC3E,+EAA+E;AAC/E,6EAA6E;AAC7E,gFAAgF;AAChF,EAAE;AACF,gFAAgF;AAChF,2EAA2E;AAW3E;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAuB,EACvB,QAAQ,GAAG,CAAC;IAEZ,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,QAAQ,CAAC;IAC/C,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAChE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA4B,EAC5B,kBAAkB,GAAG,IAAI;IAEzB,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;QACpD,kBAAkB;KACnB,CAAC;AACJ,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,QAAQ,CAAC,GAAQ,EAAE,GAAG,IAAc;IAClD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACjB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}