@womp/kakapo-sdk 0.3.0 → 0.3.1

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/README.md CHANGED
@@ -53,10 +53,10 @@ dynamically in Node.js.
53
53
 
54
54
  Applications that already own a renderer connection can pass a `KakapoAPITransport` as
55
55
  `new KakapoAPI({ transport })`. The adapter defines connection lifecycle, RPC, and token responses.
56
- `captureScreenshot({ view: "current" })` requests the renderer's JPEG without changing the camera
57
- or requiring a cached scene. Current-view captures cannot include `targetId` or `targetIds`.
58
- Framed captures restore the camera they replaced after capture. If another client moves the camera
59
- while capture is pending, the SDK detects that change and preserves the newer camera instead.
56
+ `captureScreenshot({ view: "current" })` requests the renderer's JPEG without changing the camera
57
+ or requiring a cached scene. Current-view captures cannot include `targetId` or `targetIds`.
58
+ Framed captures restore the camera they replaced after capture. If another client moves the camera
59
+ while capture is pending, the SDK detects that change and preserves the newer camera instead.
60
60
 
61
61
  All public inputs are checked at runtime as well as by TypeScript. Invalid calls throw structured
62
62
  `KakapoValidationError` objects with a stable `code`, offending `path`, expected rule, and a
@@ -80,15 +80,22 @@ typed `points` array replaces that data; use the granular operations when editin
80
80
  Engine-backed reads such as bounding boxes and screenshots remain asynchronous. Run them before the
81
81
  first draft mutation or after `editScene()` commits.
82
82
 
83
- Each `editScene()` refreshes before creating its private draft. Top-level edits are serialized per
84
- client. Changed drafts reread canonical state before submission and reject a changed baseline;
85
- camera movement alone does not invalidate an ordinary scene edit. This read does not eliminate
86
- a cross-client race between validation and submission through a backend wrapper. Commit patches add
87
- RFC 6902 `test` guards only for the wrapper partitions the draft changes: `/ext` and the remaining
88
- scene. This avoids turning a geometry-only edit into an unnecessary extension operation. Adding a
89
- missing `/ext` root is rejected because the existing wrapper cannot guard that cross-partition change;
90
- refresh or migrate the scene before retrying. Calling a scene-mutating method through raw `rpc()`
83
+ Each `editScene()` refreshes before creating its private draft. Top-level edits are serialized per
84
+ client. Changed drafts reread canonical state before submission and reject a changed baseline;
85
+ camera movement alone does not invalidate an ordinary scene edit. This read does not eliminate
86
+ a cross-client race between validation and submission through a backend wrapper. Commit patches add
87
+ RFC 6902 `test` guards only for the wrapper partitions the draft changes: `/ext` and the remaining
88
+ scene. This avoids turning a geometry-only edit into an unnecessary extension operation. Adding a
89
+ missing `/ext` root is rejected because the existing wrapper cannot guard that cross-partition change;
90
+ refresh or migrate the scene before retrying. Calling a scene-mutating method through raw `rpc()`
91
91
  deliberately marks the cache stale.
92
+
93
+ Node IDs allocated by `createNode()` and `cloneNode()` increase for the lifetime of a `KakapoAPI`
94
+ instance. IDs observed in scene snapshots, draft patches, or numeric node-extension keys remain
95
+ reserved after deletion, rollback, and reconnect. This prevents delete-and-create edits from changing
96
+ an existing renderer object's type by reusing its ID. A new API instance starts above IDs in its current
97
+ snapshot; this is not a persistent project-wide allocator. Raw patches supply their own IDs and must
98
+ preserve object identity; concurrent clients remain subject to the scene revision guards.
92
99
 
93
100
  `editSceneAndSave(callback)` submits the standard `scene_state_patch` command and, after
94
101
  acknowledgement, calls `save_scene` once on the same connection. No new Kakapo command is required.
package/dist/api.d.ts CHANGED
@@ -84,6 +84,7 @@ export declare class KakapoAPI {
84
84
  private draftScene?;
85
85
  private draftDirty;
86
86
  private rendererSyncRequired;
87
+ private nextNodeId;
87
88
  constructor(options?: KakapoAPIOptions);
88
89
  get isConnected(): boolean;
89
90
  connect(): Promise<void>;
@@ -137,6 +138,7 @@ export declare class KakapoAPI {
137
138
  private ensureRendererReady;
138
139
  private captureRendererFrame;
139
140
  private screenshotDirection;
141
+ private reserveNodeIds;
140
142
  private createNode;
141
143
  private cloneNode;
142
144
  private updateNode;
package/dist/api.js CHANGED
@@ -24,6 +24,7 @@ export class KakapoAPI {
24
24
  draftScene;
25
25
  draftDirty = false;
26
26
  rendererSyncRequired = false;
27
+ nextNodeId = 1;
27
28
  constructor(options = {}) {
28
29
  this.transport = options.transport ?? new KakapoTransport({
29
30
  url: options.url ?? "ws://127.0.0.1:5502",
@@ -77,6 +78,7 @@ export class KakapoAPI {
77
78
  const result = await this.call("scene_state_get");
78
79
  const scene = parseJsonResult(result, "scene_state_get");
79
80
  assertScene(scene, "refreshScene");
81
+ this.reserveNodeIds(scene);
80
82
  this.rawScene = scene;
81
83
  this.cacheValid = true;
82
84
  return this.getScene();
@@ -207,6 +209,7 @@ export class KakapoAPI {
207
209
  applyScenePatch(operations) {
208
210
  const candidate = applyJsonPatch(this.requireDraft("applyScenePatch"), operations);
209
211
  assertScene(candidate, "applyScenePatch");
212
+ this.reserveNodeIds(candidate);
210
213
  this.draftScene = candidate;
211
214
  this.draftDirty = true;
212
215
  return this.getScene();
@@ -439,12 +442,24 @@ export class KakapoAPI {
439
442
  };
440
443
  return directions[view];
441
444
  }
445
+ reserveNodeIds(scene) {
446
+ // Never rewind after deletion, rollback, or reconnect: old IDs may still be referenced.
447
+ for (const record of [scene.nodes, scene.ext ?? {}]) {
448
+ for (const key of Object.keys(record)) {
449
+ const id = Number(key);
450
+ if (Number.isSafeInteger(id) && id >= this.nextNodeId)
451
+ this.nextNodeId = id + 1;
452
+ }
453
+ }
454
+ }
442
455
  createNode(input) {
443
456
  const scene = this.requireDraft("createNode");
444
457
  assertId(input.parentId, "createNode", "parentId", true);
445
458
  if (!scene.nodes[String(input.parentId)])
446
459
  throw missingNode(input.parentId, "createNode");
447
- const id = nextId(scene.nodes);
460
+ const id = this.nextNodeId;
461
+ assertId(id, "createNode", "nodeId");
462
+ this.nextNodeId++;
448
463
  const initial = defaultNode(input.kind, id, input.name ?? "");
449
464
  const node = this.mergeNode(initial, input.properties ?? {});
450
465
  const children = this.treeArrays(scene);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@womp/kakapo-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Typed Node.js and browser SDK for controlling Kakapo over WebSocket",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",