@synapse-sdk/client 0.3.3 → 0.3.4
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 +7 -1
- package/dist/calls.js +19 -0
- package/dist/node.d.ts +62 -24
- package/dist/node.js +205 -66
- package/dist/scene.d.ts +150 -56
- package/dist/scene.js +995 -163
- 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 +4 -2
- package/dist/store.d.ts +22 -11
- package/dist/store.js +112 -26
- package/dist/synapse.d.ts +72 -45
- package/dist/synapse.js +179 -112
- 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 +268 -97
- 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:4443/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";
|
|
@@ -306,18 +316,25 @@ export type StoreChange = {
|
|
|
306
316
|
by: string | undefined;
|
|
307
317
|
};
|
|
308
318
|
/**
|
|
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`).
|
|
319
|
+
* A namespace as a value kept current (SDK §9): `get()` reads it, `set`/`delete` resolve once the
|
|
320
|
+
* value shows the write, `synced` says whether it is current, `onChange`/`watch()` follow changes. The runtime
|
|
321
|
+
* decides every write (`not_owner`, `unauthorized`, `store_gone`). A write made while the handle is
|
|
322
|
+
* between incarnations (its intent being replayed, e.g. after a project activation) waits for the
|
|
323
|
+
* handle to bind again and is decided then; it is refused `store_gone` only if the handle ends first.
|
|
312
324
|
*/
|
|
313
325
|
export interface StoreHandle extends AsyncDisposable {
|
|
314
326
|
readonly name: string;
|
|
315
327
|
/** The whole value, what the runtime has (the same object while nothing changed). */
|
|
316
|
-
|
|
328
|
+
get(): Record<string, unknown>;
|
|
329
|
+
/** One key's value (`undefined` when absent). */
|
|
317
330
|
get(key: string): unknown;
|
|
331
|
+
/** The one boolean a node author checks: `status.get().synced`. */
|
|
318
332
|
readonly synced: boolean;
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
/** The handle's own state, a state value: `get()` now (the same object while unchanged), `onChange` for each change, `changed()` for the next. */
|
|
334
|
+
readonly status: StateValue<StoreStatus>;
|
|
335
|
+
/** Resolves once the first value is in (the first snapshot applied, or the namespace absent). */
|
|
336
|
+
ready(): Promise<void>;
|
|
337
|
+
/** `set(key, value)`, or `set({ ... })` for several keys as one atomic write; resolves once `get()` shows it. */
|
|
321
338
|
set(key: string, value: unknown): Promise<void>;
|
|
322
339
|
set(values: Record<string, unknown>): Promise<void>;
|
|
323
340
|
delete(key: string): Promise<void>;
|
|
@@ -326,8 +343,10 @@ export interface StoreHandle extends AsyncDisposable {
|
|
|
326
343
|
destroy(): Promise<void>;
|
|
327
344
|
/** The whole value after each change; with `key`, that key's value each time it changes (`undefined` after a delete). */
|
|
328
345
|
watch(key?: string): AsyncIterable<unknown>;
|
|
329
|
-
/** The
|
|
330
|
-
|
|
346
|
+
/** The whole value after its next change; `store_gone` once this handle is closed. */
|
|
347
|
+
changed(): Promise<Record<string, unknown>>;
|
|
348
|
+
/** `cb` with the whole value on every change of the value or the status (the framework bridge); returns the remover. */
|
|
349
|
+
onChange(cb: (value: Record<string, unknown>) => void): () => void;
|
|
331
350
|
close(): void;
|
|
332
351
|
}
|
|
333
352
|
export type Vec3 = [number, number, number];
|
|
@@ -341,6 +360,11 @@ export interface Xform {
|
|
|
341
360
|
}
|
|
342
361
|
/** Which plane an entity lives in: `authored` (the project's), `calibration` (the robot's), `live` (never persisted). */
|
|
343
362
|
export type SceneLayer = "authored" | "calibration" | "live";
|
|
363
|
+
/**
|
|
364
|
+
* What an entity is. Any string is a kind; these are offered for completion: `frame` and `robot` are the two the runtime
|
|
365
|
+
* gives meaning to, `model` and `scan` the common others.
|
|
366
|
+
*/
|
|
367
|
+
export type SceneKind = "frame" | "robot" | "model" | "scan" | (string & {});
|
|
344
368
|
/** Each flag holds only when it is `true`. `locked` refuses patch/reparent/rename/despawn (a flags-only patch passes). */
|
|
345
369
|
export interface SceneFlags {
|
|
346
370
|
locked?: boolean;
|
|
@@ -414,8 +438,11 @@ export interface Hardpoint {
|
|
|
414
438
|
export interface SceneEntity {
|
|
415
439
|
/** A ULID; never changes. */
|
|
416
440
|
id: string;
|
|
417
|
-
kind:
|
|
418
|
-
/**
|
|
441
|
+
kind: SceneKind;
|
|
442
|
+
/**
|
|
443
|
+
* 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.
|
|
445
|
+
*/
|
|
419
446
|
name?: string;
|
|
420
447
|
/** The parent's id; `null` is the world root. */
|
|
421
448
|
parent: string | null;
|
|
@@ -450,7 +477,7 @@ export interface SceneEntity {
|
|
|
450
477
|
[key: string]: unknown;
|
|
451
478
|
}
|
|
452
479
|
/**
|
|
453
|
-
* An id's live row (`
|
|
480
|
+
* An id's live row (`view.live.get()`), each key present only when the row has it:
|
|
454
481
|
* `entity` is a live-layer entity's upsert row; `pose`, `joints`, `state` are the lanes; `stamp` is the
|
|
455
482
|
* newest lane stamp (the runtime's clock).
|
|
456
483
|
*/
|
|
@@ -476,15 +503,96 @@ export interface SceneTreeNode {
|
|
|
476
503
|
export type SceneTree = SceneTreeNode[];
|
|
477
504
|
/**
|
|
478
505
|
* 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.
|
|
506
|
+
* `onChange` for each change (the framework bridge), `changed()` for the next one, `watch()` for each one in a
|
|
507
|
+
* loop. It reads the handle's copy of the scene (no subscription of its own) and ends with the handle.
|
|
481
508
|
*/
|
|
482
509
|
export interface SceneView<T> {
|
|
483
510
|
get(): T;
|
|
484
|
-
|
|
485
|
-
|
|
511
|
+
/** `cb` with the value on each change; returns the remover. */
|
|
512
|
+
onChange(cb: (value: T) => void): () => void;
|
|
513
|
+
/** The value after each change (state, not history: changes in between fold into the latest); ends with the handle. */
|
|
514
|
+
watch(): AsyncIterable<T>;
|
|
515
|
+
/** 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
516
|
changed(): Promise<T>;
|
|
487
517
|
}
|
|
518
|
+
/** Which entity an entity view follows: an id (that entity only), or a name (whichever entity holds it now, on any plane). */
|
|
519
|
+
export type SceneEntityRef = string | {
|
|
520
|
+
name: string;
|
|
521
|
+
};
|
|
522
|
+
/**
|
|
523
|
+
* One entity as a view (`scene.entity(id)`, `scene.entity({ name })`), shaped like a store handle: `get()` is the
|
|
524
|
+
* merged entity now (authored with its live overrides; `undefined` when there is none), `get(component)` one of its
|
|
525
|
+
* components; `watch()` follows it. It is a `SceneView` too (`onChange`, `changed()`), so it fits wherever a view
|
|
526
|
+
* does. The scene's two planes are its two namespaces: `authored` (`get`, `set`: the entity as declared, one patch)
|
|
527
|
+
* and `live` (`get`: the live row with its `stamp`; `set`: the lanes; `remove`: the row). They have no `watch`,
|
|
528
|
+
* `onChange` or `changed` of their own: the view's cover the entity, whichever plane changed.
|
|
529
|
+
*
|
|
530
|
+
* A view by name follows the name: when the entity holding it is despawned and another takes the name, the view
|
|
531
|
+
* reads the new one (a view by id reads `undefined` from then on).
|
|
532
|
+
*/
|
|
533
|
+
export interface SceneEntityView extends SceneView<SceneEntity | undefined> {
|
|
534
|
+
/**
|
|
535
|
+
* The id of the entity the view names now: a view by id (what `upsert` returns), its id; a view by name, the id
|
|
536
|
+
* holding the name now (none holds it: `not_found`, `data: { name }`).
|
|
537
|
+
*/
|
|
538
|
+
readonly id: string;
|
|
539
|
+
/** The merged entity now; `undefined` when there is none (absent, or the handle detached or closed). */
|
|
540
|
+
get(): SceneEntity | undefined;
|
|
541
|
+
/** One component of the merged entity now (`get("xform")`); `undefined` when the entity or the component is absent. */
|
|
542
|
+
get<K extends keyof SceneEntity & string>(component: K): SceneEntity[K] | undefined;
|
|
543
|
+
/** The entity's authored plane: one object for the view's life. */
|
|
544
|
+
readonly authored: EntityAuthored;
|
|
545
|
+
/** The entity's live plane: one object for the view's life. */
|
|
546
|
+
readonly live: EntityLive;
|
|
547
|
+
/**
|
|
548
|
+
* The entity after each change (`undefined` once it is gone); with `component`, that component each time it
|
|
549
|
+
* changes. It is state, not history: changes in between are folded, the latest wins. Ends with the handle.
|
|
550
|
+
*/
|
|
551
|
+
watch(): AsyncIterable<SceneEntity | undefined>;
|
|
552
|
+
watch<K extends keyof SceneEntity & string>(component: K): AsyncIterable<SceneEntity[K] | undefined>;
|
|
553
|
+
}
|
|
554
|
+
/** An entity view's authored plane (`view.authored`). Reads as the view does: nothing once the handle is detached or closed. */
|
|
555
|
+
export interface EntityAuthored {
|
|
556
|
+
/** 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. */
|
|
557
|
+
get(): SceneEntity | undefined;
|
|
558
|
+
get<K extends keyof SceneEntity & string>(component: K): SceneEntity[K] | undefined;
|
|
559
|
+
/**
|
|
560
|
+
* `set(component, value)`, or `set({ ...components })` for several: one `patch` of the authored entity the view
|
|
561
|
+
* names now (by name: the id holding the name at the call), resolved once the entities show it; the runtime's
|
|
562
|
+
* refusal is the error. A view by id sends the patch as `scene.authored.patch(id, …)` does (an id the runtime does
|
|
563
|
+
* not have: its `not_found`). A view by name whose name no entity holds has no id to send to: `not_found`
|
|
564
|
+
* (`data: { name }`), nothing sent. Detached: `project_inactive`.
|
|
565
|
+
*/
|
|
566
|
+
set<K extends keyof SceneEntity & string>(component: K, value: SceneEntity[K]): Promise<void>;
|
|
567
|
+
set(components: Partial<SceneEntity>): Promise<void>;
|
|
568
|
+
}
|
|
569
|
+
/** An entity view's live plane (`view.live`). Reads as the view does: nothing once the handle is detached or closed. */
|
|
570
|
+
export interface EntityLive {
|
|
571
|
+
/** The entity's live row ({@link SceneEntityLive}, its `stamp` included); `undefined` without one. */
|
|
572
|
+
get(): SceneEntityLive | undefined;
|
|
573
|
+
get<K extends keyof SceneEntityLive & string>(component: K): SceneEntityLive[K] | undefined;
|
|
574
|
+
/**
|
|
575
|
+
* Writes the live lanes of the entity the view names now, as one `scene.live.batch` (synchronous, like `publish`):
|
|
576
|
+
* `xform: { t, q }` the pose lane (both required, nothing else), `joints` the joints lane, `state` the state lane; any
|
|
577
|
+
* other component is `invalid_input`. `lifetimeMs`: the override expires that long after its last write (the entity's
|
|
578
|
+
* authored values show again); a live entity keeps the lifetime it was upserted with. The first write for an id this
|
|
579
|
+
* node has not claimed asks the claim (see `scene.live.batch`), so a single write lands. A view by name whose name no
|
|
580
|
+
* entity holds: `not_found`. Not attached: dropped and counted (`status.get().detachedDrops`). Closed, or the node
|
|
581
|
+
* ended: `not_connected`.
|
|
582
|
+
*/
|
|
583
|
+
set(components: {
|
|
584
|
+
xform?: {
|
|
585
|
+
t: Vec3;
|
|
586
|
+
q: Quat;
|
|
587
|
+
};
|
|
588
|
+
joints?: Record<string, number>;
|
|
589
|
+
state?: Record<string, unknown>;
|
|
590
|
+
}, opts?: {
|
|
591
|
+
lifetimeMs?: number;
|
|
592
|
+
}): void;
|
|
593
|
+
/** Releases this node's claim of the entity the view names now (`scene.live.remove(id)`, the call): the runtime retracts its live row. */
|
|
594
|
+
remove(): Promise<void>;
|
|
595
|
+
}
|
|
488
596
|
/** `attached`: the scene of the active project is in; `detached`: none is (no project active, a session gap, between incarnations); `closed`: for good. */
|
|
489
597
|
export type SceneState = "attached" | "detached" | "closed";
|
|
490
598
|
export interface SceneStatus {
|
|
@@ -496,10 +604,12 @@ export interface SceneStatus {
|
|
|
496
604
|
/** The authored plane's version and hash as this handle has them (0 and "" while detached). */
|
|
497
605
|
version: number;
|
|
498
606
|
hash: string;
|
|
499
|
-
/** `live
|
|
607
|
+
/** Live writes (`scene.live.batch`, `view.live.set`) dropped because the handle was not attached. */
|
|
500
608
|
detachedDrops: number;
|
|
609
|
+
/** Lane rows dropped because their id's claim was refused (rows parked while it was asked, and rows written while refused). */
|
|
610
|
+
unclaimedDrops: number;
|
|
501
611
|
}
|
|
502
|
-
/** An authored change as the runtime applied it (the `
|
|
612
|
+
/** An authored change as the runtime applied it (a diff op of the scene's `authored` track). */
|
|
503
613
|
export type SceneDiffOp = {
|
|
504
614
|
op: "spawn";
|
|
505
615
|
entity: SceneEntity;
|
|
@@ -563,27 +673,27 @@ export type SceneOp = {
|
|
|
563
673
|
*/
|
|
564
674
|
export type SceneSpawnOptions = {
|
|
565
675
|
name: string;
|
|
566
|
-
parent?: string | null;
|
|
676
|
+
parent?: string | SceneEntityView | null;
|
|
567
677
|
id?: string;
|
|
568
678
|
} & Partial<Omit<SceneEntity, "id" | "kind" | "name" | "parent" | "order" | "meta">>;
|
|
569
|
-
/** Where `reparent` puts the entity among its new siblings (default: after the last). */
|
|
679
|
+
/** Where `reparent` puts the entity among its new siblings (default: after the last): a sibling by its id or its view. */
|
|
570
680
|
export type ScenePlace = {
|
|
571
|
-
before: string;
|
|
681
|
+
before: string | SceneEntityView;
|
|
572
682
|
} | {
|
|
573
|
-
after: string;
|
|
683
|
+
after: string | SceneEntityView;
|
|
574
684
|
};
|
|
575
|
-
/** One authoring batch being built (`txn`): sent as one atomic `apply`. */
|
|
685
|
+
/** 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
686
|
export interface SceneTxn {
|
|
577
687
|
/** Spawns an entity of `kind`; returns its id. */
|
|
578
|
-
spawn(kind:
|
|
579
|
-
/** Sets components of
|
|
580
|
-
patch(
|
|
581
|
-
rename(
|
|
582
|
-
reparent(
|
|
583
|
-
despawn(
|
|
688
|
+
spawn(kind: SceneKind, opts: SceneSpawnOptions): string;
|
|
689
|
+
/** Sets components of the entity (an object merges into an object one level deep). */
|
|
690
|
+
patch(entity: string | SceneEntityView, set: Partial<SceneEntity>): void;
|
|
691
|
+
rename(entity: string | SceneEntityView, name: string): void;
|
|
692
|
+
reparent(entity: string | SceneEntityView, parent: string | SceneEntityView | null, place?: ScenePlace): void;
|
|
693
|
+
despawn(entity: string | SceneEntityView, opts?: {
|
|
584
694
|
cascade?: boolean;
|
|
585
695
|
}): void;
|
|
586
|
-
setLocked(
|
|
696
|
+
setLocked(entity: string | SceneEntityView, locked: boolean): void;
|
|
587
697
|
/** The ops as they will be sent. */
|
|
588
698
|
readonly ops: readonly SceneOp[];
|
|
589
699
|
}
|
|
@@ -600,77 +710,131 @@ export interface LiveState {
|
|
|
600
710
|
id: string;
|
|
601
711
|
s: Record<string, unknown>;
|
|
602
712
|
}
|
|
603
|
-
/**
|
|
713
|
+
/**
|
|
714
|
+
* One live batch: data only, the lanes (`poses`, `joints`, `state`). `lifetimeMs` makes the override rows the batch
|
|
715
|
+
* touches expire. Claims are calls, never rows: `scene.live.upsert` and `scene.live.remove`.
|
|
716
|
+
*/
|
|
604
717
|
export interface LiveBatch {
|
|
605
|
-
upsert?: SceneEntity[];
|
|
606
718
|
poses?: LivePose[];
|
|
607
719
|
joints?: LiveJoints[];
|
|
608
720
|
state?: LiveState[];
|
|
609
|
-
remove?: string[];
|
|
610
721
|
lifetimeMs?: number;
|
|
722
|
+
upsert?: never;
|
|
723
|
+
remove?: never;
|
|
611
724
|
}
|
|
612
|
-
/** A live row removed: the writer removed it, its lifetime ran out, or its
|
|
725
|
+
/** 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
726
|
export interface LiveRemoved {
|
|
614
727
|
id: string;
|
|
615
|
-
by: "explicit" | "timeout" | "writer_gone";
|
|
728
|
+
by: "explicit" | "timeout" | "writer_gone" | "despawned";
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* The authored plane's verbs (`scene.authored`): the editor's (`spawn`, `patch`, `rename`, `reparent`, `despawn`,
|
|
732
|
+
* `setLocked`, `txn`, `apply`) and the node author's `upsert`. Every write goes to the runtime, which decides it, and
|
|
733
|
+
* resolves once the entities show it; a refusal is the runtime's error. Detached: `project_inactive`. An entity is
|
|
734
|
+
* named by its id or by a view of it.
|
|
735
|
+
*/
|
|
736
|
+
export interface SceneAuthored {
|
|
737
|
+
/** One atomic batch; `base`: the version its structural ops assume. */
|
|
738
|
+
apply(ops: SceneOp[], base?: number): Promise<void>;
|
|
739
|
+
/** The ops `build` makes, as one atomic batch. */
|
|
740
|
+
txn(build: (t: SceneTxn) => void): Promise<void>;
|
|
741
|
+
/** Spawns an entity; its id, once the entities show it. */
|
|
742
|
+
spawn(kind: SceneKind, opts: SceneSpawnOptions): Promise<string>;
|
|
743
|
+
patch(entity: string | SceneEntityView, set: Partial<SceneEntity>): Promise<void>;
|
|
744
|
+
/**
|
|
745
|
+
* 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.
|
|
752
|
+
*/
|
|
753
|
+
upsert(kind: SceneKind, opts: SceneSpawnOptions & {
|
|
754
|
+
id?: never;
|
|
755
|
+
}): Promise<SceneEntityView>;
|
|
756
|
+
rename(entity: string | SceneEntityView, name: string): Promise<void>;
|
|
757
|
+
reparent(entity: string | SceneEntityView, parent: string | SceneEntityView | null, place?: ScenePlace): Promise<void>;
|
|
758
|
+
despawn(entity: string | SceneEntityView, opts?: {
|
|
759
|
+
cascade?: boolean;
|
|
760
|
+
}): Promise<void>;
|
|
761
|
+
setLocked(entity: string | SceneEntityView, locked: boolean): Promise<void>;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* The live plane's verbs (`scene.live`). Decisions are calls, data is streams (PLAN v0.31): a claim is the call
|
|
765
|
+
* `synapse/scene/live/upsert` (`upsert`), a release the call `synapse/scene/live/remove` (`remove`); the lanes go on this
|
|
766
|
+
* node's live track (made on the first write of each session), synchronously like `publish` (`batch`, `view.live.set`).
|
|
767
|
+
* The runtime decides identity and ownership; nothing here reads the entities to decide what to send.
|
|
768
|
+
*/
|
|
769
|
+
export interface SceneLive {
|
|
770
|
+
/**
|
|
771
|
+
* 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
|
|
772
|
+
* the bound incarnation: a live entity of its `upsert`, or the override of an entity of the scene, which the first row
|
|
773
|
+
* of its id asks for (the call `upsert {id}`, once). Until the answer the latest row of each lane of that id is kept
|
|
774
|
+
* (latest wins) and sent once the claim is granted, so a single write lands; a refusal drops them (counted in
|
|
775
|
+
* `status.get().unclaimedDrops` with the rows written while refused), is a node notice (`node.notices`) and is asked
|
|
776
|
+
* again, paced, by a later row. The runtime stamps the rows; a row it does not apply comes back on `watchLiveDenied`.
|
|
777
|
+
* Not attached: dropped and counted (`status.get().detachedDrops`). Closed: `not_connected`. Never sent again.
|
|
778
|
+
*/
|
|
779
|
+
batch(batch: LiveBatch): void;
|
|
780
|
+
/**
|
|
781
|
+
* A live entity of this node's: the call `synapse/scene/live/upsert {kind, name?, parent, lifetime_ms?, ...components}`;
|
|
782
|
+
* resolves with its view (by the id the runtime answered: minted for a new one; a name this node already claimed sends
|
|
783
|
+
* that claim's id). Rejects with the runtime's refusal as it came (`name_taken {name, id, holder}`, `not_owner`,
|
|
784
|
+
* `conflict`, `invalid_tree`, `project_inactive`, `unauthorized`, ...): nothing is kept. Granted, it is this node's claim:
|
|
785
|
+
* claimed again, same id, on every attach (a new session, a project switch, a scene restart; a refused re-claim is
|
|
786
|
+
* asked again, paced, with a warning), until `remove`. A session that goes before the answer: asked at the next
|
|
787
|
+
* attach. Detached: `project_inactive`; the node ending: `not_connected`. An `id` component: `invalid_input`
|
|
788
|
+
* (`data: { component: "id" }`), nothing sent.
|
|
789
|
+
*/
|
|
790
|
+
upsert(kind: SceneKind, opts?: Omit<SceneSpawnOptions, "name" | "id"> & {
|
|
791
|
+
name?: string;
|
|
792
|
+
id?: never;
|
|
793
|
+
}, live?: {
|
|
794
|
+
lifetimeMs?: number;
|
|
795
|
+
}): Promise<SceneEntityView>;
|
|
796
|
+
/** 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. */
|
|
797
|
+
remove(entity: string | SceneEntityView): Promise<void>;
|
|
616
798
|
}
|
|
617
799
|
/**
|
|
618
800
|
* 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
|
-
*
|
|
801
|
+
* Views and feeds read the handle's copy of the scene; the writes are in `authored` (the authored plane) and `live`
|
|
802
|
+
* (this node's live rows).
|
|
621
803
|
*/
|
|
622
804
|
export interface SceneHandle extends AsyncDisposable {
|
|
623
805
|
/** The entities now, authored with the live overrides applied; the same object while nothing changed. */
|
|
624
|
-
|
|
806
|
+
get(): Readonly<Record<string, SceneEntity>>;
|
|
625
807
|
/**
|
|
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.
|
|
808
|
+
* One entity as a view ({@link SceneEntityView}), by id or by name (`entity({ name: "tool" })`: the entity holding
|
|
809
|
+
* the name now): merged, authored with its live overrides; one plane alone through the view (`view.authored`, `view.live`).
|
|
630
810
|
*/
|
|
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>;
|
|
811
|
+
entity(ref: SceneEntityRef): SceneEntityView;
|
|
640
812
|
/** The hierarchy as a view. */
|
|
641
813
|
tree(): SceneView<SceneTree>;
|
|
642
814
|
/** The entities for which `where` holds (in id order), as a view. */
|
|
643
815
|
query(where: (e: SceneEntity) => boolean): SceneView<SceneEntity[]>;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
816
|
+
/** The handle's own state, a state value: `get()` now (the same object while unchanged), `onChange` for each change, `changed()` for the next. */
|
|
817
|
+
readonly status: StateValue<SceneStatus>;
|
|
818
|
+
/** The one boolean a node author checks: `status.get().synced`. */
|
|
819
|
+
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. */
|
|
821
|
+
ready(): Promise<void>;
|
|
822
|
+
/** The entities after their next change (state, not history); `not_connected` once the handle is closed. */
|
|
823
|
+
changed(): Promise<Readonly<Record<string, SceneEntity>>>;
|
|
824
|
+
/** The entities after each change (state, not history: changes in between fold into the latest); ends with the handle. */
|
|
825
|
+
watch(): AsyncIterable<Readonly<Record<string, SceneEntity>>>;
|
|
826
|
+
/** `cb` with the entities on every change of the entities or the status (the framework bridge); returns the remover. */
|
|
827
|
+
onChange(cb: (entities: Readonly<Record<string, SceneEntity>>) => void): () => void;
|
|
648
828
|
/** The live rows removed, one batch per runtime tick that removed some. */
|
|
649
829
|
watchLiveRemovals(cb: (batch: LiveRemoved[]) => void): () => void;
|
|
650
830
|
watchLiveRemovals(): AsyncIterableIterator<LiveRemoved[]>;
|
|
651
831
|
/** The ids of this node's live writes the runtime dropped because another writer owns them, one batch per denied batch. */
|
|
652
832
|
watchLiveDenied(cb: (ids: string[]) => void): () => void;
|
|
653
833
|
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;
|
|
834
|
+
/** The authored plane's verbs. */
|
|
835
|
+
readonly authored: SceneAuthored;
|
|
836
|
+
/** This node's live writes. */
|
|
837
|
+
readonly live: SceneLive;
|
|
674
838
|
/** Ends this handle: its verbs fail `not_connected`, its views and feeds end; the node's other scene handles go on. */
|
|
675
839
|
close(): void;
|
|
676
840
|
}
|
|
@@ -748,21 +912,28 @@ export interface TaskCatalogEntry {
|
|
|
748
912
|
/** A value the runtime keeps current that this node only reads: it has no writes. */
|
|
749
913
|
export interface ReadonlyStoreHandle<V> {
|
|
750
914
|
/** The value now; the same object while nothing changed, and an unchanged part keeps its object when another part changes. */
|
|
751
|
-
|
|
915
|
+
get(): V;
|
|
752
916
|
/** `true` while the value is current. */
|
|
753
917
|
readonly synced: boolean;
|
|
754
918
|
/** Resolves once the value is current (the first time). */
|
|
755
919
|
ready(): Promise<void>;
|
|
756
920
|
/** The whole value after each change. It is state, not history: a reader slower than the changes sees the latest value. */
|
|
757
921
|
watch(): AsyncIterable<V>;
|
|
758
|
-
/**
|
|
759
|
-
|
|
922
|
+
/** `cb` with the value on every change of the value or of `synced` (the framework bridge); returns the remover. */
|
|
923
|
+
onChange(cb: (value: V) => void): () => void;
|
|
760
924
|
}
|
|
761
925
|
/** 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
|
-
|
|
926
|
+
export interface TaskDeclarations extends Omit<StoreHandle, "name" | "get" | "set" | "watch" | "destroy" | "close" | typeof Symbol.asyncDispose> {
|
|
927
|
+
/** Every declaration, `instance` → declaration. */
|
|
928
|
+
get(): Readonly<Record<string, TaskDeclaration>>;
|
|
764
929
|
get(instance: string): TaskDeclaration | undefined;
|
|
765
|
-
/**
|
|
930
|
+
/**
|
|
931
|
+
* `set(declaration)`: a new declaration under an instance the SDK mints (a ulid), resolving with that instance;
|
|
932
|
+
* `set(instance, declaration)` writes the one named; `set({ ... })` several as one atomic write. One object is a
|
|
933
|
+
* declaration when its `ref` is a string, else a map `instance` → declaration. Each waits for the value to be current
|
|
934
|
+
* first and resolves once `get()` shows the write.
|
|
935
|
+
*/
|
|
936
|
+
set(declaration: TaskDeclaration): Promise<string>;
|
|
766
937
|
set(instance: string, declaration: TaskDeclaration): Promise<void>;
|
|
767
938
|
set(declarations: Record<string, TaskDeclaration>): Promise<void>;
|
|
768
939
|
/** The whole value after each change; with `instance`, that declaration each time it changes (`undefined` after a delete). */
|
package/dist/util.d.ts
CHANGED
|
@@ -5,6 +5,19 @@ export interface Deferred<T> {
|
|
|
5
5
|
settled: boolean;
|
|
6
6
|
}
|
|
7
7
|
export declare function deferred<T>(): Deferred<T>;
|
|
8
|
+
/**
|
|
9
|
+
* A state value (`node.status`, `node.catalog`, `node.project`, the `status` of the store, scene and tasks handles):
|
|
10
|
+
* `get()` the value now (the same object while it is unchanged), `onChange` for each change (the framework bridge;
|
|
11
|
+
* `cb` gets the value; returns the remover), `changed()` for the value after the next change (at once with the value
|
|
12
|
+
* now once the node has ended), `watch()` for the value after each change in a loop (ends when the node, or the
|
|
13
|
+
* handle, ends). Not exported from the package: no user builds one.
|
|
14
|
+
*/
|
|
15
|
+
export interface StateValue<T> {
|
|
16
|
+
get(): T;
|
|
17
|
+
onChange(cb: (value: T) => void): () => void;
|
|
18
|
+
changed(): Promise<T>;
|
|
19
|
+
watch(): AsyncIterable<T>;
|
|
20
|
+
}
|
|
8
21
|
/** A value plus listeners. `changed()` resolves on the next `set`. */
|
|
9
22
|
export declare class Cell<T> {
|
|
10
23
|
#private;
|
|
@@ -16,6 +29,8 @@ export declare class Cell<T> {
|
|
|
16
29
|
changed(): Promise<T>;
|
|
17
30
|
get listenerCount(): number;
|
|
18
31
|
}
|
|
32
|
+
/** A callback's throw, rethrown on its own microtask: the caller's loop goes on and the error still surfaces. */
|
|
33
|
+
export declare const rethrowLater: (e: unknown) => void;
|
|
19
34
|
export declare const DONE: unique symbol;
|
|
20
35
|
/**
|
|
21
36
|
* Bounded FIFO with one consumer. `push` never blocks: past `capacity` the oldest item is dropped
|
|
@@ -65,3 +80,31 @@ export declare class Ring {
|
|
|
65
80
|
push(v: number): void;
|
|
66
81
|
percentile(p: number): number | undefined;
|
|
67
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* The loop form of a state value (`watch()`), built from its `onChange` and `get`: the value after each change, compared
|
|
85
|
+
* by reference (a state value keeps its object while unchanged), changes in between folded into the latest (state, not
|
|
86
|
+
* history). It ends when it is told of a change once `ended()` holds; each state value tells its listeners when it ends.
|
|
87
|
+
* Leaving the loop removes the listener.
|
|
88
|
+
*/
|
|
89
|
+
export declare function watchState<T>(onChange: (cb: () => void) => () => void, get: () => T, ended: () => boolean): AsyncGenerator<T, void, undefined>;
|
|
90
|
+
/**
|
|
91
|
+
* A handle's own state as a state value (`store.status`, `scene.status`; the `StateValue` shape): `get()` is `read()`
|
|
92
|
+
* kept the same object while every field is equal (`shallowEqual`), `onChange` calls back only when that object
|
|
93
|
+
* changes, `changed()` resolves with the next one (at once with the value now once `ended()`), `watch()` is the loop
|
|
94
|
+
* form. `source` is what tells of a possible change: the handle's own notifications.
|
|
95
|
+
*/
|
|
96
|
+
export declare class StatusCell<T> {
|
|
97
|
+
#private;
|
|
98
|
+
private readonly read;
|
|
99
|
+
private readonly source;
|
|
100
|
+
private readonly ended;
|
|
101
|
+
constructor(read: () => T, source: (l: () => void) => () => void, ended: () => boolean);
|
|
102
|
+
get(): T;
|
|
103
|
+
/** `cb` with the value on each change; once the handle has ended (or this state value was disposed) nothing will change: a no-op remover. */
|
|
104
|
+
onChange(cb: (value: T) => void): () => void;
|
|
105
|
+
/** The handle closed: every upstream registration is removed (after the listeners were told of the close); new subscriptions are refused. */
|
|
106
|
+
dispose(): void;
|
|
107
|
+
changed(): Promise<T>;
|
|
108
|
+
/** The value after each change; ends with the handle. */
|
|
109
|
+
watch(): AsyncIterable<T>;
|
|
110
|
+
}
|