@selvajs/solve 0.2.0-beta.2 → 1.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.
package/dist/client.d.ts CHANGED
@@ -1,138 +1,87 @@
1
- import { UISchema } from '@selvajs/schemas';
2
- import { a as SolveResult, S as SolveFn } from './solve-fn-DGzPCsDu.js';
3
- import { GrasshopperComputeResponse } from '@selvajs/compute';
4
-
1
+ import { n as SolveResult, t as SolveFn } from "./solve-fn-lsWj61m5.js";
2
+ import { UISchema } from "@selvajs/schemas";
3
+ import { GrasshopperComputeResponse } from "@selvajs/compute/grasshopper";
4
+ //#region src/client/drivers/driver.d.ts
5
5
  /**
6
- * The transport behind a Solve Session. Never returns outputs directly — results come
7
- * back via the session's `report()`, which lets push transports (a WebSocket streaming
8
- * mesh frames on its own schedule) satisfy the same interface as request/response HTTP.
6
+ * Transport behind a solve session. `solve()` never returns outputs directly — results
7
+ * go through the session's `report()` instead, so a push transport (e.g. a WebSocket
8
+ * streaming mesh frames on its own schedule) can satisfy the same interface as
9
+ * request/response HTTP.
10
+ *
11
+ * **Stamping `SolveResult.values` is the driver's job, and only some drivers can do it.** A
12
+ * driver that owns a request/response pair must stamp the input set it solved onto the
13
+ * result it reports — the session retains that pair, and a consumer committing what is on
14
+ * screen relies on it being atomic. A push driver that cannot attribute an incoming frame to
15
+ * a request it made must leave `values` absent rather than attach the last set it sent:
16
+ * frames that arrive unsolicited (a replay on connect, a recompute triggered outside the web
17
+ * UI) would otherwise be stamped with an unrelated input set, which is precisely the stale
18
+ * pairing the field exists to prevent.
9
19
  */
10
20
  interface SolveDriver {
11
- solve(values: Record<string, unknown>): void;
12
- cancel(): void;
13
- readonly isSolving: boolean;
14
- /**
15
- * Drops any cached solve results the driver holds. Optional — only drivers with a
16
- * client-side memo (the request/response driver) implement it. Called on rebuild so a
17
- * definition swap can't serve a stale result from a prior definition's input space.
18
- */
19
- clearCache?(): void;
21
+ solve(values: Record<string, unknown>): void;
22
+ cancel(): void;
23
+ readonly isSolving: boolean;
24
+ /**
25
+ * Drops cached solve results. Optional — only the request/response driver has a
26
+ * client-side memo to clear. Call on rebuild: a definition swap must not serve a
27
+ * stale result from the prior definition's input space.
28
+ */
29
+ clearCache?(): void;
20
30
  }
21
- /**
22
- * The slice of a SolveSession a driver feeds completed/failed solves back into.
23
- *
24
- * `TMesh` defaults to `unknown` because nothing in this package inspects meshes; a host that
25
- * knows its concrete mesh type (a three.js viewer) narrows it at its own seam.
26
- */
31
+ /** `TMesh` defaults to `unknown`: nothing here inspects meshes. A host with a concrete
32
+ * mesh type (e.g. a three.js viewer) narrows it at its own seam. */
27
33
  interface SolveReporter<TMesh = unknown> {
28
- report(result: SolveResult<TMesh>): void;
29
- reportError(message: string): void;
30
- }
31
-
32
- interface SolveSession {
33
- readonly values: Record<string, unknown>;
34
- readonly error: string;
35
- readonly computeErrors: string[];
36
- readonly computeWarnings: string[];
37
- readonly meshes: unknown[];
38
- readonly hasPendingChanges: boolean;
39
- readonly hasNeverSolved: boolean;
40
- readonly isSolving: boolean;
41
- /** `forceSolve` overrides manual mode for system-initiated reconciliation (e.g. a dynamic value list pruning a vanished selection), so the output can't lag behind the new value. */
42
- setValue(id: string, value: unknown, forceSolve?: boolean): void;
43
- solve(): void;
44
- /** Merges incoming values, then solves (auto) or marks dirty (manual). */
45
- loadValues(incoming: Record<string, unknown>): void;
46
- rebuild(schema: UISchema, scopeKey: string): void;
47
- report(result: SolveResult): void;
48
- reportError(message: string): void;
49
- /** Fires after any mutation of the getters above, including driver-reported `isSolving` transitions. */
50
- subscribe(listener: () => void): () => void;
51
- /** For driver-owned state the session only forwards: `isSolving` lives on the driver, so wire this to its `onChange`. */
52
- notify(): void;
53
- }
54
- interface SolveSessionArgs {
55
- schema: UISchema;
56
- scopeKey: string;
57
- driver: SolveDriver;
34
+ report(result: SolveResult<TMesh>): void;
35
+ reportError(message: string): void;
58
36
  }
59
- declare function createSolveSession(args: SolveSessionArgs): SolveSession;
60
-
61
- /**
62
- * Deterministic string key for a set of input values. Object keys are sorted at every
63
- * level so two logically-equal inputs (built in different key order) collide, matching the
64
- * server's stable-input keying intent. Values are plain JSON — the projected solve inputs
65
- * never contain functions or cycles.
66
- */
67
- declare function stableInputKey(values: Record<string, unknown>): string;
68
- /**
69
- * Omit for mesh-free results, or for meshes no consumer takes ownership of: the memo then
70
- * stores and serves them by reference, which is correct precisely because nothing frees
71
- * them behind its back.
72
- *
73
- * @property clone - Called on the way in AND on every hit, so neither the stored entry nor
74
- * a previously served one can be disposed by the other's owner.
75
- * @property release - Must NOT free anything shared with live content — see
76
- * `releaseSceneObjects` in `@selvajs/visualization/parse`, which skips materials for
77
- * exactly that reason.
78
- */
79
- interface MeshPolicy<TMesh> {
80
- clone(meshes: TMesh[]): TMesh[];
81
- release(meshes: TMesh[]): void;
82
- }
83
- interface SolveMemoOptions<TMesh> {
84
- /**
85
- * Entry cap (entries, not bytes). Solve results can be large, so the default is
86
- * deliberately small — this targets the tight slider-scrub loop, not a durable cache.
87
- */
88
- max?: number;
89
- meshPolicy?: MeshPolicy<TMesh>;
90
- }
91
- interface SolveMemo<TMesh = unknown> {
92
- get(values: Record<string, unknown>): SolveResult<TMesh> | undefined;
93
- /** Evicts the LRU tail if `set` pushes the map past its cap. */
94
- set(values: Record<string, unknown>, result: SolveResult<TMesh>): void;
95
- clear(): void;
96
- }
97
- declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
98
-
99
- interface RequestResponseDriverOptions<TMesh> {
100
- timeout?: number;
101
- onChange?: () => void;
102
- /** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */
103
- meshPolicy?: MeshPolicy<TMesh>;
104
- }
105
- /**
106
- * `getReporter` is lazy (`() => session`) because the session and driver reference each
107
- * other — the host builds the driver first, then the session, then can hand this back.
108
- */
109
- declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options?: RequestResponseDriverOptions<TMesh>): SolveDriver;
110
-
37
+ //#endregion
38
+ //#region src/client/external-storage.d.ts
111
39
  interface ExternalValueRef {
112
- scopeKey: string;
113
- inputId: string;
40
+ scopeKey: string;
41
+ inputId: string;
114
42
  }
115
43
  declare function writeExternalValue(args: ExternalValueRef & {
116
- value: unknown;
44
+ value: unknown;
117
45
  }): void;
118
46
  declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
119
47
  declare function clearExternalValue(ref: ExternalValueRef): void;
120
48
  interface ExternalInput {
121
- paramId: string;
122
- displayName: string;
49
+ paramId: string;
50
+ displayName: string;
123
51
  }
124
52
  declare function getExternalInputs(schema: UISchema): ExternalInput[];
125
-
53
+ //#endregion
54
+ //#region src/client/solve-session-core.d.ts
55
+ /**
56
+ * The slice of a reported result the session keeps addressable, so a host can commit exactly
57
+ * what the viewer is showing — `source` and `values` included, already memo-correct.
58
+ *
59
+ * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what
60
+ * it renders on the next scene update, which is why the memo carries a `MeshPolicy`
61
+ * clone/release contract at all. Retaining them here would hand out disposed instances after
62
+ * the next solve, with no policy governing them and no owner. Everything left is inert JSON;
63
+ * live meshes stay on `session.meshes`.
64
+ */
65
+ type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;
126
66
  interface SolveSessionState {
127
- values: Record<string, unknown>;
128
- error: string;
129
- computeErrors: string[];
130
- computeWarnings: string[];
131
- meshes: unknown[];
132
- /** Values changed since the last solve, in manual (instanceSolve === false) mode. */
133
- pendingValues: Record<string, unknown>;
134
- hasPendingChanges: boolean;
135
- hasNeverSolved: boolean;
67
+ values: Record<string, unknown>;
68
+ error: string;
69
+ computeErrors: string[];
70
+ computeWarnings: string[];
71
+ meshes: unknown[];
72
+ /** Values changed since the last solve, in manual (instanceSolve === false) mode. */
73
+ pendingValues: Record<string, unknown>;
74
+ hasPendingChanges: boolean;
75
+ hasNeverSolved: boolean;
76
+ /**
77
+ * The last result reported, minus its meshes. Null before the first solve.
78
+ *
79
+ * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a
80
+ * value change and the next report it describes the *previous* input set. That is what a
81
+ * commit gate wants — what is on screen, not what is pending — so `lastResult.values`
82
+ * disagreeing with `state.values` mid-scrub is the feature, not a bug.
83
+ */
84
+ lastResult: RetainedSolveResult | null;
136
85
  }
137
86
  type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
138
87
  /**
@@ -143,13 +92,13 @@ type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
143
92
  declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
144
93
  /** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
145
94
  declare function makeInitialFlags(instanceSolve: boolean | undefined): {
146
- hasPendingChanges: boolean;
147
- hasNeverSolved: boolean;
95
+ hasPendingChanges: boolean;
96
+ hasNeverSolved: boolean;
148
97
  };
149
98
  /** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
150
99
  declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
151
- state: SolveSessionState;
152
- shouldSolve: boolean;
100
+ state: SolveSessionState;
101
+ shouldSolve: boolean;
153
102
  };
154
103
  /**
155
104
  * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value
@@ -158,7 +107,88 @@ declare function applyValueChange(state: SolveSessionState, id: string, value: u
158
107
  */
159
108
  declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
160
109
  declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
161
-
110
+ //#endregion
111
+ //#region src/client/solve-session.d.ts
112
+ interface SolveSession {
113
+ readonly values: Record<string, unknown>;
114
+ readonly error: string;
115
+ readonly computeErrors: string[];
116
+ readonly computeWarnings: string[];
117
+ readonly meshes: unknown[];
118
+ /**
119
+ * The last reported result without its meshes — what the viewer is currently showing,
120
+ * including the `source`/`values` pair a commit path needs. Null before the first solve,
121
+ * and again after `rebuild()`. Survives `reportError`: a failed solve leaves the last good
122
+ * geometry on screen, so a host committing what it sees should still see what produced it.
123
+ */
124
+ readonly lastResult: RetainedSolveResult | null;
125
+ readonly hasPendingChanges: boolean;
126
+ readonly hasNeverSolved: boolean;
127
+ readonly isSolving: boolean;
128
+ /** `forceSolve` overrides manual mode for system-initiated reconciliation (e.g. a dynamic value list pruning a vanished selection), so the output can't lag behind the new value. */
129
+ setValue(id: string, value: unknown, forceSolve?: boolean): void;
130
+ solve(): void;
131
+ /** Merges incoming values, then solves (auto) or marks dirty (manual). */
132
+ loadValues(incoming: Record<string, unknown>): void;
133
+ rebuild(schema: UISchema, scopeKey: string): void;
134
+ report(result: SolveResult): void;
135
+ reportError(message: string): void;
136
+ /** Fires after any mutation of the getters above, including driver-reported `isSolving` transitions. */
137
+ subscribe(listener: () => void): () => void;
138
+ /** For driver-owned state the session only forwards: `isSolving` lives on the driver, so wire this to its `onChange`. */
139
+ notify(): void;
140
+ }
141
+ interface SolveSessionArgs {
142
+ schema: UISchema;
143
+ scopeKey: string;
144
+ driver: SolveDriver;
145
+ }
146
+ declare function createSolveSession(args: SolveSessionArgs): SolveSession;
147
+ //#endregion
148
+ //#region src/client/solve-memo.d.ts
149
+ /**
150
+ * Deterministic string key for a set of input values. Sorts object keys at every level so
151
+ * two logically-equal inputs built in different key order collide.
152
+ */
153
+ declare function stableInputKey(values: Record<string, unknown>): string;
154
+ /**
155
+ * Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then
156
+ * stores and serves them by reference, which is safe only because nothing frees them behind
157
+ * its back.
158
+ */
159
+ interface MeshPolicy<TMesh> {
160
+ /** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */
161
+ clone(meshes: TMesh[]): TMesh[];
162
+ /** Must not free anything shared with live content (e.g. materials another object still uses). */
163
+ release(meshes: TMesh[]): void;
164
+ }
165
+ interface SolveMemoOptions<TMesh> {
166
+ /** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */
167
+ max?: number;
168
+ meshPolicy?: MeshPolicy<TMesh>;
169
+ }
170
+ interface SolveMemo<TMesh = unknown> {
171
+ get(values: Record<string, unknown>): SolveResult<TMesh> | undefined;
172
+ set(values: Record<string, unknown>, result: SolveResult<TMesh>): void;
173
+ clear(): void;
174
+ }
175
+ declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
176
+ //#endregion
177
+ //#region src/client/drivers/request-response.d.ts
178
+ interface RequestResponseDriverOptions<TMesh> {
179
+ /** How long one solve may take before the client aborts it (ms). Pass the server's `COMPUTE_SOLVE_DEADLINE_MS` so the two agree. */
180
+ solveDeadlineMs: number;
181
+ onChange?: () => void;
182
+ /** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */
183
+ meshPolicy?: MeshPolicy<TMesh>;
184
+ }
185
+ /**
186
+ * `getReporter` is lazy (`() => session`) because the session and driver reference each
187
+ * other — the host builds the driver first, then the session, then can hand this back.
188
+ */
189
+ declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
190
+ //#endregion
191
+ //#region src/client/async-throttle.d.ts
162
192
  /**
163
193
  * Single-in-flight, latest-wins throttle for any async operation: only one run at a
164
194
  * time, a newly triggered value overwrites the single pending slot (no queue), and a
@@ -169,66 +199,55 @@ declare function applySolveResult(state: SolveSessionState, result: SolveResult)
169
199
  * directly reads correct values but won't trigger a re-render.
170
200
  */
171
201
  interface AsyncThrottleOptions {
172
- timeout?: number;
173
- onChange?: () => void;
202
+ /**
203
+ * How long one run may take before it is aborted (ms). Required and deliberately
204
+ * undefaulted: this is a generic throttle, so it has no basis for guessing what
205
+ * the caller's work costs. Callers driving a Selva solve pass the deadline the
206
+ * server enforces (`COMPUTE_SOLVE_DEADLINE_MS`) — a shorter local guess would abort
207
+ * solves the server would have finished.
208
+ */
209
+ runDeadlineMs: number;
210
+ onChange?: () => void;
174
211
  }
175
212
  interface AsyncThrottle<T> {
176
- trigger: (values: T) => void;
177
- readonly isRunning: boolean;
178
- readonly hasPending: boolean;
179
- cancel: () => void;
213
+ trigger: (values: T) => void;
214
+ readonly isRunning: boolean;
215
+ readonly hasPending: boolean;
216
+ cancel: () => void;
180
217
  }
181
- declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options?: AsyncThrottleOptions): AsyncThrottle<T>;
182
-
183
- /**
184
- * `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a
185
- * `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +
186
- * `SolveEngine` produce server-side) and unpacks the result.
187
- *
188
- * Ports the resilience behavior an interactive solve endpoint needs regardless
189
- * of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response
190
- * guarding, and abort handling at every await point — so a new consumer gets it
191
- * for free instead of re-deriving it by hand. Mesh decoding is NOT built in:
192
- * this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a
193
- * viewer-enabled consumer supplies its own `meshes.extract` hook (typically
194
- * `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).
195
- */
196
-
197
- interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
198
- /** The compute endpoint to POST to, e.g. `/api/compute`. */
199
- endpoint: string;
200
- /** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */
201
- definitionUrl: () => string;
202
- /** Schema input params, sent as the request body's `inputs`. */
203
- inputs: () => Array<{
204
- id: string;
205
- }>;
206
- /** Schema output params — drives byId/byName output extraction after a solve. */
207
- outputs: () => Array<{
208
- id: string;
209
- nickname?: string;
210
- }>;
211
- channel?: () => 'live' | 'draft' | undefined;
212
- versionId?: () => string | null | undefined;
213
- /**
214
- * Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`
215
- * and no rhino3dm module is ever loaded. `loadRhino` is called lazily on the
216
- * first solve that needs meshes and cached for the lifetime of this `SolveFn`.
217
- */
218
- meshes?: {
219
- loadRhino: () => Promise<TRhino>;
220
- extract: (response: GrasshopperComputeResponse, opts: {
221
- debug: boolean;
222
- rhino: TRhino;
223
- }) => TMesh[] | Promise<TMesh[]>;
224
- };
225
- /** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */
226
- debug?: boolean;
227
- /** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */
228
- onRateLimited?: (retryAfterSeconds: number) => void;
229
- /** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
230
- onSessionExpired?: () => void;
218
+ declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
219
+ //#endregion
220
+ //#region src/client/compute-fetch-solve-fn.d.ts
221
+ interface ComputeFetchSolveFnOptions<TMesh = unknown> {
222
+ /** The compute endpoint to POST to, e.g. `/api/compute`. */
223
+ endpoint: string;
224
+ /** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */
225
+ definitionUrl: () => string;
226
+ /** Schema input params, sent as the request body's `inputs`. */
227
+ inputs: () => Array<{
228
+ id: string;
229
+ }>;
230
+ /** Schema output params — drives byId/byName output extraction after a solve. */
231
+ outputs: () => Array<{
232
+ id: string;
233
+ nickname?: string;
234
+ }>;
235
+ channel?: () => 'live' | 'draft' | undefined;
236
+ versionId?: () => string | null | undefined;
237
+ /** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */
238
+ meshes?: {
239
+ extract: (response: GrasshopperComputeResponse, opts: {
240
+ debug: boolean;
241
+ }) => TMesh[] | Promise<TMesh[]>;
242
+ };
243
+ /** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */
244
+ debug?: boolean;
245
+ /** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */
246
+ onRateLimited?: (retryAfterSeconds: number) => void;
247
+ /** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
248
+ onSessionExpired?: () => void;
231
249
  }
232
- declare function createComputeFetchSolveFn<TMesh = unknown, TRhino = unknown>(opts: ComputeFetchSolveFnOptions<TMesh, TRhino>): SolveFn<TMesh>;
233
-
234
- export { type AsyncThrottle, type ComputeFetchSolveFnOptions, type ExternalInput, type ExternalReader, type ExternalValueRef, type MeshPolicy, type RequestResponseDriverOptions, type SolveDriver, SolveFn, type SolveMemo, type SolveMemoOptions, type SolveReporter, SolveResult, type SolveSession, type SolveSessionArgs, type SolveSessionState, applySolveResult, applyValueChange, buildInitialValues, clearExternalValue, createAsyncThrottle, createComputeFetchSolveFn, createRequestResponseDriver, createSolveMemo, createSolveSession, getExternalInputs, makeInitialFlags, pickInputValues, readExternalValue, stableInputKey, writeExternalValue };
250
+ declare function createComputeFetchSolveFn<TMesh = unknown>(opts: ComputeFetchSolveFnOptions<TMesh>): SolveFn<TMesh, GrasshopperComputeResponse>;
251
+ //#endregion
252
+ export { type AsyncThrottle, type ComputeFetchSolveFnOptions, type ExternalInput, type ExternalReader, type ExternalValueRef, type MeshPolicy, type RequestResponseDriverOptions, type RetainedSolveResult, type SolveDriver, type SolveFn, type SolveMemo, type SolveMemoOptions, type SolveReporter, type SolveResult, type SolveSession, type SolveSessionArgs, type SolveSessionState, applySolveResult, applyValueChange, buildInitialValues, clearExternalValue, createAsyncThrottle, createComputeFetchSolveFn, createRequestResponseDriver, createSolveMemo, createSolveSession, getExternalInputs, makeInitialFlags, pickInputValues, readExternalValue, stableInputKey, writeExternalValue };
253
+ //# sourceMappingURL=client.d.ts.map
package/dist/client.js CHANGED
@@ -1,2 +1,2 @@
1
- import{getInputItems as G}from"@selvajs/schemas";var X="external";function k(e,r){return`${X}:${e}:${r}`}function Y(e){let{scopeKey:r,inputId:s,value:n}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.setItem(k(r,s),JSON.stringify(n))}function x(e){let{scopeKey:r,inputId:s}=e;if(!r||!s||typeof sessionStorage>"u")return;let n=sessionStorage.getItem(k(r,s));if(n!==null)try{return JSON.parse(n)}catch{return}}function Q(e){let{scopeKey:r,inputId:s}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.removeItem(k(r,s))}function I(e){return G(e).filter(r=>r.source?.kind==="client").map(r=>({paramId:r.paramId,displayName:r.displayName??r.paramId}))}import{getDefaultValue as Z}from"@selvajs/schemas";function R(e,r,s){let n=new Set(I(e).map(t=>t.paramId)),l={};for(let t of e.inputs){if(n.has(t.id)){let i=s({scopeKey:r,inputId:t.id});i!==void 0&&(l[t.id]=i);continue}l[t.id]=t.default??Z(t.paramType)}for(let t of e.outputs)l[t.id]=null;return l}function b(e){let r=e===!1;return{hasPendingChanges:r,hasNeverSolved:r}}function C(e,r,s,n){return e.values[r]=s,n===!1?(e.pendingValues[r]=s,e.hasPendingChanges=!0,{state:e,shouldSolve:!1}):{state:e,shouldSolve:!0}}function P(e,r){if(!e?.inputs)return r;let s={};for(let n of e.inputs)n.id in r&&(s[n.id]=r[n.id]);return s}function V(e,r){return e.error="",e.computeErrors=r.errors??[],e.computeWarnings=r.warnings??[],e.meshes=r.meshes??[],Object.assign(e.values,r.outputs),e.pendingValues={},e.hasPendingChanges=!1,e.hasNeverSolved=!1,e}function ee(e){let r=e.schema,s=b(r?.instanceSolve),n={values:R(r,e.scopeKey,x),error:"",computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:s.hasPendingChanges,hasNeverSolved:s.hasNeverSolved},l=new Set,t=()=>l.forEach(o=>o());function i(){e.driver.solve({...P(r,n.values)}),t()}return{get values(){return n.values},get error(){return n.error},get computeErrors(){return n.computeErrors},get computeWarnings(){return n.computeWarnings},get meshes(){return n.meshes},get hasPendingChanges(){return n.hasPendingChanges},get hasNeverSolved(){return n.hasNeverSolved},get isSolving(){return e.driver.isSolving},setValue(o,a,u=!1){let{shouldSolve:c}=C(n,o,a,r?.instanceSolve);c||u?(u&&!c&&(n.pendingValues={},n.hasPendingChanges=!1),i()):t()},solve(){i()},loadValues(o){Object.assign(n.values,o),r?.instanceSolve!==!1?i():(n.hasPendingChanges=!0,t())},rebuild(o,a){r=o,e.driver.clearCache?.(),n.meshes=[],n.error="",n.computeErrors=[],n.computeWarnings=[],n.pendingValues={},n.values=R(o,a,x);let u=b(o?.instanceSolve);n.hasPendingChanges=u.hasPendingChanges,n.hasNeverSolved=u.hasNeverSolved,o&&Object.keys(n.values).length>0&&o.instanceSolve!==!1?i():t()},report(o){V(n,o),t()},reportError(o){n.error=o,t()},subscribe(o){return l.add(o),()=>l.delete(o)},notify:t}}function F(e,r={}){let{timeout:s=6e4,onChange:n}=r,l=!1,t=null,i=null;function o(f){l!==f&&(l=f,n?.())}function a(){i?.abort(),i=null}async function u(f){a(),i=new AbortController;let{signal:v}=i,g=setTimeout(()=>{console.warn(`[Solve/throttle] run exceeded ${s}ms \u2014 aborting`),i?.abort()},s);o(!0);try{await e(f,v)}catch(m){(!(m instanceof Error)||m.name!=="AbortError"&&m.name!=="TimeoutError")&&console.error("[Solve/throttle] unhandled error in run:",m)}finally{if(clearTimeout(g),o(!1),i=null,t!==null){let m=t;t=null,u(m)}}}function c(f){l?(t!==null&&console.debug("[Solve/throttle] superseded pending run (latest-wins)"),t=f):u(f)}function w(){t=null,a()}return{trigger:c,get isRunning(){return l},get hasPending(){return t!==null},cancel:w}}function O(e){return $(e)}function $(e){if(e===null||typeof e!="object")return JSON.stringify(e)??"null";if(Array.isArray(e))return`[${e.map($).join(",")}]`;let r=e;return`{${Object.keys(r).sort().map(n=>`${JSON.stringify(n)}:${$(r[n])}`).join(",")}}`}function j(e={}){let{max:r=16,meshPolicy:s}=e,n=new Map;function l(o){o.meshes?.length&&s?.release(o.meshes)}function t(o){return!s||!o.meshes?.length?o:{...o,meshes:s.clone(o.meshes)}}function i(o){let a=n.get(o);a!==void 0&&(n.delete(o),l(a))}return{get(o){let a=O(o),u=n.get(a);if(u!==void 0)return n.delete(a),n.set(a,u),console.info(`[Solve/memo] HIT \u2014 served from client memo (${n.size}/${r})`),t(u)},set(o,a){let u=O(o);for(i(u),n.set(u,t(a));n.size>r;){let c=n.keys().next().value;if(c===void 0)break;i(c),console.info(`[Solve/memo] evicted LRU entry (cap ${r})`)}},clear(){n.size>0&&console.info(`[Solve/memo] cleared ${n.size} entries (definition changed)`),n.forEach(l),n.clear()}}}function ne(e,r,s={}){let{meshPolicy:n,...l}=s,t=j({meshPolicy:n}),i=F(async(o,a)=>{let u=t.get(o);if(u!==void 0){if(a.aborted)return;r().report(u);return}try{let c=await e(o,a);if(a.aborted){console.debug("[Solve/driver] solve completed after abort \u2014 result discarded");return}t.set(o,c),r().report(c)}catch(c){if(a.aborted){console.debug("[Solve/driver] solve aborted (superseded, cancelled, or timed out)");return}console.warn("[Solve/driver] solve failed:",c),r().reportError(c instanceof Error?c.message:String(c))}},l);return{solve(o){i.trigger(o)},cancel(){i.cancel()},get isSolving(){return i.isRunning},clearCache(){t.clear()}}}import{GrasshopperResponseProcessor as re}from"@selvajs/compute";function oe(e){let r={};if(!e)return r;for(let s of e.split(",")){let[n,...l]=s.split(";").map(o=>o.trim()),t=l.find(o=>o.startsWith("dur="));if(!n||!t)continue;let i=Number(t.slice(4));Number.isFinite(i)&&(r[n]=i)}return r}var te=e=>{throw new Error(`Rate limit reached. Try again in ${Math.ceil(e)}s.`)},se=()=>{throw new Error("Your session has expired. Sign in again in a new tab, then re-run \u2014 your inputs are preserved.")};function ie(e){let r=e.debug??!1,s=(...i)=>{r&&console.info(...i)},n=null;function l(){if(!e.meshes)throw new Error("createComputeFetchSolveFn: meshes option not configured.");return n||(n=e.meshes.loadRhino()),n}let t=0;return async(i,o)=>{let a=t-Date.now();if(a>0)throw new Error(`Rate limit reached. Try again in ${Math.ceil(a/1e3)}s.`);let u=performance.now(),c=e.inputs(),w=e.outputs(),f=e.channel?.(),v=e.versionId?.(),g=JSON.stringify({inputs:c,values:i,definitionUrl:e.definitionUrl(),...v?{versionId:v}:f==="draft"?{channel:"draft"}:{}}),m=JSON.stringify(i).length,p;try{p=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:g,signal:o})}catch(d){if(o.aborted)return s("[Compute] solve aborted during fetch \u2014 empty result discarded"),{outputs:{}};throw new Error("Request blocked \u2014 your session may have expired. Reload the page to sign in again.",{cause:d})}if(o.aborted)return s("[Compute] solve aborted after headers \u2014 empty result discarded"),{outputs:{}};let B=p.redirected&&new URL(p.url).pathname==="/login";if((p.status===401||B)&&(e.onSessionExpired??se)(),!p.ok){if(p.status===503)throw new Error("Compute server is offline or unreachable. Please try again later.");let d=await p.text().catch(()=>""),h={};try{h=JSON.parse(d)}catch{d&&console.warn(`[Compute] non-JSON error body (HTTP ${p.status}):`,d.slice(0,300))}if(p.status===429){let y=Number(p.headers.get("Retry-After"))||Number(h.retryAfter)||5;t=Date.now()+y*1e3,(e.onRateLimited??te)(y)}throw new Error(h.message||`Compute error (HTTP ${p.status})`)}let A=performance.now()-u,z=performance.now(),N=await p.text();if(o.aborted)return s("[Compute] solve aborted mid-download \u2014 empty result discarded"),{outputs:{}};let T=performance.now()-z,U=N.length,S;try{S=JSON.parse(N)}catch(d){throw new Error("Received an invalid response from the server. Reload the page and try again.",{cause:d})}let M=new re(S,!1),D=e.meshes?await l():null,q=D!==null&&e.meshes?await e.meshes.extract(M.response,{debug:r,rhino:D}):[],K={};for(let d of w){let h=M.getValue({byId:d.id},{parseValues:!0});K[d.id]=h??(d.nickname?M.getValue({byName:d.nickname},{parseValues:!0}):void 0)}if(r){let d=oe(p.headers.get("Server-Timing")),h=d.total??0,y=Math.max(0,A-h),E=U/(1024*1024),L=E>=1?`${E.toFixed(2)} MB`:`${(U/1024).toFixed(0)} KB`,W=T>0?(E/(T/1e3)).toFixed(1):"\u221E",J=performance.memory?.usedJSHeapSize,_=J!==void 0?` | heap=${(J/(1024*1024)).toFixed(0)} MB`:"";if(s(`[Compute] req=${(g.length/1024).toFixed(0)}KB (values ${(m/1024).toFixed(0)}KB) | ttfb=${A.toFixed(0)}ms (network\u2248${y.toFixed(0)} + server ${h.toFixed(0)}) | download=${T.toFixed(0)}ms (${L} @ ${W} MB/s)${_}`),d.selva_cache!==void 0){let H=d.selva_cache?"HIT \u2014 served without calling compute":"miss \u2014 solved on Rhino.Compute";s(`[Compute] \u2514\u2500 cache: ${H}`)}}return{outputs:K,meshes:q,errors:S.errors??[],warnings:S.warnings??[]}}}export{V as applySolveResult,C as applyValueChange,R as buildInitialValues,Q as clearExternalValue,F as createAsyncThrottle,ie as createComputeFetchSolveFn,ne as createRequestResponseDriver,j as createSolveMemo,ee as createSolveSession,I as getExternalInputs,b as makeInitialFlags,P as pickInputValues,x as readExternalValue,O as stableInputKey,Y as writeExternalValue};
1
+ import{getDefaultValue as e,getInputItems as t}from"@selvajs/schemas";import{GrasshopperResponseProcessor as n}from"@selvajs/compute/grasshopper";function r(e,t){return`external:${e}:${t}`}function i(e){let{scopeKey:t,inputId:n,value:i}=e;!t||!n||typeof sessionStorage>`u`||sessionStorage.setItem(r(t,n),JSON.stringify(i))}function a(e){let{scopeKey:t,inputId:n}=e;if(!t||!n||typeof sessionStorage>`u`)return;let i=sessionStorage.getItem(r(t,n));if(i!==null)try{return JSON.parse(i)}catch{return}}function o(e){let{scopeKey:t,inputId:n}=e;!t||!n||typeof sessionStorage>`u`||sessionStorage.removeItem(r(t,n))}function s(e){return t(e).filter(e=>e.source?.kind===`client`).map(e=>({paramId:e.paramId,displayName:e.displayName??e.paramId}))}function c(t,n,r){let i=new Set(s(t).map(e=>e.paramId)),a={};for(let o of t.inputs){if(i.has(o.id)){let e=r({scopeKey:n,inputId:o.id});e!==void 0&&(a[o.id]=e);continue}a[o.id]=o.default??e(o.paramType)}for(let e of t.outputs)a[e.id]=null;return a}function l(e){let t=e===!1;return{hasPendingChanges:t,hasNeverSolved:t}}function u(e,t,n,r){return e.values[t]=n,r===!1?(e.pendingValues[t]=n,e.hasPendingChanges=!0,{state:e,shouldSolve:!1}):{state:e,shouldSolve:!0}}function d(e,t){if(!e?.inputs)return t;let n={};for(let r of e.inputs)r.id in t&&(n[r.id]=t[r.id]);return n}function f(e,t){e.error=``,e.computeErrors=t.errors??[],e.computeWarnings=t.warnings??[],e.meshes=t.meshes??[];let{meshes:n,...r}=t;return e.lastResult=r,Object.assign(e.values,t.outputs),e.pendingValues={},e.hasPendingChanges=!1,e.hasNeverSolved=!1,e}function p(e){let t=e.schema,n=l(t?.instanceSolve),r={values:c(t,e.scopeKey,a),error:``,computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:n.hasPendingChanges,hasNeverSolved:n.hasNeverSolved,lastResult:null},i=new Set,o=()=>i.forEach(e=>e());function s(){e.driver.solve({...d(t,r.values)}),o()}return{get values(){return r.values},get error(){return r.error},get computeErrors(){return r.computeErrors},get computeWarnings(){return r.computeWarnings},get meshes(){return r.meshes},get lastResult(){return r.lastResult},get hasPendingChanges(){return r.hasPendingChanges},get hasNeverSolved(){return r.hasNeverSolved},get isSolving(){return e.driver.isSolving},setValue(e,n,i=!1){let{shouldSolve:a}=u(r,e,n,t?.instanceSolve);a||i?(i&&!a&&(r.pendingValues={},r.hasPendingChanges=!1),s()):o()},solve(){s()},loadValues(e){Object.assign(r.values,e),t?.instanceSolve===!1?(r.hasPendingChanges=!0,o()):s()},rebuild(n,i){t=n,e.driver.clearCache?.(),r.meshes=[],r.lastResult=null,r.error=``,r.computeErrors=[],r.computeWarnings=[],r.pendingValues={},r.values=c(n,i,a);let u=l(n?.instanceSolve);r.hasPendingChanges=u.hasPendingChanges,r.hasNeverSolved=u.hasNeverSolved,n&&Object.keys(r.values).length>0&&n.instanceSolve!==!1?s():o()},report(e){f(r,e),o()},reportError(e){r.error=e,o()},subscribe(e){return i.add(e),()=>i.delete(e)},notify:o}}function m(e,t){let{runDeadlineMs:n,onChange:r}=t,i=!1,a=null,o=null;function s(e){i!==e&&(i=e,r?.())}function c(){o?.abort(),o=null}async function l(t){c(),o=new AbortController;let{signal:r}=o,i=setTimeout(()=>{console.warn(`[Solve/throttle] run exceeded ${n}ms — aborting`),o?.abort()},n);s(!0);try{await e(t,r)}catch(e){(!(e instanceof Error)||e.name!==`AbortError`&&e.name!==`TimeoutError`)&&console.error(`[Solve/throttle] unhandled error in run:`,e)}finally{if(clearTimeout(i),s(!1),o=null,a!==null){let e=a;a=null,l(e)}}}function u(e){i?(a!==null&&console.debug(`[Solve/throttle] superseded pending run (latest-wins)`),a=e):l(e)}function d(){a=null,c()}return{trigger:u,get isRunning(){return i},get hasPending(){return a!==null},cancel:d}}function h(e){return g(e)}function g(e){if(typeof e!=`object`||!e)return JSON.stringify(e)??`null`;if(Array.isArray(e))return`[${e.map(g).join(`,`)}]`;let t=e;return`{${Object.keys(t).sort().map(e=>`${JSON.stringify(e)}:${g(t[e])}`).join(`,`)}}`}function _(e={}){let{max:t=16,meshPolicy:n}=e,r=new Map;function i(e){e.meshes?.length&&n?.release(e.meshes)}function a(e){return!n||!e.meshes?.length?e:{...e,meshes:n.clone(e.meshes)}}function o(e){let t=r.get(e);t!==void 0&&(r.delete(e),i(t))}return{get(e){let n=h(e),i=r.get(n);if(i!==void 0)return r.delete(n),r.set(n,i),console.info(`[Solve/memo] HIT — served from client memo (${r.size}/${t})`),a(i)},set(e,n){let i=h(e);for(o(i),r.set(i,a(n));r.size>t;){let e=r.keys().next().value;if(e===void 0)break;o(e),console.info(`[Solve/memo] evicted LRU entry (cap ${t})`)}},clear(){r.size>0&&console.info(`[Solve/memo] cleared ${r.size} entries (definition changed)`),r.forEach(i),r.clear()}}}function v(e,t,n){let{meshPolicy:r,solveDeadlineMs:i,onChange:a}=n,o=_({meshPolicy:r}),s=m(async(n,r)=>{let i=o.get(n);if(i!==void 0){if(r.aborted)return;t().report(i);return}try{let i=await e(n,r);if(r.aborted){console.debug(`[Solve/driver] solve completed after abort — result discarded`);return}let a={...i,values:n};o.set(n,a),t().report(a)}catch(e){if(r.aborted){console.debug(`[Solve/driver] solve aborted (superseded, cancelled, or timed out)`);return}console.warn(`[Solve/driver] solve failed:`,e),t().reportError(e instanceof Error?e.message:String(e))}},{runDeadlineMs:i,onChange:a});return{solve(e){s.trigger(e)},cancel(){s.cancel()},get isSolving(){return s.isRunning},clearCache(){o.clear()}}}function y(e){let t={};if(!e)return t;for(let n of e.split(`,`)){let[e,...r]=n.split(`;`).map(e=>e.trim()),i=r.find(e=>e.startsWith(`dur=`));if(!e||!i)continue;let a=Number(i.slice(4));Number.isFinite(a)&&(t[e]=a)}return t}const b=e=>{throw Error(`Rate limit reached. Try again in ${Math.ceil(e)}s.`)},x=()=>{throw Error(`Your session has expired. Sign in again in a new tab, then re-run — your inputs are preserved.`)};function S(e){let t=e.debug??!1,r=(...e)=>{t&&console.info(...e)},i=0;return async(a,o)=>{let s=i-Date.now();if(s>0)throw Error(`Rate limit reached. Try again in ${Math.ceil(s/1e3)}s.`);let c=performance.now(),l=e.inputs(),u=e.outputs(),d=e.channel?.(),f=e.versionId?.(),p=JSON.stringify({inputs:l,values:a,definitionUrl:e.definitionUrl(),...f?{versionId:f}:d===`draft`?{channel:`draft`}:{}}),m=JSON.stringify(a).length,h;try{h=await fetch(e.endpoint,{method:`POST`,headers:{"Content-Type":`application/json`},body:p,signal:o})}catch(e){if(o.aborted)return r(`[Compute] solve aborted during fetch — empty result discarded`),{outputs:{}};throw Error(`Request blocked — your session may have expired. Reload the page to sign in again.`,{cause:e})}if(o.aborted)return r(`[Compute] solve aborted after headers — empty result discarded`),{outputs:{}};let g=h.redirected&&new URL(h.url).pathname===`/login`;if((h.status===401||g)&&(e.onSessionExpired??x)(),!h.ok){if(h.status===503)throw Error(`Compute server is offline or unreachable. Please try again later.`);let t=await h.text().catch(()=>``),n={};try{n=JSON.parse(t)}catch{t&&console.warn(`[Compute] non-JSON error body (HTTP ${h.status}):`,t.slice(0,300))}if(h.status===429){let t=Number(h.headers.get(`Retry-After`))||Number(n.retryAfter)||5;i=Date.now()+t*1e3,(e.onRateLimited??b)(t)}throw Error(n.message||`Compute error (HTTP ${h.status})`)}let _=performance.now()-c,v=performance.now(),S=await h.text();if(o.aborted)return r(`[Compute] solve aborted mid-download — empty result discarded`),{outputs:{}};let C=performance.now()-v,w=S.length,T;try{T=JSON.parse(S)}catch(e){throw Error(`Received an invalid response from the server. Reload the page and try again.`,{cause:e})}let E=new n(T,!1),D=e.meshes?await e.meshes.extract(E.response,{debug:t}):[],O={};for(let e of u){let t=E.getValue({byId:e.id},{parseValues:!0});O[e.id]=t??(e.nickname?E.getValue({byName:e.nickname},{parseValues:!0}):void 0)}if(t){let e=y(h.headers.get(`Server-Timing`)),t=e.total??0,n=Math.max(0,_-t),i=w/1048576,a=i>=1?`${i.toFixed(2)} MB`:`${(w/1024).toFixed(0)} KB`,o=C>0?(i/(C/1e3)).toFixed(1):`∞`,s=performance.memory?.usedJSHeapSize,c=s===void 0?``:` | heap=${(s/1048576).toFixed(0)} MB`;if(r(`[Compute] req=${(p.length/1024).toFixed(0)}KB (values ${(m/1024).toFixed(0)}KB) | ttfb=${_.toFixed(0)}ms (network≈${n.toFixed(0)} + server ${t.toFixed(0)}) | download=${C.toFixed(0)}ms (${a} @ ${o} MB/s)${c}`),e.selva_cache!==void 0){let t=e.selva_cache?`HIT — served without calling compute`:`miss — solved on Rhino.Compute`;r(`[Compute] └─ cache: ${t}`)}}return{outputs:O,meshes:D,errors:T.errors??[],warnings:T.warnings??[],source:T}}}export{f as applySolveResult,u as applyValueChange,c as buildInitialValues,o as clearExternalValue,m as createAsyncThrottle,S as createComputeFetchSolveFn,v as createRequestResponseDriver,_ as createSolveMemo,p as createSolveSession,s as getExternalInputs,l as makeInitialFlags,d as pickInputValues,a as readExternalValue,h as stableInputKey,i as writeExternalValue};
2
2
  //# sourceMappingURL=client.js.map