@womp/kakapo-sdk 0.2.0 → 0.2.2

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
@@ -51,6 +51,11 @@ kakapo.disconnect();
51
51
  The transport uses the browser's native `WebSocket` when bundled for the web and loads `ws`
52
52
  dynamically in Node.js.
53
53
 
54
+ Applications that already own a renderer connection can pass a `KakapoAPITransport` as
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
+
54
59
  All public inputs are checked at runtime as well as by TypeScript. Invalid calls throw structured
55
60
  `KakapoValidationError` objects with a stable `code`, offending `path`, expected rule, and a
56
61
  correction `hint` intended for automated agents.
package/dist/api.js CHANGED
@@ -25,7 +25,7 @@ export class KakapoAPI {
25
25
  draftDirty = false;
26
26
  rendererSyncRequired = false;
27
27
  constructor(options = {}) {
28
- this.transport = new KakapoTransport({
28
+ this.transport = options.transport ?? new KakapoTransport({
29
29
  url: options.url ?? "ws://127.0.0.1:5502",
30
30
  requestTimeoutMs: options.requestTimeoutMs ?? 10_000,
31
31
  connectTimeoutMs: options.connectTimeoutMs ?? 30_000,
@@ -211,6 +211,17 @@ export class KakapoAPI {
211
211
  async captureScreenshot(options) {
212
212
  this.assertEngineReadAllowed("captureScreenshot");
213
213
  const { targetId, timeoutMs } = options;
214
+ if (options.view === "current") {
215
+ if (targetId !== undefined || options.targetIds !== undefined) {
216
+ throw new KakapoValidationError("A current-view screenshot cannot specify framing targets.", {
217
+ code: "AMBIGUOUS_SCREENSHOT_TARGET",
218
+ operation: "captureScreenshot",
219
+ expected: "current view without target selectors",
220
+ });
221
+ }
222
+ const frame = await this.captureRendererFrame(timeoutMs);
223
+ return { data: frame.payload, mediaType: "image/jpeg", width: frame.width, height: frame.height, view: "current", targetId: 0 };
224
+ }
214
225
  if (targetId !== undefined && options.targetIds !== undefined) {
215
226
  throw new KakapoValidationError("captureScreenshot accepts targetId or targetIds, not both.", {
216
227
  code: "AMBIGUOUS_SCREENSHOT_TARGET",
@@ -282,8 +293,7 @@ export class KakapoAPI {
282
293
  async captureRendererFrame(timeoutMs) {
283
294
  const token = this.newToken();
284
295
  const waiting = this.waitForToken(token, timeoutMs);
285
- await this.call("get_color_buffer", [[token]]);
286
- const message = await waiting;
296
+ const [, message] = await Promise.all([this.call("get_color_buffer", [[token]]), waiting]);
287
297
  if (!isBinaryFrame(message)) {
288
298
  const error = "error" in message ? String(message.error) : "Kakapo returned a non-binary renderer frame";
289
299
  throw new KakapoRpcError("get_color_buffer", error);
package/dist/types.d.ts CHANGED
@@ -322,7 +322,8 @@ export interface CaptureScreenshotOptions {
322
322
  /** Omit both selectors to frame all visible top-level scene nodes. */
323
323
  targetId?: number;
324
324
  targetIds?: number[];
325
- view?: ScreenshotView;
325
+ /** "current" captures the renderer's existing camera without framing or reading scene state. */
326
+ view?: ScreenshotView | "current";
326
327
  timeoutMs?: number;
327
328
  }
328
329
  export interface ScreenshotImage {
@@ -330,7 +331,7 @@ export interface ScreenshotImage {
330
331
  mediaType: "image/jpeg";
331
332
  width: number;
332
333
  height: number;
333
- view: ScreenshotView;
334
+ view: ScreenshotView | "current";
334
335
  targetId: number;
335
336
  }
336
337
  export type JsonPatchOperation = {
@@ -357,7 +358,17 @@ export interface ListNodeOptions {
357
358
  export interface DeleteNodeOptions {
358
359
  recursive?: boolean;
359
360
  }
361
+ /** An application can supply its existing connection; its adapter owns connection lifecycle. */
362
+ export interface KakapoAPITransport {
363
+ readonly connected: boolean;
364
+ connect(): Promise<void>;
365
+ disconnect(): void;
366
+ rpc<T>(method: string, params?: JsonValue[]): Promise<T>;
367
+ newToken(): string;
368
+ waitForToken(token: string, timeoutMs?: number): Promise<TokenMessage>;
369
+ }
360
370
  export interface KakapoAPIOptions {
371
+ transport?: KakapoAPITransport;
361
372
  url?: string;
362
373
  requestTimeoutMs?: number;
363
374
  connectTimeoutMs?: number;
@@ -143,7 +143,7 @@ export function assertScene(scene, operation) {
143
143
  if (!parentRaw)
144
144
  fail(`Tree parent ${parentId} does not exist.`, operation, `tree.${parentId}`, parentId, "existing parent node", undefined, "MISSING_TREE_NODE");
145
145
  const parentKind = kindFromWire(parentRaw.type);
146
- if (!CONTAINERS.has(parentKind))
146
+ if (Object.keys(children).length > 0 && !CONTAINERS.has(parentKind))
147
147
  fail(`Node ${parentId} (${parentKind}) cannot contain children.`, operation, `tree.${parentId}`, parentKind, "Union or Group parent", "Reparent the children under a Union or Group.", "LEAF_NODE_HAS_CHILDREN");
148
148
  for (const childId of Object.values(children)) {
149
149
  if (!scene.nodes[String(childId)])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@womp/kakapo-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Typed Node.js and browser SDK for controlling Kakapo over WebSocket",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",