redweb 0.13.5 → 0.15.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 +20 -3
- package/README.md +289 -288
- package/contract.d.ts +11 -3
- package/docs/APPLICATION.md +98 -0
- package/docs/CLI.md +1 -1
- package/docs/CLIENT_DEVELOPMENT.md +9 -5
- package/docs/COVERAGE_SCOPE_AUDIT.md +7 -0
- package/docs/DEFINE_APP_VERIFICATION.md +101 -0
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/LIVE_HTML.md +2 -2
- package/docs/MIGRATION.md +1 -1
- package/docs/MULTIPLAYER_OPERATIONS.md +6 -0
- package/docs/RELEASE_TRUST.md +29 -6
- package/docs/RUNTIME_DIAGNOSTICS.md +1 -1
- package/docs/SOCKET_CONTRACTS.md +6 -3
- package/docs/SOCKET_PAGES.md +99 -0
- package/docs/SOCKET_PAGE_RELEASE_PREPARATION.md +120 -0
- package/docs/SOCKET_PAGE_VERIFICATION.md +85 -0
- package/docs/STARTER_LIFECYCLE_VERIFICATION.md +9 -0
- package/docs/generated.json +2217 -2154
- package/docs/guides/http-websocket.md +4 -4
- package/docs/guides/jsx-without-react.md +1 -1
- package/docs/reference.json +40 -1
- package/docs/releases/0.14.0.json +2208 -0
- package/docs/releases/0.15.0.json +2217 -0
- package/docs/topics.json +3 -1
- package/examples/live-html/cards.js +87 -86
- package/examples/live-html/cards.ts +3 -2
- package/examples/live-html/chatroom.js +210 -207
- package/examples/live-html/chatroom.tsx +6 -2
- package/examples/live-html/components.js +103 -102
- package/examples/live-html/components.ts +3 -2
- package/examples/live-html/counter.js +74 -73
- package/examples/live-html/counter.ts +3 -2
- package/examples/live-html/jsx-page.js +2 -1
- package/examples/live-html/jsx-page.tsx +3 -2
- package/index.d.ts +59 -7
- package/index.js +3 -0
- package/package.json +8 -5
- package/recipes/chat/app.test.cjs +3 -1
- package/recipes/chat/app.tsx +4 -7
- package/recipes/dashboard/app.test.cjs +10 -10
- package/recipes/dashboard/app.tsx +29 -29
- package/recipes/http-ws/README.md +1 -1
- package/recipes/http-ws/app.test.cjs +8 -10
- package/recipes/http-ws/app.tsx +9 -20
- package/recipes/realtime/app.tsx +3 -6
- package/recipes/shared/README.md +10 -1
- package/recipes/shared/lifecycle.test.cjs +81 -0
- package/recipes/shared/network.cjs +10 -5
- package/recipes/site/app.tsx +3 -6
- package/recipes/socket/app.tsx +3 -10
- package/src/Application.js +239 -0
- package/src/StartupCleanup.js +24 -0
- package/src/cli/SourceInspector.js +5 -0
- package/src/cli/templates.js +6 -6
- package/src/htmx/Jsx.js +2 -2
- package/src/htmx/LiveHtmlServer.js +14 -5
- package/src/htmx/PageManager.js +13 -8
- package/src/htmx/PageSocketRoute.js +132 -0
- package/src/htmx/ReactiveRenderer.js +8 -2
- package/src/htmx/SocketAction.js +19 -0
- package/src/htmx/metadata.js +6 -2
- package/src/ws/BaseHandler.js +6 -4
- package/src/ws/BaseSocketServer.js +13 -13
- package/src/ws/HandlerGuard.js +4 -0
- package/src/ws/SocketAction.js +16 -0
- package/src/ws/SocketContract.js +3 -2
- package/src/ws/SocketRoute.js +9 -4
- package/recipes/shared/run-app.test.cjs +0 -158
- package/recipes/shared/run-app.ts +0 -50
package/contract.d.ts
CHANGED
|
@@ -15,9 +15,17 @@ export interface SocketSchema<Input = unknown, Output = Input> {
|
|
|
15
15
|
export type SocketSchemas = Readonly<Record<string, SocketSchema>>;
|
|
16
16
|
export type ContractInput<Schema extends SocketSchema> = NonNullable<Schema['~standard']['types']>['input'];
|
|
17
17
|
export type ContractOutput<Schema extends SocketSchema> = Awaited<NonNullable<Schema['~standard']['types']>['output']>;
|
|
18
|
-
export type ContractMessage<Schemas extends SocketSchemas> = {
|
|
18
|
+
export type ContractMessage<Schemas extends SocketSchemas> = {
|
|
19
19
|
[Type in keyof Schemas & string]: ProtocolEnvelope<ContractOutput<Schemas[Type]>> & { type: Type };
|
|
20
|
-
}[keyof Schemas & string];
|
|
20
|
+
}[keyof Schemas & string];
|
|
21
|
+
|
|
22
|
+
declare const socketActionBrand: unique symbol;
|
|
23
|
+
export interface SocketAction { readonly [socketActionBrand]: true; }
|
|
24
|
+
export interface SocketHandler<Input> {
|
|
25
|
+
new (): BaseHandler;
|
|
26
|
+
/** Bind a JSON payload to a server-rendered rw-click/rw-submit control. */
|
|
27
|
+
with(payload: Input): SocketAction;
|
|
28
|
+
}
|
|
21
29
|
|
|
22
30
|
export interface ContractClient<Schemas extends SocketSchemas> {
|
|
23
31
|
envelope<Type extends keyof Schemas & string>(type: Type, payload: ContractInput<Schemas[Type]>, metadata?: ProtocolMetadata):
|
|
@@ -36,7 +44,7 @@ export interface SocketContract<Schemas extends SocketSchemas> {
|
|
|
36
44
|
handler<Type extends keyof Schemas & string>(type: Type, callback: (
|
|
37
45
|
socket: RedWebSocket, payload: ContractOutput<Schemas[Type]>,
|
|
38
46
|
message: ProtocolEnvelope<ContractOutput<Schemas[Type]>> & { type: Type },
|
|
39
|
-
) => unknown):
|
|
47
|
+
) => unknown): SocketHandler<ContractInput<Schemas[Type]>>;
|
|
40
48
|
client(socket: SendableSocket): ContractClient<Schemas>;
|
|
41
49
|
send<Type extends keyof Schemas & string>(socket: RedWebSocket, type: Type, payload: ContractInput<Schemas[Type]>, metadata?: ProtocolMetadata): Promise<boolean>;
|
|
42
50
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# One application, one listener
|
|
2
|
+
|
|
3
|
+
`defineApp()` describes the application. `await app.run()` initializes it and opens one port for HTTP pages and WebSocket routes. You do not create an HTTP server and then pass it to a second socket server.
|
|
4
|
+
|
|
5
|
+
Added in Redweb 0.14.0. Earlier releases do not export `defineApp`.
|
|
6
|
+
|
|
7
|
+
## The entry point
|
|
8
|
+
|
|
9
|
+
In a Redweb TypeScript project, the application can be as small as:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { defineApp, page, action, state } from 'redweb';
|
|
13
|
+
|
|
14
|
+
@page('/', { shared: true })
|
|
15
|
+
class HomePage {
|
|
16
|
+
@state() count = 0;
|
|
17
|
+
@action() increment() { this.count += 1; }
|
|
18
|
+
render() {
|
|
19
|
+
return <main><h1>Hello</h1><button rw-click="increment">Count {this.count}</button></main>;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@page('/about', { live: false })
|
|
24
|
+
class AboutPage {
|
|
25
|
+
render() { return <main><h1>About this app</h1></main>; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const app = defineApp({ pages: [HomePage, AboutPage], port: 8181 });
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
await app.run();
|
|
32
|
+
}
|
|
33
|
+
void main().catch(error => { console.error(error); process.exitCode = 1; });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The final error handler is for this CommonJS TypeScript entry point; an ESM project that permits top-level await can simply use `await app.run()`. A module that is also imported by tests should export its application definition and guard its entry-point invocation with `require.main === module`. Importing a definition never opens a port or installs signal handlers.
|
|
37
|
+
|
|
38
|
+
## Add socket routes and services
|
|
39
|
+
|
|
40
|
+
The same definition accepts all three registration arrays:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const app = defineApp({
|
|
44
|
+
pages: [HomePage, AboutPage],
|
|
45
|
+
sockets: [MatchRoute, ChatRoute],
|
|
46
|
+
services: [GameSimulation],
|
|
47
|
+
port: 8181,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await app.run();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
These names refer to your application's classes. A page's decorator chooses its HTTP path. A `SocketRoute` chooses its WebSocket path, such as `/match`; its handlers dispatch `join`, `move`, and `resume` by message `type`. Live-page connections and custom socket routes share one upgrade listener. Do not register a custom socket route at the live-page socket path.
|
|
54
|
+
|
|
55
|
+
In five-year-old terms: the app has one front door. Pages are ordinary visits, sockets are ongoing conversations, and services are the staff who prepare the building before the door opens and clean up after it closes.
|
|
56
|
+
|
|
57
|
+
`services` are application-wide lifecycle classes, not HTTP endpoint descriptors or route-specific `SocketService` classes:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import type { ApplicationContext, ApplicationService } from 'redweb';
|
|
61
|
+
|
|
62
|
+
class GameSimulation implements ApplicationService {
|
|
63
|
+
private timer?: ReturnType<typeof setInterval>;
|
|
64
|
+
private ticks = 0;
|
|
65
|
+
|
|
66
|
+
onInit(app: ApplicationContext, signal: AbortSignal) {
|
|
67
|
+
signal.throwIfAborted();
|
|
68
|
+
app.app.get('/health', (_request, response) => response.json({ ticks: this.ticks }));
|
|
69
|
+
this.timer = setInterval(() => { this.ticks++; }, 1000);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onShutdown() { clearInterval(this.timer); }
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Keep constructors inert. Acquire resources in `onInit(app, signal)` and release them in `onShutdown()`. Initializers run in registration order before the port opens; cleanup runs in reverse order, including the service whose initialization failed. Asynchronous methods are supported. Pass the abort signal to cancelable work and make cleanup safe after partial initialization. A deadline cannot stop arbitrary code that ignores cancellation or blocks the event loop.
|
|
77
|
+
|
|
78
|
+
Use `httpServices` for the existing HTTP endpoint descriptor array. Keep route-specific `SocketService` registrations on their `SocketRoute`. For an existing Express application, pass it as `server`; Redweb still creates and owns the underlying Node listener. The low-level server classes remain available for applications that explicitly manage their own listener ownership.
|
|
79
|
+
|
|
80
|
+
## Lifecycle contract
|
|
81
|
+
|
|
82
|
+
- Definitions are deferred. `app.server`, `app.app`, `app.http`, and `app.sockets` are initially `null`. Await `run()` before accessing their runtime values; its returned value has non-null HTTP members in TypeScript.
|
|
83
|
+
- `pages`, `sockets`, and `services` are optional. HTTP-only, socket-only, and non-live-page applications use the same entry point. Non-live pages need no WebSocket server unless custom socket routes were supplied.
|
|
84
|
+
- Repeated `run()` calls share the pending or successful startup promise while the application is running. `shutdown()` is idempotent. A stopped application cannot restart; define a new application instead.
|
|
85
|
+
- Startup failure rolls back resources before rejection, subject to the shutdown deadline. Cleanup failures are retained alongside the original startup error, not reported as success.
|
|
86
|
+
- `startupTimeoutMs` and `shutdownTimeoutMs` default to 5000. Each is one total application budget, not a fresh full timeout for every service. Shutdown cancels pending startup, closes admission, attempts page/socket cleanup, closes owned connections, and releases services.
|
|
87
|
+
- `signals: true` is the default. Signal handlers are installed when `run()` begins, so SIGINT/SIGTERM also cancel pending initialization. Unexpected listener closure and listener errors trigger owned shutdown. Repeated signals do not bypass active cleanup. Failed process-owned cleanup sets a failure exit status and retains a deadline for leaked handles. Explicit `shutdown()` rejects on cleanup failure but never forcibly exits its caller.
|
|
88
|
+
- Tests and embedded applications should set `signals: false` and call `shutdown()` in their own cleanup. Use `port: 0` for an OS-assigned test port. TLS uses the existing `ssl` key/certificate options and shares one HTTPS/WSS listener.
|
|
89
|
+
|
|
90
|
+
## Boundaries
|
|
91
|
+
|
|
92
|
+
This is application composition, not dependency injection, a distributed worker manager, or durable storage. It does not automatically inject services into page constructors. `shared: true` shares in-process state across visitors, not across server processes or restarts. Static file export remains the separate `exportStatic()` API; a non-live page served over HTTP is not a static export.
|
|
93
|
+
|
|
94
|
+
`app.revoke(principal)` revokes matching live-page sessions and returns their count; it returns zero when no live-page server has been created. `app.inspect()` exposes opt-in development metadata and otherwise returns `null`. These preserve the same live-page policies as `start()`.
|
|
95
|
+
|
|
96
|
+
`app.options` is the copied definition. An independent test instance can use `defineApp({ ...app.options, port: 0, signals: false })`, followed by `await run()` and owned cleanup. This creates new page/service instances, but does not clone an Express application or objects deliberately captured by class closures. The chat module exports a default `ChatroomPage`; its optional `createChatroomPage()` factory creates isolated rooms for separate apps or tests.
|
|
97
|
+
|
|
98
|
+
For rendering and CSS see [Live HTML](LIVE_HTML.md). For message validation and handler classes see [socket contracts](SOCKET_CONTRACTS.md). For deployment and persistence boundaries see [operations](MULTIPLAYER_OPERATIONS.md).
|
package/docs/CLI.md
CHANGED
|
@@ -4,7 +4,7 @@ Use the version installed in your project (`npx --no-install redweb`) when troub
|
|
|
4
4
|
|
|
5
5
|
## Add pages, components, and socket routes
|
|
6
6
|
|
|
7
|
-
These commands are available in `redweb@0.
|
|
7
|
+
These commands are available in `redweb@0.15.0`.
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
npx --no-install redweb add page dashboard
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Developing Redweb with redweb-client
|
|
2
2
|
|
|
3
3
|
The Live HTML implementation is maintained in published `redweb-client/live-html`
|
|
4
|
-
starting with client 0.2.0.
|
|
4
|
+
starting with client 0.2.0. Redweb 0.15.0 requires client `^0.3.0`;
|
|
5
5
|
normal application installation retrieves it automatically. The linking workflow
|
|
6
6
|
below is optional for contributors editing both repositories.
|
|
7
7
|
Redweb serves that module and emits only its import and `mountLivePage()` call.
|
|
8
|
-
DOM reconciliation, reactive updates, delegated actions, form feedback and page
|
|
9
|
-
disposal belong to the client. The root `redweb-client` entry remains socket-only.
|
|
8
|
+
DOM reconciliation, reactive updates, delegated actions, form feedback and page
|
|
9
|
+
disposal belong to the client. The root `redweb-client` entry remains socket-only.
|
|
10
|
+
|
|
11
|
+
Socket-bound TSX command bindings and terminal-response filtering use the published
|
|
12
|
+
client 0.3.0 runtime. No link is needed for applications using Redweb 0.15.0.
|
|
13
|
+
See [socket pages](SOCKET_PAGES.md).
|
|
10
14
|
|
|
11
15
|
## Link the sibling repositories
|
|
12
16
|
|
|
@@ -107,7 +111,7 @@ For example, from Redweb in PowerShell (use a fresh output directory):
|
|
|
107
111
|
npm --prefix ../redweb-client run build
|
|
108
112
|
New-Item -ItemType Directory -Path coverage/client-candidate
|
|
109
113
|
npm pack ../redweb-client --pack-destination coverage/client-candidate
|
|
110
|
-
$env:REDWEB_CLIENT_CANDIDATE = (Resolve-Path coverage/client-candidate/redweb-client-0.
|
|
114
|
+
$env:REDWEB_CLIENT_CANDIDATE = (Resolve-Path coverage/client-candidate/redweb-client-0.3.0.tgz).Path
|
|
111
115
|
npm run verify:live-html:package
|
|
112
116
|
Remove-Item Env:REDWEB_CLIENT_CANDIDATE
|
|
113
117
|
```
|
|
@@ -143,7 +147,7 @@ bundles with the source-tested local build and run the same browser checks.
|
|
|
143
147
|
A candidate pass is not a registry release pass. `npm run verify:package:tools` includes the fingerprint/containment
|
|
144
148
|
unit regressions; its scoped coverage is not coverage of every browser driver.
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
Historically, published `redweb-client@0.2.0` supplied both required entry points; version 0.1.0
|
|
147
151
|
does not. The 0.2.0 archive's runtime bundles match the previously source-tested
|
|
148
152
|
build, and the clean registry-installed package gate passes without an override.
|
|
149
153
|
The developer link can remain in place because registry checks own independent
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Coverage scope and remaining work
|
|
2
2
|
|
|
3
|
+
> This is a historical coverage inventory. Following the `defineApp` migration,
|
|
4
|
+
> the generated `run-app` helper and its separate verifier no longer exist.
|
|
5
|
+
> Current lifecycle coverage targets `Application` and `StartupCleanup` via
|
|
6
|
+
> `verify:starters:lifecycle:coverage`; authored starter coverage remains under
|
|
7
|
+
> `verify:starters:source-coverage`. Older results below do not establish coverage
|
|
8
|
+
> of the current release candidate.
|
|
9
|
+
|
|
3
10
|
This inventory separates shipped authored code from verification machinery.
|
|
4
11
|
Passing behavior tests do not establish complete coverage; a coverage report's
|
|
5
12
|
instrumentation hash alone does not prove correspondence with current source.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Unified application verification
|
|
2
|
+
|
|
3
|
+
Implementation history for `codex/define-app`. These are scoped results recorded
|
|
4
|
+
during development, not a substitute for final release status. The exact-head
|
|
5
|
+
checks and reviewer discussion are retained in
|
|
6
|
+
[PR #23](https://github.com/lakam99/redweb/pull/23).
|
|
7
|
+
|
|
8
|
+
## Starter migration (2026-09-01, Windows / Node 22.21.0)
|
|
9
|
+
|
|
10
|
+
- `verify:starters:lifecycle:coverage`: 72 unit and real HTTP/WebSocket/process
|
|
11
|
+
tests pass. `Application.js` and `StartupCleanup.js` are 100% covered in
|
|
12
|
+
statements, branches, functions, and lines.
|
|
13
|
+
- The CLI templates, `SourceInspector`, and `Documentation` reach all-four 100%
|
|
14
|
+
with 80 unit/source-repair integration tests. The narrower unit-only command
|
|
15
|
+
does not cover external template inspection; include the action-reference and
|
|
16
|
+
doctor-source suites when reproducing this scope.
|
|
17
|
+
- `verify:starters:source-coverage` passed for all six original TypeScript
|
|
18
|
+
applications, separately from compiler-generated JavaScript coverage. The
|
|
19
|
+
retained run is `coverage/starter-source/a011d72d-1450-409f-935a-d21623c582a6/`.
|
|
20
|
+
Its summary records input/report hashes and the actual source inventories.
|
|
21
|
+
- `verify:starter:browser:coverage`: 30 tests pass, including headed counter,
|
|
22
|
+
chatroom, and multi-page site interactions from compiled/source-removed apps.
|
|
23
|
+
- `verify:dashboard:coverage`: 27 tests pass, including headed rejected/accepted
|
|
24
|
+
login, private cards, draft preservation, logout/relogin, and deletion.
|
|
25
|
+
- The six generated applications pass real-network tests. Their shared process
|
|
26
|
+
test imports inert definitions, runs each application, closes it via signals
|
|
27
|
+
or native listener closure, and checks occupied-port startup failure. Windows
|
|
28
|
+
exercises Node signal events; Linux exercises delivered OS signals.
|
|
29
|
+
|
|
30
|
+
The independent reviewer found no runtime blocker in the migration. The stale
|
|
31
|
+
generated-helper report is now explicitly historical. The copied `run-app`
|
|
32
|
+
implementation was removed, not retained beside the library lifecycle owner.
|
|
33
|
+
|
|
34
|
+
An earlier interrupted full-suite run had a browser-control failure before its
|
|
35
|
+
intended negative assertion. A standalone rerun passed all four working controls
|
|
36
|
+
and seven intentional faults (63.9 seconds), without changing the control or
|
|
37
|
+
weakening its assertions. That does not establish the cause of the earlier
|
|
38
|
+
failure or replace a completed full-suite run. Final regression, package, CI,
|
|
39
|
+
release-catalogue, and exact-PR-head review remain outstanding.
|
|
40
|
+
|
|
41
|
+
No soak or long fixed-window acceptance test was run for this migration.
|
|
42
|
+
|
|
43
|
+
## Follow-up regression repair
|
|
44
|
+
|
|
45
|
+
The earlier Linux CI matrix exposed `StartupCleanup` replacing native errors
|
|
46
|
+
from another JavaScript context with a generic error. The existing owned-listener
|
|
47
|
+
integration reproduced both failures locally. Using Node's `isNativeError`
|
|
48
|
+
preserves the original error identity/message without treating arbitrary thrown
|
|
49
|
+
values as native errors. A real VM-context regression was added; lifecycle
|
|
50
|
+
coverage is now 73 passing tests at all-four 100%, and all eight owned-listener
|
|
51
|
+
integration tests pass. The VM/owned-listener suites also pass on Node 18.
|
|
52
|
+
|
|
53
|
+
CI also identified an obsolete test-count assertion in the starter coordinator's
|
|
54
|
+
input-mutation check. Its expected successful fixture now includes one realtime
|
|
55
|
+
test and four application-entrypoint tests; failure-on-input-mutation assertions
|
|
56
|
+
are unchanged.
|
|
57
|
+
|
|
58
|
+
The reviewer caught a Node 18 distinction: `DOMException` is an `Error` there
|
|
59
|
+
but is not recognized by `isNativeError`. Keeping both checks preserves native
|
|
60
|
+
abort-reason identity as well as foreign-context errors. A native-process abort
|
|
61
|
+
regression passes on Node 18 and 22; the lifecycle scope now has 74 passing tests
|
|
62
|
+
and all-four 100% coverage. The corrected starter coordinator gate passes all
|
|
63
|
+
56 tests with all-four 100% coverage of its three coordinator modules.
|
|
64
|
+
|
|
65
|
+
## Release catalogue checks
|
|
66
|
+
|
|
67
|
+
The prepared package and lockfile are `0.14.0`. The 68-page generated catalogue
|
|
68
|
+
matches `docs/releases/0.14.0.json`; older release snapshots were not edited.
|
|
69
|
+
The package dry run includes the unified runtime and shared entrypoint tests,
|
|
70
|
+
and the release-documentation guard passes. No package was published.
|
|
71
|
+
|
|
72
|
+
The optional docs MCP adapter previously hard-coded an unreleased-channel
|
|
73
|
+
expectation. Its tests now compare against the loaded canonical channel and
|
|
74
|
+
exercise both valid channel variants. All seven tests pass, including actual
|
|
75
|
+
MCP subprocesses and isolated production-only package installation; all three
|
|
76
|
+
adapter modules have 100% line, branch, and function coverage.
|
|
77
|
+
|
|
78
|
+
## Broader regression findings
|
|
79
|
+
|
|
80
|
+
The first completed Windows run finished with 174 passing suites and four
|
|
81
|
+
failing suites (1,984 passing tests, seven failures, five skips). It began before
|
|
82
|
+
the final source repairs and version update, so it is not final-head evidence.
|
|
83
|
+
|
|
84
|
+
- The standalone-example unit launcher still stubbed `start`. It now explicitly
|
|
85
|
+
tests `defineApp().run()` and its rejection handler. Both original-TypeScript
|
|
86
|
+
coverage runs (standard and legacy decorators) pass with unchanged real page
|
|
87
|
+
behavior checks alongside the separately labelled launcher units.
|
|
88
|
+
- Current guide version labels were left at 0.13.5. They now match 0.14.0; the
|
|
89
|
+
candidate catalogue was corrected before publication. No published release
|
|
90
|
+
snapshot was changed. The documentation unit/coverage and release guard pass.
|
|
91
|
+
- The HTML load workload timed out awaiting disconnected-session expiry. Its
|
|
92
|
+
unchanged standalone replay passed: 200 expired renders, 110 clients, and
|
|
93
|
+
8,288,824 bytes heap growth. A standalone pass does not establish the earlier
|
|
94
|
+
timeout's cause.
|
|
95
|
+
- Server recovery reproduced a server-connection cleanup timeout in isolation.
|
|
96
|
+
Evidence remains in `coverage/server-recovery-candidate-kFjY7N/`; this failed
|
|
97
|
+
run is not acceptance. Its limits and workload have not been weakened.
|
|
98
|
+
|
|
99
|
+
Coverage review also added real acceptance for non-live pages with custom
|
|
100
|
+
socket routes and direct invalid socket-registration validation. Later exact-head
|
|
101
|
+
test results and any further repairs belong to the PR's final verification record.
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Development refresh and inspection
|
|
2
2
|
|
|
3
|
-
This API is available in `redweb@0.
|
|
3
|
+
This API is available in `redweb@0.15.0`. Use documentation matching the installed package before enabling it.
|
|
4
4
|
|
|
5
5
|
## Browser refresh
|
|
6
6
|
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -47,7 +47,7 @@ For private raw socket subscriptions, see [room authorization and shared request
|
|
|
47
47
|
|
|
48
48
|
Build first. Deploy `dist/`, the package manifest, and the lockfile, then install runtime dependencies with `npm ci --omit=dev`. The starters are tested with `src/` unavailable after compilation. Configure HTTPS/WSS and a proxy that supports WebSocket upgrades when using a reverse proxy.
|
|
49
49
|
|
|
50
|
-
These deployment commands require a verified release pair. `redweb@0.
|
|
50
|
+
These deployment commands require a verified release pair. `redweb@0.15.0` installs published `redweb-client@0.3.0` automatically through its dependency. Future unreleased Redweb changes require their matching tested tarball until a release containing them is published. The `npm link` workflow is local development only: a clean production install does not preserve that link.
|
|
51
51
|
|
|
52
52
|
Before public access, add authentication, authorization, trusted-origin policy, input/rate limits, application persistence where needed, and bounded shutdown. Treat reconnect/session tokens as credentials. Do not promise exactly-once delivery or durable sessions from an in-memory starter. See [operations](MULTIPLAYER_OPERATIONS.md) and [guarantees and limits](PRODUCTION_READINESS.md).
|
|
53
53
|
|
package/docs/LIVE_HTML.md
CHANGED
|
@@ -340,7 +340,7 @@ The same bounded validation implementation is shared with socket contracts. Thei
|
|
|
340
340
|
|
|
341
341
|
## Action authorization
|
|
342
342
|
|
|
343
|
-
Identity and permission are separate: the server's existing `authenticate(request)` hook establishes `context.principal`; an action policy decides whether that identity may perform this operation. In Redweb 0.
|
|
343
|
+
Identity and permission are separate: the server's existing `authenticate(request)` hook establishes `context.principal`; an action policy decides whether that identity may perform this operation. In Redweb 0.15.0, add `authorize` to the action decorator instead of repeating permission checks inside each method:
|
|
344
344
|
|
|
345
345
|
```tsx
|
|
346
346
|
// Inside a page/component; `input` is the amount schema from the example above.
|
|
@@ -365,7 +365,7 @@ Denial returns recoverable `ACCESS_DENIED`; timeout returns `ACCESS_TIMEOUT`; co
|
|
|
365
365
|
|
|
366
366
|
## Protected pages and shared request identity
|
|
367
367
|
|
|
368
|
-
In Redweb 0.
|
|
368
|
+
In Redweb 0.15.0, a page can declare `authorize(context)` alongside its route. This is an API pattern for an application that already supplies the server's `authenticate(request)` hook, not a standalone login system:
|
|
369
369
|
|
|
370
370
|
```tsx
|
|
371
371
|
@page('/account/:id', {
|
package/docs/MIGRATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Upgrade an existing Redweb application
|
|
2
2
|
|
|
3
|
-
Match the installed package to its versioned documentation. Redweb 0.
|
|
3
|
+
Match the installed package to its versioned documentation. Redweb 0.15.0 contains the capabilities described by the 0.15.0 guides; a later development checkout may not match that release. See [release verification](RELEASE_TRUST.md) and the changelog shipped with your selected package. Keep your lockfile and rollback artifact, and run your own real HTTP/WebSocket/browser tests after upgrading.
|
|
4
4
|
|
|
5
5
|
## 0.8 migration notes
|
|
6
6
|
|
|
@@ -19,6 +19,12 @@ process.once('SIGTERM', async () => {
|
|
|
19
19
|
|
|
20
20
|
If `drainHandlers` is enabled, handlers should observe `socket.context.signal` and return promptly. Set the platform termination grace period above the application's maximum cooperative handler time plus `shutdownTimeoutMs`.
|
|
21
21
|
|
|
22
|
+
## Closing connections
|
|
23
|
+
|
|
24
|
+
Once a WebSocket closing handshake starts, Redweb lets the native `ws` transport finish it for up to 5000ms, then terminate the peer if necessary. This releases connections that exchange close frames but never finish TCP shutdown. Configure `websocketOptions: { closeTimeout: 10000 }` on a route if its peers need longer; values must be integers from 1 through 2147483647 milliseconds.
|
|
25
|
+
|
|
26
|
+
This is not an idle timeout: healthy open connections are unaffected. Heartbeats detect unresponsive open peers, session TTL controls retained application state after disconnection, and `shutdownTimeoutMs` separately bounds route shutdown.
|
|
27
|
+
|
|
22
28
|
## Placement and partitions
|
|
23
29
|
|
|
24
30
|
The admission `place(principal, request, context)` hook can return another node's `ws`/`wss` URL before upgrade. Keep placement decisions short-lived and retryable. A redirect is not a reservation: the destination must still authenticate, enforce capacity, and reject stale placement.
|
package/docs/RELEASE_TRUST.md
CHANGED
|
@@ -16,20 +16,43 @@ Redweb is a Node.js HTTP/WebSocket library with server-rendered TSX, not a hoste
|
|
|
16
16
|
|
|
17
17
|
Use the [official Node release schedule](https://nodejs.org/en/about/previous-releases) for maintained releases, the [TypeScript decorator documentation](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) for the distinction between decorator modes, and [Node's TypeScript limitations](https://nodejs.org/api/typescript.html) for native execution constraints. Current per-commit test evidence and uncompleted checks are recorded in the [release checklist](AGENT_READY_ACCEPTANCE.md), not inferred from this table.
|
|
18
18
|
|
|
19
|
-
## Pin the package and the documentation together
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
## Pin the package and the documentation together
|
|
20
|
+
|
|
21
|
+
### Temporary Express 4 dependency mitigation
|
|
22
|
+
|
|
23
|
+
As verified on 2026-09-02, Express 4.22.2 selects `qs ~6.15.1`; the maintainer's
|
|
24
|
+
[isBuffer advisory](https://github.com/ljharb/qs/security/advisories/GHSA-4mjr-xmp4-gh2g)
|
|
25
|
+
and [bracket-key advisory](https://github.com/ljharb/qs/security/advisories/GHSA-x5fp-wj9c-mxmx)
|
|
26
|
+
identify patched `qs@6.16.0`. Until compatible upstream ranges select that fix,
|
|
27
|
+
use this temporary mitigation in the **application's root** `package.json`:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{ "overrides": { "express": { "qs": "6.16.0" } } }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Merge it with existing overrides, run `npm install`, commit the resulting lockfile,
|
|
34
|
+
and verify with `npm ls qs`, `npm audit --omit=dev`, and application HTTP/form tests.
|
|
35
|
+
New generated starters include the same policy. It is scoped to Express so it
|
|
36
|
+
does not force unrelated dependency trees onto another `qs` version.
|
|
37
|
+
|
|
38
|
+
This is an application-level mitigation, not a transparent fix for every Redweb
|
|
39
|
+
consumer. [npm ignores overrides inside installed dependencies](https://docs.npmjs.com/cli/v11/configuring-npm/package-json/#overrides).
|
|
40
|
+
An ordinary installation without this root policy can still select affected
|
|
41
|
+
dependencies. No Express 5 migration, bundled fork or published shrinkwrap is
|
|
42
|
+
introduced. Audit results remain time-sensitive and application-specific.
|
|
43
|
+
|
|
44
|
+
For a published application, select an exact release, commit its lockfile, and use `npm ci` in CI/deployment. This guide is versioned for 0.15.0. Before registry publication, verify the packed candidate; after publication, repeat these registry checks from a clean application:
|
|
22
45
|
|
|
23
46
|
```sh
|
|
24
|
-
npm view redweb@0.
|
|
25
|
-
npm install --save-exact redweb@0.
|
|
47
|
+
npm view redweb@0.15.0 version engines dist.integrity dist.signatures dist.attestations gitHead --json
|
|
48
|
+
npm install --save-exact redweb@0.15.0
|
|
26
49
|
npm audit signatures
|
|
27
50
|
npm audit --omit=dev
|
|
28
51
|
```
|
|
29
52
|
|
|
30
53
|
The signature command must run in the installed application directory. Keep TLS verification enabled and use a current npm CLI; a certificate/trust-store failure is not a reason to disable verification. A lockfile's integrity value detects changed package bytes; registry signatures authenticate registry metadata; provenance, when present and verified, links an artifact to a build/source identity. Vulnerability audit is a separate check against known advisories, not an application penetration test.
|
|
31
54
|
|
|
32
|
-
Redweb 0.
|
|
55
|
+
Redweb 0.15.0 contains socket-bound TSX controls and connection-owned page state, unified application startup, server-rendered TSX, reactive state/actions, complete starters, shared socket contracts, authorization, diagnostics, lifecycle work, and bounded heartbeat grace described by these versioned guides. Keep the package and documentation version aligned; do not mix a development guide or a future checkout with 0.15.0 and assume newer APIs exist.
|
|
33
56
|
|
|
34
57
|
Redweb is pre-1.0. Consult the changelog and versioned guide before upgrading, run your own real HTTP/WebSocket/browser tests, and keep a rollback artifact. Patch/minor numbers and a compatible TypeScript build alone do not prove wire compatibility, preserved sessions, database compatibility or application authorization. HTTP-created live-page sessions are process-owned; a restart or rolling replacement does not migrate them automatically. Raw socket protocol versions are negotiated only when the route opts in, and application payload compatibility remains your contract.
|
|
35
58
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Understand failures before retrying
|
|
2
2
|
|
|
3
|
-
Status: included in `redweb@0.
|
|
3
|
+
Status: included in `redweb@0.15.0`.
|
|
4
4
|
|
|
5
5
|
Authentication identifies a visitor. Authorization decides what that visitor may do. Validation checks an input's shape. An application failure means server code or a dependency failed; it is not evidence that the visitor supplied bad credentials.
|
|
6
6
|
|
package/docs/SOCKET_CONTRACTS.md
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# Shared socket contracts
|
|
2
2
|
|
|
3
|
-
Status: included in `redweb@0.
|
|
3
|
+
Status: included in `redweb@0.15.0`.
|
|
4
4
|
|
|
5
5
|
A contract declares message payloads once. The same schema supplies runtime validation and inferred TypeScript types for senders and handlers. The URL still selects the route (`/match`), and the envelope's `type` selects an individual handler (`join`, `move`, `resume`). No socket decorators or second action dispatcher are required.
|
|
6
6
|
|
|
7
|
-
Start with `npx --yes redweb@0.
|
|
7
|
+
Start with `npx --yes redweb@0.15.0 init my-match --template socket`. The complete maintained example lives in [the socket recipe](../recipes/socket/README.md): [contract](../recipes/socket/contract.ts), [handlers](../recipes/socket/handlers.ts), [server](../recipes/socket/app.tsx), and [real-network tests](../recipes/socket/app.test.cjs).
|
|
8
8
|
|
|
9
9
|
Session ownership is separate from room fan-out. For authenticated group delivery,
|
|
10
10
|
see [room authorization](ROOM_AUTHORIZATION.md) and the complete
|
|
11
11
|
[shared page/private-room example](snippets/room-access.tsx).
|
|
12
12
|
|
|
13
|
-
## One schema, two sides
|
|
13
|
+
## One schema, two sides
|
|
14
|
+
|
|
15
|
+
Redweb 0.15.0 also supports [typed handlers in server TSX](SOCKET_PAGES.md)
|
|
16
|
+
with per-connection page state; earlier releases do not include that extension.
|
|
14
17
|
|
|
15
18
|
Import `defineSocketContract` from `redweb/contract` for a shared module, or from `redweb` in server-only code. The standalone entry does not import the HTTP server or Node socket listener. Browser consumers need a bundler capable of consuming the CommonJS package; this is not a native browser script URL or a React integration.
|
|
16
19
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Server-side TSX for custom socket routes
|
|
2
|
+
|
|
3
|
+
Added in Redweb 0.15.0 with redweb-client 0.3.0, installed automatically by Redweb.
|
|
4
|
+
Earlier Redweb 0.14.0/client 0.2.0 packages do not provide this extension.
|
|
5
|
+
Contributors editing both packages can use the optional npm-link workflow in the repository's `docs/CLIENT_DEVELOPMENT.md`.
|
|
6
|
+
|
|
7
|
+
A live page can attach to a registered custom `SocketRoute` using
|
|
8
|
+
`@page('/', { socket: MatchRoute })`. There is no new view class, no second renderer,
|
|
9
|
+
and no application-specific browser module. Its connection carries ordinary typed
|
|
10
|
+
commands and reserved `redweb:*` rendering/completion messages.
|
|
11
|
+
|
|
12
|
+
## Typed controls
|
|
13
|
+
|
|
14
|
+
`defineSocketContract(...).handler(type, callback)` still returns a handler class.
|
|
15
|
+
That class can now be used as a JSX binding:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
<form rw-submit={Join}>
|
|
19
|
+
<input name="room" required />
|
|
20
|
+
<button>Join room</button>
|
|
21
|
+
</form>
|
|
22
|
+
|
|
23
|
+
<button rw-click={Move.with({ cell, revision })}>Move</button>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`.with()` snapshots JSON data; it never invokes the handler during rendering.
|
|
27
|
+
TypeScript checks the payload's input type. Incoming wire data is independently
|
|
28
|
+
validated by the contract, including asynchronous schemas. Form fields merge over
|
|
29
|
+
the bound object payload. A click sends its bound payload, or an empty object when
|
|
30
|
+
none was supplied. Bound values are untrusted inputs, not server-held secrets or
|
|
31
|
+
authorization capabilities. Do not embed private data in them.
|
|
32
|
+
|
|
33
|
+
Bindings are server-owned identities, not arbitrary function closures. Rendering
|
|
34
|
+
rejects references to handlers absent from the attached route, even if a different
|
|
35
|
+
handler has the same message name. Existing string `rw-click`/`rw-submit` actions
|
|
36
|
+
remain unchanged on ordinary live pages. Socket-bound pages dispatch only their
|
|
37
|
+
custom route handlers; do not mix local `@action()`/`rw-bind` commands into them.
|
|
38
|
+
|
|
39
|
+
## Server-owned client state
|
|
40
|
+
|
|
41
|
+
On an attached connection, `socket.page(PageClass)` returns that connection's
|
|
42
|
+
concrete page after checking its class, ownership and active lifetime:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
if (socket.page) {
|
|
46
|
+
socket.page(GamePage).game = games.snapshot(room, account);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Assigning `@state()` fields uses the existing reactive renderer and keyed TSX.
|
|
51
|
+
Raw connections have no `page` accessor and keep their existing socket protocol.
|
|
52
|
+
Shared domain state belongs in a room/service; each private page receives only its
|
|
53
|
+
authorized perspective. Components remain ordinary page-owned Redweb components.
|
|
54
|
+
|
|
55
|
+
## Configuration and boundaries
|
|
56
|
+
|
|
57
|
+
Register the route once in `defineApp({ pages: [GamePage], sockets: [MatchRoute] })`.
|
|
58
|
+
Socket-bound pages must be live, connection-scoped and render TSX. Their route must
|
|
59
|
+
support protocol version `1` with the default `redwebVersion` query parameter and
|
|
60
|
+
explicitly allow duplicate connections (independent tabs). `redweb:*` handler names
|
|
61
|
+
are reserved. Page-enabled routes use ordered, bounded message dispatch and track
|
|
62
|
+
in-flight work for shutdown. JSON commands are supported; binary input is rejected
|
|
63
|
+
on page-attached connections. Raw route connections retain binary handling.
|
|
64
|
+
|
|
65
|
+
The adapter preserves route admission, origin and placement policies, then checks
|
|
66
|
+
the page token, exact route, page identity and page origin. Use `authenticate` for
|
|
67
|
+
page identity and `authorize` for current permissions/session validity. Authorization
|
|
68
|
+
runs before commands, again after contract validation, and before render publication.
|
|
69
|
+
Revocation and disconnect cancel ownership even when validation is still running.
|
|
70
|
+
The page token is not a substitute for credentials.
|
|
71
|
+
|
|
72
|
+
Initial page attachment completes before custom initialization hooks or commands.
|
|
73
|
+
Do not call page commands from initialization hooks expecting those hooks themselves
|
|
74
|
+
to finish first. Each page owns its connection; the same token cannot attach twice.
|
|
75
|
+
|
|
76
|
+
Reconnect reauthenticates, invokes existing `connected()` hooks, and sends a fresh
|
|
77
|
+
rendering snapshot. Game-seat recovery remains in that server hook. Commands are
|
|
78
|
+
not queued or replayed. Page-session expiration requires a reload; game persistence
|
|
79
|
+
is not provided by page retention.
|
|
80
|
+
|
|
81
|
+
## Completion and client runtime
|
|
82
|
+
|
|
83
|
+
After successful handler dispatch, Redweb sends a correlated `redweb:result`.
|
|
84
|
+
The generated runtime waits specifically for that terminal type or a protocol error,
|
|
85
|
+
not intermediate correlated progress messages. Direct client users can opt into
|
|
86
|
+
the same behavior using `client.request(type, payload, { responseType })`; omitting
|
|
87
|
+
the option preserves the existing any-correlated-response behavior.
|
|
88
|
+
|
|
89
|
+
For an expected application rejection, send a safe correlated error with
|
|
90
|
+
`socket.sendProtocolError('GAME_REJECTED', safeMessage, { requestId })` and return
|
|
91
|
+
`false` from the handler. Only literal `false` declines successful dispatch;
|
|
92
|
+
`undefined` remains success. The browser shows error feedback, preserves the form,
|
|
93
|
+
and keeps its connection usable. Returning `false` without sending a correlated
|
|
94
|
+
reply leaves a requesting client waiting until its deadline. Unexpected exceptions
|
|
95
|
+
retain the existing sanitized handler-failure behavior and close the connection.
|
|
96
|
+
|
|
97
|
+
This browser support lives in redweb-client's existing feedback/transport modules.
|
|
98
|
+
The server still owns validation, authorization, game revisions, room fan-out and
|
|
99
|
+
private rendering. Disabled controls are presentation, never permissions.
|