@steve31415/baselib 2.2.1 → 2.3.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/dist/db.d.ts +20 -1
- package/dist/db.js +41 -0
- package/dist/log-core.d.ts +2 -1
- package/dist/log-core.js +1 -1
- package/package.json +1 -1
package/dist/db.d.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import pg from 'pg';
|
|
2
|
-
import type
|
|
2
|
+
import { type Logger } from './log-core.js';
|
|
3
3
|
export interface PoolOptions {
|
|
4
4
|
/** Override the database name (default: env DB_NAME / DATABASE_URL path). */
|
|
5
5
|
database?: string;
|
|
6
6
|
max?: number;
|
|
7
|
+
/** Receives idle-client errors (see logPoolErrors). Without one they go to
|
|
8
|
+
* stderr — on the Cloud Logging backstop, but not in Axiom. */
|
|
9
|
+
logger?: Logger;
|
|
7
10
|
}
|
|
11
|
+
/** pg-pool re-emits an idle client's socket error as its own 'error' event —
|
|
12
|
+
* in production "Connection terminated unexpectedly" when Cloud SQL closes an
|
|
13
|
+
* idle connection. Unhandled, that event throws and takes the process down
|
|
14
|
+
* (todo 2026-08-20, notes 2026-08-27 ×2). The failed client has already been
|
|
15
|
+
* removed from the pool and the next checkout opens a fresh connection, so
|
|
16
|
+
* the right response is to log it and carry on. createPool attaches this;
|
|
17
|
+
* exported for apps that build their own pg.Pool. */
|
|
18
|
+
export declare function logPoolErrors(pool: pg.Pool, logger?: Logger): void;
|
|
8
19
|
export declare function createPool(opts?: PoolOptions): Promise<pg.Pool>;
|
|
20
|
+
/** End a pool and resolve only once every client's connection has actually
|
|
21
|
+
* closed. pg-pool's own `end()` resolves as soon as it has *asked* its idle
|
|
22
|
+
* clients to close — their sockets are still open — so a caller that then
|
|
23
|
+
* drops the database `with (force)` can kill a backend mid-goodbye, and the
|
|
24
|
+
* FATAL it sends back surfaces as an unhandled pool 'error' (it took down a
|
|
25
|
+
* todo2 deploy with every test green, 2026-08-20). pg-pool emits 'remove' for
|
|
26
|
+
* each client once its socket has closed; wait for all of them. */
|
|
27
|
+
export declare function endPool(pool: pg.Pool): Promise<void>;
|
|
9
28
|
export declare function migrate(pool: pg.Pool, migrationsDir: string, logger?: Logger): Promise<{
|
|
10
29
|
applied: string[];
|
|
11
30
|
}>;
|
package/dist/db.js
CHANGED
|
@@ -6,6 +6,7 @@ import { readdir, readFile } from 'node:fs/promises';
|
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import pg from 'pg';
|
|
8
8
|
import { requireEnv } from './config.js';
|
|
9
|
+
import { serializeError } from './log-core.js';
|
|
9
10
|
// DATE columns come back as 'YYYY-MM-DD' strings, not JS Dates: calendar
|
|
10
11
|
// dates are timezone-free by meaning, and a Date object smears them across
|
|
11
12
|
// zones (and JSON-serializes to a full ISO timestamp). Set here — on the
|
|
@@ -13,7 +14,27 @@ import { requireEnv } from './config.js';
|
|
|
13
14
|
// own pg copy miss this one (file:-dependency installs resolve separate pg
|
|
14
15
|
// instances).
|
|
15
16
|
pg.types.setTypeParser(pg.types.builtins.DATE, (v) => v);
|
|
17
|
+
/** pg-pool re-emits an idle client's socket error as its own 'error' event —
|
|
18
|
+
* in production "Connection terminated unexpectedly" when Cloud SQL closes an
|
|
19
|
+
* idle connection. Unhandled, that event throws and takes the process down
|
|
20
|
+
* (todo 2026-08-20, notes 2026-08-27 ×2). The failed client has already been
|
|
21
|
+
* removed from the pool and the next checkout opens a fresh connection, so
|
|
22
|
+
* the right response is to log it and carry on. createPool attaches this;
|
|
23
|
+
* exported for apps that build their own pg.Pool. */
|
|
24
|
+
export function logPoolErrors(pool, logger) {
|
|
25
|
+
pool.on('error', (err) => {
|
|
26
|
+
if (logger)
|
|
27
|
+
logger.error('database pool: idle client error', { error: err });
|
|
28
|
+
else
|
|
29
|
+
console.error('database pool: idle client error', serializeError(err));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
16
32
|
export async function createPool(opts = {}) {
|
|
33
|
+
const pool = await newPool(opts);
|
|
34
|
+
logPoolErrors(pool, opts.logger);
|
|
35
|
+
return pool;
|
|
36
|
+
}
|
|
37
|
+
async function newPool(opts) {
|
|
17
38
|
const instance = process.env.CLOUD_SQL_INSTANCE;
|
|
18
39
|
if (instance) {
|
|
19
40
|
const { Connector, AuthTypes, IpAddressTypes } = await import('@google-cloud/cloud-sql-connector');
|
|
@@ -35,6 +56,26 @@ export async function createPool(opts = {}) {
|
|
|
35
56
|
url.pathname = `/${opts.database}`;
|
|
36
57
|
return new pg.Pool({ connectionString: url.toString(), max: opts.max ?? 5 });
|
|
37
58
|
}
|
|
59
|
+
/** End a pool and resolve only once every client's connection has actually
|
|
60
|
+
* closed. pg-pool's own `end()` resolves as soon as it has *asked* its idle
|
|
61
|
+
* clients to close — their sockets are still open — so a caller that then
|
|
62
|
+
* drops the database `with (force)` can kill a backend mid-goodbye, and the
|
|
63
|
+
* FATAL it sends back surfaces as an unhandled pool 'error' (it took down a
|
|
64
|
+
* todo2 deploy with every test green, 2026-08-20). pg-pool emits 'remove' for
|
|
65
|
+
* each client once its socket has closed; wait for all of them. */
|
|
66
|
+
export async function endPool(pool) {
|
|
67
|
+
let open = pool.totalCount;
|
|
68
|
+
const allClosed = new Promise((resolve) => {
|
|
69
|
+
if (open === 0)
|
|
70
|
+
return resolve();
|
|
71
|
+
pool.on('remove', () => {
|
|
72
|
+
if (--open === 0)
|
|
73
|
+
resolve();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
await pool.end();
|
|
77
|
+
await allClosed;
|
|
78
|
+
}
|
|
38
79
|
// Plain-SQL migrations: numbered files (001-*.sql, 002-*.sql, …) applied in
|
|
39
80
|
// order, recorded in schema_migrations, each inside a transaction, guarded by
|
|
40
81
|
// an advisory lock so concurrent boots can't race. Applied at service boot.
|
package/dist/log-core.d.ts
CHANGED
|
@@ -52,7 +52,8 @@ export interface ShipperOptions {
|
|
|
52
52
|
* fire at the next CPU window (next request or shutdown), not on the
|
|
53
53
|
* wall clock. */
|
|
54
54
|
retryDelaysMs?: number[];
|
|
55
|
-
/** Per-attempt fetch timeout (default 3s
|
|
55
|
+
/** Per-attempt fetch timeout (default 10s; 3s aborted most of the fleet's
|
|
56
|
+
* attempts in the two weeks to 2026-08-28). */
|
|
56
57
|
attemptTimeoutMs?: number;
|
|
57
58
|
/** Caps across ALL undelivered batches; overflow drops oldest with an
|
|
58
59
|
* onGiveUp('buffer-overflow'). Defaults 5000 events / 5 MB. */
|
package/dist/log-core.js
CHANGED
|
@@ -70,7 +70,7 @@ export class LogShipper {
|
|
|
70
70
|
this.intervalMs = opts.intervalMs ?? 2000;
|
|
71
71
|
this.maxAttempts = opts.maxAttempts ?? 4;
|
|
72
72
|
this.retryDelaysMs = opts.retryDelaysMs ?? [5_000, 15_000, 40_000];
|
|
73
|
-
this.attemptTimeoutMs = opts.attemptTimeoutMs ??
|
|
73
|
+
this.attemptTimeoutMs = opts.attemptTimeoutMs ?? 10_000;
|
|
74
74
|
this.maxBufferedEvents = opts.maxBufferedEvents ?? 5_000;
|
|
75
75
|
this.maxBufferedBytes = opts.maxBufferedBytes ?? 5 * 1024 * 1024;
|
|
76
76
|
}
|
package/package.json
CHANGED