import { M as ComputeError, O as ComputeConfig, _ as isDefinitionRef, a as extractFilesFromComputeResponse, c as ProcessedFile, g as SolveDefinition, h as DefinitionRef, k as RetryPolicy, o as FileBaseInfo, r as downloadFileData, s as FileData } from "./index-zlzEAi12.js"; //#region src/grasshopper/server/compute-server-stats.d.ts /** * ComputeServerStats provides methods to query Rhino Compute server statistics. * * @public Use this for server health monitoring and statistics. * * @example * ```typescript * const stats = new ComputeServerStats('http://localhost:6500', 'your-api-key'); * * try { * const isOnline = await stats.isServerOnline(); * const children = await stats.getActiveChildren(); * const version = await stats.getVersion(); * * // Or get everything at once * const allStats = await stats.getServerStats(); * } finally { * await stats.dispose(); // Clean up resources * } * ``` */ declare class ComputeServerStats { private readonly serverUrl; private readonly apiKey?; private disposed; private activeMonitors; private activeTimeouts; /** Timeout (ms) for the fast read/monitoring endpoints. */ private static readonly DEFAULT_TIMEOUT_MS; /** Timeout (ms) for child-lifecycle POSTs — a cold Windows child can take ~30s to spawn. */ private static readonly LIFECYCLE_TIMEOUT_MS; /** Floor for `monitor()`'s `intervalMs` — anything lower hot-loops the server. */ private static readonly MIN_MONITOR_INTERVAL_MS; /** * @param serverUrl - Base URL of the Rhino Compute server with http:// or https:// scheme (e.g., 'http://localhost:6500') * @param apiKey - Optional API key for authentication */ constructor(serverUrl: string, apiKey?: string); /** * Build request headers with optional API key. */ private buildHeaders; /** * `fetch` wrapper that aborts after `timeoutMs` so a hung connection can't stall * a probe (or the `monitor()` loop) forever. Pass `0` to disable the timeout. */ private fetchWithTimeout; /** * Check if the server is online. * * This is a single-sample probe: it returns `true` only on a 2xx from the * proxy liveness root `/`, and `false` for every other outcome (non-2xx, * network error, or timeout). A cold or briefly-busy-but-up server can therefore * read as offline — callers that gate on this (e.g. client construction) * should retry rather than treat a single `false` as authoritative. * * @param timeoutMs - Abort the probe after this many ms (default: 5000). * Pass `0` to disable the timeout. Prevents a hung connection from * stalling the caller indefinitely. */ isServerOnline(timeoutMs?: number): Promise; /** * Detailed liveness probe backing {@link isServerOnline}. * * Reports what the probe actually saw so callers can distinguish "connection * failed" (`error` set) from "server answered non-2xx" (`status` set) — e.g. * a 401 from a proxy that requires an API key is a misconfiguration, not an * offline server. Same single-sample caveat as {@link isServerOnline}. * * @param timeoutMs - Abort the probe after this many ms (default: 5000). * Pass `0` to disable the timeout. * @returns `online` plus either the HTTP `status` the server answered with, * or the `error` the connection attempt failed with. */ probeServer(timeoutMs?: number): Promise<{ online: boolean; status?: number; error?: string; }>; /** * Get the number of active child processes on the server. * * By default the proxy's `/activechildren` endpoint will *spawn* children up * to the configured count if none are running, then report the count — which * wakes (and bills) an idle server. Pass `{ initialize: false }` for a passive * read that reports the current count without spawning; use this for * monitoring or before a purge/probe where you must not wake the server. * * @param options.initialize - When `false`, append `?initialize=false` so the * server reports without spawning. Defaults to `true` (the server's default). * @returns Number of active children, or null if unavailable */ getActiveChildren(options?: { initialize?: boolean; }): Promise; /** * Get the server version information. * * @returns Version object with rhino, compute, and git_sha, or null if unavailable */ getVersion(): Promise<{ rhino: string; compute: string; git_sha: string | null; } | null>; /** * Get the plugins installed on the server. * * Returns a `name → version` map of non-core plugins the server has loaded, * or `null` if the request failed. Pass `kind` to choose which inventory: * `'gh'` (default) lists Grasshopper add-on assemblies via * `/plugins/gh/installed`; `'rhino'` lists Rhino plugins via * `/plugins/rhino/installed`. Plugins that ship with Rhino / are core * libraries are excluded by the server. * * @param kind - `'gh'` for Grasshopper add-ons (default) or `'rhino'` for Rhino plugins. * @returns Map of plugin name to version, or `null` on failure. * * @example * ```ts * const gh = await stats.getInstalledPlugins(); // Grasshopper add-ons * const selvaVersion = gh?.['Selva'] ?? null; * ``` */ getInstalledPlugins(kind?: 'gh' | 'rhino'): Promise | null>; /** * Get comprehensive server statistics. * Fetches all available server information in parallel. * * @returns Object containing server status and available stats */ getServerStats(): Promise<{ isOnline: boolean; version?: { rhino: string; compute: string; git_sha: string | null; }; activeChildren?: number; }>; /** * Purge the server's solve-results / URL-data cache. * * POSTs to `cache/purge` and returns the number of entries removed, or `null` * if the request failed. This clears cached solve responses and fetched * definition-URL data; it does NOT evict the definition cache (active * `pointer` references stay valid). * * **Caveat:** `cache/purge` is forwarded by the rhino.compute proxy to a * single round-robin-selected child, so in a multi-child deployment one call * purges one child's cache. Call repeatedly (or size the pool to 1) if you * need a fleet-wide purge. * * @returns Number of entries removed, or `null` on failure. * * @example * ```ts * const removed = await stats.purgeCache(); * if (removed !== null) console.log(`Purged ${removed} cached solves`); * ``` */ purgeCache(): Promise; /** * Best-effort fleet-wide cache purge across a multi-child deployment. * * A single {@link purgeCache} POST is forwarded by the rhino.compute proxy to * just ONE round-robin-selected child, so the other children keep serving * stale cached solves. There is no proxy endpoint that addresses children * individually, so this method reads the active child count (passively, never * spawning) and fires `2 × count` sequential purges, relying on the proxy's * round-robin to spread the hits across the pool. * * **This is best-effort, not a guarantee.** Round-robin can revisit one child * and skip another; under concurrent traffic the rotation drifts. The result's * `confident` flag is `true` only when the server reports a single child (where * one purge is exact) — surface it so callers don't over-promise. For a hard * fleet-wide guarantee, run the deployment at `--childcount 1` or add a * server-side fan-out endpoint. * * @returns `{ totalPurged, calls, children, confident }`, or `null` if the * child count couldn't be read (server unreachable). `totalPurged` sums the * per-call counts; `calls` is how many purges were issued; `children` is the * reported pool size; `confident` is `true` only at a single-child pool. * * @example * ```ts * const r = await stats.purgeAllChildren(); * if (r && !r.confident) { * console.warn(`Purged ~${r.totalPurged} across ${r.children} children (best-effort)`); * } * ``` */ purgeAllChildren(): Promise<{ totalPurged: number; calls: number; children: number; confident: boolean; } | null>; /** * Get the server's current UTC clock. * * GETs `/servertime`, which the server emits as a JSON-encoded ISO-8601 * timestamp (e.g. `"2026-06-18T08:30:00Z"`). Useful for detecting clock skew * between caller and server. Returns `null` if the request failed or the body * isn't a parseable date. * * @returns A `Date` for the server's UTC time, or `null` on failure. */ getServerTime(): Promise; /** * Get how long the rhino.compute proxy has been idle. * * GETs `/idlespan` on the proxy, which returns the seconds elapsed since the * last request was forwarded to a compute child. This is a proxy-level metric * (not proxied to a child) used by autoscalers to decide when a node can be * drained. Returns `null` if unavailable. * * @returns Idle time in seconds, or `null` on failure. */ getIdleSpan(): Promise; /** * Fill the compute child pool up to the server's configured baseline. * * POSTs `/launch-children`. No-op when the pool is already at or above the * configured `--childcount`. Returns `{ spawned, active }` — how many children * were started and the resulting child count — or `null` on failure. * * To raise capacity above the baseline use {@link launchChild}; the baseline * itself can only be changed by restarting rhino.compute. * * @returns `{ spawned, active }`, or `null` on failure. */ launchChildren(): Promise<{ spawned: number[]; active: number; } | null>; /** * Add a single compute child to the pool, optionally on a specific port. * * POSTs `/launch-child` (with `?port=N` when `port` is given). Unlike * {@link launchChildren}, this can push the pool above the baseline, up to the * server's `MaxChildren` cap. Returns `{ spawned: [port] }` on success, or * `null` on failure (server replies 400 for a bad port, 409 if the port is in * use, 503 at the max-children cap). * * @param port - Optional specific port to launch on; otherwise the next free one. * @returns `{ spawned }` listing the launched port, or `null` on failure. */ launchChild(port?: number): Promise<{ spawned: number[]; } | null>; /** * Gracefully shut down compute children without respawning them. * * POSTs `/shutdown-children`. With no `port` it shuts down every child; with * `port` it targets just that one. Children do not respawn, but the next * `/grasshopper` request auto-spawns the pool back to the baseline. Returns * `{ shutdown, active }` — how many were stopped and the remaining count — or * `null` on failure. * * @param port - Optional port to target; omit to shut down all children. * @returns `{ shutdown, active }`, or `null` on failure. */ shutdownChildren(port?: number): Promise<{ shutdown: number; active: number; } | null>; /** * Shut down compute children and respawn replacements (rolling restart). * * POSTs `/recycle-children`. With no `port` it recycles every child; with * `port` it recycles just that one. The server recycles sequentially (each * replacement is serving before the next child is stopped) so the pool never * drops to zero mid-recycle. Returns `{ shutdown, spawned, active }`, or * `null` on failure. * * @param port - Optional port to target; omit to recycle all children. * @returns `{ shutdown, spawned, active }`, or `null` on failure. */ recycleChildren(port?: number): Promise<{ shutdown: number; spawned: number[]; active: number; } | null>; /** * POST a control endpoint that replies with a JSON object and return it, * degrading to `null` on any non-2xx, non-JSON, or network failure. Shared by * the child-lifecycle methods so their failure semantics stay identical. Uses * the longer lifecycle timeout since a spawn/recycle can take ~30s. */ private postJson; /** * Continuously monitor server stats at specified interval. * * @param callback - Function called with stats on each interval * @param intervalMs - Milliseconds between checks (default: 5000). Must be a * finite number of at least 100 ms — lower values would hot-loop the server. * @returns Function to stop monitoring * @throws {ComputeError} `INVALID_CONFIG` if `intervalMs` is not a finite number >= 100. * * @example * ```typescript * const stopMonitoring = stats.monitor((data) => { * console.log('Server stats:', data); * }, 3000); * * // Later... * stopMonitoring(); * ``` */ monitor(callback: (stats: Awaited>) => void, intervalMs?: number): () => void; /** * Disposes of all resources and stops all active monitors. * Call this when you're done using the stats instance. */ dispose(): Promise; /** * Ensures the instance hasn't been disposed. */ private ensureNotDisposed; } //#endregion //#region src/grasshopper/types/inputs.d.ts type DataTreePath = `{${string}}`; interface DataItem { type: string; data: string; id: string; } type DataTreeDefault = { [K in DataTreePath]?: T[]; }; type InnerTreeData = { [path in DataTreePath]: DataItem[]; }; /** * Tree with parameter metadata (used in compute requests and responses). * * Field casing varies by server branch: stock mcneel compute (8.x/9.x) * serializes `Resthopper.IO.DataTree` in PascalCase (`ParamName`/`InnerTree` * — the C# class carries no `[JsonProperty]` attributes), while * camelCase-serializing forks return `paramName`/`innerTree` instead. This * client always *sends* PascalCase (see `DataTree.toCompute()` in * `data-tree.ts`), so the PascalCase fields remain the canonical/required * shape; the optional camelCase fields exist so responses from camelCase * forks are representable without casts. Code that must read trees across * both server families should use `readField`/`hasField` * (`@/core/utils/read-field`, case-insensitive) rather than direct property * access — `warnOnEmptyInnerTrees` in `solve.ts` is the reference example. */ interface DataTree { /** PascalCase — stock mcneel servers, and the request shape this client sends. */ InnerTree: InnerTreeData; /** PascalCase — stock mcneel servers, and the request shape this client sends. */ ParamName: string; /** camelCase — sent instead of `InnerTree` by camelCase server forks. */ innerTree?: InnerTreeData; /** camelCase — sent instead of `ParamName` by camelCase server forks. */ paramName?: string; } type OutputType = 'System.String' | 'System.Double' | 'System.Int32' | 'System.Boolean' | 'Rhino.Geometry.Point3d' | 'Rhino.Geometry.Line' | 'Rhino.Geometry.Circle' | 'Rhino.Geometry.Arc' | 'Rhino.Geometry.NurbsCurve' | 'Rhino.Geometry.Brep' | 'Rhino.Geometry.Mesh' | 'Rhino.Geometry.Vector3d' | 'Rhino.Geometry.Plane' | 'Rhino.Geometry.Box' | (string & {}); /** * Union type for all possible default value types */ type DefaultValue = T | T[] | DataTreeDefault | undefined | null; /** * Base properties common to all processed input types. * Note: `groupName` and `id` require the custom Rhino Compute branch. */ interface BaseInputType { description: string; name: string; nickname: string | null; treeAccess: boolean; /** * Name of the group this parameter belongs to. * @requires Custom branch of compute.rhino3d */ groupName?: string; /** * Unique identifier for the parameter. * @requires Custom branch of compute.rhino3d */ id?: string; } /** * Numeric input type (Number or Integer) */ interface NumericInputType extends BaseInputType { paramType: 'Number' | 'Integer'; minimum?: number | null; maximum?: number | null; atLeast?: number | null; atMost?: number | null; stepSize?: number | null; default: DefaultValue; } /** * Text input type */ interface TextInputType extends BaseInputType { paramType: 'Text'; default: DefaultValue; } /** * Boolean input type */ interface BooleanInputType extends BaseInputType { paramType: 'Boolean'; default: DefaultValue; } /** * Geometry input type (generic geometry) */ interface GeometryInputType extends BaseInputType { paramType: 'Geometry'; default: DefaultValue; } /** * ValueList input type (dropdown/select) */ interface ValueListInputType extends BaseInputType { paramType: 'ValueList'; values: Record; default?: string; } /** * File input type */ interface FileInputType extends BaseInputType { paramType: 'File'; acceptedFormats?: string[]; default: DefaultValue; } /** * Color input type (stored as hex string) */ interface ColorInputType extends BaseInputType { paramType: 'Color'; default: DefaultValue; } /** * Discriminated union of all input parameter types */ type InputParam = NumericInputType | BooleanInputType | TextInputType | ValueListInputType | GeometryInputType | FileInputType | ColorInputType; //#endregion //#region src/grasshopper/types/schema.d.ts type RhinoModelUnit = 'None' | 'Microns' | 'Millimeters' | 'Centimeters' | 'Decimeters' | 'Meters' | 'Kilometers' | 'Mils' | 'Inches' | 'Feet' | 'Yards' | 'Miles' | 'CustomUnits' | 'Unset'; /** * Base Grasshopper schema properties shared by config, args, and response */ interface GrasshopperBaseSchema { /** Absolute tolerance used in computation */ absolutetolerance?: number | null; /** Angular tolerance used in computation */ angletolerance?: number | null; /** Model units used */ modelunits?: RhinoModelUnit | null; /** Data version (7 or 8) */ dataversion?: 7 | 8 | null; /** Whether to use cached solution */ cachesolve?: boolean | null; /** * Opt-in: cache a solve even when the definition reported GH errors. Default * (false/unset) means errored solves are never cached server-side. Some * definitions throw GH errors by design (a guarded Python component, a * filtered branch) while still producing correct geometry — set this so those * still benefit from the server's solve cache. Only meaningful with * `cachesolve`. Requires a server that honors it (VektorNode fork). */ cacheerroredsolves?: boolean | null; } /** * Definition source (used in args and response) */ interface GrasshopperDefinitionSource { /** Base64 encoded algorithm (if embedded) */ algo?: string | null; /** URL pointer to definition file */ pointer?: string | null; /** Filename of the definition */ filename?: string | null; } /** * Configuration for Grasshopper compute operations * Combines server config with optional Grasshopper-specific computation settings * * Note: The definition source (pointer/algo) is NOT part of config. * Instead, pass the definition directly to methods like solve(), getIO(), etc. */ interface GrasshopperComputeConfig extends ComputeConfig { /** Absolute tolerance used in computation */ absolutetolerance?: number | null; /** Angular tolerance used in computation */ angletolerance?: number | null; /** Model units used */ modelunits?: RhinoModelUnit | null; /** Data version (7 or 8) */ dataversion?: 7 | 8 | null; /** Whether to use cached solution */ cachesolve?: boolean | null; /** * Opt-in: cache a solve even when the definition reported GH errors. See * {@link GrasshopperBaseSchema.cacheerroredsolves}. Only meaningful with * `cachesolve`. */ cacheerroredsolves?: boolean | null; } /** * Arguments sent to Grasshopper compute endpoint * Includes config options + definition source + input values */ interface GrasshopperRequestSchema extends GrasshopperBaseSchema, GrasshopperDefinitionSource { /** Input values organized by parameter */ values?: DataTree[]; } /** * Response from Grasshopper compute server * Includes all schema fields + computed results * * `pointer` is deliberately excluded (`Omit`): the solve layer (`runSolve` in * `solve.ts`) splits the server's echoed `pointer` off as the solve's * `cacheKey` and strips it from the returned response, so client-returned * responses never carry it. * * The schema-echo fields (`cachesolve`/`modelunits`/`dataversion`) stay * optional as inherited from {@link GrasshopperBaseSchema}: the server echoes * them back when set, but nothing client-side enforces their presence — don't * rely on them without a fallback. */ interface GrasshopperComputeResponse extends GrasshopperBaseSchema, Omit { /** * Model units the definition was solved in. Every conforming server response * carries this (it drives downstream scaling, e.g. the webdisplay parser), * but note it is not validated client-side — a non-conforming server or * hand-built mock may omit it at runtime. */ modelunits: RhinoModelUnit; /** * The server echoes the request's full base64 definition back as `algo`, but the client strips * it before returning — retaining it would pin a multi-MB copy of the definition per response, * multiplied by every cache that holds responses. Always `undefined` on client-returned * responses (inherited optional field from {@link GrasshopperDefinitionSource}). */ algo?: string | null; /** Filename of the definition (always present in response) */ filename: string | null; /** Recursion level used */ recursionlevel?: number; /** Output values organized by parameter */ values: DataTree[]; /** Computation errors */ errors?: string[]; /** Computation warnings */ warnings?: string[]; } /** * Output parameter */ interface OutputParamSchema { name: string; nickname: string | null; paramType: string; /** * Grasshopper parameter instance GUID */ id: string; } /** * Input parameter */ interface InputParamSchema { /** * Grasshopper parameter instance GUID */ id: string; name: string; nickname: string | null; description: string; paramType: string; treeAccess: boolean; minimum: number | null; maximum: number | null; atLeast: number; atMost: number; stepSize?: number; default: any; /** * Key-value pairs for dropdown options */ values?: Record; /** * Accepted file formats for File input */ acceptedFormats?: string[]; groupName?: string | null; } //#endregion //#region src/grasshopper/types/outputs.d.ts /** * Parsed input/output structure with raw schemas. * * `loadWarnings` / `loadErrors` carry the server's definition-load diagnostics * (missing plugin, broken component, etc.) from the `/io` response. They are * distinct from per-input parse failures (`InputParseError`): these come from * the server loading the definition, those from the client typing an input. */ interface GrasshopperParsedIORaw { inputs: InputParamSchema[]; outputs: OutputParamSchema[]; /** Server-side definition-load warnings, if any. */ loadWarnings?: string[]; /** Server-side definition-load errors, if any (e.g. missing plugin). */ loadErrors?: string[]; } /** * Per-input parse failure. The corresponding entry in `inputs` was filled * with a safe default so the rest of the pipeline can keep going — but the * caller should surface this so the user knows their definition has a * misconfigured parameter. */ interface InputParseError { /** The input's `name` (or `'unknown'` if the schema didn't have one). */ inputName: string; /** The declared paramType from the raw schema. */ paramType: string; /** Human-readable reason from the underlying ComputeError. */ message: string; /** Error code from the underlying ComputeError, if available. */ code?: string; } /** * Parsed input/output structure with processed types. * * `parseErrors` is populated when one or more inputs failed validation and * fell back to a safe default. The result is still usable, but the UI should * surface these so the user can fix their definition. */ interface GrasshopperParsedIO { inputs: InputParam[]; outputs: OutputParamSchema[]; parseErrors?: InputParseError[]; /** * Server-side definition-load warnings from the `/io` response (e.g. an * obsolete component). Surface these so the user understands a degraded IO * list. Distinct from `parseErrors` (client-side input typing failures). */ loadWarnings?: string[]; /** * Server-side definition-load errors from the `/io` response (e.g. a missing * plugin that left inputs unresolved). When present, the inputs/outputs may * be incomplete — the user needs to fix their server/definition. */ loadErrors?: string[]; } //#endregion //#region src/grasshopper/scheduler/types.d.ts /** * Scheduling mode — controls how concurrent `solve()` calls interact. * * - `latest-wins`: One in flight at a time. New calls supersede any pending * call (in-flight one is aborted). Optimal for slider scrubs / live UIs. * - `queue`: FIFO queue. Each solve runs to completion. Concurrency capped * by `maxConcurrent`. Use for "submit job" flows where every request matters. * - `parallel`: No scheduling — calls run concurrently up to `maxConcurrent`. * Closest to plain `client.solve()` but with shared cancel/state. */ type SchedulerMode = 'latest-wins' | 'queue' | 'parallel'; interface CacheOptions { /** * Total byte budget for retained responses, evicted LRU. The only size bound * there is: responses range KB→100s of MB, so memory is the constraint that * matters and an entry count would only obscure it. * * Sizing uses the response's wire size (JSON text length, recorded at the * fetch boundary — no re-serialization); a response without that hint * (custom executor) is sized by a one-off `JSON.stringify`. A single * response larger than the whole budget is served but never retained. * * Required and must be > 0 — to disable caching, pass `cache: false`. */ maxBytes: number; /** * Time-to-live in ms. Set to `0` for no expiry (default, and the right * choice for a solve keyed by immutable definition+inputs — expiry there * only buys a paid re-solve of an identical answer). Meaningful only when a * definition reaches outside its inputs (external data source, clock), where * a stale result is genuinely wrong rather than merely old. * * Expiry is evaluated lazily on read: an expired entry keeps its bytes * counted against `maxBytes` until that exact key is next consulted. */ ttlMs?: number; /** * Cache responses that carry Grasshopper `errors`. Default `true`: an * errored solve is a valid, deterministic result — definitions raise GH * errors by design (guarded components, validation branches), so replaying * one from cache is correct. Set `false` for parity with Rhino's opt-in * `cacheerroredsolves` server flag, e.g. when a definition's errors are * transient (external data sources) rather than functions of the inputs. */ cacheErroredSolves?: boolean; } interface SolveSchedulerOptions { mode?: SchedulerMode; maxConcurrent?: number; /** * Backpressure — cap on how many calls may wait in the FIFO queue (i.e. * excluding the ones already in flight). When the queue is full, a new * `solve()` is rejected immediately with `code: QUEUE_FULL` (retryable, meant to * map to HTTP 503 + Retry-After) instead of piling up unbounded. Bounds the * miss path under load. Only applies to `queue` / `parallel` modes — * `latest-wins` has an intrinsic depth of 1. Default: unbounded. */ maxQueueDepth?: number; /** * Backpressure — max time (ms) a call may sit queued before it starts * executing. If it's still waiting after this long it's rejected with * `code: QUEUE_TIMEOUT` rather than burning compute on a stale request. * Bounds tail latency. Only applies to `queue` / `parallel` modes. Default: * no deadline. */ queueWaitMs?: number; timeoutMs?: number; retry?: RetryPolicy; /** * Response caching keyed by hash of (definition, dataTree). Omit or pass * `false` to disable; otherwise a byte budget is required, so an unbounded * cache can't be enabled by accident. */ cache?: false | CacheOptions; /** * Reuse the server's definition cache key so a large (base64/binary) * definition is uploaded once and subsequent solves reference it by * `pointer` instead of re-sending the full payload. Hugely cheaper for * multi-MB definitions on a live UI (slider scrubs, etc.). * * Requires a `cacheKeyExecutor` to be supplied (the client wires one). Has no * effect for URL-pointer definitions (already a reference). On a server-side * cache miss the executor transparently falls back to a full upload, so this * is safe to leave on. Default: `true` when a `cacheKeyExecutor` is present. */ reuseServerDefinitionCache?: boolean; /** Lifecycle hooks — fired in order. Errors thrown by hooks are logged, not rethrown. */ onStart?: (ctx: SolveContext) => void; onSettle?: (ctx: SolveContext, result: SolveResult) => void; onSuperseded?: (ctx: SolveContext) => void; } interface SolveContext { /** Stable hash of (definition, dataTree). */ key: string; /** Timestamp when scheduler.solve() was called. */ enqueuedAt: number; /** Timestamp when execution actually started (after queueing). */ startedAt: number | null; } type SolveResult = { status: 'success'; response: GrasshopperComputeResponse; durationMs: number; fromCache: boolean; /** * Definition-cache telemetry for a real compute call (not a Selva-cache * `fromCache` hit). `false` → the server reused its cached definition via * the pointer (no upload); `true` → the pointer was cold/stale so the full * definition was re-uploaded. `undefined` when the server-definition-cache * fast path didn't run (reuse disabled, or a non-reusable definition such * as a remote URL). */ definitionReuploaded?: boolean; } | { status: 'error'; error: ComputeError; durationMs: number; } | { status: 'superseded'; }; type SolveExecutor = (definition: SolveDefinition, dataTree: DataTree[], config: GrasshopperComputeConfig) => Promise; /** * Cache-key-aware executor. When `cacheKey` is provided, the executor solves by * reference (`pointer: cacheKey`) and falls back to a full upload on a server * cache miss. Always reports the (possibly refreshed) `cacheKey` so the * scheduler can update its definition→key map, plus whether the fast path * `missed` (for telemetry). When `cacheKey` is null it's a first solve — upload * fully and capture the key the server assigns. * * Supplied by the client (which owns the solve primitives); the scheduler stays * decoupled from the transport. */ type CacheKeyExecutor = (definition: SolveDefinition, dataTree: DataTree[], cacheKey: string | null, config: GrasshopperComputeConfig) => Promise<{ response: GrasshopperComputeResponse; cacheKey: string | null; missed: boolean; }>; //#endregion //#region src/grasshopper/scheduler/solve-scheduler.d.ts /** * Robust scheduler for Grasshopper solves. * * Sits between your application code and the underlying compute call, * adding: * - Configurable scheduling (latest-wins for sliders, queue for jobs) * - Backpressure (bounded queue depth + queue-wait deadline) for the miss path * - In-flight cancellation (per-call signal + cancelAll) * - Optional response caching for repeated inputs * - Lifecycle hooks for UI indicators (start / settle / superseded) * - State observability via subscribe() * * Multiple schedulers can share a single GrasshopperClient — typically one * per UI surface (e.g. one for slider scrubs, one for long-running submits). * * @example * ```ts * const scheduler = client.createScheduler({ mode: 'latest-wins', timeoutMs: 30_000 }); * * // From a slider handler: * scheduler.solve(definition, tree).then((result) => { * updateMeshes(result); * }).catch((err) => { * if (err.code !== 'SUPERSEDED') showError(err); * }); * * // From a UI binding: * scheduler.subscribe(() => { * showSpinner = scheduler.isSolving; * }); * ``` */ export declare class SolveScheduler { private readonly executor; private readonly baseConfig; private readonly mode; /** * Mutable via {@link setMaxConcurrent}: the compute server's worker pool can grow * or shrink while this scheduler is alive, and the dispatch loops re-read this on * every pass rather than capturing it. */ private maxConcurrent; private readonly maxQueueDepth; private readonly queueWaitMs; private readonly timeoutMs; private readonly retry; private readonly cacheEnabled; private readonly cacheMaxBytes; private readonly cacheTtl; private readonly cacheErroredSolves; private readonly cache; /** Sum of `sizeBytes` across retained cache entries. */ private cacheBytes; /** * Cumulative hit/miss/eviction counters for {@link cacheStats}. Deliberately * NOT reset by `clearCache()` — they measure this scheduler's whole lifetime, * so a hit rate stays comparable across a session rather than restarting at * every clear. */ private cacheHits; private cacheMisses; private cacheEvictions; /** Optional cache-key-aware executor and whether server-def-cache reuse is on. */ private readonly cacheKeyExecutor?; private readonly reuseServerDefinitionCache; /** definition identity → server cache key (`pointer`) learned from past solves. */ private readonly serverCacheKeys; private readonly onStart?; private readonly onSettle?; private readonly onSuperseded?; private readonly subscribers; private readonly inFlight; private pendingForLatestWins; private readonly fifoQueue; private _lastResult; private _lastError; private _lastDurationMs; /** Ordinal handed to each solve() call. */ private solveSeq; /** seq of the solve that last wrote _lastResult/_lastError — see writeLastState. */ private lastStateSeq; private disposed; constructor(executor: SolveExecutor, baseConfig: GrasshopperComputeConfig, options?: SolveSchedulerOptions, cacheKeyExecutor?: CacheKeyExecutor); get isSolving(): boolean; get hasPending(): boolean; get inFlightCount(): number; get queueDepth(): number; get lastResult(): GrasshopperComputeResponse | null; get lastError(): ComputeError | null; get lastDurationMs(): number | null; /** * Adjust how many solves may run at once, for when the compute server's worker * pool changes size after this scheduler was built. * * Raising it drains queued work immediately. Lowering it never interrupts work * already in flight — those finish above the new limit, and the cap applies from * the next dispatch. Values below 1 are clamped, since 0 would wedge the queue. */ setMaxConcurrent(value: number): void; /** Current concurrency cap — reflects any {@link setMaxConcurrent} adjustment. */ getMaxConcurrent(): number; /** Subscribe to state changes. */ subscribe(listener: () => void): () => void; private notify; /** * Schedule a solve. Returns a promise that: * - Resolves with the compute response on success. * - Rejects with `ComputeError` on failure. * - Rejects with `code: ErrorCodes.SUPERSEDED` when the call was canceled because * newer values arrived (latest-wins mode). * - Rejects with `code: ErrorCodes.ABORTED` when the call was canceled via * caller-supplied signal or `cancelAll()`. * - Rejects with `code: ErrorCodes.QUEUE_FULL` when `maxQueueDepth` is set and * the queue was already full (backpressure; `statusCode: 503`). * - Rejects with `code: ErrorCodes.QUEUE_TIMEOUT` when `queueWaitMs` is set and * the call sat queued longer than that before starting (`statusCode: 503`). * * Caller-supplied `signal` cancels just this call (rejects with `ABORTED`) — * including while the call is still queued, before execution starts. * * A {@link DefinitionRef} definition is keyed by its `key` (result cache and * server-pointer map alike) without materializing bytes — `load()` runs only * when an upload is unavoidable. Its immutability contract is trusted here: * a reused key serves the other content's cached solve. * * Responses served from the cache (and via `lastResult`) are shared objects, * not copies — treat them as immutable. Mutating one poisons every later * cache hit for that key. * * Partial-success contract: unlike `GrasshopperClient.solve()`, which throws * `COMPUTATION_ERROR` when the response carries solver errors, this RESOLVES * with the response as-is — check `response.errors` yourself if a partial * success must not be treated as a result (see `cacheErroredSolves` for the * caching side of the same distinction). */ solve(definition: SolveDefinition, dataTree: DataTree[], options?: { signal?: AbortSignal; }): Promise; /** * Record last-result state, but only if no newer solve has written since — * a slow solve settling late must not overwrite the state a newer solve * (or cache hit) already published. */ private writeLastState; /** latest-wins: supersede the pending item and abort everything in flight. */ private supersedeCurrent; /** Settle a still-queued item as ABORTED and remove it from its queue. */ private abortQueuedItem; /** * Backpressure: settle an incoming item as QUEUE_FULL without ever enqueuing * it. The structured context lets an HTTP layer map it to 503 + Retry-After. */ private shedAsQueueFull; /** * Arm the queue-wait deadline for an item about to be queued. If the item is * still waiting when it fires, it's rejected as QUEUE_TIMEOUT and removed from * the queue. Cleared on execute/settle via {@link clearQueueWaitTimer}. */ private armQueueWaitTimer; /** Clear a queued item's wait-deadline timer, if one is pending. */ private clearQueueWaitTimer; private enqueue; private execute; /** * Run the solve, using the server-definition-cache fast path when it's * enabled and the definition is reusable. Learns/updates the definition's * server cache key from the result so later solves can reference it. * * `definitionHash` is the {@link hashDefinition} result already computed at * `solve()` entry — threaded through rather than recomputed, so each solve * pays exactly one linear pass over the definition (issue 57). */ private runExecutor; private drainNext; private supersede; private makeAbortError; /** * Settle a pending/in-flight item exactly once with an error. * * A solve promise can be settled from four concurrent sources — the executor * resolving, the executor rejecting, `supersede`, and `cancelAll` — and a JS * promise silently ignores a second settle. This guard is the single place the * settle-once invariant lives: it makes the *first* settle win and reports * whether this call was that winner, so callers fire their own hook only when * they actually settled. Any new settle path must go through here (or * {@link settleSuccess}) so the guard can't be forgotten. * * @returns `true` if this call settled the item; `false` if it was already settled. */ private settleError; /** * Settle a pending/in-flight item exactly once with a successful response. * The success counterpart to {@link settleError}; see it for the invariant. * * @returns `true` if this call settled the item; `false` if it was already settled. */ private settleSuccess; /** Detach the queued-phase abort listener, if one is still attached. */ private removeQueuedAbortHandler; private isAbortLikeError; private normalizeExecutionError; /** Cancel everything — in-flight and pending. */ cancelAll(): void; private rejectAsAborted; private readCache; private writeCache; /** Remove one cache entry and release its bytes from the running total. */ private dropCacheEntry; clearCache(): void; /** * Observability snapshot of the solve cache: current size plus lifetime * hit/miss/eviction counters. * * `hits`/`misses` count cache CONSULTATIONS, so `hits / (hits + misses)` is the * hit rate. A TTL-expired entry counts as a miss (the solve runs either way). * `evictions` counts only entries dropped under size/byte pressure — not * replace-in-place writes or TTL expiry, which are not capacity signals. * Counters are cumulative and survive `clearCache()`. */ cacheStats(): { entries: number; bytes: number; hits: number; misses: number; evictions: number; }; dispose(): void; private runHook; } //#endregion //#region src/grasshopper/client/grasshopper-client.d.ts /** * Per-call options that override the client's default ComputeConfig values. * * Use these for per-request control without mutating the client config: * - `signal` — cancel a specific solve (e.g. when a slider value is superseded) * - `timeoutMs` — extend timeout for a long-running solve, or pass `0` to disable * - `retry` — override retry policy for this call only */ interface SolveOptions { signal?: AbortSignal; timeoutMs?: number; retry?: RetryPolicy; } /** * GrasshopperClient provides a simple API for interacting with a Rhino Compute server and grasshopper. * * @public This is the recommended high-level API for Rhino Compute operations. * * **Security Warning:** * Using this client in a browser environment exposes your server URL and API key to users. * For production, use this library server-side or proxy requests through your own backend. * * @example * ```typescript * const client = await GrasshopperClient.create({ * serverUrl: 'http://localhost:6500', * apiKey: 'your-api-key' * }); * * try { * const result = await client.solve(definitionUrl, { x: 1, y: 2 }); * } finally { * await client.dispose(); // Clean up resources * } * ``` */ declare class GrasshopperClient { private readonly config; readonly serverStats: ComputeServerStats; private disposed; /** * Per-probe timeout for the `create()` liveness gate. A healthy `GET /` on the * proxy answers in milliseconds; this bound only ever applies to a server that * is not answering, where every extra second is multiplied by the retry count * and paid by whoever is waiting on the solve. */ private static readonly CREATE_PROBE_TIMEOUT_MS; private constructor(); /** * Creates and initializes a GrasshopperClient with server validation. * * The pre-flight liveness probe (a GET on the proxy root `/`) is a * single-sample boolean gate that reads a cold or briefly-busy-but-up server * as offline. To avoid failing construction on that transient class, the probe * is retried with a short exponential backoff before giving up. * * Retries stop early when {@link classifyProbeFailure} says waiting cannot * change the answer — connection refused, or a 401/403. This runs in front of * a user who clicked Solve, so the retry ladder must not spend its whole * budget confirming that a machine is switched off. * * Each probe is bounded by {@link CREATE_PROBE_TIMEOUT_MS} rather than the * stats default: a healthy `GET /` answers in milliseconds, so a longer * per-probe timeout only multiplies the wait when the server is unreachable. * * @throws {ComputeError} with code NETWORK_ERROR if the server stays * unreachable across all attempts. `context.probeVerdict` carries the * {@link ProbeVerdict} so callers can render a specific cause. * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid */ static create(config: GrasshopperComputeConfig): Promise; /** * Gets the client's configuration. * Useful for passing to lower-level functions. */ getConfig(): GrasshopperComputeConfig; /** * Get input/output parameters of a Grasshopper definition. */ getIO(definition: string | Uint8Array): Promise; getRawIO(definition: string | Uint8Array): Promise; /** * Run a compute job with a Grasshopper definition. * * @throws {ComputeError} with code INVALID_INPUT if definition is empty * @throws {ComputeError} with code NETWORK_ERROR if server is offline * @throws {ComputeError} with code COMPUTATION_ERROR if computation fails. * On a partial-success response (some outputs computed, some errored) the * error's `context.values` carries the outputs that did compute — pass * `{ values } as GrasshopperComputeResponse` to the response processors to * render them. `context.inputSummary` describes the inputs (param names, * item counts, byte sizes) without pinning the full data tree. */ solve(definition: SolveDefinition, dataTree: DataTree[], options?: SolveOptions): Promise; /** * Create a scheduler bound to this client. Use a scheduler for any UI surface * that fires solves frequently (sliders, live editors) or that needs cancel * semantics, response caching, or state observability. * * Multiple schedulers can be created from a single client — typically one per * UI surface so their queues stay independent. * * @example * ```ts * const sliderScheduler = client.createScheduler({ mode: 'latest-wins' }); * const submitScheduler = client.createScheduler({ mode: 'queue', timeoutMs: 0, retry: { attempts: 1 } }); * ``` */ createScheduler(options?: SolveSchedulerOptions): SolveScheduler; /** * Disposes of client resources. * Call this when you're done using the client. */ dispose(): Promise; /** * Ensures the client hasn't been disposed. */ private ensureNotDisposed; /** * Validates and normalizes a compute configuration. * * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid */ private normalizeComputeConfig; } //#endregion //#region src/grasshopper/io/output/response-processors.d.ts interface ParsedContext { [key: string]: any; } interface GetValuesOptions { parseValues?: boolean; rhino?: any; /** * If true, only include values of type System.String in the result. * Non-string types are filtered out. */ stringOnly?: boolean; } interface GetValuesResult { values: T; /** * Free every rhino3dm WASM object decoded into `values`. When a `rhino` * instance was passed, decoded geometry lives on the WASM heap and is never * garbage-collected — call this once the values are consumed, or the heap * grows monotonically across solves. Idempotent; a no-op when nothing was * decoded. `values` must not be used after disposal. */ dispose: () => void; } //#endregion //#region src/grasshopper/client/grasshopper-response-processor.d.ts /** * High-level wrapper for interacting with Grasshopper Compute responses. * * This class exposes a clean, consistent API for accessing parsed values, * geometry, and produced files. It is designed to be the primary interface * when working with Grasshopper results in client applications. */ declare class GrasshopperResponseProcessor { /** * The raw compute response. Public so callers can hand it to a renderer-side parser — e.g. * `getThreeMeshesFromComputeResponse` in `@selvajs/visualization/parse`, which replaced this * class's former `extractMeshesFromResponse()`. */ readonly response: GrasshopperComputeResponse; /** * Retained for call-signature compatibility. Its only consumer was the removed * `extractMeshesFromResponse()`, which merged it into the parse options; pass `debug` * directly to the parser in `@selvajs/visualization/parse` instead. */ readonly debug: boolean; constructor( /** * The raw compute response. Public so callers can hand it to a renderer-side parser — e.g. * `getThreeMeshesFromComputeResponse` in `@selvajs/visualization/parse`, which replaced this * class's former `extractMeshesFromResponse()`. */ response: GrasshopperComputeResponse, /** * Retained for call-signature compatibility. Its only consumer was the removed * `extractMeshesFromResponse()`, which merged it into the parse options; pass `debug` * directly to the parser in `@selvajs/visualization/parse` instead. */ debug?: boolean); /** * Extract all values in the response. * * @typeParam T - Expected structure of the return value. Defaults to a simple key/value map. (later cast as needed) * @param byId - Key by parameter ID instead of name. Requires the VektorNode rhino.compute branch. * @param options - Controls parsing behavior such as Rhino geometry decoding. * @returns Parsed Grasshopper output values. * * @example * ```ts * const processor = new GrasshopperResponseProcessor(response); * const { values } = processor.getValues(); * ``` * */ getValues(byId?: boolean, options?: GetValuesOptions): GetValuesResult; /** * Retrieve a specific value by parameter name or ID. * * @param selector - `{ byName }` for the human-readable name, `{ byId }` for the parameter GUID. * @param options - Parsing configuration (e.g. disable parsing or enable Rhino). * @returns Single parsed value, array of values, or undefined if the parameter is absent. * * `byId` requires the VektorNode rhino.compute branch. * * @example * ```ts * const schema = processor.getValue({ byName: 'Schema' }); * const output = processor.getValue({ byId: 'a4be1c1e-23f9-4c27-b942-7f3bb2c45c6f' }); * ``` */ getValue(selector: { byName: string; } | { byId: string; }, options?: GetValuesOptions): any; /** * REMOVED: `extractMeshesFromResponse()`. Mesh decoding lives in `@selvajs/visualization` now, so * this package stays pure solve/data and carries no `three` dependency. Call the parser directly * with the raw response: * * ```ts * import { getThreeMeshesFromComputeResponse } from '@selvajs/visualization/parse'; * * const meshes = await getThreeMeshesFromComputeResponse(processor.response, { rhino }); * scene.add(...meshes); * ``` */ private getFileData; /** * Download all files generated by Grasshopper, optionally including * additional user-provided files. * * Files are grouped under the specified folder name when downloaded. * * @param folderName - Name for the download directory. * @param additionalFiles - Extra files to package (single file, array, or null). * * @example * ```ts * await processor.getAndDownloadFiles('gh-output'); * ``` * * @example * ```ts * const extra = { name: 'notes.txt', data: 'Example' }; * await processor.getAndDownloadFiles('project', extra); * ``` */ getAndDownloadFiles(folderName: string, additionalFiles?: FileBaseInfo[] | FileBaseInfo | null): Promise; } //#endregion //#region src/grasshopper/scheduler/stable-hash.d.ts /** * Deterministic stringify with sorted keys. {a:1,b:2} and {b:2,a:1} produce * the same string. Safely handles circular references and non-finite numbers. * * The output is a cache key, so the invariant that matters is: two payloads * that serialize differently on the wire must stringify differently here * (false misses are harmless; false hits serve the wrong cached solve). */ export declare function stableStringify(value: unknown): string; /** * Hash definition and data tree into a stable cache key. * * The definition is the *identity* of what we solve, so a binary definition is * hashed over its full content (`fnv1aBytes`) — a length-only or sampled key * would let two different `.gh` files collide and serve one's cached solve for * the other. `.gh` files are small enough that a single linear pass is * negligible. A {@link DefinitionRef} is keyed by its `key` alone — the * caller-declared identity of immutable bytes — so no bytes are materialized * or hashed at all. * * The key keeps the definition and tree hashes as separate parts rather than * collapsing them into one 32-bit hash: a single FNV pass over the pair would * birthday-collide quadratically in cache size, while requiring both 32-bit * parts (plus lengths) to collide at once makes that negligible. */ export declare function hashSolveInput(definition: SolveDefinition, dataTree: unknown): string; /** * Stable identity of a definition alone (no inputs) — used to key the * server-cache-key map so the same definition reuses its `pointer` across solves * with different inputs. Same full-content hashing as {@link hashSolveInput}: a * binary definition is hashed over all its bytes so two distinct `.gh` files of * equal length can't share a cache key. A {@link DefinitionRef} is keyed by its * `key` verbatim (refs are short identities like UUIDs, safe as Map keys) — * its immutability contract makes the key equivalent to a content hash. */ export declare function hashDefinition(definition: SolveDefinition): string; //#endregion //#region src/grasshopper/solve.d.ts /** * Runs a Rhino Compute job using the provided tree prototypes and Grasshopper definition. * * @public Use this for direct compute control. For high-level API, use `GrasshopperClient.solve()`. * * @param dataTree - An array of `DataTree` objects representing the input data for the compute job. * @param definition - The Grasshopper definition, which can be: * - A URL string (e.g., 'https://example.com/definition.gh') * - A base64-encoded string of the .gh file * - A plain string (will be base64-encoded) * - A Uint8Array of the .gh file (will be base64-encoded) * - A `DefinitionRef` (bytes are loaded via `ref.load()` for the upload) * @param config - Compute configuration (server URL, API key, etc. along with optional timeout, units, etc.) * @returns An object containing the compute result and extracted file data. * * @example * // Using a URL * await solveGrasshopperDefinition(trees, 'https://example.com/definition.gh', config); * * // Using a base64 string * await solveGrasshopperDefinition(trees, 'UEsDBBQAAAAIAL...', config); * * // Using binary data * const fileData = new Uint8Array([...]); * await solveGrasshopperDefinition(trees, fileData, config); */ export declare function solveGrasshopperDefinition(dataTree: DataTree[], definition: SolveDefinition, config: GrasshopperComputeConfig): Promise; //#endregion //#region src/grasshopper/io/definition-io.d.ts /** * Fetches raw input/output schemas from a Grasshopper definition. * * "Raw" means no per-type parsing (no default coercion, no discriminated-union * typing) — but NOT byte-for-byte wire data. The response IS normalized: * per-param field KEYS are canonicalized to camelCase across server branches * ({@link normalizeInputSchema} / {@link normalizeOutputSchema}, with honest * fallbacks for missing required fields), missing/non-array `inputs`/`outputs` * coerce to `[]`, and server load diagnostics surface as * `loadWarnings`/`loadErrors` (coerced to strings). Field VALUES — notably * `default` and the `values` dropdown-label map — pass through verbatim. * * @param definition - The Grasshopper definition (URL, base64 string, or Uint8Array) * @param config - Compute configuration (server URL, API key, etc.) * @returns Key-normalized inputs and outputs with no per-type processing * @throws {ComputeError} If fetch fails or response is invalid * * @public Use `fetchParsedDefinitionIO()` for processed, type-safe inputs */ export declare function fetchDefinitionIO(definition: string | Uint8Array, config: ComputeConfig): Promise; /** * Fetches and processes input/output schemas from a Grasshopper definition. * Returns strongly-typed, validated input parameters ready for use. * * @public This is the recommended way to fetch definition I/O schemas. * * @param definition - The Grasshopper definition (URL, base64 string, or Uint8Array) * @param config - Compute configuration (server URL, API key, etc.) * @returns Processed inputs with discriminated union types and outputs * @throws {ComputeError} If fetch fails or response is invalid * * @example * ```typescript * const { inputs, outputs } = await fetchParsedDefinitionIO( * 'https://example.com/definition.gh', * { serverUrl: 'https://compute.rhino3d.com', apiKey: 'YOUR_KEY' } * ); * * // Inputs are now strongly typed * inputs.forEach(input => { * if (input.paramType === 'Number') { * console.log(input.minimum, input.maximum); // TypeScript knows these exist * } * }); * ``` */ export declare function fetchParsedDefinitionIO(definition: string | Uint8Array, config: ComputeConfig): Promise; //#endregion //#region src/grasshopper/io/schema-endpoint.d.ts /** One entry of the schema endpoint's response, after wrapper-key normalization. */ interface SchemaEndpointResult { /** Schemas embedded in that file. Absent when the file yielded none. */ schemas?: TSchema[]; /** Per-file diagnosis. Compute reports this and still answers 200. */ error?: string; } /** * Read compute's `/grasshopper/schema` body: `[{ FileName, Schemas }]` per * uploaded file, or a bare object for a single file. * * Use this rather than unwrapping the body by hand. The wrapper's casing varies * by server branch — mcneel serializes `FileName`/`Schemas`, the VektorNode fork * `fileName`/`schemas` — so a fixed-key read silently yields `undefined` against * half the servers, and the endpoint answers 200 either way. The failure looks * like "this definition has no schemas", which sends you debugging the wrong * thing entirely. * * A blanket key-rewrite (the old `camelcaseKeys` approach) is NOT the fix: it * reaches inside the schemas and mangles user-authored names — `"Display3d"` → * `"display3d"`, value-list labels like `"Option A"` → `"optionA"`. Only the two * wrapper keys are read here; schema CONTENTS pass through untouched, which is * why `TSchema` is a pass-through type parameter this module never inspects. * * @typeParam TSchema - Your schema type (e.g. `UISchema`). Not inspected. * @param raw - The parsed JSON body. */ export declare function readSchemaResults(raw: unknown): SchemaEndpointResult[]; //#endregion //#region src/grasshopper/io/normalize-ui-schema.d.ts /** * Canonicalize a `/grasshopper/schema` body's key CASING. * * ## Why this exists * * Compute serializes the plugin's `UISchema` POCO, whose camelCase wire names * live in Newtonsoft `[JsonProperty]` attributes. `Selva.gha` ILRepack-merges * Newtonsoft into itself, so those attributes have type * `Selva!Newtonsoft.Json.JsonPropertyAttribute`. When the serializer that runs is * compute's OWN Newtonsoft assembly, it does not recognize that type as its own * attribute, reads no attributes at all, and falls back to raw CLR member names — * emitting `Inputs`/`Layout`/`SchemaVersion`. * * Nothing throws on the wire. Every consumer reads `schema.inputs` as * `undefined`, so the definition renders with no inputs, and `schemaVersion` * reads `undefined` — which also silently disables the newer-plugin version gate. * * This is the schema-body counterpart to {@link normalizeInputSchema}, which * solves the same split for the `/io` endpoint's param records. * * ## Why a casing rule, and not a key allowlist * * The wire names are the CLR names run through Newtonsoft's camelCase strategy, * so reproducing that strategy covers every key. An allowlist would have to * enumerate every structural key in `UISchema` and drift out of date each time * the schema gains a field, failing silently and in exactly this way again. * * ## What it does NOT touch * * `options`, `defaultOptions` and `values` hold USER-AUTHORED keys — dropdown * labels like `"Standart Beschichtung"`, `"Use 10 Elements instead"`, `"True"`. * Those maps are copied verbatim; only the key naming them is canonicalized. * Rewriting their contents is the exact regression that motivated deleting the * old global `camelcaseKeys` pass (see `read-field.ts`), which mangled * `"Option A"` into `"optionA"` and silently changed what a definition solved * with. * * Values are never inspected — only object keys are rewritten. */ /** * Returns a schema whose structural keys are camelCase, regardless of which * Newtonsoft serialized it. A body that is already camelCase passes through * unchanged in shape (it is still rebuilt, so the result is always a fresh * object the caller may mutate). * * Safe to call on any parsed JSON value; non-objects are returned as-is. */ export declare function normalizeUISchemaCasing(raw: T): T; //#endregion //#region src/grasshopper/io/input/input-processors.d.ts /** * Parse one raw Grasshopper input schema into a typed {@link InputParam}. * Validation failures are swallowed and replaced with a safe default; use * {@link processInputWithError} to receive them. */ export declare function processInput(rawInput: InputParamSchema): InputParam; //#endregion //#region src/grasshopper/data-tree/data-tree.d.ts /** * Value types that can be stored in a DataTree */ type DataTreeValue = string | number | boolean | object | null; /** * Simple data item for compute requests (not to be confused with DataItem interface for responses). * Note: While TypeScript defines this as string, Rhino Compute accepts boolean/number primitives in JSON. */ interface ComputeDataItem { data: string | boolean | number; } /** * InnerTree data structure for compute requests. */ type ComputeInnerTreeData = { [path in DataTreePath]: ComputeDataItem[]; }; /** * Standalone TreeBuilder class for constructing Grasshopper TreeBuilder structures. * Does not depend on RhinoCompute library. * * @example * ```ts * const tree = new TreeBuilder('MyParam') * .append([0], [1, 2, 3]) * .append([1], [4, 5]) * .toComputeFormat(); * ``` */ export declare class TreeBuilder { private innerTree; private paramName; constructor(paramName: string); /** * Append values to a specific path in the tree. * * @param path - Array of integers representing the branch path (e.g., [0], [0, 1]) * @param items - Values to append at this path * @returns this for method chaining */ append(path: number[], items: DataTreeValue[]): this; /** * Append a single value to a path. * * @param path - Branch path * @param item - Single value to append * @returns this for method chaining */ appendSingle(path: number[], item: DataTreeValue): this; /** * Set values from a DataTreeDefault structure. * Replaces any existing tree data. * * @param treeData - TreeBuilder structure with path keys like "{0;1}" * @returns this for method chaining */ fromDataTreeDefault(treeData: DataTreeDefault): this; /** * Append flattened values to path [0]. * Useful for simple flat inputs. * * @param values - Single value or array of values * @returns this for method chaining */ appendFlat(values: DataTreeValue | DataTreeValue[]): this; /** * Get the flattened list of all values in the tree. * * @returns Array of all values across all branches */ flatten(): DataTreeValue[]; /** * Get all paths in the tree. * * @returns Array of path strings */ getPaths(): DataTreePath[]; /** * Get values at a specific path. * * @param path - Path to retrieve values from * @returns Array of values or undefined if path doesn't exist */ getPath(path: number[]): DataTreeValue[] | undefined; /** * Convert to format compatible with Grasshopper Compute API. * * @returns InnerTree object ready for compute */ toComputeFormat(): DataTree; /** * Get the raw InnerTree data structure. * * @returns InnerTree data */ getInnerTree(): ComputeInnerTreeData; /** * Get the parameter name. * * @returns Parameter name */ getParamName(): string; /** * Create DataTrees from an array of InputParam definitions. * Handles tree access, numeric constraints, and value parsing. * * @param inputs - Array of input parameter definitions * @returns Array of InnerTree instances ready for compute * * @example * ```ts * const trees = TreeBuilder.fromInputParams(inputs); * ``` */ static fromInputParams(inputs: InputParam[]): DataTree[]; /** * Create a TreeBuilder from a single InputParam. * * @param input - Input parameter definition * @returns InnerTree ready for compute or undefined if value is invalid */ static fromInputParam(input: InputParam): DataTree | undefined; /** * Set or replace a parameter value within a TreeBuilder or InnerTree array. * * Supports both high-level `DataTree[]` instances and low-level `InnerTree[]` format. * * **Architecture Note:** * - Use with `DataTree[]` when building/modifying before computation * - Use with `InnerTree[]` when modifying compute API results * - `DataTree` is the high-level builder; `InnerTree` is the Rhino Compute format * * Copy-on-write: returns a new array; the caller's array is never mutated. * * @overload For TreeBuilder instances (high-level builder) * @param trees - Array of TreeBuilder instances to read from (not mutated) * @param paramName - The parameter name to set or replace * @param newValue - The new value (scalar, array, or TreeBuilder structure) * @returns A new TreeBuilder array with the updated parameter * * @overload For compiled InnerTree (low-level API format) * @param trees - The compiled InnerTree array (typically from `client.solve()`; not mutated) * @param paramName - The parameter name to set or replace * @param newValue - The new value (scalar, array, or TreeBuilder structure) * @returns A new InnerTree array with the updated parameter * * @example * ```ts * // With TreeBuilder instances (high-level) * let trees = [new TreeBuilder('X'), new TreeBuilder('Y')]; * trees = TreeBuilder.replaceTreeValue(trees, 'X', 42); * const result = await client.solve(definitionUrl, * trees.map(t => t.toComputeFormat()) * ); * ``` * * @example * ```ts * // With InnerTree format (low-level, from API) * let trees = await client.solve(definitionUrl, initialInputs); * trees = TreeBuilder.replaceTreeValue(trees, 'X', 42); * trees = TreeBuilder.replaceTreeValue(trees, 'Y', [1, 2, 3]); * ``` */ static replaceTreeValue(trees: TreeBuilder[], paramName: string, newValue: DataTreeValue): TreeBuilder[]; static replaceTreeValue(trees: DataTree[], paramName: string, newValue: DataTreeValue): DataTree[]; /** * Build a TreeBuilder from a single value, dispatching on shape: * DataTreeDefault structure, array, or scalar. */ private static buildFromValue; /** * Extract a value from a TreeBuilder or InnerTree array by parameter name. * * Automatically unwraps single values for convenience. * Works with both high-level `DataTree[]` instances and low-level `InnerTree[]` format. * * **Architecture Note:** * - Use with `DataTree[]` to read builder instances * - Use with `InnerTree[]` to read compute API responses * - Return behavior is consistent across both formats * * **Return Value Behavior:** * - Single value → unwrapped (returns `5` not `[5]`) * - Multiple values → array of values * - Not found → `null` * * @overload For TreeBuilder instances * @param trees - Array of TreeBuilder instances to read from * @param paramName - The parameter name to retrieve * @returns The unwrapped value, array of values, or null if parameter not found * * @overload For compiled InnerTree * @param trees - The compiled InnerTree array (typically from `client.solve()`) * @param paramName - The parameter name to retrieve * @returns The unwrapped value, array of values, or null if parameter not found * * @example * ```ts * // With TreeBuilder instances * const trees = [new TreeBuilder('X'), new TreeBuilder('Y')]; * trees[0].appendFlat(42); * const x = TreeBuilder.getTreeValue(trees, 'X'); // Returns 42 * ``` * * @example * ```ts * // With InnerTree from compute results * const result = await client.solve(definitionUrl, inputs); * const x = TreeBuilder.getTreeValue(result, 'X'); // Returns 42 (not [42]) * const points = TreeBuilder.getTreeValue(result, 'Points'); // Returns [point1, point2, ...] * ``` */ static getTreeValue(trees: TreeBuilder[], paramName: string): DataTreeValue | null; static getTreeValue(trees: DataTree[], paramName: string): DataTreeValue | null; /** * Read all values for `paramName` across every branch of the matching builder. * Returns null when the builder isn't found. */ private static readFromBuilders; /** * Read values from the first branch of the matching compiled InnerTree * (multi-branch responses are not flattened — current semantics, pinned by * the "reads from the first branch path only" test). */ private static readFromDataTrees; /** * Parse a TreeBuilder path string like "{0;1;2}" into [0, 1, 2]. * Negative indices ("{-1;2}") and the root path "{}" are valid. * * @param pathStr - Path string * @returns Array of path indices * @throws {ComputeError} `INVALID_INPUT` when the path string is not a valid * Grasshopper branch path. Malformed keys must never silently collapse to a * default branch — two distinct unparseable keys would merge their items * into one branch. */ static parsePathString(pathStr: string): number[]; /** * Format a path array into TreeBuilder path string format. * * @param path - Path as number array * @returns Formatted path string like "{0;1;2}" */ static formatPathString(path: number[]): DataTreePath; /** Apply numeric constraints to all tree values. */ private applyNumericConstraints; /** * Serialize a value for compute requests. * Preserves booleans and numbers as primitives for proper Grasshopper parameter handling. */ private static serializeValue; /** * Deserialize a value back to its original type. * Handles both string-encoded values and primitive values. */ private static deserializeValue; /** * Check if a value is valid for inclusion in a DataTree. */ private static hasValidValue; /** * Check if input is numeric type. */ private static isNumericInput; /** * Process array of values based on input type. */ private static processValues; /** * Clamp numeric value to constraints. */ private static clampValue; } //#endregion export { type BooleanInputType, type CacheOptions, ComputeServerStats, type DataItem, type DataTree, type DataTreeDefault, type DataTreePath, type DataTreeValue, type DefaultValue, type DefinitionRef, type FileBaseInfo, type FileData, type FileInputType, type GeometryInputType, type GetValuesOptions, type GetValuesResult, GrasshopperClient, type GrasshopperComputeConfig, type GrasshopperComputeResponse, type GrasshopperParsedIO, type GrasshopperParsedIORaw, type GrasshopperRequestSchema, GrasshopperResponseProcessor, type InnerTreeData, type InputParam, type InputParamSchema, type NumericInputType, type OutputParamSchema, type OutputType, type ParsedContext, type ProcessedFile, type RhinoModelUnit, type SchedulerMode, type SchemaEndpointResult, type SolveContext, type SolveDefinition, type SolveOptions, type SolveResult, type SolveSchedulerOptions, type TextInputType, type ValueListInputType, downloadFileData, extractFilesFromComputeResponse, isDefinitionRef }; //# sourceMappingURL=grasshopper.d.ts.map