@synapse-sdk/client 0.1.0 → 0.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.
@@ -0,0 +1,189 @@
1
+ import * as Moq from "@moq/net";
2
+ import { ClockSync, SynapseError, type CallId, type ClockInfo, type ErrorBody, type Header, type Hello, type Identity, type ProjectSnapshot, type Role, type ServicesSnapshot, type TimeOffsets, type TopicsSnapshot } from "@synapse-sdk/core";
3
+ import { type MoqLogScope } from "./moq_log.ts";
4
+ import { Cell, deferred } from "./util.ts";
5
+ import type { Notice } from "./types.ts";
6
+ /** Default datagram budget (WP §6.2): Chrome reports `maxDatagramSize` 1024; native quinn hops allow about 1150. */
7
+ export declare function defaultMaxDatagram(): number;
8
+ export declare function isNodeLike(): boolean;
9
+ /** A `stream` reader never abandons a finished group: an effectively infinite moq latency budget (`u32::MAX` ms, as the Rust client). */
10
+ export declare const STREAM_READER_MAX_AGE_MS = 4294967295;
11
+ export declare const streamSubscription: (priority: number) => Moq.Track.Subscription;
12
+ /** The latest-value subscription (snapshot tracks, `desc`, `card`): newest group only. */
13
+ export declare const latestSubscription: (priority: number, maxAgeMs?: number) => Moq.Track.Subscription;
14
+ /**
15
+ * The age budget of a call track's group (Rust `CALL_GROUP_MAX_AGE`, 60 s): the retention every call
16
+ * track we write (`req`, `out`) declares, the budget its readers (`res`, `calls`) subscribe with, and how
17
+ * long a reader waits for the group due while a later group is held.
18
+ */
19
+ export declare const CALL_GROUP_MAX_AGE_MS = 60000;
20
+ export declare const callTrackInfo: (priority: number) => Partial<Moq.Track.Info>;
21
+ /** A call track reader's subscription (`res`, `calls`): the call track budget. */
22
+ export declare const callTrackSubscription: (priority: number) => Moq.Track.Subscription;
23
+ /**
24
+ * Retention of a one-frame track (`card`, `desc`): the frame lives as long as the track. The Rust moq
25
+ * keeps a track's latest group for the track's life; the JS one prunes it after its default 5 s, so a
26
+ * subscriber joining later would get nothing.
27
+ */
28
+ export declare const RETAINED_FRAME_MAX_AGE_MS = 4294967295;
29
+ /** A call track starts a new group after this many frames (moq's cap on a group). */
30
+ export declare const CALL_GROUP_FRAMES = 8192;
31
+ /** A `stream` topic subscriber's age budget (`STREAM_SUBSCRIBE_MAX_AGE`, 3 × the group span): a reader further behind skips whole groups. */
32
+ export declare const streamTopicSubscription: (priority: number) => Moq.Track.Subscription;
33
+ /**
34
+ * A `stream` topic's writer (WP §6, the Rust `TrackWriter`): frames go into one MoQ group at a time, in
35
+ * order; the writer finishes the open group and starts the next when the open one is older than
36
+ * `STREAM_GROUP_SPAN_MS`, or when the next frame would pass moq's caps. A late joiner reads from the
37
+ * start of the group the relay holds, so it is at most one span behind.
38
+ */
39
+ export declare class StreamWriter {
40
+ #private;
41
+ readonly track: Moq.Track.Producer;
42
+ constructor(track: Moq.Track.Producer);
43
+ write(header: Header, body: Uint8Array): void;
44
+ close(): void;
45
+ }
46
+ export declare function moqTimestamp(tUs: number | bigint): Moq.Time.Timestamp;
47
+ /** The error for a frame refused because it is over moq's 32 MiB limit (Rust `frame_too_large`): `invalid_input`, naming the limit. */
48
+ export declare function frameTooLarge(what: string, len: number): SynapseError;
49
+ /**
50
+ * A call track writer (`req`, `out`): frames into one group after another. The open group is finished
51
+ * and the next one started when the next frame would pass `CALL_GROUP_FRAMES` frames or moq's 32 MiB of
52
+ * bytes in the group. A frame over 32 MiB on its own is refused (`frameTooLarge`) before anything is written.
53
+ */
54
+ export declare class CallWriter {
55
+ #private;
56
+ readonly track: Moq.Track.Producer;
57
+ constructor(track: Moq.Track.Producer);
58
+ static create(bc: Moq.Broadcast.Producer, name: string): CallWriter;
59
+ write(header: Header, body: Uint8Array): void;
60
+ close(): void;
61
+ }
62
+ /** One latest-value frame (`desc`, `card`, a snapshot): its own group, one frame, closed; the relay caches the newest. */
63
+ export declare function writeLatest(track: Moq.Track.Producer, header: Header, body: Uint8Array): void;
64
+ /** Waits for a requested broadcast to become routable. */
65
+ export declare function waitActive(req: Moq.Origin.Requesting, timeoutMs: number, signal?: AbortSignal): Promise<Moq.Broadcast.Consumer>;
66
+ /** A frame sink; a returned promise is awaited before the next frame. */
67
+ export type FrameSink = (payload: Uint8Array) => void | Promise<void>;
68
+ /**
69
+ * Reads a call track (`res`, `calls`, a FEC `report`) frame by frame, as the Rust `read_call_track`:
70
+ * groups are taken strictly in sequence from group 0, group k read to its end before group k+1. A
71
+ * group that arrives before its turn is held (a track's groups travel on streams of their own, so
72
+ * group 1 can arrive before group 0). The read ends with an error when a group older than the one due
73
+ * arrives, when a group's read fails (reset or expired), when the subscription fails, or when the
74
+ * group due has not arrived within `CALL_GROUP_MAX_AGE_MS` while a later group is held (that wait
75
+ * is armed only then; with nothing later held the reader waits for the next group with no timer).
76
+ * Returns `null` when the track ended cleanly or `signal` aborted, otherwise the error.
77
+ */
78
+ export declare function readTrack(sub: Moq.Track.Subscriber, onFrame: FrameSink, signal?: AbortSignal): Promise<unknown>;
79
+ export declare function readDatagrams(sub: Moq.Track.Subscriber, onDatagram: FrameSink, signal?: AbortSignal): Promise<unknown>;
80
+ /** Installs `@moq/web-transport` in Bun/Node when the runtime has no WebTransport. Browsers skip this. */
81
+ export declare function ensureWebTransport(): Promise<void>;
82
+ /** A connect failure's text (`@moq/net` races WebTransport against WebSocket and rejects with an `AggregateError`). */
83
+ export declare function describeError(e: unknown): string;
84
+ /**
85
+ * The error of a session's close (`classify_close`): `admission_refused` travels as the application
86
+ * close code (retryable: a live session may hold this name, the project may be settling), another
87
+ * application code is a broker-side failure, `unauthorized` is the relay's own refusal, everything
88
+ * else is the transport (`connection_lost`); a clean close is a drop too (in one-shot mode a broker
89
+ * GOAWAY looks like one).
90
+ */
91
+ export declare function closeError(err: Error | null): SynapseError;
92
+ /** `https://host:port/<root>` → `http://host:port/certificate.sha256`, the broker's plain-HTTP helper. */
93
+ export declare function brokerCertHelperUrl(brokerUrl: string): string;
94
+ /** The certificate pin when the caller gave none (SDK §13): `SYNAPSE_BROKER_CERT_SHA256` (Bun/Node), then the helper, then `undefined` = unpinned. Per attempt. */
95
+ export declare function discoverBrokerCertSha256(brokerUrl: string, opts?: {
96
+ timeoutMs?: number;
97
+ signal?: AbortSignal;
98
+ }): Promise<string | undefined>;
99
+ export interface RespFrame {
100
+ header: Header;
101
+ body: Uint8Array;
102
+ }
103
+ /** What one attempt dials (`Dial`). */
104
+ export interface Dial {
105
+ url: string;
106
+ identity: Identity;
107
+ cert: string | undefined;
108
+ }
109
+ export interface SessionKnobs {
110
+ transport: "auto" | "webtransport" | "websocket";
111
+ connectTimeoutMs: number;
112
+ maxDatagram: number;
113
+ project?: "operator";
114
+ }
115
+ /** A notice the broker wrote on this session's `res` track, handed to the node's loop. */
116
+ export type BrokerNotice = {
117
+ kind: "error";
118
+ body: ErrorBody;
119
+ } | {
120
+ kind: "project";
121
+ notice: Notice;
122
+ };
123
+ /** The key of a call in the session's pending map. */
124
+ export declare const callKey: (id: CallId) => string;
125
+ export declare class Session {
126
+ #private;
127
+ readonly url: string;
128
+ readonly conn: Moq.Connection.Established;
129
+ readonly pub: Moq.Origin.Producer;
130
+ readonly sub: Moq.Origin.Producer;
131
+ readonly hello: Hello;
132
+ readonly maxDatagram: number;
133
+ readonly log: MoqLogScope;
134
+ /** Counts *established* sessions of the node (SDK §2); set by the node when attached. */
135
+ epoch: number;
136
+ readonly startedMs: number;
137
+ readonly transport: "webtransport" | "websocket";
138
+ /** Datagrams travel only over WebTransport; the WebSocket fallback is stream-only. */
139
+ readonly datagrams: boolean;
140
+ /** The views this session reads (latest values). */
141
+ readonly topics: Cell<TopicsSnapshot>;
142
+ readonly services: Cell<ServicesSnapshot>;
143
+ readonly timeOffsets: Cell<TimeOffsets>;
144
+ readonly project: Cell<ProjectSnapshot | undefined>;
145
+ readonly clock: Cell<ClockInfo>;
146
+ /** Where notices go: the node's loop sets this at attach. */
147
+ onNotice: ((n: BrokerNotice) => void) | undefined;
148
+ /** Settles with the close's error once the connection closed. */
149
+ readonly closed: Promise<Error | null>;
150
+ constructor(url: string, conn: Moq.Connection.Established, pub: Moq.Origin.Producer, sub: Moq.Origin.Producer, hello: Hello, maxDatagram: number, log: MoqLogScope, reqBc: Moq.Broadcast.Producer, res: Moq.Broadcast.Consumer, resSub: Moq.Track.Subscriber, dom: string);
151
+ get prefix(): string;
152
+ get role(): Role;
153
+ nowUs(): number;
154
+ /** Whether the session still takes requests (not torn down). */
155
+ get isLive(): boolean;
156
+ /** The broker's reason, when it closed this session with a closing notice (or a watcher lost its track). */
157
+ get closingReason(): string | undefined;
158
+ /** Appends a frame to our `req` track (WP §8.6). `connection_lost` once the session has ended. */
159
+ writeReq(header: Header, body: Uint8Array): void;
160
+ /** Writes call `id`'s request and registers where its frames go, both while the session takes requests. */
161
+ /** `deliver(null)` = the session ended with the call pending. */
162
+ writeCall(header: Header, body: Uint8Array, id: CallId, deliver: (f: RespFrame | null) => void): void;
163
+ forgetCall(id: CallId): void;
164
+ /** Stops taking requests and fails every call pending here (`deliver(null)`: `connection_lost`). The first step of a teardown; idempotent. */
165
+ endCalls(): void;
166
+ /** A task of this session lost what it reads, or the broker announced the close: record `reason` (the first stands) and close the connection. The loop does the rest. */
167
+ lost(reason: string): void;
168
+ /** Starts the session's tasks: the `res` demux, the four snapshot watchers, the clock probe (unless the broker shares our domain). Each writes only into this session. */
169
+ start(sync: ClockSync, startCall: (svc: string, args: unknown) => Promise<unknown>): void;
170
+ /** Stops and awaits the tasks (bounded), then closes the session: track finished, MoQ close sent. */
171
+ teardown(graceMs?: number): Promise<void>;
172
+ /** The synchronous end: fail its calls, finish our track, close the connection. Idempotent. */
173
+ close(): void;
174
+ createBroadcast(path: string): Moq.Broadcast.Producer;
175
+ request(path: string): Moq.Origin.Requesting;
176
+ }
177
+ /**
178
+ * Opens one session to `dial`: connect with our identity as query parameters (WP §3.1), wait for
179
+ * the hello on `client/<name>/hello` (readiness + grant proof + our prefix), find our `res` track at
180
+ * `runtime/rpc/<sid>`, announce our `req` track under the prefix. A refused admission shows up as
181
+ * the session closing before the hello: the close decides the error. `signal` abandons the dial: every
182
+ * wait stops at once and the half-open connection is closed (`canceled`), as the connect timeout does
183
+ * (`connection_lost`).
184
+ */
185
+ export declare function openSession(knobs: SessionKnobs, dial: Dial, dom: string, signal?: AbortSignal): Promise<Session>;
186
+ /** The error a call pending on a session that ended reports (WP §8.4: the request was written, so it may have run). */
187
+ /** `data` is `{ outcome: "unknown" }`, with `reason` when the session's close had one (Rust `outcome_unknown_data`). */
188
+ export declare function outcomeUnknown(closing: string | undefined): SynapseError;
189
+ export { deferred };