@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/scene.d.ts CHANGED
@@ -3,13 +3,15 @@ import { SynapseError, type ErrorBody, type ProjectSnapshot } from "@synapse-sdk
3
3
  import type { Node } from "./node.ts";
4
4
  import { StreamWriter, type Session } from "./session.ts";
5
5
  import type { Warn } from "./topics.ts";
6
- import type { LiveBatch, LiveJoints, LivePose, LiveRemoved, LiveState, Quat, SceneDiffOp, SceneEntity, SceneEntityLive, SceneHandle, SceneOp, ScenePlace, SceneSpawnOptions, SceneState, SceneStatus, SceneTree, SceneTxn, SceneView, Vec3 } from "./types.ts";
7
- import { AsyncQueue, type Deferred } from "./util.ts";
6
+ import type { LiveJoints, LivePose, LiveRemoved, LiveState, Quat, SceneAuthored, SceneDiffOp, SceneEntity, SceneEntityLive, SceneEntityRef, SceneEntityView, SceneHandle, SceneKind, SceneLive, SceneOp, ScenePlace, SceneSpawnOptions, SceneState, SceneStatus, SceneTree, SceneTxn, SceneView, Vec3 } from "./types.ts";
7
+ import { AsyncQueue, type Deferred, type StateValue } from "./util.ts";
8
8
  /** How many batches a feed may fall behind before it is ended (the store's `STORE_FEED_CAP`). */
9
9
  export declare const SCENE_FEED_CAP = 256;
10
10
  /** Keys a `patch` may not set (the runtime refuses them); a txn never folds such a patch into a spawn. */
11
11
  export declare const PATCH_PROTECTED: readonly string[];
12
12
  type Entities = Map<string, SceneEntity>;
13
+ /** An entity named by its id or by a view of it (the id the view names now). */
14
+ type EntityArg = string | SceneEntityView;
13
15
  /**
14
16
  * A fresh entity id: a ULID (26 Crockford base-32 characters) made from a UUIDv7 (48-bit ms, version,
15
17
  * 74 random bits, variant), so ids sort by the time they were minted; within one millisecond the
@@ -37,19 +39,19 @@ export declare function treeOf(entities: Readonly<Record<string, SceneEntity>>):
37
39
  export declare class Txn implements SceneTxn {
38
40
  #private;
39
41
  get ops(): readonly SceneOp[];
40
- /** Spawns an entity of `kind` under `opts.parent` (default the world root) with the components given (`layer` `authored` when not given); its id is minted here (or `opts.id`, a known one) and returned. No `order` is sent: the runtime appends it after its siblings. */
41
- spawn(kind: string, opts: SceneSpawnOptions): string;
42
- /** Sets components of `id` (one level deep: an object merges into an object). */
43
- patch(id: string, set: Partial<SceneEntity>): void;
44
- rename(id: string, name: string): void;
45
- /** Moves `id` under `parent` (`null`: the world root), before or after a sibling (default: after the last). */
46
- reparent(id: string, parent: string | null, place?: ScenePlace): void;
47
- /** Despawns `id` (`cascade`: with its subtree). One spawned in this batch is cancelled instead, unless it has children spawned in the batch and `cascade` is off (then the runtime decides). */
48
- despawn(id: string, opts?: {
42
+ /** Spawns an entity of `kind` under `opts.parent` (default the world root) with the components given (`layer` `authored` when not given, else `calibration`; a live entity is not spawned here, it comes from `scene.live.upsert`); its id is minted here (or `opts.id`, a known one) and returned. No `order` is sent: the runtime appends it after its siblings. */
43
+ spawn(kind: SceneKind, opts: SceneSpawnOptions): string;
44
+ /** Sets components of the entity (one level deep: an object merges into an object). */
45
+ patch(entity: EntityArg, set: Partial<SceneEntity>): void;
46
+ rename(entity: EntityArg, name: string): void;
47
+ /** Moves the entity under `parent` (`null`: the world root), before or after a sibling (default: after the last). */
48
+ reparent(entity: EntityArg, parent: EntityArg | null, place?: ScenePlace): void;
49
+ /** Despawns the entity (`cascade`: with its subtree). One spawned in this batch is cancelled instead, unless it has children spawned in the batch and `cascade` is off (then the runtime decides). */
50
+ despawn(entity: EntityArg, opts?: {
49
51
  cascade?: boolean;
50
52
  }): void;
51
- /** Locks or unlocks `id` (a flags-only patch, which a lock lets through). */
52
- setLocked(id: string, locked: boolean): void;
53
+ /** Locks or unlocks the entity (a flags-only patch, which a lock lets through). */
54
+ setLocked(entity: EntityArg, locked: boolean): void;
53
55
  }
54
56
  /**
55
57
  * Applies one diff op of a batch the runtime stamped `t` by `by` to the authored entities (the
@@ -57,8 +59,6 @@ export declare class Txn implements SceneTxn {
57
59
  * `meta` is `{rev, t, by}`, as the runtime stamped it.
58
60
  */
59
61
  export declare function foldAuthored(authored: Entities, op: SceneDiffOp, t: number, by: string | null): string[];
60
- /** What changed from `old` to `next`, in the diff ops' shapes (a snapshot replacing the mirror, the mirror emptying): a spawn per new entity, a despawn per gone one, and per changed one a reparent (parent or order), a rename (name) and a patch of the other keys that differ. */
61
- export declare function netDifference(old: Entities, next: Entities): SceneDiffOp[];
62
62
  /** One live row as this node keeps it: the whole live entity of an upsert (absent: an override of an authored entity), the lanes, and the newest lane stamp (the runtime's clock). */
63
63
  export interface LiveRow {
64
64
  entity?: SceneEntity;
@@ -85,6 +85,10 @@ export interface LiveFrame {
85
85
  }
86
86
  /** A keyframe as a fresh live table. */
87
87
  export declare function liveTableOf(k: LiveFrame): LiveTable;
88
+ /** The authored or calibration entity named `name` (live overrides never count). */
89
+ export declare function authoredNamed(authored: Entities, name: string): SceneEntity | undefined;
90
+ /** The id of the live entity named `name` (a lane-only row carries no name). */
91
+ export declare function liveNamed(live: LiveTable, name: string): string | undefined;
88
92
  /** One tick folded into `table` with the runtime's rules (an upsert replaces the whole row, a lane replaces its lane, a remove drops the row); returns the ids it touched. */
89
93
  export declare function foldLive(table: LiveTable, d: LiveFrame): string[];
90
94
  /** The merged read of one entity: the pose lane replaces `xform.t`/`xform.q` (the rest of the xform stays), the joints lane replaces `joints`, the state lane replaces `state`; nothing merges deeper. */
@@ -93,9 +97,8 @@ export declare function overlay(entity: SceneEntity, row: LiveRow): SceneEntity;
93
97
  export declare function liveValue(row: LiveRow): SceneEntityLive;
94
98
  /** `id` as the entities show it: its live row over its authored entity, or a live entity's own row. */
95
99
  export declare function mergedOf(authored: Entities, live: LiveTable, id: string): SceneEntity | undefined;
96
- type FeedKind = "changes" | "removals" | "denied" | "states";
100
+ type FeedKind = "removals" | "denied" | "states";
97
101
  interface FeedItems {
98
- changes: SceneDiffOp[];
99
102
  removals: LiveRemoved[];
100
103
  denied: string[];
101
104
  states: SceneState;
@@ -143,11 +146,11 @@ export declare class Mirror {
143
146
  dropHandle(id: number): void;
144
147
  /** The hierarchy now (recomputed only after a change of the ids, a parent or an order; the same object while equal). */
145
148
  tree(): SceneTree;
146
- /** A snapshot of incarnation `epoch` replaces the authored copy; the change feeds get the net difference. Not yet `attached`: the reader catches up first (`attach`). */
149
+ /** A snapshot of incarnation `epoch` replaces the authored copy. Not yet `attached`: the reader catches up first (`attach`). */
147
150
  applySnapshot(epoch: string, s: Snapshot): void;
148
151
  /** The reader has applied everything this node held when it bound the incarnation. */
149
152
  attach(): void;
150
- /** One applied batch: its ops folded into the authored copy; the feeds get them as they came. */
153
+ /** One applied batch: its ops folded into the authored copy. */
151
154
  applyDiff(d: Diff, warn: Warn): void;
152
155
  /** A keyframe replaces the live table. */
153
156
  applyKeyframe(k: LiveFrame): void;
@@ -156,7 +159,7 @@ export declare class Mirror {
156
159
  /** Ids of this node's live rows the runtime dropped. */
157
160
  denied(ids: string[]): void;
158
161
  setPending(n: number): void;
159
- /** No incarnation: the copy empties (the change feeds see the despawns), `detached`. */
162
+ /** No incarnation: the copy empties, `detached`. */
160
163
  detach(): void;
161
164
  /** The end: emptied, `closed` (`reason` recorded), every feed ended. */
162
165
  close(reason: string): void;
@@ -180,6 +183,13 @@ type Event = {
180
183
  epoch: string;
181
184
  version: number;
182
185
  }
186
+ /** Reading `attempt` caught up with incarnation `epoch` at `version`: the task binds the claims, then attaches the handle. */
187
+ | {
188
+ kind: "attach";
189
+ attempt: number;
190
+ epoch: string;
191
+ version: number;
192
+ }
183
193
  /** The reader of attempt `attempt` ended: the incarnation it bound and its final frame's reason (none: the tracks or the session went). */
184
194
  | {
185
195
  kind: "followEnded";
@@ -210,9 +220,47 @@ type Event = {
210
220
  } | {
211
221
  kind: "paced";
212
222
  attempt: number;
223
+ }
224
+ /** `live.upsert`: a live entity's claim (its args, no id); answered with the id the runtime decided. */
225
+ | {
226
+ kind: "upsert";
227
+ args: Record<string, unknown>;
228
+ reply: Deferred<string>;
229
+ }
230
+ /** `live.remove`. */
231
+ | {
232
+ kind: "remove";
233
+ id: string;
234
+ reply: Deferred<void>;
235
+ }
236
+ /** A lane write asked the claim of `id` (marked asked by the handle). */
237
+ | {
238
+ kind: "claim";
239
+ id: string;
240
+ }
241
+ /** The runtime's answer to a claim asked in attach `bind`. */
242
+ | {
243
+ kind: "claimDone";
244
+ ask: Ask;
245
+ bind: number;
246
+ result: Result<unknown>;
247
+ }
248
+ /** A refused live entity may be due to be asked again. */
249
+ | {
250
+ kind: "reclaim";
213
251
  } | {
214
252
  kind: "close";
215
253
  };
254
+ /** A claim being asked: a `live.upsert` (its args, its caller), or one of this node's claims asked again. */
255
+ type Ask = {
256
+ kind: "first";
257
+ args: Record<string, unknown>;
258
+ reply: Deferred<string>;
259
+ token: number;
260
+ } | {
261
+ kind: "again";
262
+ id: string;
263
+ };
216
264
  /** This node's live writer: the session's `<prefix>/scene/live` broadcast and its track `live`. */
217
265
  interface LiveWriter {
218
266
  session: Session;
@@ -220,6 +268,51 @@ interface LiveWriter {
220
268
  stream: StreamWriter;
221
269
  seq: number;
222
270
  }
271
+ /** Where one of this node's claims stands in the bound incarnation. */
272
+ type ClaimState =
273
+ /** Not claimed in the bound incarnation (none bound, or not asked yet). */
274
+ {
275
+ kind: "unclaimed";
276
+ }
277
+ /** Asked (or about to be, by the task): lane rows are parked until the answer. */
278
+ | {
279
+ kind: "asking";
280
+ }
281
+ /** The runtime granted it in the bound incarnation: lane rows go out. */
282
+ | {
283
+ kind: "claimed";
284
+ }
285
+ /**
286
+ * Asked again from `at` (`performance.now()` ms) on. `refused`: the runtime refused it (rows are dropped until then; a
287
+ * live entity is asked by the task's timer, an override by its next lane write once due); not refused: a transient
288
+ * answer (rows keep parking; the task's timer asks it).
289
+ */
290
+ | {
291
+ kind: "paced";
292
+ at: number;
293
+ refused: boolean;
294
+ };
295
+ /** The latest lane rows of one id written while its claim is not answered yet: one slot per lane, latest wins (state, not history), sent once the claim is granted, dropped on a refusal. */
296
+ interface Parked {
297
+ pose?: LivePose;
298
+ joints?: LiveJoints;
299
+ state?: LiveState;
300
+ lifetimeMs?: number;
301
+ }
302
+ /** One of this node's claims: its own intent, never world state. */
303
+ declare class Claim {
304
+ readonly args: Record<string, unknown>;
305
+ state: ClaimState;
306
+ /** Refusals of it in a row (the pace of the next ask). */
307
+ refusals: number;
308
+ parked: Parked;
309
+ /** `args`: the `upsert` call's args besides `id` (a live entity's `kind`, `name`, `parent`, `lifetime_ms` and components); empty for the override of an entity of the scene. */
310
+ constructor(args: Record<string, unknown>, state: ClaimState);
311
+ /** A live entity (claimed again on every attach), not an override (claimed by its lanes). */
312
+ get isEntity(): boolean;
313
+ }
314
+ /** What the lane gate does with a row: sent now (claimed), parked (the claim is not answered yet), dropped and counted (refused, not due again). */
315
+ type Gate = "send" | "park" | "drop";
223
316
  /** The node's scene: what every handle shares with the task. */
224
317
  export declare class Scene {
225
318
  readonly node: Node;
@@ -230,11 +323,24 @@ export declare class Scene {
230
323
  /** Handles neither closed nor dropped; the one that brings it to 0 tells the task `close`. */
231
324
  handles: number;
232
325
  nextHandle: number;
233
- /** The live writer of the current session, made on the first `live()`. */
326
+ /** The live writer of the current session, made on the first live write. */
234
327
  writer: LiveWriter | undefined;
328
+ /** This node's claims by id (what `live.upsert` made and what lane writes asked for); `live.remove` drops one. Nothing else is kept. */
329
+ readonly claims: Map<string, Claim>;
235
330
  detachedDrops: number;
331
+ /** Lane rows dropped because their id's claim was refused (and not due again), or parked rows a refusal dropped. */
332
+ unclaimedDrops: number;
333
+ /** Told when a counter of the status changes (the listeners of the status state values). */
334
+ readonly counters: Set<() => void>;
236
335
  constructor(node: Node, warn: Warn);
237
336
  post(e: Event): boolean;
337
+ countersChanged(): void;
338
+ /**
339
+ * What happens to a lane row of `id` now. An id with no claim becomes the override's claim; an override that is
340
+ * unclaimed, or refused and due again, is marked asked and returned in `ask` (the task asks it: one ask per id at a
341
+ * time). A live entity's claim is asked by the task (on attach, paced after a refusal), never by a lane.
342
+ */
343
+ gate(id: string, now: number, ask: string[]): Gate;
238
344
  request<T>(make: (reply: Deferred<T>) => Event): Promise<T>;
239
345
  }
240
346
  /** The node's one scene task, if any (set with the task, cleared by the task when it exits). */
@@ -246,54 +352,42 @@ export declare function startScene(scene: Scene, registry: SceneRegistry): void;
246
352
  export declare class SceneHandleImpl implements SceneHandle {
247
353
  #private;
248
354
  private readonly scene;
355
+ /** The authored plane's verbs. */
356
+ readonly authored: SceneAuthored;
357
+ /** This node's live writes. */
358
+ readonly live: SceneLive;
359
+ /** The handle's own state, a state value: `get()` (the same object while unchanged), `onChange`, `changed()`, `watch()`. */
360
+ readonly status: StateValue<SceneStatus>;
249
361
  constructor(scene: Scene);
250
- get entities(): Readonly<Record<string, SceneEntity>>;
251
- entity(id: string, opts?: {
252
- plane?: "authored";
253
- }): SceneView<SceneEntity | undefined>;
254
- entity(id: string, opts: {
255
- plane: "live";
256
- }): SceneView<SceneEntityLive | undefined>;
257
- entity(id: string, opts?: {
258
- plane?: "authored" | "live";
259
- }): SceneView<SceneEntity | SceneEntityLive | undefined>;
362
+ /** The merged entities now; a closed handle reads nothing (the mirror may live on for the node's other handles). */
363
+ get(): Readonly<Record<string, SceneEntity>>;
364
+ /** One entity as a view: the merged entity, with its authored value (`authored`) and its live row (`live`). */
365
+ entity(ref: SceneEntityRef): SceneEntityView;
260
366
  tree(): SceneView<SceneTree>;
261
367
  query(where: (e: SceneEntity) => boolean): SceneView<SceneEntity[]>;
262
- get status(): SceneStatus;
263
- watchChanges(cb: (batch: SceneDiffOp[]) => void): () => void;
264
- watchChanges(): AsyncIterableIterator<SceneDiffOp[]>;
368
+ /** The one boolean a node author checks: `status.get().synced`. */
369
+ get synced(): boolean;
370
+ /** Resolves once the handle is attached (at once if it is); `not_connected` once it is closed (`close()`, or the node ending). */
371
+ ready(): Promise<void>;
372
+ /** The entities after their next change (state, not history); `not_connected` once this handle is closed. */
373
+ changed(): Promise<Readonly<Record<string, SceneEntity>>>;
374
+ /** The entities after each change; ends with the handle. */
375
+ watch(): AsyncIterable<Readonly<Record<string, SceneEntity>>>;
265
376
  watchLiveRemovals(cb: (batch: LiveRemoved[]) => void): () => void;
266
377
  watchLiveRemovals(): AsyncIterableIterator<LiveRemoved[]>;
267
378
  watchLiveDenied(cb: (ids: string[]) => void): () => void;
268
379
  watchLiveDenied(): AsyncIterableIterator<string[]>;
269
- watchState(cb: (state: SceneState) => void): () => void;
270
- watchState(): AsyncIterableIterator<SceneState>;
271
- apply(ops: SceneOp[], base?: number): Promise<void>;
272
- txn(build: (t: SceneTxn) => void): Promise<void>;
273
- spawn(kind: string, opts: SceneSpawnOptions): Promise<string>;
274
- patch(id: string, set: Partial<SceneEntity>): Promise<void>;
275
- rename(id: string, name: string): Promise<void>;
276
- reparent(id: string, parent: string | null, place?: ScenePlace): Promise<void>;
277
- despawn(id: string, opts?: {
278
- cascade?: boolean;
279
- }): Promise<void>;
280
- setLocked(id: string, locked: boolean): Promise<void>;
281
- /**
282
- * Writes one live batch on this node's live track (made on the first call of each session); returns
283
- * at once, like `publish`. The runtime stamps the rows and owns the outcome: an id another writer owns
284
- * comes back on `watchLiveDenied`, a malformed batch as a node notice. Not attached: dropped and counted.
285
- */
286
- live(batch: LiveBatch): void;
287
- /** The framework bridge (React's hooks): called on every change of the entities or the status. */
288
- subscribe(listener: () => void): () => void;
289
- /** Closes the handle: its verbs fail, its views and feeds end, its status says `closed`; the node's other scene handles go on. The last one ends the task, which ends this node's live writer (the runtime retracts its lifetimed rows and live entities: `writer_gone`). Idempotent. */
380
+ /** `cb` with the entities on every change of the entities or the status (the framework bridge, React's hooks), and once more when this handle closes; returns the remover. */
381
+ onChange(cb: (entities: Readonly<Record<string, SceneEntity>>) => void): () => void;
382
+ /** Closes the handle: its verbs fail, its views and feeds end, its status says `closed`; the node's other scene handles go on. The last one ends the task, which releases this node's claims (one `synapse/scene/live/remove` call per claim, best effort, not awaited; the session's end releases what remains) and ends its live writer. Idempotent. */
290
383
  close(): void;
291
384
  [Symbol.asyncDispose](): Promise<void>;
292
385
  }
293
386
  /**
294
387
  * The active project's scene (one per project; one task per node, shared by every handle). Resolves
295
388
  * once the handle is attached, or at once `detached` when the runtime says no project is active; the
296
- * handle then follows the incarnations as they come and go. `not_connected` without a session.
389
+ * handle then follows the incarnations as they come and go. Without a session (before the first, between
390
+ * two) the task waits for the next one, like after a drop.
297
391
  */
298
392
  export declare function openScene(node: Node, registry: SceneRegistry, warn: Warn): Promise<SceneHandleImpl>;
299
393
  export {};