@selvajs/compute 5.0.0-beta.8 → 5.0.0

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
@@ -21,6 +21,8 @@ objects, use [`@selvajs/visualization`](https://www.npmjs.com/package/@selvajs/v
21
21
  npm install @selvajs/compute
22
22
  ```
23
23
 
24
+ The package is ESM only, with two entrypoints: `/core` and `/grasshopper`.
25
+
24
26
  Two entrypoints: `@selvajs/compute/grasshopper` (client, scheduler, data trees, IO parsing) and
25
27
  `@selvajs/compute/core` (low-level fetch, errors, config). There is no root entry: import a
26
28
  subpath.
@@ -65,6 +67,10 @@ scheduler.subscribe(() => {
65
67
  });
66
68
  ```
67
69
 
70
+ New here? [How the client works](https://selva.dev/docs/packages/build/how-it-works) explains the pieces, and [Common tasks](https://selva.dev/docs/packages/build/common-tasks) has copy-ready examples.
71
+
72
+ > `client.solve` throws on solver errors; `scheduler.solve` resolves with them. See [Errors](#errors).
73
+
68
74
  ## Configuring the scheduler
69
75
 
70
76
  Pick the row that matches what the user is doing:
@@ -72,7 +78,7 @@ Pick the row that matches what the user is doing:
72
78
  | Workload | `mode` | `timeoutMs` | `retry` | Notes |
73
79
  | --------------------------------- | --------------- | ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
74
80
  | **Slider scrubs / live previews** | `'latest-wins'` | `30_000` | default | Aborts in-flight solves when newer values arrive. Add `cache: { maxBytes: 64 * 1024 * 1024 }` for instant repeats. |
75
- | **Submit / long-running jobs** | `'queue'` | `0` (no timeout) | `{ attempts: 1 }` | Serial queue. Pass a caller `signal` so users can hit Cancel. See [Long jobs behind a proxy](#long-jobs-behind-a-proxy). |
81
+ | **Submit / long-running jobs** | `'queue'` | `0` (no timeout) | default | Serial queue. Pass a caller `signal` so users can hit Cancel. See [Long jobs behind a proxy](#long-jobs-behind-a-proxy). |
76
82
  | **Background / batch parallel** | `'queue'` | `60_000` | `{ attempts: 2 }` | Set `maxConcurrent` (e.g. 4) to run that many solves at once; queue mode defaults to 1. |
77
83
 
78
84
  Create one scheduler per UI surface: their queues, cancel scopes and caches are independent.
@@ -110,6 +116,20 @@ work around that: raise the proxy's idle timeout before running long solves from
110
116
  solves longer than a couple of minutes, run this library server-side behind your own job queue
111
117
  and expose a status endpoint to the browser.
112
118
 
119
+ On Node, undici stops waiting for response headers at 300 s, and neither `timeoutMs` nor `0` lifts
120
+ that: the solve fails with `NETWORK_ERROR` while it is still running on the server, and a retry
121
+ would run it a second time. Past 300 s, keep `retry` off and pass a `fetch` with a longer limit:
122
+
123
+ ```ts
124
+ import { Agent, fetch as undiciFetch } from 'undici';
125
+
126
+ const dispatcher = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
127
+ const client = await GrasshopperClient.create({
128
+ serverUrl,
129
+ fetch: (url, init) => undiciFetch(url, { ...init, dispatcher })
130
+ });
131
+ ```
132
+
113
133
  ## Errors
114
134
 
115
135
  Every failure is a `ComputeError`. Branch on `err.code`, one of `ErrorCodes`, not on the message.
@@ -147,17 +167,16 @@ Read outputs through `GrasshopperResponseProcessor`, or a whole tree with
147
167
  `getTreeValue` is the shortcut for a one-branch param: it unwraps a single item and throws when
148
168
  the param has more than one branch.
149
169
 
150
- Pass a rhino3dm module as `{ rhino }` in the `getValues` / `getValue` options to decode geometry. Its objects live on the WASM
151
- heap, which garbage collection never frees: release them with `disposeRhinoObjects`. Install
152
- `rhino3dm` yourself; this package doesn't depend on it.
170
+ Pass a rhino3dm module as `{ rhino }` in the `getValues` / `getValue` options to decode geometry.
171
+ Its objects live on the WASM heap, which garbage collection never frees: release them with
172
+ `disposeRhinoObjects`. Install `rhino3dm` yourself; this package doesn't depend on it.
153
173
 
154
174
  ## File outputs
155
175
 
156
- File outputs need the [Selva plugin](https://www.food4rhino.com/en/app/selva?lang=en) and the
157
- [VektorNode compute fork](https://github.com/VektorNode/compute.rhino3d); standard Rhino Compute
158
- drops them. Read one with `getValue` and it comes back as `FileData`.
176
+ File outputs need the Selva plugin and the VektorNode fork (see [Requirements](#requirements));
177
+ standard Rhino Compute drops them. Read one with `getValue` and it comes back as `FileData`.
159
178
 
160
- Each file component has a `Sub Folder` input, where `::` nests (as in Rhino layer names). The
179
+ Each file component has a `Sub Folder` input, where `/` or `::` nests (as in Rhino layer names). The
161
180
  first segment is the root: `downloadFileDataByRoot` from `/core` turns each root into its own
162
181
  archive and puts files without one in the fallback archive. With `'export'` as the fallback name:
163
182
 
@@ -174,8 +193,6 @@ archive and puts files without one in the fallback archive. With `'export'` as t
174
193
 
175
194
  ## Requirements
176
195
 
177
- **Node.js >= 24.**
178
-
179
196
  The [official McNeel Rhino Compute](https://github.com/mcneel/compute.rhino3d) handles plain
180
197
  Grasshopper solving. The [VektorNode compute fork](https://github.com/VektorNode/compute.rhino3d)
181
198
  adds input grouping (`groupName`), persistent input ids keyed on Grasshopper object GUIDs, file
@@ -185,7 +202,9 @@ file-export components those features read.
185
202
 
186
203
  ## Troubleshooting
187
204
 
188
- **`Network error: Failed to fetch`** means the browser couldn't reach the server. Check that
205
+ **`CORS_ERROR` (`Request failed: Failed to fetch`)** means the browser got no response, which it
206
+ reports the same way for a CORS block and for an unreachable server. Outside a browser the same
207
+ failure is `NETWORK_ERROR`. Check that
189
208
  `curl http://localhost:6500/` returns 200; that a server on another origin sends
190
209
  `Access-Control-Allow-Origin` (standard Rhino Compute doesn't: put a proxy in front that adds it,
191
210
  or use the [VektorNode fork](https://github.com/VektorNode/compute.rhino3d)); that an HTTPS app
package/dist/core.d.ts CHANGED
@@ -1,2 +1,190 @@
1
- import { A as ServerErrorCodeMap, C as enableDebugLogging, D as ComputeAuth, E as fetchCompute, M as ComputeError, N as ErrorCode, O as ComputeConfig, P as ErrorCodes, S as Logger, T as setLogger, _ as SolveDefinition, a as extractFilesFromComputeResponse, b as hasField, c as FileData, d as ProbeVerdict, f as classifyProbeFailure, g as DefinitionRef, h as validateServerUrl, i as downloadFileDataByRoot, j as ServerTiming, k as RetryPolicy, l as ProcessedFile, m as ValidateServerUrlOptions, n as subFolderSegments, o as DownloadOptions, p as DEFAULT_BLOCKED_HOST, r as downloadFileData, s as FileBaseInfo, t as groupFilesByRoot, u as ProbeFailure, v as isDefinitionRef, w as getLogger, x as readField, y as decodeBase64ToBinary } from "./index-DDmprCht.js";
2
- export { type ComputeAuth, type ComputeConfig, ComputeError, DEFAULT_BLOCKED_HOST, type DefinitionRef, type DownloadOptions, type ErrorCode, ErrorCodes, type FileBaseInfo, type FileData, type Logger, type ProbeFailure, type ProbeVerdict, type ProcessedFile, type RetryPolicy, type ServerErrorCodeMap, type ServerTiming, type SolveDefinition, type ValidateServerUrlOptions, classifyProbeFailure, decodeBase64ToBinary, downloadFileData, downloadFileDataByRoot, enableDebugLogging, extractFilesFromComputeResponse, fetchCompute, getLogger, groupFilesByRoot, hasField, isDefinitionRef, readField, setLogger, subFolderSegments, validateServerUrl };
1
+ import { a as DefinitionRef, c as ComputeAuth, d as ServerErrorCodeMap, f as ServerTiming, h as ErrorCodes, i as ProcessedFile, l as ComputeConfig, m as ErrorCode, n as FileBaseInfo, o as SolveDefinition, p as ComputeError, r as FileData, s as isDefinitionRef, t as DownloadOptions, u as RetryPolicy } from "./types-Dm9QFVvA.js";
2
+ /**
3
+ * POST `args` as JSON to `<serverUrl>/<endpoint>` with retry, timeout and abort handling, and
4
+ * return the parsed response. Most callers want a higher-level API.
5
+ *
6
+ * `R` names the expected response shape; nothing checks it, so the default `unknown` forces a
7
+ * narrowing.
8
+ *
9
+ * @example
10
+ * const response = await fetchCompute('grasshopper', args, {
11
+ * serverUrl: 'https://my-server.com',
12
+ * timeoutMs: 30_000,
13
+ * retry: { attempts: 2 },
14
+ * signal: controller.signal
15
+ * });
16
+ */
17
+ export declare function fetchCompute<R = unknown>(endpoint: string, args: Record<string, any>, config: ComputeConfig): Promise<R>;
18
+ interface Logger {
19
+ debug(message: string, ...args: unknown[]): void;
20
+ info(message: string, ...args: unknown[]): void;
21
+ warn(message: string, ...args: unknown[]): void;
22
+ error(message: string, ...args: unknown[]): void;
23
+ }
24
+ export declare function getLogger(): Logger;
25
+ /**
26
+ * Route the package's logs to `logger`; `null` silences them (the default).
27
+ *
28
+ * @throws {ComputeError} `INVALID_CONFIG` if the logger lacks one of the four methods, rather
29
+ * than a "debug is not a function" later at an unrelated call site.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { setLogger } from '@selvajs/compute/core';
34
+ *
35
+ * setLogger(console);
36
+ * setLogger({
37
+ * debug: (msg, ...args) => myLogger.debug(msg, ...args),
38
+ * info: (msg, ...args) => myLogger.info(msg, ...args),
39
+ * warn: (msg, ...args) => myLogger.warn(msg, ...args),
40
+ * error: (msg, ...args) => myLogger.error(msg, ...args)
41
+ * });
42
+ * setLogger(null);
43
+ * ```
44
+ */
45
+ export declare function setLogger(logger: Logger | Console | null): void;
46
+ /** Shorthand for `setLogger(console)`. */
47
+ export declare function enableDebugLogging(): void;
48
+ /**
49
+ * Case-insensitive field reads for wire payloads.
50
+ *
51
+ * Rhino Compute branches serialize the same field in different casings. mcneel 8.x/9.x send the IO
52
+ * schema PascalCase (`ParamType`, `Default`, `InnerTree`): those C# classes carry no
53
+ * `[JsonProperty]`. The VektorNode fork sends camelCase, except inside the `default` DataTree
54
+ * (`ParamName`, `InnerTree`), an external type it can't attribute. So one response can mix casings.
55
+ * Read the fields you need through here and leave the rest verbatim: rewriting every key corrupts
56
+ * user-authored ones such as value-list labels.
57
+ */
58
+ /**
59
+ * Read `name` in any casing, preferring an exact-case match. `undefined` when no own key matches or
60
+ * `obj` isn't an object.
61
+ */
62
+ export declare function readField<T = unknown>(obj: unknown, name: string): T | undefined;
63
+ /** Whether `name` is present in any casing, even with a null or undefined value. */
64
+ export declare function hasField(obj: unknown, name: string): boolean;
65
+ /**
66
+ * Decode base64 to bytes, normalizing and validating the same way in Node and browsers.
67
+ * `packages/visualization/src/shared/encoding.ts` keeps a copy that throws its own error class;
68
+ * change both together.
69
+ *
70
+ * @throws {ComputeError} `ENCODING_ERROR` if invalid, or `ENVIRONMENT_ERROR` if the runtime has no decoder
71
+ */
72
+ export declare function decodeBase64ToBinary(base64File: string): Uint8Array;
73
+ /**
74
+ * The public McNeel endpoint, blocked by default: users must point at their own server. Its raw IP
75
+ * isn't blocked: it sits behind a load balancer with no stable address to pin.
76
+ */
77
+ export declare const DEFAULT_BLOCKED_HOST = "compute.rhino3d.com";
78
+ interface ValidateServerUrlOptions {
79
+ /**
80
+ * Hostnames rejected as a `serverUrl` — a backend's shared public endpoint,
81
+ * which callers must not point at. Defaults to `[DEFAULT_BLOCKED_HOST]`; pass
82
+ * `[]` to block nothing. Compared lowercased with any trailing dot stripped.
83
+ */
84
+ blockedHosts?: readonly string[];
85
+ }
86
+ /**
87
+ * Validate a compute `serverUrl` and return it trimmed, without trailing slashes.
88
+ *
89
+ * @throws {ComputeError} `INVALID_CONFIG` when the URL is empty, isn't http(s), doesn't parse,
90
+ * embeds credentials, has a query or fragment, or names a blocked host.
91
+ */
92
+ export declare function validateServerUrl(raw: string, options?: ValidateServerUrlOptions): string;
93
+ /**
94
+ * Why a liveness probe failed, and whether waiting can change the answer.
95
+ *
96
+ * `retryable` is the whole point: a powered-off VM and a booting one both read
97
+ * as "offline" from a single probe, but only one of them will ever come up. A
98
+ * caller that can't tell them apart has to assume the optimistic case and burn
99
+ * its full retry window on a machine that is simply off.
100
+ */
101
+ type ProbeVerdict =
102
+ /** Nothing is listening on the port — the host answered, and said no. */
103
+ 'refused' |
104
+ /** The hostname does not resolve. */
105
+ 'dns' |
106
+ /** No answer within the timeout: a booting host swallows packets rather than refusing them. */
107
+ 'timeout' |
108
+ /** The server answered, but rejected the probe's credentials (401/403). */
109
+ 'unauthorized' |
110
+ /** The server answered with a non-2xx that isn't an auth rejection. */
111
+ 'http_error' |
112
+ /** Connection failed in a way we can't attribute. */
113
+ 'unknown';
114
+ interface ProbeFailure {
115
+ verdict: ProbeVerdict;
116
+ /** Whether retrying the same probe could plausibly succeed later. */
117
+ retryable: boolean;
118
+ /** Operator-facing sentence. Never includes the API key. */
119
+ summary: string;
120
+ }
121
+ /**
122
+ * Classify a `ComputeServerStats.probeServer` result.
123
+ *
124
+ * A DNS failure is treated as retryable even though a missing record won't fix
125
+ * itself: `EAI_AGAIN` is a *timeout* talking to the resolver, and the two are
126
+ * not distinguishable from the error alone. Retrying a genuinely-wrong hostname
127
+ * costs one window; giving up on a resolver hiccup breaks a server that is fine.
128
+ *
129
+ * @param probe - The `{ online, status?, error? }` a probe returned. An `online`
130
+ * probe is not a failure and yields `null`.
131
+ */
132
+ export declare function classifyProbeFailure(probe: {
133
+ online: boolean;
134
+ status?: number;
135
+ error?: string;
136
+ }): ProbeFailure | null;
137
+ /**
138
+ * Decode the files in a compute response, plus any external `additionalFiles`, without
139
+ * downloading them. Never throws: an unnamed or undecodable item and a failed external fetch are
140
+ * logged and dropped, the rest still resolve. Colliding archive paths are renamed
141
+ * (`model.txt` → `model-2.txt`), so a consumer keying by `path` loses nothing.
142
+ */
143
+ export declare const extractFilesFromComputeResponse: (downloadableFiles: FileData[], additionalFiles?: FileBaseInfo[] | FileBaseInfo | null) => Promise<ProcessedFile[]>;
144
+ /** Downloads files from a compute response as a ZIP archive. */
145
+ export declare const downloadFileData: (downloadableFiles: FileData[], fileFoldername: string, additionalFiles?: FileBaseInfo[] | FileBaseInfo | null, options?: DownloadOptions) => Promise<void>;
146
+ /**
147
+ * Download one archive per `Sub Folder` root (see {@link groupFilesByRoot}). Files with no root go
148
+ * into `fallbackName`, so a definition that never sets `Sub Folder` downloads as one archive.
149
+ *
150
+ * Archives are saved one at a time: browsers discard concurrent downloads issued in the same tick.
151
+ * More than one save per gesture may prompt the user for permission.
152
+ *
153
+ * @param downloadableFiles - `FileData` items from the compute response.
154
+ * @param fallbackName - Archive name for files with no `Sub Folder` root.
155
+ * @param additionalFiles - Extra files to package; they carry no root and join the fallback archive.
156
+ * @param options - `saveBlob` replaces the anchor download for every archive.
157
+ */
158
+ export declare const downloadFileDataByRoot: (downloadableFiles: FileData[], fallbackName: string, additionalFiles?: FileBaseInfo[] | FileBaseInfo | null, options?: DownloadOptions) => Promise<void>;
159
+ /**
160
+ * The `Sub Folder` convention, as authored on Selva's Grasshopper file components.
161
+ *
162
+ * `::` nests, matching Rhino's layer separator (`ROOT::Panels`), and `/` and `\` are accepted
163
+ * because people type them out of habit. The plugin normalizes to `/` before sending, so these
164
+ * helpers also cover payloads from an older plugin that still emit `::`.
165
+ *
166
+ * The first segment is the **root**, which names the archive rather than becoming a folder inside
167
+ * it: `ROOT::Panels` downloads as `ROOT.zip` containing `Panels/…`. Files sharing a root travel
168
+ * together; distinct roots produce separate archives.
169
+ */
170
+ /**
171
+ * Split a `Sub Folder` value into folder segments; `/`, `\` and `::` all separate.
172
+ *
173
+ * Exported because anything rendering a folder tree has to agree with the archive on where the
174
+ * boundaries are — splitting on `/` alone leaves `Main::Panels` as one literal segment and shows
175
+ * a folder named after the separator.
176
+ */
177
+ export declare const subFolderSegments: (subFolder: string | undefined) => string[];
178
+ /**
179
+ * Group files by their `Sub Folder` root, preserving encounter order.
180
+ *
181
+ * Rootless files group under `''` — a caller naming archives supplies its own fallback for that
182
+ * bucket. Useful beyond downloading: a consumer writing to disk gets the same grouping without
183
+ * touching the DOM.
184
+ */
185
+ export declare const groupFilesByRoot: <T extends Pick<FileData, "subFolder">>(files: readonly T[]) => Array<{
186
+ root: string;
187
+ files: T[];
188
+ }>;
189
+ export { type ComputeAuth, type ComputeConfig, ComputeError, type DefinitionRef, type DownloadOptions, type ErrorCode, ErrorCodes, type FileBaseInfo, type FileData, type Logger, type ProbeFailure, type ProbeVerdict, type ProcessedFile, type RetryPolicy, type ServerErrorCodeMap, type ServerTiming, type SolveDefinition, type ValidateServerUrlOptions, isDefinitionRef };
190
+ //# sourceMappingURL=core.d.ts.map
package/dist/core.js CHANGED
@@ -1 +1 @@
1
- export{D as ComputeError,b as DEFAULT_BLOCKED_HOST,O as ErrorCodes,u as classifyProbeFailure,g as decodeBase64ToBinary,t as downloadFileData,n as downloadFileDataByRoot,w as enableDebugLogging,r as extractFilesFromComputeResponse,l as fetchCompute,T as getLogger,a as groupFilesByRoot,S as hasField,s as isDefinitionRef,C as readField,E as setLogger,o as subFolderSegments,x as validateServerUrl}from"./core-BPYXUtfq.js";
1
+ export { P as ComputeError, T as DEFAULT_BLOCKED_HOST, F as ErrorCodes, d as classifyProbeFailure, y as decodeBase64ToBinary, t as downloadFileData, n as downloadFileDataByRoot, j as enableDebugLogging, r as extractFilesFromComputeResponse, l as fetchCompute, M as getLogger, a as groupFilesByRoot, k as hasField, s as isDefinitionRef, A as readField, N as setLogger, o as subFolderSegments, E as validateServerUrl } from "./handle-files-D-4X4Uh_.js";
@@ -1,18 +1,25 @@
1
- import { D as ComputeAuth, M as ComputeError, O as ComputeConfig, _ as SolveDefinition, j as ServerTiming, k as RetryPolicy, s as FileBaseInfo } from "./index-DDmprCht.js";
1
+ import { c as ComputeAuth, f as ServerTiming, l as ComputeConfig, n as FileBaseInfo, o as SolveDefinition, p as ComputeError, u as RetryPolicy } from "./types-Dm9QFVvA.js";
2
2
  interface ServerVersion {
3
3
  rhino: string;
4
4
  compute: string;
5
5
  git_sha: string | null;
6
6
  }
7
+ /** What `GET /limits` reports. Only the VektorNode fork answers it. */
8
+ interface ServerLimits {
9
+ /** Longest the server lets one request run (`RHINO_COMPUTE_TIMEOUT`, `--timeout`). */
10
+ timeoutSeconds: number;
11
+ /** Largest request body it accepts (`RHINO_COMPUTE_MAX_REQUEST_SIZE`, `--max-request-size`). */
12
+ maxRequestBytes: number;
13
+ /** A stale definition pointer answers `definition_not_cached`, so pointer reuse is safe. */
14
+ signalsStaleDefinition: boolean;
15
+ }
7
16
  interface ServerStats {
8
17
  isOnline: boolean;
9
18
  version?: ServerVersion;
10
19
  activeChildren?: number;
11
20
  }
12
21
  /**
13
- * ComputeServerStats provides methods to query Rhino Compute server statistics.
14
- *
15
- * @public Use this for server health monitoring and statistics.
22
+ * Server health and control: liveness, version, child processes, cache purges.
16
23
  *
17
24
  * @example
18
25
  * ```typescript
@@ -83,10 +90,11 @@ declare class ComputeServerStats {
83
90
  *
84
91
  * @param timeoutMs - Abort the probe after this many ms (default: 5000).
85
92
  * Pass `0` to disable the timeout.
93
+ * @param signal - Cancels the probe, which then reports the abort as its `error`.
86
94
  * @returns `online` plus either the HTTP `status` the server answered with,
87
95
  * or the `error` the connection attempt failed with.
88
96
  */
89
- probeServer(timeoutMs?: number): Promise<{
97
+ probeServer(timeoutMs?: number, signal?: AbortSignal): Promise<{
90
98
  online: boolean;
91
99
  status?: number;
92
100
  error?: string;
@@ -107,6 +115,12 @@ declare class ComputeServerStats {
107
115
  getActiveChildren(options?: {
108
116
  initialize?: boolean;
109
117
  }): Promise<number | null>;
118
+ /**
119
+ * The server's request limits, or `null` when it can't report them: a standard rhino.compute
120
+ * has no `/limits`, and neither does an older fork. A missing route is expected, so it isn't
121
+ * logged as a failure.
122
+ */
123
+ getLimits(): Promise<ServerLimits | null>;
110
124
  /**
111
125
  * Get the server version information.
112
126
  *
@@ -317,7 +331,8 @@ type DataTreePath = `{${string}}`;
317
331
  interface DataItem {
318
332
  type: string;
319
333
  data: string;
320
- id: string;
334
+ /** **Requires:** the VektorNode compute fork. A stock server sends none. */
335
+ id?: string;
321
336
  }
322
337
  type DataTreeDefault<T = any> = { [K in DataTreePath]?: T[]; };
323
338
  type InnerTreeData = { [path in DataTreePath]: DataItem[]; };
@@ -353,12 +368,14 @@ interface BaseInputType {
353
368
  treeAccess: boolean;
354
369
  /**
355
370
  * Name of the group this parameter belongs to.
356
- * @requires Custom branch of compute.rhino3d
371
+ *
372
+ * **Requires:** the VektorNode compute fork.
357
373
  */
358
374
  groupName?: string;
359
375
  /**
360
376
  * Unique identifier for the parameter.
361
- * @requires Custom branch of compute.rhino3d
377
+ *
378
+ * **Requires:** the VektorNode compute fork.
362
379
  */
363
380
  id?: string;
364
381
  }
@@ -632,6 +649,14 @@ interface CacheOptions {
632
649
  */
633
650
  cacheErroredSolves?: boolean;
634
651
  }
652
+ /** What {@link SolveScheduler.cacheStats} reports. */
653
+ interface CacheStats {
654
+ entries: number;
655
+ bytes: number;
656
+ hits: number;
657
+ misses: number;
658
+ evictions: number;
659
+ }
635
660
  interface SolveSchedulerOptions {
636
661
  mode?: SchedulerMode;
637
662
  maxConcurrent?: number;
@@ -696,8 +721,6 @@ type SolveResult = {
696
721
  status: 'error';
697
722
  error: ComputeError;
698
723
  durationMs: number;
699
- } | {
700
- status: 'superseded';
701
724
  };
702
725
  /**
703
726
  * Solves by `cacheKey` when given one, otherwise uploads. Reports the server's
@@ -763,15 +786,7 @@ export declare class SolveScheduler {
763
786
  private readonly queueWaitMs;
764
787
  private readonly timeoutMs;
765
788
  private readonly retry;
766
- private readonly cacheEnabled;
767
- private readonly cacheMaxBytes;
768
- private readonly cacheTtl;
769
- private readonly cacheErroredSolves;
770
789
  private readonly cache;
771
- private cacheBytes;
772
- private cacheHits;
773
- private cacheMisses;
774
- private cacheEvictions;
775
790
  private readonly reuseServerDefinitionCache;
776
791
  /** Definition hash → the server's cache key (`pointer`) for it. */
777
792
  private readonly serverCacheKeys;
@@ -806,10 +821,10 @@ export declare class SolveScheduler {
806
821
  * Raising it drains queued work immediately. Lowering it never interrupts work
807
822
  * already in flight — those finish above the new limit, and the cap applies from
808
823
  * the next dispatch. Values below 1 are clamped, since 0 would wedge the queue.
824
+ *
825
+ * @throws {ComputeError} `INVALID_CONFIG` for `NaN`, which would wedge it too.
809
826
  */
810
827
  setMaxConcurrent(value: number): void;
811
- /** Current concurrency cap — reflects any {@link setMaxConcurrent} adjustment. */
812
- getMaxConcurrent(): number;
813
828
  /** Subscribe to state changes. */
814
829
  subscribe(listener: () => void): () => void;
815
830
  private notify;
@@ -870,13 +885,10 @@ export declare class SolveScheduler {
870
885
  */
871
886
  private fail;
872
887
  private settleSuccess;
873
- private removeQueuedAbortHandler;
888
+ private claimSettle;
874
889
  private normalizeExecutionError;
875
890
  /** Cancel everything — in-flight and pending. */
876
891
  cancelAll(): void;
877
- private readCache;
878
- private writeCache;
879
- private dropCacheEntry;
880
892
  clearCache(): void;
881
893
  /**
882
894
  * Observability snapshot of the solve cache: current size plus lifetime
@@ -888,13 +900,7 @@ export declare class SolveScheduler {
888
900
  * replace-in-place writes or TTL expiry, which are not capacity signals.
889
901
  * Counters are cumulative and survive `clearCache()`.
890
902
  */
891
- cacheStats(): {
892
- entries: number;
893
- bytes: number;
894
- hits: number;
895
- misses: number;
896
- evictions: number;
897
- };
903
+ cacheStats(): CacheStats;
898
904
  dispose(): void;
899
905
  private runHook;
900
906
  }
@@ -927,13 +933,10 @@ interface CreateClientOptions {
927
933
  };
928
934
  }
929
935
  /**
930
- * GrasshopperClient provides a simple API for interacting with a Rhino Compute server and grasshopper.
931
- *
932
- * @public This is the recommended high-level API for Rhino Compute operations.
936
+ * The high-level API for solving Grasshopper definitions on a Rhino Compute server.
933
937
  *
934
- * **Security Warning:**
935
- * Using this client in a browser environment exposes your server URL and API key to users.
936
- * For production, use this library server-side or proxy requests through your own backend.
938
+ * In a browser, the server URL and API key are exposed to users: run it server-side, or proxy
939
+ * requests through your own backend.
937
940
  *
938
941
  * @example
939
942
  * ```typescript
@@ -953,39 +956,26 @@ interface CreateClientOptions {
953
956
  declare class GrasshopperClient {
954
957
  private readonly config;
955
958
  readonly serverStats: ComputeServerStats;
959
+ private readonly schedulers;
956
960
  private disposed;
957
961
  /**
958
- * Per-probe timeout for the `create()` liveness gate. A healthy `GET /` on the
959
- * proxy answers in milliseconds; this bound only ever applies to a server that
960
- * is not answering, where every extra second is multiplied by the retry count
961
- * and paid by whoever is waiting on the solve.
962
+ * Per-probe timeout for the `create()` liveness gate: a healthy `GET /` answers in milliseconds,
963
+ * so this only bounds a silent server, and every extra second is paid once per retry.
962
964
  */
963
965
  private static readonly CREATE_PROBE_TIMEOUT_MS;
964
966
  private constructor();
965
967
  /**
966
- * Creates and initializes a GrasshopperClient with server validation.
968
+ * Creates a client once a liveness probe (`GET /`) says the server is up. A cold server can read
969
+ * as offline, so the probe retries with a short backoff, stopping early when
970
+ * {@link classifyProbeFailure} says waiting can't change the answer. `config.signal` cancels the
971
+ * ladder.
967
972
  *
968
- * The pre-flight liveness probe (a GET on the proxy root `/`) is a
969
- * single-sample boolean gate that reads a cold or briefly-busy-but-up server
970
- * as offline. To avoid failing construction on that transient class, the probe
971
- * is retried with a short exponential backoff before giving up.
972
- *
973
- * Retries stop early when {@link classifyProbeFailure} says waiting cannot
974
- * change the answer — connection refused, or a 401/403. This runs in front of
975
- * a user who clicked Solve, so the retry ladder must not spend its whole
976
- * budget confirming that a machine is switched off.
977
- *
978
- * @throws {ComputeError} with code NETWORK_ERROR if the server stays
979
- * unreachable across all attempts. `context.probeVerdict` carries the
980
- * {@link ProbeVerdict} so callers can render a specific cause.
981
- * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid
973
+ * @throws {ComputeError} `NETWORK_ERROR` if the server stays unreachable across all attempts.
974
+ * `context.probeVerdict` carries the verdict, so a caller can render a specific cause.
975
+ * @throws {ComputeError} `ABORTED` if `config.signal` fires first.
976
+ * @throws {ComputeError} `INVALID_CONFIG` if the configuration is invalid.
982
977
  */
983
978
  static create(config: GrasshopperComputeConfig, options?: CreateClientOptions): Promise<GrasshopperClient>;
984
- /**
985
- * Gets the client's configuration.
986
- * Useful for passing to lower-level functions.
987
- */
988
- getConfig(): GrasshopperComputeConfig;
989
979
  /**
990
980
  * Get input/output parameters of a Grasshopper definition.
991
981
  */
@@ -1015,23 +1005,31 @@ declare class GrasshopperClient {
1015
1005
  * @example
1016
1006
  * ```ts
1017
1007
  * const sliderScheduler = client.createScheduler({ mode: 'latest-wins' });
1018
- * const submitScheduler = client.createScheduler({ mode: 'queue', timeoutMs: 0, retry: { attempts: 1 } });
1008
+ * const submitScheduler = client.createScheduler({ mode: 'queue', timeoutMs: 0 });
1019
1009
  * ```
1020
1010
  */
1021
1011
  createScheduler(options?: SolveSchedulerOptions): SolveScheduler;
1022
- /**
1023
- * Disposes of client resources.
1024
- * Call this when you're done using the client.
1025
- */
1012
+ /** Disposes of client resources, including every scheduler it created. */
1026
1013
  dispose(): Promise<void>;
1027
1014
  private ensureNotDisposed;
1028
1015
  }
1016
+ /**
1017
+ * The part of rhino3dm the decoders call. Structural so the public types don't name `rhino3dm`, a
1018
+ * dev dependency here. A loaded `RhinoModule` satisfies it.
1019
+ */
1020
+ interface RhinoRuntime {
1021
+ Point: new (location: number[]) => unknown;
1022
+ Line: new (from: number[], to: number[]) => unknown;
1023
+ CommonObject: {
1024
+ decode(jsonObject: object): unknown;
1025
+ };
1026
+ }
1029
1027
  interface ParsedContext {
1030
1028
  [key: string]: any;
1031
1029
  }
1032
1030
  interface GetValuesOptions {
1033
1031
  parseValues?: boolean;
1034
- rhino?: any;
1032
+ rhino?: RhinoRuntime;
1035
1033
  /**
1036
1034
  * If true, only include values of type System.String in the result.
1037
1035
  * Non-string types are filtered out.
@@ -1096,9 +1094,7 @@ declare class GrasshopperResponseProcessor {
1096
1094
  * Download all files generated by Grasshopper, optionally including
1097
1095
  * additional user-provided files.
1098
1096
  *
1099
- * Files are grouped under the specified folder name when downloaded.
1100
- *
1101
- * @param folderName - Name for the download directory.
1097
+ * @param folderName - Name of the zip archive, without the extension.
1102
1098
  * @param additionalFiles - Extra files to package (single file, array, or null).
1103
1099
  *
1104
1100
  * @example
@@ -1108,7 +1104,7 @@ declare class GrasshopperResponseProcessor {
1108
1104
  *
1109
1105
  * @example
1110
1106
  * ```ts
1111
- * const extra = { name: 'notes.txt', data: 'Example' };
1107
+ * const extra = { fileName: 'notes.txt', filePath: 'https://example.com/notes.txt' };
1112
1108
  * await processor.getAndDownloadFiles('project', extra);
1113
1109
  * ```
1114
1110
  */
@@ -1147,14 +1143,11 @@ export declare function solveGrasshopperDefinition(dataTree: DataTree[], definit
1147
1143
  /**
1148
1144
  * Fetches a definition's input/output schemas without per-type parsing.
1149
1145
  *
1150
- * Field KEYS are canonicalized to camelCase and missing required fields get a
1151
- * fallback ({@link normalizeInputSchema}); a missing or non-array `inputs` /
1152
- * `outputs` becomes `[]`. Field VALUES, notably `default` and the `values`
1146
+ * Field KEYS are canonicalized to camelCase and missing required fields get a fallback; a missing
1147
+ * or non-array `inputs` / `outputs` becomes `[]`. Field VALUES, notably `default` and the `values`
1153
1148
  * label map, pass through verbatim.
1154
1149
  *
1155
1150
  * @throws {ComputeError} If fetch fails or response is invalid
1156
- *
1157
- * @public Use `fetchParsedDefinitionIO()` for processed, type-safe inputs
1158
1151
  */
1159
1152
  export declare function fetchDefinitionIO(definition: string | Uint8Array, config: ComputeConfig): Promise<GrasshopperParsedIORaw>;
1160
1153
  /**
@@ -1277,17 +1270,14 @@ export declare class TreeBuilder {
1277
1270
  private innerTree;
1278
1271
  private paramName;
1279
1272
  constructor(paramName: string);
1280
- /** Append values to the branch at `path` (`[0, 1]` is `{0;1}`). */
1273
+ /** Append values to the branch at `path` (`[0, 1]` is `{0;1}`). A `null` item is skipped. */
1281
1274
  append(path: number[], items: DataTreeValue[]): this;
1282
1275
  /** Replace the tree with a branch-path map like `{ '{0;1}': [1, 2] }`. */
1283
1276
  fromDataTreeDefault(treeData: DataTreeDefault): this;
1284
1277
  /** Append a value, or each value of an array, to branch `{0}`. */
1285
1278
  appendFlat(values: DataTreeValue | DataTreeValue[]): this;
1286
- /** Every value across all branches. */
1287
- flatten(): DataTreeValue[];
1288
1279
  /** The tree in the shape a solve request sends. */
1289
1280
  toComputeFormat(): DataTree;
1290
- getParamName(): string;
1291
1281
  /**
1292
1282
  * One tree per input that has a default, keyed by its nickname. Defaults are coerced and
1293
1283
  * clamped by the same rules as {@link TreeBuilder.fromValues}.
@@ -1341,5 +1331,5 @@ export declare class TreeBuilder {
1341
1331
  private static hasValidValue;
1342
1332
  private static clampValue;
1343
1333
  }
1344
- export { type BooleanInputType, type CacheOptions, type ColorInputType, ComputeServerStats, type CreateClientOptions, type DataItem, type DataTree, type DataTreeDefault, type DataTreePath, type DataTreeValue, type DefaultValue, type FileInputType, type GeometryInputType, type GetValuesOptions, type GetValuesResult, GrasshopperClient, type GrasshopperComputeConfig, type GrasshopperComputeResponse, type GrasshopperParsedIO, type GrasshopperParsedIORaw, GrasshopperResponseProcessor, type InnerTreeData, type InputParam, type InputParamSchema, type InputParseError, type NumericInputType, type OutputParamSchema, type ParsedContext, type RhinoDecodeError, type RhinoModelUnit, type SchedulerMode, type SchedulerSolveOptions, type SchemaEndpointResult, type SelvaEventTarget, type SelvaResponseBlock, type ServerStats, type ServerVersion, type SolveContext, type SolveDetail, type SolveExecutor, type SolveOptions, type SolveResult, type SolveSchedulerOptions, type TextInputType, type TreeValueEntry, type ValueListInputType };
1334
+ export { type BooleanInputType, type CacheOptions, type CacheStats, type ColorInputType, ComputeServerStats, type CreateClientOptions, type DataItem, type DataTree, type DataTreeDefault, type DataTreePath, type DataTreeValue, type DefaultValue, type FileInputType, type GeometryInputType, type GetValuesOptions, type GetValuesResult, GrasshopperClient, type GrasshopperComputeConfig, type GrasshopperComputeResponse, type GrasshopperParsedIO, type GrasshopperParsedIORaw, GrasshopperResponseProcessor, type InnerTreeData, type InputParam, type InputParamSchema, type InputParseError, type NumericInputType, type OutputParamSchema, type ParsedContext, type RhinoDecodeError, type RhinoModelUnit, type RhinoRuntime, type SchedulerMode, type SchedulerSolveOptions, type SchemaEndpointResult, type SelvaEventTarget, type SelvaResponseBlock, type ServerLimits, type ServerStats, type ServerVersion, type SolveContext, type SolveDetail, type SolveExecutor, type SolveOptions, type SolveResult, type SolveSchedulerOptions, type TextInputType, type TreeValueEntry, type ValueListInputType };
1345
1335
  //# sourceMappingURL=grasshopper.d.ts.map