redweb 0.12.0 → 0.13.0
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/CHANGELOG.md +166 -9
- package/README.md +177 -523
- package/bin/redweb.js +11 -20
- package/client.d.ts +7 -2
- package/config/tsconfig.json +14 -14
- package/contract.d.ts +45 -0
- package/contract.js +5 -0
- package/docs/ACTION_INPUT_VERIFICATION.md +96 -0
- package/docs/ADMISSION_TIMEOUT_VERIFICATION.md +69 -0
- package/docs/AGENT_ACCESS.md +35 -0
- package/docs/AGENT_EVALUATION.md +58 -0
- package/docs/AGENT_READY_ACCEPTANCE.md +763 -0
- package/docs/APPLICATION_RECORDER_VERIFICATION.md +50 -0
- package/docs/BENCHMARK_VERIFICATION.md +307 -0
- package/docs/BROWSER_OWNER_VERIFICATION.md +191 -0
- package/docs/CLI.md +116 -0
- package/docs/CLIENT_DEVELOPMENT.md +152 -0
- package/docs/CLIENT_POLISH_VERIFICATION.md +282 -0
- package/docs/COVERAGE_COUNTER_VALIDATION.md +109 -0
- package/docs/COVERAGE_SCOPE_AUDIT.md +1183 -0
- package/docs/DEVELOPMENT.md +79 -0
- package/docs/DIAGNOSTIC_COMPATIBILITY.md +76 -0
- package/docs/DOCUMENTATION.md +37 -0
- package/docs/FEEDBACK_COMMAND_VERIFICATION.md +228 -0
- package/docs/GETTING_STARTED.md +58 -0
- package/docs/JSX_PERFORMANCE_VERIFICATION.md +59 -0
- package/docs/LIVE_HTML.md +169 -21
- package/docs/LIVE_HTML_LOAD_VERIFICATION.md +98 -0
- package/docs/MIGRATION.md +28 -0
- package/docs/MULTIPLAYER_OPERATIONS.md +26 -4
- package/docs/ORIGINAL_RECOVERY_VERIFICATION.md +100 -0
- package/docs/PACKAGED_EXAMPLE_VERIFICATION.md +126 -0
- package/docs/POLISH_RELEASE_CHECKPOINT.md +91 -0
- package/docs/PROCESS_CLEANUP_OBSERVATION.md +61 -0
- package/docs/PROCESS_REAPING_VERIFICATION.md +30 -0
- package/docs/PRODUCTION_READINESS.md +11 -2
- package/docs/RECOVERY_CLIENT_HEAP.md +201 -0
- package/docs/RECOVERY_CODE_ATTRIBUTION.md +174 -0
- package/docs/RECOVERY_CODE_CENSUS.md +158 -0
- package/docs/RECOVERY_COMPARISON.md +103 -0
- package/docs/RECOVERY_DEOPTIMIZATION.md +169 -0
- package/docs/RECOVERY_FOLLOWUP_SPIKE.md +147 -0
- package/docs/RECOVERY_INVESTIGATION.md +229 -0
- package/docs/RECOVERY_RUNTIME_CONTROLS.md +181 -0
- package/docs/RELEASE_TRUST.md +58 -0
- package/docs/ROOM_AUTHORIZATION.md +49 -0
- package/docs/RUNTIME_DIAGNOSTICS.md +78 -0
- package/docs/SERVER_RECOVERY_CANDIDATE.md +185 -0
- package/docs/SOAK_ROTATION_OBSERVATION.md +160 -0
- package/docs/SOAK_VERIFICATION.md +154 -0
- package/docs/SOCKET_CONTRACTS.md +39 -0
- package/docs/SPLIT_RECOVERY_COVERAGE.md +83 -0
- package/docs/SPLIT_RECOVERY_ERROR_HANDLING.md +67 -0
- package/docs/STARTER_COORDINATOR_VERIFICATION.md +112 -0
- package/docs/STARTER_LIFECYCLE_VERIFICATION.md +75 -0
- package/docs/STARTER_REPORT_RETENTION.md +73 -0
- package/docs/VERIFICATION_EVIDENCE.md +2 -0
- package/docs/generated.json +2154 -0
- package/docs/guides/chatroom.md +27 -0
- package/docs/guides/http-websocket.md +28 -0
- package/docs/guides/jsx-without-react.md +26 -0
- package/docs/guides/realtime-dashboard.md +29 -0
- package/docs/guides/typed-websockets.md +26 -0
- package/docs/reference.json +1207 -0
- package/docs/snippets/room-access.tsx +51 -0
- package/docs/topics.json +21 -0
- package/examples/live-html/chatroom.js +207 -268
- package/examples/live-html/chatroom.tsx +167 -0
- package/examples/live-html/jsx-page.js +1 -1
- package/examples/live-html/jsx-page.tsx +1 -1
- package/examples/live-html/tsconfig.json +8 -7
- package/index.d.ts +170 -45
- package/index.js +2 -0
- package/jsx-dev-runtime.js +2 -2
- package/jsx-runtime.d.ts +7 -2
- package/package.json +88 -7
- package/recipes/add/artifact.test.cjs +57 -0
- package/recipes/add/live.tsx +18 -0
- package/recipes/add/socket-route.ts +24 -0
- package/recipes/chat/README.md +22 -0
- package/recipes/chat/app.test.cjs +105 -0
- package/recipes/chat/app.tsx +9 -0
- package/recipes/dashboard/README.md +43 -0
- package/recipes/dashboard/admin.ts +21 -0
- package/recipes/dashboard/app.css +16 -0
- package/recipes/dashboard/app.test.cjs +450 -0
- package/recipes/dashboard/app.tsx +86 -0
- package/recipes/dashboard/auth.ts +80 -0
- package/recipes/dashboard/cards.tsx +102 -0
- package/recipes/dashboard/rate-window.test.cjs +17 -0
- package/recipes/dashboard/store.ts +120 -0
- package/recipes/http-ws/README.md +11 -0
- package/recipes/http-ws/app.test.cjs +92 -0
- package/recipes/http-ws/app.tsx +36 -0
- package/recipes/realtime/README.md +8 -0
- package/recipes/realtime/app.test.cjs +15 -0
- package/recipes/realtime/app.tsx +28 -0
- package/recipes/shared/README.md +40 -0
- package/recipes/shared/app.css +8 -0
- package/recipes/shared/copy-assets.cjs +8 -0
- package/recipes/shared/network.cjs +59 -0
- package/recipes/shared/run-app.test.cjs +158 -0
- package/recipes/shared/run-app.ts +50 -0
- package/recipes/site/README.md +4 -0
- package/recipes/site/app.test.cjs +19 -0
- package/recipes/site/app.tsx +25 -0
- package/recipes/socket/README.md +39 -0
- package/recipes/socket/app.test.cjs +85 -0
- package/recipes/socket/app.tsx +30 -0
- package/recipes/socket/contract.ts +12 -0
- package/recipes/socket/handlers.ts +40 -0
- package/src/OwnedServerLifecycle.js +66 -0
- package/src/access/AccessPolicy.js +37 -0
- package/src/access/AuthenticationFailure.js +13 -0
- package/src/access/RequestFailure.js +33 -0
- package/src/access/failure-codes.json +25 -0
- package/src/async/BoundedOperation.js +62 -0
- package/src/cli/ActionReferences.js +193 -0
- package/src/cli/AdditionLayout.js +140 -0
- package/src/cli/FilePlan.js +94 -0
- package/src/cli/ProjectAddition.js +60 -0
- package/src/cli/ProjectConfig.js +26 -0
- package/src/cli/ProjectDoctor.js +112 -0
- package/src/cli/ProjectInitializer.js +20 -30
- package/src/cli/SourceInspector.js +207 -0
- package/src/cli/StaticSource.js +192 -0
- package/src/cli/arguments.js +62 -0
- package/src/cli/formatCommand.js +10 -0
- package/src/cli/run.js +57 -0
- package/src/cli/templates.js +86 -87
- package/src/context/RequestSnapshot.js +41 -0
- package/src/dataProperty.js +11 -0
- package/src/development/DevelopmentPageManager.js +48 -0
- package/src/development/Inspection.js +104 -0
- package/src/development/ObservedRenderer.js +42 -0
- package/src/development/description.js +35 -0
- package/src/development/loopbackRequest.js +27 -0
- package/src/development/refreshBrowser.js +96 -0
- package/src/development/refreshStyles.js +9 -0
- package/src/development/settings.js +17 -0
- package/src/docs/Documentation.js +182 -0
- package/src/htmx/ActionDefinition.js +44 -0
- package/src/htmx/Jsx.js +24 -8
- package/src/htmx/LiveHtmlServer.js +41 -19
- package/src/htmx/LivePage.js +63 -13
- package/src/htmx/PageIdentity.js +32 -0
- package/src/htmx/PageLifetime.js +37 -0
- package/src/htmx/PageManager.js +203 -74
- package/src/htmx/ReactiveRenderer.js +241 -0
- package/src/htmx/StaticExporter.js +1 -1
- package/src/htmx/TemplateRenderer.js +13 -7
- package/src/htmx/browserRuntime.js +2 -93
- package/src/htmx/metadata.js +19 -7
- package/src/validation/ActionInputError.js +12 -0
- package/src/validation/SchemaValidator.js +38 -0
- package/src/ws/AdmissionPolicy.js +24 -23
- package/src/ws/BaseSocketServer.js +53 -38
- package/src/ws/ContractValidationError.js +12 -0
- package/src/ws/HeartbeatMonitor.js +19 -7
- package/src/ws/ProtocolPolicy.js +1 -1
- package/src/ws/RoomAccess.js +82 -0
- package/src/ws/RoomRegistry.js +56 -6
- package/src/ws/RouteRuntime.js +56 -10
- package/src/ws/SocketContract.js +112 -0
- package/src/ws/SocketRoute.js +18 -0
- package/src/ws/protocol-schema.json +6 -1
- package/examples/live-html/chatroom.ts +0 -217
package/README.md
CHANGED
|
@@ -1,636 +1,290 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Redweb
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Build a TypeScript website and its realtime backend together. Decorated classes own state and actions; server-rendered JSX updates the browser through WebSockets. No React, frontend bundler, or separate socket glue is required.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use the same package for a live site, static HTML, Express HTTP endpoints, or routed WebSocket services.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
npm install redweb
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Start a TypeScript + TSX project with Redweb's compiler preset and a small server-rendered page:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npx redweb init
|
|
17
|
-
npm install
|
|
18
|
-
npm run dev
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Pass a directory to create a new project there: `npx redweb init my-app`. Existing files are never overwritten, so rerunning the command is safe.
|
|
9
|
+
Start with a complete, tested counter application:
|
|
22
10
|
|
|
23
|
-
|
|
11
|
+
<!-- redweb:setup:start -->
|
|
12
|
+
> Unreleased development documentation. Package metadata is 0.13.0, but these features are not claimed to be published in that npm version. Use the matching Redweb tarball described in the recipe setup; its published client dependency installs automatically. Do not install latest and assume compatibility.
|
|
24
13
|
|
|
25
|
-
|
|
26
|
-
const {
|
|
27
|
-
HttpServer, // HTTP over Express
|
|
28
|
-
HttpsServer, // HTTP with TLS (key/cert required)
|
|
29
|
-
SocketServer, // WebSocket over HTTP
|
|
30
|
-
SecureSocketServer, // WebSocket over HTTPS
|
|
31
|
-
SocketRoute, // Per-path WebSocket routing
|
|
32
|
-
SocketService, // Route-scoped background/tick logic
|
|
33
|
-
FixedStepService, // Drift-aware, non-overlapping simulation ticks
|
|
34
|
-
SocketRegistry, // Evented in-memory store
|
|
35
|
-
RoomRegistry, // Bounded route-local connection groups
|
|
36
|
-
SessionRegistry, // Bounded, expiring application-issued sessions
|
|
37
|
-
BaseHttpServer, // Express app builder for advanced composition
|
|
38
|
-
BaseHandler, // WebSocket message handler base
|
|
39
|
-
sendJson, // Utility to stringify+send
|
|
40
|
-
HTTP_OPTIONS, // Defaults for HTTP servers
|
|
41
|
-
ENCODINGS, // json/urlencoded encoding names
|
|
42
|
-
SOCKET_OPTIONS, // Defaults for socket servers
|
|
43
|
-
METHODS, // Express method helpers
|
|
44
|
-
LiveHtmlServer, // SSR plus lifecycle-safe realtime HTML
|
|
45
|
-
HtmlRenderer, // Safe HTML templates, collections, and state payloads
|
|
46
|
-
LivePage, // Optional base for advanced page internals
|
|
47
|
-
page, state, action, view, // Live HTML decorators
|
|
48
|
-
html, start // Safe HTML plus one-call page startup
|
|
49
|
-
} = require('redweb');
|
|
50
|
-
```
|
|
14
|
+
Replace `TARBALL` with the absolute path to the matching Redweb tarball produced by `npm pack` (quoted if it contains spaces). This is an explicit prerequisite, not an npm package name. Both commands must use the same tarball. The published redweb-client dependency installs automatically; no separate client checkout or linking is required:
|
|
51
15
|
|
|
52
|
-
|
|
16
|
+
```sh
|
|
17
|
+
npx --yes --package TARBALL redweb init my-realtime --template realtime
|
|
18
|
+
cd my-realtime
|
|
19
|
+
npm install --save-exact TARBALL
|
|
20
|
+
npm test
|
|
21
|
+
npm run dev
|
|
22
|
+
```
|
|
53
23
|
|
|
54
|
-
|
|
24
|
+
This prerelease Redweb artifact is development-only until its release checks finish. For released applications, use an available versioned release guide.
|
|
25
|
+
<!-- redweb:setup:end -->
|
|
55
26
|
|
|
56
|
-
|
|
27
|
+
Open two tabs at `http://localhost:8181`. Clicking either button changes the counter on the server and updates both tabs.
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
```json
|
|
61
|
-
{
|
|
62
|
-
"extends": "redweb/tsconfig.json",
|
|
63
|
-
"compilerOptions": {
|
|
64
|
-
"rootDir": "src",
|
|
65
|
-
"outDir": "dist"
|
|
66
|
-
},
|
|
67
|
-
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
|
68
|
-
}
|
|
69
|
-
```
|
|
29
|
+
This is the starter's exact `src/app.tsx`. The initializer also supplies its stylesheet, compiler configuration, shutdown helper, and real-network tests; the file is not a standalone copy-and-run program.
|
|
70
30
|
|
|
31
|
+
<!-- redweb:realtime:start -->
|
|
71
32
|
```tsx
|
|
72
|
-
import {
|
|
73
|
-
import
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<button rw-click="increment">
|
|
94
|
-
Count <output data-rw-state="count">{this.count}</output>
|
|
95
|
-
</button>
|
|
96
|
-
</Card>
|
|
97
|
-
</main>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
33
|
+
import { action, page, start, state, type LiveHtmlStartOptions } from 'redweb';
|
|
34
|
+
import { runApp } from './run-app';
|
|
35
|
+
|
|
36
|
+
@page('/', { css: 'app.css', shared: true })
|
|
37
|
+
export class CounterPage {
|
|
38
|
+
@state() count = 0;
|
|
39
|
+
|
|
40
|
+
@action()
|
|
41
|
+
increment() { this.count += 1; }
|
|
42
|
+
|
|
43
|
+
render() {
|
|
44
|
+
return (
|
|
45
|
+
<main class="home">
|
|
46
|
+
<h1>A counter owned by the server</h1>
|
|
47
|
+
<p>Open this page in two tabs. Either button updates both.</p>
|
|
48
|
+
<button rw-click="increment">
|
|
49
|
+
Count <output>{this.count}</output>
|
|
50
|
+
</button>
|
|
51
|
+
</main>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
100
54
|
}
|
|
101
55
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Text and attribute values are escaped automatically. URL attributes use Redweb's existing safe-protocol policy. `on*`, inline `style`, `srcdoc`, `srcset`, and executable `<script>` or `<style>` children are rejected; use `rw-*` server directives and external CSS or JavaScript assets. Existing `html` fragments can be nested in TSX, and TSX fragments can be nested in `html`, so migration can be incremental.
|
|
106
|
-
|
|
107
|
-
Ordinary declarative `.html` templates remain available when separating markup into a standalone file is preferable:
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
import { page, start, state } from 'redweb';
|
|
111
|
-
|
|
112
|
-
@page('/', { template: 'counter.html', css: 'counter.css' })
|
|
113
|
-
class CounterPage {
|
|
114
|
-
@state()
|
|
115
|
-
count = 0;
|
|
116
|
-
|
|
117
|
-
private ticker?: NodeJS.Timeout;
|
|
118
|
-
|
|
119
|
-
connected() {
|
|
120
|
-
this.ticker = setInterval(() => this.count++, 1000);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
disconnected() {
|
|
124
|
-
clearInterval(this.ticker);
|
|
125
|
-
}
|
|
56
|
+
export function createApp(options: LiveHtmlStartOptions = {}) {
|
|
57
|
+
return start(CounterPage, { port: Number(process.env.PORT ?? 8181), templateRoot: __dirname, ...options });
|
|
126
58
|
}
|
|
127
59
|
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
`counter.html` contains no executable server code:
|
|
132
|
-
|
|
133
|
-
```html
|
|
134
|
-
<h1>Server-side counter</h1>
|
|
135
|
-
<output aria-live="polite" data-rw-state="count"></output>
|
|
60
|
+
if (require.main === module) runApp(createApp);
|
|
136
61
|
```
|
|
62
|
+
<!-- redweb:realtime:end -->
|
|
137
63
|
|
|
138
|
-
|
|
64
|
+
## Choose what to build
|
|
139
65
|
|
|
140
|
-
|
|
66
|
+
The links below describe each starter and its boundaries. Reuse the version-correct setup above, changing both the directory name and `--template realtime` to your chosen template. Every initialized project includes all application files and real tests; complete generated recipe pages and file contents are also available in the [documentation catalogue](docs/generated.json).
|
|
141
67
|
|
|
142
|
-
|
|
68
|
+
| Build | Starter | Recipe notes |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Live site with server-owned state | `realtime` | [Counter](recipes/realtime/README.md) |
|
|
71
|
+
| Chatroom with reusable components and presence | `chat` | [Chat](recipes/chat/README.md) |
|
|
72
|
+
| Non-live pages with shared layout and CSS | `site` | [Site](recipes/site/README.md) |
|
|
73
|
+
| Typed `/match` route with join/move/resume handlers | `socket` | [Socket service](recipes/socket/README.md) |
|
|
74
|
+
| Account-private cards with persistent SQLite data | `dashboard` | [Dashboard](recipes/dashboard/README.md), Node 22.13+ |
|
|
75
|
+
| HTTP and raw WebSockets on one port | `http-ws` | [Shared listener](recipes/http-ws/README.md) |
|
|
143
76
|
|
|
144
|
-
|
|
145
|
-
@component()
|
|
146
|
-
class Chatroom {
|
|
147
|
-
@state()
|
|
148
|
-
screen = html`<form rw-submit="join"><input name="name"><button>Join</button></form>`;
|
|
77
|
+
Choose the recipe's `--template` option when initializing. Shared memory survives visitors, not server restarts. The dashboard demonstrates application-owned persistence and identity; it is single-process, not a managed database or authentication service.
|
|
149
78
|
|
|
150
|
-
|
|
151
|
-
join({ name }: { name: string }) {
|
|
152
|
-
this.screen = html`<p>Connected as ${name}</p><form rw-submit="send"><input name="message"><button>Send</button></form>`;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
@page('/chat', { css: 'chatroom.css' })
|
|
159
|
-
class ChatroomPage {
|
|
160
|
-
chat = new Chatroom();
|
|
161
|
-
render() { return html`<main>${this.chat}</main>`; }
|
|
162
|
-
}
|
|
163
|
-
```
|
|
79
|
+
## Live HTML
|
|
164
80
|
|
|
165
|
-
|
|
81
|
+
- A page is a decorated class whose `render()` returns server-side TSX.
|
|
82
|
+
- Ordinary expressions over `@state()` update after assignment. Replace arrays/objects rather than mutating them in place.
|
|
83
|
+
- `@action()` explicitly exposes a method to the browser. Validate inputs and authorize the operation on the server.
|
|
84
|
+
- Function components reuse presentation; decorated class components reuse state, actions, and lifecycle.
|
|
85
|
+
- Stable JSX keys preserve DOM identity for lists. CSS lives in ordinary external files.
|
|
86
|
+
- Pages are connection-scoped by default. `shared: true` deliberately shares one instance: do not put private visitor data there.
|
|
166
87
|
|
|
167
|
-
|
|
88
|
+
TSX and `html` templates escape text and attribute values and restrict URL protocols. Use external assets instead of inline executable markup. Ordinary `.html` templates remain available; the old executable `.htmx` sandbox does not.
|
|
168
89
|
|
|
169
|
-
|
|
90
|
+
See [pages, components, forms, CSS and rendering](docs/LIVE_HTML.md), [private rooms and request identity](docs/ROOM_AUTHORIZATION.md), and [runtime failures and retry limits](docs/RUNTIME_DIAGNOSTICS.md).
|
|
170
91
|
|
|
171
|
-
|
|
172
|
-
import { attribute, codeBlock, each, html, url } from 'redweb';
|
|
92
|
+
## HTTP servers (Express)
|
|
173
93
|
|
|
174
|
-
|
|
175
|
-
<article id="${attribute(section.id)}">
|
|
176
|
-
<h2>${section.name}</h2>
|
|
177
|
-
<a href="${url(`#${section.id}`)}">Permalink</a>
|
|
178
|
-
${each(section.methods, method => html`<section><h3>${method.name}</h3></section>`)}
|
|
179
|
-
${codeBlock(section.usage, { language: 'ts', label: 'TypeScript' })}
|
|
180
|
-
</article>
|
|
181
|
-
`);
|
|
182
|
-
```
|
|
94
|
+
Use `HttpServer` for Express services and `HttpsServer` when Node terminates TLS. HTTP and WebSockets can run independently; the example below combines them on one listener.
|
|
183
95
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
For React-free documentation or marketing pages, set `live: false`. Redweb omits page tokens, browser JavaScript, and WebSockets; adds document metadata; and serves the result with an ETag:
|
|
187
|
-
|
|
188
|
-
```ts
|
|
189
|
-
@page('/docs', {
|
|
190
|
-
template: 'docs.html',
|
|
191
|
-
css: 'docs.css',
|
|
192
|
-
live: false,
|
|
193
|
-
head: {
|
|
194
|
-
title: 'Redweb API',
|
|
195
|
-
description: 'Complete Redweb API reference.',
|
|
196
|
-
canonical: 'https://example.com/docs',
|
|
197
|
-
image: 'https://example.com/og.png',
|
|
198
|
-
},
|
|
199
|
-
cache: { maxAge: 300, staleWhileRevalidate: 3600 },
|
|
200
|
-
})
|
|
201
|
-
class DocsPage {}
|
|
202
|
-
```
|
|
96
|
+
## WebSocket servers
|
|
203
97
|
|
|
204
|
-
|
|
98
|
+
The `http-ws` starter answers `GET /health` and accepts `{"type":"hello"}` at `ws://127.0.0.1:8181/chat`, using the same port. A URL selects a route; a message's `type` selects its handler. No secondary `message.action` dispatcher is needed.
|
|
205
99
|
|
|
206
|
-
|
|
100
|
+
<!-- redweb:http-ws:start -->
|
|
101
|
+
```tsx
|
|
102
|
+
import { BaseHandler, HttpServer, METHODS, SocketRoute, SocketServer, type RedWebSocket, type SocketServerOptions } from 'redweb';
|
|
103
|
+
import { runApp } from './run-app';
|
|
207
104
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
origin: 'https://redweb.example',
|
|
211
|
-
css: 'site.css',
|
|
212
|
-
head: { description: 'Redweb documentation' },
|
|
213
|
-
layout: content => html`<body><nav>Redweb</nav><main>${content}</main></body>`,
|
|
214
|
-
});
|
|
105
|
+
export class Hello extends BaseHandler {
|
|
106
|
+
constructor() { super('hello'); }
|
|
215
107
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
108
|
+
onMessage(socket: RedWebSocket) {
|
|
109
|
+
socket.sendJson({ type: 'hello', message: 'Hello from the server!' });
|
|
110
|
+
}
|
|
219
111
|
}
|
|
220
112
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
The same API serves HTTPS/WSS when `ssl` is provided. For private pages, an optional `authenticate(request)` callback binds the page token to the same stable user identity across the HTTP render and WebSocket upgrade. Initial connections and reconnects always receive a complete authoritative state snapshot.
|
|
227
|
-
|
|
228
|
-
See the [Live HTML guide](docs/LIVE_HTML.md), runnable [TSX page](examples/live-html/jsx-page.tsx), TypeScript [server counter](examples/live-html/counter.ts), component-based [chatroom](examples/live-html/chatroom.ts), and [persistent card collection](examples/live-html/cards.ts). The chatroom separates joining from its stable message composer, tracks online members, preserves bounded history, restores identity and missed messages after reconnect, and creates an isolated room for every server. The cards page uses `shared: true`, so additions survive reloads, reconnects, and new visitors while its server is running. Run the examples with `npm run example:jsx`, `npm run example:counter`, `npm run example:chatroom`, and `npm run example:cards`. The decorated sources are compiled and exercised unchanged by mock-free HTTP/WebSocket integration tests and a real-Chromium DOM gate.
|
|
229
|
-
|
|
230
|
-
Reusable snippets can own server behavior without page-level forwarding methods. Decorate a class with `@component()`, put instances in page fields, and interpolate them directly: `` html`<main>${this.primary}${this.secondary}</main>` ``. Each instance gets isolated `@state()`, scoped `@action()` methods, nested-component support, and page-owned lifecycle cleanup. See the runnable [component counters](examples/live-html/components.ts) or run `npm run example:components`.
|
|
231
|
-
|
|
232
|
-
## Multiplayer in 0.9
|
|
233
|
-
|
|
234
|
-
Redweb keeps each production feature independent and opt-in:
|
|
235
|
-
|
|
236
|
-
| Need | Redweb primitive |
|
|
237
|
-
| --- | --- |
|
|
238
|
-
| Authenticate and place players before upgrade | Bounded `admission` hooks with origin and redirect policy |
|
|
239
|
-
| Contain abusive or slow peers | Connection, rate, queue, payload, and outbound-buffer limits |
|
|
240
|
-
| Detect dead connections cheaply | One heartbeat scheduler per route |
|
|
241
|
-
| Group players and resume ownership | Bounded rooms and expiring application-issued sessions |
|
|
242
|
-
| Run simulation work predictably | Drift-aware, non-overlapping `FixedStepService` ticks |
|
|
243
|
-
| Scale across nodes | Optional broker adapter with bounded fan-out and explicit best-effort semantics |
|
|
244
|
-
| Roll deployments safely | Readiness, draining, cooperative cancellation, and bounded shutdown |
|
|
245
|
-
| Evolve clients | Opt-in version negotiation, stable envelopes/error codes, generated types, and codec hooks |
|
|
113
|
+
export class ChatRoute extends SocketRoute {
|
|
114
|
+
constructor() {
|
|
115
|
+
super({ path: '/chat', handlers: [Hello], allowDuplicateConnections: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
246
118
|
|
|
247
|
-
|
|
119
|
+
export function createApp(options: Pick<SocketServerOptions, 'port' | 'bind' | 'logger'> = {}) {
|
|
120
|
+
const http = new HttpServer({
|
|
121
|
+
listen: false,
|
|
122
|
+
publicPaths: [],
|
|
123
|
+
services: [{ serviceName: '/health', method: METHODS.GET, function: (_req, res) => res.json({ ok: true }) }],
|
|
124
|
+
});
|
|
248
125
|
|
|
249
|
-
|
|
126
|
+
return new SocketServer({
|
|
127
|
+
port: options.port ?? Number(process.env.PORT ?? 8181),
|
|
128
|
+
bind: options.bind ?? '127.0.0.1',
|
|
129
|
+
logger: options.logger,
|
|
130
|
+
server: http.server,
|
|
131
|
+
routes: [ChatRoute],
|
|
132
|
+
listen: true,
|
|
133
|
+
closeServerOnShutdown: true, // One owner closes routes and the shared HTTP listener.
|
|
134
|
+
});
|
|
135
|
+
}
|
|
250
136
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
Options:
|
|
254
|
-
|
|
255
|
-
- `port` (number): defaults to `80`.
|
|
256
|
-
- `bind` (string): defaults to `0.0.0.0`.
|
|
257
|
-
- `publicPaths` (string[]): folders served as static assets.
|
|
258
|
-
- `services` (array): `{ serviceName, method, function }` for REST endpoints.
|
|
259
|
-
- `listen` (boolean): defaults to `true`; set `false` to build `.app` and `.server` without binding a port.
|
|
260
|
-
- `listenCallback` (function): invoked after `.listen`.
|
|
261
|
-
- `encoding` (`'json' | 'urlencoded'`): body parser selection.
|
|
262
|
-
- `corsOptions`: passed to `cors`.
|
|
263
|
-
- `corsOptions: false`: disables the CORS middleware entirely.
|
|
264
|
-
- `exposeErrors` (boolean): include WebSocket handler details in responses; defaults to `false`.
|
|
265
|
-
- `logger`: an object with optional `log`, `warn`, and `error` methods. Pass `null` to disable library logging.
|
|
266
|
-
|
|
267
|
-
Example:
|
|
268
|
-
|
|
269
|
-
```js
|
|
270
|
-
const { HttpServer, METHODS } = require('redweb');
|
|
271
|
-
|
|
272
|
-
new HttpServer({
|
|
273
|
-
port: 3000,
|
|
274
|
-
publicPaths: ['./public'],
|
|
275
|
-
services: [
|
|
276
|
-
{
|
|
277
|
-
serviceName: '/api/hello',
|
|
278
|
-
method: METHODS.GET,
|
|
279
|
-
function: (req, res) => res.json({ hello: 'world' })
|
|
280
|
-
}
|
|
281
|
-
]
|
|
282
|
-
});
|
|
137
|
+
if (require.main === module) runApp(createApp);
|
|
283
138
|
```
|
|
139
|
+
<!-- redweb:http-ws:end -->
|
|
284
140
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
## WebSocket servers
|
|
288
|
-
|
|
289
|
-
`SocketServer` uses `ws` and routes connections to `SocketRoute` instances. Clients must send JSON containing a `type` that matches a handler name.
|
|
141
|
+
Follow the [shared-listener notes](recipes/http-ws/README.md) and initialize with `--template http-ws` using the matching artifact above. The socket service explicitly owns cleanup of the supplied HTTP listener. `/health` reports liveness, not readiness. This demonstrates raw JSON messages, not a chatroom UI.
|
|
290
142
|
|
|
291
|
-
|
|
143
|
+
For validated, inferred client/server payloads, use [shared socket contracts](docs/SOCKET_CONTRACTS.md). The client wraps your transport; it does not create or reconnect one for you.
|
|
292
144
|
|
|
293
|
-
|
|
294
|
-
const { BaseHandler } = require('redweb');
|
|
145
|
+
## One development loop
|
|
295
146
|
|
|
296
|
-
|
|
297
|
-
constructor() { super('chat'); }
|
|
147
|
+
After installing the matching package:
|
|
298
148
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
149
|
+
```sh
|
|
150
|
+
npm test
|
|
151
|
+
npm run dev
|
|
303
152
|
```
|
|
304
153
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
```js
|
|
308
|
-
const { SocketRoute } = require('redweb');
|
|
154
|
+
Tests compile the application and use real HTTP/WebSocket listeners. Development watches source, CSS, HTML, and root TypeScript configuration, then rebuilds/restarts. Local HTML pages refresh; detected edits require confirmation before reload. This is not autosave or state-preserving hot-module replacement. See [development refresh and inspection](docs/DEVELOPMENT.md).
|
|
309
155
|
|
|
310
|
-
|
|
311
|
-
constructor() {
|
|
312
|
-
super({
|
|
313
|
-
path: '/chat',
|
|
314
|
-
handlers: [ChatHandler],
|
|
315
|
-
allowDuplicateConnections: true // otherwise one connection per IP
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
```
|
|
156
|
+
Build with `npm run build`, then run compiled output with `npm start`. Production ships `dist/`, the manifest, and lockfile, with runtime dependencies installed through `npm ci --omit=dev`; it does not need `src/` or TypeScript.
|
|
320
157
|
|
|
321
|
-
|
|
158
|
+
## Add to an existing project
|
|
322
159
|
|
|
323
|
-
|
|
324
|
-
const { SocketServer } = require('redweb');
|
|
160
|
+
Use the installed CLI so the tool and application agree:
|
|
325
161
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
162
|
+
```sh
|
|
163
|
+
npx --no-install redweb init --existing --dry-run --json
|
|
164
|
+
npx --no-install redweb doctor --json
|
|
165
|
+
npx --no-install redweb add page dashboard --dry-run --json
|
|
330
166
|
```
|
|
331
167
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
- `socket.sendJson(data)` to send JSON.
|
|
335
|
-
- `socket.broadcast(data)` to send JSON to all other clients on the same route.
|
|
168
|
+
Remove `--dry-run` to create missing files. Existing configuration/source is never overwritten. Incremental generation reports imports, registration steps and isolated tests; it does not rewrite startup or silently repair your project. See [CLI prerequisites, commands and limitations](docs/CLI.md).
|
|
336
169
|
|
|
337
|
-
|
|
170
|
+
## Fit and production boundaries
|
|
338
171
|
|
|
339
|
-
|
|
172
|
+
Good fit: Node-hosted live dashboards, chat, collaboration, server-rendered sites and multiplayer socket endpoints. Static HTML export is a separate deployment mode.
|
|
340
173
|
|
|
341
|
-
|
|
174
|
+
Choose something else when you need React compatibility, browser-side components, an edge-only runtime without Node listeners, or managed authentication/database/matchmaking infrastructure.
|
|
342
175
|
|
|
343
|
-
|
|
344
|
-
const { BaseHandler, SocketRoute } = require('redweb');
|
|
176
|
+
Before public deployment, configure HTTPS/WSS, trusted origins, identity, authorization, resource limits and application persistence. Reconnect is not exactly-once delivery; multiple processes do not automatically share state. See [operations](docs/MULTIPLAYER_OPERATIONS.md), [guarantees and limits](docs/PRODUCTION_READINESS.md), and [runtime compatibility and release verification](docs/RELEASE_TRUST.md).
|
|
345
177
|
|
|
346
|
-
|
|
347
|
-
constructor() { super('upload'); }
|
|
348
|
-
|
|
349
|
-
onMessage(socket, message) {
|
|
350
|
-
socket.sendJson({ type: 'upload:control', action: message.action });
|
|
351
|
-
}
|
|
178
|
+
## Exports
|
|
352
179
|
|
|
353
|
-
|
|
354
|
-
socket.sendJson({ type: 'upload:chunk', bytes: buffer.length });
|
|
355
|
-
}
|
|
356
|
-
}
|
|
180
|
+
See the [public TypeScript API](index.d.ts), [complete documentation catalogue](docs/generated.json), and [getting-started guide](docs/GETTING_STARTED.md). The catalogue includes version-labelled Markdown and executable recipes. An [optional read-only MCP adapter](docs/AGENT_ACCESS.md) serves the same source without adding SDK dependencies to your application; it is currently private/unpublished.
|
|
357
181
|
|
|
358
|
-
|
|
359
|
-
constructor() {
|
|
360
|
-
super({
|
|
361
|
-
path: '/upload',
|
|
362
|
-
handlers: [UploadHandler],
|
|
363
|
-
allowDuplicateConnections: true,
|
|
364
|
-
websocketOptions: {
|
|
365
|
-
maxPayload: 2 * 1024 * 1024
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
`BaseHandler` provides `handleBinaryMessage(socket, buffer)` and `onBinaryMessage(socket, buffer)`. Override `onBinaryMessage` for normal use. If a handler does not override it, RedWeb sends:
|
|
182
|
+
## Defaults and lifecycle
|
|
373
183
|
|
|
374
|
-
|
|
375
|
-
{ "error": "Binary messages are not supported by this handler" }
|
|
376
|
-
```
|
|
184
|
+
HTTP defaults to port 80; sockets default to 3000. Generated applications explicitly select 8181. Supplied socket listeners are neither started nor closed unless the corresponding options explicitly transfer that responsibility. Await `shutdown()`; forced transport closure does not guarantee completed application work.
|
|
377
185
|
|
|
378
|
-
|
|
186
|
+
See [production ownership and lifecycle](docs/PRODUCTION_READINESS.md).
|
|
379
187
|
|
|
380
|
-
|
|
381
|
-
class ImageHandler extends BaseHandler {
|
|
382
|
-
constructor() { super('image'); }
|
|
188
|
+
## 0.8 migration notes
|
|
383
189
|
|
|
384
|
-
|
|
385
|
-
return buffer.length > 0;
|
|
386
|
-
}
|
|
190
|
+
See [strict paths, sanitized errors and borrowed-listener ownership](docs/MIGRATION.md#08-migration-notes).
|
|
387
191
|
|
|
388
|
-
|
|
389
|
-
onBinaryMessage(socket, buffer) {}
|
|
390
|
-
}
|
|
391
|
-
```
|
|
192
|
+
## 0.9 migration notes
|
|
392
193
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
`SocketRoute` accepts `websocketOptions`, which are passed to `new WebSocketServer(...)`. Use this for `ws` server settings such as `maxPayload` or `perMessageDeflate`.
|
|
396
|
-
Redweb controls `noServer`, `path`, `server`, and `port`; do not include them in `websocketOptions`. Route selection is performed once by Redweb so strict matching and optional root fallback behave consistently. Handshake authentication can use the `ws` `verifyClient` option, although authenticating in the surrounding HTTP upgrade flow is preferable for complex applications.
|
|
397
|
-
|
|
398
|
-
```js
|
|
399
|
-
class ClipboardRoute extends SocketRoute {
|
|
400
|
-
constructor() {
|
|
401
|
-
super({
|
|
402
|
-
path: '/clipboard',
|
|
403
|
-
handlers: [ClipboardHandler],
|
|
404
|
-
websocketOptions: {
|
|
405
|
-
maxPayload: 1024 * 1024,
|
|
406
|
-
perMessageDeflate: false
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
```
|
|
194
|
+
See [opt-in multiplayer controls and protocol clients](docs/MIGRATION.md#09-migration-notes).
|
|
412
195
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
- `trustProxy`: use the first `X-Forwarded-For` value as the connection identity. Enable this only behind a trusted proxy.
|
|
416
|
-
- `getClientKey(req)`: provide application-specific connection identity logic instead of IP-based identity.
|
|
417
|
-
- `exposeErrors`: return handler exception messages to clients; defaults to `false`.
|
|
418
|
-
- `logger`: route logger with optional `log`, `warn`, and `error` methods; pass `null` to disable it.
|
|
419
|
-
- `shutdownTimeoutMs`: grace period before non-cooperating peers are terminated during shutdown; defaults to `1000`.
|
|
420
|
-
- `admission`: optional pre-upgrade authentication/origin/placement policy. It may be a function or `{ authenticate, origins, place, allowedPlacementOrigins, allowInsecurePlacement, timeoutMs }`. Secure `wss` placement is the default; returned destinations can be origin-allowlisted.
|
|
421
|
-
- `maxPendingUpgrades`: maximum concurrent pre-upgrade authorization/negotiation operations; defaults to `64`.
|
|
422
|
-
- `limits`: opt-in connection, message-rate, pending-message, and outbound-buffer limits.
|
|
423
|
-
- `orderedMessages`: process each connection's messages serially through a bounded queue; defaults to `false` for compatibility.
|
|
424
|
-
- `heartbeat`: optional `{ intervalMs, timeoutMs }` half-open detection using one scheduler per route.
|
|
425
|
-
- `rooms` and `sessions`: optional bounded route-local grouping and resumable session registries. Session payload shape and byte size remain the application's responsibility.
|
|
426
|
-
- `distribution`: optional bounded fan-out adapter. Mark it `required` to fail readiness and reject new upgrades after startup or publish failure; adapter operations receive cancellation signals.
|
|
427
|
-
- `drainHandlers`: expose a route shutdown signal to handlers and track their work within `shutdownTimeoutMs`.
|
|
428
|
-
- `protocol`: optional version negotiation, stable envelopes, and binary codec hooks.
|
|
429
|
-
|
|
430
|
-
Production protections are deliberately opt-in, so existing applications retain their behavior and disabled features add no timers or per-connection queues. A protected route can stay compact:
|
|
431
|
-
|
|
432
|
-
```js
|
|
433
|
-
class GameRoute extends SocketRoute {
|
|
434
|
-
constructor() {
|
|
435
|
-
super({
|
|
436
|
-
path: '/game',
|
|
437
|
-
handlers: [InputHandler],
|
|
438
|
-
admission: {
|
|
439
|
-
origins: ['https://game.example'],
|
|
440
|
-
timeoutMs: 3000,
|
|
441
|
-
authenticate: (request, { signal }) => verifySession(request, signal)
|
|
442
|
-
},
|
|
443
|
-
limits: {
|
|
444
|
-
maxConnections: 5000,
|
|
445
|
-
maxBufferedBytes: 1024 * 1024,
|
|
446
|
-
maxPendingMessages: 64,
|
|
447
|
-
messageRate: { capacity: 60, refillPerSecond: 30 }
|
|
448
|
-
},
|
|
449
|
-
orderedMessages: true,
|
|
450
|
-
heartbeat: { intervalMs: 30000, timeoutMs: 10000 },
|
|
451
|
-
websocketOptions: { maxPayload: 64 * 1024 }
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
```
|
|
196
|
+
## Live HTML migration
|
|
456
197
|
|
|
457
|
-
|
|
198
|
+
See [replacing the executable HTMX sandbox and configuring TSX](docs/MIGRATION.md#live-html-migration).
|
|
458
199
|
|
|
459
|
-
|
|
200
|
+
## Developing
|
|
460
201
|
|
|
461
|
-
|
|
202
|
+
Run `npm test` for unit tests, actual HTTP/HTTPS/WS/WSS integration tests, type checks and enforced 100% instrumented-library statement/branch/function/line coverage. Browser, package, performance and tool verification have separate gates; this is not a claim of exhaustive repository or application coverage.
|
|
462
203
|
|
|
463
|
-
|
|
204
|
+
`npm run verify:load` checks the default 32-client/3,200-message workload, p99 latency, throughput and slow-client containment. Its separate `npm run verify:load:coverage` gate combines unit failure tests with real malformed-message, disconnect and timeout integration tests and enforces all-four 100% coverage of the load policy, coordinator, traffic driver and shared socket helper. Coverage runs do not replace clean performance measurements. See the [scope audit](docs/COVERAGE_SCOPE_AUDIT.md) for exact evidence and remaining gaps.
|
|
464
205
|
|
|
465
|
-
|
|
206
|
+
Run `npm run verify:cli` to test the actual initializer, doctor and incremental-add commands and enforce 100% coverage of the shipped CLI entrypoint across subprocesses. This complements, rather than replaces, the library's CLI implementation coverage.
|
|
466
207
|
|
|
467
|
-
|
|
208
|
+
`npm run verify:package:examples:coverage` checks the packed counter without optional development dependencies, chat with explicit Zod, and generated TypeScript additions in a real installed consumer. Unit and real-socket failure tests require all-four 100% coverage of the three verifier modules. See [packaged-example evidence](docs/PACKAGED_EXAMPLE_VERIFICATION.md); this complements the full browser/package gate.
|
|
468
209
|
|
|
469
|
-
|
|
470
|
-
class MatchRoute extends SocketRoute {
|
|
471
|
-
constructor() {
|
|
472
|
-
super({
|
|
473
|
-
path: '/match',
|
|
474
|
-
handlers: [MatchHandler],
|
|
475
|
-
rooms: { maxRooms: 1000, maxMembersPerRoom: 32 },
|
|
476
|
-
sessions: { ttlMs: 30000, maxSessions: 10000 },
|
|
477
|
-
metrics: myMetricsSink
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
```
|
|
210
|
+
`npm run verify:action:coverage` checks the source-free typed action consumer in both decorator modes, plus real failed upgrades/responses and unit cleanup failures. It requires all-four 100% of the action-input verifier without enlarging the library coverage scope.
|
|
482
211
|
|
|
483
|
-
|
|
212
|
+
`npm run verify:reports:coverage` checks that failed starter commands retain available raw reports without overwriting or merging prior evidence. It combines unit faults with real child processes/filesystem checks and requires all-four 100% of the shared retention helper.
|
|
484
213
|
|
|
485
|
-
|
|
214
|
+
`npm run verify:starter-coordinators:coverage` checks both starter coverage runners and their shared final-report handling at all-four 100%. Real compiler/test runs prove changed inputs are rejected; filesystem failures cannot turn a failed command into success. See [starter verification](docs/STARTER_COORDINATOR_VERIFICATION.md) for the exact scope and unit/integration boundaries.
|
|
486
215
|
|
|
487
|
-
|
|
216
|
+
`npm run verify:starters:lifecycle` requires a nonempty, complete report for the deployed lifecycle helper. Its separate `npm run verify:starters:lifecycle:coverage` command covers the verifier itself. See [lifecycle evidence](docs/STARTER_LIFECYCLE_VERIFICATION.md) for the emitted-JavaScript scope and temporary source-map metadata removal.
|
|
488
217
|
|
|
489
|
-
|
|
490
|
-
super({
|
|
491
|
-
path: '/match',
|
|
492
|
-
handlers: [MatchHandler],
|
|
493
|
-
rooms: true,
|
|
494
|
-
distribution: {
|
|
495
|
-
adapter: brokerAdapter,
|
|
496
|
-
channel: 'matches',
|
|
497
|
-
nodeId: process.env.INSTANCE_ID,
|
|
498
|
-
onEvent(event, route) {
|
|
499
|
-
route.rooms.broadcast('match-42', event.payload)
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
})
|
|
503
|
-
```
|
|
218
|
+
`npm run verify:package:browser:coverage` covers the shared browser page owner and packed-browser verifier at all-four 100%, combining explicit failure units with actual Chromium counter/chat integration. Late page openings and cleanup failures retain uncertain workspaces. See [browser ownership evidence](docs/BROWSER_OWNER_VERIFICATION.md) for the checkout/package distinction and exact scope.
|
|
504
219
|
|
|
505
|
-
|
|
220
|
+
Browser and authored-source coverage share strict source-map and execution-counter validation. Malformed reports are rejected before merging; see [coverage validation evidence](docs/COVERAGE_COUNTER_VALIDATION.md) for the unit and real-browser checks.
|
|
506
221
|
|
|
507
|
-
|
|
222
|
+
Feedback and development-refresh verification share bounded browser commands so a disconnected debugging socket reaches cleanup. [Native failure evidence](docs/FEEDBACK_COMMAND_VERIFICATION.md) records the fixes, exact coverage scopes using actual Chromium/server cases, and the remaining acquisition boundary.
|
|
508
223
|
|
|
509
|
-
|
|
224
|
+
Development-refresh checks retain uncertain browser-launch cleanup and preserve shutdown failures. [Verification boundaries](docs/BROWSER_OWNER_VERIFICATION.md#development-refresh-launch-cleanup-follow-up) distinguish these fault tests from real generated-app/browser acceptance.
|
|
510
225
|
|
|
511
|
-
|
|
226
|
+
`npm run verify:live-html:load` checks 200 expired renders, 110 connected clients, presence/broadcast delivery and heap growth after client closure/session expiry, before server shutdown. Its separate `verify:live-html:load:coverage` command tests the verifier's HTTP/socket ownership, malformed responses, real timeouts and failure handling with unit and native integration tests. It requires all-four 100% coverage of the three verifier modules; instrumented tests do not replace clean memory measurements.
|
|
512
227
|
|
|
513
|
-
|
|
514
|
-
super({
|
|
515
|
-
path: '/match',
|
|
516
|
-
handlers: [MoveHandler],
|
|
517
|
-
protocol: {
|
|
518
|
-
versions: ['2', '1'],
|
|
519
|
-
binary: {
|
|
520
|
-
maxBytes: 64 * 1024,
|
|
521
|
-
encode: state => myCodec.encode(state),
|
|
522
|
-
decode: bytes => myCodec.decode(bytes)
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
})
|
|
526
|
-
```
|
|
228
|
+
`npm run verify:jsx:performance` renders 10,000 component rows and validates their complete markup outside the timed render. CI supervises the command externally; the five-second performance limit cannot itself interrupt synchronous code. `npm run verify:jsx:coverage` separately checks malformed output, measurement limits and the actual CLI, requiring all-four 100% coverage of this verifier.
|
|
527
229
|
|
|
528
|
-
|
|
230
|
+
`npm run verify:soak` checks exact per-connection replies, rotation, disconnects and resource trends. It rejects undersampled runs and reports missing replies explicitly; the existing 99% delivery allowance is not a lossless guarantee. Final heap is sampled after client closure/expiry, before server shutdown. `npm run verify:soak:coverage` separately requires all-four 100% coverage of the verifier, policy and socket owner using unit and real-network/process tests. A short test run does not certify the default one-hour workload.
|
|
529
231
|
|
|
530
|
-
|
|
232
|
+
`npm run verify:overhead -- <baseline-directory>` compares disabled-feature socket throughput and p99 latency against a separately prepared baseline. Both sides must complete every warm-up and measured exchange with valid, unique reply IDs; malformed output, timeouts and cleanup failures fail the check. The limits remain 3% throughput regression and 5% p99 regression. `npm run verify:overhead:coverage` separately enforces all-four 100% coverage of the six benchmark modules through unit and real-socket/process tests. See [benchmark evidence and limitations](docs/BENCHMARK_VERIFICATION.md); a coverage pass is not a performance pass.
|
|
531
233
|
|
|
532
|
-
`
|
|
234
|
+
Run `npm run verify:recovery:server` from this source checkout for the blocking CI recovery contract: 7,400 exact exchanges with a separately measured server, empty registries, normal worker exits and every storm within 110% of warmed server heap. The original `npm run verify:recovery` remains a visible non-blocking CI diagnostic; its historical failures are not resolved by this measurement change. CI preserves both results and logs. See the [reviewed recovery contract and evidence](docs/SERVER_RECOVERY_CANDIDATE.md).
|
|
533
235
|
|
|
534
|
-
|
|
236
|
+
`npm run verify:recovery:coverage` separately enforces all-four 100% coverage of that gate's policy, CLI, and full authored coordinator/worker source. It combines unit boundary tests with actual worker/socket integration without repeating the native workload. The instrumented behavioral run is not used as a clean heap measurement; the normal server recovery command remains separate. See [authored recovery coverage](docs/SPLIT_RECOVERY_COVERAGE.md).
|
|
535
237
|
|
|
536
|
-
|
|
238
|
+
`npm run verify:recovery:original:coverage` separately tests the original shared-process verifier with authored-source coverage units and its existing real CLI/socket/snapshot checks. Its 110% limit now compares integer bytes exactly: equality passes, a one-byte excess fails, and any over-budget storm still fails even if the final heap recovers. Displayed ratios and workloads are unchanged. Synthetic unit heap values are not memory evidence, and this rounding correction does not explain larger historical failures. See the [coverage and regression evidence](docs/ORIGINAL_RECOVERY_VERIFICATION.md).
|
|
537
239
|
|
|
538
|
-
|
|
539
|
-
const { HttpServer, METHODS, SocketServer } = require('redweb');
|
|
240
|
+
Recovery workers also retain non-Error failures and clean up rejected IPC requests immediately; empty error replies cannot masquerade as success. [Real-process regressions and server recovery evidence](docs/SPLIT_RECOVERY_ERROR_HANDLING.md) distinguish correctness fixes from coverage. Short soak checks retain raw results before assertions and preserve original reports if artifact writing fails. A [hosted delivery failure and real rotation controls](docs/SOAK_ROTATION_OBSERVATION.md) remain visible; no delivery threshold was relaxed.
|
|
540
241
|
|
|
541
|
-
|
|
542
|
-
port: 3030,
|
|
543
|
-
listen: false,
|
|
544
|
-
publicPaths: ['./public'],
|
|
545
|
-
services: [
|
|
546
|
-
{ serviceName: '/health', method: METHODS.GET, function: (req, res) => res.json({ ok: true }) },
|
|
547
|
-
{ serviceName: '/session', method: METHODS.POST, function: createSession }
|
|
548
|
-
]
|
|
549
|
-
});
|
|
242
|
+
Run `npm run verify:recovery:diagnostics` for native-source 100% coverage of the two private heap-analysis tools, including their real command-line entrypoints. It reuses the graph unit cases under Node's test runner to avoid merging Jest-transformed and original source ranges. Parser fixtures and real file/subprocess checks cover graph bounds, shared references, redaction, and malformed input; the separate recovery integration suite also checks actual V8 snapshots and a real server-held object. This gate does not cover the recovery workload verifier itself or waive its failed memory budget.
|
|
550
243
|
|
|
551
|
-
|
|
552
|
-
server: httpServer.server,
|
|
553
|
-
routes: [ClipboardRoute]
|
|
554
|
-
});
|
|
244
|
+
Run `npm run verify:package:tools` for a separate 100% coverage gate over the managed subprocess owner, failure normalizer, and starter/Markdown application verifiers. Tests use actual npm/native commands, descendants, files and generated applications. Use Node 22.13+ to exercise all six recipes, including the SQLite dashboard. Package packing/extraction and consumer checks use the same bounded owner; uncertain cleanup fails verification and retains its workspace. This is a scoped tool gate, verified on Windows, not coverage of every verification script or proof of cross-platform execution; Windows file-lock cases are skipped elsewhere.
|
|
555
245
|
|
|
556
|
-
|
|
557
|
-
```
|
|
246
|
+
Every generated starter also has `npm run test:coverage`: real application tests with coverage mapped to its TypeScript, separate from library coverage. In this repository, `npm run measure:starters:coverage` runs all six starter commands and records source/report hashes and run-specific results. It checks that every application module is measured, but does not present incomplete coverage as a passing 100% gate; compiler-generated decorator accessors appear in function counts. The chat and socket recipes include domain and real-network tests for reconnecting, identity conflicts, bounded history and session capacity.
|
|
558
247
|
|
|
559
|
-
|
|
248
|
+
`npm run verify:starters:source-coverage` separately instruments original TypeScript before compilation, so compiler-created decorator helpers do not inflate authored function counts. It runs the same application tests plain and instrumented, checks unchanged inputs, retains V8 reports, and enforces 100% of Istanbul's tracked statements/branches/functions/lines. All six starters pass with 104 tests per mode and all 600 statements, 299 branches, 160 functions and 472 lines covered. Integration tests use real networking and persistence; explicitly labelled unit cases exercise defensive failure paths. Istanbul does not independently count optional-chaining short circuits, so this is not an exhaustive semantic-branch claim or a replacement for V8 evidence. Reports identify received process reports, not every spawned child: hard termination can prevent an exit report, while every source module still starts in the denominator at zero. Node 22.13+ is required for all six recipes. Instrumentation and reports are test-only and are not shipped.
|
|
560
249
|
|
|
561
|
-
|
|
250
|
+
Run `npm run verify:browser:coverage` for native Chromium tests of the complete emitted Live HTML runtime and development-refresh script. The gate enforces 100% statement/branch/function/line coverage and runs the same cases without instrumentation. Actual HTTP/WebSocket checks cover actions, forms, state updates, reconnection and selection preservation. Refresh checks cover real reloads, draft guards, failed HTTP peers, history restoration and explicit discard under a self-only script policy; instrumentation requires no dynamic code evaluation.
|
|
562
251
|
|
|
563
|
-
|
|
564
|
-
const { SocketService } = require('redweb');
|
|
252
|
+
The refresh report (`coverage/browser-refresh/report.json`) also retains `historyRestoration.plain.bfcacheRestored` and `historyRestoration.instrumented.bfcacheRestored` for each successfully completed mode. These are actual browser observations, not requirements: history navigation and resumed polling must pass, but the browser may choose to reload instead of restoring from its back/forward cache. A mode that fails before completion may only log its observation.
|
|
565
253
|
|
|
566
|
-
|
|
567
|
-
constructor() { super('clock', 1000); } // tick every 1s
|
|
568
|
-
onTick() {
|
|
569
|
-
this.route.clients.forEach((socket) => socket.sendJson({ type: 'time', now: Date.now() }));
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
```
|
|
254
|
+
`npm run verify:refresh:coverage` separately requires 100% authored coverage of both refresh verification helpers. It combines explicit failure-boundary units with actual Chromium, HTTP uploads and socket cleanup, and is included in the browser coverage gate. Collection, page-close and socket-release failures remain visible together; a rejected non-Error value cannot become a passing result. The host-side helper map does not measure execution inside browser-expression strings; the separate generated-refresh map and native checks remain required.
|
|
573
255
|
|
|
574
|
-
|
|
256
|
+
`npm run verify:development:coverage` additionally covers the generated-app refresh verifier: real TypeScript/CSS rebuilds, browser draft preservation, adverse HTTP peers and process cleanup, plus explicit startup/cleanup failure units. Page openings remain owned if they time out or settle late; uncertain cleanup retains the workspace. CI runs this gate instead of repeating the standalone development browser command. Its 100% scope is the authored coordinator, not embedded browser programs or every possible platform failure.
|
|
575
257
|
|
|
576
|
-
|
|
258
|
+
`npm run verify:package:coordinator:coverage` runs the complete isolated-package check alongside explicit failure-boundary units and real listener-cleanup tests. It requires 100% authored coverage of the package coordinator and report helper. Every acquired example server gets its own cleanup attempt; a missing error value cannot become success, and success is printed only after workspace cleanup. CI uses this instead of repeating the standalone package command. The full consumer check requires the dashboard starter's Node version (22.13 or newer); older supported library versions run the native cleanup checks but skip that consumer case.
|
|
577
259
|
|
|
578
|
-
|
|
579
|
-
class Simulation extends FixedStepService {
|
|
580
|
-
constructor() { super('simulation', 50, 3); }
|
|
581
|
-
async onTick(stepMs, tick) {
|
|
582
|
-
await game.update(stepMs, tick);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
```
|
|
260
|
+
The isolated browser harness copies its verification helpers explicitly and checks their literal relative imports against that copied set. This catches missing test dependencies without falling back to checkout runtime code; the real packed-consumer gate still verifies installation and execution.
|
|
586
261
|
|
|
587
|
-
|
|
262
|
+
`npm run verify:evaluation:process:coverage` measures the unchanged evaluation process and evidence-sealing tools at 100% authored coverage. It combines explicit OS-boundary units with actual subprocess, archive, file-lock, CLI and listener checks. A test-only preload instruments selected code in memory; frozen source and sealed evaluation records are not rewritten. Native interface inspection is Windows-only; unsupported platforms are tested for explicit rejection. This coverage gate does not rerun an agent trial or resolve historical cleanup failures.
|
|
588
263
|
|
|
589
|
-
`
|
|
264
|
+
`npm run verify:evaluation:prepare:coverage` separately checks candidate preparation against actual npm archives, catalogue bytes and Git identity, comparing plain and instrumented CLI execution. Real launch-failure checks supplement explicit subprocess-boundary units. It uses owned temporary directories and does not publish packages or rerun sealed agent trials.
|
|
590
265
|
|
|
591
|
-
|
|
592
|
-
const { SocketRegistry } = require('redweb');
|
|
266
|
+
`npm run verify:evaluation:trial:coverage` checks the unchanged trial runner's input hashes, build outcomes and evidence retention. It combines real archive/CLI checks, explicit failure units and the evaluator's actual HTTP/WebSocket browser control on Windows. Synthetic checker fixtures are not new agent trials or substitutes for packed Redweb acceptance. Uncertain cleanup preserves the outer test workspace and its report, including leftover browser profiles even when no report was saved.
|
|
593
267
|
|
|
594
|
-
|
|
595
|
-
addPlayer(player) {
|
|
596
|
-
this.add(player);
|
|
597
|
-
this.emit('playerJoined', player);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
```
|
|
268
|
+
`npm run verify:evaluation:controls:coverage` measures the unchanged control validator and browser evaluator together: four working protocol controls and seven deliberately broken variants run in actual Chromium on Windows. Real CLI tests also cover failed builds, early exits, invalid startup URLs and HTTP rejection. Elsewhere, interface inspection must explicitly refuse support, not imply browser success. Separate browser/process/result boundary units cover reporting and cleanup faults; unexpected native outcomes retain their original errors and workspace. These evaluator controls are not new Redweb agent submissions or release acceptance.
|
|
601
269
|
|
|
602
|
-
|
|
270
|
+
`npm run verify:live-html:browser:coverage` combines the existing full browser workload (counter, chat, CSS, JSX, components, forms and dashboard) with explicit failure-path unit tests. Its 100% authored-tool coverage is separate from frontend coverage and release acceptance. The native workload requires the dashboard's supported Node version. Known limitations of the unchanged legacy browser tool— including uncertain descendant cleanup—are characterized, not silently fixed or counted as verified cleanup; see the [coverage audit](docs/COVERAGE_SCOPE_AUDIT.md).
|
|
603
271
|
|
|
604
|
-
|
|
272
|
+
The frontend is maintained in `redweb-client/live-html`; Redweb emits only a two-line mounting bootstrap. This branch depends on published `redweb-client@^0.2.0`, so ordinary application installation needs no client checkout or link. Contributors editing the client can still use the [linked development workflow](docs/CLIENT_DEVELOPMENT.md). Redweb itself remains unreleased until its remaining release checks finish.
|
|
605
273
|
|
|
606
|
-
- HTTP
|
|
607
|
-
- WebSocket defaults: port `3000`, single connection per IP unless `allowDuplicateConnections` is set.
|
|
608
|
-
- `SocketServer` owns and listens on its own server by default; if you pass `server`, you own calling `.listen()` unless you also pass `listen: true`.
|
|
609
|
-
- Upgrade paths are matched strictly by default. Set `fallbackToRoot: true` for legacy behavior that sends unmatched paths to `/`.
|
|
610
|
-
- If you do not supply `routes`, `SocketServer` registers a default route at `/` with `DefaultHandler` (it expects messages with `type: 'DefaultHandler'`).
|
|
611
|
-
- `shutdown()` closes routes and services. It closes an owned listener, but leaves a supplied listener running unless `closeServerOnShutdown: true` is set.
|
|
612
|
-
- Shutdown is best-effort: all hooks, clients, routes, and owned listeners are processed before collected cleanup errors are reported.
|
|
613
|
-
- `HttpServer` and `HttpsServer` expose an idempotent async `shutdown()` helper.
|
|
274
|
+
`npm run measure:browser:client` separately serves the exact installed socket-only module with and without instrumentation through the same real HTTP/WebSocket/browser cases and retains its source hash and counters. It exits unsuccessfully until all four coverage metrics reach 100%; incomplete results are not a passing dependency-coverage claim. Reports are local under `coverage/browser-client` and do not alter the installed dependency or published package.
|
|
614
275
|
|
|
615
|
-
|
|
276
|
+
`npm run verify:browser:coordinator:coverage` checks the browser coordinator and four runtime/refresh verification helpers together at 100% authored coverage. The umbrella browser gate uses this combined run to avoid repeating the native workloads. Failure units supplement actual Chromium/HTTP/WebSocket checks; the installed-client diagnostic must still report incomplete coverage as failure, not a release pass. CI retains the combined map and separate runtime/refresh/client reports. An additional source-build integration requires `REDWEB_VERIFY_CLIENT_SOURCE=1` and the linked client checkout with its development dependencies; ordinary registry-only CI skips that case. The standalone `verify:client:source-coverage` remains the original-source acceptance gate.
|
|
616
277
|
|
|
617
|
-
-
|
|
618
|
-
- Handler exception details are hidden unless `exposeErrors: true` is configured.
|
|
619
|
-
- Shutting down a WebSocket server no longer closes a caller-supplied HTTP/HTTPS server by default.
|
|
620
|
-
- `bind` is now honored by HTTP, HTTPS, WebSocket, and secure WebSocket listeners.
|
|
621
|
-
- `shutdown()` is asynchronous; await it when deterministic cleanup matters.
|
|
278
|
+
Ownership and stopped-poll edge cases also use native-browser unit-style tests; browser and transport APIs are not replaced. Runtime coverage now covers all Live HTML modules inside the linked client bundle, excluding its transport prefix; the development-refresh script is measured separately. Whole-application/tool coverage and cross-browser certification remain separate gates.
|
|
622
279
|
|
|
623
|
-
|
|
280
|
+
`npm run verify:client:source-coverage` measures the linked client's original TypeScript/JavaScript using one instrumentation map across its Node tests and the native browser tests. Every executable source module starts at zero; erased declarations and static export linkage are separately audited. Both test passes use identical source/test inputs, every Vitest test realm must report, and plain browser bundles must match the linked build byte-for-byte. Reports retain separate Node/browser contributions under `coverage/client-source/<run-id>`. The gate passes all 791 statements, 521 branches, 125 functions and 659 lines, with 77 Node tests per mode plus native-browser acceptance. The client's default `npm test` uses this same complete gate after linkage, build and type checks. Its original Node-only V8 diagnostic remains separately available as `npm run test:v8`, with unchanged thresholds and known missing-browser coverage. Original-source instrumentation does not count every optional-chain short circuit, replace V8 evidence, or mean all tests are mock-free: isolated unit transports remain, while integration/browser tests use actual networking.
|
|
624
281
|
|
|
625
|
-
|
|
626
|
-
- Production controls are route-local and opt-in; enable and size them from measured capacity rather than copying example limits.
|
|
627
|
-
- `ProtocolClient` is available from `redweb/client` for negotiated protocol routes without adding runtime dependencies.
|
|
628
|
-
- The minimum supported Node.js version is 18.
|
|
282
|
+
Client verification also retains raw worker files before parsing or cleanup, including failed runs. Its private coordinator has a separate 100% coverage gate; real Vitest failure fixtures exercise retention without replacing filesystem, compiler or process APIs. See [client development](docs/CLIENT_DEVELOPMENT.md) for commands and scope.
|
|
629
283
|
|
|
630
|
-
|
|
284
|
+
`npm run verify:browser:supplements` combines focused units with the existing real-browser runtime cases to require 100% authored-source coverage of the page-ownership and runtime-frame verification helpers, including anonymous callbacks. It is included in the browser coverage gate; see the [coverage scope audit](docs/COVERAGE_SCOPE_AUDIT.md) for exact boundaries and remaining gaps.
|
|
631
285
|
|
|
632
|
-
|
|
286
|
+
`npm run verify:dashboard:coverage` measures the dashboard browser verifier separately: failure-boundary units plus actual Chromium, SQLite, sign-in, private card updates, draft preservation and logout checks. Native dashboard tests require the starter's supported Node version; file-lock retention is Windows-specific. The scope is the authored verifier, not internal coverage of its browser-expression strings.
|
|
633
287
|
|
|
634
|
-
|
|
288
|
+
An unresolved Linux CI process-cleanup assertion and the diagnostics added to investigate it are tracked in [process cleanup observations](docs/PROCESS_CLEANUP_OBSERVATION.md). Passing runs do not establish its cause or waive the original failure.
|
|
635
289
|
|
|
636
|
-
|
|
290
|
+
Edit canonical recipes/guides, then run `npm run generate:docs`; do not maintain independent copies of the examples. See [documentation maintenance](docs/DOCUMENTATION.md) and the [full acceptance checklist](docs/AGENT_READY_ACCEPTANCE.md) for verification evidence and remaining release work.
|