redweb 0.13.0 → 0.13.2

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/docs/topics.json CHANGED
@@ -16,6 +16,6 @@
16
16
  { "id": "operations", "title": "Deploy and operate socket services", "summary": "Readiness, shutdown, capacity, reconnection, and distributed boundaries.", "source": "docs/MULTIPLAYER_OPERATIONS.md" },
17
17
  { "id": "production-contract", "title": "Production guarantees and limits", "summary": "Resource ownership, delivery semantics, compatibility, and release gates.", "source": "docs/PRODUCTION_READINESS.md" },
18
18
  { "id": "verification", "title": "Recorded verification evidence", "summary": "Historical measurements and their exact scope; not proof of a newer release.", "source": "docs/VERIFICATION_EVIDENCE.md" },
19
- { "id": "release-status", "title": "Development release status", "summary": "Acceptance checklist, completed increments, and unfinished release gates.", "source": "docs/AGENT_READY_ACCEPTANCE.md" },
19
+ { "id": "release-status", "title": "Release status", "summary": "Acceptance checklist, completed increments, and remaining publication or deployment boundaries.", "source": "docs/AGENT_READY_ACCEPTANCE.md" },
20
20
  { "id": "release-trust", "title": "Choose and verify a release", "summary": "Maintained runtimes, compiler/browser verification boundaries, pinned packages, registry signatures, provenance and support limits.", "source": "docs/RELEASE_TRUST.md" }
21
21
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redweb",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "A small Node.js foundation for HTTP, WebSockets, multiplayer services, and server-rendered HTML",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -44,6 +44,7 @@
44
44
  "scripts": {
45
45
  "pretest": "node scripts/build-live-html-examples.js --check && node scripts/generate-protocol-types.js --check && node scripts/generate-docs.js --check && tsc -p tests/types/tsconfig.json && tsc -p tests/types/tsconfig.jsxdev.json && tsc -p tests/types/tsconfig.standard.json",
46
46
  "prepack": "node scripts/build-live-html-examples.js --check && node scripts/generate-docs.js --check",
47
+ "prepublishOnly": "node scripts/generate-docs.js --release-check",
47
48
  "generate:docs": "node scripts/generate-docs.js",
48
49
  "verify:docs:mcp": "npm --prefix integrations/docs-mcp test",
49
50
  "verify:cli": "c8 --all --src=bin --include=bin/redweb.js --reporter=text --reporter=json --reports-dir=coverage/cli-entrypoint --check-coverage --lines=100 --branches=100 --functions=100 --statements=100 node node_modules/jest/bin/jest.js tests/integration/init-cli.integration.test.js tests/integration/add-cli.integration.test.js --testNamePattern=\"redweb init CLI integration|human and subprocess CLI\" --runInBand --coverage=false",
@@ -2,7 +2,12 @@ const { performance } = require('perf_hooks');
2
2
 
3
3
  function acknowledgePong() {
4
4
  const state = this.__redwebHeartbeatState;
5
- if (state) state.awaitingPong = false;
5
+ if (!state) return;
6
+ state.awaitingPong = false;
7
+ if (state.terminationTimer) {
8
+ clearTimeout(state.terminationTimer);
9
+ state.terminationTimer = null;
10
+ }
6
11
  }
7
12
 
8
13
  class HeartbeatMonitor {
@@ -25,7 +30,7 @@ class HeartbeatMonitor {
25
30
 
26
31
  attach(socket) {
27
32
  this.detach(socket);
28
- const state = { awaitingPong: false, lastPing: null, terminationPending: false };
33
+ const state = { awaitingPong: false, lastPing: null, terminationTimer: null };
29
34
  socket.__redwebHeartbeatState = state;
30
35
  this.sockets.set(socket, state);
31
36
  socket.on('pong', acknowledgePong);
@@ -34,6 +39,8 @@ class HeartbeatMonitor {
34
39
  detach(socket) {
35
40
  const state = this.sockets.get(socket);
36
41
  if (!state) return false;
42
+ if (state.terminationTimer) clearTimeout(state.terminationTimer);
43
+ state.terminationTimer = null;
37
44
  socket.off?.('pong', acknowledgePong);
38
45
  delete socket.__redwebHeartbeatState;
39
46
  this.sockets.delete(socket);
@@ -60,20 +67,19 @@ class HeartbeatMonitor {
60
67
  }
61
68
 
62
69
  scheduleTermination(socket, state) {
63
- if (state.terminationPending) return;
64
- state.terminationPending = true;
65
- // Defer to the check phase so already-dispatched pong handling can win.
66
- const check = setImmediate(() => {
67
- state.terminationPending = false;
68
- if (this.sockets.get(socket) !== state || !state.awaitingPong) return;
70
+ if (state.terminationTimer) return;
71
+ // Give an already-sent pong one full timeout window to reach JavaScript.
72
+ // This prevents a delayed server event loop from blaming a responsive peer.
73
+ state.terminationTimer = setTimeout(() => {
74
+ state.terminationTimer = null;
69
75
  this.detach(socket);
70
76
  try {
71
77
  socket.terminate?.();
72
78
  } catch (error) {
73
79
  this.logger?.error?.('Error terminating unresponsive socket:', error);
74
80
  }
75
- });
76
- check.unref();
81
+ }, this.timeoutMs);
82
+ state.terminationTimer.unref();
77
83
  }
78
84
 
79
85
  stop() {