@synapse-sdk/client 0.3.3 → 0.3.6

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.
@@ -1,7 +1,7 @@
1
1
  import type { SceneEntity, SceneTxn, Xform } from "./types.ts";
2
2
  /** The ops the materializers queue. */
3
3
  type FrameTxn = Pick<SceneTxn, "spawn" | "patch" | "rename" | "despawn">;
4
- /** The entities to reconcile against: `scene.entities` (id → entity) or a list of entities. */
4
+ /** The entities to reconcile against: `scene.get()` (id → entity) or a list of entities. */
5
5
  type Entities = Iterable<SceneEntity> | Readonly<Record<string, SceneEntity>>;
6
6
  /** The presentation name of a materialized derived frame: `<parent name>_<frame>` (e.g. `ur5e_tool0`). */
7
7
  export declare function derivedFrameName(parentName: string, frame: string): string;
@@ -28,7 +28,7 @@ export interface MaterializeReport {
28
28
  /**
29
29
  * Reconciles `robot`'s `robot.frames` with its derived frames (those with `props.from` equal to
30
30
  * `provenance`, default `"urdf"`; a frame without `from` counts as `"urdf"`), queueing the ops on
31
- * `txn`. `entities` is the scene now (`scene.entities`). `restPoses` maps declared names to poses
31
+ * `txn`. `entities` is the scene now (`scene.get()`). `restPoses` maps declared names to poses
32
32
  * (FK at the authored joints, the caller's work); a name with no frame yet and no pose is an error,
33
33
  * never a spawn, so a call with no poses only renames, backfills `from` and retracts.
34
34
  *
@@ -29,7 +29,7 @@ export function validateDerivedFrame(frame, parentId) {
29
29
  /**
30
30
  * Reconciles `robot`'s `robot.frames` with its derived frames (those with `props.from` equal to
31
31
  * `provenance`, default `"urdf"`; a frame without `from` counts as `"urdf"`), queueing the ops on
32
- * `txn`. `entities` is the scene now (`scene.entities`). `restPoses` maps declared names to poses
32
+ * `txn`. `entities` is the scene now (`scene.get()`). `restPoses` maps declared names to poses
33
33
  * (FK at the authored joints, the caller's work); a name with no frame yet and no pose is an error,
34
34
  * never a spawn, so a call with no poses only renames, backfills `from` and retracts.
35
35
  *
package/dist/session.d.ts CHANGED
@@ -83,7 +83,7 @@ export declare function ensureWebTransport(): Promise<void>;
83
83
  export declare function describeError(e: unknown): string;
84
84
  /**
85
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
86
+ * close code (retryable: a live session holds this name; a crashed one is released after the idle timeout), another
87
87
  * application code is a broker-side failure, `unauthorized` is the relay's own refusal, everything
88
88
  * else is the transport (`connection_lost`); a clean close is a drop too (in one-shot mode a broker
89
89
  * GOAWAY looks like one).
@@ -164,6 +164,8 @@ export declare class Session {
164
164
  readonly log: MoqLogScope;
165
165
  /** Counts *established* sessions of the node (SDK §2); set by the node when attached. */
166
166
  epoch: number;
167
+ /** The target generation this session was opened for (`Target.generation`); set by the node's loop when attached. */
168
+ generation: number;
167
169
  readonly startedMs: number;
168
170
  readonly transport: "webtransport" | "websocket";
169
171
  /** Datagrams travel only over WebTransport; the WebSocket fallback is stream-only. */
package/dist/session.js CHANGED
@@ -313,7 +313,7 @@ const ADMISSION_REFUSED_SESSION_CODE = 64 + ADMISSION_REFUSED_CLOSE_CODE;
313
313
  const ADMISSION_REFUSED_REASON = new RegExp(`app code=${ADMISSION_REFUSED_CLOSE_CODE}(?!\\d)`);
314
314
  /**
315
315
  * The error of a session's close (`classify_close`): `admission_refused` travels as the application
316
- * close code (retryable: a live session may hold this name, the project may be settling), another
316
+ * close code (retryable: a live session holds this name; a crashed one is released after the idle timeout), another
317
317
  * application code is a broker-side failure, `unauthorized` is the relay's own refusal, everything
318
318
  * else is the transport (`connection_lost`); a clean close is a drop too (in one-shot mode a broker
319
319
  * GOAWAY looks like one).
@@ -323,7 +323,7 @@ export function closeError(err) {
323
323
  return new SynapseError("connection_lost", { message: "session closed by the broker", source: "transport" });
324
324
  const text = `${err.message} ${err.reason ?? ""}`;
325
325
  if ((err instanceof Moq.Error.Session && err.code === ADMISSION_REFUSED_SESSION_CODE) || ADMISSION_REFUSED_REASON.test(text)) {
326
- return new SynapseError("admission_refused", { message: "the runtime refused this connection, retryable: a live session may hold this name, the project may be settling, or the runtime is overloaded; retry with backoff", source: "broker", cause: err });
326
+ return new SynapseError("admission_refused", { message: "the runtime refused this connection; retryable: a live session holds this name (a crashed one is released after the idle timeout); the SDK keeps retrying with backoff", source: "broker", cause: err });
327
327
  }
328
328
  if (err instanceof Moq.Error.Session && err.code === Moq.SessionCode.Unauthorized)
329
329
  return new SynapseError("unauthorized", { message: text.trim(), source: "broker", cause: err });
@@ -429,7 +429,7 @@ export async function withRoot(url, signal) {
429
429
  throw timeout.aborted ? new SynapseError("connection_lost", { message: `root helper at ${addr} did not answer`, source: "transport" }) : lost(describeError(e));
430
430
  }
431
431
  if (!isRuntimeRoot(root)) {
432
- throw new SynapseError("invalid_input", { message: `${url} names no runtime (its /root helper returned ${JSON.stringify(root.slice(0, 80))}); give the full path, e.g. https://relay:4443/runtimes/<runtime_id>`, source: "sdk" });
432
+ throw new SynapseError("invalid_input", { message: `${url} names no runtime (its /root helper returned ${JSON.stringify(root.slice(0, 80))}); give the full path, e.g. https://<relay>/cluster/<cluster>/runtimes/<runtime_id>`, source: "sdk" });
433
433
  }
434
434
  u.pathname = `/${root}`;
435
435
  return u.toString();
@@ -446,6 +446,8 @@ export class Session {
446
446
  log;
447
447
  /** Counts *established* sessions of the node (SDK §2); set by the node when attached. */
448
448
  epoch = 0;
449
+ /** The target generation this session was opened for (`Target.generation`); set by the node's loop when attached. */
450
+ generation = 0;
449
451
  startedMs = performance.now();
450
452
  transport;
451
453
  /** Datagrams travel only over WebTransport; the WebSocket fallback is stream-only. */
package/dist/store.d.ts CHANGED
@@ -3,12 +3,14 @@ import type { Node } from "./node.ts";
3
3
  import { type Session } from "./session.ts";
4
4
  import type { Warn } from "./topics.ts";
5
5
  import type { StoreChange, StoreHandle, StoreOptions, StoreStatus } from "./types.ts";
6
- import { AsyncQueue, type Deferred } from "./util.ts";
6
+ import { AsyncQueue, type Deferred, type StateValue } from "./util.ts";
7
7
  export declare const ENDED_DESTROYED = "destroyed";
8
8
  export declare const ENDED_CONNECTION_LOST = "connection_lost";
9
9
  export declare const ENDED_CLIENT = "client";
10
10
  /** A watcher this many batches behind is ended, never grown. */
11
11
  export declare const STORE_FEED_CAP = 256;
12
+ /** How long an incarnation the open's reply named has to show up on the announcement cursor (no "live" marker in @moq/net's JS cursor). */
13
+ export declare const ANNOUNCE_WITHIN_MS = 1000;
12
14
  /** The mirror and what the runtime said about the namespace; written by the task and its reader only. */
13
15
  export declare class State {
14
16
  #private;
@@ -24,7 +26,8 @@ export declare class State {
24
26
  onDeath: StoreOnDeath | undefined;
25
27
  absent: boolean;
26
28
  loaded: boolean;
27
- gap: boolean;
29
+ /** A diff skipped a version (not applied; the track is read again from its snapshot): the highest version seen then. Cleared once the copy is back at it (PLAN v0.34 §2.2). */
30
+ gap: number | undefined;
28
31
  pending: number;
29
32
  ended: string | undefined;
30
33
  subscribe(l: () => void): () => void;
@@ -32,7 +35,11 @@ export declare class State {
32
35
  /** A feed its reader has left: it is closed and gets nothing more. */
33
36
  unwatch(q: AsyncQueue<StoreChange[]>): void;
34
37
  applySnapshot(s: StoreSnapshot): void;
35
- applyDiff(d: StoreDiff, warn: Warn): void;
38
+ /**
39
+ * One diff, applied when it is the next version. Otherwise it is not applied (`false`): a gap, `synced` false until the
40
+ * copy is back at the version seen; the reader reads the track again from its snapshot (PLAN v0.34 §2.2).
41
+ */
42
+ applyDiff(d: StoreDiff, warn: Warn): boolean;
36
43
  /** Nothing of the incarnation is known until the runtime answers the next open. */
37
44
  reset(): void;
38
45
  opened(a: StoreAccess | undefined): void;
@@ -78,11 +85,19 @@ type Event = {
78
85
  epoch: string;
79
86
  version: number;
80
87
  reply: Deferred<void>;
81
- } | {
88
+ }
89
+ /** A write of handle `handle` (its parked writes are refused when it leaves). */
90
+ | {
82
91
  kind: "write";
92
+ handle: number;
83
93
  op: string;
84
94
  args: WriteOp;
85
95
  reply: Deferred<unknown>;
96
+ }
97
+ /** Handle `handle` was closed, others remain: its parked writes are refused. */
98
+ | {
99
+ kind: "leave";
100
+ handle: number;
86
101
  } | {
87
102
  kind: "writeDone";
88
103
  op: string;
@@ -107,6 +122,7 @@ export declare class Namespace {
107
122
  readonly mailbox: AsyncQueue<Event>;
108
123
  readonly exited: Deferred<void>;
109
124
  handles: number;
125
+ nextHandle: number;
110
126
  constructor(name: string, opts: StoreOptions, node: Node, warn: Warn);
111
127
  post(e: Event): boolean;
112
128
  request<T>(make: (reply: Deferred<T>) => Event): Promise<T>;
@@ -116,32 +132,35 @@ export declare function startNamespace(ns: Namespace, registry: Map<string, Name
116
132
  export declare class StoreHandleImpl implements StoreHandle {
117
133
  #private;
118
134
  private readonly ns;
135
+ /** The handle's own state, a state value: `get()` (the same object while unchanged), `onChange`, `changed()`, `watch()`. */
136
+ readonly status: StateValue<StoreStatus>;
119
137
  constructor(ns: Namespace);
120
138
  get name(): string;
121
- /** The whole value, what the runtime has; the same object while nothing changed (reference-stable for React). */
122
- get value(): Record<string, unknown>;
139
+ /** `get()`: the whole value, what the runtime has; the same object while nothing changed (reference-stable for React). `get(key)`: that key's value, `undefined` when absent. */
140
+ get(): Record<string, unknown>;
123
141
  get(key: string): unknown;
124
142
  get synced(): boolean;
125
- get status(): StoreStatus;
126
- /** `set(key, value)` or `set({ ...several keys })` (one atomic write); resolves once `value` shows it. */
143
+ /** `set(key, value)` or `set({ ...several keys })` (one atomic write); resolves once `get()` shows it. */
127
144
  set(keyOrValues: string | Record<string, unknown>, value?: unknown): Promise<void>;
128
145
  delete(key: string): Promise<void>;
129
146
  clear(): Promise<void>;
130
147
  destroy(): Promise<void>;
131
148
  /** `for await (const value of store.watch())`: the whole value after each change; `store.watch(key)`: that key's value each time it changes. Ends with the handle; leaving the loop removes the feed. */
132
149
  watch(key?: string): AsyncGenerator<unknown, void, undefined>;
133
- /** The framework bridge (React's `useStore`): called on every change of the value or the status. */
134
- subscribe(listener: () => void): () => void;
135
- /** Waits for the namespace to be in (the first snapshot applied, or absent). */
136
- join(): Promise<void>;
137
- /** `join()` under the SDK's word (Publisher and Registration have `ready()` too). */
150
+ /** The whole value after its next change; `changed(key)`: that key's value after its next change (`undefined` after a delete), as `watch(key)` yields it. `store_gone` once this handle is closed. Armed at the call. */
151
+ changed(): Promise<Record<string, unknown>>;
152
+ changed(key: string): Promise<unknown>;
153
+ /** `cb` with the whole value on every change of the value or the status (the framework bridge, React's `useStore`); returns the remover. */
154
+ onChange(cb: (value: Record<string, unknown>) => void): () => void;
155
+ /** Waits for the namespace to be in: the value current as of the open (the version the runtime answered applied), or the namespace absent. */
138
156
  ready(): Promise<void>;
139
157
  close(): void;
140
158
  [Symbol.asyncDispose](): Promise<void>;
141
159
  }
142
160
  /**
143
161
  * `store(name, opts)`: one task per namespace per node; a second `store` with the same options joins
144
- * it (no open written), with other options `invalid_input`; one exiting is waited out.
162
+ * it (no open written), with other options `invalid_input`; one exiting is waited out. Without a session
163
+ * (before the first, between two) the task waits for the next one, like after a drop; it resolves once in.
145
164
  */
146
165
  export declare function openStore(node: Node, registry: Map<string, Namespace>, name: string, opts: StoreOptions, warn: Warn): Promise<StoreHandleImpl>;
147
166
  /**