@synapse-sdk/client 0.1.0 → 0.3.0

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.
@@ -0,0 +1,679 @@
1
+ import type { Card, ClientType, Delivery, FecParams, MediaFrame, Role, ServiceRef, StoreMode, StoreOnDeath, StoreRow, TimeDomain, TopicRef, TrackDesc, Tlv, JsonSchema } from "@synapse-sdk/core";
2
+ import type { SynapseError } from "@synapse-sdk/core";
3
+ export type { Card, ClientType, Delivery, FecParams, Role, ServiceRef, StoreMode, StoreOnDeath, StoreRow, TimeDomain, TopicRef, TrackDesc };
4
+ /** What the reconnect policy is asked before every attempt. */
5
+ export interface RetryContext {
6
+ attempt: number;
7
+ }
8
+ /** How long to wait before the next connect attempt; `null` = give up (SDK §2, WP §10.4). */
9
+ export interface ReconnectPolicy {
10
+ /** The first delay; also the healthy threshold: a session that lived at least this long resets the backoff. */
11
+ initialMs: number;
12
+ nextRetryDelayMs(ctx: RetryContext): number | null;
13
+ }
14
+ export type Transport = "auto" | "webtransport" | "websocket";
15
+ export interface ConnectOptions {
16
+ /** `https://host:port/<root>`. */
17
+ url: string;
18
+ /** This node's type (WP §3.1): bounds the role it may exercise; the runtime grants the role. */
19
+ type: ClientType;
20
+ /** This node's name `[a-z0-9_-]{1,64}`; minted (`<type>-<6 hex>`) when absent and kept across reconnects. */
21
+ name?: string;
22
+ /** Shared across one human's clients so they are promoted and demoted together (WP §3.5); a `web`/`hmi` node without one mints one. */
23
+ userId?: string;
24
+ /** Reserved for signed credentials. */
25
+ credential?: string;
26
+ /** Pin of the broker's self-signed certificate (SHA-256 hex). Absent: `SYNAPSE_BROKER_CERT_SHA256`, then the broker's helper, then unpinned. */
27
+ brokerCertSha256?: string;
28
+ /** Time-domain id override (WP §7.3); default per runtime. */
29
+ domId?: string;
30
+ /** Standing project intent (`&project=`, WP §3.5). */
31
+ project?: "operator";
32
+ /** `null` = no reconnect: a dropped session ends the node `closed`. Default: 100 ms → 10 s, ×2, jitter, unlimited. */
33
+ reconnect?: ReconnectPolicy | null;
34
+ transport?: Transport;
35
+ connectTimeoutMs?: number;
36
+ /** Datagram budget the FEC symbol size is derived from (WP §6.2); default 1024 in browsers, 1150 in Bun/Node. */
37
+ maxDatagram?: number;
38
+ /** Bun/Node: close cleanly on SIGINT/SIGTERM (default true). */
39
+ signals?: boolean;
40
+ }
41
+ /** Where the node should be connected: the argument of `setTarget` (fields left out keep the node's current ones). */
42
+ export interface ConnectTarget {
43
+ url: string;
44
+ type?: ClientType;
45
+ name?: string;
46
+ userId?: string;
47
+ credential?: string;
48
+ brokerCertSha256?: string;
49
+ }
50
+ export type ConnectionState = {
51
+ status: "connecting";
52
+ attempt: number;
53
+ } | {
54
+ status: "connected";
55
+ url: string;
56
+ sinceMs: number;
57
+ } | {
58
+ status: "reconnecting";
59
+ attempt: number;
60
+ reason: string;
61
+ error: SynapseError;
62
+ nextRetryAtMs: number;
63
+ } | {
64
+ status: "closing";
65
+ } | {
66
+ status: "closed";
67
+ reason: string;
68
+ } | {
69
+ status: "failed";
70
+ error: SynapseError;
71
+ };
72
+ /** This node's identity and the current session's hello facts; empty strings between sessions. */
73
+ export interface Info {
74
+ name: string;
75
+ type: ClientType;
76
+ userId?: string;
77
+ prefix: string;
78
+ role: Role;
79
+ dom: TimeDomain;
80
+ brokerDom: string;
81
+ brokerVersion: string; /** The robot the runtime serves (fixed for the runtime's life; empty between sessions). */
82
+ robot: string;
83
+ url: string;
84
+ }
85
+ /** A `promoted`/`demoted` notice on the session (WP §3.5). */
86
+ export type Notice = {
87
+ kind: "promoted";
88
+ role: Role;
89
+ project: string | null;
90
+ } | {
91
+ kind: "demoted";
92
+ role: Role;
93
+ project: string | null;
94
+ };
95
+ export interface PublishOptions {
96
+ /** The frame's `t_us` (this node's clock); default now. */
97
+ tUs?: number;
98
+ flags?: number;
99
+ }
100
+ /** Where a publisher stands with the runtime on the current session. */
101
+ export type Verdict = {
102
+ status: "unbound";
103
+ } | {
104
+ status: "pending";
105
+ epoch: number;
106
+ } | {
107
+ status: "admitted";
108
+ epoch: number;
109
+ };
110
+ export interface PublisherStats {
111
+ published: number;
112
+ droppedDisconnected: number;
113
+ fec?: {
114
+ blocks: number;
115
+ plain: number;
116
+ superseded: number;
117
+ };
118
+ }
119
+ export interface Publisher<TIn> extends AsyncDisposable {
120
+ readonly topic: string;
121
+ readonly verdict: Verdict;
122
+ /** Resolves once admitted and announced on the current session; `not_connected` once the node ended. A refusal is asked again, paced and warned about. */
123
+ ready(): Promise<void>;
124
+ /** Synchronous: writes to the installed track, or drops and counts while unbound (SDK §12.4). */
125
+ publish(value: TIn, opts?: PublishOptions): void;
126
+ /** Raw topics: any frame type, flags and TLVs (WP §4.1); `MEDIA` frames via `publishMedia`. */
127
+ publishFrame(type: number, flags: number, tUs: number, tlvs: Tlv[], body: Uint8Array): void;
128
+ /** Raw topics: a media access unit (`MEDIA`, flags from the frame, `t_us` = capture time, `STAGES` when stamped; a `sentUs` of 0 is stamped at the write). */
129
+ publishMedia(frame: MediaFrame): void;
130
+ stats(): PublisherStats;
131
+ close(): void;
132
+ }
133
+ export interface Envelope<T> {
134
+ data: T;
135
+ tUs: number;
136
+ seq: number;
137
+ /** The publisher's name. */
138
+ publisher: string;
139
+ /** The publisher's time-domain id. */
140
+ dom: string;
141
+ flags: number;
142
+ type: number;
143
+ tlvs: Tlv[];
144
+ }
145
+ export interface SubscribeOptions {
146
+ /** Deliver at most one message per interval, newest wins (skipped counted `coalesced`). */
147
+ throttleMs?: number;
148
+ /** `queueDepth` 1 (default): a read returns the newest message of each publisher with news; 0: every message, in order. */
149
+ queueDepth?: 0 | 1;
150
+ /** MoQ priority 0..7 (default 3). */
151
+ priority?: number;
152
+ /** `stream` topics: how far behind the live edge the relay may serve (ms; default 0 = live edge). */
153
+ maxAgeMs?: number;
154
+ /** Drop a message older than this on arrival (same-domain publishers only). */
155
+ deadlineMs?: number;
156
+ /** `fec` publishers: override the reassembly give-up budget. */
157
+ fecDeadlineMs?: number;
158
+ /** `fec` publishers: send WP §6.4 reports (default true). */
159
+ fecFeedback?: boolean;
160
+ }
161
+ export interface SubStats {
162
+ read: number;
163
+ superseded: number;
164
+ staleDrops: number;
165
+ mismatch: number;
166
+ fecGiveup: number;
167
+ repaired: number;
168
+ coalesced: number;
169
+ }
170
+ export interface Subscription<T> extends AsyncIterable<Envelope<T>[]>, AsyncDisposable {
171
+ readonly topic: string;
172
+ readonly stats: SubStats;
173
+ /** The next batch (never empty): latest per publisher (`queueDepth` 1) or everything ready (0). `undefined` once the node ended. */
174
+ next(): Promise<Envelope<T>[] | undefined>;
175
+ unsubscribe(): void;
176
+ }
177
+ export type ViewStatus = "pending" | "live" | "stale";
178
+ export interface ViewState<S> {
179
+ data: S | undefined;
180
+ status: ViewStatus;
181
+ connection: ConnectionState["status"];
182
+ /** This node's clock when the value changed, µs. */
183
+ lastChangeAtUs: number | undefined;
184
+ }
185
+ export interface ViewOptions<T, S> extends SubscribeOptions {
186
+ /** A value older than this (by its publisher's stamp, on our clock) reads as `stale`. */
187
+ staleTime?: number;
188
+ select?: (v: T) => S;
189
+ }
190
+ /** A topic's value now, as a cell: `get()` any time, `subscribe` for changes (the framework bridge), `changed()` for the next one; subscribed from creation until `close()`. */
191
+ export interface View<S> extends AsyncDisposable {
192
+ get(): ViewState<S>;
193
+ subscribe(listener: () => void): () => void;
194
+ ready(): Promise<void>;
195
+ changed(): Promise<ViewState<S>>;
196
+ close(): void;
197
+ }
198
+ export type RunStatus = "pending" | "running" | "succeeded" | "failed" | "canceled";
199
+ export interface CallOptions<U = unknown> {
200
+ /** End-to-end deadline enforced by the broker (WP §8.4). */
201
+ deadlineMs?: number;
202
+ /** Local: fail `idle_timeout` (and cancel) when no frame arrives for this long. */
203
+ idleTimeoutMs?: number;
204
+ signal?: AbortSignal;
205
+ onUpdate?: (u: U) => void;
206
+ }
207
+ /** A started call (WP §8.3): its updates, its result, and cancel. */
208
+ /** `await run` is `await run.result`. */
209
+ export interface Run<U, R> extends AsyncDisposable, PromiseLike<R> {
210
+ readonly id: string;
211
+ readonly status: RunStatus;
212
+ /** The next progress update; `undefined` once the terminal frame arrived (then `result`). */
213
+ nextUpdate(): Promise<U | undefined>;
214
+ updates(): AsyncIterable<U>;
215
+ readonly result: Promise<R>;
216
+ /** Cooperative cancel: tells the broker, resolves on its `canceled` frame. */
217
+ cancel(reason?: string): Promise<void>;
218
+ }
219
+ export type CallEvent<U, R> = {
220
+ type: "update";
221
+ data: U;
222
+ } | {
223
+ type: "result";
224
+ data: R;
225
+ };
226
+ export interface ServeContext<U> {
227
+ readonly call: string;
228
+ readonly caller: string;
229
+ readonly role: Role;
230
+ readonly deadlineMs: number | undefined;
231
+ /** Aborted when the caller cancels or the registration ends; `reason` names why. */
232
+ readonly signal: AbortSignal;
233
+ update(u: U): void;
234
+ }
235
+ export type ServeHandler<I, U, R> = (input: I, ctx: ServeContext<U>) => R | Promise<R>;
236
+ export interface ServeOptions {
237
+ minRole?: Role;
238
+ description?: string;
239
+ }
240
+ export type RegVerdict = {
241
+ status: "unbound";
242
+ } | {
243
+ status: "pending";
244
+ epoch: number;
245
+ } | {
246
+ status: "registered";
247
+ epoch: number;
248
+ };
249
+ export interface Registration extends AsyncDisposable {
250
+ readonly name: string;
251
+ readonly verdict: RegVerdict;
252
+ /** The current binding id (fresh per session and renewal); `undefined` while unbound. */
253
+ readonly bind: string | undefined;
254
+ ready(): Promise<void>;
255
+ unregister(): void;
256
+ }
257
+ export interface StoreOptions {
258
+ access?: StoreMode;
259
+ onDeath?: StoreOnDeath;
260
+ }
261
+ export interface StoreStatus {
262
+ /** The value is current: connected, nothing of ours pending, every change the runtime sent applied. */
263
+ synced: boolean;
264
+ connected: boolean;
265
+ pending: number;
266
+ mode: StoreMode | undefined;
267
+ owner: string | undefined;
268
+ mine: boolean;
269
+ minRole: Role | undefined;
270
+ onDeath: StoreOnDeath | undefined;
271
+ absent: boolean;
272
+ /** Diagnostics: the last applied version and the bound incarnation. */
273
+ version: number;
274
+ epoch: string | undefined;
275
+ /** `destroyed`, `connection_lost` or `client`. */
276
+ ended: string | undefined;
277
+ }
278
+ export type StoreChange = {
279
+ kind: "put";
280
+ key: string;
281
+ value: unknown;
282
+ previousValue: unknown | undefined;
283
+ by: string | undefined;
284
+ } | {
285
+ kind: "delete";
286
+ key: string;
287
+ previousValue: unknown;
288
+ by: string | undefined;
289
+ };
290
+ /**
291
+ * A namespace as a value kept current (SDK §9): `value`/`get` read it, `set`/`delete` resolve once the
292
+ * value shows the write, `synced` says whether it is current, `watch()` follows changes. The runtime
293
+ * decides every write (`not_owner`, `unauthorized`, `store_gone`).
294
+ */
295
+ export interface StoreHandle extends AsyncDisposable {
296
+ readonly name: string;
297
+ /** The whole value, what the runtime has (the same object while nothing changed). */
298
+ readonly value: Record<string, unknown>;
299
+ get(key: string): unknown;
300
+ readonly synced: boolean;
301
+ readonly status: StoreStatus;
302
+ /** `set(key, value)`, or `set({ ... })` for several keys as one atomic write; resolves once `value` shows it. */
303
+ set(key: string, value: unknown): Promise<void>;
304
+ set(values: Record<string, unknown>): Promise<void>;
305
+ delete(key: string): Promise<void>;
306
+ clear(): Promise<void>;
307
+ /** Ends the namespace for everyone (the owner, or an operator); every handle ends `destroyed`. */
308
+ destroy(): Promise<void>;
309
+ /** The whole value after each change; with `key`, that key's value each time it changes (`undefined` after a delete). */
310
+ watch(key?: string): AsyncIterable<unknown>;
311
+ /** The framework bridge (React): called on every change of the value or the status. */
312
+ subscribe(listener: () => void): () => void;
313
+ close(): void;
314
+ }
315
+ export type Vec3 = [number, number, number];
316
+ /** A rotation as a quaternion `[x, y, z, w]`. */
317
+ export type Quat = [number, number, number, number];
318
+ /** A transform relative to the parent: translation, rotation, scale (each absent = identity). */
319
+ export interface Xform {
320
+ t?: Vec3;
321
+ q?: Quat;
322
+ s?: Vec3;
323
+ }
324
+ /** Which plane an entity lives in: `authored` (the project's), `calibration` (the robot's), `live` (never persisted). */
325
+ export type SceneLayer = "authored" | "calibration" | "live";
326
+ /** Each flag holds only when it is `true`. `locked` refuses patch/reparent/rename/despawn (a flags-only patch passes). */
327
+ export interface SceneFlags {
328
+ locked?: boolean;
329
+ bound?: boolean;
330
+ static?: boolean;
331
+ derived?: boolean;
332
+ }
333
+ /** Written by the runtime on every change of the entity: revision, the runtime's clock (µs), who wrote it. */
334
+ export interface SceneMeta {
335
+ rev: number;
336
+ t: number;
337
+ by: string | null;
338
+ }
339
+ /**
340
+ * One piece of an entity's `visual` or `collision` list (pieces are the geometry unit), placed by its
341
+ * own `xform` inside the entity. The shapes and their fields are the scene spec's §1.2.
342
+ */
343
+ export type Piece = ({
344
+ kind: "box";
345
+ size: Vec3;
346
+ } | {
347
+ kind: "sphere";
348
+ radius: number;
349
+ } | {
350
+ kind: "cylinder";
351
+ radius: number;
352
+ length: number;
353
+ } | {
354
+ kind: "capsule";
355
+ radius: number;
356
+ length: number;
357
+ } | {
358
+ kind: "blob";
359
+ blob: string;
360
+ }) & {
361
+ xform?: Xform;
362
+ color?: string;
363
+ [key: string]: unknown;
364
+ };
365
+ export interface Physics {
366
+ dynamic?: boolean;
367
+ collide?: boolean;
368
+ mass?: number;
369
+ material?: {
370
+ friction?: number;
371
+ restitution?: number;
372
+ [key: string]: unknown;
373
+ };
374
+ [key: string]: unknown;
375
+ }
376
+ /** A robot's declaration: its description (URDF), the topic of its joint states, its control, the frames materialized into the tree. */
377
+ export interface RobotComponent {
378
+ description?: string;
379
+ joints_topic?: string;
380
+ control?: unknown;
381
+ frames?: string[];
382
+ floating_base?: boolean;
383
+ [key: string]: unknown;
384
+ }
385
+ /** A named mounting point of an entity; materialized as a derived frame. */
386
+ export interface Hardpoint {
387
+ name: string;
388
+ mate_type?: string | null;
389
+ xform?: Xform;
390
+ [key: string]: unknown;
391
+ }
392
+ /**
393
+ * An entity of the scene. The fields below are the ones the runtime and the early users read;
394
+ * every other key passes through untouched.
395
+ */
396
+ export interface SceneEntity {
397
+ /** A ULID; never changes. */
398
+ id: string;
399
+ kind: string;
400
+ /** Unique across the authored and calibration planes. */
401
+ name?: string;
402
+ /** The parent's id; `null` is the world root. */
403
+ parent: string | null;
404
+ /** The place among its siblings (lowercase hex; siblings sort by `(order, id)`); absent on live entities. */
405
+ order?: string;
406
+ layer: SceneLayer;
407
+ xform?: Xform;
408
+ flags?: SceneFlags;
409
+ meta?: SceneMeta;
410
+ lifetime_ms?: number;
411
+ visual?: Piece[];
412
+ collision?: Piece[];
413
+ physics?: Physics;
414
+ robot?: RobotComponent;
415
+ /** Joint name → position; on a robot the authored ones are its rest configuration, the live plane overrides them. */
416
+ joints?: Record<string, number>;
417
+ /** Presentation/status the live plane sets per entity. */
418
+ state?: Record<string, unknown>;
419
+ hardpoints?: Hardpoint[];
420
+ behaviors?: Record<string, unknown>;
421
+ possession?: Record<string, unknown>;
422
+ labels?: string[];
423
+ props?: Record<string, unknown>;
424
+ asset?: {
425
+ id: string;
426
+ prefab?: string;
427
+ [key: string]: unknown;
428
+ };
429
+ [key: string]: unknown;
430
+ }
431
+ /**
432
+ * An id's live row (`scene.entity(id, { plane: "live" })`), each key present only when the row has it:
433
+ * `entity` is a live-layer entity's upsert row; `pose`, `joints`, `state` are the lanes; `stamp` is the
434
+ * newest lane stamp (the runtime's clock).
435
+ */
436
+ export interface SceneEntityLive {
437
+ entity?: SceneEntity;
438
+ pose?: {
439
+ t: Vec3;
440
+ q: Quat;
441
+ };
442
+ joints?: Record<string, number>;
443
+ state?: Record<string, unknown>;
444
+ stamp?: number;
445
+ }
446
+ /** One node of the hierarchy: ids and ordering only (a pose, rename or patch does not change it). */
447
+ export interface SceneTreeNode {
448
+ id: string;
449
+ children: SceneTreeNode[];
450
+ }
451
+ /**
452
+ * The hierarchy now, as its roots: siblings in `(order, id)` order (an entity without an `order`
453
+ * after the ordered ones); an entity whose parent is not in the scene is a root.
454
+ */
455
+ export type SceneTree = SceneTreeNode[];
456
+ /**
457
+ * A value of the scene, kept current: `get()` the value now (the same object until it changes),
458
+ * `subscribe` for each change (the framework bridge), `changed()` for the next one. It reads the
459
+ * handle's copy of the scene (no subscription of its own) and ends with the handle.
460
+ */
461
+ export interface SceneView<T> {
462
+ get(): T;
463
+ subscribe(listener: () => void): () => void;
464
+ /** Resolves with the value after its next change (with the value as it is when the handle closes). */
465
+ changed(): Promise<T>;
466
+ }
467
+ /** `attached`: the scene of the active project is in; `detached`: none is (no project active, a session gap, between incarnations); `closed`: for good. */
468
+ export type SceneState = "attached" | "detached" | "closed";
469
+ export interface SceneStatus {
470
+ state: SceneState;
471
+ /** Attached, connected, no write of ours in flight, every authored change the runtime sent applied. */
472
+ synced: boolean;
473
+ /** Authoring batches awaiting the runtime's answer. */
474
+ pending: number;
475
+ /** The authored plane's version and hash as this handle has them (0 and "" while detached). */
476
+ version: number;
477
+ hash: string;
478
+ /** `live()` batches dropped because the handle was not attached. */
479
+ detachedDrops: number;
480
+ }
481
+ /** An authored change as the runtime applied it (the `watchChanges` feed). */
482
+ export type SceneDiffOp = {
483
+ op: "spawn";
484
+ entity: SceneEntity;
485
+ } | {
486
+ op: "despawn";
487
+ id: string;
488
+ removed: string[];
489
+ old_parent: string | null;
490
+ old_order: string | null;
491
+ } | {
492
+ op: "patch";
493
+ id: string;
494
+ set: Partial<SceneEntity>;
495
+ rev: number;
496
+ } | {
497
+ op: "reparent";
498
+ id: string;
499
+ parent: string | null;
500
+ order: string;
501
+ xform?: Xform;
502
+ old_parent: string | null;
503
+ old_order: string | null;
504
+ rev: number;
505
+ } | {
506
+ op: "rename";
507
+ id: string;
508
+ name: string;
509
+ old_name: string | null;
510
+ rev: number;
511
+ };
512
+ /** An op of an authoring batch (`apply`): the runtime validates every one. */
513
+ export type SceneOp = {
514
+ op: "spawn";
515
+ entity: Partial<SceneEntity> & {
516
+ id: string;
517
+ };
518
+ } | {
519
+ op: "despawn";
520
+ id: string;
521
+ cascade?: boolean;
522
+ } | {
523
+ op: "patch";
524
+ id: string;
525
+ set: Partial<SceneEntity>;
526
+ } | {
527
+ op: "reparent";
528
+ id: string;
529
+ parent: string | null;
530
+ before?: string;
531
+ after?: string;
532
+ preserve_world?: boolean;
533
+ } | {
534
+ op: "rename";
535
+ id: string;
536
+ name: string;
537
+ };
538
+ /**
539
+ * What `spawn` takes besides the kind: the name, the parent (default the world root), the components.
540
+ * The id is minted here (a ULID) unless `id` is given: a known id kept, as a restore of despawned
541
+ * entities needs (the runtime refuses one already in use, `conflict`).
542
+ */
543
+ export type SceneSpawnOptions = {
544
+ name: string;
545
+ parent?: string | null;
546
+ id?: string;
547
+ } & Partial<Omit<SceneEntity, "id" | "kind" | "name" | "parent" | "order" | "meta">>;
548
+ /** Where `reparent` puts the entity among its new siblings (default: after the last). */
549
+ export type ScenePlace = {
550
+ before: string;
551
+ } | {
552
+ after: string;
553
+ };
554
+ /** One authoring batch being built (`txn`): sent as one atomic `apply`. */
555
+ export interface SceneTxn {
556
+ /** Spawns an entity of `kind`; returns its id. */
557
+ spawn(kind: string, opts: SceneSpawnOptions): string;
558
+ /** Sets components of `id` (an object merges into an object one level deep). */
559
+ patch(id: string, set: Partial<SceneEntity>): void;
560
+ rename(id: string, name: string): void;
561
+ reparent(id: string, parent: string | null, place?: ScenePlace): void;
562
+ despawn(id: string, opts?: {
563
+ cascade?: boolean;
564
+ }): void;
565
+ setLocked(id: string, locked: boolean): void;
566
+ /** The ops as they will be sent. */
567
+ readonly ops: readonly SceneOp[];
568
+ }
569
+ export interface LivePose {
570
+ id: string;
571
+ t: Vec3;
572
+ q: Quat;
573
+ }
574
+ export interface LiveJoints {
575
+ id: string;
576
+ j: Record<string, number>;
577
+ }
578
+ export interface LiveState {
579
+ id: string;
580
+ s: Record<string, unknown>;
581
+ }
582
+ /** One live batch: live entities (`layer: "live"`), lane overrides, removals; `lifetimeMs` makes the override rows it touches expire. */
583
+ export interface LiveBatch {
584
+ upsert?: SceneEntity[];
585
+ poses?: LivePose[];
586
+ joints?: LiveJoints[];
587
+ state?: LiveState[];
588
+ remove?: string[];
589
+ lifetimeMs?: number;
590
+ }
591
+ /** A live row removed: the writer removed it, its lifetime ran out, or its writer left. */
592
+ export interface LiveRemoved {
593
+ id: string;
594
+ by: "explicit" | "timeout" | "writer_gone";
595
+ }
596
+ /**
597
+ * The active project's scene as this node sees it (PLAN v0.24 §5): authored ⊕ live, kept current.
598
+ * Views and feeds read the handle's copy of the scene; every write goes to the runtime, which
599
+ * decides it, and resolves once the entities show it.
600
+ */
601
+ export interface SceneHandle extends AsyncDisposable {
602
+ /** The entities now, authored with the live overrides applied; the same object while nothing changed. */
603
+ readonly entities: Readonly<Record<string, SceneEntity>>;
604
+ /**
605
+ * One entity as a view (`undefined` when it is not in the scene). `plane` reads one layer alone and
606
+ * the view changes only when that layer's value for the id does: `authored` is the entity as
607
+ * declared without the live lanes (the authored entity, or a live entity's upsert row); `live` is
608
+ * the id's live row (`SceneEntityLive`), `undefined` without one. Without `plane`: merged.
609
+ */
610
+ entity(id: string, opts?: {
611
+ plane?: "authored";
612
+ }): SceneView<SceneEntity | undefined>;
613
+ entity(id: string, opts: {
614
+ plane: "live";
615
+ }): SceneView<SceneEntityLive | undefined>;
616
+ entity(id: string, opts?: {
617
+ plane?: "authored" | "live";
618
+ }): SceneView<SceneEntity | SceneEntityLive | undefined>;
619
+ /** The hierarchy as a view. */
620
+ tree(): SceneView<SceneTree>;
621
+ /** The entities for which `where` holds (in id order), as a view. */
622
+ query(where: (e: SceneEntity) => boolean): SceneView<SceneEntity[]>;
623
+ readonly status: SceneStatus;
624
+ /** The authored changes, one batch per applied change. */
625
+ watchChanges(cb: (batch: SceneDiffOp[]) => void): () => void;
626
+ watchChanges(): AsyncIterableIterator<SceneDiffOp[]>;
627
+ /** The live rows removed, one batch per runtime tick that removed some. */
628
+ watchLiveRemovals(cb: (batch: LiveRemoved[]) => void): () => void;
629
+ watchLiveRemovals(): AsyncIterableIterator<LiveRemoved[]>;
630
+ /** The ids of this node's live writes the runtime dropped because another writer owns them, one batch per denied batch. */
631
+ watchLiveDenied(cb: (ids: string[]) => void): () => void;
632
+ watchLiveDenied(): AsyncIterableIterator<string[]>;
633
+ /** The handle's state at each change; ends after `closed`. */
634
+ watchState(cb: (state: SceneState) => void): () => void;
635
+ watchState(): AsyncIterableIterator<SceneState>;
636
+ /** One atomic batch; `base`: the version its structural ops assume. Resolves once the entities show it; a refusal is the runtime's error. Detached: `project_inactive`. */
637
+ apply(ops: SceneOp[], base?: number): Promise<void>;
638
+ /** The ops `build` makes, as one atomic batch. */
639
+ txn(build: (t: SceneTxn) => void): Promise<void>;
640
+ /** Spawns an entity; its id, once the entities show it. */
641
+ spawn(kind: string, opts: SceneSpawnOptions): Promise<string>;
642
+ patch(id: string, set: Partial<SceneEntity>): Promise<void>;
643
+ rename(id: string, name: string): Promise<void>;
644
+ reparent(id: string, parent: string | null, place?: ScenePlace): Promise<void>;
645
+ despawn(id: string, opts?: {
646
+ cascade?: boolean;
647
+ }): Promise<void>;
648
+ setLocked(id: string, locked: boolean): Promise<void>;
649
+ /** Writes one live batch on this node's live track; returns at once (like `publish`). Not attached: dropped and counted (`status.detachedDrops`). */
650
+ live(batch: LiveBatch): void;
651
+ /** The framework bridge (React): called on every change of the entities or the status. */
652
+ subscribe(listener: () => void): () => void;
653
+ /** Ends this handle: its verbs fail `not_connected`, its views and feeds end; the node's other scene handles go on. */
654
+ close(): void;
655
+ }
656
+ export interface Catalog {
657
+ readonly topics: Readonly<Record<string, {
658
+ desc?: TrackDesc;
659
+ publishers: Record<string, {
660
+ name: string;
661
+ bind: string;
662
+ }>;
663
+ }>>;
664
+ readonly services: Readonly<Record<string, {
665
+ name: string;
666
+ prefix: string;
667
+ card: Card;
668
+ builtin: boolean;
669
+ }>>;
670
+ /** The listed card of `service`, if any. */
671
+ describe(service: string): Card | undefined;
672
+ }
673
+ export interface Metrics {
674
+ sessions: number;
675
+ reconnects: number;
676
+ callsSent: number;
677
+ callsAnswered: number;
678
+ }
679
+ export type { JsonSchema };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};