@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.
- package/dist/calls.d.ts +12 -6
- package/dist/calls.js +35 -18
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/node.d.ts +62 -24
- package/dist/node.js +205 -66
- package/dist/scene.d.ts +166 -61
- package/dist/scene.js +1254 -343
- package/dist/scene_frames.d.ts +2 -2
- package/dist/scene_frames.js +1 -1
- package/dist/session.d.ts +3 -1
- package/dist/session.js +5 -3
- package/dist/store.d.ts +33 -14
- package/dist/store.js +209 -93
- package/dist/synapse.d.ts +73 -49
- package/dist/synapse.js +180 -116
- package/dist/tasks.d.ts +1 -1
- package/dist/tasks.js +24 -10
- package/dist/topics.d.ts +2 -0
- package/dist/topics.js +17 -1
- package/dist/types.d.ts +292 -110
- package/dist/util.d.ts +43 -0
- package/dist/util.js +97 -1
- package/dist/view.d.ts +5 -3
- package/dist/view.js +21 -10
- package/package.json +2 -2
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Card, ClientType, Delivery, FecParams, Role, ServiceRef, StoreMode, StoreOnDeath, StoreRow, TimeDomain, TopicRef, TrackDesc, Tlv, JsonSchema } from "@synapse-sdk/core";
|
|
2
2
|
import type { SynapseError, TasksRun } from "@synapse-sdk/core";
|
|
3
|
+
import type { StateValue } from "./util.ts";
|
|
3
4
|
export type { Card, ClientType, Delivery, FecParams, Role, ServiceRef, StoreMode, StoreOnDeath, StoreRow, TimeDomain, TopicRef, TrackDesc };
|
|
4
5
|
/** What the reconnect policy is asked before every attempt. */
|
|
5
6
|
export interface RetryContext {
|
|
@@ -12,31 +13,21 @@ export interface ReconnectPolicy {
|
|
|
12
13
|
nextRetryDelayMs(ctx: RetryContext): number | null;
|
|
13
14
|
}
|
|
14
15
|
export type Transport = "auto" | "webtransport" | "websocket";
|
|
16
|
+
/** The node's own options (`new Synapse(opts)`): who it is and how it behaves. Where it connects is a `ConnectTarget`, given to `connect()`. */
|
|
15
17
|
export interface ConnectOptions {
|
|
16
|
-
/**
|
|
17
|
-
* The runtime, `https://host:4470` (the path to dial is learned from the runtime at every attempt). Through a relay: the relay with
|
|
18
|
-
* the runtime's path, `https://relay:4443/runtimes/<runtime_id>`, and `token`. A `jwt=` in the URL is taken as the token.
|
|
19
|
-
* Absent: `SYNAPSE_ENDPOINT` (Bun/Node; the runtime sets it for every task it starts); a browser has no environment,
|
|
20
|
-
* so there it is `invalid_input`.
|
|
21
|
-
*/
|
|
22
|
-
url?: string;
|
|
23
|
-
/** The relay token that lets this node through a relay; none for a runtime dialed directly. Never printed. */
|
|
24
|
-
token?: string;
|
|
25
18
|
/** This node's type (WP §3.1): bounds the role it may exercise; the runtime grants the role. */
|
|
26
19
|
type: ClientType;
|
|
27
20
|
/** This node's name `[a-z0-9_-]{1,64}`; minted (`<type>-<6 hex>`) when absent and kept across reconnects. */
|
|
28
21
|
name?: string;
|
|
29
|
-
/** Shared across one human's clients so they are promoted and demoted together (WP §3.5); a `web`/`hmi` node without one mints one. */
|
|
22
|
+
/** Shared across one human's web clients so they are promoted and demoted together (WP §3.5; an hmi is never promoted or demoted); a `web`/`hmi` node without one mints one. */
|
|
30
23
|
userId?: string;
|
|
31
24
|
/** Reserved for signed credentials. */
|
|
32
25
|
credential?: string;
|
|
33
|
-
/** Pin of the broker's self-signed certificate (SHA-256 hex). Absent: `SYNAPSE_BROKER_CERT_SHA256`, then the broker's helper, then unpinned. */
|
|
34
|
-
brokerCertSha256?: string;
|
|
35
26
|
/** Time-domain id override (WP §7.3); default per runtime. */
|
|
36
27
|
domId?: string;
|
|
37
28
|
/** Standing project intent (WP §3.5), sent with the connect request. */
|
|
38
29
|
project?: "operator";
|
|
39
|
-
/** `null` = no reconnect: a dropped session
|
|
30
|
+
/** `null` = no reconnect: a dropped session leaves the node `failed` (resting; `connect()` dials again). Default: 100 ms → 10 s, ×2, jitter, unlimited. */
|
|
40
31
|
reconnect?: ReconnectPolicy | null;
|
|
41
32
|
transport?: Transport;
|
|
42
33
|
connectTimeoutMs?: number;
|
|
@@ -45,18 +36,34 @@ export interface ConnectOptions {
|
|
|
45
36
|
/** Bun/Node: close cleanly on SIGINT/SIGTERM (default true). */
|
|
46
37
|
signals?: boolean;
|
|
47
38
|
}
|
|
48
|
-
/** Where the node should be connected: the argument of `
|
|
39
|
+
/** Where the node should be connected: the argument of `connect()` (identity fields left out keep the node's current ones). */
|
|
49
40
|
export interface ConnectTarget {
|
|
41
|
+
/**
|
|
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>/cluster/<cluster>/runtimes/<runtime_id>`, and `token`. A `jwt=` in the URL is taken as the token.
|
|
44
|
+
*/
|
|
50
45
|
url: string;
|
|
46
|
+
/** The type to declare there; left out keeps the node's current one. */
|
|
51
47
|
type?: ClientType;
|
|
48
|
+
/** The name to declare there; left out keeps the node's current one. */
|
|
52
49
|
name?: string;
|
|
50
|
+
/** The user id to declare there; left out keeps the node's current one. */
|
|
53
51
|
userId?: string;
|
|
52
|
+
/** The credential to present there; left out keeps the node's current one. Never printed. */
|
|
54
53
|
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). */
|
|
55
55
|
brokerCertSha256?: string;
|
|
56
|
-
/** The relay token for *this* target; left out = no token (a token for another runtime would be refused). */
|
|
56
|
+
/** The relay token for *this* target; left out = no token (a token for another runtime would be refused). Never printed. */
|
|
57
57
|
token?: string;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Where the node stands. `idle`: no target (constructed and not yet `connect()`ed, or after `disconnect()`). `failed`:
|
|
61
|
+
* the dialing gave up (`unauthorized`, the bounded `connect()` timed out, the reconnect policy declined); the node rests
|
|
62
|
+
* there and a later `connect()` dials again. `closed`: after `close()`, for good.
|
|
63
|
+
*/
|
|
59
64
|
export type ConnectionState = {
|
|
65
|
+
status: "idle";
|
|
66
|
+
} | {
|
|
60
67
|
status: "connecting";
|
|
61
68
|
attempt: number;
|
|
62
69
|
} | {
|
|
@@ -205,12 +212,15 @@ export interface ViewOptions<T, S> extends SubscribeOptions {
|
|
|
205
212
|
staleTime?: number;
|
|
206
213
|
select?: (v: T) => S;
|
|
207
214
|
}
|
|
208
|
-
/** A topic's value now, as a
|
|
215
|
+
/** A topic's value now, as a state value: `get()` any time, `onChange` for each change (the framework bridge), `changed()` for the next one, `watch()` for each one in a loop; subscribed from creation until `close()`. */
|
|
209
216
|
export interface View<S> extends AsyncDisposable {
|
|
210
217
|
get(): ViewState<S>;
|
|
211
|
-
|
|
218
|
+
/** `cb` with the state on each change; returns the remover. */
|
|
219
|
+
onChange(cb: (state: ViewState<S>) => void): () => void;
|
|
212
220
|
ready(): Promise<void>;
|
|
213
221
|
changed(): Promise<ViewState<S>>;
|
|
222
|
+
/** The state after each change (state, not history: changes in between fold into the latest); ends when the view is closed or the node ends. */
|
|
223
|
+
watch(): AsyncIterable<ViewState<S>>;
|
|
214
224
|
close(): void;
|
|
215
225
|
}
|
|
216
226
|
export type RunStatus = "pending" | "running" | "succeeded" | "failed" | "canceled";
|
|
@@ -220,27 +230,32 @@ export interface CallOptions<U = unknown> {
|
|
|
220
230
|
/** Local: fail `idle_timeout` (and cancel) when no frame arrives for this long. */
|
|
221
231
|
idleTimeoutMs?: number;
|
|
222
232
|
signal?: AbortSignal;
|
|
233
|
+
/** 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. */
|
|
223
234
|
onUpdate?: (u: U) => void;
|
|
224
235
|
}
|
|
225
|
-
/**
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
/**
|
|
237
|
+
* A started call (WP §8.3). Its progress is a stream, the run itself a promise:
|
|
238
|
+
* `for await (const u of run)` the updates until the run ended, `await run.next()` one update
|
|
239
|
+
* (`undefined` once the run ended), or `onUpdate` in the call options (the SDK reads them);
|
|
240
|
+
* `await run` (or `.then`/`.catch`) the result, rejecting with the `SynapseError` of a
|
|
241
|
+
* `failed` or `canceled` run; `cancel()`.
|
|
242
|
+
*/
|
|
243
|
+
export interface Run<U, R> extends AsyncDisposable, PromiseLike<R>, AsyncIterable<U> {
|
|
228
244
|
readonly id: string;
|
|
229
245
|
readonly status: RunStatus;
|
|
230
|
-
/** The next progress update; `undefined` once the
|
|
231
|
-
|
|
232
|
-
|
|
246
|
+
/** The next progress update; `undefined` once the run ended (then `await run` settles at once). */
|
|
247
|
+
next(): Promise<U | undefined>;
|
|
248
|
+
/** `await run` / `.then` as on a promise (returns a `Promise`, so `.then(...).catch(...)` chains). */
|
|
249
|
+
then<A = R, B = never>(onfulfilled?: ((value: R) => A | PromiseLike<A>) | null, onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<A | B>;
|
|
250
|
+
/** The result as a plain promise (what `await run` waits for). */
|
|
233
251
|
readonly result: Promise<R>;
|
|
252
|
+
/** `.catch` as on a promise: `onRejected` gets the run's `SynapseError` when it failed or was canceled. */
|
|
253
|
+
catch<B = never>(onRejected?: ((e: unknown) => B | PromiseLike<B>) | null): Promise<R | B>;
|
|
254
|
+
/** `.finally` as on a promise: `onFinally` runs once the run ended, whichever way. */
|
|
255
|
+
finally(onFinally?: (() => void) | null): Promise<R>;
|
|
234
256
|
/** Cooperative cancel: tells the broker, resolves on its `canceled` frame. */
|
|
235
257
|
cancel(reason?: string): Promise<void>;
|
|
236
258
|
}
|
|
237
|
-
export type CallEvent<U, R> = {
|
|
238
|
-
type: "update";
|
|
239
|
-
data: U;
|
|
240
|
-
} | {
|
|
241
|
-
type: "result";
|
|
242
|
-
data: R;
|
|
243
|
-
};
|
|
244
259
|
export interface ServeContext<U> {
|
|
245
260
|
readonly call: string;
|
|
246
261
|
readonly caller: string;
|
|
@@ -306,18 +321,25 @@ export type StoreChange = {
|
|
|
306
321
|
by: string | undefined;
|
|
307
322
|
};
|
|
308
323
|
/**
|
|
309
|
-
* A namespace as a value kept current (SDK §9): `
|
|
310
|
-
* value shows the write, `synced` says whether it is current, `watch()`
|
|
311
|
-
* decides every write (`not_owner`, `unauthorized`, `store_gone`).
|
|
324
|
+
* A namespace as a value kept current (SDK §9): `get()` reads it, `set`/`delete` resolve once the
|
|
325
|
+
* value shows the write, `synced` says whether it is current, `onChange`/`watch()` follow changes. The runtime
|
|
326
|
+
* decides every write (`not_owner`, `unauthorized`, `store_gone`). A write made while the handle is
|
|
327
|
+
* between incarnations (its intent being replayed, e.g. after a project activation) waits for the
|
|
328
|
+
* handle to bind again and is decided then; it is refused `store_gone` only if the handle ends first.
|
|
312
329
|
*/
|
|
313
330
|
export interface StoreHandle extends AsyncDisposable {
|
|
314
331
|
readonly name: string;
|
|
315
332
|
/** The whole value, what the runtime has (the same object while nothing changed). */
|
|
316
|
-
|
|
333
|
+
get(): Record<string, unknown>;
|
|
334
|
+
/** One key's value (`undefined` when absent). */
|
|
317
335
|
get(key: string): unknown;
|
|
336
|
+
/** The one boolean a node author checks: `status.get().synced`. */
|
|
318
337
|
readonly synced: boolean;
|
|
319
|
-
|
|
320
|
-
|
|
338
|
+
/** The handle's own state, a state value: `get()` now (the same object while unchanged), `onChange` for each change, `changed()` for the next. */
|
|
339
|
+
readonly status: StateValue<StoreStatus>;
|
|
340
|
+
/** Resolves once the value is in: current as of the open (the version the runtime answered then applied), or the namespace absent. */
|
|
341
|
+
ready(): Promise<void>;
|
|
342
|
+
/** `set(key, value)`, or `set({ ... })` for several keys as one atomic write; resolves once `get()` shows it. */
|
|
321
343
|
set(key: string, value: unknown): Promise<void>;
|
|
322
344
|
set(values: Record<string, unknown>): Promise<void>;
|
|
323
345
|
delete(key: string): Promise<void>;
|
|
@@ -326,8 +348,11 @@ export interface StoreHandle extends AsyncDisposable {
|
|
|
326
348
|
destroy(): Promise<void>;
|
|
327
349
|
/** The whole value after each change; with `key`, that key's value each time it changes (`undefined` after a delete). */
|
|
328
350
|
watch(key?: string): AsyncIterable<unknown>;
|
|
329
|
-
/** The
|
|
330
|
-
|
|
351
|
+
/** 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. */
|
|
352
|
+
changed(): Promise<Record<string, unknown>>;
|
|
353
|
+
changed(key: string): Promise<unknown>;
|
|
354
|
+
/** `cb` with the whole value on every change of the value or the status (the framework bridge); returns the remover. */
|
|
355
|
+
onChange(cb: (value: Record<string, unknown>) => void): () => void;
|
|
331
356
|
close(): void;
|
|
332
357
|
}
|
|
333
358
|
export type Vec3 = [number, number, number];
|
|
@@ -341,6 +366,11 @@ export interface Xform {
|
|
|
341
366
|
}
|
|
342
367
|
/** Which plane an entity lives in: `authored` (the project's), `calibration` (the robot's), `live` (never persisted). */
|
|
343
368
|
export type SceneLayer = "authored" | "calibration" | "live";
|
|
369
|
+
/**
|
|
370
|
+
* What an entity is. Any string is a kind; these are offered for completion: `frame` and `robot` are the two the runtime
|
|
371
|
+
* gives meaning to, `model` and `scan` the common others.
|
|
372
|
+
*/
|
|
373
|
+
export type SceneKind = "frame" | "robot" | "model" | "scan" | (string & {});
|
|
344
374
|
/** Each flag holds only when it is `true`. `locked` refuses patch/reparent/rename/despawn (a flags-only patch passes). */
|
|
345
375
|
export interface SceneFlags {
|
|
346
376
|
locked?: boolean;
|
|
@@ -414,8 +444,11 @@ export interface Hardpoint {
|
|
|
414
444
|
export interface SceneEntity {
|
|
415
445
|
/** A ULID; never changes. */
|
|
416
446
|
id: string;
|
|
417
|
-
kind:
|
|
418
|
-
/**
|
|
447
|
+
kind: SceneKind;
|
|
448
|
+
/**
|
|
449
|
+
* Unique across every plane (authored, calibration, live): a write that would give an entity a name another entity
|
|
450
|
+
* holds is refused `name_taken`, its `data.holder` the id holding the name (`data.kind`, `data.layer`: the holder's).
|
|
451
|
+
*/
|
|
419
452
|
name?: string;
|
|
420
453
|
/** The parent's id; `null` is the world root. */
|
|
421
454
|
parent: string | null;
|
|
@@ -450,7 +483,7 @@ export interface SceneEntity {
|
|
|
450
483
|
[key: string]: unknown;
|
|
451
484
|
}
|
|
452
485
|
/**
|
|
453
|
-
* An id's live row (`
|
|
486
|
+
* An id's live row (`view.live.get()`), each key present only when the row has it:
|
|
454
487
|
* `entity` is a live-layer entity's upsert row; `pose`, `joints`, `state` are the lanes; `stamp` is the
|
|
455
488
|
* newest lane stamp (the runtime's clock).
|
|
456
489
|
*/
|
|
@@ -476,15 +509,99 @@ export interface SceneTreeNode {
|
|
|
476
509
|
export type SceneTree = SceneTreeNode[];
|
|
477
510
|
/**
|
|
478
511
|
* A value of the scene, kept current: `get()` the value now (the same object until it changes),
|
|
479
|
-
* `
|
|
480
|
-
* handle's copy of the scene (no subscription of its own) and ends with the handle.
|
|
512
|
+
* `onChange` for each change (the framework bridge), `changed()` for the next one, `watch()` for each one in a
|
|
513
|
+
* loop. It reads the handle's copy of the scene (no subscription of its own) and ends with the handle.
|
|
481
514
|
*/
|
|
482
515
|
export interface SceneView<T> {
|
|
483
516
|
get(): T;
|
|
484
|
-
|
|
485
|
-
|
|
517
|
+
/** `cb` with the value on each change; returns the remover. */
|
|
518
|
+
onChange(cb: (value: T) => void): () => void;
|
|
519
|
+
/** The value after each change (state, not history: changes in between fold into the latest); ends with the handle. */
|
|
520
|
+
watch(): AsyncIterable<T>;
|
|
521
|
+
/** Resolves with the value after its next change relative to the value at the call: a change already readable when it is called is not its change (with the value as it is when the handle closes). */
|
|
486
522
|
changed(): Promise<T>;
|
|
487
523
|
}
|
|
524
|
+
/** Which entity an entity view follows: an id (that entity only), or a name (whichever entity holds it now, on any plane). */
|
|
525
|
+
export type SceneEntityRef = string | {
|
|
526
|
+
name: string;
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* One entity as a view (`scene.entity(id)`, `scene.entity({ name })`), shaped like a store handle: `get()` is the
|
|
530
|
+
* merged entity now (authored with its live overrides; `undefined` when there is none), `get(component)` one of its
|
|
531
|
+
* components; `watch()` follows it. It is a `SceneView` too (`onChange`, `changed()`), so it fits wherever a view
|
|
532
|
+
* does. The scene's two planes are its two namespaces: `authored` (`get`, `set`: the entity as declared, one patch)
|
|
533
|
+
* and `live` (`get`: the live row with its `stamp`; `set`: the lanes; `remove`: the row). They have no `watch`,
|
|
534
|
+
* `onChange` or `changed` of their own: the view's cover the entity, whichever plane changed.
|
|
535
|
+
*
|
|
536
|
+
* A view by name follows the name: when the entity holding it is despawned and another takes the name, the view
|
|
537
|
+
* reads the new one (a view by id reads `undefined` from then on).
|
|
538
|
+
*/
|
|
539
|
+
export interface SceneEntityView extends SceneView<SceneEntity | undefined> {
|
|
540
|
+
/**
|
|
541
|
+
* The id of the entity the view names now: a view by id (what `upsert` returns), its id; a view by name, the id
|
|
542
|
+
* holding the name now (none holds it: `not_found`, `data: { name }`).
|
|
543
|
+
*/
|
|
544
|
+
readonly id: string;
|
|
545
|
+
/** The merged entity now; `undefined` when there is none (absent, or the handle detached or closed). */
|
|
546
|
+
get(): SceneEntity | undefined;
|
|
547
|
+
/** One component of the merged entity now (`get("xform")`); `undefined` when the entity or the component is absent. */
|
|
548
|
+
get<K extends keyof SceneEntity & string>(component: K): SceneEntity[K] | undefined;
|
|
549
|
+
/** The entity's authored plane: one object for the view's life. */
|
|
550
|
+
readonly authored: EntityAuthored;
|
|
551
|
+
/** The entity's live plane: one object for the view's life. */
|
|
552
|
+
readonly live: EntityLive;
|
|
553
|
+
/**
|
|
554
|
+
* The entity after each change (`undefined` once it is gone); with `component`, that component each time it
|
|
555
|
+
* changes. It is state, not history: changes in between are folded, the latest wins. Ends with the handle.
|
|
556
|
+
*/
|
|
557
|
+
watch(): AsyncIterable<SceneEntity | undefined>;
|
|
558
|
+
watch<K extends keyof SceneEntity & string>(component: K): AsyncIterable<SceneEntity[K] | undefined>;
|
|
559
|
+
/** The entity after its next change; with `component`, that component after its next change (`undefined` once gone). The value now once the handle ends. */
|
|
560
|
+
changed(): Promise<SceneEntity | undefined>;
|
|
561
|
+
changed<K extends keyof SceneEntity & string>(component: K): Promise<SceneEntity[K] | undefined>;
|
|
562
|
+
}
|
|
563
|
+
/** An entity view's authored plane (`view.authored`). Reads as the view does: nothing once the handle is detached or closed. */
|
|
564
|
+
export interface EntityAuthored {
|
|
565
|
+
/** The entity as declared (the authored entity, or a live entity's upsert row), without the live lanes; its object changes only when that value does. */
|
|
566
|
+
get(): SceneEntity | undefined;
|
|
567
|
+
get<K extends keyof SceneEntity & string>(component: K): SceneEntity[K] | undefined;
|
|
568
|
+
/**
|
|
569
|
+
* `set(component, value)`, or `set({ ...components })` for several: one `patch` of the authored entity the view
|
|
570
|
+
* names now (by name: the id holding the name at the call), resolved once the entities show it; the runtime's
|
|
571
|
+
* refusal is the error. A view by id sends the patch as `scene.authored.patch(id, …)` does (an id the runtime does
|
|
572
|
+
* not have: its `not_found`). A view by name whose name no entity holds has no id to send to: `not_found`
|
|
573
|
+
* (`data: { name }`), nothing sent. Detached: `project_inactive`.
|
|
574
|
+
*/
|
|
575
|
+
set<K extends keyof SceneEntity & string>(component: K, value: SceneEntity[K]): Promise<void>;
|
|
576
|
+
set(components: Partial<SceneEntity>): Promise<void>;
|
|
577
|
+
}
|
|
578
|
+
/** An entity view's live plane (`view.live`). Reads as the view does: nothing once the handle is detached or closed. */
|
|
579
|
+
export interface EntityLive {
|
|
580
|
+
/** The entity's live row ({@link SceneEntityLive}, its `stamp` included); `undefined` without one. */
|
|
581
|
+
get(): SceneEntityLive | undefined;
|
|
582
|
+
get<K extends keyof SceneEntityLive & string>(component: K): SceneEntityLive[K] | undefined;
|
|
583
|
+
/**
|
|
584
|
+
* Writes the live lanes of the entity the view names now, as one `scene.live.batch` (synchronous, like `publish`):
|
|
585
|
+
* `xform: { t, q }` the pose lane (both required, nothing else), `joints` the joints lane, `state` the state lane; any
|
|
586
|
+
* other component is `invalid_input`. `lifetimeMs`: the override expires that long after its last write (the entity's
|
|
587
|
+
* authored values show again); a live entity keeps the lifetime it was upserted with. The first write for an id this
|
|
588
|
+
* node has not claimed asks the claim (see `scene.live.batch`), so a single write lands. A view by name whose name no
|
|
589
|
+
* entity holds: `not_found`. Not attached: dropped and counted (`status.get().detachedDrops`). Closed, or the node
|
|
590
|
+
* ended: `not_connected`.
|
|
591
|
+
*/
|
|
592
|
+
set(components: {
|
|
593
|
+
xform?: {
|
|
594
|
+
t: Vec3;
|
|
595
|
+
q: Quat;
|
|
596
|
+
};
|
|
597
|
+
joints?: Record<string, number>;
|
|
598
|
+
state?: Record<string, unknown>;
|
|
599
|
+
}, opts?: {
|
|
600
|
+
lifetimeMs?: number;
|
|
601
|
+
}): void;
|
|
602
|
+
/** Releases this node's claim of the entity the view names now (`scene.live.remove(id)`, the call): the runtime retracts its live row. */
|
|
603
|
+
remove(): Promise<void>;
|
|
604
|
+
}
|
|
488
605
|
/** `attached`: the scene of the active project is in; `detached`: none is (no project active, a session gap, between incarnations); `closed`: for good. */
|
|
489
606
|
export type SceneState = "attached" | "detached" | "closed";
|
|
490
607
|
export interface SceneStatus {
|
|
@@ -496,10 +613,12 @@ export interface SceneStatus {
|
|
|
496
613
|
/** The authored plane's version and hash as this handle has them (0 and "" while detached). */
|
|
497
614
|
version: number;
|
|
498
615
|
hash: string;
|
|
499
|
-
/** `live
|
|
616
|
+
/** Live writes (`scene.live.batch`, `view.live.set`) dropped because the handle was not attached. */
|
|
500
617
|
detachedDrops: number;
|
|
618
|
+
/** Lane rows dropped because their id's claim was refused (rows parked while it was asked, and rows written while refused). */
|
|
619
|
+
unclaimedDrops: number;
|
|
501
620
|
}
|
|
502
|
-
/** An authored change as the runtime applied it (the `
|
|
621
|
+
/** An authored change as the runtime applied it (a diff op of the scene's `authored` track). */
|
|
503
622
|
export type SceneDiffOp = {
|
|
504
623
|
op: "spawn";
|
|
505
624
|
entity: SceneEntity;
|
|
@@ -563,27 +682,27 @@ export type SceneOp = {
|
|
|
563
682
|
*/
|
|
564
683
|
export type SceneSpawnOptions = {
|
|
565
684
|
name: string;
|
|
566
|
-
parent?: string | null;
|
|
685
|
+
parent?: string | SceneEntityView | null;
|
|
567
686
|
id?: string;
|
|
568
687
|
} & Partial<Omit<SceneEntity, "id" | "kind" | "name" | "parent" | "order" | "meta">>;
|
|
569
|
-
/** Where `reparent` puts the entity among its new siblings (default: after the last). */
|
|
688
|
+
/** Where `reparent` puts the entity among its new siblings (default: after the last): a sibling by its id or its view. */
|
|
570
689
|
export type ScenePlace = {
|
|
571
|
-
before: string;
|
|
690
|
+
before: string | SceneEntityView;
|
|
572
691
|
} | {
|
|
573
|
-
after: string;
|
|
692
|
+
after: string | SceneEntityView;
|
|
574
693
|
};
|
|
575
|
-
/** One authoring batch being built (`txn`): sent as one atomic `apply`. */
|
|
694
|
+
/** One authoring batch being built (`txn`): sent as one atomic `apply`. An entity is named by its id or by a view of it (`upsert`'s result). */
|
|
576
695
|
export interface SceneTxn {
|
|
577
696
|
/** Spawns an entity of `kind`; returns its id. */
|
|
578
|
-
spawn(kind:
|
|
579
|
-
/** Sets components of
|
|
580
|
-
patch(
|
|
581
|
-
rename(
|
|
582
|
-
reparent(
|
|
583
|
-
despawn(
|
|
697
|
+
spawn(kind: SceneKind, opts: SceneSpawnOptions): string;
|
|
698
|
+
/** Sets components of the entity (an object merges into an object one level deep). */
|
|
699
|
+
patch(entity: string | SceneEntityView, set: Partial<SceneEntity>): void;
|
|
700
|
+
rename(entity: string | SceneEntityView, name: string): void;
|
|
701
|
+
reparent(entity: string | SceneEntityView, parent: string | SceneEntityView | null, place?: ScenePlace): void;
|
|
702
|
+
despawn(entity: string | SceneEntityView, opts?: {
|
|
584
703
|
cascade?: boolean;
|
|
585
704
|
}): void;
|
|
586
|
-
setLocked(
|
|
705
|
+
setLocked(entity: string | SceneEntityView, locked: boolean): void;
|
|
587
706
|
/** The ops as they will be sent. */
|
|
588
707
|
readonly ops: readonly SceneOp[];
|
|
589
708
|
}
|
|
@@ -600,77 +719,133 @@ export interface LiveState {
|
|
|
600
719
|
id: string;
|
|
601
720
|
s: Record<string, unknown>;
|
|
602
721
|
}
|
|
603
|
-
/**
|
|
722
|
+
/**
|
|
723
|
+
* One live batch: data only, the lanes (`poses`, `joints`, `state`). `lifetimeMs` makes the override rows the batch
|
|
724
|
+
* touches expire. Claims are calls, never rows: `scene.live.upsert` and `scene.live.remove`.
|
|
725
|
+
*/
|
|
604
726
|
export interface LiveBatch {
|
|
605
|
-
upsert?: SceneEntity[];
|
|
606
727
|
poses?: LivePose[];
|
|
607
728
|
joints?: LiveJoints[];
|
|
608
729
|
state?: LiveState[];
|
|
609
|
-
remove?: string[];
|
|
610
730
|
lifetimeMs?: number;
|
|
731
|
+
upsert?: never;
|
|
732
|
+
remove?: never;
|
|
611
733
|
}
|
|
612
|
-
/** A live row removed: the writer removed it, its lifetime ran out, or its
|
|
734
|
+
/** A live row removed: the writer removed it, its lifetime ran out, its writer left, or its entity (or a parent) was despawned (the claim is released). */
|
|
613
735
|
export interface LiveRemoved {
|
|
614
736
|
id: string;
|
|
615
|
-
by: "explicit" | "timeout" | "writer_gone";
|
|
737
|
+
by: "explicit" | "timeout" | "writer_gone" | "despawned";
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* The authored plane's verbs (`scene.authored`): the editor's (`spawn`, `patch`, `rename`, `reparent`, `despawn`,
|
|
741
|
+
* `setLocked`, `txn`, `apply`) and the node author's `upsert`. Every write goes to the runtime, which decides it, and
|
|
742
|
+
* resolves once the entities show it; a refusal is the runtime's error. Detached: `project_inactive`. An entity is
|
|
743
|
+
* named by its id or by a view of it.
|
|
744
|
+
*/
|
|
745
|
+
export interface SceneAuthored {
|
|
746
|
+
/** One atomic batch; `base`: the version its structural ops assume. */
|
|
747
|
+
apply(ops: SceneOp[], base?: number): Promise<void>;
|
|
748
|
+
/** The ops `build` makes, as one atomic batch. */
|
|
749
|
+
txn(build: (t: SceneTxn) => void): Promise<void>;
|
|
750
|
+
/** Spawns an entity; its id, once the entities show it. */
|
|
751
|
+
spawn(kind: SceneKind, opts: SceneSpawnOptions): Promise<string>;
|
|
752
|
+
patch(entity: string | SceneEntityView, set: Partial<SceneEntity>): Promise<void>;
|
|
753
|
+
/**
|
|
754
|
+
* A builder's one verb, keyed by `name`; resolves with the entity's view (by id: that entity) once the entities show
|
|
755
|
+
* it. The id is the runtime's answer, never the copy's: `spawn(kind, { name, parent, ...components })` (a fresh id) is
|
|
756
|
+
* sent first; the runtime refusing it `name_taken` names the holder and its kind (`data.holder`, `data.kind`,
|
|
757
|
+
* `data.layer`). A holder of this kind in the authored or calibration layer is patched with the components that differ
|
|
758
|
+
* from what the copy shows of it (one-level merge; live overrides never count; all of them when the copy does not show
|
|
759
|
+
* it yet; equal, nothing more is sent): the copy only trims the patch, it never decides. A holder of another kind, a
|
|
760
|
+
* live one, or an answer without them: the `name_taken` as it came. `parent` is used by the spawn only: an existing
|
|
761
|
+
* entity is not reparented. Any other refusal as it came (`not_found` on the patch: the entity went meanwhile; upsert
|
|
762
|
+
* again). An `id` component: `invalid_input` (`data: { component: "id" }`), nothing sent.
|
|
763
|
+
*/
|
|
764
|
+
upsert(kind: SceneKind, opts: SceneSpawnOptions & {
|
|
765
|
+
id?: never;
|
|
766
|
+
}): Promise<SceneEntityView>;
|
|
767
|
+
rename(entity: string | SceneEntityView, name: string): Promise<void>;
|
|
768
|
+
reparent(entity: string | SceneEntityView, parent: string | SceneEntityView | null, place?: ScenePlace): Promise<void>;
|
|
769
|
+
despawn(entity: string | SceneEntityView, opts?: {
|
|
770
|
+
cascade?: boolean;
|
|
771
|
+
}): Promise<void>;
|
|
772
|
+
setLocked(entity: string | SceneEntityView, locked: boolean): Promise<void>;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* The live plane's verbs (`scene.live`). Decisions are calls, data is streams (PLAN v0.31): a claim is the call
|
|
776
|
+
* `synapse/scene/live/upsert` (`upsert`), a release the call `synapse/scene/live/remove` (`remove`); the lanes go on this
|
|
777
|
+
* node's live track (made on the first write of each session), synchronously like `publish` (`batch`, `view.live.set`).
|
|
778
|
+
* The runtime decides identity and ownership; nothing here reads the entities to decide what to send.
|
|
779
|
+
*/
|
|
780
|
+
export interface SceneLive {
|
|
781
|
+
/**
|
|
782
|
+
* One batch of lanes (the sim's one call per tick); returns at once. A row goes out only for an id this node claimed in
|
|
783
|
+
* the bound incarnation: a live entity of its `upsert`, or the override of an entity of the scene, which the first row
|
|
784
|
+
* of its id asks for (the call `upsert {id}`, once). Until the answer the latest row of each lane of that id is kept
|
|
785
|
+
* (latest wins) and sent once the claim is granted, so a single write lands; a refusal drops them (counted in
|
|
786
|
+
* `status.get().unclaimedDrops` with the rows written while refused), is a node notice (`node.notices`) and is asked
|
|
787
|
+
* again, paced, by a later row. The runtime stamps the rows; a row it does not apply comes back on `watchLiveDenied`.
|
|
788
|
+
* Not attached: dropped and counted (`status.get().detachedDrops`). Closed: `not_connected`. Never sent again.
|
|
789
|
+
*/
|
|
790
|
+
batch(batch: LiveBatch): void;
|
|
791
|
+
/**
|
|
792
|
+
* A live entity of this node's: the call `synapse/scene/live/upsert {kind, name?, parent, lifetime_ms?, ...components}`;
|
|
793
|
+
* resolves with its view (by the id the runtime answered: minted for a new one; a name this node already claimed sends
|
|
794
|
+
* that claim's id). Rejects with the runtime's refusal as it came (`name_taken {name, id, holder}`, `not_owner`,
|
|
795
|
+
* `conflict`, `invalid_tree`, `project_inactive`, `unauthorized`, ...): nothing is kept. Granted, it is this node's claim:
|
|
796
|
+
* claimed again, same id, on every attach (a new session, a project switch, a scene restart; a refused re-claim is
|
|
797
|
+
* asked again, paced, with a warning), until `remove`. A session that goes before the answer: asked at the next
|
|
798
|
+
* attach. Detached: `project_inactive`; the node ending: `not_connected`. An `id` component: `invalid_input`
|
|
799
|
+
* (`data: { component: "id" }`), nothing sent.
|
|
800
|
+
*/
|
|
801
|
+
upsert(kind: SceneKind, opts?: Omit<SceneSpawnOptions, "name" | "id"> & {
|
|
802
|
+
name?: string;
|
|
803
|
+
id?: never;
|
|
804
|
+
}, live?: {
|
|
805
|
+
lifetimeMs?: number;
|
|
806
|
+
}): Promise<SceneEntityView>;
|
|
807
|
+
/** Releases this node's claim of the entity: the call `synapse/scene/live/remove {id}` (the runtime retracts its row); the claim is not made again. Not held in the bound incarnation: resolves at once. */
|
|
808
|
+
remove(entity: string | SceneEntityView): Promise<void>;
|
|
616
809
|
}
|
|
617
810
|
/**
|
|
618
811
|
* The active project's scene as this node sees it (PLAN v0.24 §5): authored ⊕ live, kept current.
|
|
619
|
-
* Views and feeds read the handle's copy of the scene;
|
|
620
|
-
*
|
|
812
|
+
* Views and feeds read the handle's copy of the scene; the writes are in `authored` (the authored plane) and `live`
|
|
813
|
+
* (this node's live rows).
|
|
621
814
|
*/
|
|
622
815
|
export interface SceneHandle extends AsyncDisposable {
|
|
623
816
|
/** The entities now, authored with the live overrides applied; the same object while nothing changed. */
|
|
624
|
-
|
|
817
|
+
get(): Readonly<Record<string, SceneEntity>>;
|
|
625
818
|
/**
|
|
626
|
-
* One entity as a view (
|
|
627
|
-
* the
|
|
628
|
-
* declared without the live lanes (the authored entity, or a live entity's upsert row); `live` is
|
|
629
|
-
* the id's live row (`SceneEntityLive`), `undefined` without one. Without `plane`: merged.
|
|
819
|
+
* One entity as a view ({@link SceneEntityView}), by id or by name (`entity({ name: "tool" })`: the entity holding
|
|
820
|
+
* the name now): merged, authored with its live overrides; one plane alone through the view (`view.authored`, `view.live`).
|
|
630
821
|
*/
|
|
631
|
-
entity(
|
|
632
|
-
plane?: "authored";
|
|
633
|
-
}): SceneView<SceneEntity | undefined>;
|
|
634
|
-
entity(id: string, opts: {
|
|
635
|
-
plane: "live";
|
|
636
|
-
}): SceneView<SceneEntityLive | undefined>;
|
|
637
|
-
entity(id: string, opts?: {
|
|
638
|
-
plane?: "authored" | "live";
|
|
639
|
-
}): SceneView<SceneEntity | SceneEntityLive | undefined>;
|
|
822
|
+
entity(ref: SceneEntityRef): SceneEntityView;
|
|
640
823
|
/** The hierarchy as a view. */
|
|
641
824
|
tree(): SceneView<SceneTree>;
|
|
642
825
|
/** The entities for which `where` holds (in id order), as a view. */
|
|
643
826
|
query(where: (e: SceneEntity) => boolean): SceneView<SceneEntity[]>;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
827
|
+
/** The handle's own state, a state value: `get()` now (the same object while unchanged), `onChange` for each change, `changed()` for the next. */
|
|
828
|
+
readonly status: StateValue<SceneStatus>;
|
|
829
|
+
/** The one boolean a node author checks: `status.get().synced`. */
|
|
830
|
+
readonly synced: boolean;
|
|
831
|
+
/** 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. */
|
|
832
|
+
ready(): Promise<void>;
|
|
833
|
+
/** The entities after their next change (state, not history); `not_connected` once the handle is closed. */
|
|
834
|
+
changed(): Promise<Readonly<Record<string, SceneEntity>>>;
|
|
835
|
+
/** The entities after each change (state, not history: changes in between fold into the latest); ends with the handle. */
|
|
836
|
+
watch(): AsyncIterable<Readonly<Record<string, SceneEntity>>>;
|
|
837
|
+
/** `cb` with the entities on every change of the entities or the status (the framework bridge); returns the remover. */
|
|
838
|
+
onChange(cb: (entities: Readonly<Record<string, SceneEntity>>) => void): () => void;
|
|
648
839
|
/** The live rows removed, one batch per runtime tick that removed some. */
|
|
649
840
|
watchLiveRemovals(cb: (batch: LiveRemoved[]) => void): () => void;
|
|
650
841
|
watchLiveRemovals(): AsyncIterableIterator<LiveRemoved[]>;
|
|
651
842
|
/** The ids of this node's live writes the runtime dropped because another writer owns them, one batch per denied batch. */
|
|
652
843
|
watchLiveDenied(cb: (ids: string[]) => void): () => void;
|
|
653
844
|
watchLiveDenied(): AsyncIterableIterator<string[]>;
|
|
654
|
-
/** The
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
apply(ops: SceneOp[], base?: number): Promise<void>;
|
|
659
|
-
/** The ops `build` makes, as one atomic batch. */
|
|
660
|
-
txn(build: (t: SceneTxn) => void): Promise<void>;
|
|
661
|
-
/** Spawns an entity; its id, once the entities show it. */
|
|
662
|
-
spawn(kind: string, opts: SceneSpawnOptions): Promise<string>;
|
|
663
|
-
patch(id: string, set: Partial<SceneEntity>): Promise<void>;
|
|
664
|
-
rename(id: string, name: string): Promise<void>;
|
|
665
|
-
reparent(id: string, parent: string | null, place?: ScenePlace): Promise<void>;
|
|
666
|
-
despawn(id: string, opts?: {
|
|
667
|
-
cascade?: boolean;
|
|
668
|
-
}): Promise<void>;
|
|
669
|
-
setLocked(id: string, locked: boolean): Promise<void>;
|
|
670
|
-
/** Writes one live batch on this node's live track; returns at once (like `publish`). Not attached: dropped and counted (`status.detachedDrops`). */
|
|
671
|
-
live(batch: LiveBatch): void;
|
|
672
|
-
/** The framework bridge (React): called on every change of the entities or the status. */
|
|
673
|
-
subscribe(listener: () => void): () => void;
|
|
845
|
+
/** The authored plane's verbs. */
|
|
846
|
+
readonly authored: SceneAuthored;
|
|
847
|
+
/** This node's live writes. */
|
|
848
|
+
readonly live: SceneLive;
|
|
674
849
|
/** Ends this handle: its verbs fail `not_connected`, its views and feeds end; the node's other scene handles go on. */
|
|
675
850
|
close(): void;
|
|
676
851
|
}
|
|
@@ -748,21 +923,28 @@ export interface TaskCatalogEntry {
|
|
|
748
923
|
/** A value the runtime keeps current that this node only reads: it has no writes. */
|
|
749
924
|
export interface ReadonlyStoreHandle<V> {
|
|
750
925
|
/** The value now; the same object while nothing changed, and an unchanged part keeps its object when another part changes. */
|
|
751
|
-
|
|
926
|
+
get(): V;
|
|
752
927
|
/** `true` while the value is current. */
|
|
753
928
|
readonly synced: boolean;
|
|
754
929
|
/** Resolves once the value is current (the first time). */
|
|
755
930
|
ready(): Promise<void>;
|
|
756
931
|
/** The whole value after each change. It is state, not history: a reader slower than the changes sees the latest value. */
|
|
757
932
|
watch(): AsyncIterable<V>;
|
|
758
|
-
/**
|
|
759
|
-
|
|
933
|
+
/** `cb` with the value on every change of the value or of `synced` (the framework bridge); returns the remover. */
|
|
934
|
+
onChange(cb: (value: V) => void): () => void;
|
|
760
935
|
}
|
|
761
936
|
/** The robot's task declarations, `instance` → declaration (operators write them). The node owns this handle: it has no `close` or `destroy`. */
|
|
762
|
-
export interface TaskDeclarations extends Omit<StoreHandle, "name" | "
|
|
763
|
-
|
|
937
|
+
export interface TaskDeclarations extends Omit<StoreHandle, "name" | "get" | "set" | "watch" | "destroy" | "close" | typeof Symbol.asyncDispose> {
|
|
938
|
+
/** Every declaration, `instance` → declaration. */
|
|
939
|
+
get(): Readonly<Record<string, TaskDeclaration>>;
|
|
764
940
|
get(instance: string): TaskDeclaration | undefined;
|
|
765
|
-
/**
|
|
941
|
+
/**
|
|
942
|
+
* `set(declaration)`: a new declaration under an instance the SDK mints (a ulid), resolving with that instance;
|
|
943
|
+
* `set(instance, declaration)` writes the one named; `set({ ... })` several as one atomic write. One object is a
|
|
944
|
+
* declaration when its `ref` is a string, else a map `instance` → declaration. Each waits for the value to be current
|
|
945
|
+
* first and resolves once `get()` shows the write.
|
|
946
|
+
*/
|
|
947
|
+
set(declaration: TaskDeclaration): Promise<string>;
|
|
766
948
|
set(instance: string, declaration: TaskDeclaration): Promise<void>;
|
|
767
949
|
set(declarations: Record<string, TaskDeclaration>): Promise<void>;
|
|
768
950
|
/** The whole value after each change; with `instance`, that declaration each time it changes (`undefined` after a delete). */
|