@selvajs/compute 4.0.0-beta.1 → 4.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/chunk-AYH5O3VG.js +4 -0
- package/dist/chunk-AYH5O3VG.js.map +1 -0
- package/dist/chunk-MLPWPV6F.cjs +4 -0
- package/dist/chunk-MLPWPV6F.cjs.map +1 -0
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +81 -56
- package/dist/core.d.ts +81 -56
- package/dist/core.js +1 -1
- package/dist/grasshopper.cjs +1 -1
- package/dist/grasshopper.cjs.map +1 -1
- package/dist/grasshopper.d.cts +371 -65
- package/dist/grasshopper.d.ts +371 -65
- package/dist/grasshopper.js +1 -1
- package/dist/grasshopper.js.map +1 -1
- package/dist/handle-files-DMK9bCOW.d.cts +231 -0
- package/dist/handle-files-DMK9bCOW.d.ts +231 -0
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +0 -1
- package/package.json +1 -1
- package/dist/chunk-BCVDB6AE.js +0 -2
- package/dist/chunk-BCVDB6AE.js.map +0 -1
- package/dist/chunk-BVMGO7IG.cjs +0 -2
- package/dist/chunk-BVMGO7IG.cjs.map +0 -1
- package/dist/chunk-LLUNR66G.cjs +0 -4
- package/dist/chunk-LLUNR66G.cjs.map +0 -1
- package/dist/chunk-XT3XWDDV.js +0 -4
- package/dist/chunk-XT3XWDDV.js.map +0 -1
- package/dist/handle-files-DcA24IkS.d.cts +0 -478
- package/dist/handle-files-DcA24IkS.d.ts +0 -478
package/dist/grasshopper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/grasshopper/server/compute-server-stats.ts","../src/core/utils/warnings.ts","../src/grasshopper/solve.ts","../src/grasshopper/scheduler/stable-hash.ts","../src/grasshopper/scheduler/solve-scheduler.ts","../src/grasshopper/client/grasshopper-client.ts","../src/grasshopper/io/output/rhino-decoder.ts","../src/grasshopper/io/output/response-processors.ts","../src/grasshopper/client/grasshopper-response-processor.ts","../src/grasshopper/data-tree/tree-path.ts","../src/grasshopper/io/input/numeric-rounding.ts","../src/grasshopper/io/input/transformers.ts","../src/grasshopper/io/input/input-type-parsers.ts","../src/grasshopper/io/input/normalize-default.ts","../src/grasshopper/io/input/input-processors.ts","../src/grasshopper/io/normalize-schema.ts","../src/grasshopper/io/definition-io.ts","../src/grasshopper/io/schema-endpoint.ts","../src/grasshopper/data-tree/data-tree.ts"],"sourcesContent":["import { ComputeError, ErrorCodes } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\nimport { validateServerUrl } from '@/core/server/validate-server-url';\n\n/**\n * ComputeServerStats provides methods to query Rhino Compute server statistics.\n *\n * @public Use this for server health monitoring and statistics.\n *\n * @example\n * ```typescript\n * const stats = new ComputeServerStats('http://localhost:6500', 'your-api-key');\n *\n * try {\n * const isOnline = await stats.isServerOnline();\n * const children = await stats.getActiveChildren();\n * const version = await stats.getVersion();\n *\n * // Or get everything at once\n * const allStats = await stats.getServerStats();\n * } finally {\n * await stats.dispose(); // Clean up resources\n * }\n * ```\n */\nexport default class ComputeServerStats {\n\tprivate readonly serverUrl: string;\n\tprivate readonly apiKey?: string;\n\tprivate disposed = false;\n\tprivate activeMonitors: Set<() => void> = new Set();\n\tprivate activeTimeouts: Set<ReturnType<typeof setTimeout>> = new Set();\n\n\t/** Timeout (ms) for the fast read/monitoring endpoints. */\n\tprivate static readonly DEFAULT_TIMEOUT_MS = 5000;\n\n\t/** Timeout (ms) for child-lifecycle POSTs — a cold Windows child can take ~30s to spawn. */\n\tprivate static readonly LIFECYCLE_TIMEOUT_MS = 60_000;\n\n\t/** Floor for `monitor()`'s `intervalMs` — anything lower hot-loops the server. */\n\tprivate static readonly MIN_MONITOR_INTERVAL_MS = 100;\n\n\t/**\n\t * @param serverUrl - Base URL of the Rhino Compute server with http:// or https:// scheme (e.g., 'http://localhost:6500')\n\t * @param apiKey - Optional API key for authentication\n\t */\n\tconstructor(serverUrl: string, apiKey?: string) {\n\t\tthis.serverUrl = validateServerUrl(serverUrl);\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Build request headers with optional API key.\n\t */\n\tprivate buildHeaders(): Record<string, string> {\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Content-Type': 'application/json'\n\t\t};\n\n\t\tif (this.apiKey) {\n\t\t\theaders['RhinoComputeKey'] = this.apiKey;\n\t\t}\n\n\t\treturn headers;\n\t}\n\n\t/**\n\t * `fetch` wrapper that aborts after `timeoutMs` so a hung connection can't stall\n\t * a probe (or the `monitor()` loop) forever. Pass `0` to disable the timeout.\n\t */\n\tprivate fetchWithTimeout(\n\t\turl: string,\n\t\tinit: RequestInit = {},\n\t\ttimeoutMs: number = ComputeServerStats.DEFAULT_TIMEOUT_MS\n\t): Promise<Response> {\n\t\t// Merge caller headers OVER the defaults instead of `{ headers: …, ...init }`,\n\t\t// where any `init.headers` would silently replace the whole set (dropping\n\t\t// the API key). BOTH sides go through `new Headers()` so every key is\n\t\t// normalized (lowercased) identically — a caller's `Content-Type` replaces\n\t\t// the default instead of coexisting as a differently-cased duplicate that\n\t\t// the runtime would combine into \"application/json, text/plain\".\n\t\tconst merged = new Headers(this.buildHeaders());\n\t\tif (init.headers) {\n\t\t\tnew Headers(init.headers).forEach((value, key) => {\n\t\t\t\tmerged.set(key, value);\n\t\t\t});\n\t\t}\n\t\tconst headers: Record<string, string> = {};\n\t\tmerged.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tconst requestInit: RequestInit = { ...init, headers };\n\t\tif (timeoutMs > 0 && !requestInit.signal) {\n\t\t\trequestInit.signal = AbortSignal.timeout(timeoutMs);\n\t\t}\n\t\treturn fetch(url, requestInit);\n\t}\n\n\t/**\n\t * Check if the server is online.\n\t *\n\t * This is a single-sample probe: it returns `true` only on a 2xx from the\n\t * proxy liveness root `/`, and `false` for every other outcome (non-2xx,\n\t * network error, or timeout). A cold or briefly-busy-but-up server can therefore\n\t * read as offline — callers that gate on this (e.g. client construction)\n\t * should retry rather than treat a single `false` as authoritative.\n\t *\n\t * @param timeoutMs - Abort the probe after this many ms (default: 5000).\n\t * Pass `0` to disable the timeout. Prevents a hung connection from\n\t * stalling the caller indefinitely.\n\t */\n\tpublic async isServerOnline(timeoutMs: number = 5000): Promise<boolean> {\n\t\tthis.ensureNotDisposed();\n\n\t\t// The rhino.compute proxy has no `/healthcheck` route; its real liveness\n\t\t// signal is `GET /`, which returns \"compute.rhino3d running\". Probing an\n\t\t// unknown path would instead be forwarded to a child and only tells us the\n\t\t// proxy can reach one.\n\t\tconst url = `${this.serverUrl}/`;\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(url, { method: 'GET' }, timeoutMs);\n\n\t\t\treturn response.ok;\n\t\t} catch (err) {\n\t\t\tgetLogger().debug('[ComputeServerStats] Fetch error:', err);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Get the number of active child processes on the server.\n\t *\n\t * By default the proxy's `/activechildren` endpoint will *spawn* children up\n\t * to the configured count if none are running, then report the count — which\n\t * wakes (and bills) an idle server. Pass `{ initialize: false }` for a passive\n\t * read that reports the current count without spawning; use this for\n\t * monitoring or before a purge/probe where you must not wake the server.\n\t *\n\t * @param options.initialize - When `false`, append `?initialize=false` so the\n\t * server reports without spawning. Defaults to `true` (the server's default).\n\t * @returns Number of active children, or null if unavailable\n\t */\n\tpublic async getActiveChildren(options: { initialize?: boolean } = {}): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\tconst { initialize = true } = options;\n\t\tconst url = initialize\n\t\t\t? `${this.serverUrl}/activechildren`\n\t\t\t: `${this.serverUrl}/activechildren?initialize=false`;\n\n\t\t// `initialize` mode may spawn children before answering — give it the\n\t\t// lifecycle budget; the passive read stays on the short default.\n\t\tconst timeoutMs = initialize\n\t\t\t? ComputeServerStats.LIFECYCLE_TIMEOUT_MS\n\t\t\t: ComputeServerStats.DEFAULT_TIMEOUT_MS;\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(url, {}, timeoutMs);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch active children:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Strict integer parse: `parseInt` would accept garbage-prefixed bodies\n\t\t\t// (e.g. an HTML error page starting \"302 Found\" → 302 children).\n\t\t\tconst text = (await response.text()).trim();\n\t\t\tif (!/^\\d+$/.test(text)) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Invalid active children response:', text);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn parseInt(text, 10);\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching active children:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get the server version information.\n\t *\n\t * @returns Version object with rhino, compute, and git_sha, or null if unavailable\n\t */\n\tpublic async getVersion(): Promise<{\n\t\trhino: string;\n\t\tcompute: string;\n\t\tgit_sha: string | null;\n\t} | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/version`);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch version:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Read body as text first, then try JSON.parse — avoids the\n\t\t\t// \"Body has already been read\" error if response.json() fails.\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn {\n\t\t\t\t\trhino: json.rhino ?? '',\n\t\t\t\t\tcompute: json.compute ?? '',\n\t\t\t\t\tgit_sha: json.git_sha ?? null\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\treturn { rhino: text, compute: '', git_sha: null };\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching version:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get the plugins installed on the server.\n\t *\n\t * Returns a `name → version` map of non-core plugins the server has loaded,\n\t * or `null` if the request failed. Pass `kind` to choose which inventory:\n\t * `'gh'` (default) lists Grasshopper add-on assemblies via\n\t * `/plugins/gh/installed`; `'rhino'` lists Rhino plugins via\n\t * `/plugins/rhino/installed`. Plugins that ship with Rhino / are core\n\t * libraries are excluded by the server.\n\t *\n\t * @param kind - `'gh'` for Grasshopper add-ons (default) or `'rhino'` for Rhino plugins.\n\t * @returns Map of plugin name to version, or `null` on failure.\n\t *\n\t * @example\n\t * ```ts\n\t * const gh = await stats.getInstalledPlugins(); // Grasshopper add-ons\n\t * const selvaVersion = gh?.['Selva'] ?? null;\n\t * ```\n\t */\n\tpublic async getInstalledPlugins(\n\t\tkind: 'gh' | 'rhino' = 'gh'\n\t): Promise<Record<string, string> | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/plugins/${kind}/installed`);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn(`[ComputeServerStats] Failed to fetch ${kind} plugins:`, response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn json && typeof json === 'object' ? (json as Record<string, string>) : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn(`[ComputeServerStats] Error fetching ${kind} plugins:`, err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get comprehensive server statistics.\n\t * Fetches all available server information in parallel.\n\t *\n\t * @returns Object containing server status and available stats\n\t */\n\tpublic async getServerStats(): Promise<{\n\t\tisOnline: boolean;\n\t\tversion?: { rhino: string; compute: string; git_sha: string | null };\n\t\tactiveChildren?: number;\n\t}> {\n\t\tthis.ensureNotDisposed();\n\n\t\tconst isOnline = await this.isServerOnline();\n\n\t\tif (!isOnline) {\n\t\t\treturn { isOnline: false };\n\t\t}\n\n\t\t// Passive child count — never spawn from a stats read, or merely viewing\n\t\t// server health would wake (and bill) an idle server.\n\t\tconst [version, activeChildren] = await Promise.all([\n\t\t\tthis.getVersion(),\n\t\t\tthis.getActiveChildren({ initialize: false })\n\t\t]);\n\n\t\treturn {\n\t\t\tisOnline: true,\n\t\t\t...(version && { version }),\n\t\t\t...(activeChildren !== null && { activeChildren })\n\t\t};\n\t}\n\n\t/**\n\t * Purge the server's solve-results / URL-data cache.\n\t *\n\t * POSTs to `cache/purge` and returns the number of entries removed, or `null`\n\t * if the request failed. This clears cached solve responses and fetched\n\t * definition-URL data; it does NOT evict the definition cache (active\n\t * `pointer` references stay valid).\n\t *\n\t * **Caveat:** `cache/purge` is forwarded by the rhino.compute proxy to a\n\t * single round-robin-selected child, so in a multi-child deployment one call\n\t * purges one child's cache. Call repeatedly (or size the pool to 1) if you\n\t * need a fleet-wide purge.\n\t *\n\t * @returns Number of entries removed, or `null` on failure.\n\t *\n\t * @example\n\t * ```ts\n\t * const removed = await stats.purgeCache();\n\t * if (removed !== null) console.log(`Purged ${removed} cached solves`);\n\t * ```\n\t */\n\tpublic async purgeCache(): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/cache/purge`, {\n\t\t\t\tmethod: 'POST'\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to purge cache:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Read text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn typeof json.purged === 'number' ? json.purged : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error purging cache:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Best-effort fleet-wide cache purge across a multi-child deployment.\n\t *\n\t * A single {@link purgeCache} POST is forwarded by the rhino.compute proxy to\n\t * just ONE round-robin-selected child, so the other children keep serving\n\t * stale cached solves. There is no proxy endpoint that addresses children\n\t * individually, so this method reads the active child count (passively, never\n\t * spawning) and fires `2 × count` sequential purges, relying on the proxy's\n\t * round-robin to spread the hits across the pool.\n\t *\n\t * **This is best-effort, not a guarantee.** Round-robin can revisit one child\n\t * and skip another; under concurrent traffic the rotation drifts. The result's\n\t * `confident` flag is `true` only when the server reports a single child (where\n\t * one purge is exact) — surface it so callers don't over-promise. For a hard\n\t * fleet-wide guarantee, run the deployment at `--childcount 1` or add a\n\t * server-side fan-out endpoint.\n\t *\n\t * @returns `{ totalPurged, calls, children, confident }`, or `null` if the\n\t * child count couldn't be read (server unreachable). `totalPurged` sums the\n\t * per-call counts; `calls` is how many purges were issued; `children` is the\n\t * reported pool size; `confident` is `true` only at a single-child pool.\n\t *\n\t * @example\n\t * ```ts\n\t * const r = await stats.purgeAllChildren();\n\t * if (r && !r.confident) {\n\t * console.warn(`Purged ~${r.totalPurged} across ${r.children} children (best-effort)`);\n\t * }\n\t * ```\n\t */\n\tpublic async purgeAllChildren(): Promise<{\n\t\ttotalPurged: number;\n\t\tcalls: number;\n\t\tchildren: number;\n\t\tconfident: boolean;\n\t} | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\t// Passive read — must not spawn children just to purge them.\n\t\tconst children = await this.getActiveChildren({ initialize: false });\n\t\tif (children === null) {\n\t\t\tgetLogger().warn('[ComputeServerStats] purgeAllChildren: could not read child count');\n\t\t\treturn null;\n\t\t}\n\n\t\tif (children === 0) {\n\t\t\t// No live children means nothing is cached on the compute side.\n\t\t\treturn { totalPurged: 0, calls: 0, children: 0, confident: true };\n\t\t}\n\n\t\t// 2× the pool size gives round-robin a strong chance of reaching every\n\t\t// child at least once. Sequential (not parallel) so the proxy advances its\n\t\t// round-robin cursor one child per call rather than racing them onto one.\n\t\tconst calls = children * 2;\n\t\tlet totalPurged = 0;\n\t\tfor (let i = 0; i < calls; i++) {\n\t\t\tconst purged = await this.purgeCache();\n\t\t\tif (purged !== null) totalPurged += purged;\n\t\t}\n\n\t\treturn { totalPurged, calls, children, confident: children === 1 };\n\t}\n\n\t/**\n\t * Get the server's current UTC clock.\n\t *\n\t * GETs `/servertime`, which the server emits as a JSON-encoded ISO-8601\n\t * timestamp (e.g. `\"2026-06-18T08:30:00Z\"`). Useful for detecting clock skew\n\t * between caller and server. Returns `null` if the request failed or the body\n\t * isn't a parseable date.\n\t *\n\t * @returns A `Date` for the server's UTC time, or `null` on failure.\n\t */\n\tpublic async getServerTime(): Promise<Date | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/servertime`);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch server time:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Body is a JSON string (\"2026-…Z\"); strip surrounding quotes if present.\n\t\t\tconst text = (await response.text()).trim().replace(/^\"|\"$/g, '');\n\t\t\tconst date = new Date(text);\n\t\t\treturn isNaN(date.getTime()) ? null : date;\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching server time:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get how long the rhino.compute proxy has been idle.\n\t *\n\t * GETs `/idlespan` on the proxy, which returns the seconds elapsed since the\n\t * last request was forwarded to a compute child. This is a proxy-level metric\n\t * (not proxied to a child) used by autoscalers to decide when a node can be\n\t * drained. Returns `null` if unavailable.\n\t *\n\t * @returns Idle time in seconds, or `null` on failure.\n\t */\n\tpublic async getIdleSpan(): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/idlespan`);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch idle span:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst seconds = parseFloat((await response.text()).trim());\n\t\t\treturn isNaN(seconds) ? null : seconds;\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching idle span:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Fill the compute child pool up to the server's configured baseline.\n\t *\n\t * POSTs `/launch-children`. No-op when the pool is already at or above the\n\t * configured `--childcount`. Returns `{ spawned, active }` — how many children\n\t * were started and the resulting child count — or `null` on failure.\n\t *\n\t * To raise capacity above the baseline use {@link launchChild}; the baseline\n\t * itself can only be changed by restarting rhino.compute.\n\t *\n\t * @returns `{ spawned, active }`, or `null` on failure.\n\t */\n\tpublic async launchChildren(): Promise<{ spawned: number[]; active: number } | null> {\n\t\treturn this.postJson('/launch-children');\n\t}\n\n\t/**\n\t * Add a single compute child to the pool, optionally on a specific port.\n\t *\n\t * POSTs `/launch-child` (with `?port=N` when `port` is given). Unlike\n\t * {@link launchChildren}, this can push the pool above the baseline, up to the\n\t * server's `MaxChildren` cap. Returns `{ spawned: [port] }` on success, or\n\t * `null` on failure (server replies 400 for a bad port, 409 if the port is in\n\t * use, 503 at the max-children cap).\n\t *\n\t * @param port - Optional specific port to launch on; otherwise the next free one.\n\t * @returns `{ spawned }` listing the launched port, or `null` on failure.\n\t */\n\tpublic async launchChild(port?: number): Promise<{ spawned: number[] } | null> {\n\t\tconst path = port === undefined ? '/launch-child' : `/launch-child?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * Gracefully shut down compute children without respawning them.\n\t *\n\t * POSTs `/shutdown-children`. With no `port` it shuts down every child; with\n\t * `port` it targets just that one. Children do not respawn, but the next\n\t * `/grasshopper` request auto-spawns the pool back to the baseline. Returns\n\t * `{ shutdown, active }` — how many were stopped and the remaining count — or\n\t * `null` on failure.\n\t *\n\t * @param port - Optional port to target; omit to shut down all children.\n\t * @returns `{ shutdown, active }`, or `null` on failure.\n\t */\n\tpublic async shutdownChildren(\n\t\tport?: number\n\t): Promise<{ shutdown: number; active: number } | null> {\n\t\tconst path = port === undefined ? '/shutdown-children' : `/shutdown-children?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * Shut down compute children and respawn replacements (rolling restart).\n\t *\n\t * POSTs `/recycle-children`. With no `port` it recycles every child; with\n\t * `port` it recycles just that one. The server recycles sequentially (each\n\t * replacement is serving before the next child is stopped) so the pool never\n\t * drops to zero mid-recycle. Returns `{ shutdown, spawned, active }`, or\n\t * `null` on failure.\n\t *\n\t * @param port - Optional port to target; omit to recycle all children.\n\t * @returns `{ shutdown, spawned, active }`, or `null` on failure.\n\t */\n\tpublic async recycleChildren(\n\t\tport?: number\n\t): Promise<{ shutdown: number; spawned: number[]; active: number } | null> {\n\t\tconst path = port === undefined ? '/recycle-children' : `/recycle-children?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * POST a control endpoint that replies with a JSON object and return it,\n\t * degrading to `null` on any non-2xx, non-JSON, or network failure. Shared by\n\t * the child-lifecycle methods so their failure semantics stay identical. Uses\n\t * the longer lifecycle timeout since a spawn/recycle can take ~30s.\n\t */\n\tprivate async postJson<T>(path: string): Promise<T | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(\n\t\t\t\t`${this.serverUrl}${path}`,\n\t\t\t\t{ method: 'POST' },\n\t\t\t\tComputeServerStats.LIFECYCLE_TIMEOUT_MS\n\t\t\t);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn(`[ComputeServerStats] POST ${path} failed:`, response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(text) as T;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn(`[ComputeServerStats] Error on POST ${path}:`, err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Continuously monitor server stats at specified interval.\n\t *\n\t * @param callback - Function called with stats on each interval\n\t * @param intervalMs - Milliseconds between checks (default: 5000). Must be a\n\t * finite number of at least 100 ms — lower values would hot-loop the server.\n\t * @returns Function to stop monitoring\n\t * @throws {RangeError} If `intervalMs` is not a finite number >= 100.\n\t *\n\t * @example\n\t * ```typescript\n\t * const stopMonitoring = stats.monitor((data) => {\n\t * console.log('Server stats:', data);\n\t * }, 3000);\n\t *\n\t * // Later...\n\t * stopMonitoring();\n\t * ```\n\t */\n\tpublic monitor(\n\t\tcallback: (stats: Awaited<ReturnType<typeof this.getServerStats>>) => void,\n\t\tintervalMs: number = 5000\n\t): () => void {\n\t\tthis.ensureNotDisposed();\n\n\t\tif (!Number.isFinite(intervalMs) || intervalMs < ComputeServerStats.MIN_MONITOR_INTERVAL_MS) {\n\t\t\tthrow new RangeError(\n\t\t\t\t`monitor() intervalMs must be a finite number >= ${ComputeServerStats.MIN_MONITOR_INTERVAL_MS}ms, ` +\n\t\t\t\t\t`got ${intervalMs}. Sub-${ComputeServerStats.MIN_MONITOR_INTERVAL_MS}ms polling would hot-loop the server.`\n\t\t\t);\n\t\t}\n\n\t\tlet active = true;\n\t\tlet currentTimeoutId: ReturnType<typeof setTimeout> | null = null;\n\n\t\tgetLogger().info(`🔄 Starting server stats monitoring every ${intervalMs}ms`);\n\n\t\tconst check = async () => {\n\t\t\t// Clear current timeout from tracking since it has fired\n\t\t\tif (currentTimeoutId !== null) {\n\t\t\t\tthis.activeTimeouts.delete(currentTimeoutId);\n\t\t\t\tcurrentTimeoutId = null;\n\t\t\t}\n\n\t\t\tif (!active || this.disposed) return;\n\n\t\t\ttry {\n\t\t\t\tconst _stats = await this.getServerStats();\n\n\t\t\t\t// Check again after async operation to prevent race condition\n\t\t\t\tif (!active || this.disposed) return;\n\n\t\t\t\ttry {\n\t\t\t\t\tcallback(_stats);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tgetLogger().error('[ComputeServerStats] Monitor callback threw:', err);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (!active || this.disposed) {\n\t\t\t\t\t// dispose()/stop racing an in-flight poll is normal shutdown, not an\n\t\t\t\t\t// error — the nested ensureNotDisposed() throw is expected here.\n\t\t\t\t\tgetLogger().debug('[ComputeServerStats] Monitor poll cancelled during shutdown:', err);\n\t\t\t\t} else {\n\t\t\t\t\tgetLogger().error('[ComputeServerStats] Failed to fetch stats during monitor:', err);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (active && !this.disposed) {\n\t\t\t\tcurrentTimeoutId = setTimeout(() => void check(), intervalMs);\n\t\t\t\tthis.activeTimeouts.add(currentTimeoutId);\n\t\t\t}\n\t\t};\n\n\t\tconst stopMonitoring = () => {\n\t\t\tactive = false;\n\n\t\t\t// Clear any pending timeout\n\t\t\tif (currentTimeoutId !== null) {\n\t\t\t\tclearTimeout(currentTimeoutId);\n\t\t\t\tthis.activeTimeouts.delete(currentTimeoutId);\n\t\t\t\tcurrentTimeoutId = null;\n\t\t\t}\n\n\t\t\tthis.activeMonitors.delete(stopMonitoring);\n\t\t};\n\n\t\tthis.activeMonitors.add(stopMonitoring);\n\n\t\t// Explicitly mark as fire-and-forget since we don't need to await the initial call\n\t\tvoid check();\n\n\t\treturn stopMonitoring;\n\t}\n\n\t/**\n\t * Disposes of all resources and stops all active monitors.\n\t * Call this when you're done using the stats instance.\n\t */\n\tpublic async dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\n\t\t// Stop all active monitors (this will also clear their timeouts)\n\t\tfor (const stopMonitor of this.activeMonitors) {\n\t\t\tstopMonitor();\n\t\t}\n\t\tthis.activeMonitors.clear();\n\n\t\t// Clear any remaining timeouts (defensive cleanup)\n\t\tfor (const timeoutId of this.activeTimeouts) {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t\tthis.activeTimeouts.clear();\n\t}\n\n\t/**\n\t * Ensures the instance hasn't been disposed.\n\t */\n\tprivate ensureNotDisposed(): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t'ComputeServerStats has been disposed and cannot be used',\n\t\t\t\tErrorCodes.INVALID_STATE,\n\t\t\t\t{ context: { disposed: this.disposed } }\n\t\t\t);\n\t\t}\n\t}\n}\n","import { getLogger } from './logger';\n\n/** Functions that already warned — the exposure risk doesn't change per call, so once is enough. */\nconst warnedFunctions = new Set<string>();\n\n/**\n * True only in a GENUINE browser/worker context — the case where an API key in\n * the config is actually exposed to end users. `typeof window !== 'undefined'`\n * alone is not enough: jsdom test environments define `window` too, and every\n * unsuppressed vitest/jest run would warn noisily (issue 110).\n *\n * Detection: `window` must exist, AND we must not be in Node (jsdom runs in\n * Node, so `process.versions.node` is defined there but never in a real\n * browser), AND the user agent must not carry jsdom's marker (belt-and-braces\n * for exotic setups that hide `process`). Genuine browsers pass all three.\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) {\n\t\treturn;\n\t}\n\n\tif (isRealBrowser() && !warnedFunctions.has(functionName)) {\n\t\twarnedFunctions.add(functionName);\n\t\tgetLogger().warn(\n\t\t\t`Warning: ${functionName} is running on the client side. For better performance and security, consider running this on the server side.`\n\t\t);\n\t}\n}\n\n/** @internal Reset the once-per-function dedupe — for tests only. */\nexport function resetClientSideWarnings(): void {\n\twarnedFunctions.clear();\n}\n","import { fetchCompute, ComputeError, ErrorCodes } from '@/core';\nimport type { ComputeConfig, ServerErrorCodeMap } from '@/core/types';\nimport { getResponseWireSize, setResponseWireSize } from '@/core/compute-fetch/wire-size';\nimport { base64ByteArray, detectBase64Payload, encodeStringToBase64 } from '@/core/utils/encoding';\nimport { getLogger } from '@/core/utils/logger';\nimport { readField } from '@/core/utils/read-field';\nimport { warnIfClientSide } from '@/core/utils/warnings';\n\nimport {\n\tGrasshopperRequestSchema,\n\tGrasshopperComputeConfig,\n\tGrasshopperComputeResponse,\n\tDataTree\n} from './types';\nimport { isDefinitionRef, type SolveDefinition } from '@/core/definition-ref';\n\n/**\n * The exact message the server throws when it can neither resolve a `pointer`\n * nor a base64 `algo` to a definition (ResthopperEndpoints.cs). This is the\n * signal that a cache-key pointer missed the server's definition cache (GC'd, or\n * a different child in the pool), so the caller should retry with the full\n * definition. Matched as a substring because the server wraps it with a category\n * prefix in its exception handler.\n *\n * NOTE: the server only includes this message when running in debug mode; its\n * production exception handler scrubs the message to a generic string. The\n * reliable signal is therefore {@link ErrorCodes.DEFINITION_NOT_CACHED}, derived\n * from the server's machine `code` (which isn't scrubbed). The string match is\n * kept as a fallback for debug-mode servers and forks that don't yet emit a code.\n */\nconst DEFINITION_LOAD_FAILED = 'Unable to load grasshopper definition';\n\n/**\n * Wire codes rhino.compute tags onto its error bodies. Passed to the transport\n * on every Grasshopper request — `core/` deliberately knows no backend's codes.\n */\nexport const GRASSHOPPER_SERVER_ERROR_CODES: ServerErrorCodeMap = {\n\tdefinition_not_cached: ErrorCodes.DEFINITION_NOT_CACHED\n};\n\n/** Attach the Grasshopper wire-code table without clobbering a caller's own entries. */\nexport function withGrasshopperErrorCodes<T extends ComputeConfig>(config: T): T {\n\treturn {\n\t\t...config,\n\t\tserverErrorCodes: { ...GRASSHOPPER_SERVER_ERROR_CODES, ...config.serverErrorCodes }\n\t};\n}\n\n/** Does this error look like a server-side definition-load miss? */\nfunction isDefinitionLoadMiss(error: unknown): boolean {\n\tif (!(error instanceof ComputeError)) return false;\n\treturn (\n\t\terror.code === ErrorCodes.DEFINITION_NOT_CACHED ||\n\t\terror.message.includes(DEFINITION_LOAD_FAILED)\n\t);\n}\n\n/**\n * Debug aid: a solve can return successfully yet hand back outputs whose\n * `InnerTree` is empty (`{}`), meaning that parameter produced nothing — often a\n * sign the definition didn't actually compute (wrong/missing inputs, a guarded\n * branch). The names tell you exactly which output was empty so you can trace it\n * back to the responsible branch.\n *\n * Only logs when `debug` is set: an empty output can be legitimate, so this is a\n * diagnostic, never a hard failure. Reads `ParamName` / `InnerTree`\n * case-insensitively to stay robust across server-branch casing.\n *\n * @internal Exported for testing.\n */\nexport function warnOnEmptyInnerTrees(response: GrasshopperComputeResponse, debug?: boolean): void {\n\tif (!debug) return;\n\n\tconst values = readField<unknown[]>(response, 'values');\n\tif (!Array.isArray(values) || values.length === 0) return;\n\n\tconst empty: string[] = [];\n\tfor (const param of values) {\n\t\tconst innerTree = readField<Record<string, unknown>>(param, 'innerTree');\n\t\t// Treat a missing or empty innerTree as \"produced nothing\".\n\t\tif (!innerTree || Object.keys(innerTree).length === 0) {\n\t\t\tempty.push(readField<string>(param, 'paramName') ?? '<unnamed>');\n\t\t}\n\t}\n\n\tif (empty.length === 0) return;\n\n\tconst scope = empty.length === values.length ? 'all' : `${empty.length}/${values.length}`;\n\tgetLogger().warn(\n\t\t`Solve returned empty output(s) (${scope}): ${empty.join(', ')}. ` +\n\t\t\t`These parameters produced no data — check the definition's inputs and the branch feeding each.`\n\t);\n}\n\n/**\n * Result of a solve that also reports the definition's server-side cache key.\n *\n * `cacheKey` is the `md5_…` identifier the server assigned to the (base64)\n * definition — stable for identical content. A caller that holds it can solve\n * the same definition again by reference (`pointer: cacheKey`) instead of\n * re-uploading the full base64, which matters a lot for large (multi-MB)\n * definitions on a live UI. For a URL-pointer solve the server echoes the\n * request schema back, so `cacheKey` is the definition URL itself (already a\n * reference — nothing gained by re-pointing at it). `null` only when the\n * server's response carried no `pointer` at all — do NOT use `null` to detect\n * URL-pointer solves.\n */\nexport interface SolveWithCacheKey {\n\tresponse: GrasshopperComputeResponse;\n\tcacheKey: string | null;\n}\n\n/**\n * Runs a Rhino Compute job using the provided tree prototypes and Grasshopper definition.\n *\n * @public Use this for direct compute control. For high-level API, use `GrasshopperClient.solve()`.\n *\n * @param dataTree - An array of `DataTree` objects representing the input data for the compute job.\n * @param definition - The Grasshopper definition, which can be:\n * - A URL string (e.g., 'https://example.com/definition.gh')\n * - A base64-encoded string of the .gh file\n * - A plain string (will be base64-encoded)\n * - A Uint8Array of the .gh file (will be base64-encoded)\n * - A `DefinitionRef` (bytes are loaded via `ref.load()` for the upload)\n * @param config - Compute configuration (server URL, API key, etc. along with optional timeout, units, etc.)\n * @returns An object containing the compute result and extracted file data.\n *\n * @example\n * // Using a URL\n * await solveGrasshopperDefinition(trees, 'https://example.com/definition.gh', config);\n *\n * // Using a base64 string\n * await solveGrasshopperDefinition(trees, 'UEsDBBQAAAAIAL...', config);\n *\n * // Using binary data\n * const fileData = new Uint8Array([...]);\n * await solveGrasshopperDefinition(trees, fileData, config);\n */\nexport async function solveGrasshopperDefinition(\n\tdataTree: DataTree[],\n\tdefinition: SolveDefinition,\n\tconfig: GrasshopperComputeConfig\n): Promise<GrasshopperComputeResponse> {\n\t// Not gated on `debug`: exposing an API key in the browser is a security\n\t// concern in every configuration. `suppressBrowserWarning` is the opt-out.\n\twarnIfClientSide('solveGrasshopperDefinition', config.suppressBrowserWarning);\n\n\tconst bytes = await materializeDefinition(definition);\n\tconst { response } = await runSolve(prepareGrasshopperArgs(bytes, dataTree), config);\n\treturn response;\n}\n\n/**\n * Solve while reporting the server's definition cache key.\n *\n * Behaves like {@link solveGrasshopperDefinition} but returns the `cacheKey` the\n * server assigned, so a caller (e.g. the scheduler) can later solve the same\n * definition by reference instead of re-uploading it. The cache key is only\n * meaningful for base64/binary definitions; a URL-pointer solve returns the URL.\n *\n * @internal\n */\nexport async function solveGrasshopperDefinitionWithCacheKey(\n\tdataTree: DataTree[],\n\tdefinition: SolveDefinition,\n\tconfig: GrasshopperComputeConfig\n): Promise<SolveWithCacheKey> {\n\twarnIfClientSide('solveGrasshopperDefinitionWithCacheKey', config.suppressBrowserWarning);\n\n\tconst bytes = await materializeDefinition(definition);\n\treturn runSolve(prepareGrasshopperArgs(bytes, dataTree), config);\n}\n\n/**\n * Solve a definition by its server-side cache key (`pointer: cacheKey`),\n * skipping the (potentially multi-MB) base64 upload. If the key has been evicted\n * from the server's definition cache — `DEFINITION_LOAD_FAILED` — transparently\n * retry once with the full `definition` and report the fresh cache key so the\n * caller can update its mapping. A `DefinitionRef` definition is only\n * materialized (`ref.load()`) inside that miss branch — a pointer hit never\n * touches the bytes.\n *\n * @returns The solve result plus the (possibly refreshed) cache key, and whether\n * the fast path missed (so callers can record the new key / track hit rate).\n * @internal\n */\nexport async function solveByCacheKey(\n\tdataTree: DataTree[],\n\tcacheKey: string,\n\tdefinition: SolveDefinition,\n\tconfig: GrasshopperComputeConfig\n): Promise<SolveWithCacheKey & { missed: boolean }> {\n\twarnIfClientSide('solveByCacheKey', config.suppressBrowserWarning);\n\n\tconst pointerArgs: GrasshopperRequestSchema = { algo: null, pointer: cacheKey, values: dataTree };\n\n\ttry {\n\t\tconst fast = await runSolve(pointerArgs, config);\n\t\treturn { ...fast, missed: false };\n\t} catch (error) {\n\t\tif (!isDefinitionLoadMiss(error)) throw error;\n\t\t// Cache miss — fall back to the full upload and capture the fresh key.\n\t\tconst bytes = await materializeDefinition(definition);\n\t\tconst full = await runSolve(prepareGrasshopperArgs(bytes, dataTree), config);\n\t\treturn { ...full, missed: true };\n\t}\n}\n\n/**\n * Resolve a definition to uploadable form: a `DefinitionRef` is materialized\n * via `load()` (failures are wrapped so callers get a `ComputeError`\n * naming the ref, not an opaque loader exception); other forms pass through.\n */\nasync function materializeDefinition(definition: SolveDefinition): Promise<string | Uint8Array> {\n\tif (!isDefinitionRef(definition)) return definition;\n\ttry {\n\t\treturn await definition.load();\n\t} catch (error) {\n\t\tthrow new ComputeError(\n\t\t\t`Failed to load definition bytes for ref '${definition.key}'`,\n\t\t\tErrorCodes.INVALID_INPUT,\n\t\t\t{\n\t\t\t\tcontext: { definitionKey: definition.key },\n\t\t\t\toriginalError: error instanceof Error ? error : new Error(String(error))\n\t\t\t}\n\t\t);\n\t}\n}\n\n/**\n * Shared solve body: apply optional settings, POST, and split the server's\n * `pointer` (its cache key) off the response. `algo` — the request's full\n * base64 definition echoed back on every solve — is stripped too: keeping it\n * pins a multi-MB copy per response, which would consume the scheduler cache's\n * byte budget many times over. Stripping via shallow copy rather than `delete`\n * keeps any already-observed response object unmutated.\n */\nasync function runSolve(\n\targs: GrasshopperRequestSchema,\n\tconfig: GrasshopperComputeConfig\n): Promise<SolveWithCacheKey> {\n\tapplyOptionalComputeSettings(args, config);\n\n\tconst result = await fetchCompute<GrasshopperComputeResponse>(\n\t\t'grasshopper',\n\t\targs,\n\t\twithGrasshopperErrorCodes(config)\n\t);\n\n\tconst {\n\t\tpointer,\n\t\talgo: _algo,\n\t\t...rest\n\t} = result as GrasshopperComputeResponse & { pointer?: unknown; algo?: unknown };\n\tconst response = rest as GrasshopperComputeResponse;\n\t// The wire-size hint follows object identity, so the stripped copy must\n\t// re-register it — minus the echoed `algo`, which is no longer retained (it\n\t// can dwarf the actual outputs for a large definition).\n\tconst wireSize = getResponseWireSize(result);\n\tif (wireSize !== undefined) {\n\t\tconst algoLength = typeof _algo === 'string' ? _algo.length : 0;\n\t\tsetResponseWireSize(response, Math.max(0, wireSize - algoLength));\n\t}\n\twarnOnEmptyInnerTrees(response, config.debug);\n\treturn {\n\t\tresponse,\n\t\tcacheKey: typeof pointer === 'string' ? pointer : null\n\t};\n}\n\n// ============================================================================\n// Grasshopper Arguments\n// ============================================================================\n\n/**\n * Prepares Grasshopper arguments from a definition and data tree.\n * Automatically detects the definition format and converts it appropriately.\n *\n * @param definition - Can be a URL, base64 string, plain string, or Uint8Array\n * @param dataTree - Array of DataTree objects for compute inputs\n * @internal\n */\nexport function prepareGrasshopperArgs(\n\tdefinition: string | Uint8Array,\n\tdataTree: DataTree[]\n): GrasshopperRequestSchema {\n\tconst args: GrasshopperRequestSchema = {\n\t\talgo: null,\n\t\tpointer: null,\n\t\tvalues: dataTree\n\t};\n\n\tif (definition instanceof Uint8Array) {\n\t\t// Binary data → convert to base64\n\t\targs.algo = base64ByteArray(definition);\n\t} else if (/^https?:\\/\\//i.test(definition)) {\n\t\t// URL → use as pointer reference\n\t\targs.pointer = definition;\n\t} else {\n\t\t// Base64 detection is a heuristic (see detectBase64Payload): only long\n\t\t// (≥64 data chars), canonical base64 is passed through — normalized, so\n\t\t// newline-wrapped/unpadded definitions reach the server decodable instead\n\t\t// of double-encoded. Everything else (incl. short base64-shaped strings\n\t\t// like \"test\") is treated as a plain string and encoded. Pass a\n\t\t// Uint8Array to bypass sniffing entirely.\n\t\targs.algo = detectBase64Payload(definition) ?? encodeStringToBase64(definition);\n\t}\n\n\treturn args;\n}\n\n/**\n * @internal\n */\nexport function applyOptionalComputeSettings(\n\targlist: GrasshopperRequestSchema,\n\toptions: GrasshopperComputeConfig\n): void {\n\tif (options.cachesolve != null) arglist.cachesolve = options.cachesolve;\n\tif (options.cacheerroredsolves != null) arglist.cacheerroredsolves = options.cacheerroredsolves;\n\tif (options.modelunits != null) arglist.modelunits = options.modelunits;\n\tif (options.angletolerance != null) arglist.angletolerance = options.angletolerance;\n\tif (options.absolutetolerance != null) arglist.absolutetolerance = options.absolutetolerance;\n\tif (options.dataversion != null) arglist.dataversion = options.dataversion;\n}\n","/**\n * Stable hashing for solve deduplication and caching.\n *\n * The public keying surface here — {@link stableStringify}, {@link hashDefinition},\n * {@link hashSolveInput} — is exported from the package barrel so a durable\n * (app-layer) cache or a pre-solved bundle viewer can reproduce the *exact*\n * canonicalize-and-hash path the scheduler's in-process cache uses. Key parity\n * across the two layers is a correctness requirement: a durable-cache key that\n * canonicalizes even slightly differently would miss every entry the scheduler\n * wrote (harmless) or, worse, collide differently (not). The FNV plumbing\n * (`fnv1a`/`fnv1aBytes`) and the pre-hashed-definition fast path\n * (`hashSolveInputForDefinition`) stay internal.\n */\n\nimport { isDefinitionRef, type SolveDefinition } from '@/core/definition-ref';\n\n/**\n * Deterministic stringify with sorted keys. {a:1,b:2} and {b:2,a:1} produce\n * the same string. Safely handles circular references and non-finite numbers.\n *\n * The output is a cache key, so the invariant that matters is: two payloads\n * that serialize differently on the wire must stringify differently here\n * (false misses are harmless; false hits serve the wrong cached solve).\n */\nexport function stableStringify(value: unknown): string {\n\t// Tracks the current recursion path only (entries are removed on the way\n\t// out), so genuine cycles read \"[Circular]\" while shared non-circular\n\t// references stringify by content each time they appear.\n\tconst path = new WeakSet<object>();\n\n\tconst stringify = (v: unknown): string => {\n\t\tif (v === undefined) return 'undefined';\n\t\tif (v === null) return 'null';\n\t\tif (typeof v === 'number') {\n\t\t\treturn Number.isFinite(v) ? String(v) : 'null';\n\t\t}\n\t\tif (typeof v === 'string' || typeof v === 'boolean') return JSON.stringify(v);\n\t\t// Unquoted `n` suffix keeps 1n distinct from the string \"1\".\n\t\tif (typeof v === 'bigint') return `${v}n`;\n\t\tif (v instanceof Uint8Array) {\n\t\t\t// Full-content hash in one linear pass — sampling head/tail let two\n\t\t\t// buffers differing only in the middle share a cache key.\n\t\t\treturn `{\"__u8\":${v.length},\"hash\":\"${fnv1aBytes(v)}\"}`;\n\t\t}\n\t\tif (Array.isArray(v)) {\n\t\t\tif (path.has(v)) return '\"[Circular]\"';\n\t\t\tpath.add(v);\n\t\t\tconst parts: string[] = [];\n\t\t\t// Indexed access (not .map) so holes stringify like undefined instead\n\t\t\t// of vanishing from the joined output.\n\t\t\tfor (let i = 0; i < v.length; i++) parts.push(stringify(v[i]));\n\t\t\tpath.delete(v);\n\t\t\treturn `[${parts.join(',')}]`;\n\t\t}\n\t\tif (typeof v === 'object') {\n\t\t\tif (path.has(v)) return '\"[Circular]\"';\n\t\t\tpath.add(v);\n\t\t\tlet out: string;\n\t\t\tif (typeof (v as { toJSON?: unknown }).toJSON === 'function') {\n\t\t\t\t// Matches wire behavior: JSON.stringify calls toJSON (Date → ISO string).\n\t\t\t\tout = stringify((v as { toJSON: () => unknown }).toJSON());\n\t\t\t} else if (v instanceof Map) {\n\t\t\t\tconst entries = [...v.entries()].map(([k, val]) => `[${stringify(k)},${stringify(val)}]`);\n\t\t\t\tout = `{\"__map\":[${entries.sort().join(',')}]}`;\n\t\t\t} else if (v instanceof Set) {\n\t\t\t\tconst items = [...v.values()].map(stringify);\n\t\t\t\tout = `{\"__set\":[${items.sort().join(',')}]}`;\n\t\t\t} else {\n\t\t\t\tconst keys = Object.keys(v as object).sort();\n\t\t\t\tconst parts = keys.map(\n\t\t\t\t\t(k) => `${JSON.stringify(k)}:${stringify((v as Record<string, unknown>)[k])}`\n\t\t\t\t);\n\t\t\t\tout = `{${parts.join(',')}}`;\n\t\t\t}\n\t\t\tpath.delete(v);\n\t\t\treturn out;\n\t\t}\n\t\t// Fallback for functions, symbols, etc.\n\t\treturn 'null';\n\t};\n\n\treturn stringify(value);\n}\n\n/**\n * 32-bit FNV-1a core over a sequence of byte/char codes. Returns unsigned hex.\n * Shared by the string and byte hashers so they stay the same algorithm.\n */\nfunction fnv1aCore(length: number, codeAt: (i: number) => number): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < length; i++) {\n\t\thash ^= codeAt(i);\n\t\thash = (hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24))) >>> 0;\n\t}\n\treturn hash.toString(16).padStart(8, '0');\n}\n\n/**\n * 32-bit FNV-1a— fast, no dependencies. Returns unsigned hex string.\n * @internal\n */\nexport function fnv1a(input: string): string {\n\treturn fnv1aCore(input.length, (i) => input.charCodeAt(i));\n}\n\n/**\n * 32-bit FNV-1a over raw bytes. Returns unsigned hex string.\n * @internal\n */\nexport function fnv1aBytes(bytes: Uint8Array): string {\n\treturn fnv1aCore(bytes.length, (i) => bytes[i]);\n}\n\n/**\n * Hash definition and data tree into a stable cache key.\n *\n * The definition is the *identity* of what we solve, so a binary definition is\n * hashed over its full content (`fnv1aBytes`) — a length-only or sampled key\n * would let two different `.gh` files collide and serve one's cached solve for\n * the other. `.gh` files are small enough that a single linear pass is\n * negligible. A {@link DefinitionRef} is keyed by its `key` alone — the\n * caller-declared identity of immutable bytes — so no bytes are materialized\n * or hashed at all.\n *\n * The key keeps the definition and tree hashes as separate parts rather than\n * collapsing them into one 32-bit hash: a single FNV pass over the pair would\n * birthday-collide quadratically in cache size, while requiring both 32-bit\n * parts (plus lengths) to collide at once makes that negligible.\n */\nexport function hashSolveInput(definition: SolveDefinition, dataTree: unknown): string {\n\treturn hashSolveInputForDefinition(hashDefinition(definition), dataTree);\n}\n\n/**\n * Build the solve cache key from an already-computed definition hash (a\n * {@link hashDefinition} result) plus the data tree. Split out so a caller\n * that needs the definition hash anyway (the scheduler keys its\n * server-cache-key map by it) computes it once at `solve()` entry and threads\n * it through, instead of paying a second linear FNV pass over a potentially\n * multi-MB base64 definition per solve.\n *\n * Invariant: `hashSolveInputForDefinition(hashDefinition(d), t)` produces the\n * exact same key as `hashSolveInput(d, t)` — cache-key semantics are unchanged.\n *\n * @internal\n */\nexport function hashSolveInputForDefinition(definitionHash: string, dataTree: unknown): string {\n\tconst tree = stableStringify(dataTree);\n\treturn `${definitionHash}|t:${tree.length}:${fnv1a(tree)}`;\n}\n\n/**\n * Stable identity of a definition alone (no inputs) — used to key the\n * server-cache-key map so the same definition reuses its `pointer` across solves\n * with different inputs. Same full-content hashing as {@link hashSolveInput}: a\n * binary definition is hashed over all its bytes so two distinct `.gh` files of\n * equal length can't share a cache key. A {@link DefinitionRef} is keyed by its\n * `key` verbatim (refs are short identities like UUIDs, safe as Map keys) —\n * its immutability contract makes the key equivalent to a content hash.\n */\nexport function hashDefinition(definition: SolveDefinition): string {\n\tif (isDefinitionRef(definition)) return `r:${definition.key}`;\n\t// Hash strings too (don't return the raw definition): a multi-MB base64 `.gh`\n\t// would otherwise become the literal Map key in serverCacheKeys / the cache.\n\treturn typeof definition === 'string'\n\t\t? `s:${definition.length}:${fnv1a(definition)}`\n\t\t: `u8:${definition.length}:${fnv1aBytes(definition)}`;\n}\n","import { ComputeError, ErrorCodes } from '@/core/errors';\nimport type { RetryPolicy } from '@/core/types';\nimport { getLogger } from '@/core/utils/logger';\nimport { getResponseWireSize } from '@/core/compute-fetch/wire-size';\n\nimport type { DataTree, GrasshopperComputeResponse, GrasshopperComputeConfig } from '../types';\nimport type { SolveDefinition } from '@/core/definition-ref';\nimport { hashSolveInputForDefinition, hashDefinition } from './stable-hash';\n\nimport type {\n\tCacheOptions,\n\tCacheKeyExecutor,\n\tSchedulerMode,\n\tSolveContext,\n\tSolveExecutor,\n\tSolveResult,\n\tSolveSchedulerOptions\n} from './types';\n\nexport type {\n\tCacheOptions,\n\tCacheKeyExecutor,\n\tSchedulerMode,\n\tSolveContext,\n\tSolveExecutor,\n\tSolveResult,\n\tSolveSchedulerOptions\n};\n\ninterface CacheEntry {\n\tresponse: GrasshopperComputeResponse;\n\tinsertedAt: number;\n\t/** Wire size (or stringify-fallback estimate) counted against `maxBytes`. */\n\tsizeBytes: number;\n}\n\n/** Cap on the definition→server-cache-key map so it can't grow without bound. */\nconst SERVER_CACHE_KEYS_MAX = 100;\n\ninterface PendingItem {\n\tdefinition: SolveDefinition;\n\tdataTree: DataTree[];\n\t/**\n\t * Definition hash computed once at `solve()` entry ({@link hashDefinition})\n\t * and threaded through to {@link SolveScheduler.runExecutor}, so the\n\t * (potentially multi-MB) definition is never linearly hashed a second time\n\t * for the server-cache-key map.\n\t */\n\tdefinitionHash: string;\n\tctx: SolveContext;\n\t/** Monotonic solve() ordinal — used to keep older items' late settles from overwriting newer state. */\n\tseq: number;\n\tresolve: (response: GrasshopperComputeResponse) => void;\n\treject: (error: ComputeError) => void;\n\texternalSignal?: AbortSignal;\n\t/**\n\t * Abort listener attached while the item waits in a queue, so a signal\n\t * firing pre-execution settles it instead of being silently ignored.\n\t * Removed when execution starts (the in-flight controller takes over) or\n\t * when the item settles by any other path.\n\t */\n\tqueuedAbortHandler?: () => void;\n\t/**\n\t * Queue-wait deadline timer ({@link SolveSchedulerOptions.queueWaitMs}).\n\t * Cleared when the item starts executing or settles by any other path.\n\t */\n\tqueueWaitTimer?: ReturnType<typeof setTimeout>;\n\t/** Set once the promise has been settled, so a late executor rejection becomes a no-op. */\n\tsettled?: { error: ComputeError } | { ok: true };\n}\n\ninterface InFlightItem extends PendingItem {\n\tcontroller: AbortController;\n}\n\n/**\n * Adapter for the underlying solve function. Lets the scheduler be tested\n * without a real Compute server, and decouples it from the client class.\n */\n\n/**\n * Whether a definition is worth solving by server cache key. Binary and\n * base64/plain-string definitions are uploaded in full, so referencing them by\n * key on later solves saves the (potentially huge) payload — and a\n * `DefinitionRef` additionally saves the `load()` itself on a pointer hit. An\n * `http(s)://` URL is already a reference — the server keys it by URL and\n * there's nothing to re-upload — so the fast path adds no value there.\n */\nfunction isReusableDefinition(definition: SolveDefinition): boolean {\n\tif (typeof definition !== 'string') return true;\n\treturn !/^https?:\\/\\//i.test(definition);\n}\n\n/**\n * Fallback sizing for a response that never crossed the fetch boundary (custom\n * executor, hand-built test responses) and so carries no wire-size hint. A full\n * stringify — linear, once per cache write, never on the hit path. Responses\n * are JSON-derived, but guard anyway: an unserializable one is sized 0 (cached\n * under the count bound only) rather than failing the solve.\n */\nfunction estimateResponseSize(response: GrasshopperComputeResponse): number {\n\ttry {\n\t\treturn JSON.stringify(response)?.length ?? 0;\n\t} catch {\n\t\treturn 0;\n\t}\n}\n\n/**\n * Robust scheduler for Grasshopper solves.\n *\n * Sits between your application code and the underlying compute call,\n * adding:\n * - Configurable scheduling (latest-wins for sliders, queue for jobs)\n * - Backpressure (bounded queue depth + queue-wait deadline) for the miss path\n * - In-flight cancellation (per-call signal + cancelAll)\n * - Optional response caching for repeated inputs\n * - Lifecycle hooks for UI indicators (start / settle / superseded)\n * - State observability via subscribe()\n *\n * Multiple schedulers can share a single GrasshopperClient — typically one\n * per UI surface (e.g. one for slider scrubs, one for long-running submits).\n *\n * @example\n * ```ts\n * const scheduler = client.createScheduler({ mode: 'latest-wins', timeoutMs: 30_000 });\n *\n * // From a slider handler:\n * scheduler.solve(definition, tree).then((result) => {\n * updateMeshes(result);\n * }).catch((err) => {\n * if (err.code !== 'SUPERSEDED') showError(err);\n * });\n *\n * // From a UI binding:\n * scheduler.subscribe(() => {\n * showSpinner = scheduler.isSolving;\n * });\n * ```\n */\nexport class SolveScheduler {\n\tprivate readonly executor: SolveExecutor;\n\tprivate readonly baseConfig: GrasshopperComputeConfig;\n\n\tprivate readonly mode: SchedulerMode;\n\t/**\n\t * Mutable via {@link setMaxConcurrent}: the compute server's worker pool can grow\n\t * or shrink while this scheduler is alive, and the dispatch loops re-read this on\n\t * every pass rather than capturing it.\n\t */\n\tprivate maxConcurrent: number;\n\tprivate readonly maxQueueDepth: number | undefined;\n\tprivate readonly queueWaitMs: number | undefined;\n\tprivate readonly timeoutMs: number | undefined;\n\tprivate readonly retry: RetryPolicy | undefined;\n\n\tprivate readonly cacheEnabled: boolean;\n\tprivate readonly cacheMaxBytes: number;\n\tprivate readonly cacheTtl: number;\n\tprivate readonly cacheErroredSolves: boolean;\n\tprivate readonly cache = new Map<string, CacheEntry>();\n\t/** Sum of `sizeBytes` across retained cache entries. */\n\tprivate cacheBytes = 0;\n\t/**\n\t * Cumulative hit/miss/eviction counters for {@link cacheStats}. Deliberately\n\t * NOT reset by `clearCache()` — they measure this scheduler's whole lifetime,\n\t * so a hit rate stays comparable across a session rather than restarting at\n\t * every clear.\n\t */\n\tprivate cacheHits = 0;\n\tprivate cacheMisses = 0;\n\tprivate cacheEvictions = 0;\n\n\t/** Optional cache-key-aware executor and whether server-def-cache reuse is on. */\n\tprivate readonly cacheKeyExecutor?: CacheKeyExecutor;\n\tprivate readonly reuseServerDefinitionCache: boolean;\n\t/** definition identity → server cache key (`pointer`) learned from past solves. */\n\tprivate readonly serverCacheKeys = new Map<string, string>();\n\n\tprivate readonly onStart?: SolveSchedulerOptions['onStart'];\n\tprivate readonly onSettle?: SolveSchedulerOptions['onSettle'];\n\tprivate readonly onSuperseded?: SolveSchedulerOptions['onSuperseded'];\n\n\tprivate readonly subscribers = new Set<() => void>();\n\n\tprivate readonly inFlight = new Set<InFlightItem>();\n\tprivate pendingForLatestWins: PendingItem | null = null;\n\tprivate readonly fifoQueue: PendingItem[] = [];\n\n\tprivate _lastResult: GrasshopperComputeResponse | null = null;\n\tprivate _lastError: ComputeError | null = null;\n\tprivate _lastDurationMs: number | null = null;\n\n\t/** Ordinal handed to each solve() call. */\n\tprivate solveSeq = 0;\n\t/** seq of the solve that last wrote _lastResult/_lastError — see writeLastState. */\n\tprivate lastStateSeq = 0;\n\n\tprivate disposed = false;\n\n\tconstructor(\n\t\texecutor: SolveExecutor,\n\t\tbaseConfig: GrasshopperComputeConfig,\n\t\toptions: SolveSchedulerOptions = {},\n\t\tcacheKeyExecutor?: CacheKeyExecutor\n\t) {\n\t\tthis.executor = executor;\n\t\tthis.cacheKeyExecutor = cacheKeyExecutor;\n\t\tthis.baseConfig = baseConfig;\n\t\tthis.mode = options.mode ?? 'latest-wins';\n\t\tthis.maxConcurrent = Math.max(1, options.maxConcurrent ?? (this.mode === 'parallel' ? 4 : 1));\n\t\t// Backpressure bounds — only meaningful for queue/parallel (latest-wins has\n\t\t// depth 1). A non-positive maxQueueDepth is treated as unbounded rather than\n\t\t// \"reject everything\", which would silently break the scheduler.\n\t\tthis.maxQueueDepth =\n\t\t\toptions.maxQueueDepth !== undefined && options.maxQueueDepth > 0\n\t\t\t\t? Math.floor(options.maxQueueDepth)\n\t\t\t\t: undefined;\n\t\tthis.queueWaitMs =\n\t\t\toptions.queueWaitMs !== undefined && options.queueWaitMs > 0\n\t\t\t\t? options.queueWaitMs\n\t\t\t\t: undefined;\n\t\tthis.timeoutMs = options.timeoutMs;\n\t\tthis.retry = options.retry;\n\n\t\tconst cacheOpt = options.cache;\n\t\tconst cacheConfig = typeof cacheOpt === 'object' ? cacheOpt : null;\n\t\tthis.cacheMaxBytes = Math.max(0, cacheConfig?.maxBytes ?? 0);\n\t\t// A zero budget retains nothing, so treat it as off rather than running a\n\t\t// cache that can only ever miss.\n\t\tthis.cacheEnabled = this.cacheMaxBytes > 0;\n\t\tthis.cacheTtl = cacheConfig?.ttlMs ?? 0;\n\t\tthis.cacheErroredSolves = cacheConfig?.cacheErroredSolves ?? true;\n\n\t\t// On by default when the client wired a cache-key executor — it's a pure\n\t\t// win for reusable definitions and falls back safely on a miss.\n\t\tthis.reuseServerDefinitionCache =\n\t\t\t!!cacheKeyExecutor && (options.reuseServerDefinitionCache ?? true);\n\n\t\tthis.onStart = options.onStart;\n\t\tthis.onSettle = options.onSettle;\n\t\tthis.onSuperseded = options.onSuperseded;\n\t}\n\n\tget isSolving(): boolean {\n\t\treturn this.inFlight.size > 0;\n\t}\n\n\tget hasPending(): boolean {\n\t\treturn this.pendingForLatestWins !== null || this.fifoQueue.length > 0;\n\t}\n\n\tget inFlightCount(): number {\n\t\treturn this.inFlight.size;\n\t}\n\n\tget queueDepth(): number {\n\t\treturn this.fifoQueue.length + (this.pendingForLatestWins ? 1 : 0);\n\t}\n\n\tget lastResult(): GrasshopperComputeResponse | null {\n\t\treturn this._lastResult;\n\t}\n\n\tget lastError(): ComputeError | null {\n\t\treturn this._lastError;\n\t}\n\n\tget lastDurationMs(): number | null {\n\t\treturn this._lastDurationMs;\n\t}\n\n\t/**\n\t * Adjust how many solves may run at once, for when the compute server's worker\n\t * pool changes size after this scheduler was built.\n\t *\n\t * Raising it drains queued work immediately. Lowering it never interrupts work\n\t * already in flight — those finish above the new limit, and the cap applies from\n\t * the next dispatch. Values below 1 are clamped, since 0 would wedge the queue.\n\t */\n\tsetMaxConcurrent(value: number): void {\n\t\tconst next = Math.max(1, Math.floor(value));\n\t\tif (next === this.maxConcurrent) return;\n\t\tconst raised = next > this.maxConcurrent;\n\t\tthis.maxConcurrent = next;\n\t\tif (raised) this.drainNext();\n\t}\n\n\t/** Current concurrency cap — reflects any {@link setMaxConcurrent} adjustment. */\n\tgetMaxConcurrent(): number {\n\t\treturn this.maxConcurrent;\n\t}\n\n\t/** Subscribe to state changes. */\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.subscribers.add(listener);\n\t\treturn () => this.subscribers.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tfor (const listener of this.subscribers) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch (err) {\n\t\t\t\tgetLogger().error('[SolveScheduler] subscriber threw:', err);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Schedule a solve. Returns a promise that:\n\t * - Resolves with the compute response on success.\n\t * - Rejects with `ComputeError` on failure.\n\t * - Rejects with `code: ErrorCodes.SUPERSEDED` when the call was canceled because\n\t * newer values arrived (latest-wins mode).\n\t * - Rejects with `code: ErrorCodes.ABORTED` when the call was canceled via\n\t * caller-supplied signal or `cancelAll()`.\n\t * - Rejects with `code: ErrorCodes.QUEUE_FULL` when `maxQueueDepth` is set and\n\t * the queue was already full (backpressure; `statusCode: 503`).\n\t * - Rejects with `code: ErrorCodes.QUEUE_TIMEOUT` when `queueWaitMs` is set and\n\t * the call sat queued longer than that before starting (`statusCode: 503`).\n\t *\n\t * Caller-supplied `signal` cancels just this call (rejects with `ABORTED`) —\n\t * including while the call is still queued, before execution starts.\n\t *\n\t * A {@link DefinitionRef} definition is keyed by its `key` (result cache and\n\t * server-pointer map alike) without materializing bytes — `load()` runs only\n\t * when an upload is unavoidable. Its immutability contract is trusted here:\n\t * a reused key serves the other content's cached solve.\n\t *\n\t * Responses served from the cache (and via `lastResult`) are shared objects,\n\t * not copies — treat them as immutable. Mutating one poisons every later\n\t * cache hit for that key.\n\t */\n\tsolve(\n\t\tdefinition: SolveDefinition,\n\t\tdataTree: DataTree[],\n\t\toptions?: { signal?: AbortSignal }\n\t): Promise<GrasshopperComputeResponse> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(\n\t\t\t\tnew ComputeError(\n\t\t\t\t\t'SolveScheduler has been disposed and cannot be used',\n\t\t\t\t\tErrorCodes.INVALID_STATE\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Hash the definition once here; the hash is both part of the solve key\n\t\t// and (threaded via the pending item) the server-cache-key map's key, so\n\t\t// runExecutor never re-hashes the potentially multi-MB definition.\n\t\tconst definitionHash = hashDefinition(definition);\n\t\tconst key = hashSolveInputForDefinition(definitionHash, dataTree);\n\t\tconst seq = ++this.solveSeq;\n\t\tconst ctx: SolveContext = {\n\t\t\tkey,\n\t\t\tenqueuedAt: Date.now(),\n\t\t\tstartedAt: null\n\t\t};\n\n\t\t// An already-aborted signal rejects before anything else — including the\n\t\t// cache: the documented contract is ABORTED, not a result.\n\t\tif (options?.signal?.aborted) {\n\t\t\treturn Promise.reject(this.makeAbortError(ctx));\n\t\t}\n\n\t\t// Cache hit — return synchronously-resolved promise\n\t\tif (this.cacheEnabled) {\n\t\t\tconst cached = this.readCache(key);\n\t\t\tif (cached) {\n\t\t\t\t// This call is now the newest result. In latest-wins mode that means\n\t\t\t\t// any older in-flight/pending solve is stale — supersede it, or its\n\t\t\t\t// later completion would overwrite this hit and snap the UI back.\n\t\t\t\tif (this.mode === 'latest-wins') {\n\t\t\t\t\tthis.supersedeCurrent();\n\t\t\t\t}\n\t\t\t\tconst result: SolveResult = {\n\t\t\t\t\tstatus: 'success',\n\t\t\t\t\tresponse: cached,\n\t\t\t\t\tdurationMs: 0,\n\t\t\t\t\tfromCache: true\n\t\t\t\t};\n\t\t\t\tthis.writeLastState(seq, { result: cached, durationMs: 0 });\n\t\t\t\tthis.runHook(this.onStart, ctx);\n\t\t\t\tthis.runHook(this.onSettle, ctx, result);\n\t\t\t\tthis.notify();\n\t\t\t\treturn Promise.resolve(cached);\n\t\t\t}\n\t\t}\n\n\t\treturn new Promise<GrasshopperComputeResponse>((resolve, reject) => {\n\t\t\tconst item: PendingItem = {\n\t\t\t\tdefinition,\n\t\t\t\tdataTree,\n\t\t\t\tdefinitionHash,\n\t\t\t\tctx,\n\t\t\t\tseq,\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t\texternalSignal: options?.signal\n\t\t\t};\n\n\t\t\t// A signal firing while the item waits in a queue must settle it as\n\t\t\t// ABORTED and drop it — not leave it to run a full solve anyway. The\n\t\t\t// listener is removed when execution starts or the item settles.\n\t\t\tif (item.externalSignal) {\n\t\t\t\titem.queuedAbortHandler = () => this.abortQueuedItem(item);\n\t\t\t\titem.externalSignal.addEventListener('abort', item.queuedAbortHandler, { once: true });\n\t\t\t}\n\n\t\t\tthis.enqueue(item);\n\t\t});\n\t}\n\n\t/**\n\t * Record last-result state, but only if no newer solve has written since —\n\t * a slow solve settling late must not overwrite the state a newer solve\n\t * (or cache hit) already published.\n\t */\n\tprivate writeLastState(\n\t\tseq: number,\n\t\tstate:\n\t\t\t| { result: GrasshopperComputeResponse; durationMs: number }\n\t\t\t| { error: ComputeError; durationMs: number }\n\t): boolean {\n\t\tif (seq < this.lastStateSeq) return false;\n\t\tthis.lastStateSeq = seq;\n\t\tif ('result' in state) {\n\t\t\tthis._lastResult = state.result;\n\t\t\tthis._lastError = null;\n\t\t} else {\n\t\t\tthis._lastError = state.error;\n\t\t}\n\t\tthis._lastDurationMs = state.durationMs;\n\t\treturn true;\n\t}\n\n\t/** latest-wins: supersede the pending item and abort everything in flight. */\n\tprivate supersedeCurrent(): void {\n\t\tif (this.pendingForLatestWins) {\n\t\t\tthis.supersede(this.pendingForLatestWins);\n\t\t\tthis.pendingForLatestWins = null;\n\t\t}\n\t\tfor (const inflight of this.inFlight) {\n\t\t\tthis.supersede(inflight);\n\t\t\tinflight.controller.abort();\n\t\t}\n\t}\n\n\t/** Settle a still-queued item as ABORTED and remove it from its queue. */\n\tprivate abortQueuedItem(item: PendingItem): void {\n\t\tif (this.pendingForLatestWins === item) {\n\t\t\tthis.pendingForLatestWins = null;\n\t\t}\n\t\tconst queued = this.fifoQueue.indexOf(item);\n\t\tif (queued >= 0) this.fifoQueue.splice(queued, 1);\n\n\t\tthis.rejectAsAborted(item);\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Backpressure: settle an incoming item as QUEUE_FULL without ever enqueuing\n\t * it. The structured context lets an HTTP layer map it to 503 + Retry-After.\n\t */\n\tprivate shedAsQueueFull(item: PendingItem): void {\n\t\tconst err = new ComputeError(\n\t\t\t'Solve queue is full; request rejected (backpressure)',\n\t\t\tErrorCodes.QUEUE_FULL,\n\t\t\t{\n\t\t\t\tstatusCode: 503,\n\t\t\t\tcontext: {\n\t\t\t\t\tkey: item.ctx.key,\n\t\t\t\t\tqueueDepth: this.fifoQueue.length,\n\t\t\t\t\tmaxQueueDepth: this.maxQueueDepth\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t\tif (this.settleError(item, err)) {\n\t\t\tthis.runHook(this.onSettle, item.ctx, { status: 'error', error: err, durationMs: 0 });\n\t\t}\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Arm the queue-wait deadline for an item about to be queued. If the item is\n\t * still waiting when it fires, it's rejected as QUEUE_TIMEOUT and removed from\n\t * the queue. Cleared on execute/settle via {@link clearQueueWaitTimer}.\n\t */\n\tprivate armQueueWaitTimer(item: PendingItem): void {\n\t\tif (this.queueWaitMs === undefined) return;\n\t\tconst waitMs = this.queueWaitMs;\n\t\titem.queueWaitTimer = setTimeout(() => {\n\t\t\t// Only meaningful if it's still queued and unsettled.\n\t\t\tif (item.settled) return;\n\t\t\tconst queued = this.fifoQueue.indexOf(item);\n\t\t\tif (queued >= 0) this.fifoQueue.splice(queued, 1);\n\n\t\t\tconst err = new ComputeError(\n\t\t\t\t`Solve waited longer than ${waitMs}ms in queue; rejected (backpressure)`,\n\t\t\t\tErrorCodes.QUEUE_TIMEOUT,\n\t\t\t\t{\n\t\t\t\t\tstatusCode: 503,\n\t\t\t\t\tcontext: {\n\t\t\t\t\t\tkey: item.ctx.key,\n\t\t\t\t\t\twaitedMs: Date.now() - item.ctx.enqueuedAt,\n\t\t\t\t\t\tqueueWaitMs: waitMs\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t\tif (this.settleError(item, err)) {\n\t\t\t\tthis.runHook(this.onSettle, item.ctx, { status: 'error', error: err, durationMs: 0 });\n\t\t\t}\n\t\t\tthis.notify();\n\t\t}, waitMs);\n\t}\n\n\t/** Clear a queued item's wait-deadline timer, if one is pending. */\n\tprivate clearQueueWaitTimer(item: PendingItem): void {\n\t\tif (item.queueWaitTimer !== undefined) {\n\t\t\tclearTimeout(item.queueWaitTimer);\n\t\t\titem.queueWaitTimer = undefined;\n\t\t}\n\t}\n\n\tprivate enqueue(item: PendingItem): void {\n\t\tswitch (this.mode) {\n\t\t\tcase 'latest-wins': {\n\t\t\t\t// Reject any pending / abort any in-flight one as superseded\n\t\t\t\tthis.supersedeCurrent();\n\t\t\t\t// Run immediately if no slot is taken\n\t\t\t\tif (this.inFlight.size === 0) {\n\t\t\t\t\tthis.execute(item);\n\t\t\t\t} else {\n\t\t\t\t\tthis.pendingForLatestWins = item;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'queue':\n\t\t\tcase 'parallel': {\n\t\t\t\t// Same dispatch logic — the modes differ only in `maxConcurrent`'s\n\t\t\t\t// default (1 for queue, 4 for parallel), set in the constructor.\n\t\t\t\tif (this.inFlight.size < this.maxConcurrent) {\n\t\t\t\t\tthis.execute(item);\n\t\t\t\t} else if (\n\t\t\t\t\tthis.maxQueueDepth !== undefined &&\n\t\t\t\t\tthis.fifoQueue.length >= this.maxQueueDepth\n\t\t\t\t) {\n\t\t\t\t\t// Backpressure: the queue is full. Shed the *newest* call (this one)\n\t\t\t\t\t// so already-accepted work keeps its place, and give the caller an\n\t\t\t\t\t// immediate, honest QUEUE_FULL rather than an unbounded wait.\n\t\t\t\t\tthis.shedAsQueueFull(item);\n\t\t\t\t} else {\n\t\t\t\t\tthis.armQueueWaitTimer(item);\n\t\t\t\t\tthis.fifoQueue.push(item);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tprivate async execute(item: PendingItem): Promise<void> {\n\t\tconst controller = new AbortController();\n\t\t// Attach the controller to the SAME object (not a spread copy) so every\n\t\t// settle path — supersede, cancelAll, executor success/error — shares one\n\t\t// `settled` slot. A copy would split that state and fire onSettle twice.\n\t\tconst inflight = item as InFlightItem;\n\t\tinflight.controller = controller;\n\t\tthis.inFlight.add(inflight);\n\t\titem.ctx.startedAt = Date.now();\n\n\t\t// The item is now running: it can no longer time out in the queue.\n\t\tthis.clearQueueWaitTimer(item);\n\t\t// Hand abort handling over from the queued-phase listener to the\n\t\t// in-flight controller.\n\t\tthis.removeQueuedAbortHandler(item);\n\t\tconst externalAbortHandler = () => controller.abort();\n\t\titem.externalSignal?.addEventListener('abort', externalAbortHandler, { once: true });\n\n\t\tthis.runHook(this.onStart, item.ctx);\n\t\tthis.notify();\n\n\t\tconst startTime = performance.now();\n\t\ttry {\n\t\t\tconst config: GrasshopperComputeConfig = {\n\t\t\t\t...this.baseConfig,\n\t\t\t\tsignal: controller.signal,\n\t\t\t\t...(this.timeoutMs !== undefined && { timeoutMs: this.timeoutMs }),\n\t\t\t\t...(this.retry !== undefined && { retry: this.retry })\n\t\t\t};\n\n\t\t\tconst { response, definitionReuploaded } = await this.runExecutor(\n\t\t\t\titem.definition,\n\t\t\t\titem.dataTree,\n\t\t\t\titem.definitionHash,\n\t\t\t\tconfig\n\t\t\t);\n\t\t\tconst durationMs = performance.now() - startTime;\n\n\t\t\tif (this.cacheEnabled) this.writeCache(item.ctx.key, response);\n\n\t\t\t// Already superseded mid-flight — drop the late success silently.\n\t\t\tif (!this.settleSuccess(item, response)) return;\n\n\t\t\tthis.writeLastState(item.seq, { result: response, durationMs });\n\n\t\t\tthis.runHook(this.onSettle, item.ctx, {\n\t\t\t\tstatus: 'success',\n\t\t\t\tresponse,\n\t\t\t\tdurationMs,\n\t\t\t\tfromCache: false,\n\t\t\t\tdefinitionReuploaded\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst durationMs = performance.now() - startTime;\n\t\t\t// Resolve the error against the (possibly already-settled) item *before*\n\t\t\t// settling: if this was superseded mid-flight, normalizeExecutionError\n\t\t\t// returns the original cause, and _lastError should reflect it — unless\n\t\t\t// a newer solve already published state, which this late settle must\n\t\t\t// not clobber (writeLastState's seq guard).\n\t\t\tconst err = this.normalizeExecutionError(error, inflight);\n\n\t\t\tthis.writeLastState(item.seq, { error: err, durationMs });\n\n\t\t\tif (this.settleError(item, err)) {\n\t\t\t\tthis.runHook(this.onSettle, item.ctx, { status: 'error', error: err, durationMs });\n\t\t\t}\n\t\t} finally {\n\t\t\titem.externalSignal?.removeEventListener('abort', externalAbortHandler);\n\t\t\tthis.inFlight.delete(inflight);\n\t\t\tthis.drainNext();\n\t\t\tthis.notify();\n\t\t}\n\t}\n\n\t/**\n\t * Run the solve, using the server-definition-cache fast path when it's\n\t * enabled and the definition is reusable. Learns/updates the definition's\n\t * server cache key from the result so later solves can reference it.\n\t *\n\t * `definitionHash` is the {@link hashDefinition} result already computed at\n\t * `solve()` entry — threaded through rather than recomputed, so each solve\n\t * pays exactly one linear pass over the definition (issue 57).\n\t */\n\tprivate async runExecutor(\n\t\tdefinition: SolveDefinition,\n\t\tdataTree: DataTree[],\n\t\tdefinitionHash: string,\n\t\tconfig: GrasshopperComputeConfig\n\t): Promise<{ response: GrasshopperComputeResponse; definitionReuploaded?: boolean }> {\n\t\tif (\n\t\t\t!this.cacheKeyExecutor ||\n\t\t\t!this.reuseServerDefinitionCache ||\n\t\t\t!isReusableDefinition(definition)\n\t\t) {\n\t\t\treturn { response: await this.executor(definition, dataTree, config) };\n\t\t}\n\n\t\tconst defKey = definitionHash;\n\t\tconst knownKey = this.serverCacheKeys.get(defKey) ?? null;\n\n\t\tconst result = await this.cacheKeyExecutor(definition, dataTree, knownKey, config);\n\n\t\t// Record the server's (possibly refreshed) key for next time; drop a stale\n\t\t// one if the server stopped returning a key. Re-set on a hit to refresh\n\t\t// insertion order so the bounded eviction below is LRU-ish.\n\t\tif (result.cacheKey) {\n\t\t\tthis.serverCacheKeys.delete(defKey);\n\t\t\tthis.serverCacheKeys.set(defKey, result.cacheKey);\n\t\t\twhile (this.serverCacheKeys.size > SERVER_CACHE_KEYS_MAX) {\n\t\t\t\tconst oldest = this.serverCacheKeys.keys().next().value;\n\t\t\t\tif (oldest === undefined) break;\n\t\t\t\tthis.serverCacheKeys.delete(oldest);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.serverCacheKeys.delete(defKey);\n\t\t}\n\n\t\treturn { response: result.response, definitionReuploaded: result.missed };\n\t}\n\n\tprivate drainNext(): void {\n\t\tif (this.disposed) return;\n\n\t\t// latest-wins: promote pending if no in-flight\n\t\tif (this.mode === 'latest-wins') {\n\t\t\tif (this.pendingForLatestWins && this.inFlight.size === 0) {\n\t\t\t\tconst next = this.pendingForLatestWins;\n\t\t\t\tthis.pendingForLatestWins = null;\n\t\t\t\tthis.execute(next);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// queue / parallel: pull from FIFO until at capacity\n\t\twhile (this.fifoQueue.length > 0 && this.inFlight.size < this.maxConcurrent) {\n\t\t\tconst next = this.fifoQueue.shift()!;\n\t\t\tthis.execute(next);\n\t\t}\n\t}\n\n\tprivate supersede(item: PendingItem): void {\n\t\tconst err = new ComputeError('Superseded by newer solve', ErrorCodes.SUPERSEDED, {\n\t\t\tcontext: { key: item.ctx.key, enqueuedAt: item.ctx.enqueuedAt }\n\t\t});\n\t\tif (this.settleError(item, err)) {\n\t\t\tthis.runHook(this.onSuperseded, item.ctx);\n\t\t}\n\t}\n\n\tprivate makeAbortError(ctx: SolveContext): ComputeError {\n\t\treturn new ComputeError('Request aborted by caller', ErrorCodes.ABORTED, {\n\t\t\tcontext: { key: ctx.key, enqueuedAt: ctx.enqueuedAt }\n\t\t});\n\t}\n\n\t/**\n\t * Settle a pending/in-flight item exactly once with an error.\n\t *\n\t * A solve promise can be settled from four concurrent sources — the executor\n\t * resolving, the executor rejecting, `supersede`, and `cancelAll` — and a JS\n\t * promise silently ignores a second settle. This guard is the single place the\n\t * settle-once invariant lives: it makes the *first* settle win and reports\n\t * whether this call was that winner, so callers fire their own hook only when\n\t * they actually settled. Any new settle path must go through here (or\n\t * {@link settleSuccess}) so the guard can't be forgotten.\n\t *\n\t * @returns `true` if this call settled the item; `false` if it was already settled.\n\t */\n\tprivate settleError(item: PendingItem, err: ComputeError): boolean {\n\t\tif (item.settled) return false;\n\t\titem.settled = { error: err };\n\t\tthis.clearQueueWaitTimer(item);\n\t\tthis.removeQueuedAbortHandler(item);\n\t\titem.reject(err);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Settle a pending/in-flight item exactly once with a successful response.\n\t * The success counterpart to {@link settleError}; see it for the invariant.\n\t *\n\t * @returns `true` if this call settled the item; `false` if it was already settled.\n\t */\n\tprivate settleSuccess(item: PendingItem, response: GrasshopperComputeResponse): boolean {\n\t\tif (item.settled) return false;\n\t\titem.settled = { ok: true };\n\t\tthis.clearQueueWaitTimer(item);\n\t\tthis.removeQueuedAbortHandler(item);\n\t\titem.resolve(response);\n\t\treturn true;\n\t}\n\n\t/** Detach the queued-phase abort listener, if one is still attached. */\n\tprivate removeQueuedAbortHandler(item: PendingItem): void {\n\t\tif (item.queuedAbortHandler && item.externalSignal) {\n\t\t\titem.externalSignal.removeEventListener('abort', item.queuedAbortHandler);\n\t\t}\n\t\titem.queuedAbortHandler = undefined;\n\t}\n\n\tprivate isAbortLikeError(error: unknown): boolean {\n\t\tif (error instanceof Error) {\n\t\t\tif (error.name === 'AbortError') return true;\n\t\t\tif (typeof DOMException !== 'undefined' && error instanceof DOMException) {\n\t\t\t\treturn error.name === 'AbortError';\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate normalizeExecutionError(error: unknown, item: InFlightItem): ComputeError {\n\t\t// If the item was already settled (e.g. by supersede), return that error so\n\t\t// _lastError reflects the original cause rather than the downstream abort.\n\t\tif (item.settled && 'error' in item.settled) {\n\t\t\treturn item.settled.error;\n\t\t}\n\n\t\tif (error instanceof ComputeError) return error;\n\n\t\tif (this.isAbortLikeError(error)) {\n\t\t\treturn this.makeAbortError(item.ctx);\n\t\t}\n\n\t\treturn new ComputeError(\n\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\tErrorCodes.UNKNOWN_ERROR,\n\t\t\t{ originalError: error instanceof Error ? error : new Error(String(error)) }\n\t\t);\n\t}\n\n\t// --------------------------------------------------------------------------\n\t// Cancellation\n\t// --------------------------------------------------------------------------\n\n\t/** Cancel everything — in-flight and pending. */\n\tcancelAll(): void {\n\t\t// Reject pending\n\t\tif (this.pendingForLatestWins) {\n\t\t\tthis.rejectAsAborted(this.pendingForLatestWins);\n\t\t\tthis.pendingForLatestWins = null;\n\t\t}\n\t\twhile (this.fifoQueue.length > 0) {\n\t\t\tconst item = this.fifoQueue.shift()!;\n\t\t\tthis.rejectAsAborted(item);\n\t\t}\n\t\t// Abort in-flight — their finally blocks will reject their promises\n\t\tfor (const inflight of this.inFlight) {\n\t\t\tconst err = this.makeAbortError(inflight.ctx);\n\t\t\tif (this.settleError(inflight, err)) {\n\t\t\t\tthis.runHook(this.onSettle, inflight.ctx, {\n\t\t\t\t\tstatus: 'error',\n\t\t\t\t\terror: err,\n\t\t\t\t\t// startedAt is a Date.now() timestamp — measure with the same clock.\n\t\t\t\t\tdurationMs: inflight.ctx.startedAt ? Date.now() - inflight.ctx.startedAt : 0\n\t\t\t\t});\n\t\t\t}\n\t\t\tinflight.controller.abort();\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tprivate rejectAsAborted(item: PendingItem): void {\n\t\tconst err = this.makeAbortError(item.ctx);\n\t\t// Queued items get the same settle hook as in-flight ones, so consumers\n\t\t// pairing solve() calls with settles don't leak \"in progress\" indicators.\n\t\tif (this.settleError(item, err)) {\n\t\t\tthis.runHook(this.onSettle, item.ctx, { status: 'error', error: err, durationMs: 0 });\n\t\t}\n\t}\n\n\t// --------------------------------------------------------------------------\n\t// Cache\n\t// --------------------------------------------------------------------------\n\n\tprivate readCache(key: string): GrasshopperComputeResponse | null {\n\t\tif (!this.cacheEnabled) return null;\n\t\tconst entry = this.cache.get(key);\n\t\tif (!entry) {\n\t\t\tthis.cacheMisses += 1;\n\t\t\treturn null;\n\t\t}\n\t\tif (this.cacheTtl > 0 && Date.now() - entry.insertedAt > this.cacheTtl) {\n\t\t\tthis.dropCacheEntry(key);\n\t\t\t// An expired entry is a miss, not a separate outcome — the solve runs\n\t\t\t// either way, which is what a hit rate is measuring.\n\t\t\tthis.cacheMisses += 1;\n\t\t\treturn null;\n\t\t}\n\t\t// LRU touch\n\t\tthis.cache.delete(key);\n\t\tthis.cache.set(key, entry);\n\t\tthis.cacheHits += 1;\n\t\treturn entry.response;\n\t}\n\n\tprivate writeCache(key: string, response: GrasshopperComputeResponse): void {\n\t\tif (!this.cacheEnabled) return;\n\t\tif (!this.cacheErroredSolves && response.errors && response.errors.length > 0) return;\n\t\t// Prefer the wire-size hint recorded at the fetch boundary; a response\n\t\t// that never crossed the fetch layer (custom executor, tests) pays a\n\t\t// one-off stringify here — once per fresh solve, never per hit.\n\t\tconst sizeBytes = getResponseWireSize(response) ?? estimateResponseSize(response);\n\t\t// An entry larger than the whole byte budget would evict everything\n\t\t// (including itself) — serve it through, retain nothing.\n\t\tif (sizeBytes > this.cacheMaxBytes) return;\n\t\tthis.dropCacheEntry(key); // replace-in-place: release the old copy's bytes\n\t\tthis.cache.set(key, { response, insertedAt: Date.now(), sizeBytes });\n\t\tthis.cacheBytes += sizeBytes;\n\t\twhile (this.cacheBytes > this.cacheMaxBytes && this.cache.size > 0) {\n\t\t\tconst oldest = this.cache.keys().next().value;\n\t\t\tif (oldest === undefined) break;\n\t\t\tthis.dropCacheEntry(oldest);\n\t\t\tthis.cacheEvictions += 1;\n\t\t}\n\t}\n\n\t/** Remove one cache entry and release its bytes from the running total. */\n\tprivate dropCacheEntry(key: string): void {\n\t\tconst entry = this.cache.get(key);\n\t\tif (!entry) return;\n\t\tthis.cache.delete(key);\n\t\tthis.cacheBytes -= entry.sizeBytes;\n\t}\n\n\tclearCache(): void {\n\t\tthis.cache.clear();\n\t\tthis.cacheBytes = 0;\n\t}\n\n\t/**\n\t * Observability snapshot of the solve cache: current size plus lifetime\n\t * hit/miss/eviction counters.\n\t *\n\t * `hits`/`misses` count cache CONSULTATIONS, so `hits / (hits + misses)` is the\n\t * hit rate. A TTL-expired entry counts as a miss (the solve runs either way).\n\t * `evictions` counts only entries dropped under size/byte pressure — not\n\t * replace-in-place writes or TTL expiry, which are not capacity signals.\n\t * Counters are cumulative and survive `clearCache()`.\n\t */\n\tcacheStats(): {\n\t\tentries: number;\n\t\tbytes: number;\n\t\thits: number;\n\t\tmisses: number;\n\t\tevictions: number;\n\t} {\n\t\treturn {\n\t\t\tentries: this.cache.size,\n\t\t\tbytes: this.cacheBytes,\n\t\t\thits: this.cacheHits,\n\t\t\tmisses: this.cacheMisses,\n\t\t\tevictions: this.cacheEvictions\n\t\t};\n\t}\n\n\t// --------------------------------------------------------------------------\n\t// Lifecycle\n\t// --------------------------------------------------------------------------\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tthis.cancelAll();\n\t\tthis.subscribers.clear();\n\t\tthis.clearCache();\n\t}\n\n\tprivate runHook<H extends (...args: any[]) => void>(\n\t\thook: H | undefined,\n\t\t...args: Parameters<H>\n\t): void {\n\t\tif (!hook) return;\n\t\ttry {\n\t\t\thook(...args);\n\t\t} catch (err) {\n\t\t\tgetLogger().error('[SolveScheduler] hook threw:', err);\n\t\t}\n\t}\n}\n","import { ErrorCodes, ComputeError } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\nimport { readField } from '@/core/utils/read-field';\nimport ComputeServerStats from '../server/compute-server-stats';\nimport { validateServerUrl } from '@/core/server/validate-server-url';\nimport { ComputeConfig, RetryPolicy } from '@/core/types';\n\nimport { fetchDefinitionIO, fetchParsedDefinitionIO, solveGrasshopperDefinition } from '..';\nimport { solveByCacheKey, solveGrasshopperDefinitionWithCacheKey } from '../solve';\nimport { GrasshopperComputeConfig, GrasshopperComputeResponse, DataTree } from '../types';\nimport { isDefinitionRef, type SolveDefinition } from '@/core/definition-ref';\nimport {\n\tSolveScheduler,\n\tSolveSchedulerOptions,\n\tCacheKeyExecutor\n} from '../scheduler/solve-scheduler';\n\n/**\n * Per-call options that override the client's default ComputeConfig values.\n *\n * Use these for per-request control without mutating the client config:\n * - `signal` — cancel a specific solve (e.g. when a slider value is superseded)\n * - `timeoutMs` — extend timeout for a long-running solve, or pass `0` to disable\n * - `retry` — override retry policy for this call only\n */\nexport interface SolveOptions {\n\tsignal?: AbortSignal;\n\ttimeoutMs?: number;\n\tretry?: RetryPolicy;\n}\n\n/** Compact description of a definition for error context — never the full payload. */\nfunction describeDefinition(definition: SolveDefinition): string {\n\tif (isDefinitionRef(definition)) return `ref:${definition.key}`;\n\tif (typeof definition === 'string' && definition.length < 200) return definition;\n\treturn '...content...';\n}\n\n/** One input parameter's footprint in the error-context summary (issue 83). */\nexport interface DataTreeSummaryEntry {\n\t/** The input's `ParamName` (`<unnamed>` when absent). */\n\tparam: string;\n\t/** Total items across all branches. */\n\titems: number;\n\t/** Approximate payload size: summed `data` string lengths across items. */\n\tbytes: number;\n}\n\n/**\n * Compact summary of the input data tree for error context — never the full\n * payload (issue 83). Trees can embed multi-MB geometry/base64; attaching them\n * to thrown errors pins those buffers in every logger/telemetry/error-boundary\n * that retains the error. Param names, item counts, and byte sizes are enough\n * to correlate a failure with its inputs.\n *\n * Reads `ParamName`/`InnerTree` case-insensitively and never throws — error\n * construction must not fail on a malformed tree.\n */\nfunction summarizeDataTree(dataTree: DataTree[]): DataTreeSummaryEntry[] {\n\tif (!Array.isArray(dataTree)) return [];\n\n\treturn dataTree.map((tree) => {\n\t\tlet items = 0;\n\t\tlet bytes = 0;\n\n\t\tconst innerTree = readField<Record<string, unknown>>(tree, 'innerTree');\n\t\tif (innerTree && typeof innerTree === 'object') {\n\t\t\tfor (const branch of Object.values(innerTree)) {\n\t\t\t\tif (!Array.isArray(branch)) continue;\n\t\t\t\titems += branch.length;\n\t\t\t\tfor (const item of branch) {\n\t\t\t\t\tconst data = (item as { data?: unknown } | null)?.data;\n\t\t\t\t\tif (typeof data === 'string') bytes += data.length;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { param: readField<string>(tree, 'paramName') ?? '<unnamed>', items, bytes };\n\t});\n}\n\n/**\n * GrasshopperClient provides a simple API for interacting with a Rhino Compute server and grasshopper.\n *\n * @public This is the recommended high-level API for Rhino Compute operations.\n *\n * **Security Warning:**\n * Using this client in a browser environment exposes your server URL and API key to users.\n * For production, use this library server-side or proxy requests through your own backend.\n *\n * @example\n * ```typescript\n * const client = await GrasshopperClient.create({\n * serverUrl: 'http://localhost:6500',\n * apiKey: 'your-api-key'\n * });\n *\n * try {\n * const result = await client.solve(definitionUrl, { x: 1, y: 2 });\n * } finally {\n * await client.dispose(); // Clean up resources\n * }\n * ```\n */\nexport default class GrasshopperClient {\n\tprivate readonly config: GrasshopperComputeConfig;\n\tpublic readonly serverStats: ComputeServerStats;\n\tprivate disposed = false;\n\n\tprivate constructor(config: GrasshopperComputeConfig) {\n\t\tthis.config = this.normalizeComputeConfig(config);\n\t\tthis.serverStats = new ComputeServerStats(this.config.serverUrl, this.config.apiKey);\n\t}\n\n\t/**\n\t * Creates and initializes a GrasshopperClient with server validation.\n\t *\n\t * The pre-flight liveness probe (a GET on the proxy root `/`) is a\n\t * single-sample boolean gate that reads a cold or briefly-busy-but-up server\n\t * as offline. To avoid failing\n\t * construction on that transient class, the probe is retried with a short\n\t * exponential backoff before giving up. Each probe is also bounded by a\n\t * timeout so a hung connection can't stall construction.\n\t *\n\t * @throws {ComputeError} with code NETWORK_ERROR if the server stays\n\t * unreachable across all attempts\n\t * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid\n\t */\n\tstatic async create(config: GrasshopperComputeConfig): Promise<GrasshopperClient> {\n\t\tconst client = new GrasshopperClient(config);\n\n\t\t// A single liveness miss isn't authoritative — a cold/busy-but-up\n\t\t// server flickers non-200. Retry a few times with backoff before failing.\n\t\tconst attempts = Math.max(1, (config.retry?.attempts ?? 2) + 1);\n\t\tconst baseDelayMs = config.retry?.baseDelayMs ?? 250;\n\t\tconst maxDelayMs = config.retry?.maxDelayMs ?? 1000;\n\n\t\t// The liveness probe is a trivial GET — always bound it, independent of the\n\t\t// solve timeout (which may be 0 to allow arbitrarily long solves).\n\t\tfor (let attempt = 0; attempt < attempts; attempt++) {\n\t\t\tif (await client.serverStats.isServerOnline()) {\n\t\t\t\treturn client;\n\t\t\t}\n\n\t\t\tif (attempt < attempts - 1) {\n\t\t\t\tconst delay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t\t}\n\t\t}\n\n\t\tawait client.dispose();\n\t\tthrow new ComputeError('Rhino Compute server is not online', ErrorCodes.NETWORK_ERROR, {\n\t\t\tcontext: { serverUrl: client.config.serverUrl, attempts }\n\t\t});\n\t}\n\n\t/**\n\t * Gets the client's configuration.\n\t * Useful for passing to lower-level functions.\n\t */\n\tpublic getConfig(): GrasshopperComputeConfig {\n\t\tthis.ensureNotDisposed();\n\t\treturn { ...this.config };\n\t}\n\n\t/**\n\t * Get input/output parameters of a Grasshopper definition.\n\t */\n\tpublic async getIO(definition: string | Uint8Array) {\n\t\tthis.ensureNotDisposed();\n\t\treturn fetchParsedDefinitionIO(definition, this.config);\n\t}\n\n\tpublic async getRawIO(definition: string | Uint8Array) {\n\t\tthis.ensureNotDisposed();\n\t\treturn fetchDefinitionIO(definition, this.config);\n\t}\n\n\t/**\n\t * Run a compute job with a Grasshopper definition.\n\t *\n\t * @throws {ComputeError} with code INVALID_INPUT if definition is empty\n\t * @throws {ComputeError} with code NETWORK_ERROR if server is offline\n\t * @throws {ComputeError} with code COMPUTATION_ERROR if computation fails.\n\t * On a partial-success response (some outputs computed, some errored) the\n\t * error's `context.values` carries the outputs that did compute — pass\n\t * `{ values } as GrasshopperComputeResponse` to the response processors to\n\t * render them. `context.inputSummary` describes the inputs (param names,\n\t * item counts, byte sizes) without pinning the full data tree.\n\t */\n\tpublic async solve(\n\t\tdefinition: SolveDefinition,\n\t\tdataTree: DataTree[],\n\t\toptions?: SolveOptions\n\t): Promise<GrasshopperComputeResponse> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\t// Validate inputs\n\t\t\tif (typeof definition === 'string' && !definition?.trim()) {\n\t\t\t\tthrow new ComputeError('Definition URL/content is required', ErrorCodes.INVALID_INPUT, {\n\t\t\t\t\tcontext: { receivedUrl: definition }\n\t\t\t\t});\n\t\t\t} else if (definition instanceof Uint8Array && definition.length === 0) {\n\t\t\t\tthrow new ComputeError('Definition content is empty', ErrorCodes.INVALID_INPUT);\n\t\t\t} else if (isDefinitionRef(definition) && !definition.key.trim()) {\n\t\t\t\tthrow new ComputeError('DefinitionRef key is empty', ErrorCodes.INVALID_INPUT);\n\t\t\t}\n\n\t\t\t// Per-call options override the client's stored config for this request only\n\t\t\tconst effectiveConfig: GrasshopperComputeConfig = {\n\t\t\t\t...this.config,\n\t\t\t\t...(options?.signal !== undefined && { signal: options.signal }),\n\t\t\t\t...(options?.timeoutMs !== undefined && { timeoutMs: options.timeoutMs }),\n\t\t\t\t...(options?.retry !== undefined && { retry: options.retry })\n\t\t\t};\n\n\t\t\t// Skip the redundant pre-flight healthcheck — fetchCompute already surfaces\n\t\t\t// network failures with a NETWORK_ERROR code, so adding a roundtrip here only\n\t\t\t// doubles latency on every solve.\n\t\t\tconst result = await solveGrasshopperDefinition(dataTree, definition, effectiveConfig);\n\n\t\t\t// Compute may return a partial-success response (HTTP 500 with a body\n\t\t\t// containing both `values` and `errors`/`warnings`). Surface that as a\n\t\t\t// COMPUTATION_ERROR so callers don't silently consume a broken result.\n\t\t\tif (result?.errors && result.errors.length > 0) {\n\t\t\t\tthrow new ComputeError(\n\t\t\t\t\tresult.errors.join('; ') || 'Computation failed',\n\t\t\t\t\tErrorCodes.COMPUTATION_ERROR,\n\t\t\t\t\t{\n\t\t\t\t\t\tcontext: {\n\t\t\t\t\t\t\tdefinition: describeDefinition(definition),\n\t\t\t\t\t\t\t// Summary only — attaching the full dataTree would pin\n\t\t\t\t\t\t\t// multi-MB input buffers in telemetry (issue 83).\n\t\t\t\t\t\t\tinputSummary: summarizeDataTree(dataTree),\n\t\t\t\t\t\t\terrors: result.errors,\n\t\t\t\t\t\t\twarnings: result.warnings,\n\t\t\t\t\t\t\t// The outputs that DID compute (issue 63): the transport\n\t\t\t\t\t\t\t// parses partial values out of the 500 body, so hand them\n\t\t\t\t\t\t\t// to callers who want to render what succeeded and inspect\n\t\t\t\t\t\t\t// what failed (e.g. via getValues on { values }).\n\t\t\t\t\t\t\tvalues: result.values\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tif (this.config.debug) {\n\t\t\t\tgetLogger().error('Compute failed:', error);\n\t\t\t}\n\n\t\t\tif (error instanceof ComputeError) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tthrow new ComputeError(\n\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\tErrorCodes.COMPUTATION_ERROR,\n\t\t\t\t{\n\t\t\t\t\tcontext: {\n\t\t\t\t\t\tdefinition: describeDefinition(definition),\n\t\t\t\t\t\t// Summary only — never the full dataTree (issue 83).\n\t\t\t\t\t\tinputSummary: summarizeDataTree(dataTree)\n\t\t\t\t\t},\n\t\t\t\t\toriginalError: error instanceof Error ? error : new Error(String(error))\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Create a scheduler bound to this client. Use a scheduler for any UI surface\n\t * that fires solves frequently (sliders, live editors) or that needs cancel\n\t * semantics, response caching, or state observability.\n\t *\n\t * Multiple schedulers can be created from a single client — typically one per\n\t * UI surface so their queues stay independent.\n\t *\n\t * @example\n\t * ```ts\n\t * const sliderScheduler = client.createScheduler({ mode: 'latest-wins' });\n\t * const submitScheduler = client.createScheduler({ mode: 'queue', timeoutMs: 0, retry: { attempts: 1 } });\n\t * ```\n\t */\n\tpublic createScheduler(options?: SolveSchedulerOptions): SolveScheduler {\n\t\tthis.ensureNotDisposed();\n\t\tconst executor = (\n\t\t\tdefinition: SolveDefinition,\n\t\t\tdataTree: DataTree[],\n\t\t\tconfig: GrasshopperComputeConfig\n\t\t) => solveGrasshopperDefinition(dataTree, definition, config);\n\n\t\t// Cache-key-aware executor: solve by `pointer: cacheKey` when known (skips\n\t\t// re-uploading large definitions), capturing/refreshing the key and\n\t\t// falling back to a full upload on a server cache miss.\n\t\tconst cacheKeyExecutor: CacheKeyExecutor = (definition, dataTree, cacheKey, config) =>\n\t\t\tcacheKey === null\n\t\t\t\t? solveGrasshopperDefinitionWithCacheKey(dataTree, definition, config).then((r) => ({\n\t\t\t\t\t\t...r,\n\t\t\t\t\t\tmissed: false\n\t\t\t\t\t}))\n\t\t\t\t: solveByCacheKey(dataTree, cacheKey, definition, config);\n\n\t\treturn new SolveScheduler(executor, this.config, options, cacheKeyExecutor);\n\t}\n\n\t/**\n\t * Disposes of client resources.\n\t * Call this when you're done using the client.\n\t */\n\tpublic async dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\t\tawait this.serverStats.dispose();\n\t}\n\n\t/**\n\t * Ensures the client hasn't been disposed.\n\t */\n\tprivate ensureNotDisposed(): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t'GrasshopperClient has been disposed and cannot be used',\n\t\t\t\tErrorCodes.INVALID_STATE\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Validates and normalizes a compute configuration.\n\t *\n\t * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid\n\t */\n\tprivate normalizeComputeConfig<T extends ComputeConfig | GrasshopperComputeConfig>(config: T): T {\n\t\treturn {\n\t\t\t...config,\n\t\t\tserverUrl: validateServerUrl(config.serverUrl),\n\t\t\tapiKey: config.apiKey,\n\t\t\tauthToken: config.authToken,\n\t\t\tdebug: config.debug ?? false,\n\t\t\tsuppressBrowserWarning: config.suppressBrowserWarning\n\t\t} as T;\n\t}\n}\n","import type { RhinoModule } from 'rhino3dm';\nimport { getLogger } from '@/core';\n\n// -----------------------------------------------------------------------------\n// Decoder Types\n// -----------------------------------------------------------------------------\n\ntype RhinoDecoder = (rhino: RhinoModule, data: unknown) => unknown;\n\nconst decoderRegistry = new Map<string, RhinoDecoder>();\n\n// -----------------------------------------------------------------------------\n// Registration\n// -----------------------------------------------------------------------------\n\nexport function registerDecoder(typeName: string, decoder: RhinoDecoder): void {\n\tdecoderRegistry.set(typeName, decoder);\n}\n\nregisterDecoder('Rhino.Geometry.Point3d', (rhino, data) => {\n\tconst d = data as any;\n\tif (!d || typeof d.X !== 'number') return null;\n\treturn new rhino.Point([d.X, d.Y, d.Z]);\n});\n\nregisterDecoder('Rhino.Geometry.Line', (rhino, data) => {\n\tconst d = data as any;\n\tif (!d || !d.From || !d.To) return null;\n\treturn new rhino.Line([d.From.X, d.From.Y, d.From.Z], [d.To.X, d.To.Y, d.To.Z]);\n});\n\n// -----------------------------------------------------------------------------\n// Utility Functions\n// -----------------------------------------------------------------------------\n\nfunction findDecoder(rhinoType: string): RhinoDecoder | undefined {\n\tif (decoderRegistry.has(rhinoType)) return decoderRegistry.get(rhinoType);\n\tfor (const [key, dec] of decoderRegistry) {\n\t\tif (rhinoType.startsWith(key)) return dec;\n\t}\n\treturn undefined;\n}\n\n/**\n * Whether the parsed item looks like a rhino3dm serialization envelope\n * (`{version, archive3dm, opennurbs, data: \"<base64>\"}`). `CommonObject.decode` expects the WHOLE\n * envelope — unwrapping `.data` first hands it the bare base64 string, which throws or decodes to\n * garbage (see the correct usage in display-items-parser.ts).\n */\nfunction isDecodableEnvelope(parsedData: unknown): parsedData is object {\n\treturn Boolean(\n\t\tparsedData && typeof parsedData === 'object' && typeof (parsedData as any).data === 'string'\n\t);\n}\n\n// -----------------------------------------------------------------------------\n// Geometry Decoding\n// -----------------------------------------------------------------------------\n\n/**\n * Sentinel returned by {@link decodeRhinoGeometry} when a decode was attempted\n * and failed hard (a registered decoder or `CommonObject.decode` threw). Carries\n * the original payload so callers can log or retry. Use\n * {@link isRhinoDecodeError} to detect it.\n */\nexport interface RhinoDecodeError {\n\t__decodeError: true;\n\t/** The Rhino type name the failed decode was attempted for. */\n\ttype: string;\n\t/** The original (undecoded) payload. */\n\traw: unknown;\n}\n\n/** Type guard for the {@link RhinoDecodeError} sentinel. */\nexport function isRhinoDecodeError(value: unknown): value is RhinoDecodeError {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as RhinoDecodeError).__decodeError === true\n\t);\n}\n\nfunction makeDecodeError(rhinoType: string, raw: unknown): RhinoDecodeError {\n\treturn { __decodeError: true, type: rhinoType, raw };\n}\n\n/**\n * Decode one typed payload from a solve response into a rhino3dm object.\n *\n * Failure signaling is a single coherent scheme (issue 85) — every call yields\n * exactly one of three outcomes:\n *\n * 1. **Decoded value** — a registered decoder recognized the shape, or the\n * payload was a rhino3dm serialization envelope and `CommonObject.decode`\n * succeeded.\n * 2. **Raw passthrough** (`=== parsedData`) — no decode path *applied*: no\n * decoder matched the type (or the matched decoder returned `null`, meaning\n * \"not my shape\") and the payload is not a decodable envelope. Not an error;\n * the caller keeps the parsed JSON as-is.\n * 3. **{@link RhinoDecodeError} sentinel** — a decode was *attempted and threw*\n * (a registered decoder threw and no envelope fallback could recover, or\n * `CommonObject.decode` threw). Detect with {@link isRhinoDecodeError}.\n *\n * A decoder returning `null` is a soft miss, never an error: it falls through\n * to the envelope fallback. This matters for prefix collisions — e.g.\n * `Rhino.Geometry.LineCurve` matches the `Rhino.Geometry.Line` decoder but is\n * a CommonObject envelope, not a `{From, To}` pair, and must reach\n * `CommonObject.decode`.\n */\nexport function decodeRhinoGeometry(\n\tparsedData: unknown,\n\trhinoType: string,\n\trhino: RhinoModule\n): unknown {\n\tconst decoder = findDecoder(rhinoType);\n\t// A throwing decoder is a hard failure (unlike a null return, which is a\n\t// soft \"not my shape\" miss) — remember it so that when no envelope fallback\n\t// can recover we return the error sentinel instead of silently passing the\n\t// raw payload through as if no decode had ever been attempted.\n\tlet decoderThrew = false;\n\tif (decoder) {\n\t\ttry {\n\t\t\tconst decoded = decoder(rhino, parsedData);\n\t\t\tif (decoded != null) return decoded;\n\t\t} catch (error) {\n\t\t\tgetLogger().warn(`Failed to decode Rhino type ${rhinoType}:`, error);\n\t\t\tdecoderThrew = true;\n\t\t}\n\t}\n\n\t// Fallback using CommonObject.decode — fed the full envelope, not the unwrapped payload.\n\ttry {\n\t\tif (isDecodableEnvelope(parsedData)) return rhino.CommonObject.decode(parsedData);\n\t} catch (error) {\n\t\tgetLogger().warn(`Failed to decode ${rhinoType} with CommonObject:`, error);\n\t\treturn makeDecodeError(rhinoType, parsedData);\n\t}\n\n\treturn decoderThrew ? makeDecodeError(rhinoType, parsedData) : parsedData;\n}\n\n// -----------------------------------------------------------------------------\n// Disposal\n// -----------------------------------------------------------------------------\n\n/**\n * Free every rhino3dm WASM object reachable from `value`.\n *\n * rhino3dm objects are emscripten bindings — JS GC never reclaims their WASM\n * heap allocation, so everything decoded from a solve response (`getValues`,\n * `getValue`, `decodeRhinoObject`) must be deleted explicitly or the heap\n * grows monotonically across solves (e.g. a UI decoding per slider tick).\n *\n * Walks arrays and plain objects recursively; anything exposing a `delete()`\n * method is treated as a WASM binding and freed (skipped if already deleted).\n * Safe to call more than once and on values containing no WASM objects.\n */\nexport function disposeRhinoObjects(value: unknown): void {\n\t// Aliased references (the same decoded object aggregated under two keys)\n\t// must only be deleted once.\n\tconst seen = new WeakSet<object>();\n\n\tconst walk = (v: unknown): void => {\n\t\tif (!v || typeof v !== 'object') return;\n\t\tif (seen.has(v)) return;\n\t\tseen.add(v);\n\n\t\tconst del = (v as { delete?: unknown }).delete;\n\t\tif (typeof del === 'function') {\n\t\t\tconst isDeleted = (v as { isDeleted?: () => boolean }).isDeleted;\n\t\t\tif (typeof isDeleted !== 'function' || !isDeleted.call(v)) {\n\t\t\t\t(del as () => void).call(v);\n\t\t\t}\n\t\t\treturn; // a WASM binding's internals are not ours to walk\n\t\t}\n\n\t\tif (Array.isArray(v)) {\n\t\t\tfor (const item of v) walk(item);\n\t\t\treturn;\n\t\t}\n\t\t// Only walk plain containers — class instances other than WASM bindings\n\t\t// (Dates, typed arrays, ...) hold nothing decodable.\n\t\tconst proto = Object.getPrototypeOf(v);\n\t\tif (proto === Object.prototype || proto === null) {\n\t\t\tfor (const item of Object.values(v)) walk(item);\n\t\t}\n\t};\n\n\twalk(value);\n}\n\n// -----------------------------------------------------------------------------\n// Object Decoder\n// -----------------------------------------------------------------------------\n\nexport interface DecodeRhinoOptions {\n\tkeys?: string[];\n\tskipKeys?: string[];\n\tdeep?: boolean;\n}\n\nexport function decodeRhinoObject<T extends Record<string, unknown>>(\n\tobj: T,\n\trhino: RhinoModule,\n\toptions: DecodeRhinoOptions = {}\n): T {\n\tconst { keys, skipKeys, deep } = options;\n\tconst out: Record<string, unknown> = { ...obj };\n\n\tconst shouldProcessKey = (k: string) => {\n\t\tif (skipKeys?.includes(k)) return false;\n\t\tif (keys && !keys.includes(k)) return false;\n\t\treturn true;\n\t};\n\n\tfor (const [key, value] of Object.entries(obj)) {\n\t\tif (!shouldProcessKey(key)) continue;\n\t\tif (!value || typeof value !== 'object') continue;\n\n\t\tconst v: any = value;\n\t\tconst maybeType = !Array.isArray(v) && typeof v.type === 'string' ? v.type : undefined;\n\n\t\tif (maybeType) {\n\t\t\tout[key] = decodeRhinoGeometry(v, maybeType, rhino);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (deep) {\n\t\t\t// Arrays must stay arrays (issue 61): recursing into decodeRhinoObject\n\t\t\t// would object-spread `[a, b]` into `{0: a, 1: b}`, breaking\n\t\t\t// Array.isArray/.map downstream. Map over elements instead.\n\t\t\tout[key] = Array.isArray(v)\n\t\t\t\t? decodeDeepArray(v, rhino, options)\n\t\t\t\t: decodeRhinoObject(v as any, rhino, options);\n\t\t}\n\t}\n\n\treturn out as T;\n}\n\n/**\n * Deep-mode helper: decode every element of an array while preserving the\n * array shape. Type-tagged elements decode via {@link decodeRhinoGeometry},\n * nested arrays recurse, plain objects recurse through\n * {@link decodeRhinoObject} (so `keys`/`skipKeys` filtering still applies to\n * their fields), and primitives pass through untouched.\n */\nfunction decodeDeepArray(\n\tarr: unknown[],\n\trhino: RhinoModule,\n\toptions: DecodeRhinoOptions\n): unknown[] {\n\treturn arr.map((el) => {\n\t\tif (!el || typeof el !== 'object') return el;\n\t\tif (Array.isArray(el)) return decodeDeepArray(el, rhino, options);\n\t\tconst maybeType = typeof (el as any).type === 'string' ? (el as any).type : undefined;\n\t\tif (maybeType) return decodeRhinoGeometry(el, maybeType, rhino);\n\t\treturn decodeRhinoObject(el as Record<string, unknown>, rhino, options);\n\t});\n}\n","import { FileData } from '@/core/files/types';\nimport { readField } from '@/core/utils/read-field';\nimport { GrasshopperComputeResponse, DataItem } from '../../types';\nimport { decodeRhinoGeometry, disposeRhinoObjects } from './rhino-decoder';\n\nexport interface ParsedContext {\n\t[key: string]: any;\n}\n\nexport interface GetValuesOptions {\n\tparseValues?: boolean;\n\trhino?: any;\n\t/**\n\t * If true, only include values of type System.String in the result.\n\t * Non-string types are filtered out.\n\t */\n\tstringOnly?: boolean;\n}\n\nexport interface GetValuesResult<T = ParsedContext> {\n\tvalues: T;\n\t/**\n\t * Free every rhino3dm WASM object decoded into `values`. When a `rhino`\n\t * instance was passed, decoded geometry lives on the WASM heap and is never\n\t * garbage-collected — call this once the values are consumed, or the heap\n\t * grows monotonically across solves. Idempotent; a no-op when nothing was\n\t * decoded. `values` must not be used after disposal.\n\t */\n\tdispose: () => void;\n}\n\n// -----------------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------------\n\nconst SYSTEM_TYPES = {\n\tSTRING: 'System.String',\n\tINT: 'System.Int32',\n\tDOUBLE: 'System.Double',\n\tBOOL: 'System.Boolean'\n};\n\nconst RHINO_GEOMETRY_PREFIX = 'Rhino.Geometry.';\n\nconst EXCLUDED_TYPES = ['WebDisplay'];\nconst FILE_DATA_TYPE = 'FileData';\n\n/** Checks if a type is excluded by EXCLUDED_TYPES. */\nfunction isExcludedType(type: string): boolean {\n\treturn EXCLUDED_TYPES.some((t) => type.includes(t));\n}\n\nfunction tryDecodeJSON(value: string): any {\n\tif (typeof value !== 'string') return value;\n\n\tconst trimmed = value.trim();\n\tconst looksJson = trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('\"');\n\tif (!looksJson) return value;\n\n\ttry {\n\t\tconst first = JSON.parse(trimmed);\n\t\tif (typeof first === 'string') {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(first);\n\t\t\t} catch {\n\t\t\t\treturn first;\n\t\t\t}\n\t\t}\n\t\treturn first;\n\t} catch {\n\t\treturn value;\n\t}\n}\n\nfunction decodeBySystemType(raw: any, type: string, rhino?: any): any {\n\tswitch (type) {\n\t\tcase SYSTEM_TYPES.STRING:\n\t\t\tif (typeof raw !== 'string') return raw;\n\t\t\treturn raw.replace(/^\"(.*)\"$/, '$1');\n\n\t\tcase SYSTEM_TYPES.INT:\n\t\t\treturn Number.parseInt(raw, 10);\n\n\t\tcase SYSTEM_TYPES.DOUBLE:\n\t\t\treturn Number.parseFloat(raw);\n\n\t\tcase SYSTEM_TYPES.BOOL: {\n\t\t\tconst str = String(raw).toLowerCase();\n\t\t\treturn str === 'true';\n\t\t}\n\n\t\tdefault:\n\t\t\tif (rhino && type.startsWith(RHINO_GEOMETRY_PREFIX)) {\n\t\t\t\treturn decodeRhinoGeometry(raw, type, rhino);\n\t\t\t}\n\t\t\treturn raw;\n\t}\n}\n\n/**\n * Per-item memo of the (potentially double) JSON.parse in {@link tryDecodeJSON}\n * (issue 84). Response items are immutable wire data, so the parse result is\n * stable; repeated `getValues()`/`getValue()` calls over the same response — a\n * common pattern (read one param, then another) — otherwise re-run up to two\n * full `JSON.parse` passes per item over potentially multi-MB envelope strings.\n *\n * Keyed weakly on the item object, so the cache lives exactly as long as the\n * response it belongs to. Consequence: parsed JSON *objects* are shared across\n * reads of the same response — callers must treat them as read-only. Rhino\n * geometry is NOT shared: `decodeRhinoGeometry` constructs a fresh WASM object\n * per call, so `dispose()` on one read never invalidates another.\n */\nconst decodedJsonCache = new WeakMap<DataItem, unknown>();\n\nfunction decodeItemJSON(item: DataItem): unknown {\n\tif (decodedJsonCache.has(item)) return decodedJsonCache.get(item);\n\tconst parsed = tryDecodeJSON(item.data);\n\tdecodedJsonCache.set(item, parsed);\n\treturn parsed;\n}\n\n// Main extractor — assumes type has already been filtered through isExcludedType\n// at the call site. Returning a sentinel from here would pollute the aggregated\n// arrays in getValues / getValue when multiple branches are mixed.\nfunction extractItemValue(item: DataItem, type: string, parseValues: boolean, rhino?: any): any {\n\tif (typeof item.data !== 'string') return item.data;\n\n\tconst raw = parseValues ? decodeItemJSON(item) : item.data;\n\treturn decodeBySystemType(raw, type, rhino);\n}\n\n/**\n * Type guard for {@link FileData}. The Compute server emits these as JSON\n * blobs inside `FileData`-typed values; this checks that the parsed shape\n * has every required field before we trust it.\n */\nfunction isFileData(value: unknown): value is FileData {\n\tif (!value || typeof value !== 'object') return false;\n\tconst v = value as Record<string, unknown>;\n\treturn (\n\t\ttypeof v.fileName === 'string' &&\n\t\ttypeof v.fileType === 'string' &&\n\t\t'data' in v &&\n\t\ttypeof v.isBase64Encoded === 'boolean' &&\n\t\ttypeof v.subFolder === 'string'\n\t);\n}\n\n// Traversal helpers\n\n/**\n * The response's params array, read case-insensitively (`values` vs `Values`\n * across server branches) with a null guard. Partial-success and warnings-only\n * responses can arrive with `values` missing entirely — treat that as \"no\n * params\" instead of crashing (issue 62), mirroring the defense in solve.ts.\n */\nfunction getResponseParams(response: GrasshopperComputeResponse): unknown[] {\n\tconst values = readField<unknown[]>(response, 'values');\n\treturn Array.isArray(values) ? values : [];\n}\n\n/** A param's item type, tolerating items whose `type` is missing/non-string. */\nfunction itemType(item: DataItem): string {\n\treturn typeof item.type === 'string' ? item.type : '';\n}\n\n/**\n * Iterates over every data item within a Grasshopper tree structure.\n *\n * Tolerates a missing/null tree (params in warnings-only partial successes and\n * the e2e fixtures ship without `InnerTree` — issue 62) and skips non-object\n * items so handlers never see `null`.\n *\n * @param tree - The Grasshopper tree structure containing branches of items.\n * @param handler - A callback function invoked for each {@link DataItem} found within the tree branches.\n */\nfunction forEachTreeItem(tree: unknown, handler: (item: DataItem) => void) {\n\tif (!tree || typeof tree !== 'object') return;\n\tfor (const list of Object.values(tree)) {\n\t\tif (Array.isArray(list)) {\n\t\t\tfor (const item of list) {\n\t\t\t\tif (item && typeof item === 'object') handler(item as DataItem);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// -----------------------------------------------------------------------------\n// Public API\n// -----------------------------------------------------------------------------\n\n/**\n * Read all output values from a Grasshopper Compute response, keyed by parameter\n * name (or ID when `byId`). Duplicate keys aggregate into an array.\n *\n * `ParamName`/`InnerTree` are read case-insensitively (server branches differ in\n * casing) and params without an `InnerTree` are skipped rather than crashing.\n *\n * Parsed (non-geometry) JSON values are memoized per response item — repeated\n * reads of the same response return the same object identity, so treat parsed\n * values as read-only. Geometry decoded via `rhino` is always freshly\n * constructed; free it with `dispose()`.\n *\n * @param options.parseValues - Parse complex data types into JS objects (default true).\n * @param options.rhino - Rhino3dm instance for geometry decoding.\n * @param options.stringOnly - Keep only string-typed items.\n */\nexport function getValues<T = ParsedContext>(\n\tresponse: GrasshopperComputeResponse,\n\tbyId: boolean = false,\n\toptions: GetValuesOptions = {}\n): GetValuesResult<T> {\n\tconst { parseValues = true, rhino, stringOnly = false } = options;\n\tconst result: ParsedContext = {};\n\t// Keys holding an aggregation array (vs. a single value that happens to BE an array,\n\t// e.g. parsed JSON `[1,2,3]`) — `Array.isArray(result[key])` can't tell those apart.\n\tconst aggregated = new Set<string>();\n\n\tfor (const param of getResponseParams(response)) {\n\t\tconst paramName = readField<string>(param, 'paramName');\n\t\tforEachTreeItem(readField(param, 'innerTree'), (item) => {\n\t\t\tconst type = itemType(item);\n\t\t\t// Skip excluded types (e.g. WebDisplay) entirely — leaving them in\n\t\t\t// would write null into the aggregated result.\n\t\t\tif (isExcludedType(type)) return;\n\t\t\t// Skip non-string types if stringOnly is enabled\n\t\t\tif (stringOnly && type !== SYSTEM_TYPES.STRING) return;\n\n\t\t\tconst key = byId ? item.id : paramName;\n\t\t\tif (!key) return;\n\n\t\t\tconst value = extractItemValue(item, type, parseValues, rhino);\n\n\t\t\tif (!(key in result)) {\n\t\t\t\tresult[key] = value;\n\t\t\t} else if (aggregated.has(key)) {\n\t\t\t\tresult[key].push(value);\n\t\t\t} else {\n\t\t\t\tresult[key] = [result[key], value];\n\t\t\t\taggregated.add(key);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn { values: result as T, dispose: () => disposeRhinoObjects(result) };\n}\n\n/** Decode every file-data item in a response into {@link FileData} objects. */\nexport function extractFileData(response: GrasshopperComputeResponse): FileData[] {\n\tconst output: FileData[] = [];\n\n\tfor (const param of getResponseParams(response)) {\n\t\tforEachTreeItem(readField(param, 'innerTree'), (item) => {\n\t\t\tif (!itemType(item).includes(FILE_DATA_TYPE)) return;\n\n\t\t\tconst parsed = decodeItemJSON(item);\n\t\t\tif (isFileData(parsed)) {\n\t\t\t\toutput.push(parsed);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn output;\n}\n\n/**\n * Read one parameter's value(s) from a response — `byName` matches a `ParamName`,\n * `byId` matches an item ID. Returns `undefined` if absent, a single value for one\n * match, or an array for several.\n *\n * `ParamName`/`InnerTree` are read case-insensitively with null guards, and the\n * scan is a single pass that stops at the first matching parameter (issue 84 —\n * previously `byId` walked every tree twice). Parsed non-geometry JSON values\n * are memoized per response item; treat them as read-only.\n *\n * When a `rhino` instance is passed, decoded geometry lives on the WASM heap —\n * free it with {@link disposeRhinoObjects} once consumed.\n *\n * @param parseOptions.parseValues - Parse raw data into formatted values (default true).\n * @param parseOptions.rhino - Rhino3dm instance for geometry decoding.\n * @param parseOptions.stringOnly - Keep only string-typed items.\n */\nexport function getValue(\n\tresponse: GrasshopperComputeResponse,\n\toptions: { byName: string } | { byId: string },\n\tparseOptions: GetValuesOptions = {}\n): any {\n\tconst { parseValues = true, rhino, stringOnly = false } = parseOptions;\n\n\t// Single pass with early exit: the first param that matches (by name, or by\n\t// containing an item with the target id) is THE target — collect from it and\n\t// return without scanning the remaining trees.\n\tfor (const param of getResponseParams(response)) {\n\t\tconst tree = readField(param, 'innerTree');\n\n\t\tlet matched = false;\n\t\tif ('byName' in options) {\n\t\t\tif (readField<string>(param, 'paramName') !== options.byName) continue;\n\t\t\tmatched = true;\n\t\t}\n\n\t\tconst collected: any[] = [];\n\t\tforEachTreeItem(tree, (item) => {\n\t\t\tif ('byId' in options) {\n\t\t\t\tif (item.id !== options.byId) return;\n\t\t\t\t// The param owning this id is the target even if every matching\n\t\t\t\t// item is filtered out below (preserves the previous two-pass\n\t\t\t\t// semantics: found-but-filtered → undefined, not keep-scanning).\n\t\t\t\tmatched = true;\n\t\t\t}\n\t\t\tconst type = itemType(item);\n\t\t\t// Skip excluded types (e.g. WebDisplay) entirely.\n\t\t\tif (isExcludedType(type)) return;\n\t\t\t// Skip non-string types if stringOnly is enabled\n\t\t\tif (stringOnly && type !== SYSTEM_TYPES.STRING) return;\n\t\t\tcollected.push(extractItemValue(item, type, parseValues, rhino));\n\t\t});\n\n\t\tif (matched) {\n\t\t\tif (collected.length === 0) return undefined;\n\t\t\tif (collected.length === 1) return collected[0];\n\t\t\treturn collected;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n","import { downloadFileData } from '@/core/files/handle-files';\nimport { FileBaseInfo, FileData } from '@/core/files/types';\n\nimport { GrasshopperComputeResponse } from '../types';\n\nimport {\n\textractFileData,\n\tgetValue,\n\tgetValues,\n\tGetValuesOptions,\n\tGetValuesResult,\n\tParsedContext\n} from '../io/output/response-processors';\n\n/**\n * High-level wrapper for interacting with Grasshopper Compute responses.\n *\n * This class exposes a clean, consistent API for accessing parsed values,\n * geometry, and produced files. It is designed to be the primary interface\n * when working with Grasshopper results in client applications.\n */\nexport default class GrasshopperResponseProcessor {\n\tconstructor(\n\t\t/**\n\t\t * The raw compute response. Public so callers can hand it to a renderer-side parser — e.g.\n\t\t * `getThreeMeshesFromComputeResponse` in `@selvajs/visualization/parse`, which replaced this\n\t\t * class's former `extractMeshesFromResponse()`.\n\t\t */\n\t\tpublic readonly response: GrasshopperComputeResponse,\n\t\t/**\n\t\t * Retained for call-signature compatibility. Its only consumer was the removed\n\t\t * `extractMeshesFromResponse()`, which merged it into the parse options; pass `debug`\n\t\t * directly to the parser in `@selvajs/visualization/parse` instead.\n\t\t */\n\t\tpublic readonly debug: boolean = false\n\t) {}\n\n\t/**\n\t * Extract all values in the response.\n\t *\n\t * @typeParam T - Expected structure of the return value. Defaults to a simple key/value map. (later cast as needed)\n\t * @param byId - Key by parameter ID instead of name. Requires the VektorNode rhino.compute branch.\n\t * @param options - Controls parsing behavior such as Rhino geometry decoding.\n\t * @returns Parsed Grasshopper output values.\n\t *\n\t * @example\n\t * ```ts\n\t * const processor = new GrasshopperResponseProcessor(response);\n\t * const { values } = processor.getValues();\n\t * ```\n\t *\n\t */\n\tpublic getValues<T = ParsedContext>(\n\t\tbyId: boolean = false,\n\t\toptions: GetValuesOptions = {}\n\t): GetValuesResult<T> {\n\t\treturn getValues<T>(this.response, byId, options);\n\t}\n\n\t/**\n\t * Retrieve a specific value by parameter name or ID.\n\t *\n\t * @param selector - `{ byName }` for the human-readable name, `{ byId }` for the parameter GUID.\n\t * @param options - Parsing configuration (e.g. disable parsing or enable Rhino).\n\t * @returns Single parsed value, array of values, or undefined if the parameter is absent.\n\t *\n\t * `byId` requires the VektorNode rhino.compute branch.\n\t *\n\t * @example\n\t * ```ts\n\t * const schema = processor.getValue({ byName: 'Schema' });\n\t * const output = processor.getValue({ byId: 'a4be1c1e-23f9-4c27-b942-7f3bb2c45c6f' });\n\t * ```\n\t */\n\tpublic getValue(\n\t\tselector: { byName: string } | { byId: string },\n\t\toptions?: GetValuesOptions\n\t): any {\n\t\treturn getValue(this.response, selector, options);\n\t}\n\n\t/**\n\t * REMOVED: `extractMeshesFromResponse()`. Mesh decoding lives in `@selvajs/visualization` now, so\n\t * this package stays pure solve/data and carries no `three` dependency. Call the parser directly\n\t * with the raw response:\n\t *\n\t * ```ts\n\t * import { getThreeMeshesFromComputeResponse } from '@selvajs/visualization/parse';\n\t *\n\t * const meshes = await getThreeMeshesFromComputeResponse(processor.response, { rhino });\n\t * scene.add(...meshes);\n\t * ```\n\t */\n\n\tprivate getFileData(): FileData[] {\n\t\treturn extractFileData(this.response);\n\t}\n\n\t/**\n\t * Download all files generated by Grasshopper, optionally including\n\t * additional user-provided files.\n\t *\n\t * Files are grouped under the specified folder name when downloaded.\n\t *\n\t * @param folderName - Name for the download directory.\n\t * @param additionalFiles - Extra files to package (single file, array, or null).\n\t *\n\t * @example\n\t * ```ts\n\t * await processor.getAndDownloadFiles('gh-output');\n\t * ```\n\t *\n\t * @example\n\t * ```ts\n\t * const extra = { name: 'notes.txt', data: 'Example' };\n\t * await processor.getAndDownloadFiles('project', extra);\n\t * ```\n\t */\n\tpublic async getAndDownloadFiles(\n\t\tfolderName: string,\n\t\tadditionalFiles?: FileBaseInfo[] | FileBaseInfo | null\n\t): Promise<void> {\n\t\tconst files = this.getFileData();\n\t\tawait downloadFileData(files, folderName, additionalFiles);\n\t}\n}\n","import type { DataTreeDefault } from '../types';\n\n/**\n * Canonical matcher for a Grasshopper branch path key like `{0}`, `{0;1}`, or\n * the root path `{}`. This is the single source of truth for the branch-path\n * shape (`DataTreePath`); anything testing \"does this string name a branch?\"\n * should use this rather than re-inlining the regex.\n *\n * Matches `{}`, `{0}`, `{0;1;2}`, and negative indices like `{-1;2}` (GH_Path\n * allows them) — but NOT empty segments like `{0;}` / `{;}` / `{0;;1}`, which\n * would otherwise split to phantom 0s (`Number('') === 0`).\n */\nexport const TREE_PATH_RE = /^\\{(-?\\d+(?:;-?\\d+)*)?\\}$/;\n\n/**\n * Membership test for a {@link DataTreeDefault}: an object keyed entirely by\n * branch paths, each mapping to an array of values. This is the one predicate\n * both the input-type parsers (to pass a tree-access default through untouched)\n * and `TreeBuilder` (to dispatch it to `fromDataTreeDefault`) ask — so the two\n * agree by construction on exactly which values are trees.\n */\nexport function isDataTreeDefault(value: unknown): value is DataTreeDefault {\n\tif (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n\tconst entries = Object.entries(value);\n\treturn (\n\t\tentries.length > 0 &&\n\t\tentries.every(([key, val]) => TREE_PATH_RE.test(key) && Array.isArray(val))\n\t);\n}\n","// Numeric step-size and precision handling for slider-backed inputs.\n\nimport type { InputParamSchema } from '../../types';\n\nexport function applyRounding(value: number, decimalPlaces: number, tolerance: number): number {\n\tconst rounded = Number(value.toFixed(decimalPlaces));\n\tif (Math.abs(value - rounded) < tolerance) return rounded;\n\treturn value;\n}\n\nexport function getInputStepSize(value: number, roundingTolerance: number): number {\n\tif (!Number.isFinite(value)) return 0.1;\n\tif (value === 0) return 0.1;\n\n\tconst abs = Math.abs(value);\n\n\tif (abs >= 1) {\n\t\tconst str = String(value);\n\t\tconst decimalPart = str.split('.')[1];\n\t\tif (decimalPart && decimalPart.length > 0) {\n\t\t\tconst decimals = Math.min(decimalPart.length, 12);\n\t\t\tconst step = Math.pow(10, -decimals);\n\t\t\tconst rounded = Number(step.toFixed(decimals));\n\t\t\treturn Math.abs(rounded - step) < roundingTolerance ? rounded : step;\n\t\t}\n\t\treturn 1;\n\t}\n\n\t// Handle exponential notation\n\tconst s = String(value);\n\tconst expMatch = s.toLowerCase().match(/e(-?\\d+)/);\n\tif (expMatch) {\n\t\tconst exp = Number(expMatch[1]);\n\t\tif (exp < 0 || s.toLowerCase().includes('e-')) {\n\t\t\tconst absExp = Math.abs(exp);\n\t\t\tconst step = Math.pow(10, -absExp);\n\t\t\tconst rounded = Number(step.toFixed(absExp));\n\t\t\treturn Math.abs(rounded - step) < roundingTolerance ? rounded : step;\n\t\t}\n\t\treturn 0.1;\n\t}\n\n\t// Handle standard decimal notation\n\tconst MAX_DECIMALS = 12;\n\tconst fixed = abs.toFixed(MAX_DECIMALS);\n\tconst trimmed = fixed.replace(/0+$/, '');\n\tconst decimals = Math.min((trimmed.split('.')[1] || '').length, MAX_DECIMALS);\n\n\tif (decimals === 0) return 0.1;\n\n\tconst step = Math.pow(10, -decimals);\n\tconst rounded = Number(step.toFixed(decimals));\n\treturn Math.abs(rounded - step) < roundingTolerance ? rounded : step;\n}\n\n/**\n * A server-authored `stepSize` (read off the wire by `normalizeInputSchema`),\n * or `undefined` when absent/unusable — callers fall back to the heuristic.\n */\nexport function serverStepSize(schema: InputParamSchema): number | undefined {\n\treturn typeof schema.stepSize === 'number' &&\n\t\tNumber.isFinite(schema.stepSize) &&\n\t\tschema.stepSize > 0\n\t\t? schema.stepSize\n\t\t: undefined;\n}\n\n/**\n * Computes the coerced default + stepSize for a Number/Integer input.\n * Mirrors the old `processNumericInput`, plus: a server-provided\n * `schema.stepSize` is honored verbatim; the default/min/max heuristic is\n * only the fallback when the server didn't author one.\n */\n","// Value transformers: coerce a schema's raw `default` to a typed value.\n\nimport { getLogger } from '@/core';\n\n/**\n * Coerce one raw default value to `T`, or return `null` when it can't be\n * coerced. Transformers never throw — the caller decides whether a `null`\n * is filtered (array items) or surfaced as a parse error (scalars).\n */\nexport type ValueTransformer<T> = (value: unknown) => T | null;\n\n/**\n * Coerce a schema's `default` through a transformer, mirroring the old\n * `processInputValue`: arrays map+filter (empty → undefined), scalars\n * transform-or-(undefined|preserve). Returns the new default value rather than\n * mutating.\n */\nexport function coerceDefault<T>(\n\tvalue: unknown,\n\ttransform: ValueTransformer<T>,\n\tsetUndefinedOnEmpty: boolean\n): unknown {\n\tif (value === undefined || value === null) {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst processed = value.map(transform).filter((v): v is T => v !== null);\n\t\treturn processed.length > 0 ? processed : undefined;\n\t}\n\n\tconst transformed = transform(value);\n\tif (transformed !== null) {\n\t\treturn transformed;\n\t}\n\treturn setUndefinedOnEmpty ? undefined : value;\n}\n\n/**\n * @internal Shared with `normalize-default.ts` so tree-access items are parsed\n * with the exact same rules as scalar/array defaults (issue: the tree path used\n * to hand-roll `Number(data)`, turning a blank double into `0`).\n */\nexport const numericTransformer: ValueTransformer<number> = (value) => {\n\tif (typeof value === 'number') return Number.isFinite(value) ? value : null;\n\tif (typeof value === 'string') {\n\t\tconst trimmed = value.trim();\n\t\t// `Number('')` is 0, so reject empty/whitespace before coercing — an empty\n\t\t// default should drop to null, not silently become 0.\n\t\tif (trimmed === '') return null;\n\t\t// `Number()` also accepts hex/binary/octal literals ('0x10' → 16) and\n\t\t// 'Infinity' — neither is a value a Grasshopper numeric default can\n\t\t// legitimately hold (Infinity even survives applyRounding). Only decimal\n\t\t// and exponent notation are accepted.\n\t\tif (/^[+-]?0[xbo]/i.test(trimmed)) return null;\n\t\tconst parsed = Number(trimmed);\n\t\treturn Number.isFinite(parsed) ? parsed : null;\n\t}\n\treturn null;\n};\n\n/**\n * @internal Shared with `normalize-default.ts` (see {@link numericTransformer}).\n * Trims like the numeric transformer and follows the {@link ValueTransformer}\n * contract: `null` on a bad value, never a throw — bad array items are filtered\n * like non-string junk, and the boolean parser surfaces bad scalars itself.\n */\nexport const booleanTransformer: ValueTransformer<boolean> = (value) => {\n\tif (typeof value === 'boolean') return value;\n\tif (typeof value === 'string') {\n\t\tconst lower = value.trim().toLowerCase();\n\t\tif (lower === 'true') return true;\n\t\tif (lower === 'false') return false;\n\t}\n\treturn null;\n};\n\nexport const textTransformer: ValueTransformer<string> = (value) => {\n\tif (typeof value === 'string') {\n\t\tif (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"'))\n\t\t\treturn value.slice(1, -1);\n\t\t// Unbalanced leading quote: strip only the quote, not the last character.\n\t\tif (value.startsWith('\"')) return value.slice(1);\n\t\treturn value;\n\t}\n\treturn null;\n};\n\nexport const colorTransformer: ValueTransformer<string> = (value) => {\n\tif (typeof value === 'string') {\n\t\tlet cleaned = value.trim();\n\t\tif (cleaned.startsWith('\"') && cleaned.endsWith('\"')) {\n\t\t\tcleaned = cleaned.slice(1, -1).trim();\n\t\t}\n\t\treturn cleaned;\n\t}\n\treturn null;\n};\n\nexport function objectTransformer(inputName: string): ValueTransformer<object> {\n\treturn (value) => {\n\t\tif (typeof value === 'object' && value !== null) return value;\n\t\tif (typeof value === 'string' && value.trim() !== '') {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(value);\n\t\t\t\tif (typeof parsed === 'object' && parsed !== null) return parsed;\n\t\t\t\tgetLogger().warn(`Parsed value for input ${inputName} is not an object`);\n\t\t\t\treturn null;\n\t\t\t} catch (err) {\n\t\t\t\tgetLogger().warn(`Failed to parse object value \"${value}\" for input ${inputName}`, err);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t};\n}\n","import { ComputeError, ErrorCodes } from '@/core/errors';\nimport { getLogger } from '@/core';\nimport { isDataTreeDefault } from '../../data-tree/tree-path';\nimport type {\n\tBaseInputType,\n\tBooleanInputType,\n\tColorInputType,\n\tFileInputType,\n\tGeometryInputType,\n\tInputParam,\n\tInputParamSchema,\n\tNumericInputType,\n\tTextInputType,\n\tValueListInputType\n} from '../../types';\n\n/**\n * @internal The input-type parser seam.\n *\n * One adapter per Grasshopper param type. A parser owns EVERYTHING about its\n * type: value coercion, type-specific fields (e.g. numeric step size), the\n * typed-param construction, and its own safe fallback when input is bad. New\n * param types plug in by adding an entry to {@link INPUT_TYPE_PARSERS}.\n *\n * Parsers are pure: they read from a (already-`normalizeDefault`'d) schema and\n * return a typed param. They do not mutate the schema. `parse` throws a\n * {@link ComputeError} on recoverable bad input; the registry boundary\n * catches it and pairs it with `fallback`.\n */\nexport interface InputTypeParser<T extends InputParam = InputParam> {\n\t/** Canonical paramType(s) this parser owns, e.g. ['Number','Integer']. */\n\treadonly types: readonly string[];\n\t/** Schema (with normalized default) → typed param. Throws on bad input. */\n\tparse(schema: InputParamSchema, base: BaseInputType): T;\n\t/** This type's safe fallback param when {@link parse} throws. */\n\tfallback(schema: InputParamSchema, base: BaseInputType): T;\n}\n\nimport { applyRounding, getInputStepSize, serverStepSize } from './numeric-rounding';\nimport {\n\tbooleanTransformer,\n\tcoerceDefault,\n\tcolorTransformer,\n\tnumericTransformer,\n\tobjectTransformer,\n\ttextTransformer\n} from './transformers';\n\n// Re-exported for `normalize-default.ts`, which coerces raw defaults before parsing.\nexport { booleanTransformer, numericTransformer };\n\nfunction computeNumeric(\n\tschema: InputParamSchema,\n\troundingTolerance = 1e-8\n): { default: NumericInputType['default']; stepSize: number } {\n\tconst isIntegerType = schema.paramType === 'Integer';\n\tconst serverStep = serverStepSize(schema);\n\n\t// A tree-access default is a DataTreeDefault keyed by branch paths; pass it\n\t// through untouched (numeric constraints are applied later by TreeBuilder).\n\t// Without this guard the scalar numericTransformer mangles the tree object to\n\t// `undefined`, silently dropping a tree-access slider's default. Sharing\n\t// `isDataTreeDefault` with TreeBuilder guarantees we pass through exactly the\n\t// values it will treat as trees — no looser, no stricter.\n\tif (isDataTreeDefault(schema.default)) {\n\t\treturn {\n\t\t\tdefault: schema.default as NumericInputType['default'],\n\t\t\tstepSize: serverStep ?? (isIntegerType ? 1 : 0.1)\n\t\t};\n\t}\n\n\t// A scalar string default that isn't blank and doesn't parse (locale comma\n\t// '1,5', 'Infinity', hex, plain junk) is bad input — surface it as a parse\n\t// error instead of silently collapsing the default to `undefined`. Blank\n\t// strings still mean \"no default\" (deliberate, see numericTransformer);\n\t// array items keep the filter semantics.\n\tif (\n\t\ttypeof schema.default === 'string' &&\n\t\tschema.default.trim() !== '' &&\n\t\tnumericTransformer(schema.default) === null\n\t) {\n\t\tthrow new ComputeError(\n\t\t\t`Invalid numeric default \"${schema.default}\" for input \"${schema.name || 'unknown'}\"`,\n\t\t\tErrorCodes.VALIDATION_ERROR,\n\t\t\t{ context: { inputName: schema.name, default: schema.default } }\n\t\t);\n\t}\n\n\tlet value = coerceDefault(schema.default, numericTransformer, true);\n\n\tif (isIntegerType) {\n\t\tif (Array.isArray(value)) {\n\t\t\tvalue = value.map((val) => (typeof val === 'number' ? Math.round(val) : val));\n\t\t} else if (typeof value === 'number') {\n\t\t\tvalue = Math.round(value);\n\t\t}\n\t\treturn { default: value as NumericInputType['default'], stepSize: serverStep ?? 1 };\n\t}\n\n\tconst firstValue = Array.isArray(value) ? value[0] : value;\n\n\tlet stepSource: number | undefined;\n\tif (typeof firstValue === 'number' && Number.isFinite(firstValue) && firstValue !== 0) {\n\t\tstepSource = firstValue;\n\t} else if (\n\t\ttypeof schema.minimum === 'number' &&\n\t\tNumber.isFinite(schema.minimum) &&\n\t\tschema.minimum !== 0\n\t) {\n\t\tstepSource = schema.minimum;\n\t} else if (\n\t\ttypeof schema.maximum === 'number' &&\n\t\tNumber.isFinite(schema.maximum) &&\n\t\tschema.maximum !== 0\n\t) {\n\t\tstepSource = schema.maximum;\n\t}\n\n\tconst stepSize =\n\t\tserverStep ??\n\t\t(stepSource !== undefined ? getInputStepSize(stepSource, roundingTolerance) : 0.1);\n\n\t// Apply precision to all numeric values\n\tlet decimalPlaces = 0;\n\tconst stepStr = String(stepSize);\n\tconst expMatch = stepStr.toLowerCase().match(/e(-?\\d+)/);\n\tif (expMatch) {\n\t\tdecimalPlaces = Math.abs(Number(expMatch[1]));\n\t} else {\n\t\tdecimalPlaces = stepStr.split('.')[1]?.length ?? 0;\n\t}\n\n\tif (\n\t\tdecimalPlaces === 0 &&\n\t\ttypeof firstValue === 'number' &&\n\t\tfirstValue !== 0 &&\n\t\tMath.abs(firstValue) < 1\n\t) {\n\t\tconst inferred = Math.ceil(-Math.log10(Math.abs(firstValue)));\n\t\tif (Number.isFinite(inferred) && inferred > 0) {\n\t\t\tdecimalPlaces = inferred;\n\t\t}\n\t}\n\n\tdecimalPlaces = Math.min(Math.max(decimalPlaces, 0), 12);\n\n\tif (Array.isArray(value)) {\n\t\tvalue = value.map((val) =>\n\t\t\ttypeof val === 'number' ? applyRounding(val, decimalPlaces, roundingTolerance) : val\n\t\t);\n\t} else if (typeof value === 'number') {\n\t\tvalue = applyRounding(value, decimalPlaces, roundingTolerance);\n\t}\n\n\treturn { default: value as NumericInputType['default'], stepSize };\n}\n\n// ============================================================================\n// Parsers — one per type\n// ============================================================================\n\nconst numericParser: InputTypeParser<NumericInputType> = {\n\ttypes: ['Number', 'Integer'],\n\tparse(schema, base) {\n\t\tconst { default: def, stepSize } = computeNumeric(schema);\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: schema.paramType as 'Number' | 'Integer',\n\t\t\tminimum: schema.minimum,\n\t\t\tmaximum: schema.maximum,\n\t\t\tatLeast: schema.atLeast,\n\t\t\tatMost: schema.atMost,\n\t\t\tstepSize,\n\t\t\tdefault: def\n\t\t};\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\t// The safe default must respect the input's own floor: with minimum > 0 a\n\t\t// plain 0 would render a slider default below its own range.\n\t\tconst safeValue =\n\t\t\ttypeof schema.minimum === 'number' && Number.isFinite(schema.minimum) && schema.minimum > 0\n\t\t\t\t? schema.minimum\n\t\t\t\t: 0;\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: schema.paramType as 'Number' | 'Integer',\n\t\t\tminimum: schema.minimum,\n\t\t\tmaximum: schema.maximum,\n\t\t\tatLeast: schema.atLeast,\n\t\t\tatMost: schema.atMost,\n\t\t\t// The parse path always sets a stepSize — the fallback param must too.\n\t\t\tstepSize: serverStepSize(schema) ?? (schema.paramType === 'Integer' ? 1 : 0.1),\n\t\t\tdefault: isList ? [safeValue] : safeValue\n\t\t};\n\t}\n};\n\nconst booleanParser: InputTypeParser<BooleanInputType> = {\n\ttypes: ['Boolean'],\n\tparse(schema, base) {\n\t\t// Tree-access defaults pass through untouched for TreeBuilder, same\n\t\t// rationale as computeNumeric's guard.\n\t\tif (isDataTreeDefault(schema.default)) {\n\t\t\treturn {\n\t\t\t\t...base,\n\t\t\t\tparamType: 'Boolean',\n\t\t\t\tdefault: schema.default as BooleanInputType['default']\n\t\t\t};\n\t\t}\n\t\tconst value = coerceDefault(schema.default, booleanTransformer, false);\n\t\t// The transformer follows the ValueTransformer contract (null on bad input,\n\t\t// never a throw), so bad ARRAY items are filtered like non-string junk —\n\t\t// one 'maybe' in ['true','maybe'] no longer aborts the whole array. A bad\n\t\t// SCALAR survives coercion verbatim (setUndefinedOnEmpty=false); surface it\n\t\t// as a parse error instead of shipping a non-boolean default.\n\t\tif (\n\t\t\tvalue !== undefined &&\n\t\t\tvalue !== null &&\n\t\t\ttypeof value !== 'boolean' &&\n\t\t\t!Array.isArray(value)\n\t\t) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t`Invalid boolean default \"${String(value)}\" for input \"${schema.name || 'unknown'}\"`,\n\t\t\t\tErrorCodes.VALIDATION_ERROR,\n\t\t\t\t{ context: { inputName: schema.name, default: schema.default } }\n\t\t\t);\n\t\t}\n\t\treturn { ...base, paramType: 'Boolean', default: value as BooleanInputType['default'] };\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'Boolean', default: isList ? [false] : false };\n\t}\n};\n\nconst textParser: InputTypeParser<TextInputType> = {\n\ttypes: ['Text'],\n\tparse(schema, base) {\n\t\tconst value = coerceDefault(schema.default, textTransformer, false);\n\t\treturn { ...base, paramType: 'Text', default: value as TextInputType['default'] };\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'Text', default: isList ? [''] : '' };\n\t}\n};\n\nconst valueListParser: InputTypeParser<ValueListInputType> = {\n\ttypes: ['ValueList'],\n\tparse(schema, base) {\n\t\tif (\n\t\t\t!schema.values ||\n\t\t\ttypeof schema.values !== 'object' ||\n\t\t\tObject.keys(schema.values).length === 0\n\t\t) {\n\t\t\tthrow ComputeError.missingValues(schema.nickname || 'unnamed', 'ValueList');\n\t\t}\n\n\t\tlet defaultValue = schema.default as string | undefined;\n\t\tif (schema.default !== undefined && schema.default !== null) {\n\t\t\t// A tree/array-shaped default can't index the values map — `String()`\n\t\t\t// would silently turn it into '[object Object]'. Reject it properly.\n\t\t\tif (typeof schema.default === 'object') {\n\t\t\t\tthrow new ComputeError(\n\t\t\t\t\t`ValueList input \"${schema.nickname || 'unnamed'}\" default is not a string-able value`,\n\t\t\t\t\tErrorCodes.VALIDATION_ERROR,\n\t\t\t\t\t{ context: { inputName: schema.name, default: schema.default } }\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst defaultLower = String(schema.default).toLowerCase();\n\t\t\t// Membership is case-insensitive, but downstream lookups (`values[default]`)\n\t\t\t// are not — return the canonical-cased key on a match.\n\t\t\tconst match = Object.keys(schema.values).find((key) => key.toLowerCase() === defaultLower);\n\t\t\tif (match !== undefined) {\n\t\t\t\tdefaultValue = match;\n\t\t\t} else {\n\t\t\t\t// Out-of-range default only warns — it still succeeds (pinned behavior).\n\t\t\t\tgetLogger().warn(\n\t\t\t\t\t`ValueList input \"${schema.nickname || 'unnamed'}\" default value \"${schema.default}\" is not in available values`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: 'ValueList',\n\t\t\tvalues: schema.values as Record<string, string>,\n\t\t\tdefault: defaultValue\n\t\t};\n\t},\n\tfallback(schema, base) {\n\t\t// A ValueList only falls back when its values map is missing/empty or its\n\t\t// default couldn't be interpreted — either way the default can't be\n\t\t// validated against the map, so drop it (never fabricate `[undefined]` or\n\t\t// keep a value provably absent from an empty map).\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: 'ValueList',\n\t\t\tvalues: schema.values && typeof schema.values === 'object' ? schema.values : {},\n\t\t\tdefault: undefined\n\t\t};\n\t}\n};\n\nconst geometryParser: InputTypeParser<GeometryInputType> = {\n\ttypes: ['Geometry'],\n\tparse(schema, base) {\n\t\tconst value = coerceDefault(\n\t\t\tschema.default,\n\t\t\tobjectTransformer(schema.nickname || 'unnamed'),\n\t\t\ttrue\n\t\t);\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: 'Geometry',\n\t\t\tdefault: value as GeometryInputType['default']\n\t\t};\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'Geometry', default: isList ? [null] : (null as any) };\n\t}\n};\n\nconst fileParser: InputTypeParser<FileInputType> = {\n\ttypes: ['File'],\n\tparse(schema, base) {\n\t\tconst value = coerceDefault(\n\t\t\tschema.default,\n\t\t\tobjectTransformer(schema.nickname || 'unnamed'),\n\t\t\ttrue\n\t\t);\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: 'File',\n\t\t\tacceptedFormats: schema.acceptedFormats,\n\t\t\tdefault: value as FileInputType['default']\n\t\t};\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'File', default: isList ? [null] : (null as any) };\n\t}\n};\n\nconst colorParser: InputTypeParser<ColorInputType> = {\n\ttypes: ['Color'],\n\tparse(schema, base) {\n\t\tconst value = coerceDefault(schema.default, colorTransformer, false);\n\t\treturn { ...base, paramType: 'Color', default: value as ColorInputType['default'] };\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'Color', default: isList ? ['0, 0, 0'] : '0, 0, 0' };\n\t}\n};\n\n// ============================================================================\n// Registry\n// ============================================================================\n\nconst ALL_PARSERS: InputTypeParser[] = [\n\tnumericParser,\n\tbooleanParser,\n\ttextParser,\n\tvalueListParser,\n\tgeometryParser,\n\tfileParser,\n\tcolorParser\n];\n\n/** Registry keyed by canonical paramType. */\nexport const INPUT_TYPE_PARSERS: ReadonlyMap<string, InputTypeParser> = new Map(\n\tALL_PARSERS.flatMap((parser) => parser.types.map((type) => [type, parser] as const))\n);\n\n/**\n * The Geometry parser is the registry's fallback for an unknown paramType,\n * matching the old `createSafeDefault` default branch (geometry-shaped null).\n */\nexport const UNKNOWN_TYPE_FALLBACK: InputTypeParser = geometryParser;\n","import { getLogger } from '@/core';\nimport { readField, hasField } from '@/core/utils/read-field';\nimport { booleanTransformer, numericTransformer } from './input-type-parsers';\nimport type { InputParamSchema } from '../../types';\n\n/** A non-fatal reason normalizeDefault couldn't interpret a raw default. */\nexport interface NormalizeDefaultWarning {\n\tcode: 'MALFORMED_DEFAULT';\n\tmessage: string;\n}\n\n/**\n * Read an item's `data` / `type` case-insensitively. Items are lowercase\n * (`data`/`type`) on every known server branch — they carry `[JsonProperty]` —\n * but reading them defensively costs nothing and guards against future drift.\n */\nfunction itemData(item: unknown): unknown {\n\treturn readField(item, 'data');\n}\nfunction itemType(item: unknown): string | undefined {\n\treturn readField<string>(item, 'type');\n}\n\n/**\n * Wire item types parsed as numbers in a tree-access default. Matches the\n * numeric CLR types Grasshopper serializes — the old code only handled\n * `Double`/`Int32`, leaving `Single`/`Int64`/`Decimal` items as strings inside\n * a `DefaultValue<number>` tree.\n */\nconst NUMERIC_ITEM_TYPES = new Set([\n\t'System.Double',\n\t'System.Single',\n\t'System.Decimal',\n\t'System.Int32',\n\t'System.Int64'\n]);\n\n/** Integral wire item types — rounded like the scalar Integer path. */\nconst INTEGER_ITEM_TYPES = new Set(['System.Int32', 'System.Int64']);\n\n/**\n * Normalizes a raw input's `default` by flattening the innerTree structure.\n * Reads `default` keys case-insensitively (casing varies by server branch).\n * Returns the normalized schema plus an optional `warning` for malformed defaults.\n */\nexport function normalizeDefaultWithWarning(input: InputParamSchema): {\n\tschema: InputParamSchema;\n\twarning?: NormalizeDefaultWarning;\n} {\n\tif (typeof input.default !== 'object' || input.default === null) {\n\t\treturn { schema: input };\n\t}\n\n\t// An array default is already in the shape the per-type parsers expect\n\t// (coerceDefault maps arrays) — `processInputs` is public and documents this\n\t// shape. It must NOT fall into the malformed branch below just because\n\t// `typeof [] === 'object'`.\n\tif (Array.isArray(input.default)) {\n\t\treturn { schema: input };\n\t}\n\n\tif (!hasField(input.default, 'innerTree')) {\n\t\tconst message = `Input \"${input.name ?? 'unknown'}\" default had an unrecognized shape (no innerTree key); the default was dropped.`;\n\t\tgetLogger().warn('Unexpected structure in input.default:', input.default);\n\t\treturn {\n\t\t\tschema: { ...input, default: null },\n\t\t\twarning: { code: 'MALFORMED_DEFAULT', message }\n\t\t};\n\t}\n\n\tconst innerTree = readField<Record<string, unknown>>(input.default, 'innerTree') ?? {};\n\n\t// If innerTree is empty, set default to undefined\n\tif (Object.keys(innerTree).length === 0) {\n\t\treturn { schema: { ...input, default: undefined } };\n\t}\n\n\t// If treeAccess is true or atMost > 1, preserve the tree structure\n\tif (input.treeAccess || (input.atMost && input.atMost > 1)) {\n\t\t// Convert each branch to an array of parsed data. Items are parsed with the\n\t\t// SAME transformers as scalar defaults (issue: this path used to hand-roll\n\t\t// `Number(data)` / `data === 'true'`, so a blank double became 0 and any\n\t\t// junk boolean silently became false).\n\t\tconst tree: Record<string, any[]> = {};\n\t\tconst invalidItems: string[] = [];\n\t\tfor (const [branch, items] of Object.entries(innerTree)) {\n\t\t\t// Mirror the flatten path's Array.isArray guard: a non-array branch value must degrade to\n\t\t\t// a MALFORMED_DEFAULT warning, not a raw TypeError that aborts the whole definition-IO fetch.\n\t\t\tif (!Array.isArray(items)) {\n\t\t\t\tconst message = `Input \"${input.name ?? 'unknown'}\" default had a non-array innerTree branch (\"${branch}\"); the default was dropped.`;\n\t\t\t\tgetLogger().warn('Unexpected structure in input.default innerTree:', input.default);\n\t\t\t\treturn {\n\t\t\t\t\tschema: { ...input, default: null },\n\t\t\t\t\twarning: { code: 'MALFORMED_DEFAULT', message }\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst parsed: any[] = [];\n\t\t\tfor (const item of items as any[]) {\n\t\t\t\tconst data = itemData(item);\n\t\t\t\tconst type = itemType(item);\n\t\t\t\tif (type && NUMERIC_ITEM_TYPES.has(type)) {\n\t\t\t\t\t// Blank means \"no value\" — drop it silently, mirroring the scalar\n\t\t\t\t\t// path where a blank string default becomes `undefined`, never 0.\n\t\t\t\t\tif (typeof data === 'string' && data.trim() === '') continue;\n\t\t\t\t\tconst num = numericTransformer(data);\n\t\t\t\t\tif (num === null) {\n\t\t\t\t\t\tinvalidItems.push(`\"${String(data)}\" (${type})`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tparsed.push(INTEGER_ITEM_TYPES.has(type) ? Math.round(num) : num);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (type === 'System.Boolean') {\n\t\t\t\t\t// Strict true/false parsing — 'maybe'/'1'/'' are dropped and\n\t\t\t\t\t// surfaced, not silently coerced to false.\n\t\t\t\t\tconst bool = booleanTransformer(data);\n\t\t\t\t\tif (bool === null) {\n\t\t\t\t\t\tinvalidItems.push(`\"${String(data)}\" (${type})`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tparsed.push(bool);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Only geometry is JSON-encoded on the wire. A `System.String`\n\t\t\t\t// must stay a string: value-list labels routinely start with\n\t\t\t\t// `[`/`{` (e.g. `[1,2,3]`), and JSON-parsing them would put a\n\t\t\t\t// non-string into the leaf `data`, which the Rhino.Compute\n\t\t\t\t// fork's Newtonsoft reader rejects (\"Unexpected character ... [\").\n\t\t\t\tif (typeof data === 'string' && type?.startsWith('Rhino.Geometry')) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsed.push(JSON.parse(data));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tparsed.push(data);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tparsed.push(data);\n\t\t\t}\n\t\t\ttree[branch] = parsed;\n\t\t}\n\n\t\t// Unparseable items are dropped but NOT silently: the good values survive\n\t\t// in the tree, and the drop is surfaced through the warning channel so it\n\t\t// reaches the client's parseErrors.\n\t\tlet warning: NormalizeDefaultWarning | undefined;\n\t\tif (invalidItems.length > 0) {\n\t\t\tconst message = `Input \"${input.name ?? 'unknown'}\" default contained ${invalidItems.length} tree value(s) that could not be parsed and were dropped: ${invalidItems.join(', ')}.`;\n\t\t\tgetLogger().warn(message);\n\t\t\twarning = { code: 'MALFORMED_DEFAULT', message };\n\t\t}\n\t\treturn { schema: { ...input, default: tree }, ...(warning && { warning }) };\n\t}\n\n\t// Otherwise, flatten all values as before\n\tconst allValues: any[] = [];\n\tfor (const items of Object.values(innerTree)) {\n\t\tif (Array.isArray(items)) {\n\t\t\titems.forEach((item) => {\n\t\t\t\tif (item && typeof item === 'object' && hasField(item, 'data')) {\n\t\t\t\t\tallValues.push(itemData(item));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\tif (allValues.length === 0) {\n\t\treturn { schema: { ...input, default: undefined } };\n\t} else if (allValues.length === 1) {\n\t\treturn { schema: { ...input, default: allValues[0] } };\n\t} else {\n\t\treturn { schema: { ...input, default: allValues } };\n\t}\n}\n","import { ComputeError } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\n\nimport { normalizeDefaultWithWarning } from './normalize-default';\nimport { INPUT_TYPE_PARSERS, UNKNOWN_TYPE_FALLBACK } from './input-type-parsers';\n\nimport type { BaseInputType, InputParam, InputParamSchema, InputParseError } from '../../types';\n\n/** Canonical paramType for each supported type, keyed by its lowercased form. */\nconst CANONICAL_PARAM_TYPES = new Map(\n\t[...INPUT_TYPE_PARSERS.keys()].map((key) => [key.toLowerCase(), key])\n);\n\n/**\n * Returns the canonical casing for a paramType (e.g. \"valuelist\" → \"ValueList\"),\n * or the original value unchanged when it isn't a known type so the\n * unknown-paramType error still surfaces downstream.\n */\nfunction canonicalizeParamType(paramType: string): string {\n\treturn CANONICAL_PARAM_TYPES.get(paramType?.toLowerCase()) ?? paramType;\n}\n\n/**\n * Parse one raw Grasshopper input schema into a typed {@link InputParam}.\n * Validation failures are swallowed and replaced with a safe default; use\n * {@link processInputWithError} to receive them.\n */\nexport function processInput(rawInput: InputParamSchema): InputParam {\n\treturn processInputWithError(rawInput).input;\n}\n\n/**\n * Like {@link processInput}, but reports validation failures back to the caller\n * instead of swallowing them with a logger warning.\n *\n * On success: `{ input, error: undefined }`.\n * On a recoverable validation failure: `{ input: <safe default>, error: {...} }`.\n *\n * A single input can fail TWICE — a malformed default (normalization warning)\n * on an input whose type parser then also throws. `errors` carries every\n * failure; `error` remains the primary one (the parser error when present,\n * else the default warning) for convenience.\n *\n * Error entries report the RAW declared `paramType` per the\n * {@link InputParseError} docs — not the canonicalized casing — so clients can\n * match on the casing they sent.\n *\n * Unexpected (non-ComputeError) failures still throw — they indicate a\n * programming bug, not bad user input.\n *\n * @internal Used by {@link processInputsWithErrors} / {@link fetchParsedDefinitionIO}.\n */\nexport function processInputWithError(rawInput: InputParamSchema): {\n\tinput: InputParam;\n\terror?: InputParseError;\n\terrors?: InputParseError[];\n} {\n\tconst baseInput: BaseInputType = {\n\t\tdescription: rawInput.description,\n\t\tname: rawInput.name,\n\t\tnickname: rawInput.nickname,\n\t\ttreeAccess: rawInput.treeAccess,\n\t\t// `null`/absent means \"no group\" — keep it absent instead of collapsing it\n\t\t// to '' and erasing the absent-vs-empty distinction.\n\t\tgroupName: rawInput.groupName ?? undefined,\n\t\tid: rawInput.id\n\t};\n\n\t// Normalize paramType to its canonical casing so callers can send any case\n\t// (e.g. Selva schemas emit lowercase \"valueList\" while the plugin reports\n\t// \"ValueList\"). The registry is keyed by canonical type.\n\tconst paramType = canonicalizeParamType(rawInput.paramType);\n\n\t// Shared, type-independent step: flatten the raw innerTree default into the\n\t// shape the per-type parsers expect (pure — does not mutate rawInput). An\n\t// unrecognized default shape nulls the value AND returns a warning so the\n\t// drop is surfaced to the client via parseErrors instead of vanishing.\n\tlet schema: InputParamSchema = { ...rawInput, paramType };\n\tlet defaultWarningError: InputParseError | undefined;\n\ttry {\n\t\tconst { schema: normalized, warning } = normalizeDefaultWithWarning(schema);\n\t\tschema = normalized;\n\t\tdefaultWarningError = warning && {\n\t\t\tinputName: rawInput.name || 'unknown',\n\t\t\t// InputParseError.paramType is documented as the RAW declared type.\n\t\t\tparamType: rawInput.paramType,\n\t\t\tmessage: warning.message,\n\t\t\tcode: warning.code\n\t\t};\n\t} catch (error) {\n\t\t// A default too malformed for the normalizer to even walk is bad server/user data, not a\n\t\t// programming bug — null it and report per-input rather than aborting the whole\n\t\t// definition-IO fetch over one input.\n\t\tschema = { ...schema, default: null };\n\t\tdefaultWarningError = {\n\t\t\tinputName: rawInput.name || 'unknown',\n\t\t\tparamType: rawInput.paramType,\n\t\t\tmessage: `Input \"${rawInput.name ?? 'unknown'}\" default could not be normalized and was dropped: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`,\n\t\t\tcode: 'MALFORMED_DEFAULT'\n\t\t};\n\t}\n\tconst parser = INPUT_TYPE_PARSERS.get(paramType);\n\n\ttry {\n\t\tif (!parser) {\n\t\t\tthrow ComputeError.unknownParamType(paramType, rawInput.name);\n\t\t}\n\t\t// A malformed-default warning rides through on the otherwise-successful parse.\n\t\treturn {\n\t\t\tinput: parser.parse(schema, baseInput),\n\t\t\terror: defaultWarningError,\n\t\t\t...(defaultWarningError && { errors: [defaultWarningError] })\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof ComputeError) {\n\t\t\tgetLogger().error(`Validation error for input ${rawInput.name || 'unknown'}:`, error.message);\n\t\t\tconst parserError: InputParseError = {\n\t\t\t\tinputName: rawInput.name || 'unknown',\n\t\t\t\tparamType: rawInput.paramType,\n\t\t\t\tmessage: error.message,\n\t\t\t\tcode: error.code\n\t\t\t};\n\t\t\t// The parser owns its own fallback; an unknown type falls back to the\n\t\t\t// geometry-shaped safe default (matching the old behavior). BOTH\n\t\t\t// failures are reported when the default warning and the parser error\n\t\t\t// occurred on the same input — the warning must not be shadowed.\n\t\t\treturn {\n\t\t\t\tinput: (parser ?? UNKNOWN_TYPE_FALLBACK).fallback(schema, baseInput),\n\t\t\t\terror: parserError,\n\t\t\t\terrors: defaultWarningError ? [defaultWarningError, parserError] : [parserError]\n\t\t\t};\n\t\t}\n\n\t\t// Unexpected failure — surface it.\n\t\tthrow new ComputeError(\n\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t'VALIDATION_ERROR',\n\t\t\t{\n\t\t\t\tcontext: { paramName: rawInput.name, paramType },\n\t\t\t\toriginalError: error instanceof Error ? error : new Error(String(error))\n\t\t\t}\n\t\t);\n\t}\n}\n\n/**\n * Parse an array of raw input schemas into typed {@link InputParam}s, each via\n * {@link processInput}. Use {@link processInputsWithErrors} to also collect the\n * inputs that failed validation.\n */\nexport function processInputs(rawInputs: InputParamSchema[]): InputParam[] {\n\treturn processInputsWithErrors(rawInputs).inputs;\n}\n\n/**\n * Like {@link processInputs}, but additionally returns a list of inputs that\n * failed validation and were filled with a safe default.\n *\n * @internal Used by {@link fetchParsedDefinitionIO}.\n */\nexport function processInputsWithErrors(rawInputs: InputParamSchema[]): {\n\tinputs: InputParam[];\n\tparseErrors: InputParseError[];\n} {\n\tconst inputs: InputParam[] = [];\n\tconst parseErrors: InputParseError[] = [];\n\tfor (const raw of rawInputs) {\n\t\tconst { input, errors } = processInputWithError(raw);\n\t\tinputs.push(input);\n\t\tif (errors) parseErrors.push(...errors);\n\t}\n\treturn { inputs, parseErrors };\n}\n","import { readField } from '@/core/utils/read-field';\nimport type { InputParamSchema, OutputParamSchema } from '../types';\n\n/**\n * @internal Canonicalize a raw `/io` param record's field CASING.\n *\n * ## Why this exists\n *\n * The Rhino Compute `/io` response is only partially camelCased, and how much\n * depends on the server branch:\n *\n * - mcneel 8.x/9.x and the upstream-tracking `8.x.selva` branch keep the IO\n * schema close to the raw C# classes, which carry few/no `[JsonProperty]`\n * attributes — so most per-param fields serialize PascalCase (`ParamType`,\n * `Minimum`, `Name`, `Default`, …), with only `id` / `groupName` / `values`\n * lowercased.\n * - The VektorNode Compute8 fork added `[JsonProperty(\"camelCase\")]` to every\n * field, so the same record arrives fully camelCase.\n *\n * The per-type parsers ({@link INPUT_TYPE_PARSERS}) and base-field extraction\n * read fields straight through (`schema.paramType`, `schema.minimum`, …). On a\n * PascalCase server those reads all miss, so every input parses as an unknown\n * type with a `null` default — the definition looks like it has no usable\n * inputs. Normalizing the casing ONCE here, at the parse boundary, lets the\n * whole downstream pipeline stay branch-agnostic without threading `readField`\n * through every parser.\n *\n * ## What it does NOT touch\n *\n * Only the top-level FIELD KEYS are canonicalized. The VALUES are passed through\n * verbatim — in particular `default` (the nested DataTree, whose `InnerTree` /\n * item casing is handled separately and case-insensitively by\n * `normalizeDefault`) and `values` (user-authored dropdown label keys like\n * \"Option A\", which a naive deep camelCase pass would mangle to \"optionA\" — the\n * exact regression that motivated removing the old global `camelcaseKeys`).\n *\n * ## Missing wire fields\n *\n * `InputParamSchema` declares several fields required, but a degraded server\n * response can omit any of them. Rather than casting a hole into the type\n * (`as string` on a possibly-`undefined` read), each required field gets an\n * honest fallback that keeps the declared type true AND lets the failure\n * surface where it can be reported:\n *\n * - `paramType` → `''`: an empty type is unknown to the parser registry, so\n * the input degrades to a safe fallback WITH an unknown-paramType\n * `parseErrors` entry — a missing required wire field becomes a visible\n * per-input parse error instead of a downstream `undefined.toLowerCase()`.\n * - `name`/`id`/`description` → `''` (a blank name reports as `'unknown'`).\n * - `treeAccess` → `false`, `atLeast`/`atMost` → `1`: Grasshopper's\n * item-access defaults.\n * - `groupName` → `null`, NOT `''`: the type is `string | null` precisely to\n * distinguish \"no group\" from \"empty group name\".\n * - `stepSize` is optional; a non-numeric wire value is dropped rather than\n * passed through typed as `number`.\n */\nexport function normalizeInputSchema(raw: unknown): InputParamSchema {\n\tconst stepSize = readField<unknown>(raw, 'stepSize');\n\treturn {\n\t\tid: readField<string>(raw, 'id') ?? '',\n\t\tname: readField<string>(raw, 'name') ?? '',\n\t\tnickname: readField<string | null>(raw, 'nickname') ?? null,\n\t\tdescription: readField<string>(raw, 'description') ?? '',\n\t\tparamType: readField<string>(raw, 'paramType') ?? '',\n\t\ttreeAccess: readField<boolean>(raw, 'treeAccess') ?? false,\n\t\tminimum: readField<number | null>(raw, 'minimum') ?? null,\n\t\tmaximum: readField<number | null>(raw, 'maximum') ?? null,\n\t\tatLeast: readField<number>(raw, 'atLeast') ?? 1,\n\t\tatMost: readField<number>(raw, 'atMost') ?? 1,\n\t\tstepSize: typeof stepSize === 'number' && Number.isFinite(stepSize) ? stepSize : undefined,\n\t\tdefault: readField(raw, 'default'),\n\t\tvalues: readField<Record<string, string>>(raw, 'values'),\n\t\tacceptedFormats: readField<string[]>(raw, 'acceptedFormats'),\n\t\tgroupName: readField<string | null>(raw, 'groupName') ?? null\n\t};\n}\n\n/**\n * @internal Canonicalize a raw `/io` output record's field casing.\n *\n * Same rationale as {@link normalizeInputSchema}.\n */\nexport function normalizeOutputSchema(raw: unknown): OutputParamSchema {\n\treturn {\n\t\tname: readField<string>(raw, 'name') ?? '',\n\t\tnickname: readField<string | null>(raw, 'nickname') ?? null,\n\t\tparamType: readField<string>(raw, 'paramType') ?? '',\n\t\tid: readField<string>(raw, 'id') ?? ''\n\t};\n}\n","import { ComputeConfig, ComputeError, ErrorCodes } from '@/core';\nimport { fetchCompute } from '@/core/compute-fetch/compute-fetch';\nimport { readField } from '@/core/utils/read-field';\nimport { warnIfClientSide } from '@/core/utils/warnings';\nimport { prepareGrasshopperArgs, withGrasshopperErrorCodes } from '../solve';\n\nimport { GrasshopperParsedIO, GrasshopperParsedIORaw, IoResponseSchema } from '../types';\n\nimport { processInputsWithErrors } from './input/input-processors';\nimport { normalizeInputSchema, normalizeOutputSchema } from './normalize-schema';\n\n/**\n * Fetches raw input/output schemas from a Grasshopper definition.\n *\n * \"Raw\" means no per-type parsing (no default coercion, no discriminated-union\n * typing) — but NOT byte-for-byte wire data. The response IS normalized:\n * per-param field KEYS are canonicalized to camelCase across server branches\n * ({@link normalizeInputSchema} / {@link normalizeOutputSchema}, with honest\n * fallbacks for missing required fields), missing/non-array `inputs`/`outputs`\n * coerce to `[]`, and server load diagnostics surface as\n * `loadWarnings`/`loadErrors` (coerced to strings). Field VALUES — notably\n * `default` and the `values` dropdown-label map — pass through verbatim.\n *\n * @param definition - The Grasshopper definition (URL, base64 string, or Uint8Array)\n * @param config - Compute configuration (server URL, API key, etc.)\n * @returns Key-normalized inputs and outputs with no per-type processing\n * @throws {ComputeError} If fetch fails or response is invalid\n *\n * @public Use `fetchParsedDefinitionIO()` for processed, type-safe inputs\n */\nexport async function fetchDefinitionIO(\n\tdefinition: string | Uint8Array,\n\tconfig: ComputeConfig\n): Promise<GrasshopperParsedIORaw> {\n\tconst args = prepareGrasshopperArgs(definition, []);\n\tconst payload: { algo?: string | null; pointer?: string | null } = {};\n\tif (args.algo) payload.algo = args.algo;\n\tif (args.pointer) payload.pointer = args.pointer;\n\n\tif (!payload.algo && !payload.pointer) {\n\t\tthrow new ComputeError(\n\t\t\t'Definition must resolve to either a URL pointer or base64 algo',\n\t\t\tErrorCodes.INVALID_INPUT,\n\t\t\t{ context: { definition } }\n\t\t);\n\t}\n\n\tconst response = await fetchCompute<IoResponseSchema>(\n\t\t'io',\n\t\tpayload,\n\t\twithGrasshopperErrorCodes(config)\n\t);\n\n\tif (!response || typeof response !== 'object') {\n\t\tthrow new ComputeError('Invalid IO response structure', ErrorCodes.INVALID_INPUT, {\n\t\t\tcontext: { response, definition }\n\t\t});\n\t}\n\n\t// The `/io` response is only partially camelCased, and how much depends on the\n\t// server branch. Upstream-tracking branches (mcneel 8.x/9.x, `8.x.selva`) keep\n\t// the C# classes close to source — they carry few/no `[JsonProperty]`, so the\n\t// top-level wrapper is PascalCase `Inputs` / `Outputs` and per-param fields are\n\t// `ParamType` / `Minimum` / … The VektorNode Compute8 fork camelCases every\n\t// field. So we read every field we depend on case-insensitively via `readField`\n\t// rather than straight-through. A deep `camelcaseKeys` pass is NOT an option: it\n\t// mangled user-authored value-list label keys (\"Option A\" → \"optionA\") and item\n\t// `data` JSON — which is why per-field reads exist instead (per-input field\n\t// normalization lives in normalize-schema.ts; the nested `default` DataTree is\n\t// handled by normalize-default.ts).\n\t//\n\t// The server also reports definition-LOAD diagnostics on the IO response\n\t// (`errors`/`warnings` — e.g. a missing plugin that left inputs unresolved).\n\t// Surface them so a degraded input list comes with an explanation instead of\n\t// silently looking empty. Only attach when non-empty to keep the common\n\t// happy-path result clean.\n\tconst loadWarnings = nonEmptyStrings(readField(response, 'warnings'));\n\tconst loadErrors = nonEmptyStrings(readField(response, 'errors'));\n\n\t// Read the top-level Inputs/Outputs case-insensitively, then guard to arrays.\n\t// A server fault can also return a 200 whose body omits these (e.g. a load\n\t// failure surfacing as malformed-success), and the downstream `for...of` in\n\t// processInputsWithErrors throws \"inputs is not iterable\". Array.isArray (not\n\t// `?? []`) is deliberate: the symptom is non-iterability, so a non-array truthy\n\t// value (`{}`, a string) must coerce to `[]` too. The loadErrors/loadWarnings\n\t// surfaced above explain *why* a list came back empty.\n\tconst rawInputs = readField(response, 'inputs');\n\tconst rawOutputs = readField(response, 'outputs');\n\treturn {\n\t\tinputs: Array.isArray(rawInputs) ? rawInputs.map(normalizeInputSchema) : [],\n\t\toutputs: Array.isArray(rawOutputs) ? rawOutputs.map(normalizeOutputSchema) : [],\n\t\t...(loadWarnings && { loadWarnings }),\n\t\t...(loadErrors && { loadErrors })\n\t};\n}\n\n/**\n * Coerce a server `errors`/`warnings` array (typed `any[]`) into a clean\n * `string[]`, or `undefined` when there's nothing to report.\n *\n * Non-string diagnostics are KEPT, not dropped: a server fork reporting errors\n * as `{ message }` objects must not yield a mysteriously empty inputs list with\n * zero explanation (the exact failure this surfacing exists to prevent).\n * Objects coerce to their `message` field when it's a non-blank string, else\n * to JSON; other primitives via `String(...)`. Only `null`/`undefined` and\n * blank entries are discarded.\n */\nfunction nonEmptyStrings(value: unknown): string[] | undefined {\n\tif (!Array.isArray(value)) return undefined;\n\tconst cleaned = value.map(coerceDiagnostic).filter((v): v is string => v !== undefined);\n\treturn cleaned.length > 0 ? cleaned : undefined;\n}\n\n/** One diagnostic entry → non-blank string, or `undefined` when there's nothing to say. */\nfunction coerceDiagnostic(value: unknown): string | undefined {\n\tif (value === null || value === undefined) return undefined;\n\tlet text: string;\n\tif (typeof value === 'string') {\n\t\ttext = value;\n\t} else if (typeof value === 'object') {\n\t\tconst message = readField<unknown>(value, 'message');\n\t\tif (typeof message === 'string' && message.trim().length > 0) {\n\t\t\ttext = message;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\ttext = JSON.stringify(value) ?? String(value);\n\t\t\t} catch {\n\t\t\t\ttext = String(value);\n\t\t\t}\n\t\t}\n\t} else {\n\t\ttext = String(value);\n\t}\n\treturn text.trim().length > 0 ? text : undefined;\n}\n\n/**\n * Fetches and processes input/output schemas from a Grasshopper definition.\n * Returns strongly-typed, validated input parameters ready for use.\n *\n * @public This is the recommended way to fetch definition I/O schemas.\n *\n * @param definition - The Grasshopper definition (URL, base64 string, or Uint8Array)\n * @param config - Compute configuration (server URL, API key, etc.)\n * @returns Processed inputs with discriminated union types and outputs\n * @throws {ComputeError} If fetch fails or response is invalid\n *\n * @example\n * ```typescript\n * const { inputs, outputs } = await fetchParsedDefinitionIO(\n * 'https://example.com/definition.gh',\n * { serverUrl: 'https://compute.rhino3d.com', apiKey: 'YOUR_KEY' }\n * );\n *\n * // Inputs are now strongly typed\n * inputs.forEach(input => {\n * if (input.paramType === 'Number') {\n * console.log(input.minimum, input.maximum); // TypeScript knows these exist\n * }\n * });\n * ```\n */\nexport async function fetchParsedDefinitionIO(\n\tdefinition: string | Uint8Array,\n\tconfig: ComputeConfig\n): Promise<GrasshopperParsedIO> {\n\twarnIfClientSide('fetchParsedDefinitionIO', config.suppressBrowserWarning);\n\n\tconst {\n\t\tinputs: rawInputs,\n\t\toutputs,\n\t\tloadWarnings,\n\t\tloadErrors\n\t} = await fetchDefinitionIO(definition, config);\n\tconst { inputs, parseErrors } = processInputsWithErrors(rawInputs);\n\n\treturn {\n\t\tinputs,\n\t\toutputs,\n\t\t...(parseErrors.length > 0 && { parseErrors }),\n\t\t...(loadWarnings && { loadWarnings }),\n\t\t...(loadErrors && { loadErrors })\n\t};\n}\n","import { readField } from '@/core/utils/read-field';\n\n/** One entry of the schema endpoint's response, after wrapper-key normalization. */\nexport interface SchemaEndpointResult<TSchema = unknown> {\n\t/** Schemas embedded in that file. Absent when the file yielded none. */\n\tschemas?: TSchema[];\n\t/** Per-file diagnosis. Compute reports this and still answers 200. */\n\terror?: string;\n}\n\n/**\n * Read compute's `/grasshopper/schema` body: `[{ FileName, Schemas }]` per\n * uploaded file, or a bare object for a single file.\n *\n * Use this rather than unwrapping the body by hand. The wrapper's casing varies\n * by server branch — mcneel serializes `FileName`/`Schemas`, the VektorNode fork\n * `fileName`/`schemas` — so a fixed-key read silently yields `undefined` against\n * half the servers, and the endpoint answers 200 either way. The failure looks\n * like \"this definition has no schemas\", which sends you debugging the wrong\n * thing entirely.\n *\n * A blanket key-rewrite (the old `camelcaseKeys` approach) is NOT the fix: it\n * reaches inside the schemas and mangles user-authored names — `\"Display3d\"` →\n * `\"display3d\"`, value-list labels like `\"Option A\"` → `\"optionA\"`. Only the two\n * wrapper keys are read here; schema CONTENTS pass through untouched, which is\n * why `TSchema` is a pass-through type parameter this module never inspects.\n *\n * @typeParam TSchema - Your schema type (e.g. `UISchema`). Not inspected.\n * @param raw - The parsed JSON body.\n */\nexport function readSchemaResults<TSchema = unknown>(\n\traw: unknown\n): SchemaEndpointResult<TSchema>[] {\n\tconst entries = Array.isArray(raw) ? raw : [raw];\n\treturn entries.map((entry) => ({\n\t\tschemas: readField<TSchema[]>(entry, 'schemas'),\n\t\terror: readField<string>(entry, 'error')\n\t}));\n}\n","import { DataTreeDefault, DataTreePath, InputParam, DataTree } from '../types';\nimport { getLogger } from '@/core';\nimport { isDataTreeDefault, TREE_PATH_RE } from './tree-path';\n\n/**\n * Value types that can be stored in a DataTree\n */\nexport type DataTreeValue = string | number | boolean | object | null;\n\n/**\n * Simple data item for compute requests (not to be confused with DataItem interface for responses).\n * Note: While TypeScript defines this as string, Rhino Compute accepts boolean/number primitives in JSON.\n */\ninterface ComputeDataItem {\n\tdata: string | boolean | number;\n}\n\n/**\n * InnerTree data structure for compute requests.\n */\ntype ComputeInnerTreeData = {\n\t[path in DataTreePath]: ComputeDataItem[];\n};\n\n/**\n * Standalone TreeBuilder class for constructing Grasshopper TreeBuilder structures.\n * Does not depend on RhinoCompute library.\n *\n * @example\n * ```ts\n * const tree = new TreeBuilder('MyParam')\n * .append([0], [1, 2, 3])\n * .append([1], [4, 5])\n * .toComputeFormat();\n * ```\n */\nexport class TreeBuilder {\n\tprivate innerTree: ComputeInnerTreeData;\n\tprivate paramName: string;\n\n\tconstructor(paramName: string) {\n\t\tthis.paramName = paramName;\n\t\tthis.innerTree = {} as ComputeInnerTreeData;\n\t}\n\n\t/**\n\t * Append values to a specific path in the tree.\n\t *\n\t * @param path - Array of integers representing the branch path (e.g., [0], [0, 1])\n\t * @param items - Values to append at this path\n\t * @returns this for method chaining\n\t */\n\tpublic append(path: number[], items: DataTreeValue[]): this {\n\t\tconst pathKey = TreeBuilder.formatPathString(path);\n\n\t\tif (!this.innerTree[pathKey]) {\n\t\t\tthis.innerTree[pathKey] = [];\n\t\t}\n\n\t\tconst dataItems: ComputeDataItem[] = items.map((item) => ({\n\t\t\tdata: TreeBuilder.serializeValue(item)\n\t\t}));\n\n\t\tthis.innerTree[pathKey].push(...dataItems);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Append a single value to a path.\n\t *\n\t * @param path - Branch path\n\t * @param item - Single value to append\n\t * @returns this for method chaining\n\t */\n\tpublic appendSingle(path: number[], item: DataTreeValue): this {\n\t\treturn this.append(path, [item]);\n\t}\n\n\t/**\n\t * Set values from a DataTreeDefault structure.\n\t * Replaces any existing tree data.\n\t *\n\t * @param treeData - TreeBuilder structure with path keys like \"{0;1}\"\n\t * @returns this for method chaining\n\t */\n\tpublic fromDataTreeDefault(treeData: DataTreeDefault): this {\n\t\tthis.innerTree = {} as ComputeInnerTreeData;\n\n\t\tfor (const [pathStr, items] of Object.entries(treeData)) {\n\t\t\tif (!Array.isArray(items)) continue;\n\t\t\tconst path = TreeBuilder.parsePathString(pathStr);\n\t\t\tthis.append(path, items);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Append flattened values to path [0].\n\t * Useful for simple flat inputs.\n\t *\n\t * @param values - Single value or array of values\n\t * @returns this for method chaining\n\t */\n\tpublic appendFlat(values: DataTreeValue | DataTreeValue[]): this {\n\t\tconst items = Array.isArray(values) ? values : [values];\n\t\treturn this.append([0], items);\n\t}\n\n\t/**\n\t * Get the flattened list of all values in the tree.\n\t *\n\t * @returns Array of all values across all branches\n\t */\n\tpublic flatten(): DataTreeValue[] {\n\t\tconst result: DataTreeValue[] = [];\n\n\t\tfor (const items of Object.values(this.innerTree)) {\n\t\t\tif (Array.isArray(items)) {\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tresult.push(TreeBuilder.deserializeValue(item.data));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get all paths in the tree.\n\t *\n\t * @returns Array of path strings\n\t */\n\tpublic getPaths(): DataTreePath[] {\n\t\treturn Object.keys(this.innerTree) as DataTreePath[];\n\t}\n\n\t/**\n\t * Get values at a specific path.\n\t *\n\t * @param path - Path to retrieve values from\n\t * @returns Array of values or undefined if path doesn't exist\n\t */\n\tpublic getPath(path: number[]): DataTreeValue[] | undefined {\n\t\tconst pathKey = TreeBuilder.formatPathString(path);\n\t\tconst items = this.innerTree[pathKey];\n\t\tif (!items) return undefined;\n\t\treturn items.map((item: ComputeDataItem) => TreeBuilder.deserializeValue(item.data));\n\t}\n\n\t/**\n\t * Convert to format compatible with Grasshopper Compute API.\n\t *\n\t * @returns InnerTree object ready for compute\n\t */\n\tpublic toComputeFormat(): DataTree {\n\t\treturn {\n\t\t\tParamName: this.paramName,\n\t\t\tInnerTree: this.innerTree as any // Cast to any because request format differs from response type\n\t\t};\n\t}\n\n\t/**\n\t * Get the raw InnerTree data structure.\n\t *\n\t * @returns InnerTree data\n\t */\n\tpublic getInnerTree(): ComputeInnerTreeData {\n\t\treturn this.innerTree;\n\t}\n\n\t/**\n\t * Get the parameter name.\n\t *\n\t * @returns Parameter name\n\t */\n\tpublic getParamName(): string {\n\t\treturn this.paramName;\n\t}\n\n\t/**\n\t * Create DataTrees from an array of InputParam definitions.\n\t * Handles tree access, numeric constraints, and value parsing.\n\t *\n\t * @param inputs - Array of input parameter definitions\n\t * @returns Array of InnerTree instances ready for compute\n\t *\n\t * @example\n\t * ```ts\n\t * const trees = TreeBuilder.fromInputParams(inputs);\n\t * ```\n\t */\n\tpublic static fromInputParams(inputs: InputParam[]): DataTree[] {\n\t\treturn inputs\n\t\t\t.filter((input) => TreeBuilder.hasValidValue(input.default))\n\t\t\t.map((input) => {\n\t\t\t\tconst tree = new TreeBuilder(input.nickname || 'unnamed');\n\t\t\t\tconst value = input.default;\n\n\t\t\t\t// Handle tree access (complex TreeBuilder structure)\n\t\t\t\tif (input.treeAccess && isDataTreeDefault(value)) {\n\t\t\t\t\ttree.fromDataTreeDefault(value);\n\n\t\t\t\t\t// Apply numeric constraints to tree items\n\t\t\t\t\tif (TreeBuilder.isNumericInput(input)) {\n\t\t\t\t\t\ttree.applyNumericConstraints(input.minimum, input.maximum, input.nickname || 'unnamed');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Handle flat inputs\n\t\t\t\telse {\n\t\t\t\t\tconst values = Array.isArray(value) ? value : [value];\n\t\t\t\t\tconst processed = TreeBuilder.processValues(values, input);\n\t\t\t\t\ttree.appendFlat(processed);\n\t\t\t\t}\n\n\t\t\t\treturn tree.toComputeFormat();\n\t\t\t});\n\t}\n\n\t/**\n\t * Create a TreeBuilder from a single InputParam.\n\t *\n\t * @param input - Input parameter definition\n\t * @returns InnerTree ready for compute or undefined if value is invalid\n\t */\n\tpublic static fromInputParam(input: InputParam): DataTree | undefined {\n\t\tif (!TreeBuilder.hasValidValue(input.default)) return undefined;\n\n\t\tconst trees = TreeBuilder.fromInputParams([input]);\n\t\treturn trees[0];\n\t}\n\n\t/**\n\t * Set or replace a parameter value within a TreeBuilder or InnerTree array.\n\t *\n\t * Supports both high-level `DataTree[]` instances and low-level `InnerTree[]` format.\n\t *\n\t * **Architecture Note:**\n\t * - Use with `DataTree[]` when building/modifying before computation\n\t * - Use with `InnerTree[]` when modifying compute API results\n\t * - `DataTree` is the high-level builder; `InnerTree` is the Rhino Compute format\n\t *\n\t * Copy-on-write: returns a new array; the caller's array is never mutated.\n\t *\n\t * @overload For TreeBuilder instances (high-level builder)\n\t * @param trees - Array of TreeBuilder instances to read from (not mutated)\n\t * @param paramName - The parameter name to set or replace\n\t * @param newValue - The new value (scalar, array, or TreeBuilder structure)\n\t * @returns A new TreeBuilder array with the updated parameter\n\t *\n\t * @overload For compiled InnerTree (low-level API format)\n\t * @param trees - The compiled InnerTree array (typically from `client.solve()`; not mutated)\n\t * @param paramName - The parameter name to set or replace\n\t * @param newValue - The new value (scalar, array, or TreeBuilder structure)\n\t * @returns A new InnerTree array with the updated parameter\n\t *\n\t * @example\n\t * ```ts\n\t * // With TreeBuilder instances (high-level)\n\t * let trees = [new TreeBuilder('X'), new TreeBuilder('Y')];\n\t * trees = TreeBuilder.replaceTreeValue(trees, 'X', 42);\n\t * const result = await client.solve(definitionUrl,\n\t * trees.map(t => t.toComputeFormat())\n\t * );\n\t * ```\n\t *\n\t * @example\n\t * ```ts\n\t * // With InnerTree format (low-level, from API)\n\t * let trees = await client.solve(definitionUrl, initialInputs);\n\t * trees = TreeBuilder.replaceTreeValue(trees, 'X', 42);\n\t * trees = TreeBuilder.replaceTreeValue(trees, 'Y', [1, 2, 3]);\n\t * ```\n\t */\n\tpublic static replaceTreeValue(\n\t\ttrees: TreeBuilder[],\n\t\tparamName: string,\n\t\tnewValue: DataTreeValue\n\t): TreeBuilder[];\n\tpublic static replaceTreeValue(\n\t\ttrees: DataTree[],\n\t\tparamName: string,\n\t\tnewValue: DataTreeValue\n\t): DataTree[];\n\tpublic static replaceTreeValue(\n\t\ttrees: TreeBuilder[] | DataTree[],\n\t\tparamName: string,\n\t\tnewValue: DataTreeValue\n\t): TreeBuilder[] | DataTree[] {\n\t\tconst isBuilderArray = trees.length > 0 && trees[0] instanceof TreeBuilder;\n\t\tconst builder = TreeBuilder.buildFromValue(paramName, newValue);\n\n\t\tif (isBuilderArray) {\n\t\t\t// Copy-on-write: never mutate the caller's array (e.g. pristine\n\t\t\t// defaults from fromInputParams must survive slider updates).\n\t\t\tconst builders = (trees as TreeBuilder[]).slice();\n\t\t\tconst idx = builders.findIndex((t) => t.getParamName() === paramName);\n\t\t\tif (idx !== -1) builders[idx] = builder;\n\t\t\telse builders.push(builder);\n\t\t\treturn builders;\n\t\t}\n\n\t\t// Empty arrays land here too — see the \"empty array\" characterization\n\t\t// test in data-tree.test.ts: pins the current behavior of returning the\n\t\t// compute-format shape rather than a TreeBuilder.\n\t\tconst dataTrees = (trees as DataTree[]).slice();\n\t\tconst compiled = builder.toComputeFormat();\n\t\tconst idx = dataTrees.findIndex((t) => t.ParamName === paramName);\n\t\tif (idx !== -1) dataTrees[idx] = compiled;\n\t\telse dataTrees.push(compiled);\n\t\treturn dataTrees;\n\t}\n\n\t/**\n\t * Build a TreeBuilder from a single value, dispatching on shape:\n\t * DataTreeDefault structure, array, or scalar.\n\t */\n\tprivate static buildFromValue(paramName: string, value: DataTreeValue): TreeBuilder {\n\t\tconst tree = new TreeBuilder(paramName);\n\t\tif (isDataTreeDefault(value)) {\n\t\t\ttree.fromDataTreeDefault(value);\n\t\t} else {\n\t\t\ttree.appendFlat(value);\n\t\t}\n\t\treturn tree;\n\t}\n\n\t/**\n\t * Extract a value from a TreeBuilder or InnerTree array by parameter name.\n\t *\n\t * Automatically unwraps single values for convenience.\n\t * Works with both high-level `DataTree[]` instances and low-level `InnerTree[]` format.\n\t *\n\t * **Architecture Note:**\n\t * - Use with `DataTree[]` to read builder instances\n\t * - Use with `InnerTree[]` to read compute API responses\n\t * - Return behavior is consistent across both formats\n\t *\n\t * **Return Value Behavior:**\n\t * - Single value → unwrapped (returns `5` not `[5]`)\n\t * - Multiple values → array of values\n\t * - Not found → `null`\n\t *\n\t * @overload For TreeBuilder instances\n\t * @param trees - Array of TreeBuilder instances to read from\n\t * @param paramName - The parameter name to retrieve\n\t * @returns The unwrapped value, array of values, or null if parameter not found\n\t *\n\t * @overload For compiled InnerTree\n\t * @param trees - The compiled InnerTree array (typically from `client.solve()`)\n\t * @param paramName - The parameter name to retrieve\n\t * @returns The unwrapped value, array of values, or null if parameter not found\n\t *\n\t * @example\n\t * ```ts\n\t * // With TreeBuilder instances\n\t * const trees = [new TreeBuilder('X'), new TreeBuilder('Y')];\n\t * trees[0].appendFlat(42);\n\t * const x = TreeBuilder.getTreeValue(trees, 'X'); // Returns 42\n\t * ```\n\t *\n\t * @example\n\t * ```ts\n\t * // With InnerTree from compute results\n\t * const result = await client.solve(definitionUrl, inputs);\n\t * const x = TreeBuilder.getTreeValue(result, 'X'); // Returns 42 (not [42])\n\t * const points = TreeBuilder.getTreeValue(result, 'Points'); // Returns [point1, point2, ...]\n\t * ```\n\t */\n\tpublic static getTreeValue(trees: TreeBuilder[], paramName: string): DataTreeValue | null;\n\tpublic static getTreeValue(trees: DataTree[], paramName: string): DataTreeValue | null;\n\tpublic static getTreeValue(\n\t\ttrees: TreeBuilder[] | DataTree[],\n\t\tparamName: string\n\t): DataTreeValue | null {\n\t\tconst isBuilderArray = trees.length > 0 && trees[0] instanceof TreeBuilder;\n\n\t\tconst values = isBuilderArray\n\t\t\t? TreeBuilder.readFromBuilders(trees as TreeBuilder[], paramName)\n\t\t\t: TreeBuilder.readFromDataTrees(trees as DataTree[], paramName);\n\n\t\tif (values === null) return null;\n\t\tif (values.length === 0) return null;\n\t\tif (values.length === 1) return values[0];\n\t\treturn values;\n\t}\n\n\t/**\n\t * Read all values for `paramName` across every branch of the matching builder.\n\t * Returns null when the builder isn't found.\n\t */\n\tprivate static readFromBuilders(\n\t\tbuilders: TreeBuilder[],\n\t\tparamName: string\n\t): DataTreeValue[] | null {\n\t\tconst tree = builders.find((t) => t.getParamName() === paramName);\n\t\treturn tree ? tree.flatten() : null;\n\t}\n\n\t/**\n\t * Read values from the first branch of the matching compiled InnerTree\n\t * (multi-branch responses are not flattened — current semantics, pinned by\n\t * the \"reads from the first branch path only\" test).\n\t */\n\tprivate static readFromDataTrees(\n\t\tdataTrees: DataTree[],\n\t\tparamName: string\n\t): DataTreeValue[] | null {\n\t\tconst tree = dataTrees.find((t) => t.ParamName === paramName);\n\t\tif (!tree?.InnerTree) return null;\n\n\t\tconst firstKey = Object.keys(tree.InnerTree)[0];\n\t\tif (!firstKey) return null;\n\n\t\t// @ts-expect-error - Dynamic key access on innerTree\n\t\tconst items = tree.InnerTree[firstKey];\n\n\t\tif (Array.isArray(items)) {\n\t\t\t// Preserve nulls (legitimate GH items) so indices don't shift; an\n\t\t\t// item without `data` deserializes to null in place. \"Param not\n\t\t\t// found\" is signalled only by a missing/empty tree, never by nulls.\n\t\t\treturn items.map((item) =>\n\t\t\t\titem?.data !== undefined ? TreeBuilder.deserializeValue(item.data) : null\n\t\t\t);\n\t\t}\n\n\t\tif (items?.data !== undefined) return [TreeBuilder.deserializeValue(items.data)];\n\t\treturn items !== undefined ? [items as DataTreeValue] : null;\n\t}\n\n\t/**\n\t * Parse a TreeBuilder path string like \"{0;1;2}\" into [0, 1, 2].\n\t * Negative indices (\"{-1;2}\") and the root path \"{}\" are valid.\n\t *\n\t * @param pathStr - Path string\n\t * @returns Array of path indices\n\t * @throws Error when the path string is not a valid Grasshopper branch path.\n\t * Malformed keys must never silently collapse to a default branch —\n\t * two distinct unparseable keys would merge their items into one branch.\n\t */\n\tpublic static parsePathString(pathStr: string): number[] {\n\t\t// Allow the legitimate root path \"{}\" alongside \"{0;1;2}\" / \"{-1;2}\"\n\t\tconst match = pathStr.match(TREE_PATH_RE);\n\t\tif (!match) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid Grasshopper tree path: \"${pathStr}\". ` +\n\t\t\t\t\t`Expected \"{}\", \"{0}\", or \"{0;1;2}\" (negative indices allowed, no empty segments).`\n\t\t\t);\n\t\t}\n\t\t// Root path \"{}\" — the (optional) capture group is undefined/empty.\n\t\tif (!match[1]) return [];\n\t\treturn match[1].split(';').map(Number);\n\t}\n\n\t/**\n\t * Format a path array into TreeBuilder path string format.\n\t *\n\t * @param path - Path as number array\n\t * @returns Formatted path string like \"{0;1;2}\"\n\t */\n\tpublic static formatPathString(path: number[]): DataTreePath {\n\t\treturn `{${path.join(';')}}` as DataTreePath;\n\t}\n\n\t/** Apply numeric constraints to all tree values. */\n\tprivate applyNumericConstraints(\n\t\tmin: number | null | undefined,\n\t\tmax: number | null | undefined,\n\t\tinputName: string\n\t): void {\n\t\tfor (const items of Object.values(this.innerTree)) {\n\t\t\tif (!Array.isArray(items)) continue;\n\n\t\t\tfor (const item of items) {\n\t\t\t\tconst value = TreeBuilder.deserializeValue(item.data);\n\t\t\t\tif (typeof value === 'number') {\n\t\t\t\t\tconst clamped = TreeBuilder.clampValue(value, min, max, inputName);\n\t\t\t\t\titem.data = TreeBuilder.serializeValue(clamped);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Serialize a value for compute requests.\n\t * Preserves booleans and numbers as primitives for proper Grasshopper parameter handling.\n\t */\n\tprivate static serializeValue(value: DataTreeValue): string | boolean | number {\n\t\tif (typeof value === 'boolean') return value;\n\t\tif (typeof value === 'number') return value;\n\t\tif (typeof value === 'string') return value;\n\t\tif (typeof value === 'object' && value !== null) {\n\t\t\treturn JSON.stringify(value);\n\t\t}\n\t\treturn String(value);\n\t}\n\n\t/**\n\t * Deserialize a value back to its original type.\n\t * Handles both string-encoded values and primitive values.\n\t */\n\tprivate static deserializeValue(data: string | boolean | number): DataTreeValue {\n\t\t// If already a primitive type, return as-is\n\t\tif (typeof data === 'boolean') return data;\n\t\tif (typeof data === 'number') return data;\n\n\t\t// Handle string values\n\t\tif (typeof data !== 'string') return data;\n\n\t\t// Try to parse as JSON first\n\t\tif (data.startsWith('{') || data.startsWith('[')) {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(data);\n\t\t\t} catch {\n\t\t\t\treturn data;\n\t\t\t}\n\t\t}\n\t\t// Coerce to number only when the string is the *canonical* form of a\n\t\t// finite number, i.e. it round-trips exactly (String(Number(s)) === s).\n\t\t// API responses encode numbers as strings ('42', '3.14', '1e+21'), and\n\t\t// those must come back numeric — but non-canonical numeric-looking\n\t\t// strings ('007', '1e5', 'Infinity', '', ' 5') were strings on the\n\t\t// wire and must stay strings.\n\t\tconst num = Number(data);\n\t\tif (Number.isFinite(num) && String(num) === data) {\n\t\t\treturn num;\n\t\t}\n\t\t// Try to parse as boolean\n\t\tif (data === 'true') return true;\n\t\tif (data === 'false') return false;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Check if a value is valid for inclusion in a DataTree.\n\t */\n\tprivate static hasValidValue(value: unknown): boolean {\n\t\tif (value === undefined || value === null) return false;\n\t\tif (typeof value === 'string') return true;\n\t\tif (Array.isArray(value) && value.length === 0) return false;\n\t\tif (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0)\n\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/**\n\t * Check if input is numeric type.\n\t */\n\tprivate static isNumericInput(input: InputParam): input is InputParam & {\n\t\tparamType: 'Number' | 'Integer';\n\t\tminimum?: number | null;\n\t\tmaximum?: number | null;\n\t} {\n\t\treturn input.paramType === 'Number' || input.paramType === 'Integer';\n\t}\n\n\t/**\n\t * Process array of values based on input type.\n\t */\n\tprivate static processValues(values: DataTreeValue[], input: InputParam): DataTreeValue[] {\n\t\treturn values\n\t\t\t.map((val) => {\n\t\t\t\t// Apply numeric constraints\n\t\t\t\tif (TreeBuilder.isNumericInput(input) && typeof val === 'number') {\n\t\t\t\t\treturn TreeBuilder.clampValue(\n\t\t\t\t\t\tval,\n\t\t\t\t\t\tinput.minimum,\n\t\t\t\t\t\tinput.maximum,\n\t\t\t\t\t\tinput.nickname || 'unnamed'\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Keep objects and strings as-is (serialization happens in append)\n\t\t\t\treturn val;\n\t\t\t})\n\t\t\t.filter((v) => v !== null && v !== undefined);\n\t}\n\n\t/**\n\t * Clamp numeric value to constraints.\n\t */\n\tprivate static clampValue(\n\t\tvalue: number,\n\t\tmin: number | null | undefined,\n\t\tmax: number | null | undefined,\n\t\tinputName: string\n\t): number {\n\t\tlet result = value;\n\n\t\tif (min !== null && min !== undefined && result < min) {\n\t\t\tgetLogger().warn(`${inputName}: ${value} below min ${min}, clamping`);\n\t\t\tresult = min;\n\t\t}\n\t\tif (max !== null && max !== undefined && result > max) {\n\t\t\tgetLogger().warn(`${inputName}: ${value} above max ${max}, clamping`);\n\t\t\tresult = max;\n\t\t}\n\n\t\treturn result;\n\t}\n}\n"],"mappings":"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","names":["_ComputeServerStats","serverUrl","apiKey","__publicField","validateServerUrl","headers","url","init","timeoutMs","merged","value","key","requestInit","err","getLogger","options","initialize","response","text","json","kind","version","activeChildren","children","calls","totalPurged","i","purged","date","seconds","port","path","callback","intervalMs","active","currentTimeoutId","check","_stats","stopMonitoring","stopMonitor","timeoutId","ComputeError","ErrorCodes","ComputeServerStats","warnedFunctions","isRealBrowser","nav","userAgent","warnIfClientSide","functionName","suppress","getLogger","DEFINITION_LOAD_FAILED","GRASSHOPPER_SERVER_ERROR_CODES","ErrorCodes","withGrasshopperErrorCodes","config","isDefinitionLoadMiss","error","ComputeError","warnOnEmptyInnerTrees","response","debug","values","readField","empty","param","innerTree","scope","getLogger","solveGrasshopperDefinition","dataTree","definition","warnIfClientSide","bytes","materializeDefinition","runSolve","prepareGrasshopperArgs","solveGrasshopperDefinitionWithCacheKey","solveByCacheKey","cacheKey","pointerArgs","isDefinitionRef","args","applyOptionalComputeSettings","result","fetchCompute","pointer","_algo","rest","wireSize","getResponseWireSize","algoLength","setResponseWireSize","base64ByteArray","detectBase64Payload","encodeStringToBase64","arglist","options","stableStringify","value","path","stringify","v","fnv1aBytes","parts","i","out","k","val","fnv1aCore","length","codeAt","hash","fnv1a","input","bytes","hashSolveInput","definition","dataTree","hashSolveInputForDefinition","hashDefinition","definitionHash","tree","isDefinitionRef","SERVER_CACHE_KEYS_MAX","isReusableDefinition","definition","estimateResponseSize","response","SolveScheduler","executor","baseConfig","options","cacheKeyExecutor","__publicField","cacheOpt","cacheConfig","value","next","raised","listener","err","getLogger","dataTree","ComputeError","ErrorCodes","definitionHash","hashDefinition","key","hashSolveInputForDefinition","seq","ctx","cached","result","resolve","reject","item","state","inflight","queued","waitMs","controller","externalAbortHandler","startTime","config","definitionReuploaded","durationMs","error","defKey","knownKey","oldest","entry","sizeBytes","getResponseWireSize","hook","args","describeDefinition","definition","isDefinitionRef","summarizeDataTree","dataTree","tree","items","bytes","innerTree","readField","branch","item","data","GrasshopperClient","_GrasshopperClient","config","__publicField","ComputeServerStats","client","attempts","baseDelayMs","maxDelayMs","attempt","delay","resolve","ComputeError","ErrorCodes","fetchParsedDefinitionIO","fetchDefinitionIO","options","effectiveConfig","result","solveGrasshopperDefinition","error","getLogger","executor","cacheKeyExecutor","cacheKey","solveGrasshopperDefinitionWithCacheKey","r","solveByCacheKey","SolveScheduler","validateServerUrl","decoderRegistry","registerDecoder","typeName","decoder","rhino","data","d","findDecoder","rhinoType","key","dec","isDecodableEnvelope","parsedData","makeDecodeError","rhinoType","raw","decodeRhinoGeometry","parsedData","rhino","decoder","findDecoder","decoderThrew","decoded","error","getLogger","isDecodableEnvelope","disposeRhinoObjects","value","seen","walk","v","del","isDeleted","item","proto","SYSTEM_TYPES","RHINO_GEOMETRY_PREFIX","EXCLUDED_TYPES","FILE_DATA_TYPE","isExcludedType","type","t","tryDecodeJSON","value","trimmed","first","decodeBySystemType","raw","rhino","decodeRhinoGeometry","decodedJsonCache","decodeItemJSON","item","parsed","extractItemValue","parseValues","isFileData","v","getResponseParams","response","values","readField","itemType","forEachTreeItem","tree","handler","list","getValues","byId","options","stringOnly","result","aggregated","param","paramName","key","disposeRhinoObjects","extractFileData","output","getValue","parseOptions","matched","collected","GrasshopperResponseProcessor","response","debug","__publicField","byId","options","getValues","selector","getValue","extractFileData","folderName","additionalFiles","files","downloadFileData","TREE_PATH_RE","isDataTreeDefault","value","entries","key","val","applyRounding","value","decimalPlaces","tolerance","rounded","getInputStepSize","roundingTolerance","abs","decimalPart","decimals","step","s","expMatch","exp","absExp","MAX_DECIMALS","trimmed","serverStepSize","schema","coerceDefault","value","transform","setUndefinedOnEmpty","processed","v","transformed","numericTransformer","trimmed","parsed","booleanTransformer","lower","textTransformer","colorTransformer","cleaned","objectTransformer","inputName","getLogger","err","computeNumeric","schema","roundingTolerance","isIntegerType","serverStep","serverStepSize","isDataTreeDefault","numericTransformer","ComputeError","ErrorCodes","value","coerceDefault","val","firstValue","stepSource","stepSize","getInputStepSize","decimalPlaces","stepStr","expMatch","inferred","applyRounding","numericParser","base","def","isList","safeValue","booleanParser","booleanTransformer","textParser","textTransformer","valueListParser","defaultValue","defaultLower","match","key","getLogger","geometryParser","objectTransformer","fileParser","colorParser","colorTransformer","ALL_PARSERS","INPUT_TYPE_PARSERS","parser","type","UNKNOWN_TYPE_FALLBACK","itemData","item","readField","itemType","NUMERIC_ITEM_TYPES","INTEGER_ITEM_TYPES","normalizeDefaultWithWarning","input","hasField","message","getLogger","innerTree","tree","invalidItems","branch","items","parsed","data","type","num","numericTransformer","bool","booleanTransformer","warning","allValues","CANONICAL_PARAM_TYPES","INPUT_TYPE_PARSERS","key","canonicalizeParamType","paramType","processInput","rawInput","processInputWithError","baseInput","schema","defaultWarningError","normalized","warning","normalizeDefaultWithWarning","error","parser","ComputeError","getLogger","parserError","UNKNOWN_TYPE_FALLBACK","processInputsWithErrors","rawInputs","inputs","parseErrors","raw","input","errors","processInputWithError","normalizeInputSchema","raw","stepSize","readField","normalizeOutputSchema","fetchDefinitionIO","definition","config","args","prepareGrasshopperArgs","payload","ComputeError","ErrorCodes","response","fetchCompute","withGrasshopperErrorCodes","loadWarnings","nonEmptyStrings","readField","loadErrors","rawInputs","rawOutputs","normalizeInputSchema","normalizeOutputSchema","value","cleaned","coerceDiagnostic","v","text","message","fetchParsedDefinitionIO","warnIfClientSide","outputs","inputs","parseErrors","processInputsWithErrors","readSchemaResults","raw","entry","readField","TreeBuilder","_TreeBuilder","paramName","__publicField","path","items","pathKey","dataItems","item","treeData","pathStr","values","result","inputs","input","tree","value","isDataTreeDefault","processed","trees","newValue","isBuilderArray","builder","builders","idx","t","dataTrees","compiled","firstKey","match","TREE_PATH_RE","min","max","inputName","clamped","data","num","val","v","getLogger"]}
|