@selvajs/compute 4.0.0-beta.0 → 4.0.0-beta.3
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 +7 -3
- package/dist/chunk-AYH5O3VG.js +4 -0
- package/dist/chunk-AYH5O3VG.js.map +1 -0
- package/dist/chunk-MLPWPV6F.cjs +4 -0
- package/dist/chunk-MLPWPV6F.cjs.map +1 -0
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +81 -56
- package/dist/core.d.ts +81 -56
- package/dist/core.js +1 -1
- package/dist/grasshopper.cjs +1 -1
- package/dist/grasshopper.cjs.map +1 -1
- package/dist/grasshopper.d.cts +400 -81
- package/dist/grasshopper.d.ts +400 -81
- package/dist/grasshopper.js +1 -1
- package/dist/grasshopper.js.map +1 -1
- package/dist/handle-files-DMK9bCOW.d.cts +231 -0
- package/dist/handle-files-DMK9bCOW.d.ts +231 -0
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +0 -1
- package/package.json +1 -1
- package/dist/chunk-G7NVQAK5.cjs +0 -2
- package/dist/chunk-G7NVQAK5.cjs.map +0 -1
- package/dist/chunk-LLUNR66G.cjs +0 -4
- package/dist/chunk-LLUNR66G.cjs.map +0 -1
- package/dist/chunk-RFFCVRKM.js +0 -2
- package/dist/chunk-RFFCVRKM.js.map +0 -1
- package/dist/chunk-XT3XWDDV.js +0 -4
- package/dist/chunk-XT3XWDDV.js.map +0 -1
- package/dist/handle-files-DcA24IkS.d.cts +0 -478
- package/dist/handle-files-DcA24IkS.d.ts +0 -478
package/dist/core.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as ComputeConfig } from './handle-files-
|
|
2
|
-
export { a as
|
|
1
|
+
import { C as ComputeConfig } from './handle-files-DMK9bCOW.cjs';
|
|
2
|
+
export { a as ComputeError, D as DefinitionRef, E as ErrorCode, b as ErrorCodes, F as FileBaseInfo, c as FileData, P as ProcessedFile, R as RetryPolicy, S as ServerErrorCodeMap, d as ServerTiming, e as SolveDefinition, f as downloadFileData, g as extractFilesFromComputeResponse, i as isDefinitionRef } from './handle-files-DMK9bCOW.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Generic Rhino Compute fetch function.
|
|
@@ -33,7 +33,7 @@ export { a as ComputeServerStats, E as ErrorCode, b as ErrorCodes, F as FileBase
|
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
35
35
|
* // Basic usage for the Grasshopper endpoint:
|
|
36
|
-
* const response = await
|
|
36
|
+
* const response = await fetchCompute(
|
|
37
37
|
* 'grasshopper',
|
|
38
38
|
* { ... },
|
|
39
39
|
* {
|
|
@@ -45,7 +45,7 @@ export { a as ComputeServerStats, E as ErrorCode, b as ErrorCodes, F as FileBase
|
|
|
45
45
|
* }
|
|
46
46
|
* );
|
|
47
47
|
*/
|
|
48
|
-
declare function
|
|
48
|
+
declare function fetchCompute<R = unknown>(endpoint: string, args: Record<string, any>, config: ComputeConfig): Promise<R>;
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Side-channel carrying a response's wire size (its JSON text length) alongside
|
|
@@ -91,13 +91,13 @@ declare function getLogger(): Logger;
|
|
|
91
91
|
* @public Use this to configure custom logging behavior.
|
|
92
92
|
*
|
|
93
93
|
* @param logger - Custom logger implementation or null to disable logging
|
|
94
|
-
* @throws {
|
|
94
|
+
* @throws {ComputeError} `INVALID_CONFIG` if the logger is missing any of
|
|
95
95
|
* the four required methods — failing here beats a confusing
|
|
96
96
|
* "getLogger().debug is not a function" at some later, unrelated call site.
|
|
97
97
|
*
|
|
98
98
|
* @example
|
|
99
99
|
* ```typescript
|
|
100
|
-
* import { setLogger } from '@selvajs/compute';
|
|
100
|
+
* import { setLogger } from '@selvajs/compute/core';
|
|
101
101
|
*
|
|
102
102
|
* // Enable console logging
|
|
103
103
|
* setLogger(console);
|
|
@@ -122,7 +122,7 @@ declare function setLogger(logger: Logger | Console | null): void;
|
|
|
122
122
|
*
|
|
123
123
|
* @example
|
|
124
124
|
* ```typescript
|
|
125
|
-
* import { enableDebugLogging } from '@selvajs/compute';
|
|
125
|
+
* import { enableDebugLogging } from '@selvajs/compute/core';
|
|
126
126
|
*
|
|
127
127
|
* enableDebugLogging();
|
|
128
128
|
* ```
|
|
@@ -130,56 +130,37 @@ declare function setLogger(logger: Logger | Console | null): void;
|
|
|
130
130
|
declare function enableDebugLogging(): void;
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
133
|
+
* Case-insensitive single-key reader for wire payloads.
|
|
134
|
+
*
|
|
135
|
+
* The Rhino Compute family serializes the same logical field with different
|
|
136
|
+
* casing depending on the server branch:
|
|
137
|
+
*
|
|
138
|
+
* - mcneel 8.x / 9.x: the IO schema is PascalCase (`ParamType`, `Default`,
|
|
139
|
+
* `InnerTree`, …) because those C# classes carry no `[JsonProperty]`.
|
|
140
|
+
* - VektorNode Compute8: the IO schema is camelCase (`paramType`, `default`,
|
|
141
|
+
* …) because the fork added `[JsonProperty("camelCase")]`, BUT the nested
|
|
142
|
+
* `default` DataTree wrapper stays PascalCase (`ParamName` / `InnerTree`)
|
|
143
|
+
* since `Resthopper.IO.DataTree` is an external type the fork can't attribute.
|
|
144
|
+
*
|
|
145
|
+
* So a single response can mix casings, and which casing a given field uses
|
|
146
|
+
* depends on the server branch. Rather than deep-camelCasing the whole payload
|
|
147
|
+
* (the old `camelcaseKeys` approach — which corrupted user-authored value-list
|
|
148
|
+
* label keys and item `data` JSON), read the specific fields we care about
|
|
149
|
+
* case-insensitively and leave everything else verbatim.
|
|
150
|
+
*
|
|
151
|
+
* Prefers an exact-case match when present, then falls back to the first
|
|
152
|
+
* case-insensitive match. Returns `undefined` when no key matches.
|
|
153
|
+
*
|
|
154
|
+
* @param obj - The source object (any non-object input yields `undefined`).
|
|
155
|
+
* @param name - The logical field name, in any casing.
|
|
150
156
|
*/
|
|
151
|
-
declare function
|
|
152
|
-
preserveSpaces?: boolean;
|
|
153
|
-
}): string;
|
|
157
|
+
declare function readField<T = unknown>(obj: unknown, name: string): T | undefined;
|
|
154
158
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* traversed; any other object — `Date`, `Map`, `Set`, `Uint8Array`/typed
|
|
159
|
-
* arrays, class instances, … — is passed through untouched (previously deep
|
|
160
|
-
* mode reduced a `Date` to `{}` and a `Uint8Array` to `{0: …, 1: …}`).
|
|
161
|
-
*
|
|
162
|
-
* When two keys camelCase to the same name (`inner_tree` + `innerTree`), the
|
|
163
|
-
* LAST one in key order wins (matching plain object-spread semantics) and a
|
|
164
|
-
* warning is logged naming both keys — the earlier value is silently
|
|
165
|
-
* unreachable otherwise.
|
|
166
|
-
*
|
|
167
|
-
* @warning Do not use `deep: true` on Rhino Compute wire payloads: it
|
|
168
|
-
* corrupts user-authored keys (value-list labels, item `data` JSON). Read the
|
|
169
|
-
* specific fields you need case-insensitively with `readField`/`hasField` from
|
|
170
|
-
* `core/utils/read-field` instead. Shallow (non-deep) use on wrapper keys is fine.
|
|
171
|
-
*
|
|
172
|
-
* @param obj - The object to process
|
|
173
|
-
* @param options - Options object
|
|
174
|
-
* - deep: If true, process deeply
|
|
175
|
-
* - preserveSpaces: If true, spaces are preserved in keys
|
|
176
|
-
* @returns The new object with camelCased keys
|
|
177
|
-
* @internal
|
|
159
|
+
* True when `obj` has a key matching `name` (case-insensitively). Distinguishes
|
|
160
|
+
* "field present but value is null/undefined" from "field absent" — needed where
|
|
161
|
+
* presence itself carries meaning (e.g. an `innerTree` that exists but is empty).
|
|
178
162
|
*/
|
|
179
|
-
declare function
|
|
180
|
-
deep?: boolean;
|
|
181
|
-
preserveSpaces?: boolean;
|
|
182
|
-
}): unknown;
|
|
163
|
+
declare function hasField(obj: unknown, name: string): boolean;
|
|
183
164
|
|
|
184
165
|
/**
|
|
185
166
|
* Decodes a base64 string to binary data (Uint8Array).
|
|
@@ -187,8 +168,52 @@ declare function camelcaseKeys(obj: unknown, options?: {
|
|
|
187
168
|
*
|
|
188
169
|
* @param base64File - Base64 encoded string
|
|
189
170
|
* @returns Decoded binary data as Uint8Array
|
|
190
|
-
* @throws {
|
|
171
|
+
* @throws {ComputeError} `ENCODING_ERROR` if invalid, or `INVALID_STATE` if decoder unavailable
|
|
191
172
|
*/
|
|
192
173
|
declare function decodeBase64ToBinary(base64File: string): Uint8Array;
|
|
193
174
|
|
|
194
|
-
|
|
175
|
+
/**
|
|
176
|
+
* The public McNeel endpoint's host — the default blocked host; users must point at
|
|
177
|
+
* their own server. Compared against the parsed hostname lowercased and with any
|
|
178
|
+
* trailing dot stripped, so the FQDN form (`compute.rhino3d.com.`) can't bypass it.
|
|
179
|
+
* Known limitation: the endpoint's raw IP is not blocked — it sits behind a load
|
|
180
|
+
* balancer with no single stable, verifiable address to pin.
|
|
181
|
+
*/
|
|
182
|
+
declare const DEFAULT_BLOCKED_HOST = "compute.rhino3d.com";
|
|
183
|
+
interface ValidateServerUrlOptions {
|
|
184
|
+
/**
|
|
185
|
+
* Hostnames rejected as a `serverUrl` — a backend's shared public endpoint,
|
|
186
|
+
* which callers must not point at. Defaults to `[DEFAULT_BLOCKED_HOST]`; pass
|
|
187
|
+
* `[]` to block nothing. Compared lowercased with any trailing dot stripped.
|
|
188
|
+
*/
|
|
189
|
+
blockedHosts?: readonly string[];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Validate and normalize a compute `serverUrl`.
|
|
193
|
+
*
|
|
194
|
+
* This is the single source of truth for "is this a usable server URL?" — both
|
|
195
|
+
* `GrasshopperClient` (via `normalizeComputeConfig`) and the standalone-exported
|
|
196
|
+
* `ComputeServerStats` constructor delegate here, so a given URL is accepted or
|
|
197
|
+
* rejected identically no matter which entry point a caller uses.
|
|
198
|
+
*
|
|
199
|
+
* Rules (all enforced, on the *trimmed* input — the trimmed form is what's
|
|
200
|
+
* returned, so no stray whitespace survives into later `fetch` calls):
|
|
201
|
+
* - non-empty (after trim)
|
|
202
|
+
* - `http://` or `https://` scheme (case-insensitive, per RFC 3986)
|
|
203
|
+
* - parseable by `new URL()`
|
|
204
|
+
* - no embedded credentials (`http://user:pass@host`) — `fetch`/`new Request`
|
|
205
|
+
* reject credentialed URLs at runtime, so they must fail here instead
|
|
206
|
+
* - no query string or fragment — endpoint paths are appended to this URL
|
|
207
|
+
* (`${serverUrl}/version`), which a `?…` or `#…` suffix would corrupt
|
|
208
|
+
* - not a blocked host (by default the public McNeel endpoint) — compared by
|
|
209
|
+
* parsed hostname (lowercased, trailing dot stripped), so scheme, casing, port,
|
|
210
|
+
* path, trailing-slash, or FQDN-dot variants can't slip past the block
|
|
211
|
+
*
|
|
212
|
+
* @param raw - The candidate server URL.
|
|
213
|
+
* @param options - Override the blocked-host list for a non-Rhino backend.
|
|
214
|
+
* @returns The trimmed, normalized URL with any trailing slashes removed.
|
|
215
|
+
* @throws {ComputeError} `INVALID_CONFIG` if any rule fails.
|
|
216
|
+
*/
|
|
217
|
+
declare function validateServerUrl(raw: string, options?: ValidateServerUrlOptions): string;
|
|
218
|
+
|
|
219
|
+
export { ComputeConfig, DEFAULT_BLOCKED_HOST, type Logger, type ValidateServerUrlOptions, decodeBase64ToBinary, enableDebugLogging, fetchCompute, getLogger, getResponseWireSize, hasField, readField, setLogger, setResponseWireSize, validateServerUrl };
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as ComputeConfig } from './handle-files-
|
|
2
|
-
export { a as
|
|
1
|
+
import { C as ComputeConfig } from './handle-files-DMK9bCOW.js';
|
|
2
|
+
export { a as ComputeError, D as DefinitionRef, E as ErrorCode, b as ErrorCodes, F as FileBaseInfo, c as FileData, P as ProcessedFile, R as RetryPolicy, S as ServerErrorCodeMap, d as ServerTiming, e as SolveDefinition, f as downloadFileData, g as extractFilesFromComputeResponse, i as isDefinitionRef } from './handle-files-DMK9bCOW.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Generic Rhino Compute fetch function.
|
|
@@ -33,7 +33,7 @@ export { a as ComputeServerStats, E as ErrorCode, b as ErrorCodes, F as FileBase
|
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
35
35
|
* // Basic usage for the Grasshopper endpoint:
|
|
36
|
-
* const response = await
|
|
36
|
+
* const response = await fetchCompute(
|
|
37
37
|
* 'grasshopper',
|
|
38
38
|
* { ... },
|
|
39
39
|
* {
|
|
@@ -45,7 +45,7 @@ export { a as ComputeServerStats, E as ErrorCode, b as ErrorCodes, F as FileBase
|
|
|
45
45
|
* }
|
|
46
46
|
* );
|
|
47
47
|
*/
|
|
48
|
-
declare function
|
|
48
|
+
declare function fetchCompute<R = unknown>(endpoint: string, args: Record<string, any>, config: ComputeConfig): Promise<R>;
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Side-channel carrying a response's wire size (its JSON text length) alongside
|
|
@@ -91,13 +91,13 @@ declare function getLogger(): Logger;
|
|
|
91
91
|
* @public Use this to configure custom logging behavior.
|
|
92
92
|
*
|
|
93
93
|
* @param logger - Custom logger implementation or null to disable logging
|
|
94
|
-
* @throws {
|
|
94
|
+
* @throws {ComputeError} `INVALID_CONFIG` if the logger is missing any of
|
|
95
95
|
* the four required methods — failing here beats a confusing
|
|
96
96
|
* "getLogger().debug is not a function" at some later, unrelated call site.
|
|
97
97
|
*
|
|
98
98
|
* @example
|
|
99
99
|
* ```typescript
|
|
100
|
-
* import { setLogger } from '@selvajs/compute';
|
|
100
|
+
* import { setLogger } from '@selvajs/compute/core';
|
|
101
101
|
*
|
|
102
102
|
* // Enable console logging
|
|
103
103
|
* setLogger(console);
|
|
@@ -122,7 +122,7 @@ declare function setLogger(logger: Logger | Console | null): void;
|
|
|
122
122
|
*
|
|
123
123
|
* @example
|
|
124
124
|
* ```typescript
|
|
125
|
-
* import { enableDebugLogging } from '@selvajs/compute';
|
|
125
|
+
* import { enableDebugLogging } from '@selvajs/compute/core';
|
|
126
126
|
*
|
|
127
127
|
* enableDebugLogging();
|
|
128
128
|
* ```
|
|
@@ -130,56 +130,37 @@ declare function setLogger(logger: Logger | Console | null): void;
|
|
|
130
130
|
declare function enableDebugLogging(): void;
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
133
|
+
* Case-insensitive single-key reader for wire payloads.
|
|
134
|
+
*
|
|
135
|
+
* The Rhino Compute family serializes the same logical field with different
|
|
136
|
+
* casing depending on the server branch:
|
|
137
|
+
*
|
|
138
|
+
* - mcneel 8.x / 9.x: the IO schema is PascalCase (`ParamType`, `Default`,
|
|
139
|
+
* `InnerTree`, …) because those C# classes carry no `[JsonProperty]`.
|
|
140
|
+
* - VektorNode Compute8: the IO schema is camelCase (`paramType`, `default`,
|
|
141
|
+
* …) because the fork added `[JsonProperty("camelCase")]`, BUT the nested
|
|
142
|
+
* `default` DataTree wrapper stays PascalCase (`ParamName` / `InnerTree`)
|
|
143
|
+
* since `Resthopper.IO.DataTree` is an external type the fork can't attribute.
|
|
144
|
+
*
|
|
145
|
+
* So a single response can mix casings, and which casing a given field uses
|
|
146
|
+
* depends on the server branch. Rather than deep-camelCasing the whole payload
|
|
147
|
+
* (the old `camelcaseKeys` approach — which corrupted user-authored value-list
|
|
148
|
+
* label keys and item `data` JSON), read the specific fields we care about
|
|
149
|
+
* case-insensitively and leave everything else verbatim.
|
|
150
|
+
*
|
|
151
|
+
* Prefers an exact-case match when present, then falls back to the first
|
|
152
|
+
* case-insensitive match. Returns `undefined` when no key matches.
|
|
153
|
+
*
|
|
154
|
+
* @param obj - The source object (any non-object input yields `undefined`).
|
|
155
|
+
* @param name - The logical field name, in any casing.
|
|
150
156
|
*/
|
|
151
|
-
declare function
|
|
152
|
-
preserveSpaces?: boolean;
|
|
153
|
-
}): string;
|
|
157
|
+
declare function readField<T = unknown>(obj: unknown, name: string): T | undefined;
|
|
154
158
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* traversed; any other object — `Date`, `Map`, `Set`, `Uint8Array`/typed
|
|
159
|
-
* arrays, class instances, … — is passed through untouched (previously deep
|
|
160
|
-
* mode reduced a `Date` to `{}` and a `Uint8Array` to `{0: …, 1: …}`).
|
|
161
|
-
*
|
|
162
|
-
* When two keys camelCase to the same name (`inner_tree` + `innerTree`), the
|
|
163
|
-
* LAST one in key order wins (matching plain object-spread semantics) and a
|
|
164
|
-
* warning is logged naming both keys — the earlier value is silently
|
|
165
|
-
* unreachable otherwise.
|
|
166
|
-
*
|
|
167
|
-
* @warning Do not use `deep: true` on Rhino Compute wire payloads: it
|
|
168
|
-
* corrupts user-authored keys (value-list labels, item `data` JSON). Read the
|
|
169
|
-
* specific fields you need case-insensitively with `readField`/`hasField` from
|
|
170
|
-
* `core/utils/read-field` instead. Shallow (non-deep) use on wrapper keys is fine.
|
|
171
|
-
*
|
|
172
|
-
* @param obj - The object to process
|
|
173
|
-
* @param options - Options object
|
|
174
|
-
* - deep: If true, process deeply
|
|
175
|
-
* - preserveSpaces: If true, spaces are preserved in keys
|
|
176
|
-
* @returns The new object with camelCased keys
|
|
177
|
-
* @internal
|
|
159
|
+
* True when `obj` has a key matching `name` (case-insensitively). Distinguishes
|
|
160
|
+
* "field present but value is null/undefined" from "field absent" — needed where
|
|
161
|
+
* presence itself carries meaning (e.g. an `innerTree` that exists but is empty).
|
|
178
162
|
*/
|
|
179
|
-
declare function
|
|
180
|
-
deep?: boolean;
|
|
181
|
-
preserveSpaces?: boolean;
|
|
182
|
-
}): unknown;
|
|
163
|
+
declare function hasField(obj: unknown, name: string): boolean;
|
|
183
164
|
|
|
184
165
|
/**
|
|
185
166
|
* Decodes a base64 string to binary data (Uint8Array).
|
|
@@ -187,8 +168,52 @@ declare function camelcaseKeys(obj: unknown, options?: {
|
|
|
187
168
|
*
|
|
188
169
|
* @param base64File - Base64 encoded string
|
|
189
170
|
* @returns Decoded binary data as Uint8Array
|
|
190
|
-
* @throws {
|
|
171
|
+
* @throws {ComputeError} `ENCODING_ERROR` if invalid, or `INVALID_STATE` if decoder unavailable
|
|
191
172
|
*/
|
|
192
173
|
declare function decodeBase64ToBinary(base64File: string): Uint8Array;
|
|
193
174
|
|
|
194
|
-
|
|
175
|
+
/**
|
|
176
|
+
* The public McNeel endpoint's host — the default blocked host; users must point at
|
|
177
|
+
* their own server. Compared against the parsed hostname lowercased and with any
|
|
178
|
+
* trailing dot stripped, so the FQDN form (`compute.rhino3d.com.`) can't bypass it.
|
|
179
|
+
* Known limitation: the endpoint's raw IP is not blocked — it sits behind a load
|
|
180
|
+
* balancer with no single stable, verifiable address to pin.
|
|
181
|
+
*/
|
|
182
|
+
declare const DEFAULT_BLOCKED_HOST = "compute.rhino3d.com";
|
|
183
|
+
interface ValidateServerUrlOptions {
|
|
184
|
+
/**
|
|
185
|
+
* Hostnames rejected as a `serverUrl` — a backend's shared public endpoint,
|
|
186
|
+
* which callers must not point at. Defaults to `[DEFAULT_BLOCKED_HOST]`; pass
|
|
187
|
+
* `[]` to block nothing. Compared lowercased with any trailing dot stripped.
|
|
188
|
+
*/
|
|
189
|
+
blockedHosts?: readonly string[];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Validate and normalize a compute `serverUrl`.
|
|
193
|
+
*
|
|
194
|
+
* This is the single source of truth for "is this a usable server URL?" — both
|
|
195
|
+
* `GrasshopperClient` (via `normalizeComputeConfig`) and the standalone-exported
|
|
196
|
+
* `ComputeServerStats` constructor delegate here, so a given URL is accepted or
|
|
197
|
+
* rejected identically no matter which entry point a caller uses.
|
|
198
|
+
*
|
|
199
|
+
* Rules (all enforced, on the *trimmed* input — the trimmed form is what's
|
|
200
|
+
* returned, so no stray whitespace survives into later `fetch` calls):
|
|
201
|
+
* - non-empty (after trim)
|
|
202
|
+
* - `http://` or `https://` scheme (case-insensitive, per RFC 3986)
|
|
203
|
+
* - parseable by `new URL()`
|
|
204
|
+
* - no embedded credentials (`http://user:pass@host`) — `fetch`/`new Request`
|
|
205
|
+
* reject credentialed URLs at runtime, so they must fail here instead
|
|
206
|
+
* - no query string or fragment — endpoint paths are appended to this URL
|
|
207
|
+
* (`${serverUrl}/version`), which a `?…` or `#…` suffix would corrupt
|
|
208
|
+
* - not a blocked host (by default the public McNeel endpoint) — compared by
|
|
209
|
+
* parsed hostname (lowercased, trailing dot stripped), so scheme, casing, port,
|
|
210
|
+
* path, trailing-slash, or FQDN-dot variants can't slip past the block
|
|
211
|
+
*
|
|
212
|
+
* @param raw - The candidate server URL.
|
|
213
|
+
* @param options - Override the blocked-host list for a non-Rhino backend.
|
|
214
|
+
* @returns The trimmed, normalized URL with any trailing slashes removed.
|
|
215
|
+
* @throws {ComputeError} `INVALID_CONFIG` if any rule fails.
|
|
216
|
+
*/
|
|
217
|
+
declare function validateServerUrl(raw: string, options?: ValidateServerUrlOptions): string;
|
|
218
|
+
|
|
219
|
+
export { ComputeConfig, DEFAULT_BLOCKED_HOST, type Logger, type ValidateServerUrlOptions, decodeBase64ToBinary, enableDebugLogging, fetchCompute, getLogger, getResponseWireSize, hasField, readField, setLogger, setResponseWireSize, validateServerUrl };
|
package/dist/core.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as a,c as b,
|
|
1
|
+
import{b as a,c as b,d as c,e as d,f as e,g as f,h as g,i as h,j as i,m as j,o as k,p as l,q as m,r as n,s as o,t as p}from"./chunk-AYH5O3VG.js";export{b as ComputeError,h as DEFAULT_BLOCKED_HOST,a as ErrorCodes,j as decodeBase64ToBinary,p as downloadFileData,e as enableDebugLogging,o as extractFilesFromComputeResponse,m as fetchCompute,c as getLogger,l as getResponseWireSize,g as hasField,n as isDefinitionRef,f as readField,d as setLogger,k as setResponseWireSize,i as validateServerUrl};
|
|
2
2
|
//# sourceMappingURL=core.js.map
|
package/dist/grasshopper.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkG7NVQAK5cjs = require('./chunk-G7NVQAK5.cjs');var _chunkLLUNR66Gcjs = require('./chunk-LLUNR66G.cjs');exports.GrasshopperClient = _chunkG7NVQAK5cjs.g; exports.GrasshopperResponseProcessor = _chunkG7NVQAK5cjs.h; exports.SolveScheduler = _chunkG7NVQAK5cjs.f; exports.TreeBuilder = _chunkG7NVQAK5cjs.l; exports.downloadFileData = _chunkLLUNR66Gcjs.u; exports.extractFilesFromComputeResponse = _chunkLLUNR66Gcjs.t; exports.fetchDefinitionIO = _chunkG7NVQAK5cjs.j; exports.fetchParsedDefinitionIO = _chunkG7NVQAK5cjs.k; exports.hashDefinition = _chunkG7NVQAK5cjs.e; exports.hashSolveInput = _chunkG7NVQAK5cjs.d; exports.isDefinitionRef = _chunkG7NVQAK5cjs.a; exports.processInput = _chunkG7NVQAK5cjs.i; exports.solveGrasshopperDefinition = _chunkG7NVQAK5cjs.b; exports.stableStringify = _chunkG7NVQAK5cjs.c;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _chunkMLPWPV6Fcjs = require('./chunk-MLPWPV6F.cjs');var b=class b{constructor(e,r){_chunkMLPWPV6Fcjs.a.call(void 0, this,"serverUrl");_chunkMLPWPV6Fcjs.a.call(void 0, this,"apiKey");_chunkMLPWPV6Fcjs.a.call(void 0, this,"disposed",!1);_chunkMLPWPV6Fcjs.a.call(void 0, this,"activeMonitors",new Set);_chunkMLPWPV6Fcjs.a.call(void 0, this,"activeTimeouts",new Set);this.serverUrl=_chunkMLPWPV6Fcjs.j.call(void 0, e),this.apiKey=r}buildHeaders(){let e={"Content-Type":"application/json"};return this.apiKey&&(e.RhinoComputeKey=this.apiKey),e}fetchWithTimeout(e,r={},n=b.DEFAULT_TIMEOUT_MS){let s=new Headers(this.buildHeaders());r.headers&&new Headers(r.headers).forEach((a,u)=>{s.set(u,a)});let o={};s.forEach((a,u)=>{o[u]=a});let i={...r,headers:o};return n>0&&!i.signal&&(i.signal=AbortSignal.timeout(n)),fetch(e,i)}async isServerOnline(e=5e3){this.ensureNotDisposed();let r=`${this.serverUrl}/`;try{return(await this.fetchWithTimeout(r,{method:"GET"},e)).ok}catch(n){return _chunkMLPWPV6Fcjs.d.call(void 0, ).debug("[ComputeServerStats] Fetch error:",n),!1}}async getActiveChildren(e={}){this.ensureNotDisposed();let{initialize:r=!0}=e,n=r?`${this.serverUrl}/activechildren`:`${this.serverUrl}/activechildren?initialize=false`,s=r?b.LIFECYCLE_TIMEOUT_MS:b.DEFAULT_TIMEOUT_MS;try{let o=await this.fetchWithTimeout(n,{},s);if(!o.ok)return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Failed to fetch active children:",o.status),null;let i=(await o.text()).trim();return/^\d+$/.test(i)?parseInt(i,10):(_chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Invalid active children response:",i),null)}catch(o){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Error fetching active children:",o),null}}async getVersion(){this.ensureNotDisposed();try{let e=await this.fetchWithTimeout(`${this.serverUrl}/version`);if(!e.ok)return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Failed to fetch version:",e.status),null;let r=await e.text();try{let n=JSON.parse(r);return{rhino:_nullishCoalesce(n.rhino, () => ("")),compute:_nullishCoalesce(n.compute, () => ("")),git_sha:_nullishCoalesce(n.git_sha, () => (null))}}catch (e2){return{rhino:r,compute:"",git_sha:null}}}catch(e){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Error fetching version:",e),null}}async getInstalledPlugins(e="gh"){this.ensureNotDisposed();try{let r=await this.fetchWithTimeout(`${this.serverUrl}/plugins/${e}/installed`);if(!r.ok)return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`[ComputeServerStats] Failed to fetch ${e} plugins:`,r.status),null;let n=await r.text();try{let s=JSON.parse(n);return s&&typeof s=="object"?s:null}catch (e3){return null}}catch(r){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`[ComputeServerStats] Error fetching ${e} plugins:`,r),null}}async getServerStats(){if(this.ensureNotDisposed(),!await this.isServerOnline())return{isOnline:!1};let[r,n]=await Promise.all([this.getVersion(),this.getActiveChildren({initialize:!1})]);return{isOnline:!0,...r&&{version:r},...n!==null&&{activeChildren:n}}}async purgeCache(){this.ensureNotDisposed();try{let e=await this.fetchWithTimeout(`${this.serverUrl}/cache/purge`,{method:"POST"});if(!e.ok)return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Failed to purge cache:",e.status),null;let r=await e.text();try{let n=JSON.parse(r);return typeof n.purged=="number"?n.purged:null}catch (e4){return null}}catch(e){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Error purging cache:",e),null}}async purgeAllChildren(){this.ensureNotDisposed();let e=await this.getActiveChildren({initialize:!1});if(e===null)return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] purgeAllChildren: could not read child count"),null;if(e===0)return{totalPurged:0,calls:0,children:0,confident:!0};let r=e*2,n=0;for(let s=0;s<r;s++){let o=await this.purgeCache();o!==null&&(n+=o)}return{totalPurged:n,calls:r,children:e,confident:e===1}}async getServerTime(){this.ensureNotDisposed();try{let e=await this.fetchWithTimeout(`${this.serverUrl}/servertime`);if(!e.ok)return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Failed to fetch server time:",e.status),null;let r=(await e.text()).trim().replace(/^"|"$/g,""),n=new Date(r);return isNaN(n.getTime())?null:n}catch(e){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Error fetching server time:",e),null}}async getIdleSpan(){this.ensureNotDisposed();try{let e=await this.fetchWithTimeout(`${this.serverUrl}/idlespan`);if(!e.ok)return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Failed to fetch idle span:",e.status),null;let r=parseFloat((await e.text()).trim());return isNaN(r)?null:r}catch(e){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("[ComputeServerStats] Error fetching idle span:",e),null}}async launchChildren(){return this.postJson("/launch-children")}async launchChild(e){let r=e===void 0?"/launch-child":`/launch-child?port=${e}`;return this.postJson(r)}async shutdownChildren(e){let r=e===void 0?"/shutdown-children":`/shutdown-children?port=${e}`;return this.postJson(r)}async recycleChildren(e){let r=e===void 0?"/recycle-children":`/recycle-children?port=${e}`;return this.postJson(r)}async postJson(e){this.ensureNotDisposed();try{let r=await this.fetchWithTimeout(`${this.serverUrl}${e}`,{method:"POST"},b.LIFECYCLE_TIMEOUT_MS);if(!r.ok)return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`[ComputeServerStats] POST ${e} failed:`,r.status),null;let n=await r.text();try{return JSON.parse(n)}catch (e5){return null}}catch(r){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`[ComputeServerStats] Error on POST ${e}:`,r),null}}monitor(e,r=5e3){if(this.ensureNotDisposed(),!Number.isFinite(r)||r<b.MIN_MONITOR_INTERVAL_MS)throw new RangeError(`monitor() intervalMs must be a finite number >= ${b.MIN_MONITOR_INTERVAL_MS}ms, got ${r}. Sub-${b.MIN_MONITOR_INTERVAL_MS}ms polling would hot-loop the server.`);let n=!0,s=null;_chunkMLPWPV6Fcjs.d.call(void 0, ).info(`\u{1F504} Starting server stats monitoring every ${r}ms`);let o=async()=>{if(s!==null&&(this.activeTimeouts.delete(s),s=null),!(!n||this.disposed)){try{let a=await this.getServerStats();if(!n||this.disposed)return;try{e(a)}catch(u){_chunkMLPWPV6Fcjs.d.call(void 0, ).error("[ComputeServerStats] Monitor callback threw:",u)}}catch(a){!n||this.disposed?_chunkMLPWPV6Fcjs.d.call(void 0, ).debug("[ComputeServerStats] Monitor poll cancelled during shutdown:",a):_chunkMLPWPV6Fcjs.d.call(void 0, ).error("[ComputeServerStats] Failed to fetch stats during monitor:",a)}n&&!this.disposed&&(s=setTimeout(()=>{o()},r),this.activeTimeouts.add(s))}},i=()=>{n=!1,s!==null&&(clearTimeout(s),this.activeTimeouts.delete(s),s=null),this.activeMonitors.delete(i)};return this.activeMonitors.add(i),o(),i}async dispose(){if(!this.disposed){this.disposed=!0;for(let e of this.activeMonitors)e();this.activeMonitors.clear();for(let e of this.activeTimeouts)clearTimeout(e);this.activeTimeouts.clear()}}ensureNotDisposed(){if(this.disposed)throw new (0, _chunkMLPWPV6Fcjs.c)("ComputeServerStats has been disposed and cannot be used",_chunkMLPWPV6Fcjs.b.INVALID_STATE,{context:{disposed:this.disposed}})}};_chunkMLPWPV6Fcjs.a.call(void 0, b,"DEFAULT_TIMEOUT_MS",5e3),_chunkMLPWPV6Fcjs.a.call(void 0, b,"LIFECYCLE_TIMEOUT_MS",6e4),_chunkMLPWPV6Fcjs.a.call(void 0, b,"MIN_MONITOR_INTERVAL_MS",100);var I=b;var ue=new Set;function $e(){if(typeof window>"u"||_optionalChain([globalThis, 'access', _2 => _2.process, 'optionalAccess', _3 => _3.versions, 'optionalAccess', _4 => _4.node])!=null)return!1;let e=globalThis.navigator,r=typeof _optionalChain([e, 'optionalAccess', _5 => _5.userAgent])=="string"?e.userAgent:"";return!/jsdom/i.test(r)}function D(t,e){e||$e()&&!ue.has(t)&&(ue.add(t),_chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`Warning: ${t} is running on the client side. For better performance and security, consider running this on the server side.`))}var _e="Unable to load grasshopper definition",je={definition_not_cached:_chunkMLPWPV6Fcjs.b.DEFINITION_NOT_CACHED};function B(t){return{...t,serverErrorCodes:{...je,...t.serverErrorCodes}}}function Be(t){return t instanceof _chunkMLPWPV6Fcjs.c?t.code===_chunkMLPWPV6Fcjs.b.DEFINITION_NOT_CACHED||t.message.includes(_e):!1}function Ue(t,e){if(!e)return;let r=_chunkMLPWPV6Fcjs.g.call(void 0, t,"values");if(!Array.isArray(r)||r.length===0)return;let n=[];for(let o of r){let i=_chunkMLPWPV6Fcjs.g.call(void 0, o,"innerTree");(!i||Object.keys(i).length===0)&&n.push(_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, o,"paramName"), () => ("<unnamed>")))}if(n.length===0)return;let s=n.length===r.length?"all":`${n.length}/${r.length}`;_chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`Solve returned empty output(s) (${s}): ${n.join(", ")}. These parameters produced no data \u2014 check the definition's inputs and the branch feeding each.`)}async function k(t,e,r){D("solveGrasshopperDefinition",r.suppressBrowserWarning);let n=await U(e),{response:s}=await M(E(n,t),r);return s}async function le(t,e,r){D("solveGrasshopperDefinitionWithCacheKey",r.suppressBrowserWarning);let n=await U(e);return M(E(n,t),r)}async function pe(t,e,r,n){D("solveByCacheKey",n.suppressBrowserWarning);let s={algo:null,pointer:e,values:t};try{return{...await M(s,n),missed:!1}}catch(o){if(!Be(o))throw o;let i=await U(r);return{...await M(E(i,t),n),missed:!0}}}async function U(t){if(!_chunkMLPWPV6Fcjs.r.call(void 0, t))return t;try{return await t.load()}catch(e){throw new (0, _chunkMLPWPV6Fcjs.c)(`Failed to load definition bytes for ref '${t.key}'`,_chunkMLPWPV6Fcjs.b.INVALID_INPUT,{context:{definitionKey:t.key},originalError:e instanceof Error?e:new Error(String(e))})}}async function M(t,e){ze(t,e);let r=await _chunkMLPWPV6Fcjs.q.call(void 0, "grasshopper",t,B(e)),{pointer:n,algo:s,...o}=r,i=o,a=_chunkMLPWPV6Fcjs.p.call(void 0, r);if(a!==void 0){let u=typeof s=="string"?s.length:0;_chunkMLPWPV6Fcjs.o.call(void 0, i,Math.max(0,a-u))}return Ue(i,e.debug),{response:i,cacheKey:typeof n=="string"?n:null}}function E(t,e){let r={algo:null,pointer:null,values:e};return t instanceof Uint8Array?r.algo=_chunkMLPWPV6Fcjs.n.call(void 0, t):/^https?:\/\//i.test(t)?r.pointer=t:r.algo=_nullishCoalesce(_chunkMLPWPV6Fcjs.l.call(void 0, t), () => (_chunkMLPWPV6Fcjs.k.call(void 0, t))),r}function ze(t,e){e.cachesolve!=null&&(t.cachesolve=e.cachesolve),e.cacheerroredsolves!=null&&(t.cacheerroredsolves=e.cacheerroredsolves),e.modelunits!=null&&(t.modelunits=e.modelunits),e.angletolerance!=null&&(t.angletolerance=e.angletolerance),e.absolutetolerance!=null&&(t.absolutetolerance=e.absolutetolerance),e.dataversion!=null&&(t.dataversion=e.dataversion)}function ce(t){let e=new WeakSet,r=n=>{if(n===void 0)return"undefined";if(n===null)return"null";if(typeof n=="number")return Number.isFinite(n)?String(n):"null";if(typeof n=="string"||typeof n=="boolean")return JSON.stringify(n);if(typeof n=="bigint")return`${n}n`;if(n instanceof Uint8Array)return`{"__u8":${n.length},"hash":"${me(n)}"}`;if(Array.isArray(n)){if(e.has(n))return'"[Circular]"';e.add(n);let s=[];for(let o=0;o<n.length;o++)s.push(r(n[o]));return e.delete(n),`[${s.join(",")}]`}if(typeof n=="object"){if(e.has(n))return'"[Circular]"';e.add(n);let s;return typeof n.toJSON=="function"?s=r(n.toJSON()):n instanceof Map?s=`{"__map":[${[...n.entries()].map(([i,a])=>`[${r(i)},${r(a)}]`).sort().join(",")}]}`:n instanceof Set?s=`{"__set":[${[...n.values()].map(r).sort().join(",")}]}`:s=`{${Object.keys(n).sort().map(a=>`${JSON.stringify(a)}:${r(n[a])}`).join(",")}}`,e.delete(n),s}return"null"};return r(t)}function fe(t,e){let r=2166136261;for(let n=0;n<t;n++)r^=e(n),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r.toString(16).padStart(8,"0")}function de(t){return fe(t.length,e=>t.charCodeAt(e))}function me(t){return fe(t.length,e=>t[e])}function qe(t,e){return z(F(t),e)}function z(t,e){let r=ce(e);return`${t}|t:${r.length}:${de(r)}`}function F(t){return _chunkMLPWPV6Fcjs.r.call(void 0, t)?`r:${t.key}`:typeof t=="string"?`s:${t.length}:${de(t)}`:`u8:${t.length}:${me(t)}`}var Ke=100;function He(t){return typeof t!="string"?!0:!/^https?:\/\//i.test(t)}function Qe(t){try{return _nullishCoalesce(_optionalChain([JSON, 'access', _6 => _6.stringify, 'call', _7 => _7(t), 'optionalAccess', _8 => _8.length]), () => (0))}catch (e6){return 0}}var A=class{constructor(e,r,n={},s){_chunkMLPWPV6Fcjs.a.call(void 0, this,"executor");_chunkMLPWPV6Fcjs.a.call(void 0, this,"baseConfig");_chunkMLPWPV6Fcjs.a.call(void 0, this,"mode");_chunkMLPWPV6Fcjs.a.call(void 0, this,"maxConcurrent");_chunkMLPWPV6Fcjs.a.call(void 0, this,"maxQueueDepth");_chunkMLPWPV6Fcjs.a.call(void 0, this,"queueWaitMs");_chunkMLPWPV6Fcjs.a.call(void 0, this,"timeoutMs");_chunkMLPWPV6Fcjs.a.call(void 0, this,"retry");_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheEnabled");_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheMaxBytes");_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheTtl");_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheErroredSolves");_chunkMLPWPV6Fcjs.a.call(void 0, this,"cache",new Map);_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheBytes",0);_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheHits",0);_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheMisses",0);_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheEvictions",0);_chunkMLPWPV6Fcjs.a.call(void 0, this,"cacheKeyExecutor");_chunkMLPWPV6Fcjs.a.call(void 0, this,"reuseServerDefinitionCache");_chunkMLPWPV6Fcjs.a.call(void 0, this,"serverCacheKeys",new Map);_chunkMLPWPV6Fcjs.a.call(void 0, this,"onStart");_chunkMLPWPV6Fcjs.a.call(void 0, this,"onSettle");_chunkMLPWPV6Fcjs.a.call(void 0, this,"onSuperseded");_chunkMLPWPV6Fcjs.a.call(void 0, this,"subscribers",new Set);_chunkMLPWPV6Fcjs.a.call(void 0, this,"inFlight",new Set);_chunkMLPWPV6Fcjs.a.call(void 0, this,"pendingForLatestWins",null);_chunkMLPWPV6Fcjs.a.call(void 0, this,"fifoQueue",[]);_chunkMLPWPV6Fcjs.a.call(void 0, this,"_lastResult",null);_chunkMLPWPV6Fcjs.a.call(void 0, this,"_lastError",null);_chunkMLPWPV6Fcjs.a.call(void 0, this,"_lastDurationMs",null);_chunkMLPWPV6Fcjs.a.call(void 0, this,"solveSeq",0);_chunkMLPWPV6Fcjs.a.call(void 0, this,"lastStateSeq",0);_chunkMLPWPV6Fcjs.a.call(void 0, this,"disposed",!1);this.executor=e,this.cacheKeyExecutor=s,this.baseConfig=r,this.mode=_nullishCoalesce(n.mode, () => ("latest-wins")),this.maxConcurrent=Math.max(1,_nullishCoalesce(n.maxConcurrent, () => ((this.mode==="parallel"?4:1)))),this.maxQueueDepth=n.maxQueueDepth!==void 0&&n.maxQueueDepth>0?Math.floor(n.maxQueueDepth):void 0,this.queueWaitMs=n.queueWaitMs!==void 0&&n.queueWaitMs>0?n.queueWaitMs:void 0,this.timeoutMs=n.timeoutMs,this.retry=n.retry;let o=n.cache,i=typeof o=="object"?o:null;this.cacheMaxBytes=Math.max(0,_nullishCoalesce(_optionalChain([i, 'optionalAccess', _9 => _9.maxBytes]), () => (0))),this.cacheEnabled=this.cacheMaxBytes>0,this.cacheTtl=_nullishCoalesce(_optionalChain([i, 'optionalAccess', _10 => _10.ttlMs]), () => (0)),this.cacheErroredSolves=_nullishCoalesce(_optionalChain([i, 'optionalAccess', _11 => _11.cacheErroredSolves]), () => (!0)),this.reuseServerDefinitionCache=!!s&&(_nullishCoalesce(n.reuseServerDefinitionCache, () => (!0))),this.onStart=n.onStart,this.onSettle=n.onSettle,this.onSuperseded=n.onSuperseded}get isSolving(){return this.inFlight.size>0}get hasPending(){return this.pendingForLatestWins!==null||this.fifoQueue.length>0}get inFlightCount(){return this.inFlight.size}get queueDepth(){return this.fifoQueue.length+(this.pendingForLatestWins?1:0)}get lastResult(){return this._lastResult}get lastError(){return this._lastError}get lastDurationMs(){return this._lastDurationMs}setMaxConcurrent(e){let r=Math.max(1,Math.floor(e));if(r===this.maxConcurrent)return;let n=r>this.maxConcurrent;this.maxConcurrent=r,n&&this.drainNext()}getMaxConcurrent(){return this.maxConcurrent}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(){for(let e of this.subscribers)try{e()}catch(r){_chunkMLPWPV6Fcjs.d.call(void 0, ).error("[SolveScheduler] subscriber threw:",r)}}solve(e,r,n){if(this.disposed)return Promise.reject(new (0, _chunkMLPWPV6Fcjs.c)("SolveScheduler has been disposed and cannot be used",_chunkMLPWPV6Fcjs.b.INVALID_STATE));let s=F(e),o=z(s,r),i=++this.solveSeq,a={key:o,enqueuedAt:Date.now(),startedAt:null};if(_optionalChain([n, 'optionalAccess', _12 => _12.signal, 'optionalAccess', _13 => _13.aborted]))return Promise.reject(this.makeAbortError(a));if(this.cacheEnabled){let u=this.readCache(o);if(u){this.mode==="latest-wins"&&this.supersedeCurrent();let m={status:"success",response:u,durationMs:0,fromCache:!0};return this.writeLastState(i,{result:u,durationMs:0}),this.runHook(this.onStart,a),this.runHook(this.onSettle,a,m),this.notify(),Promise.resolve(u)}}return new Promise((u,m)=>{let f={definition:e,dataTree:r,definitionHash:s,ctx:a,seq:i,resolve:u,reject:m,externalSignal:_optionalChain([n, 'optionalAccess', _14 => _14.signal])};f.externalSignal&&(f.queuedAbortHandler=()=>this.abortQueuedItem(f),f.externalSignal.addEventListener("abort",f.queuedAbortHandler,{once:!0})),this.enqueue(f)})}writeLastState(e,r){return e<this.lastStateSeq?!1:(this.lastStateSeq=e,"result"in r?(this._lastResult=r.result,this._lastError=null):this._lastError=r.error,this._lastDurationMs=r.durationMs,!0)}supersedeCurrent(){this.pendingForLatestWins&&(this.supersede(this.pendingForLatestWins),this.pendingForLatestWins=null);for(let e of this.inFlight)this.supersede(e),e.controller.abort()}abortQueuedItem(e){this.pendingForLatestWins===e&&(this.pendingForLatestWins=null);let r=this.fifoQueue.indexOf(e);r>=0&&this.fifoQueue.splice(r,1),this.rejectAsAborted(e),this.notify()}shedAsQueueFull(e){let r=new (0, _chunkMLPWPV6Fcjs.c)("Solve queue is full; request rejected (backpressure)",_chunkMLPWPV6Fcjs.b.QUEUE_FULL,{statusCode:503,context:{key:e.ctx.key,queueDepth:this.fifoQueue.length,maxQueueDepth:this.maxQueueDepth}});this.settleError(e,r)&&this.runHook(this.onSettle,e.ctx,{status:"error",error:r,durationMs:0}),this.notify()}armQueueWaitTimer(e){if(this.queueWaitMs===void 0)return;let r=this.queueWaitMs;e.queueWaitTimer=setTimeout(()=>{if(e.settled)return;let n=this.fifoQueue.indexOf(e);n>=0&&this.fifoQueue.splice(n,1);let s=new (0, _chunkMLPWPV6Fcjs.c)(`Solve waited longer than ${r}ms in queue; rejected (backpressure)`,_chunkMLPWPV6Fcjs.b.QUEUE_TIMEOUT,{statusCode:503,context:{key:e.ctx.key,waitedMs:Date.now()-e.ctx.enqueuedAt,queueWaitMs:r}});this.settleError(e,s)&&this.runHook(this.onSettle,e.ctx,{status:"error",error:s,durationMs:0}),this.notify()},r)}clearQueueWaitTimer(e){e.queueWaitTimer!==void 0&&(clearTimeout(e.queueWaitTimer),e.queueWaitTimer=void 0)}enqueue(e){switch(this.mode){case"latest-wins":{this.supersedeCurrent(),this.inFlight.size===0?this.execute(e):this.pendingForLatestWins=e;break}case"queue":case"parallel":{this.inFlight.size<this.maxConcurrent?this.execute(e):this.maxQueueDepth!==void 0&&this.fifoQueue.length>=this.maxQueueDepth?this.shedAsQueueFull(e):(this.armQueueWaitTimer(e),this.fifoQueue.push(e));break}}this.notify()}async execute(e){let r=new AbortController,n=e;n.controller=r,this.inFlight.add(n),e.ctx.startedAt=Date.now(),this.clearQueueWaitTimer(e),this.removeQueuedAbortHandler(e);let s=()=>r.abort();_optionalChain([e, 'access', _15 => _15.externalSignal, 'optionalAccess', _16 => _16.addEventListener, 'call', _17 => _17("abort",s,{once:!0})]),this.runHook(this.onStart,e.ctx),this.notify();let o=performance.now();try{let i={...this.baseConfig,signal:r.signal,...this.timeoutMs!==void 0&&{timeoutMs:this.timeoutMs},...this.retry!==void 0&&{retry:this.retry}},{response:a,definitionReuploaded:u}=await this.runExecutor(e.definition,e.dataTree,e.definitionHash,i),m=performance.now()-o;if(this.cacheEnabled&&this.writeCache(e.ctx.key,a),!this.settleSuccess(e,a))return;this.writeLastState(e.seq,{result:a,durationMs:m}),this.runHook(this.onSettle,e.ctx,{status:"success",response:a,durationMs:m,fromCache:!1,definitionReuploaded:u})}catch(i){let a=performance.now()-o,u=this.normalizeExecutionError(i,n);this.writeLastState(e.seq,{error:u,durationMs:a}),this.settleError(e,u)&&this.runHook(this.onSettle,e.ctx,{status:"error",error:u,durationMs:a})}finally{_optionalChain([e, 'access', _18 => _18.externalSignal, 'optionalAccess', _19 => _19.removeEventListener, 'call', _20 => _20("abort",s)]),this.inFlight.delete(n),this.drainNext(),this.notify()}}async runExecutor(e,r,n,s){if(!this.cacheKeyExecutor||!this.reuseServerDefinitionCache||!He(e))return{response:await this.executor(e,r,s)};let o=n,i=_nullishCoalesce(this.serverCacheKeys.get(o), () => (null)),a=await this.cacheKeyExecutor(e,r,i,s);if(a.cacheKey)for(this.serverCacheKeys.delete(o),this.serverCacheKeys.set(o,a.cacheKey);this.serverCacheKeys.size>Ke;){let u=this.serverCacheKeys.keys().next().value;if(u===void 0)break;this.serverCacheKeys.delete(u)}else this.serverCacheKeys.delete(o);return{response:a.response,definitionReuploaded:a.missed}}drainNext(){if(!this.disposed){if(this.mode==="latest-wins"){if(this.pendingForLatestWins&&this.inFlight.size===0){let e=this.pendingForLatestWins;this.pendingForLatestWins=null,this.execute(e)}return}for(;this.fifoQueue.length>0&&this.inFlight.size<this.maxConcurrent;){let e=this.fifoQueue.shift();this.execute(e)}}}supersede(e){let r=new (0, _chunkMLPWPV6Fcjs.c)("Superseded by newer solve",_chunkMLPWPV6Fcjs.b.SUPERSEDED,{context:{key:e.ctx.key,enqueuedAt:e.ctx.enqueuedAt}});this.settleError(e,r)&&this.runHook(this.onSuperseded,e.ctx)}makeAbortError(e){return new (0, _chunkMLPWPV6Fcjs.c)("Request aborted by caller",_chunkMLPWPV6Fcjs.b.ABORTED,{context:{key:e.key,enqueuedAt:e.enqueuedAt}})}settleError(e,r){return e.settled?!1:(e.settled={error:r},this.clearQueueWaitTimer(e),this.removeQueuedAbortHandler(e),e.reject(r),!0)}settleSuccess(e,r){return e.settled?!1:(e.settled={ok:!0},this.clearQueueWaitTimer(e),this.removeQueuedAbortHandler(e),e.resolve(r),!0)}removeQueuedAbortHandler(e){e.queuedAbortHandler&&e.externalSignal&&e.externalSignal.removeEventListener("abort",e.queuedAbortHandler),e.queuedAbortHandler=void 0}isAbortLikeError(e){if(e instanceof Error){if(e.name==="AbortError")return!0;if(typeof DOMException<"u"&&e instanceof DOMException)return e.name==="AbortError"}return!1}normalizeExecutionError(e,r){return r.settled&&"error"in r.settled?r.settled.error:e instanceof _chunkMLPWPV6Fcjs.c?e:this.isAbortLikeError(e)?this.makeAbortError(r.ctx):new (0, _chunkMLPWPV6Fcjs.c)(e instanceof Error?e.message:String(e),_chunkMLPWPV6Fcjs.b.UNKNOWN_ERROR,{originalError:e instanceof Error?e:new Error(String(e))})}cancelAll(){for(this.pendingForLatestWins&&(this.rejectAsAborted(this.pendingForLatestWins),this.pendingForLatestWins=null);this.fifoQueue.length>0;){let e=this.fifoQueue.shift();this.rejectAsAborted(e)}for(let e of this.inFlight){let r=this.makeAbortError(e.ctx);this.settleError(e,r)&&this.runHook(this.onSettle,e.ctx,{status:"error",error:r,durationMs:e.ctx.startedAt?Date.now()-e.ctx.startedAt:0}),e.controller.abort()}this.notify()}rejectAsAborted(e){let r=this.makeAbortError(e.ctx);this.settleError(e,r)&&this.runHook(this.onSettle,e.ctx,{status:"error",error:r,durationMs:0})}readCache(e){if(!this.cacheEnabled)return null;let r=this.cache.get(e);return r?this.cacheTtl>0&&Date.now()-r.insertedAt>this.cacheTtl?(this.dropCacheEntry(e),this.cacheMisses+=1,null):(this.cache.delete(e),this.cache.set(e,r),this.cacheHits+=1,r.response):(this.cacheMisses+=1,null)}writeCache(e,r){if(!this.cacheEnabled||!this.cacheErroredSolves&&r.errors&&r.errors.length>0)return;let n=_nullishCoalesce(_chunkMLPWPV6Fcjs.p.call(void 0, r), () => (Qe(r)));if(!(n>this.cacheMaxBytes))for(this.dropCacheEntry(e),this.cache.set(e,{response:r,insertedAt:Date.now(),sizeBytes:n}),this.cacheBytes+=n;this.cacheBytes>this.cacheMaxBytes&&this.cache.size>0;){let s=this.cache.keys().next().value;if(s===void 0)break;this.dropCacheEntry(s),this.cacheEvictions+=1}}dropCacheEntry(e){let r=this.cache.get(e);r&&(this.cache.delete(e),this.cacheBytes-=r.sizeBytes)}clearCache(){this.cache.clear(),this.cacheBytes=0}cacheStats(){return{entries:this.cache.size,bytes:this.cacheBytes,hits:this.cacheHits,misses:this.cacheMisses,evictions:this.cacheEvictions}}dispose(){this.disposed||(this.disposed=!0,this.cancelAll(),this.subscribers.clear(),this.clearCache())}runHook(e,...r){if(e)try{e(...r)}catch(n){_chunkMLPWPV6Fcjs.d.call(void 0, ).error("[SolveScheduler] hook threw:",n)}}};function he(t){return _chunkMLPWPV6Fcjs.r.call(void 0, t)?`ref:${t.key}`:typeof t=="string"&&t.length<200?t:"...content..."}function ye(t){return Array.isArray(t)?t.map(e=>{let r=0,n=0,s=_chunkMLPWPV6Fcjs.g.call(void 0, e,"innerTree");if(s&&typeof s=="object"){for(let o of Object.values(s))if(Array.isArray(o)){r+=o.length;for(let i of o){let a=_optionalChain([i, 'optionalAccess', _21 => _21.data]);typeof a=="string"&&(n+=a.length)}}}return{param:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, e,"paramName"), () => ("<unnamed>")),items:r,bytes:n}}):[]}var L=class t{constructor(e){_chunkMLPWPV6Fcjs.a.call(void 0, this,"config");_chunkMLPWPV6Fcjs.a.call(void 0, this,"serverStats");_chunkMLPWPV6Fcjs.a.call(void 0, this,"disposed",!1);this.config=this.normalizeComputeConfig(e),this.serverStats=new I(this.config.serverUrl,this.config.apiKey)}static async create(e){let r=new t(e),n=Math.max(1,(_nullishCoalesce(_optionalChain([e, 'access', _22 => _22.retry, 'optionalAccess', _23 => _23.attempts]), () => (2)))+1),s=_nullishCoalesce(_optionalChain([e, 'access', _24 => _24.retry, 'optionalAccess', _25 => _25.baseDelayMs]), () => (250)),o=_nullishCoalesce(_optionalChain([e, 'access', _26 => _26.retry, 'optionalAccess', _27 => _27.maxDelayMs]), () => (1e3));for(let i=0;i<n;i++){if(await r.serverStats.isServerOnline())return r;if(i<n-1){let a=Math.min(s*2**i,o);await new Promise(u=>setTimeout(u,a))}}throw await r.dispose(),new (0, _chunkMLPWPV6Fcjs.c)("Rhino Compute server is not online",_chunkMLPWPV6Fcjs.b.NETWORK_ERROR,{context:{serverUrl:r.config.serverUrl,attempts:n}})}getConfig(){return this.ensureNotDisposed(),{...this.config}}async getIO(e){return this.ensureNotDisposed(),q(e,this.config)}async getRawIO(e){return this.ensureNotDisposed(),V(e,this.config)}async solve(e,r,n){this.ensureNotDisposed();try{if(typeof e=="string"&&!_optionalChain([e, 'optionalAccess', _28 => _28.trim, 'call', _29 => _29()]))throw new (0, _chunkMLPWPV6Fcjs.c)("Definition URL/content is required",_chunkMLPWPV6Fcjs.b.INVALID_INPUT,{context:{receivedUrl:e}});if(e instanceof Uint8Array&&e.length===0)throw new (0, _chunkMLPWPV6Fcjs.c)("Definition content is empty",_chunkMLPWPV6Fcjs.b.INVALID_INPUT);if(_chunkMLPWPV6Fcjs.r.call(void 0, e)&&!e.key.trim())throw new (0, _chunkMLPWPV6Fcjs.c)("DefinitionRef key is empty",_chunkMLPWPV6Fcjs.b.INVALID_INPUT);let s={...this.config,..._optionalChain([n, 'optionalAccess', _30 => _30.signal])!==void 0&&{signal:n.signal},..._optionalChain([n, 'optionalAccess', _31 => _31.timeoutMs])!==void 0&&{timeoutMs:n.timeoutMs},..._optionalChain([n, 'optionalAccess', _32 => _32.retry])!==void 0&&{retry:n.retry}},o=await k(r,e,s);if(_optionalChain([o, 'optionalAccess', _33 => _33.errors])&&o.errors.length>0)throw new (0, _chunkMLPWPV6Fcjs.c)(o.errors.join("; ")||"Computation failed",_chunkMLPWPV6Fcjs.b.COMPUTATION_ERROR,{context:{definition:he(e),inputSummary:ye(r),errors:o.errors,warnings:o.warnings,values:o.values}});return o}catch(s){throw this.config.debug&&_chunkMLPWPV6Fcjs.d.call(void 0, ).error("Compute failed:",s),s instanceof _chunkMLPWPV6Fcjs.c?s:new (0, _chunkMLPWPV6Fcjs.c)(s instanceof Error?s.message:String(s),_chunkMLPWPV6Fcjs.b.COMPUTATION_ERROR,{context:{definition:he(e),inputSummary:ye(r)},originalError:s instanceof Error?s:new Error(String(s))})}}createScheduler(e){this.ensureNotDisposed();let r=(s,o,i)=>k(o,s,i),n=(s,o,i,a)=>i===null?le(o,s,a).then(u=>({...u,missed:!1})):pe(o,i,s,a);return new A(r,this.config,e,n)}async dispose(){this.disposed||(this.disposed=!0,await this.serverStats.dispose())}ensureNotDisposed(){if(this.disposed)throw new (0, _chunkMLPWPV6Fcjs.c)("GrasshopperClient has been disposed and cannot be used",_chunkMLPWPV6Fcjs.b.INVALID_STATE)}normalizeComputeConfig(e){return{...e,serverUrl:_chunkMLPWPV6Fcjs.j.call(void 0, e.serverUrl),apiKey:e.apiKey,authToken:e.authToken,debug:_nullishCoalesce(e.debug, () => (!1)),suppressBrowserWarning:e.suppressBrowserWarning}}};var G=new Map;function be(t,e){G.set(t,e)}be("Rhino.Geometry.Point3d",(t,e)=>{let r=e;return!r||typeof r.X!="number"?null:new t.Point([r.X,r.Y,r.Z])});be("Rhino.Geometry.Line",(t,e)=>{let r=e;return!r||!r.From||!r.To?null:new t.Line([r.From.X,r.From.Y,r.From.Z],[r.To.X,r.To.Y,r.To.Z])});function Je(t){if(G.has(t))return G.get(t);for(let[e,r]of G)if(t.startsWith(e))return r}function Ye(t){return!!(t&&typeof t=="object"&&typeof t.data=="string")}function ge(t,e){return{__decodeError:!0,type:t,raw:e}}function Te(t,e,r){let n=Je(e),s=!1;if(n)try{let o=n(r,t);if(o!=null)return o}catch(o){_chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`Failed to decode Rhino type ${e}:`,o),s=!0}try{if(Ye(t))return r.CommonObject.decode(t)}catch(o){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`Failed to decode ${e} with CommonObject:`,o),ge(e,t)}return s?ge(e,t):t}function Se(t){let e=new WeakSet,r=n=>{if(!n||typeof n!="object"||e.has(n))return;e.add(n);let s=n.delete;if(typeof s=="function"){let i=n.isDeleted;(typeof i!="function"||!i.call(n))&&s.call(n);return}if(Array.isArray(n)){for(let i of n)r(i);return}let o=Object.getPrototypeOf(n);if(o===Object.prototype||o===null)for(let i of Object.values(n))r(i)};r(t)}var x={STRING:"System.String",INT:"System.Int32",DOUBLE:"System.Double",BOOL:"System.Boolean"},Xe="Rhino.Geometry.",Ze=["WebDisplay"],et="FileData";function Ce(t){return Ze.some(e=>t.includes(e))}function tt(t){if(typeof t!="string")return t;let e=t.trim();if(!(e.startsWith("{")||e.startsWith("[")||e.startsWith('"')))return t;try{let n=JSON.parse(e);if(typeof n=="string")try{return JSON.parse(n)}catch (e7){return n}return n}catch (e8){return t}}function rt(t,e,r){switch(e){case x.STRING:return typeof t!="string"?t:t.replace(/^"(.*)"$/,"$1");case x.INT:return Number.parseInt(t,10);case x.DOUBLE:return Number.parseFloat(t);case x.BOOL:return String(t).toLowerCase()==="true";default:return r&&e.startsWith(Xe)?Te(t,e,r):t}}var K=new WeakMap;function ve(t){if(K.has(t))return K.get(t);let e=tt(t.data);return K.set(t,e),e}function Ie(t,e,r,n){if(typeof t.data!="string")return t.data;let s=r?ve(t):t.data;return rt(s,e,n)}function nt(t){if(!t||typeof t!="object")return!1;let e=t;return typeof e.fileName=="string"&&typeof e.fileType=="string"&&"data"in e&&typeof e.isBase64Encoded=="boolean"&&typeof e.subFolder=="string"}function H(t){let e=_chunkMLPWPV6Fcjs.g.call(void 0, t,"values");return Array.isArray(e)?e:[]}function Q(t){return typeof t.type=="string"?t.type:""}function J(t,e){if(!(!t||typeof t!="object")){for(let r of Object.values(t))if(Array.isArray(r))for(let n of r)n&&typeof n=="object"&&e(n)}}function De(t,e=!1,r={}){let{parseValues:n=!0,rhino:s,stringOnly:o=!1}=r,i={},a=new Set;for(let u of H(t)){let m=_chunkMLPWPV6Fcjs.g.call(void 0, u,"paramName");J(_chunkMLPWPV6Fcjs.g.call(void 0, u,"innerTree"),f=>{let h=Q(f);if(Ce(h)||o&&h!==x.STRING)return;let g=e?f.id:m;if(!g)return;let T=Ie(f,h,n,s);g in i?a.has(g)?i[g].push(T):(i[g]=[i[g],T],a.add(g)):i[g]=T})}return{values:i,dispose:()=>Se(i)}}function xe(t){let e=[];for(let r of H(t))J(_chunkMLPWPV6Fcjs.g.call(void 0, r,"innerTree"),n=>{if(!Q(n).includes(et))return;let s=ve(n);nt(s)&&e.push(s)});return e}function we(t,e,r={}){let{parseValues:n=!0,rhino:s,stringOnly:o=!1}=r;for(let i of H(t)){let a=_chunkMLPWPV6Fcjs.g.call(void 0, i,"innerTree"),u=!1;if("byName"in e){if(_chunkMLPWPV6Fcjs.g.call(void 0, i,"paramName")!==e.byName)continue;u=!0}let m=[];if(J(a,f=>{if("byId"in e){if(f.id!==e.byId)return;u=!0}let h=Q(f);Ce(h)||o&&h!==x.STRING||m.push(Ie(f,h,n,s))}),u)return m.length===0?void 0:m.length===1?m[0]:m}}var W=class{constructor(e,r=!1){_chunkMLPWPV6Fcjs.a.call(void 0, this,"response",e);_chunkMLPWPV6Fcjs.a.call(void 0, this,"debug",r)}getValues(e=!1,r={}){return De(this.response,e,r)}getValue(e,r){return we(this.response,e,r)}getFileData(){return xe(this.response)}async getAndDownloadFiles(e,r){let n=this.getFileData();await _chunkMLPWPV6Fcjs.t.call(void 0, n,e,r)}};var Y=/^\{(-?\d+(?:;-?\d+)*)?\}$/;function w(t){if(typeof t!="object"||t===null||Array.isArray(t))return!1;let e=Object.entries(t);return e.length>0&&e.every(([r,n])=>Y.test(r)&&Array.isArray(n))}function X(t,e,r){let n=Number(t.toFixed(e));return Math.abs(t-n)<r?n:t}function Ee(t,e){if(!Number.isFinite(t)||t===0)return .1;let r=Math.abs(t);if(r>=1){let g=String(t).split(".")[1];if(g&&g.length>0){let T=Math.min(g.length,12),v=Math.pow(10,-T),ne=Number(v.toFixed(T));return Math.abs(ne-v)<e?ne:v}return 1}let n=String(t),s=n.toLowerCase().match(/e(-?\d+)/);if(s){let h=Number(s[1]);if(h<0||n.toLowerCase().includes("e-")){let g=Math.abs(h),T=Math.pow(10,-g),v=Number(T.toFixed(g));return Math.abs(v-T)<e?v:T}return .1}let o=12,a=r.toFixed(o).replace(/0+$/,""),u=Math.min((a.split(".")[1]||"").length,o);if(u===0)return .1;let m=Math.pow(10,-u),f=Number(m.toFixed(u));return Math.abs(f-m)<e?f:m}function Z(t){return typeof t.stepSize=="number"&&Number.isFinite(t.stepSize)&&t.stepSize>0?t.stepSize:void 0}function C(t,e,r){if(t==null)return t;if(Array.isArray(t)){let s=t.map(e).filter(o=>o!==null);return s.length>0?s:void 0}let n=e(t);return n!==null?n:r?void 0:t}var R=t=>{if(typeof t=="number")return Number.isFinite(t)?t:null;if(typeof t=="string"){let e=t.trim();if(e===""||/^[+-]?0[xbo]/i.test(e))return null;let r=Number(e);return Number.isFinite(r)?r:null}return null},$=t=>{if(typeof t=="boolean")return t;if(typeof t=="string"){let e=t.trim().toLowerCase();if(e==="true")return!0;if(e==="false")return!1}return null},Ae=t=>typeof t=="string"?t.length>=2&&t.startsWith('"')&&t.endsWith('"')?t.slice(1,-1):t.startsWith('"')?t.slice(1):t:null,Re=t=>{if(typeof t=="string"){let e=t.trim();return e.startsWith('"')&&e.endsWith('"')&&(e=e.slice(1,-1).trim()),e}return null};function ee(t){return e=>{if(typeof e=="object"&&e!==null)return e;if(typeof e=="string"&&e.trim()!=="")try{let r=JSON.parse(e);return typeof r=="object"&&r!==null?r:(_chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`Parsed value for input ${t} is not an object`),null)}catch(r){return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`Failed to parse object value "${e}" for input ${t}`,r),null}return null}}function st(t,e=1e-8){let r=t.paramType==="Integer",n=Z(t);if(w(t.default))return{default:t.default,stepSize:_nullishCoalesce(n, () => ((r?1:.1)))};if(typeof t.default=="string"&&t.default.trim()!==""&&R(t.default)===null)throw new (0, _chunkMLPWPV6Fcjs.c)(`Invalid numeric default "${t.default}" for input "${t.name||"unknown"}"`,_chunkMLPWPV6Fcjs.b.VALIDATION_ERROR,{context:{inputName:t.name,default:t.default}});let s=C(t.default,R,!0);if(r)return Array.isArray(s)?s=s.map(h=>typeof h=="number"?Math.round(h):h):typeof s=="number"&&(s=Math.round(s)),{default:s,stepSize:_nullishCoalesce(n, () => (1))};let o=Array.isArray(s)?s[0]:s,i;typeof o=="number"&&Number.isFinite(o)&&o!==0?i=o:typeof t.minimum=="number"&&Number.isFinite(t.minimum)&&t.minimum!==0?i=t.minimum:typeof t.maximum=="number"&&Number.isFinite(t.maximum)&&t.maximum!==0&&(i=t.maximum);let a=_nullishCoalesce(n, () => ((i!==void 0?Ee(i,e):.1))),u=0,m=String(a),f=m.toLowerCase().match(/e(-?\d+)/);if(f?u=Math.abs(Number(f[1])):u=_nullishCoalesce(_optionalChain([m, 'access', _34 => _34.split, 'call', _35 => _35("."), 'access', _36 => _36[1], 'optionalAccess', _37 => _37.length]), () => (0)),u===0&&typeof o=="number"&&o!==0&&Math.abs(o)<1){let h=Math.ceil(-Math.log10(Math.abs(o)));Number.isFinite(h)&&h>0&&(u=h)}return u=Math.min(Math.max(u,0),12),Array.isArray(s)?s=s.map(h=>typeof h=="number"?X(h,u,e):h):typeof s=="number"&&(s=X(s,u,e)),{default:s,stepSize:a}}var ot={types:["Number","Integer"],parse(t,e){let{default:r,stepSize:n}=st(t);return{...e,paramType:t.paramType,minimum:t.minimum,maximum:t.maximum,atLeast:t.atLeast,atMost:t.atMost,stepSize:n,default:r}},fallback(t,e){let r=(_nullishCoalesce(t.atMost, () => (1)))>1,n=typeof t.minimum=="number"&&Number.isFinite(t.minimum)&&t.minimum>0?t.minimum:0;return{...e,paramType:t.paramType,minimum:t.minimum,maximum:t.maximum,atLeast:t.atLeast,atMost:t.atMost,stepSize:_nullishCoalesce(Z(t), () => ((t.paramType==="Integer"?1:.1))),default:r?[n]:n}}},it={types:["Boolean"],parse(t,e){if(w(t.default))return{...e,paramType:"Boolean",default:t.default};let r=C(t.default,$,!1);if(r!=null&&typeof r!="boolean"&&!Array.isArray(r))throw new (0, _chunkMLPWPV6Fcjs.c)(`Invalid boolean default "${String(r)}" for input "${t.name||"unknown"}"`,_chunkMLPWPV6Fcjs.b.VALIDATION_ERROR,{context:{inputName:t.name,default:t.default}});return{...e,paramType:"Boolean",default:r}},fallback(t,e){let r=(_nullishCoalesce(t.atMost, () => (1)))>1;return{...e,paramType:"Boolean",default:r?[!1]:!1}}},at={types:["Text"],parse(t,e){let r=C(t.default,Ae,!1);return{...e,paramType:"Text",default:r}},fallback(t,e){let r=(_nullishCoalesce(t.atMost, () => (1)))>1;return{...e,paramType:"Text",default:r?[""]:""}}},ut={types:["ValueList"],parse(t,e){if(!t.values||typeof t.values!="object"||Object.keys(t.values).length===0)throw _chunkMLPWPV6Fcjs.c.missingValues(t.nickname||"unnamed","ValueList");let r=t.default;if(t.default!==void 0&&t.default!==null){if(typeof t.default=="object")throw new (0, _chunkMLPWPV6Fcjs.c)(`ValueList input "${t.nickname||"unnamed"}" default is not a string-able value`,_chunkMLPWPV6Fcjs.b.VALIDATION_ERROR,{context:{inputName:t.name,default:t.default}});let n=String(t.default).toLowerCase(),s=Object.keys(t.values).find(o=>o.toLowerCase()===n);s!==void 0?r=s:_chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`ValueList input "${t.nickname||"unnamed"}" default value "${t.default}" is not in available values`)}return{...e,paramType:"ValueList",values:t.values,default:r}},fallback(t,e){return{...e,paramType:"ValueList",values:t.values&&typeof t.values=="object"?t.values:{},default:void 0}}},Pe={types:["Geometry"],parse(t,e){let r=C(t.default,ee(t.nickname||"unnamed"),!0);return{...e,paramType:"Geometry",default:r}},fallback(t,e){let r=(_nullishCoalesce(t.atMost, () => (1)))>1;return{...e,paramType:"Geometry",default:r?[null]:null}}},lt={types:["File"],parse(t,e){let r=C(t.default,ee(t.nickname||"unnamed"),!0);return{...e,paramType:"File",acceptedFormats:t.acceptedFormats,default:r}},fallback(t,e){let r=(_nullishCoalesce(t.atMost, () => (1)))>1;return{...e,paramType:"File",default:r?[null]:null}}},pt={types:["Color"],parse(t,e){let r=C(t.default,Re,!1);return{...e,paramType:"Color",default:r}},fallback(t,e){let r=(_nullishCoalesce(t.atMost, () => (1)))>1;return{...e,paramType:"Color",default:r?["0, 0, 0"]:"0, 0, 0"}}},ct=[ot,it,at,ut,Pe,lt,pt],te=new Map(ct.flatMap(t=>t.types.map(e=>[e,t]))),Ne=Pe;function Oe(t){return _chunkMLPWPV6Fcjs.g.call(void 0, t,"data")}function ft(t){return _chunkMLPWPV6Fcjs.g.call(void 0, t,"type")}var dt=new Set(["System.Double","System.Single","System.Decimal","System.Int32","System.Int64"]),mt=new Set(["System.Int32","System.Int64"]);function Me(t){if(typeof t.default!="object"||t.default===null)return{schema:t};if(Array.isArray(t.default))return{schema:t};if(!_chunkMLPWPV6Fcjs.h.call(void 0, t.default,"innerTree")){let n=`Input "${_nullishCoalesce(t.name, () => ("unknown"))}" default had an unrecognized shape (no innerTree key); the default was dropped.`;return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("Unexpected structure in input.default:",t.default),{schema:{...t,default:null},warning:{code:"MALFORMED_DEFAULT",message:n}}}let e=_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t.default,"innerTree"), () => ({}));if(Object.keys(e).length===0)return{schema:{...t,default:void 0}};if(t.treeAccess||t.atMost&&t.atMost>1){let n={},s=[];for(let[i,a]of Object.entries(e)){if(!Array.isArray(a)){let m=`Input "${_nullishCoalesce(t.name, () => ("unknown"))}" default had a non-array innerTree branch ("${i}"); the default was dropped.`;return _chunkMLPWPV6Fcjs.d.call(void 0, ).warn("Unexpected structure in input.default innerTree:",t.default),{schema:{...t,default:null},warning:{code:"MALFORMED_DEFAULT",message:m}}}let u=[];for(let m of a){let f=Oe(m),h=ft(m);if(h&&dt.has(h)){if(typeof f=="string"&&f.trim()==="")continue;let g=R(f);if(g===null){s.push(`"${String(f)}" (${h})`);continue}u.push(mt.has(h)?Math.round(g):g);continue}if(h==="System.Boolean"){let g=$(f);if(g===null){s.push(`"${String(f)}" (${h})`);continue}u.push(g);continue}if(typeof f=="string"&&_optionalChain([h, 'optionalAccess', _38 => _38.startsWith, 'call', _39 => _39("Rhino.Geometry")])){try{u.push(JSON.parse(f))}catch (e9){u.push(f)}continue}u.push(f)}n[i]=u}let o;if(s.length>0){let i=`Input "${_nullishCoalesce(t.name, () => ("unknown"))}" default contained ${s.length} tree value(s) that could not be parsed and were dropped: ${s.join(", ")}.`;_chunkMLPWPV6Fcjs.d.call(void 0, ).warn(i),o={code:"MALFORMED_DEFAULT",message:i}}return{schema:{...t,default:n},...o&&{warning:o}}}let r=[];for(let n of Object.values(e))Array.isArray(n)&&n.forEach(s=>{s&&typeof s=="object"&&_chunkMLPWPV6Fcjs.h.call(void 0, s,"data")&&r.push(Oe(s))});return r.length===0?{schema:{...t,default:void 0}}:r.length===1?{schema:{...t,default:r[0]}}:{schema:{...t,default:r}}}var ht=new Map([...te.keys()].map(t=>[t.toLowerCase(),t]));function yt(t){return _nullishCoalesce(ht.get(_optionalChain([t, 'optionalAccess', _40 => _40.toLowerCase, 'call', _41 => _41()])), () => (t))}function gt(t){return ke(t).input}function ke(t){let e={description:t.description,name:t.name,nickname:t.nickname,treeAccess:t.treeAccess,groupName:_nullishCoalesce(t.groupName, () => (void 0)),id:t.id},r=yt(t.paramType),n={...t,paramType:r},s;try{let{schema:i,warning:a}=Me(n);n=i,s=a&&{inputName:t.name||"unknown",paramType:t.paramType,message:a.message,code:a.code}}catch(i){n={...n,default:null},s={inputName:t.name||"unknown",paramType:t.paramType,message:`Input "${_nullishCoalesce(t.name, () => ("unknown"))}" default could not be normalized and was dropped: ${i instanceof Error?i.message:String(i)}`,code:"MALFORMED_DEFAULT"}}let o=te.get(r);try{if(!o)throw _chunkMLPWPV6Fcjs.c.unknownParamType(r,t.name);return{input:o.parse(n,e),error:s,...s&&{errors:[s]}}}catch(i){if(i instanceof _chunkMLPWPV6Fcjs.c){_chunkMLPWPV6Fcjs.d.call(void 0, ).error(`Validation error for input ${t.name||"unknown"}:`,i.message);let a={inputName:t.name||"unknown",paramType:t.paramType,message:i.message,code:i.code};return{input:(_nullishCoalesce(o, () => (Ne))).fallback(n,e),error:a,errors:s?[s,a]:[a]}}throw new (0, _chunkMLPWPV6Fcjs.c)(i instanceof Error?i.message:String(i),"VALIDATION_ERROR",{context:{paramName:t.name,paramType:r},originalError:i instanceof Error?i:new Error(String(i))})}}function Fe(t){let e=[],r=[];for(let n of t){let{input:s,errors:o}=ke(n);e.push(s),o&&r.push(...o)}return{inputs:e,parseErrors:r}}function Le(t){let e=_chunkMLPWPV6Fcjs.g.call(void 0, t,"stepSize");return{id:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"id"), () => ("")),name:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"name"), () => ("")),nickname:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"nickname"), () => (null)),description:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"description"), () => ("")),paramType:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"paramType"), () => ("")),treeAccess:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"treeAccess"), () => (!1)),minimum:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"minimum"), () => (null)),maximum:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"maximum"), () => (null)),atLeast:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"atLeast"), () => (1)),atMost:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"atMost"), () => (1)),stepSize:typeof e=="number"&&Number.isFinite(e)?e:void 0,default:_chunkMLPWPV6Fcjs.g.call(void 0, t,"default"),values:_chunkMLPWPV6Fcjs.g.call(void 0, t,"values"),acceptedFormats:_chunkMLPWPV6Fcjs.g.call(void 0, t,"acceptedFormats"),groupName:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"groupName"), () => (null))}}function Ve(t){return{name:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"name"), () => ("")),nickname:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"nickname"), () => (null)),paramType:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"paramType"), () => ("")),id:_nullishCoalesce(_chunkMLPWPV6Fcjs.g.call(void 0, t,"id"), () => (""))}}async function V(t,e){let r=E(t,[]),n={};if(r.algo&&(n.algo=r.algo),r.pointer&&(n.pointer=r.pointer),!n.algo&&!n.pointer)throw new (0, _chunkMLPWPV6Fcjs.c)("Definition must resolve to either a URL pointer or base64 algo",_chunkMLPWPV6Fcjs.b.INVALID_INPUT,{context:{definition:t}});let s=await _chunkMLPWPV6Fcjs.q.call(void 0, "io",n,B(e));if(!s||typeof s!="object")throw new (0, _chunkMLPWPV6Fcjs.c)("Invalid IO response structure",_chunkMLPWPV6Fcjs.b.INVALID_INPUT,{context:{response:s,definition:t}});let o=Ge(_chunkMLPWPV6Fcjs.g.call(void 0, s,"warnings")),i=Ge(_chunkMLPWPV6Fcjs.g.call(void 0, s,"errors")),a=_chunkMLPWPV6Fcjs.g.call(void 0, s,"inputs"),u=_chunkMLPWPV6Fcjs.g.call(void 0, s,"outputs");return{inputs:Array.isArray(a)?a.map(Le):[],outputs:Array.isArray(u)?u.map(Ve):[],...o&&{loadWarnings:o},...i&&{loadErrors:i}}}function Ge(t){if(!Array.isArray(t))return;let e=t.map(bt).filter(r=>r!==void 0);return e.length>0?e:void 0}function bt(t){if(t==null)return;let e;if(typeof t=="string")e=t;else if(typeof t=="object"){let r=_chunkMLPWPV6Fcjs.g.call(void 0, t,"message");if(typeof r=="string"&&r.trim().length>0)e=r;else try{e=_nullishCoalesce(JSON.stringify(t), () => (String(t)))}catch (e10){e=String(t)}}else e=String(t);return e.trim().length>0?e:void 0}async function q(t,e){D("fetchParsedDefinitionIO",e.suppressBrowserWarning);let{inputs:r,outputs:n,loadWarnings:s,loadErrors:o}=await V(t,e),{inputs:i,parseErrors:a}=Fe(r);return{inputs:i,outputs:n,...a.length>0&&{parseErrors:a},...s&&{loadWarnings:s},...o&&{loadErrors:o}}}function Tt(t){return(Array.isArray(t)?t:[t]).map(r=>({schemas:_chunkMLPWPV6Fcjs.g.call(void 0, r,"schemas"),error:_chunkMLPWPV6Fcjs.g.call(void 0, r,"error")}))}var re=class t{constructor(e){_chunkMLPWPV6Fcjs.a.call(void 0, this,"innerTree");_chunkMLPWPV6Fcjs.a.call(void 0, this,"paramName");this.paramName=e,this.innerTree={}}append(e,r){let n=t.formatPathString(e);this.innerTree[n]||(this.innerTree[n]=[]);let s=r.map(o=>({data:t.serializeValue(o)}));return this.innerTree[n].push(...s),this}appendSingle(e,r){return this.append(e,[r])}fromDataTreeDefault(e){this.innerTree={};for(let[r,n]of Object.entries(e)){if(!Array.isArray(n))continue;let s=t.parsePathString(r);this.append(s,n)}return this}appendFlat(e){let r=Array.isArray(e)?e:[e];return this.append([0],r)}flatten(){let e=[];for(let r of Object.values(this.innerTree))if(Array.isArray(r))for(let n of r)e.push(t.deserializeValue(n.data));return e}getPaths(){return Object.keys(this.innerTree)}getPath(e){let r=t.formatPathString(e),n=this.innerTree[r];if(n)return n.map(s=>t.deserializeValue(s.data))}toComputeFormat(){return{ParamName:this.paramName,InnerTree:this.innerTree}}getInnerTree(){return this.innerTree}getParamName(){return this.paramName}static fromInputParams(e){return e.filter(r=>t.hasValidValue(r.default)).map(r=>{let n=new t(r.nickname||"unnamed"),s=r.default;if(r.treeAccess&&w(s))n.fromDataTreeDefault(s),t.isNumericInput(r)&&n.applyNumericConstraints(r.minimum,r.maximum,r.nickname||"unnamed");else{let o=Array.isArray(s)?s:[s],i=t.processValues(o,r);n.appendFlat(i)}return n.toComputeFormat()})}static fromInputParam(e){return t.hasValidValue(e.default)?t.fromInputParams([e])[0]:void 0}static replaceTreeValue(e,r,n){let s=e.length>0&&e[0]instanceof t,o=t.buildFromValue(r,n);if(s){let m=e.slice(),f=m.findIndex(h=>h.getParamName()===r);return f!==-1?m[f]=o:m.push(o),m}let i=e.slice(),a=o.toComputeFormat(),u=i.findIndex(m=>m.ParamName===r);return u!==-1?i[u]=a:i.push(a),i}static buildFromValue(e,r){let n=new t(e);return w(r)?n.fromDataTreeDefault(r):n.appendFlat(r),n}static getTreeValue(e,r){let s=e.length>0&&e[0]instanceof t?t.readFromBuilders(e,r):t.readFromDataTrees(e,r);return s===null||s.length===0?null:s.length===1?s[0]:s}static readFromBuilders(e,r){let n=e.find(s=>s.getParamName()===r);return n?n.flatten():null}static readFromDataTrees(e,r){let n=e.find(i=>i.ParamName===r);if(!_optionalChain([n, 'optionalAccess', _42 => _42.InnerTree]))return null;let s=Object.keys(n.InnerTree)[0];if(!s)return null;let o=n.InnerTree[s];return Array.isArray(o)?o.map(i=>_optionalChain([i, 'optionalAccess', _43 => _43.data])!==void 0?t.deserializeValue(i.data):null):_optionalChain([o, 'optionalAccess', _44 => _44.data])!==void 0?[t.deserializeValue(o.data)]:o!==void 0?[o]:null}static parsePathString(e){let r=e.match(Y);if(!r)throw new Error(`Invalid Grasshopper tree path: "${e}". Expected "{}", "{0}", or "{0;1;2}" (negative indices allowed, no empty segments).`);return r[1]?r[1].split(";").map(Number):[]}static formatPathString(e){return`{${e.join(";")}}`}applyNumericConstraints(e,r,n){for(let s of Object.values(this.innerTree))if(Array.isArray(s))for(let o of s){let i=t.deserializeValue(o.data);if(typeof i=="number"){let a=t.clampValue(i,e,r,n);o.data=t.serializeValue(a)}}}static serializeValue(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:typeof e=="object"&&e!==null?JSON.stringify(e):String(e)}static deserializeValue(e){if(typeof e=="boolean"||typeof e=="number"||typeof e!="string")return e;if(e.startsWith("{")||e.startsWith("["))try{return JSON.parse(e)}catch (e11){return e}let r=Number(e);return Number.isFinite(r)&&String(r)===e?r:e==="true"?!0:e==="false"?!1:e}static hasValidValue(e){return e==null?!1:typeof e=="string"?!0:!(Array.isArray(e)&&e.length===0||typeof e=="object"&&!Array.isArray(e)&&Object.keys(e).length===0)}static isNumericInput(e){return e.paramType==="Number"||e.paramType==="Integer"}static processValues(e,r){return e.map(n=>t.isNumericInput(r)&&typeof n=="number"?t.clampValue(n,r.minimum,r.maximum,r.nickname||"unnamed"):n).filter(n=>n!=null)}static clampValue(e,r,n,s){let o=e;return r!=null&&o<r&&(_chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`${s}: ${e} below min ${r}, clamping`),o=r),n!=null&&o>n&&(_chunkMLPWPV6Fcjs.d.call(void 0, ).warn(`${s}: ${e} above max ${n}, clamping`),o=n),o}};exports.ComputeServerStats = I; exports.GrasshopperClient = L; exports.GrasshopperResponseProcessor = W; exports.SolveScheduler = A; exports.TreeBuilder = re; exports.downloadFileData = _chunkMLPWPV6Fcjs.t; exports.extractFilesFromComputeResponse = _chunkMLPWPV6Fcjs.s; exports.fetchDefinitionIO = V; exports.fetchParsedDefinitionIO = q; exports.hashDefinition = F; exports.hashSolveInput = qe; exports.isDefinitionRef = _chunkMLPWPV6Fcjs.r; exports.processInput = gt; exports.readSchemaResults = Tt; exports.solveGrasshopperDefinition = k; exports.stableStringify = ce;
|
|
2
2
|
//# sourceMappingURL=grasshopper.cjs.map
|
package/dist/grasshopper.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/selva/selva/packages/compute/dist/grasshopper.cjs"],"names":[],"mappings":"AAAA,iIAAqH,wDAAgD,4rBAAyV","file":"/home/runner/work/selva/selva/packages/compute/dist/grasshopper.cjs"}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/selva/selva/packages/compute/dist/grasshopper.cjs","../src/grasshopper/server/compute-server-stats.ts"],"names":["_ComputeServerStats","serverUrl","apiKey","__publicField","validateServerUrl","headers","url","init","timeoutMs","merged","value","key","requestInit"],"mappings":"AAAA,yuBAAsJ,ICyBjIA,CAAAA,CAArB,MAAqBA,CAAmB,CAoBvC,WAAA,CAAYC,CAAAA,CAAmBC,CAAAA,CAAiB,CAnBhDC,iCAAAA,IAAA,CAAiB,WAAA,CAAA,CACjBA,iCAAAA,IAAA,CAAiB,QAAA,CAAA,CACjBA,iCAAAA,IAAA,CAAQ,UAAA,CAAW,CAAA,CAAA,CAAA,CACnBA,iCAAAA,IAAA,CAAQ,gBAAA,CAAkC,IAAI,GAAA,CAAA,CAC9CA,iCAAAA,IAAA,CAAQ,gBAAA,CAAqD,IAAI,GAAA,CAAA,CAgBhE,IAAA,CAAK,SAAA,CAAYC,iCAAAA,CAA2B,CAAA,CAC5C,IAAA,CAAK,MAAA,CAASF,CACf,CAKQ,YAAA,CAAA,CAAuC,CAC9C,IAAMG,CAAAA,CAAkC,CACvC,cAAA,CAAgB,kBACjB,CAAA,CAEA,OAAI,IAAA,CAAK,MAAA,EAAA,CACRA,CAAAA,CAAQ,eAAA,CAAqB,IAAA,CAAK,MAAA,CAAA,CAG5BA,CACR,CAMQ,gBAAA,CACPC,CAAAA,CACAC,CAAAA,CAAoB,CAAC,CAAA,CACrBC,CAAAA,CAAoBR,CAAAA,CAAmB,kBAAA,CACnB,CAOpB,IAAMS,CAAAA,CAAS,IAAI,OAAA,CAAQ,IAAA,CAAK,YAAA,CAAa,CAAC,CAAA,CAC1CF,CAAAA,CAAK,OAAA,EACR,IAAI,OAAA,CAAQA,CAAAA,CAAK,OAAO,CAAA,CAAE,OAAA,CAAQ,CAACG,CAAAA,CAAOC,CAAAA,CAAAA,EAAQ,CACjDF,CAAAA,CAAO,GAAA,CAAIE,CAAAA,CAAKD,CAAK,CACtB,CAAC,CAAA,CAEF,IAAML,CAAAA,CAAkC,CAAC,CAAA,CACzCI,CAAAA,CAAO,OAAA,CAAQ,CAACC,CAAAA,CAAOC,CAAAA,CAAAA,EAAQ,CAC9BN,CAAAA,CAAQM,CAAG,CAAA,CAAID,CAChB,CAAC,CAAA,CACD,IAAME,CAAAA,CAA2B,CAAE,GAAGL,CAAAA,CAAM,OAAA,CAAAF,CAAQ,CAAA,CACpD,OAAIG,CAAAA,CAAY,CAAA,EAAK,CAACI,CAAAA,CAAY,MAAA,EAAA,CACjCA,CAAAA,CAAY,MAAA,CAAS,WAAA,CAAY,OAAA,CAAQJ,CAAS,CAAA,CAAA,CAE5C,KAAA,CAAMF,CAAAA,CAAKM,CAAW,CAC9B,CAeA,MAAa,cAAA,CAAeJ,CAAAA,CAAoB,GAAA,CAAwB,CACvE,IAAA,CAAK,iBAAA,CAAkB,CAAA,CAMvB,IAAMF,CAAAA,CAAM,CAAA,EAAA","file":"/home/runner/work/selva/selva/packages/compute/dist/grasshopper.cjs","sourcesContent":[null,"import { ComputeError, ErrorCodes } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\nimport { validateServerUrl } from '@/core/server/validate-server-url';\n\n/**\n * ComputeServerStats provides methods to query Rhino Compute server statistics.\n *\n * @public Use this for server health monitoring and statistics.\n *\n * @example\n * ```typescript\n * const stats = new ComputeServerStats('http://localhost:6500', 'your-api-key');\n *\n * try {\n * const isOnline = await stats.isServerOnline();\n * const children = await stats.getActiveChildren();\n * const version = await stats.getVersion();\n *\n * // Or get everything at once\n * const allStats = await stats.getServerStats();\n * } finally {\n * await stats.dispose(); // Clean up resources\n * }\n * ```\n */\nexport default class ComputeServerStats {\n\tprivate readonly serverUrl: string;\n\tprivate readonly apiKey?: string;\n\tprivate disposed = false;\n\tprivate activeMonitors: Set<() => void> = new Set();\n\tprivate activeTimeouts: Set<ReturnType<typeof setTimeout>> = new Set();\n\n\t/** Timeout (ms) for the fast read/monitoring endpoints. */\n\tprivate static readonly DEFAULT_TIMEOUT_MS = 5000;\n\n\t/** Timeout (ms) for child-lifecycle POSTs — a cold Windows child can take ~30s to spawn. */\n\tprivate static readonly LIFECYCLE_TIMEOUT_MS = 60_000;\n\n\t/** Floor for `monitor()`'s `intervalMs` — anything lower hot-loops the server. */\n\tprivate static readonly MIN_MONITOR_INTERVAL_MS = 100;\n\n\t/**\n\t * @param serverUrl - Base URL of the Rhino Compute server with http:// or https:// scheme (e.g., 'http://localhost:6500')\n\t * @param apiKey - Optional API key for authentication\n\t */\n\tconstructor(serverUrl: string, apiKey?: string) {\n\t\tthis.serverUrl = validateServerUrl(serverUrl);\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Build request headers with optional API key.\n\t */\n\tprivate buildHeaders(): Record<string, string> {\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Content-Type': 'application/json'\n\t\t};\n\n\t\tif (this.apiKey) {\n\t\t\theaders['RhinoComputeKey'] = this.apiKey;\n\t\t}\n\n\t\treturn headers;\n\t}\n\n\t/**\n\t * `fetch` wrapper that aborts after `timeoutMs` so a hung connection can't stall\n\t * a probe (or the `monitor()` loop) forever. Pass `0` to disable the timeout.\n\t */\n\tprivate fetchWithTimeout(\n\t\turl: string,\n\t\tinit: RequestInit = {},\n\t\ttimeoutMs: number = ComputeServerStats.DEFAULT_TIMEOUT_MS\n\t): Promise<Response> {\n\t\t// Merge caller headers OVER the defaults instead of `{ headers: …, ...init }`,\n\t\t// where any `init.headers` would silently replace the whole set (dropping\n\t\t// the API key). BOTH sides go through `new Headers()` so every key is\n\t\t// normalized (lowercased) identically — a caller's `Content-Type` replaces\n\t\t// the default instead of coexisting as a differently-cased duplicate that\n\t\t// the runtime would combine into \"application/json, text/plain\".\n\t\tconst merged = new Headers(this.buildHeaders());\n\t\tif (init.headers) {\n\t\t\tnew Headers(init.headers).forEach((value, key) => {\n\t\t\t\tmerged.set(key, value);\n\t\t\t});\n\t\t}\n\t\tconst headers: Record<string, string> = {};\n\t\tmerged.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tconst requestInit: RequestInit = { ...init, headers };\n\t\tif (timeoutMs > 0 && !requestInit.signal) {\n\t\t\trequestInit.signal = AbortSignal.timeout(timeoutMs);\n\t\t}\n\t\treturn fetch(url, requestInit);\n\t}\n\n\t/**\n\t * Check if the server is online.\n\t *\n\t * This is a single-sample probe: it returns `true` only on a 2xx from the\n\t * proxy liveness root `/`, and `false` for every other outcome (non-2xx,\n\t * network error, or timeout). A cold or briefly-busy-but-up server can therefore\n\t * read as offline — callers that gate on this (e.g. client construction)\n\t * should retry rather than treat a single `false` as authoritative.\n\t *\n\t * @param timeoutMs - Abort the probe after this many ms (default: 5000).\n\t * Pass `0` to disable the timeout. Prevents a hung connection from\n\t * stalling the caller indefinitely.\n\t */\n\tpublic async isServerOnline(timeoutMs: number = 5000): Promise<boolean> {\n\t\tthis.ensureNotDisposed();\n\n\t\t// The rhino.compute proxy has no `/healthcheck` route; its real liveness\n\t\t// signal is `GET /`, which returns \"compute.rhino3d running\". Probing an\n\t\t// unknown path would instead be forwarded to a child and only tells us the\n\t\t// proxy can reach one.\n\t\tconst url = `${this.serverUrl}/`;\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(url, { method: 'GET' }, timeoutMs);\n\n\t\t\treturn response.ok;\n\t\t} catch (err) {\n\t\t\tgetLogger().debug('[ComputeServerStats] Fetch error:', err);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Get the number of active child processes on the server.\n\t *\n\t * By default the proxy's `/activechildren` endpoint will *spawn* children up\n\t * to the configured count if none are running, then report the count — which\n\t * wakes (and bills) an idle server. Pass `{ initialize: false }` for a passive\n\t * read that reports the current count without spawning; use this for\n\t * monitoring or before a purge/probe where you must not wake the server.\n\t *\n\t * @param options.initialize - When `false`, append `?initialize=false` so the\n\t * server reports without spawning. Defaults to `true` (the server's default).\n\t * @returns Number of active children, or null if unavailable\n\t */\n\tpublic async getActiveChildren(options: { initialize?: boolean } = {}): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\tconst { initialize = true } = options;\n\t\tconst url = initialize\n\t\t\t? `${this.serverUrl}/activechildren`\n\t\t\t: `${this.serverUrl}/activechildren?initialize=false`;\n\n\t\t// `initialize` mode may spawn children before answering — give it the\n\t\t// lifecycle budget; the passive read stays on the short default.\n\t\tconst timeoutMs = initialize\n\t\t\t? ComputeServerStats.LIFECYCLE_TIMEOUT_MS\n\t\t\t: ComputeServerStats.DEFAULT_TIMEOUT_MS;\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(url, {}, timeoutMs);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch active children:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Strict integer parse: `parseInt` would accept garbage-prefixed bodies\n\t\t\t// (e.g. an HTML error page starting \"302 Found\" → 302 children).\n\t\t\tconst text = (await response.text()).trim();\n\t\t\tif (!/^\\d+$/.test(text)) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Invalid active children response:', text);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn parseInt(text, 10);\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching active children:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get the server version information.\n\t *\n\t * @returns Version object with rhino, compute, and git_sha, or null if unavailable\n\t */\n\tpublic async getVersion(): Promise<{\n\t\trhino: string;\n\t\tcompute: string;\n\t\tgit_sha: string | null;\n\t} | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/version`);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch version:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Read body as text first, then try JSON.parse — avoids the\n\t\t\t// \"Body has already been read\" error if response.json() fails.\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn {\n\t\t\t\t\trhino: json.rhino ?? '',\n\t\t\t\t\tcompute: json.compute ?? '',\n\t\t\t\t\tgit_sha: json.git_sha ?? null\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\treturn { rhino: text, compute: '', git_sha: null };\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching version:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get the plugins installed on the server.\n\t *\n\t * Returns a `name → version` map of non-core plugins the server has loaded,\n\t * or `null` if the request failed. Pass `kind` to choose which inventory:\n\t * `'gh'` (default) lists Grasshopper add-on assemblies via\n\t * `/plugins/gh/installed`; `'rhino'` lists Rhino plugins via\n\t * `/plugins/rhino/installed`. Plugins that ship with Rhino / are core\n\t * libraries are excluded by the server.\n\t *\n\t * @param kind - `'gh'` for Grasshopper add-ons (default) or `'rhino'` for Rhino plugins.\n\t * @returns Map of plugin name to version, or `null` on failure.\n\t *\n\t * @example\n\t * ```ts\n\t * const gh = await stats.getInstalledPlugins(); // Grasshopper add-ons\n\t * const selvaVersion = gh?.['Selva'] ?? null;\n\t * ```\n\t */\n\tpublic async getInstalledPlugins(\n\t\tkind: 'gh' | 'rhino' = 'gh'\n\t): Promise<Record<string, string> | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/plugins/${kind}/installed`);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn(`[ComputeServerStats] Failed to fetch ${kind} plugins:`, response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn json && typeof json === 'object' ? (json as Record<string, string>) : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn(`[ComputeServerStats] Error fetching ${kind} plugins:`, err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get comprehensive server statistics.\n\t * Fetches all available server information in parallel.\n\t *\n\t * @returns Object containing server status and available stats\n\t */\n\tpublic async getServerStats(): Promise<{\n\t\tisOnline: boolean;\n\t\tversion?: { rhino: string; compute: string; git_sha: string | null };\n\t\tactiveChildren?: number;\n\t}> {\n\t\tthis.ensureNotDisposed();\n\n\t\tconst isOnline = await this.isServerOnline();\n\n\t\tif (!isOnline) {\n\t\t\treturn { isOnline: false };\n\t\t}\n\n\t\t// Passive child count — never spawn from a stats read, or merely viewing\n\t\t// server health would wake (and bill) an idle server.\n\t\tconst [version, activeChildren] = await Promise.all([\n\t\t\tthis.getVersion(),\n\t\t\tthis.getActiveChildren({ initialize: false })\n\t\t]);\n\n\t\treturn {\n\t\t\tisOnline: true,\n\t\t\t...(version && { version }),\n\t\t\t...(activeChildren !== null && { activeChildren })\n\t\t};\n\t}\n\n\t/**\n\t * Purge the server's solve-results / URL-data cache.\n\t *\n\t * POSTs to `cache/purge` and returns the number of entries removed, or `null`\n\t * if the request failed. This clears cached solve responses and fetched\n\t * definition-URL data; it does NOT evict the definition cache (active\n\t * `pointer` references stay valid).\n\t *\n\t * **Caveat:** `cache/purge` is forwarded by the rhino.compute proxy to a\n\t * single round-robin-selected child, so in a multi-child deployment one call\n\t * purges one child's cache. Call repeatedly (or size the pool to 1) if you\n\t * need a fleet-wide purge.\n\t *\n\t * @returns Number of entries removed, or `null` on failure.\n\t *\n\t * @example\n\t * ```ts\n\t * const removed = await stats.purgeCache();\n\t * if (removed !== null) console.log(`Purged ${removed} cached solves`);\n\t * ```\n\t */\n\tpublic async purgeCache(): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/cache/purge`, {\n\t\t\t\tmethod: 'POST'\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to purge cache:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Read text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn typeof json.purged === 'number' ? json.purged : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error purging cache:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Best-effort fleet-wide cache purge across a multi-child deployment.\n\t *\n\t * A single {@link purgeCache} POST is forwarded by the rhino.compute proxy to\n\t * just ONE round-robin-selected child, so the other children keep serving\n\t * stale cached solves. There is no proxy endpoint that addresses children\n\t * individually, so this method reads the active child count (passively, never\n\t * spawning) and fires `2 × count` sequential purges, relying on the proxy's\n\t * round-robin to spread the hits across the pool.\n\t *\n\t * **This is best-effort, not a guarantee.** Round-robin can revisit one child\n\t * and skip another; under concurrent traffic the rotation drifts. The result's\n\t * `confident` flag is `true` only when the server reports a single child (where\n\t * one purge is exact) — surface it so callers don't over-promise. For a hard\n\t * fleet-wide guarantee, run the deployment at `--childcount 1` or add a\n\t * server-side fan-out endpoint.\n\t *\n\t * @returns `{ totalPurged, calls, children, confident }`, or `null` if the\n\t * child count couldn't be read (server unreachable). `totalPurged` sums the\n\t * per-call counts; `calls` is how many purges were issued; `children` is the\n\t * reported pool size; `confident` is `true` only at a single-child pool.\n\t *\n\t * @example\n\t * ```ts\n\t * const r = await stats.purgeAllChildren();\n\t * if (r && !r.confident) {\n\t * console.warn(`Purged ~${r.totalPurged} across ${r.children} children (best-effort)`);\n\t * }\n\t * ```\n\t */\n\tpublic async purgeAllChildren(): Promise<{\n\t\ttotalPurged: number;\n\t\tcalls: number;\n\t\tchildren: number;\n\t\tconfident: boolean;\n\t} | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\t// Passive read — must not spawn children just to purge them.\n\t\tconst children = await this.getActiveChildren({ initialize: false });\n\t\tif (children === null) {\n\t\t\tgetLogger().warn('[ComputeServerStats] purgeAllChildren: could not read child count');\n\t\t\treturn null;\n\t\t}\n\n\t\tif (children === 0) {\n\t\t\t// No live children means nothing is cached on the compute side.\n\t\t\treturn { totalPurged: 0, calls: 0, children: 0, confident: true };\n\t\t}\n\n\t\t// 2× the pool size gives round-robin a strong chance of reaching every\n\t\t// child at least once. Sequential (not parallel) so the proxy advances its\n\t\t// round-robin cursor one child per call rather than racing them onto one.\n\t\tconst calls = children * 2;\n\t\tlet totalPurged = 0;\n\t\tfor (let i = 0; i < calls; i++) {\n\t\t\tconst purged = await this.purgeCache();\n\t\t\tif (purged !== null) totalPurged += purged;\n\t\t}\n\n\t\treturn { totalPurged, calls, children, confident: children === 1 };\n\t}\n\n\t/**\n\t * Get the server's current UTC clock.\n\t *\n\t * GETs `/servertime`, which the server emits as a JSON-encoded ISO-8601\n\t * timestamp (e.g. `\"2026-06-18T08:30:00Z\"`). Useful for detecting clock skew\n\t * between caller and server. Returns `null` if the request failed or the body\n\t * isn't a parseable date.\n\t *\n\t * @returns A `Date` for the server's UTC time, or `null` on failure.\n\t */\n\tpublic async getServerTime(): Promise<Date | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/servertime`);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch server time:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Body is a JSON string (\"2026-…Z\"); strip surrounding quotes if present.\n\t\t\tconst text = (await response.text()).trim().replace(/^\"|\"$/g, '');\n\t\t\tconst date = new Date(text);\n\t\t\treturn isNaN(date.getTime()) ? null : date;\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching server time:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get how long the rhino.compute proxy has been idle.\n\t *\n\t * GETs `/idlespan` on the proxy, which returns the seconds elapsed since the\n\t * last request was forwarded to a compute child. This is a proxy-level metric\n\t * (not proxied to a child) used by autoscalers to decide when a node can be\n\t * drained. Returns `null` if unavailable.\n\t *\n\t * @returns Idle time in seconds, or `null` on failure.\n\t */\n\tpublic async getIdleSpan(): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/idlespan`);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch idle span:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst seconds = parseFloat((await response.text()).trim());\n\t\t\treturn isNaN(seconds) ? null : seconds;\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching idle span:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Fill the compute child pool up to the server's configured baseline.\n\t *\n\t * POSTs `/launch-children`. No-op when the pool is already at or above the\n\t * configured `--childcount`. Returns `{ spawned, active }` — how many children\n\t * were started and the resulting child count — or `null` on failure.\n\t *\n\t * To raise capacity above the baseline use {@link launchChild}; the baseline\n\t * itself can only be changed by restarting rhino.compute.\n\t *\n\t * @returns `{ spawned, active }`, or `null` on failure.\n\t */\n\tpublic async launchChildren(): Promise<{ spawned: number[]; active: number } | null> {\n\t\treturn this.postJson('/launch-children');\n\t}\n\n\t/**\n\t * Add a single compute child to the pool, optionally on a specific port.\n\t *\n\t * POSTs `/launch-child` (with `?port=N` when `port` is given). Unlike\n\t * {@link launchChildren}, this can push the pool above the baseline, up to the\n\t * server's `MaxChildren` cap. Returns `{ spawned: [port] }` on success, or\n\t * `null` on failure (server replies 400 for a bad port, 409 if the port is in\n\t * use, 503 at the max-children cap).\n\t *\n\t * @param port - Optional specific port to launch on; otherwise the next free one.\n\t * @returns `{ spawned }` listing the launched port, or `null` on failure.\n\t */\n\tpublic async launchChild(port?: number): Promise<{ spawned: number[] } | null> {\n\t\tconst path = port === undefined ? '/launch-child' : `/launch-child?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * Gracefully shut down compute children without respawning them.\n\t *\n\t * POSTs `/shutdown-children`. With no `port` it shuts down every child; with\n\t * `port` it targets just that one. Children do not respawn, but the next\n\t * `/grasshopper` request auto-spawns the pool back to the baseline. Returns\n\t * `{ shutdown, active }` — how many were stopped and the remaining count — or\n\t * `null` on failure.\n\t *\n\t * @param port - Optional port to target; omit to shut down all children.\n\t * @returns `{ shutdown, active }`, or `null` on failure.\n\t */\n\tpublic async shutdownChildren(\n\t\tport?: number\n\t): Promise<{ shutdown: number; active: number } | null> {\n\t\tconst path = port === undefined ? '/shutdown-children' : `/shutdown-children?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * Shut down compute children and respawn replacements (rolling restart).\n\t *\n\t * POSTs `/recycle-children`. With no `port` it recycles every child; with\n\t * `port` it recycles just that one. The server recycles sequentially (each\n\t * replacement is serving before the next child is stopped) so the pool never\n\t * drops to zero mid-recycle. Returns `{ shutdown, spawned, active }`, or\n\t * `null` on failure.\n\t *\n\t * @param port - Optional port to target; omit to recycle all children.\n\t * @returns `{ shutdown, spawned, active }`, or `null` on failure.\n\t */\n\tpublic async recycleChildren(\n\t\tport?: number\n\t): Promise<{ shutdown: number; spawned: number[]; active: number } | null> {\n\t\tconst path = port === undefined ? '/recycle-children' : `/recycle-children?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * POST a control endpoint that replies with a JSON object and return it,\n\t * degrading to `null` on any non-2xx, non-JSON, or network failure. Shared by\n\t * the child-lifecycle methods so their failure semantics stay identical. Uses\n\t * the longer lifecycle timeout since a spawn/recycle can take ~30s.\n\t */\n\tprivate async postJson<T>(path: string): Promise<T | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(\n\t\t\t\t`${this.serverUrl}${path}`,\n\t\t\t\t{ method: 'POST' },\n\t\t\t\tComputeServerStats.LIFECYCLE_TIMEOUT_MS\n\t\t\t);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn(`[ComputeServerStats] POST ${path} failed:`, response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(text) as T;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn(`[ComputeServerStats] Error on POST ${path}:`, err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Continuously monitor server stats at specified interval.\n\t *\n\t * @param callback - Function called with stats on each interval\n\t * @param intervalMs - Milliseconds between checks (default: 5000). Must be a\n\t * finite number of at least 100 ms — lower values would hot-loop the server.\n\t * @returns Function to stop monitoring\n\t * @throws {RangeError} If `intervalMs` is not a finite number >= 100.\n\t *\n\t * @example\n\t * ```typescript\n\t * const stopMonitoring = stats.monitor((data) => {\n\t * console.log('Server stats:', data);\n\t * }, 3000);\n\t *\n\t * // Later...\n\t * stopMonitoring();\n\t * ```\n\t */\n\tpublic monitor(\n\t\tcallback: (stats: Awaited<ReturnType<typeof this.getServerStats>>) => void,\n\t\tintervalMs: number = 5000\n\t): () => void {\n\t\tthis.ensureNotDisposed();\n\n\t\tif (!Number.isFinite(intervalMs) || intervalMs < ComputeServerStats.MIN_MONITOR_INTERVAL_MS) {\n\t\t\tthrow new RangeError(\n\t\t\t\t`monitor() intervalMs must be a finite number >= ${ComputeServerStats.MIN_MONITOR_INTERVAL_MS}ms, ` +\n\t\t\t\t\t`got ${intervalMs}. Sub-${ComputeServerStats.MIN_MONITOR_INTERVAL_MS}ms polling would hot-loop the server.`\n\t\t\t);\n\t\t}\n\n\t\tlet active = true;\n\t\tlet currentTimeoutId: ReturnType<typeof setTimeout> | null = null;\n\n\t\tgetLogger().info(`🔄 Starting server stats monitoring every ${intervalMs}ms`);\n\n\t\tconst check = async () => {\n\t\t\t// Clear current timeout from tracking since it has fired\n\t\t\tif (currentTimeoutId !== null) {\n\t\t\t\tthis.activeTimeouts.delete(currentTimeoutId);\n\t\t\t\tcurrentTimeoutId = null;\n\t\t\t}\n\n\t\t\tif (!active || this.disposed) return;\n\n\t\t\ttry {\n\t\t\t\tconst _stats = await this.getServerStats();\n\n\t\t\t\t// Check again after async operation to prevent race condition\n\t\t\t\tif (!active || this.disposed) return;\n\n\t\t\t\ttry {\n\t\t\t\t\tcallback(_stats);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tgetLogger().error('[ComputeServerStats] Monitor callback threw:', err);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (!active || this.disposed) {\n\t\t\t\t\t// dispose()/stop racing an in-flight poll is normal shutdown, not an\n\t\t\t\t\t// error — the nested ensureNotDisposed() throw is expected here.\n\t\t\t\t\tgetLogger().debug('[ComputeServerStats] Monitor poll cancelled during shutdown:', err);\n\t\t\t\t} else {\n\t\t\t\t\tgetLogger().error('[ComputeServerStats] Failed to fetch stats during monitor:', err);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (active && !this.disposed) {\n\t\t\t\tcurrentTimeoutId = setTimeout(() => void check(), intervalMs);\n\t\t\t\tthis.activeTimeouts.add(currentTimeoutId);\n\t\t\t}\n\t\t};\n\n\t\tconst stopMonitoring = () => {\n\t\t\tactive = false;\n\n\t\t\t// Clear any pending timeout\n\t\t\tif (currentTimeoutId !== null) {\n\t\t\t\tclearTimeout(currentTimeoutId);\n\t\t\t\tthis.activeTimeouts.delete(currentTimeoutId);\n\t\t\t\tcurrentTimeoutId = null;\n\t\t\t}\n\n\t\t\tthis.activeMonitors.delete(stopMonitoring);\n\t\t};\n\n\t\tthis.activeMonitors.add(stopMonitoring);\n\n\t\t// Explicitly mark as fire-and-forget since we don't need to await the initial call\n\t\tvoid check();\n\n\t\treturn stopMonitoring;\n\t}\n\n\t/**\n\t * Disposes of all resources and stops all active monitors.\n\t * Call this when you're done using the stats instance.\n\t */\n\tpublic async dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\n\t\t// Stop all active monitors (this will also clear their timeouts)\n\t\tfor (const stopMonitor of this.activeMonitors) {\n\t\t\tstopMonitor();\n\t\t}\n\t\tthis.activeMonitors.clear();\n\n\t\t// Clear any remaining timeouts (defensive cleanup)\n\t\tfor (const timeoutId of this.activeTimeouts) {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t\tthis.activeTimeouts.clear();\n\t}\n\n\t/**\n\t * Ensures the instance hasn't been disposed.\n\t */\n\tprivate ensureNotDisposed(): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t'ComputeServerStats has been disposed and cannot be used',\n\t\t\t\tErrorCodes.INVALID_STATE,\n\t\t\t\t{ context: { disposed: this.disposed } }\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|