@arpabet/vrpc 0.2.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 ADDED
@@ -0,0 +1,75 @@
1
+ # @arpabet/vrpc
2
+
3
+ value-rpc protocol core for browsers, Node ≥ 22, and Bun. Zero runtime
4
+ dependencies. See the [repo README](../../README.md) for the full tour.
5
+
6
+ ```ts
7
+ import { createClient } from "@arpabet/vrpc";
8
+
9
+ const client = createClient({ url: "wss://example.com/rpc" });
10
+
11
+ await client.call("greet", ["world"]); // unary
12
+ for await (const v of client.getStream("count", [10])) {} // server stream
13
+ await client.putStream("upload", null, values); // client stream
14
+ const chat = client.chat("chat.echo"); // bidirectional
15
+
16
+ client.addFunction("notify", (args) => null); // server calls YOU
17
+ ```
18
+
19
+ ## Options (`createClient`)
20
+
21
+ | Option | Default | Meaning |
22
+ |---|---|---|
23
+ | `url` | — | `ws://` / `wss://` endpoint |
24
+ | `codec` | `"msgpack"` | `"msgpack"` works with every server; `"json"` needs a JSON-codec server (negotiated via the `vrpc.json` subprotocol) |
25
+ | `auth` | — | credential (or async factory) for the handshake `auth` field |
26
+ | `timeoutMs` | `5000` | default unary timeout, sent as the request SLA |
27
+ | `connectTimeoutMs` | `10000` | dial + handshake bound |
28
+ | `metadata` | — | `Metadata` or per-request factory (`traceparent`, …) |
29
+ | `reconnect` | enabled | backoff options or `false` |
30
+ | `resume` | `true` | hash-chain session resumption across reconnects |
31
+ | `maxPending` | `4096` | per-stream receive window (flow-control credit) |
32
+ | `dialect` | standard | wire field names; must match the server's `Dialect` |
33
+ | `webSocket` / `dialer` | global / WS | overrides for tests and custom transports |
34
+
35
+ ## Error model
36
+
37
+ Every failure is a `VrpcError` with a `code` mirroring `valuerpc.Code`:
38
+
39
+ ```ts
40
+ try {
41
+ await client.call("user.get", [42]);
42
+ } catch (err) {
43
+ if (err instanceof VrpcError && err.code === Code.Unavailable) retryLater();
44
+ }
45
+ ```
46
+
47
+ Timeouts map to `Code.DeadlineExceeded`, aborts to `Code.Canceled`,
48
+ connection loss to `Code.Unavailable` — the same branches a Go caller writes
49
+ with `valuerpc.CodeOf`.
50
+
51
+ ## Value model
52
+
53
+ `null | boolean | number | bigint | string | Uint8Array | Value[] | {…} |
54
+ VrpcDouble | VrpcDecimal | VrpcExt`
55
+
56
+ - Integer `number`s encode as value LONG; beyond ±2^53 pass a `bigint`
57
+ (exact int64 over msgpack). `double(x)` forces a DOUBLE encoding.
58
+ - `Uint8Array` is value RAW bytes.
59
+ - Maps are plain objects; keys encode sorted (canonical frames).
60
+ - `VrpcDecimal` (`decimal("1.045")`) and `VrpcExt` round-trip over msgpack.
61
+
62
+ ## Streams & flow control
63
+
64
+ `getStream` returns an async iterator; **credit is granted to the server as
65
+ you consume**, so a slow consumer backpressures the producer losslessly.
66
+ `break`/`return` sends a `CancelRequest`. `putStream`/`chat.send` honor the
67
+ credit the server grants — a misbehaving peer is cut off with
68
+ `Code.ResourceExhausted`, exactly like the Go client.
69
+
70
+ ## Reverse calls
71
+
72
+ The server can invoke functions and open streams on this client
73
+ (`addFunction`, `addOutgoingStream`, `addIncomingStream`, `addChat`) — the
74
+ same registrar surface `valueserver.Server` has. Handlers receive
75
+ `(args, ctx)` where `ctx.signal` aborts on cancellation/disconnect.
@@ -0,0 +1,561 @@
1
+ /**
2
+ * The TypeScript projection of the `go.arpabet.com/value` model.
3
+ *
4
+ * | value kind | TypeScript |
5
+ * |-----------------|-------------------------------------|
6
+ * | NULL | null (undefined encodes as null) |
7
+ * | BOOL | boolean |
8
+ * | NUMBER/LONG | number (safe integers) or bigint |
9
+ * | NUMBER/DOUBLE | number (or VrpcDouble to force) |
10
+ * | NUMBER/BIGINT | bigint beyond int64 |
11
+ * | NUMBER/DECIMAL | VrpcDecimal |
12
+ * | STRING/UTF8 | string |
13
+ * | STRING/RAW | Uint8Array |
14
+ * | LIST | Array |
15
+ * | MAP | plain object (string keys) |
16
+ * | UNKNOWN (ext) | VrpcExt |
17
+ */
18
+ type Value = null | boolean | number | bigint | string | Uint8Array | VrpcDouble | VrpcDecimal | VrpcExt | Value[] | {
19
+ [key: string]: Value;
20
+ };
21
+ /** A string-keyed map value (the envelope shape). */
22
+ type ValueMap = {
23
+ [key: string]: Value;
24
+ };
25
+ /**
26
+ * Forces a JS number to encode as a value DOUBLE (float64) even when it is
27
+ * integer-valued. Bare integer-valued numbers encode as LONG; use
28
+ * `double(42)` when the peer's handler verifies a DOUBLE argument.
29
+ */
30
+ declare class VrpcDouble {
31
+ readonly value: number;
32
+ constructor(value: number);
33
+ valueOf(): number;
34
+ toString(): string;
35
+ }
36
+ /** Convenience constructor for {@link VrpcDouble}. */
37
+ declare function double(value: number): VrpcDouble;
38
+ /**
39
+ * An arbitrary-precision decimal: `coefficient * 10^exponent`, mirroring the
40
+ * shopspring/decimal representation used by value's NUMBER/DECIMAL kind.
41
+ * Round-trips through the msgpack codec (ext tag 2). The JSON codec cannot
42
+ * represent it (matching the Go-side asymmetry) and rejects it on encode.
43
+ */
44
+ declare class VrpcDecimal {
45
+ readonly coefficient: bigint;
46
+ readonly exponent: number;
47
+ constructor(coefficient: bigint, exponent: number);
48
+ /** Parses "123", "-1.045", "1.2e-5" style decimal strings. */
49
+ static fromString(s: string): VrpcDecimal;
50
+ toString(): string;
51
+ toNumber(): number;
52
+ equals(other: VrpcDecimal): boolean;
53
+ }
54
+ /** Convenience constructor for {@link VrpcDecimal} from a decimal string. */
55
+ declare function decimal(s: string): VrpcDecimal;
56
+ /**
57
+ * An opaque msgpack extension value (value kind UNKNOWN): the ext tag plus its
58
+ * payload bytes. Preserved through both codecs so unrecognized extensions
59
+ * round-trip unchanged. The JSON form is the value-library convention
60
+ * `"data:application/x-msgpack-ext;base64,<tag byte + data, base64>"`.
61
+ */
62
+ declare class VrpcExt {
63
+ readonly tag: number;
64
+ readonly data: Uint8Array;
65
+ constructor(tag: number, data: Uint8Array);
66
+ equals(other: VrpcExt): boolean;
67
+ }
68
+
69
+ /**
70
+ * MessagePack codec producing the canonical byte form of go.arpabet.com/value
71
+ * (see its CANONICAL.md): minimal integer family, doubles always float64,
72
+ * str/bin distinct, map keys sorted by UTF-8 byte order, BIGINT as ext tag 1
73
+ * (big.Int gob framing), DECIMAL as ext tag 2 (shopspring framing). Equal
74
+ * values therefore produce byte-identical frames on both sides of the wire.
75
+ */
76
+
77
+ /** Decode limits mirroring value's limits.go defaults. */
78
+ interface DecodeLimits {
79
+ maxDepth: number;
80
+ maxCollectionLen: number;
81
+ maxByteLen: number;
82
+ }
83
+ declare const DEFAULT_LIMITS: DecodeLimits;
84
+ /** Encodes a single value in value's canonical MessagePack form. */
85
+ declare function msgpackEncode(v: Value): Uint8Array;
86
+ /** Decodes one value; throws on malformed, truncated, or oversized input. */
87
+ declare function msgpackDecode(data: Uint8Array, limits?: DecodeLimits): Value;
88
+
89
+ type CodecName = "msgpack" | "json";
90
+ /**
91
+ * The wire codec seam (WEB.md §3.3): one envelope per WebSocket frame,
92
+ * msgpack in binary frames, JSON in text frames.
93
+ */
94
+ interface WireCodec {
95
+ readonly name: CodecName;
96
+ /**
97
+ * Subprotocol offered on dial. msgpack offers none: "no subprotocol" IS the
98
+ * msgpack negotiation, and it keeps compatibility with servers that predate
99
+ * codec negotiation. JSON offers "vrpc.json" and requires the server to echo
100
+ * it (a server that does not support JSON fails the connect with a clear
101
+ * error instead of choking on text frames).
102
+ */
103
+ readonly subprotocols: string[];
104
+ readonly textFrames: boolean;
105
+ encode(msg: ValueMap): Uint8Array | string;
106
+ decode(data: Uint8Array | string): ValueMap;
107
+ }
108
+ declare function msgpackCodec(limits?: DecodeLimits): WireCodec;
109
+ declare function jsonCodec(limits?: DecodeLimits): WireCodec;
110
+ declare function codecByName(name: CodecName, limits?: DecodeLimits): WireCodec;
111
+
112
+ /**
113
+ * Machine-readable RPC status, mirroring valuerpc.Code (a useful subset of
114
+ * gRPC status codes). Carried on the wire in ErrorResponse frames.
115
+ */
116
+ declare enum Code {
117
+ OK = 0,
118
+ Unknown = 1,
119
+ Canceled = 2,
120
+ InvalidArgument = 3,
121
+ DeadlineExceeded = 4,
122
+ NotFound = 5,
123
+ ResourceExhausted = 6,
124
+ Unavailable = 7,
125
+ Unauthenticated = 8,
126
+ Internal = 9
127
+ }
128
+ declare function codeName(code: Code): string;
129
+ /**
130
+ * A coded RPC error, mirroring *valuerpc.Error. Server-raised failures arrive
131
+ * with the server's code; client-local failures (timeout, connection loss,
132
+ * cancellation) use the matching local code so callers can branch on
133
+ * `err.code` the way Go callers branch on valuerpc.CodeOf(err).
134
+ */
135
+ declare class VrpcError extends Error {
136
+ readonly code: Code;
137
+ readonly name = "VrpcError";
138
+ constructor(code: Code, message: string, options?: {
139
+ cause?: unknown;
140
+ });
141
+ /** The message without the "vrpc <Code>: " prefix. */
142
+ get detail(): string;
143
+ }
144
+ /** The Code carried by err: VrpcError's code, or Unknown for foreign errors. */
145
+ declare function codeOf(err: unknown): Code;
146
+
147
+ /** vRPC message types (wire numbering; 12/13 reserved). */
148
+ declare enum MessageType {
149
+ HandshakeRequest = 0,
150
+ HandshakeResponse = 1,
151
+ FunctionRequest = 2,
152
+ FunctionResponse = 3,
153
+ GetStreamRequest = 4,
154
+ PutStreamRequest = 5,
155
+ ChatRequest = 6,
156
+ ErrorResponse = 7,
157
+ StreamReady = 8,
158
+ StreamValue = 9,
159
+ StreamEnd = 10,
160
+ CancelRequest = 11,
161
+ StreamCredit = 14
162
+ }
163
+ /**
164
+ * Dialect names the wire-level fields and markers of the vRPC envelope,
165
+ * mirroring valuerpc.Dialect. Both peers MUST share the same dialect.
166
+ * Override fields only if your Go deployment installed a custom Dialect.
167
+ */
168
+ interface Dialect {
169
+ magic: string;
170
+ version: number;
171
+ handshakeRequestId: number;
172
+ messageTypeField: string;
173
+ magicField: string;
174
+ versionField: string;
175
+ requestIdField: string;
176
+ timeoutField: string;
177
+ clientIdField: string;
178
+ sessionTokenField: string;
179
+ authField: string;
180
+ functionNameField: string;
181
+ argumentsField: string;
182
+ resultField: string;
183
+ errorField: string;
184
+ codeField: string;
185
+ creditField: string;
186
+ metadataField: string;
187
+ valueField: string;
188
+ }
189
+ /** The standard vRPC dialect (the default wire format). */
190
+ declare function newDialect(): Dialect;
191
+ type Metadata = Record<string, string>;
192
+ /** Envelope builders and field accessors bound to one dialect. */
193
+ declare class Protocol {
194
+ readonly d: Dialect;
195
+ constructor(d: Dialect);
196
+ handshakeRequest(clientId: number, token: string, auth: Value | undefined): ValueMap;
197
+ request(mt: MessageType, requestId: number, name: string, args: Value | undefined, timeoutMs: number, metadata: Metadata | undefined): ValueMap;
198
+ functionResult(requestId: number, result: Value): ValueMap;
199
+ errorResponse(requestId: number, code: Code, message: string): ValueMap;
200
+ streamReady(requestId: number): ValueMap;
201
+ streamValue(requestId: number, value: Value): ValueMap;
202
+ streamEnd(requestId: number, value?: Value): ValueMap;
203
+ streamCredit(requestId: number, credit: number): ValueMap;
204
+ cancelRequest(requestId: number): ValueMap;
205
+ messageType(msg: ValueMap): MessageType | undefined;
206
+ requestId(msg: ValueMap): number | undefined;
207
+ functionName(msg: ValueMap): string | undefined;
208
+ args(msg: ValueMap): Value;
209
+ result(msg: ValueMap): Value;
210
+ streamVal(msg: ValueMap): Value;
211
+ credit(msg: ValueMap): number | undefined;
212
+ errorOf(msg: ValueMap): {
213
+ code: Code;
214
+ message: string;
215
+ };
216
+ metadata(msg: ValueMap): Metadata | undefined;
217
+ }
218
+
219
+ /**
220
+ * One established message connection. Deliberately tiny (WEB.md §7): a future
221
+ * WebTransport implementation slots in without touching the protocol layer.
222
+ */
223
+ interface Transport {
224
+ send(data: Uint8Array | string): void;
225
+ close(): void;
226
+ onMessage: ((data: Uint8Array | string) => void) | null;
227
+ /** Fired exactly once, for both clean and failed closes. */
228
+ onClose: ((err?: Error) => void) | null;
229
+ }
230
+ interface DialOptions {
231
+ url: string;
232
+ subprotocols: string[];
233
+ timeoutMs: number;
234
+ /** Override the WebSocket constructor (tests, custom agents). */
235
+ webSocket?: typeof WebSocket;
236
+ }
237
+ type Dialer = (opts: DialOptions) => Promise<Transport>;
238
+ /** Dials a WebSocket and resolves once the connection is open. */
239
+ declare const webSocketDialer: Dialer;
240
+
241
+ type Status = "idle" | "connecting" | "open" | "resuming" | "closed";
242
+ interface ReconnectOptions {
243
+ /** Re-establish dropped connections automatically. Default true. */
244
+ enabled?: boolean;
245
+ /** First backoff delay. Default 300 ms. */
246
+ initialDelayMs?: number;
247
+ /** Backoff cap. Default 10 000 ms. */
248
+ maxDelayMs?: number;
249
+ /** Give up after this many consecutive failed attempts. Default Infinity. */
250
+ maxAttempts?: number;
251
+ /** Equal jitter (half fixed, half random), like the Go client. Default true. */
252
+ jitter?: boolean;
253
+ }
254
+ interface ClientOptions {
255
+ /** WebSocket endpoint, e.g. "wss://example.com/rpc". */
256
+ url: string;
257
+ /**
258
+ * Wire codec. "msgpack" (default) speaks to every value-rpc server —
259
+ * binary frames, no subprotocol. "json" offers the "vrpc.json" subprotocol
260
+ * and requires a server with JSON codec support (WEB.md phase 1).
261
+ */
262
+ codec?: CodecName | WireCodec;
263
+ /**
264
+ * Credential for the handshake `auth` field, validated by the server's
265
+ * Authenticator. A function is re-evaluated on every (re)connect, so
266
+ * refreshed tokens are picked up automatically.
267
+ */
268
+ auth?: Value | (() => Value | Promise<Value>);
269
+ /** Default unary timeout / request SLA in ms. Default 5000. */
270
+ timeoutMs?: number;
271
+ /** Dial + handshake timeout in ms. Default 10 000. */
272
+ connectTimeoutMs?: number;
273
+ /** Metadata attached to every request (static or per-request factory). */
274
+ metadata?: Metadata | ((name: string) => Metadata | undefined);
275
+ reconnect?: boolean | ReconnectOptions;
276
+ /**
277
+ * Hash-chain session resumption (default true): reconnects present a
278
+ * one-time token proving continuity, so the server reattaches the session.
279
+ * When disabled, every connection is a fresh session with a fresh clientId.
280
+ */
281
+ resume?: boolean;
282
+ /** Resumption chain length = max reconnects per session. Default 1024. */
283
+ chainLength?: number;
284
+ /** Per-stream receive window (flow-control credit). Default 4096. */
285
+ maxPending?: number;
286
+ /** Wire dialect overrides; must match the server's Dialect. */
287
+ dialect?: Partial<Dialect>;
288
+ /** WebSocket constructor override (tests, custom agents). */
289
+ webSocket?: typeof WebSocket;
290
+ /** Transport override (in-memory tests, future WebTransport). */
291
+ dialer?: Dialer;
292
+ }
293
+ interface CallOptions {
294
+ /** Per-call timeout in ms; also sent to the server as the request SLA. */
295
+ timeoutMs?: number;
296
+ /** Abort maps to a vRPC CancelRequest toward the server. */
297
+ signal?: AbortSignal;
298
+ metadata?: Metadata;
299
+ }
300
+ /** A server->client stream (getStream): pull-based, credit-backed. */
301
+ interface VrpcStream extends AsyncIterableIterator<Value> {
302
+ readonly requestId: number;
303
+ /** Resolves when the server acks the stream (StreamReady). */
304
+ readonly ready: Promise<void>;
305
+ /** Cancels the stream (sends CancelRequest); the iterator ends. */
306
+ cancel(): void;
307
+ }
308
+ /** A bidirectional stream (chat). */
309
+ interface VrpcChat extends AsyncIterable<Value> {
310
+ readonly requestId: number;
311
+ readonly ready: Promise<void>;
312
+ readonly incoming: AsyncIterableIterator<Value>;
313
+ /** Sends one value; awaits flow-control credit from the peer. */
314
+ send(v: Value): Promise<void>;
315
+ /** Half-closes our sending side (StreamEnd); receiving continues. */
316
+ end(): void;
317
+ /** Tears the whole chat down (CancelRequest). */
318
+ cancel(): void;
319
+ }
320
+ interface HandlerContext {
321
+ readonly requestId: number;
322
+ readonly metadata: Metadata | undefined;
323
+ /** Aborted when the peer cancels the request or the connection drops. */
324
+ readonly signal: AbortSignal;
325
+ }
326
+ type UnaryHandler = (args: Value, ctx: HandlerContext) => Value | Promise<Value>;
327
+ type OutgoingStreamHandler = (args: Value, ctx: HandlerContext) => Iterable<Value> | AsyncIterable<Value> | Promise<Iterable<Value> | AsyncIterable<Value>>;
328
+ type IncomingStreamHandler = (args: Value, incoming: AsyncIterableIterator<Value>, ctx: HandlerContext) => void | Promise<void>;
329
+ type ChatHandler = (args: Value, incoming: AsyncIterableIterator<Value>, ctx: HandlerContext) => Iterable<Value> | AsyncIterable<Value> | Promise<Iterable<Value> | AsyncIterable<Value>>;
330
+ interface ClientEvents {
331
+ status: (status: Status) => void;
332
+ /** Fired per successful handshake with the server's handshake response. */
333
+ open: (handshake: ValueMap) => void;
334
+ close: () => void;
335
+ /** Non-fatal protocol anomalies (unroutable or malformed frames). */
336
+ error: (err: Error) => void;
337
+ }
338
+ /**
339
+ * A value-rpc client over WebSocket. Symmetric peer: it calls the server
340
+ * (call / getStream / putStream / chat) and serves the server's reverse
341
+ * calls (addFunction / addOutgoingStream / addIncomingStream / addChat).
342
+ */
343
+ declare class VrpcClient {
344
+ readonly protocol: Protocol;
345
+ private readonly codec;
346
+ private readonly opts;
347
+ private readonly reconnect;
348
+ private readonly timeoutMs;
349
+ private readonly connectTimeoutMs;
350
+ private readonly maxPending;
351
+ private readonly dialer;
352
+ private _status;
353
+ private _clientId;
354
+ private transport;
355
+ private chain;
356
+ private established;
357
+ private nextRequestId;
358
+ private reconnects;
359
+ private pending;
360
+ private serving;
361
+ private handlers;
362
+ private listeners;
363
+ private connectPromise;
364
+ private resumeAbort;
365
+ private openWaiters;
366
+ private handshakeWaiter;
367
+ private handshakeTransport;
368
+ private hintsInstalled;
369
+ constructor(options: ClientOptions);
370
+ get status(): Status;
371
+ get connected(): boolean;
372
+ get clientId(): number;
373
+ get codecName(): CodecName;
374
+ stats(): {
375
+ requests: number;
376
+ reconnects: number;
377
+ pending: number;
378
+ serving: number;
379
+ };
380
+ on<E extends keyof ClientEvents>(event: E, fn: ClientEvents[E]): () => void;
381
+ private emit;
382
+ private setStatus;
383
+ /** Connects (idempotent). Calls also auto-connect on first use. */
384
+ connect(): Promise<void>;
385
+ private handshakeToken;
386
+ private doConnect;
387
+ /** Waits until the client is open, connecting if idle. */
388
+ private ensureOpen;
389
+ private onTransportClose;
390
+ private resumeLoop;
391
+ /** Browser wake hints: retry immediately when connectivity likely returned. */
392
+ private installBrowserHints;
393
+ private failAllInFlight;
394
+ /** Closes the client permanently. */
395
+ close(): void;
396
+ private sendFrame;
397
+ private trySendFrame;
398
+ cancelRequest(requestId: number): void;
399
+ private buildMetadata;
400
+ /** Calls a unary function on the server. */
401
+ call(name: string, args?: Value, opts?: CallOptions): Promise<Value>;
402
+ /**
403
+ * Opens a server->client stream. Consume with `for await`; breaking out of
404
+ * the loop cancels the stream. Credit is granted as values are consumed, so
405
+ * a slow consumer applies real backpressure to the server.
406
+ */
407
+ getStream(name: string, args?: Value, opts?: CallOptions): VrpcStream;
408
+ private openConsumingStream;
409
+ private establishStream;
410
+ private retireGetSide;
411
+ /**
412
+ * Opens a client->server stream and sends every value from source, honoring
413
+ * the server's flow-control credit. Resolves once the stream ended cleanly.
414
+ */
415
+ putStream(name: string, args: Value | undefined, source: Iterable<Value> | AsyncIterable<Value>, opts?: CallOptions): Promise<void>;
416
+ /** Opens a bidirectional stream (chat). */
417
+ chat(name: string, args?: Value, opts?: CallOptions): VrpcChat;
418
+ /** Registers a unary function the server may call on this client. */
419
+ addFunction(name: string, fn: UnaryHandler): void;
420
+ /** Registers a stream the server can consume from this client (GetStream). */
421
+ addOutgoingStream(name: string, fn: OutgoingStreamHandler): void;
422
+ /** Registers a stream the server can send to this client (PutStream). */
423
+ addIncomingStream(name: string, fn: IncomingStreamHandler): void;
424
+ /** Registers a bidirectional stream the server can open (Chat). */
425
+ addChat(name: string, fn: ChatHandler): void;
426
+ removeHandler(name: string): void;
427
+ private onFrame;
428
+ private processResponse;
429
+ private handlerCtx;
430
+ private serveInboundCall;
431
+ private serveInboundStream;
432
+ private serveRunning;
433
+ }
434
+ /** Creates a {@link VrpcClient}. The client connects lazily on first call. */
435
+ declare function createClient(options: ClientOptions): VrpcClient;
436
+
437
+ /**
438
+ * JSON codec per WEB.md §3.1 — the value library's existing JSON conventions:
439
+ *
440
+ * - raw bytes <-> "base64,<standard base64, no padding>"
441
+ * - msgpack ext <-> "data:application/x-msgpack-ext;base64,<tag+data>"
442
+ * - NaN / ±Inf -> null (decode never produces them)
443
+ * - map keys -> sorted by UTF-8 byte order (canonical, deterministic)
444
+ * - integers -> plain JSON numbers; throws beyond ±2^53 on encode
445
+ * - bigint -> encoded as a number when within ±2^53, else rejected
446
+ * (the wire cannot carry it exactly; use msgpack)
447
+ * - VrpcDecimal -> rejected on encode (does not round-trip through JSON)
448
+ *
449
+ * Known hazard inherited from the convention: a *string* that genuinely starts
450
+ * with "base64," or "data:application/x-msgpack-ext;" is indistinguishable
451
+ * from encoded bytes/ext and will decode as such.
452
+ */
453
+
454
+ /** Serializes a value to canonical JSON text (sorted keys). */
455
+ declare function jsonEncode(v: Value): string;
456
+ /** Parses JSON text into a value, applying the string-prefix conventions. */
457
+ declare function jsonDecode(text: string, limits?: DecodeLimits): Value;
458
+
459
+ /**
460
+ * Client-held reverse hash chain (S/KEY style) for replay-resistant session
461
+ * resumption, mirroring valuerpc.HashChain:
462
+ *
463
+ * seed = h[0], h[i] = SHA-256(h[i-1]), anchor = h[N]
464
+ *
465
+ * The anchor is presented (hex) in the first handshake's `tok` field; each
466
+ * reconnect reveals the next pre-image in reverse order. The chain always
467
+ * advances — even when a handshake is lost — and the server self-heals by
468
+ * hashing forward (its resync window), so a link is never revealed twice.
469
+ *
470
+ * Browser-sized default: 1024 links (~1024 awaited SHA-256 digests at build
471
+ * time, milliseconds) bounds reconnects per session; an exhausted chain makes
472
+ * the client start a fresh session (new clientId + new chain).
473
+ */
474
+ declare class HashChain {
475
+ private readonly links;
476
+ private next;
477
+ private constructor();
478
+ static readonly DEFAULT_LENGTH = 1024;
479
+ static create(n?: number): Promise<HashChain>;
480
+ /** The public commitment h[N], hex-encoded. Resending it consumes nothing. */
481
+ anchor(): string;
482
+ /**
483
+ * Reveals the next one-time resumption token and advances. Returns "" when
484
+ * the chain is exhausted and a fresh session is required.
485
+ */
486
+ nextToken(): string;
487
+ remaining(): number;
488
+ }
489
+
490
+ /**
491
+ * Typed calls without codegen — the TS analogue of valuerpc.Codec[T] /
492
+ * CallUnary. Describe your API as an interface and get compile-time checked
493
+ * method names, argument tuples, and result types:
494
+ *
495
+ * ```ts
496
+ * interface Api {
497
+ * "user.get": { args: [id: number]; result: User };
498
+ * "events.tail": { args: [topic: string]; stream: OrderEvent };
499
+ * "logs.upload": { args: []; put: string };
500
+ * "support.chat": { args: [ticket: number]; in: Message; out: string };
501
+ * }
502
+ * const api = typedClient<Api>(client);
503
+ * const user = await api.call("user.get", [42]);
504
+ * for await (const ev of api.getStream("events.tail", ["orders"])) { ... }
505
+ * ```
506
+ */
507
+
508
+ interface UnaryShape {
509
+ args: Value[];
510
+ result: unknown;
511
+ }
512
+ interface GetStreamShape {
513
+ args: Value[];
514
+ stream: unknown;
515
+ }
516
+ interface PutStreamShape {
517
+ args: Value[];
518
+ put: unknown;
519
+ }
520
+ interface ChatShape {
521
+ args: Value[];
522
+ in: unknown;
523
+ out: unknown;
524
+ }
525
+ type ApiShape = Record<string, UnaryShape | GetStreamShape | PutStreamShape | ChatShape>;
526
+ type UnaryNames<A extends ApiShape> = {
527
+ [K in keyof A]: A[K] extends UnaryShape ? K : never;
528
+ }[keyof A];
529
+ type GetNames<A extends ApiShape> = {
530
+ [K in keyof A]: A[K] extends GetStreamShape ? K : never;
531
+ }[keyof A];
532
+ type PutNames<A extends ApiShape> = {
533
+ [K in keyof A]: A[K] extends PutStreamShape ? K : never;
534
+ }[keyof A];
535
+ type ChatNames<A extends ApiShape> = {
536
+ [K in keyof A]: A[K] extends ChatShape ? K : never;
537
+ }[keyof A];
538
+ interface TypedStream<T> extends AsyncIterableIterator<T> {
539
+ readonly requestId: number;
540
+ readonly ready: Promise<void>;
541
+ cancel(): void;
542
+ }
543
+ interface TypedChat<TIn, TOut> extends AsyncIterable<TIn> {
544
+ readonly requestId: number;
545
+ readonly ready: Promise<void>;
546
+ readonly incoming: AsyncIterableIterator<TIn>;
547
+ send(v: TOut): Promise<void>;
548
+ end(): void;
549
+ cancel(): void;
550
+ }
551
+ interface TypedClient<A extends ApiShape> {
552
+ call<K extends UnaryNames<A>>(name: K, args: A[K] extends UnaryShape ? A[K]["args"] : never, opts?: CallOptions): Promise<A[K] extends UnaryShape ? A[K]["result"] : never>;
553
+ getStream<K extends GetNames<A>>(name: K, args: A[K] extends GetStreamShape ? A[K]["args"] : never, opts?: CallOptions): TypedStream<A[K] extends GetStreamShape ? A[K]["stream"] : never>;
554
+ putStream<K extends PutNames<A>>(name: K, args: A[K] extends PutStreamShape ? A[K]["args"] : never, source: A[K] extends PutStreamShape ? Iterable<A[K]["put"]> | AsyncIterable<A[K]["put"]> : never, opts?: CallOptions): Promise<void>;
555
+ chat<K extends ChatNames<A>>(name: K, args: A[K] extends ChatShape ? A[K]["args"] : never, opts?: CallOptions): TypedChat<A[K] extends ChatShape ? A[K]["in"] : never, A[K] extends ChatShape ? A[K]["out"] : never>;
556
+ readonly client: VrpcClient;
557
+ }
558
+ /** Wraps a client with a compile-time-typed API surface (zero runtime cost). */
559
+ declare function typedClient<A extends ApiShape>(client: VrpcClient): TypedClient<A>;
560
+
561
+ export { type ApiShape, type CallOptions, type ChatHandler, type ClientEvents, type ClientOptions, Code, type CodecName, DEFAULT_LIMITS, type DecodeLimits, type DialOptions, type Dialect, type Dialer, type HandlerContext, HashChain, type IncomingStreamHandler, MessageType, type Metadata, type OutgoingStreamHandler, Protocol, type ReconnectOptions, type Status, type Transport, type TypedChat, type TypedClient, type TypedStream, type UnaryHandler, type Value, type ValueMap, type VrpcChat, VrpcClient, VrpcDecimal, VrpcDouble, VrpcError, VrpcExt, type VrpcStream, type WireCodec, codeName, codeOf, codecByName, createClient, decimal, double, jsonCodec, jsonDecode, jsonEncode, msgpackCodec, msgpackDecode, msgpackEncode, newDialect, typedClient, webSocketDialer };