@synapse-sdk/client 0.3.7 → 0.3.8

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,7 +3,7 @@ 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 { 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";
6
+ import type { LiveJoints, LivePose, LiveRemoved, LiveState, Quat, SceneAuthored, SceneDiffOp, SceneEntity, SceneEntityLive, SceneEntityRef, SceneEntityView, SceneHandle, SceneKind, SceneLive, SceneOp, SceneSpawnOptions, SceneState, SceneStatus, SceneTree, SceneTxn, SceneView, Vec3 } from "./types.ts";
7
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;
@@ -12,6 +12,9 @@ export declare const PATCH_PROTECTED: readonly string[];
12
12
  type Entities = Map<string, SceneEntity>;
13
13
  /** An entity named by its id or by a view of it (the id the view names now). */
14
14
  type EntityArg = string | SceneEntityView;
15
+ /** A patch's `set` and `reparent`'s options, as the public interfaces take them. */
16
+ type PatchSet = Parameters<SceneTxn["patch"]>[1];
17
+ type ReparentOptions = NonNullable<Parameters<SceneTxn["reparent"]>[2]>;
15
18
  /**
16
19
  * A fresh entity id: a ULID (26 Crockford base-32 characters) made from a UUIDv7 (48-bit ms, version,
17
20
  * 74 random bits, variant), so ids sort by the time they were minted; within one millisecond the
@@ -30,6 +33,14 @@ export declare function orderBetween(left: string | null | undefined, right: str
30
33
  * prototype's rule). A cycle cannot come from the runtime; were one there, it is cut where it closes.
31
34
  */
32
35
  export declare function treeOf(entities: Readonly<Record<string, SceneEntity>>): SceneTree;
36
+ /**
37
+ * The runtime's patch merge (`merge_set`): merges one level deep; a null deletes the key it names. A top-level null
38
+ * removes the component; an object merges into an object component key by key, a null sub-key removing that sub-key;
39
+ * an object given for a component that is absent or not an object replaces it without its null sub-keys, and nulls
40
+ * alone leave no component (an explicitly given `{}` is kept); any other value replaces. `undefined` is not given (the
41
+ * encoder skips it).
42
+ */
43
+ export declare function mergeSet(into: Record<string, unknown>, set: Record<string, unknown>): Record<string, unknown>;
33
44
  /**
34
45
  * One authoring batch being built (`txn`): the ops it sends as one atomic `apply`. Ops on an entity
35
46
  * spawned in the same batch fold into its spawn: a patch (of keys a patch may set) or a rename merges
@@ -41,11 +52,15 @@ export declare class Txn implements SceneTxn {
41
52
  get ops(): readonly SceneOp[];
42
53
  /** 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
54
  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;
55
+ /** Sets components of the entity: merges one level deep; a null deletes the key it names. */
56
+ patch(entity: EntityArg, set: PatchSet): void;
46
57
  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;
58
+ /**
59
+ * Moves the entity under `parent` (`null`: the world root), `before` or `after` a sibling (default: after the last).
60
+ * By default it keeps its world pose (the runtime's default: nothing sent); `preserveWorld: false` sends
61
+ * `preserve_world: false`, and it keeps its local xform.
62
+ */
63
+ reparent(entity: EntityArg, parent: EntityArg | null, opts?: ReparentOptions): void;
49
64
  /** 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
65
  despawn(entity: EntityArg, opts?: {
51
66
  cascade?: boolean;
package/dist/scene.js CHANGED
@@ -231,14 +231,45 @@ export function treeOf(entities) {
231
231
  return build(null);
232
232
  }
233
233
  // -------------------------------------------------------------------------------------------- txn
234
- /** One level of the runtime's patch merge: an object merges into an object key by key; anything else replaces. */
235
- function mergeSet(into, set) {
234
+ /**
235
+ * The runtime's patch merge (`merge_set`): merges one level deep; a null deletes the key it names. A top-level null
236
+ * removes the component; an object merges into an object component key by key, a null sub-key removing that sub-key;
237
+ * an object given for a component that is absent or not an object replaces it without its null sub-keys, and nulls
238
+ * alone leave no component (an explicitly given `{}` is kept); any other value replaces. `undefined` is not given (the
239
+ * encoder skips it).
240
+ */
241
+ export function mergeSet(into, set) {
236
242
  const out = { ...into };
237
243
  for (const [k, v] of Object.entries(set)) {
238
244
  if (v === undefined)
239
245
  continue;
246
+ if (v === null) {
247
+ delete out[k];
248
+ continue;
249
+ }
250
+ if (!isObject(v)) {
251
+ out[k] = v;
252
+ continue;
253
+ }
254
+ const given = Object.entries(v).filter(([, s]) => s !== undefined);
240
255
  const old = out[k];
241
- out[k] = isObject(old) && isObject(v) ? { ...old, ...v } : v;
256
+ if (isObject(old)) {
257
+ const next = { ...old };
258
+ for (const [s, sv] of given) {
259
+ if (sv === null)
260
+ delete next[s];
261
+ else
262
+ next[s] = sv;
263
+ }
264
+ out[k] = next;
265
+ }
266
+ else {
267
+ const kept = given.filter(([, s]) => s !== null);
268
+ if (kept.length === 0 && given.length > 0)
269
+ delete out[k];
270
+ else
271
+ out[k] = Object.fromEntries(kept);
272
+ }
242
273
  }
243
274
  return out;
244
275
  }
@@ -264,7 +295,7 @@ export class Txn {
264
295
  this.#ops.push({ op: "spawn", entity });
265
296
  return id;
266
297
  }
267
- /** Sets components of the entity (one level deep: an object merges into an object). */
298
+ /** Sets components of the entity: merges one level deep; a null deletes the key it names. */
268
299
  patch(entity, set) {
269
300
  const id = idOf(entity);
270
301
  // A protected key is the runtime's to refuse: such a patch is sent as it is.
@@ -287,13 +318,19 @@ export class Txn {
287
318
  }
288
319
  this.#ops.push({ op: "rename", id, name });
289
320
  }
290
- /** Moves the entity under `parent` (`null`: the world root), before or after a sibling (default: after the last). */
291
- reparent(entity, parent, place) {
321
+ /**
322
+ * Moves the entity under `parent` (`null`: the world root), `before` or `after` a sibling (default: after the last).
323
+ * By default it keeps its world pose (the runtime's default: nothing sent); `preserveWorld: false` sends
324
+ * `preserve_world: false`, and it keeps its local xform.
325
+ */
326
+ reparent(entity, parent, opts = {}) {
292
327
  const op = { op: "reparent", id: idOf(entity), parent: parent === null ? null : idOf(parent, "parent") };
293
- if (place && "before" in place)
294
- op.before = idOf(place.before, "before");
295
- else if (place && "after" in place)
296
- op.after = idOf(place.after, "after");
328
+ if (opts.before !== undefined)
329
+ op.before = idOf(opts.before, "before");
330
+ else if (opts.after !== undefined)
331
+ op.after = idOf(opts.after, "after");
332
+ if (opts.preserveWorld === false)
333
+ op.preserve_world = false;
297
334
  this.#ops.push(op);
298
335
  }
299
336
  /** 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). */
@@ -1972,7 +2009,7 @@ export class SceneHandleImpl {
1972
2009
  patch: (entity, set) => this.#txn(t => t.patch(entity, set)),
1973
2010
  upsert: (kind, opts) => this.#upsert(kind, opts),
1974
2011
  rename: (entity, name) => this.#txn(t => t.rename(entity, name)),
1975
- reparent: (entity, parent, place) => this.#txn(t => t.reparent(entity, parent, place)),
2012
+ reparent: (entity, parent, opts) => this.#txn(t => t.reparent(entity, parent, opts)),
1976
2013
  despawn: (entity, opts) => this.#txn(t => t.despawn(entity, opts)),
1977
2014
  setLocked: (entity, locked) => this.#txn(t => t.setLocked(entity, locked)),
1978
2015
  });
package/dist/synapse.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type Age, type ErrorBody, type ProjectSnapshot, type ServiceContract, t
2
2
  import { Node } from "./node.ts";
3
3
  import { StoreHandleImpl } from "./store.ts";
4
4
  import { type Warn } from "./topics.ts";
5
- import type { CallOptions, Catalog, ConnectOptions, ConnectTarget, ConnectionState, Discovery, DiscoveryState, Envelope, FetchToken, Info, Metrics, Publisher, Registration, Run, ServeHandler, ServeOptions, SceneHandle, StoreHandle, StoreOptions, SubscribeOptions, Subscription, Tasks, View, ViewOptions } from "./types.ts";
5
+ import type { CallOptions, Catalog, ConnectOptions, ConnectTarget, ConnectionState, Discovery, DiscoveryState, Envelope, FetchToken, Info, Metrics, ProjectListing, Publisher, Registration, Run, ServeHandler, ServeOptions, SceneHandle, StoreHandle, StoreOptions, SubscribeOptions, Subscription, Tasks, View, ViewOptions } from "./types.ts";
6
6
  import { type StateValue } from "./util.ts";
7
7
  export { FIRST_CONNECT_GIVE_UP_MS } from "./node.ts";
8
8
  export declare class Synapse implements AsyncDisposable {
@@ -133,6 +133,11 @@ export declare class Synapse implements AsyncDisposable {
133
133
  * resolves once the demoted session is connected.
134
134
  */
135
135
  leaveProject(): Promise<ProjectSnapshot>;
136
+ /**
137
+ * `synapse/project/list {}`: every project the runtime has, sorted by id: what each names (`name` when it has one,
138
+ * `scene_id`, `store_id`, whether it is the active one), or why it cannot be read (`error`). Changes nothing.
139
+ */
140
+ listProjects(): Promise<ProjectListing>;
136
141
  /**
137
142
  * The robot's task declarations, what runs (`status`), what the runtime can run (`catalog`), and the verbs; the same
138
143
  * object for the node's life. Each handle opens on its first touch and is the node's own (`node.close()` ends it).
package/dist/synapse.js CHANGED
@@ -355,6 +355,11 @@ export class Synapse {
355
355
  * resolves once the demoted session is connected.
356
356
  */
357
357
  leaveProject() { return this.#projectCall("leave", {}); }
358
+ /**
359
+ * `synapse/project/list {}`: every project the runtime has, sorted by id: what each names (`name` when it has one,
360
+ * `scene_id`, `store_id`, whether it is the active one), or why it cannot be read (`error`). Changes nothing.
361
+ */
362
+ listProjects() { return this.call("synapse/project/list", {}).result; }
358
363
  // -------------------------------------------------------------------------------- tasks
359
364
  #tasks;
360
365
  /**
package/dist/types.d.ts CHANGED
@@ -488,6 +488,8 @@ export interface Hardpoint {
488
488
  /**
489
489
  * An entity of the scene. The fields below are the ones the runtime and the early users read;
490
490
  * every other key passes through untouched.
491
+ * A component is absent (none), `{}` (there, nothing specified yet: every field at its default) or
492
+ * specified; a null component or sub-key is never stored (a patch deletes the key it names, a spawn drops it).
491
493
  */
492
494
  export interface SceneEntity {
493
495
  /** A ULID; never changes. */
@@ -620,8 +622,8 @@ export interface EntityAuthored {
620
622
  * not have: its `not_found`). A view by name whose name no entity holds has no id to send to: `not_found`
621
623
  * (`data: { name }`), nothing sent. Detached: `project_inactive`.
622
624
  */
623
- set<K extends keyof SceneEntity & string>(component: K, value: SceneEntity[K]): Promise<void>;
624
- set(components: Partial<SceneEntity>): Promise<void>;
625
+ set<K extends keyof SceneEntity & string>(component: K, value: PatchSet[K]): Promise<void>;
626
+ set(components: PatchSet): Promise<void>;
625
627
  }
626
628
  /** An entity view's live plane (`view.live`). Reads as the view does: nothing once the handle is detached or closed. */
627
629
  export interface EntityLive {
@@ -666,6 +668,12 @@ export interface SceneStatus {
666
668
  /** Lane rows dropped because their id's claim was refused (rows parked while it was asked, and rows written while refused). */
667
669
  unclaimedDrops: number;
668
670
  }
671
+ type NullSubKeys<T> = T extends readonly unknown[] ? T : T extends object ? {
672
+ [S in keyof T]?: T[S] | null;
673
+ } : T;
674
+ type PatchSet = {
675
+ [K in keyof SceneEntity]?: unknown extends SceneEntity[K] ? unknown : NullSubKeys<NonNullable<SceneEntity[K]>> | null;
676
+ };
669
677
  /** An authored change as the runtime applied it (a diff op of the scene's `authored` track). */
670
678
  export type SceneDiffOp = {
671
679
  op: "spawn";
@@ -679,7 +687,7 @@ export type SceneDiffOp = {
679
687
  } | {
680
688
  op: "patch";
681
689
  id: string;
682
- set: Partial<SceneEntity>;
690
+ set: PatchSet;
683
691
  rev: number;
684
692
  } | {
685
693
  op: "reparent";
@@ -710,7 +718,7 @@ export type SceneOp = {
710
718
  } | {
711
719
  op: "patch";
712
720
  id: string;
713
- set: Partial<SceneEntity>;
721
+ set: PatchSet;
714
722
  } | {
715
723
  op: "reparent";
716
724
  id: string;
@@ -733,20 +741,22 @@ export type SceneSpawnOptions = {
733
741
  parent?: string | SceneEntityView | null;
734
742
  id?: string;
735
743
  } & Partial<Omit<SceneEntity, "id" | "kind" | "name" | "parent" | "order" | "meta">>;
736
- /** Where `reparent` puts the entity among its new siblings (default: after the last): a sibling by its id or its view. */
737
- export type ScenePlace = {
738
- before: string | SceneEntityView;
739
- } | {
740
- after: string | SceneEntityView;
741
- };
742
744
  /** 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). */
743
745
  export interface SceneTxn {
744
746
  /** Spawns an entity of `kind`; returns its id. */
745
747
  spawn(kind: SceneKind, opts: SceneSpawnOptions): string;
746
- /** Sets components of the entity (an object merges into an object one level deep). */
747
- patch(entity: string | SceneEntityView, set: Partial<SceneEntity>): void;
748
+ /** Sets components of the entity: merges one level deep; a null deletes the key it names. */
749
+ patch(entity: string | SceneEntityView, set: PatchSet): void;
748
750
  rename(entity: string | SceneEntityView, name: string): void;
749
- reparent(entity: string | SceneEntityView, parent: string | SceneEntityView | null, place?: ScenePlace): void;
751
+ /**
752
+ * Moves the entity under `parent` (`null`: the world root), `before` or `after` a sibling (default: after the last).
753
+ * By default it keeps its world pose (its local xform is recomputed); `preserveWorld: false` keeps its local xform.
754
+ */
755
+ reparent(entity: string | SceneEntityView, parent: string | SceneEntityView | null, opts?: {
756
+ before?: string | SceneEntityView;
757
+ after?: string | SceneEntityView;
758
+ preserveWorld?: boolean;
759
+ }): void;
750
760
  despawn(entity: string | SceneEntityView, opts?: {
751
761
  cascade?: boolean;
752
762
  }): void;
@@ -797,7 +807,8 @@ export interface SceneAuthored {
797
807
  txn(build: (t: SceneTxn) => void): Promise<void>;
798
808
  /** Spawns an entity; its id, once the entities show it. */
799
809
  spawn(kind: SceneKind, opts: SceneSpawnOptions): Promise<string>;
800
- patch(entity: string | SceneEntityView, set: Partial<SceneEntity>): Promise<void>;
810
+ /** Sets components of the entity: merges one level deep; a null deletes the key it names. */
811
+ patch(entity: string | SceneEntityView, set: PatchSet): Promise<void>;
801
812
  /**
802
813
  * A builder's one verb, keyed by `name`; resolves with the entity's view (by id: that entity) once the entities show
803
814
  * it. The id is the runtime's answer, never the copy's: `spawn(kind, { name, parent, ...components })` (a fresh id) is
@@ -813,7 +824,16 @@ export interface SceneAuthored {
813
824
  id?: never;
814
825
  }): Promise<SceneEntityView>;
815
826
  rename(entity: string | SceneEntityView, name: string): Promise<void>;
816
- reparent(entity: string | SceneEntityView, parent: string | SceneEntityView | null, place?: ScenePlace): Promise<void>;
827
+ /**
828
+ * Moves the entity under `parent` (`null`: the world root), `before` or `after` a sibling (default: after the last).
829
+ * By default the entity keeps its world pose (its local xform is recomputed). With `preserveWorld: false` it keeps its
830
+ * local xform instead, so a tool moved to another flange snaps to it.
831
+ */
832
+ reparent(entity: string | SceneEntityView, parent: string | SceneEntityView | null, opts?: {
833
+ before?: string | SceneEntityView;
834
+ after?: string | SceneEntityView;
835
+ preserveWorld?: boolean;
836
+ }): Promise<void>;
817
837
  despawn(entity: string | SceneEntityView, opts?: {
818
838
  cascade?: boolean;
819
839
  }): Promise<void>;
@@ -1024,6 +1044,21 @@ export interface Tasks {
1024
1044
  /** Stops every project task (also when none ran). Robot tasks are not touched. */
1025
1045
  stopProjectTasks(): Promise<void>;
1026
1046
  }
1047
+ /** The answer of `node.listProjects()` (`synapse/project/list`): every project the runtime has, sorted by id. */
1048
+ export interface ProjectListing {
1049
+ projects: ProjectEntry[];
1050
+ }
1051
+ /** One project of a {@link ProjectListing}: what its document names (`name` when it has one), or why it cannot be read (`error`). */
1052
+ export type ProjectEntry = {
1053
+ id: string;
1054
+ name?: string;
1055
+ scene_id: string;
1056
+ store_id: string;
1057
+ active: boolean;
1058
+ } | {
1059
+ id: string;
1060
+ error: string;
1061
+ };
1027
1062
  export interface Catalog {
1028
1063
  readonly topics: Readonly<Record<string, {
1029
1064
  desc?: TrackDesc;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synapse-sdk/client",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Synapse SDK client: connect a node to the runtime; topics, services, calls, store, video.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -14,7 +14,7 @@
14
14
  "dist"
15
15
  ],
16
16
  "dependencies": {
17
- "@synapse-sdk/core": "0.3.7",
17
+ "@synapse-sdk/core": "0.3.8",
18
18
  "@moq/net": "0.4.2",
19
19
  "smol-toml": "1.9.0"
20
20
  },