@fuzdev/fuz_app 0.66.0 → 0.67.1
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/spawn_backend.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAQ,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAI5D,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIvD,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC7B,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;
|
|
1
|
+
{"version":3,"file":"spawn_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/spawn_backend.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAQ,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAI5D,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIvD,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC7B,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AA4ID;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAU,QAAQ,aAAa,KAAG,OAAO,CAAC,aAAa,CAiHhF,CAAC"}
|
|
@@ -31,6 +31,14 @@ import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
|
31
31
|
import { dirname } from 'node:path';
|
|
32
32
|
/** Number of ms between health-probe attempts. Tuned to be cheap on busy CI runners. */
|
|
33
33
|
const HEALTH_PROBE_INTERVAL_MS = 100;
|
|
34
|
+
/**
|
|
35
|
+
* Grace window after teardown's SIGTERM before escalating to SIGKILL on the
|
|
36
|
+
* process group. A well-behaved backend exits within milliseconds; this only
|
|
37
|
+
* fires for one that ignores SIGTERM or whose graceful shutdown never
|
|
38
|
+
* completes (e.g. a runtime whose `server.stop()` promise never resolves), so
|
|
39
|
+
* the await below can't strand the whole run forever.
|
|
40
|
+
*/
|
|
41
|
+
const TEARDOWN_SIGKILL_GRACE_MS = 3_000;
|
|
34
42
|
/**
|
|
35
43
|
* Sleep helper for the probe loop. Resolves after `ms`.
|
|
36
44
|
*/
|
|
@@ -208,10 +216,30 @@ export const spawn_backend = async (config) => {
|
|
|
208
216
|
live_teardowns.delete(teardown_sync);
|
|
209
217
|
if (exit_info !== null)
|
|
210
218
|
return;
|
|
211
|
-
// Wait for the child to actually exit so callers can be sure the
|
|
212
|
-
//
|
|
219
|
+
// Wait for the child to actually exit so callers can be sure the port
|
|
220
|
+
// is free. A backend that ignores SIGTERM — or whose graceful shutdown
|
|
221
|
+
// wedges (e.g. a runtime whose `server.stop()` never resolves) — would
|
|
222
|
+
// otherwise hang this await forever and strand the run, so escalate to
|
|
223
|
+
// SIGKILL on the process group after a grace window. SIGKILL is
|
|
224
|
+
// uncatchable, so the child then exits and the `'exit'` listener
|
|
225
|
+
// resolves. Defense-in-depth: the per-runtime adapter shutdown is the
|
|
226
|
+
// primary fix; this guarantees teardown completes regardless.
|
|
213
227
|
await new Promise((resolve) => {
|
|
214
|
-
|
|
228
|
+
const kill_timer = setTimeout(() => {
|
|
229
|
+
if (child.pid !== undefined && exit_info === null) {
|
|
230
|
+
try {
|
|
231
|
+
// Negative pid → process group.
|
|
232
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// Already dead; ignore.
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}, TEARDOWN_SIGKILL_GRACE_MS);
|
|
239
|
+
child.once('exit', () => {
|
|
240
|
+
clearTimeout(kill_timer);
|
|
241
|
+
resolve();
|
|
242
|
+
});
|
|
215
243
|
});
|
|
216
244
|
};
|
|
217
245
|
live_teardowns.add(teardown_sync);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing_server_bun.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_bun.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6B9B,OAAO,KAAK,EAAc,oBAAoB,EAAC,MAAM,0BAA0B,CAAC;AAehF,kDAAkD;AAClD,eAAO,MAAM,0BAA0B,QAAO,
|
|
1
|
+
{"version":3,"file":"testing_server_bun.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_bun.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6B9B,OAAO,KAAK,EAAc,oBAAoB,EAAC,MAAM,0BAA0B,CAAC;AAehF,kDAAkD;AAClD,eAAO,MAAM,0BAA0B,QAAO,oBA0D5C,CAAC"}
|
|
@@ -43,8 +43,35 @@ export const create_bun_testing_adapter = () => ({
|
|
|
43
43
|
websocket,
|
|
44
44
|
});
|
|
45
45
|
const handle = {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// Bun bug (1.3.14): after a *server-initiated* WebSocket close
|
|
47
|
+
// (`ServerWebSocket.close()` / `hono/bun`'s `WSContext.close()`),
|
|
48
|
+
// `server.stop()` never resolves — Bun doesn't decrement its
|
|
49
|
+
// active-connection count for a server-closed socket, so the stop
|
|
50
|
+
// waits forever for a connection it already closed. The trigger is
|
|
51
|
+
// orthogonal to HTTP load, hono-vs-raw `Bun.serve`, in-vs-cross
|
|
52
|
+
// process, the force flag (`stop()`/`stop(false)`/`stop(true)` all
|
|
53
|
+
// hang), and the client runtime — a single server-closed WS is
|
|
54
|
+
// necessary and sufficient. Client-initiated close or leaving the
|
|
55
|
+
// socket open both stop cleanly in ~0ms. In this suite the trigger is
|
|
56
|
+
// `create_ws_auth_guard` closing the socket on `session_revoke_all`
|
|
57
|
+
// (the `ws.cross.test.ts` close-on-revoke case) — the only
|
|
58
|
+
// server-initiated WS close, which is why teardown hangs there and
|
|
59
|
+
// not under HTTP-only or client-closed WS traffic.
|
|
60
|
+
//
|
|
61
|
+
// So initiate a force-close (`true` drops active connections, no
|
|
62
|
+
// drain) but DON'T await it: awaiting hangs `start_testing_server`'s
|
|
63
|
+
// shutdown forever — `built.close()` and `exit(0)` never run, and the
|
|
64
|
+
// spawning harness blocks on a child that never exits (observed as a
|
|
65
|
+
// multi-minute hang needing SIGKILL). The force-close still tears the
|
|
66
|
+
// live sockets down; the `exit(0)` the core fires immediately after
|
|
67
|
+
// does the real teardown a few ms later. Node/Deno don't need this —
|
|
68
|
+
// their `shutdown()`/`close()` resolve normally. The `.catch` guards a
|
|
69
|
+
// future Bun that rejects rather than hangs; if a future Bun resolves
|
|
70
|
+
// the promise after a server-initiated close, revert to
|
|
71
|
+
// `await server.stop(true)` to mirror the Node/Deno adapters.
|
|
72
|
+
shutdown: () => {
|
|
73
|
+
void Promise.resolve(server.stop(true)).catch(() => { });
|
|
74
|
+
return Promise.resolve();
|
|
48
75
|
},
|
|
49
76
|
native: server,
|
|
50
77
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzdev/fuz_app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.1",
|
|
4
4
|
"description": "fullstack app library",
|
|
5
5
|
"glyph": "🗝",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"build": "gro build",
|
|
15
15
|
"check": "gro check",
|
|
16
16
|
"test": "gro test",
|
|
17
|
+
"test:cross": "FUZ_TEST_CROSS_BACKEND=1 vitest run --project cross_backend_ts_node --project cross_backend_ts_deno --project cross_backend_ts_bun",
|
|
18
|
+
"test:cross:spine-stub": "FUZ_TEST_CROSS_BACKEND=1 vitest run --project cross_backend_spine_stub",
|
|
19
|
+
"benchmark:cross-impl": "gro run src/benchmarks/cross_impl.bench.ts",
|
|
17
20
|
"preview": "vite preview",
|
|
18
21
|
"deploy": "gro deploy"
|
|
19
22
|
},
|
|
@@ -25,6 +28,8 @@
|
|
|
25
28
|
"@electric-sql/pglite": ">=0.4",
|
|
26
29
|
"@fuzdev/blake3_wasm": ">=0.1.0",
|
|
27
30
|
"@fuzdev/fuz_util": ">=0.53.4",
|
|
31
|
+
"@hono/node-server": ">=1",
|
|
32
|
+
"@hono/node-ws": ">=1",
|
|
28
33
|
"@node-rs/argon2": ">=2",
|
|
29
34
|
"@sveltejs/kit": "^2",
|
|
30
35
|
"hono": ">=4",
|
|
@@ -37,6 +42,12 @@
|
|
|
37
42
|
"@electric-sql/pglite": {
|
|
38
43
|
"optional": true
|
|
39
44
|
},
|
|
45
|
+
"@hono/node-server": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"@hono/node-ws": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
40
51
|
"pg": {
|
|
41
52
|
"optional": true
|
|
42
53
|
},
|
|
@@ -50,8 +61,10 @@
|
|
|
50
61
|
"@fuzdev/fuz_code": "^0.45.1",
|
|
51
62
|
"@fuzdev/fuz_css": "^0.59.0",
|
|
52
63
|
"@fuzdev/fuz_ui": "^0.192.0",
|
|
53
|
-
"@fuzdev/fuz_util": "^0.
|
|
54
|
-
"@fuzdev/gro": "^0.199.
|
|
64
|
+
"@fuzdev/fuz_util": "^0.61.0",
|
|
65
|
+
"@fuzdev/gro": "^0.199.1",
|
|
66
|
+
"@hono/node-server": "^1.19.14",
|
|
67
|
+
"@hono/node-ws": "^1.3.1",
|
|
55
68
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
56
69
|
"@node-rs/argon2": "^2.0.2",
|
|
57
70
|
"@ryanatkn/eslint-config": "^0.12.1",
|