@selvajs/compute 4.3.0-beta.1 → 5.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle-files-D-4X4Uh_.js","names":[],"sources":["../src/core/compute-fetch/retry.ts","../src/core/errors.ts","../src/core/utils/logger.ts","../src/core/utils/read-field.ts","../src/core/utils/redact-url.ts","../src/core/utils/try-parse-json.ts","../src/core/server/validate-server-url.ts","../src/core/utils/warnings.ts","../src/core/compute-fetch/request.ts","../src/core/utils/encoding.ts","../src/core/compute-fetch/read-body.ts","../src/core/compute-fetch/server-timing.ts","../src/core/compute-fetch/signal.ts","../src/core/compute-fetch/wire-size.ts","../src/core/compute-fetch/response.ts","../src/core/server/classify-probe-failure.ts","../src/core/compute-fetch/request-compression.ts","../src/core/compute-fetch/compute-fetch.ts","../src/core/definition-ref.ts","../src/core/files/sub-folder.ts","../src/core/files/handle-files.ts"],"sourcesContent":["import type { RetryPolicy } from '../types';\n\nconst DEFAULT_RETRY: Required<RetryPolicy> = {\n\tattempts: 0,\n\tbaseDelayMs: 500,\n\tmaxDelayMs: 30_000,\n\tretryOn429: true\n};\n\nconst RETRYABLE_STATUS = new Set([502, 503, 504]);\n\n/**\n * Ceiling on a server-sent `Retry-After` wait. The server's window outranks\n * `RetryPolicy.maxDelayMs` (retrying sooner all but guarantees another 429), but a bad or hostile\n * header must not park the client for minutes. A `maxDelayMs` above this cap raises it.\n */\nconst RETRY_AFTER_CAP_MS = 60_000;\n\nexport function resolveRetryPolicy(policy: RetryPolicy | undefined): Required<RetryPolicy> {\n\tif (!policy) return DEFAULT_RETRY;\n\treturn {\n\t\tattempts: policy.attempts ?? DEFAULT_RETRY.attempts,\n\t\tbaseDelayMs: policy.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs,\n\t\tmaxDelayMs: policy.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs,\n\t\tretryOn429: policy.retryOn429 ?? DEFAULT_RETRY.retryOn429\n\t};\n}\n\nexport function isRetryableStatus(status: number, policy: Required<RetryPolicy>): boolean {\n\treturn RETRYABLE_STATUS.has(status) || (policy.retryOn429 && status === 429);\n}\n\n/** Delay before the next attempt: the server's `Retry-After` when it sent one, else backoff. */\nexport function retryDelay(\n\tretryAfter: string | null,\n\tattempt: number,\n\tpolicy: Required<RetryPolicy>\n): number {\n\tconst retryAfterMs = parseRetryAfter(retryAfter);\n\treturn retryAfterMs === null\n\t\t? backoffDelay(attempt, policy)\n\t\t: Math.min(retryAfterMs, Math.max(policy.maxDelayMs, RETRY_AFTER_CAP_MS));\n}\n\n/** A `Retry-After` value (seconds or HTTP-date) in ms; null when missing or unparseable. */\nfunction parseRetryAfter(headerValue: string | null): number | null {\n\tif (!headerValue) return null;\n\tconst seconds = Number(headerValue);\n\tif (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;\n\tconst dateMs = Date.parse(headerValue);\n\tif (Number.isFinite(dateMs)) {\n\t\tconst delta = dateMs - Date.now();\n\t\treturn delta > 0 ? delta : 0;\n\t}\n\treturn null;\n}\n\nexport function backoffDelay(attempt: number, policy: Required<RetryPolicy>): number {\n\tconst exponential = policy.baseDelayMs * Math.pow(2, attempt);\n\tconst jitter = Math.random() * policy.baseDelayMs;\n\treturn Math.min(exponential + jitter, policy.maxDelayMs);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new DOMException('Aborted', 'AbortError'));\n\t\t\treturn;\n\t\t}\n\t\tconst id = setTimeout(() => {\n\t\t\tsignal?.removeEventListener('abort', onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(id);\n\t\t\treject(new DOMException('Aborted', 'AbortError'));\n\t\t};\n\t\tsignal?.addEventListener('abort', onAbort, { once: true });\n\t});\n}\n","export const ErrorCodes = {\n\t/** Connection failure, a gateway status (502/503/504), or a 2xx body cut off mid-stream. */\n\tNETWORK_ERROR: 'NETWORK_ERROR',\n\t/** HTTP 401 or 403: the server rejected the credentials. */\n\tAUTH_ERROR: 'AUTH_ERROR',\n\t/** The server rejected the request (HTTP 400, 413), or an input failed a client-side check. */\n\tVALIDATION_ERROR: 'VALIDATION_ERROR',\n\t/** The server failed the solve (HTTP 500), or the solve reported errors. */\n\tCOMPUTATION_ERROR: 'COMPUTATION_ERROR',\n\t/** No response within `ComputeConfig.timeoutMs`. */\n\tTIMEOUT_ERROR: 'TIMEOUT_ERROR',\n\t/** A browser fetch failed with no response: most often CORS, which the browser hides. */\n\tCORS_ERROR: 'CORS_ERROR',\n\t/** HTTP 404: the endpoint doesn't exist on this server. Never retried. */\n\tNOT_FOUND: 'NOT_FOUND',\n\t/** HTTP 429. Retried, honouring `Retry-After`, when the policy allows (`RetryPolicy.retryOn429`). */\n\tRATE_LIMIT: 'RATE_LIMIT',\n\t/**\n\t * A 2xx with a body the client can't use: declared non-JSON (a captive portal, a proxy login page)\n\t * or JSON that isn't an object. Never retried.\n\t */\n\tINVALID_RESPONSE: 'INVALID_RESPONSE',\n\t/**\n\t * The response body passed `ComputeConfig.maxResponseBytes`; the download was cancelled there.\n\t * Never retried. `context` carries `{ maxResponseBytes }`.\n\t */\n\tRESPONSE_TOO_LARGE: 'RESPONSE_TOO_LARGE',\n\t/** Any failure not classified by another code. */\n\tUNKNOWN_ERROR: 'UNKNOWN_ERROR',\n\t/** An object was used after `dispose()`, or a file download failed part-way. */\n\tINVALID_STATE: 'INVALID_STATE',\n\t/** A caller-supplied argument is unusable: an empty definition, an unserializable body. */\n\tINVALID_INPUT: 'INVALID_INPUT',\n\t/**\n\t * Bad configuration: an invalid `serverUrl`, an unsendable header, an unusable number, a server\n\t * that answers with a redirect, an incomplete logger, an interval out of range.\n\t */\n\tINVALID_CONFIG: 'INVALID_CONFIG',\n\t/** A browser-only API (file download) was called outside a browser. */\n\tBROWSER_ONLY: 'BROWSER_ONLY',\n\t/** The runtime has no base64 codec: neither `Buffer` nor `atob`/`btoa`. */\n\tENVIRONMENT_ERROR: 'ENVIRONMENT_ERROR',\n\t/** A string that should be base64 isn't. */\n\tENCODING_ERROR: 'ENCODING_ERROR',\n\t/** An input's `default` had a shape the normalizer didn't recognize. */\n\tMALFORMED_DEFAULT: 'MALFORMED_DEFAULT',\n\t/** Latest-wins scheduling: a newer call replaced this one. */\n\tSUPERSEDED: 'SUPERSEDED',\n\t/** The scheduler or a caller-supplied `AbortSignal` aborted this call. */\n\tABORTED: 'ABORTED',\n\t/**\n\t * The queue was at `maxQueueDepth`, so the call was rejected without queueing. Retryable after a\n\t * back-off. `context` carries `{ queueDepth, maxQueueDepth }`.\n\t */\n\tQUEUE_FULL: 'QUEUE_FULL',\n\t/**\n\t * The call waited longer than `queueWaitMs` without starting, and was rejected rather than run\n\t * stale. Retryable. `context` carries `{ waitedMs, queueWaitMs }`.\n\t */\n\tQUEUE_TIMEOUT: 'QUEUE_TIMEOUT',\n\t/**\n\t * The server no longer holds the definition a `pointer` named (evicted, restarted, another\n\t * child). Retry with the full definition. Read from the error body's\n\t * `code: \"definition_not_cached\"`.\n\t */\n\tDEFINITION_NOT_CACHED: 'DEFINITION_NOT_CACHED'\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Error with a typed `code`: switch on it rather than on the message. */\nexport class ComputeError extends Error {\n\tpublic readonly code: ErrorCode;\n\tpublic readonly statusCode?: number;\n\tpublic readonly context?: Record<string, unknown>;\n\tpublic readonly originalError?: Error;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: ErrorCode = ErrorCodes.UNKNOWN_ERROR,\n\t\toptions?: { statusCode?: number; context?: Record<string, unknown>; originalError?: unknown }\n\t) {\n\t\tconst original = options?.originalError;\n\t\tconst cause =\n\t\t\toriginal === undefined || original instanceof Error ? original : new Error(String(original));\n\t\tsuper(message, cause && { cause });\n\t\tthis.name = 'ComputeError';\n\t\tthis.code = code;\n\t\tthis.statusCode = options?.statusCode;\n\t\tthis.context = options?.context;\n\t\tthis.originalError = cause;\n\t}\n}\n\n/** A caller's signal cancelled the work. */\nexport function abortedByCaller(\n\tcontext: Record<string, unknown>,\n\toriginalError?: unknown\n): ComputeError {\n\treturn new ComputeError('Request aborted by caller', ErrorCodes.ABORTED, {\n\t\tcontext,\n\t\toriginalError\n\t});\n}\n","import { ComputeError, ErrorCodes } from '../errors';\n\nexport interface Logger {\n\tdebug(message: string, ...args: unknown[]): void;\n\tinfo(message: string, ...args: unknown[]): void;\n\twarn(message: string, ...args: unknown[]): void;\n\terror(message: string, ...args: unknown[]): void;\n}\n\nconst silent: Logger = {\n\tdebug() {},\n\tinfo() {},\n\twarn() {},\n\terror() {}\n};\n\nlet internalLogger: Logger = silent;\n\nexport function getLogger(): Logger {\n\treturn internalLogger;\n}\n\n/**\n * Route the package's logs to `logger`; `null` silences them (the default).\n *\n * @throws {ComputeError} `INVALID_CONFIG` if the logger lacks one of the four methods, rather\n * than a \"debug is not a function\" later at an unrelated call site.\n *\n * @example\n * ```typescript\n * import { setLogger } from '@selvajs/compute/core';\n *\n * setLogger(console);\n * setLogger({\n * debug: (msg, ...args) => myLogger.debug(msg, ...args),\n * info: (msg, ...args) => myLogger.info(msg, ...args),\n * warn: (msg, ...args) => myLogger.warn(msg, ...args),\n * error: (msg, ...args) => myLogger.error(msg, ...args)\n * });\n * setLogger(null);\n * ```\n */\nexport function setLogger(logger: Logger | Console | null): void {\n\tif (logger === null) {\n\t\tinternalLogger = silent;\n\t\treturn;\n\t}\n\n\tconst missing = (['debug', 'info', 'warn', 'error'] as const).filter(\n\t\t(method) => typeof (logger as unknown as Record<string, unknown>)[method] !== 'function'\n\t);\n\tif (missing.length > 0) {\n\t\tthrow new ComputeError(\n\t\t\t`Logger is missing required method(s): ${missing.join(', ')}. A logger must implement debug, info, warn and error.`,\n\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t{ context: { missingMethods: missing } }\n\t\t);\n\t}\n\n\tinternalLogger = logger as Logger;\n}\n\n/** Shorthand for `setLogger(console)`. */\nexport function enableDebugLogging(): void {\n\tsetLogger(console);\n}\n","/**\n * Case-insensitive field reads for wire payloads.\n *\n * Rhino Compute branches serialize the same field in different casings. mcneel 8.x/9.x send the IO\n * schema PascalCase (`ParamType`, `Default`, `InnerTree`): those C# classes carry no\n * `[JsonProperty]`. The VektorNode fork sends camelCase, except inside the `default` DataTree\n * (`ParamName`, `InnerTree`), an external type it can't attribute. So one response can mix casings.\n * Read the fields you need through here and leave the rest verbatim: rewriting every key corrupts\n * user-authored ones such as value-list labels.\n */\n\n/** Own keys only: a payload probed for `constructor` must not find `Object.prototype`'s. */\nfunction findOwnKey(obj: unknown, name: string): string | undefined {\n\tif (!obj || typeof obj !== 'object') return undefined;\n\tif (Object.hasOwn(obj, name)) return name;\n\tconst lower = name.toLowerCase();\n\treturn Object.keys(obj).find((key) => key.toLowerCase() === lower);\n}\n\n/**\n * Read `name` in any casing, preferring an exact-case match. `undefined` when no own key matches or\n * `obj` isn't an object.\n */\nexport function readField<T = unknown>(obj: unknown, name: string): T | undefined {\n\tconst key = findOwnKey(obj, name);\n\treturn key === undefined ? undefined : ((obj as Record<string, unknown>)[key] as T);\n}\n\n/** Whether `name` is present in any casing, even with a null or undefined value. */\nexport function hasField(obj: unknown, name: string): boolean {\n\treturn findOwnKey(obj, name) !== undefined;\n}\n","const UNREADABLE = '<unreadable URL>';\n\n/**\n * Only a URL with a real host can be cut safely. Anything else could hold a secret at any position:\n * `https://admin:p/ss@host` splits into a bad port and a path.\n */\nfunction parseWithHost(url: string): URL | null {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\treturn parsed.host ? parsed : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** `url` without credentials, query or fragment, for errors and logs. */\nexport function redactUrl(url: string): string {\n\tconst parsed = parseWithHost(url);\n\tif (!parsed) return UNREADABLE;\n\tparsed.username = '';\n\tparsed.password = '';\n\tparsed.search = '';\n\tparsed.hash = '';\n\treturn parsed.href;\n}\n\n/** Scheme and host alone. For a URL that was rejected, whose path can't be trusted either. */\nexport function redactToOrigin(url: string): string {\n\tconst parsed = parseWithHost(url);\n\treturn parsed ? `${parsed.protocol}//${parsed.host}` : UNREADABLE;\n}\n","/** The parsed value, or `undefined` when `text` isn't JSON. */\nexport function tryParseJson(text: string): unknown {\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n","import { ComputeError, ErrorCodes } from '@/core/errors';\nimport { redactToOrigin } from '@/core/utils/redact-url';\n\n/**\n * The public McNeel endpoint, blocked by default: users must point at their own server. Its raw IP\n * isn't blocked: it sits behind a load balancer with no stable address to pin.\n */\nexport const DEFAULT_BLOCKED_HOST = 'compute.rhino3d.com';\n\nexport interface ValidateServerUrlOptions {\n\t/**\n\t * Hostnames rejected as a `serverUrl` — a backend's shared public endpoint,\n\t * which callers must not point at. Defaults to `[DEFAULT_BLOCKED_HOST]`; pass\n\t * `[]` to block nothing. Compared lowercased with any trailing dot stripped.\n\t */\n\tblockedHosts?: readonly string[];\n}\n\n/**\n * Validate a compute `serverUrl` and return it trimmed, without trailing slashes.\n *\n * @throws {ComputeError} `INVALID_CONFIG` when the URL is empty, isn't http(s), doesn't parse,\n * embeds credentials, has a query or fragment, or names a blocked host.\n */\nexport function validateServerUrl(raw: string, options?: ValidateServerUrlOptions): string {\n\tconst trimmed = raw?.trim() ?? '';\n\tif (!trimmed) {\n\t\tthrow new ComputeError('serverUrl is required', ErrorCodes.INVALID_CONFIG, {\n\t\t\tcontext: { receivedServerUrl: raw }\n\t\t});\n\t}\n\n\tconst redacted = redactToOrigin(trimmed);\n\tconst invalid = (reason: string) =>\n\t\tnew ComputeError(`Invalid serverUrl: \"${redacted}\". ${reason}`, ErrorCodes.INVALID_CONFIG, {\n\t\t\tcontext: { receivedServerUrl: redacted }\n\t\t});\n\n\tif (!/^https?:\\/\\//i.test(trimmed)) {\n\t\tthrow invalid(\n\t\t\t'Must start with \"http://\" or \"https://\". ' +\n\t\t\t\t'For example: \"http://localhost:6500\" or \"https://example.com\"'\n\t\t);\n\t}\n\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(trimmed);\n\t} catch {\n\t\t// Not the cause: a URL error carries its input, and some engines quote it in the message.\n\t\tthrow invalid('Must be a valid URL.');\n\t}\n\n\tif (parsed.username !== '' || parsed.password !== '') {\n\t\tthrow invalid(\n\t\t\t'Must not embed credentials (user:pass@host) — ' +\n\t\t\t\t'fetch rejects credentialed URLs at request time. Pass the API key separately.'\n\t\t);\n\t}\n\n\t// A string check: a bare trailing \"?\" or \"#\" parses to an empty search/hash but still corrupts\n\t// the endpoint paths appended to this URL.\n\tif (trimmed.includes('?') || trimmed.includes('#')) {\n\t\tthrow invalid(\n\t\t\t'Must not contain a query string or fragment — ' +\n\t\t\t\t'endpoint paths are appended to this URL (e.g. \"${serverUrl}/version\"), ' +\n\t\t\t\t'and a \"?\" or \"#\" suffix would corrupt every request path.'\n\t\t);\n\t}\n\n\t// Lowercase + strip any trailing dot so the FQDN form can't bypass the block.\n\tconst hostname = trimTrailing(parsed.hostname.toLowerCase(), '.');\n\tconst blocked = (options?.blockedHosts ?? [DEFAULT_BLOCKED_HOST]).map((h) =>\n\t\ttrimTrailing(h.toLowerCase(), '.')\n\t);\n\tif (blocked.includes(hostname)) {\n\t\tthrow new ComputeError(\n\t\t\t'serverUrl must be set to your Compute server URL. The shared public endpoint is not allowed.',\n\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t{ context: { receivedServerUrl: redacted } }\n\t\t);\n\t}\n\n\treturn trimTrailing(trimmed, '/');\n}\n\n/** A loop, not `/x+$/`: that regex retries from every position of a long run and goes quadratic. */\nfunction trimTrailing(value: string, char: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && value[end - 1] === char) end--;\n\treturn value.slice(0, end);\n}\n","import { getLogger } from './logger';\n\nconst warned = new Set<string>();\n\n/** Log `message` the first time `key` is seen: what it reports doesn't change per call. */\nexport function warnOnce(key: string, message: string): void {\n\tif (warned.has(key)) return;\n\twarned.add(key);\n\tgetLogger().warn(message);\n}\n\n/**\n * True only in a real browser, where an API key in the config is exposed to users. jsdom defines\n * `window` too, so Node is ruled out by `process.versions.node`, and by jsdom's user-agent marker\n * for setups that hide `process`.\n */\nfunction isRealBrowser(): boolean {\n\tif (typeof window === 'undefined') return false;\n\n\tconst proc = (globalThis as { process?: { versions?: { node?: unknown } } }).process;\n\tif (proc?.versions?.node != null) return false;\n\n\tconst nav = (globalThis as { navigator?: { userAgent?: unknown } }).navigator;\n\tconst userAgent = typeof nav?.userAgent === 'string' ? nav.userAgent : '';\n\tif (/jsdom/i.test(userAgent)) return false;\n\n\treturn true;\n}\n\nexport function warnIfClientSide(functionName: string, suppress?: boolean): void {\n\tif (suppress || !isRealBrowser()) return;\n\twarnOnce(\n\t\t`client-side:${functionName}`,\n\t\t`Warning: ${functionName} is running on the client side. For better performance and security, consider running this on the server side.`\n\t);\n}\n\n/** Forget what already warned. For tests. */\nexport function resetWarnings(): void {\n\twarned.clear();\n}\n","import { ComputeError, ErrorCodes } from '../errors';\nimport { warnOnce } from '../utils/warnings';\n\nimport type { ComputeAuth, ComputeConfig } from '../types';\n\n/** `serverUrl` as `validateServerUrl` returns it: no trailing slash. */\nexport function buildUrl(endpoint: string, serverUrl: string): string {\n\treturn `${serverUrl}/${endpoint.replace(/^\\/+/, '')}`;\n}\n\nfunction isLocalhost(serverUrl: string): boolean {\n\t// `hostname` (not `host`) strips the port; IPv6 hostnames keep their\n\t// brackets, so `http://[::1]:6500` yields `[::1]`.\n\tconst hostname = new URL(serverUrl).hostname.toLowerCase();\n\treturn hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';\n}\n\nconst DEFAULT_API_KEY_HEADER = 'RhinoComputeKey';\n\n/**\n * Caller headers plus credentials. Every request to the server builds its headers here,\n * so a liveness probe authenticates exactly like the solve it gates.\n */\nexport function buildAuthHeaders(auth: ComputeAuth): Record<string, string> {\n\treturn assertSendable(mergeUnder(auth.headers, credentialHeaders(auth)));\n}\n\nexport function buildHeaders(requestId: string, config: ComputeConfig): Record<string, string> {\n\tconst headers = assertSendable(\n\t\tmergeUnder(config.headers, {\n\t\t\t...credentialHeaders(config),\n\t\t\t'X-Request-ID': requestId,\n\t\t\t'Content-Type': 'application/json'\n\t\t})\n\t);\n\n\tif (!config.apiKey && !config.authToken && !isLocalhost(config.serverUrl)) {\n\t\twarnOnce(\n\t\t\t`no-auth:${config.serverUrl}`,\n\t\t\t`[Compute] Request [${requestId}] targets remote server (${config.serverUrl}) but no API key or auth token is configured. Requests may fail or be rate-limited. (warned once per server)`\n\t\t);\n\t}\n\n\treturn headers;\n}\n\n/** `headers` with a JSON `Content-Type` that replaces one of any casing already there. */\nexport function withJsonContentType(headers: Record<string, string>): Record<string, string> {\n\treturn mergeUnder(headers, { 'Content-Type': 'application/json' });\n}\n\nfunction credentialHeaders(auth: ComputeAuth): Record<string, string> {\n\treturn {\n\t\t...(auth.authToken && { Authorization: auth.authToken }),\n\t\t...(auth.apiKey && { [auth.apiKeyHeader ?? DEFAULT_API_KEY_HEADER]: auth.apiKey })\n\t};\n}\n\n/**\n * Header names are case-insensitive and fetch joins duplicates, so a caller key\n * matching a transport key in any case is dropped rather than sent alongside it.\n */\nfunction mergeUnder(\n\tcaller: Record<string, string> | undefined,\n\ttransport: Record<string, string>\n): Record<string, string> {\n\tconst taken = new Set(Object.keys(transport).map((name) => name.toLowerCase()));\n\tconst kept = Object.entries(caller ?? {}).filter(([name]) => !taken.has(name.toLowerCase()));\n\treturn { ...Object.fromEntries(kept), ...transport };\n}\n\n/**\n * Left to fetch, a bad value becomes a retried TypeError that quotes it, and the value may be the\n * API key. `Headers` throws the same message, so its error is not kept as the cause.\n */\nfunction assertSendable(headers: Record<string, string>): Record<string, string> {\n\tconst check = new Headers();\n\tfor (const [name, value] of Object.entries(headers)) {\n\t\ttry {\n\t\t\tcheck.set(name, value);\n\t\t} catch {\n\t\t\tthrow new ComputeError(\n\t\t\t\t`Header \"${name}\" has an invalid name or value`,\n\t\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t\t{ context: { header: name } }\n\t\t\t);\n\t\t}\n\t}\n\treturn headers;\n}\n\nexport function generateRequestId(): string {\n\treturn `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\n}\n","import { ComputeError, ErrorCodes } from '../errors';\n\nfunction getNodeBuffer(): typeof Buffer | undefined {\n\tconst buf = (globalThis as { Buffer?: typeof Buffer }).Buffer;\n\treturn typeof buf === 'function' ? buf : undefined;\n}\n\nexport function encodeStringToBase64(str: string): string {\n\tconst Buffer = getNodeBuffer();\n\tif (Buffer) {\n\t\treturn Buffer.from(str, 'utf-8').toString('base64');\n\t}\n\treturn base64ByteArray(new TextEncoder().encode(str));\n}\n\n/**\n * WHATWG forgiving-base64 normalization: strip ASCII whitespace, then drop trailing padding where\n * the spec allows it (total length a multiple of 4). `null` when the length can't be base64. Leaves\n * the alphabet check to the decoders, which make it faster than a regex over a multi-MB payload.\n */\nfunction stripBase64(str: string): string | null {\n\tlet data = str.replace(/[\\t\\n\\f\\r ]/g, '');\n\tif (data.length % 4 === 0) data = data.replace(/={1,2}$/, '');\n\treturn data.length % 4 === 1 ? null : data;\n}\n\n/** Shortest string treated as base64 (≈ 48 bytes): human-written strings rarely reach it. */\nexport const BASE64_DETECT_MIN_LENGTH = 64;\n\nconst B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n/**\n * Whether an untyped string is base64 content: the canonical padded form when confident, else\n * `null` (treat it as plain text). A heuristic; pass a `Uint8Array` when it must be exact.\n */\nexport function detectBase64Payload(str: string): string | null {\n\tconst data = stripBase64(str);\n\tif (data === null || data.length < BASE64_DETECT_MIN_LENGTH) return null;\n\tif (/[^A-Za-z0-9+/]/.test(data)) return null;\n\n\t// An encoder leaves the final character's unused low bits zero; otherwise re-encoding the\n\t// decoded bytes wouldn't reproduce the input.\n\tconst rem = data.length % 4;\n\tconst lastValue = B64_ALPHABET.indexOf(data.charAt(data.length - 1));\n\tif (rem === 2 && (lastValue & 0x0f) !== 0) return null;\n\tif (rem === 3 && (lastValue & 0x03) !== 0) return null;\n\n\treturn rem === 0 ? data : data + '=='.slice(0, 4 - rem);\n}\n\n/**\n * Decode base64 to bytes, normalizing and validating the same way in Node and browsers.\n * `packages/visualization/src/shared/encoding.ts` keeps a copy that throws its own error class;\n * change both together.\n *\n * @throws {ComputeError} `ENCODING_ERROR` if invalid, or `ENVIRONMENT_ERROR` if the runtime has no decoder\n */\nexport function decodeBase64ToBinary(base64File: string): Uint8Array {\n\tconst data = stripBase64(base64File);\n\tconst bytes = data === null ? null : decodeStripped(data);\n\tif (bytes === null) {\n\t\tthrow new ComputeError('Invalid base64 input.', ErrorCodes.ENCODING_ERROR, {\n\t\t\tcontext: { inputLength: base64File.length }\n\t\t});\n\t}\n\treturn bytes;\n}\n\n/** Decode {@link stripBase64} output; `null` when a character is outside the alphabet. */\nfunction decodeStripped(data: string): Uint8Array | null {\n\t// Buffer over atob in Node: faster, and skips the latin-1 string detour.\n\tconst Buffer = getNodeBuffer();\n\tif (Buffer) {\n\t\t// Buffer decodes leniently: `-` and `_` as base64url, a non-ASCII char as its low byte, and\n\t\t// any other stray char is skipped. Rule out the first two; a skip leaves the result short.\n\t\t// Several times faster than an alphabet regex over a multi-MB payload.\n\t\tif (Buffer.byteLength(data, 'utf8') !== data.length) return null;\n\t\tif (data.includes('-') || data.includes('_')) return null;\n\t\tconst decoded = Buffer.from(data, 'base64');\n\t\tif (decoded.length !== Math.floor((data.length * 3) / 4)) return null;\n\t\t// A small result is a view over Node's shared 8 KiB pool slab: copy it out, or anyone\n\t\t// touching `.buffer` sees unrelated pooled bytes. A large one owns its memory outright.\n\t\tconst ownsBuffer = decoded.byteOffset === 0 && decoded.buffer.byteLength === decoded.length;\n\t\treturn ownsBuffer ? new Uint8Array(decoded.buffer, 0, decoded.length) : new Uint8Array(decoded);\n\t}\n\tif (typeof globalThis.atob === 'function') {\n\t\tlet binary: string;\n\t\ttry {\n\t\t\tbinary = globalThis.atob(data);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t\tconst bytes = new Uint8Array(binary.length);\n\t\tfor (let i = 0; i < binary.length; i++) {\n\t\t\tbytes[i] = binary.charCodeAt(i) & 0xff;\n\t\t}\n\t\treturn bytes;\n\t}\n\n\tthrow new ComputeError(\n\t\t'Base64 decoding not supported in this environment.',\n\t\tErrorCodes.ENVIRONMENT_ERROR,\n\t\t{ context: { environmentInfo: 'atob or Buffer not available' } }\n\t);\n}\n\n/** UTF-8 byte length. Sizes every request body: `Buffer` counts without allocating the copy. */\nexport function utf8ByteLength(str: string): number {\n\tconst Buffer = getNodeBuffer();\n\treturn Buffer ? Buffer.byteLength(str, 'utf-8') : new TextEncoder().encode(str).length;\n}\n\nexport function base64ByteArray(bytes: Uint8Array): string {\n\tconst Buffer = getNodeBuffer();\n\tif (Buffer) {\n\t\treturn Buffer.from(bytes).toString('base64');\n\t}\n\tif (typeof globalThis.btoa === 'function') {\n\t\t// Chunked so memory doesn't peak at ~3× the input. CHUNK must be a multiple of 3: every\n\t\t// non-final chunk then encodes without padding, and the joined pieces equal a one-shot\n\t\t// encode. 32766 = 3 × 10922, well under fromCharCode.apply's argument limit.\n\t\tconst CHUNK = 32766;\n\t\tconst parts: string[] = [];\n\t\tfor (let i = 0; i < bytes.length; i += CHUNK) {\n\t\t\tparts.push(\n\t\t\t\tglobalThis.btoa(\n\t\t\t\t\tString.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK) as unknown as number[])\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\treturn parts.join('');\n\t}\n\tthrow new ComputeError(\n\t\t'Base64 encoding not supported in this environment.',\n\t\tErrorCodes.ENVIRONMENT_ERROR,\n\t\t{ context: { environmentInfo: 'btoa or Buffer not available' } }\n\t);\n}\n","import { utf8ByteLength } from '../utils/encoding';\n\n/**\n * Cap on an error body: it is read for its `code` and message only. A 500 is read in full instead,\n * since a partial success carries the computed values.\n */\nexport const ERROR_BODY_MAX_BYTES = 1 << 20;\n\nexport interface BodyText {\n\ttext: string;\n\t/** The body passed the limit. `text` holds only what arrived before the cut. */\n\ttruncated: boolean;\n}\n\n/** Read a body as text, cancelling the download once it passes `limit` UTF-8 bytes. */\nexport async function readBodyText(response: Response, limit = Infinity): Promise<BodyText> {\n\tif (limit === Infinity) return { text: await response.text(), truncated: false };\n\n\t// A body without a stream can't be cut short: read it, then apply the limit.\n\tif (!response.body) {\n\t\tconst text = await response.text();\n\t\treturn { text, truncated: utf8ByteLength(text) > limit };\n\t}\n\n\t// Refuse a declared oversize body unread. Not when compressed: the header then counts the\n\t// compressed bytes, not the ones read.\n\tconst declared = Number(response.headers.get('Content-Length'));\n\tif (!response.headers.has('Content-Encoding') && declared > limit) {\n\t\tawait response.body.cancel().catch(() => {});\n\t\treturn { text: '', truncated: true };\n\t}\n\n\tconst reader = response.body.getReader();\n\tconst decoder = new TextDecoder();\n\tconst parts: string[] = [];\n\tlet received = 0;\n\tfor (;;) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\treceived += value.byteLength;\n\t\tparts.push(decoder.decode(value, { stream: true }));\n\t\tif (received > limit) {\n\t\t\tawait reader.cancel().catch(() => {});\n\t\t\treturn { text: parts.join(''), truncated: true };\n\t\t}\n\t}\n\tparts.push(decoder.decode());\n\treturn { text: parts.join(''), truncated: false };\n}\n","import { getLogger } from '../utils/logger';\n\nimport type { ServerTiming } from '../types';\n\n/**\n * Parse a W3C Server Timing header, as the solve endpoint emits it:\n * `decode;dur=3, solve;dur=120, gh;dur=110, encode;dur=8, recomputed;desc=\"4/97\"`, or\n * `cache;desc=\"hit\"` when the server answered from its result cache. Null when it carries no\n * recognised metric.\n */\nexport function parseServerTiming(headerValue: string | null): ServerTiming | null {\n\tif (!headerValue) return null;\n\tconst timing: ServerTiming = { raw: headerValue };\n\tlet sawMetric = false;\n\tfor (const part of headerValue.split(',')) {\n\t\tconst [rawName, ...params] = part.trim().split(';');\n\t\tconst name = rawName.trim().toLowerCase();\n\t\tif (name === 'decode' || name === 'solve' || name === 'encode' || name === 'gh') {\n\t\t\tconst dur = Number(param(params, 'dur'));\n\t\t\tif (!Number.isFinite(dur)) continue;\n\t\t\ttiming[name] = dur;\n\t\t\tsawMetric = true;\n\t\t} else if (name === 'recomputed') {\n\t\t\tconst [ran, total] = (param(params, 'desc') ?? '').split('/').map(Number);\n\t\t\tif (!Number.isInteger(ran) || !Number.isInteger(total)) continue;\n\t\t\ttiming.recomputed = { ran, total };\n\t\t\tsawMetric = true;\n\t\t} else if (name === 'cache' && param(params, 'desc') === 'hit') {\n\t\t\ttiming.cacheHit = true;\n\t\t\tsawMetric = true;\n\t\t}\n\t}\n\treturn sawMetric ? timing : null;\n}\n\nfunction param(params: string[], key: string): string | undefined {\n\tfor (const p of params) {\n\t\tconst [k, ...v] = p.split('=');\n\t\tif (k.trim().toLowerCase() !== key) continue;\n\t\tconst value = v.join('=').trim();\n\t\treturn value.replace(/^\"(.*)\"$/, '$1');\n\t}\n\treturn undefined;\n}\n\nexport function fireServerTiming(\n\tresponse: Response,\n\trequestId: string,\n\tonServerTiming: ((timing: ServerTiming, requestId: string) => void) | undefined\n): void {\n\tif (!onServerTiming) return;\n\tconst timing = parseServerTiming(response.headers.get('Server-Timing'));\n\tif (!timing) return;\n\ttry {\n\t\tonServerTiming(timing, requestId);\n\t} catch (err) {\n\t\tgetLogger().debug(\n\t\t\t`onServerTiming callback threw ${err instanceof Error ? err.name : typeof err}`\n\t\t);\n\t}\n}\n","/** What fetch, or a body read, rejects with when its signal fires: a caller abort or a timeout. */\nexport function isAbortError(error: unknown): error is Error {\n\treturn error instanceof Error && (error.name === 'AbortError' || error.name === 'TimeoutError');\n}\n\n/** The most `AbortSignal.timeout` takes on Node, which wants a whole uint32. */\nexport const MAX_TIMEOUT_MS = 2 ** 32 - 1;\n\n/** `promise`'s outcome, or a rejection with `signal`'s reason if that fires first. */\nexport function raceSignal<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n\tif (!signal) return promise;\n\treturn new Promise<T>((resolve, reject) => {\n\t\tconst onAbort = () => reject(signal.reason);\n\t\tif (signal.aborted) onAbort();\n\t\tsignal.addEventListener('abort', onAbort, { once: true });\n\t\tpromise.then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));\n\t});\n}\n\n/**\n * Compose a caller-supplied AbortSignal with an optional timeout; `undefined` when neither was\n * given. `AbortSignal.timeout` rather than `setTimeout`: its timer isn't throttled in a hidden tab.\n */\nexport function composeSignal(\n\tcallerSignal: AbortSignal | undefined,\n\ttimeoutMs: number | undefined\n): { signal: AbortSignal | undefined; cleanup: () => void } {\n\tconst noCleanup = () => {};\n\tif (typeof timeoutMs !== 'number' || timeoutMs <= 0) {\n\t\treturn { signal: callerSignal, cleanup: noCleanup };\n\t}\n\n\tconst timeoutSignal = AbortSignal.timeout(Math.floor(timeoutMs));\n\tif (!callerSignal) return { signal: timeoutSignal, cleanup: noCleanup };\n\n\t// Not AbortSignal.any: it can't unregister its link on the caller's signal, so a long-lived\n\t// signal reused across solves would collect one registration per attempt.\n\tconst ctrl = new AbortController();\n\tconst sources = [callerSignal, timeoutSignal];\n\t// Forwarding the reason keeps fetch's rejection name ('TimeoutError' vs 'AbortError').\n\tconst onAbort = function (this: AbortSignal) {\n\t\tctrl.abort(this.reason);\n\t};\n\n\tfor (const s of sources) {\n\t\tif (s.aborted) {\n\t\t\tctrl.abort(s.reason);\n\t\t\tbreak;\n\t\t}\n\t\ts.addEventListener('abort', onAbort, { once: true });\n\t}\n\n\treturn {\n\t\tsignal: ctrl.signal,\n\t\tcleanup: () => {\n\t\t\tfor (const s of sources) s.removeEventListener('abort', onAbort);\n\t\t}\n\t};\n}\n","/**\n * A response's JSON text length, so byte-budgeted caches never re-serialize a huge tree. A WeakMap\n * rather than a field keeps `JSON.stringify(response)` clean; it is keyed by identity, so a derived\n * copy must re-register. `.length` stands in for UTF-8 bytes: the JSON is near-pure ASCII.\n */\n\nconst wireSizes = new WeakMap<object, number>();\n\n/** Record `response`'s wire size. No-op for non-object/null values. */\nexport function setResponseWireSize(response: unknown, size: number): void {\n\tif (typeof response !== 'object' || response === null) return;\n\twireSizes.set(response, size);\n}\n\n/** The wire size recorded for `response`, or undefined when never registered. */\nexport function getResponseWireSize(response: unknown): number | undefined {\n\tif (typeof response !== 'object' || response === null) return undefined;\n\treturn wireSizes.get(response);\n}\n","import { ComputeError, ErrorCodes, type ErrorCode } from '../errors';\nimport { getLogger } from '../utils/logger';\nimport { readField } from '../utils/read-field';\nimport { redactUrl } from '../utils/redact-url';\nimport { tryParseJson } from '../utils/try-parse-json';\nimport { fireServerTiming } from './server-timing';\nimport { ERROR_BODY_MAX_BYTES, readBodyText } from './read-body';\nimport { isAbortError } from './signal';\nimport { setResponseWireSize } from './wire-size';\n\nimport type { ComputeConfig } from '../types';\n\nexport interface RequestContext {\n\tfullUrl: string;\n\trequestId: string;\n\trequestSize: number;\n\tconfig: ComputeConfig;\n}\n\nexport type AttemptOutcome =\n\t| { ok: true; value: unknown }\n\t| { ok: false; cause: ComputeError; retryable: boolean; delayMs?: number };\n\n/** Upper bound for the raw server body stored on error `context.responseBody`. */\nconst MAX_CONTEXT_BODY_CHARS = 4096;\n\n/** An allowlist: errors reach logs and telemetry, and `Set-Cookie` or a proxy's token must not. */\nconst CONTEXT_HEADERS = [\n\t'content-length',\n\t'content-type',\n\t'retry-after',\n\t'server-timing',\n\t'www-authenticate',\n\t'x-request-id'\n];\n\nfunction truncateBody(body: string): string {\n\tif (body.length <= MAX_CONTEXT_BODY_CHARS) return body;\n\treturn `${body.slice(0, MAX_CONTEXT_BODY_CHARS)}… [truncated ${body.length - MAX_CONTEXT_BODY_CHARS} chars]`;\n}\n\nfunction contextHeaders(headers: Headers): Record<string, string> {\n\tconst kept: Record<string, string> = {};\n\tfor (const name of CONTEXT_HEADERS) {\n\t\tconst value = headers.get(name);\n\t\tif (value !== null) kept[name] = value;\n\t}\n\treturn kept;\n}\n\ninterface HttpFailure {\n\tstatus: number;\n\tstatusText: string;\n\tbodyHint: string;\n\tfullUrl: string;\n\trequestSize: number;\n}\n\ninterface StatusRule {\n\tcode: ErrorCode;\n\t/** Omitted: {@link plainMessage}. */\n\tmessage?: (failure: HttpFailure) => string;\n}\n\nconst plainMessage = ({ status, statusText, bodyHint }: HttpFailure) =>\n\t`HTTP ${status}: ${statusText}${bodyHint}`;\n\nconst labelled =\n\t(label: string) =>\n\t({ statusText, bodyHint }: HttpFailure) =>\n\t\t`${label}: ${statusText}${bodyHint}`;\n\nconst STATUS_RULES: Record<number, StatusRule> = {\n\t400: { code: ErrorCodes.VALIDATION_ERROR, message: labelled('Bad request') },\n\t401: { code: ErrorCodes.AUTH_ERROR },\n\t403: { code: ErrorCodes.AUTH_ERROR },\n\t404: {\n\t\tcode: ErrorCodes.NOT_FOUND,\n\t\tmessage: ({ fullUrl }) => `Endpoint not found: ${fullUrl}`\n\t},\n\t413: {\n\t\tcode: ErrorCodes.VALIDATION_ERROR,\n\t\tmessage: ({ requestSize }) => `Request too large: ${(requestSize / 1024).toFixed(2)}KB`\n\t},\n\t429: {\n\t\tcode: ErrorCodes.RATE_LIMIT,\n\t\tmessage: ({ bodyHint }) => `Rate limit exceeded${bodyHint}`\n\t},\n\t500: { code: ErrorCodes.COMPUTATION_ERROR, message: labelled('Server error') },\n\t502: { code: ErrorCodes.NETWORK_ERROR, message: labelled('Bad gateway') },\n\t503: { code: ErrorCodes.NETWORK_ERROR, message: labelled('Service unavailable') },\n\t504: { code: ErrorCodes.NETWORK_ERROR, message: labelled('Gateway timeout') }\n};\n\n/**\n * `rawBody` is what came over the wire and goes on `context.responseBody`; `detail` may be a\n * message rewritten from it and only feeds the error message.\n */\nfunction throwHttpError(\n\tresponse: Response,\n\tctx: RequestContext,\n\trawBody: string,\n\tdetail: string,\n\tserverCode: string | undefined\n): never {\n\tconst { status, statusText } = response;\n\tconst { fullUrl, requestId, requestSize, config } = ctx;\n\n\tconst trimmed = detail.trim();\n\tconst bodyHint = trimmed ? ` — ${trimmed.slice(0, 200)}${trimmed.length > 200 ? '…' : ''}` : '';\n\n\tconst rule: StatusRule = STATUS_RULES[status] ?? { code: ErrorCodes.UNKNOWN_ERROR };\n\tconst message = (rule.message ?? plainMessage)({\n\t\tstatus,\n\t\tstatusText,\n\t\tbodyHint,\n\t\tfullUrl,\n\t\trequestSize\n\t});\n\n\tconst codes = config.serverErrorCodes;\n\t// The code is server-supplied: `constructor` must not resolve through the prototype.\n\tconst serverMapped =\n\t\tserverCode && codes && Object.hasOwn(codes, serverCode) ? codes[serverCode] : undefined;\n\n\tthrow new ComputeError(message, serverMapped ?? rule.code, {\n\t\tstatusCode: status,\n\t\tcontext: {\n\t\t\turl: fullUrl,\n\t\t\trequestId,\n\t\t\tmethod: 'POST',\n\t\t\trequestSize,\n\t\t\tserverUrl: config.serverUrl,\n\t\t\tresponseBody: rawBody ? truncateBody(rawBody) : undefined,\n\t\t\tresponseHeaders: contextHeaders(response.headers)\n\t\t}\n\t});\n}\n\n/** A 3xx, or a browser's `redirect: 'manual'` stand-in for one, which hides the status. */\nexport function isRedirect(response: Response): boolean {\n\treturn response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400);\n}\n\n/** Following a redirect would send the API key header, which fetch keeps cross-origin, to the target. */\nfunction redirectError(response: Response, ctx: RequestContext): ComputeError {\n\tconst header = response.headers.get('Location');\n\t// The target is chosen by the server, so it can carry credentials or a token in its URL.\n\tconst location = header ? redactUrl(header) : undefined;\n\treturn new ComputeError(\n\t\t`Server redirected the request${location ? ` to ${location}` : ''}. Redirects are not followed, ` +\n\t\t\t'so credentials never leave serverUrl: set serverUrl to the address it redirects to.',\n\t\tErrorCodes.INVALID_CONFIG,\n\t\t{\n\t\t\t...(response.status > 0 && { statusCode: response.status }),\n\t\t\tcontext: { url: ctx.fullUrl, requestId: ctx.requestId, location }\n\t\t}\n\t);\n}\n\nfunction tooLargeError(response: Response, ctx: RequestContext): ComputeError {\n\tconst { maxResponseBytes } = ctx.config;\n\treturn new ComputeError(\n\t\t`Response body exceeded maxResponseBytes (${maxResponseBytes} bytes)`,\n\t\tErrorCodes.RESPONSE_TOO_LARGE,\n\t\t{\n\t\t\tstatusCode: response.status,\n\t\t\tcontext: { url: ctx.fullUrl, requestId: ctx.requestId, maxResponseBytes }\n\t\t}\n\t);\n}\n\n/** Sizes and identifiers only: bodies and solver messages can hold user data. */\nfunction logDebug(ctx: RequestContext, message: string): void {\n\tif (ctx.config.debug) getLogger().debug(`Request [${ctx.requestId}] ${message}`);\n}\n\n/** Values plus errors or warnings: the solve ran, so this 500 is a partial success. */\nfunction partialSuccessCounts(parsed: unknown): { errors: number; warnings: number } | null {\n\tconst solveErrors = readField<unknown[]>(parsed, 'errors');\n\tconst solveWarnings = readField<unknown[]>(parsed, 'warnings');\n\tif (!readField(parsed, 'values') || !(solveErrors || solveWarnings)) return null;\n\treturn { errors: solveErrors?.length ?? 0, warnings: solveWarnings?.length ?? 0 };\n}\n\n/**\n * Compute8's exception handler sends `{ error, message: \"<category>: <detail>\", stackTrace? }`;\n * older servers sent PascalCase `Message`/`ExceptionType`/`StackTrace`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- an unvalidated error body, read defensively\nfunction serverErrorDetail(parsed: any): string | undefined {\n\tconst serverMessage =\n\t\t(typeof parsed?.message === 'string' && parsed.message) ||\n\t\t(typeof parsed?.Message === 'string' && parsed.Message) ||\n\t\t'';\n\tif (serverMessage) {\n\t\tconst exceptionType = (typeof parsed?.ExceptionType === 'string' && parsed.ExceptionType) || '';\n\t\tconst prefix = exceptionType ? `${exceptionType}: ` : '';\n\t\tconst stack = parsed?.stackTrace ?? parsed?.StackTrace;\n\t\tconst stackStr = Array.isArray(stack) ? stack.join('\\n') : stack || '';\n\t\treturn `${prefix}${serverMessage}${stackStr ? `\\n${stackStr}` : ''}`;\n\t}\n\tif (parsed?.error) {\n\t\treturn typeof parsed.error === 'string' ? parsed.error : JSON.stringify(parsed.error, null, 2);\n\t}\n\treturn undefined;\n}\n\nasync function handleErrorStatus(\n\tresponse: Response,\n\tctx: RequestContext,\n\tresponseTime: number\n): Promise<AttemptOutcome> {\n\tconst { maxResponseBytes = Infinity, onServerTiming } = ctx.config;\n\tconst isServerError = response.status === 500;\n\tconst { text: rawBody, truncated } = await readBodyText(\n\t\tresponse,\n\t\tisServerError ? maxResponseBytes : Math.min(ERROR_BODY_MAX_BYTES, maxResponseBytes)\n\t);\n\tif (truncated && isServerError) throw tooLargeError(response, ctx);\n\n\tlogDebug(\n\t\tctx,\n\t\t`failed with HTTP ${response.status} in ${responseTime}ms (${rawBody.length} chars)`\n\t);\n\n\tconst parsed = truncated ? undefined : tryParseJson(rawBody);\n\tlet detail = rawBody;\n\n\tif (parsed !== undefined) {\n\t\tconst partial = isServerError ? partialSuccessCounts(parsed) : null;\n\t\tif (partial) {\n\t\t\tsetResponseWireSize(parsed, rawBody.length);\n\t\t\tlogDebug(\n\t\t\t\tctx,\n\t\t\t\t`completed in ${responseTime}ms with ${partial.errors} solver error(s), ${partial.warnings} warning(s)`\n\t\t\t);\n\t\t\tfireServerTiming(response, ctx.requestId, onServerTiming);\n\t\t\treturn { ok: true, value: parsed };\n\t\t}\n\t\tdetail = serverErrorDetail(parsed) ?? rawBody;\n\t}\n\n\t// Any error status can carry a `code`, not just 500.\n\tconst code = (parsed as { code?: unknown } | null | undefined)?.code;\n\tthrowHttpError(response, ctx, rawBody, detail, typeof code === 'string' ? code : undefined);\n}\n\nasync function parseSuccessBody(response: Response, ctx: RequestContext): Promise<AttemptOutcome> {\n\tconst { maxResponseBytes = Infinity, signal } = ctx.config;\n\ttry {\n\t\t// Text, then parse, so the wire size rides along for byte-budgeted caches.\n\t\tconst { text: rawBody, truncated } = await readBodyText(response, maxResponseBytes);\n\t\tif (truncated) throw tooLargeError(response, ctx);\n\t\tconst parsed = JSON.parse(rawBody);\n\t\tsetResponseWireSize(parsed, rawBody.length);\n\t\treturn { ok: true, value: parsed };\n\t} catch (error) {\n\t\t// The signal stays armed while the body streams: an abort here is the caller's or the\n\t\t// timeout's, not a truncated body. A caller can abort with any reason, so ask the signal.\n\t\tif (signal?.aborted || isAbortError(error) || error instanceof ComputeError) throw error;\n\t\t// A declared non-JSON body (captive portal, proxy login page) comes back identical on a\n\t\t// retry. Anything else that fails to parse was most likely cut off mid-stream.\n\t\tconst contentType = (response.headers.get('Content-Type') ?? '').toLowerCase();\n\t\tconst declaredNonJson = contentType !== '' && !contentType.includes('json');\n\t\tif (declaredNonJson) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t`Server returned a non-JSON response (Content-Type: ${contentType}) — check the server URL / proxy configuration`,\n\t\t\t\tErrorCodes.INVALID_RESPONSE,\n\t\t\t\t{\n\t\t\t\t\tstatusCode: response.status,\n\t\t\t\t\tcontext: { url: ctx.fullUrl, requestId: ctx.requestId, contentType },\n\t\t\t\t\toriginalError: error\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tok: false,\n\t\t\tretryable: true,\n\t\t\tcause: new ComputeError('Failed to parse JSON response', ErrorCodes.NETWORK_ERROR, {\n\t\t\t\tstatusCode: response.status,\n\t\t\t\tcontext: { url: ctx.fullUrl, requestId: ctx.requestId },\n\t\t\t\toriginalError: error\n\t\t\t})\n\t\t};\n\t}\n}\n\n/** Throws a {@link ComputeError} for anything a retry can't fix. */\nexport async function handleResponse(\n\tresponse: Response,\n\tctx: RequestContext,\n\tstartTime: number\n): Promise<AttemptOutcome> {\n\tconst responseTime = Math.round(performance.now() - startTime);\n\n\tif (isRedirect(response)) {\n\t\tawait response.body?.cancel().catch(() => {});\n\t\tthrow redirectError(response, ctx);\n\t}\n\n\tif (!response.ok) return handleErrorStatus(response, ctx, responseTime);\n\n\tlogDebug(ctx, `completed in ${responseTime}ms`);\n\tfireServerTiming(response, ctx.requestId, ctx.config.onServerTiming);\n\treturn parseSuccessBody(response, ctx);\n}\n","/**\n * Why a liveness probe failed, and whether waiting can change the answer.\n *\n * `retryable` is the whole point: a powered-off VM and a booting one both read\n * as \"offline\" from a single probe, but only one of them will ever come up. A\n * caller that can't tell them apart has to assume the optimistic case and burn\n * its full retry window on a machine that is simply off.\n */\nexport type ProbeVerdict =\n\t/** Nothing is listening on the port — the host answered, and said no. */\n\t| 'refused'\n\t/** The hostname does not resolve. */\n\t| 'dns'\n\t/** No answer within the timeout: a booting host swallows packets rather than refusing them. */\n\t| 'timeout'\n\t/** The server answered, but rejected the probe's credentials (401/403). */\n\t| 'unauthorized'\n\t/** The server answered with a non-2xx that isn't an auth rejection. */\n\t| 'http_error'\n\t/** Connection failed in a way we can't attribute. */\n\t| 'unknown';\n\nexport interface ProbeFailure {\n\tverdict: ProbeVerdict;\n\t/** Whether retrying the same probe could plausibly succeed later. */\n\tretryable: boolean;\n\t/** Operator-facing sentence. Never includes the API key. */\n\tsummary: string;\n}\n\n/**\n * Matched against the probe's error string, which `probeServer` builds with undici's `cause` in\n * it. A browser collapses every failure into an opaque \"Failed to fetch\", which reads as unknown.\n */\nconst REFUSED_PATTERN = /ECONNREFUSED|ERR_CONNECTION_REFUSED|connection refused/i;\nconst DNS_PATTERN = /ENOTFOUND|EAI_AGAIN|getaddrinfo|ERR_NAME_NOT_RESOLVED|dns/i;\nconst TIMEOUT_PATTERN =\n\t/TimeoutError|ETIMEDOUT|ECONNRESET|EHOSTUNREACH|ENETUNREACH|abort|timed? ?out/i;\n\n/**\n * Classify a `ComputeServerStats.probeServer` result.\n *\n * A DNS failure is treated as retryable even though a missing record won't fix\n * itself: `EAI_AGAIN` is a *timeout* talking to the resolver, and the two are\n * not distinguishable from the error alone. Retrying a genuinely-wrong hostname\n * costs one window; giving up on a resolver hiccup breaks a server that is fine.\n *\n * @param probe - The `{ online, status?, error? }` a probe returned. An `online`\n * probe is not a failure and yields `null`.\n */\nexport function classifyProbeFailure(probe: {\n\tonline: boolean;\n\tstatus?: number;\n\terror?: string;\n}): ProbeFailure | null {\n\tif (probe.online) return null;\n\n\tif (probe.status !== undefined) {\n\t\tif (probe.status === 401 || probe.status === 403) {\n\t\t\treturn {\n\t\t\t\tverdict: 'unauthorized',\n\t\t\t\tretryable: false,\n\t\t\t\tsummary: `The server rejected the liveness probe with HTTP ${probe.status} — check the API key.`\n\t\t\t};\n\t\t}\n\t\t// 5xx from a proxy in front of a starting child does clear on its own;\n\t\t// a 4xx means we asked for something this server will never serve.\n\t\tconst retryable = probe.status >= 500;\n\t\treturn {\n\t\t\tverdict: 'http_error',\n\t\t\tretryable,\n\t\t\tsummary: retryable\n\t\t\t\t? `The server answered HTTP ${probe.status} — it may still be starting up.`\n\t\t\t\t: `The server answered HTTP ${probe.status}, which will not change on retry.`\n\t\t};\n\t}\n\n\tconst error = probe.error ?? '';\n\n\tif (REFUSED_PATTERN.test(error)) {\n\t\treturn {\n\t\t\tverdict: 'refused',\n\t\t\tretryable: false,\n\t\t\tsummary:\n\t\t\t\t'Connection refused — the host is reachable but nothing is listening on that port. ' +\n\t\t\t\t'Rhino.Compute is not running, or the URL names the wrong port.'\n\t\t};\n\t}\n\n\tif (DNS_PATTERN.test(error)) {\n\t\treturn {\n\t\t\tverdict: 'dns',\n\t\t\tretryable: true,\n\t\t\tsummary: 'The server hostname could not be resolved — check the URL and DNS.'\n\t\t};\n\t}\n\n\tif (TIMEOUT_PATTERN.test(error)) {\n\t\treturn {\n\t\t\tverdict: 'timeout',\n\t\t\tretryable: true,\n\t\t\tsummary:\n\t\t\t\t'No response before the timeout — the machine may be powered off, blocked by a ' +\n\t\t\t\t'firewall, or still booting.'\n\t\t};\n\t}\n\n\treturn {\n\t\tverdict: 'unknown',\n\t\tretryable: true,\n\t\tsummary: error ? `The connection failed: ${error}` : 'The connection failed.'\n\t};\n}\n","/**\n * Gzip request bodies, but only for a server that advertised it: its last response carried\n * `Accept-Encoding: gzip` (RFC 7694), as Rhino.Compute does with `RHINO_COMPUTE_COMPRESSION` on.\n * An older server reads a gzipped body as malformed JSON. A response without the header forgets\n * the server, so one failed request recovers from compression being turned off. Browsers only\n * learn it same-origin: CORS hides the header otherwise.\n */\nconst gzipServers = new Set<string>();\n\n/** Smaller bodies gain too little; the server uses the same cut-off for its responses. */\nexport const GZIP_MIN_BYTES = 1024;\n\nexport function noteRequestEncodings(serverUrl: string, response: Response): void {\n\tconst accepted = response.headers?.get('Accept-Encoding');\n\tif (accepted && /(^|[\\s,])gzip($|[\\s,;])/i.test(accepted)) gzipServers.add(serverUrl);\n\telse gzipServers.delete(serverUrl);\n}\n\n/** Synchronous, so an uncompressed request reaches fetch in the same tick as before. */\nexport function shouldGzipRequest(serverUrl: string, byteLength: number): boolean {\n\treturn (\n\t\tbyteLength >= GZIP_MIN_BYTES &&\n\t\tgzipServers.has(serverUrl) &&\n\t\ttypeof CompressionStream !== 'undefined'\n\t);\n}\n\nexport async function gzip(body: string): Promise<Uint8Array<ArrayBuffer>> {\n\tconst stream = new Blob([body]).stream().pipeThrough(new CompressionStream('gzip'));\n\treturn new Uint8Array(await new Response(stream).arrayBuffer());\n}\n","import { abortedByCaller, ComputeError, ErrorCodes } from '../errors';\nimport { utf8ByteLength } from '../utils/encoding';\nimport { getLogger } from '../utils/logger';\nimport { validateServerUrl } from '../server/validate-server-url';\nimport { ERROR_BODY_MAX_BYTES, readBodyText } from './read-body';\nimport { buildUrl, buildHeaders, generateRequestId } from './request';\nimport { gzip, noteRequestEncodings, shouldGzipRequest } from './request-compression';\nimport { handleResponse, type AttemptOutcome, type RequestContext } from './response';\nimport { backoffDelay, isRetryableStatus, resolveRetryPolicy, retryDelay, sleep } from './retry';\nimport { composeSignal, isAbortError, MAX_TIMEOUT_MS } from './signal';\n\nimport type { ComputeConfig, RetryPolicy } from '../types';\n\ninterface AttemptContext extends RequestContext {\n\tendpoint: string;\n\t/** Validated: the key request compression is remembered under. */\n\tserverUrl: string;\n\tbody: string;\n\theaders: Record<string, string>;\n\terrorContext: Record<string, unknown>;\n}\n\nasync function attemptFetch(\n\tctx: AttemptContext,\n\tpolicy: Required<RetryPolicy>,\n\tattempt: number,\n\tisFinal: boolean\n): Promise<AttemptOutcome> {\n\tconst { signal, cleanup } = composeSignal(ctx.config.signal, ctx.config.timeoutMs);\n\tconst startTime = performance.now();\n\tconst send = ctx.config.fetch ?? fetch;\n\n\ttry {\n\t\tconst gzipped = shouldGzipRequest(ctx.serverUrl, ctx.requestSize) ? await gzip(ctx.body) : null;\n\t\tconst response = await send(ctx.fullUrl, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: gzipped ?? ctx.body,\n\t\t\theaders: gzipped ? { ...ctx.headers, 'Content-Encoding': 'gzip' } : ctx.headers,\n\t\t\tsignal,\n\t\t\t// handleResponse rejects a redirect rather than let the API key follow it.\n\t\t\tredirect: 'manual'\n\t\t});\n\t\tnoteRequestEncodings(ctx.serverUrl, response);\n\n\t\tif (!isFinal && isRetryableStatus(response.status, policy)) {\n\t\t\t// Drain so the connection can be reused. The final attempt skips this branch so\n\t\t\t// handleResponse reads the body into the error instead.\n\t\t\tawait readBodyText(response, ERROR_BODY_MAX_BYTES).catch(() => {});\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tretryable: true,\n\t\t\t\tdelayMs: retryDelay(response.headers.get('Retry-After'), attempt, policy),\n\t\t\t\tcause: new ComputeError(\n\t\t\t\t\t`HTTP ${response.status} ${response.statusText} (will retry)`,\n\t\t\t\t\tresponse.status === 429 ? ErrorCodes.RATE_LIMIT : ErrorCodes.NETWORK_ERROR,\n\t\t\t\t\t{ statusCode: response.status, context: ctx.errorContext }\n\t\t\t\t)\n\t\t\t};\n\t\t}\n\n\t\treturn await handleResponse(response, ctx, startTime);\n\t} catch (error) {\n\t\treturn classifyFetchError(error, ctx);\n\t} finally {\n\t\tcleanup();\n\t}\n}\n\nfunction classifyFetchError(error: unknown, ctx: AttemptContext): AttemptOutcome {\n\tif (error instanceof ComputeError) return { ok: false, retryable: false, cause: error };\n\n\t// Fetch rejects with `signal.reason`, and a caller can abort with anything.\n\tif (ctx.config.signal?.aborted) {\n\t\treturn { ok: false, retryable: false, cause: abortedByCaller(ctx.errorContext, error) };\n\t}\n\n\tif (isAbortError(error)) {\n\t\t// With no timeout armed, the runtime aborted on its own (e.g. an undici socket teardown):\n\t\t// as transient as a network drop, but not a timeout.\n\t\tconst { timeoutMs } = ctx.config;\n\t\tconst cause =\n\t\t\ttypeof timeoutMs === 'number' && timeoutMs > 0\n\t\t\t\t? new ComputeError(`Request timed out after ${timeoutMs}ms`, ErrorCodes.TIMEOUT_ERROR, {\n\t\t\t\t\t\tcontext: { ...ctx.errorContext, timeoutMs },\n\t\t\t\t\t\toriginalError: error\n\t\t\t\t\t})\n\t\t\t\t: new ComputeError(\n\t\t\t\t\t\t`Request aborted by the runtime (${error.name}): ${error.message}`,\n\t\t\t\t\t\tErrorCodes.NETWORK_ERROR,\n\t\t\t\t\t\t{ context: ctx.errorContext, originalError: error }\n\t\t\t\t\t);\n\t\treturn { ok: false, retryable: true, cause };\n\t}\n\n\tif (error instanceof TypeError) {\n\t\t// In a browser an opaque fetch TypeError is most often CORS, which the browser hides by\n\t\t// design. An offline network throws the same error, so it stays retryable either way.\n\t\tconst inBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';\n\t\tconst cause = inBrowser\n\t\t\t? new ComputeError(\n\t\t\t\t\t`Request failed: ${error.message} — in a browser this usually means a CORS misconfiguration on the server (or a network failure; the browser does not distinguish them)`,\n\t\t\t\t\tErrorCodes.CORS_ERROR,\n\t\t\t\t\t{ context: ctx.errorContext, originalError: error }\n\t\t\t\t)\n\t\t\t: new ComputeError(`Network error: ${error.message}`, ErrorCodes.NETWORK_ERROR, {\n\t\t\t\t\tcontext: ctx.errorContext,\n\t\t\t\t\toriginalError: error\n\t\t\t\t});\n\t\treturn { ok: false, retryable: true, cause };\n\t}\n\n\treturn {\n\t\tok: false,\n\t\tretryable: false,\n\t\tcause: new ComputeError(\n\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\tErrorCodes.UNKNOWN_ERROR,\n\t\t\t{\n\t\t\t\tcontext: ctx.errorContext,\n\t\t\t\toriginalError: error\n\t\t\t}\n\t\t)\n\t};\n}\n\nfunction requireNumber(\n\tname: string,\n\tvalue: number | undefined,\n\tisValid: (n: number) => boolean\n): void {\n\tif (value !== undefined && !isValid(value)) {\n\t\tthrow new ComputeError(`${name} is not usable: ${value}`, ErrorCodes.INVALID_CONFIG, {\n\t\t\tcontext: { [name]: value }\n\t\t});\n\t}\n}\n\n/** `NaN` fails every comparison, so unchecked it disables a loop's exit or a limit. */\nexport function validateNumbers({ timeoutMs, maxResponseBytes, retry }: ComputeConfig): void {\n\tconst notNegative = (n: number) => n >= 0;\n\trequireNumber('timeoutMs', timeoutMs, (n) => n <= MAX_TIMEOUT_MS);\n\trequireNumber('maxResponseBytes', maxResponseBytes, notNegative);\n\trequireNumber('retry.attempts', retry?.attempts, (n) => notNegative(n) && Number.isFinite(n));\n\trequireNumber('retry.baseDelayMs', retry?.baseDelayMs, notNegative);\n\trequireNumber('retry.maxDelayMs', retry?.maxDelayMs, notNegative);\n}\n\n/**\n * POST `args` as JSON to `<serverUrl>/<endpoint>` with retry, timeout and abort handling, and\n * return the parsed response. Most callers want a higher-level API.\n *\n * `R` names the expected response shape; nothing checks it, so the default `unknown` forces a\n * narrowing.\n *\n * @example\n * const response = await fetchCompute('grasshopper', args, {\n * serverUrl: 'https://my-server.com',\n * timeoutMs: 30_000,\n * retry: { attempts: 2 },\n * signal: controller.signal\n * });\n */\nexport async function fetchCompute<R = unknown>(\n\tendpoint: string,\n\targs: Record<string, any>,\n\tconfig: ComputeConfig\n): Promise<R> {\n\tconst requestId = generateRequestId();\n\t// Fail a circular or BigInt payload as INVALID_INPUT, before anything touches the network.\n\tlet body: string;\n\ttry {\n\t\tbody = JSON.stringify(args);\n\t} catch (error) {\n\t\tthrow new ComputeError(\n\t\t\t`Request body is not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\tErrorCodes.INVALID_INPUT,\n\t\t\t{\n\t\t\t\tcontext: { endpoint, requestId },\n\t\t\t\toriginalError: error\n\t\t\t}\n\t\t);\n\t}\n\t// UTF-8 bytes: `body.length` counts UTF-16 code units and undercounts non-ASCII payloads.\n\tconst requestSize = utf8ByteLength(body);\n\t// Shape only: which hosts to block is the backend's call, made where its client is built.\n\tconst serverUrl = validateServerUrl(config.serverUrl, { blockedHosts: [] });\n\tvalidateNumbers(config);\n\tconst fullUrl = buildUrl(endpoint, serverUrl);\n\tconst headers = buildHeaders(requestId, config);\n\tconst retryPolicy = resolveRetryPolicy(config.retry);\n\tconst totalAttempts = retryPolicy.attempts + 1;\n\n\tif (config.debug) {\n\t\tconst sizeKb = (requestSize / 1024).toFixed(2);\n\t\tgetLogger().debug(`Starting compute request [${requestId}]: ${endpoint} (${sizeKb}KB)`);\n\t}\n\n\tconst ctx: AttemptContext = {\n\t\tendpoint,\n\t\tserverUrl,\n\t\tbody,\n\t\trequestSize,\n\t\tfullUrl,\n\t\trequestId,\n\t\theaders,\n\t\tconfig,\n\t\terrorContext: { serverUrl, url: fullUrl, endpoint, requestId, requestSize }\n\t};\n\n\tfor (let attempt = 0; ; attempt++) {\n\t\tconst isFinal = attempt >= retryPolicy.attempts;\n\t\tconst result = await attemptFetch(ctx, retryPolicy, attempt, isFinal);\n\n\t\tif (result.ok) return result.value as R;\n\t\tif (isFinal || !result.retryable) throw result.cause;\n\n\t\tconst delayMs = result.delayMs ?? backoffDelay(attempt, retryPolicy);\n\t\tif (config.debug) {\n\t\t\tgetLogger().debug(\n\t\t\t\t`Request [${requestId}] retrying after ${delayMs}ms (attempt ${attempt + 2}/${totalAttempts}): ${result.cause.code}`\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tawait sleep(delayMs, config.signal);\n\t\t} catch {\n\t\t\tthrow abortedByCaller(ctx.errorContext, result.cause);\n\t\t}\n\t}\n}\n","/** A definition identified by a stable key, with its bytes loaded only when an upload is unavoidable. */\nexport interface DefinitionRef {\n\t/**\n\t * Identity of immutable bytes, such as a version UUID; never a mutable name or path. Every\n\t * cache trusts the key without looking at the bytes, so two contents sharing a key are served\n\t * each other's solves, with no diagnostic.\n\t */\n\tkey: string;\n\t/**\n\t * Called when the solve runs, not when it is scheduled: a solve aborted while queued never\n\t * loads. `signal` fires on an abort or at `timeoutMs`, and the solve stops waiting then; pass it\n\t * on to stop the read as well.\n\t */\n\tload: (signal?: AbortSignal) => Promise<Uint8Array>;\n}\n\n/**\n * A URL is sent as a pointer, a string is uploaded as base64 (or encoded text), bytes are\n * uploaded as-is. Strings and bytes are content-hashed for caching; a `DefinitionRef` is keyed\n * by its `key`.\n */\nexport type SolveDefinition = string | Uint8Array | DefinitionRef;\n\nexport function isDefinitionRef(definition: SolveDefinition): definition is DefinitionRef {\n\treturn (\n\t\ttypeof definition === 'object' &&\n\t\t!(definition instanceof Uint8Array) &&\n\t\ttypeof definition.key === 'string' &&\n\t\ttypeof definition.load === 'function'\n\t);\n}\n\n/** A URL is already a server-side reference: it is never uploaded or keyed by server cache key. */\nexport function isDefinitionUrl(definition: SolveDefinition): definition is string {\n\treturn typeof definition === 'string' && /^https?:\\/\\//i.test(definition);\n}\n","import type { FileData } from './types';\n\n/**\n * The `Sub Folder` convention, as authored on Selva's Grasshopper file components.\n *\n * `::` nests, matching Rhino's layer separator (`ROOT::Panels`), and `/` and `\\` are accepted\n * because people type them out of habit. The plugin normalizes to `/` before sending, so these\n * helpers also cover payloads from an older plugin that still emit `::`.\n *\n * The first segment is the **root**, which names the archive rather than becoming a folder inside\n * it: `ROOT::Panels` downloads as `ROOT.zip` containing `Panels/…`. Files sharing a root travel\n * together; distinct roots produce separate archives.\n */\n\n/**\n * Split a `Sub Folder` value into folder segments; `/`, `\\` and `::` all separate.\n *\n * Exported because anything rendering a folder tree has to agree with the archive on where the\n * boundaries are — splitting on `/` alone leaves `Main::Panels` as one literal segment and shows\n * a folder named after the separator.\n */\nexport const subFolderSegments = (subFolder: string | undefined): string[] =>\n\t(subFolder ?? '')\n\t\t.replace(/::/g, '/')\n\t\t.replace(/\\\\/g, '/')\n\t\t.split('/')\n\t\t.map((segment) => segment.trim())\n\t\t.filter((segment) => segment !== '');\n\n/** First `Sub Folder` segment, or `''` when the file sits at the top level. */\nconst rootOf = (file: Pick<FileData, 'subFolder'>): string =>\n\tsubFolderSegments(file.subFolder)[0] ?? '';\n\n/**\n * Everything below the root, as a `/`-joined path. The root names the archive, so repeating it\n * inside would nest it twice.\n */\nexport const pathBelowRoot = (file: Pick<FileData, 'subFolder'>): string =>\n\tsubFolderSegments(file.subFolder).slice(1).join('/');\n\n/**\n * Group files by their `Sub Folder` root, preserving encounter order.\n *\n * Rootless files group under `''` — a caller naming archives supplies its own fallback for that\n * bucket. Useful beyond downloading: a consumer writing to disk gets the same grouping without\n * touching the DOM.\n */\nexport const groupFilesByRoot = <T extends Pick<FileData, 'subFolder'>>(\n\tfiles: readonly T[]\n): Array<{ root: string; files: T[] }> => {\n\tconst groups = new Map<string, T[]>();\n\n\tfor (const file of files) {\n\t\tconst root = rootOf(file);\n\t\tconst bucket = groups.get(root);\n\t\tif (bucket) bucket.push(file);\n\t\telse groups.set(root, [file]);\n\t}\n\n\treturn Array.from(groups, ([root, grouped]) => ({ root, files: grouped }));\n};\n\n/**\n * Make a root usable as a download filename: path separators and the characters Windows forbids\n * would otherwise reach the download attribute verbatim and produce a broken or misdirected save.\n */\nexport const toArchiveName = (root: string): string => {\n\tconst safe = root\n\t\t.replace(/[/\\\\:*?\"<>|]/g, '_')\n\t\t.replace(/\\s+/g, ' ')\n\t\t.trim()\n\t\t.replace(/^\\.+/, '');\n\n\treturn safe === '' ? 'files' : safe;\n};\n","import { ComputeError, ErrorCodes } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\nimport { decodeBase64ToBinary } from '@/core/utils/encoding';\nimport { readField } from '@/core/utils/read-field';\nimport { redactUrl } from '@/core/utils/redact-url';\n\nimport type { DownloadOptions, FileBaseInfo, FileData, ProcessedFile } from './types';\nimport { groupFilesByRoot, pathBelowRoot, subFolderSegments, toArchiveName } from './sub-folder';\n\n/**\n * Decode the files in a compute response, plus any external `additionalFiles`, without\n * downloading them. Never throws: an unnamed or undecodable item and a failed external fetch are\n * logged and dropped, the rest still resolve. Colliding archive paths are renamed\n * (`model.txt` → `model-2.txt`), so a consumer keying by `path` loses nothing.\n */\nexport const extractFilesFromComputeResponse = async (\n\tdownloadableFiles: FileData[],\n\tadditionalFiles: FileBaseInfo[] | FileBaseInfo | null = null\n): Promise<ProcessedFile[]> => {\n\tconst processedFiles = decodeResponseFiles(downloadableFiles);\n\n\tif (additionalFiles) {\n\t\tconst filesArray = Array.isArray(additionalFiles) ? additionalFiles : [additionalFiles];\n\t\tprocessedFiles.push(...(await fetchRemoteFiles(filesArray)));\n\t}\n\n\tconst taken = new Set<string>();\n\treturn processedFiles.map((file) => {\n\t\tconst path = uniqueArchivePath(file.path, taken);\n\t\ttaken.add(path);\n\t\tif (path === file.path) return file;\n\t\tgetLogger().warn(`Duplicate archive path \"${file.path}\" — storing as \"${path}\".`);\n\t\treturn { ...file, path, fileName: path.slice(path.lastIndexOf('/') + 1) };\n\t});\n};\n\n/** Downloads files from a compute response as a ZIP archive. */\nexport const downloadFileData = async (\n\tdownloadableFiles: FileData[],\n\tfileFoldername: string,\n\tadditionalFiles: FileBaseInfo[] | FileBaseInfo | null = null,\n\toptions: DownloadOptions = {}\n): Promise<void> => {\n\tif (typeof document === 'undefined' || typeof Blob === 'undefined') {\n\t\tthrow new ComputeError(\n\t\t\t'File download functionality is only available in browser environments. This function requires the DOM API (document, Blob).',\n\t\t\tErrorCodes.BROWSER_ONLY,\n\t\t\t{\n\t\t\t\tcontext: {\n\t\t\t\t\tenvironment: typeof window !== 'undefined' ? 'browser (SSR)' : 'Node.js',\n\t\t\t\t\tdocumentAvailable: typeof document !== 'undefined',\n\t\t\t\t\tblobAvailable: typeof Blob !== 'undefined'\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\ttry {\n\t\tconst processedFiles = await extractFilesFromComputeResponse(\n\t\t\tdownloadableFiles,\n\t\t\tadditionalFiles\n\t\t);\n\t\tawait createAndDownloadZip(processedFiles, fileFoldername, options.saveBlob ?? saveFile);\n\t} catch (err) {\n\t\tif (err instanceof ComputeError) {\n\t\t\tthrow err;\n\t\t}\n\t\tthrow new ComputeError(\n\t\t\t'Failed to download files from compute response',\n\t\t\tErrorCodes.INVALID_STATE,\n\t\t\t{\n\t\t\t\tcontext: { fileCount: downloadableFiles.length, zipName: fileFoldername },\n\t\t\t\toriginalError: err\n\t\t\t}\n\t\t);\n\t}\n};\n\n/**\n * Download one archive per `Sub Folder` root (see {@link groupFilesByRoot}). Files with no root go\n * into `fallbackName`, so a definition that never sets `Sub Folder` downloads as one archive.\n *\n * Archives are saved one at a time: browsers discard concurrent downloads issued in the same tick.\n * More than one save per gesture may prompt the user for permission.\n *\n * @param downloadableFiles - `FileData` items from the compute response.\n * @param fallbackName - Archive name for files with no `Sub Folder` root.\n * @param additionalFiles - Extra files to package; they carry no root and join the fallback archive.\n * @param options - `saveBlob` replaces the anchor download for every archive.\n */\nexport const downloadFileDataByRoot = async (\n\tdownloadableFiles: FileData[],\n\tfallbackName: string,\n\tadditionalFiles: FileBaseInfo[] | FileBaseInfo | null = null,\n\toptions: DownloadOptions = {}\n): Promise<void> => {\n\tconst groups = groupFilesByRoot(downloadableFiles);\n\n\t// Extras alone still deserve an archive; without this they'd be dropped for having no root.\n\tif (groups.length === 0) {\n\t\tawait downloadFileData([], fallbackName, additionalFiles, options);\n\t\treturn;\n\t}\n\n\t// Extras belong to the definition rather than to any one root, so they ride with the rootless\n\t// archive — or with the first one when every file is rooted, rather than being dropped.\n\tconst extrasRoot = (groups.find((group) => group.root === '') ?? groups[0]).root;\n\n\tfor (const { root, files } of groups) {\n\t\tawait downloadFileData(\n\t\t\troot === '' ? files : files.map((file) => ({ ...file, subFolder: pathBelowRoot(file) })),\n\t\t\troot === '' ? fallbackName : toArchiveName(root),\n\t\t\troot === extrasRoot ? additionalFiles : null,\n\t\t\toptions\n\t\t);\n\t}\n};\n\n/**\n * Zip-slip defense for a server-controlled path: drop `.`, `..` and drive-prefixed segments\n * (`C:`, and `C:evil`, which is relative to that drive's working directory) so no entry escapes\n * the extraction directory. `''` when nothing safe remains.\n */\nconst sanitizeArchivePath = (raw: string): string =>\n\tsubFolderSegments(raw)\n\t\t.filter((segment) => segment !== '.' && segment !== '..' && !/^[a-zA-Z]:/.test(segment))\n\t\t.join('/');\n\n/** The `isBase64Encoded` wire flag. Some server branches send booleans as strings (`\"True\"`). */\nexport const isBase64Flag = (flag: unknown): boolean =>\n\tflag === true || (typeof flag === 'string' && flag.trim().toLowerCase() === 'true');\n\nconst joinArchivePath = (subFolder: string, name: string): string =>\n\tsubFolder !== '' ? `${subFolder}/${name}` : name;\n\nconst decodeResponseFiles = (dataItems: FileData[]): ProcessedFile[] => {\n\tconst processedFiles: ProcessedFile[] = [];\n\n\tdataItems.forEach((item) => {\n\t\tconst rawName = readField<string>(item, 'fileName') ?? '';\n\t\tconst rawType = readField<string>(item, 'fileType') ?? '';\n\t\tconst fileName = sanitizeArchivePath(`${rawName}${rawType}`);\n\t\tif (fileName === '') {\n\t\t\tgetLogger().warn(\n\t\t\t\t`Skipping file with unusable name \"${rawName}${rawType}\": no safe archive path remains after sanitization.`\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst rawSubFolder = readField<string>(item, 'subFolder');\n\t\tconst subFolder = rawSubFolder ? sanitizeArchivePath(rawSubFolder) : '';\n\t\tconst filePath = joinArchivePath(subFolder, fileName);\n\n\t\tconst data = readField<unknown>(item, 'data');\n\t\tif (typeof data !== 'string' || data === '') {\n\t\t\tgetLogger().warn(`Skipping file \"${filePath}\": item carries no usable data.`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet content: Uint8Array | string = data;\n\t\tif (isBase64Flag(readField<unknown>(item, 'isBase64Encoded'))) {\n\t\t\ttry {\n\t\t\t\tcontent = decodeBase64ToBinary(data);\n\t\t\t} catch (err) {\n\t\t\t\tgetLogger().warn(`Skipping file \"${filePath}\": base64 decode failed.`, err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst metadata = readField<Record<string, string>>(item, 'metadata');\n\t\tprocessedFiles.push({\n\t\t\tfileName,\n\t\t\tcontent,\n\t\t\tpath: filePath,\n\t\t\tsubFolder,\n\t\t\t...(metadata ? { metadata } : {})\n\t\t});\n\t});\n\n\treturn processedFiles;\n};\n\n/** One dead URL must degrade the batch, not stall it forever. */\nconst REMOTE_FILE_TIMEOUT_MS = 30_000;\n\nconst fetchRemoteFiles = async (refs: FileBaseInfo[]): Promise<ProcessedFile[]> => {\n\tconst fetched = await Promise.all(\n\t\trefs.map(async (file) => {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(file.filePath, {\n\t\t\t\t\tsignal: AbortSignal.timeout(REMOTE_FILE_TIMEOUT_MS)\n\t\t\t\t});\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tgetLogger().warn(`Failed to fetch additional file: ${redactUrl(file.filePath)}`);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst arrayBuffer = await response.arrayBuffer();\n\t\t\t\tconst safeName = sanitizeArchivePath(file.fileName);\n\t\t\t\tif (safeName === '') {\n\t\t\t\t\tgetLogger().warn(`Skipping fetched file with unusable name: ${file.fileName}`);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst subFolder = file.subFolder ? sanitizeArchivePath(file.subFolder) : '';\n\t\t\t\treturn {\n\t\t\t\t\tfileName: safeName,\n\t\t\t\t\tcontent: new Uint8Array(arrayBuffer),\n\t\t\t\t\tpath: joinArchivePath(subFolder, safeName),\n\t\t\t\t\t// No `metadata`: an external URL carries no GH authoring context.\n\t\t\t\t\tsubFolder\n\t\t\t\t} as ProcessedFile;\n\t\t\t} catch (error) {\n\t\t\t\t// The error's name only: a bad URL's message quotes it, signed query included.\n\t\t\t\tconst reason = error instanceof Error ? error.name : 'unknown error';\n\t\t\t\tgetLogger().error(`Error fetching additional file ${redactUrl(file.filePath)}: ${reason}`);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t})\n\t);\n\n\treturn fetched.filter((f): f is ProcessedFile => f !== null);\n};\n\nasync function createAndDownloadZip(\n\tfiles: ProcessedFile[],\n\tzipName: string,\n\tsave: (blob: Blob, fileName: string) => unknown\n): Promise<void> {\n\tconst { zip, strToU8 } = await import('fflate');\n\n\t// Paths are already unique: extractFilesFromComputeResponse renames collisions. fflate\n\t// flattens entries into a plain object of its own, where a root entry named `__proto__` is lost.\n\tconst taken = new Set(files.map((file) => file.path));\n\tconst zipData: Record<string, Uint8Array> = {};\n\tfor (const file of files) {\n\t\tconst path = file.path === '__proto__' ? uniqueArchivePath(file.path, taken) : file.path;\n\t\ttaken.add(path);\n\t\tzipData[path] = typeof file.content === 'string' ? strToU8(file.content) : file.content;\n\t}\n\n\t// Async `zip` deflates on a worker thread; `zipSync` would block the UI on large exports.\n\tconst zipped = await new Promise<Uint8Array>((resolve, reject) => {\n\t\tzip(zipData, { level: 6 }, (err, data) => (err ? reject(err) : resolve(data)));\n\t});\n\n\tconst blob = new Blob([zipped as BlobPart], { type: 'application/zip' });\n\tawait save(blob, `${zipName}.zip`);\n}\n\n/**\n * First archive path not already taken in `taken`, disambiguating with a\n * numeric suffix before the extension: `dir/model.txt` → `dir/model-2.txt`.\n */\nfunction uniqueArchivePath(path: string, taken: ReadonlySet<string>): string {\n\tif (!taken.has(path)) return path;\n\tconst slash = path.lastIndexOf('/');\n\tconst dot = path.lastIndexOf('.');\n\tconst stemEnd = dot > slash ? dot : path.length;\n\tconst stem = path.slice(0, stemEnd);\n\tconst ext = path.slice(stemEnd);\n\tfor (let i = 2; ; i++) {\n\t\tconst candidate = `${stem}-${i}${ext}`;\n\t\tif (!taken.has(candidate)) return candidate;\n\t}\n}\n\nfunction saveFile(blob: Blob, filename: string) {\n\tconst url = URL.createObjectURL(blob);\n\tconst a = document.createElement('a');\n\ta.href = url;\n\ta.download = filename;\n\t// Firefox requires the anchor to be in the DOM for the click to download.\n\tdocument.body.appendChild(a);\n\ta.click();\n\ta.remove();\n\t// Revoking synchronously can abort the download: the browser pins the blob only once it starts.\n\tsetTimeout(() => URL.revokeObjectURL(url), 10_000);\n}\n"],"mappings":"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"}
@@ -0,0 +1,247 @@
1
+ declare const ErrorCodes: {
2
+ /** Connection failure, a gateway status (502/503/504), or a 2xx body cut off mid-stream. */
3
+ readonly NETWORK_ERROR: "NETWORK_ERROR";
4
+ /** HTTP 401 or 403: the server rejected the credentials. */
5
+ readonly AUTH_ERROR: "AUTH_ERROR";
6
+ /** The server rejected the request (HTTP 400, 413), or an input failed a client-side check. */
7
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
8
+ /** The server failed the solve (HTTP 500), or the solve reported errors. */
9
+ readonly COMPUTATION_ERROR: "COMPUTATION_ERROR";
10
+ /** No response within `ComputeConfig.timeoutMs`. */
11
+ readonly TIMEOUT_ERROR: "TIMEOUT_ERROR";
12
+ /** A browser fetch failed with no response: most often CORS, which the browser hides. */
13
+ readonly CORS_ERROR: "CORS_ERROR";
14
+ /** HTTP 404: the endpoint doesn't exist on this server. Never retried. */
15
+ readonly NOT_FOUND: "NOT_FOUND";
16
+ /** HTTP 429. Retried, honouring `Retry-After`, when the policy allows (`RetryPolicy.retryOn429`). */
17
+ readonly RATE_LIMIT: "RATE_LIMIT";
18
+ /**
19
+ * A 2xx with a body the client can't use: declared non-JSON (a captive portal, a proxy login page)
20
+ * or JSON that isn't an object. Never retried.
21
+ */
22
+ readonly INVALID_RESPONSE: "INVALID_RESPONSE";
23
+ /**
24
+ * The response body passed `ComputeConfig.maxResponseBytes`; the download was cancelled there.
25
+ * Never retried. `context` carries `{ maxResponseBytes }`.
26
+ */
27
+ readonly RESPONSE_TOO_LARGE: "RESPONSE_TOO_LARGE";
28
+ /** Any failure not classified by another code. */
29
+ readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
30
+ /** An object was used after `dispose()`, or a file download failed part-way. */
31
+ readonly INVALID_STATE: "INVALID_STATE";
32
+ /** A caller-supplied argument is unusable: an empty definition, an unserializable body. */
33
+ readonly INVALID_INPUT: "INVALID_INPUT";
34
+ /**
35
+ * Bad configuration: an invalid `serverUrl`, an unsendable header, an unusable number, a server
36
+ * that answers with a redirect, an incomplete logger, an interval out of range.
37
+ */
38
+ readonly INVALID_CONFIG: "INVALID_CONFIG";
39
+ /** A browser-only API (file download) was called outside a browser. */
40
+ readonly BROWSER_ONLY: "BROWSER_ONLY";
41
+ /** The runtime has no base64 codec: neither `Buffer` nor `atob`/`btoa`. */
42
+ readonly ENVIRONMENT_ERROR: "ENVIRONMENT_ERROR";
43
+ /** A string that should be base64 isn't. */
44
+ readonly ENCODING_ERROR: "ENCODING_ERROR";
45
+ /** An input's `default` had a shape the normalizer didn't recognize. */
46
+ readonly MALFORMED_DEFAULT: "MALFORMED_DEFAULT";
47
+ /** Latest-wins scheduling: a newer call replaced this one. */
48
+ readonly SUPERSEDED: "SUPERSEDED";
49
+ /** The scheduler or a caller-supplied `AbortSignal` aborted this call. */
50
+ readonly ABORTED: "ABORTED";
51
+ /**
52
+ * The queue was at `maxQueueDepth`, so the call was rejected without queueing. Retryable after a
53
+ * back-off. `context` carries `{ queueDepth, maxQueueDepth }`.
54
+ */
55
+ readonly QUEUE_FULL: "QUEUE_FULL";
56
+ /**
57
+ * The call waited longer than `queueWaitMs` without starting, and was rejected rather than run
58
+ * stale. Retryable. `context` carries `{ waitedMs, queueWaitMs }`.
59
+ */
60
+ readonly QUEUE_TIMEOUT: "QUEUE_TIMEOUT";
61
+ /**
62
+ * The server no longer holds the definition a `pointer` named (evicted, restarted, another
63
+ * child). Retry with the full definition. Read from the error body's
64
+ * `code: "definition_not_cached"`.
65
+ */
66
+ readonly DEFINITION_NOT_CACHED: "DEFINITION_NOT_CACHED";
67
+ };
68
+ type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
69
+ /** Error with a typed `code`: switch on it rather than on the message. */
70
+ declare class ComputeError extends Error {
71
+ readonly code: ErrorCode;
72
+ readonly statusCode?: number;
73
+ readonly context?: Record<string, unknown>;
74
+ readonly originalError?: Error;
75
+ constructor(message: string, code?: ErrorCode, options?: {
76
+ statusCode?: number;
77
+ context?: Record<string, unknown>;
78
+ originalError?: unknown;
79
+ });
80
+ }
81
+ /** A backend's error-body `code` → one of our `ErrorCodes`. */
82
+ type ServerErrorCodeMap = Record<string, ErrorCode>;
83
+ /**
84
+ * Exponential backoff with jitter; a server `Retry-After` replaces the backoff for that attempt.
85
+ *
86
+ * Requests are POSTs: a connection lost after the body was sent may already have run on the
87
+ * server, and the retry runs it again. A solve is deterministic, so that wastes work but corrupts
88
+ * nothing; a non-idempotent endpoint should keep `attempts: 0`.
89
+ */
90
+ interface RetryPolicy {
91
+ /** Maximum number of retry attempts after the initial request (default: 0). */
92
+ attempts?: number;
93
+ /** Base delay in milliseconds for exponential backoff (default: 500). */
94
+ baseDelayMs?: number;
95
+ /** Upper bound for backoff delay (default: 30_000). */
96
+ maxDelayMs?: number;
97
+ /** Whether to retry on 429 responses (default: true — honors Retry-After). */
98
+ retryOn429?: boolean;
99
+ }
100
+ interface ComputeConfig {
101
+ serverUrl: string;
102
+ /** Optional API key for authenticating with the server. Sent as {@link apiKeyHeader}. */
103
+ apiKey?: string;
104
+ /**
105
+ * Header name carrying `apiKey`. Defaults to `'RhinoComputeKey'` — the name
106
+ * rhino.compute expects. A different backend sets its own.
107
+ */
108
+ apiKeyHeader?: string;
109
+ /** Sent verbatim as the `Authorization` header, so include the scheme (`Bearer …`). */
110
+ authToken?: string;
111
+ /**
112
+ * Extra request headers, e.g. routing hints a reverse proxy reads. A header the transport sets
113
+ * (credentials, `X-Request-ID`, `Content-Type`) wins over a caller header of any casing.
114
+ */
115
+ headers?: Record<string, string>;
116
+ /** Log each request at debug level through the configured logger (see `setLogger`). */
117
+ debug?: boolean;
118
+ /** Silence the warning logged when a call runs in a browser, where the config is exposed. */
119
+ suppressBrowserWarning?: boolean;
120
+ /**
121
+ * Per-attempt timeout, re-armed on every retry: the worst case is `(attempts + 1) × timeoutMs`
122
+ * plus backoff. Pass `signal` for an overall deadline. On Node it can't outlast undici's own
123
+ * `headersTimeout`, and neither can `0`: see {@link fetch}. Loading a `DefinitionRef` gets its
124
+ * own `timeoutMs`, before the first attempt.
125
+ */
126
+ timeoutMs?: number;
127
+ /**
128
+ * Sends requests in place of the global `fetch`. A Node host with solves past undici's default
129
+ * `headersTimeout` needs one, bound to a dispatcher with a longer limit.
130
+ */
131
+ fetch?: typeof fetch;
132
+ /** Retry policy for transient errors. Default: no retries. */
133
+ retry?: RetryPolicy;
134
+ /**
135
+ * Cancels the request, including during a retry backoff. Composes with `timeoutMs`: whichever
136
+ * fires first wins.
137
+ */
138
+ signal?: AbortSignal;
139
+ /**
140
+ * Largest response body, in UTF-8 bytes, read before the download is cancelled with
141
+ * `RESPONSE_TOO_LARGE`. A Grasshopper solve leaves out the uploaded definition the server echoes
142
+ * back. Default: no limit.
143
+ */
144
+ maxResponseBytes?: number;
145
+ /**
146
+ * Maps the `code` field of this backend's JSON error bodies to our `ErrorCodes`. A listed
147
+ * code outranks the status-based mapping: the server's production mode scrubs the human message
148
+ * but not the code. Core knows no backend's codes; the client supplies its own table.
149
+ */
150
+ serverErrorCodes?: ServerErrorCodeMap;
151
+ /**
152
+ * Called with the server's `Server-Timing` breakdown when the response carries a recognised
153
+ * metric, including on a 500 partial success. `requestId` matches the `X-Request-ID` header.
154
+ * A throwing callback doesn't fail the request.
155
+ */
156
+ onServerTiming?: (timing: ServerTiming, requestId: string) => void;
157
+ }
158
+ /** The config fields that decide how a request authenticates. */
159
+ type ComputeAuth = Pick<ComputeConfig, 'apiKey' | 'apiKeyHeader' | 'authToken' | 'headers'>;
160
+ /** The solve endpoint's `Server-Timing`, durations in ms. `raw` keeps the whole header, unrecognised metrics included. */
161
+ interface ServerTiming {
162
+ decode?: number;
163
+ solve?: number;
164
+ encode?: number;
165
+ /** The part of `solve` spent inside Grasshopper; the rest collects the outputs. */
166
+ gh?: number;
167
+ /** Objects the solve ran out of all it holds. Few ran when only a downstream input changed. */
168
+ recomputed?: {
169
+ ran: number;
170
+ total: number;
171
+ };
172
+ /** The server answered from its result cache and solved nothing. */
173
+ cacheHit?: boolean;
174
+ raw: string;
175
+ }
176
+ /** A definition identified by a stable key, with its bytes loaded only when an upload is unavoidable. */
177
+ interface DefinitionRef {
178
+ /**
179
+ * Identity of immutable bytes, such as a version UUID; never a mutable name or path. Every
180
+ * cache trusts the key without looking at the bytes, so two contents sharing a key are served
181
+ * each other's solves, with no diagnostic.
182
+ */
183
+ key: string;
184
+ /**
185
+ * Called when the solve runs, not when it is scheduled: a solve aborted while queued never
186
+ * loads. `signal` fires on an abort or at `timeoutMs`, and the solve stops waiting then; pass it
187
+ * on to stop the read as well.
188
+ */
189
+ load: (signal?: AbortSignal) => Promise<Uint8Array>;
190
+ }
191
+ /**
192
+ * A URL is sent as a pointer, a string is uploaded as base64 (or encoded text), bytes are
193
+ * uploaded as-is. Strings and bytes are content-hashed for caching; a `DefinitionRef` is keyed
194
+ * by its `key`.
195
+ */
196
+ type SolveDefinition = string | Uint8Array | DefinitionRef;
197
+ declare function isDefinitionRef(definition: SolveDefinition): definition is DefinitionRef;
198
+ /** A file as a compute response carries it; {@link extractFilesFromComputeResponse} decodes it. */
199
+ type FileData = {
200
+ /** Base filename without extension (e.g., "model") */
201
+ fileName: string;
202
+ /** File content, base64-encoded or plain string depending on isBase64Encoded flag */
203
+ data: string;
204
+ /** File extension including the dot (e.g., ".3dm", ".json"). Appended to fileName to create the full filename */
205
+ fileType: string;
206
+ /** Whether data is base64-encoded. If true, must be decoded to binary before use */
207
+ isBase64Encoded: boolean;
208
+ /** Directory path for archive organization (e.g., "subfolder/nested"). Empty string for root-level files */
209
+ subFolder: string;
210
+ /** Arbitrary metadata attached in Grasshopper. Not interpreted by compute; passed through for downstream consumers. May be absent on older payloads */
211
+ metadata?: Record<string, string>;
212
+ };
213
+ /** A decoded or fetched file, ready to archive or hand to a caller. */
214
+ type ProcessedFile = {
215
+ /** Filename including extension (e.g., "model.3dm"). Holds a nested path when the name had one. */
216
+ fileName: string;
217
+ /** File content as binary data (Uint8Array for decoded base64 or fetched binary) or text */
218
+ content: Uint8Array | string;
219
+ /** File path for archive organization (e.g., "subfolder/model.3dm") */
220
+ path: string;
221
+ /**
222
+ * The sanitized `subFolder`, kept apart from `path` because re-splitting a renamed duplicate
223
+ * path is ambiguous. `''` is the archive root; absent on a hand-built `ProcessedFile`.
224
+ */
225
+ subFolder?: string;
226
+ /** Carried through from {@link FileData}; absent when it had none, and on fetched files. */
227
+ metadata?: Record<string, string>;
228
+ };
229
+ /** An external file to fetch over HTTP and package alongside the response's own files. */
230
+ type FileBaseInfo = {
231
+ /** Destination filename for the file in the archive or result set (e.g., "additional-data.json") */
232
+ fileName: string;
233
+ /** URL to fetch the file from. Must be accessible from the runtime environment */
234
+ filePath: string;
235
+ /** Optional directory path for archive organization (e.g., "extras/docs"). When omitted, file lands at archive root */
236
+ subFolder?: string;
237
+ };
238
+ type DownloadOptions = {
239
+ /**
240
+ * Saves each finished archive instead of the default `<a download>` click. For hosts where an
241
+ * anchor download doesn't work, such as an embedded WebView that hands saves to native code.
242
+ * Awaited when it returns a promise, so archives still save one at a time.
243
+ */
244
+ saveBlob?: (blob: Blob, fileName: string) => unknown;
245
+ };
246
+ export { DefinitionRef as a, ComputeAuth as c, ServerErrorCodeMap as d, ServerTiming as f, ErrorCodes as h, ProcessedFile as i, ComputeConfig as l, ErrorCode as m, FileBaseInfo as n, SolveDefinition as o, ComputeError as p, FileData as r, isDefinitionRef as s, DownloadOptions as t, RetryPolicy as u };
247
+ //# sourceMappingURL=types-Dm9QFVvA.d.ts.map