@synapse-sdk/client 0.3.4 → 0.3.7

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/dist/types.d.ts CHANGED
@@ -40,7 +40,19 @@ export interface ConnectOptions {
40
40
  export interface ConnectTarget {
41
41
  /**
42
42
  * The runtime, `https://host:4470` (the path to dial is learned from the runtime at every attempt). Through a relay: the relay with
43
- * the runtime's path, `https://relay:4443/runtimes/<runtime_id>`, and `token`. A `jwt=` in the URL is taken as the token.
43
+ * the runtime's path, `https://<relay>/cluster/<cluster>/runtimes/<runtime_id>`, and `token`. A `jwt=` in the URL is taken as the token.
44
+ *
45
+ * Or, in Bun/Node, a target as the CLI's `-t` names it (anything without `<scheme>://`): `"infra-1@devel"` (runtime `infra-1`
46
+ * of cluster `devel`), `"infra-1"` (in the default cluster), `"arm@medatech/devel"` (an org's cluster, by its slug). The relay
47
+ * URL and the token come from the CLI's files on this machine (`~/.synapse/cluster.toml` and the tokens beside it), read again
48
+ * at every attempt, so a token the CLI refreshed is used on the next reconnect (`token` given here wins). The SDK only reads
49
+ * those files: `synapse login` and `synapse cluster login <cluster>` first. A name this machine does not know is
50
+ * `invalid_input`, a missing or expired token `unauthorized`, each naming the CLI command that fixes it.
51
+ *
52
+ * With a person signed in (`node.setAuth`), in a browser or in Bun/Node, the same names are resolved through the key
53
+ * server instead, from your clusters: `"infra-1@devel"`, `"arm@medatech/devel"` (the cluster is always named). A name
54
+ * not in your clusters is `invalid_input`; nobody signed in, or a sign-in the key server does not take, `unauthorized`.
55
+ * In a browser with nobody signed in, a name is refused at once (`invalid_input`): sign in, or pass the relay URL and a token.
44
56
  */
45
57
  url: string;
46
58
  /** The type to declare there; left out keeps the node's current one. */
@@ -51,7 +63,7 @@ export interface ConnectTarget {
51
63
  userId?: string;
52
64
  /** The credential to present there; left out keeps the node's current one. Never printed. */
53
65
  credential?: string;
54
- /** Pin of *this* broker's self-signed certificate (SHA-256 hex). Absent: `SYNAPSE_BROKER_CERT_SHA256`, then the broker's helper, then unpinned (per attempt). */
66
+ /** Pin of *this* runtime's self-signed certificate (SHA-256 hex). Absent: `SYNAPSE_BROKER_CERT_SHA256`, then, for a runtime URL (no path), its helper; a URL naming a root (a relay) connects unpinned against the system's roots (per attempt). */
55
67
  brokerCertSha256?: string;
56
68
  /** The relay token for *this* target; left out = no token (a token for another runtime would be refused). Never printed. */
57
69
  token?: string;
@@ -85,6 +97,42 @@ export type ConnectionState = {
85
97
  status: "failed";
86
98
  error: SynapseError;
87
99
  };
100
+ /**
101
+ * Gives the signed-in person's credential for the key server, fetched each time the node asks it (`setAuth`);
102
+ * `null` or `undefined` means nobody is signed in.
103
+ */
104
+ export type FetchToken = () => Promise<string | null | undefined>;
105
+ /** The key server's answer about the signed-in person (`node.discover()`): the relay to dial and your clusters with their runtimes. */
106
+ export interface Discovery {
107
+ /** The relay a runtime of these clusters is dialed through. */
108
+ relay: string;
109
+ /** The signed-in person. */
110
+ userId: string;
111
+ /** Your clusters: your own, and those of the organizations you belong to. */
112
+ clusters: {
113
+ id: string;
114
+ name: string;
115
+ /** Who owns it: `kind` `user` (yours) or `org`, its `id`, and the organization's `slug` when it has one. */
116
+ owner: {
117
+ kind: string;
118
+ id: string;
119
+ slug?: string;
120
+ };
121
+ /** How `connect()` names it: `name` for yours, `<slug>/<name>` (or `<org id>/<name>`) for an organization's: `"infra-1@" + label`. */
122
+ label: string;
123
+ /** Its runtimes, name → runtime id. */
124
+ runtimes: Record<string, string>;
125
+ }[];
126
+ }
127
+ /**
128
+ * `node.discovery`: `idle` with nobody signed in (or just after `setAuth`), `pending` while the key server is asked,
129
+ * `success` with the answer, `error` with why it failed (`data` still holds the last answer, if there was one).
130
+ */
131
+ export interface DiscoveryState {
132
+ status: "idle" | "pending" | "success" | "error";
133
+ data?: Discovery;
134
+ error?: SynapseError;
135
+ }
88
136
  /** This node's identity and the current session's hello facts; empty strings between sessions. */
89
137
  export interface Info {
90
138
  name: string;
@@ -230,27 +278,32 @@ export interface CallOptions<U = unknown> {
230
278
  /** Local: fail `idle_timeout` (and cancel) when no frame arrives for this long. */
231
279
  idleTimeoutMs?: number;
232
280
  signal?: AbortSignal;
281
+ /** The callback form: the SDK reads the updates and calls `onUpdate(u)` for each (the run's loop then has none); a throwing `onUpdate` is rethrown later and the reading goes on. */
233
282
  onUpdate?: (u: U) => void;
234
283
  }
235
- /** A started call (WP §8.3): its updates, its result, and cancel. */
236
- /** `await run` is `await run.result`. */
237
- export interface Run<U, R> extends AsyncDisposable, PromiseLike<R> {
284
+ /**
285
+ * A started call (WP §8.3). Its progress is a stream, the run itself a promise:
286
+ * `for await (const u of run)` the updates until the run ended, `await run.next()` one update
287
+ * (`undefined` once the run ended), or `onUpdate` in the call options (the SDK reads them);
288
+ * `await run` (or `.then`/`.catch`) the result, rejecting with the `SynapseError` of a
289
+ * `failed` or `canceled` run; `cancel()`.
290
+ */
291
+ export interface Run<U, R> extends AsyncDisposable, PromiseLike<R>, AsyncIterable<U> {
238
292
  readonly id: string;
239
293
  readonly status: RunStatus;
240
- /** The next progress update; `undefined` once the terminal frame arrived (then `result`). */
241
- nextUpdate(): Promise<U | undefined>;
242
- updates(): AsyncIterable<U>;
294
+ /** The next progress update; `undefined` once the run ended (then `await run` settles at once). */
295
+ next(): Promise<U | undefined>;
296
+ /** `await run` / `.then` as on a promise (returns a `Promise`, so `.then(...).catch(...)` chains). */
297
+ then<A = R, B = never>(onfulfilled?: ((value: R) => A | PromiseLike<A>) | null, onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<A | B>;
298
+ /** The result as a plain promise (what `await run` waits for). */
243
299
  readonly result: Promise<R>;
300
+ /** `.catch` as on a promise: `onRejected` gets the run's `SynapseError` when it failed or was canceled. */
301
+ catch<B = never>(onRejected?: ((e: unknown) => B | PromiseLike<B>) | null): Promise<R | B>;
302
+ /** `.finally` as on a promise: `onFinally` runs once the run ended, whichever way. */
303
+ finally(onFinally?: (() => void) | null): Promise<R>;
244
304
  /** Cooperative cancel: tells the broker, resolves on its `canceled` frame. */
245
305
  cancel(reason?: string): Promise<void>;
246
306
  }
247
- export type CallEvent<U, R> = {
248
- type: "update";
249
- data: U;
250
- } | {
251
- type: "result";
252
- data: R;
253
- };
254
307
  export interface ServeContext<U> {
255
308
  readonly call: string;
256
309
  readonly caller: string;
@@ -332,7 +385,7 @@ export interface StoreHandle extends AsyncDisposable {
332
385
  readonly synced: boolean;
333
386
  /** The handle's own state, a state value: `get()` now (the same object while unchanged), `onChange` for each change, `changed()` for the next. */
334
387
  readonly status: StateValue<StoreStatus>;
335
- /** Resolves once the first value is in (the first snapshot applied, or the namespace absent). */
388
+ /** Resolves once the value is in: current as of the open (the version the runtime answered then applied), or the namespace absent. */
336
389
  ready(): Promise<void>;
337
390
  /** `set(key, value)`, or `set({ ... })` for several keys as one atomic write; resolves once `get()` shows it. */
338
391
  set(key: string, value: unknown): Promise<void>;
@@ -343,8 +396,9 @@ export interface StoreHandle extends AsyncDisposable {
343
396
  destroy(): Promise<void>;
344
397
  /** The whole value after each change; with `key`, that key's value each time it changes (`undefined` after a delete). */
345
398
  watch(key?: string): AsyncIterable<unknown>;
346
- /** The whole value after its next change; `store_gone` once this handle is closed. */
399
+ /** The whole value after its next change; with `key`, that key's value after its next change (`undefined` after a delete). `store_gone` once this handle is closed. */
347
400
  changed(): Promise<Record<string, unknown>>;
401
+ changed(key: string): Promise<unknown>;
348
402
  /** `cb` with the whole value on every change of the value or the status (the framework bridge); returns the remover. */
349
403
  onChange(cb: (value: Record<string, unknown>) => void): () => void;
350
404
  close(): void;
@@ -441,7 +495,7 @@ export interface SceneEntity {
441
495
  kind: SceneKind;
442
496
  /**
443
497
  * Unique across every plane (authored, calibration, live): a write that would give an entity a name another entity
444
- * holds is refused `name_taken`, its `data.holder` the id holding the name.
498
+ * holds is refused `name_taken`, its `data.holder` the id holding the name (`data.kind`, `data.layer`: the holder's).
445
499
  */
446
500
  name?: string;
447
501
  /** The parent's id; `null` is the world root. */
@@ -550,6 +604,9 @@ export interface SceneEntityView extends SceneView<SceneEntity | undefined> {
550
604
  */
551
605
  watch(): AsyncIterable<SceneEntity | undefined>;
552
606
  watch<K extends keyof SceneEntity & string>(component: K): AsyncIterable<SceneEntity[K] | undefined>;
607
+ /** The entity after its next change; with `component`, that component after its next change (`undefined` once gone). The value now once the handle ends. */
608
+ changed(): Promise<SceneEntity | undefined>;
609
+ changed<K extends keyof SceneEntity & string>(component: K): Promise<SceneEntity[K] | undefined>;
553
610
  }
554
611
  /** An entity view's authored plane (`view.authored`). Reads as the view does: nothing once the handle is detached or closed. */
555
612
  export interface EntityAuthored {
@@ -743,12 +800,14 @@ export interface SceneAuthored {
743
800
  patch(entity: string | SceneEntityView, set: Partial<SceneEntity>): Promise<void>;
744
801
  /**
745
802
  * A builder's one verb, keyed by `name`; resolves with the entity's view (by id: that entity) once the entities show
746
- * it. No authored entity of that name: `spawn(kind, { name, parent, ...components })`. One of `kind`: its authored
747
- * value is compared with what `patch(id, components)` would make of it (one-level merge; live overrides never
748
- * count): equal, nothing is sent; else a patch of the components that differ. `parent` is used by the spawn only: an
749
- * existing entity is not reparented. One of another kind, or a name a live entity holds: the spawn is sent and the
750
- * runtime answers (`name_taken`, `data.holder`, while the name is held; the copy here may be stale). An `id`
751
- * component: `invalid_input` (`data: { component: "id" }`), nothing sent.
803
+ * it. The id is the runtime's answer, never the copy's: `spawn(kind, { name, parent, ...components })` (a fresh id) is
804
+ * sent first; the runtime refusing it `name_taken` names the holder and its kind (`data.holder`, `data.kind`,
805
+ * `data.layer`). A holder of this kind in the authored or calibration layer is patched with the components that differ
806
+ * from what the copy shows of it (one-level merge; live overrides never count; all of them when the copy does not show
807
+ * it yet; equal, nothing more is sent): the copy only trims the patch, it never decides. A holder of another kind, a
808
+ * live one, or an answer without them: the `name_taken` as it came. `parent` is used by the spawn only: an existing
809
+ * entity is not reparented. Any other refusal as it came (`not_found` on the patch: the entity went meanwhile; upsert
810
+ * again). An `id` component: `invalid_input` (`data: { component: "id" }`), nothing sent.
752
811
  */
753
812
  upsert(kind: SceneKind, opts: SceneSpawnOptions & {
754
813
  id?: never;
@@ -817,7 +876,7 @@ export interface SceneHandle extends AsyncDisposable {
817
876
  readonly status: StateValue<SceneStatus>;
818
877
  /** The one boolean a node author checks: `status.get().synced`. */
819
878
  readonly synced: boolean;
820
- /** Resolves once the handle is attached (at once if it is); `not_connected` once the handle is closed. No timeout: pass an `AbortSignal` to what awaits it, or race it. */
879
+ /** Resolves once the handle is attached (at once if it is): the copy current as of the attach, at the version the runtime answered when the scene was opened, or past it; `not_connected` once the handle is closed. No timeout: pass an `AbortSignal` to what awaits it, or race it. */
821
880
  ready(): Promise<void>;
822
881
  /** The entities after their next change (state, not history); `not_connected` once the handle is closed. */
823
882
  changed(): Promise<Readonly<Record<string, SceneEntity>>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synapse-sdk/client",
3
- "version": "0.3.4",
3
+ "version": "0.3.7",
4
4
  "description": "Synapse SDK client: connect a node to the runtime; topics, services, calls, store, video.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -14,8 +14,9 @@
14
14
  "dist"
15
15
  ],
16
16
  "dependencies": {
17
- "@synapse-sdk/core": "0.3.4",
18
- "@moq/net": "0.4.2"
17
+ "@synapse-sdk/core": "0.3.7",
18
+ "@moq/net": "0.4.2",
19
+ "smol-toml": "1.9.0"
19
20
  },
20
21
  "peerDependencies": {
21
22
  "@moq/web-transport": "0.1.5"