@selvajs/solve 1.1.0 → 1.1.1
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 +16 -16
- package/dist/client.d.ts +16 -16
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/server.d.cts +12 -12
- package/dist/server.d.ts +12 -12
- package/package.json +9 -9
package/dist/client.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@selvajs/schemas"),t=require("@selvajs/compute/grasshopper");function n(e,t){return`external:${e}:${t}`}function r(e){let{scopeKey:t,inputId:r,value:i}=e
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@selvajs/schemas"),t=require("@selvajs/compute/grasshopper");function n(e,t){return`external:${e}:${t}`}function r(e){let{scopeKey:t,inputId:r,value:i}=e;t&&r&&(typeof sessionStorage>`u`||sessionStorage.setItem(n(t,r),JSON.stringify(i)))}function i(e){let{scopeKey:t,inputId:r}=e;if(!t||!r||typeof sessionStorage>`u`)return;let i=sessionStorage.getItem(n(t,r));if(i!==null)try{return JSON.parse(i)}catch{return}}function a(e){let{scopeKey:t,inputId:r}=e;t&&r&&(typeof sessionStorage>`u`||sessionStorage.removeItem(n(t,r)))}function o(t){return(0,e.getInputItems)(t).filter(e=>e.source?.kind===`client`).map(e=>({paramId:e.paramId,displayName:e.displayName??e.paramId}))}function s(t,n,r){let i=new Set(o(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??(0,e.getDefaultValue)(o.paramType)}for(let e of t.outputs)a[e.id]=null;return a}function c(e){let t=e===!1;return{hasPendingChanges:t,hasNeverSolved:t}}function l(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 u(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 d(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 f(e){let t=e.schema,n=c(t?.instanceSolve),r={values:s(t,e.scopeKey,i),error:``,computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:n.hasPendingChanges,hasNeverSolved:n.hasNeverSolved,lastResult:null},a=new Set,o=()=>a.forEach(e=>e());function f(){e.driver.solve({...u(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}=l(r,e,n,t?.instanceSolve);a||i?(i&&!a&&(r.pendingValues={},r.hasPendingChanges=!1),f()):o()},solve(){f()},loadValues(e){Object.assign(r.values,e),t?.instanceSolve===!1?(r.hasPendingChanges=!0,o()):f()},rebuild(n,a){t=n,e.driver.clearCache?.(),r.meshes=[],r.lastResult=null,r.error=``,r.computeErrors=[],r.computeWarnings=[],r.pendingValues={},r.values=s(n,a,i);let l=c(n?.instanceSolve);r.hasPendingChanges=l.hasPendingChanges,r.hasNeverSolved=l.hasNeverSolved,n&&Object.keys(r.values).length>0&&n.instanceSolve!==!1?f():o()},report(e){d(r,e),o()},reportError(e){r.error=e,o()},subscribe(e){return a.add(e),()=>a.delete(e)},notify:o}}function p(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 m(e){return h(e)}function h(e){if(typeof e!=`object`||!e)return JSON.stringify(e)??`null`;if(Array.isArray(e))return`[${e.map(h).join(`,`)}]`;let t=e;return`{${Object.keys(t).sort().map(e=>`${JSON.stringify(e)}:${h(t[e])}`).join(`,`)}}`}function g(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=m(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=m(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 _(e,t,n){let{meshPolicy:r,solveDeadlineMs:i,onChange:a}=n,o=g({meshPolicy:r}),s=p(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 v(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 y=e=>{throw Error(`Rate limit reached. Try again in ${Math.ceil(e)}s.`)},b=()=>{throw Error(`Your session has expired. Sign in again in a new tab, then re-run — your inputs are preserved.`)};function x(e){let n=e.debug??!1,r=(...e)=>{n&&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;try{m=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 h=m.redirected&&new URL(m.url).pathname===`/login`;if((m.status===401||h)&&(e.onSessionExpired??b)(),!m.ok){let t=await m.text().catch(()=>``),n={};try{n=JSON.parse(t)}catch{t&&console.warn(`[Compute] non-JSON error body (HTTP ${m.status}):`,t.slice(0,300))}if(m.status===503||m.status===504)throw Error(n.message||`Compute server is offline or unreachable. Please try again later.`);if(m.status===429){let t=Number(m.headers.get(`Retry-After`))||Number(n.retryAfter)||5;i=Date.now()+t*1e3,(e.onRateLimited??y)(t)}throw Error(n.message||`Compute error (HTTP ${m.status})`)}let g=performance.now()-c,_=performance.now(),x=await m.text();if(o.aborted)return r(`[Compute] solve aborted mid-download — empty result discarded`),{outputs:{}};let S=performance.now()-_,C=x.length,w;try{w=JSON.parse(x)}catch(e){throw Error(`Received an invalid response from the server. Reload the page and try again.`,{cause:e})}let T=new t.GrasshopperResponseProcessor(w,!1),E=e.meshes?await e.meshes.extract(T.response,{debug:n}):[],D={};for(let e of u){let t=T.getValue({byId:e.id},{parseValues:!0});D[e.id]=t??(e.nickname?T.getValue({byName:e.nickname},{parseValues:!0}):void 0)}if(n){let e=JSON.stringify(a).length,t=v(m.headers.get(`Server-Timing`)),n=t.total??0,i=Math.max(0,g-n),o=C/1048576,s=o>=1?`${o.toFixed(2)} MB`:`${(C/1024).toFixed(0)} KB`,c=S>0?(o/(S/1e3)).toFixed(1):`∞`,l=performance.memory?.usedJSHeapSize,u=l===void 0?``:` | heap=${(l/1048576).toFixed(0)} MB`;if(r(`[Compute] req=${(p.length/1024).toFixed(0)}KB (values ${(e/1024).toFixed(0)}KB) | ttfb=${g.toFixed(0)}ms (network≈${i.toFixed(0)} + server ${n.toFixed(0)}) | download=${S.toFixed(0)}ms (${s} @ ${c} MB/s)${u}`),t.selva_cache!==void 0){let e=t.selva_cache?`HIT — served without calling compute`:`miss — solved on Rhino.Compute`;r(`[Compute] └─ cache: ${e}`)}}return{outputs:D,meshes:E,errors:w.errors??[],warnings:w.warnings??[],source:w}}}exports.applySolveResult=d,exports.applyValueChange=l,exports.buildInitialValues=s,exports.clearExternalValue=a,exports.createAsyncThrottle=p,exports.createComputeFetchSolveFn=x,exports.createRequestResponseDriver=_,exports.createSolveMemo=g,exports.createSolveSession=f,exports.getExternalInputs=o,exports.makeInitialFlags=c,exports.pickInputValues=u,exports.readExternalValue=i,exports.stableInputKey=m,exports.writeExternalValue=r;
|
|
2
2
|
//# sourceMappingURL=client.cjs.map
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.cjs","names":["getInputItems","getDefaultValue","GrasshopperResponseProcessor"],"sources":["../src/client/external-storage.ts","../src/client/solve-session-core.ts","../src/client/solve-session.ts","../src/client/async-throttle.ts","../src/client/solve-memo.ts","../src/client/drivers/request-response.ts","../src/client/compute-fetch-solve-fn.ts"],"sourcesContent":["// Transit storage for inputs with source.kind === 'client': a producer route writes the\n// value here, the solver route reads it back. Keyed by (scopeKey, inputId) so values\n// don't bleed across solver contexts — scopeKey is whatever uniquely identifies one\n// (sessionId in plugin-ui/preview, definition guid in selva/library).\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getInputItems } from '@selvajs/schemas';\n\nconst STORAGE_PREFIX = 'external';\n\nfunction makeKey(scopeKey: string, inputId: string): string {\n\treturn `${STORAGE_PREFIX}:${scopeKey}:${inputId}`;\n}\n\nexport interface ExternalValueRef {\n\tscopeKey: string;\n\tinputId: string;\n}\n\nexport function writeExternalValue(args: ExternalValueRef & { value: unknown }): void {\n\tconst { scopeKey, inputId, value } = args;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.setItem(makeKey(scopeKey, inputId), JSON.stringify(value));\n}\n\nexport function readExternalValue(ref: ExternalValueRef): unknown | undefined {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return undefined;\n\tif (typeof sessionStorage === 'undefined') return undefined;\n\tconst raw = sessionStorage.getItem(makeKey(scopeKey, inputId));\n\tif (raw === null) return undefined;\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function clearExternalValue(ref: ExternalValueRef): void {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.removeItem(makeKey(scopeKey, inputId));\n}\n\nexport interface ExternalInput {\n\tparamId: string;\n\tdisplayName: string;\n}\n\nexport function getExternalInputs(schema: UISchema): ExternalInput[] {\n\treturn getInputItems(schema)\n\t\t.filter((item) => item.source?.kind === 'client')\n\t\t.map((item) => ({\n\t\t\tparamId: item.paramId,\n\t\t\tdisplayName: item.displayName ?? item.paramId\n\t\t}));\n}\n","// Pure transition logic for a Solve Session.\n//\n// This module holds the value/lifecycle state machine with no transport and no state\n// ownership: how values are seeded (including client-sourced hydration), when a value\n// change should dispatch a solve vs. defer it, and what a reported solve result does to\n// the flags. `solve-session.ts` is a thin shell that owns the state object and the\n// subscriber set; every decision lives here, which is what makes it directly testable.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getDefaultValue } from '@selvajs/schemas';\nimport { getExternalInputs, type ExternalValueRef } from './external-storage.js';\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * The slice of a reported result the session keeps addressable, so a host can commit exactly\n * what the viewer is showing — `source` and `values` included, already memo-correct.\n *\n * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what\n * it renders on the next scene update, which is why the memo carries a `MeshPolicy`\n * clone/release contract at all. Retaining them here would hand out disposed instances after\n * the next solve, with no policy governing them and no owner. Everything left is inert JSON;\n * live meshes stay on `session.meshes`.\n */\nexport type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;\n\nexport interface SolveSessionState {\n\tvalues: Record<string, unknown>;\n\terror: string;\n\tcomputeErrors: string[];\n\tcomputeWarnings: string[];\n\tmeshes: unknown[];\n\t/** Values changed since the last solve, in manual (instanceSolve === false) mode. */\n\tpendingValues: Record<string, unknown>;\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n\t/**\n\t * The last result reported, minus its meshes. Null before the first solve.\n\t *\n\t * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a\n\t * value change and the next report it describes the *previous* input set. That is what a\n\t * commit gate wants — what is on screen, not what is pending — so `lastResult.values`\n\t * disagreeing with `state.values` mid-scrub is the feature, not a bug.\n\t */\n\tlastResult: RetainedSolveResult | null;\n}\n\nexport type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;\n\n/**\n * Client-sourced inputs are hydrated from `read` and left absent when no stored value\n * exists, so the missing-inputs panel can detect them. Other inputs fall back to their\n * declared default, then the paramType default.\n */\nexport function buildInitialValues(\n\tschema: UISchema,\n\tscopeKey: string,\n\tread: ExternalReader\n): Record<string, unknown> {\n\tconst clientSet = new Set(getExternalInputs(schema).map((e) => e.paramId));\n\tconst values: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (clientSet.has(input.id)) {\n\t\t\tconst stored = read({ scopeKey, inputId: input.id });\n\t\t\tif (stored !== undefined) values[input.id] = stored;\n\t\t\tcontinue;\n\t\t}\n\t\tvalues[input.id] = input.default ?? getDefaultValue(input.paramType);\n\t}\n\tfor (const output of schema.outputs) {\n\t\tvalues[output.id] = null;\n\t}\n\treturn values;\n}\n\n/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */\nexport function makeInitialFlags(instanceSolve: boolean | undefined): {\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n} {\n\tconst manual = instanceSolve === false;\n\treturn { hasPendingChanges: manual, hasNeverSolved: manual };\n}\n\n/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */\nexport function applyValueChange(\n\tstate: SolveSessionState,\n\tid: string,\n\tvalue: unknown,\n\tinstanceSolve: boolean | undefined\n): { state: SolveSessionState; shouldSolve: boolean } {\n\tstate.values[id] = value;\n\tif (instanceSolve === false) {\n\t\tstate.pendingValues[id] = value;\n\t\tstate.hasPendingChanges = true;\n\t\treturn { state, shouldSolve: false };\n\t}\n\treturn { state, shouldSolve: true };\n}\n\n/**\n * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value\n * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB\n * options-list output showed this can re-upload a multi-MB payload no backend reads.\n */\nexport function pickInputValues(\n\tschema: UISchema | undefined,\n\tvalues: Record<string, unknown>\n): Record<string, unknown> {\n\tif (!schema?.inputs) return values;\n\tconst picked: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (input.id in values) picked[input.id] = values[input.id];\n\t}\n\treturn picked;\n}\n\nexport function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState {\n\tstate.error = '';\n\tstate.computeErrors = result.errors ?? [];\n\tstate.computeWarnings = result.warnings ?? [];\n\tstate.meshes = result.meshes ?? [];\n\tconst { meshes: _meshes, ...retained } = result;\n\tstate.lastResult = retained;\n\tObject.assign(state.values, result.outputs);\n\tstate.pendingValues = {};\n\tstate.hasPendingChanges = false;\n\tstate.hasNeverSolved = false;\n\treturn state;\n}\n","// A framework-free shell over the pure transition logic in solve-session-core.ts: owns the\n// live values/flags, delegates every transition to the core, and drives solves through a\n// transport-agnostic SolveDriver.\n//\n// State is exposed as plain getters; it carries no framework reactivity of its own, so a\n// Svelte host must wrap it — see `useSolveSession.svelte.ts` in `@selvajs/ui`, which\n// republishes these getters as $state. Reading a getter without subscribing gives a\n// correct value but will not re-render.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { readExternalValue } from './external-storage.js';\nimport type { SolveResult } from '../shared/solve-fn.js';\nimport type { SolveDriver } from './drivers/driver.js';\nimport {\n\tbuildInitialValues,\n\tmakeInitialFlags,\n\tapplyValueChange,\n\tapplySolveResult,\n\tpickInputValues,\n\ttype SolveSessionState,\n\ttype RetainedSolveResult\n} from './solve-session-core.js';\n\nexport interface SolveSession {\n\treadonly values: Record<string, unknown>;\n\treadonly error: string;\n\treadonly computeErrors: string[];\n\treadonly computeWarnings: string[];\n\treadonly meshes: unknown[];\n\t/**\n\t * The last reported result without its meshes — what the viewer is currently showing,\n\t * including the `source`/`values` pair a commit path needs. Null before the first solve,\n\t * and again after `rebuild()`. Survives `reportError`: a failed solve leaves the last good\n\t * geometry on screen, so a host committing what it sees should still see what produced it.\n\t */\n\treadonly lastResult: RetainedSolveResult | null;\n\treadonly hasPendingChanges: boolean;\n\treadonly hasNeverSolved: boolean;\n\treadonly isSolving: boolean;\n\t/** `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. */\n\tsetValue(id: string, value: unknown, forceSolve?: boolean): void;\n\tsolve(): void;\n\t/** Merges incoming values, then solves (auto) or marks dirty (manual). */\n\tloadValues(incoming: Record<string, unknown>): void;\n\trebuild(schema: UISchema, scopeKey: string): void;\n\treport(result: SolveResult): void;\n\treportError(message: string): void;\n\t/** Fires after any mutation of the getters above, including driver-reported `isSolving` transitions. */\n\tsubscribe(listener: () => void): () => void;\n\t/** For driver-owned state the session only forwards: `isSolving` lives on the driver, so wire this to its `onChange`. */\n\tnotify(): void;\n}\n\nexport interface SolveSessionArgs {\n\tschema: UISchema;\n\tscopeKey: string;\n\tdriver: SolveDriver;\n}\n\nexport function createSolveSession(args: SolveSessionArgs): SolveSession {\n\tlet currentSchema = args.schema;\n\n\tconst flags = makeInitialFlags(currentSchema?.instanceSolve);\n\tconst state: SolveSessionState = {\n\t\tvalues: buildInitialValues(currentSchema, args.scopeKey, readExternalValue),\n\t\terror: '',\n\t\tcomputeErrors: [],\n\t\tcomputeWarnings: [],\n\t\tmeshes: [],\n\t\tpendingValues: {},\n\t\thasPendingChanges: flags.hasPendingChanges,\n\t\thasNeverSolved: flags.hasNeverSolved,\n\t\tlastResult: null\n\t};\n\n\tconst listeners = new Set<() => void>();\n\tconst emit = () => listeners.forEach((l) => l());\n\n\tfunction dispatch() {\n\t\t// Copy: state.values is the live map, and a driver may hold the payload across an\n\t\t// async solve — a later setValue must not mutate what's already in flight.\n\t\targs.driver.solve({ ...pickInputValues(currentSchema, state.values) });\n\t\temit();\n\t}\n\n\treturn {\n\t\tget values() {\n\t\t\treturn state.values;\n\t\t},\n\t\tget error() {\n\t\t\treturn state.error;\n\t\t},\n\t\tget computeErrors() {\n\t\t\treturn state.computeErrors;\n\t\t},\n\t\tget computeWarnings() {\n\t\t\treturn state.computeWarnings;\n\t\t},\n\t\tget meshes() {\n\t\t\treturn state.meshes;\n\t\t},\n\t\tget lastResult() {\n\t\t\treturn state.lastResult;\n\t\t},\n\t\tget hasPendingChanges() {\n\t\t\treturn state.hasPendingChanges;\n\t\t},\n\t\tget hasNeverSolved() {\n\t\t\treturn state.hasNeverSolved;\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn args.driver.isSolving;\n\t\t},\n\n\t\tsetValue(id, value, forceSolve = false) {\n\t\t\tconst { shouldSolve } = applyValueChange(state, id, value, currentSchema?.instanceSolve);\n\t\t\tif (shouldSolve || forceSolve) {\n\t\t\t\tif (forceSolve && !shouldSolve) {\n\t\t\t\t\tstate.pendingValues = {};\n\t\t\t\t\tstate.hasPendingChanges = false;\n\t\t\t\t}\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\tsolve() {\n\t\t\tdispatch();\n\t\t},\n\n\t\tloadValues(incoming) {\n\t\t\tObject.assign(state.values, incoming);\n\t\t\tif (currentSchema?.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\tstate.hasPendingChanges = true;\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\trebuild(schema, scopeKey) {\n\t\t\tcurrentSchema = schema;\n\t\t\t// The new definition has its own input space; a matching input key from the prior\n\t\t\t// one must not serve that definition's cached result.\n\t\t\targs.driver.clearCache?.();\n\t\t\tstate.meshes = [];\n\t\t\t// Same reason as clearCache: a retained result belongs to the previous definition.\n\t\t\tstate.lastResult = null;\n\t\t\tstate.error = '';\n\t\t\tstate.computeErrors = [];\n\t\t\tstate.computeWarnings = [];\n\t\t\tstate.pendingValues = {};\n\t\t\tstate.values = buildInitialValues(schema, scopeKey, readExternalValue);\n\t\t\tconst f = makeInitialFlags(schema?.instanceSolve);\n\t\t\tstate.hasPendingChanges = f.hasPendingChanges;\n\t\t\tstate.hasNeverSolved = f.hasNeverSolved;\n\t\t\tif (schema && Object.keys(state.values).length > 0 && schema.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\treport(result) {\n\t\t\tapplySolveResult(state, result);\n\t\t\temit();\n\t\t},\n\n\t\treportError(message) {\n\t\t\tstate.error = message;\n\t\t\temit();\n\t\t},\n\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\n\t\tnotify: emit\n\t};\n}\n","/**\n * Single-in-flight, latest-wins throttle for any async operation: only one run at a\n * time, a newly triggered value overwrites the single pending slot (no queue), and a\n * stale run can be aborted via `AbortController` or timeout.\n *\n * Framework-free: `isRunning` is a plain getter, not a store. A reactive host must\n * subscribe via `onChange` (see `@selvajs/ui`'s rune adapter) — polling the getter\n * directly reads correct values but won't trigger a re-render.\n */\ninterface AsyncThrottleOptions {\n\t/**\n\t * How long one run may take before it is aborted (ms). Required and deliberately\n\t * undefaulted: this is a generic throttle, so it has no basis for guessing what\n\t * the caller's work costs. Callers driving a Selva solve pass the deadline the\n\t * server enforces (`COMPUTE_SOLVE_DEADLINE_MS`) — a shorter local guess would abort\n\t * solves the server would have finished.\n\t */\n\trunDeadlineMs: number;\n\tonChange?: () => void;\n}\n\nexport interface AsyncThrottle<T> {\n\ttrigger: (values: T) => void;\n\treadonly isRunning: boolean;\n\treadonly hasPending: boolean;\n\tcancel: () => void;\n}\n\nexport function createAsyncThrottle<T>(\n\trun: (values: T, signal: AbortSignal) => Promise<void>,\n\toptions: AsyncThrottleOptions\n): AsyncThrottle<T> {\n\tconst { runDeadlineMs, onChange } = options;\n\n\tlet isRunning = false;\n\tlet pendingValues: T | null = null;\n\tlet currentAbortController: AbortController | null = null;\n\n\tfunction setRunning(next: boolean) {\n\t\tif (isRunning === next) return;\n\t\tisRunning = next;\n\t\tonChange?.();\n\t}\n\n\tfunction abortCurrent() {\n\t\tcurrentAbortController?.abort();\n\t\tcurrentAbortController = null;\n\t}\n\n\tasync function execute(values: T) {\n\t\tabortCurrent();\n\n\t\tcurrentAbortController = new AbortController();\n\t\tconst { signal } = currentAbortController;\n\t\tconst deadlineId = setTimeout(() => {\n\t\t\t// Cleared in `finally` on every other path, so firing means a genuine\n\t\t\t// timeout — the only signal for it (the abort itself is swallowed below).\n\t\t\tconsole.warn(`[Solve/throttle] run exceeded ${runDeadlineMs}ms — aborting`);\n\t\t\tcurrentAbortController?.abort();\n\t\t}, runDeadlineMs);\n\n\t\tsetRunning(true);\n\t\ttry {\n\t\t\tawait run(values, signal);\n\t\t} catch (err) {\n\t\t\t// AbortError is expected (timeout or cancel). Non-abort errors must be\n\t\t\t// handled inside `run` — re-throwing here would produce an unhandled\n\t\t\t// rejection because `execute` is always called fire-and-forget.\n\t\t\tif (!(err instanceof Error) || (err.name !== 'AbortError' && err.name !== 'TimeoutError')) {\n\t\t\t\tconsole.error('[Solve/throttle] unhandled error in run:', err);\n\t\t\t}\n\t\t} finally {\n\t\t\tclearTimeout(deadlineId);\n\t\t\tsetRunning(false);\n\t\t\tcurrentAbortController = null;\n\n\t\t\tif (pendingValues !== null) {\n\t\t\t\tconst next = pendingValues;\n\t\t\t\tpendingValues = null;\n\t\t\t\texecute(next);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction trigger(values: T) {\n\t\tif (isRunning) {\n\t\t\tif (pendingValues !== null) {\n\t\t\t\t// Latest-wins: the previously-queued values are dropped, not run.\n\t\t\t\t// `debug`, not `info`: a slider scrub fires this per frame, and it is a trace of\n\t\t\t\t// normal operation. Promoting it would spam a host's console on every drag.\n\t\t\t\t// eslint-disable-next-line no-console -- see above\n\t\t\t\tconsole.debug('[Solve/throttle] superseded pending run (latest-wins)');\n\t\t\t}\n\t\t\tpendingValues = values;\n\t\t} else {\n\t\t\texecute(values);\n\t\t}\n\t}\n\n\tfunction cancel() {\n\t\tpendingValues = null;\n\t\tabortCurrent();\n\t}\n\n\treturn {\n\t\ttrigger,\n\t\tget isRunning() {\n\t\t\treturn isRunning;\n\t\t},\n\t\tget hasPending() {\n\t\t\treturn pendingValues !== null;\n\t\t},\n\t\tcancel\n\t};\n}\n","// LRU cache of solve results keyed on a stable serialization of the inputs, so re-solving\n// something already seen this session (e.g. dragging a slider back) returns instantly\n// instead of round-tripping.\n//\n// A viewer typically takes ownership of every mesh it renders, disposing the previous\n// content on the next update. So the memo can't hand out its own stored instances (they'd\n// get disposed under it) or drop entries silently (their GPU buffers would leak): it keeps\n// private copies via an injected clone/release policy, which also keeps `three` out of this\n// package — `TMesh` stays opaque here.\n\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * Deterministic string key for a set of input values. Sorts object keys at every level so\n * two logically-equal inputs built in different key order collide.\n */\nexport function stableInputKey(values: Record<string, unknown>): string {\n\treturn serialize(values);\n}\n\nfunction serialize(value: unknown): string {\n\tif (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';\n\tif (Array.isArray(value)) return `[${value.map(serialize).join(',')}]`;\n\tconst obj = value as Record<string, unknown>;\n\tconst keys = Object.keys(obj).sort();\n\treturn `{${keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(',')}}`;\n}\n\n/**\n * Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then\n * stores and serves them by reference, which is safe only because nothing frees them behind\n * its back.\n */\nexport interface MeshPolicy<TMesh> {\n\t/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */\n\tclone(meshes: TMesh[]): TMesh[];\n\t/** Must not free anything shared with live content (e.g. materials another object still uses). */\n\trelease(meshes: TMesh[]): void;\n}\n\nexport interface SolveMemoOptions<TMesh> {\n\t/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */\n\tmax?: number;\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\nexport interface SolveMemo<TMesh = unknown> {\n\tget(values: Record<string, unknown>): SolveResult<TMesh> | undefined;\n\tset(values: Record<string, unknown>, result: SolveResult<TMesh>): void;\n\tclear(): void;\n}\n\nexport function createSolveMemo<TMesh = unknown>(\n\toptions: SolveMemoOptions<TMesh> = {}\n): SolveMemo<TMesh> {\n\tconst { max = 16, meshPolicy } = options;\n\tconst entries = new Map<string, SolveResult<TMesh>>();\n\n\tfunction release(result: SolveResult<TMesh>): void {\n\t\tif (result.meshes?.length) meshPolicy?.release(result.meshes);\n\t}\n\n\tfunction copy(result: SolveResult<TMesh>): SolveResult<TMesh> {\n\t\tif (!meshPolicy || !result.meshes?.length) return result;\n\t\treturn { ...result, meshes: meshPolicy.clone(result.meshes) };\n\t}\n\n\tfunction evict(key: string): void {\n\t\tconst entry = entries.get(key);\n\t\tif (entry === undefined) return;\n\t\tentries.delete(key);\n\t\trelease(entry);\n\t}\n\n\treturn {\n\t\tget(values) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tconst hit = entries.get(key);\n\t\t\tif (hit === undefined) return undefined;\n\t\t\tentries.delete(key);\n\t\t\tentries.set(key, hit);\n\t\t\tconsole.info(`[Solve/memo] HIT — served from client memo (${entries.size}/${max})`);\n\t\t\treturn copy(hit);\n\t\t},\n\t\tset(values, result) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tevict(key);\n\t\t\tentries.set(key, copy(result));\n\t\t\twhile (entries.size > max) {\n\t\t\t\tconst oldest = entries.keys().next().value;\n\t\t\t\tif (oldest === undefined) break;\n\t\t\t\tevict(oldest);\n\t\t\t\tconsole.info(`[Solve/memo] evicted LRU entry (cap ${max})`);\n\t\t\t}\n\t\t},\n\t\tclear() {\n\t\t\tif (entries.size > 0) {\n\t\t\t\tconsole.info(`[Solve/memo] cleared ${entries.size} entries (definition changed)`);\n\t\t\t}\n\t\t\tentries.forEach(release);\n\t\t\tentries.clear();\n\t\t}\n\t};\n}\n","import { createAsyncThrottle } from '../async-throttle.js';\nimport { createSolveMemo, type MeshPolicy } from '../solve-memo.js';\nimport type { SolveFn } from '../../shared/solve-fn.js';\nimport type { SolveDriver, SolveReporter } from './driver.js';\n\nexport interface RequestResponseDriverOptions<TMesh> {\n\t/** How long one solve may take before the client aborts it (ms). Pass the server's `COMPUTE_SOLVE_DEADLINE_MS` so the two agree. */\n\tsolveDeadlineMs: number;\n\tonChange?: () => void;\n\t/** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\n/**\n * `getReporter` is lazy (`() => session`) because the session and driver reference each\n * other — the host builds the driver first, then the session, then can hand this back.\n */\nexport function createRequestResponseDriver<TMesh = unknown>(\n\tonSolve: SolveFn<TMesh>,\n\tgetReporter: () => SolveReporter<TMesh>,\n\toptions: RequestResponseDriverOptions<TMesh>\n): SolveDriver {\n\tconst { meshPolicy, solveDeadlineMs, onChange } = options;\n\n\t// Checked inside the throttled run, not before triggering it, so a hit only serves\n\t// after the throttle's latest-wins ordering has picked these values to run.\n\tconst memo = createSolveMemo<TMesh>({ meshPolicy });\n\n\tconst throttle = createAsyncThrottle<Record<string, unknown>>(\n\t\tasync (values, signal) => {\n\t\t\tconst cached = memo.get(values);\n\t\t\tif (cached !== undefined) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tgetReporter().report(cached);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await onSolve(values, signal);\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve completed after abort — result discarded');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Stamped here rather than in the SolveFn: a memo hit never calls it, so values\n\t\t\t\t// captured inside would pair the served result with whatever solved last.\n\t\t\t\tconst stamped = { ...result, values };\n\t\t\t\tmemo.set(values, stamped);\n\t\t\t\tgetReporter().report(stamped);\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve aborted (superseded, cancelled, or timed out)');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconsole.warn('[Solve/driver] solve failed:', err);\n\t\t\t\tgetReporter().reportError(err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t},\n\t\t// The throttle is generic, so the solve deadline is what bounds one of its runs.\n\t\t{ runDeadlineMs: solveDeadlineMs, onChange }\n\t);\n\n\treturn {\n\t\tsolve(values) {\n\t\t\tthrottle.trigger(values);\n\t\t},\n\t\tcancel() {\n\t\t\tthrottle.cancel();\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn throttle.isRunning;\n\t\t},\n\t\tclearCache() {\n\t\t\tmemo.clear();\n\t\t}\n\t};\n}\n","/**\n * `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a\n * `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +\n * `SolveEngine` produce server-side) and unpacks the result.\n *\n * Ports the resilience behavior an interactive solve endpoint needs regardless\n * of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response\n * guarding, and abort handling at every await point — so a new consumer gets it\n * for free instead of re-deriving it by hand. Mesh decoding is NOT built in:\n * this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a\n * viewer-enabled consumer supplies its own `meshes.extract` hook (typically\n * `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).\n */\n\nimport {\n\tGrasshopperResponseProcessor,\n\ttype GrasshopperComputeResponse\n} from '@selvajs/compute/grasshopper';\nimport type { SolveFn, SolveResult } from '../shared/solve-fn.js';\n\nexport interface ComputeFetchSolveFnOptions<TMesh = unknown> {\n\t/** The compute endpoint to POST to, e.g. `/api/compute`. */\n\tendpoint: string;\n\t/** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */\n\tdefinitionUrl: () => string;\n\t/** Schema input params, sent as the request body's `inputs`. */\n\tinputs: () => Array<{ id: string }>;\n\t/** Schema output params — drives byId/byName output extraction after a solve. */\n\toutputs: () => Array<{ id: string; nickname?: string }>;\n\tchannel?: () => 'live' | 'draft' | undefined;\n\tversionId?: () => string | null | undefined;\n\t/** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */\n\tmeshes?: {\n\t\textract: (\n\t\t\tresponse: GrasshopperComputeResponse,\n\t\t\topts: { debug: boolean }\n\t\t) => TMesh[] | Promise<TMesh[]>;\n\t};\n\t/** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */\n\tdebug?: boolean;\n\t/** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */\n\tonRateLimited?: (retryAfterSeconds: number) => void;\n\t/** Called on a 401 or a login-page redirect. Default: throws an actionable \"reload to sign in again\" Error. */\n\tonSessionExpired?: () => void;\n}\n\nfunction parseServerTiming(header: string | null): Record<string, number> {\n\tconst out: Record<string, number> = {};\n\tif (!header) return out;\n\tfor (const part of header.split(',')) {\n\t\tconst [name, ...params] = part.split(';').map((s) => s.trim());\n\t\tconst dur = params.find((p) => p.startsWith('dur='));\n\t\tif (!name || !dur) continue;\n\t\tconst ms = Number(dur.slice(4));\n\t\tif (Number.isFinite(ms)) out[name] = ms;\n\t}\n\treturn out;\n}\n\nconst defaultOnRateLimited = (retryAfterSeconds: number): never => {\n\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(retryAfterSeconds)}s.`);\n};\n\nconst defaultOnSessionExpired = (): never => {\n\tthrow new Error(\n\t\t'Your session has expired. Sign in again in a new tab, then re-run — your inputs are preserved.'\n\t);\n};\n\nexport function createComputeFetchSolveFn<TMesh = unknown>(\n\topts: ComputeFetchSolveFnOptions<TMesh>\n): SolveFn<TMesh, GrasshopperComputeResponse> {\n\tconst debug = opts.debug ?? false;\n\tconst log = (...args: unknown[]) => {\n\t\tif (debug) console.info(...args);\n\t};\n\n\tlet cooldownUntil = 0;\n\n\treturn async (values, signal): Promise<SolveResult<TMesh, GrasshopperComputeResponse>> => {\n\t\tconst remainingMs = cooldownUntil - Date.now();\n\t\tif (remainingMs > 0) {\n\t\t\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(remainingMs / 1000)}s.`);\n\t\t}\n\n\t\tconst solveStart = performance.now();\n\t\tconst inputs = opts.inputs();\n\t\tconst outputs = opts.outputs();\n\t\tconst channel = opts.channel?.();\n\t\tconst versionId = opts.versionId?.();\n\n\t\tconst payload = JSON.stringify({\n\t\t\tinputs,\n\t\t\tvalues,\n\t\t\tdefinitionUrl: opts.definitionUrl(),\n\t\t\t...(versionId ? { versionId } : channel === 'draft' ? { channel: 'draft' } : {})\n\t\t});\n\n\t\tlet res: Response;\n\t\ttry {\n\t\t\tres = await fetch(opts.endpoint, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: payload,\n\t\t\t\tsignal\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (signal.aborted) {\n\t\t\t\tlog('[Compute] solve aborted during fetch — empty result discarded');\n\t\t\t\treturn { outputs: {} };\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t'Request blocked — your session may have expired. Reload the page to sign in again.',\n\t\t\t\t{ cause: err }\n\t\t\t);\n\t\t}\n\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted after headers — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\n\t\tconst redirectedToLogin = res.redirected && new URL(res.url).pathname === '/login';\n\t\tif (res.status === 401 || redirectedToLogin) {\n\t\t\t(opts.onSessionExpired ?? defaultOnSessionExpired)();\n\t\t}\n\n\t\tif (!res.ok) {\n\t\t\tconst errorBody = await res.text().catch(() => '');\n\t\t\tlet d: { message?: string; retryAfter?: number } = {};\n\t\t\ttry {\n\t\t\t\td = JSON.parse(errorBody);\n\t\t\t} catch {\n\t\t\t\tif (errorBody) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[Compute] non-JSON error body (HTTP ${res.status}):`,\n\t\t\t\t\t\terrorBody.slice(0, 300)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (res.status === 503 || res.status === 504) {\n\t\t\t\t// The server names the failure class (unreachable, bad API key,\n\t\t\t\t// rate-limited, timed out) — show it rather than a blanket \"offline\".\n\t\t\t\tthrow new Error(\n\t\t\t\t\td.message || 'Compute server is offline or unreachable. Please try again later.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (res.status === 429) {\n\t\t\t\tconst retryAfter = Number(res.headers.get('Retry-After')) || Number(d.retryAfter) || 5;\n\t\t\t\tcooldownUntil = Date.now() + retryAfter * 1000;\n\t\t\t\t(opts.onRateLimited ?? defaultOnRateLimited)(retryAfter);\n\t\t\t}\n\t\t\tthrow new Error(d.message || `Compute error (HTTP ${res.status})`);\n\t\t}\n\n\t\tconst ttfbMs = performance.now() - solveStart;\n\n\t\tconst downloadStart = performance.now();\n\t\tconst bodyText = await res.text();\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted mid-download — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\t\tconst downloadMs = performance.now() - downloadStart;\n\t\tconst bytes = bodyText.length;\n\n\t\tlet solved: GrasshopperComputeResponse;\n\t\ttry {\n\t\t\tsolved = JSON.parse(bodyText);\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t'Received an invalid response from the server. Reload the page and try again.',\n\t\t\t\t{\n\t\t\t\t\tcause: err\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\tconst processor = new GrasshopperResponseProcessor(solved, false);\n\n\t\tconst meshes = opts.meshes ? await opts.meshes.extract(processor.response, { debug }) : [];\n\n\t\tconst resultOutputs: Record<string, unknown> = {};\n\t\tfor (const o of outputs) {\n\t\t\tconst byId = processor.getValue({ byId: o.id }, { parseValues: true });\n\t\t\tresultOutputs[o.id] =\n\t\t\t\tbyId ??\n\t\t\t\t(o.nickname\n\t\t\t\t\t? processor.getValue({ byName: o.nickname }, { parseValues: true })\n\t\t\t\t\t: undefined);\n\t\t}\n\n\t\tif (debug) {\n\t\t\tconst valuesBytes = JSON.stringify(values).length;\n\t\t\tconst serverTiming = parseServerTiming(res.headers.get('Server-Timing'));\n\t\t\tconst serverTotal = serverTiming.total ?? 0;\n\t\t\tconst networkMs = Math.max(0, ttfbMs - serverTotal);\n\t\t\tconst sizeMB = bytes / (1024 * 1024);\n\t\t\tconst size = sizeMB >= 1 ? `${sizeMB.toFixed(2)} MB` : `${(bytes / 1024).toFixed(0)} KB`;\n\t\t\tconst mbps = downloadMs > 0 ? (sizeMB / (downloadMs / 1000)).toFixed(1) : '∞';\n\t\t\tconst heapMB = (performance as Performance & { memory?: { usedJSHeapSize: number } }).memory\n\t\t\t\t?.usedJSHeapSize;\n\t\t\tconst heap = heapMB !== undefined ? ` | heap=${(heapMB / (1024 * 1024)).toFixed(0)} MB` : '';\n\t\t\tlog(\n\t\t\t\t`[Compute] req=${(payload.length / 1024).toFixed(0)}KB (values ${(valuesBytes / 1024).toFixed(0)}KB) | ` +\n\t\t\t\t\t`ttfb=${ttfbMs.toFixed(0)}ms (network≈${networkMs.toFixed(0)} + server ${serverTotal.toFixed(0)}) | ` +\n\t\t\t\t\t`download=${downloadMs.toFixed(0)}ms (${size} @ ${mbps} MB/s)${heap}`\n\t\t\t);\n\t\t\tif (serverTiming.selva_cache !== undefined) {\n\t\t\t\tconst cacheMsg = serverTiming.selva_cache\n\t\t\t\t\t? 'HIT — served without calling compute'\n\t\t\t\t\t: 'miss — solved on Rhino.Compute';\n\t\t\t\tlog(`[Compute] └─ cache: ${cacheMsg}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\toutputs: resultOutputs,\n\t\t\tmeshes,\n\t\t\terrors: solved.errors ?? [],\n\t\t\twarnings: solved.warnings ?? [],\n\t\t\tsource: solved\n\t\t};\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.cjs","names":["getInputItems","getDefaultValue","GrasshopperResponseProcessor"],"sources":["../src/client/external-storage.ts","../src/client/solve-session-core.ts","../src/client/solve-session.ts","../src/client/async-throttle.ts","../src/client/solve-memo.ts","../src/client/drivers/request-response.ts","../src/client/compute-fetch-solve-fn.ts"],"sourcesContent":["// Transit storage for inputs with source.kind === 'client': a producer route writes the\n// value here, the solver route reads it back. Keyed by (scopeKey, inputId) so values\n// don't bleed across solver contexts — scopeKey is whatever uniquely identifies one\n// (sessionId in plugin-ui/preview, definition guid in selva/library).\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getInputItems } from '@selvajs/schemas';\n\nconst STORAGE_PREFIX = 'external';\n\nfunction makeKey(scopeKey: string, inputId: string): string {\n\treturn `${STORAGE_PREFIX}:${scopeKey}:${inputId}`;\n}\n\nexport interface ExternalValueRef {\n\tscopeKey: string;\n\tinputId: string;\n}\n\nexport function writeExternalValue(args: ExternalValueRef & { value: unknown }): void {\n\tconst { scopeKey, inputId, value } = args;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.setItem(makeKey(scopeKey, inputId), JSON.stringify(value));\n}\n\nexport function readExternalValue(ref: ExternalValueRef): unknown | undefined {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return undefined;\n\tif (typeof sessionStorage === 'undefined') return undefined;\n\tconst raw = sessionStorage.getItem(makeKey(scopeKey, inputId));\n\tif (raw === null) return undefined;\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function clearExternalValue(ref: ExternalValueRef): void {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.removeItem(makeKey(scopeKey, inputId));\n}\n\nexport interface ExternalInput {\n\tparamId: string;\n\tdisplayName: string;\n}\n\nexport function getExternalInputs(schema: UISchema): ExternalInput[] {\n\treturn getInputItems(schema)\n\t\t.filter((item) => item.source?.kind === 'client')\n\t\t.map((item) => ({\n\t\t\tparamId: item.paramId,\n\t\t\tdisplayName: item.displayName ?? item.paramId\n\t\t}));\n}\n","// Pure transition logic for a Solve Session.\n//\n// This module holds the value/lifecycle state machine with no transport and no state\n// ownership: how values are seeded (including client-sourced hydration), when a value\n// change should dispatch a solve vs. defer it, and what a reported solve result does to\n// the flags. `solve-session.ts` is a thin shell that owns the state object and the\n// subscriber set; every decision lives here, which is what makes it directly testable.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getDefaultValue } from '@selvajs/schemas';\nimport { getExternalInputs, type ExternalValueRef } from './external-storage.js';\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * The slice of a reported result the session keeps addressable, so a host can commit exactly\n * what the viewer is showing — `source` and `values` included, already memo-correct.\n *\n * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what\n * it renders on the next scene update, which is why the memo carries a `MeshPolicy`\n * clone/release contract at all. Retaining them here would hand out disposed instances after\n * the next solve, with no policy governing them and no owner. Everything left is inert JSON;\n * live meshes stay on `session.meshes`.\n */\nexport type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;\n\nexport interface SolveSessionState {\n\tvalues: Record<string, unknown>;\n\terror: string;\n\tcomputeErrors: string[];\n\tcomputeWarnings: string[];\n\tmeshes: unknown[];\n\t/** Values changed since the last solve, in manual (instanceSolve === false) mode. */\n\tpendingValues: Record<string, unknown>;\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n\t/**\n\t * The last result reported, minus its meshes. Null before the first solve.\n\t *\n\t * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a\n\t * value change and the next report it describes the *previous* input set. That is what a\n\t * commit gate wants — what is on screen, not what is pending — so `lastResult.values`\n\t * disagreeing with `state.values` mid-scrub is the feature, not a bug.\n\t */\n\tlastResult: RetainedSolveResult | null;\n}\n\nexport type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;\n\n/**\n * Client-sourced inputs are hydrated from `read` and left absent when no stored value\n * exists, so the missing-inputs panel can detect them. Other inputs fall back to their\n * declared default, then the paramType default.\n */\nexport function buildInitialValues(\n\tschema: UISchema,\n\tscopeKey: string,\n\tread: ExternalReader\n): Record<string, unknown> {\n\tconst clientSet = new Set(getExternalInputs(schema).map((e) => e.paramId));\n\tconst values: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (clientSet.has(input.id)) {\n\t\t\tconst stored = read({ scopeKey, inputId: input.id });\n\t\t\tif (stored !== undefined) values[input.id] = stored;\n\t\t\tcontinue;\n\t\t}\n\t\tvalues[input.id] = input.default ?? getDefaultValue(input.paramType);\n\t}\n\tfor (const output of schema.outputs) {\n\t\tvalues[output.id] = null;\n\t}\n\treturn values;\n}\n\n/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */\nexport function makeInitialFlags(instanceSolve: boolean | undefined): {\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n} {\n\tconst manual = instanceSolve === false;\n\treturn { hasPendingChanges: manual, hasNeverSolved: manual };\n}\n\n/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */\nexport function applyValueChange(\n\tstate: SolveSessionState,\n\tid: string,\n\tvalue: unknown,\n\tinstanceSolve: boolean | undefined\n): { state: SolveSessionState; shouldSolve: boolean } {\n\tstate.values[id] = value;\n\tif (instanceSolve === false) {\n\t\tstate.pendingValues[id] = value;\n\t\tstate.hasPendingChanges = true;\n\t\treturn { state, shouldSolve: false };\n\t}\n\treturn { state, shouldSolve: true };\n}\n\n/**\n * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value\n * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB\n * options-list output showed this can re-upload a multi-MB payload no backend reads.\n */\nexport function pickInputValues(\n\tschema: UISchema | undefined,\n\tvalues: Record<string, unknown>\n): Record<string, unknown> {\n\tif (!schema?.inputs) return values;\n\tconst picked: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (input.id in values) picked[input.id] = values[input.id];\n\t}\n\treturn picked;\n}\n\nexport function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState {\n\tstate.error = '';\n\tstate.computeErrors = result.errors ?? [];\n\tstate.computeWarnings = result.warnings ?? [];\n\tstate.meshes = result.meshes ?? [];\n\tconst { meshes: _meshes, ...retained } = result;\n\tstate.lastResult = retained;\n\tObject.assign(state.values, result.outputs);\n\tstate.pendingValues = {};\n\tstate.hasPendingChanges = false;\n\tstate.hasNeverSolved = false;\n\treturn state;\n}\n","// A framework-free shell over the pure transition logic in solve-session-core.ts: owns the\n// live values/flags, delegates every transition to the core, and drives solves through a\n// transport-agnostic SolveDriver.\n//\n// State is exposed as plain getters; it carries no framework reactivity of its own, so a\n// Svelte host must wrap it — see `useSolveSession.svelte.ts` in `@selvajs/ui`, which\n// republishes these getters as $state. Reading a getter without subscribing gives a\n// correct value but will not re-render.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { readExternalValue } from './external-storage.js';\nimport type { SolveResult } from '../shared/solve-fn.js';\nimport type { SolveDriver } from './drivers/driver.js';\nimport {\n\tbuildInitialValues,\n\tmakeInitialFlags,\n\tapplyValueChange,\n\tapplySolveResult,\n\tpickInputValues,\n\ttype SolveSessionState,\n\ttype RetainedSolveResult\n} from './solve-session-core.js';\n\nexport interface SolveSession {\n\treadonly values: Record<string, unknown>;\n\treadonly error: string;\n\treadonly computeErrors: string[];\n\treadonly computeWarnings: string[];\n\treadonly meshes: unknown[];\n\t/**\n\t * The last reported result without its meshes — what the viewer is currently showing,\n\t * including the `source`/`values` pair a commit path needs. Null before the first solve,\n\t * and again after `rebuild()`. Survives `reportError`: a failed solve leaves the last good\n\t * geometry on screen, so a host committing what it sees should still see what produced it.\n\t */\n\treadonly lastResult: RetainedSolveResult | null;\n\treadonly hasPendingChanges: boolean;\n\treadonly hasNeverSolved: boolean;\n\treadonly isSolving: boolean;\n\t/** `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. */\n\tsetValue(id: string, value: unknown, forceSolve?: boolean): void;\n\tsolve(): void;\n\t/** Merges incoming values, then solves (auto) or marks dirty (manual). */\n\tloadValues(incoming: Record<string, unknown>): void;\n\trebuild(schema: UISchema, scopeKey: string): void;\n\treport(result: SolveResult): void;\n\treportError(message: string): void;\n\t/** Fires after any mutation of the getters above, including driver-reported `isSolving` transitions. */\n\tsubscribe(listener: () => void): () => void;\n\t/** For driver-owned state the session only forwards: `isSolving` lives on the driver, so wire this to its `onChange`. */\n\tnotify(): void;\n}\n\nexport interface SolveSessionArgs {\n\tschema: UISchema;\n\tscopeKey: string;\n\tdriver: SolveDriver;\n}\n\nexport function createSolveSession(args: SolveSessionArgs): SolveSession {\n\tlet currentSchema = args.schema;\n\n\tconst flags = makeInitialFlags(currentSchema?.instanceSolve);\n\tconst state: SolveSessionState = {\n\t\tvalues: buildInitialValues(currentSchema, args.scopeKey, readExternalValue),\n\t\terror: '',\n\t\tcomputeErrors: [],\n\t\tcomputeWarnings: [],\n\t\tmeshes: [],\n\t\tpendingValues: {},\n\t\thasPendingChanges: flags.hasPendingChanges,\n\t\thasNeverSolved: flags.hasNeverSolved,\n\t\tlastResult: null\n\t};\n\n\tconst listeners = new Set<() => void>();\n\tconst emit = () => listeners.forEach((l) => l());\n\n\tfunction dispatch() {\n\t\t// Copy: state.values is the live map, and a driver may hold the payload across an\n\t\t// async solve — a later setValue must not mutate what's already in flight.\n\t\targs.driver.solve({ ...pickInputValues(currentSchema, state.values) });\n\t\temit();\n\t}\n\n\treturn {\n\t\tget values() {\n\t\t\treturn state.values;\n\t\t},\n\t\tget error() {\n\t\t\treturn state.error;\n\t\t},\n\t\tget computeErrors() {\n\t\t\treturn state.computeErrors;\n\t\t},\n\t\tget computeWarnings() {\n\t\t\treturn state.computeWarnings;\n\t\t},\n\t\tget meshes() {\n\t\t\treturn state.meshes;\n\t\t},\n\t\tget lastResult() {\n\t\t\treturn state.lastResult;\n\t\t},\n\t\tget hasPendingChanges() {\n\t\t\treturn state.hasPendingChanges;\n\t\t},\n\t\tget hasNeverSolved() {\n\t\t\treturn state.hasNeverSolved;\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn args.driver.isSolving;\n\t\t},\n\n\t\tsetValue(id, value, forceSolve = false) {\n\t\t\tconst { shouldSolve } = applyValueChange(state, id, value, currentSchema?.instanceSolve);\n\t\t\tif (shouldSolve || forceSolve) {\n\t\t\t\tif (forceSolve && !shouldSolve) {\n\t\t\t\t\tstate.pendingValues = {};\n\t\t\t\t\tstate.hasPendingChanges = false;\n\t\t\t\t}\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\tsolve() {\n\t\t\tdispatch();\n\t\t},\n\n\t\tloadValues(incoming) {\n\t\t\tObject.assign(state.values, incoming);\n\t\t\tif (currentSchema?.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\tstate.hasPendingChanges = true;\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\trebuild(schema, scopeKey) {\n\t\t\tcurrentSchema = schema;\n\t\t\t// The new definition has its own input space; a matching input key from the prior\n\t\t\t// one must not serve that definition's cached result.\n\t\t\targs.driver.clearCache?.();\n\t\t\tstate.meshes = [];\n\t\t\t// Same reason as clearCache: a retained result belongs to the previous definition.\n\t\t\tstate.lastResult = null;\n\t\t\tstate.error = '';\n\t\t\tstate.computeErrors = [];\n\t\t\tstate.computeWarnings = [];\n\t\t\tstate.pendingValues = {};\n\t\t\tstate.values = buildInitialValues(schema, scopeKey, readExternalValue);\n\t\t\tconst f = makeInitialFlags(schema?.instanceSolve);\n\t\t\tstate.hasPendingChanges = f.hasPendingChanges;\n\t\t\tstate.hasNeverSolved = f.hasNeverSolved;\n\t\t\tif (schema && Object.keys(state.values).length > 0 && schema.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\treport(result) {\n\t\t\tapplySolveResult(state, result);\n\t\t\temit();\n\t\t},\n\n\t\treportError(message) {\n\t\t\tstate.error = message;\n\t\t\temit();\n\t\t},\n\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\n\t\tnotify: emit\n\t};\n}\n","/**\n * Single-in-flight, latest-wins throttle for any async operation: only one run at a\n * time, a newly triggered value overwrites the single pending slot (no queue), and a\n * stale run can be aborted via `AbortController` or timeout.\n *\n * Framework-free: `isRunning` is a plain getter, not a store. A reactive host must\n * subscribe via `onChange` (see `@selvajs/ui`'s rune adapter) — polling the getter\n * directly reads correct values but won't trigger a re-render.\n */\ninterface AsyncThrottleOptions {\n\t/**\n\t * How long one run may take before it is aborted (ms). Required and deliberately\n\t * undefaulted: this is a generic throttle, so it has no basis for guessing what\n\t * the caller's work costs. Callers driving a Selva solve pass the deadline the\n\t * server enforces (`COMPUTE_SOLVE_DEADLINE_MS`) — a shorter local guess would abort\n\t * solves the server would have finished.\n\t */\n\trunDeadlineMs: number;\n\tonChange?: () => void;\n}\n\nexport interface AsyncThrottle<T> {\n\ttrigger: (values: T) => void;\n\treadonly isRunning: boolean;\n\treadonly hasPending: boolean;\n\tcancel: () => void;\n}\n\nexport function createAsyncThrottle<T>(\n\trun: (values: T, signal: AbortSignal) => Promise<void>,\n\toptions: AsyncThrottleOptions\n): AsyncThrottle<T> {\n\tconst { runDeadlineMs, onChange } = options;\n\n\tlet isRunning = false;\n\tlet pendingValues: T | null = null;\n\tlet currentAbortController: AbortController | null = null;\n\n\tfunction setRunning(next: boolean) {\n\t\tif (isRunning === next) return;\n\t\tisRunning = next;\n\t\tonChange?.();\n\t}\n\n\tfunction abortCurrent() {\n\t\tcurrentAbortController?.abort();\n\t\tcurrentAbortController = null;\n\t}\n\n\tasync function execute(values: T) {\n\t\tabortCurrent();\n\n\t\tcurrentAbortController = new AbortController();\n\t\tconst { signal } = currentAbortController;\n\t\tconst deadlineId = setTimeout(() => {\n\t\t\t// Cleared in `finally` on every other path, so firing means a genuine\n\t\t\t// timeout — the only signal for it (the abort itself is swallowed below).\n\t\t\tconsole.warn(`[Solve/throttle] run exceeded ${runDeadlineMs}ms — aborting`);\n\t\t\tcurrentAbortController?.abort();\n\t\t}, runDeadlineMs);\n\n\t\tsetRunning(true);\n\t\ttry {\n\t\t\tawait run(values, signal);\n\t\t} catch (err) {\n\t\t\t// AbortError is expected (timeout or cancel). Non-abort errors must be\n\t\t\t// handled inside `run` — re-throwing here would produce an unhandled\n\t\t\t// rejection because `execute` is always called fire-and-forget.\n\t\t\tif (!(err instanceof Error) || (err.name !== 'AbortError' && err.name !== 'TimeoutError')) {\n\t\t\t\tconsole.error('[Solve/throttle] unhandled error in run:', err);\n\t\t\t}\n\t\t} finally {\n\t\t\tclearTimeout(deadlineId);\n\t\t\tsetRunning(false);\n\t\t\tcurrentAbortController = null;\n\n\t\t\tif (pendingValues !== null) {\n\t\t\t\tconst next = pendingValues;\n\t\t\t\tpendingValues = null;\n\t\t\t\texecute(next);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction trigger(values: T) {\n\t\tif (isRunning) {\n\t\t\tif (pendingValues !== null) {\n\t\t\t\t// Latest-wins: the previously-queued values are dropped, not run.\n\t\t\t\t// `debug`, not `info`: a slider scrub fires this per frame, and it is a trace of\n\t\t\t\t// normal operation. Promoting it would spam a host's console on every drag.\n\t\t\t\t// eslint-disable-next-line no-console -- see above\n\t\t\t\tconsole.debug('[Solve/throttle] superseded pending run (latest-wins)');\n\t\t\t}\n\t\t\tpendingValues = values;\n\t\t} else {\n\t\t\texecute(values);\n\t\t}\n\t}\n\n\tfunction cancel() {\n\t\tpendingValues = null;\n\t\tabortCurrent();\n\t}\n\n\treturn {\n\t\ttrigger,\n\t\tget isRunning() {\n\t\t\treturn isRunning;\n\t\t},\n\t\tget hasPending() {\n\t\t\treturn pendingValues !== null;\n\t\t},\n\t\tcancel\n\t};\n}\n","// LRU cache of solve results keyed on a stable serialization of the inputs, so re-solving\n// something already seen this session (e.g. dragging a slider back) returns instantly\n// instead of round-tripping.\n//\n// A viewer typically takes ownership of every mesh it renders, disposing the previous\n// content on the next update. So the memo can't hand out its own stored instances (they'd\n// get disposed under it) or drop entries silently (their GPU buffers would leak): it keeps\n// private copies via an injected clone/release policy, which also keeps `three` out of this\n// package — `TMesh` stays opaque here.\n\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * Deterministic string key for a set of input values. Sorts object keys at every level so\n * two logically-equal inputs built in different key order collide.\n */\nexport function stableInputKey(values: Record<string, unknown>): string {\n\treturn serialize(values);\n}\n\nfunction serialize(value: unknown): string {\n\tif (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';\n\tif (Array.isArray(value)) return `[${value.map(serialize).join(',')}]`;\n\tconst obj = value as Record<string, unknown>;\n\tconst keys = Object.keys(obj).sort();\n\treturn `{${keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(',')}}`;\n}\n\n/**\n * Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then\n * stores and serves them by reference, which is safe only because nothing frees them behind\n * its back.\n */\nexport interface MeshPolicy<TMesh> {\n\t/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */\n\tclone(meshes: TMesh[]): TMesh[];\n\t/** Must not free anything shared with live content (e.g. materials another object still uses). */\n\trelease(meshes: TMesh[]): void;\n}\n\nexport interface SolveMemoOptions<TMesh> {\n\t/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */\n\tmax?: number;\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\nexport interface SolveMemo<TMesh = unknown> {\n\tget(values: Record<string, unknown>): SolveResult<TMesh> | undefined;\n\tset(values: Record<string, unknown>, result: SolveResult<TMesh>): void;\n\tclear(): void;\n}\n\nexport function createSolveMemo<TMesh = unknown>(\n\toptions: SolveMemoOptions<TMesh> = {}\n): SolveMemo<TMesh> {\n\tconst { max = 16, meshPolicy } = options;\n\tconst entries = new Map<string, SolveResult<TMesh>>();\n\n\tfunction release(result: SolveResult<TMesh>): void {\n\t\tif (result.meshes?.length) meshPolicy?.release(result.meshes);\n\t}\n\n\tfunction copy(result: SolveResult<TMesh>): SolveResult<TMesh> {\n\t\tif (!meshPolicy || !result.meshes?.length) return result;\n\t\treturn { ...result, meshes: meshPolicy.clone(result.meshes) };\n\t}\n\n\tfunction evict(key: string): void {\n\t\tconst entry = entries.get(key);\n\t\tif (entry === undefined) return;\n\t\tentries.delete(key);\n\t\trelease(entry);\n\t}\n\n\treturn {\n\t\tget(values) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tconst hit = entries.get(key);\n\t\t\tif (hit === undefined) return undefined;\n\t\t\tentries.delete(key);\n\t\t\tentries.set(key, hit);\n\t\t\tconsole.info(`[Solve/memo] HIT — served from client memo (${entries.size}/${max})`);\n\t\t\treturn copy(hit);\n\t\t},\n\t\tset(values, result) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tevict(key);\n\t\t\tentries.set(key, copy(result));\n\t\t\twhile (entries.size > max) {\n\t\t\t\tconst oldest = entries.keys().next().value;\n\t\t\t\tif (oldest === undefined) break;\n\t\t\t\tevict(oldest);\n\t\t\t\tconsole.info(`[Solve/memo] evicted LRU entry (cap ${max})`);\n\t\t\t}\n\t\t},\n\t\tclear() {\n\t\t\tif (entries.size > 0) {\n\t\t\t\tconsole.info(`[Solve/memo] cleared ${entries.size} entries (definition changed)`);\n\t\t\t}\n\t\t\tentries.forEach(release);\n\t\t\tentries.clear();\n\t\t}\n\t};\n}\n","import { createAsyncThrottle } from '../async-throttle.js';\nimport { createSolveMemo, type MeshPolicy } from '../solve-memo.js';\nimport type { SolveFn } from '../../shared/solve-fn.js';\nimport type { SolveDriver, SolveReporter } from './driver.js';\n\nexport interface RequestResponseDriverOptions<TMesh> {\n\t/** How long one solve may take before the client aborts it (ms). Pass the server's `COMPUTE_SOLVE_DEADLINE_MS` so the two agree. */\n\tsolveDeadlineMs: number;\n\tonChange?: () => void;\n\t/** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\n/**\n * `getReporter` is lazy (`() => session`) because the session and driver reference each\n * other — the host builds the driver first, then the session, then can hand this back.\n */\nexport function createRequestResponseDriver<TMesh = unknown>(\n\tonSolve: SolveFn<TMesh>,\n\tgetReporter: () => SolveReporter<TMesh>,\n\toptions: RequestResponseDriverOptions<TMesh>\n): SolveDriver {\n\tconst { meshPolicy, solveDeadlineMs, onChange } = options;\n\n\t// Checked inside the throttled run, not before triggering it, so a hit only serves\n\t// after the throttle's latest-wins ordering has picked these values to run.\n\tconst memo = createSolveMemo<TMesh>({ meshPolicy });\n\n\tconst throttle = createAsyncThrottle<Record<string, unknown>>(\n\t\tasync (values, signal) => {\n\t\t\tconst cached = memo.get(values);\n\t\t\tif (cached !== undefined) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tgetReporter().report(cached);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await onSolve(values, signal);\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve completed after abort — result discarded');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Stamped here rather than in the SolveFn: a memo hit never calls it, so values\n\t\t\t\t// captured inside would pair the served result with whatever solved last.\n\t\t\t\tconst stamped = { ...result, values };\n\t\t\t\tmemo.set(values, stamped);\n\t\t\t\tgetReporter().report(stamped);\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve aborted (superseded, cancelled, or timed out)');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconsole.warn('[Solve/driver] solve failed:', err);\n\t\t\t\tgetReporter().reportError(err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t},\n\t\t// The throttle is generic, so the solve deadline is what bounds one of its runs.\n\t\t{ runDeadlineMs: solveDeadlineMs, onChange }\n\t);\n\n\treturn {\n\t\tsolve(values) {\n\t\t\tthrottle.trigger(values);\n\t\t},\n\t\tcancel() {\n\t\t\tthrottle.cancel();\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn throttle.isRunning;\n\t\t},\n\t\tclearCache() {\n\t\t\tmemo.clear();\n\t\t}\n\t};\n}\n","/**\n * `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a\n * `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +\n * `SolveEngine` produce server-side) and unpacks the result.\n *\n * Ports the resilience behavior an interactive solve endpoint needs regardless\n * of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response\n * guarding, and abort handling at every await point — so a new consumer gets it\n * for free instead of re-deriving it by hand. Mesh decoding is NOT built in:\n * this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a\n * viewer-enabled consumer supplies its own `meshes.extract` hook (typically\n * `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).\n */\n\nimport {\n\tGrasshopperResponseProcessor,\n\ttype GrasshopperComputeResponse\n} from '@selvajs/compute/grasshopper';\nimport type { SolveFn, SolveResult } from '../shared/solve-fn.js';\n\nexport interface ComputeFetchSolveFnOptions<TMesh = unknown> {\n\t/** The compute endpoint to POST to, e.g. `/api/compute`. */\n\tendpoint: string;\n\t/** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */\n\tdefinitionUrl: () => string;\n\t/** Schema input params, sent as the request body's `inputs`. */\n\tinputs: () => Array<{ id: string }>;\n\t/** Schema output params — drives byId/byName output extraction after a solve. */\n\toutputs: () => Array<{ id: string; nickname?: string }>;\n\tchannel?: () => 'live' | 'draft' | undefined;\n\tversionId?: () => string | null | undefined;\n\t/** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */\n\tmeshes?: {\n\t\textract: (\n\t\t\tresponse: GrasshopperComputeResponse,\n\t\t\topts: { debug: boolean }\n\t\t) => TMesh[] | Promise<TMesh[]>;\n\t};\n\t/** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */\n\tdebug?: boolean;\n\t/** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */\n\tonRateLimited?: (retryAfterSeconds: number) => void;\n\t/** Called on a 401 or a login-page redirect. Default: throws an actionable \"reload to sign in again\" Error. */\n\tonSessionExpired?: () => void;\n}\n\nfunction parseServerTiming(header: string | null): Record<string, number> {\n\tconst out: Record<string, number> = {};\n\tif (!header) return out;\n\tfor (const part of header.split(',')) {\n\t\tconst [name, ...params] = part.split(';').map((s) => s.trim());\n\t\tconst dur = params.find((p) => p.startsWith('dur='));\n\t\tif (!name || !dur) continue;\n\t\tconst ms = Number(dur.slice(4));\n\t\tif (Number.isFinite(ms)) out[name] = ms;\n\t}\n\treturn out;\n}\n\nconst defaultOnRateLimited = (retryAfterSeconds: number): never => {\n\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(retryAfterSeconds)}s.`);\n};\n\nconst defaultOnSessionExpired = (): never => {\n\tthrow new Error(\n\t\t'Your session has expired. Sign in again in a new tab, then re-run — your inputs are preserved.'\n\t);\n};\n\nexport function createComputeFetchSolveFn<TMesh = unknown>(\n\topts: ComputeFetchSolveFnOptions<TMesh>\n): SolveFn<TMesh, GrasshopperComputeResponse> {\n\tconst debug = opts.debug ?? false;\n\tconst log = (...args: unknown[]) => {\n\t\tif (debug) console.info(...args);\n\t};\n\n\tlet cooldownUntil = 0;\n\n\treturn async (values, signal): Promise<SolveResult<TMesh, GrasshopperComputeResponse>> => {\n\t\tconst remainingMs = cooldownUntil - Date.now();\n\t\tif (remainingMs > 0) {\n\t\t\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(remainingMs / 1000)}s.`);\n\t\t}\n\n\t\tconst solveStart = performance.now();\n\t\tconst inputs = opts.inputs();\n\t\tconst outputs = opts.outputs();\n\t\tconst channel = opts.channel?.();\n\t\tconst versionId = opts.versionId?.();\n\n\t\tconst payload = JSON.stringify({\n\t\t\tinputs,\n\t\t\tvalues,\n\t\t\tdefinitionUrl: opts.definitionUrl(),\n\t\t\t...(versionId ? { versionId } : channel === 'draft' ? { channel: 'draft' } : {})\n\t\t});\n\n\t\tlet res: Response;\n\t\ttry {\n\t\t\tres = await fetch(opts.endpoint, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: payload,\n\t\t\t\tsignal\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (signal.aborted) {\n\t\t\t\tlog('[Compute] solve aborted during fetch — empty result discarded');\n\t\t\t\treturn { outputs: {} };\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t'Request blocked — your session may have expired. Reload the page to sign in again.',\n\t\t\t\t{ cause: err }\n\t\t\t);\n\t\t}\n\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted after headers — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\n\t\tconst redirectedToLogin = res.redirected && new URL(res.url).pathname === '/login';\n\t\tif (res.status === 401 || redirectedToLogin) {\n\t\t\t(opts.onSessionExpired ?? defaultOnSessionExpired)();\n\t\t}\n\n\t\tif (!res.ok) {\n\t\t\tconst errorBody = await res.text().catch(() => '');\n\t\t\tlet d: { message?: string; retryAfter?: number } = {};\n\t\t\ttry {\n\t\t\t\td = JSON.parse(errorBody);\n\t\t\t} catch {\n\t\t\t\tif (errorBody) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[Compute] non-JSON error body (HTTP ${res.status}):`,\n\t\t\t\t\t\terrorBody.slice(0, 300)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (res.status === 503 || res.status === 504) {\n\t\t\t\t// The server names the failure class (unreachable, bad API key,\n\t\t\t\t// rate-limited, timed out) — show it rather than a blanket \"offline\".\n\t\t\t\tthrow new Error(\n\t\t\t\t\td.message || 'Compute server is offline or unreachable. Please try again later.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (res.status === 429) {\n\t\t\t\tconst retryAfter = Number(res.headers.get('Retry-After')) || Number(d.retryAfter) || 5;\n\t\t\t\tcooldownUntil = Date.now() + retryAfter * 1000;\n\t\t\t\t(opts.onRateLimited ?? defaultOnRateLimited)(retryAfter);\n\t\t\t}\n\t\t\tthrow new Error(d.message || `Compute error (HTTP ${res.status})`);\n\t\t}\n\n\t\tconst ttfbMs = performance.now() - solveStart;\n\n\t\tconst downloadStart = performance.now();\n\t\tconst bodyText = await res.text();\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted mid-download — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\t\tconst downloadMs = performance.now() - downloadStart;\n\t\tconst bytes = bodyText.length;\n\n\t\tlet solved: GrasshopperComputeResponse;\n\t\ttry {\n\t\t\tsolved = JSON.parse(bodyText);\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t'Received an invalid response from the server. Reload the page and try again.',\n\t\t\t\t{\n\t\t\t\t\tcause: err\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\tconst processor = new GrasshopperResponseProcessor(solved, false);\n\n\t\tconst meshes = opts.meshes ? await opts.meshes.extract(processor.response, { debug }) : [];\n\n\t\tconst resultOutputs: Record<string, unknown> = {};\n\t\tfor (const o of outputs) {\n\t\t\tconst byId = processor.getValue({ byId: o.id }, { parseValues: true });\n\t\t\tresultOutputs[o.id] =\n\t\t\t\tbyId ??\n\t\t\t\t(o.nickname\n\t\t\t\t\t? processor.getValue({ byName: o.nickname }, { parseValues: true })\n\t\t\t\t\t: undefined);\n\t\t}\n\n\t\tif (debug) {\n\t\t\tconst valuesBytes = JSON.stringify(values).length;\n\t\t\tconst serverTiming = parseServerTiming(res.headers.get('Server-Timing'));\n\t\t\tconst serverTotal = serverTiming.total ?? 0;\n\t\t\tconst networkMs = Math.max(0, ttfbMs - serverTotal);\n\t\t\tconst sizeMB = bytes / (1024 * 1024);\n\t\t\tconst size = sizeMB >= 1 ? `${sizeMB.toFixed(2)} MB` : `${(bytes / 1024).toFixed(0)} KB`;\n\t\t\tconst mbps = downloadMs > 0 ? (sizeMB / (downloadMs / 1000)).toFixed(1) : '∞';\n\t\t\tconst heapMB = (performance as Performance & { memory?: { usedJSHeapSize: number } }).memory\n\t\t\t\t?.usedJSHeapSize;\n\t\t\tconst heap = heapMB !== undefined ? ` | heap=${(heapMB / (1024 * 1024)).toFixed(0)} MB` : '';\n\t\t\tlog(\n\t\t\t\t`[Compute] req=${(payload.length / 1024).toFixed(0)}KB (values ${(valuesBytes / 1024).toFixed(0)}KB) | ` +\n\t\t\t\t\t`ttfb=${ttfbMs.toFixed(0)}ms (network≈${networkMs.toFixed(0)} + server ${serverTotal.toFixed(0)}) | ` +\n\t\t\t\t\t`download=${downloadMs.toFixed(0)}ms (${size} @ ${mbps} MB/s)${heap}`\n\t\t\t);\n\t\t\tif (serverTiming.selva_cache !== undefined) {\n\t\t\t\tconst cacheMsg = serverTiming.selva_cache\n\t\t\t\t\t? 'HIT — served without calling compute'\n\t\t\t\t\t: 'miss — solved on Rhino.Compute';\n\t\t\t\tlog(`[Compute] └─ cache: ${cacheMsg}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\toutputs: resultOutputs,\n\t\t\tmeshes,\n\t\t\terrors: solved.errors ?? [],\n\t\t\twarnings: solved.warnings ?? [],\n\t\t\tsource: solved\n\t\t};\n\t};\n}\n"],"mappings":"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"}
|
package/dist/client.d.cts
CHANGED
|
@@ -40,16 +40,16 @@ interface ExternalValueRef {
|
|
|
40
40
|
scopeKey: string;
|
|
41
41
|
inputId: string;
|
|
42
42
|
}
|
|
43
|
-
declare function writeExternalValue(args: ExternalValueRef & {
|
|
43
|
+
export declare function writeExternalValue(args: ExternalValueRef & {
|
|
44
44
|
value: unknown;
|
|
45
45
|
}): void;
|
|
46
|
-
declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
|
|
47
|
-
declare function clearExternalValue(ref: ExternalValueRef): void;
|
|
46
|
+
export declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
|
|
47
|
+
export declare function clearExternalValue(ref: ExternalValueRef): void;
|
|
48
48
|
interface ExternalInput {
|
|
49
49
|
paramId: string;
|
|
50
50
|
displayName: string;
|
|
51
51
|
}
|
|
52
|
-
declare function getExternalInputs(schema: UISchema): ExternalInput[];
|
|
52
|
+
export declare function getExternalInputs(schema: UISchema): ExternalInput[];
|
|
53
53
|
//#endregion
|
|
54
54
|
//#region src/client/solve-session-core.d.ts
|
|
55
55
|
/**
|
|
@@ -89,14 +89,14 @@ type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
|
|
|
89
89
|
* exists, so the missing-inputs panel can detect them. Other inputs fall back to their
|
|
90
90
|
* declared default, then the paramType default.
|
|
91
91
|
*/
|
|
92
|
-
declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
|
|
92
|
+
export declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
|
|
93
93
|
/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
|
|
94
|
-
declare function makeInitialFlags(instanceSolve: boolean | undefined): {
|
|
94
|
+
export declare function makeInitialFlags(instanceSolve: boolean | undefined): {
|
|
95
95
|
hasPendingChanges: boolean;
|
|
96
96
|
hasNeverSolved: boolean;
|
|
97
97
|
};
|
|
98
98
|
/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
|
|
99
|
-
declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
|
|
99
|
+
export declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
|
|
100
100
|
state: SolveSessionState;
|
|
101
101
|
shouldSolve: boolean;
|
|
102
102
|
};
|
|
@@ -105,8 +105,8 @@ declare function applyValueChange(state: SolveSessionState, id: string, value: u
|
|
|
105
105
|
* lists can read them) but must not be echoed back to the driver: a measured 6.4 MB
|
|
106
106
|
* options-list output showed this can re-upload a multi-MB payload no backend reads.
|
|
107
107
|
*/
|
|
108
|
-
declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
|
|
109
|
-
declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
|
|
108
|
+
export declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
|
|
109
|
+
export declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
|
|
110
110
|
//#endregion
|
|
111
111
|
//#region src/client/solve-session.d.ts
|
|
112
112
|
interface SolveSession {
|
|
@@ -143,14 +143,14 @@ interface SolveSessionArgs {
|
|
|
143
143
|
scopeKey: string;
|
|
144
144
|
driver: SolveDriver;
|
|
145
145
|
}
|
|
146
|
-
declare function createSolveSession(args: SolveSessionArgs): SolveSession;
|
|
146
|
+
export declare function createSolveSession(args: SolveSessionArgs): SolveSession;
|
|
147
147
|
//#endregion
|
|
148
148
|
//#region src/client/solve-memo.d.ts
|
|
149
149
|
/**
|
|
150
150
|
* Deterministic string key for a set of input values. Sorts object keys at every level so
|
|
151
151
|
* two logically-equal inputs built in different key order collide.
|
|
152
152
|
*/
|
|
153
|
-
declare function stableInputKey(values: Record<string, unknown>): string;
|
|
153
|
+
export declare function stableInputKey(values: Record<string, unknown>): string;
|
|
154
154
|
/**
|
|
155
155
|
* Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then
|
|
156
156
|
* stores and serves them by reference, which is safe only because nothing frees them behind
|
|
@@ -172,7 +172,7 @@ interface SolveMemo<TMesh = unknown> {
|
|
|
172
172
|
set(values: Record<string, unknown>, result: SolveResult<TMesh>): void;
|
|
173
173
|
clear(): void;
|
|
174
174
|
}
|
|
175
|
-
declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
|
|
175
|
+
export declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
|
|
176
176
|
//#endregion
|
|
177
177
|
//#region src/client/drivers/request-response.d.ts
|
|
178
178
|
interface RequestResponseDriverOptions<TMesh> {
|
|
@@ -186,7 +186,7 @@ interface RequestResponseDriverOptions<TMesh> {
|
|
|
186
186
|
* `getReporter` is lazy (`() => session`) because the session and driver reference each
|
|
187
187
|
* other — the host builds the driver first, then the session, then can hand this back.
|
|
188
188
|
*/
|
|
189
|
-
declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
|
|
189
|
+
export declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
|
|
190
190
|
//#endregion
|
|
191
191
|
//#region src/client/async-throttle.d.ts
|
|
192
192
|
/**
|
|
@@ -215,7 +215,7 @@ interface AsyncThrottle<T> {
|
|
|
215
215
|
readonly hasPending: boolean;
|
|
216
216
|
cancel: () => void;
|
|
217
217
|
}
|
|
218
|
-
declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
|
|
218
|
+
export declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
|
|
219
219
|
//#endregion
|
|
220
220
|
//#region src/client/compute-fetch-solve-fn.d.ts
|
|
221
221
|
interface ComputeFetchSolveFnOptions<TMesh = unknown> {
|
|
@@ -247,7 +247,7 @@ interface ComputeFetchSolveFnOptions<TMesh = unknown> {
|
|
|
247
247
|
/** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
|
|
248
248
|
onSessionExpired?: () => void;
|
|
249
249
|
}
|
|
250
|
-
declare function createComputeFetchSolveFn<TMesh = unknown>(opts: ComputeFetchSolveFnOptions<TMesh>): SolveFn<TMesh, GrasshopperComputeResponse>;
|
|
250
|
+
export declare function createComputeFetchSolveFn<TMesh = unknown>(opts: ComputeFetchSolveFnOptions<TMesh>): SolveFn<TMesh, GrasshopperComputeResponse>;
|
|
251
251
|
//#endregion
|
|
252
|
-
export {
|
|
252
|
+
export type { AsyncThrottle, ComputeFetchSolveFnOptions, ExternalInput, ExternalReader, ExternalValueRef, MeshPolicy, RequestResponseDriverOptions, RetainedSolveResult, SolveDriver, SolveFn, SolveMemo, SolveMemoOptions, SolveReporter, SolveResult, SolveSession, SolveSessionArgs, SolveSessionState };
|
|
253
253
|
//# sourceMappingURL=client.d.cts.map
|
package/dist/client.d.ts
CHANGED
|
@@ -40,16 +40,16 @@ interface ExternalValueRef {
|
|
|
40
40
|
scopeKey: string;
|
|
41
41
|
inputId: string;
|
|
42
42
|
}
|
|
43
|
-
declare function writeExternalValue(args: ExternalValueRef & {
|
|
43
|
+
export declare function writeExternalValue(args: ExternalValueRef & {
|
|
44
44
|
value: unknown;
|
|
45
45
|
}): void;
|
|
46
|
-
declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
|
|
47
|
-
declare function clearExternalValue(ref: ExternalValueRef): void;
|
|
46
|
+
export declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
|
|
47
|
+
export declare function clearExternalValue(ref: ExternalValueRef): void;
|
|
48
48
|
interface ExternalInput {
|
|
49
49
|
paramId: string;
|
|
50
50
|
displayName: string;
|
|
51
51
|
}
|
|
52
|
-
declare function getExternalInputs(schema: UISchema): ExternalInput[];
|
|
52
|
+
export declare function getExternalInputs(schema: UISchema): ExternalInput[];
|
|
53
53
|
//#endregion
|
|
54
54
|
//#region src/client/solve-session-core.d.ts
|
|
55
55
|
/**
|
|
@@ -89,14 +89,14 @@ type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
|
|
|
89
89
|
* exists, so the missing-inputs panel can detect them. Other inputs fall back to their
|
|
90
90
|
* declared default, then the paramType default.
|
|
91
91
|
*/
|
|
92
|
-
declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
|
|
92
|
+
export declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
|
|
93
93
|
/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
|
|
94
|
-
declare function makeInitialFlags(instanceSolve: boolean | undefined): {
|
|
94
|
+
export declare function makeInitialFlags(instanceSolve: boolean | undefined): {
|
|
95
95
|
hasPendingChanges: boolean;
|
|
96
96
|
hasNeverSolved: boolean;
|
|
97
97
|
};
|
|
98
98
|
/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
|
|
99
|
-
declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
|
|
99
|
+
export declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
|
|
100
100
|
state: SolveSessionState;
|
|
101
101
|
shouldSolve: boolean;
|
|
102
102
|
};
|
|
@@ -105,8 +105,8 @@ declare function applyValueChange(state: SolveSessionState, id: string, value: u
|
|
|
105
105
|
* lists can read them) but must not be echoed back to the driver: a measured 6.4 MB
|
|
106
106
|
* options-list output showed this can re-upload a multi-MB payload no backend reads.
|
|
107
107
|
*/
|
|
108
|
-
declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
|
|
109
|
-
declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
|
|
108
|
+
export declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
|
|
109
|
+
export declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
|
|
110
110
|
//#endregion
|
|
111
111
|
//#region src/client/solve-session.d.ts
|
|
112
112
|
interface SolveSession {
|
|
@@ -143,14 +143,14 @@ interface SolveSessionArgs {
|
|
|
143
143
|
scopeKey: string;
|
|
144
144
|
driver: SolveDriver;
|
|
145
145
|
}
|
|
146
|
-
declare function createSolveSession(args: SolveSessionArgs): SolveSession;
|
|
146
|
+
export declare function createSolveSession(args: SolveSessionArgs): SolveSession;
|
|
147
147
|
//#endregion
|
|
148
148
|
//#region src/client/solve-memo.d.ts
|
|
149
149
|
/**
|
|
150
150
|
* Deterministic string key for a set of input values. Sorts object keys at every level so
|
|
151
151
|
* two logically-equal inputs built in different key order collide.
|
|
152
152
|
*/
|
|
153
|
-
declare function stableInputKey(values: Record<string, unknown>): string;
|
|
153
|
+
export declare function stableInputKey(values: Record<string, unknown>): string;
|
|
154
154
|
/**
|
|
155
155
|
* Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then
|
|
156
156
|
* stores and serves them by reference, which is safe only because nothing frees them behind
|
|
@@ -172,7 +172,7 @@ interface SolveMemo<TMesh = unknown> {
|
|
|
172
172
|
set(values: Record<string, unknown>, result: SolveResult<TMesh>): void;
|
|
173
173
|
clear(): void;
|
|
174
174
|
}
|
|
175
|
-
declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
|
|
175
|
+
export declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
|
|
176
176
|
//#endregion
|
|
177
177
|
//#region src/client/drivers/request-response.d.ts
|
|
178
178
|
interface RequestResponseDriverOptions<TMesh> {
|
|
@@ -186,7 +186,7 @@ interface RequestResponseDriverOptions<TMesh> {
|
|
|
186
186
|
* `getReporter` is lazy (`() => session`) because the session and driver reference each
|
|
187
187
|
* other — the host builds the driver first, then the session, then can hand this back.
|
|
188
188
|
*/
|
|
189
|
-
declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
|
|
189
|
+
export declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
|
|
190
190
|
//#endregion
|
|
191
191
|
//#region src/client/async-throttle.d.ts
|
|
192
192
|
/**
|
|
@@ -215,7 +215,7 @@ interface AsyncThrottle<T> {
|
|
|
215
215
|
readonly hasPending: boolean;
|
|
216
216
|
cancel: () => void;
|
|
217
217
|
}
|
|
218
|
-
declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
|
|
218
|
+
export declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
|
|
219
219
|
//#endregion
|
|
220
220
|
//#region src/client/compute-fetch-solve-fn.d.ts
|
|
221
221
|
interface ComputeFetchSolveFnOptions<TMesh = unknown> {
|
|
@@ -247,7 +247,7 @@ interface ComputeFetchSolveFnOptions<TMesh = unknown> {
|
|
|
247
247
|
/** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
|
|
248
248
|
onSessionExpired?: () => void;
|
|
249
249
|
}
|
|
250
|
-
declare function createComputeFetchSolveFn<TMesh = unknown>(opts: ComputeFetchSolveFnOptions<TMesh>): SolveFn<TMesh, GrasshopperComputeResponse>;
|
|
250
|
+
export declare function createComputeFetchSolveFn<TMesh = unknown>(opts: ComputeFetchSolveFnOptions<TMesh>): SolveFn<TMesh, GrasshopperComputeResponse>;
|
|
251
251
|
//#endregion
|
|
252
|
-
export {
|
|
252
|
+
export type { AsyncThrottle, ComputeFetchSolveFnOptions, ExternalInput, ExternalReader, ExternalValueRef, MeshPolicy, RequestResponseDriverOptions, RetainedSolveResult, SolveDriver, SolveFn, SolveMemo, SolveMemoOptions, SolveReporter, SolveResult, SolveSession, SolveSessionArgs, SolveSessionState };
|
|
253
253
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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
|
|
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;try{m=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 h=m.redirected&&new URL(m.url).pathname===`/login`;if((m.status===401||h)&&(e.onSessionExpired??x)(),!m.ok){let t=await m.text().catch(()=>``),n={};try{n=JSON.parse(t)}catch{t&&console.warn(`[Compute] non-JSON error body (HTTP ${m.status}):`,t.slice(0,300))}if(m.status===503||m.status===504)throw Error(n.message||`Compute server is offline or unreachable. Please try again later.`);if(m.status===429){let t=Number(m.headers.get(`Retry-After`))||Number(n.retryAfter)||5;i=Date.now()+t*1e3,(e.onRateLimited??b)(t)}throw Error(n.message||`Compute error (HTTP ${m.status})`)}let g=performance.now()-c,_=performance.now(),v=await m.text();if(o.aborted)return r(`[Compute] solve aborted mid-download — empty result discarded`),{outputs:{}};let S=performance.now()-_,C=v.length,w;try{w=JSON.parse(v)}catch(e){throw Error(`Received an invalid response from the server. Reload the page and try again.`,{cause:e})}let T=new n(w,!1),E=e.meshes?await e.meshes.extract(T.response,{debug:t}):[],D={};for(let e of u){let t=T.getValue({byId:e.id},{parseValues:!0});D[e.id]=t??(e.nickname?T.getValue({byName:e.nickname},{parseValues:!0}):void 0)}if(t){let e=JSON.stringify(a).length,t=y(m.headers.get(`Server-Timing`)),n=t.total??0,i=Math.max(0,g-n),o=C/1048576,s=o>=1?`${o.toFixed(2)} MB`:`${(C/1024).toFixed(0)} KB`,c=S>0?(o/(S/1e3)).toFixed(1):`∞`,l=performance.memory?.usedJSHeapSize,u=l===void 0?``:` | heap=${(l/1048576).toFixed(0)} MB`;if(r(`[Compute] req=${(p.length/1024).toFixed(0)}KB (values ${(e/1024).toFixed(0)}KB) | ttfb=${g.toFixed(0)}ms (network≈${i.toFixed(0)} + server ${n.toFixed(0)}) | download=${S.toFixed(0)}ms (${s} @ ${c} MB/s)${u}`),t.selva_cache!==void 0){let e=t.selva_cache?`HIT — served without calling compute`:`miss — solved on Rhino.Compute`;r(`[Compute] └─ cache: ${e}`)}}return{outputs:D,meshes:E,errors:w.errors??[],warnings:w.warnings??[],source:w}}}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
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../src/client/external-storage.ts","../src/client/solve-session-core.ts","../src/client/solve-session.ts","../src/client/async-throttle.ts","../src/client/solve-memo.ts","../src/client/drivers/request-response.ts","../src/client/compute-fetch-solve-fn.ts"],"sourcesContent":["// Transit storage for inputs with source.kind === 'client': a producer route writes the\n// value here, the solver route reads it back. Keyed by (scopeKey, inputId) so values\n// don't bleed across solver contexts — scopeKey is whatever uniquely identifies one\n// (sessionId in plugin-ui/preview, definition guid in selva/library).\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getInputItems } from '@selvajs/schemas';\n\nconst STORAGE_PREFIX = 'external';\n\nfunction makeKey(scopeKey: string, inputId: string): string {\n\treturn `${STORAGE_PREFIX}:${scopeKey}:${inputId}`;\n}\n\nexport interface ExternalValueRef {\n\tscopeKey: string;\n\tinputId: string;\n}\n\nexport function writeExternalValue(args: ExternalValueRef & { value: unknown }): void {\n\tconst { scopeKey, inputId, value } = args;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.setItem(makeKey(scopeKey, inputId), JSON.stringify(value));\n}\n\nexport function readExternalValue(ref: ExternalValueRef): unknown | undefined {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return undefined;\n\tif (typeof sessionStorage === 'undefined') return undefined;\n\tconst raw = sessionStorage.getItem(makeKey(scopeKey, inputId));\n\tif (raw === null) return undefined;\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function clearExternalValue(ref: ExternalValueRef): void {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.removeItem(makeKey(scopeKey, inputId));\n}\n\nexport interface ExternalInput {\n\tparamId: string;\n\tdisplayName: string;\n}\n\nexport function getExternalInputs(schema: UISchema): ExternalInput[] {\n\treturn getInputItems(schema)\n\t\t.filter((item) => item.source?.kind === 'client')\n\t\t.map((item) => ({\n\t\t\tparamId: item.paramId,\n\t\t\tdisplayName: item.displayName ?? item.paramId\n\t\t}));\n}\n","// Pure transition logic for a Solve Session.\n//\n// This module holds the value/lifecycle state machine with no transport and no state\n// ownership: how values are seeded (including client-sourced hydration), when a value\n// change should dispatch a solve vs. defer it, and what a reported solve result does to\n// the flags. `solve-session.ts` is a thin shell that owns the state object and the\n// subscriber set; every decision lives here, which is what makes it directly testable.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getDefaultValue } from '@selvajs/schemas';\nimport { getExternalInputs, type ExternalValueRef } from './external-storage.js';\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * The slice of a reported result the session keeps addressable, so a host can commit exactly\n * what the viewer is showing — `source` and `values` included, already memo-correct.\n *\n * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what\n * it renders on the next scene update, which is why the memo carries a `MeshPolicy`\n * clone/release contract at all. Retaining them here would hand out disposed instances after\n * the next solve, with no policy governing them and no owner. Everything left is inert JSON;\n * live meshes stay on `session.meshes`.\n */\nexport type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;\n\nexport interface SolveSessionState {\n\tvalues: Record<string, unknown>;\n\terror: string;\n\tcomputeErrors: string[];\n\tcomputeWarnings: string[];\n\tmeshes: unknown[];\n\t/** Values changed since the last solve, in manual (instanceSolve === false) mode. */\n\tpendingValues: Record<string, unknown>;\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n\t/**\n\t * The last result reported, minus its meshes. Null before the first solve.\n\t *\n\t * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a\n\t * value change and the next report it describes the *previous* input set. That is what a\n\t * commit gate wants — what is on screen, not what is pending — so `lastResult.values`\n\t * disagreeing with `state.values` mid-scrub is the feature, not a bug.\n\t */\n\tlastResult: RetainedSolveResult | null;\n}\n\nexport type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;\n\n/**\n * Client-sourced inputs are hydrated from `read` and left absent when no stored value\n * exists, so the missing-inputs panel can detect them. Other inputs fall back to their\n * declared default, then the paramType default.\n */\nexport function buildInitialValues(\n\tschema: UISchema,\n\tscopeKey: string,\n\tread: ExternalReader\n): Record<string, unknown> {\n\tconst clientSet = new Set(getExternalInputs(schema).map((e) => e.paramId));\n\tconst values: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (clientSet.has(input.id)) {\n\t\t\tconst stored = read({ scopeKey, inputId: input.id });\n\t\t\tif (stored !== undefined) values[input.id] = stored;\n\t\t\tcontinue;\n\t\t}\n\t\tvalues[input.id] = input.default ?? getDefaultValue(input.paramType);\n\t}\n\tfor (const output of schema.outputs) {\n\t\tvalues[output.id] = null;\n\t}\n\treturn values;\n}\n\n/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */\nexport function makeInitialFlags(instanceSolve: boolean | undefined): {\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n} {\n\tconst manual = instanceSolve === false;\n\treturn { hasPendingChanges: manual, hasNeverSolved: manual };\n}\n\n/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */\nexport function applyValueChange(\n\tstate: SolveSessionState,\n\tid: string,\n\tvalue: unknown,\n\tinstanceSolve: boolean | undefined\n): { state: SolveSessionState; shouldSolve: boolean } {\n\tstate.values[id] = value;\n\tif (instanceSolve === false) {\n\t\tstate.pendingValues[id] = value;\n\t\tstate.hasPendingChanges = true;\n\t\treturn { state, shouldSolve: false };\n\t}\n\treturn { state, shouldSolve: true };\n}\n\n/**\n * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value\n * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB\n * options-list output showed this can re-upload a multi-MB payload no backend reads.\n */\nexport function pickInputValues(\n\tschema: UISchema | undefined,\n\tvalues: Record<string, unknown>\n): Record<string, unknown> {\n\tif (!schema?.inputs) return values;\n\tconst picked: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (input.id in values) picked[input.id] = values[input.id];\n\t}\n\treturn picked;\n}\n\nexport function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState {\n\tstate.error = '';\n\tstate.computeErrors = result.errors ?? [];\n\tstate.computeWarnings = result.warnings ?? [];\n\tstate.meshes = result.meshes ?? [];\n\tconst { meshes: _meshes, ...retained } = result;\n\tstate.lastResult = retained;\n\tObject.assign(state.values, result.outputs);\n\tstate.pendingValues = {};\n\tstate.hasPendingChanges = false;\n\tstate.hasNeverSolved = false;\n\treturn state;\n}\n","// A framework-free shell over the pure transition logic in solve-session-core.ts: owns the\n// live values/flags, delegates every transition to the core, and drives solves through a\n// transport-agnostic SolveDriver.\n//\n// State is exposed as plain getters; it carries no framework reactivity of its own, so a\n// Svelte host must wrap it — see `useSolveSession.svelte.ts` in `@selvajs/ui`, which\n// republishes these getters as $state. Reading a getter without subscribing gives a\n// correct value but will not re-render.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { readExternalValue } from './external-storage.js';\nimport type { SolveResult } from '../shared/solve-fn.js';\nimport type { SolveDriver } from './drivers/driver.js';\nimport {\n\tbuildInitialValues,\n\tmakeInitialFlags,\n\tapplyValueChange,\n\tapplySolveResult,\n\tpickInputValues,\n\ttype SolveSessionState,\n\ttype RetainedSolveResult\n} from './solve-session-core.js';\n\nexport interface SolveSession {\n\treadonly values: Record<string, unknown>;\n\treadonly error: string;\n\treadonly computeErrors: string[];\n\treadonly computeWarnings: string[];\n\treadonly meshes: unknown[];\n\t/**\n\t * The last reported result without its meshes — what the viewer is currently showing,\n\t * including the `source`/`values` pair a commit path needs. Null before the first solve,\n\t * and again after `rebuild()`. Survives `reportError`: a failed solve leaves the last good\n\t * geometry on screen, so a host committing what it sees should still see what produced it.\n\t */\n\treadonly lastResult: RetainedSolveResult | null;\n\treadonly hasPendingChanges: boolean;\n\treadonly hasNeverSolved: boolean;\n\treadonly isSolving: boolean;\n\t/** `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. */\n\tsetValue(id: string, value: unknown, forceSolve?: boolean): void;\n\tsolve(): void;\n\t/** Merges incoming values, then solves (auto) or marks dirty (manual). */\n\tloadValues(incoming: Record<string, unknown>): void;\n\trebuild(schema: UISchema, scopeKey: string): void;\n\treport(result: SolveResult): void;\n\treportError(message: string): void;\n\t/** Fires after any mutation of the getters above, including driver-reported `isSolving` transitions. */\n\tsubscribe(listener: () => void): () => void;\n\t/** For driver-owned state the session only forwards: `isSolving` lives on the driver, so wire this to its `onChange`. */\n\tnotify(): void;\n}\n\nexport interface SolveSessionArgs {\n\tschema: UISchema;\n\tscopeKey: string;\n\tdriver: SolveDriver;\n}\n\nexport function createSolveSession(args: SolveSessionArgs): SolveSession {\n\tlet currentSchema = args.schema;\n\n\tconst flags = makeInitialFlags(currentSchema?.instanceSolve);\n\tconst state: SolveSessionState = {\n\t\tvalues: buildInitialValues(currentSchema, args.scopeKey, readExternalValue),\n\t\terror: '',\n\t\tcomputeErrors: [],\n\t\tcomputeWarnings: [],\n\t\tmeshes: [],\n\t\tpendingValues: {},\n\t\thasPendingChanges: flags.hasPendingChanges,\n\t\thasNeverSolved: flags.hasNeverSolved,\n\t\tlastResult: null\n\t};\n\n\tconst listeners = new Set<() => void>();\n\tconst emit = () => listeners.forEach((l) => l());\n\n\tfunction dispatch() {\n\t\t// Copy: state.values is the live map, and a driver may hold the payload across an\n\t\t// async solve — a later setValue must not mutate what's already in flight.\n\t\targs.driver.solve({ ...pickInputValues(currentSchema, state.values) });\n\t\temit();\n\t}\n\n\treturn {\n\t\tget values() {\n\t\t\treturn state.values;\n\t\t},\n\t\tget error() {\n\t\t\treturn state.error;\n\t\t},\n\t\tget computeErrors() {\n\t\t\treturn state.computeErrors;\n\t\t},\n\t\tget computeWarnings() {\n\t\t\treturn state.computeWarnings;\n\t\t},\n\t\tget meshes() {\n\t\t\treturn state.meshes;\n\t\t},\n\t\tget lastResult() {\n\t\t\treturn state.lastResult;\n\t\t},\n\t\tget hasPendingChanges() {\n\t\t\treturn state.hasPendingChanges;\n\t\t},\n\t\tget hasNeverSolved() {\n\t\t\treturn state.hasNeverSolved;\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn args.driver.isSolving;\n\t\t},\n\n\t\tsetValue(id, value, forceSolve = false) {\n\t\t\tconst { shouldSolve } = applyValueChange(state, id, value, currentSchema?.instanceSolve);\n\t\t\tif (shouldSolve || forceSolve) {\n\t\t\t\tif (forceSolve && !shouldSolve) {\n\t\t\t\t\tstate.pendingValues = {};\n\t\t\t\t\tstate.hasPendingChanges = false;\n\t\t\t\t}\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\tsolve() {\n\t\t\tdispatch();\n\t\t},\n\n\t\tloadValues(incoming) {\n\t\t\tObject.assign(state.values, incoming);\n\t\t\tif (currentSchema?.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\tstate.hasPendingChanges = true;\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\trebuild(schema, scopeKey) {\n\t\t\tcurrentSchema = schema;\n\t\t\t// The new definition has its own input space; a matching input key from the prior\n\t\t\t// one must not serve that definition's cached result.\n\t\t\targs.driver.clearCache?.();\n\t\t\tstate.meshes = [];\n\t\t\t// Same reason as clearCache: a retained result belongs to the previous definition.\n\t\t\tstate.lastResult = null;\n\t\t\tstate.error = '';\n\t\t\tstate.computeErrors = [];\n\t\t\tstate.computeWarnings = [];\n\t\t\tstate.pendingValues = {};\n\t\t\tstate.values = buildInitialValues(schema, scopeKey, readExternalValue);\n\t\t\tconst f = makeInitialFlags(schema?.instanceSolve);\n\t\t\tstate.hasPendingChanges = f.hasPendingChanges;\n\t\t\tstate.hasNeverSolved = f.hasNeverSolved;\n\t\t\tif (schema && Object.keys(state.values).length > 0 && schema.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\treport(result) {\n\t\t\tapplySolveResult(state, result);\n\t\t\temit();\n\t\t},\n\n\t\treportError(message) {\n\t\t\tstate.error = message;\n\t\t\temit();\n\t\t},\n\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\n\t\tnotify: emit\n\t};\n}\n","/**\n * Single-in-flight, latest-wins throttle for any async operation: only one run at a\n * time, a newly triggered value overwrites the single pending slot (no queue), and a\n * stale run can be aborted via `AbortController` or timeout.\n *\n * Framework-free: `isRunning` is a plain getter, not a store. A reactive host must\n * subscribe via `onChange` (see `@selvajs/ui`'s rune adapter) — polling the getter\n * directly reads correct values but won't trigger a re-render.\n */\ninterface AsyncThrottleOptions {\n\t/**\n\t * How long one run may take before it is aborted (ms). Required and deliberately\n\t * undefaulted: this is a generic throttle, so it has no basis for guessing what\n\t * the caller's work costs. Callers driving a Selva solve pass the deadline the\n\t * server enforces (`COMPUTE_SOLVE_DEADLINE_MS`) — a shorter local guess would abort\n\t * solves the server would have finished.\n\t */\n\trunDeadlineMs: number;\n\tonChange?: () => void;\n}\n\nexport interface AsyncThrottle<T> {\n\ttrigger: (values: T) => void;\n\treadonly isRunning: boolean;\n\treadonly hasPending: boolean;\n\tcancel: () => void;\n}\n\nexport function createAsyncThrottle<T>(\n\trun: (values: T, signal: AbortSignal) => Promise<void>,\n\toptions: AsyncThrottleOptions\n): AsyncThrottle<T> {\n\tconst { runDeadlineMs, onChange } = options;\n\n\tlet isRunning = false;\n\tlet pendingValues: T | null = null;\n\tlet currentAbortController: AbortController | null = null;\n\n\tfunction setRunning(next: boolean) {\n\t\tif (isRunning === next) return;\n\t\tisRunning = next;\n\t\tonChange?.();\n\t}\n\n\tfunction abortCurrent() {\n\t\tcurrentAbortController?.abort();\n\t\tcurrentAbortController = null;\n\t}\n\n\tasync function execute(values: T) {\n\t\tabortCurrent();\n\n\t\tcurrentAbortController = new AbortController();\n\t\tconst { signal } = currentAbortController;\n\t\tconst deadlineId = setTimeout(() => {\n\t\t\t// Cleared in `finally` on every other path, so firing means a genuine\n\t\t\t// timeout — the only signal for it (the abort itself is swallowed below).\n\t\t\tconsole.warn(`[Solve/throttle] run exceeded ${runDeadlineMs}ms — aborting`);\n\t\t\tcurrentAbortController?.abort();\n\t\t}, runDeadlineMs);\n\n\t\tsetRunning(true);\n\t\ttry {\n\t\t\tawait run(values, signal);\n\t\t} catch (err) {\n\t\t\t// AbortError is expected (timeout or cancel). Non-abort errors must be\n\t\t\t// handled inside `run` — re-throwing here would produce an unhandled\n\t\t\t// rejection because `execute` is always called fire-and-forget.\n\t\t\tif (!(err instanceof Error) || (err.name !== 'AbortError' && err.name !== 'TimeoutError')) {\n\t\t\t\tconsole.error('[Solve/throttle] unhandled error in run:', err);\n\t\t\t}\n\t\t} finally {\n\t\t\tclearTimeout(deadlineId);\n\t\t\tsetRunning(false);\n\t\t\tcurrentAbortController = null;\n\n\t\t\tif (pendingValues !== null) {\n\t\t\t\tconst next = pendingValues;\n\t\t\t\tpendingValues = null;\n\t\t\t\texecute(next);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction trigger(values: T) {\n\t\tif (isRunning) {\n\t\t\tif (pendingValues !== null) {\n\t\t\t\t// Latest-wins: the previously-queued values are dropped, not run.\n\t\t\t\t// `debug`, not `info`: a slider scrub fires this per frame, and it is a trace of\n\t\t\t\t// normal operation. Promoting it would spam a host's console on every drag.\n\t\t\t\t// eslint-disable-next-line no-console -- see above\n\t\t\t\tconsole.debug('[Solve/throttle] superseded pending run (latest-wins)');\n\t\t\t}\n\t\t\tpendingValues = values;\n\t\t} else {\n\t\t\texecute(values);\n\t\t}\n\t}\n\n\tfunction cancel() {\n\t\tpendingValues = null;\n\t\tabortCurrent();\n\t}\n\n\treturn {\n\t\ttrigger,\n\t\tget isRunning() {\n\t\t\treturn isRunning;\n\t\t},\n\t\tget hasPending() {\n\t\t\treturn pendingValues !== null;\n\t\t},\n\t\tcancel\n\t};\n}\n","// LRU cache of solve results keyed on a stable serialization of the inputs, so re-solving\n// something already seen this session (e.g. dragging a slider back) returns instantly\n// instead of round-tripping.\n//\n// A viewer typically takes ownership of every mesh it renders, disposing the previous\n// content on the next update. So the memo can't hand out its own stored instances (they'd\n// get disposed under it) or drop entries silently (their GPU buffers would leak): it keeps\n// private copies via an injected clone/release policy, which also keeps `three` out of this\n// package — `TMesh` stays opaque here.\n\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * Deterministic string key for a set of input values. Sorts object keys at every level so\n * two logically-equal inputs built in different key order collide.\n */\nexport function stableInputKey(values: Record<string, unknown>): string {\n\treturn serialize(values);\n}\n\nfunction serialize(value: unknown): string {\n\tif (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';\n\tif (Array.isArray(value)) return `[${value.map(serialize).join(',')}]`;\n\tconst obj = value as Record<string, unknown>;\n\tconst keys = Object.keys(obj).sort();\n\treturn `{${keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(',')}}`;\n}\n\n/**\n * Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then\n * stores and serves them by reference, which is safe only because nothing frees them behind\n * its back.\n */\nexport interface MeshPolicy<TMesh> {\n\t/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */\n\tclone(meshes: TMesh[]): TMesh[];\n\t/** Must not free anything shared with live content (e.g. materials another object still uses). */\n\trelease(meshes: TMesh[]): void;\n}\n\nexport interface SolveMemoOptions<TMesh> {\n\t/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */\n\tmax?: number;\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\nexport interface SolveMemo<TMesh = unknown> {\n\tget(values: Record<string, unknown>): SolveResult<TMesh> | undefined;\n\tset(values: Record<string, unknown>, result: SolveResult<TMesh>): void;\n\tclear(): void;\n}\n\nexport function createSolveMemo<TMesh = unknown>(\n\toptions: SolveMemoOptions<TMesh> = {}\n): SolveMemo<TMesh> {\n\tconst { max = 16, meshPolicy } = options;\n\tconst entries = new Map<string, SolveResult<TMesh>>();\n\n\tfunction release(result: SolveResult<TMesh>): void {\n\t\tif (result.meshes?.length) meshPolicy?.release(result.meshes);\n\t}\n\n\tfunction copy(result: SolveResult<TMesh>): SolveResult<TMesh> {\n\t\tif (!meshPolicy || !result.meshes?.length) return result;\n\t\treturn { ...result, meshes: meshPolicy.clone(result.meshes) };\n\t}\n\n\tfunction evict(key: string): void {\n\t\tconst entry = entries.get(key);\n\t\tif (entry === undefined) return;\n\t\tentries.delete(key);\n\t\trelease(entry);\n\t}\n\n\treturn {\n\t\tget(values) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tconst hit = entries.get(key);\n\t\t\tif (hit === undefined) return undefined;\n\t\t\tentries.delete(key);\n\t\t\tentries.set(key, hit);\n\t\t\tconsole.info(`[Solve/memo] HIT — served from client memo (${entries.size}/${max})`);\n\t\t\treturn copy(hit);\n\t\t},\n\t\tset(values, result) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tevict(key);\n\t\t\tentries.set(key, copy(result));\n\t\t\twhile (entries.size > max) {\n\t\t\t\tconst oldest = entries.keys().next().value;\n\t\t\t\tif (oldest === undefined) break;\n\t\t\t\tevict(oldest);\n\t\t\t\tconsole.info(`[Solve/memo] evicted LRU entry (cap ${max})`);\n\t\t\t}\n\t\t},\n\t\tclear() {\n\t\t\tif (entries.size > 0) {\n\t\t\t\tconsole.info(`[Solve/memo] cleared ${entries.size} entries (definition changed)`);\n\t\t\t}\n\t\t\tentries.forEach(release);\n\t\t\tentries.clear();\n\t\t}\n\t};\n}\n","import { createAsyncThrottle } from '../async-throttle.js';\nimport { createSolveMemo, type MeshPolicy } from '../solve-memo.js';\nimport type { SolveFn } from '../../shared/solve-fn.js';\nimport type { SolveDriver, SolveReporter } from './driver.js';\n\nexport interface RequestResponseDriverOptions<TMesh> {\n\t/** How long one solve may take before the client aborts it (ms). Pass the server's `COMPUTE_SOLVE_DEADLINE_MS` so the two agree. */\n\tsolveDeadlineMs: number;\n\tonChange?: () => void;\n\t/** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\n/**\n * `getReporter` is lazy (`() => session`) because the session and driver reference each\n * other — the host builds the driver first, then the session, then can hand this back.\n */\nexport function createRequestResponseDriver<TMesh = unknown>(\n\tonSolve: SolveFn<TMesh>,\n\tgetReporter: () => SolveReporter<TMesh>,\n\toptions: RequestResponseDriverOptions<TMesh>\n): SolveDriver {\n\tconst { meshPolicy, solveDeadlineMs, onChange } = options;\n\n\t// Checked inside the throttled run, not before triggering it, so a hit only serves\n\t// after the throttle's latest-wins ordering has picked these values to run.\n\tconst memo = createSolveMemo<TMesh>({ meshPolicy });\n\n\tconst throttle = createAsyncThrottle<Record<string, unknown>>(\n\t\tasync (values, signal) => {\n\t\t\tconst cached = memo.get(values);\n\t\t\tif (cached !== undefined) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tgetReporter().report(cached);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await onSolve(values, signal);\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve completed after abort — result discarded');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Stamped here rather than in the SolveFn: a memo hit never calls it, so values\n\t\t\t\t// captured inside would pair the served result with whatever solved last.\n\t\t\t\tconst stamped = { ...result, values };\n\t\t\t\tmemo.set(values, stamped);\n\t\t\t\tgetReporter().report(stamped);\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve aborted (superseded, cancelled, or timed out)');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconsole.warn('[Solve/driver] solve failed:', err);\n\t\t\t\tgetReporter().reportError(err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t},\n\t\t// The throttle is generic, so the solve deadline is what bounds one of its runs.\n\t\t{ runDeadlineMs: solveDeadlineMs, onChange }\n\t);\n\n\treturn {\n\t\tsolve(values) {\n\t\t\tthrottle.trigger(values);\n\t\t},\n\t\tcancel() {\n\t\t\tthrottle.cancel();\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn throttle.isRunning;\n\t\t},\n\t\tclearCache() {\n\t\t\tmemo.clear();\n\t\t}\n\t};\n}\n","/**\n * `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a\n * `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +\n * `SolveEngine` produce server-side) and unpacks the result.\n *\n * Ports the resilience behavior an interactive solve endpoint needs regardless\n * of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response\n * guarding, and abort handling at every await point — so a new consumer gets it\n * for free instead of re-deriving it by hand. Mesh decoding is NOT built in:\n * this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a\n * viewer-enabled consumer supplies its own `meshes.extract` hook (typically\n * `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).\n */\n\nimport {\n\tGrasshopperResponseProcessor,\n\ttype GrasshopperComputeResponse\n} from '@selvajs/compute/grasshopper';\nimport type { SolveFn, SolveResult } from '../shared/solve-fn.js';\n\nexport interface ComputeFetchSolveFnOptions<TMesh = unknown> {\n\t/** The compute endpoint to POST to, e.g. `/api/compute`. */\n\tendpoint: string;\n\t/** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */\n\tdefinitionUrl: () => string;\n\t/** Schema input params, sent as the request body's `inputs`. */\n\tinputs: () => Array<{ id: string }>;\n\t/** Schema output params — drives byId/byName output extraction after a solve. */\n\toutputs: () => Array<{ id: string; nickname?: string }>;\n\tchannel?: () => 'live' | 'draft' | undefined;\n\tversionId?: () => string | null | undefined;\n\t/** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */\n\tmeshes?: {\n\t\textract: (\n\t\t\tresponse: GrasshopperComputeResponse,\n\t\t\topts: { debug: boolean }\n\t\t) => TMesh[] | Promise<TMesh[]>;\n\t};\n\t/** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */\n\tdebug?: boolean;\n\t/** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */\n\tonRateLimited?: (retryAfterSeconds: number) => void;\n\t/** Called on a 401 or a login-page redirect. Default: throws an actionable \"reload to sign in again\" Error. */\n\tonSessionExpired?: () => void;\n}\n\nfunction parseServerTiming(header: string | null): Record<string, number> {\n\tconst out: Record<string, number> = {};\n\tif (!header) return out;\n\tfor (const part of header.split(',')) {\n\t\tconst [name, ...params] = part.split(';').map((s) => s.trim());\n\t\tconst dur = params.find((p) => p.startsWith('dur='));\n\t\tif (!name || !dur) continue;\n\t\tconst ms = Number(dur.slice(4));\n\t\tif (Number.isFinite(ms)) out[name] = ms;\n\t}\n\treturn out;\n}\n\nconst defaultOnRateLimited = (retryAfterSeconds: number): never => {\n\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(retryAfterSeconds)}s.`);\n};\n\nconst defaultOnSessionExpired = (): never => {\n\tthrow new Error(\n\t\t'Your session has expired. Sign in again in a new tab, then re-run — your inputs are preserved.'\n\t);\n};\n\nexport function createComputeFetchSolveFn<TMesh = unknown>(\n\topts: ComputeFetchSolveFnOptions<TMesh>\n): SolveFn<TMesh, GrasshopperComputeResponse> {\n\tconst debug = opts.debug ?? false;\n\tconst log = (...args: unknown[]) => {\n\t\tif (debug) console.info(...args);\n\t};\n\n\tlet cooldownUntil = 0;\n\n\treturn async (values, signal): Promise<SolveResult<TMesh, GrasshopperComputeResponse>> => {\n\t\tconst remainingMs = cooldownUntil - Date.now();\n\t\tif (remainingMs > 0) {\n\t\t\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(remainingMs / 1000)}s.`);\n\t\t}\n\n\t\tconst solveStart = performance.now();\n\t\tconst inputs = opts.inputs();\n\t\tconst outputs = opts.outputs();\n\t\tconst channel = opts.channel?.();\n\t\tconst versionId = opts.versionId?.();\n\n\t\tconst payload = JSON.stringify({\n\t\t\tinputs,\n\t\t\tvalues,\n\t\t\tdefinitionUrl: opts.definitionUrl(),\n\t\t\t...(versionId ? { versionId } : channel === 'draft' ? { channel: 'draft' } : {})\n\t\t});\n\n\t\tlet res: Response;\n\t\ttry {\n\t\t\tres = await fetch(opts.endpoint, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: payload,\n\t\t\t\tsignal\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (signal.aborted) {\n\t\t\t\tlog('[Compute] solve aborted during fetch — empty result discarded');\n\t\t\t\treturn { outputs: {} };\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t'Request blocked — your session may have expired. Reload the page to sign in again.',\n\t\t\t\t{ cause: err }\n\t\t\t);\n\t\t}\n\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted after headers — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\n\t\tconst redirectedToLogin = res.redirected && new URL(res.url).pathname === '/login';\n\t\tif (res.status === 401 || redirectedToLogin) {\n\t\t\t(opts.onSessionExpired ?? defaultOnSessionExpired)();\n\t\t}\n\n\t\tif (!res.ok) {\n\t\t\tconst errorBody = await res.text().catch(() => '');\n\t\t\tlet d: { message?: string; retryAfter?: number } = {};\n\t\t\ttry {\n\t\t\t\td = JSON.parse(errorBody);\n\t\t\t} catch {\n\t\t\t\tif (errorBody) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[Compute] non-JSON error body (HTTP ${res.status}):`,\n\t\t\t\t\t\terrorBody.slice(0, 300)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (res.status === 503 || res.status === 504) {\n\t\t\t\t// The server names the failure class (unreachable, bad API key,\n\t\t\t\t// rate-limited, timed out) — show it rather than a blanket \"offline\".\n\t\t\t\tthrow new Error(\n\t\t\t\t\td.message || 'Compute server is offline or unreachable. Please try again later.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (res.status === 429) {\n\t\t\t\tconst retryAfter = Number(res.headers.get('Retry-After')) || Number(d.retryAfter) || 5;\n\t\t\t\tcooldownUntil = Date.now() + retryAfter * 1000;\n\t\t\t\t(opts.onRateLimited ?? defaultOnRateLimited)(retryAfter);\n\t\t\t}\n\t\t\tthrow new Error(d.message || `Compute error (HTTP ${res.status})`);\n\t\t}\n\n\t\tconst ttfbMs = performance.now() - solveStart;\n\n\t\tconst downloadStart = performance.now();\n\t\tconst bodyText = await res.text();\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted mid-download — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\t\tconst downloadMs = performance.now() - downloadStart;\n\t\tconst bytes = bodyText.length;\n\n\t\tlet solved: GrasshopperComputeResponse;\n\t\ttry {\n\t\t\tsolved = JSON.parse(bodyText);\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t'Received an invalid response from the server. Reload the page and try again.',\n\t\t\t\t{\n\t\t\t\t\tcause: err\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\tconst processor = new GrasshopperResponseProcessor(solved, false);\n\n\t\tconst meshes = opts.meshes ? await opts.meshes.extract(processor.response, { debug }) : [];\n\n\t\tconst resultOutputs: Record<string, unknown> = {};\n\t\tfor (const o of outputs) {\n\t\t\tconst byId = processor.getValue({ byId: o.id }, { parseValues: true });\n\t\t\tresultOutputs[o.id] =\n\t\t\t\tbyId ??\n\t\t\t\t(o.nickname\n\t\t\t\t\t? processor.getValue({ byName: o.nickname }, { parseValues: true })\n\t\t\t\t\t: undefined);\n\t\t}\n\n\t\tif (debug) {\n\t\t\tconst valuesBytes = JSON.stringify(values).length;\n\t\t\tconst serverTiming = parseServerTiming(res.headers.get('Server-Timing'));\n\t\t\tconst serverTotal = serverTiming.total ?? 0;\n\t\t\tconst networkMs = Math.max(0, ttfbMs - serverTotal);\n\t\t\tconst sizeMB = bytes / (1024 * 1024);\n\t\t\tconst size = sizeMB >= 1 ? `${sizeMB.toFixed(2)} MB` : `${(bytes / 1024).toFixed(0)} KB`;\n\t\t\tconst mbps = downloadMs > 0 ? (sizeMB / (downloadMs / 1000)).toFixed(1) : '∞';\n\t\t\tconst heapMB = (performance as Performance & { memory?: { usedJSHeapSize: number } }).memory\n\t\t\t\t?.usedJSHeapSize;\n\t\t\tconst heap = heapMB !== undefined ? ` | heap=${(heapMB / (1024 * 1024)).toFixed(0)} MB` : '';\n\t\t\tlog(\n\t\t\t\t`[Compute] req=${(payload.length / 1024).toFixed(0)}KB (values ${(valuesBytes / 1024).toFixed(0)}KB) | ` +\n\t\t\t\t\t`ttfb=${ttfbMs.toFixed(0)}ms (network≈${networkMs.toFixed(0)} + server ${serverTotal.toFixed(0)}) | ` +\n\t\t\t\t\t`download=${downloadMs.toFixed(0)}ms (${size} @ ${mbps} MB/s)${heap}`\n\t\t\t);\n\t\t\tif (serverTiming.selva_cache !== undefined) {\n\t\t\t\tconst cacheMsg = serverTiming.selva_cache\n\t\t\t\t\t? 'HIT — served without calling compute'\n\t\t\t\t\t: 'miss — solved on Rhino.Compute';\n\t\t\t\tlog(`[Compute] └─ cache: ${cacheMsg}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\toutputs: resultOutputs,\n\t\t\tmeshes,\n\t\t\terrors: solved.errors ?? [],\n\t\t\twarnings: solved.warnings ?? [],\n\t\t\tsource: solved\n\t\t};\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../src/client/external-storage.ts","../src/client/solve-session-core.ts","../src/client/solve-session.ts","../src/client/async-throttle.ts","../src/client/solve-memo.ts","../src/client/drivers/request-response.ts","../src/client/compute-fetch-solve-fn.ts"],"sourcesContent":["// Transit storage for inputs with source.kind === 'client': a producer route writes the\n// value here, the solver route reads it back. Keyed by (scopeKey, inputId) so values\n// don't bleed across solver contexts — scopeKey is whatever uniquely identifies one\n// (sessionId in plugin-ui/preview, definition guid in selva/library).\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getInputItems } from '@selvajs/schemas';\n\nconst STORAGE_PREFIX = 'external';\n\nfunction makeKey(scopeKey: string, inputId: string): string {\n\treturn `${STORAGE_PREFIX}:${scopeKey}:${inputId}`;\n}\n\nexport interface ExternalValueRef {\n\tscopeKey: string;\n\tinputId: string;\n}\n\nexport function writeExternalValue(args: ExternalValueRef & { value: unknown }): void {\n\tconst { scopeKey, inputId, value } = args;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.setItem(makeKey(scopeKey, inputId), JSON.stringify(value));\n}\n\nexport function readExternalValue(ref: ExternalValueRef): unknown | undefined {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return undefined;\n\tif (typeof sessionStorage === 'undefined') return undefined;\n\tconst raw = sessionStorage.getItem(makeKey(scopeKey, inputId));\n\tif (raw === null) return undefined;\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function clearExternalValue(ref: ExternalValueRef): void {\n\tconst { scopeKey, inputId } = ref;\n\tif (!scopeKey || !inputId) return;\n\tif (typeof sessionStorage === 'undefined') return;\n\tsessionStorage.removeItem(makeKey(scopeKey, inputId));\n}\n\nexport interface ExternalInput {\n\tparamId: string;\n\tdisplayName: string;\n}\n\nexport function getExternalInputs(schema: UISchema): ExternalInput[] {\n\treturn getInputItems(schema)\n\t\t.filter((item) => item.source?.kind === 'client')\n\t\t.map((item) => ({\n\t\t\tparamId: item.paramId,\n\t\t\tdisplayName: item.displayName ?? item.paramId\n\t\t}));\n}\n","// Pure transition logic for a Solve Session.\n//\n// This module holds the value/lifecycle state machine with no transport and no state\n// ownership: how values are seeded (including client-sourced hydration), when a value\n// change should dispatch a solve vs. defer it, and what a reported solve result does to\n// the flags. `solve-session.ts` is a thin shell that owns the state object and the\n// subscriber set; every decision lives here, which is what makes it directly testable.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { getDefaultValue } from '@selvajs/schemas';\nimport { getExternalInputs, type ExternalValueRef } from './external-storage.js';\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * The slice of a reported result the session keeps addressable, so a host can commit exactly\n * what the viewer is showing — `source` and `values` included, already memo-correct.\n *\n * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what\n * it renders on the next scene update, which is why the memo carries a `MeshPolicy`\n * clone/release contract at all. Retaining them here would hand out disposed instances after\n * the next solve, with no policy governing them and no owner. Everything left is inert JSON;\n * live meshes stay on `session.meshes`.\n */\nexport type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;\n\nexport interface SolveSessionState {\n\tvalues: Record<string, unknown>;\n\terror: string;\n\tcomputeErrors: string[];\n\tcomputeWarnings: string[];\n\tmeshes: unknown[];\n\t/** Values changed since the last solve, in manual (instanceSolve === false) mode. */\n\tpendingValues: Record<string, unknown>;\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n\t/**\n\t * The last result reported, minus its meshes. Null before the first solve.\n\t *\n\t * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a\n\t * value change and the next report it describes the *previous* input set. That is what a\n\t * commit gate wants — what is on screen, not what is pending — so `lastResult.values`\n\t * disagreeing with `state.values` mid-scrub is the feature, not a bug.\n\t */\n\tlastResult: RetainedSolveResult | null;\n}\n\nexport type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;\n\n/**\n * Client-sourced inputs are hydrated from `read` and left absent when no stored value\n * exists, so the missing-inputs panel can detect them. Other inputs fall back to their\n * declared default, then the paramType default.\n */\nexport function buildInitialValues(\n\tschema: UISchema,\n\tscopeKey: string,\n\tread: ExternalReader\n): Record<string, unknown> {\n\tconst clientSet = new Set(getExternalInputs(schema).map((e) => e.paramId));\n\tconst values: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (clientSet.has(input.id)) {\n\t\t\tconst stored = read({ scopeKey, inputId: input.id });\n\t\t\tif (stored !== undefined) values[input.id] = stored;\n\t\t\tcontinue;\n\t\t}\n\t\tvalues[input.id] = input.default ?? getDefaultValue(input.paramType);\n\t}\n\tfor (const output of schema.outputs) {\n\t\tvalues[output.id] = null;\n\t}\n\treturn values;\n}\n\n/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */\nexport function makeInitialFlags(instanceSolve: boolean | undefined): {\n\thasPendingChanges: boolean;\n\thasNeverSolved: boolean;\n} {\n\tconst manual = instanceSolve === false;\n\treturn { hasPendingChanges: manual, hasNeverSolved: manual };\n}\n\n/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */\nexport function applyValueChange(\n\tstate: SolveSessionState,\n\tid: string,\n\tvalue: unknown,\n\tinstanceSolve: boolean | undefined\n): { state: SolveSessionState; shouldSolve: boolean } {\n\tstate.values[id] = value;\n\tif (instanceSolve === false) {\n\t\tstate.pendingValues[id] = value;\n\t\tstate.hasPendingChanges = true;\n\t\treturn { state, shouldSolve: false };\n\t}\n\treturn { state, shouldSolve: true };\n}\n\n/**\n * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value\n * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB\n * options-list output showed this can re-upload a multi-MB payload no backend reads.\n */\nexport function pickInputValues(\n\tschema: UISchema | undefined,\n\tvalues: Record<string, unknown>\n): Record<string, unknown> {\n\tif (!schema?.inputs) return values;\n\tconst picked: Record<string, unknown> = {};\n\tfor (const input of schema.inputs) {\n\t\tif (input.id in values) picked[input.id] = values[input.id];\n\t}\n\treturn picked;\n}\n\nexport function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState {\n\tstate.error = '';\n\tstate.computeErrors = result.errors ?? [];\n\tstate.computeWarnings = result.warnings ?? [];\n\tstate.meshes = result.meshes ?? [];\n\tconst { meshes: _meshes, ...retained } = result;\n\tstate.lastResult = retained;\n\tObject.assign(state.values, result.outputs);\n\tstate.pendingValues = {};\n\tstate.hasPendingChanges = false;\n\tstate.hasNeverSolved = false;\n\treturn state;\n}\n","// A framework-free shell over the pure transition logic in solve-session-core.ts: owns the\n// live values/flags, delegates every transition to the core, and drives solves through a\n// transport-agnostic SolveDriver.\n//\n// State is exposed as plain getters; it carries no framework reactivity of its own, so a\n// Svelte host must wrap it — see `useSolveSession.svelte.ts` in `@selvajs/ui`, which\n// republishes these getters as $state. Reading a getter without subscribing gives a\n// correct value but will not re-render.\n\nimport type { UISchema } from '@selvajs/schemas';\nimport { readExternalValue } from './external-storage.js';\nimport type { SolveResult } from '../shared/solve-fn.js';\nimport type { SolveDriver } from './drivers/driver.js';\nimport {\n\tbuildInitialValues,\n\tmakeInitialFlags,\n\tapplyValueChange,\n\tapplySolveResult,\n\tpickInputValues,\n\ttype SolveSessionState,\n\ttype RetainedSolveResult\n} from './solve-session-core.js';\n\nexport interface SolveSession {\n\treadonly values: Record<string, unknown>;\n\treadonly error: string;\n\treadonly computeErrors: string[];\n\treadonly computeWarnings: string[];\n\treadonly meshes: unknown[];\n\t/**\n\t * The last reported result without its meshes — what the viewer is currently showing,\n\t * including the `source`/`values` pair a commit path needs. Null before the first solve,\n\t * and again after `rebuild()`. Survives `reportError`: a failed solve leaves the last good\n\t * geometry on screen, so a host committing what it sees should still see what produced it.\n\t */\n\treadonly lastResult: RetainedSolveResult | null;\n\treadonly hasPendingChanges: boolean;\n\treadonly hasNeverSolved: boolean;\n\treadonly isSolving: boolean;\n\t/** `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. */\n\tsetValue(id: string, value: unknown, forceSolve?: boolean): void;\n\tsolve(): void;\n\t/** Merges incoming values, then solves (auto) or marks dirty (manual). */\n\tloadValues(incoming: Record<string, unknown>): void;\n\trebuild(schema: UISchema, scopeKey: string): void;\n\treport(result: SolveResult): void;\n\treportError(message: string): void;\n\t/** Fires after any mutation of the getters above, including driver-reported `isSolving` transitions. */\n\tsubscribe(listener: () => void): () => void;\n\t/** For driver-owned state the session only forwards: `isSolving` lives on the driver, so wire this to its `onChange`. */\n\tnotify(): void;\n}\n\nexport interface SolveSessionArgs {\n\tschema: UISchema;\n\tscopeKey: string;\n\tdriver: SolveDriver;\n}\n\nexport function createSolveSession(args: SolveSessionArgs): SolveSession {\n\tlet currentSchema = args.schema;\n\n\tconst flags = makeInitialFlags(currentSchema?.instanceSolve);\n\tconst state: SolveSessionState = {\n\t\tvalues: buildInitialValues(currentSchema, args.scopeKey, readExternalValue),\n\t\terror: '',\n\t\tcomputeErrors: [],\n\t\tcomputeWarnings: [],\n\t\tmeshes: [],\n\t\tpendingValues: {},\n\t\thasPendingChanges: flags.hasPendingChanges,\n\t\thasNeverSolved: flags.hasNeverSolved,\n\t\tlastResult: null\n\t};\n\n\tconst listeners = new Set<() => void>();\n\tconst emit = () => listeners.forEach((l) => l());\n\n\tfunction dispatch() {\n\t\t// Copy: state.values is the live map, and a driver may hold the payload across an\n\t\t// async solve — a later setValue must not mutate what's already in flight.\n\t\targs.driver.solve({ ...pickInputValues(currentSchema, state.values) });\n\t\temit();\n\t}\n\n\treturn {\n\t\tget values() {\n\t\t\treturn state.values;\n\t\t},\n\t\tget error() {\n\t\t\treturn state.error;\n\t\t},\n\t\tget computeErrors() {\n\t\t\treturn state.computeErrors;\n\t\t},\n\t\tget computeWarnings() {\n\t\t\treturn state.computeWarnings;\n\t\t},\n\t\tget meshes() {\n\t\t\treturn state.meshes;\n\t\t},\n\t\tget lastResult() {\n\t\t\treturn state.lastResult;\n\t\t},\n\t\tget hasPendingChanges() {\n\t\t\treturn state.hasPendingChanges;\n\t\t},\n\t\tget hasNeverSolved() {\n\t\t\treturn state.hasNeverSolved;\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn args.driver.isSolving;\n\t\t},\n\n\t\tsetValue(id, value, forceSolve = false) {\n\t\t\tconst { shouldSolve } = applyValueChange(state, id, value, currentSchema?.instanceSolve);\n\t\t\tif (shouldSolve || forceSolve) {\n\t\t\t\tif (forceSolve && !shouldSolve) {\n\t\t\t\t\tstate.pendingValues = {};\n\t\t\t\t\tstate.hasPendingChanges = false;\n\t\t\t\t}\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\tsolve() {\n\t\t\tdispatch();\n\t\t},\n\n\t\tloadValues(incoming) {\n\t\t\tObject.assign(state.values, incoming);\n\t\t\tif (currentSchema?.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\tstate.hasPendingChanges = true;\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\trebuild(schema, scopeKey) {\n\t\t\tcurrentSchema = schema;\n\t\t\t// The new definition has its own input space; a matching input key from the prior\n\t\t\t// one must not serve that definition's cached result.\n\t\t\targs.driver.clearCache?.();\n\t\t\tstate.meshes = [];\n\t\t\t// Same reason as clearCache: a retained result belongs to the previous definition.\n\t\t\tstate.lastResult = null;\n\t\t\tstate.error = '';\n\t\t\tstate.computeErrors = [];\n\t\t\tstate.computeWarnings = [];\n\t\t\tstate.pendingValues = {};\n\t\t\tstate.values = buildInitialValues(schema, scopeKey, readExternalValue);\n\t\t\tconst f = makeInitialFlags(schema?.instanceSolve);\n\t\t\tstate.hasPendingChanges = f.hasPendingChanges;\n\t\t\tstate.hasNeverSolved = f.hasNeverSolved;\n\t\t\tif (schema && Object.keys(state.values).length > 0 && schema.instanceSolve !== false) {\n\t\t\t\tdispatch();\n\t\t\t} else {\n\t\t\t\temit();\n\t\t\t}\n\t\t},\n\n\t\treport(result) {\n\t\t\tapplySolveResult(state, result);\n\t\t\temit();\n\t\t},\n\n\t\treportError(message) {\n\t\t\tstate.error = message;\n\t\t\temit();\n\t\t},\n\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\n\t\tnotify: emit\n\t};\n}\n","/**\n * Single-in-flight, latest-wins throttle for any async operation: only one run at a\n * time, a newly triggered value overwrites the single pending slot (no queue), and a\n * stale run can be aborted via `AbortController` or timeout.\n *\n * Framework-free: `isRunning` is a plain getter, not a store. A reactive host must\n * subscribe via `onChange` (see `@selvajs/ui`'s rune adapter) — polling the getter\n * directly reads correct values but won't trigger a re-render.\n */\ninterface AsyncThrottleOptions {\n\t/**\n\t * How long one run may take before it is aborted (ms). Required and deliberately\n\t * undefaulted: this is a generic throttle, so it has no basis for guessing what\n\t * the caller's work costs. Callers driving a Selva solve pass the deadline the\n\t * server enforces (`COMPUTE_SOLVE_DEADLINE_MS`) — a shorter local guess would abort\n\t * solves the server would have finished.\n\t */\n\trunDeadlineMs: number;\n\tonChange?: () => void;\n}\n\nexport interface AsyncThrottle<T> {\n\ttrigger: (values: T) => void;\n\treadonly isRunning: boolean;\n\treadonly hasPending: boolean;\n\tcancel: () => void;\n}\n\nexport function createAsyncThrottle<T>(\n\trun: (values: T, signal: AbortSignal) => Promise<void>,\n\toptions: AsyncThrottleOptions\n): AsyncThrottle<T> {\n\tconst { runDeadlineMs, onChange } = options;\n\n\tlet isRunning = false;\n\tlet pendingValues: T | null = null;\n\tlet currentAbortController: AbortController | null = null;\n\n\tfunction setRunning(next: boolean) {\n\t\tif (isRunning === next) return;\n\t\tisRunning = next;\n\t\tonChange?.();\n\t}\n\n\tfunction abortCurrent() {\n\t\tcurrentAbortController?.abort();\n\t\tcurrentAbortController = null;\n\t}\n\n\tasync function execute(values: T) {\n\t\tabortCurrent();\n\n\t\tcurrentAbortController = new AbortController();\n\t\tconst { signal } = currentAbortController;\n\t\tconst deadlineId = setTimeout(() => {\n\t\t\t// Cleared in `finally` on every other path, so firing means a genuine\n\t\t\t// timeout — the only signal for it (the abort itself is swallowed below).\n\t\t\tconsole.warn(`[Solve/throttle] run exceeded ${runDeadlineMs}ms — aborting`);\n\t\t\tcurrentAbortController?.abort();\n\t\t}, runDeadlineMs);\n\n\t\tsetRunning(true);\n\t\ttry {\n\t\t\tawait run(values, signal);\n\t\t} catch (err) {\n\t\t\t// AbortError is expected (timeout or cancel). Non-abort errors must be\n\t\t\t// handled inside `run` — re-throwing here would produce an unhandled\n\t\t\t// rejection because `execute` is always called fire-and-forget.\n\t\t\tif (!(err instanceof Error) || (err.name !== 'AbortError' && err.name !== 'TimeoutError')) {\n\t\t\t\tconsole.error('[Solve/throttle] unhandled error in run:', err);\n\t\t\t}\n\t\t} finally {\n\t\t\tclearTimeout(deadlineId);\n\t\t\tsetRunning(false);\n\t\t\tcurrentAbortController = null;\n\n\t\t\tif (pendingValues !== null) {\n\t\t\t\tconst next = pendingValues;\n\t\t\t\tpendingValues = null;\n\t\t\t\texecute(next);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction trigger(values: T) {\n\t\tif (isRunning) {\n\t\t\tif (pendingValues !== null) {\n\t\t\t\t// Latest-wins: the previously-queued values are dropped, not run.\n\t\t\t\t// `debug`, not `info`: a slider scrub fires this per frame, and it is a trace of\n\t\t\t\t// normal operation. Promoting it would spam a host's console on every drag.\n\t\t\t\t// eslint-disable-next-line no-console -- see above\n\t\t\t\tconsole.debug('[Solve/throttle] superseded pending run (latest-wins)');\n\t\t\t}\n\t\t\tpendingValues = values;\n\t\t} else {\n\t\t\texecute(values);\n\t\t}\n\t}\n\n\tfunction cancel() {\n\t\tpendingValues = null;\n\t\tabortCurrent();\n\t}\n\n\treturn {\n\t\ttrigger,\n\t\tget isRunning() {\n\t\t\treturn isRunning;\n\t\t},\n\t\tget hasPending() {\n\t\t\treturn pendingValues !== null;\n\t\t},\n\t\tcancel\n\t};\n}\n","// LRU cache of solve results keyed on a stable serialization of the inputs, so re-solving\n// something already seen this session (e.g. dragging a slider back) returns instantly\n// instead of round-tripping.\n//\n// A viewer typically takes ownership of every mesh it renders, disposing the previous\n// content on the next update. So the memo can't hand out its own stored instances (they'd\n// get disposed under it) or drop entries silently (their GPU buffers would leak): it keeps\n// private copies via an injected clone/release policy, which also keeps `three` out of this\n// package — `TMesh` stays opaque here.\n\nimport type { SolveResult } from '../shared/solve-fn.js';\n\n/**\n * Deterministic string key for a set of input values. Sorts object keys at every level so\n * two logically-equal inputs built in different key order collide.\n */\nexport function stableInputKey(values: Record<string, unknown>): string {\n\treturn serialize(values);\n}\n\nfunction serialize(value: unknown): string {\n\tif (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';\n\tif (Array.isArray(value)) return `[${value.map(serialize).join(',')}]`;\n\tconst obj = value as Record<string, unknown>;\n\tconst keys = Object.keys(obj).sort();\n\treturn `{${keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(',')}}`;\n}\n\n/**\n * Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then\n * stores and serves them by reference, which is safe only because nothing frees them behind\n * its back.\n */\nexport interface MeshPolicy<TMesh> {\n\t/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */\n\tclone(meshes: TMesh[]): TMesh[];\n\t/** Must not free anything shared with live content (e.g. materials another object still uses). */\n\trelease(meshes: TMesh[]): void;\n}\n\nexport interface SolveMemoOptions<TMesh> {\n\t/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */\n\tmax?: number;\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\nexport interface SolveMemo<TMesh = unknown> {\n\tget(values: Record<string, unknown>): SolveResult<TMesh> | undefined;\n\tset(values: Record<string, unknown>, result: SolveResult<TMesh>): void;\n\tclear(): void;\n}\n\nexport function createSolveMemo<TMesh = unknown>(\n\toptions: SolveMemoOptions<TMesh> = {}\n): SolveMemo<TMesh> {\n\tconst { max = 16, meshPolicy } = options;\n\tconst entries = new Map<string, SolveResult<TMesh>>();\n\n\tfunction release(result: SolveResult<TMesh>): void {\n\t\tif (result.meshes?.length) meshPolicy?.release(result.meshes);\n\t}\n\n\tfunction copy(result: SolveResult<TMesh>): SolveResult<TMesh> {\n\t\tif (!meshPolicy || !result.meshes?.length) return result;\n\t\treturn { ...result, meshes: meshPolicy.clone(result.meshes) };\n\t}\n\n\tfunction evict(key: string): void {\n\t\tconst entry = entries.get(key);\n\t\tif (entry === undefined) return;\n\t\tentries.delete(key);\n\t\trelease(entry);\n\t}\n\n\treturn {\n\t\tget(values) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tconst hit = entries.get(key);\n\t\t\tif (hit === undefined) return undefined;\n\t\t\tentries.delete(key);\n\t\t\tentries.set(key, hit);\n\t\t\tconsole.info(`[Solve/memo] HIT — served from client memo (${entries.size}/${max})`);\n\t\t\treturn copy(hit);\n\t\t},\n\t\tset(values, result) {\n\t\t\tconst key = stableInputKey(values);\n\t\t\tevict(key);\n\t\t\tentries.set(key, copy(result));\n\t\t\twhile (entries.size > max) {\n\t\t\t\tconst oldest = entries.keys().next().value;\n\t\t\t\tif (oldest === undefined) break;\n\t\t\t\tevict(oldest);\n\t\t\t\tconsole.info(`[Solve/memo] evicted LRU entry (cap ${max})`);\n\t\t\t}\n\t\t},\n\t\tclear() {\n\t\t\tif (entries.size > 0) {\n\t\t\t\tconsole.info(`[Solve/memo] cleared ${entries.size} entries (definition changed)`);\n\t\t\t}\n\t\t\tentries.forEach(release);\n\t\t\tentries.clear();\n\t\t}\n\t};\n}\n","import { createAsyncThrottle } from '../async-throttle.js';\nimport { createSolveMemo, type MeshPolicy } from '../solve-memo.js';\nimport type { SolveFn } from '../../shared/solve-fn.js';\nimport type { SolveDriver, SolveReporter } from './driver.js';\n\nexport interface RequestResponseDriverOptions<TMesh> {\n\t/** How long one solve may take before the client aborts it (ms). Pass the server's `COMPUTE_SOLVE_DEADLINE_MS` so the two agree. */\n\tsolveDeadlineMs: number;\n\tonChange?: () => void;\n\t/** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */\n\tmeshPolicy?: MeshPolicy<TMesh>;\n}\n\n/**\n * `getReporter` is lazy (`() => session`) because the session and driver reference each\n * other — the host builds the driver first, then the session, then can hand this back.\n */\nexport function createRequestResponseDriver<TMesh = unknown>(\n\tonSolve: SolveFn<TMesh>,\n\tgetReporter: () => SolveReporter<TMesh>,\n\toptions: RequestResponseDriverOptions<TMesh>\n): SolveDriver {\n\tconst { meshPolicy, solveDeadlineMs, onChange } = options;\n\n\t// Checked inside the throttled run, not before triggering it, so a hit only serves\n\t// after the throttle's latest-wins ordering has picked these values to run.\n\tconst memo = createSolveMemo<TMesh>({ meshPolicy });\n\n\tconst throttle = createAsyncThrottle<Record<string, unknown>>(\n\t\tasync (values, signal) => {\n\t\t\tconst cached = memo.get(values);\n\t\t\tif (cached !== undefined) {\n\t\t\t\tif (signal.aborted) return;\n\t\t\t\tgetReporter().report(cached);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await onSolve(values, signal);\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve completed after abort — result discarded');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Stamped here rather than in the SolveFn: a memo hit never calls it, so values\n\t\t\t\t// captured inside would pair the served result with whatever solved last.\n\t\t\t\tconst stamped = { ...result, values };\n\t\t\t\tmemo.set(values, stamped);\n\t\t\t\tgetReporter().report(stamped);\n\t\t\t} catch (err) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- normal during a slider scrub; not a warning\n\t\t\t\t\tconsole.debug('[Solve/driver] solve aborted (superseded, cancelled, or timed out)');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconsole.warn('[Solve/driver] solve failed:', err);\n\t\t\t\tgetReporter().reportError(err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t},\n\t\t// The throttle is generic, so the solve deadline is what bounds one of its runs.\n\t\t{ runDeadlineMs: solveDeadlineMs, onChange }\n\t);\n\n\treturn {\n\t\tsolve(values) {\n\t\t\tthrottle.trigger(values);\n\t\t},\n\t\tcancel() {\n\t\t\tthrottle.cancel();\n\t\t},\n\t\tget isSolving() {\n\t\t\treturn throttle.isRunning;\n\t\t},\n\t\tclearCache() {\n\t\t\tmemo.clear();\n\t\t}\n\t};\n}\n","/**\n * `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a\n * `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +\n * `SolveEngine` produce server-side) and unpacks the result.\n *\n * Ports the resilience behavior an interactive solve endpoint needs regardless\n * of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response\n * guarding, and abort handling at every await point — so a new consumer gets it\n * for free instead of re-deriving it by hand. Mesh decoding is NOT built in:\n * this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a\n * viewer-enabled consumer supplies its own `meshes.extract` hook (typically\n * `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).\n */\n\nimport {\n\tGrasshopperResponseProcessor,\n\ttype GrasshopperComputeResponse\n} from '@selvajs/compute/grasshopper';\nimport type { SolveFn, SolveResult } from '../shared/solve-fn.js';\n\nexport interface ComputeFetchSolveFnOptions<TMesh = unknown> {\n\t/** The compute endpoint to POST to, e.g. `/api/compute`. */\n\tendpoint: string;\n\t/** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */\n\tdefinitionUrl: () => string;\n\t/** Schema input params, sent as the request body's `inputs`. */\n\tinputs: () => Array<{ id: string }>;\n\t/** Schema output params — drives byId/byName output extraction after a solve. */\n\toutputs: () => Array<{ id: string; nickname?: string }>;\n\tchannel?: () => 'live' | 'draft' | undefined;\n\tversionId?: () => string | null | undefined;\n\t/** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */\n\tmeshes?: {\n\t\textract: (\n\t\t\tresponse: GrasshopperComputeResponse,\n\t\t\topts: { debug: boolean }\n\t\t) => TMesh[] | Promise<TMesh[]>;\n\t};\n\t/** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */\n\tdebug?: boolean;\n\t/** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */\n\tonRateLimited?: (retryAfterSeconds: number) => void;\n\t/** Called on a 401 or a login-page redirect. Default: throws an actionable \"reload to sign in again\" Error. */\n\tonSessionExpired?: () => void;\n}\n\nfunction parseServerTiming(header: string | null): Record<string, number> {\n\tconst out: Record<string, number> = {};\n\tif (!header) return out;\n\tfor (const part of header.split(',')) {\n\t\tconst [name, ...params] = part.split(';').map((s) => s.trim());\n\t\tconst dur = params.find((p) => p.startsWith('dur='));\n\t\tif (!name || !dur) continue;\n\t\tconst ms = Number(dur.slice(4));\n\t\tif (Number.isFinite(ms)) out[name] = ms;\n\t}\n\treturn out;\n}\n\nconst defaultOnRateLimited = (retryAfterSeconds: number): never => {\n\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(retryAfterSeconds)}s.`);\n};\n\nconst defaultOnSessionExpired = (): never => {\n\tthrow new Error(\n\t\t'Your session has expired. Sign in again in a new tab, then re-run — your inputs are preserved.'\n\t);\n};\n\nexport function createComputeFetchSolveFn<TMesh = unknown>(\n\topts: ComputeFetchSolveFnOptions<TMesh>\n): SolveFn<TMesh, GrasshopperComputeResponse> {\n\tconst debug = opts.debug ?? false;\n\tconst log = (...args: unknown[]) => {\n\t\tif (debug) console.info(...args);\n\t};\n\n\tlet cooldownUntil = 0;\n\n\treturn async (values, signal): Promise<SolveResult<TMesh, GrasshopperComputeResponse>> => {\n\t\tconst remainingMs = cooldownUntil - Date.now();\n\t\tif (remainingMs > 0) {\n\t\t\tthrow new Error(`Rate limit reached. Try again in ${Math.ceil(remainingMs / 1000)}s.`);\n\t\t}\n\n\t\tconst solveStart = performance.now();\n\t\tconst inputs = opts.inputs();\n\t\tconst outputs = opts.outputs();\n\t\tconst channel = opts.channel?.();\n\t\tconst versionId = opts.versionId?.();\n\n\t\tconst payload = JSON.stringify({\n\t\t\tinputs,\n\t\t\tvalues,\n\t\t\tdefinitionUrl: opts.definitionUrl(),\n\t\t\t...(versionId ? { versionId } : channel === 'draft' ? { channel: 'draft' } : {})\n\t\t});\n\n\t\tlet res: Response;\n\t\ttry {\n\t\t\tres = await fetch(opts.endpoint, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: payload,\n\t\t\t\tsignal\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (signal.aborted) {\n\t\t\t\tlog('[Compute] solve aborted during fetch — empty result discarded');\n\t\t\t\treturn { outputs: {} };\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t'Request blocked — your session may have expired. Reload the page to sign in again.',\n\t\t\t\t{ cause: err }\n\t\t\t);\n\t\t}\n\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted after headers — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\n\t\tconst redirectedToLogin = res.redirected && new URL(res.url).pathname === '/login';\n\t\tif (res.status === 401 || redirectedToLogin) {\n\t\t\t(opts.onSessionExpired ?? defaultOnSessionExpired)();\n\t\t}\n\n\t\tif (!res.ok) {\n\t\t\tconst errorBody = await res.text().catch(() => '');\n\t\t\tlet d: { message?: string; retryAfter?: number } = {};\n\t\t\ttry {\n\t\t\t\td = JSON.parse(errorBody);\n\t\t\t} catch {\n\t\t\t\tif (errorBody) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[Compute] non-JSON error body (HTTP ${res.status}):`,\n\t\t\t\t\t\terrorBody.slice(0, 300)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (res.status === 503 || res.status === 504) {\n\t\t\t\t// The server names the failure class (unreachable, bad API key,\n\t\t\t\t// rate-limited, timed out) — show it rather than a blanket \"offline\".\n\t\t\t\tthrow new Error(\n\t\t\t\t\td.message || 'Compute server is offline or unreachable. Please try again later.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (res.status === 429) {\n\t\t\t\tconst retryAfter = Number(res.headers.get('Retry-After')) || Number(d.retryAfter) || 5;\n\t\t\t\tcooldownUntil = Date.now() + retryAfter * 1000;\n\t\t\t\t(opts.onRateLimited ?? defaultOnRateLimited)(retryAfter);\n\t\t\t}\n\t\t\tthrow new Error(d.message || `Compute error (HTTP ${res.status})`);\n\t\t}\n\n\t\tconst ttfbMs = performance.now() - solveStart;\n\n\t\tconst downloadStart = performance.now();\n\t\tconst bodyText = await res.text();\n\t\tif (signal.aborted) {\n\t\t\tlog('[Compute] solve aborted mid-download — empty result discarded');\n\t\t\treturn { outputs: {} };\n\t\t}\n\t\tconst downloadMs = performance.now() - downloadStart;\n\t\tconst bytes = bodyText.length;\n\n\t\tlet solved: GrasshopperComputeResponse;\n\t\ttry {\n\t\t\tsolved = JSON.parse(bodyText);\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t'Received an invalid response from the server. Reload the page and try again.',\n\t\t\t\t{\n\t\t\t\t\tcause: err\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\tconst processor = new GrasshopperResponseProcessor(solved, false);\n\n\t\tconst meshes = opts.meshes ? await opts.meshes.extract(processor.response, { debug }) : [];\n\n\t\tconst resultOutputs: Record<string, unknown> = {};\n\t\tfor (const o of outputs) {\n\t\t\tconst byId = processor.getValue({ byId: o.id }, { parseValues: true });\n\t\t\tresultOutputs[o.id] =\n\t\t\t\tbyId ??\n\t\t\t\t(o.nickname\n\t\t\t\t\t? processor.getValue({ byName: o.nickname }, { parseValues: true })\n\t\t\t\t\t: undefined);\n\t\t}\n\n\t\tif (debug) {\n\t\t\tconst valuesBytes = JSON.stringify(values).length;\n\t\t\tconst serverTiming = parseServerTiming(res.headers.get('Server-Timing'));\n\t\t\tconst serverTotal = serverTiming.total ?? 0;\n\t\t\tconst networkMs = Math.max(0, ttfbMs - serverTotal);\n\t\t\tconst sizeMB = bytes / (1024 * 1024);\n\t\t\tconst size = sizeMB >= 1 ? `${sizeMB.toFixed(2)} MB` : `${(bytes / 1024).toFixed(0)} KB`;\n\t\t\tconst mbps = downloadMs > 0 ? (sizeMB / (downloadMs / 1000)).toFixed(1) : '∞';\n\t\t\tconst heapMB = (performance as Performance & { memory?: { usedJSHeapSize: number } }).memory\n\t\t\t\t?.usedJSHeapSize;\n\t\t\tconst heap = heapMB !== undefined ? ` | heap=${(heapMB / (1024 * 1024)).toFixed(0)} MB` : '';\n\t\t\tlog(\n\t\t\t\t`[Compute] req=${(payload.length / 1024).toFixed(0)}KB (values ${(valuesBytes / 1024).toFixed(0)}KB) | ` +\n\t\t\t\t\t`ttfb=${ttfbMs.toFixed(0)}ms (network≈${networkMs.toFixed(0)} + server ${serverTotal.toFixed(0)}) | ` +\n\t\t\t\t\t`download=${downloadMs.toFixed(0)}ms (${size} @ ${mbps} MB/s)${heap}`\n\t\t\t);\n\t\t\tif (serverTiming.selva_cache !== undefined) {\n\t\t\t\tconst cacheMsg = serverTiming.selva_cache\n\t\t\t\t\t? 'HIT — served without calling compute'\n\t\t\t\t\t: 'miss — solved on Rhino.Compute';\n\t\t\t\tlog(`[Compute] └─ cache: ${cacheMsg}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\toutputs: resultOutputs,\n\t\t\tmeshes,\n\t\t\terrors: solved.errors ?? [],\n\t\t\twarnings: solved.warnings ?? [],\n\t\t\tsource: solved\n\t\t};\n\t};\n}\n"],"mappings":"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"}
|
package/dist/server.d.cts
CHANGED
|
@@ -18,7 +18,7 @@ interface ResolvedServer {
|
|
|
18
18
|
/** Sent as the `RhinoComputeKey` header. */
|
|
19
19
|
apiKey?: string;
|
|
20
20
|
}
|
|
21
|
-
declare function serverIdentity(server: Pick<ResolvedServer, 'id'>): ServerIdentity;
|
|
21
|
+
export declare function serverIdentity(server: Pick<ResolvedServer, 'id'>): ServerIdentity;
|
|
22
22
|
interface CachedClient {
|
|
23
23
|
client: GrasshopperClient;
|
|
24
24
|
scheduler: SolveScheduler;
|
|
@@ -125,7 +125,7 @@ interface SolveCacheStats {
|
|
|
125
125
|
/** Entries dropped under size/byte pressure (not replacement or manual clears). */
|
|
126
126
|
evictions: number;
|
|
127
127
|
}
|
|
128
|
-
declare function createClientCache(config: ClientCacheConfig): ClientCache;
|
|
128
|
+
export declare function createClientCache(config: ClientCacheConfig): ClientCache;
|
|
129
129
|
//#endregion
|
|
130
130
|
//#region src/server/definition-byte-cache.d.ts
|
|
131
131
|
/**
|
|
@@ -179,12 +179,12 @@ interface DefinitionByteCache {
|
|
|
179
179
|
* @param maxBytes total retained-byte budget; `0` (or negative) disables caching
|
|
180
180
|
* entirely — every `load()` calls the loader and nothing is retained.
|
|
181
181
|
*/
|
|
182
|
-
declare function createDefinitionByteCache(maxBytes: number): DefinitionByteCache;
|
|
182
|
+
export declare function createDefinitionByteCache(maxBytes: number): DefinitionByteCache;
|
|
183
183
|
//#endregion
|
|
184
184
|
//#region src/server/solve-pipeline.d.ts
|
|
185
185
|
/** Compute-response contract version. Bump (and document the change) whenever the envelope's shape changes in a way a consumer could observe. */
|
|
186
|
-
declare const COMPUTE_CONTRACT_VERSION: 1;
|
|
187
|
-
declare const COMPUTE_VERSION_HEADER = "X-Selva-Compute-Version";
|
|
186
|
+
export declare const COMPUTE_CONTRACT_VERSION: 1;
|
|
187
|
+
export declare const COMPUTE_VERSION_HEADER = "X-Selva-Compute-Version";
|
|
188
188
|
type PipelineInput = SchemaInput & {
|
|
189
189
|
minimum?: number;
|
|
190
190
|
maximum?: number;
|
|
@@ -287,8 +287,8 @@ type SolveOutcome = {
|
|
|
287
287
|
error: unknown;
|
|
288
288
|
};
|
|
289
289
|
/** The transformed input tree exactly as handed to the scheduler; `runSolvePipeline` calls this itself unless the caller supplies {@link SolvePipelineArgs.inputTree}. */
|
|
290
|
-
declare function buildSolveInputTree(inputs: PipelineInput[], values: Record<string, unknown>): DataTree[];
|
|
291
|
-
declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome>;
|
|
290
|
+
export declare function buildSolveInputTree(inputs: PipelineInput[], values: Record<string, unknown>): DataTree[];
|
|
291
|
+
export declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome>;
|
|
292
292
|
/**
|
|
293
293
|
* Re-key a coalesced envelope to a single waiter's `Accept-Encoding`.
|
|
294
294
|
*
|
|
@@ -304,7 +304,7 @@ declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome
|
|
|
304
304
|
* a non-gzip waiter is gunzipped back to JSON. The reverse (plain JSON to a
|
|
305
305
|
* gzip-capable waiter) is left uncompressed — correct, just not maximally small.
|
|
306
306
|
*/
|
|
307
|
-
declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding: string): {
|
|
307
|
+
export declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding: string): {
|
|
308
308
|
body: string | Uint8Array;
|
|
309
309
|
headers: Record<string, string>;
|
|
310
310
|
};
|
|
@@ -314,7 +314,7 @@ declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding
|
|
|
314
314
|
* `SchemaInput` has no `values`/`acceptedFormats` (those live on the layout item's config), so
|
|
315
315
|
* valueList/file inputs here carry just the selected value with no option list.
|
|
316
316
|
*/
|
|
317
|
-
declare function transformInputParameter(input: SchemaInput & {
|
|
317
|
+
export declare function transformInputParameter(input: SchemaInput & {
|
|
318
318
|
minimum?: number;
|
|
319
319
|
maximum?: number;
|
|
320
320
|
stepSize?: number;
|
|
@@ -355,7 +355,7 @@ interface SolveCacheSingleFlight {
|
|
|
355
355
|
interface SolveCacheSingleFlightOptions {
|
|
356
356
|
onJoin?: (key: string) => void;
|
|
357
357
|
}
|
|
358
|
-
declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
|
|
358
|
+
export declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
|
|
359
359
|
//#endregion
|
|
360
360
|
//#region src/server/solve-engine.d.ts
|
|
361
361
|
/**
|
|
@@ -427,7 +427,7 @@ interface SolveEngineStats {
|
|
|
427
427
|
inFlight: number;
|
|
428
428
|
};
|
|
429
429
|
}
|
|
430
|
-
declare class SolveEngine {
|
|
430
|
+
export declare class SolveEngine {
|
|
431
431
|
private readonly limits;
|
|
432
432
|
private readonly clientCache;
|
|
433
433
|
private readonly byteCache;
|
|
@@ -456,5 +456,5 @@ declare class SolveEngine {
|
|
|
456
456
|
private resolveDefinitionSource;
|
|
457
457
|
}
|
|
458
458
|
//#endregion
|
|
459
|
-
export { type ByteCacheRef, type ByteCacheStats, type ByteRefOutcome,
|
|
459
|
+
export { type ByteCacheRef, type ByteCacheStats, type ByteRefOutcome, type CachedClient, type ClientCache, type ClientCacheConfig, type DefinitionByteCache, type DefinitionRef, type FrameworkAgnosticResponse, type PipelineInput, type ResolvedServer, type ServerIdentity, type SolveCacheSingleFlight, type SolveCacheSingleFlightOptions, type SolveCacheStats, type SolveDefinition, type SolveEngineDefinitionSource, type SolveEngineLimits, type SolveEngineOptions, type SolveEngineSolveArgs, type SolveEngineStats, type SolveEnvelope, type SolveInput, type SolveOutcome, type SolvePhaseMetrics, type SolvePipelineArgs, isDefinitionRef };
|
|
460
460
|
//# sourceMappingURL=server.d.cts.map
|
package/dist/server.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ interface ResolvedServer {
|
|
|
18
18
|
/** Sent as the `RhinoComputeKey` header. */
|
|
19
19
|
apiKey?: string;
|
|
20
20
|
}
|
|
21
|
-
declare function serverIdentity(server: Pick<ResolvedServer, 'id'>): ServerIdentity;
|
|
21
|
+
export declare function serverIdentity(server: Pick<ResolvedServer, 'id'>): ServerIdentity;
|
|
22
22
|
interface CachedClient {
|
|
23
23
|
client: GrasshopperClient;
|
|
24
24
|
scheduler: SolveScheduler;
|
|
@@ -125,7 +125,7 @@ interface SolveCacheStats {
|
|
|
125
125
|
/** Entries dropped under size/byte pressure (not replacement or manual clears). */
|
|
126
126
|
evictions: number;
|
|
127
127
|
}
|
|
128
|
-
declare function createClientCache(config: ClientCacheConfig): ClientCache;
|
|
128
|
+
export declare function createClientCache(config: ClientCacheConfig): ClientCache;
|
|
129
129
|
//#endregion
|
|
130
130
|
//#region src/server/definition-byte-cache.d.ts
|
|
131
131
|
/**
|
|
@@ -179,12 +179,12 @@ interface DefinitionByteCache {
|
|
|
179
179
|
* @param maxBytes total retained-byte budget; `0` (or negative) disables caching
|
|
180
180
|
* entirely — every `load()` calls the loader and nothing is retained.
|
|
181
181
|
*/
|
|
182
|
-
declare function createDefinitionByteCache(maxBytes: number): DefinitionByteCache;
|
|
182
|
+
export declare function createDefinitionByteCache(maxBytes: number): DefinitionByteCache;
|
|
183
183
|
//#endregion
|
|
184
184
|
//#region src/server/solve-pipeline.d.ts
|
|
185
185
|
/** Compute-response contract version. Bump (and document the change) whenever the envelope's shape changes in a way a consumer could observe. */
|
|
186
|
-
declare const COMPUTE_CONTRACT_VERSION: 1;
|
|
187
|
-
declare const COMPUTE_VERSION_HEADER = "X-Selva-Compute-Version";
|
|
186
|
+
export declare const COMPUTE_CONTRACT_VERSION: 1;
|
|
187
|
+
export declare const COMPUTE_VERSION_HEADER = "X-Selva-Compute-Version";
|
|
188
188
|
type PipelineInput = SchemaInput & {
|
|
189
189
|
minimum?: number;
|
|
190
190
|
maximum?: number;
|
|
@@ -287,8 +287,8 @@ type SolveOutcome = {
|
|
|
287
287
|
error: unknown;
|
|
288
288
|
};
|
|
289
289
|
/** The transformed input tree exactly as handed to the scheduler; `runSolvePipeline` calls this itself unless the caller supplies {@link SolvePipelineArgs.inputTree}. */
|
|
290
|
-
declare function buildSolveInputTree(inputs: PipelineInput[], values: Record<string, unknown>): DataTree[];
|
|
291
|
-
declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome>;
|
|
290
|
+
export declare function buildSolveInputTree(inputs: PipelineInput[], values: Record<string, unknown>): DataTree[];
|
|
291
|
+
export declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome>;
|
|
292
292
|
/**
|
|
293
293
|
* Re-key a coalesced envelope to a single waiter's `Accept-Encoding`.
|
|
294
294
|
*
|
|
@@ -304,7 +304,7 @@ declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome
|
|
|
304
304
|
* a non-gzip waiter is gunzipped back to JSON. The reverse (plain JSON to a
|
|
305
305
|
* gzip-capable waiter) is left uncompressed — correct, just not maximally small.
|
|
306
306
|
*/
|
|
307
|
-
declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding: string): {
|
|
307
|
+
export declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding: string): {
|
|
308
308
|
body: string | Uint8Array;
|
|
309
309
|
headers: Record<string, string>;
|
|
310
310
|
};
|
|
@@ -314,7 +314,7 @@ declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding
|
|
|
314
314
|
* `SchemaInput` has no `values`/`acceptedFormats` (those live on the layout item's config), so
|
|
315
315
|
* valueList/file inputs here carry just the selected value with no option list.
|
|
316
316
|
*/
|
|
317
|
-
declare function transformInputParameter(input: SchemaInput & {
|
|
317
|
+
export declare function transformInputParameter(input: SchemaInput & {
|
|
318
318
|
minimum?: number;
|
|
319
319
|
maximum?: number;
|
|
320
320
|
stepSize?: number;
|
|
@@ -355,7 +355,7 @@ interface SolveCacheSingleFlight {
|
|
|
355
355
|
interface SolveCacheSingleFlightOptions {
|
|
356
356
|
onJoin?: (key: string) => void;
|
|
357
357
|
}
|
|
358
|
-
declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
|
|
358
|
+
export declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
|
|
359
359
|
//#endregion
|
|
360
360
|
//#region src/server/solve-engine.d.ts
|
|
361
361
|
/**
|
|
@@ -427,7 +427,7 @@ interface SolveEngineStats {
|
|
|
427
427
|
inFlight: number;
|
|
428
428
|
};
|
|
429
429
|
}
|
|
430
|
-
declare class SolveEngine {
|
|
430
|
+
export declare class SolveEngine {
|
|
431
431
|
private readonly limits;
|
|
432
432
|
private readonly clientCache;
|
|
433
433
|
private readonly byteCache;
|
|
@@ -456,5 +456,5 @@ declare class SolveEngine {
|
|
|
456
456
|
private resolveDefinitionSource;
|
|
457
457
|
}
|
|
458
458
|
//#endregion
|
|
459
|
-
export { type ByteCacheRef, type ByteCacheStats, type ByteRefOutcome,
|
|
459
|
+
export { type ByteCacheRef, type ByteCacheStats, type ByteRefOutcome, type CachedClient, type ClientCache, type ClientCacheConfig, type DefinitionByteCache, type DefinitionRef, type FrameworkAgnosticResponse, type PipelineInput, type ResolvedServer, type ServerIdentity, type SolveCacheSingleFlight, type SolveCacheSingleFlightOptions, type SolveCacheStats, type SolveDefinition, type SolveEngineDefinitionSource, type SolveEngineLimits, type SolveEngineOptions, type SolveEngineSolveArgs, type SolveEngineStats, type SolveEnvelope, type SolveInput, type SolveOutcome, type SolvePhaseMetrics, type SolvePipelineArgs, isDefinitionRef };
|
|
460
460
|
//# sourceMappingURL=server.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selvajs/solve",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "One owner for the Selva solve flow — input change to solve result, on both sides of the wire, with no transport and no UI",
|
|
5
5
|
"author": "VektorNode",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,21 +62,21 @@
|
|
|
62
62
|
],
|
|
63
63
|
"sideEffects": false,
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@selvajs/compute": "^4.1.
|
|
66
|
-
"@selvajs/
|
|
67
|
-
"@selvajs/
|
|
65
|
+
"@selvajs/compute": "^4.1.3",
|
|
66
|
+
"@selvajs/schemas": "5.0.2",
|
|
67
|
+
"@selvajs/platform": "^0.20.1"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@eslint/js": "^10.0.1",
|
|
71
|
-
"@types/node": "^26.
|
|
71
|
+
"@types/node": "^26.5.1",
|
|
72
72
|
"esbuild": "^0.28.2",
|
|
73
|
-
"eslint": "^10.
|
|
73
|
+
"eslint": "^10.10.0",
|
|
74
74
|
"eslint-config-prettier": "^10.1.8",
|
|
75
|
-
"globals": "^17.
|
|
75
|
+
"globals": "^17.12.0",
|
|
76
76
|
"prettier": "^3.9.6",
|
|
77
|
-
"tsdown": "^0.
|
|
77
|
+
"tsdown": "^0.23.0",
|
|
78
78
|
"typescript": "~6.0.3",
|
|
79
|
-
"vite": "^8.
|
|
79
|
+
"vite": "^8.3.0",
|
|
80
80
|
"vitest": "^4.1.11",
|
|
81
81
|
"@selvajs/config": "0.0.4"
|
|
82
82
|
},
|