@trevrpc/trevrpc-js 1.1.0 → 1.3.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/README.md +34 -5
- package/package.json +12 -4
- package/src/call.js +401 -58
- package/src/channel.js +47 -10
- package/src/client.js +93 -22
- package/src/compression.js +330 -0
- package/src/connection-metadata.js +114 -0
- package/src/datagram.js +48 -27
- package/src/framing.js +23 -6
- package/src/generator.js +134 -14
- package/src/index.d.ts +75 -14
- package/src/qmux/session.js +2 -1
- package/src/stream-compression.js +298 -0
- package/src/wire.js +43 -11
- package/src/zstd.d.ts +35 -0
- package/src/zstd.js +407 -0
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ call.
|
|
|
101
101
|
|
|
102
102
|
- Failed calls throw `TrevRpcError` with a `code` (`Code.NotFound`, ...), `statusMessage` and `metadata`. A deadline surfaces as `Code.DeadlineExceeded`, a cancelled call as `Code.Cancelled` (with the abort reason as `cause`), and a lost connection as `Code.Unavailable`.
|
|
103
103
|
- Streaming responses end with the server's final status: iteration finishes after `OK` and throws the `TrevRpcError` otherwise. `stream.status` settles with the same outcome and carries trailing metadata. Leaving a `for await` loop early, or calling `close()`, cancels the call.
|
|
104
|
-
- Response headers and trailing metadata are separate. Every call reports them to the `onResponseHeaders(headers)` and `onResponseMetadata(metadata)` callbacks of its options: the headers (empty when the server sent none) as soon as the first
|
|
104
|
+
- Response headers and trailing metadata are separate. Every call reports them to the `onResponseHeaders(headers)` and `onResponseMetadata(metadata)` callbacks of its options: the headers (empty when the server sent none) as soon as they, the first message or the final status arrive, and the trailing metadata of the final status when the call succeeds, before its result is returned or its stream ends. A failed call carries its metadata on the `TrevRpcError`, and a callback that throws fails the call. Server-streaming and bidirectional calls also have a `headers` promise that resolves as soon as the headers, the first message or the final status arrive, so headers are available before the first message, and rejects with the call's error if the call fails first.
|
|
105
105
|
- Frames follow the [wire format](https://trev.zip/llc/TrevRPC/wiki/Protocol-and-Wire-Format): a type, a length and a payload, with messages sent as raw protobuf. The client skips frame types it does not know, and `maxFrameSize` limits every frame, unknown ones included.
|
|
106
106
|
- The server's response decides the outcome of a call, and a call reports it as soon as the final status arrives. If the server ends a call before the upload is done (client-streaming and bidirectional calls), the upload is cancelled and `send()` rejects at once, without waiting for the server to stop reading and whether or not the caller receives:
|
|
107
107
|
- with the server's `TrevRpcError` if its status is not OK, and the same error is the call's outcome (`closeAndRecv()`, `recv()`, `status`);
|
|
@@ -128,10 +128,39 @@ const channel = await connect(url, {
|
|
|
128
128
|
|
|
129
129
|
`serverCertificateHashes` trusts a self-signed ECDSA certificate that is valid for at most 14 days by its SHA-256 hash, which is how development servers and the interop tests are reached. It applies to WebTransport only: the WebSocket fallback needs a certificate the browser trusts. `allowPooling`, `congestionControl` and `requireUnreliable` are passed to the `WebTransport` constructor.
|
|
130
130
|
|
|
131
|
-
`connect()` always offers the WebTransport protocol `trevrpc-1` (wire version 1) through the `protocols` constructor option; it cannot be changed. If the server selects any other protocol, that connection attempt fails with `Code.FailedPrecondition` and the channel carries on as after any failed dial: the WebSocket fallback starts, or a reconnect is retried with backoff. An empty or missing selection means version 1, because older browsers cannot negotiate protocols
|
|
131
|
+
`connect()` always offers the WebTransport protocol `trevrpc-1` (wire version 1) through the `protocols` constructor option; it cannot be changed. If the server selects any other protocol, that connection attempt fails with `Code.FailedPrecondition` and the channel carries on as after any failed dial: the WebSocket fallback starts, or a reconnect is retried with backoff. An empty or missing selection means version 1, because older browsers cannot negotiate protocols.
|
|
132
132
|
|
|
133
133
|
Any object with `createBidirectionalStream()`, including a plain `WebTransport` session, can be used as the transport of a generated client. Create such a session with `new WebTransport(url, { protocols: ["trevrpc-1"] })` so the server knows the wire version. Requests name the method by its full path, `/<package.Service>/<Method>` (for example `/example.greeter.Greeter/SayHello`), which the generated clients build from the service and method names.
|
|
134
134
|
|
|
135
|
+
### Connection metadata
|
|
136
|
+
|
|
137
|
+
Metadata every call of a channel carries, such as a bearer token, can be set once on the channel instead of on each call. The channel sends it in full on the first calls of each session, until the server confirms that it stored it for the session, and refers to it by ID after that, so it crosses the connection once. The server merges it into each call's metadata, and a call's own `metadata` wins over an entry with the same key.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const channel = await connect(url, { connectionMetadata: { authorization: `Bearer ${token}` } });
|
|
141
|
+
channel.setConnectionMetadata({ authorization: `Bearer ${refreshed}` }); // calls started from now on
|
|
142
|
+
channel.setConnectionMetadata(); // removes it
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A changed set is sent in full again on every session, and so is the set on a session after a reconnect. Keys follow the rules of call metadata. The `Channel` is the only transport that supports it.
|
|
146
|
+
|
|
147
|
+
### Compression
|
|
148
|
+
|
|
149
|
+
Messages can travel compressed with zstd and a dictionary trained on them (see [Compression](https://trev.zip/llc/TrevRPC/wiki/Compression)). The codec lives in a separate entry point, `@trevrpc/trevrpc-js/zstd`, which loads libzstd as WebAssembly (about 85 KB gzipped), so applications without compression never load it:
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
import { createCompression } from "@trevrpc/trevrpc-js/zstd";
|
|
153
|
+
|
|
154
|
+
const compression = await createCompression({
|
|
155
|
+
dictionaries: [{ data: dictionaryBytes, messages: ["example.greeter.HelloRequest"] }],
|
|
156
|
+
});
|
|
157
|
+
const channel = await connect(url, { compression });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Generated clients pick each message's dictionary by its protobuf type. The channel asks each new session's server which dictionaries it holds, so a message whose dictionary the server lacks travels uncompressed, and request messages are compressed once the answer arrives. A transport other than a `Channel` compresses when it has a `compression` property with these settings.
|
|
161
|
+
|
|
162
|
+
Compression settings also enable stream compression, even without dictionaries (`createCompression()`): streaming methods marked `option (trevrpc.method) = {compression: COMPRESSION_STREAM}` compress each streamed direction with one zstd context per call, from its third message, so later messages compress against earlier ones. The contexts of a channel's calls may hold up to `streamCompressionMemory` bytes (64 MiB by default; 0 turns stream compression off).
|
|
163
|
+
|
|
135
164
|
### WebSocket fallback
|
|
136
165
|
|
|
137
166
|
`connect()` races two transports. WebTransport starts first; QMux over WebSocket (subprotocol `qmux-02.trevrpc-1`, served by Go and Rust on the server's TCP port) starts after `fallbackDelayMs` (default 300) or immediately if WebTransport fails or the browser has no `WebTransport` API. The first session to be ready wins and the other is closed. After the WebSocket wins, reconnects use it directly until `reprobeIntervalMs` (default 5 minutes) has passed since WebTransport was lost, then race again.
|
|
@@ -158,17 +187,17 @@ trevrpc-interop-browser --browser chromium|firefox|safari \
|
|
|
158
187
|
--url https://127.0.0.1:4433/trevrpc \
|
|
159
188
|
--cert-hash <hex sha256 of the server certificate DER> \
|
|
160
189
|
[--transport webtransport|websocket|auto] [--ws-url wss://127.0.0.1:4433/trevrpc] \
|
|
161
|
-
[--expect-transport webtransport|websocket]
|
|
190
|
+
[--expect-transport webtransport|websocket] [--dictionary <file>]
|
|
162
191
|
```
|
|
163
192
|
|
|
164
|
-
It prints `ok <name>` or `fail <name>: <reason>` per scenario and exits 0 only if all scenarios passed. Chromium and Firefox are launched through Playwright from `PLAYWRIGHT_BROWSERS_PATH`, or from `TREVRPC_BROWSER_CHROMIUM` and `TREVRPC_BROWSER_FIREFOX` when those name an executable, and ignore certificate errors because WSS cannot pin a certificate hash. Safari is driven through `safaridriver` (`TREVRPC_BROWSER_SAFARI` overrides its path), which needs `sudo safaridriver --enable` once; Safari cannot ignore certificate errors, so the server certificate must be trusted by the system, as `interop/run.py` does. The CLI serves the page from a loopback origin; the page fetches its input from there and posts each result back, so where the browser has no WebTransport at all every scenario fails with a clear reason. `--write-page <dir>` writes that page to a directory instead, and `--page-url <url> --cert <pem> --key <pem>` opens it from a TrevRPC server that serves the directory through its HTTP fallback; the page then reports to the CLI over HTTPS, at the origin in its `report` query parameter. `--page-protocol h2|h3` has the browser load that page over HTTP/2 or HTTP/3 and fails every scenario unless it did (`h3` in Chromium only).
|
|
193
|
+
It prints `ok <name>` or `fail <name>: <reason>` per scenario and exits 0 only if all scenarios passed. `--dictionary` makes the channel compress Echo messages with that zstd dictionary, the one the interop servers hold. `trevrpc-interop-browser bench` runs the load benchmark in a browser instead (see [`bench/README.md`](../bench/README.md#browsers)). Chromium and Firefox are launched through Playwright from `PLAYWRIGHT_BROWSERS_PATH`, or from `TREVRPC_BROWSER_CHROMIUM` and `TREVRPC_BROWSER_FIREFOX` when those name an executable, and ignore certificate errors because WSS cannot pin a certificate hash. Safari is driven through `safaridriver` (`TREVRPC_BROWSER_SAFARI` overrides its path), which needs `sudo safaridriver --enable` once; Safari cannot ignore certificate errors, so the server certificate must be trusted by the system, as `interop/run.py` does. The CLI serves the page from a loopback origin; the page fetches its input from there and posts each result back, so where the browser has no WebTransport at all every scenario fails with a clear reason. `--write-page <dir>` writes that page to a directory instead, and `--page-url <url> --cert <pem> --key <pem>` opens it from a TrevRPC server that serves the directory through its HTTP fallback; the page then reports to the CLI over HTTPS, at the origin in its `report` query parameter. `--page-protocol h2|h3` has the browser load that page over HTTP/2 or HTTP/3 and fails every scenario unless it did (`h3` in Chromium only).
|
|
165
194
|
|
|
166
195
|
## Development
|
|
167
196
|
|
|
168
197
|
```sh
|
|
169
198
|
npm ci
|
|
170
199
|
npm run check # format check, lint, typecheck, tests
|
|
171
|
-
npm run build # generate the
|
|
200
|
+
npm run build # generate the interop clients and bundle the interop and benchmark pages into build/interop/
|
|
172
201
|
npm run interop -- --browser chromium --url ... --cert-hash ...
|
|
173
202
|
```
|
|
174
203
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trevrpc/trevrpc-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Browser WebTransport client and protobuf.js code generator for TrevRPC",
|
|
5
5
|
"homepage": "https://trev.zip/llc/TrevRPC",
|
|
6
6
|
"bugs": {
|
|
@@ -26,13 +26,17 @@
|
|
|
26
26
|
".": {
|
|
27
27
|
"types": "./src/index.d.ts",
|
|
28
28
|
"import": "./src/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./zstd": {
|
|
31
|
+
"types": "./src/zstd.d.ts",
|
|
32
|
+
"import": "./src/zstd.js"
|
|
29
33
|
}
|
|
30
34
|
},
|
|
31
35
|
"publishConfig": {
|
|
32
36
|
"access": "public"
|
|
33
37
|
},
|
|
34
38
|
"scripts": {
|
|
35
|
-
"build": "node interop-browser/build.js",
|
|
39
|
+
"build": "node zstd-wasm/embed.js && node interop-browser/build.js",
|
|
36
40
|
"format": "oxfmt --write",
|
|
37
41
|
"format:check": "oxfmt --check",
|
|
38
42
|
"lint": "oxlint --deny-warnings",
|
|
@@ -45,12 +49,16 @@
|
|
|
45
49
|
"protobufjs": "^8.8.1"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
52
|
+
"@bufbuild/protobuf": "^2.16.0",
|
|
53
|
+
"@bufbuild/protoc-gen-es": "^2.16.0",
|
|
54
|
+
"@connectrpc/connect": "^2.3.0",
|
|
55
|
+
"@connectrpc/connect-web": "^2.3.0",
|
|
48
56
|
"playwright": "1.64.1",
|
|
49
|
-
"rolldown": "^1.2.
|
|
57
|
+
"rolldown": "^1.2.16",
|
|
50
58
|
"typescript": "^7.0.2"
|
|
51
59
|
},
|
|
52
60
|
"engines": {
|
|
53
61
|
"node": ">=24"
|
|
54
62
|
},
|
|
55
|
-
"packageManager": "npm@12.2.
|
|
63
|
+
"packageManager": "npm@12.2.1"
|
|
56
64
|
}
|