@selvajs/solve 0.2.0-beta.4 → 1.0.0-beta.7

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 CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _schemas = require('@selvajs/schemas');var H="external";function k(e,r){return`${H}:${e}:${r}`}function X(e){let{scopeKey:r,inputId:s,value:n}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.setItem(k(r,s),JSON.stringify(n))}function R(e){let{scopeKey:r,inputId:s}=e;if(!r||!s||typeof sessionStorage>"u")return;let n=sessionStorage.getItem(k(r,s));if(n!==null)try{return JSON.parse(n)}catch (e2){return}}function Y(e){let{scopeKey:r,inputId:s}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.removeItem(k(r,s))}function I(e){return _schemas.getInputItems.call(void 0, e).filter(r=>_optionalChain([r, 'access', _2 => _2.source, 'optionalAccess', _3 => _3.kind])==="client").map(r=>({paramId:r.paramId,displayName:_nullishCoalesce(r.displayName, () => (r.paramId))}))}function b(e,r,s){let n=new Set(I(e).map(t=>t.paramId)),l={};for(let t of e.inputs){if(n.has(t.id)){let i=s({scopeKey:r,inputId:t.id});i!==void 0&&(l[t.id]=i);continue}l[t.id]=_nullishCoalesce(t.default, () => (_schemas.getDefaultValue.call(void 0, t.paramType)))}for(let t of e.outputs)l[t.id]=null;return l}function w(e){let r=e===!1;return{hasPendingChanges:r,hasNeverSolved:r}}function C(e,r,s,n){return e.values[r]=s,n===!1?(e.pendingValues[r]=s,e.hasPendingChanges=!0,{state:e,shouldSolve:!1}):{state:e,shouldSolve:!0}}function P(e,r){if(!_optionalChain([e, 'optionalAccess', _4 => _4.inputs]))return r;let s={};for(let n of e.inputs)n.id in r&&(s[n.id]=r[n.id]);return s}function V(e,r){return e.error="",e.computeErrors=_nullishCoalesce(r.errors, () => ([])),e.computeWarnings=_nullishCoalesce(r.warnings, () => ([])),e.meshes=_nullishCoalesce(r.meshes, () => ([])),Object.assign(e.values,r.outputs),e.pendingValues={},e.hasPendingChanges=!1,e.hasNeverSolved=!1,e}function Z(e){let r=e.schema,s=w(_optionalChain([r, 'optionalAccess', _5 => _5.instanceSolve])),n={values:b(r,e.scopeKey,R),error:"",computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:s.hasPendingChanges,hasNeverSolved:s.hasNeverSolved},l=new Set,t=()=>l.forEach(o=>o());function i(){e.driver.solve({...P(r,n.values)}),t()}return{get values(){return n.values},get error(){return n.error},get computeErrors(){return n.computeErrors},get computeWarnings(){return n.computeWarnings},get meshes(){return n.meshes},get hasPendingChanges(){return n.hasPendingChanges},get hasNeverSolved(){return n.hasNeverSolved},get isSolving(){return e.driver.isSolving},setValue(o,a,u=!1){let{shouldSolve:c}=C(n,o,a,_optionalChain([r, 'optionalAccess', _6 => _6.instanceSolve]));c||u?(u&&!c&&(n.pendingValues={},n.hasPendingChanges=!1),i()):t()},solve(){i()},loadValues(o){Object.assign(n.values,o),_optionalChain([r, 'optionalAccess', _7 => _7.instanceSolve])!==!1?i():(n.hasPendingChanges=!0,t())},rebuild(o,a){r=o,_optionalChain([e, 'access', _8 => _8.driver, 'access', _9 => _9.clearCache, 'optionalCall', _10 => _10()]),n.meshes=[],n.error="",n.computeErrors=[],n.computeWarnings=[],n.pendingValues={},n.values=b(o,a,R);let u=w(_optionalChain([o, 'optionalAccess', _11 => _11.instanceSolve]));n.hasPendingChanges=u.hasPendingChanges,n.hasNeverSolved=u.hasNeverSolved,o&&Object.keys(n.values).length>0&&o.instanceSolve!==!1?i():t()},report(o){V(n,o),t()},reportError(o){n.error=o,t()},subscribe(o){return l.add(o),()=>l.delete(o)},notify:t}}function F(e,r={}){let{timeout:s=6e4,onChange:n}=r,l=!1,t=null,i=null;function o(m){l!==m&&(l=m,_optionalChain([n, 'optionalCall', _12 => _12()]))}function a(){_optionalChain([i, 'optionalAccess', _13 => _13.abort, 'call', _14 => _14()]),i=null}async function u(m){a(),i=new AbortController;let{signal:S}=i,y=setTimeout(()=>{console.warn(`[Solve/throttle] run exceeded ${s}ms \u2014 aborting`),_optionalChain([i, 'optionalAccess', _15 => _15.abort, 'call', _16 => _16()])},s);o(!0);try{await e(m,S)}catch(f){(!(f instanceof Error)||f.name!=="AbortError"&&f.name!=="TimeoutError")&&console.error("[Solve/throttle] unhandled error in run:",f)}finally{if(clearTimeout(y),o(!1),i=null,t!==null){let f=t;t=null,u(f)}}}function c(m){l?(t!==null&&console.debug("[Solve/throttle] superseded pending run (latest-wins)"),t=m):u(m)}function v(){t=null,a()}return{trigger:c,get isRunning(){return l},get hasPending(){return t!==null},cancel:v}}function O(e){return $(e)}function $(e){if(e===null||typeof e!="object")return _nullishCoalesce(JSON.stringify(e), () => ("null"));if(Array.isArray(e))return`[${e.map($).join(",")}]`;let r=e;return`{${Object.keys(r).sort().map(n=>`${JSON.stringify(n)}:${$(r[n])}`).join(",")}}`}function j(e={}){let{max:r=16,meshPolicy:s}=e,n=new Map;function l(o){_optionalChain([o, 'access', _17 => _17.meshes, 'optionalAccess', _18 => _18.length])&&_optionalChain([s, 'optionalAccess', _19 => _19.release, 'call', _20 => _20(o.meshes)])}function t(o){return!s||!_optionalChain([o, 'access', _21 => _21.meshes, 'optionalAccess', _22 => _22.length])?o:{...o,meshes:s.clone(o.meshes)}}function i(o){let a=n.get(o);a!==void 0&&(n.delete(o),l(a))}return{get(o){let a=O(o),u=n.get(a);if(u!==void 0)return n.delete(a),n.set(a,u),console.info(`[Solve/memo] HIT \u2014 served from client memo (${n.size}/${r})`),t(u)},set(o,a){let u=O(o);for(i(u),n.set(u,t(a));n.size>r;){let c=n.keys().next().value;if(c===void 0)break;i(c),console.info(`[Solve/memo] evicted LRU entry (cap ${r})`)}},clear(){n.size>0&&console.info(`[Solve/memo] cleared ${n.size} entries (definition changed)`),n.forEach(l),n.clear()}}}function ee(e,r,s={}){let{meshPolicy:n,...l}=s,t=j({meshPolicy:n}),i=F(async(o,a)=>{let u=t.get(o);if(u!==void 0){if(a.aborted)return;r().report(u);return}try{let c=await e(o,a);if(a.aborted){console.debug("[Solve/driver] solve completed after abort \u2014 result discarded");return}let v={...c,values:o};t.set(o,v),r().report(v)}catch(c){if(a.aborted){console.debug("[Solve/driver] solve aborted (superseded, cancelled, or timed out)");return}console.warn("[Solve/driver] solve failed:",c),r().reportError(c instanceof Error?c.message:String(c))}},l);return{solve(o){i.trigger(o)},cancel(){i.cancel()},get isSolving(){return i.isRunning},clearCache(){t.clear()}}}var _compute = require('@selvajs/compute');function re(e){let r={};if(!e)return r;for(let s of e.split(",")){let[n,...l]=s.split(";").map(o=>o.trim()),t=l.find(o=>o.startsWith("dur="));if(!n||!t)continue;let i=Number(t.slice(4));Number.isFinite(i)&&(r[n]=i)}return r}var oe=e=>{throw new Error(`Rate limit reached. Try again in ${Math.ceil(e)}s.`)},te=()=>{throw new Error("Your session has expired. Sign in again in a new tab, then re-run \u2014 your inputs are preserved.")};function se(e){let r=_nullishCoalesce(e.debug, () => (!1)),s=(...i)=>{r&&console.info(...i)},n=null;function l(){if(!e.meshes)throw new Error("createComputeFetchSolveFn: meshes option not configured.");return n||(n=e.meshes.loadRhino()),n}let t=0;return async(i,o)=>{let a=t-Date.now();if(a>0)throw new Error(`Rate limit reached. Try again in ${Math.ceil(a/1e3)}s.`);let u=performance.now(),c=e.inputs(),v=e.outputs(),m=_optionalChain([e, 'access', _23 => _23.channel, 'optionalCall', _24 => _24()]),S=_optionalChain([e, 'access', _25 => _25.versionId, 'optionalCall', _26 => _26()]),y=JSON.stringify({inputs:c,values:i,definitionUrl:e.definitionUrl(),...S?{versionId:S}:m==="draft"?{channel:"draft"}:{}}),f=JSON.stringify(i).length,p;try{p=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:y,signal:o})}catch(d){if(o.aborted)return s("[Compute] solve aborted during fetch \u2014 empty result discarded"),{outputs:{}};throw new Error("Request blocked \u2014 your session may have expired. Reload the page to sign in again.",{cause:d})}if(o.aborted)return s("[Compute] solve aborted after headers \u2014 empty result discarded"),{outputs:{}};let J=p.redirected&&new URL(p.url).pathname==="/login";if((p.status===401||J)&&(_nullishCoalesce(e.onSessionExpired, () => (te)))(),!p.ok){if(p.status===503)throw new Error("Compute server is offline or unreachable. Please try again later.");let d=await p.text().catch(()=>""),h={};try{h=JSON.parse(d)}catch (e3){d&&console.warn(`[Compute] non-JSON error body (HTTP ${p.status}):`,d.slice(0,300))}if(p.status===429){let x=Number(p.headers.get("Retry-After"))||Number(h.retryAfter)||5;t=Date.now()+x*1e3,(_nullishCoalesce(e.onRateLimited, () => (oe)))(x)}throw new Error(h.message||`Compute error (HTTP ${p.status})`)}let A=performance.now()-u,B=performance.now(),N=await p.text();if(o.aborted)return s("[Compute] solve aborted mid-download \u2014 empty result discarded"),{outputs:{}};let T=performance.now()-B,U=N.length,g;try{g=JSON.parse(N)}catch(d){throw new Error("Received an invalid response from the server. Reload the page and try again.",{cause:d})}let M=new (0, _compute.GrasshopperResponseProcessor)(g,!1),z=e.meshes?await e.meshes.extract(M.response,{debug:r,rhino:await l()}):[],D={};for(let d of v){let h=M.getValue({byId:d.id},{parseValues:!0});D[d.id]=_nullishCoalesce(h, () => ((d.nickname?M.getValue({byName:d.nickname},{parseValues:!0}):void 0)))}if(r){let d=re(p.headers.get("Server-Timing")),h=_nullishCoalesce(d.total, () => (0)),x=Math.max(0,A-h),E=U/(1024*1024),q=E>=1?`${E.toFixed(2)} MB`:`${(U/1024).toFixed(0)} KB`,L=T>0?(E/(T/1e3)).toFixed(1):"\u221E",K=_optionalChain([performance, 'access', _27 => _27.memory, 'optionalAccess', _28 => _28.usedJSHeapSize]),W=K!==void 0?` | heap=${(K/(1024*1024)).toFixed(0)} MB`:"";if(s(`[Compute] req=${(y.length/1024).toFixed(0)}KB (values ${(f/1024).toFixed(0)}KB) | ttfb=${A.toFixed(0)}ms (network\u2248${x.toFixed(0)} + server ${h.toFixed(0)}) | download=${T.toFixed(0)}ms (${q} @ ${L} MB/s)${W}`),d.selva_cache!==void 0){let _=d.selva_cache?"HIT \u2014 served without calling compute":"miss \u2014 solved on Rhino.Compute";s(`[Compute] \u2514\u2500 cache: ${_}`)}}return{outputs:D,meshes:z,errors:_nullishCoalesce(g.errors, () => ([])),warnings:_nullishCoalesce(g.warnings, () => ([])),source:g}}}exports.applySolveResult = V; exports.applyValueChange = C; exports.buildInitialValues = b; exports.clearExternalValue = Y; exports.createAsyncThrottle = F; exports.createComputeFetchSolveFn = se; exports.createRequestResponseDriver = ee; exports.createSolveMemo = j; exports.createSolveSession = Z; exports.getExternalInputs = I; exports.makeInitialFlags = w; exports.pickInputValues = P; exports.readExternalValue = R; exports.stableInputKey = O; exports.writeExternalValue = X;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _schemas = require('@selvajs/schemas');var H="external";function k(e,r){return`${H}:${e}:${r}`}function L(e){let{scopeKey:r,inputId:s,value:n}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.setItem(k(r,s),JSON.stringify(n))}function y(e){let{scopeKey:r,inputId:s}=e;if(!r||!s||typeof sessionStorage>"u")return;let n=sessionStorage.getItem(k(r,s));if(n!==null)try{return JSON.parse(n)}catch (e2){return}}function _(e){let{scopeKey:r,inputId:s}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.removeItem(k(r,s))}function E(e){return _schemas.getInputItems.call(void 0, e).filter(r=>_optionalChain([r, 'access', _2 => _2.source, 'optionalAccess', _3 => _3.kind])==="client").map(r=>({paramId:r.paramId,displayName:_nullishCoalesce(r.displayName, () => (r.paramId))}))}function R(e,r,s){let n=new Set(E(e).map(t=>t.paramId)),a={};for(let t of e.inputs){if(n.has(t.id)){let i=s({scopeKey:r,inputId:t.id});i!==void 0&&(a[t.id]=i);continue}a[t.id]=_nullishCoalesce(t.default, () => (_schemas.getDefaultValue.call(void 0, t.paramType)))}for(let t of e.outputs)a[t.id]=null;return a}function x(e){let r=e===!1;return{hasPendingChanges:r,hasNeverSolved:r}}function I(e,r,s,n){return e.values[r]=s,n===!1?(e.pendingValues[r]=s,e.hasPendingChanges=!0,{state:e,shouldSolve:!1}):{state:e,shouldSolve:!0}}function C(e,r){if(!_optionalChain([e, 'optionalAccess', _4 => _4.inputs]))return r;let s={};for(let n of e.inputs)n.id in r&&(s[n.id]=r[n.id]);return s}function V(e,r){e.error="",e.computeErrors=_nullishCoalesce(r.errors, () => ([])),e.computeWarnings=_nullishCoalesce(r.warnings, () => ([])),e.meshes=_nullishCoalesce(r.meshes, () => ([]));let{meshes:s,...n}=r;return e.lastResult=n,Object.assign(e.values,r.outputs),e.pendingValues={},e.hasPendingChanges=!1,e.hasNeverSolved=!1,e}function Y(e){let r=e.schema,s=x(_optionalChain([r, 'optionalAccess', _5 => _5.instanceSolve])),n={values:R(r,e.scopeKey,y),error:"",computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:s.hasPendingChanges,hasNeverSolved:s.hasNeverSolved,lastResult:null},a=new Set,t=()=>a.forEach(o=>o());function i(){e.driver.solve({...C(r,n.values)}),t()}return{get values(){return n.values},get error(){return n.error},get computeErrors(){return n.computeErrors},get computeWarnings(){return n.computeWarnings},get meshes(){return n.meshes},get lastResult(){return n.lastResult},get hasPendingChanges(){return n.hasPendingChanges},get hasNeverSolved(){return n.hasNeverSolved},get isSolving(){return e.driver.isSolving},setValue(o,l,u=!1){let{shouldSolve:f}=I(n,o,l,_optionalChain([r, 'optionalAccess', _6 => _6.instanceSolve]));f||u?(u&&!f&&(n.pendingValues={},n.hasPendingChanges=!1),i()):t()},solve(){i()},loadValues(o){Object.assign(n.values,o),_optionalChain([r, 'optionalAccess', _7 => _7.instanceSolve])!==!1?i():(n.hasPendingChanges=!0,t())},rebuild(o,l){r=o,_optionalChain([e, 'access', _8 => _8.driver, 'access', _9 => _9.clearCache, 'optionalCall', _10 => _10()]),n.meshes=[],n.lastResult=null,n.error="",n.computeErrors=[],n.computeWarnings=[],n.pendingValues={},n.values=R(o,l,y);let u=x(_optionalChain([o, 'optionalAccess', _11 => _11.instanceSolve]));n.hasPendingChanges=u.hasPendingChanges,n.hasNeverSolved=u.hasNeverSolved,o&&Object.keys(n.values).length>0&&o.instanceSolve!==!1?i():t()},report(o){V(n,o),t()},reportError(o){n.error=o,t()},subscribe(o){return a.add(o),()=>a.delete(o)},notify:t}}function P(e,r){let{runDeadlineMs:s,onChange:n}=r,a=!1,t=null,i=null;function o(p){a!==p&&(a=p,_optionalChain([n, 'optionalCall', _12 => _12()]))}function l(){_optionalChain([i, 'optionalAccess', _13 => _13.abort, 'call', _14 => _14()]),i=null}async function u(p){l(),i=new AbortController;let{signal:b}=i,d=setTimeout(()=>{console.warn(`[Solve/throttle] run exceeded ${s}ms \u2014 aborting`),_optionalChain([i, 'optionalAccess', _15 => _15.abort, 'call', _16 => _16()])},s);o(!0);try{await e(p,b)}catch(v){(!(v instanceof Error)||v.name!=="AbortError"&&v.name!=="TimeoutError")&&console.error("[Solve/throttle] unhandled error in run:",v)}finally{if(clearTimeout(d),o(!1),i=null,t!==null){let v=t;t=null,u(v)}}}function f(p){a?(t!==null&&console.debug("[Solve/throttle] superseded pending run (latest-wins)"),t=p):u(p)}function m(){t=null,l()}return{trigger:f,get isRunning(){return a},get hasPending(){return t!==null},cancel:m}}function F(e){return O(e)}function O(e){if(e===null||typeof e!="object")return _nullishCoalesce(JSON.stringify(e), () => ("null"));if(Array.isArray(e))return`[${e.map(O).join(",")}]`;let r=e;return`{${Object.keys(r).sort().map(n=>`${JSON.stringify(n)}:${O(r[n])}`).join(",")}}`}function $(e={}){let{max:r=16,meshPolicy:s}=e,n=new Map;function a(o){_optionalChain([o, 'access', _17 => _17.meshes, 'optionalAccess', _18 => _18.length])&&_optionalChain([s, 'optionalAccess', _19 => _19.release, 'call', _20 => _20(o.meshes)])}function t(o){return!s||!_optionalChain([o, 'access', _21 => _21.meshes, 'optionalAccess', _22 => _22.length])?o:{...o,meshes:s.clone(o.meshes)}}function i(o){let l=n.get(o);l!==void 0&&(n.delete(o),a(l))}return{get(o){let l=F(o),u=n.get(l);if(u!==void 0)return n.delete(l),n.set(l,u),console.info(`[Solve/memo] HIT \u2014 served from client memo (${n.size}/${r})`),t(u)},set(o,l){let u=F(o);for(i(u),n.set(u,t(l));n.size>r;){let f=n.keys().next().value;if(f===void 0)break;i(f),console.info(`[Solve/memo] evicted LRU entry (cap ${r})`)}},clear(){n.size>0&&console.info(`[Solve/memo] cleared ${n.size} entries (definition changed)`),n.forEach(a),n.clear()}}}function Q(e,r,s){let{meshPolicy:n,solveDeadlineMs:a,onChange:t}=s,i=$({meshPolicy:n}),o=P(async(l,u)=>{let f=i.get(l);if(f!==void 0){if(u.aborted)return;r().report(f);return}try{let m=await e(l,u);if(u.aborted){console.debug("[Solve/driver] solve completed after abort \u2014 result discarded");return}let p={...m,values:l};i.set(l,p),r().report(p)}catch(m){if(u.aborted){console.debug("[Solve/driver] solve aborted (superseded, cancelled, or timed out)");return}console.warn("[Solve/driver] solve failed:",m),r().reportError(m instanceof Error?m.message:String(m))}},{runDeadlineMs:a,onChange:t});return{solve(l){o.trigger(l)},cancel(){o.cancel()},get isSolving(){return o.isRunning},clearCache(){i.clear()}}}var _grasshopper = require('@selvajs/compute/grasshopper');function ee(e){let r={};if(!e)return r;for(let s of e.split(",")){let[n,...a]=s.split(";").map(o=>o.trim()),t=a.find(o=>o.startsWith("dur="));if(!n||!t)continue;let i=Number(t.slice(4));Number.isFinite(i)&&(r[n]=i)}return r}var ne=e=>{throw new Error(`Rate limit reached. Try again in ${Math.ceil(e)}s.`)},re=()=>{throw new Error("Your session has expired. Sign in again in a new tab, then re-run \u2014 your inputs are preserved.")};function oe(e){let r=_nullishCoalesce(e.debug, () => (!1)),s=(...a)=>{r&&console.info(...a)},n=0;return async(a,t)=>{let i=n-Date.now();if(i>0)throw new Error(`Rate limit reached. Try again in ${Math.ceil(i/1e3)}s.`);let o=performance.now(),l=e.inputs(),u=e.outputs(),f=_optionalChain([e, 'access', _23 => _23.channel, 'optionalCall', _24 => _24()]),m=_optionalChain([e, 'access', _25 => _25.versionId, 'optionalCall', _26 => _26()]),p=JSON.stringify({inputs:l,values:a,definitionUrl:e.definitionUrl(),...m?{versionId:m}:f==="draft"?{channel:"draft"}:{}}),b=JSON.stringify(a).length,d;try{d=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:p,signal:t})}catch(c){if(t.aborted)return s("[Compute] solve aborted during fetch \u2014 empty result discarded"),{outputs:{}};throw new Error("Request blocked \u2014 your session may have expired. Reload the page to sign in again.",{cause:c})}if(t.aborted)return s("[Compute] solve aborted after headers \u2014 empty result discarded"),{outputs:{}};let v=d.redirected&&new URL(d.url).pathname==="/login";if((d.status===401||v)&&(_nullishCoalesce(e.onSessionExpired, () => (re)))(),!d.ok){if(d.status===503)throw new Error("Compute server is offline or unreachable. Please try again later.");let c=await d.text().catch(()=>""),h={};try{h=JSON.parse(c)}catch (e3){c&&console.warn(`[Compute] non-JSON error body (HTTP ${d.status}):`,c.slice(0,300))}if(d.status===429){let S=Number(d.headers.get("Retry-After"))||Number(h.retryAfter)||5;n=Date.now()+S*1e3,(_nullishCoalesce(e.onRateLimited, () => (ne)))(S)}throw new Error(h.message||`Compute error (HTTP ${d.status})`)}let j=performance.now()-o,K=performance.now(),N=await d.text();if(t.aborted)return s("[Compute] solve aborted mid-download \u2014 empty result discarded"),{outputs:{}};let w=performance.now()-K,A=N.length,g;try{g=JSON.parse(N)}catch(c){throw new Error("Received an invalid response from the server. Reload the page and try again.",{cause:c})}let T=new (0, _grasshopper.GrasshopperResponseProcessor)(g,!1),J=e.meshes?await e.meshes.extract(T.response,{debug:r}):[],D={};for(let c of u){let h=T.getValue({byId:c.id},{parseValues:!0});D[c.id]=_nullishCoalesce(h, () => ((c.nickname?T.getValue({byName:c.nickname},{parseValues:!0}):void 0)))}if(r){let c=ee(d.headers.get("Server-Timing")),h=_nullishCoalesce(c.total, () => (0)),S=Math.max(0,j-h),M=A/(1024*1024),B=M>=1?`${M.toFixed(2)} MB`:`${(A/1024).toFixed(0)} KB`,z=w>0?(M/(w/1e3)).toFixed(1):"\u221E",U=_optionalChain([performance, 'access', _27 => _27.memory, 'optionalAccess', _28 => _28.usedJSHeapSize]),q=U!==void 0?` | heap=${(U/(1024*1024)).toFixed(0)} MB`:"";if(s(`[Compute] req=${(p.length/1024).toFixed(0)}KB (values ${(b/1024).toFixed(0)}KB) | ttfb=${j.toFixed(0)}ms (network\u2248${S.toFixed(0)} + server ${h.toFixed(0)}) | download=${w.toFixed(0)}ms (${B} @ ${z} MB/s)${q}`),c.selva_cache!==void 0){let W=c.selva_cache?"HIT \u2014 served without calling compute":"miss \u2014 solved on Rhino.Compute";s(`[Compute] \u2514\u2500 cache: ${W}`)}}return{outputs:D,meshes:J,errors:_nullishCoalesce(g.errors, () => ([])),warnings:_nullishCoalesce(g.warnings, () => ([])),source:g}}}exports.applySolveResult = V; exports.applyValueChange = I; exports.buildInitialValues = R; exports.clearExternalValue = _; exports.createAsyncThrottle = P; exports.createComputeFetchSolveFn = oe; exports.createRequestResponseDriver = Q; exports.createSolveMemo = $; exports.createSolveSession = Y; exports.getExternalInputs = E; exports.makeInitialFlags = x; exports.pickInputValues = C; exports.readExternalValue = y; exports.stableInputKey = F; exports.writeExternalValue = L;
2
2
  //# sourceMappingURL=client.cjs.map
package/dist/client.d.cts CHANGED
@@ -1,12 +1,21 @@
1
1
  import { UISchema } from '@selvajs/schemas';
2
- import { a as SolveResult, S as SolveFn } from './solve-fn-0hPOtZVD.cjs';
3
- import { GrasshopperComputeResponse } from '@selvajs/compute';
2
+ import { a as SolveResult, S as SolveFn } from './solve-fn-DrRgaPZ5.cjs';
3
+ import { GrasshopperComputeResponse } from '@selvajs/compute/grasshopper';
4
4
 
5
5
  /**
6
6
  * Transport behind a solve session. `solve()` never returns outputs directly — results
7
7
  * go through the session's `report()` instead, so a push transport (e.g. a WebSocket
8
8
  * streaming mesh frames on its own schedule) can satisfy the same interface as
9
9
  * request/response HTTP.
10
+ *
11
+ * **Stamping `SolveResult.values` is the driver's job, and only some drivers can do it.** A
12
+ * driver that owns a request/response pair must stamp the input set it solved onto the
13
+ * result it reports — the session retains that pair, and a consumer committing what is on
14
+ * screen relies on it being atomic. A push driver that cannot attribute an incoming frame to
15
+ * a request it made must leave `values` absent rather than attach the last set it sent:
16
+ * frames that arrive unsolicited (a replay on connect, a recompute triggered outside the web
17
+ * UI) would otherwise be stamped with an unrelated input set, which is precisely the stale
18
+ * pairing the field exists to prevent.
10
19
  */
11
20
  interface SolveDriver {
12
21
  solve(values: Record<string, unknown>): void;
@@ -26,12 +35,90 @@ interface SolveReporter<TMesh = unknown> {
26
35
  reportError(message: string): void;
27
36
  }
28
37
 
38
+ interface ExternalValueRef {
39
+ scopeKey: string;
40
+ inputId: string;
41
+ }
42
+ declare function writeExternalValue(args: ExternalValueRef & {
43
+ value: unknown;
44
+ }): void;
45
+ declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
46
+ declare function clearExternalValue(ref: ExternalValueRef): void;
47
+ interface ExternalInput {
48
+ paramId: string;
49
+ displayName: string;
50
+ }
51
+ declare function getExternalInputs(schema: UISchema): ExternalInput[];
52
+
53
+ /**
54
+ * The slice of a reported result the session keeps addressable, so a host can commit exactly
55
+ * what the viewer is showing — `source` and `values` included, already memo-correct.
56
+ *
57
+ * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what
58
+ * it renders on the next scene update, which is why the memo carries a `MeshPolicy`
59
+ * clone/release contract at all. Retaining them here would hand out disposed instances after
60
+ * the next solve, with no policy governing them and no owner. Everything left is inert JSON;
61
+ * live meshes stay on `session.meshes`.
62
+ */
63
+ type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;
64
+ interface SolveSessionState {
65
+ values: Record<string, unknown>;
66
+ error: string;
67
+ computeErrors: string[];
68
+ computeWarnings: string[];
69
+ meshes: unknown[];
70
+ /** Values changed since the last solve, in manual (instanceSolve === false) mode. */
71
+ pendingValues: Record<string, unknown>;
72
+ hasPendingChanges: boolean;
73
+ hasNeverSolved: boolean;
74
+ /**
75
+ * The last result reported, minus its meshes. Null before the first solve.
76
+ *
77
+ * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a
78
+ * value change and the next report it describes the *previous* input set. That is what a
79
+ * commit gate wants — what is on screen, not what is pending — so `lastResult.values`
80
+ * disagreeing with `state.values` mid-scrub is the feature, not a bug.
81
+ */
82
+ lastResult: RetainedSolveResult | null;
83
+ }
84
+ type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
85
+ /**
86
+ * Client-sourced inputs are hydrated from `read` and left absent when no stored value
87
+ * exists, so the missing-inputs panel can detect them. Other inputs fall back to their
88
+ * declared default, then the paramType default.
89
+ */
90
+ declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
91
+ /** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
92
+ declare function makeInitialFlags(instanceSolve: boolean | undefined): {
93
+ hasPendingChanges: boolean;
94
+ hasNeverSolved: boolean;
95
+ };
96
+ /** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
97
+ declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
98
+ state: SolveSessionState;
99
+ shouldSolve: boolean;
100
+ };
101
+ /**
102
+ * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value
103
+ * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB
104
+ * options-list output showed this can re-upload a multi-MB payload no backend reads.
105
+ */
106
+ declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
107
+ declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
108
+
29
109
  interface SolveSession {
30
110
  readonly values: Record<string, unknown>;
31
111
  readonly error: string;
32
112
  readonly computeErrors: string[];
33
113
  readonly computeWarnings: string[];
34
114
  readonly meshes: unknown[];
115
+ /**
116
+ * The last reported result without its meshes — what the viewer is currently showing,
117
+ * including the `source`/`values` pair a commit path needs. Null before the first solve,
118
+ * and again after `rebuild()`. Survives `reportError`: a failed solve leaves the last good
119
+ * geometry on screen, so a host committing what it sees should still see what produced it.
120
+ */
121
+ readonly lastResult: RetainedSolveResult | null;
35
122
  readonly hasPendingChanges: boolean;
36
123
  readonly hasNeverSolved: boolean;
37
124
  readonly isSolving: boolean;
@@ -84,7 +171,8 @@ interface SolveMemo<TMesh = unknown> {
84
171
  declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
85
172
 
86
173
  interface RequestResponseDriverOptions<TMesh> {
87
- timeout?: number;
174
+ /** How long one solve may take before the client aborts it (ms). Pass the server's `COMPUTE_SOLVE_DEADLINE_MS` so the two agree. */
175
+ solveDeadlineMs: number;
88
176
  onChange?: () => void;
89
177
  /** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */
90
178
  meshPolicy?: MeshPolicy<TMesh>;
@@ -93,58 +181,7 @@ interface RequestResponseDriverOptions<TMesh> {
93
181
  * `getReporter` is lazy (`() => session`) because the session and driver reference each
94
182
  * other — the host builds the driver first, then the session, then can hand this back.
95
183
  */
96
- declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options?: RequestResponseDriverOptions<TMesh>): SolveDriver;
97
-
98
- interface ExternalValueRef {
99
- scopeKey: string;
100
- inputId: string;
101
- }
102
- declare function writeExternalValue(args: ExternalValueRef & {
103
- value: unknown;
104
- }): void;
105
- declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
106
- declare function clearExternalValue(ref: ExternalValueRef): void;
107
- interface ExternalInput {
108
- paramId: string;
109
- displayName: string;
110
- }
111
- declare function getExternalInputs(schema: UISchema): ExternalInput[];
112
-
113
- interface SolveSessionState {
114
- values: Record<string, unknown>;
115
- error: string;
116
- computeErrors: string[];
117
- computeWarnings: string[];
118
- meshes: unknown[];
119
- /** Values changed since the last solve, in manual (instanceSolve === false) mode. */
120
- pendingValues: Record<string, unknown>;
121
- hasPendingChanges: boolean;
122
- hasNeverSolved: boolean;
123
- }
124
- type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
125
- /**
126
- * Client-sourced inputs are hydrated from `read` and left absent when no stored value
127
- * exists, so the missing-inputs panel can detect them. Other inputs fall back to their
128
- * declared default, then the paramType default.
129
- */
130
- declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
131
- /** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
132
- declare function makeInitialFlags(instanceSolve: boolean | undefined): {
133
- hasPendingChanges: boolean;
134
- hasNeverSolved: boolean;
135
- };
136
- /** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
137
- declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
138
- state: SolveSessionState;
139
- shouldSolve: boolean;
140
- };
141
- /**
142
- * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value
143
- * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB
144
- * options-list output showed this can re-upload a multi-MB payload no backend reads.
145
- */
146
- declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
147
- declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
184
+ declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
148
185
 
149
186
  /**
150
187
  * Single-in-flight, latest-wins throttle for any async operation: only one run at a
@@ -156,7 +193,14 @@ declare function applySolveResult(state: SolveSessionState, result: SolveResult)
156
193
  * directly reads correct values but won't trigger a re-render.
157
194
  */
158
195
  interface AsyncThrottleOptions {
159
- timeout?: number;
196
+ /**
197
+ * How long one run may take before it is aborted (ms). Required and deliberately
198
+ * undefaulted: this is a generic throttle, so it has no basis for guessing what
199
+ * the caller's work costs. Callers driving a Selva solve pass the deadline the
200
+ * server enforces (`COMPUTE_SOLVE_DEADLINE_MS`) — a shorter local guess would abort
201
+ * solves the server would have finished.
202
+ */
203
+ runDeadlineMs: number;
160
204
  onChange?: () => void;
161
205
  }
162
206
  interface AsyncThrottle<T> {
@@ -165,7 +209,7 @@ interface AsyncThrottle<T> {
165
209
  readonly hasPending: boolean;
166
210
  cancel: () => void;
167
211
  }
168
- declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options?: AsyncThrottleOptions): AsyncThrottle<T>;
212
+ declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
169
213
 
170
214
  /**
171
215
  * `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a
@@ -181,7 +225,7 @@ declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) =>
181
225
  * `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).
182
226
  */
183
227
 
184
- interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
228
+ interface ComputeFetchSolveFnOptions<TMesh = unknown> {
185
229
  /** The compute endpoint to POST to, e.g. `/api/compute`. */
186
230
  endpoint: string;
187
231
  /** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */
@@ -197,16 +241,10 @@ interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
197
241
  }>;
198
242
  channel?: () => 'live' | 'draft' | undefined;
199
243
  versionId?: () => string | null | undefined;
200
- /**
201
- * Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`
202
- * and no rhino3dm module is ever loaded. `loadRhino` is called lazily on the
203
- * first solve that needs meshes and cached for the lifetime of this `SolveFn`.
204
- */
244
+ /** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */
205
245
  meshes?: {
206
- loadRhino: () => Promise<TRhino>;
207
246
  extract: (response: GrasshopperComputeResponse, opts: {
208
247
  debug: boolean;
209
- rhino: TRhino;
210
248
  }) => TMesh[] | Promise<TMesh[]>;
211
249
  };
212
250
  /** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */
@@ -216,6 +254,6 @@ interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
216
254
  /** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
217
255
  onSessionExpired?: () => void;
218
256
  }
219
- declare function createComputeFetchSolveFn<TMesh = unknown, TRhino = unknown>(opts: ComputeFetchSolveFnOptions<TMesh, TRhino>): SolveFn<TMesh, GrasshopperComputeResponse>;
257
+ declare function createComputeFetchSolveFn<TMesh = unknown>(opts: ComputeFetchSolveFnOptions<TMesh>): SolveFn<TMesh, GrasshopperComputeResponse>;
220
258
 
221
- export { type AsyncThrottle, type ComputeFetchSolveFnOptions, type ExternalInput, type ExternalReader, type ExternalValueRef, type MeshPolicy, type RequestResponseDriverOptions, type SolveDriver, SolveFn, type SolveMemo, type SolveMemoOptions, type SolveReporter, SolveResult, type SolveSession, type SolveSessionArgs, type SolveSessionState, applySolveResult, applyValueChange, buildInitialValues, clearExternalValue, createAsyncThrottle, createComputeFetchSolveFn, createRequestResponseDriver, createSolveMemo, createSolveSession, getExternalInputs, makeInitialFlags, pickInputValues, readExternalValue, stableInputKey, writeExternalValue };
259
+ export { type AsyncThrottle, type ComputeFetchSolveFnOptions, type ExternalInput, type ExternalReader, type ExternalValueRef, type MeshPolicy, type RequestResponseDriverOptions, type RetainedSolveResult, type SolveDriver, SolveFn, type SolveMemo, type SolveMemoOptions, type SolveReporter, SolveResult, type SolveSession, type SolveSessionArgs, type SolveSessionState, applySolveResult, applyValueChange, buildInitialValues, clearExternalValue, createAsyncThrottle, createComputeFetchSolveFn, createRequestResponseDriver, createSolveMemo, createSolveSession, getExternalInputs, makeInitialFlags, pickInputValues, readExternalValue, stableInputKey, writeExternalValue };
package/dist/client.d.ts CHANGED
@@ -1,12 +1,21 @@
1
1
  import { UISchema } from '@selvajs/schemas';
2
- import { a as SolveResult, S as SolveFn } from './solve-fn-0hPOtZVD.js';
3
- import { GrasshopperComputeResponse } from '@selvajs/compute';
2
+ import { a as SolveResult, S as SolveFn } from './solve-fn-DrRgaPZ5.js';
3
+ import { GrasshopperComputeResponse } from '@selvajs/compute/grasshopper';
4
4
 
5
5
  /**
6
6
  * Transport behind a solve session. `solve()` never returns outputs directly — results
7
7
  * go through the session's `report()` instead, so a push transport (e.g. a WebSocket
8
8
  * streaming mesh frames on its own schedule) can satisfy the same interface as
9
9
  * request/response HTTP.
10
+ *
11
+ * **Stamping `SolveResult.values` is the driver's job, and only some drivers can do it.** A
12
+ * driver that owns a request/response pair must stamp the input set it solved onto the
13
+ * result it reports — the session retains that pair, and a consumer committing what is on
14
+ * screen relies on it being atomic. A push driver that cannot attribute an incoming frame to
15
+ * a request it made must leave `values` absent rather than attach the last set it sent:
16
+ * frames that arrive unsolicited (a replay on connect, a recompute triggered outside the web
17
+ * UI) would otherwise be stamped with an unrelated input set, which is precisely the stale
18
+ * pairing the field exists to prevent.
10
19
  */
11
20
  interface SolveDriver {
12
21
  solve(values: Record<string, unknown>): void;
@@ -26,12 +35,90 @@ interface SolveReporter<TMesh = unknown> {
26
35
  reportError(message: string): void;
27
36
  }
28
37
 
38
+ interface ExternalValueRef {
39
+ scopeKey: string;
40
+ inputId: string;
41
+ }
42
+ declare function writeExternalValue(args: ExternalValueRef & {
43
+ value: unknown;
44
+ }): void;
45
+ declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
46
+ declare function clearExternalValue(ref: ExternalValueRef): void;
47
+ interface ExternalInput {
48
+ paramId: string;
49
+ displayName: string;
50
+ }
51
+ declare function getExternalInputs(schema: UISchema): ExternalInput[];
52
+
53
+ /**
54
+ * The slice of a reported result the session keeps addressable, so a host can commit exactly
55
+ * what the viewer is showing — `source` and `values` included, already memo-correct.
56
+ *
57
+ * Mesh-free by construction. `SolveResult.meshes` are GPU-backed and the viewer disposes what
58
+ * it renders on the next scene update, which is why the memo carries a `MeshPolicy`
59
+ * clone/release contract at all. Retaining them here would hand out disposed instances after
60
+ * the next solve, with no policy governing them and no owner. Everything left is inert JSON;
61
+ * live meshes stay on `session.meshes`.
62
+ */
63
+ type RetainedSolveResult<TSource = unknown> = Omit<SolveResult<never, TSource>, 'meshes'>;
64
+ interface SolveSessionState {
65
+ values: Record<string, unknown>;
66
+ error: string;
67
+ computeErrors: string[];
68
+ computeWarnings: string[];
69
+ meshes: unknown[];
70
+ /** Values changed since the last solve, in manual (instanceSolve === false) mode. */
71
+ pendingValues: Record<string, unknown>;
72
+ hasPendingChanges: boolean;
73
+ hasNeverSolved: boolean;
74
+ /**
75
+ * The last result reported, minus its meshes. Null before the first solve.
76
+ *
77
+ * Lags `values` by design: nothing updates it on `setValue` or `loadValues`, so between a
78
+ * value change and the next report it describes the *previous* input set. That is what a
79
+ * commit gate wants — what is on screen, not what is pending — so `lastResult.values`
80
+ * disagreeing with `state.values` mid-scrub is the feature, not a bug.
81
+ */
82
+ lastResult: RetainedSolveResult | null;
83
+ }
84
+ type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
85
+ /**
86
+ * Client-sourced inputs are hydrated from `read` and left absent when no stored value
87
+ * exists, so the missing-inputs panel can detect them. Other inputs fall back to their
88
+ * declared default, then the paramType default.
89
+ */
90
+ declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
91
+ /** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
92
+ declare function makeInitialFlags(instanceSolve: boolean | undefined): {
93
+ hasPendingChanges: boolean;
94
+ hasNeverSolved: boolean;
95
+ };
96
+ /** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
97
+ declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
98
+ state: SolveSessionState;
99
+ shouldSolve: boolean;
100
+ };
101
+ /**
102
+ * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value
103
+ * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB
104
+ * options-list output showed this can re-upload a multi-MB payload no backend reads.
105
+ */
106
+ declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
107
+ declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
108
+
29
109
  interface SolveSession {
30
110
  readonly values: Record<string, unknown>;
31
111
  readonly error: string;
32
112
  readonly computeErrors: string[];
33
113
  readonly computeWarnings: string[];
34
114
  readonly meshes: unknown[];
115
+ /**
116
+ * The last reported result without its meshes — what the viewer is currently showing,
117
+ * including the `source`/`values` pair a commit path needs. Null before the first solve,
118
+ * and again after `rebuild()`. Survives `reportError`: a failed solve leaves the last good
119
+ * geometry on screen, so a host committing what it sees should still see what produced it.
120
+ */
121
+ readonly lastResult: RetainedSolveResult | null;
35
122
  readonly hasPendingChanges: boolean;
36
123
  readonly hasNeverSolved: boolean;
37
124
  readonly isSolving: boolean;
@@ -84,7 +171,8 @@ interface SolveMemo<TMesh = unknown> {
84
171
  declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
85
172
 
86
173
  interface RequestResponseDriverOptions<TMesh> {
87
- timeout?: number;
174
+ /** How long one solve may take before the client aborts it (ms). Pass the server's `COMPUTE_SOLVE_DEADLINE_MS` so the two agree. */
175
+ solveDeadlineMs: number;
88
176
  onChange?: () => void;
89
177
  /** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */
90
178
  meshPolicy?: MeshPolicy<TMesh>;
@@ -93,58 +181,7 @@ interface RequestResponseDriverOptions<TMesh> {
93
181
  * `getReporter` is lazy (`() => session`) because the session and driver reference each
94
182
  * other — the host builds the driver first, then the session, then can hand this back.
95
183
  */
96
- declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options?: RequestResponseDriverOptions<TMesh>): SolveDriver;
97
-
98
- interface ExternalValueRef {
99
- scopeKey: string;
100
- inputId: string;
101
- }
102
- declare function writeExternalValue(args: ExternalValueRef & {
103
- value: unknown;
104
- }): void;
105
- declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
106
- declare function clearExternalValue(ref: ExternalValueRef): void;
107
- interface ExternalInput {
108
- paramId: string;
109
- displayName: string;
110
- }
111
- declare function getExternalInputs(schema: UISchema): ExternalInput[];
112
-
113
- interface SolveSessionState {
114
- values: Record<string, unknown>;
115
- error: string;
116
- computeErrors: string[];
117
- computeWarnings: string[];
118
- meshes: unknown[];
119
- /** Values changed since the last solve, in manual (instanceSolve === false) mode. */
120
- pendingValues: Record<string, unknown>;
121
- hasPendingChanges: boolean;
122
- hasNeverSolved: boolean;
123
- }
124
- type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
125
- /**
126
- * Client-sourced inputs are hydrated from `read` and left absent when no stored value
127
- * exists, so the missing-inputs panel can detect them. Other inputs fall back to their
128
- * declared default, then the paramType default.
129
- */
130
- declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
131
- /** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
132
- declare function makeInitialFlags(instanceSolve: boolean | undefined): {
133
- hasPendingChanges: boolean;
134
- hasNeverSolved: boolean;
135
- };
136
- /** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
137
- declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
138
- state: SolveSessionState;
139
- shouldSolve: boolean;
140
- };
141
- /**
142
- * Solve outputs live in the same `values` map as inputs (so widgets like dynamic value
143
- * lists can read them) but must not be echoed back to the driver: a measured 6.4 MB
144
- * options-list output showed this can re-upload a multi-MB payload no backend reads.
145
- */
146
- declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
147
- declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
184
+ declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
148
185
 
149
186
  /**
150
187
  * Single-in-flight, latest-wins throttle for any async operation: only one run at a
@@ -156,7 +193,14 @@ declare function applySolveResult(state: SolveSessionState, result: SolveResult)
156
193
  * directly reads correct values but won't trigger a re-render.
157
194
  */
158
195
  interface AsyncThrottleOptions {
159
- timeout?: number;
196
+ /**
197
+ * How long one run may take before it is aborted (ms). Required and deliberately
198
+ * undefaulted: this is a generic throttle, so it has no basis for guessing what
199
+ * the caller's work costs. Callers driving a Selva solve pass the deadline the
200
+ * server enforces (`COMPUTE_SOLVE_DEADLINE_MS`) — a shorter local guess would abort
201
+ * solves the server would have finished.
202
+ */
203
+ runDeadlineMs: number;
160
204
  onChange?: () => void;
161
205
  }
162
206
  interface AsyncThrottle<T> {
@@ -165,7 +209,7 @@ interface AsyncThrottle<T> {
165
209
  readonly hasPending: boolean;
166
210
  cancel: () => void;
167
211
  }
168
- declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options?: AsyncThrottleOptions): AsyncThrottle<T>;
212
+ declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
169
213
 
170
214
  /**
171
215
  * `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a
@@ -181,7 +225,7 @@ declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) =>
181
225
  * `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).
182
226
  */
183
227
 
184
- interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
228
+ interface ComputeFetchSolveFnOptions<TMesh = unknown> {
185
229
  /** The compute endpoint to POST to, e.g. `/api/compute`. */
186
230
  endpoint: string;
187
231
  /** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */
@@ -197,16 +241,10 @@ interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
197
241
  }>;
198
242
  channel?: () => 'live' | 'draft' | undefined;
199
243
  versionId?: () => string | null | undefined;
200
- /**
201
- * Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`
202
- * and no rhino3dm module is ever loaded. `loadRhino` is called lazily on the
203
- * first solve that needs meshes and cached for the lifetime of this `SolveFn`.
204
- */
244
+ /** Omit entirely for a non-viewer consumer — `meshes` on the result stays `[]`. */
205
245
  meshes?: {
206
- loadRhino: () => Promise<TRhino>;
207
246
  extract: (response: GrasshopperComputeResponse, opts: {
208
247
  debug: boolean;
209
- rhino: TRhino;
210
248
  }) => TMesh[] | Promise<TMesh[]>;
211
249
  };
212
250
  /** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */
@@ -216,6 +254,6 @@ interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
216
254
  /** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
217
255
  onSessionExpired?: () => void;
218
256
  }
219
- declare function createComputeFetchSolveFn<TMesh = unknown, TRhino = unknown>(opts: ComputeFetchSolveFnOptions<TMesh, TRhino>): SolveFn<TMesh, GrasshopperComputeResponse>;
257
+ declare function createComputeFetchSolveFn<TMesh = unknown>(opts: ComputeFetchSolveFnOptions<TMesh>): SolveFn<TMesh, GrasshopperComputeResponse>;
220
258
 
221
- export { type AsyncThrottle, type ComputeFetchSolveFnOptions, type ExternalInput, type ExternalReader, type ExternalValueRef, type MeshPolicy, type RequestResponseDriverOptions, type SolveDriver, SolveFn, type SolveMemo, type SolveMemoOptions, type SolveReporter, SolveResult, type SolveSession, type SolveSessionArgs, type SolveSessionState, applySolveResult, applyValueChange, buildInitialValues, clearExternalValue, createAsyncThrottle, createComputeFetchSolveFn, createRequestResponseDriver, createSolveMemo, createSolveSession, getExternalInputs, makeInitialFlags, pickInputValues, readExternalValue, stableInputKey, writeExternalValue };
259
+ export { type AsyncThrottle, type ComputeFetchSolveFnOptions, type ExternalInput, type ExternalReader, type ExternalValueRef, type MeshPolicy, type RequestResponseDriverOptions, type RetainedSolveResult, type SolveDriver, SolveFn, type SolveMemo, type SolveMemoOptions, type SolveReporter, SolveResult, type SolveSession, type SolveSessionArgs, type SolveSessionState, applySolveResult, applyValueChange, buildInitialValues, clearExternalValue, createAsyncThrottle, createComputeFetchSolveFn, createRequestResponseDriver, createSolveMemo, createSolveSession, getExternalInputs, makeInitialFlags, pickInputValues, readExternalValue, stableInputKey, writeExternalValue };
package/dist/client.js CHANGED
@@ -1,2 +1,2 @@
1
- import{getInputItems as G}from"@selvajs/schemas";var H="external";function k(e,r){return`${H}:${e}:${r}`}function X(e){let{scopeKey:r,inputId:s,value:n}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.setItem(k(r,s),JSON.stringify(n))}function R(e){let{scopeKey:r,inputId:s}=e;if(!r||!s||typeof sessionStorage>"u")return;let n=sessionStorage.getItem(k(r,s));if(n!==null)try{return JSON.parse(n)}catch{return}}function Y(e){let{scopeKey:r,inputId:s}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.removeItem(k(r,s))}function I(e){return G(e).filter(r=>r.source?.kind==="client").map(r=>({paramId:r.paramId,displayName:r.displayName??r.paramId}))}import{getDefaultValue as Q}from"@selvajs/schemas";function b(e,r,s){let n=new Set(I(e).map(t=>t.paramId)),l={};for(let t of e.inputs){if(n.has(t.id)){let i=s({scopeKey:r,inputId:t.id});i!==void 0&&(l[t.id]=i);continue}l[t.id]=t.default??Q(t.paramType)}for(let t of e.outputs)l[t.id]=null;return l}function w(e){let r=e===!1;return{hasPendingChanges:r,hasNeverSolved:r}}function C(e,r,s,n){return e.values[r]=s,n===!1?(e.pendingValues[r]=s,e.hasPendingChanges=!0,{state:e,shouldSolve:!1}):{state:e,shouldSolve:!0}}function P(e,r){if(!e?.inputs)return r;let s={};for(let n of e.inputs)n.id in r&&(s[n.id]=r[n.id]);return s}function V(e,r){return e.error="",e.computeErrors=r.errors??[],e.computeWarnings=r.warnings??[],e.meshes=r.meshes??[],Object.assign(e.values,r.outputs),e.pendingValues={},e.hasPendingChanges=!1,e.hasNeverSolved=!1,e}function Z(e){let r=e.schema,s=w(r?.instanceSolve),n={values:b(r,e.scopeKey,R),error:"",computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:s.hasPendingChanges,hasNeverSolved:s.hasNeverSolved},l=new Set,t=()=>l.forEach(o=>o());function i(){e.driver.solve({...P(r,n.values)}),t()}return{get values(){return n.values},get error(){return n.error},get computeErrors(){return n.computeErrors},get computeWarnings(){return n.computeWarnings},get meshes(){return n.meshes},get hasPendingChanges(){return n.hasPendingChanges},get hasNeverSolved(){return n.hasNeverSolved},get isSolving(){return e.driver.isSolving},setValue(o,a,u=!1){let{shouldSolve:c}=C(n,o,a,r?.instanceSolve);c||u?(u&&!c&&(n.pendingValues={},n.hasPendingChanges=!1),i()):t()},solve(){i()},loadValues(o){Object.assign(n.values,o),r?.instanceSolve!==!1?i():(n.hasPendingChanges=!0,t())},rebuild(o,a){r=o,e.driver.clearCache?.(),n.meshes=[],n.error="",n.computeErrors=[],n.computeWarnings=[],n.pendingValues={},n.values=b(o,a,R);let u=w(o?.instanceSolve);n.hasPendingChanges=u.hasPendingChanges,n.hasNeverSolved=u.hasNeverSolved,o&&Object.keys(n.values).length>0&&o.instanceSolve!==!1?i():t()},report(o){V(n,o),t()},reportError(o){n.error=o,t()},subscribe(o){return l.add(o),()=>l.delete(o)},notify:t}}function F(e,r={}){let{timeout:s=6e4,onChange:n}=r,l=!1,t=null,i=null;function o(m){l!==m&&(l=m,n?.())}function a(){i?.abort(),i=null}async function u(m){a(),i=new AbortController;let{signal:S}=i,y=setTimeout(()=>{console.warn(`[Solve/throttle] run exceeded ${s}ms \u2014 aborting`),i?.abort()},s);o(!0);try{await e(m,S)}catch(f){(!(f instanceof Error)||f.name!=="AbortError"&&f.name!=="TimeoutError")&&console.error("[Solve/throttle] unhandled error in run:",f)}finally{if(clearTimeout(y),o(!1),i=null,t!==null){let f=t;t=null,u(f)}}}function c(m){l?(t!==null&&console.debug("[Solve/throttle] superseded pending run (latest-wins)"),t=m):u(m)}function v(){t=null,a()}return{trigger:c,get isRunning(){return l},get hasPending(){return t!==null},cancel:v}}function O(e){return $(e)}function $(e){if(e===null||typeof e!="object")return JSON.stringify(e)??"null";if(Array.isArray(e))return`[${e.map($).join(",")}]`;let r=e;return`{${Object.keys(r).sort().map(n=>`${JSON.stringify(n)}:${$(r[n])}`).join(",")}}`}function j(e={}){let{max:r=16,meshPolicy:s}=e,n=new Map;function l(o){o.meshes?.length&&s?.release(o.meshes)}function t(o){return!s||!o.meshes?.length?o:{...o,meshes:s.clone(o.meshes)}}function i(o){let a=n.get(o);a!==void 0&&(n.delete(o),l(a))}return{get(o){let a=O(o),u=n.get(a);if(u!==void 0)return n.delete(a),n.set(a,u),console.info(`[Solve/memo] HIT \u2014 served from client memo (${n.size}/${r})`),t(u)},set(o,a){let u=O(o);for(i(u),n.set(u,t(a));n.size>r;){let c=n.keys().next().value;if(c===void 0)break;i(c),console.info(`[Solve/memo] evicted LRU entry (cap ${r})`)}},clear(){n.size>0&&console.info(`[Solve/memo] cleared ${n.size} entries (definition changed)`),n.forEach(l),n.clear()}}}function ee(e,r,s={}){let{meshPolicy:n,...l}=s,t=j({meshPolicy:n}),i=F(async(o,a)=>{let u=t.get(o);if(u!==void 0){if(a.aborted)return;r().report(u);return}try{let c=await e(o,a);if(a.aborted){console.debug("[Solve/driver] solve completed after abort \u2014 result discarded");return}let v={...c,values:o};t.set(o,v),r().report(v)}catch(c){if(a.aborted){console.debug("[Solve/driver] solve aborted (superseded, cancelled, or timed out)");return}console.warn("[Solve/driver] solve failed:",c),r().reportError(c instanceof Error?c.message:String(c))}},l);return{solve(o){i.trigger(o)},cancel(){i.cancel()},get isSolving(){return i.isRunning},clearCache(){t.clear()}}}import{GrasshopperResponseProcessor as ne}from"@selvajs/compute";function re(e){let r={};if(!e)return r;for(let s of e.split(",")){let[n,...l]=s.split(";").map(o=>o.trim()),t=l.find(o=>o.startsWith("dur="));if(!n||!t)continue;let i=Number(t.slice(4));Number.isFinite(i)&&(r[n]=i)}return r}var oe=e=>{throw new Error(`Rate limit reached. Try again in ${Math.ceil(e)}s.`)},te=()=>{throw new Error("Your session has expired. Sign in again in a new tab, then re-run \u2014 your inputs are preserved.")};function se(e){let r=e.debug??!1,s=(...i)=>{r&&console.info(...i)},n=null;function l(){if(!e.meshes)throw new Error("createComputeFetchSolveFn: meshes option not configured.");return n||(n=e.meshes.loadRhino()),n}let t=0;return async(i,o)=>{let a=t-Date.now();if(a>0)throw new Error(`Rate limit reached. Try again in ${Math.ceil(a/1e3)}s.`);let u=performance.now(),c=e.inputs(),v=e.outputs(),m=e.channel?.(),S=e.versionId?.(),y=JSON.stringify({inputs:c,values:i,definitionUrl:e.definitionUrl(),...S?{versionId:S}:m==="draft"?{channel:"draft"}:{}}),f=JSON.stringify(i).length,p;try{p=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:y,signal:o})}catch(d){if(o.aborted)return s("[Compute] solve aborted during fetch \u2014 empty result discarded"),{outputs:{}};throw new Error("Request blocked \u2014 your session may have expired. Reload the page to sign in again.",{cause:d})}if(o.aborted)return s("[Compute] solve aborted after headers \u2014 empty result discarded"),{outputs:{}};let J=p.redirected&&new URL(p.url).pathname==="/login";if((p.status===401||J)&&(e.onSessionExpired??te)(),!p.ok){if(p.status===503)throw new Error("Compute server is offline or unreachable. Please try again later.");let d=await p.text().catch(()=>""),h={};try{h=JSON.parse(d)}catch{d&&console.warn(`[Compute] non-JSON error body (HTTP ${p.status}):`,d.slice(0,300))}if(p.status===429){let x=Number(p.headers.get("Retry-After"))||Number(h.retryAfter)||5;t=Date.now()+x*1e3,(e.onRateLimited??oe)(x)}throw new Error(h.message||`Compute error (HTTP ${p.status})`)}let A=performance.now()-u,B=performance.now(),N=await p.text();if(o.aborted)return s("[Compute] solve aborted mid-download \u2014 empty result discarded"),{outputs:{}};let T=performance.now()-B,U=N.length,g;try{g=JSON.parse(N)}catch(d){throw new Error("Received an invalid response from the server. Reload the page and try again.",{cause:d})}let M=new ne(g,!1),z=e.meshes?await e.meshes.extract(M.response,{debug:r,rhino:await l()}):[],D={};for(let d of v){let h=M.getValue({byId:d.id},{parseValues:!0});D[d.id]=h??(d.nickname?M.getValue({byName:d.nickname},{parseValues:!0}):void 0)}if(r){let d=re(p.headers.get("Server-Timing")),h=d.total??0,x=Math.max(0,A-h),E=U/(1024*1024),q=E>=1?`${E.toFixed(2)} MB`:`${(U/1024).toFixed(0)} KB`,L=T>0?(E/(T/1e3)).toFixed(1):"\u221E",K=performance.memory?.usedJSHeapSize,W=K!==void 0?` | heap=${(K/(1024*1024)).toFixed(0)} MB`:"";if(s(`[Compute] req=${(y.length/1024).toFixed(0)}KB (values ${(f/1024).toFixed(0)}KB) | ttfb=${A.toFixed(0)}ms (network\u2248${x.toFixed(0)} + server ${h.toFixed(0)}) | download=${T.toFixed(0)}ms (${q} @ ${L} MB/s)${W}`),d.selva_cache!==void 0){let _=d.selva_cache?"HIT \u2014 served without calling compute":"miss \u2014 solved on Rhino.Compute";s(`[Compute] \u2514\u2500 cache: ${_}`)}}return{outputs:D,meshes:z,errors:g.errors??[],warnings:g.warnings??[],source:g}}}export{V as applySolveResult,C as applyValueChange,b as buildInitialValues,Y as clearExternalValue,F as createAsyncThrottle,se as createComputeFetchSolveFn,ee as createRequestResponseDriver,j as createSolveMemo,Z as createSolveSession,I as getExternalInputs,w as makeInitialFlags,P as pickInputValues,R as readExternalValue,O as stableInputKey,X as writeExternalValue};
1
+ import{getInputItems as G}from"@selvajs/schemas";var H="external";function k(e,r){return`${H}:${e}:${r}`}function L(e){let{scopeKey:r,inputId:s,value:n}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.setItem(k(r,s),JSON.stringify(n))}function y(e){let{scopeKey:r,inputId:s}=e;if(!r||!s||typeof sessionStorage>"u")return;let n=sessionStorage.getItem(k(r,s));if(n!==null)try{return JSON.parse(n)}catch{return}}function _(e){let{scopeKey:r,inputId:s}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.removeItem(k(r,s))}function E(e){return G(e).filter(r=>r.source?.kind==="client").map(r=>({paramId:r.paramId,displayName:r.displayName??r.paramId}))}import{getDefaultValue as X}from"@selvajs/schemas";function R(e,r,s){let n=new Set(E(e).map(t=>t.paramId)),a={};for(let t of e.inputs){if(n.has(t.id)){let i=s({scopeKey:r,inputId:t.id});i!==void 0&&(a[t.id]=i);continue}a[t.id]=t.default??X(t.paramType)}for(let t of e.outputs)a[t.id]=null;return a}function x(e){let r=e===!1;return{hasPendingChanges:r,hasNeverSolved:r}}function I(e,r,s,n){return e.values[r]=s,n===!1?(e.pendingValues[r]=s,e.hasPendingChanges=!0,{state:e,shouldSolve:!1}):{state:e,shouldSolve:!0}}function C(e,r){if(!e?.inputs)return r;let s={};for(let n of e.inputs)n.id in r&&(s[n.id]=r[n.id]);return s}function V(e,r){e.error="",e.computeErrors=r.errors??[],e.computeWarnings=r.warnings??[],e.meshes=r.meshes??[];let{meshes:s,...n}=r;return e.lastResult=n,Object.assign(e.values,r.outputs),e.pendingValues={},e.hasPendingChanges=!1,e.hasNeverSolved=!1,e}function Y(e){let r=e.schema,s=x(r?.instanceSolve),n={values:R(r,e.scopeKey,y),error:"",computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:s.hasPendingChanges,hasNeverSolved:s.hasNeverSolved,lastResult:null},a=new Set,t=()=>a.forEach(o=>o());function i(){e.driver.solve({...C(r,n.values)}),t()}return{get values(){return n.values},get error(){return n.error},get computeErrors(){return n.computeErrors},get computeWarnings(){return n.computeWarnings},get meshes(){return n.meshes},get lastResult(){return n.lastResult},get hasPendingChanges(){return n.hasPendingChanges},get hasNeverSolved(){return n.hasNeverSolved},get isSolving(){return e.driver.isSolving},setValue(o,l,u=!1){let{shouldSolve:f}=I(n,o,l,r?.instanceSolve);f||u?(u&&!f&&(n.pendingValues={},n.hasPendingChanges=!1),i()):t()},solve(){i()},loadValues(o){Object.assign(n.values,o),r?.instanceSolve!==!1?i():(n.hasPendingChanges=!0,t())},rebuild(o,l){r=o,e.driver.clearCache?.(),n.meshes=[],n.lastResult=null,n.error="",n.computeErrors=[],n.computeWarnings=[],n.pendingValues={},n.values=R(o,l,y);let u=x(o?.instanceSolve);n.hasPendingChanges=u.hasPendingChanges,n.hasNeverSolved=u.hasNeverSolved,o&&Object.keys(n.values).length>0&&o.instanceSolve!==!1?i():t()},report(o){V(n,o),t()},reportError(o){n.error=o,t()},subscribe(o){return a.add(o),()=>a.delete(o)},notify:t}}function P(e,r){let{runDeadlineMs:s,onChange:n}=r,a=!1,t=null,i=null;function o(p){a!==p&&(a=p,n?.())}function l(){i?.abort(),i=null}async function u(p){l(),i=new AbortController;let{signal:b}=i,d=setTimeout(()=>{console.warn(`[Solve/throttle] run exceeded ${s}ms \u2014 aborting`),i?.abort()},s);o(!0);try{await e(p,b)}catch(v){(!(v instanceof Error)||v.name!=="AbortError"&&v.name!=="TimeoutError")&&console.error("[Solve/throttle] unhandled error in run:",v)}finally{if(clearTimeout(d),o(!1),i=null,t!==null){let v=t;t=null,u(v)}}}function f(p){a?(t!==null&&console.debug("[Solve/throttle] superseded pending run (latest-wins)"),t=p):u(p)}function m(){t=null,l()}return{trigger:f,get isRunning(){return a},get hasPending(){return t!==null},cancel:m}}function F(e){return O(e)}function O(e){if(e===null||typeof e!="object")return JSON.stringify(e)??"null";if(Array.isArray(e))return`[${e.map(O).join(",")}]`;let r=e;return`{${Object.keys(r).sort().map(n=>`${JSON.stringify(n)}:${O(r[n])}`).join(",")}}`}function $(e={}){let{max:r=16,meshPolicy:s}=e,n=new Map;function a(o){o.meshes?.length&&s?.release(o.meshes)}function t(o){return!s||!o.meshes?.length?o:{...o,meshes:s.clone(o.meshes)}}function i(o){let l=n.get(o);l!==void 0&&(n.delete(o),a(l))}return{get(o){let l=F(o),u=n.get(l);if(u!==void 0)return n.delete(l),n.set(l,u),console.info(`[Solve/memo] HIT \u2014 served from client memo (${n.size}/${r})`),t(u)},set(o,l){let u=F(o);for(i(u),n.set(u,t(l));n.size>r;){let f=n.keys().next().value;if(f===void 0)break;i(f),console.info(`[Solve/memo] evicted LRU entry (cap ${r})`)}},clear(){n.size>0&&console.info(`[Solve/memo] cleared ${n.size} entries (definition changed)`),n.forEach(a),n.clear()}}}function Q(e,r,s){let{meshPolicy:n,solveDeadlineMs:a,onChange:t}=s,i=$({meshPolicy:n}),o=P(async(l,u)=>{let f=i.get(l);if(f!==void 0){if(u.aborted)return;r().report(f);return}try{let m=await e(l,u);if(u.aborted){console.debug("[Solve/driver] solve completed after abort \u2014 result discarded");return}let p={...m,values:l};i.set(l,p),r().report(p)}catch(m){if(u.aborted){console.debug("[Solve/driver] solve aborted (superseded, cancelled, or timed out)");return}console.warn("[Solve/driver] solve failed:",m),r().reportError(m instanceof Error?m.message:String(m))}},{runDeadlineMs:a,onChange:t});return{solve(l){o.trigger(l)},cancel(){o.cancel()},get isSolving(){return o.isRunning},clearCache(){i.clear()}}}import{GrasshopperResponseProcessor as Z}from"@selvajs/compute/grasshopper";function ee(e){let r={};if(!e)return r;for(let s of e.split(",")){let[n,...a]=s.split(";").map(o=>o.trim()),t=a.find(o=>o.startsWith("dur="));if(!n||!t)continue;let i=Number(t.slice(4));Number.isFinite(i)&&(r[n]=i)}return r}var ne=e=>{throw new Error(`Rate limit reached. Try again in ${Math.ceil(e)}s.`)},re=()=>{throw new Error("Your session has expired. Sign in again in a new tab, then re-run \u2014 your inputs are preserved.")};function oe(e){let r=e.debug??!1,s=(...a)=>{r&&console.info(...a)},n=0;return async(a,t)=>{let i=n-Date.now();if(i>0)throw new Error(`Rate limit reached. Try again in ${Math.ceil(i/1e3)}s.`);let o=performance.now(),l=e.inputs(),u=e.outputs(),f=e.channel?.(),m=e.versionId?.(),p=JSON.stringify({inputs:l,values:a,definitionUrl:e.definitionUrl(),...m?{versionId:m}:f==="draft"?{channel:"draft"}:{}}),b=JSON.stringify(a).length,d;try{d=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:p,signal:t})}catch(c){if(t.aborted)return s("[Compute] solve aborted during fetch \u2014 empty result discarded"),{outputs:{}};throw new Error("Request blocked \u2014 your session may have expired. Reload the page to sign in again.",{cause:c})}if(t.aborted)return s("[Compute] solve aborted after headers \u2014 empty result discarded"),{outputs:{}};let v=d.redirected&&new URL(d.url).pathname==="/login";if((d.status===401||v)&&(e.onSessionExpired??re)(),!d.ok){if(d.status===503)throw new Error("Compute server is offline or unreachable. Please try again later.");let c=await d.text().catch(()=>""),h={};try{h=JSON.parse(c)}catch{c&&console.warn(`[Compute] non-JSON error body (HTTP ${d.status}):`,c.slice(0,300))}if(d.status===429){let S=Number(d.headers.get("Retry-After"))||Number(h.retryAfter)||5;n=Date.now()+S*1e3,(e.onRateLimited??ne)(S)}throw new Error(h.message||`Compute error (HTTP ${d.status})`)}let j=performance.now()-o,K=performance.now(),N=await d.text();if(t.aborted)return s("[Compute] solve aborted mid-download \u2014 empty result discarded"),{outputs:{}};let w=performance.now()-K,A=N.length,g;try{g=JSON.parse(N)}catch(c){throw new Error("Received an invalid response from the server. Reload the page and try again.",{cause:c})}let T=new Z(g,!1),J=e.meshes?await e.meshes.extract(T.response,{debug:r}):[],D={};for(let c of u){let h=T.getValue({byId:c.id},{parseValues:!0});D[c.id]=h??(c.nickname?T.getValue({byName:c.nickname},{parseValues:!0}):void 0)}if(r){let c=ee(d.headers.get("Server-Timing")),h=c.total??0,S=Math.max(0,j-h),M=A/(1024*1024),B=M>=1?`${M.toFixed(2)} MB`:`${(A/1024).toFixed(0)} KB`,z=w>0?(M/(w/1e3)).toFixed(1):"\u221E",U=performance.memory?.usedJSHeapSize,q=U!==void 0?` | heap=${(U/(1024*1024)).toFixed(0)} MB`:"";if(s(`[Compute] req=${(p.length/1024).toFixed(0)}KB (values ${(b/1024).toFixed(0)}KB) | ttfb=${j.toFixed(0)}ms (network\u2248${S.toFixed(0)} + server ${h.toFixed(0)}) | download=${w.toFixed(0)}ms (${B} @ ${z} MB/s)${q}`),c.selva_cache!==void 0){let W=c.selva_cache?"HIT \u2014 served without calling compute":"miss \u2014 solved on Rhino.Compute";s(`[Compute] \u2514\u2500 cache: ${W}`)}}return{outputs:D,meshes:J,errors:g.errors??[],warnings:g.warnings??[],source:g}}}export{V as applySolveResult,I as applyValueChange,R as buildInitialValues,_ as clearExternalValue,P as createAsyncThrottle,oe as createComputeFetchSolveFn,Q as createRequestResponseDriver,$ as createSolveMemo,Y as createSolveSession,E as getExternalInputs,x as makeInitialFlags,C as pickInputValues,y as readExternalValue,F as stableInputKey,L as writeExternalValue};
2
2
  //# sourceMappingURL=client.js.map