@selvajs/compute 4.3.0-beta.1 → 5.0.0-beta.10
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 +125 -99
- package/dist/core.d.ts +190 -2
- package/dist/core.js +1 -1
- package/dist/grasshopper.d.ts +314 -766
- package/dist/grasshopper.js +3081 -1
- package/dist/grasshopper.js.map +1 -1
- package/dist/handle-files-D-4X4Uh_.js +1379 -0
- package/dist/handle-files-D-4X4Uh_.js.map +1 -0
- package/dist/types-Dm9QFVvA.d.ts +247 -0
- package/package.json +21 -83
- package/dist/core-rp3fyoca.js +0 -3
- package/dist/core-rp3fyoca.js.map +0 -1
- package/dist/core-zk0-pvdL.cjs +0 -3
- package/dist/core-zk0-pvdL.cjs.map +0 -1
- package/dist/core.cjs +0 -1
- package/dist/core.d.cts +0 -2
- package/dist/grasshopper.cjs +0 -2
- package/dist/grasshopper.cjs.map +0 -1
- package/dist/grasshopper.d.cts +0 -1787
- package/dist/index-BR_XZvjj.d.cts +0 -574
- package/dist/index-BR_XZvjj.d.ts +0 -574
- package/dist/index.cjs +0 -0
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@selvajs/compute)
|
|
5
5
|
[](https://www.npmjs.com/package/@selvajs/compute)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
8
7
|
[](https://nodejs.org/)
|
|
9
8
|
[](https://github.com/VektorNode/selva)
|
|
10
9
|
|
|
@@ -15,28 +14,27 @@
|
|
|
15
14
|
A TypeScript client for Rhino Compute and Grasshopper definitions: typed definition IO, data trees,
|
|
16
15
|
and a scheduler that handles cancellation, retries and caching.
|
|
17
16
|
|
|
18
|
-
Pure solve/data
|
|
17
|
+
Pure solve/data: no rendering layer, no `three` dependency. To turn a solve response into Three.js
|
|
19
18
|
objects, use [`@selvajs/visualization`](https://www.npmjs.com/package/@selvajs/visualization).
|
|
20
19
|
|
|
21
20
|
```bash
|
|
22
21
|
npm install @selvajs/compute
|
|
23
22
|
```
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
`@selvajs/compute/core` (low-level fetch, errors, config). The root is empty on purpose — import a
|
|
27
|
-
subpath.
|
|
24
|
+
The package is ESM only, with two entrypoints: `/core` and `/grasshopper`.
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
Two entrypoints: `@selvajs/compute/grasshopper` (client, scheduler, data trees, IO parsing) and
|
|
27
|
+
`@selvajs/compute/core` (low-level fetch, errors, config). There is no root entry: import a
|
|
28
|
+
subpath.
|
|
32
29
|
|
|
33
30
|
## Quickstart
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
— things every real app needs and shouldn't have to rebuild.
|
|
32
|
+
`client.solve` sends one request. A scheduler adds cancellation, retries, loading state and an
|
|
33
|
+
optional response cache on top; use one for any UI that fires solves.
|
|
38
34
|
|
|
39
35
|
```ts
|
|
36
|
+
import { readFile } from 'node:fs/promises';
|
|
37
|
+
import { ErrorCodes } from '@selvajs/compute/core';
|
|
40
38
|
import {
|
|
41
39
|
GrasshopperClient,
|
|
42
40
|
TreeBuilder,
|
|
@@ -48,15 +46,15 @@ const client = await GrasshopperClient.create({
|
|
|
48
46
|
apiKey: 'your-api-key'
|
|
49
47
|
});
|
|
50
48
|
|
|
51
|
-
// Configure the scheduler for your workload (see "Configuring the scheduler" below).
|
|
52
49
|
const scheduler = client.createScheduler({ mode: 'latest-wins', timeoutMs: 30_000 });
|
|
53
50
|
|
|
54
|
-
//
|
|
55
|
-
const
|
|
51
|
+
// A URL, or the .gh bytes: a bare file name would be sent as the definition text.
|
|
52
|
+
const definition = new Uint8Array(await readFile('my-definition.gh'));
|
|
53
|
+
|
|
54
|
+
const io = await client.getIO(definition);
|
|
56
55
|
const inputTree = TreeBuilder.fromInputParams(io.inputs);
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
const result = await scheduler.solve('my-definition.gh', inputTree);
|
|
57
|
+
const result = await scheduler.solve(definition, inputTree);
|
|
60
58
|
const { values } = new GrasshopperResponseProcessor(result).getValues();
|
|
61
59
|
```
|
|
62
60
|
|
|
@@ -69,128 +67,156 @@ scheduler.subscribe(() => {
|
|
|
69
67
|
});
|
|
70
68
|
```
|
|
71
69
|
|
|
72
|
-
|
|
73
|
-
in-flight solve, or when the user aborts:
|
|
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.
|
|
74
71
|
|
|
75
|
-
|
|
76
|
-
scheduler.solve(definition, inputTree).catch((err) => {
|
|
77
|
-
if (/superseded|aborted/i.test(err.message)) return; // expected, not an error
|
|
78
|
-
showError(err);
|
|
79
|
-
});
|
|
80
|
-
```
|
|
72
|
+
> `client.solve` throws on solver errors; `scheduler.solve` resolves with them. See [Errors](#errors).
|
|
81
73
|
|
|
82
74
|
## Configuring the scheduler
|
|
83
75
|
|
|
84
|
-
|
|
85
|
-
plus a couple of optional ones. Pick the row that matches what the user is
|
|
86
|
-
doing in your UI:
|
|
76
|
+
Pick the row that matches what the user is doing:
|
|
87
77
|
|
|
88
|
-
| Workload | `mode` | `timeoutMs` | `retry` | Notes
|
|
89
|
-
| --------------------------------- | --------------- | ---------------- | ----------------- |
|
|
90
|
-
| **Slider scrubs / live previews** | `'latest-wins'` | `30_000` | default | Aborts in-flight solves when newer values arrive. Add `cache: {
|
|
91
|
-
| **Submit / long-running jobs** | `'queue'` | `0` (no timeout) |
|
|
92
|
-
| **Background / batch parallel** | `'
|
|
78
|
+
| Workload | `mode` | `timeoutMs` | `retry` | Notes |
|
|
79
|
+
| --------------------------------- | --------------- | ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
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. |
|
|
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). |
|
|
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. |
|
|
93
83
|
|
|
94
|
-
|
|
95
|
-
surface. They share the connection pool but their queues, cancel scopes, and
|
|
96
|
-
caches are independent:
|
|
84
|
+
Create one scheduler per UI surface: their queues, cancel scopes and caches are independent.
|
|
97
85
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
86
|
+
`config.retry` covers solves. The liveness probe `GrasshopperClient.create` runs has its own
|
|
87
|
+
ladder: pass `{ probe: { attempts, baseDelayMs, maxDelayMs } }` as its second argument.
|
|
88
|
+
|
|
89
|
+
`scheduler.solveDetailed()` resolves `{ response, fromCache, definitionReuploaded, durationMs }`
|
|
90
|
+
for that one call, and takes a per-call `onServerTiming`. Use it to record how each solve went;
|
|
91
|
+
`onSettle` fires for every solve on the scheduler.
|
|
92
|
+
|
|
93
|
+
Don't mutate a `Uint8Array` definition after solving it. The scheduler hashes a buffer once and
|
|
94
|
+
remembers the hash by identity, so a changed buffer is served the old content's cached responses.
|
|
106
95
|
|
|
107
96
|
### Cancellation
|
|
108
97
|
|
|
109
|
-
|
|
110
|
-
cancel everything (e.g. on route change or component unmount):
|
|
98
|
+
A superseded or cancelled solve rejects. Filter those by code:
|
|
111
99
|
|
|
112
100
|
```ts
|
|
113
101
|
const ctrl = new AbortController();
|
|
114
|
-
scheduler.solve(definition,
|
|
102
|
+
scheduler.solve(definition, inputTree, { signal: ctrl.signal }).catch((err) => {
|
|
103
|
+
if (err.code === ErrorCodes.SUPERSEDED || err.code === ErrorCodes.ABORTED) return;
|
|
104
|
+
showError(err);
|
|
105
|
+
});
|
|
115
106
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
scheduler.
|
|
119
|
-
scheduler.dispose(); // cancel everything and tear down the scheduler
|
|
107
|
+
ctrl.abort(); // cancel this call
|
|
108
|
+
scheduler.cancelAll(); // cancel everything in flight and pending, e.g. on unmount
|
|
109
|
+
scheduler.dispose(); // cancel everything and tear the scheduler down
|
|
120
110
|
```
|
|
121
111
|
|
|
122
112
|
### Long jobs behind a proxy
|
|
123
113
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
around proxy timeouts.
|
|
128
|
-
|
|
129
|
-
For solves longer than ~2 minutes, the safer architecture is to run this
|
|
130
|
-
library **server-side** behind your own job queue (BullMQ / SQS / Cloud Tasks)
|
|
114
|
+
A proxy in front of Compute closes idle connections after its own timeout, and the library can't
|
|
115
|
+
work around that: raise the proxy's idle timeout before running long solves from a browser. For
|
|
116
|
+
solves longer than a couple of minutes, run this library server-side behind your own job queue
|
|
131
117
|
and expose a status endpoint to the browser.
|
|
132
118
|
|
|
133
|
-
|
|
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:
|
|
134
122
|
|
|
135
|
-
|
|
123
|
+
```ts
|
|
124
|
+
import { Agent, fetch as undiciFetch } from 'undici';
|
|
136
125
|
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
```
|
|
139
132
|
|
|
140
|
-
|
|
141
|
-
(`groupName`), persistent input ids keyed on Grasshopper object GUIDs, file export, and block
|
|
142
|
-
instance support.
|
|
143
|
-
- The [Selva Rhino plugin](https://www.food4rhino.com/en/app/selva?lang=en) supplies the Display
|
|
144
|
-
and file-export components those features read.
|
|
133
|
+
## Errors
|
|
145
134
|
|
|
146
|
-
|
|
135
|
+
Every failure is a `ComputeError`. Branch on `err.code`, one of `ErrorCodes`, not on the message.
|
|
147
136
|
|
|
148
|
-
|
|
137
|
+
`client.solve` and `scheduler.solve` differ on a response that carries Grasshopper `errors`.
|
|
138
|
+
`client.solve` throws `COMPUTATION_ERROR`, with the outputs that did compute in
|
|
139
|
+
`err.context.values`. `scheduler.solve` resolves with the response, so check it yourself:
|
|
149
140
|
|
|
150
|
-
|
|
141
|
+
```ts
|
|
142
|
+
import { readField } from '@selvajs/compute/core';
|
|
151
143
|
|
|
152
|
-
|
|
144
|
+
const response = await scheduler.solve(definition, inputTree);
|
|
145
|
+
// Read case-insensitively: stock Rhino Compute sends `Errors`.
|
|
146
|
+
const solveErrors = readField<string[]>(response, 'errors');
|
|
147
|
+
if (solveErrors?.length) showError(solveErrors.join('\n'));
|
|
148
|
+
```
|
|
153
149
|
|
|
154
|
-
|
|
155
|
-
a 200.
|
|
156
|
-
2. **CORS** — if your Compute server is on a different origin than your app,
|
|
157
|
-
the server must send `Access-Control-Allow-Origin`. Standard Rhino Compute
|
|
158
|
-
does **not** ship with CORS enabled; you'll need to put it behind a proxy
|
|
159
|
-
that adds the headers, or use the [VektorNode custom branch](https://github.com/VektorNode/compute.rhino3d).
|
|
160
|
-
3. **Mixed content** — an HTTPS app can't fetch from an HTTP server. Either
|
|
161
|
-
serve Compute over HTTPS or develop locally on HTTP.
|
|
162
|
-
4. **API key** — you'll see the same error if your `apiKey` is missing for a
|
|
163
|
-
server that requires one (the server typically returns 401 with no CORS
|
|
164
|
-
headers, which the browser surfaces as a network error).
|
|
150
|
+
## Data trees
|
|
165
151
|
|
|
166
|
-
|
|
152
|
+
`TreeBuilder.fromInputParams` builds one tree per input from its default, keyed by the input's
|
|
153
|
+
**nickname**. `TreeBuilder.fromValues` builds them from your own values instead: it coerces
|
|
154
|
+
Number, Integer and Boolean items, clamps numbers to `minimum`/`maximum`, and drops what it can't
|
|
155
|
+
coerce, so Grasshopper keeps its own value for that input. `replaceTreeValue` is copy-on-write: it
|
|
156
|
+
returns a new array and leaves yours untouched, so assign the result or the solve sends the
|
|
157
|
+
defaults.
|
|
167
158
|
|
|
168
|
-
|
|
169
|
-
|
|
159
|
+
```ts
|
|
160
|
+
let inputTree = TreeBuilder.fromInputParams(io.inputs);
|
|
161
|
+
inputTree = TreeBuilder.replaceTreeValue(inputTree, 'Radius', 10);
|
|
162
|
+
inputTree = TreeBuilder.replaceTreeValue(inputTree, 'Counts', { '{0}': [1, 2], '{0;1}': [3] });
|
|
163
|
+
```
|
|
170
164
|
|
|
171
|
-
|
|
165
|
+
Read outputs through `GrasshopperResponseProcessor`, or a whole tree with
|
|
166
|
+
`TreeBuilder.getTree(response.values, 'Points')`: a `Map` from branch path (`'{0;1}'`) to items.
|
|
167
|
+
`getTreeValue` is the shortcut for a one-branch param: it unwraps a single item and throws when
|
|
168
|
+
the param has more than one branch.
|
|
172
169
|
|
|
173
|
-
|
|
174
|
-
|
|
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.
|
|
175
173
|
|
|
176
|
-
|
|
174
|
+
## File outputs
|
|
177
175
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- **403 Forbidden** — your `authToken` (Bearer) was rejected by an upstream
|
|
181
|
-
proxy/API gateway. The Compute server itself almost never returns 403.
|
|
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`.
|
|
182
178
|
|
|
183
|
-
|
|
184
|
-
|
|
179
|
+
Each file component has a `Sub Folder` input, where `/` or `::` nests (as in Rhino layer names). The
|
|
180
|
+
first segment is the root: `downloadFileDataByRoot` from `/core` turns each root into its own
|
|
181
|
+
archive and puts files without one in the fallback archive. With `'export'` as the fallback name:
|
|
185
182
|
|
|
186
|
-
|
|
183
|
+
| `Sub Folder` | Archive | Path in the archive |
|
|
184
|
+
| --------------------- | ------------ | ------------------------ |
|
|
185
|
+
| _(empty)_ | `export.zip` | `model.3dm` |
|
|
186
|
+
| `Panels` | `Panels.zip` | `model.3dm` |
|
|
187
|
+
| `ROOT::Panels` | `ROOT.zip` | `Panels/model.3dm` |
|
|
188
|
+
| `ROOT::First::Second` | `ROOT.zip` | `First/Second/model.3dm` |
|
|
187
189
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
`groupFilesByRoot` returns the same grouping without the DOM, for writing files in Node.
|
|
191
|
+
`processor.getAndDownloadFiles(name)` skips the split: one `name.zip`, each file at its full
|
|
192
|
+
`Sub Folder` path.
|
|
190
193
|
|
|
191
|
-
##
|
|
194
|
+
## Requirements
|
|
195
|
+
|
|
196
|
+
The [official McNeel Rhino Compute](https://github.com/mcneel/compute.rhino3d) handles plain
|
|
197
|
+
Grasshopper solving. The [VektorNode compute fork](https://github.com/VektorNode/compute.rhino3d)
|
|
198
|
+
adds input grouping (`groupName`), persistent input ids keyed on Grasshopper object GUIDs, file
|
|
199
|
+
export, and block instance support. The
|
|
200
|
+
[Selva Rhino plugin](https://www.food4rhino.com/en/app/selva?lang=en) supplies the Display and
|
|
201
|
+
file-export components those features read.
|
|
202
|
+
|
|
203
|
+
## Troubleshooting
|
|
204
|
+
|
|
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
|
|
208
|
+
`curl http://localhost:6500/` returns 200; that a server on another origin sends
|
|
209
|
+
`Access-Control-Allow-Origin` (standard Rhino Compute doesn't: put a proxy in front that adds it,
|
|
210
|
+
or use the [VektorNode fork](https://github.com/VektorNode/compute.rhino3d)); that an HTTPS app
|
|
211
|
+
isn't calling an HTTP server; and that the API key is set, since a 401 without CORS headers reads
|
|
212
|
+
as a network error too.
|
|
192
213
|
|
|
193
|
-
|
|
214
|
+
**401** means `apiKey` (the `RhinoComputeKey` header) is missing or wrong. **403** usually comes
|
|
215
|
+
from a proxy or API gateway rejecting `authToken`, which is sent verbatim as the `Authorization`
|
|
216
|
+
header, so include the scheme (`Bearer …`). The error message carries an excerpt of the response
|
|
217
|
+
body.
|
|
218
|
+
|
|
219
|
+
## Acknowledgement
|
|
194
220
|
|
|
195
221
|
**Key references:**
|
|
196
222
|
|
package/dist/core.d.ts
CHANGED
|
@@ -1,2 +1,190 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
|
|
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";
|