@synapse-sdk/client 0.1.0 → 0.3.3
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 +1 -111
- package/dist/calls.d.ts +37 -0
- package/dist/calls.js +192 -0
- package/dist/channel.d.ts +84 -0
- package/dist/channel.js +215 -0
- package/dist/fec.d.ts +85 -0
- package/dist/fec.js +281 -0
- package/dist/index.d.ts +11 -74
- package/dist/index.js +11 -15
- package/dist/moq_log.d.ts +23 -0
- package/dist/moq_log.js +176 -0
- package/dist/node.d.ts +145 -0
- package/dist/node.js +408 -0
- package/dist/scene.d.ts +299 -0
- package/dist/scene.js +1531 -0
- package/dist/scene_frames.d.ts +51 -0
- package/dist/scene_frames.js +215 -0
- package/dist/serve.d.ts +46 -0
- package/dist/serve.js +281 -0
- package/dist/session.d.ts +250 -0
- package/dist/session.js +963 -0
- package/dist/signals.d.ts +10 -0
- package/dist/signals.js +63 -0
- package/dist/store.d.ts +155 -0
- package/dist/store.js +924 -0
- package/dist/synapse.d.ts +93 -0
- package/dist/synapse.js +268 -0
- package/dist/tasks.d.ts +37 -0
- package/dist/tasks.js +132 -0
- package/dist/topics.d.ts +151 -0
- package/dist/topics.js +959 -0
- package/dist/types.d.ts +820 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +67 -0
- package/dist/util.js +227 -0
- package/dist/view.d.ts +19 -0
- package/dist/view.js +145 -0
- package/package.json +18 -39
- package/dist/node-CSSIXG4k.js +0 -1501
- package/dist/node-DbD7G0Nx.d.ts +0 -466
- package/dist/react.d.ts +0 -63
- package/dist/react.js +0 -141
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import * as Moq from "@moq/net";
|
|
2
|
+
import { ClockSync, SynapseError, type CallId, type ClockInfo, type ConnectRequest, 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
|
+
/**
|
|
95
|
+
* The runtime's URL when the caller gave none: `SYNAPSE_ENDPOINT` (Bun/Node; the runtime sets it for every task it starts).
|
|
96
|
+
* A browser has no environment: there, as with neither, `invalid_input`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function urlOrEndpoint(url: string | undefined): string;
|
|
99
|
+
/** The certificate pin when the caller gave none (SDK §13): `SYNAPSE_BROKER_CERT_SHA256` (Bun/Node), then the helper, then `undefined` = unpinned. Per attempt. */
|
|
100
|
+
export declare function discoverBrokerCertSha256(brokerUrl: string, opts?: {
|
|
101
|
+
timeoutMs?: number;
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
}): Promise<string | undefined>;
|
|
104
|
+
/**
|
|
105
|
+
* `token_from_url`: `url` without its `jwt=` query parameters, and the last non-empty one (as the relays
|
|
106
|
+
* read it). A URL without `jwt=`, or one that does not parse, is returned as it is (the dial reports it).
|
|
107
|
+
*/
|
|
108
|
+
export declare function tokenFromUrl(url: string): {
|
|
109
|
+
url: string;
|
|
110
|
+
token: string | undefined;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* `Dial::new`: the one place a user's target becomes a dial. A `jwt=` in the URL is the token (the form
|
|
114
|
+
* the relays read), moved out of the URL here so no URL kept or printed afterwards holds it; a `token`
|
|
115
|
+
* given as such wins over one in the URL.
|
|
116
|
+
*/
|
|
117
|
+
export declare function makeDial(url: string, identity: Identity, cert: string | undefined, token: string | undefined): Dial;
|
|
118
|
+
/** `root_runtime_id`: a root names a runtime when it is exactly `runtimes/<runtime id>` (a canonical lowercase UUID). */
|
|
119
|
+
export declare function isRuntimeRoot(root: string): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* `with_root`: the URL to dial for `url`. `url` itself when it names a path (a runtime's root, or a
|
|
122
|
+
* relay's `runtimes/<runtime_id>`); else `url` with the root the runtime names at its plain-HTTP helper
|
|
123
|
+
* (`http://<host>:<port>/root`, the port of `url`, 443 when it has none), asked at every attempt. A
|
|
124
|
+
* helper that cannot be reached, answers non-2xx or answers incompletely is `connection_lost` (the
|
|
125
|
+
* runtime may be coming back); an answer that is not a root (`runtimes/<runtime id>` exactly; a relay has
|
|
126
|
+
* none: it serves many runtimes) is `invalid_input`, naming the fix. `signal` abandons the ask (its reason).
|
|
127
|
+
*/
|
|
128
|
+
export declare function withRoot(url: string, signal?: AbortSignal): Promise<string>;
|
|
129
|
+
export interface RespFrame {
|
|
130
|
+
header: Header;
|
|
131
|
+
body: Uint8Array;
|
|
132
|
+
}
|
|
133
|
+
/** What one attempt dials (`Dial`): built by `makeDial` only, so `url` never holds the token. */
|
|
134
|
+
export interface Dial {
|
|
135
|
+
url: string;
|
|
136
|
+
identity: Identity;
|
|
137
|
+
cert: string | undefined;
|
|
138
|
+
token: string | undefined;
|
|
139
|
+
}
|
|
140
|
+
export interface SessionKnobs {
|
|
141
|
+
transport: "auto" | "webtransport" | "websocket";
|
|
142
|
+
connectTimeoutMs: number;
|
|
143
|
+
maxDatagram: number;
|
|
144
|
+
project?: "operator";
|
|
145
|
+
}
|
|
146
|
+
/** A notice the broker wrote on this session's `res` track, handed to the node's loop. */
|
|
147
|
+
export type BrokerNotice = {
|
|
148
|
+
kind: "error";
|
|
149
|
+
body: ErrorBody;
|
|
150
|
+
} | {
|
|
151
|
+
kind: "project";
|
|
152
|
+
notice: Notice;
|
|
153
|
+
};
|
|
154
|
+
/** The key of a call in the session's pending map. */
|
|
155
|
+
export declare const callKey: (id: CallId) => string;
|
|
156
|
+
export declare class Session {
|
|
157
|
+
#private;
|
|
158
|
+
readonly url: string;
|
|
159
|
+
readonly conn: Moq.Connection.Established;
|
|
160
|
+
readonly pub: Moq.Origin.Producer;
|
|
161
|
+
readonly sub: Moq.Origin.Producer;
|
|
162
|
+
readonly hello: Hello;
|
|
163
|
+
readonly maxDatagram: number;
|
|
164
|
+
readonly log: MoqLogScope;
|
|
165
|
+
/** Counts *established* sessions of the node (SDK §2); set by the node when attached. */
|
|
166
|
+
epoch: number;
|
|
167
|
+
readonly startedMs: number;
|
|
168
|
+
readonly transport: "webtransport" | "websocket";
|
|
169
|
+
/** Datagrams travel only over WebTransport; the WebSocket fallback is stream-only. */
|
|
170
|
+
readonly datagrams: boolean;
|
|
171
|
+
/** The views this session reads (latest values). */
|
|
172
|
+
readonly topics: Cell<TopicsSnapshot>;
|
|
173
|
+
readonly services: Cell<ServicesSnapshot>;
|
|
174
|
+
readonly timeOffsets: Cell<TimeOffsets>;
|
|
175
|
+
readonly project: Cell<ProjectSnapshot | undefined>;
|
|
176
|
+
readonly clock: Cell<ClockInfo>;
|
|
177
|
+
/** Where notices go: the node's loop sets this at attach. */
|
|
178
|
+
onNotice: ((n: BrokerNotice) => void) | undefined;
|
|
179
|
+
/** Settles with the close's error once the connection closed. */
|
|
180
|
+
readonly closed: Promise<Error | null>;
|
|
181
|
+
constructor(url: string, conn: Moq.Connection.Established, pub: Moq.Origin.Producer, sub: Moq.Origin.Producer, hello: Hello, maxDatagram: number, log: MoqLogScope, connect: ConnectTrack, reqBc: Moq.Broadcast.Producer, res: Moq.Broadcast.Consumer, resSub: Moq.Track.Subscriber, dom: string);
|
|
182
|
+
get prefix(): string;
|
|
183
|
+
get role(): Role;
|
|
184
|
+
nowUs(): number;
|
|
185
|
+
/** Whether the session still takes requests (not torn down). */
|
|
186
|
+
get isLive(): boolean;
|
|
187
|
+
/** The broker's reason, when it closed this session with a closing notice (or a watcher lost its track). */
|
|
188
|
+
get closingReason(): string | undefined;
|
|
189
|
+
/** Appends a frame to our `req` track (WP §8.6). `connection_lost` once the session has ended. */
|
|
190
|
+
writeReq(header: Header, body: Uint8Array): void;
|
|
191
|
+
/** Writes call `id`'s request and registers where its frames go, both while the session takes requests. */
|
|
192
|
+
/** `deliver(null)` = the session ended with the call pending. */
|
|
193
|
+
writeCall(header: Header, body: Uint8Array, id: CallId, deliver: (f: RespFrame | null) => void): void;
|
|
194
|
+
forgetCall(id: CallId): void;
|
|
195
|
+
/** Stops taking requests and fails every call pending here (`deliver(null)`: `connection_lost`). The first step of a teardown; idempotent. */
|
|
196
|
+
endCalls(): void;
|
|
197
|
+
/** 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. */
|
|
198
|
+
lost(reason: string): void;
|
|
199
|
+
/** 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. */
|
|
200
|
+
start(sync: ClockSync, startCall: (svc: string, args: unknown) => Promise<unknown>): void;
|
|
201
|
+
/** Stops and awaits the tasks (bounded), then closes the session: track finished, MoQ close sent. */
|
|
202
|
+
teardown(graceMs?: number): Promise<void>;
|
|
203
|
+
/** The synchronous end: withdraw our connect request (the runtime ends the session), fail its calls, finish our track, close the connection. Idempotent. */
|
|
204
|
+
close(): void;
|
|
205
|
+
createBroadcast(path: string): Moq.Broadcast.Producer;
|
|
206
|
+
request(path: string): Moq.Origin.Requesting;
|
|
207
|
+
}
|
|
208
|
+
/** What a connect attempt that got no answer reports (PLAN v0.26 §4 step 1): the request may have gone nowhere. */
|
|
209
|
+
export declare const NO_ANSWER = "no runtime answered under this root (a relay drops out-of-scope publishes silently: check the token)";
|
|
210
|
+
/** A session id for one connect attempt (PLAN v0.26 §5.1): 8 lowercase hex characters, 32 random bits. */
|
|
211
|
+
export declare function mintSid(): string;
|
|
212
|
+
/** Our connect request broadcast `client/<name>/<sid>/connect` and its `hello` track: the session lasts as long as it is announced. */
|
|
213
|
+
export interface ConnectTrack {
|
|
214
|
+
bc: Moq.Broadcast.Producer;
|
|
215
|
+
track: CallWriter;
|
|
216
|
+
}
|
|
217
|
+
/** What the runtime answered a connect request with: the hello, and our `res` track at the path it grants (`hello.res`). */
|
|
218
|
+
export interface Answered {
|
|
219
|
+
connect: ConnectTrack;
|
|
220
|
+
hello: Hello;
|
|
221
|
+
res: Moq.Broadcast.Consumer;
|
|
222
|
+
resSub: Moq.Track.Subscriber;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Asks the runtime for a session over an established connection (PLAN v0.26 §3.1): mint a `sid` for
|
|
226
|
+
* this attempt, write our connect request (`RPC_CALL`, `END`) on track `hello` of
|
|
227
|
+
* `client/<name>/<sid>/connect` and announce it on `pub`, then wait on `sub` for the answer on
|
|
228
|
+
* `client/<name>/<sid>/hello`: the hello (our prefix and role), or an error frame (`END|ERR`, the
|
|
229
|
+
* runtime's refusal, which is the attempt's error as it is: `admission_refused` when a live session
|
|
230
|
+
* holds the name, `invalid_input`, ...). Then our `res` track at the path the hello grants (`hello.res`,
|
|
231
|
+
* `runtime/rpc/@<epoch>`, PLAN v0.26 §7.2). No answer within
|
|
232
|
+
* `timeoutMs` is `not_connected` (`NO_ANSWER`). On any failure the request is withdrawn. `signal`
|
|
233
|
+
* abandons the wait (its reason is the error).
|
|
234
|
+
*/
|
|
235
|
+
export declare function requestSession(pub: Moq.Origin.Producer, sub: Moq.Origin.Producer, request: ConnectRequest, timeoutMs: number, log: MoqLogScope, signal: AbortSignal): Promise<Answered>;
|
|
236
|
+
/**
|
|
237
|
+
* Opens one session to `dial` (PLAN v0.26 §3.1): the path to dial is the URL's own or, for a bare
|
|
238
|
+
* `https://host:port`, the root the runtime names (`withRoot`, asked at every attempt); dial with our name
|
|
239
|
+
* (the relay level's scope) and the token (`jwt=`, through a relay) as the only query parameters; ask the runtime for a session (`requestSession`: our identity and project
|
|
240
|
+
* intent go in the connect request), then announce our `req` track on the path the hello grants (`hello.rpc`).
|
|
241
|
+
* A relay's refusal of the connection shows up as the session closing: an observed close wins, and a
|
|
242
|
+
* transport failure of a step is settled against the close (`CLOSE_GRACE_MS`). `signal` abandons the
|
|
243
|
+
* dial: every wait stops at once and the half-open connection is closed (`canceled`); the connection
|
|
244
|
+
* not established within the connect timeout is `connection_lost`.
|
|
245
|
+
*/
|
|
246
|
+
export declare function openSession(knobs: SessionKnobs, dial: Dial, dom: string, signal?: AbortSignal): Promise<Session>;
|
|
247
|
+
/** The error a call pending on a session that ended reports (WP §8.4: the request was written, so it may have run). */
|
|
248
|
+
/** `data` is `{ outcome: "unknown" }`, with `reason` when the session's close had one (Rust `outcome_unknown_data`). */
|
|
249
|
+
export declare function outcomeUnknown(closing: string | undefined): SynapseError;
|
|
250
|
+
export { deferred };
|