@everystack/server 0.4.14 → 0.4.15
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/package.json +6 -2
- package/src/backup.ts +50 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.15",
|
|
4
4
|
"description": "Server runtime primitives for Lambda — event adapters, routing, SSR, image processing",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
"types": "./src/cdn/index.ts",
|
|
74
74
|
"default": "./src/cdn/index.ts"
|
|
75
75
|
},
|
|
76
|
+
"./cdn/transforms": {
|
|
77
|
+
"types": "./src/cdn/transforms.ts",
|
|
78
|
+
"default": "./src/cdn/transforms.ts"
|
|
79
|
+
},
|
|
76
80
|
"./kvs": {
|
|
77
81
|
"types": "./src/kvs.ts",
|
|
78
82
|
"default": "./src/kvs.ts"
|
|
@@ -160,7 +164,7 @@
|
|
|
160
164
|
"ts-jest": "29.4.9",
|
|
161
165
|
"typescript": "5.9.3",
|
|
162
166
|
"@everystack/auth": "0.4.0",
|
|
163
|
-
"@everystack/cli": "0.4.
|
|
167
|
+
"@everystack/cli": "0.4.35"
|
|
164
168
|
},
|
|
165
169
|
"scripts": {
|
|
166
170
|
"test": "jest",
|
package/src/backup.ts
CHANGED
|
@@ -274,13 +274,59 @@ export function dbNameFromUrl(url: string): string {
|
|
|
274
274
|
return decodeURIComponent(new URL(url).pathname.replace(/^\//, ''));
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
// --- TCP keepalives: why every long-running pg binary needs them -------------------------------
|
|
278
|
+
//
|
|
279
|
+
// Verified failure, 2026-07-27 (db:swap, but the same shape applies to every command here). A
|
|
280
|
+
// restore's COPY phase saturates the connection; the index/constraint phase that FOLLOWS it sends
|
|
281
|
+
// one statement and then goes quiet for minutes. During that quiet period a stateful middlebox on
|
|
282
|
+
// the path (NAT gateway, firewall) drops the flow's state. The server's own keepalive then finds a
|
|
283
|
+
// dead peer and terminates the backend. The CLIENT, on the far side of the break, never receives a
|
|
284
|
+
// FIN or RST: its socket stays ESTABLISHED and it blocks on a read that will never return.
|
|
285
|
+
//
|
|
286
|
+
// Measured: psql alive at 8m54s with 0.05s CPU, socket ESTABLISHED, Send-Q 0 — while a SECOND
|
|
287
|
+
// connection to the same database confirmed via pg_stat_activity that no psql backend existed.
|
|
288
|
+
//
|
|
289
|
+
// Client keepalives fix both halves. Probing every 30s of quiet keeps the middlebox's state alive
|
|
290
|
+
// so the flow is never dropped, and if the peer does die the client declares it dead in ~80s
|
|
291
|
+
// (30 + 5 x 10) instead of hanging forever.
|
|
292
|
+
//
|
|
293
|
+
// These MUST travel as libpq connection parameters — there is no PG* environment variable for
|
|
294
|
+
// keepalives. Passing them via `-d` keyword/value form still leaves host/user/password/sslmode to
|
|
295
|
+
// the PG* env, because libpq resolves each parameter from the conninfo first and the environment
|
|
296
|
+
// second. So the credential stays off argv.
|
|
297
|
+
|
|
298
|
+
export const PG_KEEPALIVE_IDLE_S = 30;
|
|
299
|
+
export const PG_KEEPALIVE_INTERVAL_S = 10;
|
|
300
|
+
export const PG_KEEPALIVE_COUNT = 5;
|
|
301
|
+
|
|
302
|
+
/** Escape a value for libpq keyword/value conninfo (single-quoted, backslash-escaped). */
|
|
303
|
+
function conninfoValue(v: string): string {
|
|
304
|
+
return `'${v.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* A libpq keyword/value conninfo carrying ONLY keepalives (plus `dbname` when given). Everything
|
|
309
|
+
* else still resolves from the PG* env — see the note above. Safe to log: it holds no credential.
|
|
310
|
+
*/
|
|
311
|
+
export function pgKeepaliveConninfo(dbName?: string): string {
|
|
312
|
+
const parts = [
|
|
313
|
+
'keepalives=1',
|
|
314
|
+
`keepalives_idle=${PG_KEEPALIVE_IDLE_S}`,
|
|
315
|
+
`keepalives_interval=${PG_KEEPALIVE_INTERVAL_S}`,
|
|
316
|
+
`keepalives_count=${PG_KEEPALIVE_COUNT}`,
|
|
317
|
+
];
|
|
318
|
+
if (dbName) parts.unshift(`dbname=${conninfoValue(dbName)}`);
|
|
319
|
+
return parts.join(' ');
|
|
320
|
+
}
|
|
321
|
+
|
|
277
322
|
/**
|
|
278
323
|
* Args for `pg_dump` — custom format (portable, restorable under a different role/stage). The
|
|
279
324
|
* connection comes from PG* env, so no host/db on argv. --no-owner/--no-privileges/--no-comments
|
|
280
325
|
* make the dump restore cleanly into a different stage (and dodge the RDS plpgsql-comment error).
|
|
281
326
|
*/
|
|
282
327
|
export function pgDumpArgs(opts: { schema?: string } = {}): string[] {
|
|
283
|
-
|
|
328
|
+
// -d carries keepalives only; host/user/password/db still come from the PG* env (see above).
|
|
329
|
+
const args = ['-d', pgKeepaliveConninfo(), '-Fc', '--no-owner', '--no-privileges', '--no-comments'];
|
|
284
330
|
if (opts.schema) {
|
|
285
331
|
// A schema-scoped artifact dumps ONE schema (the canonical-sync export). pg_dump's --schema
|
|
286
332
|
// takes a pattern; a plain identifier scopes to exactly that schema and its objects.
|
|
@@ -301,5 +347,7 @@ export function pgDumpArgs(opts: { schema?: string } = {}): string[] {
|
|
|
301
347
|
* With no archive-file argument pg_restore reads the dump from stdin.
|
|
302
348
|
*/
|
|
303
349
|
export function pgRestoreArgs(dbName: string): string[] {
|
|
304
|
-
|
|
350
|
+
// The database name rides the same conninfo as the keepalives. libpq treats a `-d` string
|
|
351
|
+
// containing `=` as keyword/value form, so dbname must be a keyword here rather than bare.
|
|
352
|
+
return ['--clean', '--if-exists', '--no-owner', '--no-privileges', '--no-comments', '-d', pgKeepaliveConninfo(dbName)];
|
|
305
353
|
}
|