@solarisdk/core 0.4.3 → 0.4.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/desktop.d.ts CHANGED
@@ -41,6 +41,11 @@ export declare class Desktop extends SessionHandle {
41
41
  doubleClick: (x: number, y: number, opts?: ClickOptions) => Promise<void>;
42
42
  down: (x: number, y: number, button?: MouseButton) => Promise<void>;
43
43
  up: (x: number, y: number, button?: MouseButton) => Promise<void>;
44
+ /**
45
+ * Scroll the wheel with the pointer at (x, y). `direction` defaults to "down", `amount` (wheel clicks, 1-50)
46
+ * to 3. The guest takes the wheel delta in the `y` field and does not move the pointer, so the pointer is moved
47
+ * first and (x, y) is never passed as the delta (it used to be: scroll(x, 500) fired 500 clicks, 20-30 s).
48
+ */
44
49
  scroll: (x: number, y: number, opts?: ScrollOptions) => Promise<void>;
45
50
  drag: (from: {
46
51
  x: number;
package/dist/desktop.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { SessionHandle } from "./handle.js";
2
+ import { SolariError } from "./errors.js";
2
3
  import { base64ToBytes } from "./transport.js";
3
4
  function buttonToCode(button) {
4
5
  if (button === undefined)
@@ -79,7 +80,23 @@ export class Desktop extends SessionHandle {
79
80
  }).then(() => undefined),
80
81
  down: (x, y, button = "left") => this.mouseCall(x, y, "down", button),
81
82
  up: (x, y, button = "left") => this.mouseCall(x, y, "up", button),
82
- scroll: (x, y, opts = {}) => this.mouseCall(x, y, "scroll", opts.button, opts.humanize),
83
+ scroll: async (x, y, opts = {}) => {
84
+ const amount = opts.amount ?? 3;
85
+ if (!Number.isInteger(amount) || amount < 1 || amount > 50) {
86
+ throw new SolariError("scroll amount must be a whole number of wheel clicks from 1 to 50");
87
+ }
88
+ const direction = opts.direction ?? "down";
89
+ if (direction !== "up" && direction !== "down") {
90
+ throw new SolariError('scroll direction must be "up" or "down"');
91
+ }
92
+ await this.mouseCall(x, y, "move", undefined, opts.humanize);
93
+ await this.guiCall("input.mouse", {
94
+ x,
95
+ y: direction === "up" ? amount : -amount,
96
+ action: "scroll",
97
+ button: buttonToCode(opts.button),
98
+ });
99
+ },
83
100
  drag: (from, to, button = "left") => this.guiCall("input.drag", { from, to, button: buttonToCode(button) }).then(() => undefined),
84
101
  };
85
102
  mouseCall(x, y, action, button, humanize) {
package/dist/handle.d.ts CHANGED
@@ -56,6 +56,16 @@ export interface SessionConfig extends ControlChannelConfig {
56
56
  }
57
57
  /** The longest `timeoutMs` the gateway accepts for a command (600 000 ms), and the default for `commands.run`. */
58
58
  export declare const DEFAULT_COMMAND_TIMEOUT_MS = 600000;
59
+ /**
60
+ * The session `id` is no longer running (killed or paused, through any handle or by id), so the exit reminder must not
61
+ * name it. Tracking is by id, not by handle: two handles to one session, or a `client.kill(id)` with no handle at all,
62
+ * all clear the same entry.
63
+ */
64
+ export declare function markSessionReleased(id: string): void;
65
+ /** The lines the exit reminder prints: one per distinct session id, however many handles track it. */
66
+ export declare function exitReminderMessages(): string[];
67
+ /** Ids the exit reminder would name right now (for tests). */
68
+ export declare function unreleasedSessionIds(): string[];
59
69
  /** Cap on PTY output held before the first onData() subscriber (bytes, total). */
60
70
  export declare const PTY_EARLY_BUFFER_MAX: number;
61
71
  export interface SessionHandle {
@@ -172,10 +182,11 @@ export declare class SessionHandle {
172
182
  }>;
173
183
  readonly volumes: {
174
184
  /**
175
- * Mount a volume into the guest at `path`. Thin pass-through to the guest
176
- * `volume.mount` RPC; the gateway volume routes may be a 501 stub today.
185
+ * Not available at runtime. A volume is mounted when the sandbox is created (or resumed): the guest cannot fetch the
186
+ * storage credentials itself, and the gateway has no runtime-mount route, so the old pass-through to the guest
187
+ * `volume.mount` RPC always failed with "bucket is required". Pass `volumes: [{ volumeId, path }]` at create.
177
188
  */
178
- mount: (volId: string, path: string) => Promise<void>;
189
+ mount: (_volId: string, _path: string) => Promise<void>;
179
190
  };
180
191
  readonly git: {
181
192
  /** Clone `url` into `opts.path` (or git's default dir) under `opts.cwd`. */
package/dist/handle.js CHANGED
@@ -6,6 +6,18 @@ export const DEFAULT_COMMAND_TIMEOUT_MS = 600_000;
6
6
  const unreleased = new Map();
7
7
  const MAX_TRACKED_UNRELEASED = 1000;
8
8
  let exitHookInstalled = false;
9
+ export function markSessionReleased(id) {
10
+ for (const [token, tracked] of unreleased)
11
+ if (tracked === id)
12
+ unreleased.delete(token);
13
+ }
14
+ export function exitReminderMessages() {
15
+ return [...new Set(unreleased.values())].map((id) => `[solari] this process is exiting but session ${shortId(id)} is still running and will bill until its ` +
16
+ `idle timeout. Call kill() to release it (or declare it with \`await using\`).`);
17
+ }
18
+ export function unreleasedSessionIds() {
19
+ return [...unreleased.values()];
20
+ }
9
21
  function trackUnreleased(token, id) {
10
22
  unreleased.set(token, id);
11
23
  if (unreleased.size > MAX_TRACKED_UNRELEASED)
@@ -14,10 +26,8 @@ function trackUnreleased(token, id) {
14
26
  return;
15
27
  exitHookInstalled = true;
16
28
  process.on("exit", () => {
17
- for (const id of unreleased.values()) {
18
- console.warn(`[solari] this process is exiting but session ${shortId(id)} is still running and will bill until its ` +
19
- `idle timeout. Call kill() to release it (or declare it with \`await using\`).`);
20
- }
29
+ for (const line of exitReminderMessages())
30
+ console.warn(line);
21
31
  unreleased.clear();
22
32
  });
23
33
  }
@@ -70,7 +80,8 @@ export class SessionHandle {
70
80
  return this.channel.reconnect();
71
81
  }
72
82
  close(opts = {}) {
73
- unreleased.delete(this.releaseToken);
83
+ if (!opts.quiet)
84
+ unreleased.delete(this.releaseToken);
74
85
  if (!this.killed && !opts.quiet && typeof console !== "undefined") {
75
86
  console.warn(`[solari] close() on session ${shortId(this.id)} closed the local channel only — ` +
76
87
  `the remote machine is still running and will bill until its idle ` +
@@ -399,9 +410,7 @@ export class SessionHandle {
399
410
  return this.hooks.uploadUrl(this.id, path);
400
411
  }
401
412
  volumes = {
402
- mount: (volId, path) => this.channel
403
- .call("volume.mount", { volumeId: volId, path })
404
- .then(() => undefined),
413
+ mount: (_volId, _path) => Promise.reject(new SolariError("volumes.mount() is not supported on a running sandbox: pass volumes: [{ volumeId, path }] when you create it")),
405
414
  };
406
415
  git = {
407
416
  clone: (url, opts = {}) => this.gitClone(url, opts),
@@ -624,6 +633,7 @@ export class SessionHandle {
624
633
  throw new Error("pause requires a client-created handle");
625
634
  }
626
635
  await this.hooks.pause(this.id);
636
+ markSessionReleased(this.id);
627
637
  this.close({ quiet: true });
628
638
  }
629
639
  async resume() {
@@ -631,6 +641,7 @@ export class SessionHandle {
631
641
  throw new Error("resume requires a client-created handle");
632
642
  }
633
643
  const r = await this.hooks.resume(this.id);
644
+ trackUnreleased(this.releaseToken, this.id);
634
645
  this.channel.setControlUrl(r.controlUrl);
635
646
  await this.channel.reconnect();
636
647
  }
@@ -673,6 +684,7 @@ export class SessionHandle {
673
684
  if (this.hooks.kill)
674
685
  await this.hooks.kill(this.id);
675
686
  this.killed = true;
687
+ markSessionReleased(this.id);
676
688
  this.close();
677
689
  }
678
690
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { DEFAULT_COMMAND_TIMEOUT_MS, SessionHandle } from "./handle.js";
1
+ export { DEFAULT_COMMAND_TIMEOUT_MS, SessionHandle, markSessionReleased, unreleasedSessionIds, exitReminderMessages } from "./handle.js";
2
2
  export { shortId } from "./ids.js";
3
3
  export { formatIdlePolicy } from "./idle-policy.js";
4
4
  export type { SessionConfig, SessionHooks } from "./handle.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { DEFAULT_COMMAND_TIMEOUT_MS, SessionHandle } from "./handle.js";
1
+ export { DEFAULT_COMMAND_TIMEOUT_MS, SessionHandle, markSessionReleased, unreleasedSessionIds, exitReminderMessages } from "./handle.js";
2
2
  export { shortId } from "./ids.js";
3
3
  export { formatIdlePolicy } from "./idle-policy.js";
4
4
  export { Desktop } from "./desktop.js";
package/dist/types.d.ts CHANGED
@@ -632,6 +632,10 @@ export interface MouseResult {
632
632
  export interface ScrollOptions {
633
633
  button?: MouseButton;
634
634
  humanize?: boolean;
635
+ /** Which way the wheel turns. Default "down". */
636
+ direction?: "up" | "down";
637
+ /** Wheel clicks, a whole number from 1 to 50. Default 3. */
638
+ amount?: number;
635
639
  }
636
640
  export interface ClickOptions {
637
641
  button?: MouseButton;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solarisdk/core",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Shared runtime for the Solari SDK \u2014 transport, session handles, images, templates, and types used by @solarisdk/desktop and @solarisdk/sandbox.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",