orez 0.1.34 → 0.1.36
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/admin/server.d.ts.map +1 -1
- package/dist/admin/server.js +12 -1
- package/dist/admin/server.js.map +1 -1
- package/dist/admin/ui.d.ts.map +1 -1
- package/dist/admin/ui.js +103 -79
- package/dist/admin/ui.js.map +1 -1
- package/dist/pg-proxy.js +13 -3
- package/dist/pg-proxy.js.map +1 -1
- package/dist/replication/handler.d.ts.map +1 -1
- package/dist/replication/handler.js +8 -5
- package/dist/replication/handler.js.map +1 -1
- package/package.json +4 -4
- package/src/admin/server.ts +12 -1
- package/src/admin/ui.ts +103 -79
- package/src/cf-pglite/README.md +19 -0
- package/src/pg-proxy.ts +13 -3
- package/src/replication/handler.ts +10 -5
- package/src/replication/zero-compat.test.ts +4 -0
package/src/admin/ui.ts
CHANGED
|
@@ -36,14 +36,14 @@ export function getAdminHtml(): string {
|
|
|
36
36
|
'.header {\n' +
|
|
37
37
|
' display: flex;\n' +
|
|
38
38
|
' align-items: center;\n' +
|
|
39
|
-
' padding: 12px
|
|
39
|
+
' padding: 4px 12px;\n' +
|
|
40
40
|
' background: var(--surface);\n' +
|
|
41
41
|
' border-bottom: 0.5px solid var(--border);\n' +
|
|
42
|
-
' gap:
|
|
42
|
+
' gap: 8px;\n' +
|
|
43
43
|
' flex-shrink: 0;\n' +
|
|
44
44
|
'}\n' +
|
|
45
45
|
'.header .logo {\n' +
|
|
46
|
-
' font-size:
|
|
46
|
+
' font-size: 12px;\n' +
|
|
47
47
|
' font-weight: 700;\n' +
|
|
48
48
|
' color: var(--accent);\n' +
|
|
49
49
|
' letter-spacing: -0.5px;\n' +
|
|
@@ -51,12 +51,12 @@ export function getAdminHtml(): string {
|
|
|
51
51
|
'.badge {\n' +
|
|
52
52
|
' display: inline-flex;\n' +
|
|
53
53
|
' align-items: center;\n' +
|
|
54
|
-
' padding:
|
|
54
|
+
' padding: 1px 6px;\n' +
|
|
55
55
|
' border-radius: 12px;\n' +
|
|
56
|
-
' font-size:
|
|
56
|
+
' font-size: 10px;\n' +
|
|
57
57
|
' border: 0.5px solid var(--border);\n' +
|
|
58
58
|
' color: var(--text-dim);\n' +
|
|
59
|
-
' gap:
|
|
59
|
+
' gap: 3px;\n' +
|
|
60
60
|
'}\n' +
|
|
61
61
|
'.badge .dot {\n' +
|
|
62
62
|
' width: 6px;\n' +
|
|
@@ -67,15 +67,15 @@ export function getAdminHtml(): string {
|
|
|
67
67
|
'.spacer { flex: 1; }\n' +
|
|
68
68
|
'.tabs {\n' +
|
|
69
69
|
' display: flex;\n' +
|
|
70
|
-
' padding: 0
|
|
70
|
+
' padding: 0 12px;\n' +
|
|
71
71
|
' background: var(--surface);\n' +
|
|
72
72
|
' border-bottom: 0.5px solid var(--border);\n' +
|
|
73
|
-
' gap:
|
|
73
|
+
' gap: 0;\n' +
|
|
74
74
|
' flex-shrink: 0;\n' +
|
|
75
75
|
'}\n' +
|
|
76
76
|
'.tab {\n' +
|
|
77
|
-
' padding:
|
|
78
|
-
' font-size:
|
|
77
|
+
' padding: 4px 10px;\n' +
|
|
78
|
+
' font-size: 11px;\n' +
|
|
79
79
|
' color: var(--text-dim);\n' +
|
|
80
80
|
' cursor: pointer;\n' +
|
|
81
81
|
' border-bottom: 2px solid transparent;\n' +
|
|
@@ -94,13 +94,13 @@ export function getAdminHtml(): string {
|
|
|
94
94
|
'.toolbar {\n' +
|
|
95
95
|
' display: flex;\n' +
|
|
96
96
|
' align-items: center;\n' +
|
|
97
|
-
' padding:
|
|
98
|
-
' gap:
|
|
97
|
+
' padding: 3px 12px;\n' +
|
|
98
|
+
' gap: 8px;\n' +
|
|
99
99
|
' border-bottom: 0.5px solid var(--border);\n' +
|
|
100
100
|
' flex-shrink: 0;\n' +
|
|
101
101
|
'}\n' +
|
|
102
102
|
'.toolbar label {\n' +
|
|
103
|
-
' font-size:
|
|
103
|
+
' font-size: 10px;\n' +
|
|
104
104
|
' color: var(--text-dim);\n' +
|
|
105
105
|
' text-transform: uppercase;\n' +
|
|
106
106
|
' letter-spacing: 0.5px;\n' +
|
|
@@ -109,9 +109,9 @@ export function getAdminHtml(): string {
|
|
|
109
109
|
' background: var(--surface);\n' +
|
|
110
110
|
' color: var(--text);\n' +
|
|
111
111
|
' border: 0.5px solid var(--border);\n' +
|
|
112
|
-
' border-radius:
|
|
113
|
-
' padding:
|
|
114
|
-
' font-size:
|
|
112
|
+
' border-radius: 4px;\n' +
|
|
113
|
+
' padding: 2px 6px;\n' +
|
|
114
|
+
' font-size: 11px;\n' +
|
|
115
115
|
' font-family: inherit;\n' +
|
|
116
116
|
' cursor: pointer;\n' +
|
|
117
117
|
'}\n' +
|
|
@@ -120,23 +120,23 @@ export function getAdminHtml(): string {
|
|
|
120
120
|
' background: var(--surface);\n' +
|
|
121
121
|
' color: var(--text);\n' +
|
|
122
122
|
' border: 0.5px solid var(--border);\n' +
|
|
123
|
-
' border-radius:
|
|
124
|
-
' padding:
|
|
125
|
-
' font-size:
|
|
123
|
+
' border-radius: 4px;\n' +
|
|
124
|
+
' padding: 2px 6px;\n' +
|
|
125
|
+
' font-size: 11px;\n' +
|
|
126
126
|
' font-family: inherit;\n' +
|
|
127
|
-
' width:
|
|
127
|
+
' width: 180px;\n' +
|
|
128
128
|
'}\n' +
|
|
129
129
|
'.toolbar input[type="text"]:focus { outline: none; border-color: var(--accent); }\n' +
|
|
130
130
|
'.toolbar input[type="text"]::placeholder { color: var(--text-dim); }\n' +
|
|
131
131
|
'.sep { width: 1px; height: 20px; background: var(--border); }\n' +
|
|
132
132
|
'.action-btn {\n' +
|
|
133
|
-
' padding:
|
|
134
|
-
' border-radius:
|
|
133
|
+
' padding: 2px 8px;\n' +
|
|
134
|
+
' border-radius: 4px;\n' +
|
|
135
135
|
' border: 1px solid;\n' +
|
|
136
136
|
' background: transparent;\n' +
|
|
137
137
|
' cursor: pointer;\n' +
|
|
138
138
|
' font-family: inherit;\n' +
|
|
139
|
-
' font-size:
|
|
139
|
+
' font-size: 10px;\n' +
|
|
140
140
|
' transition: all 0.15s ease;\n' +
|
|
141
141
|
' white-space: nowrap;\n' +
|
|
142
142
|
'}\n' +
|
|
@@ -164,9 +164,9 @@ export function getAdminHtml(): string {
|
|
|
164
164
|
'.log-view {\n' +
|
|
165
165
|
' height: 100%;\n' +
|
|
166
166
|
' overflow-y: auto;\n' +
|
|
167
|
-
' padding:
|
|
168
|
-
' font-size:
|
|
169
|
-
' line-height: 1.
|
|
167
|
+
' padding: 4px 12px;\n' +
|
|
168
|
+
' font-size: 11px;\n' +
|
|
169
|
+
' line-height: 1.4;\n' +
|
|
170
170
|
'}\n' +
|
|
171
171
|
'.log-line { white-space: pre-wrap; word-break: break-all; }\n' +
|
|
172
172
|
'.log-line .ts { color: var(--text-dim); }\n' +
|
|
@@ -291,27 +291,12 @@ export function getAdminHtml(): string {
|
|
|
291
291
|
'}\n' +
|
|
292
292
|
'.http-detail .hdr-key { color: var(--accent); }\n' +
|
|
293
293
|
'.http-detail .hdr-val { color: var(--text-dim); }\n' +
|
|
294
|
-
|
|
295
|
-
'.actions-panel {\n' +
|
|
296
|
-
' flex-shrink: 0;\n' +
|
|
297
|
-
' border-top: 0.5px solid var(--border);\n' +
|
|
298
|
-
' background: var(--surface);\n' +
|
|
299
|
-
' padding: 8px 16px;\n' +
|
|
300
|
-
'}\n' +
|
|
301
|
-
'.action-row {\n' +
|
|
294
|
+
'.toolbar-actions {\n' +
|
|
302
295
|
' display: flex;\n' +
|
|
303
296
|
' align-items: center;\n' +
|
|
304
|
-
' gap:
|
|
305
|
-
'
|
|
297
|
+
' gap: 6px;\n' +
|
|
298
|
+
' margin-left: auto;\n' +
|
|
306
299
|
'}\n' +
|
|
307
|
-
'.action-label {\n' +
|
|
308
|
-
' font-size: 11px;\n' +
|
|
309
|
-
' font-weight: 600;\n' +
|
|
310
|
-
' width: 7ch;\n' +
|
|
311
|
-
' flex-shrink: 0;\n' +
|
|
312
|
-
'}\n' +
|
|
313
|
-
'.action-label.zero { color: var(--purple); }\n' +
|
|
314
|
-
'.action-label.logs { color: var(--text-dim); }\n' +
|
|
315
300
|
// toast
|
|
316
301
|
'.toast {\n' +
|
|
317
302
|
' position: fixed;\n' +
|
|
@@ -336,7 +321,7 @@ export function getAdminHtml(): string {
|
|
|
336
321
|
'</style>\n' +
|
|
337
322
|
'</head>\n' +
|
|
338
323
|
'<body>\n' +
|
|
339
|
-
' <div class="header">\n' +
|
|
324
|
+
' <div class="header" id="admin-header">\n' +
|
|
340
325
|
' <span class="logo">◆ oreZ admin</span>\n' +
|
|
341
326
|
' <div class="spacer"></div>\n' +
|
|
342
327
|
' <span class="badge"><span class="dot"></span> pg <span id="pg-port">-</span></span>\n' +
|
|
@@ -365,11 +350,34 @@ export function getAdminHtml(): string {
|
|
|
365
350
|
' <option value="info">info+</option>\n' +
|
|
366
351
|
' <option value="debug">debug+</option>\n' +
|
|
367
352
|
' </select>\n' +
|
|
353
|
+
' <div class="toolbar-actions" id="toolbar-log-actions">\n' +
|
|
354
|
+
' <button class="action-btn gray" onclick="doAction(\'clear-logs\', this)">✕ Clear</button>\n' +
|
|
355
|
+
' </div>\n' +
|
|
356
|
+
' </div>\n' +
|
|
357
|
+
'\n' +
|
|
358
|
+
' <div class="toolbar" id="zero-toolbar" style="display:none">\n' +
|
|
359
|
+
' <label>Level</label>\n' +
|
|
360
|
+
' <select id="zero-level-filter">\n' +
|
|
361
|
+
' <option value="">all levels</option>\n' +
|
|
362
|
+
' <option value="error">error only</option>\n' +
|
|
363
|
+
' <option value="warn">warn+</option>\n' +
|
|
364
|
+
' <option value="info" selected>info+</option>\n' +
|
|
365
|
+
' <option value="debug">debug+</option>\n' +
|
|
366
|
+
' </select>\n' +
|
|
367
|
+
' <div class="toolbar-actions">\n' +
|
|
368
|
+
' <button class="action-btn blue" data-zero-action onclick="doAction(\'restart-zero\', this)">↻ Restart</button>\n' +
|
|
369
|
+
' <button class="action-btn orange" data-zero-action onclick="doAction(\'reset-zero\', this)">↺ Reset</button>\n' +
|
|
370
|
+
' <button class="action-btn red" data-zero-action onclick="doAction(\'reset-zero-full\', this)">⚠ Full</button>\n' +
|
|
371
|
+
' <button class="action-btn gray" onclick="doAction(\'clear-logs\', this)">✕ Clear</button>\n' +
|
|
372
|
+
' </div>\n' +
|
|
368
373
|
' </div>\n' +
|
|
369
374
|
'\n' +
|
|
370
375
|
' <div class="toolbar" id="http-toolbar" style="display:none">\n' +
|
|
371
376
|
' <label>Filter</label>\n' +
|
|
372
377
|
' <input type="text" id="http-path-filter" placeholder="filter by path...">\n' +
|
|
378
|
+
' <div class="toolbar-actions">\n' +
|
|
379
|
+
' <button class="action-btn gray" onclick="doAction(\'clear-http\', this)">✕ Clear</button>\n' +
|
|
380
|
+
' </div>\n' +
|
|
373
381
|
' </div>\n' +
|
|
374
382
|
'\n' +
|
|
375
383
|
' <div class="content-area">\n' +
|
|
@@ -396,32 +404,23 @@ export function getAdminHtml(): string {
|
|
|
396
404
|
' </div>\n' +
|
|
397
405
|
' <button class="jump-btn" id="jump-btn" onclick="jumpToBottom()">↓ Jump to bottom</button>\n' +
|
|
398
406
|
' </div>\n' +
|
|
399
|
-
'\n' +
|
|
400
|
-
' <div class="actions-panel" id="actions-panel">\n' +
|
|
401
|
-
' <div class="action-row">\n' +
|
|
402
|
-
' <span class="action-label zero">zero</span>\n' +
|
|
403
|
-
' <button class="action-btn blue" data-zero-action onclick="doAction(\'restart-zero\', this)">↻ Restart</button>\n' +
|
|
404
|
-
' <button class="action-btn orange" data-zero-action onclick="doAction(\'reset-zero\', this)">↺ Reset</button>\n' +
|
|
405
|
-
' <button class="action-btn red" data-zero-action onclick="doAction(\'reset-zero-full\', this)">⚠ Full Reset</button>\n' +
|
|
406
|
-
' </div>\n' +
|
|
407
|
-
' <div class="action-row">\n' +
|
|
408
|
-
' <span class="action-label logs">logs</span>\n' +
|
|
409
|
-
' <button class="action-btn gray" onclick="doAction(\'clear-logs\', this)">✕ Clear Logs</button>\n' +
|
|
410
|
-
' <button class="action-btn gray" onclick="doAction(\'clear-http\', this)">✕ Clear HTTP</button>\n' +
|
|
411
|
-
' </div>\n' +
|
|
412
|
-
' </div>\n' +
|
|
413
407
|
' </div>\n' +
|
|
414
408
|
'\n' +
|
|
415
409
|
' <div class="toast" id="toast"></div>\n' +
|
|
416
410
|
'\n' +
|
|
417
411
|
'<script>\n' +
|
|
418
|
-
'
|
|
419
|
-
'var
|
|
412
|
+
'// resolve initial tab from url path\n' +
|
|
413
|
+
'var pathMap = {"/":"","/all":"","/zero":"zero","/pglite":"pglite","/proxy":"proxy","/orez":"orez","/s3":"s3","/http":"http","/env":"env"};\n' +
|
|
414
|
+
'var initPath = window.location.pathname.replace(/\\/$/, "") || "/";\n' +
|
|
415
|
+
'var initSource = pathMap[initPath] !== undefined ? pathMap[initPath] : "";\n' +
|
|
416
|
+
'var standalone = initPath !== "/" && initPath !== "/all";\n' +
|
|
417
|
+
'var activeSource = initSource;\n' +
|
|
418
|
+
'var activeLevel = initSource === "zero" ? "info" : "";\n' +
|
|
420
419
|
'var lastCursor = 0;\n' +
|
|
421
420
|
'var autoScroll = true;\n' +
|
|
422
421
|
'var envLoaded = false;\n' +
|
|
423
|
-
'var isEnvTab =
|
|
424
|
-
'var isHttpTab =
|
|
422
|
+
'var isEnvTab = initSource === "env";\n' +
|
|
423
|
+
'var isHttpTab = initSource === "http";\n' +
|
|
425
424
|
'var httpCursor = 0;\n' +
|
|
426
425
|
'var httpAutoScroll = true;\n' +
|
|
427
426
|
'\n' +
|
|
@@ -431,20 +430,21 @@ export function getAdminHtml(): string {
|
|
|
431
430
|
'var jumpBtn = document.getElementById("jump-btn");\n' +
|
|
432
431
|
'var toastEl = document.getElementById("toast");\n' +
|
|
433
432
|
'var toolbar = document.getElementById("toolbar");\n' +
|
|
433
|
+
'var zeroToolbar = document.getElementById("zero-toolbar");\n' +
|
|
434
434
|
'var httpToolbar = document.getElementById("http-toolbar");\n' +
|
|
435
435
|
'\n' +
|
|
436
|
-
'
|
|
437
|
-
'
|
|
438
|
-
'
|
|
439
|
-
' document.querySelectorAll(".tab").forEach(function(t) { t.classList.remove("active"); });\n' +
|
|
440
|
-
' tab.classList.add("active");\n' +
|
|
441
|
-
' var source = tab.dataset.source;\n' +
|
|
436
|
+
'function sourceToPath(s) { return s ? "/" + s : "/"; }\n' +
|
|
437
|
+
'\n' +
|
|
438
|
+
'function switchTab(source, pushState) {\n' +
|
|
442
439
|
' isEnvTab = source === "env";\n' +
|
|
443
440
|
' isHttpTab = source === "http";\n' +
|
|
441
|
+
' var isZero = source === "zero";\n' +
|
|
442
|
+
' if (pushState) history.pushState(null, "", sourceToPath(source));\n' +
|
|
444
443
|
' logView.style.display = "none";\n' +
|
|
445
444
|
' envView.style.display = "none";\n' +
|
|
446
445
|
' httpView.style.display = "none";\n' +
|
|
447
446
|
' toolbar.style.display = "none";\n' +
|
|
447
|
+
' zeroToolbar.style.display = "none";\n' +
|
|
448
448
|
' httpToolbar.style.display = "none";\n' +
|
|
449
449
|
' if (isEnvTab) {\n' +
|
|
450
450
|
' envView.style.display = "block";\n' +
|
|
@@ -457,22 +457,34 @@ export function getAdminHtml(): string {
|
|
|
457
457
|
' fetchHttp();\n' +
|
|
458
458
|
' } else {\n' +
|
|
459
459
|
' logView.style.display = "block";\n' +
|
|
460
|
-
' toolbar.style.display = "flex";\n' +
|
|
461
460
|
' activeSource = source;\n' +
|
|
462
|
-
'
|
|
463
|
-
'
|
|
464
|
-
' if (source === "zero") {\n' +
|
|
461
|
+
' if (isZero) {\n' +
|
|
462
|
+
' zeroToolbar.style.display = "flex";\n' +
|
|
465
463
|
' activeLevel = "info";\n' +
|
|
466
|
-
'
|
|
467
|
-
'
|
|
468
|
-
'
|
|
469
|
-
' activeLevel = "";\n' +
|
|
470
|
-
' levelSelect.value = "";\n' +
|
|
464
|
+
' } else {\n' +
|
|
465
|
+
' toolbar.style.display = "flex";\n' +
|
|
466
|
+
' if (activeLevel === "info") { activeLevel = ""; document.getElementById("level-filter").value = ""; }\n' +
|
|
471
467
|
' }\n' +
|
|
472
468
|
' lastCursor = 0;\n' +
|
|
473
469
|
' logView.innerHTML = "";\n' +
|
|
474
470
|
' fetchLogs();\n' +
|
|
475
471
|
' }\n' +
|
|
472
|
+
'}\n' +
|
|
473
|
+
'\n' +
|
|
474
|
+
'// standalone mode: hide header + tabs\n' +
|
|
475
|
+
'if (standalone) {\n' +
|
|
476
|
+
' document.getElementById("admin-header").style.display = "none";\n' +
|
|
477
|
+
' document.getElementById("tab-bar").style.display = "none";\n' +
|
|
478
|
+
'}\n' +
|
|
479
|
+
'// activate initial tab\n' +
|
|
480
|
+
'switchTab(initSource, false);\n' +
|
|
481
|
+
'\n' +
|
|
482
|
+
'document.getElementById("tab-bar").addEventListener("click", function(e) {\n' +
|
|
483
|
+
' var tab = e.target.closest(".tab");\n' +
|
|
484
|
+
' if (!tab) return;\n' +
|
|
485
|
+
' document.querySelectorAll(".tab").forEach(function(t) { t.classList.remove("active"); });\n' +
|
|
486
|
+
' tab.classList.add("active");\n' +
|
|
487
|
+
' switchTab(tab.dataset.source, true);\n' +
|
|
476
488
|
'});\n' +
|
|
477
489
|
'\n' +
|
|
478
490
|
'document.getElementById("level-filter").addEventListener("change", function(e) {\n' +
|
|
@@ -482,6 +494,13 @@ export function getAdminHtml(): string {
|
|
|
482
494
|
' fetchLogs();\n' +
|
|
483
495
|
'});\n' +
|
|
484
496
|
'\n' +
|
|
497
|
+
'document.getElementById("zero-level-filter").addEventListener("change", function(e) {\n' +
|
|
498
|
+
' activeLevel = e.target.value;\n' +
|
|
499
|
+
' lastCursor = 0;\n' +
|
|
500
|
+
' logView.innerHTML = "";\n' +
|
|
501
|
+
' fetchLogs();\n' +
|
|
502
|
+
'});\n' +
|
|
503
|
+
'\n' +
|
|
485
504
|
'var httpFilterTimeout = null;\n' +
|
|
486
505
|
'document.getElementById("http-path-filter").addEventListener("input", function() {\n' +
|
|
487
506
|
' clearTimeout(httpFilterTimeout);\n' +
|
|
@@ -684,7 +703,6 @@ export function getAdminHtml(): string {
|
|
|
684
703
|
' setTimeout(function() { toastEl.className = "toast"; }, 2500);\n' +
|
|
685
704
|
'}\n' +
|
|
686
705
|
'\n' +
|
|
687
|
-
'fetchLogs();\n' +
|
|
688
706
|
'fetchStatus();\n' +
|
|
689
707
|
'setInterval(function() {\n' +
|
|
690
708
|
' if (document.hidden) return;\n' +
|
|
@@ -698,6 +716,12 @@ export function getAdminHtml(): string {
|
|
|
698
716
|
' else if (!isEnvTab) fetchLogs();\n' +
|
|
699
717
|
' fetchStatus();\n' +
|
|
700
718
|
'});\n' +
|
|
719
|
+
'window.addEventListener("popstate", function() {\n' +
|
|
720
|
+
' var p = window.location.pathname.replace(/\\/$/, "") || "/";\n' +
|
|
721
|
+
' var s = pathMap[p] !== undefined ? pathMap[p] : "";\n' +
|
|
722
|
+
' var tab = document.querySelector(".tab[data-source=\\"" + s + "\\"]");\n' +
|
|
723
|
+
' if (tab) tab.click();\n' +
|
|
724
|
+
'});\n' +
|
|
701
725
|
'</script>\n' +
|
|
702
726
|
'</body>\n' +
|
|
703
727
|
'</html>'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# CF-Compatible PGlite Build
|
|
2
|
+
|
|
3
|
+
PGlite compiled without `-sMAIN_MODULE=2` and `-sALLOW_TABLE_GROWTH`
|
|
4
|
+
for Cloudflare Workers compatibility.
|
|
5
|
+
|
|
6
|
+
Key differences from standard PGlite:
|
|
7
|
+
|
|
8
|
+
- No dynamic linking (extensions must be statically linked)
|
|
9
|
+
- No `addFunction`/`removeFunction` (no runtime WASM compilation)
|
|
10
|
+
- `ENVIRONMENT=web,worker` only (no node — avoids require("fs"))
|
|
11
|
+
- No side modules (no dlopen/dlsym)
|
|
12
|
+
|
|
13
|
+
Files:
|
|
14
|
+
|
|
15
|
+
- `pglite.wasm` — CF-compatible postgres WASM binary
|
|
16
|
+
- `pglite.data` — filesystem bundle (postgres data directory)
|
|
17
|
+
- `pglite.js` — Emscripten JS glue
|
|
18
|
+
|
|
19
|
+
Build: `cd postgres-pglite && docker run ... ./build-pglite-cf.sh`
|
package/src/pg-proxy.ts
CHANGED
|
@@ -936,12 +936,17 @@ async function handleReplicationMessage(
|
|
|
936
936
|
let aborted = false
|
|
937
937
|
const writer = {
|
|
938
938
|
write(chunk: Uint8Array) {
|
|
939
|
-
if (!socket.destroyed && !aborted) {
|
|
940
|
-
|
|
939
|
+
if (!socket.destroyed && !socket.writableEnded && !aborted) {
|
|
940
|
+
try {
|
|
941
|
+
socket.write(chunk)
|
|
942
|
+
} catch {
|
|
943
|
+
// socket may have closed between our check and write (EPIPE)
|
|
944
|
+
aborted = true
|
|
945
|
+
}
|
|
941
946
|
}
|
|
942
947
|
},
|
|
943
948
|
get closed() {
|
|
944
|
-
return socket.destroyed || aborted
|
|
949
|
+
return socket.destroyed || socket.writableEnded || aborted
|
|
945
950
|
},
|
|
946
951
|
}
|
|
947
952
|
|
|
@@ -961,6 +966,11 @@ async function handleReplicationMessage(
|
|
|
961
966
|
// drain incoming standby status updates
|
|
962
967
|
socket.on('data', (_chunk: Buffer) => {})
|
|
963
968
|
|
|
969
|
+
// suppress socket errors (EPIPE/ECONNRESET) during shutdown
|
|
970
|
+
socket.on('error', () => {
|
|
971
|
+
aborted = true
|
|
972
|
+
})
|
|
973
|
+
|
|
964
974
|
socket.on('close', abort)
|
|
965
975
|
|
|
966
976
|
handleStartReplication(query, writer, db, mutex).catch((err) => {
|
|
@@ -591,9 +591,9 @@ export async function handleStartReplication(
|
|
|
591
591
|
let idleTimeoutCount = 0
|
|
592
592
|
|
|
593
593
|
while (running) {
|
|
594
|
-
// check if the connection was closed
|
|
595
|
-
if (writer.closed) {
|
|
596
|
-
log.debug.proxy('replication: writer closed, exiting poll loop')
|
|
594
|
+
// check if the connection or database was closed
|
|
595
|
+
if (writer.closed || db.closed) {
|
|
596
|
+
log.debug.proxy('replication: writer/db closed, exiting poll loop')
|
|
597
597
|
running = false
|
|
598
598
|
break
|
|
599
599
|
}
|
|
@@ -606,7 +606,7 @@ export async function handleStartReplication(
|
|
|
606
606
|
// check if a signal arrived while we were processing
|
|
607
607
|
if (!signalPending) {
|
|
608
608
|
const wasSignaled = await waitForWakeup(pollIntervalIdle)
|
|
609
|
-
if (writer.closed) {
|
|
609
|
+
if (writer.closed || db.closed) {
|
|
610
610
|
running = false
|
|
611
611
|
break
|
|
612
612
|
}
|
|
@@ -779,7 +779,12 @@ export async function handleStartReplication(
|
|
|
779
779
|
} catch (err: unknown) {
|
|
780
780
|
const msg = err instanceof Error ? err.message : String(err)
|
|
781
781
|
log.repl(`replication poll error: ${msg}`)
|
|
782
|
-
if (
|
|
782
|
+
if (
|
|
783
|
+
msg.includes('closed') ||
|
|
784
|
+
msg.includes('destroyed') ||
|
|
785
|
+
msg.includes('ECONNRESET') ||
|
|
786
|
+
msg.includes('EPIPE')
|
|
787
|
+
) {
|
|
783
788
|
running = false
|
|
784
789
|
break
|
|
785
790
|
}
|
|
@@ -488,6 +488,10 @@ describe('zero-cache pgoutput compatibility', { timeout: 30000 }, () => {
|
|
|
488
488
|
afterEach(async () => {
|
|
489
489
|
signalReplicationChange()
|
|
490
490
|
server?.close()
|
|
491
|
+
// yield to let socket close events propagate so the replication
|
|
492
|
+
// poll loop exits before we close pglite (0.4.x close() is stricter)
|
|
493
|
+
await new Promise((r) => setTimeout(r, 50))
|
|
494
|
+
signalReplicationChange()
|
|
491
495
|
await db?.close()
|
|
492
496
|
})
|
|
493
497
|
|