@selvajs/solve 1.0.0-beta.8 → 1.0.0-beta.9
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.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +144 -150
- package/dist/client.d.ts +144 -150
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +291 -308
- package/dist/server.d.ts +291 -308
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/shared.cjs +0 -1
- package/dist/shared.d.cts +3 -3
- package/dist/shared.d.ts +3 -3
- package/dist/shared.js +1 -1
- package/dist/solve-fn-lsWj61m5.d.cts +48 -0
- package/dist/solve-fn-lsWj61m5.d.ts +48 -0
- package/dist/{solve-input-CqYtetTA.d.ts → solve-input-BDv_JdvS.d.cts} +8 -7
- package/dist/{solve-input-CqYtetTA.d.cts → solve-input-BDv_JdvS.d.ts} +8 -7
- package/package.json +13 -10
- package/dist/shared.cjs.map +0 -1
- package/dist/shared.js.map +0 -1
- package/dist/solve-fn-DrRgaPZ5.d.cts +0 -46
- package/dist/solve-fn-DrRgaPZ5.d.ts +0 -46
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { GrasshopperComputeResponse } from
|
|
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
6
|
* Transport behind a solve session. `solve()` never returns outputs directly — results
|
|
7
7
|
* go through the session's `report()` instead, so a push transport (e.g. a WebSocket
|
|
@@ -18,38 +18,40 @@ import { GrasshopperComputeResponse } from '@selvajs/compute/grasshopper';
|
|
|
18
18
|
* pairing the field exists to prevent.
|
|
19
19
|
*/
|
|
20
20
|
interface SolveDriver {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
30
30
|
}
|
|
31
31
|
/** `TMesh` defaults to `unknown`: nothing here inspects meshes. A host with a concrete
|
|
32
32
|
* mesh type (e.g. a three.js viewer) narrows it at its own seam. */
|
|
33
33
|
interface SolveReporter<TMesh = unknown> {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
report(result: SolveResult<TMesh>): void;
|
|
35
|
+
reportError(message: string): void;
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/client/external-storage.d.ts
|
|
38
39
|
interface ExternalValueRef {
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
scopeKey: string;
|
|
41
|
+
inputId: string;
|
|
41
42
|
}
|
|
42
43
|
declare function writeExternalValue(args: ExternalValueRef & {
|
|
43
|
-
|
|
44
|
+
value: unknown;
|
|
44
45
|
}): void;
|
|
45
46
|
declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
|
|
46
47
|
declare function clearExternalValue(ref: ExternalValueRef): void;
|
|
47
48
|
interface ExternalInput {
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
paramId: string;
|
|
50
|
+
displayName: string;
|
|
50
51
|
}
|
|
51
52
|
declare function getExternalInputs(schema: UISchema): ExternalInput[];
|
|
52
|
-
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/client/solve-session-core.d.ts
|
|
53
55
|
/**
|
|
54
56
|
* The slice of a reported result the session keeps addressable, so a host can commit exactly
|
|
55
57
|
* what the viewer is showing — `source` and `values` included, already memo-correct.
|
|
@@ -62,24 +64,24 @@ declare function getExternalInputs(schema: UISchema): ExternalInput[];
|
|
|
62
64
|
*/
|
|
63
65
|
type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;
|
|
64
66
|
interface SolveSessionState {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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;
|
|
83
85
|
}
|
|
84
86
|
type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
|
|
85
87
|
/**
|
|
@@ -90,13 +92,13 @@ type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
|
|
|
90
92
|
declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
|
|
91
93
|
/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
|
|
92
94
|
declare function makeInitialFlags(instanceSolve: boolean | undefined): {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
hasPendingChanges: boolean;
|
|
96
|
+
hasNeverSolved: boolean;
|
|
95
97
|
};
|
|
96
98
|
/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
|
|
97
99
|
declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
state: SolveSessionState;
|
|
101
|
+
shouldSolve: boolean;
|
|
100
102
|
};
|
|
101
103
|
/**
|
|
102
104
|
* Solve outputs live in the same `values` map as inputs (so widgets like dynamic value
|
|
@@ -105,43 +107,45 @@ declare function applyValueChange(state: SolveSessionState, id: string, value: u
|
|
|
105
107
|
*/
|
|
106
108
|
declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
|
|
107
109
|
declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
|
|
108
|
-
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/client/solve-session.d.ts
|
|
109
112
|
interface SolveSession {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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;
|
|
137
140
|
}
|
|
138
141
|
interface SolveSessionArgs {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
schema: UISchema;
|
|
143
|
+
scopeKey: string;
|
|
144
|
+
driver: SolveDriver;
|
|
142
145
|
}
|
|
143
146
|
declare function createSolveSession(args: SolveSessionArgs): SolveSession;
|
|
144
|
-
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/client/solve-memo.d.ts
|
|
145
149
|
/**
|
|
146
150
|
* Deterministic string key for a set of input values. Sorts object keys at every level so
|
|
147
151
|
* two logically-equal inputs built in different key order collide.
|
|
@@ -153,36 +157,38 @@ declare function stableInputKey(values: Record<string, unknown>): string;
|
|
|
153
157
|
* its back.
|
|
154
158
|
*/
|
|
155
159
|
interface MeshPolicy<TMesh> {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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;
|
|
160
164
|
}
|
|
161
165
|
interface SolveMemoOptions<TMesh> {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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>;
|
|
165
169
|
}
|
|
166
170
|
interface SolveMemo<TMesh = unknown> {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
get(values: Record<string, unknown>): SolveResult<TMesh> | undefined;
|
|
172
|
+
set(values: Record<string, unknown>, result: SolveResult<TMesh>): void;
|
|
173
|
+
clear(): void;
|
|
170
174
|
}
|
|
171
175
|
declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
|
|
172
|
-
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/client/drivers/request-response.d.ts
|
|
173
178
|
interface RequestResponseDriverOptions<TMesh> {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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>;
|
|
179
184
|
}
|
|
180
185
|
/**
|
|
181
186
|
* `getReporter` is lazy (`() => session`) because the session and driver reference each
|
|
182
187
|
* other — the host builds the driver first, then the session, then can hand this back.
|
|
183
188
|
*/
|
|
184
189
|
declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
|
|
185
|
-
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/client/async-throttle.d.ts
|
|
186
192
|
/**
|
|
187
193
|
* Single-in-flight, latest-wins throttle for any async operation: only one run at a
|
|
188
194
|
* time, a newly triggered value overwrites the single pending slot (no queue), and a
|
|
@@ -193,67 +199,55 @@ declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<T
|
|
|
193
199
|
* directly reads correct values but won't trigger a re-render.
|
|
194
200
|
*/
|
|
195
201
|
interface AsyncThrottleOptions {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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;
|
|
205
211
|
}
|
|
206
212
|
interface AsyncThrottle<T> {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
trigger: (values: T) => void;
|
|
214
|
+
readonly isRunning: boolean;
|
|
215
|
+
readonly hasPending: boolean;
|
|
216
|
+
cancel: () => void;
|
|
211
217
|
}
|
|
212
218
|
declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
* `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a
|
|
216
|
-
* `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +
|
|
217
|
-
* `SolveEngine` produce server-side) and unpacks the result.
|
|
218
|
-
*
|
|
219
|
-
* Ports the resilience behavior an interactive solve endpoint needs regardless
|
|
220
|
-
* of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response
|
|
221
|
-
* guarding, and abort handling at every await point — so a new consumer gets it
|
|
222
|
-
* for free instead of re-deriving it by hand. Mesh decoding is NOT built in:
|
|
223
|
-
* this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a
|
|
224
|
-
* viewer-enabled consumer supplies its own `meshes.extract` hook (typically
|
|
225
|
-
* `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).
|
|
226
|
-
*/
|
|
227
|
-
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/client/compute-fetch-solve-fn.d.ts
|
|
228
221
|
interface ComputeFetchSolveFnOptions<TMesh = unknown> {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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;
|
|
256
249
|
}
|
|
257
250
|
declare function createComputeFetchSolveFn<TMesh = unknown>(opts: ComputeFetchSolveFnOptions<TMesh>): SolveFn<TMesh, GrasshopperComputeResponse>;
|
|
258
|
-
|
|
259
|
-
export { type AsyncThrottle, type ComputeFetchSolveFnOptions, type ExternalInput, type ExternalReader, type ExternalValueRef, type MeshPolicy, type RequestResponseDriverOptions, type RetainedSolveResult, 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 };
|
|
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
|
|
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
|