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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,25 @@
1
- import { A as RetryPolicy, N as ComputeError, O as ComputeAuth, _ as isDefinitionRef, a as extractFilesFromComputeResponse, c as ProcessedFile, g as SolveDefinition, h as DefinitionRef, k as ComputeConfig, o as FileBaseInfo, r as downloadFileData, s as FileData } from "./index-BR_XZvjj.js";
2
- //#region src/grasshopper/server/compute-server-stats.d.ts
1
+ import { c as ComputeAuth, f as ServerTiming, l as ComputeConfig, n as FileBaseInfo, o as SolveDefinition, p as ComputeError, u as RetryPolicy } from "./types-Dm9QFVvA.js";
2
+ interface ServerVersion {
3
+ rhino: string;
4
+ compute: string;
5
+ git_sha: string | null;
6
+ }
7
+ /** What `GET /limits` reports. Only the VektorNode fork answers it. */
8
+ interface ServerLimits {
9
+ /** Longest the server lets one request run (`RHINO_COMPUTE_TIMEOUT`, `--timeout`). */
10
+ timeoutSeconds: number;
11
+ /** Largest request body it accepts (`RHINO_COMPUTE_MAX_REQUEST_SIZE`, `--max-request-size`). */
12
+ maxRequestBytes: number;
13
+ /** A stale definition pointer answers `definition_not_cached`, so pointer reuse is safe. */
14
+ signalsStaleDefinition: boolean;
15
+ }
16
+ interface ServerStats {
17
+ isOnline: boolean;
18
+ version?: ServerVersion;
19
+ activeChildren?: number;
20
+ }
3
21
  /**
4
- * ComputeServerStats provides methods to query Rhino Compute server statistics.
5
- *
6
- * @public Use this for server health monitoring and statistics.
22
+ * Server health and control: liveness, version, child processes, cache purges.
7
23
  *
8
24
  * @example
9
25
  * ```typescript
@@ -26,13 +42,14 @@ declare class ComputeServerStats {
26
42
  private readonly headers;
27
43
  private disposed;
28
44
  private activeMonitors;
29
- private activeTimeouts;
30
45
  /** Timeout (ms) for the fast read/monitoring endpoints. */
31
46
  private static readonly DEFAULT_TIMEOUT_MS;
32
47
  /** Timeout (ms) for child-lifecycle POSTs — a cold Windows child can take ~30s to spawn. */
33
48
  private static readonly LIFECYCLE_TIMEOUT_MS;
34
49
  /** Floor for `monitor()`'s `intervalMs` — anything lower hot-loops the server. */
35
50
  private static readonly MIN_MONITOR_INTERVAL_MS;
51
+ /** Every control-plane answer is a count, a version or a short map. */
52
+ private static readonly MAX_BODY_BYTES;
36
53
  /**
37
54
  * @param serverUrl - Base URL of the Rhino Compute server with http:// or https:// scheme (e.g., 'http://localhost:6500')
38
55
  * @param auth - An API key, or the same auth fields a solve's `ComputeConfig` carries
@@ -40,10 +57,15 @@ declare class ComputeServerStats {
40
57
  */
41
58
  constructor(serverUrl: string, auth?: string | ComputeAuth);
42
59
  /**
43
- * `fetch` wrapper that aborts after `timeoutMs` so a hung connection can't stall
44
- * a probe (or the `monitor()` loop) forever. Pass `0` to disable the timeout.
60
+ * Aborts after `timeoutMs` so a hung connection can't stall a probe or `monitor()`; `0` disables
61
+ * it. Redirects are not followed: fetch would carry the API key header to the target.
45
62
  */
46
63
  private fetchWithTimeout;
64
+ /**
65
+ * The response body of `path`, or `null` (logged) on a non-2xx or a network
66
+ * failure. Throws only when disposed.
67
+ */
68
+ private getText;
47
69
  /**
48
70
  * Check if the server is online.
49
71
  *
@@ -68,10 +90,11 @@ declare class ComputeServerStats {
68
90
  *
69
91
  * @param timeoutMs - Abort the probe after this many ms (default: 5000).
70
92
  * Pass `0` to disable the timeout.
93
+ * @param signal - Cancels the probe, which then reports the abort as its `error`.
71
94
  * @returns `online` plus either the HTTP `status` the server answered with,
72
95
  * or the `error` the connection attempt failed with.
73
96
  */
74
- probeServer(timeoutMs?: number): Promise<{
97
+ probeServer(timeoutMs?: number, signal?: AbortSignal): Promise<{
75
98
  online: boolean;
76
99
  status?: number;
77
100
  error?: string;
@@ -92,16 +115,18 @@ declare class ComputeServerStats {
92
115
  getActiveChildren(options?: {
93
116
  initialize?: boolean;
94
117
  }): Promise<number | null>;
118
+ /**
119
+ * The server's request limits, or `null` when it can't report them: a standard rhino.compute
120
+ * has no `/limits`, and neither does an older fork. A missing route is expected, so it isn't
121
+ * logged as a failure.
122
+ */
123
+ getLimits(): Promise<ServerLimits | null>;
95
124
  /**
96
125
  * Get the server version information.
97
126
  *
98
127
  * @returns Version object with rhino, compute, and git_sha, or null if unavailable
99
128
  */
100
- getVersion(): Promise<{
101
- rhino: string;
102
- compute: string;
103
- git_sha: string | null;
104
- } | null>;
129
+ getVersion(): Promise<ServerVersion | null>;
105
130
  /**
106
131
  * Get the plugins installed on the server.
107
132
  *
@@ -128,15 +153,7 @@ declare class ComputeServerStats {
128
153
  *
129
154
  * @returns Object containing server status and available stats
130
155
  */
131
- getServerStats(): Promise<{
132
- isOnline: boolean;
133
- version?: {
134
- rhino: string;
135
- compute: string;
136
- git_sha: string | null;
137
- };
138
- activeChildren?: number;
139
- }>;
156
+ getServerStats(): Promise<ServerStats>;
140
157
  /**
141
158
  * Purge the server's solve-results / URL-data cache.
142
159
  *
@@ -281,12 +298,7 @@ declare class ComputeServerStats {
281
298
  spawned: number[];
282
299
  active: number;
283
300
  } | null>;
284
- /**
285
- * POST a control endpoint that replies with a JSON object and return it,
286
- * degrading to `null` on any non-2xx, non-JSON, or network failure. Shared by
287
- * the child-lifecycle methods so their failure semantics stay identical. Uses
288
- * the longer lifecycle timeout since a spawn/recycle can take ~30s.
289
- */
301
+ /** A child-lifecycle POST: a spawn or recycle can take ~30 s. */
290
302
  private postJson;
291
303
  /**
292
304
  * Continuously monitor server stats at specified interval.
@@ -307,41 +319,29 @@ declare class ComputeServerStats {
307
319
  * stopMonitoring();
308
320
  * ```
309
321
  */
310
- monitor(callback: (stats: Awaited<ReturnType<typeof this.getServerStats>>) => void, intervalMs?: number): () => void;
322
+ monitor(callback: (stats: ServerStats) => void, intervalMs?: number): () => void;
311
323
  /**
312
324
  * Disposes of all resources and stops all active monitors.
313
325
  * Call this when you're done using the stats instance.
314
326
  */
315
327
  dispose(): Promise<void>;
316
- /**
317
- * Ensures the instance hasn't been disposed.
318
- */
319
328
  private ensureNotDisposed;
320
329
  }
321
- //#endregion
322
- //#region src/grasshopper/types/inputs.d.ts
323
330
  type DataTreePath = `{${string}}`;
324
331
  interface DataItem {
325
332
  type: string;
326
333
  data: string;
327
- id: string;
334
+ /** **Requires:** the VektorNode compute fork. A stock server sends none. */
335
+ id?: string;
328
336
  }
329
337
  type DataTreeDefault<T = any> = { [K in DataTreePath]?: T[]; };
330
338
  type InnerTreeData = { [path in DataTreePath]: DataItem[]; };
331
339
  /**
332
340
  * Tree with parameter metadata (used in compute requests and responses).
333
341
  *
334
- * Field casing varies by server branch: stock mcneel compute (8.x/9.x)
335
- * serializes `Resthopper.IO.DataTree` in PascalCase (`ParamName`/`InnerTree`
336
- * — the C# class carries no `[JsonProperty]` attributes), while
337
- * camelCase-serializing forks return `paramName`/`innerTree` instead. This
338
- * client always *sends* PascalCase (see `DataTree.toCompute()` in
339
- * `data-tree.ts`), so the PascalCase fields remain the canonical/required
340
- * shape; the optional camelCase fields exist so responses from camelCase
341
- * forks are representable without casts. Code that must read trees across
342
- * both server families should use `readField`/`hasField`
343
- * (`@/core/utils/read-field`, case-insensitive) rather than direct property
344
- * access — `warnOnEmptyInnerTrees` in `solve.ts` is the reference example.
342
+ * This client sends PascalCase (`TreeBuilder.toComputeFormat()`), so those
343
+ * fields are required; a response's casing varies by server branch, so read
344
+ * one with `readField` rather than by property.
345
345
  */
346
346
  interface DataTree {
347
347
  /** PascalCase — stock mcneel servers, and the request shape this client sends. */
@@ -353,7 +353,6 @@ interface DataTree {
353
353
  /** camelCase — sent instead of `ParamName` by camelCase server forks. */
354
354
  paramName?: string;
355
355
  }
356
- 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 & {});
357
356
  /**
358
357
  * Union type for all possible default value types
359
358
  */
@@ -369,12 +368,14 @@ interface BaseInputType {
369
368
  treeAccess: boolean;
370
369
  /**
371
370
  * Name of the group this parameter belongs to.
372
- * @requires Custom branch of compute.rhino3d
371
+ *
372
+ * **Requires:** the VektorNode compute fork.
373
373
  */
374
374
  groupName?: string;
375
375
  /**
376
376
  * Unique identifier for the parameter.
377
- * @requires Custom branch of compute.rhino3d
377
+ *
378
+ * **Requires:** the VektorNode compute fork.
378
379
  */
379
380
  id?: string;
380
381
  }
@@ -428,7 +429,7 @@ interface FileInputType extends BaseInputType {
428
429
  default: DefaultValue<object | string>;
429
430
  }
430
431
  /**
431
- * Color input type (stored as hex string)
432
+ * Color input type. Values are `"R, G, B"` strings (`"255, 0, 0"`), not hex.
432
433
  */
433
434
  interface ColorInputType extends BaseInputType {
434
435
  paramType: 'Color';
@@ -438,8 +439,6 @@ interface ColorInputType extends BaseInputType {
438
439
  * Discriminated union of all input parameter types
439
440
  */
440
441
  type InputParam = NumericInputType | BooleanInputType | TextInputType | ValueListInputType | GeometryInputType | FileInputType | ColorInputType;
441
- //#endregion
442
- //#region src/grasshopper/types/schema.d.ts
443
442
  type RhinoModelUnit = 'None' | 'Microns' | 'Millimeters' | 'Centimeters' | 'Decimeters' | 'Meters' | 'Kilometers' | 'Mils' | 'Inches' | 'Feet' | 'Yards' | 'Miles' | 'CustomUnits' | 'Unset';
444
443
  /**
445
444
  * Base Grasshopper schema properties shared by config, args, and response
@@ -455,15 +454,6 @@ interface GrasshopperBaseSchema {
455
454
  dataversion?: 7 | 8 | null;
456
455
  /** Whether to use cached solution */
457
456
  cachesolve?: boolean | null;
458
- /**
459
- * Opt-in: cache a solve even when the definition reported GH errors. Default
460
- * (false/unset) means errored solves are never cached server-side. Some
461
- * definitions throw GH errors by design (a guarded Python component, a
462
- * filtered branch) while still producing correct geometry — set this so those
463
- * still benefit from the server's solve cache. Only meaningful with
464
- * `cachesolve`. Requires a server that honors it (VektorNode fork).
465
- */
466
- cacheerroredsolves?: boolean | null;
467
457
  }
468
458
  /**
469
459
  * Where a Selva-family plugin running inside the solve may POST live events, and
@@ -487,55 +477,19 @@ interface GrasshopperDefinitionSource {
487
477
  filename?: string | null;
488
478
  }
489
479
  /**
490
- * Configuration for Grasshopper compute operations
491
- * Combines server config with optional Grasshopper-specific computation settings
492
- *
493
- * Note: The definition source (pointer/algo) is NOT part of config.
494
- * Instead, pass the definition directly to methods like solve(), getIO(), etc.
480
+ * Server config plus the Grasshopper solve settings. The definition is not part
481
+ * of it: pass that to `solve()` and friends.
495
482
  */
496
- interface GrasshopperComputeConfig extends ComputeConfig {
497
- /** Absolute tolerance used in computation */
498
- absolutetolerance?: number | null;
499
- /** Angular tolerance used in computation */
500
- angletolerance?: number | null;
501
- /** Model units used */
502
- modelunits?: RhinoModelUnit | null;
503
- /** Data version (7 or 8) */
504
- dataversion?: 7 | 8 | null;
505
- /** Whether to use cached solution */
506
- cachesolve?: boolean | null;
507
- /**
508
- * Opt-in: cache a solve even when the definition reported GH errors. See
509
- * {@link GrasshopperBaseSchema.cacheerroredsolves}. Only meaningful with
510
- * `cachesolve`.
511
- */
512
- cacheerroredsolves?: boolean | null;
513
- /** Per-solve live-event callback; see {@link SelvaEventTarget}. */
514
- selvaevents?: SelvaEventTarget | null;
515
- }
516
- /**
517
- * Arguments sent to Grasshopper compute endpoint
518
- * Includes config options + definition source + input values
519
- */
520
- interface GrasshopperRequestSchema extends GrasshopperBaseSchema, GrasshopperDefinitionSource {
521
- /** Input values organized by parameter */
522
- values?: DataTree[];
483
+ interface GrasshopperComputeConfig extends ComputeConfig, GrasshopperBaseSchema {
523
484
  /** Per-solve live-event callback; see {@link SelvaEventTarget}. */
524
485
  selvaevents?: SelvaEventTarget | null;
525
486
  }
526
487
  /**
527
- * Response from Grasshopper compute server
528
- * Includes all schema fields + computed results
529
- *
530
- * `pointer` is deliberately excluded (`Omit`): the solve layer (`runSolve` in
531
- * `solve.ts`) splits the server's echoed `pointer` off as the solve's
532
- * `cacheKey` and strips it from the returned response, so client-returned
533
- * responses never carry it.
488
+ * Response from Grasshopper compute server.
534
489
  *
535
- * The schema-echo fields (`cachesolve`/`modelunits`/`dataversion`) stay
536
- * optional as inherited from {@link GrasshopperBaseSchema}: the server echoes
537
- * them back when set, but nothing client-side enforces their presence — don't
538
- * rely on them without a fallback.
490
+ * No `pointer`: the solve returns the server's echoed pointer as its `cacheKey`
491
+ * instead. The echoed settings (`cachesolve`, `dataversion`) stay optional:
492
+ * nothing client-side enforces them.
539
493
  */
540
494
  interface GrasshopperComputeResponse extends GrasshopperBaseSchema, Omit<GrasshopperDefinitionSource, 'pointer'> {
541
495
  /**
@@ -545,12 +499,7 @@ interface GrasshopperComputeResponse extends GrasshopperBaseSchema, Omit<Grassho
545
499
  * hand-built mock may omit it at runtime.
546
500
  */
547
501
  modelunits: RhinoModelUnit;
548
- /**
549
- * The server echoes the request's full base64 definition back as `algo`, but the client strips
550
- * it before returning — retaining it would pin a multi-MB copy of the definition per response,
551
- * multiplied by every cache that holds responses. Always `undefined` on client-returned
552
- * responses (inherited optional field from {@link GrasshopperDefinitionSource}).
553
- */
502
+ /** Always `undefined` on a client-returned response: the solve strips it. */
554
503
  algo?: string | null;
555
504
  /** Filename of the definition (always present in response) */
556
505
  filename: string | null;
@@ -615,22 +564,19 @@ interface InputParamSchema {
615
564
  acceptedFormats?: string[];
616
565
  groupName?: string | null;
617
566
  }
618
- //#endregion
619
- //#region src/grasshopper/types/outputs.d.ts
620
- /**
621
- * Parsed input/output structure with raw schemas.
622
- *
623
- * `loadWarnings` / `loadErrors` carry the server's definition-load diagnostics
624
- * (missing plugin, broken component, etc.) from the `/io` response. They are
625
- * distinct from per-input parse failures (`InputParseError`): these come from
626
- * the server loading the definition, those from the client typing an input.
627
- */
567
+ /** Input/output schemas as the `/io` endpoint reports them, keys canonicalized. */
628
568
  interface GrasshopperParsedIORaw {
629
569
  inputs: InputParamSchema[];
630
570
  outputs: OutputParamSchema[];
631
- /** Server-side definition-load warnings, if any. */
571
+ /**
572
+ * Warnings the server raised loading the definition (e.g. an obsolete
573
+ * component). Distinct from `parseErrors`, the client failing to type an input.
574
+ */
632
575
  loadWarnings?: string[];
633
- /** Server-side definition-load errors, if any (e.g. missing plugin). */
576
+ /**
577
+ * Errors the server raised loading the definition (e.g. a missing plugin).
578
+ * When present, `inputs`/`outputs` may be incomplete.
579
+ */
634
580
  loadErrors?: string[];
635
581
  }
636
582
  /**
@@ -649,43 +595,24 @@ interface InputParseError {
649
595
  /** Error code from the underlying ComputeError, if available. */
650
596
  code?: string;
651
597
  }
652
- /**
653
- * Parsed input/output structure with processed types.
654
- *
655
- * `parseErrors` is populated when one or more inputs failed validation and
656
- * fell back to a safe default. The result is still usable, but the UI should
657
- * surface these so the user can fix their definition.
658
- */
659
- interface GrasshopperParsedIO {
598
+ /** {@link GrasshopperParsedIORaw} with each input parsed into its typed {@link InputParam}. */
599
+ interface GrasshopperParsedIO extends Omit<GrasshopperParsedIORaw, 'inputs'> {
660
600
  inputs: InputParam[];
661
- outputs: OutputParamSchema[];
662
- parseErrors?: InputParseError[];
663
601
  /**
664
- * Server-side definition-load warnings from the `/io` response (e.g. an
665
- * obsolete component). Surface these so the user understands a degraded IO
666
- * list. Distinct from `parseErrors` (client-side input typing failures).
602
+ * Inputs that failed validation and got a safe default. The result is still
603
+ * usable, but surface these so the user can fix their definition.
667
604
  */
668
- loadWarnings?: string[];
669
- /**
670
- * Server-side definition-load errors from the `/io` response (e.g. a missing
671
- * plugin that left inputs unresolved). When present, the inputs/outputs may
672
- * be incomplete — the user needs to fix their server/definition.
673
- */
674
- loadErrors?: string[];
605
+ parseErrors?: InputParseError[];
675
606
  }
676
- //#endregion
677
- //#region src/grasshopper/scheduler/types.d.ts
678
607
  /**
679
- * Scheduling mode — controls how concurrent `solve()` calls interact.
608
+ * How concurrent `solve()` calls interact.
680
609
  *
681
- * - `latest-wins`: One in flight at a time. New calls supersede any pending
682
- * call (in-flight one is aborted). Optimal for slider scrubs / live UIs.
683
- * - `queue`: FIFO queue. Each solve runs to completion. Concurrency capped
684
- * by `maxConcurrent`. Use for "submit job" flows where every request matters.
685
- * - `parallel`: No scheduling — calls run concurrently up to `maxConcurrent`.
686
- * Closest to plain `client.solve()` but with shared cancel/state.
610
+ * - `latest-wins`: one in flight at a time. A new call supersedes the pending
611
+ * one and aborts the one in flight. For slider scrubs and live UIs.
612
+ * - `queue`: FIFO, every call runs to completion, up to `maxConcurrent` at
613
+ * once (default 1). For "submit job" flows where every request matters.
687
614
  */
688
- type SchedulerMode = 'latest-wins' | 'queue' | 'parallel';
615
+ type SchedulerMode = 'latest-wins' | 'queue';
689
616
  interface CacheOptions {
690
617
  /**
691
618
  * Total byte budget for retained responses, evicted LRU. The only size bound
@@ -696,6 +623,8 @@ interface CacheOptions {
696
623
  * fetch boundary — no re-serialization); a response without that hint
697
624
  * (custom executor) is sized by a one-off `JSON.stringify`. A single
698
625
  * response larger than the whole budget is served but never retained.
626
+ * Parsed, a response of many small items holds more heap than its wire size,
627
+ * so leave headroom.
699
628
  *
700
629
  * Required and must be > 0 — to disable caching, pass `cache: false`.
701
630
  */
@@ -715,12 +644,19 @@ interface CacheOptions {
715
644
  * Cache responses that carry Grasshopper `errors`. Default `true`: an
716
645
  * errored solve is a valid, deterministic result — definitions raise GH
717
646
  * errors by design (guarded components, validation branches), so replaying
718
- * one from cache is correct. Set `false` for parity with Rhino's opt-in
719
- * `cacheerroredsolves` server flag, e.g. when a definition's errors are
647
+ * one from cache is correct. Set `false` when a definition's errors are
720
648
  * transient (external data sources) rather than functions of the inputs.
721
649
  */
722
650
  cacheErroredSolves?: boolean;
723
651
  }
652
+ /** What {@link SolveScheduler.cacheStats} reports. */
653
+ interface CacheStats {
654
+ entries: number;
655
+ bytes: number;
656
+ hits: number;
657
+ misses: number;
658
+ evictions: number;
659
+ }
724
660
  interface SolveSchedulerOptions {
725
661
  mode?: SchedulerMode;
726
662
  maxConcurrent?: number;
@@ -729,7 +665,7 @@ interface SolveSchedulerOptions {
729
665
  * excluding the ones already in flight). When the queue is full, a new
730
666
  * `solve()` is rejected immediately with `code: QUEUE_FULL` (retryable, meant to
731
667
  * map to HTTP 503 + Retry-After) instead of piling up unbounded. Bounds the
732
- * miss path under load. Only applies to `queue` / `parallel` modes —
668
+ * miss path under load. Only applies to `queue` mode —
733
669
  * `latest-wins` has an intrinsic depth of 1. Default: unbounded.
734
670
  */
735
671
  maxQueueDepth?: number;
@@ -737,8 +673,7 @@ interface SolveSchedulerOptions {
737
673
  * Backpressure — max time (ms) a call may sit queued before it starts
738
674
  * executing. If it's still waiting after this long it's rejected with
739
675
  * `code: QUEUE_TIMEOUT` rather than burning compute on a stale request.
740
- * Bounds tail latency. Only applies to `queue` / `parallel` modes. Default:
741
- * no deadline.
676
+ * Bounds tail latency. Only applies to `queue` mode. Default: no deadline.
742
677
  */
743
678
  queueWaitMs?: number;
744
679
  timeoutMs?: number;
@@ -750,15 +685,11 @@ interface SolveSchedulerOptions {
750
685
  */
751
686
  cache?: false | CacheOptions;
752
687
  /**
753
- * Reuse the server's definition cache key so a large (base64/binary)
754
- * definition is uploaded once and subsequent solves reference it by
755
- * `pointer` instead of re-sending the full payload. Hugely cheaper for
756
- * multi-MB definitions on a live UI (slider scrubs, etc.).
757
- *
758
- * Requires a `cacheKeyExecutor` to be supplied (the client wires one). Has no
759
- * effect for URL-pointer definitions (already a reference). On a server-side
760
- * cache miss the executor transparently falls back to a full upload, so this
761
- * is safe to leave on. Default: `true` when a `cacheKeyExecutor` is present.
688
+ * Upload a definition once, then solve it by the server's cache key (`pointer`)
689
+ * instead of re-sending the bytes; a `DefinitionRef` also skips its `load()`.
690
+ * When the server has evicted the key, the solve falls back to a full upload,
691
+ * so this is safe to leave on. No effect for URL definitions, which are
692
+ * already a reference. Default `true`.
762
693
  */
763
694
  reuseServerDefinitionCache?: boolean;
764
695
  /** Lifecycle hooks — fired in order. Errors thrown by hooks are logged, not rethrown. */
@@ -780,54 +711,54 @@ type SolveResult = {
780
711
  durationMs: number;
781
712
  fromCache: boolean;
782
713
  /**
783
- * Definition-cache telemetry for a real compute call (not a Selva-cache
784
- * `fromCache` hit). `false` → the server reused its cached definition via
785
- * the pointer (no upload); `true` → the pointer was cold/stale so the full
786
- * definition was re-uploaded. `undefined` when the server-definition-cache
787
- * fast path didn't run (reuse disabled, or a non-reusable definition such
788
- * as a remote URL).
714
+ * Whether this solve uploaded the full definition: `true` on its first
715
+ * solve or after the server evicted the cache key, `false` when the server
716
+ * reused its cached copy via the pointer. `undefined` for a `fromCache`
717
+ * hit, or when reuse is off or the definition is a URL.
789
718
  */
790
719
  definitionReuploaded?: boolean;
791
720
  } | {
792
721
  status: 'error';
793
722
  error: ComputeError;
794
723
  durationMs: number;
795
- } | {
796
- status: 'superseded';
797
724
  };
798
- type SolveExecutor = (definition: SolveDefinition, dataTree: DataTree[], config: GrasshopperComputeConfig) => Promise<GrasshopperComputeResponse>;
799
725
  /**
800
- * Cache-key-aware executor. When `cacheKey` is provided, the executor solves by
801
- * reference (`pointer: cacheKey`) and falls back to a full upload on a server
802
- * cache miss. Always reports the (possibly refreshed) `cacheKey` so the
803
- * scheduler can update its definition→key map, plus whether the fast path
804
- * `missed` (for telemetry). When `cacheKey` is null it's a first solve — upload
805
- * fully and capture the key the server assigns.
806
- *
807
- * Supplied by the client (which owns the solve primitives); the scheduler stays
808
- * decoupled from the transport.
726
+ * Solves by `cacheKey` when given one, otherwise uploads. Reports the server's
727
+ * (possibly new) key and whether the full definition was uploaded (`missed`).
728
+ * The scheduler passes `null` whenever server-definition reuse is off.
809
729
  */
810
- type CacheKeyExecutor = (definition: SolveDefinition, dataTree: DataTree[], cacheKey: string | null, config: GrasshopperComputeConfig) => Promise<{
730
+ type SolveExecutor = (definition: SolveDefinition, dataTree: DataTree[], cacheKey: string | null, config: GrasshopperComputeConfig) => Promise<{
811
731
  response: GrasshopperComputeResponse;
812
732
  cacheKey: string | null;
813
733
  missed: boolean;
814
734
  }>;
815
- //#endregion
816
- //#region src/grasshopper/scheduler/solve-scheduler.d.ts
735
+ interface SchedulerSolveOptions {
736
+ signal?: AbortSignal;
737
+ /** Per-solve live-event callback. Not part of the cache key: it names where events go, not what is solved. */
738
+ selvaevents?: SelvaEventTarget;
739
+ /**
740
+ * This call's `Server-Timing` breakdown, after the client config's own `onServerTiming`.
741
+ * Never fires for a `fromCache` hit, which sends no request.
742
+ */
743
+ onServerTiming?: (timing: ServerTiming, requestId: string) => void;
744
+ }
745
+ /** What `solveDetailed()` resolves with: the response plus how this call got it. */
746
+ interface SolveDetail {
747
+ response: GrasshopperComputeResponse;
748
+ /** Served from the scheduler's response cache without a request. */
749
+ fromCache: boolean;
750
+ /** Same meaning as on {@link SolveResult}. */
751
+ definitionReuploaded?: boolean;
752
+ /** Executor wall time; `0` for a `fromCache` hit. */
753
+ durationMs: number;
754
+ }
817
755
  /**
818
- * Robust scheduler for Grasshopper solves.
819
- *
820
- * Sits between your application code and the underlying compute call,
821
- * adding:
822
- * - Configurable scheduling (latest-wins for sliders, queue for jobs)
823
- * - Backpressure (bounded queue depth + queue-wait deadline) for the miss path
824
- * - In-flight cancellation (per-call signal + cancelAll)
756
+ * Adds to a plain solve call:
757
+ * - Scheduling (latest-wins for sliders, queue for jobs)
758
+ * - Backpressure (bounded queue depth + queue-wait deadline)
759
+ * - Cancellation (per-call signal + cancelAll)
825
760
  * - Optional response caching for repeated inputs
826
- * - Lifecycle hooks for UI indicators (start / settle / superseded)
827
- * - State observability via subscribe()
828
- *
829
- * Multiple schedulers can share a single GrasshopperClient — typically one
830
- * per UI surface (e.g. one for slider scrubs, one for long-running submits).
761
+ * - Lifecycle hooks (start / settle / superseded) and state via subscribe()
831
762
  *
832
763
  * @example
833
764
  * ```ts
@@ -850,53 +781,32 @@ export declare class SolveScheduler {
850
781
  private readonly executor;
851
782
  private readonly baseConfig;
852
783
  private readonly mode;
853
- /**
854
- * Mutable via {@link setMaxConcurrent}: the compute server's worker pool can grow
855
- * or shrink while this scheduler is alive, and the dispatch loops re-read this on
856
- * every pass rather than capturing it.
857
- */
858
784
  private maxConcurrent;
859
785
  private readonly maxQueueDepth;
860
786
  private readonly queueWaitMs;
861
787
  private readonly timeoutMs;
862
788
  private readonly retry;
863
- private readonly cacheEnabled;
864
- private readonly cacheMaxBytes;
865
- private readonly cacheTtl;
866
- private readonly cacheErroredSolves;
867
789
  private readonly cache;
868
- /** Sum of `sizeBytes` across retained cache entries. */
869
- private cacheBytes;
870
- /**
871
- * Cumulative hit/miss/eviction counters for {@link cacheStats}. Deliberately
872
- * NOT reset by `clearCache()` — they measure this scheduler's whole lifetime,
873
- * so a hit rate stays comparable across a session rather than restarting at
874
- * every clear.
875
- */
876
- private cacheHits;
877
- private cacheMisses;
878
- private cacheEvictions;
879
- /** Optional cache-key-aware executor and whether server-def-cache reuse is on. */
880
- private readonly cacheKeyExecutor?;
881
790
  private readonly reuseServerDefinitionCache;
882
- /** definition identity → server cache key (`pointer`) learned from past solves. */
791
+ /** Definition hash → the server's cache key (`pointer`) for it. */
883
792
  private readonly serverCacheKeys;
793
+ /** A string definition re-sent unchanged is compared with this, far cheaper than hashing it. */
794
+ private lastStringDefinition;
884
795
  private readonly onStart?;
885
796
  private readonly onSettle?;
886
797
  private readonly onSuperseded?;
887
798
  private readonly subscribers;
888
799
  private readonly inFlight;
889
800
  private pendingForLatestWins;
890
- private readonly fifoQueue;
801
+ private readonly fifo;
891
802
  private _lastResult;
892
803
  private _lastError;
893
804
  private _lastDurationMs;
894
- /** Ordinal handed to each solve() call. */
895
805
  private solveSeq;
896
- /** seq of the solve that last wrote _lastResult/_lastError — see writeLastState. */
806
+ /** seq of the solve that last wrote `_lastResult`/`_lastError`. */
897
807
  private lastStateSeq;
898
808
  private disposed;
899
- constructor(executor: SolveExecutor, baseConfig: GrasshopperComputeConfig, options?: SolveSchedulerOptions, cacheKeyExecutor?: CacheKeyExecutor);
809
+ constructor(executor: SolveExecutor, baseConfig: GrasshopperComputeConfig, options?: SolveSchedulerOptions);
900
810
  get isSolving(): boolean;
901
811
  get hasPending(): boolean;
902
812
  get inFlightCount(): number;
@@ -911,10 +821,10 @@ export declare class SolveScheduler {
911
821
  * Raising it drains queued work immediately. Lowering it never interrupts work
912
822
  * already in flight — those finish above the new limit, and the cap applies from
913
823
  * the next dispatch. Values below 1 are clamped, since 0 would wedge the queue.
824
+ *
825
+ * @throws {ComputeError} `INVALID_CONFIG` for `NaN`, which would wedge it too.
914
826
  */
915
827
  setMaxConcurrent(value: number): void;
916
- /** Current concurrency cap — reflects any {@link setMaxConcurrent} adjustment. */
917
- getMaxConcurrent(): number;
918
828
  /** Subscribe to state changes. */
919
829
  subscribe(listener: () => void): () => void;
920
830
  private notify;
@@ -925,23 +835,16 @@ export declare class SolveScheduler {
925
835
  * - Rejects with `code: ErrorCodes.SUPERSEDED` when the call was canceled because
926
836
  * newer values arrived (latest-wins mode).
927
837
  * - Rejects with `code: ErrorCodes.ABORTED` when the call was canceled via
928
- * caller-supplied signal or `cancelAll()`.
838
+ * its own `signal` (queued or running) or `cancelAll()`.
929
839
  * - Rejects with `code: ErrorCodes.QUEUE_FULL` when `maxQueueDepth` is set and
930
840
  * the queue was already full (backpressure; `statusCode: 503`).
931
841
  * - Rejects with `code: ErrorCodes.QUEUE_TIMEOUT` when `queueWaitMs` is set and
932
842
  * the call sat queued longer than that before starting (`statusCode: 503`).
933
843
  *
934
- * Caller-supplied `signal` cancels just this call (rejects with `ABORTED`) —
935
- * including while the call is still queued, before execution starts.
936
- *
937
- * A {@link DefinitionRef} definition is keyed by its `key` (result cache and
938
- * server-pointer map alike) without materializing bytes — `load()` runs only
939
- * when an upload is unavoidable. Its immutability contract is trusted here:
940
- * a reused key serves the other content's cached solve.
941
- *
942
- * Responses served from the cache (and via `lastResult`) are shared objects,
943
- * not copies — treat them as immutable. Mutating one poisons every later
944
- * cache hit for that key.
844
+ * Don't mutate a `Uint8Array` definition after solving it. Responses served
845
+ * from the cache (and via `lastResult`) are shared objects, not copies —
846
+ * treat them as immutable. Mutating one poisons every later cache hit for
847
+ * that key.
945
848
  *
946
849
  * Partial-success contract: unlike `GrasshopperClient.solve()`, which throws
947
850
  * `COMPUTATION_ERROR` when the response carries solver errors, this RESOLVES
@@ -949,80 +852,43 @@ export declare class SolveScheduler {
949
852
  * success must not be treated as a result (see `cacheErroredSolves` for the
950
853
  * caching side of the same distinction).
951
854
  */
952
- solve(definition: SolveDefinition, dataTree: DataTree[], options?: {
953
- signal?: AbortSignal;
954
- selvaevents?: SelvaEventTarget;
955
- }): Promise<GrasshopperComputeResponse>;
956
- /**
957
- * Record last-result state, but only if no newer solve has written since —
958
- * a slow solve settling late must not overwrite the state a newer solve
959
- * (or cache hit) already published.
960
- */
855
+ solve(definition: SolveDefinition, dataTree: DataTree[], options?: SchedulerSolveOptions): Promise<GrasshopperComputeResponse>;
856
+ /** {@link solve}, also resolving with whether this call hit the cache and how long it took. */
857
+ solveDetailed(definition: SolveDefinition, dataTree: DataTree[], options?: SchedulerSolveOptions): Promise<SolveDetail>;
858
+ /** A slow solve settling late must not overwrite state a newer solve or cache hit published. */
961
859
  private writeLastState;
962
860
  /** latest-wins: supersede the pending item and abort everything in flight. */
963
861
  private supersedeCurrent;
964
- /** Settle a still-queued item as ABORTED and remove it from its queue. */
862
+ private dequeue;
965
863
  private abortQueuedItem;
966
- /**
967
- * Backpressure: settle an incoming item as QUEUE_FULL without ever enqueuing
968
- * it. The structured context lets an HTTP layer map it to 503 + Retry-After.
969
- */
864
+ /** The structured context lets an HTTP layer map QUEUE_FULL to 503 + Retry-After. */
970
865
  private shedAsQueueFull;
971
- /**
972
- * Arm the queue-wait deadline for an item about to be queued. If the item is
973
- * still waiting when it fires, it's rejected as QUEUE_TIMEOUT and removed from
974
- * the queue. Cleared on execute/settle via {@link clearQueueWaitTimer}.
975
- */
976
866
  private armQueueWaitTimer;
977
- /** Clear a queued item's wait-deadline timer, if one is pending. */
978
867
  private clearQueueWaitTimer;
979
868
  private enqueue;
980
869
  private execute;
981
- /**
982
- * Run the solve, using the server-definition-cache fast path when it's
983
- * enabled and the definition is reusable. Learns/updates the definition's
984
- * server cache key from the result so later solves can reference it.
985
- *
986
- * `definitionHash` is the {@link hashDefinition} result already computed at
987
- * `solve()` entry — threaded through rather than recomputed, so each solve
988
- * pays exactly one linear pass over the definition (issue 57).
989
- */
990
870
  private runExecutor;
871
+ private hashDefinitionMemo;
991
872
  private drainNext;
992
873
  private supersede;
993
874
  private makeAbortError;
994
875
  /**
995
- * Settle a pending/in-flight item exactly once with an error.
996
- *
997
- * A solve promise can be settled from four concurrent sources — the executor
998
- * resolving, the executor rejecting, `supersede`, and `cancelAll` — and a JS
999
- * promise silently ignores a second settle. This guard is the single place the
1000
- * settle-once invariant lives: it makes the *first* settle win and reports
1001
- * whether this call was that winner, so callers fire their own hook only when
1002
- * they actually settled. Any new settle path must go through here (or
1003
- * {@link settleSuccess}) so the guard can't be forgotten.
1004
- *
1005
- * @returns `true` if this call settled the item; `false` if it was already settled.
876
+ * The executor, `supersede`, `cancelAll`, abort and the queue limits can all race to settle one
877
+ * item, and a promise ignores a second settle silently. The first settle wins; the return value
878
+ * says whether this call won, so only the winner fires its hook. Every settle path goes through
879
+ * here or {@link settleSuccess}.
1006
880
  */
1007
881
  private settleError;
1008
882
  /**
1009
- * Settle a pending/in-flight item exactly once with a successful response.
1010
- * The success counterpart to {@link settleError}; see it for the invariant.
1011
- *
1012
- * @returns `true` if this call settled the item; `false` if it was already settled.
883
+ * Fires `onSettle` for queued items too, so a consumer counting solves in progress doesn't
884
+ * leak one. A supersede settles through {@link settleError} and fires `onSuperseded` instead.
1013
885
  */
886
+ private fail;
1014
887
  private settleSuccess;
1015
- /** Detach the queued-phase abort listener, if one is still attached. */
1016
- private removeQueuedAbortHandler;
1017
- private isAbortLikeError;
888
+ private claimSettle;
1018
889
  private normalizeExecutionError;
1019
890
  /** Cancel everything — in-flight and pending. */
1020
891
  cancelAll(): void;
1021
- private rejectAsAborted;
1022
- private readCache;
1023
- private writeCache;
1024
- /** Remove one cache entry and release its bytes from the running total. */
1025
- private dropCacheEntry;
1026
892
  clearCache(): void;
1027
893
  /**
1028
894
  * Observability snapshot of the solve cache: current size plus lifetime
@@ -1034,18 +900,10 @@ export declare class SolveScheduler {
1034
900
  * replace-in-place writes or TTL expiry, which are not capacity signals.
1035
901
  * Counters are cumulative and survive `clearCache()`.
1036
902
  */
1037
- cacheStats(): {
1038
- entries: number;
1039
- bytes: number;
1040
- hits: number;
1041
- misses: number;
1042
- evictions: number;
1043
- };
903
+ cacheStats(): CacheStats;
1044
904
  dispose(): void;
1045
905
  private runHook;
1046
906
  }
1047
- //#endregion
1048
- //#region src/grasshopper/client/grasshopper-client.d.ts
1049
907
  /**
1050
908
  * Per-call options that override the client's default ComputeConfig values.
1051
909
  *
@@ -1061,14 +919,24 @@ interface SolveOptions {
1061
919
  /** Per-solve live-event callback, forwarded onto the request body. */
1062
920
  selvaevents?: SelvaEventTarget;
1063
921
  }
922
+ /** Options for {@link GrasshopperClient.create} that shape construction, not the solves. */
923
+ interface CreateClientOptions {
924
+ /**
925
+ * Retry ladder for the liveness probe: `attempts` retries after the first probe (default 2),
926
+ * with exponential backoff from `baseDelayMs` (default 250) capped at `maxDelayMs` (default 1000).
927
+ * Independent of `config.retry`, which governs solves only.
928
+ */
929
+ probe?: {
930
+ attempts?: number;
931
+ baseDelayMs?: number;
932
+ maxDelayMs?: number;
933
+ };
934
+ }
1064
935
  /**
1065
- * GrasshopperClient provides a simple API for interacting with a Rhino Compute server and grasshopper.
936
+ * The high-level API for solving Grasshopper definitions on a Rhino Compute server.
1066
937
  *
1067
- * @public This is the recommended high-level API for Rhino Compute operations.
1068
- *
1069
- * **Security Warning:**
1070
- * Using this client in a browser environment exposes your server URL and API key to users.
1071
- * For production, use this library server-side or proxy requests through your own backend.
938
+ * In a browser, the server URL and API key are exposed to users: run it server-side, or proxy
939
+ * requests through your own backend.
1072
940
  *
1073
941
  * @example
1074
942
  * ```typescript
@@ -1078,7 +946,8 @@ interface SolveOptions {
1078
946
  * });
1079
947
  *
1080
948
  * try {
1081
- * const result = await client.solve(definitionUrl, { x: 1, y: 2 });
949
+ * const io = await client.getIO(definitionUrl);
950
+ * const result = await client.solve(definitionUrl, TreeBuilder.fromInputParams(io.inputs));
1082
951
  * } finally {
1083
952
  * await client.dispose(); // Clean up resources
1084
953
  * }
@@ -1087,43 +956,26 @@ interface SolveOptions {
1087
956
  declare class GrasshopperClient {
1088
957
  private readonly config;
1089
958
  readonly serverStats: ComputeServerStats;
959
+ private readonly schedulers;
1090
960
  private disposed;
1091
961
  /**
1092
- * Per-probe timeout for the `create()` liveness gate. A healthy `GET /` on the
1093
- * proxy answers in milliseconds; this bound only ever applies to a server that
1094
- * is not answering, where every extra second is multiplied by the retry count
1095
- * and paid by whoever is waiting on the solve.
962
+ * Per-probe timeout for the `create()` liveness gate: a healthy `GET /` answers in milliseconds,
963
+ * so this only bounds a silent server, and every extra second is paid once per retry.
1096
964
  */
1097
965
  private static readonly CREATE_PROBE_TIMEOUT_MS;
1098
966
  private constructor();
1099
967
  /**
1100
- * Creates and initializes a GrasshopperClient with server validation.
1101
- *
1102
- * The pre-flight liveness probe (a GET on the proxy root `/`) is a
1103
- * single-sample boolean gate that reads a cold or briefly-busy-but-up server
1104
- * as offline. To avoid failing construction on that transient class, the probe
1105
- * is retried with a short exponential backoff before giving up.
968
+ * Creates a client once a liveness probe (`GET /`) says the server is up. A cold server can read
969
+ * as offline, so the probe retries with a short backoff, stopping early when
970
+ * {@link classifyProbeFailure} says waiting can't change the answer. `config.signal` cancels the
971
+ * ladder.
1106
972
  *
1107
- * Retries stop early when {@link classifyProbeFailure} says waiting cannot
1108
- * change the answer — connection refused, or a 401/403. This runs in front of
1109
- * a user who clicked Solve, so the retry ladder must not spend its whole
1110
- * budget confirming that a machine is switched off.
1111
- *
1112
- * Each probe is bounded by {@link CREATE_PROBE_TIMEOUT_MS} rather than the
1113
- * stats default: a healthy `GET /` answers in milliseconds, so a longer
1114
- * per-probe timeout only multiplies the wait when the server is unreachable.
1115
- *
1116
- * @throws {ComputeError} with code NETWORK_ERROR if the server stays
1117
- * unreachable across all attempts. `context.probeVerdict` carries the
1118
- * {@link ProbeVerdict} so callers can render a specific cause.
1119
- * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid
1120
- */
1121
- static create(config: GrasshopperComputeConfig): Promise<GrasshopperClient>;
1122
- /**
1123
- * Gets the client's configuration.
1124
- * Useful for passing to lower-level functions.
973
+ * @throws {ComputeError} `NETWORK_ERROR` if the server stays unreachable across all attempts.
974
+ * `context.probeVerdict` carries the verdict, so a caller can render a specific cause.
975
+ * @throws {ComputeError} `ABORTED` if `config.signal` fires first.
976
+ * @throws {ComputeError} `INVALID_CONFIG` if the configuration is invalid.
1125
977
  */
1126
- getConfig(): GrasshopperComputeConfig;
978
+ static create(config: GrasshopperComputeConfig, options?: CreateClientOptions): Promise<GrasshopperClient>;
1127
979
  /**
1128
980
  * Get input/output parameters of a Grasshopper definition.
1129
981
  */
@@ -1153,34 +1005,31 @@ declare class GrasshopperClient {
1153
1005
  * @example
1154
1006
  * ```ts
1155
1007
  * const sliderScheduler = client.createScheduler({ mode: 'latest-wins' });
1156
- * const submitScheduler = client.createScheduler({ mode: 'queue', timeoutMs: 0, retry: { attempts: 1 } });
1008
+ * const submitScheduler = client.createScheduler({ mode: 'queue', timeoutMs: 0 });
1157
1009
  * ```
1158
1010
  */
1159
1011
  createScheduler(options?: SolveSchedulerOptions): SolveScheduler;
1160
- /**
1161
- * Disposes of client resources.
1162
- * Call this when you're done using the client.
1163
- */
1012
+ /** Disposes of client resources, including every scheduler it created. */
1164
1013
  dispose(): Promise<void>;
1165
- /**
1166
- * Ensures the client hasn't been disposed.
1167
- */
1168
1014
  private ensureNotDisposed;
1169
- /**
1170
- * Validates and normalizes a compute configuration.
1171
- *
1172
- * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid
1173
- */
1174
- private normalizeComputeConfig;
1175
1015
  }
1176
- //#endregion
1177
- //#region src/grasshopper/io/output/response-processors.d.ts
1016
+ /**
1017
+ * The part of rhino3dm the decoders call. Structural so the public types don't name `rhino3dm`, a
1018
+ * dev dependency here. A loaded `RhinoModule` satisfies it.
1019
+ */
1020
+ interface RhinoRuntime {
1021
+ Point: new (location: number[]) => unknown;
1022
+ Line: new (from: number[], to: number[]) => unknown;
1023
+ CommonObject: {
1024
+ decode(jsonObject: object): unknown;
1025
+ };
1026
+ }
1178
1027
  interface ParsedContext {
1179
1028
  [key: string]: any;
1180
1029
  }
1181
1030
  interface GetValuesOptions {
1182
1031
  parseValues?: boolean;
1183
- rhino?: any;
1032
+ rhino?: RhinoRuntime;
1184
1033
  /**
1185
1034
  * If true, only include values of type System.String in the result.
1186
1035
  * Non-string types are filtered out.
@@ -1198,41 +1047,13 @@ interface GetValuesResult<T = ParsedContext> {
1198
1047
  */
1199
1048
  dispose: () => void;
1200
1049
  }
1201
- //#endregion
1202
- //#region src/grasshopper/client/grasshopper-response-processor.d.ts
1203
- /**
1204
- * High-level wrapper for interacting with Grasshopper Compute responses.
1205
- *
1206
- * This class exposes a clean, consistent API for accessing parsed values,
1207
- * geometry, and produced files. It is designed to be the primary interface
1208
- * when working with Grasshopper results in client applications.
1209
- */
1050
+ /** Reads parsed values and produced files out of one compute response. */
1210
1051
  declare class GrasshopperResponseProcessor {
1211
- /**
1212
- * The raw compute response. Public so callers can hand it to a renderer-side parser — e.g.
1213
- * `getThreeMeshesFromComputeResponse` in `@selvajs/visualization/parse`, which replaced this
1214
- * class's former `extractMeshesFromResponse()`.
1215
- */
1052
+ /** Hand it to a mesh parser such as `@selvajs/visualization/parse`. */
1216
1053
  readonly response: GrasshopperComputeResponse;
1217
- /**
1218
- * Retained for call-signature compatibility. Its only consumer was the removed
1219
- * `extractMeshesFromResponse()`, which merged it into the parse options; pass `debug`
1220
- * directly to the parser in `@selvajs/visualization/parse` instead.
1221
- */
1222
- readonly debug: boolean;
1223
1054
  constructor(
1224
- /**
1225
- * The raw compute response. Public so callers can hand it to a renderer-side parser — e.g.
1226
- * `getThreeMeshesFromComputeResponse` in `@selvajs/visualization/parse`, which replaced this
1227
- * class's former `extractMeshesFromResponse()`.
1228
- */
1229
- response: GrasshopperComputeResponse,
1230
- /**
1231
- * Retained for call-signature compatibility. Its only consumer was the removed
1232
- * `extractMeshesFromResponse()`, which merged it into the parse options; pass `debug`
1233
- * directly to the parser in `@selvajs/visualization/parse` instead.
1234
- */
1235
- debug?: boolean);
1055
+ /** Hand it to a mesh parser such as `@selvajs/visualization/parse`. */
1056
+ response: GrasshopperComputeResponse);
1236
1057
  /**
1237
1058
  * Extract all values in the response.
1238
1059
  *
@@ -1269,26 +1090,11 @@ declare class GrasshopperResponseProcessor {
1269
1090
  } | {
1270
1091
  byId: string;
1271
1092
  }, options?: GetValuesOptions): any;
1272
- /**
1273
- * REMOVED: `extractMeshesFromResponse()`. Mesh decoding lives in `@selvajs/visualization` now, so
1274
- * this package stays pure solve/data and carries no `three` dependency. Call the parser directly
1275
- * with the raw response:
1276
- *
1277
- * ```ts
1278
- * import { getThreeMeshesFromComputeResponse } from '@selvajs/visualization/parse';
1279
- *
1280
- * const meshes = await getThreeMeshesFromComputeResponse(processor.response, { rhino });
1281
- * scene.add(...meshes);
1282
- * ```
1283
- */
1284
- private getFileData;
1285
1093
  /**
1286
1094
  * Download all files generated by Grasshopper, optionally including
1287
1095
  * additional user-provided files.
1288
1096
  *
1289
- * Files are grouped under the specified folder name when downloaded.
1290
- *
1291
- * @param folderName - Name for the download directory.
1097
+ * @param folderName - Name of the zip archive, without the extension.
1292
1098
  * @param additionalFiles - Extra files to package (single file, array, or null).
1293
1099
  *
1294
1100
  * @example
@@ -1298,14 +1104,12 @@ declare class GrasshopperResponseProcessor {
1298
1104
  *
1299
1105
  * @example
1300
1106
  * ```ts
1301
- * const extra = { name: 'notes.txt', data: 'Example' };
1107
+ * const extra = { fileName: 'notes.txt', filePath: 'https://example.com/notes.txt' };
1302
1108
  * await processor.getAndDownloadFiles('project', extra);
1303
1109
  * ```
1304
1110
  */
1305
1111
  getAndDownloadFiles(folderName: string, additionalFiles?: FileBaseInfo[] | FileBaseInfo | null): Promise<void>;
1306
1112
  }
1307
- //#endregion
1308
- //#region src/grasshopper/scheduler/stable-hash.d.ts
1309
1113
  /**
1310
1114
  * Deterministic stringify with sorted keys. {a:1,b:2} and {b:2,a:1} produce
1311
1115
  * the same string. Safely handles circular references and non-finite numbers.
@@ -1313,102 +1117,51 @@ declare class GrasshopperResponseProcessor {
1313
1117
  * The output is a cache key, so the invariant that matters is: two payloads
1314
1118
  * that serialize differently on the wire must stringify differently here
1315
1119
  * (false misses are harmless; false hits serve the wrong cached solve).
1120
+ * Non-JSON values get unquoted markers (`{u8:…}`, `[Circular]`) that no JSON
1121
+ * value stringifies to, so a plain object can't pose as one.
1316
1122
  */
1317
1123
  export declare function stableStringify(value: unknown): string;
1318
- /**
1319
- * Hash definition and data tree into a stable cache key.
1320
- *
1321
- * The definition is the *identity* of what we solve, so a binary definition is
1322
- * hashed over its full content (`fnv1aBytes`) — a length-only or sampled key
1323
- * would let two different `.gh` files collide and serve one's cached solve for
1324
- * the other. `.gh` files are small enough that a single linear pass is
1325
- * negligible. A {@link DefinitionRef} is keyed by its `key` alone — the
1326
- * caller-declared identity of immutable bytes — so no bytes are materialized
1327
- * or hashed at all.
1328
- *
1329
- * The key keeps the definition and tree hashes as separate parts rather than
1330
- * collapsing them into one 32-bit hash: a single FNV pass over the pair would
1331
- * birthday-collide quadratically in cache size, while requiring both 32-bit
1332
- * parts (plus lengths) to collide at once makes that negligible.
1333
- */
1124
+ /** The scheduler's solve-cache key for a definition and its input tree. */
1334
1125
  export declare function hashSolveInput(definition: SolveDefinition, dataTree: unknown): string;
1335
1126
  /**
1336
- * Stable identity of a definition alone (no inputs) — used to key the
1337
- * server-cache-key map so the same definition reuses its `pointer` across solves
1338
- * with different inputs. Same full-content hashing as {@link hashSolveInput}: a
1339
- * binary definition is hashed over all its bytes so two distinct `.gh` files of
1340
- * equal length can't share a cache key. A {@link DefinitionRef} is keyed by its
1341
- * `key` verbatim (refs are short identities like UUIDs, safe as Map keys) —
1342
- * its immutability contract makes the key equivalent to a content hash.
1127
+ * A definition's identity, without its inputs. Strings and bytes are hashed over their full
1128
+ * content; a `DefinitionRef` uses its `key`.
1343
1129
  */
1344
1130
  export declare function hashDefinition(definition: SolveDefinition): string;
1345
- //#endregion
1346
- //#region src/grasshopper/solve.d.ts
1347
1131
  /**
1348
- * Runs a Rhino Compute job using the provided tree prototypes and Grasshopper definition.
1349
- *
1350
- * @public Use this for direct compute control. For high-level API, use `GrasshopperClient.solve()`.
1132
+ * Run one solve. `GrasshopperClient.solve()` adds input validation and throws on solver errors.
1351
1133
  *
1352
- * @param dataTree - An array of `DataTree` objects representing the input data for the compute job.
1353
- * @param definition - The Grasshopper definition, which can be:
1354
- * - A URL string (e.g., 'https://example.com/definition.gh')
1355
- * - A base64-encoded string of the .gh file
1356
- * - A plain string (will be base64-encoded)
1357
- * - A Uint8Array of the .gh file (will be base64-encoded)
1358
- * - A `DefinitionRef` (bytes are loaded via `ref.load()` for the upload)
1359
- * @param config - Compute configuration (server URL, API key, etc. along with optional timeout, units, etc.)
1360
- * @returns An object containing the compute result and extracted file data.
1134
+ * @param definition - A URL (sent as a pointer), base64 `.gh` content, raw `.gh` bytes, or a
1135
+ * `DefinitionRef` (loaded for the upload). Any other string is encoded as the definition text.
1136
+ * @returns The compute response, without the server's `pointer` and echoed `algo`.
1361
1137
  *
1362
1138
  * @example
1363
- * // Using a URL
1364
1139
  * await solveGrasshopperDefinition(trees, 'https://example.com/definition.gh', config);
1365
- *
1366
- * // Using a base64 string
1367
- * await solveGrasshopperDefinition(trees, 'UEsDBBQAAAAIAL...', config);
1368
- *
1369
- * // Using binary data
1370
- * const fileData = new Uint8Array([...]);
1371
- * await solveGrasshopperDefinition(trees, fileData, config);
1140
+ * await solveGrasshopperDefinition(trees, new Uint8Array(await readFile('model.gh')), config);
1372
1141
  */
1373
1142
  export declare function solveGrasshopperDefinition(dataTree: DataTree[], definition: SolveDefinition, config: GrasshopperComputeConfig): Promise<GrasshopperComputeResponse>;
1374
- //#endregion
1375
- //#region src/grasshopper/io/definition-io.d.ts
1376
1143
  /**
1377
- * Fetches raw input/output schemas from a Grasshopper definition.
1144
+ * Fetches a definition's input/output schemas without per-type parsing.
1378
1145
  *
1379
- * "Raw" means no per-type parsing (no default coercion, no discriminated-union
1380
- * typing) — but NOT byte-for-byte wire data. The response IS normalized:
1381
- * per-param field KEYS are canonicalized to camelCase across server branches
1382
- * ({@link normalizeInputSchema} / {@link normalizeOutputSchema}, with honest
1383
- * fallbacks for missing required fields), missing/non-array `inputs`/`outputs`
1384
- * coerce to `[]`, and server load diagnostics surface as
1385
- * `loadWarnings`/`loadErrors` (coerced to strings). Field VALUES — notably
1386
- * `default` and the `values` dropdown-label map — pass through verbatim.
1146
+ * Field KEYS are canonicalized to camelCase and missing required fields get a fallback; a missing
1147
+ * or non-array `inputs` / `outputs` becomes `[]`. Field VALUES, notably `default` and the `values`
1148
+ * label map, pass through verbatim.
1387
1149
  *
1388
- * @param definition - The Grasshopper definition (URL, base64 string, or Uint8Array)
1389
- * @param config - Compute configuration (server URL, API key, etc.)
1390
- * @returns Key-normalized inputs and outputs with no per-type processing
1391
1150
  * @throws {ComputeError} If fetch fails or response is invalid
1392
- *
1393
- * @public Use `fetchParsedDefinitionIO()` for processed, type-safe inputs
1394
1151
  */
1395
1152
  export declare function fetchDefinitionIO(definition: string | Uint8Array, config: ComputeConfig): Promise<GrasshopperParsedIORaw>;
1396
1153
  /**
1397
- * Fetches and processes input/output schemas from a Grasshopper definition.
1398
- * Returns strongly-typed, validated input parameters ready for use.
1399
- *
1400
- * @public This is the recommended way to fetch definition I/O schemas.
1154
+ * Fetches a definition's input/output schemas and parses each input into its
1155
+ * typed `InputParam`. An input that fails to parse gets a safe default
1156
+ * and an entry in `parseErrors`.
1401
1157
  *
1402
- * @param definition - The Grasshopper definition (URL, base64 string, or Uint8Array)
1403
- * @param config - Compute configuration (server URL, API key, etc.)
1404
- * @returns Processed inputs with discriminated union types and outputs
1405
1158
  * @throws {ComputeError} If fetch fails or response is invalid
1406
1159
  *
1407
1160
  * @example
1408
1161
  * ```typescript
1409
1162
  * const { inputs, outputs } = await fetchParsedDefinitionIO(
1410
1163
  * 'https://example.com/definition.gh',
1411
- * { serverUrl: 'https://compute.rhino3d.com', apiKey: 'YOUR_KEY' }
1164
+ * { serverUrl: 'http://localhost:6500', apiKey: 'YOUR_KEY' }
1412
1165
  * );
1413
1166
  *
1414
1167
  * // Inputs are now strongly typed
@@ -1420,8 +1173,6 @@ export declare function fetchDefinitionIO(definition: string | Uint8Array, confi
1420
1173
  * ```
1421
1174
  */
1422
1175
  export declare function fetchParsedDefinitionIO(definition: string | Uint8Array, config: ComputeConfig): Promise<GrasshopperParsedIO>;
1423
- //#endregion
1424
- //#region src/grasshopper/io/schema-endpoint.d.ts
1425
1176
  /** One entry of the schema endpoint's response, after wrapper-key normalization. */
1426
1177
  interface SchemaEndpointResult<TSchema = unknown> {
1427
1178
  /** Schemas embedded in that file. Absent when the file yielded none. */
@@ -1433,101 +1184,79 @@ interface SchemaEndpointResult<TSchema = unknown> {
1433
1184
  * Read compute's `/grasshopper/schema` body: `[{ FileName, Schemas }]` per
1434
1185
  * uploaded file, or a bare object for a single file.
1435
1186
  *
1436
- * Use this rather than unwrapping the body by hand. The wrapper's casing varies
1437
- * by server branch — mcneel serializes `FileName`/`Schemas`, the VektorNode fork
1438
- * `fileName`/`schemas` — so a fixed-key read silently yields `undefined` against
1439
- * half the servers, and the endpoint answers 200 either way. The failure looks
1440
- * like "this definition has no schemas", which sends you debugging the wrong
1441
- * thing entirely.
1442
- *
1443
- * A blanket key-rewrite (the old `camelcaseKeys` approach) is NOT the fix: it
1444
- * reaches inside the schemas and mangles user-authored names — `"Display3d"` →
1445
- * `"display3d"`, value-list labels like `"Option A"` → `"optionA"`. Only the two
1446
- * wrapper keys are read here; schema CONTENTS pass through untouched, which is
1447
- * why `TSchema` is a pass-through type parameter this module never inspects.
1187
+ * Use this rather than unwrapping the body by hand: the wrapper keys' casing
1188
+ * varies by server branch, and a fixed-key read yields "no schemas" with a 200.
1189
+ * Only the two wrapper keys are read; schema contents pass through untouched.
1448
1190
  *
1449
1191
  * @typeParam TSchema - Your schema type (e.g. `UISchema`). Not inspected.
1450
- * @param raw - The parsed JSON body.
1451
1192
  */
1452
1193
  export declare function readSchemaResults<TSchema = unknown>(raw: unknown): SchemaEndpointResult<TSchema>[];
1453
- //#endregion
1454
- //#region src/grasshopper/io/normalize-ui-schema.d.ts
1455
1194
  /**
1456
- * Canonicalize a `/grasshopper/schema` body's key CASING.
1457
- *
1458
- * ## Why this exists
1459
- *
1460
- * Compute serializes the plugin's `UISchema` POCO, whose camelCase wire names
1461
- * live in Newtonsoft `[JsonProperty]` attributes. `Selva.gha` ILRepack-merges
1462
- * Newtonsoft into itself, so those attributes have type
1463
- * `Selva!Newtonsoft.Json.JsonPropertyAttribute`. When the serializer that runs is
1464
- * compute's OWN Newtonsoft assembly, it does not recognize that type as its own
1465
- * attribute, reads no attributes at all, and falls back to raw CLR member names —
1466
- * emitting `Inputs`/`Layout`/`SchemaVersion`.
1467
- *
1468
- * Nothing throws on the wire. Every consumer reads `schema.inputs` as
1469
- * `undefined`, so the definition renders with no inputs, and `schemaVersion`
1470
- * reads `undefined` — which also silently disables the newer-plugin version gate.
1195
+ * Canonicalize a `/grasshopper/schema` body's key casing.
1471
1196
  *
1472
- * This is the schema-body counterpart to {@link normalizeInputSchema}, which
1473
- * solves the same split for the `/io` endpoint's param records.
1197
+ * Compute serializes the plugin's `UISchema`, whose camelCase wire names live in
1198
+ * Newtonsoft `[JsonProperty]` attributes. `Selva.gha` ILRepack-merges Newtonsoft,
1199
+ * so those attributes are `Selva!Newtonsoft.Json.JsonPropertyAttribute`. When
1200
+ * compute's OWN Newtonsoft serializes, it doesn't recognize them, reads no
1201
+ * attributes, and emits raw CLR names (`Inputs`, `SchemaVersion`). Nothing throws:
1202
+ * `schema.inputs` reads `undefined`, and so does the `schemaVersion` gate.
1474
1203
  *
1475
- * ## Why a casing rule, and not a key allowlist
1476
- *
1477
- * The wire names are the CLR names run through Newtonsoft's camelCase strategy,
1478
- * so reproducing that strategy covers every key. An allowlist would have to
1479
- * enumerate every structural key in `UISchema` and drift out of date each time
1480
- * the schema gains a field, failing silently and in exactly this way again.
1481
- *
1482
- * ## What it does NOT touch
1483
- *
1484
- * `options`, `defaultOptions` and `values` hold USER-AUTHORED keys — dropdown
1485
- * labels like `"Standart Beschichtung"`, `"Use 10 Elements instead"`, `"True"`.
1486
- * Those maps are copied verbatim; only the key naming them is canonicalized.
1487
- * Rewriting their contents is the exact regression that motivated deleting the
1488
- * old global `camelcaseKeys` pass (see `read-field.ts`), which mangled
1489
- * `"Option A"` into `"optionA"` and silently changed what a definition solved
1490
- * with.
1491
- *
1492
- * Values are never inspected — only object keys are rewritten.
1204
+ * A casing rule, not a key allowlist: the wire names are the CLR names run
1205
+ * through Newtonsoft's camelCase strategy, so reproducing it covers every key
1206
+ * without drifting each time `UISchema` gains a field.
1493
1207
  */
1494
1208
  /**
1495
- * Returns a schema whose structural keys are camelCase, regardless of which
1496
- * Newtonsoft serialized it. A body that is already camelCase passes through
1497
- * unchanged in shape (it is still rebuilt, so the result is always a fresh
1498
- * object the caller may mutate).
1499
- *
1500
- * Safe to call on any parsed JSON value; non-objects are returned as-is.
1209
+ * Returns a schema whose structural keys are camelCase, whichever Newtonsoft
1210
+ * serialized it. Always a fresh object the caller may mutate; non-objects are
1211
+ * returned as-is.
1501
1212
  */
1502
1213
  export declare function normalizeUISchemaCasing<T>(raw: T): T;
1503
- //#endregion
1504
- //#region src/grasshopper/io/input/input-processors.d.ts
1505
1214
  /**
1506
1215
  * Parse one raw Grasshopper input schema into a typed {@link InputParam}.
1507
- * Validation failures are swallowed and replaced with a safe default; use
1508
- * {@link processInputWithError} to receive them.
1216
+ * A validation failure is swallowed and replaced with a safe default.
1509
1217
  */
1510
1218
  export declare function processInput(rawInput: InputParamSchema): InputParam;
1511
- //#endregion
1512
- //#region src/grasshopper/data-tree/data-tree.d.ts
1513
1219
  /**
1514
- * Value types that can be stored in a DataTree
1220
+ * Sentinel a Rhino geometry decode returns when a decode was attempted
1221
+ * and failed hard (a registered decoder or `CommonObject.decode` threw). Carries
1222
+ * the original payload so callers can log or retry. Use
1223
+ * {@link isRhinoDecodeError} to detect it.
1515
1224
  */
1516
- type DataTreeValue = string | number | boolean | object | null;
1517
- /**
1518
- * Simple data item for compute requests (not to be confused with DataItem interface for responses).
1519
- * Note: While TypeScript defines this as string, Rhino Compute accepts boolean/number primitives in JSON.
1520
- */
1521
- interface ComputeDataItem {
1522
- data: string | boolean | number;
1225
+ interface RhinoDecodeError {
1226
+ __decodeError: true;
1227
+ /** The Rhino type name the failed decode was attempted for. */
1228
+ type: string;
1229
+ /** The original (undecoded) payload. */
1230
+ raw: unknown;
1523
1231
  }
1232
+ /** Type guard for the {@link RhinoDecodeError} sentinel. */
1233
+ export declare function isRhinoDecodeError(value: unknown): value is RhinoDecodeError;
1524
1234
  /**
1525
- * InnerTree data structure for compute requests.
1235
+ * Free every rhino3dm WASM object reachable from `value`.
1236
+ *
1237
+ * rhino3dm objects are emscripten bindings — JS GC never reclaims their WASM
1238
+ * heap allocation, so everything decoded from a solve response (`getValues`,
1239
+ * `getValue`) must be deleted explicitly or the heap
1240
+ * grows monotonically across solves (e.g. a UI decoding per slider tick).
1241
+ *
1242
+ * Walks arrays and plain objects recursively; anything exposing a `delete()`
1243
+ * method is treated as a WASM binding and freed (skipped if already deleted).
1244
+ * Safe to call more than once and on values containing no WASM objects.
1526
1245
  */
1527
- type ComputeInnerTreeData = { [path in DataTreePath]: ComputeDataItem[]; };
1246
+ export declare function disposeRhinoObjects(value: unknown): void;
1247
+ type DataTreeValue = string | number | boolean | object | null;
1248
+ /** One input's value for {@link TreeBuilder.fromValues}. */
1249
+ interface TreeValueEntry {
1250
+ paramName: string;
1251
+ /** Matched case-insensitively. */
1252
+ paramType: string;
1253
+ /** A scalar, an array (branch `{0}`), or a branch-path map like `{ '{0;1}': [1, 2] }`. */
1254
+ value: unknown;
1255
+ minimum?: number | null;
1256
+ maximum?: number | null;
1257
+ }
1528
1258
  /**
1529
- * Standalone TreeBuilder class for constructing Grasshopper TreeBuilder structures.
1530
- * Does not depend on RhinoCompute library.
1259
+ * Builds the Grasshopper data trees a solve takes as input.
1531
1260
  *
1532
1261
  * @example
1533
1262
  * ```ts
@@ -1541,247 +1270,66 @@ export declare class TreeBuilder {
1541
1270
  private innerTree;
1542
1271
  private paramName;
1543
1272
  constructor(paramName: string);
1544
- /**
1545
- * Append values to a specific path in the tree.
1546
- *
1547
- * @param path - Array of integers representing the branch path (e.g., [0], [0, 1])
1548
- * @param items - Values to append at this path
1549
- * @returns this for method chaining
1550
- */
1273
+ /** Append values to the branch at `path` (`[0, 1]` is `{0;1}`). A `null` item is skipped. */
1551
1274
  append(path: number[], items: DataTreeValue[]): this;
1552
- /**
1553
- * Append a single value to a path.
1554
- *
1555
- * @param path - Branch path
1556
- * @param item - Single value to append
1557
- * @returns this for method chaining
1558
- */
1559
- appendSingle(path: number[], item: DataTreeValue): this;
1560
- /**
1561
- * Set values from a DataTreeDefault structure.
1562
- * Replaces any existing tree data.
1563
- *
1564
- * @param treeData - TreeBuilder structure with path keys like "{0;1}"
1565
- * @returns this for method chaining
1566
- */
1275
+ /** Replace the tree with a branch-path map like `{ '{0;1}': [1, 2] }`. */
1567
1276
  fromDataTreeDefault(treeData: DataTreeDefault): this;
1568
- /**
1569
- * Append flattened values to path [0].
1570
- * Useful for simple flat inputs.
1571
- *
1572
- * @param values - Single value or array of values
1573
- * @returns this for method chaining
1574
- */
1277
+ /** Append a value, or each value of an array, to branch `{0}`. */
1575
1278
  appendFlat(values: DataTreeValue | DataTreeValue[]): this;
1576
- /**
1577
- * Get the flattened list of all values in the tree.
1578
- *
1579
- * @returns Array of all values across all branches
1580
- */
1581
- flatten(): DataTreeValue[];
1582
- /**
1583
- * Get all paths in the tree.
1584
- *
1585
- * @returns Array of path strings
1586
- */
1587
- getPaths(): DataTreePath[];
1588
- /**
1589
- * Get values at a specific path.
1590
- *
1591
- * @param path - Path to retrieve values from
1592
- * @returns Array of values or undefined if path doesn't exist
1593
- */
1594
- getPath(path: number[]): DataTreeValue[] | undefined;
1595
- /**
1596
- * Convert to format compatible with Grasshopper Compute API.
1597
- *
1598
- * @returns InnerTree object ready for compute
1599
- */
1279
+ /** The tree in the shape a solve request sends. */
1600
1280
  toComputeFormat(): DataTree;
1601
1281
  /**
1602
- * Get the raw InnerTree data structure.
1603
- *
1604
- * @returns InnerTree data
1605
- */
1606
- getInnerTree(): ComputeInnerTreeData;
1607
- /**
1608
- * Get the parameter name.
1609
- *
1610
- * @returns Parameter name
1611
- */
1612
- getParamName(): string;
1613
- /**
1614
- * Create DataTrees from an array of InputParam definitions.
1615
- * Handles tree access, numeric constraints, and value parsing.
1616
- *
1617
- * @param inputs - Array of input parameter definitions
1618
- * @returns Array of InnerTree instances ready for compute
1619
- *
1620
- * @example
1621
- * ```ts
1622
- * const trees = TreeBuilder.fromInputParams(inputs);
1623
- * ```
1282
+ * One tree per input that has a default, keyed by its nickname. Defaults are coerced and
1283
+ * clamped by the same rules as {@link TreeBuilder.fromValues}.
1624
1284
  */
1625
1285
  static fromInputParams(inputs: InputParam[]): DataTree[];
1626
1286
  /**
1627
- * Create a TreeBuilder from a single InputParam.
1628
- *
1629
- * @param input - Input parameter definition
1630
- * @returns InnerTree ready for compute or undefined if value is invalid
1287
+ * One tree per entry, coercing UI values the way Grasshopper reads them. Number and Integer
1288
+ * items go through the numeric coercion (Integer rounds) and are clamped to
1289
+ * `minimum`/`maximum`; Boolean items also accept `'true'`/`'false'`; every other type passes
1290
+ * through. An item that fails coercion is dropped with a warning. An entry left with no items
1291
+ * is omitted, so Grasshopper uses its own value; `''` is an item, not an empty value.
1631
1292
  */
1632
- static fromInputParam(input: InputParam): DataTree | undefined;
1293
+ static fromValues(entries: TreeValueEntry[]): DataTree[];
1294
+ private static branchesOf;
1295
+ /** `null` means the item was rejected. */
1296
+ private static itemCoercer;
1633
1297
  /**
1634
- * Set or replace a parameter value within a TreeBuilder or InnerTree array.
1635
- *
1636
- * Supports both high-level `DataTree[]` instances and low-level `InnerTree[]` format.
1637
- *
1638
- * **Architecture Note:**
1639
- * - Use with `DataTree[]` when building/modifying before computation
1640
- * - Use with `InnerTree[]` when modifying compute API results
1641
- * - `DataTree` is the high-level builder; `InnerTree` is the Rhino Compute format
1642
- *
1643
- * Copy-on-write: returns a new array; the caller's array is never mutated.
1644
- *
1645
- * @overload For TreeBuilder instances (high-level builder)
1646
- * @param trees - Array of TreeBuilder instances to read from (not mutated)
1647
- * @param paramName - The parameter name to set or replace
1648
- * @param newValue - The new value (scalar, array, or TreeBuilder structure)
1649
- * @returns A new TreeBuilder array with the updated parameter
1650
- *
1651
- * @overload For compiled InnerTree (low-level API format)
1652
- * @param trees - The compiled InnerTree array (typically from `client.solve()`; not mutated)
1653
- * @param paramName - The parameter name to set or replace
1654
- * @param newValue - The new value (scalar, array, or TreeBuilder structure)
1655
- * @returns A new InnerTree array with the updated parameter
1656
- *
1657
- * @example
1658
- * ```ts
1659
- * // With TreeBuilder instances (high-level)
1660
- * let trees = [new TreeBuilder('X'), new TreeBuilder('Y')];
1661
- * trees = TreeBuilder.replaceTreeValue(trees, 'X', 42);
1662
- * const result = await client.solve(definitionUrl,
1663
- * trees.map(t => t.toComputeFormat())
1664
- * );
1665
- * ```
1666
- *
1667
- * @example
1668
- * ```ts
1669
- * // With InnerTree format (low-level, from API)
1670
- * let trees = await client.solve(definitionUrl, initialInputs);
1671
- * trees = TreeBuilder.replaceTreeValue(trees, 'X', 42);
1672
- * trees = TreeBuilder.replaceTreeValue(trees, 'Y', [1, 2, 3]);
1673
- * ```
1298
+ * Set or add `paramName`'s tree to `newValue`: a scalar, an array (branch `{0}`), or a
1299
+ * branch-path map. Copy-on-write: returns a new array and leaves the caller's untouched, so
1300
+ * assign the result.
1674
1301
  */
1675
- static replaceTreeValue(trees: TreeBuilder[], paramName: string, newValue: DataTreeValue): TreeBuilder[];
1676
1302
  static replaceTreeValue(trees: DataTree[], paramName: string, newValue: DataTreeValue): DataTree[];
1303
+ private static buildFromValue;
1677
1304
  /**
1678
- * Build a TreeBuilder from a single value, dispatching on shape:
1679
- * DataTreeDefault structure, array, or scalar.
1305
+ * `paramName`'s whole tree from a solve result's `values`, keyed by branch path (`'{0;1}'`).
1306
+ * `null` when no tree has that param, an empty `Map` when it has no branches. An item without
1307
+ * `data` reads as `null` in place, so indices don't shift.
1680
1308
  */
1681
- private static buildFromValue;
1309
+ static getTree(trees: DataTree[], paramName: string): Map<DataTreePath, DataTreeValue[]> | null;
1682
1310
  /**
1683
- * Extract a value from a TreeBuilder or InnerTree array by parameter name.
1684
- *
1685
- * Automatically unwraps single values for convenience.
1686
- * Works with both high-level `DataTree[]` instances and low-level `InnerTree[]` format.
1311
+ * `paramName`'s value when its tree has a single branch: one item unwrapped (`5`, not `[5]`),
1312
+ * several as an array. `null` for a missing param or an empty tree.
1687
1313
  *
1688
- * **Architecture Note:**
1689
- * - Use with `DataTree[]` to read builder instances
1690
- * - Use with `InnerTree[]` to read compute API responses
1691
- * - Return behavior is consistent across both formats
1692
- *
1693
- * **Return Value Behavior:**
1694
- * - Single value → unwrapped (returns `5` not `[5]`)
1695
- * - Multiple values → array of values
1696
- * - Not found → `null`
1697
- *
1698
- * @overload For TreeBuilder instances
1699
- * @param trees - Array of TreeBuilder instances to read from
1700
- * @param paramName - The parameter name to retrieve
1701
- * @returns The unwrapped value, array of values, or null if parameter not found
1702
- *
1703
- * @overload For compiled InnerTree
1704
- * @param trees - The compiled InnerTree array (typically from `client.solve()`)
1705
- * @param paramName - The parameter name to retrieve
1706
- * @returns The unwrapped value, array of values, or null if parameter not found
1707
- *
1708
- * @example
1709
- * ```ts
1710
- * // With TreeBuilder instances
1711
- * const trees = [new TreeBuilder('X'), new TreeBuilder('Y')];
1712
- * trees[0].appendFlat(42);
1713
- * const x = TreeBuilder.getTreeValue(trees, 'X'); // Returns 42
1714
- * ```
1715
- *
1716
- * @example
1717
- * ```ts
1718
- * // With InnerTree from compute results
1719
- * const result = await client.solve(definitionUrl, inputs);
1720
- * const x = TreeBuilder.getTreeValue(result, 'X'); // Returns 42 (not [42])
1721
- * const points = TreeBuilder.getTreeValue(result, 'Points'); // Returns [point1, point2, ...]
1722
- * ```
1314
+ * @throws {ComputeError} `INVALID_INPUT` when the tree has more than one branch; read those
1315
+ * with {@link TreeBuilder.getTree}.
1723
1316
  */
1724
- static getTreeValue(trees: TreeBuilder[], paramName: string): DataTreeValue | null;
1725
1317
  static getTreeValue(trees: DataTree[], paramName: string): DataTreeValue | null;
1318
+ private static paramNameOf;
1726
1319
  /**
1727
- * Read all values for `paramName` across every branch of the matching builder.
1728
- * Returns null when the builder isn't found.
1729
- */
1730
- private static readFromBuilders;
1731
- /**
1732
- * Read values from the first branch of the matching compiled InnerTree
1733
- * (multi-branch responses are not flattened — current semantics, pinned by
1734
- * the "reads from the first branch path only" test).
1735
- */
1736
- private static readFromDataTrees;
1737
- /**
1738
- * Parse a TreeBuilder path string like "{0;1;2}" into [0, 1, 2].
1739
- * Negative indices ("{-1;2}") and the root path "{}" are valid.
1320
+ * Parse a branch path like `"{0;1;2}"` into `[0, 1, 2]`.
1740
1321
  *
1741
- * @param pathStr - Path string
1742
- * @returns Array of path indices
1743
- * @throws {ComputeError} `INVALID_INPUT` when the path string is not a valid
1744
- * Grasshopper branch path. Malformed keys must never silently collapse to a
1745
- * default branch — two distinct unparseable keys would merge their items
1746
- * into one branch.
1322
+ * @throws {ComputeError} `INVALID_INPUT` for a malformed path, rather than
1323
+ * collapsing it to a default branch where two bad keys would merge.
1747
1324
  */
1748
1325
  static parsePathString(pathStr: string): number[];
1749
- /**
1750
- * Format a path array into TreeBuilder path string format.
1751
- *
1752
- * @param path - Path as number array
1753
- * @returns Formatted path string like "{0;1;2}"
1754
- */
1326
+ /** `[0, 1, 2]` → `"{0;1;2}"`. */
1755
1327
  static formatPathString(path: number[]): DataTreePath;
1756
- /** Apply numeric constraints to all tree values. */
1757
- private applyNumericConstraints;
1758
- /**
1759
- * Serialize a value for compute requests.
1760
- * Preserves booleans and numbers as primitives for proper Grasshopper parameter handling.
1761
- */
1328
+ /** Booleans and numbers stay primitives: Grasshopper reads them typed. */
1762
1329
  private static serializeValue;
1763
- /**
1764
- * Deserialize a value back to its original type.
1765
- * Handles both string-encoded values and primitive values.
1766
- */
1767
1330
  private static deserializeValue;
1768
- /**
1769
- * Check if a value is valid for inclusion in a DataTree.
1770
- */
1771
1331
  private static hasValidValue;
1772
- /**
1773
- * Check if input is numeric type.
1774
- */
1775
- private static isNumericInput;
1776
- /**
1777
- * Process array of values based on input type.
1778
- */
1779
- private static processValues;
1780
- /**
1781
- * Clamp numeric value to constraints.
1782
- */
1783
1332
  private static clampValue;
1784
1333
  }
1785
- //#endregion
1786
- 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 SelvaEventTarget, type SelvaResponseBlock, type SolveContext, type SolveDefinition, type SolveOptions, type SolveResult, type SolveSchedulerOptions, type TextInputType, type ValueListInputType, downloadFileData, extractFilesFromComputeResponse, isDefinitionRef };
1334
+ export { type BooleanInputType, type CacheOptions, type CacheStats, type ColorInputType, ComputeServerStats, type CreateClientOptions, type DataItem, type DataTree, type DataTreeDefault, type DataTreePath, type DataTreeValue, type DefaultValue, type FileInputType, type GeometryInputType, type GetValuesOptions, type GetValuesResult, GrasshopperClient, type GrasshopperComputeConfig, type GrasshopperComputeResponse, type GrasshopperParsedIO, type GrasshopperParsedIORaw, GrasshopperResponseProcessor, type InnerTreeData, type InputParam, type InputParamSchema, type InputParseError, type NumericInputType, type OutputParamSchema, type ParsedContext, type RhinoDecodeError, type RhinoModelUnit, type RhinoRuntime, type SchedulerMode, type SchedulerSolveOptions, type SchemaEndpointResult, type SelvaEventTarget, type SelvaResponseBlock, type ServerLimits, type ServerStats, type ServerVersion, type SolveContext, type SolveDetail, type SolveExecutor, type SolveOptions, type SolveResult, type SolveSchedulerOptions, type TextInputType, type TreeValueEntry, type ValueListInputType };
1787
1335
  //# sourceMappingURL=grasshopper.d.ts.map