@selvajs/compute 4.0.0-beta.4 → 4.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core-CEa0SxYF.cjs +3 -0
- package/dist/core-CEa0SxYF.cjs.map +1 -0
- package/dist/core-DONB46aK.js +3 -0
- package/dist/core-DONB46aK.js.map +1 -0
- package/dist/core.cjs +1 -2
- package/dist/core.d.cts +2 -219
- package/dist/core.d.ts +2 -219
- package/dist/core.js +1 -2
- package/dist/grasshopper.cjs +1 -1
- package/dist/grasshopper.cjs.map +1 -1
- package/dist/grasshopper.d.cts +1214 -1220
- package/dist/grasshopper.d.ts +1214 -1220
- package/dist/grasshopper.js +1 -1
- package/dist/grasshopper.js.map +1 -1
- package/dist/index-BNsid0_9.d.cts +458 -0
- package/dist/index-BNsid0_9.d.ts +458 -0
- package/dist/index.cjs +0 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -1
- package/package.json +12 -10
- package/dist/chunk-AYH5O3VG.js +0 -4
- package/dist/chunk-AYH5O3VG.js.map +0 -1
- package/dist/chunk-MLPWPV6F.cjs +0 -4
- package/dist/chunk-MLPWPV6F.cjs.map +0 -1
- package/dist/core.cjs.map +0 -1
- package/dist/core.js.map +0 -1
- package/dist/handle-files-DMK9bCOW.d.cts +0 -231
- package/dist/handle-files-DMK9bCOW.d.ts +0 -231
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
//#region src/core/errors.d.ts
|
|
2
|
+
declare const ErrorCodes: {
|
|
3
|
+
readonly NETWORK_ERROR: "NETWORK_ERROR";
|
|
4
|
+
readonly AUTH_ERROR: "AUTH_ERROR";
|
|
5
|
+
readonly VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
6
|
+
readonly COMPUTATION_ERROR: "COMPUTATION_ERROR";
|
|
7
|
+
readonly TIMEOUT_ERROR: "TIMEOUT_ERROR";
|
|
8
|
+
readonly CORS_ERROR: "CORS_ERROR";
|
|
9
|
+
/** HTTP 404 — the endpoint path does not exist on the server (usually a typo'd route or wrong server). Never retried. */
|
|
10
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
11
|
+
/** HTTP 429 — the server is rate-limiting requests. Retried by default (honors `Retry-After`); see `RetryPolicy.retryOn429`. */
|
|
12
|
+
readonly RATE_LIMIT: "RATE_LIMIT";
|
|
13
|
+
/**
|
|
14
|
+
* The server answered 2xx but the body is deterministically not JSON (the
|
|
15
|
+
* response declared a non-JSON `Content-Type`, e.g. an HTML captive-portal or
|
|
16
|
+
* proxy login page, or a misconfigured endpoint). Never retried — refetching
|
|
17
|
+
* returns the same page.
|
|
18
|
+
*/
|
|
19
|
+
readonly INVALID_RESPONSE: "INVALID_RESPONSE";
|
|
20
|
+
readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
|
|
21
|
+
readonly INVALID_STATE: "INVALID_STATE";
|
|
22
|
+
readonly INVALID_INPUT: "INVALID_INPUT";
|
|
23
|
+
readonly INVALID_CONFIG: "INVALID_CONFIG";
|
|
24
|
+
readonly BROWSER_ONLY: "BROWSER_ONLY";
|
|
25
|
+
readonly ENVIRONMENT_ERROR: "ENVIRONMENT_ERROR";
|
|
26
|
+
readonly ENCODING_ERROR: "ENCODING_ERROR";
|
|
27
|
+
/** An input's `default` had a shape the normalizer didn't recognize (no innerTree key). */
|
|
28
|
+
readonly MALFORMED_DEFAULT: "MALFORMED_DEFAULT";
|
|
29
|
+
/** Scheduler latest-wins: this call was replaced by a newer one. */
|
|
30
|
+
readonly SUPERSEDED: "SUPERSEDED";
|
|
31
|
+
/** Scheduler / caller-supplied AbortSignal: this call was aborted. */
|
|
32
|
+
readonly ABORTED: "ABORTED";
|
|
33
|
+
/**
|
|
34
|
+
* Scheduler backpressure: the queue was already at `maxQueueDepth` when this
|
|
35
|
+
* call arrived, so it was rejected immediately rather than queued. Retryable — the
|
|
36
|
+
* caller (or an HTTP layer, as 503 + Retry-After) should back off and retry.
|
|
37
|
+
* The error `context` carries `{ queueDepth, maxQueueDepth }`.
|
|
38
|
+
*/
|
|
39
|
+
readonly QUEUE_FULL: "QUEUE_FULL";
|
|
40
|
+
/**
|
|
41
|
+
* Scheduler backpressure: this call sat queued longer than `queueWaitMs`
|
|
42
|
+
* without starting, so it was rejected before wasting compute on a stale request.
|
|
43
|
+
* Retryable. The error `context` carries `{ waitedMs, queueWaitMs }`.
|
|
44
|
+
*/
|
|
45
|
+
readonly QUEUE_TIMEOUT: "QUEUE_TIMEOUT";
|
|
46
|
+
/**
|
|
47
|
+
* A solve referenced a definition by `pointer` (server-side cache key), but the
|
|
48
|
+
* server no longer holds that definition (evicted / GC'd / a different child in
|
|
49
|
+
* the pool / server restarted). The caller should retry with the full
|
|
50
|
+
* definition. Surfaced when the server tags its error body with
|
|
51
|
+
* `code: "definition_not_cached"`, so it survives production message-scrubbing.
|
|
52
|
+
*/
|
|
53
|
+
readonly DEFINITION_NOT_CACHED: "DEFINITION_NOT_CACHED";
|
|
54
|
+
};
|
|
55
|
+
type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
|
56
|
+
/**
|
|
57
|
+
* Simplified error for Rhino Compute operations
|
|
58
|
+
*
|
|
59
|
+
* @public Use this for error handling with error codes and context.
|
|
60
|
+
*/
|
|
61
|
+
declare class ComputeError extends Error {
|
|
62
|
+
readonly code: ErrorCode;
|
|
63
|
+
readonly statusCode?: number;
|
|
64
|
+
readonly context?: Record<string, unknown>;
|
|
65
|
+
readonly originalError?: Error;
|
|
66
|
+
constructor(message: string, code?: ErrorCode, options?: {
|
|
67
|
+
statusCode?: number;
|
|
68
|
+
context?: Record<string, unknown>;
|
|
69
|
+
originalError?: Error;
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Create an error for missing/empty values
|
|
73
|
+
*/
|
|
74
|
+
static missingValues(inputName: string, expectedType?: string, context?: Record<string, unknown>): ComputeError;
|
|
75
|
+
/**
|
|
76
|
+
* Create an error for unknown parameter type
|
|
77
|
+
*/
|
|
78
|
+
static unknownParamType(paramType: string, paramName?: string, context?: Record<string, unknown>): ComputeError;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/core/types.d.ts
|
|
82
|
+
/**
|
|
83
|
+
* Backend wire code (the JSON error body's `code` field) → one of our
|
|
84
|
+
* {@link ErrorCodes}. An unlisted code falls back to the status-based mapping.
|
|
85
|
+
*/
|
|
86
|
+
type ServerErrorCodeMap = Record<string, ErrorCode>;
|
|
87
|
+
interface RetryPolicy {
|
|
88
|
+
/** Maximum number of retry attempts after the initial request (default: 0). */
|
|
89
|
+
attempts?: number;
|
|
90
|
+
/** Base delay in milliseconds for exponential backoff (default: 500). */
|
|
91
|
+
baseDelayMs?: number;
|
|
92
|
+
/** Upper bound for backoff delay (default: 30_000). */
|
|
93
|
+
maxDelayMs?: number;
|
|
94
|
+
/** Whether to retry on 429 responses (default: true — honors Retry-After). */
|
|
95
|
+
retryOn429?: boolean;
|
|
96
|
+
}
|
|
97
|
+
interface ComputeConfig {
|
|
98
|
+
serverUrl: string;
|
|
99
|
+
/** Optional API key for authenticating with the server. Sent as {@link apiKeyHeader}. */
|
|
100
|
+
apiKey?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Header name carrying `apiKey`. Defaults to `'RhinoComputeKey'` — the name
|
|
103
|
+
* rhino.compute expects. A different backend sets its own.
|
|
104
|
+
*/
|
|
105
|
+
apiKeyHeader?: string;
|
|
106
|
+
/** Optional Bearer token for authentication (e.g., when behind a proxy or API gateway) */
|
|
107
|
+
authToken?: string;
|
|
108
|
+
headers?: Record<string, string>;
|
|
109
|
+
/** Enable debug logging to the console */
|
|
110
|
+
debug?: boolean;
|
|
111
|
+
/** Suppress browser security warnings in the console */
|
|
112
|
+
suppressBrowserWarning?: boolean;
|
|
113
|
+
timeoutMs?: number;
|
|
114
|
+
/**
|
|
115
|
+
* Retry policy for transient errors. Default: no retries.
|
|
116
|
+
*/
|
|
117
|
+
retry?: RetryPolicy;
|
|
118
|
+
/**
|
|
119
|
+
* Optional caller-supplied AbortSignal. Composes with the internal timeout —
|
|
120
|
+
* whichever fires first wins. Lets callers cancel in-flight requests
|
|
121
|
+
* (e.g. on component unmount or when superseding a stale solve).
|
|
122
|
+
*/
|
|
123
|
+
signal?: AbortSignal;
|
|
124
|
+
/**
|
|
125
|
+
* Machine-readable error codes this backend tags onto its error bodies, mapped
|
|
126
|
+
* to our {@link ErrorCodes}. A code here outranks the status-based mapping.
|
|
127
|
+
* Core knows no backend's codes — the client supplies its own table.
|
|
128
|
+
*/
|
|
129
|
+
serverErrorCodes?: ServerErrorCodeMap;
|
|
130
|
+
onServerTiming?: (timing: ServerTiming, requestId: string) => void;
|
|
131
|
+
}
|
|
132
|
+
interface ServerTiming {
|
|
133
|
+
decode?: number;
|
|
134
|
+
solve?: number;
|
|
135
|
+
encode?: number;
|
|
136
|
+
raw: string;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/core/compute-fetch/compute-fetch.d.ts
|
|
140
|
+
/**
|
|
141
|
+
* Generic Rhino Compute fetch function.
|
|
142
|
+
* Sends a POST request to any Compute endpoint with pre-prepared arguments.
|
|
143
|
+
*
|
|
144
|
+
* Use this for advanced, low-level control over compute requests. For most use cases, prefer higher-level APIs.
|
|
145
|
+
*
|
|
146
|
+
* The transport is response-type-agnostic: it does not know which response a
|
|
147
|
+
* given endpoint returns. Callers supply the response type via `R` (defaulting
|
|
148
|
+
* to `unknown`, which forces an explicit narrowing before use).
|
|
149
|
+
*
|
|
150
|
+
* Timeout semantics: `config.timeoutMs` is a PER-ATTEMPT timeout, re-armed for
|
|
151
|
+
* every retry. With `retry: { attempts: N }` the worst-case wall clock is
|
|
152
|
+
* `(N + 1) × timeoutMs` plus backoff sleeps (and a server `Retry-After` can
|
|
153
|
+
* stretch a single sleep up to 60s). For a hard overall deadline, pass
|
|
154
|
+
* `config.signal` (e.g. `AbortSignal.timeout(totalMs)`) — a caller abort wins
|
|
155
|
+
* immediately, including during backoff.
|
|
156
|
+
*
|
|
157
|
+
* Retry caveats: requests are POSTs, so a retry after a mid-flight connection
|
|
158
|
+
* loss may re-execute a request the server already ran (see {@link RetryPolicy}).
|
|
159
|
+
* A 2xx response that declares a non-JSON `Content-Type` (captive portal,
|
|
160
|
+
* proxy login page) fails immediately with `INVALID_RESPONSE` and is never
|
|
161
|
+
* retried; a body that fails to parse under a JSON content-type is treated as
|
|
162
|
+
* a truncated stream and is retried.
|
|
163
|
+
*
|
|
164
|
+
* @typeParam R - The expected response shape. The caller names it at the call site.
|
|
165
|
+
* @param endpoint - The Compute API endpoint (e.g., 'grasshopper', 'io', 'mesh').
|
|
166
|
+
* @param args - Pre-prepared arguments for the request body.
|
|
167
|
+
* @param config - Compute configuration (server URL, API key, timeout, debug, retry, signal).
|
|
168
|
+
* @returns The parsed JSON response from the server, typed as `R`.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* // Basic usage for the Grasshopper endpoint:
|
|
172
|
+
* const response = await fetchCompute(
|
|
173
|
+
* 'grasshopper',
|
|
174
|
+
* { ... },
|
|
175
|
+
* {
|
|
176
|
+
* serverUrl: 'https://my-server.com',
|
|
177
|
+
* debug: true,
|
|
178
|
+
* timeoutMs: 30_000,
|
|
179
|
+
* retry: { attempts: 2 },
|
|
180
|
+
* signal: controller.signal,
|
|
181
|
+
* }
|
|
182
|
+
* );
|
|
183
|
+
*/
|
|
184
|
+
declare function fetchCompute<R = unknown>(endpoint: string, args: Record<string, any>, config: ComputeConfig): Promise<R>;
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/core/compute-fetch/wire-size.d.ts
|
|
187
|
+
/**
|
|
188
|
+
* Side-channel carrying a response's wire size (its JSON text length) alongside
|
|
189
|
+
* the parsed object, so downstream caches can budget by bytes without
|
|
190
|
+
* re-serializing (audit C2/C3 — the response tree can be hundreds of MB, and
|
|
191
|
+
* every extra `JSON.stringify` pass over it is a real cost).
|
|
192
|
+
*
|
|
193
|
+
* A `WeakMap` rather than a property on the response: the response type is the
|
|
194
|
+
* server's schema, and an extra enumerable field would leak into every
|
|
195
|
+
* `JSON.stringify(response)` on the way back out to clients. Derived copies
|
|
196
|
+
* (e.g. the `algo`-stripped shallow copy in `runSolve`) must re-register — the
|
|
197
|
+
* hint follows object identity, not content.
|
|
198
|
+
*
|
|
199
|
+
* The size is `text.length` (UTF-16 code units), not strict UTF-8 bytes —
|
|
200
|
+
* compute responses are ASCII-dominated JSON (base64 + numerals), so the two
|
|
201
|
+
* are interchangeable for budgeting purposes and `.length` is free.
|
|
202
|
+
*/
|
|
203
|
+
/** Record `response`'s wire size. No-op for non-object/null values. */
|
|
204
|
+
declare function setResponseWireSize(response: unknown, size: number): void;
|
|
205
|
+
/** The wire size recorded for `response`, or undefined when never registered. */
|
|
206
|
+
declare function getResponseWireSize(response: unknown): number | undefined;
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/core/utils/logger.d.ts
|
|
209
|
+
/**
|
|
210
|
+
* Logger interface for structured logging
|
|
211
|
+
*
|
|
212
|
+
* @public Implement this interface to provide custom logging behavior.
|
|
213
|
+
*/
|
|
214
|
+
interface Logger {
|
|
215
|
+
debug(message: string, ...args: unknown[]): void;
|
|
216
|
+
info(message: string, ...args: unknown[]): void;
|
|
217
|
+
warn(message: string, ...args: unknown[]): void;
|
|
218
|
+
error(message: string, ...args: unknown[]): void;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get the current logger instance
|
|
222
|
+
*
|
|
223
|
+
* @returns The current logger instance
|
|
224
|
+
*/
|
|
225
|
+
declare function getLogger(): Logger;
|
|
226
|
+
/**
|
|
227
|
+
* Set a custom logger instance
|
|
228
|
+
*
|
|
229
|
+
* @public Use this to configure custom logging behavior.
|
|
230
|
+
*
|
|
231
|
+
* @param logger - Custom logger implementation or null to disable logging
|
|
232
|
+
* @throws {ComputeError} `INVALID_CONFIG` if the logger is missing any of
|
|
233
|
+
* the four required methods — failing here beats a confusing
|
|
234
|
+
* "getLogger().debug is not a function" at some later, unrelated call site.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* import { setLogger } from '@selvajs/compute/core';
|
|
239
|
+
*
|
|
240
|
+
* // Enable console logging
|
|
241
|
+
* setLogger(console);
|
|
242
|
+
*
|
|
243
|
+
* // Use a custom logger
|
|
244
|
+
* setLogger({
|
|
245
|
+
* debug: (msg, ...args) => myLogger.debug(msg, ...args),
|
|
246
|
+
* info: (msg, ...args) => myLogger.info(msg, ...args),
|
|
247
|
+
* warn: (msg, ...args) => myLogger.warn(msg, ...args),
|
|
248
|
+
* error: (msg, ...args) => myLogger.error(msg, ...args)
|
|
249
|
+
* });
|
|
250
|
+
*
|
|
251
|
+
* // Disable logging
|
|
252
|
+
* setLogger(null);
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
declare function setLogger(logger: Logger | Console | null): void;
|
|
256
|
+
/**
|
|
257
|
+
* Enable debug logging to console
|
|
258
|
+
*
|
|
259
|
+
* @public Convenience method to enable console logging.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* import { enableDebugLogging } from '@selvajs/compute/core';
|
|
264
|
+
*
|
|
265
|
+
* enableDebugLogging();
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
declare function enableDebugLogging(): void;
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/core/utils/read-field.d.ts
|
|
271
|
+
/**
|
|
272
|
+
* Case-insensitive single-key reader for wire payloads.
|
|
273
|
+
*
|
|
274
|
+
* The Rhino Compute family serializes the same logical field with different
|
|
275
|
+
* casing depending on the server branch:
|
|
276
|
+
*
|
|
277
|
+
* - mcneel 8.x / 9.x: the IO schema is PascalCase (`ParamType`, `Default`,
|
|
278
|
+
* `InnerTree`, …) because those C# classes carry no `[JsonProperty]`.
|
|
279
|
+
* - VektorNode Compute8: the IO schema is camelCase (`paramType`, `default`,
|
|
280
|
+
* …) because the fork added `[JsonProperty("camelCase")]`, BUT the nested
|
|
281
|
+
* `default` DataTree wrapper stays PascalCase (`ParamName` / `InnerTree`)
|
|
282
|
+
* since `Resthopper.IO.DataTree` is an external type the fork can't attribute.
|
|
283
|
+
*
|
|
284
|
+
* So a single response can mix casings, and which casing a given field uses
|
|
285
|
+
* depends on the server branch. Rather than deep-camelCasing the whole payload
|
|
286
|
+
* (the old `camelcaseKeys` approach — which corrupted user-authored value-list
|
|
287
|
+
* label keys and item `data` JSON), read the specific fields we care about
|
|
288
|
+
* case-insensitively and leave everything else verbatim.
|
|
289
|
+
*
|
|
290
|
+
* Prefers an exact-case match when present, then falls back to the first
|
|
291
|
+
* case-insensitive match. Returns `undefined` when no key matches.
|
|
292
|
+
*
|
|
293
|
+
* @param obj - The source object (any non-object input yields `undefined`).
|
|
294
|
+
* @param name - The logical field name, in any casing.
|
|
295
|
+
*/
|
|
296
|
+
declare function readField<T = unknown>(obj: unknown, name: string): T | undefined;
|
|
297
|
+
/**
|
|
298
|
+
* True when `obj` has a key matching `name` (case-insensitively). Distinguishes
|
|
299
|
+
* "field present but value is null/undefined" from "field absent" — needed where
|
|
300
|
+
* presence itself carries meaning (e.g. an `innerTree` that exists but is empty).
|
|
301
|
+
*/
|
|
302
|
+
declare function hasField(obj: unknown, name: string): boolean;
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region src/core/utils/encoding.d.ts
|
|
305
|
+
/**
|
|
306
|
+
* Decodes a base64 string to binary data (Uint8Array).
|
|
307
|
+
* Normalizes and validates input per WHATWG forgiving-base64 so both runtimes fail consistently.
|
|
308
|
+
*
|
|
309
|
+
* @param base64File - Base64 encoded string
|
|
310
|
+
* @returns Decoded binary data as Uint8Array
|
|
311
|
+
* @throws {ComputeError} `ENCODING_ERROR` if invalid, or `INVALID_STATE` if decoder unavailable
|
|
312
|
+
*/
|
|
313
|
+
declare function decodeBase64ToBinary(base64File: string): Uint8Array;
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/core/definition-ref.d.ts
|
|
316
|
+
/**
|
|
317
|
+
* By-reference definition form for solves.
|
|
318
|
+
*
|
|
319
|
+
* Lets a caller that already knows a definition's identity (e.g. a stored
|
|
320
|
+
* version's UUID) schedule solves without materializing the multi-MB bytes:
|
|
321
|
+
* cache keys and the server-pointer map are derived from `key` alone, and
|
|
322
|
+
* `load()` is only called when an upload is genuinely unavoidable (first solve
|
|
323
|
+
* of a definition, or a server-side pointer miss).
|
|
324
|
+
*/
|
|
325
|
+
/**
|
|
326
|
+
* A definition identified by a stable key, with bytes materialized on demand.
|
|
327
|
+
*
|
|
328
|
+
* **Immutability contract — read this before constructing one.** `key` must
|
|
329
|
+
* identify IMMUTABLE bytes: every `load()` for a given `key` must return the
|
|
330
|
+
* same content, forever. All caching (the scheduler's result cache, the
|
|
331
|
+
* server-pointer map, and any durable cache built on these keys) trusts the
|
|
332
|
+
* key as the definition's identity WITHOUT looking at the bytes. If two
|
|
333
|
+
* different byte contents ever share a key, cached solves from one are served
|
|
334
|
+
* for the other — silent cache poisoning with no diagnostic. Use an identity
|
|
335
|
+
* that can never be reused for different content (e.g. a version UUID), never
|
|
336
|
+
* a mutable name or path.
|
|
337
|
+
*/
|
|
338
|
+
interface DefinitionRef {
|
|
339
|
+
/**
|
|
340
|
+
* Identity of immutable bytes (e.g. a version UUID). Two different byte
|
|
341
|
+
* contents must never share a key — cache poisoning otherwise.
|
|
342
|
+
*/
|
|
343
|
+
key: string;
|
|
344
|
+
/**
|
|
345
|
+
* Materialize the bytes. Called ONLY when an upload is unavoidable — inside
|
|
346
|
+
* the solve execution, so it counts toward the solve's abort semantics.
|
|
347
|
+
*/
|
|
348
|
+
load: () => Promise<Uint8Array>;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Every definition form accepted by the solve entry points:
|
|
352
|
+
* - a URL, base64 string, or plain string (content-hashed for caching)
|
|
353
|
+
* - raw `.gh` bytes (content-hashed)
|
|
354
|
+
* - a {@link DefinitionRef} (identity-keyed; bytes loaded lazily)
|
|
355
|
+
*/
|
|
356
|
+
type SolveDefinition = string | Uint8Array | DefinitionRef;
|
|
357
|
+
/** Narrow a {@link SolveDefinition} to the by-reference form. */
|
|
358
|
+
declare function isDefinitionRef(definition: SolveDefinition): definition is DefinitionRef;
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/core/server/validate-server-url.d.ts
|
|
361
|
+
/**
|
|
362
|
+
* The public McNeel endpoint's host — the default blocked host; users must point at
|
|
363
|
+
* their own server. Compared against the parsed hostname lowercased and with any
|
|
364
|
+
* trailing dot stripped, so the FQDN form (`compute.rhino3d.com.`) can't bypass it.
|
|
365
|
+
* Known limitation: the endpoint's raw IP is not blocked — it sits behind a load
|
|
366
|
+
* balancer with no single stable, verifiable address to pin.
|
|
367
|
+
*/
|
|
368
|
+
declare const DEFAULT_BLOCKED_HOST = "compute.rhino3d.com";
|
|
369
|
+
interface ValidateServerUrlOptions {
|
|
370
|
+
/**
|
|
371
|
+
* Hostnames rejected as a `serverUrl` — a backend's shared public endpoint,
|
|
372
|
+
* which callers must not point at. Defaults to `[DEFAULT_BLOCKED_HOST]`; pass
|
|
373
|
+
* `[]` to block nothing. Compared lowercased with any trailing dot stripped.
|
|
374
|
+
*/
|
|
375
|
+
blockedHosts?: readonly string[];
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Validate and normalize a compute `serverUrl`.
|
|
379
|
+
*
|
|
380
|
+
* This is the single source of truth for "is this a usable server URL?" — both
|
|
381
|
+
* `GrasshopperClient` (via `normalizeComputeConfig`) and the standalone-exported
|
|
382
|
+
* `ComputeServerStats` constructor delegate here, so a given URL is accepted or
|
|
383
|
+
* rejected identically no matter which entry point a caller uses.
|
|
384
|
+
*
|
|
385
|
+
* Rules (all enforced, on the *trimmed* input — the trimmed form is what's
|
|
386
|
+
* returned, so no stray whitespace survives into later `fetch` calls):
|
|
387
|
+
* - non-empty (after trim)
|
|
388
|
+
* - `http://` or `https://` scheme (case-insensitive, per RFC 3986)
|
|
389
|
+
* - parseable by `new URL()`
|
|
390
|
+
* - no embedded credentials (`http://user:pass@host`) — `fetch`/`new Request`
|
|
391
|
+
* reject credentialed URLs at runtime, so they must fail here instead
|
|
392
|
+
* - no query string or fragment — endpoint paths are appended to this URL
|
|
393
|
+
* (`${serverUrl}/version`), which a `?…` or `#…` suffix would corrupt
|
|
394
|
+
* - not a blocked host (by default the public McNeel endpoint) — compared by
|
|
395
|
+
* parsed hostname (lowercased, trailing dot stripped), so scheme, casing, port,
|
|
396
|
+
* path, trailing-slash, or FQDN-dot variants can't slip past the block
|
|
397
|
+
*
|
|
398
|
+
* @param raw - The candidate server URL.
|
|
399
|
+
* @param options - Override the blocked-host list for a non-Rhino backend.
|
|
400
|
+
* @returns The trimmed, normalized URL with any trailing slashes removed.
|
|
401
|
+
* @throws {ComputeError} `INVALID_CONFIG` if any rule fails.
|
|
402
|
+
*/
|
|
403
|
+
declare function validateServerUrl(raw: string, options?: ValidateServerUrlOptions): string;
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/core/files/types.d.ts
|
|
406
|
+
/**
|
|
407
|
+
* Raw file data from Grasshopper/Rhino Compute response, with metadata for processing.
|
|
408
|
+
* Files are typically combined with additional files and packaged into a ZIP archive.
|
|
409
|
+
* @see {@link extractFilesFromComputeResponse} for extraction from compute responses
|
|
410
|
+
*/
|
|
411
|
+
type FileData = {
|
|
412
|
+
/** Base filename without extension (e.g., "model") */
|
|
413
|
+
fileName: string;
|
|
414
|
+
/** File content, base64-encoded or plain string depending on isBase64Encoded flag */
|
|
415
|
+
data: string;
|
|
416
|
+
/** File extension including the dot (e.g., ".3dm", ".json"). Appended to fileName to create the full filename */
|
|
417
|
+
fileType: string;
|
|
418
|
+
/** Whether data is base64-encoded. If true, must be decoded to binary before use */
|
|
419
|
+
isBase64Encoded: boolean;
|
|
420
|
+
/** Directory path for archive organization (e.g., "subfolder/nested"). Empty string for root-level files */
|
|
421
|
+
subFolder: string;
|
|
422
|
+
/** Arbitrary metadata attached in Grasshopper. Not interpreted by compute; passed through for downstream consumers. May be absent on older payloads */
|
|
423
|
+
metadata?: Record<string, string>;
|
|
424
|
+
};
|
|
425
|
+
/**
|
|
426
|
+
* Normalized file ready for consumption or archival.
|
|
427
|
+
* Unified intermediate format from FileData or FileBaseInfo, ready to be packaged into archives or returned to callers.
|
|
428
|
+
*/
|
|
429
|
+
type ProcessedFile = {
|
|
430
|
+
/** Full filename including extension (e.g., "model.3dm") */
|
|
431
|
+
fileName: string;
|
|
432
|
+
/** File content as binary data (Uint8Array for decoded base64 or fetched binary) or text */
|
|
433
|
+
content: Uint8Array | string;
|
|
434
|
+
/** File path for archive organization (e.g., "subfolder/model.3dm") */
|
|
435
|
+
path: string;
|
|
436
|
+
};
|
|
437
|
+
/**
|
|
438
|
+
* Reference to an external file to be fetched and included in file operations.
|
|
439
|
+
* Specifies additional files (beyond compute response files) to fetch and process as ProcessedFile.
|
|
440
|
+
* @see {@link fetchRemoteFiles} for how FileBaseInfo is fetched and converted
|
|
441
|
+
*/
|
|
442
|
+
type FileBaseInfo = {
|
|
443
|
+
/** Destination filename for the file in the archive or result set (e.g., "additional-data.json") */
|
|
444
|
+
fileName: string;
|
|
445
|
+
/** URL to fetch the file from. Must be accessible from the runtime environment */
|
|
446
|
+
filePath: string;
|
|
447
|
+
/** Optional directory path for archive organization (e.g., "extras/docs"). When omitted, file lands at archive root */
|
|
448
|
+
subFolder?: string;
|
|
449
|
+
};
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/core/files/handle-files.d.ts
|
|
452
|
+
/** Extracts and processes files from compute response data without downloading them. */
|
|
453
|
+
declare const extractFilesFromComputeResponse: (downloadableFiles: FileData[], additionalFiles?: FileBaseInfo[] | FileBaseInfo | null) => Promise<ProcessedFile[]>;
|
|
454
|
+
/** Downloads files from a compute response as a ZIP archive. */
|
|
455
|
+
declare const downloadFileData: (downloadableFiles: FileData[], fileFoldername: string, additionalFiles?: FileBaseInfo[] | FileBaseInfo | null) => Promise<void>;
|
|
456
|
+
//#endregion
|
|
457
|
+
export { RetryPolicy as C, ErrorCode as D, ComputeError as E, ErrorCodes as O, ComputeConfig as S, ServerTiming as T, getLogger as _, ProcessedFile as a, setResponseWireSize as b, validateServerUrl as c, isDefinitionRef as d, decodeBase64ToBinary as f, enableDebugLogging as g, Logger as h, FileData as i, DefinitionRef as l, readField as m, extractFilesFromComputeResponse as n, DEFAULT_BLOCKED_HOST as o, hasField as p, FileBaseInfo as r, ValidateServerUrlOptions as s, downloadFileData as t, SolveDefinition as u, setLogger as v, ServerErrorCodeMap as w, fetchCompute as x, getResponseWireSize as y };
|
|
458
|
+
//# sourceMappingURL=index-BNsid0_9.d.ts.map
|
package/dist/index.cjs
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { }
|
|
1
|
+
export {}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { }
|
|
1
|
+
export {}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selvajs/compute",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.5",
|
|
4
4
|
"description": "TypeScript library for Rhino Compute Server - Grasshopper and RhinoCommon",
|
|
5
5
|
"author": "VektorNode",
|
|
6
6
|
"license": "MIT",
|
|
@@ -68,7 +68,10 @@
|
|
|
68
68
|
"files": [
|
|
69
69
|
"dist",
|
|
70
70
|
"README.md",
|
|
71
|
-
"LICENSE"
|
|
71
|
+
"LICENSE",
|
|
72
|
+
"!**/__tests__/**",
|
|
73
|
+
"!**/*.test.*",
|
|
74
|
+
"!**/*.spec.*"
|
|
72
75
|
],
|
|
73
76
|
"engines": {
|
|
74
77
|
"node": ">=22.0.0"
|
|
@@ -89,7 +92,6 @@
|
|
|
89
92
|
"devDependencies": {
|
|
90
93
|
"@eslint/js": "^10.0.1",
|
|
91
94
|
"@types/node": "^26.1.2",
|
|
92
|
-
"@vitest/coverage-v8": "^4.1.10",
|
|
93
95
|
"eslint": "^10.8.0",
|
|
94
96
|
"eslint-config-prettier": "^10.1.8",
|
|
95
97
|
"eslint-plugin-svelte": "^3.22.0",
|
|
@@ -99,25 +101,25 @@
|
|
|
99
101
|
"prettier-plugin-svelte": "^4.1.1",
|
|
100
102
|
"prettier-plugin-tailwindcss": "^0.8.1",
|
|
101
103
|
"svelte": "5.56.8",
|
|
102
|
-
"
|
|
104
|
+
"tsdown": "^0.22.14",
|
|
103
105
|
"typedoc": "^0.28.17",
|
|
104
106
|
"typedoc-plugin-extras": "^4.0.1",
|
|
105
107
|
"typedoc-plugin-missing-exports": "^4.1.2",
|
|
106
108
|
"typescript": "~6.0.3",
|
|
107
109
|
"typescript-eslint": "^8.65.0",
|
|
108
110
|
"vite": "^8.1.5",
|
|
109
|
-
"vitest": "^4.1.10"
|
|
111
|
+
"vitest": "^4.1.10",
|
|
112
|
+
"@selvajs/config": "0.0.3"
|
|
110
113
|
},
|
|
111
114
|
"scripts": {
|
|
112
|
-
"build": "
|
|
113
|
-
"dev": "
|
|
114
|
-
"clean": "node --input-type=module -e \"import fs from 'fs'; fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
115
|
+
"build": "pnpm type-check && tsdown",
|
|
116
|
+
"dev": "tsdown --watch",
|
|
115
117
|
"lint": "eslint .",
|
|
116
118
|
"lint:fix": "eslint . --fix",
|
|
117
119
|
"format": "prettier --write .",
|
|
118
120
|
"format:check": "prettier --check .",
|
|
119
|
-
"test": "vitest",
|
|
120
|
-
"test:watch": "vitest
|
|
121
|
+
"test": "vitest run",
|
|
122
|
+
"test:watch": "vitest",
|
|
121
123
|
"bench": "vitest bench --run",
|
|
122
124
|
"contract:snapshot": "node scripts/fetch-server-contract.mjs",
|
|
123
125
|
"contract:check": "node scripts/fetch-server-contract.mjs --check",
|
package/dist/chunk-AYH5O3VG.js
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
var oe=Object.defineProperty;var se=(e,r,t)=>r in e?oe(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t;var b=(e,r,t)=>se(e,typeof r!="symbol"?r+"":r,t);var u={NETWORK_ERROR:"NETWORK_ERROR",AUTH_ERROR:"AUTH_ERROR",VALIDATION_ERROR:"VALIDATION_ERROR",COMPUTATION_ERROR:"COMPUTATION_ERROR",TIMEOUT_ERROR:"TIMEOUT_ERROR",CORS_ERROR:"CORS_ERROR",NOT_FOUND:"NOT_FOUND",RATE_LIMIT:"RATE_LIMIT",INVALID_RESPONSE:"INVALID_RESPONSE",UNKNOWN_ERROR:"UNKNOWN_ERROR",INVALID_STATE:"INVALID_STATE",INVALID_INPUT:"INVALID_INPUT",INVALID_CONFIG:"INVALID_CONFIG",BROWSER_ONLY:"BROWSER_ONLY",ENVIRONMENT_ERROR:"ENVIRONMENT_ERROR",ENCODING_ERROR:"ENCODING_ERROR",MALFORMED_DEFAULT:"MALFORMED_DEFAULT",SUPERSEDED:"SUPERSEDED",ABORTED:"ABORTED",QUEUE_FULL:"QUEUE_FULL",QUEUE_TIMEOUT:"QUEUE_TIMEOUT",DEFINITION_NOT_CACHED:"DEFINITION_NOT_CACHED"},f=class e extends Error{constructor(t,n=u.UNKNOWN_ERROR,o){super(t);b(this,"code");b(this,"statusCode");b(this,"context");b(this,"originalError");this.name="ComputeError",this.code=n,this.statusCode=o?.statusCode,this.context=o?.context,this.originalError=o?.originalError,o?.originalError&&(this.cause=o.originalError)}static missingValues(t,n,o){return new e(`Input "${t}" has no values defined${n?` (expected ${n})`:""}`,u.INVALID_INPUT,{context:{inputName:t,expectedType:n,...o}})}static unknownParamType(t,n,o){return new e(`Unknown paramType: ${t}`,u.VALIDATION_ERROR,{context:{receivedParamType:t,paramName:n,...o}})}};function S(){let e=globalThis.Buffer;return typeof e=="function"?e:void 0}function Le(e){let r=S();return r?r.from(e,"utf-8").toString("base64"):ue(new TextEncoder().encode(e))}var ie=64,ae="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function De(e){if(!e)return null;let r=e.replace(/[\t\n\f\r ]/g,"");r.length%4===0&&(r=r.replace(/={1,2}$/,""));let t=r.length%4;if(t===1||r.length<ie||!/^[A-Za-z0-9+/]*$/.test(r))return null;let n=ae.indexOf(r.charAt(r.length-1));return t===2&&(n&15)!==0||t===3&&(n&3)!==0?null:t===0?r:r+"==".slice(0,4-t)}function $(e){let r=e.replace(/[\t\n\f\r ]/g,"");if(r.length%4===0&&(r=r.replace(/={1,2}$/,"")),r.length%4===1||!/^[A-Za-z0-9+/]*$/.test(r))throw new f("Invalid base64 input.",u.ENCODING_ERROR,{context:{inputLength:e.length}});let t=S();if(t)return new Uint8Array(t.from(r,"base64"));if(typeof globalThis.atob=="function"){let n=globalThis.atob(r),o=new Uint8Array(n.length);for(let i=0;i<n.length;i++)o[i]=n.charCodeAt(i)&255;return o}throw new f("Base64 decoding not supported in this environment.",u.INVALID_STATE,{context:{environmentInfo:"atob or Buffer not available"}})}function k(e){let r=S();if(r)return r.byteLength(e,"utf-8");let t=0;for(let n=0;n<e.length;n++){let o=e.charCodeAt(n);o<128?t+=1:o<2048?t+=2:o>=55296&&o<=56319&&n+1<e.length&&(e.charCodeAt(n+1)&64512)===56320?(t+=4,n++):t+=3}return t}function ue(e){let r=S();if(r)return r.from(e).toString("base64");if(typeof globalThis.btoa=="function"){let n=[];for(let o=0;o<e.length;o+=32766)n.push(globalThis.btoa(String.fromCharCode.apply(null,e.subarray(o,o+32766))));return n.join("")}throw new f("Base64 encoding not supported in this environment.",u.INVALID_STATE,{context:{environmentInfo:"btoa or Buffer not available"}})}var I=class{debug(){}info(){}warn(){}error(){}},U=class{debug(r,...t){console.debug(r,...t)}info(r,...t){console.info(r,...t)}warn(r,...t){console.warn(r,...t)}error(r,...t){console.error(r,...t)}},_=new I;function y(){return _}function M(e){if(e===null){_=new I;return}let r=["debug","info","warn","error"].filter(t=>typeof e[t]!="function");if(r.length>0)throw new f(`Logger is missing required method(s): ${r.join(", ")}. A logger must implement debug, info, warn and error.`,u.INVALID_CONFIG,{context:{missingMethods:r}});_=e}function le(){M(new U)}function R(e,r){r&&y().debug(e)}function P(e,r){let t=r.replace(/\/+$/,""),n=e.replace(/^\/+/,"");return`${t}/${n}`}function ce(e){try{let r=new URL(e).hostname.toLowerCase();return r==="localhost"||r==="127.0.0.1"||r==="[::1]"}catch{return/(localhost|127\.0\.0\.1|\[::1\])/i.test(e)}}var B=new Set,fe="RhinoComputeKey";function q(e,r){let t={...r.headers,"X-Request-ID":e,"Content-Type":"application/json",...r.authToken&&{Authorization:r.authToken},...r.apiKey&&{[r.apiKeyHeader??fe]:r.apiKey}};return!r.apiKey&&!r.authToken&&!B.has(r.serverUrl)&&!ce(r.serverUrl)&&(B.add(r.serverUrl),y().warn(`\u26A0\uFE0F [Compute] Request [${e}] targets remote server (${r.serverUrl}) but no API key or auth token is configured. Requests may fail or be rate-limited. (warned once per server)`)),t}function H(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function de(e){if(!e)return null;let r={raw:e},t=!1;for(let n of e.split(",")){let[o,...i]=n.trim().split(";"),c=i.find(d=>d.trim().toLowerCase().startsWith("dur"));if(!c)continue;let s=Number(c.split("=")[1]);if(!Number.isFinite(s))continue;let l=o.trim().toLowerCase();(l==="decode"||l==="solve"||l==="encode")&&(r[l]=s,t=!0)}return t?r:null}function x(e,r,t,n){if(!t)return;let o=de(e.headers.get("Server-Timing"));if(o)try{t(o,r)}catch(i){n&&R(` onServerTiming callback threw: ${i}`,!0)}}var K=new WeakMap;function N(e,r){typeof e!="object"||e===null||K.set(e,r)}function pe(e){if(!(typeof e!="object"||e===null))return K.get(e)}var L=4096;function ge(e){return e.length<=L?e:`${e.slice(0,L)}\u2026 [truncated ${e.length-L} chars]`}function me(e,r,t,n,o,i,c,s,l){let{status:d,statusText:a}=e,g={};e.headers.forEach((te,ne)=>{g[ne]=te});let m=i.trim(),p=m?` \u2014 ${m.slice(0,200)}${m.length>200?"\u2026":""}`:"",h=s??i,A={url:r,requestId:t,method:"POST",requestSize:n,serverUrl:o,responseBody:h?ge(h):void 0,responseHeaders:g},w={401:{message:`HTTP ${d}: ${a}${p}`,code:u.AUTH_ERROR},403:{message:`HTTP ${d}: ${a}${p}`,code:u.AUTH_ERROR},404:{message:`Endpoint not found: ${r}`,code:u.NOT_FOUND},413:{message:`Request too large: ${(n/1024).toFixed(2)}KB`,code:u.VALIDATION_ERROR},429:{message:`Rate limit exceeded${p}`,code:u.RATE_LIMIT},500:{message:`Server error: ${a}${p}`,code:u.COMPUTATION_ERROR},502:{message:`Service unavailable: ${a}${p}`,code:u.NETWORK_ERROR},503:{message:`Service unavailable: ${a}${p}`,code:u.NETWORK_ERROR},504:{message:`Service unavailable: ${a}${p}`,code:u.NETWORK_ERROR}}[d]||{message:`HTTP ${d}: ${a}${p}`,code:u.UNKNOWN_ERROR},F=Re(c,l)??w.code;throw new f(w.message,F,{statusCode:d,context:A})}function Re(e,r){if(!(!e||!r))return r[e]}async function z(e,r,t,n,o,i,c,s,l){let d=Math.round(performance.now()-i);if(!e.ok){let a=await e.text(),g=a;c&&(R(`\u274C Request [${t}] failed with HTTP ${e.status} in ${d}ms`,!0),R(` URL: ${r}`,!0),R(` Status: ${e.status} ${e.statusText}`,!0),g&&R(` Response body: ${g.substring(0,500)}${g.length>500?"...":""}`,!0));let m;try{let p=JSON.parse(g);typeof p?.code=="string"&&(m=p.code)}catch{}if(e.status===500)try{let p=JSON.parse(g);if(p?.values&&(p.errors||p.warnings))return N(p,g.length),c&&(R(`\u26A0\uFE0F Request [${t}] completed with solver errors in ${d}ms`,!0),p.errors?.length>0&&R(` Errors: ${JSON.stringify(p.errors,null,2)}`,!0),p.warnings?.length>0&&R(` Warnings: ${JSON.stringify(p.warnings,null,2)}`,!0)),x(e,t,s,c),p;let h=typeof p?.message=="string"&&p.message||typeof p?.Message=="string"&&p.Message||"",A=typeof p?.ExceptionType=="string"&&p.ExceptionType||"",v=p?.stackTrace??p?.StackTrace,w=Array.isArray(v)?v.join(`
|
|
2
|
-
`):v||"";h?g=`${A?`${A}: `:""}${h}${w?`
|
|
3
|
-
${w}`:""}`:p?.error&&(g=typeof p.error=="string"?p.error:JSON.stringify(p.error,null,2))}catch(p){c&&R(` Failed to parse error body as JSON: ${p}`,!0)}me(e,r,t,n,o,g,m,a,l)}R(`\u2705 Request [${t}] completed in ${d}ms`,c),x(e,t,s,c);try{let a=await e.text(),g=JSON.parse(a);return N(g,a.length),g}catch(a){let g=(e.headers.get("Content-Type")??"").toLowerCase();throw g!==""&&!g.includes("json")?new f(`Server returned a non-JSON response (Content-Type: ${g}) \u2014 check the server URL / proxy configuration`,u.INVALID_RESPONSE,{statusCode:e.status,context:{url:r,requestId:t,contentType:g},originalError:a instanceof Error?a:new Error(String(a))}):new f("Failed to parse JSON response",u.NETWORK_ERROR,{statusCode:e.status,context:{url:r,requestId:t},originalError:a instanceof Error?a:new Error(String(a))})}}var T={attempts:0,baseDelayMs:500,maxDelayMs:3e4,retryOn429:!0},D=new Set([502,503,504]),V=6e4;function W(e){return e?{attempts:e.attempts??T.attempts,baseDelayMs:e.baseDelayMs??T.baseDelayMs,maxDelayMs:e.maxDelayMs??T.maxDelayMs,retryOn429:e.retryOn429??T.retryOn429}:T}function j(e){if(!e)return null;let r=Number(e);if(Number.isFinite(r)&&r>=0)return r*1e3;let t=Date.parse(e);if(Number.isFinite(t)){let n=t-Date.now();return n>0?n:0}return null}function O(e,r){let t=r.baseDelayMs*Math.pow(2,e),n=Math.random()*r.baseDelayMs;return Math.min(t+n,r.maxDelayMs)}function G(e,r){return new Promise((t,n)=>{if(r?.aborted){n(new DOMException("Aborted","AbortError"));return}let o=setTimeout(()=>{r?.removeEventListener("abort",i),t()},e),i=()=>{clearTimeout(o),n(new DOMException("Aborted","AbortError"))};r?.addEventListener("abort",i,{once:!0})})}function J(e,r){let t=()=>{},n=typeof r=="number"&&r>0;if(!e&&!n)return{signal:void 0,cleanup:t};if(e&&!n)return{signal:e,cleanup:t};let o=typeof AbortSignal<"u"&&typeof AbortSignal.timeout=="function";if(!e){if(o)return{signal:AbortSignal.timeout(r),cleanup:t};let a=new AbortController,g=setTimeout(()=>a.abort(),r);return{signal:a.signal,cleanup:()=>clearTimeout(g)}}let i=new AbortController,c,s;if(o)c=AbortSignal.timeout(r);else{let a=new AbortController;s=setTimeout(()=>a.abort(),r),c=a.signal}let l=[e,c],d=function(){i.abort(this.reason)};for(let a of l){if(a.aborted){i.abort(a.reason);break}a.addEventListener("abort",d,{once:!0})}return{signal:i.signal,cleanup:()=>{s!==void 0&&clearTimeout(s);for(let a of l)a.removeEventListener("abort",d)}}}async function ye(e,r,t,n){let{signal:o,cleanup:i}=J(e.config.signal,e.config.timeoutMs),c=performance.now();try{let s=await fetch(e.fullUrl,{method:"POST",body:e.body,headers:e.headers,signal:o});if((D.has(s.status)||r.retryOn429&&s.status===429)&&t<n-1){let a=j(s.headers.get("Retry-After")),g=a!==null?Math.min(a,Math.max(r.maxDelayMs,V)):O(t,r);return await s.text().catch(()=>{}),{ok:!1,retry:!0,delayMs:g,cause:new f(`HTTP ${s.status} ${s.statusText} (will retry)`,s.status===429?u.RATE_LIMIT:u.NETWORK_ERROR,{statusCode:s.status,context:{requestId:e.requestId}})}}return{ok:!0,value:await z(s,e.fullUrl,e.requestId,e.requestSize,e.config.serverUrl,c,e.config.debug,e.config.onServerTiming,e.config.serverErrorCodes)}}catch(s){if(s instanceof Error&&(s.name==="AbortError"||s.name==="TimeoutError")){if(e.config.signal?.aborted===!0)return{ok:!1,retry:!1,cause:new f("Request aborted by caller",u.ABORTED,{context:{endpoint:e.endpoint,requestId:e.requestId,requestSize:e.requestSize},originalError:s})};let a=typeof e.config.timeoutMs=="number"&&e.config.timeoutMs>0?new f(`Request timed out after ${e.config.timeoutMs}ms`,u.TIMEOUT_ERROR,{context:{serverUrl:e.config.serverUrl,timeoutMs:e.config.timeoutMs,url:e.fullUrl,requestId:e.requestId,endpoint:e.endpoint,requestSize:e.requestSize},originalError:s}):new f(`Request aborted by the runtime (${s.name}): ${s.message}`,u.NETWORK_ERROR,{context:{serverUrl:e.config.serverUrl,url:e.fullUrl,requestId:e.requestId,endpoint:e.endpoint,requestSize:e.requestSize},originalError:s});return t<n-1?{ok:!1,retry:!0,delayMs:O(t,r),cause:a}:{ok:!1,retry:!1,cause:a}}if(s instanceof TypeError){let l=typeof window<"u"&&typeof window.document<"u",d={serverUrl:e.config.serverUrl,url:e.fullUrl,requestId:e.requestId,endpoint:e.endpoint,requestSize:e.requestSize},a=l?new f(`Request failed: ${s.message} \u2014 in a browser this usually means a CORS misconfiguration on the server (or a network failure; the browser does not distinguish them)`,u.CORS_ERROR,{context:d,originalError:s}):new f(`Network error: ${s.message}`,u.NETWORK_ERROR,{context:d,originalError:s});return t<n-1?{ok:!1,retry:!0,delayMs:O(t,r),cause:a}:{ok:!1,retry:!1,cause:a}}if(s instanceof f){let l=s.statusCode;return(s.code===u.NETWORK_ERROR&&l!==void 0&&l>=200&&l<300||l!==void 0&&(D.has(l)||r.retryOn429&&l===429))&&t<n-1?{ok:!1,retry:!0,delayMs:O(t,r),cause:s}:{ok:!1,retry:!1,cause:s}}return{ok:!1,retry:!1,cause:new f(s instanceof Error?s.message:String(s),u.UNKNOWN_ERROR,{context:{endpoint:e.endpoint,requestId:e.requestId},originalError:s instanceof Error?s:new Error(String(s))})}}finally{i()}}async function Ee(e,r,t){let n=H(),o=JSON.stringify(r),i=k(o),c=P(e,t.serverUrl),s=q(n,t),l=W(t.retry),d=l.attempts+1;if(t.debug){let g=(i/1024).toFixed(2),m=i>1e5?"\u26A0\uFE0F":"\u{1F680}";R(`${m} Starting compute request [${n}]: ${e} (${g}KB)`,!0)}let a={endpoint:e,body:o,requestSize:i,fullUrl:c,requestId:n,headers:s,config:t};for(let g=0;;g++){let m=await ye(a,l,g,d);if(m.ok)return m.value;if(!m.retry)throw m.cause;t.debug&&R(`\u{1F501} Request [${n}] retrying after ${m.delayMs}ms (attempt ${g+2}/${d}): ${m.cause.message}`,!0);try{await G(m.delayMs,t.signal)}catch{throw new f("Request aborted by caller",u.ABORTED,{context:{endpoint:e,requestId:n,requestSize:i},originalError:m.cause})}}}function E(e,r){if(!e||typeof e!="object")return;let t=e;if(Z(t,r))return t[r];let n=Q(t).get(r.toLowerCase());return n===void 0?void 0:t[n]}var Z=(e,r)=>Object.prototype.hasOwnProperty.call(e,r);function he(e,r){if(!e||typeof e!="object")return!1;let t=e;return Z(t,r)?!0:Q(t).has(r.toLowerCase())}var Y=new WeakMap;function Q(e){let r=Object.keys(e),t=Y.get(e);if(t&&t.keyCount===r.length)return t.map;let n=new Map;for(let o of r){let i=o.toLowerCase();n.has(i)||n.set(i,o)}return Y.set(e,{keyCount:r.length,map:n}),n}function we(e){return typeof e=="object"&&!(e instanceof Uint8Array)&&typeof e.key=="string"&&typeof e.load=="function"}var X="compute.rhino3d.com";function be(e,r){let t=e?.trim()??"";if(!t)throw new f("serverUrl is required",u.INVALID_CONFIG,{context:{receivedServerUrl:e}});if(!/^https?:\/\//i.test(t))throw new f(`Invalid serverUrl: "${t}". Must start with "http://" or "https://". For example: "http://localhost:5000" or "https://example.com"`,u.INVALID_CONFIG,{context:{receivedServerUrl:e}});let n;try{n=new URL(t)}catch(c){throw new f(`Invalid serverUrl: "${t}". Must be a valid URL. Received error: ${c instanceof Error?c.message:String(c)}`,u.INVALID_CONFIG,{context:{receivedServerUrl:e},originalError:c instanceof Error?c:void 0})}if(n.username!==""||n.password!=="")throw new f(`Invalid serverUrl: "${t}". Must not embed credentials (user:pass@host) \u2014 fetch rejects credentialed URLs at request time. Pass the API key separately.`,u.INVALID_CONFIG,{context:{receivedServerUrl:e}});if(t.includes("?")||t.includes("#"))throw new f(`Invalid serverUrl: "${t}". Must not contain a query string or fragment \u2014 endpoint paths are appended to this URL (e.g. "\${serverUrl}/version"), and a "?" or "#" suffix would corrupt every request path.`,u.INVALID_CONFIG,{context:{receivedServerUrl:e}});let o=n.hostname.toLowerCase().replace(/\.+$/,"");if((r?.blockedHosts??[X]).map(c=>c.toLowerCase().replace(/\.+$/,"")).includes(o))throw new f("serverUrl must be set to your Compute server URL. The shared public endpoint is not allowed.",u.INVALID_CONFIG,{context:{receivedServerUrl:e}});return t.replace(/\/+$/,"")}var Te=async(e,r=null)=>{try{return await ee(e,r)}catch(t){throw new f("Failed to extract files from compute response",u.INVALID_STATE,{context:{originalError:t instanceof Error?t.message:String(t)},originalError:t instanceof Error?t:void 0})}},Oe=async(e,r,t=null)=>{if(typeof document>"u"||typeof Blob>"u")throw new f("File download functionality is only available in browser environments. This function requires the DOM API (document, Blob).",u.BROWSER_ONLY,{context:{environment:typeof window<"u"?"browser (SSR)":"Node.js",documentAvailable:typeof document<"u",blobAvailable:typeof Blob<"u"}});try{let n=await ee(e,t);await Ne(n,r)}catch(n){throw n instanceof f?n:new f("Failed to download files from compute response",u.INVALID_STATE,{context:{originalError:n instanceof Error?n.message:String(n)},originalError:n instanceof Error?n:void 0})}},C=e=>e.replace(/\\/g,"/").split("/").map(r=>r.trim()).filter(r=>r!==""&&r!=="."&&r!==".."&&!/^[a-zA-Z]:$/.test(r)).join("/"),Ae=e=>e===!0||typeof e=="string"&&e.trim().toLowerCase()==="true",ve=e=>{let r=[];return e.forEach(t=>{let n=E(t,"fileName")??"",o=E(t,"fileType")??"",i=C(`${n}${o}`);if(i===""){y().warn(`Skipping file with unusable name "${n}${o}": no safe archive path remains after sanitization.`);return}let c=E(t,"subFolder"),s=c?C(c):"",l=s!==""?`${s}/${i}`:i,d=E(t,"data");if(typeof d!="string"||d===""){y().warn(`Skipping file "${l}": item carries no usable data.`);return}if(Ae(E(t,"isBase64Encoded")))try{r.push({fileName:i,content:$(d),path:l})}catch(a){y().warn(`Skipping file "${l}": base64 decode failed.`,a)}else r.push({fileName:i,content:d,path:l})}),r},Se=3e4,Ie=async e=>(await Promise.all(e.map(async t=>{try{let n=typeof AbortSignal<"u"&&typeof AbortSignal.timeout=="function"?AbortSignal.timeout(Se):void 0,o=await fetch(t.filePath,{signal:n});if(!o.ok)return y().warn(`Failed to fetch additional file from URL: ${t.filePath}`),null;let i=await o.arrayBuffer(),c=C(t.fileName);if(c==="")return y().warn(`Skipping fetched file with unusable name: ${t.fileName}`),null;let s=t.subFolder?C(t.subFolder):"";return{fileName:c,content:new Uint8Array(i),path:s!==""?`${s}/${c}`:c}}catch(n){return y().error(`Error fetching additional file from URL: ${t.filePath}`,n),null}}))).filter(t=>t!==null),ee=async(e,r)=>{let t=ve(e);if(r){let o=Array.isArray(r)?r:[r];t.push(...await Ie(o))}let n=new Set;return t.map(o=>{let i=re(o.path,n);return n.add(i),i===o.path?o:(y().warn(`Duplicate archive path "${o.path}" \u2014 storing as "${i}".`),{...o,path:i,fileName:i.slice(i.lastIndexOf("/")+1)})})};async function Ne(e,r){let{zip:t,strToU8:n}=await import("fflate"),o={},i=new Set;e.forEach(l=>{let d=re(l.path,i);i.add(d),d!==l.path&&y().warn(`Duplicate archive path "${l.path}" \u2014 storing as "${d}".`),o[d]=typeof l.content=="string"?n(l.content):l.content});let c=await new Promise((l,d)=>{t(o,{level:6},(a,g)=>a?d(a):l(g))}),s=new Blob([c],{type:"application/zip"});Ce(s,`${r}.zip`)}function re(e,r){if(!r.has(e))return e;let t=e.lastIndexOf("/"),n=e.lastIndexOf("."),o=n>t?n:e.length,i=e.slice(0,o),c=e.slice(o);for(let s=2;;s++){let l=`${i}-${s}${c}`;if(!r.has(l))return l}}function Ce(e,r){if(typeof document>"u")throw new f("saveFile requires a browser environment with DOM API access.",u.BROWSER_ONLY,{context:{function:"saveFile",requiredAPI:"document"}});let t=URL.createObjectURL(e),n=document.createElement("a");n.href=t,n.download=r,document.body.appendChild(n),n.click(),n.remove(),setTimeout(()=>URL.revokeObjectURL(t),1e4)}export{b as a,u as b,f as c,y as d,M as e,le as f,E as g,he as h,X as i,be as j,Le as k,De as l,$ as m,ue as n,N as o,pe as p,Ee as q,we as r,Te as s,Oe as t};
|
|
4
|
-
//# sourceMappingURL=chunk-AYH5O3VG.js.map
|