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/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 16px;\n' +
39
+ ' padding: 4px 12px;\n' +
40
40
  ' background: var(--surface);\n' +
41
41
  ' border-bottom: 0.5px solid var(--border);\n' +
42
- ' gap: 12px;\n' +
42
+ ' gap: 8px;\n' +
43
43
  ' flex-shrink: 0;\n' +
44
44
  '}\n' +
45
45
  '.header .logo {\n' +
46
- ' font-size: 15px;\n' +
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: 2px 8px;\n' +
54
+ ' padding: 1px 6px;\n' +
55
55
  ' border-radius: 12px;\n' +
56
- ' font-size: 11px;\n' +
56
+ ' font-size: 10px;\n' +
57
57
  ' border: 0.5px solid var(--border);\n' +
58
58
  ' color: var(--text-dim);\n' +
59
- ' gap: 4px;\n' +
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 16px;\n' +
70
+ ' padding: 0 12px;\n' +
71
71
  ' background: var(--surface);\n' +
72
72
  ' border-bottom: 0.5px solid var(--border);\n' +
73
- ' gap: 2px;\n' +
73
+ ' gap: 0;\n' +
74
74
  ' flex-shrink: 0;\n' +
75
75
  '}\n' +
76
76
  '.tab {\n' +
77
- ' padding: 8px 14px;\n' +
78
- ' font-size: 12px;\n' +
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: 8px 16px;\n' +
98
- ' gap: 10px;\n' +
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: 11px;\n' +
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: 6px;\n' +
113
- ' padding: 4px 8px;\n' +
114
- ' font-size: 12px;\n' +
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: 6px;\n' +
124
- ' padding: 4px 8px;\n' +
125
- ' font-size: 12px;\n' +
123
+ ' border-radius: 4px;\n' +
124
+ ' padding: 2px 6px;\n' +
125
+ ' font-size: 11px;\n' +
126
126
  ' font-family: inherit;\n' +
127
- ' width: 200px;\n' +
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: 5px 12px;\n' +
134
- ' border-radius: 6px;\n' +
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: 11px;\n' +
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: 8px 16px;\n' +
168
- ' font-size: 12px;\n' +
169
- ' line-height: 1.5;\n' +
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
- // actions panel
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: 8px;\n' +
305
- ' padding: 4px 0;\n' +
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">&#9670; 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)">&#x2715; 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)">&#x21bb; Restart</button>\n' +
369
+ ' <button class="action-btn orange" data-zero-action onclick="doAction(\'reset-zero\', this)">&#x21ba; Reset</button>\n' +
370
+ ' <button class="action-btn red" data-zero-action onclick="doAction(\'reset-zero-full\', this)">&#x26a0; Full</button>\n' +
371
+ ' <button class="action-btn gray" onclick="doAction(\'clear-logs\', this)">&#x2715; 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)">&#x2715; 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()">&#x2193; 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)">&#x21bb; Restart</button>\n' +
404
- ' <button class="action-btn orange" data-zero-action onclick="doAction(\'reset-zero\', this)">&#x21ba; Reset</button>\n' +
405
- ' <button class="action-btn red" data-zero-action onclick="doAction(\'reset-zero-full\', this)">&#x26a0; 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)">&#x2715; Clear Logs</button>\n' +
410
- ' <button class="action-btn gray" onclick="doAction(\'clear-http\', this)">&#x2715; 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
- 'var activeSource = "";\n' +
419
- 'var activeLevel = "";\n' +
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 = false;\n' +
424
- 'var isHttpTab = false;\n' +
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
- 'document.getElementById("tab-bar").addEventListener("click", function(e) {\n' +
437
- ' var tab = e.target.closest(".tab");\n' +
438
- ' if (!tab) return;\n' +
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
- ' // default zero tab to info level (too verbose otherwise)\n' +
463
- ' var levelSelect = document.getElementById("level-filter");\n' +
464
- ' if (source === "zero") {\n' +
461
+ ' if (isZero) {\n' +
462
+ ' zeroToolbar.style.display = "flex";\n' +
465
463
  ' activeLevel = "info";\n' +
466
- ' levelSelect.value = "info";\n' +
467
- ' } else if (activeLevel === "info" && levelSelect.value === "info") {\n' +
468
- ' // reset to all levels when leaving zero tab if still on info\n' +
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
- socket.write(chunk)
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 (msg.includes('closed') || msg.includes('destroyed')) {
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