@lesomnus/grpc-dgram 0.0.1 → 0.1.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 +43 -0
- package/README.md +61 -21
- package/dist/{conn-DOmx4nbt.mjs → conn-CmYPHTPR.mjs} +327 -23
- package/dist/conn-Dc57BC-h.d.mts +775 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +238 -48
- package/dist/{protocol-K4Zy8MuQ.mjs → protocol-CTxSUZQF.mjs} +60 -5
- package/dist/transport/connect.d.mts +1 -1
- package/dist/transport/node-udp.d.mts +5 -4
- package/dist/transport/node-udp.mjs +16 -10
- package/dist/transport/port.d.mts +5 -4
- package/dist/transport/port.mjs +15 -8
- package/dist/transport/webrtc.d.mts +5 -4
- package/dist/transport/webrtc.mjs +13 -7
- package/dist/transport/websocket.d.mts +5 -4
- package/dist/transport/websocket.mjs +14 -8
- package/dist/transport/webtransport.d.mts +53 -0
- package/dist/transport/webtransport.mjs +144 -0
- package/dist/wasm/worker.mjs +20 -5
- package/dist/wasm.d.mts +7 -3
- package/dist/wasm.mjs +21 -9
- package/dist/{wire-BR8KiyRg.mjs → wire-DqHx0oUs.mjs} +211 -39
- package/package.json +6 -3
- package/dist/conn-DTWG9vIx.d.mts +0 -331
- package/dist/server-xIr1mwqq.d.mts +0 -112
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
`@lesomnus/grpc-dgram` is the TypeScript port of gRPC-dgram. The wire
|
|
4
|
+
protocol it speaks is unversioned by design (`docs/PROTOCOL.md`, §10.6); a
|
|
5
|
+
package version is a cut of that text at one date, and a minor bump while the
|
|
6
|
+
major is 0 means the wire broke. Both ends of a channel must be deployed
|
|
7
|
+
together.
|
|
8
|
+
|
|
9
|
+
## 0.1.0 — 2026-09-12
|
|
10
|
+
|
|
11
|
+
**Wire, breaking against 0.0.1** — a peer on 0.0.1 cannot talk to one on
|
|
12
|
+
0.1.0:
|
|
13
|
+
|
|
14
|
+
- `Metadata` is an ordered list of entries, `repeated Entry { key, values }`,
|
|
15
|
+
emitted in ascending key order; a receiver merges a repeated key (§11).
|
|
16
|
+
- `Frame.conn_window` (field 18) advertises the connection window: the client
|
|
17
|
+
on every OPEN, the server on every H and T. The assumed-then-raised window
|
|
18
|
+
is gone, with everything that existed only for it (§4.2.1).
|
|
19
|
+
- The wire unit is spelled `Envelope`; the adapter seam is `EnvelopeSender`
|
|
20
|
+
(`sendFrames`) beside `FrameHandler`, and batching is a seam, not a
|
|
21
|
+
component (§4.1).
|
|
22
|
+
- Field 6 is unassigned and held for `ack`; a breaking generation of the wire
|
|
23
|
+
sets flag bit 64 on the first frame of every call (§10.6).
|
|
24
|
+
- A proto `string` field that is not valid UTF-8 makes the envelope
|
|
25
|
+
undecodable and dropped whole, as in the Go core (§5, §11).
|
|
26
|
+
|
|
27
|
+
**Behaviour**
|
|
28
|
+
|
|
29
|
+
- The per-peer connection window (`limits.maxPeerWindow`, §4.2.1): one ledger
|
|
30
|
+
per peer, `sid = 0` grants, overrun fails only the offending call.
|
|
31
|
+
- A call RESET before the `Conn` has locked to a server incarnation refunds
|
|
32
|
+
the connection credit its frames took (§4.2.1 *Sending*).
|
|
33
|
+
- Interceptors: `interceptors: [...]` on `ConnOptions` and `ServerOptions`,
|
|
34
|
+
unary and stream, chained in gRPC's order.
|
|
35
|
+
- `ProtocolStats` and `Counters` (§14), so a browser client reports gaps.
|
|
36
|
+
- WebTransport datagrams: `@lesomnus/grpc-dgram/transport/webtransport`.
|
|
37
|
+
- The wasm bridge: `dial({ entryPoint })` for a second server in one
|
|
38
|
+
instance, a per-dial `readyTimeoutMs`, and the timers a Go program leaves
|
|
39
|
+
pending are cancelled when it exits.
|
|
40
|
+
|
|
41
|
+
## 0.0.1 — 2026-08-08
|
|
42
|
+
|
|
43
|
+
First publish. Deprecated on 2026-09-12: it speaks a wire that 0.1.0 broke.
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @lesomnus/grpc-dgram
|
|
2
2
|
|
|
3
3
|
TypeScript port of [gRPC-dgram](../): gRPC-style RPC over unreliable datagram
|
|
4
|
-
channels, implementing the **dRPC wire protocol
|
|
4
|
+
channels, implementing the **dRPC wire protocol** (`../docs/PROTOCOL.md`).
|
|
5
5
|
Wire-compatible with the Go implementation — the §5 golden byte vectors are
|
|
6
6
|
shared between the two test suites — so a TS client interoperates with a Go
|
|
7
7
|
server and vice versa.
|
|
8
8
|
|
|
9
|
-
- **Zero runtime dependencies.** The three wire messages (`Frame`, `
|
|
9
|
+
- **Zero runtime dependencies.** The three wire messages (`Frame`, `Envelope`,
|
|
10
10
|
`Metadata`) are hand-encoded; user payloads go through pluggable
|
|
11
11
|
per-method marshallers (protobuf-es, JSON, anything that produces bytes).
|
|
12
12
|
A **protobuf-es binding** (`@lesomnus/grpc-dgram/transport/protobuf-es`, optional peer
|
|
@@ -17,22 +17,37 @@ server and vice versa.
|
|
|
17
17
|
mode machinery: seq windows and dedup, epoch/`peer_epoch` incarnation
|
|
18
18
|
isolation, control-frame retransmission, tombstones + aged watermark,
|
|
19
19
|
PING/probe liveness, and the §15 resource caps. On a reliable transport all
|
|
20
|
-
timers are off, sequencing is strict fail-loud (§10.6), and **
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
timers are off, sequencing is strict fail-loud (§10.6), and **flow
|
|
21
|
+
control** (§4.2.1) keeps one slow consumer from stalling the other calls on
|
|
22
|
+
the channel — the browser case where a blocked receive path would otherwise
|
|
23
|
+
wedge the whole event loop: a credit window per stream, and beside it a
|
|
24
|
+
connection window per peer (`limits.maxPeerWindow`, 1024 messages by
|
|
25
|
+
default, advertised on every OPEN by a `Conn` and on every H and T by a
|
|
26
|
+
`Server`) that bounds what one peer can pin across *all* of its calls, as
|
|
27
|
+
HTTP/2's does.
|
|
28
|
+
- **gRPC-fidelity surface.** Binary metadata (`-bin` keys carry arbitrary octets;
|
|
25
29
|
base64 at the TS API, raw bytes on the wire), rich status details on the
|
|
26
30
|
terminal frame, per-message compression (`gzip` via the platform's
|
|
27
31
|
`CompressionStream`, never expanding a payload), per-call recv/send size
|
|
28
32
|
caps, and the shape/modifier flag split (§7.1) — an unimplemented flag bit
|
|
29
33
|
fails the call instead of silently corrupting or gapping it.
|
|
30
34
|
- **WebSocket adapter** (`@lesomnus/grpc-dgram/transport/websocket`), the TS
|
|
31
|
-
twin of the Go `transport/gorilla` adapter: reliable mode, one
|
|
35
|
+
twin of the Go `transport/gorilla` adapter: reliable mode, one Envelope per
|
|
32
36
|
message, and the §4.5 teardown duty carried by `onclose`/`onerror` plus a
|
|
33
37
|
keepalive — browser-safe (WhatWG `WebSocket`, `binaryType='arraybuffer'`).
|
|
34
38
|
`dialWebSocket(url)` is a `Conn` in one line; `new WebSocketTransport(ws)` is
|
|
35
39
|
the path for a socket you brought yourself.
|
|
40
|
+
- **WebTransport datagram adapter** (`@lesomnus/grpc-dgram/transport/webtransport`),
|
|
41
|
+
the TS twin of the Go `transport/webtransport` adapter: unreliable mode over
|
|
42
|
+
the session's datagrams, one Envelope per datagram — the browser's datagram
|
|
43
|
+
channel with no signaling, a URL and nothing else. `dialWebTransport(url)` is
|
|
44
|
+
a `Conn` in one line (`https://` only; `serverCertificateHashes` pins a
|
|
45
|
+
development certificate), `new WebTransportDatagramTransport(wt)` the path
|
|
46
|
+
for a session you brought. The size ceiling follows the session's own
|
|
47
|
+
`datagrams.maxDatagramSize` (1200 B when it reports none), and the §4.5
|
|
48
|
+
teardown comes from the session — `closed` settling, `ready` rejecting — not
|
|
49
|
+
the read loop. Client only: Node has no `WebTransport`, so the server side is
|
|
50
|
+
the Go module.
|
|
36
51
|
- **Message-port adapter** (`@lesomnus/grpc-dgram/transport/port`), the TS twin
|
|
37
52
|
of the Go `transport/jsport` adapter: `PortTransport`/`PortGateway` over
|
|
38
53
|
anything with `postMessage` and a `message` event — a `MessagePort`, a
|
|
@@ -72,7 +87,8 @@ server and vice versa.
|
|
|
72
87
|
|
|
73
88
|
**Getting a connection.** Four cases, and the verb says which:
|
|
74
89
|
`new Conn(new XTransport(ch), opts)` when you already hold the channel;
|
|
75
|
-
`dialUdp` / `dialWebSocket` / `dialWorker` when the
|
|
90
|
+
`dialUdp` / `dialWebSocket` / `dialWebTransport` / `dialWorker` when the
|
|
91
|
+
library should make it —
|
|
76
92
|
each hands back a `Conn`, and takes one options bag, since `ConnOptions` and an
|
|
77
93
|
adapter's own options share no key; `open(app)` when there is no peer yet,
|
|
78
94
|
which returns a `Sock` whose `dial()` is the connection; and, on the serving
|
|
@@ -204,34 +220,58 @@ else maps to `UNKNOWN`).
|
|
|
204
220
|
| `drpc.ErrMessageTooLarge` | `MessageTooLargeError` (adapters throw it or set it as `cause`) |
|
|
205
221
|
| `NewPeerContext` / `NewReliableContext` | `FrameContext { peer, reliable, signal }` argument to `handle` |
|
|
206
222
|
| mutexes + atomics | none needed: state transitions are synchronous between `await` points |
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
(`
|
|
211
|
-
|
|
223
|
+
| `WithProtocolStats(obs)` (repeatable) | `protocolStats: obs` or `protocolStats: [obs, …]` on `ConnOptions` / `ServerOptions`; `Counters.observe` is the ready-made observer |
|
|
224
|
+
| `ProtocolEventKind` (`EventSkipped` … `EventFlowStall`, `EventFlowResume`, `EventPeerFlowStall`, `EventPeerFlowResume`) | `ProtocolEventKind`, the strings Go's `String()` fixes (`'skipped'` … `'flow-stall'`, `'flow-resume'`, `'peer-flow-stall'`, `'peer-flow-resume'`); `Counters` keeps `flowStall` / `flowResume` and `peerFlowStall` / `peerFlowResume` apart, as Go does |
|
|
225
|
+
| `WithLimits(Limits{MaxPeerWindow: n})` (the §4.2.1 connection window) | `limits: { maxPeerWindow: n }` on `ConnOptions` / `ServerOptions` — advertised as `Frame.connWindow` on every OPEN (`Conn`) and on every H and T (`Server`), the peer honouring the first it hears; floored at `W_CONN` (1024, exported beside `W_INIT`) because a client streams on that assumption until the server's first H or T; capped at 2³² − 1 — the wire's uint32 — so `Infinity` means that much, not off |
|
|
226
|
+
| `WithChainUnaryInterceptor(…)` / `ChainUnaryInterceptor(…)` and the stream twins | `unaryInterceptors: […]` / `streamInterceptors: […]` on `ConnOptions` / `ServerOptions` — same order (element 0 outermost), `(req, call, next)` shape; see `docs/typescript.md` |
|
|
227
|
+
|
|
228
|
+
Deliberately not ported (yet): the `stats.Handler` bridge (a grpc-go type; the
|
|
229
|
+
drpc half of the observability surface,
|
|
230
|
+
`ProtocolStats`/`Counters`, is ported — `protocolStats` on `ConnOptions` and
|
|
231
|
+
`ServerOptions`, `docs/observability.md`).
|
|
232
|
+
|
|
233
|
+
Batching is the application's policy in both languages and neither ships a
|
|
234
|
+
batcher (`docs/TODO.md` §1); both ship the seam to write one against. Here it
|
|
235
|
+
is `EnvelopeSender`, implemented by every exported adapter class as
|
|
236
|
+
`sendFrames`, with `handle` delegating to it so an override reaches the same
|
|
237
|
+
code the core would. It
|
|
238
|
+
mirrors that class's own `handle`: `sendFrames(frames)` where `handle` takes
|
|
239
|
+
a frame alone, `sendFrames(frames, ctx)` where `handle` takes a
|
|
240
|
+
`FrameContext` — on the gateways the ctx *is* the address, so a batch may
|
|
241
|
+
hold only frames whose contexts name one peer. Subclass the adapter and override `handle`: the core finds
|
|
242
|
+
`reliable()`, `attachConn()` and `close()` structurally, so the prototype
|
|
243
|
+
keeps all three. `src/transport/node-udp/batcher.test.ts` is the worked
|
|
244
|
+
example, per-peer gateway batching included.
|
|
212
245
|
|
|
213
246
|
Receive-path note for browsers: an `RTCDataChannel` cannot pause delivery, so
|
|
214
|
-
adapter-level buffering is unavoidable — but since
|
|
247
|
+
adapter-level buffering is unavoidable — but since per-stream flow control the *protocol* paces
|
|
215
248
|
the sender instead of the receiver (§4.2.1 flow control), so a slow consumer
|
|
216
249
|
no longer needs the receive path to block at all, and never stalls the other
|
|
217
250
|
calls sharing the channel.
|
|
218
251
|
|
|
219
252
|
## Tests
|
|
220
253
|
|
|
221
|
-
`pnpm test` —
|
|
222
|
-
byte-for-byte (including the
|
|
254
|
+
`pnpm test` — 575 tests mirroring the Go suites: the §5 golden wire vectors
|
|
255
|
+
byte-for-byte (including the 2026-07-25 vectors generated from the Go
|
|
223
256
|
implementation), e2e for all four RPC types, the §10 timeout system under
|
|
224
257
|
deterministic fake-timer loss (blackhole, lost terminals/acks/half-closes,
|
|
225
258
|
probes, liveness), the §6.5 restart walkthroughs, §15 caps and §4.2 drop
|
|
226
|
-
policies, §4.2.1 flow control (advertisement, parking, grants,
|
|
227
|
-
overrun)
|
|
228
|
-
|
|
259
|
+
policies, §4.2.1 flow control — per stream (advertisement, parking, grants,
|
|
260
|
+
`T_stall`, overrun) and per peer (the advertisement on every OPEN / H / T and
|
|
261
|
+
its once-only adoption, the `W_conn` assumption until it arrives, absent =
|
|
262
|
+
off, `sid = 0` grants, the starvation clause, credit returned for every
|
|
263
|
+
non-buffered frame, one stall budget across both windows, the evicted
|
|
264
|
+
sender's stash) — compression, size caps and binary metadata, each adapter
|
|
265
|
+
(WebRTC/WebSocket/WebTransport/Port/UDP/protobuf-es/Connect) next to its
|
|
266
|
+
source, `open()`
|
|
229
267
|
and the worker it ships against a fake `Go` (`src/wasm/`), and two
|
|
230
268
|
cross-language conformance tests driving a real Go `drpc.Server`: one over
|
|
231
269
|
loopback UDP, one a `js/wasm` build of the server loaded into the test process
|
|
232
270
|
and talked to across a `MessageChannel` — the second being where reliable mode
|
|
233
271
|
is genuinely reliable rather than asserted, so the §4.2.1 windows are exercised
|
|
234
|
-
between the two implementations
|
|
272
|
+
between the two implementations — per stream, and on `sid = 0` past `W_conn`
|
|
273
|
+
each way across three streams, which completes only because both ends grant
|
|
274
|
+
on the connection window.
|
|
235
275
|
|
|
236
276
|
**Layout.** Unit and per-adapter tests are **co-located** next to their source
|
|
237
277
|
(`src/wire.test.ts`, `src/transport/webrtc/index.test.ts`, …); cross-cutting
|