@fabricorg/experiments-db-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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/index.cjs +380 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +373 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fabric
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @fabricorg/experiments-db-pool
|
|
2
|
+
|
|
3
|
+
Singleton `pg.Pool` configured for serverless runtimes (Vercel, AWS Lambda, etc.).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { getPool } from '@fabricorg/experiments-db-pool';
|
|
9
|
+
|
|
10
|
+
const pool = getPool();
|
|
11
|
+
const result = await pool.query('SELECT 1');
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Singleton guarantee
|
|
15
|
+
|
|
16
|
+
`getPool()` returns the same `Pool` instance on every call within a single process:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
getPool() === getPool(); // true
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Environment variables
|
|
23
|
+
|
|
24
|
+
| Variable | Required | Default | Description |
|
|
25
|
+
|----------|----------|---------|-------------|
|
|
26
|
+
| `DATABASE_URL` | **yes** | — | Postgres connection string |
|
|
27
|
+
| `PG_POOL_MAX` | no | `5` | Max connections in the pool |
|
|
28
|
+
|
|
29
|
+
## Pool sizing rationale
|
|
30
|
+
|
|
31
|
+
Serverless platforms (Vercel, AWS Lambda, Cloudflare Pages Functions) spin up many
|
|
32
|
+
concurrent execution environments. Each environment that creates its own `Pool` can
|
|
33
|
+
hold idle connections indefinitely, exhausting the Postgres `max_connections` limit.
|
|
34
|
+
|
|
35
|
+
A **single shared singleton** eliminates duplicate pools within one process.
|
|
36
|
+
|
|
37
|
+
### Default: `max: 5`
|
|
38
|
+
|
|
39
|
+
- **Why 5?** Most serverless functions are I/O-bound and rarely need more than a
|
|
40
|
+
handful of concurrent queries.
|
|
41
|
+
- **Why not 1?** A single connection serializes all queries; 2–5 allows modest
|
|
42
|
+
parallelism for routes that fan out (e.g. health checks + data fetches).
|
|
43
|
+
|
|
44
|
+
### `idleTimeoutMillis: 30000`
|
|
45
|
+
|
|
46
|
+
Recycles connections after 30s of inactivity. This is **half of Vercel's default
|
|
47
|
+
serverless timeout** (60s), ensuring we don't leak idle sockets across invocations.
|
|
48
|
+
|
|
49
|
+
### `connectionTimeoutMillis: 5000`
|
|
50
|
+
|
|
51
|
+
Fail-fast when the database is unreachable. In a cold-start scenario, waiting the
|
|
52
|
+
default 0 (infinite) can hang the function until the platform kills it.
|
|
53
|
+
|
|
54
|
+
## Adjusting for your workload
|
|
55
|
+
|
|
56
|
+
If you run heavy analytics queries or batch jobs inside the same process, raise
|
|
57
|
+
`PG_POOL_MAX` via environment variable rather than forking the package:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
PG_POOL_MAX=20 node my-script.js
|
|
61
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var pg = require('pg');
|
|
4
|
+
var events = require('events');
|
|
5
|
+
|
|
6
|
+
// src/providers/neon.ts
|
|
7
|
+
var NeonDatabaseProvider = class {
|
|
8
|
+
kind = "neon";
|
|
9
|
+
pool;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.pool = new pg.Pool({
|
|
12
|
+
connectionString: options.connectionString,
|
|
13
|
+
max: options.max ?? Number.parseInt(process.env.PG_POOL_MAX ?? "5", 10),
|
|
14
|
+
idleTimeoutMillis: options.idleTimeoutMillis ?? 3e4,
|
|
15
|
+
connectionTimeoutMillis: options.connectionTimeoutMillis ?? 5e3
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
getPool() {
|
|
19
|
+
return this.pool;
|
|
20
|
+
}
|
|
21
|
+
async close() {
|
|
22
|
+
await this.pool.end();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function databricksIdentity(principal) {
|
|
26
|
+
if (principal.kind === "pat") {
|
|
27
|
+
const { token } = principal;
|
|
28
|
+
return async () => token;
|
|
29
|
+
}
|
|
30
|
+
return servicePrincipalProvider(principal);
|
|
31
|
+
}
|
|
32
|
+
function databricksPrincipalFromEnv(env = typeof process !== "undefined" ? process.env : {}) {
|
|
33
|
+
const token = env.DATABRICKS_TOKEN?.trim();
|
|
34
|
+
if (token) return { kind: "pat", token };
|
|
35
|
+
const clientId = env.DATABRICKS_CLIENT_ID?.trim();
|
|
36
|
+
const clientSecret = env.DATABRICKS_CLIENT_SECRET?.trim();
|
|
37
|
+
const host = env.DATABRICKS_HOST?.trim();
|
|
38
|
+
if (clientId && clientSecret) {
|
|
39
|
+
if (!host) throw new Error("Missing DATABRICKS_HOST for Databricks OAuth M2M.");
|
|
40
|
+
return {
|
|
41
|
+
kind: "service-principal",
|
|
42
|
+
host,
|
|
43
|
+
clientId,
|
|
44
|
+
clientSecret,
|
|
45
|
+
scope: env.DATABRICKS_SCOPE,
|
|
46
|
+
refreshSkewSeconds: env.DATABRICKS_REFRESH_SKEW_SECONDS ? Number.parseInt(env.DATABRICKS_REFRESH_SKEW_SECONDS, 10) : void 0
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Missing Databricks credentials: DATABRICKS_TOKEN or DATABRICKS_CLIENT_ID+DATABRICKS_CLIENT_SECRET."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function servicePrincipalProvider(principal) {
|
|
54
|
+
const host = principal.host.replace(/\/$/, "");
|
|
55
|
+
const scope = principal.scope ?? "all-apis";
|
|
56
|
+
const refreshSkewMs = (principal.refreshSkewSeconds ?? 60) * 1e3;
|
|
57
|
+
let cached;
|
|
58
|
+
let refreshing;
|
|
59
|
+
const refresh = async () => {
|
|
60
|
+
const response = await fetch(`${host}/oidc/v1/token`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: {
|
|
63
|
+
authorization: `Basic ${Buffer.from(`${principal.clientId}:${principal.clientSecret}`).toString("base64")}`,
|
|
64
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
65
|
+
},
|
|
66
|
+
body: new URLSearchParams({ grant_type: "client_credentials", scope })
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const text = await response.text();
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Databricks OAuth token exchange failed: ${response.status} ${response.statusText}: ${text.slice(0, 500)}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const value = await response.json();
|
|
75
|
+
const token = typeof value.access_token === "string" ? value.access_token : void 0;
|
|
76
|
+
if (!token) throw new Error("Databricks OAuth token exchange returned no access_token.");
|
|
77
|
+
const expiresInSeconds = typeof value.expires_in === "number" ? value.expires_in : 3600;
|
|
78
|
+
const expiresAtMs = Date.now() + expiresInSeconds * 1e3;
|
|
79
|
+
cached = { token, refreshAtMs: expiresAtMs - refreshSkewMs };
|
|
80
|
+
return token;
|
|
81
|
+
};
|
|
82
|
+
return async () => {
|
|
83
|
+
if (cached && Date.now() < cached.refreshAtMs) return cached.token;
|
|
84
|
+
refreshing ??= refresh().finally(() => {
|
|
85
|
+
refreshing = void 0;
|
|
86
|
+
});
|
|
87
|
+
return refreshing;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function lakebaseCredentialProvider(options) {
|
|
91
|
+
const workspaceHost = options.workspaceHost.replace(/\/$/, "");
|
|
92
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
93
|
+
const now = options.now ?? Date.now;
|
|
94
|
+
const random = options.random ?? Math.random;
|
|
95
|
+
const refreshBeforeMs = options.refreshBeforeMs ?? 5 * 6e4;
|
|
96
|
+
const refreshJitterMs = options.refreshJitterMs ?? 3e4;
|
|
97
|
+
let cached;
|
|
98
|
+
let refreshing;
|
|
99
|
+
const refresh = async () => {
|
|
100
|
+
const workspaceToken = typeof options.token === "function" ? await options.token() : options.token;
|
|
101
|
+
const response = await fetchImpl(`${workspaceHost}/api/2.0/postgres/credentials`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: {
|
|
104
|
+
authorization: `Bearer ${workspaceToken}`,
|
|
105
|
+
"content-type": "application/json"
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify({ endpoint: options.endpoint })
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const text = await response.text();
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Lakebase credential exchange failed: ${response.status} ${response.statusText}: ${text.slice(0, 500)}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const value = await response.json();
|
|
116
|
+
if (typeof value.token !== "string" || !value.token) {
|
|
117
|
+
throw new Error("Lakebase credential exchange returned no token.");
|
|
118
|
+
}
|
|
119
|
+
const expiresAt = parseExpireTime(value.expire_time);
|
|
120
|
+
if (!Number.isFinite(expiresAt)) {
|
|
121
|
+
throw new Error("Lakebase credential exchange returned an invalid expire_time.");
|
|
122
|
+
}
|
|
123
|
+
const jitter = Math.max(0, refreshJitterMs) * random();
|
|
124
|
+
cached = {
|
|
125
|
+
token: value.token,
|
|
126
|
+
refreshAt: Math.max(now(), expiresAt - Math.max(0, refreshBeforeMs) - jitter)
|
|
127
|
+
};
|
|
128
|
+
return value.token;
|
|
129
|
+
};
|
|
130
|
+
return async () => {
|
|
131
|
+
if (cached && now() < cached.refreshAt) return cached.token;
|
|
132
|
+
refreshing ??= refresh().finally(() => {
|
|
133
|
+
refreshing = void 0;
|
|
134
|
+
});
|
|
135
|
+
return refreshing;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function parseExpireTime(value) {
|
|
139
|
+
if (typeof value === "number") {
|
|
140
|
+
return value < 1e12 ? value * 1e3 : value;
|
|
141
|
+
}
|
|
142
|
+
if (typeof value === "string") return new Date(value).getTime();
|
|
143
|
+
return Number.NaN;
|
|
144
|
+
}
|
|
145
|
+
var DelegatingPool = class extends events.EventEmitter {
|
|
146
|
+
current;
|
|
147
|
+
initPromise;
|
|
148
|
+
closed = false;
|
|
149
|
+
attachErrorHandler(pool) {
|
|
150
|
+
pool.on("error", (error) => {
|
|
151
|
+
this.emit("error", error);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
initialize(createPool, getPassword) {
|
|
155
|
+
this.initPromise = getPassword().then((password) => {
|
|
156
|
+
if (!this.closed) this.swap(createPool(password));
|
|
157
|
+
}).catch((error) => {
|
|
158
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
159
|
+
throw error;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
swap(pool) {
|
|
163
|
+
if (this.closed) {
|
|
164
|
+
pool.end().catch(() => void 0);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const previous = this.current;
|
|
168
|
+
this.current = pool;
|
|
169
|
+
this.attachErrorHandler(pool);
|
|
170
|
+
if (previous) {
|
|
171
|
+
previous.removeAllListeners();
|
|
172
|
+
previous.end().catch(() => void 0);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async ensureInitialized() {
|
|
176
|
+
if (this.initPromise) await this.initPromise;
|
|
177
|
+
}
|
|
178
|
+
query(...args) {
|
|
179
|
+
return this.ensureInitialized().then(() => this.current.query(...args));
|
|
180
|
+
}
|
|
181
|
+
connect(...args) {
|
|
182
|
+
return this.ensureInitialized().then(() => this.current.connect(...args));
|
|
183
|
+
}
|
|
184
|
+
end() {
|
|
185
|
+
this.closed = true;
|
|
186
|
+
this.removeAllListeners();
|
|
187
|
+
return this.current?.end() ?? Promise.resolve();
|
|
188
|
+
}
|
|
189
|
+
get totalCount() {
|
|
190
|
+
return this.current?.totalCount ?? 0;
|
|
191
|
+
}
|
|
192
|
+
get idleCount() {
|
|
193
|
+
return this.current?.idleCount ?? 0;
|
|
194
|
+
}
|
|
195
|
+
get waitingCount() {
|
|
196
|
+
return this.current?.waitingCount ?? 0;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
var LakebaseDatabaseProvider = class {
|
|
200
|
+
kind = "databricks-lakebase";
|
|
201
|
+
options;
|
|
202
|
+
poolFactory;
|
|
203
|
+
getPassword;
|
|
204
|
+
delegatingPool;
|
|
205
|
+
refreshTimer;
|
|
206
|
+
constructor(options) {
|
|
207
|
+
this.options = options;
|
|
208
|
+
this.poolFactory = options.poolFactory ?? defaultPoolFactory();
|
|
209
|
+
this.delegatingPool = new DelegatingPool();
|
|
210
|
+
if (options.password !== void 0) {
|
|
211
|
+
this.getPassword = async () => options.password;
|
|
212
|
+
this.delegatingPool.swap(this.createPool(options.password));
|
|
213
|
+
} else {
|
|
214
|
+
if (!options.token || !options.workspaceHost || !options.endpoint) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
"Lakebase OAuth requires token, workspaceHost, and endpoint. A workspace token cannot be used directly as the Postgres password."
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
this.getPassword = lakebaseCredentialProvider({
|
|
220
|
+
workspaceHost: options.workspaceHost,
|
|
221
|
+
endpoint: options.endpoint,
|
|
222
|
+
token: options.token,
|
|
223
|
+
fetchImpl: options.fetchImpl
|
|
224
|
+
});
|
|
225
|
+
this.delegatingPool.initialize((password) => this.createPool(password), this.getPassword);
|
|
226
|
+
const intervalMs = options.refreshIntervalMs ?? 50 * 6e4;
|
|
227
|
+
this.refreshTimer = setInterval(() => {
|
|
228
|
+
void this.refreshPool();
|
|
229
|
+
}, intervalMs);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
createPool(password) {
|
|
233
|
+
return this.poolFactory({
|
|
234
|
+
host: this.options.host,
|
|
235
|
+
port: this.options.port ?? 5432,
|
|
236
|
+
database: this.options.database,
|
|
237
|
+
user: this.options.user,
|
|
238
|
+
password,
|
|
239
|
+
ssl: this.options.ssl ?? { rejectUnauthorized: true },
|
|
240
|
+
max: this.options.max ?? Number.parseInt(process.env.PG_POOL_MAX ?? "5", 10),
|
|
241
|
+
idleTimeoutMillis: this.options.idleTimeoutMillis ?? 3e4,
|
|
242
|
+
connectionTimeoutMillis: this.options.connectionTimeoutMillis ?? 5e3
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async refreshPool() {
|
|
246
|
+
try {
|
|
247
|
+
const password = await this.getPassword();
|
|
248
|
+
const pool = this.createPool(password);
|
|
249
|
+
this.delegatingPool.swap(pool);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
this.delegatingPool.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
getPool() {
|
|
255
|
+
return this.delegatingPool;
|
|
256
|
+
}
|
|
257
|
+
async close() {
|
|
258
|
+
if (this.refreshTimer) {
|
|
259
|
+
clearInterval(this.refreshTimer);
|
|
260
|
+
this.refreshTimer = void 0;
|
|
261
|
+
}
|
|
262
|
+
await this.delegatingPool.end();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
function defaultPoolFactory() {
|
|
266
|
+
return (config) => new pg.Pool(config);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/index.ts
|
|
270
|
+
var provider = null;
|
|
271
|
+
function createProvider() {
|
|
272
|
+
const kind = process.env.DATABASE_PROVIDER ?? "neon";
|
|
273
|
+
if (kind === "databricks-lakebase") {
|
|
274
|
+
const host = process.env.DATABRICKS_LAKEBASE_HOST?.trim();
|
|
275
|
+
const database = process.env.DATABRICKS_LAKEBASE_DATABASE?.trim() ?? "databricks_postgres";
|
|
276
|
+
const user = process.env.DATABRICKS_LAKEBASE_USER?.trim();
|
|
277
|
+
const workspaceHost = process.env.DATABRICKS_HOST?.trim();
|
|
278
|
+
const endpoint = process.env.DATABRICKS_LAKEBASE_ENDPOINT?.trim();
|
|
279
|
+
const password = process.env.DATABRICKS_LAKEBASE_PASSWORD?.trim();
|
|
280
|
+
if (!host) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
"DATABRICKS_LAKEBASE_HOST is required when DATABASE_PROVIDER=databricks-lakebase"
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
if (!user) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
"DATABRICKS_LAKEBASE_USER is required when DATABASE_PROVIDER=databricks-lakebase"
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (password) {
|
|
291
|
+
return new LakebaseDatabaseProvider({ host, database, user, password });
|
|
292
|
+
}
|
|
293
|
+
if (!workspaceHost || !endpoint) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
"DATABRICKS_HOST and DATABRICKS_LAKEBASE_ENDPOINT are required for Lakebase OAuth, or provide DATABRICKS_LAKEBASE_PASSWORD for native auth."
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
const principal = databricksPrincipalFromEnv(process.env);
|
|
299
|
+
return new LakebaseDatabaseProvider({
|
|
300
|
+
host,
|
|
301
|
+
database,
|
|
302
|
+
user,
|
|
303
|
+
workspaceHost,
|
|
304
|
+
endpoint,
|
|
305
|
+
token: databricksIdentity(principal)
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const connectionString = process.env.DATABASE_URL;
|
|
309
|
+
if (!connectionString) {
|
|
310
|
+
throw new Error("DATABASE_URL is required");
|
|
311
|
+
}
|
|
312
|
+
return new NeonDatabaseProvider({ connectionString });
|
|
313
|
+
}
|
|
314
|
+
function getPool() {
|
|
315
|
+
if (!provider) {
|
|
316
|
+
provider = createProvider();
|
|
317
|
+
}
|
|
318
|
+
return provider.getPool();
|
|
319
|
+
}
|
|
320
|
+
async function closePool() {
|
|
321
|
+
await provider?.close();
|
|
322
|
+
provider = null;
|
|
323
|
+
}
|
|
324
|
+
function resetPoolProvider() {
|
|
325
|
+
provider = null;
|
|
326
|
+
}
|
|
327
|
+
function getProvider() {
|
|
328
|
+
if (!provider) {
|
|
329
|
+
provider = createProvider();
|
|
330
|
+
}
|
|
331
|
+
return provider;
|
|
332
|
+
}
|
|
333
|
+
function ensurePoolGauges(collector) {
|
|
334
|
+
const reg = collector.registry;
|
|
335
|
+
const existingTotal = reg.get("db_pool_connections_total");
|
|
336
|
+
if (existingTotal) {
|
|
337
|
+
return {
|
|
338
|
+
total: existingTotal,
|
|
339
|
+
idle: reg.get("db_pool_connections_idle"),
|
|
340
|
+
waiting: reg.get("db_pool_connections_waiting"),
|
|
341
|
+
active: reg.get("db_pool_connections_active")
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
total: collector.gauge("db_pool_connections_total", "Total connections in the pool"),
|
|
346
|
+
idle: collector.gauge("db_pool_connections_idle", "Idle connections in the pool"),
|
|
347
|
+
waiting: collector.gauge("db_pool_connections_waiting", "Waiting clients in the pool"),
|
|
348
|
+
active: collector.gauge(
|
|
349
|
+
"db_pool_connections_active",
|
|
350
|
+
"Active (non-idle) connections in the pool"
|
|
351
|
+
)
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function updatePoolMetrics(collector, poolInstance) {
|
|
355
|
+
const p = poolInstance ?? getPool();
|
|
356
|
+
const total = p.totalCount ?? 0;
|
|
357
|
+
const idle = p.idleCount ?? 0;
|
|
358
|
+
const waiting = p.waitingCount ?? 0;
|
|
359
|
+
const gauges = ensurePoolGauges(collector);
|
|
360
|
+
gauges.total.set({}, total);
|
|
361
|
+
gauges.idle.set({}, idle);
|
|
362
|
+
gauges.waiting.set({}, waiting);
|
|
363
|
+
gauges.active.set({}, Math.max(0, total - idle));
|
|
364
|
+
}
|
|
365
|
+
function registerPoolMetrics(collector, poolInstance, intervalMs = 5e3) {
|
|
366
|
+
updatePoolMetrics(collector, poolInstance);
|
|
367
|
+
const interval = setInterval(() => {
|
|
368
|
+
updatePoolMetrics(collector, poolInstance);
|
|
369
|
+
}, intervalMs);
|
|
370
|
+
return () => clearInterval(interval);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
exports.closePool = closePool;
|
|
374
|
+
exports.getPool = getPool;
|
|
375
|
+
exports.getProvider = getProvider;
|
|
376
|
+
exports.registerPoolMetrics = registerPoolMetrics;
|
|
377
|
+
exports.resetPoolProvider = resetPoolProvider;
|
|
378
|
+
exports.updatePoolMetrics = updatePoolMetrics;
|
|
379
|
+
//# sourceMappingURL=index.cjs.map
|
|
380
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/neon.ts","../src/providers/lakebase.ts","../src/index.ts"],"names":["Pool","EventEmitter"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Pool } from 'pg';\nimport type { DatabaseProvider } from './types.js';\n\nexport interface NeonProviderOptions {\n connectionString: string;\n max?: number;\n idleTimeoutMillis?: number;\n connectionTimeoutMillis?: number;\n}\n\n/** Standard Postgres provider using a static connection string. */\nexport class NeonDatabaseProvider implements DatabaseProvider {\n readonly kind = 'neon' as const;\n private readonly pool: Pool;\n\n constructor(options: NeonProviderOptions) {\n this.pool = new Pool({\n connectionString: options.connectionString,\n max: options.max ?? Number.parseInt(process.env.PG_POOL_MAX ?? '5', 10),\n idleTimeoutMillis: options.idleTimeoutMillis ?? 30_000,\n connectionTimeoutMillis: options.connectionTimeoutMillis ?? 5_000,\n });\n }\n\n getPool(): Pool {\n return this.pool;\n }\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n}\n","import { EventEmitter } from 'node:events';\nimport { Pool } from 'pg';\nimport type { DatabaseProvider } from './types.js';\n\n/** A resolver that returns a valid Databricks workspace OAuth token. */\nexport type DatabricksTokenProvider = () => Promise<string>;\n\n/** Supported Databricks authentication shapes. */\nexport type DatabricksPrincipal =\n | { kind: 'pat'; token: string }\n | {\n kind: 'service-principal';\n host: string;\n clientId: string;\n clientSecret: string;\n scope?: string;\n refreshSkewSeconds?: number;\n };\n\n/** Resolve a PAT or OAuth M2M principal to a cached, auto-refreshing token provider. */\nexport function databricksIdentity(principal: DatabricksPrincipal): DatabricksTokenProvider {\n if (principal.kind === 'pat') {\n const { token } = principal;\n return async () => token;\n }\n return servicePrincipalProvider(principal);\n}\n\n/** Build a principal from environment variables. */\nexport function databricksPrincipalFromEnv(\n env: Record<string, string | undefined> = typeof process !== 'undefined' ? process.env : {},\n): Extract<DatabricksPrincipal, { kind: 'pat' | 'service-principal' }> {\n const token = env.DATABRICKS_TOKEN?.trim();\n if (token) return { kind: 'pat', token };\n\n const clientId = env.DATABRICKS_CLIENT_ID?.trim();\n const clientSecret = env.DATABRICKS_CLIENT_SECRET?.trim();\n const host = env.DATABRICKS_HOST?.trim();\n if (clientId && clientSecret) {\n if (!host) throw new Error('Missing DATABRICKS_HOST for Databricks OAuth M2M.');\n return {\n kind: 'service-principal',\n host,\n clientId,\n clientSecret,\n scope: env.DATABRICKS_SCOPE,\n refreshSkewSeconds: env.DATABRICKS_REFRESH_SKEW_SECONDS\n ? Number.parseInt(env.DATABRICKS_REFRESH_SKEW_SECONDS, 10)\n : undefined,\n };\n }\n throw new Error(\n 'Missing Databricks credentials: DATABRICKS_TOKEN or DATABRICKS_CLIENT_ID+DATABRICKS_CLIENT_SECRET.',\n );\n}\n\ninterface CachedToken {\n token: string;\n refreshAtMs: number;\n}\n\nfunction servicePrincipalProvider(\n principal: Extract<DatabricksPrincipal, { kind: 'service-principal' }>,\n): DatabricksTokenProvider {\n const host = principal.host.replace(/\\/$/, '');\n const scope = principal.scope ?? 'all-apis';\n const refreshSkewMs = (principal.refreshSkewSeconds ?? 60) * 1_000;\n let cached: CachedToken | undefined;\n let refreshing: Promise<string> | undefined;\n\n const refresh = async (): Promise<string> => {\n const response = await fetch(`${host}/oidc/v1/token`, {\n method: 'POST',\n headers: {\n authorization: `Basic ${Buffer.from(`${principal.clientId}:${principal.clientSecret}`).toString('base64')}`,\n 'content-type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({ grant_type: 'client_credentials', scope }),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `Databricks OAuth token exchange failed: ${response.status} ${response.statusText}: ${text.slice(0, 500)}`,\n );\n }\n const value = (await response.json()) as { access_token?: unknown; expires_in?: unknown };\n const token = typeof value.access_token === 'string' ? value.access_token : undefined;\n if (!token) throw new Error('Databricks OAuth token exchange returned no access_token.');\n const expiresInSeconds = typeof value.expires_in === 'number' ? value.expires_in : 3_600;\n const expiresAtMs = Date.now() + expiresInSeconds * 1_000;\n cached = { token, refreshAtMs: expiresAtMs - refreshSkewMs };\n return token;\n };\n\n return async () => {\n if (cached && Date.now() < cached.refreshAtMs) return cached.token;\n refreshing ??= refresh().finally(() => {\n refreshing = undefined;\n });\n return refreshing;\n };\n}\n\nexport interface LakebaseCredentialProviderOptions {\n workspaceHost: string;\n endpoint: string;\n token: string | DatabricksTokenProvider;\n fetchImpl?: typeof fetch;\n refreshBeforeMs?: number;\n refreshJitterMs?: number;\n now?: () => number;\n random?: () => number;\n}\n\ninterface LakebaseCredentialResponse {\n token?: unknown;\n expire_time?: unknown;\n}\n\n/** Exchange a workspace OAuth token for a Lakebase database credential. Caches, refreshes early, single-flight. */\nexport function lakebaseCredentialProvider(\n options: LakebaseCredentialProviderOptions,\n): () => Promise<string> {\n const workspaceHost = options.workspaceHost.replace(/\\/$/, '');\n const fetchImpl = options.fetchImpl ?? fetch;\n const now = options.now ?? Date.now;\n const random = options.random ?? Math.random;\n const refreshBeforeMs = options.refreshBeforeMs ?? 5 * 60_000;\n const refreshJitterMs = options.refreshJitterMs ?? 30_000;\n let cached: { token: string; refreshAt: number } | undefined;\n let refreshing: Promise<string> | undefined;\n\n const refresh = async (): Promise<string> => {\n const workspaceToken =\n typeof options.token === 'function' ? await options.token() : options.token;\n const response = await fetchImpl(`${workspaceHost}/api/2.0/postgres/credentials`, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${workspaceToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ endpoint: options.endpoint }),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `Lakebase credential exchange failed: ${response.status} ${response.statusText}: ${text.slice(0, 500)}`,\n );\n }\n const value = (await response.json()) as LakebaseCredentialResponse;\n if (typeof value.token !== 'string' || !value.token) {\n throw new Error('Lakebase credential exchange returned no token.');\n }\n const expiresAt = parseExpireTime(value.expire_time);\n if (!Number.isFinite(expiresAt)) {\n throw new Error('Lakebase credential exchange returned an invalid expire_time.');\n }\n const jitter = Math.max(0, refreshJitterMs) * random();\n cached = {\n token: value.token,\n refreshAt: Math.max(now(), expiresAt - Math.max(0, refreshBeforeMs) - jitter),\n };\n return value.token;\n };\n\n return async () => {\n if (cached && now() < cached.refreshAt) return cached.token;\n refreshing ??= refresh().finally(() => {\n refreshing = undefined;\n });\n return refreshing;\n };\n}\n\nfunction parseExpireTime(value: unknown): number {\n if (typeof value === 'number') {\n return value < 1_000_000_000_000 ? value * 1_000 : value;\n }\n if (typeof value === 'string') return new Date(value).getTime();\n return Number.NaN;\n}\n\nexport interface LakebasePoolConfig {\n host: string;\n port: number;\n database: string;\n user: string;\n password?: string;\n ssl?: boolean | { rejectUnauthorized?: boolean };\n max?: number;\n idleTimeoutMillis?: number;\n connectionTimeoutMillis?: number;\n}\n\nexport type LakebasePoolFactory = (config: LakebasePoolConfig) => Pool;\n\nexport interface LakebaseProviderOptions {\n /** Direct endpoint host from list-endpoints (NOT the pooled host). */\n host: string;\n port?: number;\n database: string;\n user: string;\n /** Static database password. If omitted, OAuth exchange is required. */\n password?: string;\n /** Workspace URL, e.g. https://adb-....azuredatabricks.net */\n workspaceHost?: string;\n /** Full endpoint resource name: projects/.../branches/.../endpoints/... */\n endpoint?: string;\n /** Workspace OAuth token or provider. */\n token?: string | DatabricksTokenProvider;\n fetchImpl?: typeof fetch;\n ssl?: boolean | { rejectUnauthorized?: boolean };\n max?: number;\n idleTimeoutMillis?: number;\n connectionTimeoutMillis?: number;\n /** How often to refresh the credential and recreate the pool. Default 50 minutes. */\n refreshIntervalMs?: number;\n /** Injected pool factory for tests. */\n poolFactory?: LakebasePoolFactory;\n /** Observe recoverable idle pooled-connection failures. */\n onPoolError?: (error: Error) => void;\n}\n\n/** A Pool-like facade that lazily initializes and always delegates to the current underlying pool. */\nclass DelegatingPool extends EventEmitter {\n private current: Pool | undefined;\n private initPromise: Promise<void> | undefined;\n private closed = false;\n\n private attachErrorHandler(pool: Pool) {\n pool.on('error', (error) => {\n this.emit('error', error);\n });\n }\n\n initialize(createPool: (password: string) => Pool, getPassword: () => Promise<string>) {\n this.initPromise = getPassword()\n .then((password) => {\n if (!this.closed) this.swap(createPool(password));\n })\n .catch((error) => {\n this.emit('error', error instanceof Error ? error : new Error(String(error)));\n throw error;\n });\n }\n\n swap(pool: Pool) {\n if (this.closed) {\n pool.end().catch(() => undefined);\n return;\n }\n const previous = this.current;\n this.current = pool;\n this.attachErrorHandler(pool);\n if (previous) {\n previous.removeAllListeners();\n previous.end().catch(() => undefined);\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) await this.initPromise;\n }\n\n query(...args: Parameters<Pool['query']>): Promise<unknown> {\n return this.ensureInitialized().then(() => this.current!.query(...args));\n }\n\n connect(...args: Parameters<Pool['connect']>): Promise<unknown> {\n return this.ensureInitialized().then(() => this.current!.connect(...args));\n }\n\n end(): Promise<void> {\n this.closed = true;\n this.removeAllListeners();\n return this.current?.end() ?? Promise.resolve();\n }\n\n get totalCount() {\n return this.current?.totalCount ?? 0;\n }\n\n get idleCount() {\n return this.current?.idleCount ?? 0;\n }\n\n get waitingCount() {\n return this.current?.waitingCount ?? 0;\n }\n}\n\n/** Databricks Lakebase Postgres provider with credential refresh. */\nexport class LakebaseDatabaseProvider implements DatabaseProvider {\n readonly kind = 'databricks-lakebase' as const;\n private readonly options: LakebaseProviderOptions;\n private readonly poolFactory: LakebasePoolFactory;\n private readonly getPassword: () => Promise<string>;\n private readonly delegatingPool: DelegatingPool;\n private refreshTimer: NodeJS.Timeout | undefined;\n\n constructor(options: LakebaseProviderOptions) {\n this.options = options;\n this.poolFactory = options.poolFactory ?? defaultPoolFactory();\n this.delegatingPool = new DelegatingPool();\n if (options.password !== undefined) {\n this.getPassword = async () => options.password as string;\n this.delegatingPool.swap(this.createPool(options.password));\n } else {\n if (!options.token || !options.workspaceHost || !options.endpoint) {\n throw new Error(\n 'Lakebase OAuth requires token, workspaceHost, and endpoint. A workspace token cannot be used directly as the Postgres password.',\n );\n }\n this.getPassword = lakebaseCredentialProvider({\n workspaceHost: options.workspaceHost,\n endpoint: options.endpoint,\n token: options.token,\n fetchImpl: options.fetchImpl,\n });\n this.delegatingPool.initialize((password) => this.createPool(password), this.getPassword);\n const intervalMs = options.refreshIntervalMs ?? 50 * 60_000;\n this.refreshTimer = setInterval(() => {\n void this.refreshPool();\n }, intervalMs);\n }\n }\n\n private createPool(password?: string): Pool {\n return this.poolFactory({\n host: this.options.host,\n port: this.options.port ?? 5432,\n database: this.options.database,\n user: this.options.user,\n password,\n ssl: this.options.ssl ?? { rejectUnauthorized: true },\n max: this.options.max ?? Number.parseInt(process.env.PG_POOL_MAX ?? '5', 10),\n idleTimeoutMillis: this.options.idleTimeoutMillis ?? 30_000,\n connectionTimeoutMillis: this.options.connectionTimeoutMillis ?? 5_000,\n });\n }\n\n private async refreshPool(): Promise<void> {\n try {\n const password = await this.getPassword();\n const pool = this.createPool(password);\n this.delegatingPool.swap(pool);\n } catch (error) {\n this.delegatingPool.emit('error', error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n getPool(): Pool {\n return this.delegatingPool as unknown as Pool;\n }\n\n async close(): Promise<void> {\n if (this.refreshTimer) {\n clearInterval(this.refreshTimer);\n this.refreshTimer = undefined;\n }\n await this.delegatingPool.end();\n }\n}\n\nfunction defaultPoolFactory(): LakebasePoolFactory {\n return (config) => new Pool(config);\n}\n","import type { Gauge, MetricsCollector } from '@fabricorg/experiments-metrics';\nimport type { Pool } from 'pg';\nimport {\n type DatabaseProvider,\n LakebaseDatabaseProvider,\n NeonDatabaseProvider,\n databricksIdentity,\n databricksPrincipalFromEnv,\n} from './providers/index.js';\n\nlet provider: DatabaseProvider | null = null;\n\nfunction createProvider(): DatabaseProvider {\n const kind = process.env.DATABASE_PROVIDER ?? 'neon';\n\n if (kind === 'databricks-lakebase') {\n const host = process.env.DATABRICKS_LAKEBASE_HOST?.trim();\n const database = process.env.DATABRICKS_LAKEBASE_DATABASE?.trim() ?? 'databricks_postgres';\n const user = process.env.DATABRICKS_LAKEBASE_USER?.trim();\n const workspaceHost = process.env.DATABRICKS_HOST?.trim();\n const endpoint = process.env.DATABRICKS_LAKEBASE_ENDPOINT?.trim();\n const password = process.env.DATABRICKS_LAKEBASE_PASSWORD?.trim();\n\n if (!host) {\n throw new Error(\n 'DATABRICKS_LAKEBASE_HOST is required when DATABASE_PROVIDER=databricks-lakebase',\n );\n }\n if (!user) {\n throw new Error(\n 'DATABRICKS_LAKEBASE_USER is required when DATABASE_PROVIDER=databricks-lakebase',\n );\n }\n\n if (password) {\n return new LakebaseDatabaseProvider({ host, database, user, password });\n }\n\n if (!workspaceHost || !endpoint) {\n throw new Error(\n 'DATABRICKS_HOST and DATABRICKS_LAKEBASE_ENDPOINT are required for Lakebase OAuth, or provide DATABRICKS_LAKEBASE_PASSWORD for native auth.',\n );\n }\n\n const principal = databricksPrincipalFromEnv(process.env);\n return new LakebaseDatabaseProvider({\n host,\n database,\n user,\n workspaceHost,\n endpoint,\n token: databricksIdentity(principal),\n });\n }\n\n const connectionString = process.env.DATABASE_URL;\n if (!connectionString) {\n throw new Error('DATABASE_URL is required');\n }\n return new NeonDatabaseProvider({ connectionString });\n}\n\n/**\n * Return a singleton `pg.Pool` configured for the active database provider.\n *\n * Provider selection:\n * - `DATABASE_PROVIDER=neon` (default): uses `DATABASE_URL`.\n * - `DATABASE_PROVIDER=databricks-lakebase`: uses Lakebase OAuth credentials.\n *\n * On the first call, a new Pool is created with the following defaults:\n * - `max`: parseInt(process.env.PG_POOL_MAX ?? '5') — caps concurrent connections.\n * - `idleTimeoutMillis`: 30000 — recycle idle connections before Vercel's 60s timeout.\n * - `connectionTimeoutMillis`: 5000 — fail fast when the database is unreachable.\n */\nexport function getPool(): Pool {\n if (!provider) {\n provider = createProvider();\n }\n return provider.getPool();\n}\n\n/** Close the active provider and any background refresh loops. */\nexport async function closePool(): Promise<void> {\n await provider?.close();\n provider = null;\n}\n\n/** Reset the singleton provider. Intended for tests. */\nexport function resetPoolProvider(): void {\n provider = null;\n}\n\n/** Return the active provider, or create it. */\nexport function getProvider(): DatabaseProvider {\n if (!provider) {\n provider = createProvider();\n }\n return provider;\n}\n\ninterface PoolGauges {\n total: Gauge;\n idle: Gauge;\n waiting: Gauge;\n active: Gauge;\n}\n\nfunction ensurePoolGauges(collector: MetricsCollector): PoolGauges {\n const reg = collector.registry;\n const existingTotal = reg.get('db_pool_connections_total');\n if (existingTotal) {\n return {\n total: existingTotal as Gauge,\n idle: reg.get('db_pool_connections_idle') as Gauge,\n waiting: reg.get('db_pool_connections_waiting') as Gauge,\n active: reg.get('db_pool_connections_active') as Gauge,\n };\n }\n\n return {\n total: collector.gauge('db_pool_connections_total', 'Total connections in the pool'),\n idle: collector.gauge('db_pool_connections_idle', 'Idle connections in the pool'),\n waiting: collector.gauge('db_pool_connections_waiting', 'Waiting clients in the pool'),\n active: collector.gauge(\n 'db_pool_connections_active',\n 'Active (non-idle) connections in the pool',\n ),\n };\n}\n\n/** Update pool gauge values from the current pool state. */\nexport function updatePoolMetrics(collector: MetricsCollector, poolInstance?: Pool): void {\n const p = poolInstance ?? getPool();\n const total = (p as unknown as { totalCount: number }).totalCount ?? 0;\n const idle = p.idleCount ?? 0;\n const waiting = p.waitingCount ?? 0;\n\n const gauges = ensurePoolGauges(collector);\n gauges.total.set({}, total);\n gauges.idle.set({}, idle);\n gauges.waiting.set({}, waiting);\n gauges.active.set({}, Math.max(0, total - idle));\n}\n\n/**\n * Register pool metrics with the given collector.\n *\n * Returns a cleanup function that stops the update interval.\n */\nexport function registerPoolMetrics(\n collector: MetricsCollector,\n poolInstance?: Pool,\n intervalMs = 5000,\n): () => void {\n updatePoolMetrics(collector, poolInstance);\n\n const interval = setInterval(() => {\n updatePoolMetrics(collector, poolInstance);\n }, intervalMs);\n\n return () => clearInterval(interval);\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MetricsCollector } from '@fabricorg/experiments-metrics';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
|
|
4
|
+
/** Backend selection for the control-plane database. */
|
|
5
|
+
interface DatabaseProvider {
|
|
6
|
+
readonly kind: 'neon' | 'databricks-lakebase';
|
|
7
|
+
/** Return the active connection pool. Callers should not cache the returned instance. */
|
|
8
|
+
getPool(): Pool;
|
|
9
|
+
/** Close the provider and any background refresh loops. */
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Return a singleton `pg.Pool` configured for the active database provider.
|
|
15
|
+
*
|
|
16
|
+
* Provider selection:
|
|
17
|
+
* - `DATABASE_PROVIDER=neon` (default): uses `DATABASE_URL`.
|
|
18
|
+
* - `DATABASE_PROVIDER=databricks-lakebase`: uses Lakebase OAuth credentials.
|
|
19
|
+
*
|
|
20
|
+
* On the first call, a new Pool is created with the following defaults:
|
|
21
|
+
* - `max`: parseInt(process.env.PG_POOL_MAX ?? '5') — caps concurrent connections.
|
|
22
|
+
* - `idleTimeoutMillis`: 30000 — recycle idle connections before Vercel's 60s timeout.
|
|
23
|
+
* - `connectionTimeoutMillis`: 5000 — fail fast when the database is unreachable.
|
|
24
|
+
*/
|
|
25
|
+
declare function getPool(): Pool;
|
|
26
|
+
/** Close the active provider and any background refresh loops. */
|
|
27
|
+
declare function closePool(): Promise<void>;
|
|
28
|
+
/** Reset the singleton provider. Intended for tests. */
|
|
29
|
+
declare function resetPoolProvider(): void;
|
|
30
|
+
/** Return the active provider, or create it. */
|
|
31
|
+
declare function getProvider(): DatabaseProvider;
|
|
32
|
+
/** Update pool gauge values from the current pool state. */
|
|
33
|
+
declare function updatePoolMetrics(collector: MetricsCollector, poolInstance?: Pool): void;
|
|
34
|
+
/**
|
|
35
|
+
* Register pool metrics with the given collector.
|
|
36
|
+
*
|
|
37
|
+
* Returns a cleanup function that stops the update interval.
|
|
38
|
+
*/
|
|
39
|
+
declare function registerPoolMetrics(collector: MetricsCollector, poolInstance?: Pool, intervalMs?: number): () => void;
|
|
40
|
+
|
|
41
|
+
export { closePool, getPool, getProvider, registerPoolMetrics, resetPoolProvider, updatePoolMetrics };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MetricsCollector } from '@fabricorg/experiments-metrics';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
|
|
4
|
+
/** Backend selection for the control-plane database. */
|
|
5
|
+
interface DatabaseProvider {
|
|
6
|
+
readonly kind: 'neon' | 'databricks-lakebase';
|
|
7
|
+
/** Return the active connection pool. Callers should not cache the returned instance. */
|
|
8
|
+
getPool(): Pool;
|
|
9
|
+
/** Close the provider and any background refresh loops. */
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Return a singleton `pg.Pool` configured for the active database provider.
|
|
15
|
+
*
|
|
16
|
+
* Provider selection:
|
|
17
|
+
* - `DATABASE_PROVIDER=neon` (default): uses `DATABASE_URL`.
|
|
18
|
+
* - `DATABASE_PROVIDER=databricks-lakebase`: uses Lakebase OAuth credentials.
|
|
19
|
+
*
|
|
20
|
+
* On the first call, a new Pool is created with the following defaults:
|
|
21
|
+
* - `max`: parseInt(process.env.PG_POOL_MAX ?? '5') — caps concurrent connections.
|
|
22
|
+
* - `idleTimeoutMillis`: 30000 — recycle idle connections before Vercel's 60s timeout.
|
|
23
|
+
* - `connectionTimeoutMillis`: 5000 — fail fast when the database is unreachable.
|
|
24
|
+
*/
|
|
25
|
+
declare function getPool(): Pool;
|
|
26
|
+
/** Close the active provider and any background refresh loops. */
|
|
27
|
+
declare function closePool(): Promise<void>;
|
|
28
|
+
/** Reset the singleton provider. Intended for tests. */
|
|
29
|
+
declare function resetPoolProvider(): void;
|
|
30
|
+
/** Return the active provider, or create it. */
|
|
31
|
+
declare function getProvider(): DatabaseProvider;
|
|
32
|
+
/** Update pool gauge values from the current pool state. */
|
|
33
|
+
declare function updatePoolMetrics(collector: MetricsCollector, poolInstance?: Pool): void;
|
|
34
|
+
/**
|
|
35
|
+
* Register pool metrics with the given collector.
|
|
36
|
+
*
|
|
37
|
+
* Returns a cleanup function that stops the update interval.
|
|
38
|
+
*/
|
|
39
|
+
declare function registerPoolMetrics(collector: MetricsCollector, poolInstance?: Pool, intervalMs?: number): () => void;
|
|
40
|
+
|
|
41
|
+
export { closePool, getPool, getProvider, registerPoolMetrics, resetPoolProvider, updatePoolMetrics };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
|
|
4
|
+
// src/providers/neon.ts
|
|
5
|
+
var NeonDatabaseProvider = class {
|
|
6
|
+
kind = "neon";
|
|
7
|
+
pool;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.pool = new Pool({
|
|
10
|
+
connectionString: options.connectionString,
|
|
11
|
+
max: options.max ?? Number.parseInt(process.env.PG_POOL_MAX ?? "5", 10),
|
|
12
|
+
idleTimeoutMillis: options.idleTimeoutMillis ?? 3e4,
|
|
13
|
+
connectionTimeoutMillis: options.connectionTimeoutMillis ?? 5e3
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
getPool() {
|
|
17
|
+
return this.pool;
|
|
18
|
+
}
|
|
19
|
+
async close() {
|
|
20
|
+
await this.pool.end();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function databricksIdentity(principal) {
|
|
24
|
+
if (principal.kind === "pat") {
|
|
25
|
+
const { token } = principal;
|
|
26
|
+
return async () => token;
|
|
27
|
+
}
|
|
28
|
+
return servicePrincipalProvider(principal);
|
|
29
|
+
}
|
|
30
|
+
function databricksPrincipalFromEnv(env = typeof process !== "undefined" ? process.env : {}) {
|
|
31
|
+
const token = env.DATABRICKS_TOKEN?.trim();
|
|
32
|
+
if (token) return { kind: "pat", token };
|
|
33
|
+
const clientId = env.DATABRICKS_CLIENT_ID?.trim();
|
|
34
|
+
const clientSecret = env.DATABRICKS_CLIENT_SECRET?.trim();
|
|
35
|
+
const host = env.DATABRICKS_HOST?.trim();
|
|
36
|
+
if (clientId && clientSecret) {
|
|
37
|
+
if (!host) throw new Error("Missing DATABRICKS_HOST for Databricks OAuth M2M.");
|
|
38
|
+
return {
|
|
39
|
+
kind: "service-principal",
|
|
40
|
+
host,
|
|
41
|
+
clientId,
|
|
42
|
+
clientSecret,
|
|
43
|
+
scope: env.DATABRICKS_SCOPE,
|
|
44
|
+
refreshSkewSeconds: env.DATABRICKS_REFRESH_SKEW_SECONDS ? Number.parseInt(env.DATABRICKS_REFRESH_SKEW_SECONDS, 10) : void 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
throw new Error(
|
|
48
|
+
"Missing Databricks credentials: DATABRICKS_TOKEN or DATABRICKS_CLIENT_ID+DATABRICKS_CLIENT_SECRET."
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function servicePrincipalProvider(principal) {
|
|
52
|
+
const host = principal.host.replace(/\/$/, "");
|
|
53
|
+
const scope = principal.scope ?? "all-apis";
|
|
54
|
+
const refreshSkewMs = (principal.refreshSkewSeconds ?? 60) * 1e3;
|
|
55
|
+
let cached;
|
|
56
|
+
let refreshing;
|
|
57
|
+
const refresh = async () => {
|
|
58
|
+
const response = await fetch(`${host}/oidc/v1/token`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
authorization: `Basic ${Buffer.from(`${principal.clientId}:${principal.clientSecret}`).toString("base64")}`,
|
|
62
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
63
|
+
},
|
|
64
|
+
body: new URLSearchParams({ grant_type: "client_credentials", scope })
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const text = await response.text();
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Databricks OAuth token exchange failed: ${response.status} ${response.statusText}: ${text.slice(0, 500)}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const value = await response.json();
|
|
73
|
+
const token = typeof value.access_token === "string" ? value.access_token : void 0;
|
|
74
|
+
if (!token) throw new Error("Databricks OAuth token exchange returned no access_token.");
|
|
75
|
+
const expiresInSeconds = typeof value.expires_in === "number" ? value.expires_in : 3600;
|
|
76
|
+
const expiresAtMs = Date.now() + expiresInSeconds * 1e3;
|
|
77
|
+
cached = { token, refreshAtMs: expiresAtMs - refreshSkewMs };
|
|
78
|
+
return token;
|
|
79
|
+
};
|
|
80
|
+
return async () => {
|
|
81
|
+
if (cached && Date.now() < cached.refreshAtMs) return cached.token;
|
|
82
|
+
refreshing ??= refresh().finally(() => {
|
|
83
|
+
refreshing = void 0;
|
|
84
|
+
});
|
|
85
|
+
return refreshing;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function lakebaseCredentialProvider(options) {
|
|
89
|
+
const workspaceHost = options.workspaceHost.replace(/\/$/, "");
|
|
90
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
91
|
+
const now = options.now ?? Date.now;
|
|
92
|
+
const random = options.random ?? Math.random;
|
|
93
|
+
const refreshBeforeMs = options.refreshBeforeMs ?? 5 * 6e4;
|
|
94
|
+
const refreshJitterMs = options.refreshJitterMs ?? 3e4;
|
|
95
|
+
let cached;
|
|
96
|
+
let refreshing;
|
|
97
|
+
const refresh = async () => {
|
|
98
|
+
const workspaceToken = typeof options.token === "function" ? await options.token() : options.token;
|
|
99
|
+
const response = await fetchImpl(`${workspaceHost}/api/2.0/postgres/credentials`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
authorization: `Bearer ${workspaceToken}`,
|
|
103
|
+
"content-type": "application/json"
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify({ endpoint: options.endpoint })
|
|
106
|
+
});
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const text = await response.text();
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Lakebase credential exchange failed: ${response.status} ${response.statusText}: ${text.slice(0, 500)}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const value = await response.json();
|
|
114
|
+
if (typeof value.token !== "string" || !value.token) {
|
|
115
|
+
throw new Error("Lakebase credential exchange returned no token.");
|
|
116
|
+
}
|
|
117
|
+
const expiresAt = parseExpireTime(value.expire_time);
|
|
118
|
+
if (!Number.isFinite(expiresAt)) {
|
|
119
|
+
throw new Error("Lakebase credential exchange returned an invalid expire_time.");
|
|
120
|
+
}
|
|
121
|
+
const jitter = Math.max(0, refreshJitterMs) * random();
|
|
122
|
+
cached = {
|
|
123
|
+
token: value.token,
|
|
124
|
+
refreshAt: Math.max(now(), expiresAt - Math.max(0, refreshBeforeMs) - jitter)
|
|
125
|
+
};
|
|
126
|
+
return value.token;
|
|
127
|
+
};
|
|
128
|
+
return async () => {
|
|
129
|
+
if (cached && now() < cached.refreshAt) return cached.token;
|
|
130
|
+
refreshing ??= refresh().finally(() => {
|
|
131
|
+
refreshing = void 0;
|
|
132
|
+
});
|
|
133
|
+
return refreshing;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function parseExpireTime(value) {
|
|
137
|
+
if (typeof value === "number") {
|
|
138
|
+
return value < 1e12 ? value * 1e3 : value;
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === "string") return new Date(value).getTime();
|
|
141
|
+
return Number.NaN;
|
|
142
|
+
}
|
|
143
|
+
var DelegatingPool = class extends EventEmitter {
|
|
144
|
+
current;
|
|
145
|
+
initPromise;
|
|
146
|
+
closed = false;
|
|
147
|
+
attachErrorHandler(pool) {
|
|
148
|
+
pool.on("error", (error) => {
|
|
149
|
+
this.emit("error", error);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
initialize(createPool, getPassword) {
|
|
153
|
+
this.initPromise = getPassword().then((password) => {
|
|
154
|
+
if (!this.closed) this.swap(createPool(password));
|
|
155
|
+
}).catch((error) => {
|
|
156
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
157
|
+
throw error;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
swap(pool) {
|
|
161
|
+
if (this.closed) {
|
|
162
|
+
pool.end().catch(() => void 0);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const previous = this.current;
|
|
166
|
+
this.current = pool;
|
|
167
|
+
this.attachErrorHandler(pool);
|
|
168
|
+
if (previous) {
|
|
169
|
+
previous.removeAllListeners();
|
|
170
|
+
previous.end().catch(() => void 0);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async ensureInitialized() {
|
|
174
|
+
if (this.initPromise) await this.initPromise;
|
|
175
|
+
}
|
|
176
|
+
query(...args) {
|
|
177
|
+
return this.ensureInitialized().then(() => this.current.query(...args));
|
|
178
|
+
}
|
|
179
|
+
connect(...args) {
|
|
180
|
+
return this.ensureInitialized().then(() => this.current.connect(...args));
|
|
181
|
+
}
|
|
182
|
+
end() {
|
|
183
|
+
this.closed = true;
|
|
184
|
+
this.removeAllListeners();
|
|
185
|
+
return this.current?.end() ?? Promise.resolve();
|
|
186
|
+
}
|
|
187
|
+
get totalCount() {
|
|
188
|
+
return this.current?.totalCount ?? 0;
|
|
189
|
+
}
|
|
190
|
+
get idleCount() {
|
|
191
|
+
return this.current?.idleCount ?? 0;
|
|
192
|
+
}
|
|
193
|
+
get waitingCount() {
|
|
194
|
+
return this.current?.waitingCount ?? 0;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
var LakebaseDatabaseProvider = class {
|
|
198
|
+
kind = "databricks-lakebase";
|
|
199
|
+
options;
|
|
200
|
+
poolFactory;
|
|
201
|
+
getPassword;
|
|
202
|
+
delegatingPool;
|
|
203
|
+
refreshTimer;
|
|
204
|
+
constructor(options) {
|
|
205
|
+
this.options = options;
|
|
206
|
+
this.poolFactory = options.poolFactory ?? defaultPoolFactory();
|
|
207
|
+
this.delegatingPool = new DelegatingPool();
|
|
208
|
+
if (options.password !== void 0) {
|
|
209
|
+
this.getPassword = async () => options.password;
|
|
210
|
+
this.delegatingPool.swap(this.createPool(options.password));
|
|
211
|
+
} else {
|
|
212
|
+
if (!options.token || !options.workspaceHost || !options.endpoint) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
"Lakebase OAuth requires token, workspaceHost, and endpoint. A workspace token cannot be used directly as the Postgres password."
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
this.getPassword = lakebaseCredentialProvider({
|
|
218
|
+
workspaceHost: options.workspaceHost,
|
|
219
|
+
endpoint: options.endpoint,
|
|
220
|
+
token: options.token,
|
|
221
|
+
fetchImpl: options.fetchImpl
|
|
222
|
+
});
|
|
223
|
+
this.delegatingPool.initialize((password) => this.createPool(password), this.getPassword);
|
|
224
|
+
const intervalMs = options.refreshIntervalMs ?? 50 * 6e4;
|
|
225
|
+
this.refreshTimer = setInterval(() => {
|
|
226
|
+
void this.refreshPool();
|
|
227
|
+
}, intervalMs);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
createPool(password) {
|
|
231
|
+
return this.poolFactory({
|
|
232
|
+
host: this.options.host,
|
|
233
|
+
port: this.options.port ?? 5432,
|
|
234
|
+
database: this.options.database,
|
|
235
|
+
user: this.options.user,
|
|
236
|
+
password,
|
|
237
|
+
ssl: this.options.ssl ?? { rejectUnauthorized: true },
|
|
238
|
+
max: this.options.max ?? Number.parseInt(process.env.PG_POOL_MAX ?? "5", 10),
|
|
239
|
+
idleTimeoutMillis: this.options.idleTimeoutMillis ?? 3e4,
|
|
240
|
+
connectionTimeoutMillis: this.options.connectionTimeoutMillis ?? 5e3
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async refreshPool() {
|
|
244
|
+
try {
|
|
245
|
+
const password = await this.getPassword();
|
|
246
|
+
const pool = this.createPool(password);
|
|
247
|
+
this.delegatingPool.swap(pool);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
this.delegatingPool.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
getPool() {
|
|
253
|
+
return this.delegatingPool;
|
|
254
|
+
}
|
|
255
|
+
async close() {
|
|
256
|
+
if (this.refreshTimer) {
|
|
257
|
+
clearInterval(this.refreshTimer);
|
|
258
|
+
this.refreshTimer = void 0;
|
|
259
|
+
}
|
|
260
|
+
await this.delegatingPool.end();
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
function defaultPoolFactory() {
|
|
264
|
+
return (config) => new Pool(config);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/index.ts
|
|
268
|
+
var provider = null;
|
|
269
|
+
function createProvider() {
|
|
270
|
+
const kind = process.env.DATABASE_PROVIDER ?? "neon";
|
|
271
|
+
if (kind === "databricks-lakebase") {
|
|
272
|
+
const host = process.env.DATABRICKS_LAKEBASE_HOST?.trim();
|
|
273
|
+
const database = process.env.DATABRICKS_LAKEBASE_DATABASE?.trim() ?? "databricks_postgres";
|
|
274
|
+
const user = process.env.DATABRICKS_LAKEBASE_USER?.trim();
|
|
275
|
+
const workspaceHost = process.env.DATABRICKS_HOST?.trim();
|
|
276
|
+
const endpoint = process.env.DATABRICKS_LAKEBASE_ENDPOINT?.trim();
|
|
277
|
+
const password = process.env.DATABRICKS_LAKEBASE_PASSWORD?.trim();
|
|
278
|
+
if (!host) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
"DATABRICKS_LAKEBASE_HOST is required when DATABASE_PROVIDER=databricks-lakebase"
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
if (!user) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
"DATABRICKS_LAKEBASE_USER is required when DATABASE_PROVIDER=databricks-lakebase"
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (password) {
|
|
289
|
+
return new LakebaseDatabaseProvider({ host, database, user, password });
|
|
290
|
+
}
|
|
291
|
+
if (!workspaceHost || !endpoint) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
"DATABRICKS_HOST and DATABRICKS_LAKEBASE_ENDPOINT are required for Lakebase OAuth, or provide DATABRICKS_LAKEBASE_PASSWORD for native auth."
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
const principal = databricksPrincipalFromEnv(process.env);
|
|
297
|
+
return new LakebaseDatabaseProvider({
|
|
298
|
+
host,
|
|
299
|
+
database,
|
|
300
|
+
user,
|
|
301
|
+
workspaceHost,
|
|
302
|
+
endpoint,
|
|
303
|
+
token: databricksIdentity(principal)
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const connectionString = process.env.DATABASE_URL;
|
|
307
|
+
if (!connectionString) {
|
|
308
|
+
throw new Error("DATABASE_URL is required");
|
|
309
|
+
}
|
|
310
|
+
return new NeonDatabaseProvider({ connectionString });
|
|
311
|
+
}
|
|
312
|
+
function getPool() {
|
|
313
|
+
if (!provider) {
|
|
314
|
+
provider = createProvider();
|
|
315
|
+
}
|
|
316
|
+
return provider.getPool();
|
|
317
|
+
}
|
|
318
|
+
async function closePool() {
|
|
319
|
+
await provider?.close();
|
|
320
|
+
provider = null;
|
|
321
|
+
}
|
|
322
|
+
function resetPoolProvider() {
|
|
323
|
+
provider = null;
|
|
324
|
+
}
|
|
325
|
+
function getProvider() {
|
|
326
|
+
if (!provider) {
|
|
327
|
+
provider = createProvider();
|
|
328
|
+
}
|
|
329
|
+
return provider;
|
|
330
|
+
}
|
|
331
|
+
function ensurePoolGauges(collector) {
|
|
332
|
+
const reg = collector.registry;
|
|
333
|
+
const existingTotal = reg.get("db_pool_connections_total");
|
|
334
|
+
if (existingTotal) {
|
|
335
|
+
return {
|
|
336
|
+
total: existingTotal,
|
|
337
|
+
idle: reg.get("db_pool_connections_idle"),
|
|
338
|
+
waiting: reg.get("db_pool_connections_waiting"),
|
|
339
|
+
active: reg.get("db_pool_connections_active")
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
total: collector.gauge("db_pool_connections_total", "Total connections in the pool"),
|
|
344
|
+
idle: collector.gauge("db_pool_connections_idle", "Idle connections in the pool"),
|
|
345
|
+
waiting: collector.gauge("db_pool_connections_waiting", "Waiting clients in the pool"),
|
|
346
|
+
active: collector.gauge(
|
|
347
|
+
"db_pool_connections_active",
|
|
348
|
+
"Active (non-idle) connections in the pool"
|
|
349
|
+
)
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function updatePoolMetrics(collector, poolInstance) {
|
|
353
|
+
const p = poolInstance ?? getPool();
|
|
354
|
+
const total = p.totalCount ?? 0;
|
|
355
|
+
const idle = p.idleCount ?? 0;
|
|
356
|
+
const waiting = p.waitingCount ?? 0;
|
|
357
|
+
const gauges = ensurePoolGauges(collector);
|
|
358
|
+
gauges.total.set({}, total);
|
|
359
|
+
gauges.idle.set({}, idle);
|
|
360
|
+
gauges.waiting.set({}, waiting);
|
|
361
|
+
gauges.active.set({}, Math.max(0, total - idle));
|
|
362
|
+
}
|
|
363
|
+
function registerPoolMetrics(collector, poolInstance, intervalMs = 5e3) {
|
|
364
|
+
updatePoolMetrics(collector, poolInstance);
|
|
365
|
+
const interval = setInterval(() => {
|
|
366
|
+
updatePoolMetrics(collector, poolInstance);
|
|
367
|
+
}, intervalMs);
|
|
368
|
+
return () => clearInterval(interval);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export { closePool, getPool, getProvider, registerPoolMetrics, resetPoolProvider, updatePoolMetrics };
|
|
372
|
+
//# sourceMappingURL=index.js.map
|
|
373
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/neon.ts","../src/providers/lakebase.ts","../src/index.ts"],"names":["Pool"],"mappings":"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","file":"index.js","sourcesContent":["import { Pool } from 'pg';\nimport type { DatabaseProvider } from './types.js';\n\nexport interface NeonProviderOptions {\n connectionString: string;\n max?: number;\n idleTimeoutMillis?: number;\n connectionTimeoutMillis?: number;\n}\n\n/** Standard Postgres provider using a static connection string. */\nexport class NeonDatabaseProvider implements DatabaseProvider {\n readonly kind = 'neon' as const;\n private readonly pool: Pool;\n\n constructor(options: NeonProviderOptions) {\n this.pool = new Pool({\n connectionString: options.connectionString,\n max: options.max ?? Number.parseInt(process.env.PG_POOL_MAX ?? '5', 10),\n idleTimeoutMillis: options.idleTimeoutMillis ?? 30_000,\n connectionTimeoutMillis: options.connectionTimeoutMillis ?? 5_000,\n });\n }\n\n getPool(): Pool {\n return this.pool;\n }\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n}\n","import { EventEmitter } from 'node:events';\nimport { Pool } from 'pg';\nimport type { DatabaseProvider } from './types.js';\n\n/** A resolver that returns a valid Databricks workspace OAuth token. */\nexport type DatabricksTokenProvider = () => Promise<string>;\n\n/** Supported Databricks authentication shapes. */\nexport type DatabricksPrincipal =\n | { kind: 'pat'; token: string }\n | {\n kind: 'service-principal';\n host: string;\n clientId: string;\n clientSecret: string;\n scope?: string;\n refreshSkewSeconds?: number;\n };\n\n/** Resolve a PAT or OAuth M2M principal to a cached, auto-refreshing token provider. */\nexport function databricksIdentity(principal: DatabricksPrincipal): DatabricksTokenProvider {\n if (principal.kind === 'pat') {\n const { token } = principal;\n return async () => token;\n }\n return servicePrincipalProvider(principal);\n}\n\n/** Build a principal from environment variables. */\nexport function databricksPrincipalFromEnv(\n env: Record<string, string | undefined> = typeof process !== 'undefined' ? process.env : {},\n): Extract<DatabricksPrincipal, { kind: 'pat' | 'service-principal' }> {\n const token = env.DATABRICKS_TOKEN?.trim();\n if (token) return { kind: 'pat', token };\n\n const clientId = env.DATABRICKS_CLIENT_ID?.trim();\n const clientSecret = env.DATABRICKS_CLIENT_SECRET?.trim();\n const host = env.DATABRICKS_HOST?.trim();\n if (clientId && clientSecret) {\n if (!host) throw new Error('Missing DATABRICKS_HOST for Databricks OAuth M2M.');\n return {\n kind: 'service-principal',\n host,\n clientId,\n clientSecret,\n scope: env.DATABRICKS_SCOPE,\n refreshSkewSeconds: env.DATABRICKS_REFRESH_SKEW_SECONDS\n ? Number.parseInt(env.DATABRICKS_REFRESH_SKEW_SECONDS, 10)\n : undefined,\n };\n }\n throw new Error(\n 'Missing Databricks credentials: DATABRICKS_TOKEN or DATABRICKS_CLIENT_ID+DATABRICKS_CLIENT_SECRET.',\n );\n}\n\ninterface CachedToken {\n token: string;\n refreshAtMs: number;\n}\n\nfunction servicePrincipalProvider(\n principal: Extract<DatabricksPrincipal, { kind: 'service-principal' }>,\n): DatabricksTokenProvider {\n const host = principal.host.replace(/\\/$/, '');\n const scope = principal.scope ?? 'all-apis';\n const refreshSkewMs = (principal.refreshSkewSeconds ?? 60) * 1_000;\n let cached: CachedToken | undefined;\n let refreshing: Promise<string> | undefined;\n\n const refresh = async (): Promise<string> => {\n const response = await fetch(`${host}/oidc/v1/token`, {\n method: 'POST',\n headers: {\n authorization: `Basic ${Buffer.from(`${principal.clientId}:${principal.clientSecret}`).toString('base64')}`,\n 'content-type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({ grant_type: 'client_credentials', scope }),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `Databricks OAuth token exchange failed: ${response.status} ${response.statusText}: ${text.slice(0, 500)}`,\n );\n }\n const value = (await response.json()) as { access_token?: unknown; expires_in?: unknown };\n const token = typeof value.access_token === 'string' ? value.access_token : undefined;\n if (!token) throw new Error('Databricks OAuth token exchange returned no access_token.');\n const expiresInSeconds = typeof value.expires_in === 'number' ? value.expires_in : 3_600;\n const expiresAtMs = Date.now() + expiresInSeconds * 1_000;\n cached = { token, refreshAtMs: expiresAtMs - refreshSkewMs };\n return token;\n };\n\n return async () => {\n if (cached && Date.now() < cached.refreshAtMs) return cached.token;\n refreshing ??= refresh().finally(() => {\n refreshing = undefined;\n });\n return refreshing;\n };\n}\n\nexport interface LakebaseCredentialProviderOptions {\n workspaceHost: string;\n endpoint: string;\n token: string | DatabricksTokenProvider;\n fetchImpl?: typeof fetch;\n refreshBeforeMs?: number;\n refreshJitterMs?: number;\n now?: () => number;\n random?: () => number;\n}\n\ninterface LakebaseCredentialResponse {\n token?: unknown;\n expire_time?: unknown;\n}\n\n/** Exchange a workspace OAuth token for a Lakebase database credential. Caches, refreshes early, single-flight. */\nexport function lakebaseCredentialProvider(\n options: LakebaseCredentialProviderOptions,\n): () => Promise<string> {\n const workspaceHost = options.workspaceHost.replace(/\\/$/, '');\n const fetchImpl = options.fetchImpl ?? fetch;\n const now = options.now ?? Date.now;\n const random = options.random ?? Math.random;\n const refreshBeforeMs = options.refreshBeforeMs ?? 5 * 60_000;\n const refreshJitterMs = options.refreshJitterMs ?? 30_000;\n let cached: { token: string; refreshAt: number } | undefined;\n let refreshing: Promise<string> | undefined;\n\n const refresh = async (): Promise<string> => {\n const workspaceToken =\n typeof options.token === 'function' ? await options.token() : options.token;\n const response = await fetchImpl(`${workspaceHost}/api/2.0/postgres/credentials`, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${workspaceToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ endpoint: options.endpoint }),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `Lakebase credential exchange failed: ${response.status} ${response.statusText}: ${text.slice(0, 500)}`,\n );\n }\n const value = (await response.json()) as LakebaseCredentialResponse;\n if (typeof value.token !== 'string' || !value.token) {\n throw new Error('Lakebase credential exchange returned no token.');\n }\n const expiresAt = parseExpireTime(value.expire_time);\n if (!Number.isFinite(expiresAt)) {\n throw new Error('Lakebase credential exchange returned an invalid expire_time.');\n }\n const jitter = Math.max(0, refreshJitterMs) * random();\n cached = {\n token: value.token,\n refreshAt: Math.max(now(), expiresAt - Math.max(0, refreshBeforeMs) - jitter),\n };\n return value.token;\n };\n\n return async () => {\n if (cached && now() < cached.refreshAt) return cached.token;\n refreshing ??= refresh().finally(() => {\n refreshing = undefined;\n });\n return refreshing;\n };\n}\n\nfunction parseExpireTime(value: unknown): number {\n if (typeof value === 'number') {\n return value < 1_000_000_000_000 ? value * 1_000 : value;\n }\n if (typeof value === 'string') return new Date(value).getTime();\n return Number.NaN;\n}\n\nexport interface LakebasePoolConfig {\n host: string;\n port: number;\n database: string;\n user: string;\n password?: string;\n ssl?: boolean | { rejectUnauthorized?: boolean };\n max?: number;\n idleTimeoutMillis?: number;\n connectionTimeoutMillis?: number;\n}\n\nexport type LakebasePoolFactory = (config: LakebasePoolConfig) => Pool;\n\nexport interface LakebaseProviderOptions {\n /** Direct endpoint host from list-endpoints (NOT the pooled host). */\n host: string;\n port?: number;\n database: string;\n user: string;\n /** Static database password. If omitted, OAuth exchange is required. */\n password?: string;\n /** Workspace URL, e.g. https://adb-....azuredatabricks.net */\n workspaceHost?: string;\n /** Full endpoint resource name: projects/.../branches/.../endpoints/... */\n endpoint?: string;\n /** Workspace OAuth token or provider. */\n token?: string | DatabricksTokenProvider;\n fetchImpl?: typeof fetch;\n ssl?: boolean | { rejectUnauthorized?: boolean };\n max?: number;\n idleTimeoutMillis?: number;\n connectionTimeoutMillis?: number;\n /** How often to refresh the credential and recreate the pool. Default 50 minutes. */\n refreshIntervalMs?: number;\n /** Injected pool factory for tests. */\n poolFactory?: LakebasePoolFactory;\n /** Observe recoverable idle pooled-connection failures. */\n onPoolError?: (error: Error) => void;\n}\n\n/** A Pool-like facade that lazily initializes and always delegates to the current underlying pool. */\nclass DelegatingPool extends EventEmitter {\n private current: Pool | undefined;\n private initPromise: Promise<void> | undefined;\n private closed = false;\n\n private attachErrorHandler(pool: Pool) {\n pool.on('error', (error) => {\n this.emit('error', error);\n });\n }\n\n initialize(createPool: (password: string) => Pool, getPassword: () => Promise<string>) {\n this.initPromise = getPassword()\n .then((password) => {\n if (!this.closed) this.swap(createPool(password));\n })\n .catch((error) => {\n this.emit('error', error instanceof Error ? error : new Error(String(error)));\n throw error;\n });\n }\n\n swap(pool: Pool) {\n if (this.closed) {\n pool.end().catch(() => undefined);\n return;\n }\n const previous = this.current;\n this.current = pool;\n this.attachErrorHandler(pool);\n if (previous) {\n previous.removeAllListeners();\n previous.end().catch(() => undefined);\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) await this.initPromise;\n }\n\n query(...args: Parameters<Pool['query']>): Promise<unknown> {\n return this.ensureInitialized().then(() => this.current!.query(...args));\n }\n\n connect(...args: Parameters<Pool['connect']>): Promise<unknown> {\n return this.ensureInitialized().then(() => this.current!.connect(...args));\n }\n\n end(): Promise<void> {\n this.closed = true;\n this.removeAllListeners();\n return this.current?.end() ?? Promise.resolve();\n }\n\n get totalCount() {\n return this.current?.totalCount ?? 0;\n }\n\n get idleCount() {\n return this.current?.idleCount ?? 0;\n }\n\n get waitingCount() {\n return this.current?.waitingCount ?? 0;\n }\n}\n\n/** Databricks Lakebase Postgres provider with credential refresh. */\nexport class LakebaseDatabaseProvider implements DatabaseProvider {\n readonly kind = 'databricks-lakebase' as const;\n private readonly options: LakebaseProviderOptions;\n private readonly poolFactory: LakebasePoolFactory;\n private readonly getPassword: () => Promise<string>;\n private readonly delegatingPool: DelegatingPool;\n private refreshTimer: NodeJS.Timeout | undefined;\n\n constructor(options: LakebaseProviderOptions) {\n this.options = options;\n this.poolFactory = options.poolFactory ?? defaultPoolFactory();\n this.delegatingPool = new DelegatingPool();\n if (options.password !== undefined) {\n this.getPassword = async () => options.password as string;\n this.delegatingPool.swap(this.createPool(options.password));\n } else {\n if (!options.token || !options.workspaceHost || !options.endpoint) {\n throw new Error(\n 'Lakebase OAuth requires token, workspaceHost, and endpoint. A workspace token cannot be used directly as the Postgres password.',\n );\n }\n this.getPassword = lakebaseCredentialProvider({\n workspaceHost: options.workspaceHost,\n endpoint: options.endpoint,\n token: options.token,\n fetchImpl: options.fetchImpl,\n });\n this.delegatingPool.initialize((password) => this.createPool(password), this.getPassword);\n const intervalMs = options.refreshIntervalMs ?? 50 * 60_000;\n this.refreshTimer = setInterval(() => {\n void this.refreshPool();\n }, intervalMs);\n }\n }\n\n private createPool(password?: string): Pool {\n return this.poolFactory({\n host: this.options.host,\n port: this.options.port ?? 5432,\n database: this.options.database,\n user: this.options.user,\n password,\n ssl: this.options.ssl ?? { rejectUnauthorized: true },\n max: this.options.max ?? Number.parseInt(process.env.PG_POOL_MAX ?? '5', 10),\n idleTimeoutMillis: this.options.idleTimeoutMillis ?? 30_000,\n connectionTimeoutMillis: this.options.connectionTimeoutMillis ?? 5_000,\n });\n }\n\n private async refreshPool(): Promise<void> {\n try {\n const password = await this.getPassword();\n const pool = this.createPool(password);\n this.delegatingPool.swap(pool);\n } catch (error) {\n this.delegatingPool.emit('error', error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n getPool(): Pool {\n return this.delegatingPool as unknown as Pool;\n }\n\n async close(): Promise<void> {\n if (this.refreshTimer) {\n clearInterval(this.refreshTimer);\n this.refreshTimer = undefined;\n }\n await this.delegatingPool.end();\n }\n}\n\nfunction defaultPoolFactory(): LakebasePoolFactory {\n return (config) => new Pool(config);\n}\n","import type { Gauge, MetricsCollector } from '@fabricorg/experiments-metrics';\nimport type { Pool } from 'pg';\nimport {\n type DatabaseProvider,\n LakebaseDatabaseProvider,\n NeonDatabaseProvider,\n databricksIdentity,\n databricksPrincipalFromEnv,\n} from './providers/index.js';\n\nlet provider: DatabaseProvider | null = null;\n\nfunction createProvider(): DatabaseProvider {\n const kind = process.env.DATABASE_PROVIDER ?? 'neon';\n\n if (kind === 'databricks-lakebase') {\n const host = process.env.DATABRICKS_LAKEBASE_HOST?.trim();\n const database = process.env.DATABRICKS_LAKEBASE_DATABASE?.trim() ?? 'databricks_postgres';\n const user = process.env.DATABRICKS_LAKEBASE_USER?.trim();\n const workspaceHost = process.env.DATABRICKS_HOST?.trim();\n const endpoint = process.env.DATABRICKS_LAKEBASE_ENDPOINT?.trim();\n const password = process.env.DATABRICKS_LAKEBASE_PASSWORD?.trim();\n\n if (!host) {\n throw new Error(\n 'DATABRICKS_LAKEBASE_HOST is required when DATABASE_PROVIDER=databricks-lakebase',\n );\n }\n if (!user) {\n throw new Error(\n 'DATABRICKS_LAKEBASE_USER is required when DATABASE_PROVIDER=databricks-lakebase',\n );\n }\n\n if (password) {\n return new LakebaseDatabaseProvider({ host, database, user, password });\n }\n\n if (!workspaceHost || !endpoint) {\n throw new Error(\n 'DATABRICKS_HOST and DATABRICKS_LAKEBASE_ENDPOINT are required for Lakebase OAuth, or provide DATABRICKS_LAKEBASE_PASSWORD for native auth.',\n );\n }\n\n const principal = databricksPrincipalFromEnv(process.env);\n return new LakebaseDatabaseProvider({\n host,\n database,\n user,\n workspaceHost,\n endpoint,\n token: databricksIdentity(principal),\n });\n }\n\n const connectionString = process.env.DATABASE_URL;\n if (!connectionString) {\n throw new Error('DATABASE_URL is required');\n }\n return new NeonDatabaseProvider({ connectionString });\n}\n\n/**\n * Return a singleton `pg.Pool` configured for the active database provider.\n *\n * Provider selection:\n * - `DATABASE_PROVIDER=neon` (default): uses `DATABASE_URL`.\n * - `DATABASE_PROVIDER=databricks-lakebase`: uses Lakebase OAuth credentials.\n *\n * On the first call, a new Pool is created with the following defaults:\n * - `max`: parseInt(process.env.PG_POOL_MAX ?? '5') — caps concurrent connections.\n * - `idleTimeoutMillis`: 30000 — recycle idle connections before Vercel's 60s timeout.\n * - `connectionTimeoutMillis`: 5000 — fail fast when the database is unreachable.\n */\nexport function getPool(): Pool {\n if (!provider) {\n provider = createProvider();\n }\n return provider.getPool();\n}\n\n/** Close the active provider and any background refresh loops. */\nexport async function closePool(): Promise<void> {\n await provider?.close();\n provider = null;\n}\n\n/** Reset the singleton provider. Intended for tests. */\nexport function resetPoolProvider(): void {\n provider = null;\n}\n\n/** Return the active provider, or create it. */\nexport function getProvider(): DatabaseProvider {\n if (!provider) {\n provider = createProvider();\n }\n return provider;\n}\n\ninterface PoolGauges {\n total: Gauge;\n idle: Gauge;\n waiting: Gauge;\n active: Gauge;\n}\n\nfunction ensurePoolGauges(collector: MetricsCollector): PoolGauges {\n const reg = collector.registry;\n const existingTotal = reg.get('db_pool_connections_total');\n if (existingTotal) {\n return {\n total: existingTotal as Gauge,\n idle: reg.get('db_pool_connections_idle') as Gauge,\n waiting: reg.get('db_pool_connections_waiting') as Gauge,\n active: reg.get('db_pool_connections_active') as Gauge,\n };\n }\n\n return {\n total: collector.gauge('db_pool_connections_total', 'Total connections in the pool'),\n idle: collector.gauge('db_pool_connections_idle', 'Idle connections in the pool'),\n waiting: collector.gauge('db_pool_connections_waiting', 'Waiting clients in the pool'),\n active: collector.gauge(\n 'db_pool_connections_active',\n 'Active (non-idle) connections in the pool',\n ),\n };\n}\n\n/** Update pool gauge values from the current pool state. */\nexport function updatePoolMetrics(collector: MetricsCollector, poolInstance?: Pool): void {\n const p = poolInstance ?? getPool();\n const total = (p as unknown as { totalCount: number }).totalCount ?? 0;\n const idle = p.idleCount ?? 0;\n const waiting = p.waitingCount ?? 0;\n\n const gauges = ensurePoolGauges(collector);\n gauges.total.set({}, total);\n gauges.idle.set({}, idle);\n gauges.waiting.set({}, waiting);\n gauges.active.set({}, Math.max(0, total - idle));\n}\n\n/**\n * Register pool metrics with the given collector.\n *\n * Returns a cleanup function that stops the update interval.\n */\nexport function registerPoolMetrics(\n collector: MetricsCollector,\n poolInstance?: Pool,\n intervalMs = 5000,\n): () => void {\n updatePoolMetrics(collector, poolInstance);\n\n const interval = setInterval(() => {\n updatePoolMetrics(collector, poolInstance);\n }, intervalMs);\n\n return () => clearInterval(interval);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fabricorg/experiments-db-pool",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Singleton pg.Pool for serverless runtimes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./dist/index.d.cts",
|
|
19
|
+
"default": "./dist/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"pg": "^8.13.0",
|
|
28
|
+
"@fabricorg/experiments-metrics": "0.0.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.15.3",
|
|
32
|
+
"@types/pg": "^8.11.10",
|
|
33
|
+
"tsup": "^8.3.5",
|
|
34
|
+
"typescript": "^5.8.3",
|
|
35
|
+
"vitest": "^2.1.8"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"type-check": "tsc --noEmit",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"clean": "rm -rf dist"
|
|
42
|
+
}
|
|
43
|
+
}
|