@selvajs/solve 0.2.0-beta.2 → 1.0.0-beta.5
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/README.md +10 -14
- package/dist/client.cjs +1 -1
- package/dist/client.d.cts +119 -88
- package/dist/client.d.ts +119 -88
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +98 -160
- package/dist/server.d.ts +98 -160
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/shared.d.cts +1 -1
- package/dist/shared.d.ts +1 -1
- package/dist/solve-fn-DrRgaPZ5.d.cts +46 -0
- package/dist/solve-fn-DrRgaPZ5.d.ts +46 -0
- package/package.json +1 -1
- package/dist/solve-fn-DGzPCsDu.d.cts +0 -22
- package/dist/solve-fn-DGzPCsDu.d.ts +0 -22
package/README.md
CHANGED
|
@@ -14,10 +14,9 @@ client/ form state machine, auto/manual decision, throttle, result memo, drive
|
|
|
14
14
|
server/ solve pipeline (tree build → solve → serialize → envelope), caches, single-flight
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`client/` and `server/` both depend on `shared/`, and **never on each other
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
([`src/__tests__/client-bundle-boundary.test.ts`](./src/__tests__/client-bundle-boundary.test.ts)).
|
|
17
|
+
`client/` and `server/` both depend on `shared/`, and **never on each other**. See
|
|
18
|
+
[`src/server/README.md`](./src/server/README.md#node-only-and-that-is-load-bearing) for how that's
|
|
19
|
+
enforced.
|
|
21
20
|
|
|
22
21
|
See [`src/client/README.md`](./src/client/README.md) for the session, the driver seam, and how to
|
|
23
22
|
write a transport; [`src/server/README.md`](./src/server/README.md) for the pipeline and the cache
|
|
@@ -28,19 +27,19 @@ tiers.
|
|
|
28
27
|
- **No UI framework.** No Svelte, no runes, no DOM. `client/` is a state machine, not a component.
|
|
29
28
|
- **No renderer.** No `three`. `SolveResult<TMesh>` keeps meshes opaque — the app that parses a
|
|
30
29
|
response into `THREE.Object3D[]` is the only place that knows the concrete type. This is what lets
|
|
31
|
-
a headless CLI solve without dragging in a parse layer.
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
a headless CLI solve without dragging in a parse layer. See
|
|
31
|
+
[`src/client/README.md`](./src/client/README.md#mesh-ownership-is-injected-not-known-here) for how
|
|
32
|
+
the result memo handles mesh ownership without knowing what a mesh is.
|
|
34
33
|
- **No authorization, orgs, projects, or share links.** App policy.
|
|
35
34
|
- **No HTTP.** `client/` stops at a `SolveFn`; `server/` stops at a `SolveOutcome`. Mapping either to
|
|
36
35
|
a status code is the app's job.
|
|
37
36
|
|
|
38
37
|
## No root barrel — on purpose
|
|
39
38
|
|
|
40
|
-
`@selvajs/solve` exports `./shared`, `./client` and `./server`. There is deliberately
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
`@selvajs/solve` exports `./shared`, `./client` and `./server`. There is deliberately no `.` export:
|
|
40
|
+
a root barrel re-exporting both halves would let a browser bundle reach server code — and server
|
|
41
|
+
credentials — through one innocent-looking import, defeating every other guard. Adding one is a
|
|
42
|
+
boundary change, not a convenience.
|
|
44
43
|
|
|
45
44
|
```ts
|
|
46
45
|
import type { SolveResult } from '@selvajs/solve/shared';
|
|
@@ -54,6 +53,3 @@ import { runSolvePipeline } from '@selvajs/solve/server';
|
|
|
54
53
|
`@selvajs/server/compute` keeps only the HTTP request policy it owns (limits, rate limiting, the
|
|
55
54
|
SSRF guard, remote-definition fetch) and does not re-export any of this — the two packages are
|
|
56
55
|
independent.
|
|
57
|
-
|
|
58
|
-
In a Svelte app use `useSolveSession` from `@selvajs/ui` rather than `createSolveSession` directly —
|
|
59
|
-
the raw factory returns correct values that never re-render.
|
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 X="external";function k(e,r){return`${X}:${e}:${r}`}function Y(e){let{scopeKey:r,inputId:s,value:n}=e;!r||!s||typeof sessionStorage>"u"||sessionStorage.setItem(k(r,s),JSON.stringify(n))}function x(e){let{scopeKey:r,inputId:s}=e;if(!r||!s||typeof sessionStorage>"u")return;let n=sessionStorage.getItem(k(r,s));if(n!==null)try{return JSON.parse(n)}catch (e2){return}}function Q(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 R(e,r,s){let n=new Set(I(e).map(t=>t.paramId)),l={};for(let t of e.inputs){if(n.has(t.id)){let i=s({scopeKey:r,inputId:t.id});i!==void 0&&(l[t.id]=i);continue}l[t.id]=_nullishCoalesce(t.default, () => (_schemas.getDefaultValue.call(void 0, t.paramType)))}for(let t of e.outputs)l[t.id]=null;return l}function b(e){let r=e===!1;return{hasPendingChanges:r,hasNeverSolved:r}}function C(e,r,s,n){return e.values[r]=s,n===!1?(e.pendingValues[r]=s,e.hasPendingChanges=!0,{state:e,shouldSolve:!1}):{state:e,shouldSolve:!0}}function P(e,r){if(!_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 ee(e){let r=e.schema,s=b(_optionalChain([r, 'optionalAccess', _5 => _5.instanceSolve])),n={values:R(r,e.scopeKey,x),error:"",computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:s.hasPendingChanges,hasNeverSolved:s.hasNeverSolved},l=new Set,t=()=>l.forEach(o=>o());function i(){e.driver.solve({...P(r,n.values)}),t()}return{get values(){return n.values},get error(){return n.error},get computeErrors(){return n.computeErrors},get computeWarnings(){return n.computeWarnings},get meshes(){return n.meshes},get hasPendingChanges(){return n.hasPendingChanges},get hasNeverSolved(){return n.hasNeverSolved},get isSolving(){return e.driver.isSolving},setValue(o,a,u=!1){let{shouldSolve:c}=C(n,o,a,_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=R(o,a,x);let u=b(_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(f){l!==f&&(l=f,_optionalChain([n, 'optionalCall', _12 => _12()]))}function a(){_optionalChain([i, 'optionalAccess', _13 => _13.abort, 'call', _14 => _14()]),i=null}async function u(f){a(),i=new AbortController;let{signal:v}=i,g=setTimeout(()=>{console.warn(`[Solve/throttle] run exceeded ${s}ms \u2014 aborting`),_optionalChain([i, 'optionalAccess', _15 => _15.abort, 'call', _16 => _16()])},s);o(!0);try{await e(f,v)}catch(m){(!(m instanceof Error)||m.name!=="AbortError"&&m.name!=="TimeoutError")&&console.error("[Solve/throttle] unhandled error in run:",m)}finally{if(clearTimeout(g),o(!1),i=null,t!==null){let m=t;t=null,u(m)}}}function c(f){l?(t!==null&&console.debug("[Solve/throttle] superseded pending run (latest-wins)"),t=f):u(f)}function w(){t=null,a()}return{trigger:c,get isRunning(){return l},get hasPending(){return t!==null},cancel:w}}function O(e){return $(e)}function $(e){if(e===null||typeof e!="object")return _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 ne(e,r,s={}){let{meshPolicy:n,...l}=s,t=j({meshPolicy:n}),i=F(async(o,a)=>{let u=t.get(o);if(u!==void 0){if(a.aborted)return;r().report(u);return}try{let c=await e(o,a);if(a.aborted){console.debug("[Solve/driver] solve completed after abort \u2014 result discarded");return}t.set(o,c),r().report(c)}catch(c){if(a.aborted){console.debug("[Solve/driver] solve aborted (superseded, cancelled, or timed out)");return}console.warn("[Solve/driver] solve failed:",c),r().reportError(c instanceof Error?c.message:String(c))}},l);return{solve(o){i.trigger(o)},cancel(){i.cancel()},get isSolving(){return i.isRunning},clearCache(){t.clear()}}}var _compute = require('@selvajs/compute');function oe(e){let r={};if(!e)return r;for(let s of e.split(",")){let[n,...l]=s.split(";").map(o=>o.trim()),t=l.find(o=>o.startsWith("dur="));if(!n||!t)continue;let i=Number(t.slice(4));Number.isFinite(i)&&(r[n]=i)}return r}var te=e=>{throw new Error(`Rate limit reached. Try again in ${Math.ceil(e)}s.`)},se=()=>{throw new Error("Your session has expired. Sign in again in a new tab, then re-run \u2014 your inputs are preserved.")};function ie(e){let r=_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(),w=e.outputs(),f=_optionalChain([e, 'access', _23 => _23.channel, 'optionalCall', _24 => _24()]),v=_optionalChain([e, 'access', _25 => _25.versionId, 'optionalCall', _26 => _26()]),g=JSON.stringify({inputs:c,values:i,definitionUrl:e.definitionUrl(),...v?{versionId:v}:f==="draft"?{channel:"draft"}:{}}),m=JSON.stringify(i).length,p;try{p=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:g,signal:o})}catch(d){if(o.aborted)return s("[Compute] solve aborted during fetch \u2014 empty result discarded"),{outputs:{}};throw new Error("Request blocked \u2014 your session may have expired. Reload the page to sign in again.",{cause:d})}if(o.aborted)return s("[Compute] solve aborted after headers \u2014 empty result discarded"),{outputs:{}};let B=p.redirected&&new URL(p.url).pathname==="/login";if((p.status===401||B)&&(_nullishCoalesce(e.onSessionExpired, () => (se)))(),!p.ok){if(p.status===503)throw new Error("Compute server is offline or unreachable. Please try again later.");let d=await p.text().catch(()=>""),h={};try{h=JSON.parse(d)}catch (e3){d&&console.warn(`[Compute] non-JSON error body (HTTP ${p.status}):`,d.slice(0,300))}if(p.status===429){let y=Number(p.headers.get("Retry-After"))||Number(h.retryAfter)||5;t=Date.now()+y*1e3,(_nullishCoalesce(e.onRateLimited, () => (te)))(y)}throw new Error(h.message||`Compute error (HTTP ${p.status})`)}let A=performance.now()-u,z=performance.now(),N=await p.text();if(o.aborted)return s("[Compute] solve aborted mid-download \u2014 empty result discarded"),{outputs:{}};let T=performance.now()-z,U=N.length,S;try{S=JSON.parse(N)}catch(d){throw new Error("Received an invalid response from the server. Reload the page and try again.",{cause:d})}let M=new (0, _compute.GrasshopperResponseProcessor)(S,!1),D=e.meshes?await l():null,q=D!==null&&e.meshes?await e.meshes.extract(M.response,{debug:r,rhino:D}):[],K={};for(let d of w){let h=M.getValue({byId:d.id},{parseValues:!0});K[d.id]=_nullishCoalesce(h, () => ((d.nickname?M.getValue({byName:d.nickname},{parseValues:!0}):void 0)))}if(r){let d=oe(p.headers.get("Server-Timing")),h=_nullishCoalesce(d.total, () => (0)),y=Math.max(0,A-h),E=U/(1024*1024),L=E>=1?`${E.toFixed(2)} MB`:`${(U/1024).toFixed(0)} KB`,W=T>0?(E/(T/1e3)).toFixed(1):"\u221E",J=_optionalChain([performance, 'access', _27 => _27.memory, 'optionalAccess', _28 => _28.usedJSHeapSize]),_=J!==void 0?` | heap=${(J/(1024*1024)).toFixed(0)} MB`:"";if(s(`[Compute] req=${(g.length/1024).toFixed(0)}KB (values ${(m/1024).toFixed(0)}KB) | ttfb=${A.toFixed(0)}ms (network\u2248${y.toFixed(0)} + server ${h.toFixed(0)}) | download=${T.toFixed(0)}ms (${L} @ ${W} MB/s)${_}`),d.selva_cache!==void 0){let H=d.selva_cache?"HIT \u2014 served without calling compute":"miss \u2014 solved on Rhino.Compute";s(`[Compute] \u2514\u2500 cache: ${H}`)}}return{outputs:K,meshes:q,errors:_nullishCoalesce(S.errors, () => ([])),warnings:_nullishCoalesce(S.warnings, () => ([]))}}}exports.applySolveResult = V; exports.applyValueChange = C; exports.buildInitialValues = R; exports.clearExternalValue = Q; exports.createAsyncThrottle = F; exports.createComputeFetchSolveFn = ie; exports.createRequestResponseDriver = ne; exports.createSolveMemo = j; exports.createSolveSession = ee; exports.getExternalInputs = I; exports.makeInitialFlags = b; exports.pickInputValues = P; exports.readExternalValue = x; exports.stableInputKey = O; exports.writeExternalValue = Y;
|
|
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 _="external";function E(e,r){return`${_}:${e}:${r}`}function X(e){let{scopeKey:r,inputId:t,value:n}=e;!r||!t||typeof sessionStorage>"u"||sessionStorage.setItem(E(r,t),JSON.stringify(n))}function x(e){let{scopeKey:r,inputId:t}=e;if(!r||!t||typeof sessionStorage>"u")return;let n=sessionStorage.getItem(E(r,t));if(n!==null)try{return JSON.parse(n)}catch (e2){return}}function Y(e){let{scopeKey:r,inputId:t}=e;!r||!t||typeof sessionStorage>"u"||sessionStorage.removeItem(E(r,t))}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,t){let n=new Set(I(e).map(s=>s.paramId)),l={};for(let s of e.inputs){if(n.has(s.id)){let i=t({scopeKey:r,inputId:s.id});i!==void 0&&(l[s.id]=i);continue}l[s.id]=_nullishCoalesce(s.default, () => (_schemas.getDefaultValue.call(void 0, s.paramType)))}for(let s of e.outputs)l[s.id]=null;return l}function w(e){let r=e===!1;return{hasPendingChanges:r,hasNeverSolved:r}}function C(e,r,t,n){return e.values[r]=t,n===!1?(e.pendingValues[r]=t,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 t={};for(let n of e.inputs)n.id in r&&(t[n.id]=r[n.id]);return t}function V(e,r){e.error="",e.computeErrors=_nullishCoalesce(r.errors, () => ([])),e.computeWarnings=_nullishCoalesce(r.warnings, () => ([])),e.meshes=_nullishCoalesce(r.meshes, () => ([]));let{meshes:t,...n}=r;return e.lastResult=n,Object.assign(e.values,r.outputs),e.pendingValues={},e.hasPendingChanges=!1,e.hasNeverSolved=!1,e}function Z(e){let r=e.schema,t=w(_optionalChain([r, 'optionalAccess', _5 => _5.instanceSolve])),n={values:b(r,e.scopeKey,x),error:"",computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:t.hasPendingChanges,hasNeverSolved:t.hasNeverSolved,lastResult:null},l=new Set,s=()=>l.forEach(o=>o());function i(){e.driver.solve({...P(r,n.values)}),s()}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,a,u=!1){let{shouldSolve:p}=C(n,o,a,_optionalChain([r, 'optionalAccess', _6 => _6.instanceSolve]));p||u?(u&&!p&&(n.pendingValues={},n.hasPendingChanges=!1),i()):s()},solve(){i()},loadValues(o){Object.assign(n.values,o),_optionalChain([r, 'optionalAccess', _7 => _7.instanceSolve])!==!1?i():(n.hasPendingChanges=!0,s())},rebuild(o,a){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=b(o,a,x);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():s()},report(o){V(n,o),s()},reportError(o){n.error=o,s()},subscribe(o){return l.add(o),()=>l.delete(o)},notify:s}}function F(e,r){let{runDeadlineMs:t,onChange:n}=r,l=!1,s=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 ${t}ms \u2014 aborting`),_optionalChain([i, 'optionalAccess', _15 => _15.abort, 'call', _16 => _16()])},t);o(!0);try{await e(m,S)}catch(h){(!(h instanceof Error)||h.name!=="AbortError"&&h.name!=="TimeoutError")&&console.error("[Solve/throttle] unhandled error in run:",h)}finally{if(clearTimeout(y),o(!1),i=null,s!==null){let h=s;s=null,u(h)}}}function p(m){l?(s!==null&&console.debug("[Solve/throttle] superseded pending run (latest-wins)"),s=m):u(m)}function f(){s=null,a()}return{trigger:p,get isRunning(){return l},get hasPending(){return s!==null},cancel:f}}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:t}=e,n=new Map;function l(o){_optionalChain([o, 'access', _17 => _17.meshes, 'optionalAccess', _18 => _18.length])&&_optionalChain([t, 'optionalAccess', _19 => _19.release, 'call', _20 => _20(o.meshes)])}function s(o){return!t||!_optionalChain([o, 'access', _21 => _21.meshes, 'optionalAccess', _22 => _22.length])?o:{...o,meshes:t.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})`),s(u)},set(o,a){let u=O(o);for(i(u),n.set(u,s(a));n.size>r;){let p=n.keys().next().value;if(p===void 0)break;i(p),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,t){let{meshPolicy:n,solveDeadlineMs:l,onChange:s}=t,i=j({meshPolicy:n}),o=F(async(a,u)=>{let p=i.get(a);if(p!==void 0){if(u.aborted)return;r().report(p);return}try{let f=await e(a,u);if(u.aborted){console.debug("[Solve/driver] solve completed after abort \u2014 result discarded");return}let m={...f,values:a};i.set(a,m),r().report(m)}catch(f){if(u.aborted){console.debug("[Solve/driver] solve aborted (superseded, cancelled, or timed out)");return}console.warn("[Solve/driver] solve failed:",f),r().reportError(f instanceof Error?f.message:String(f))}},{runDeadlineMs:l,onChange:s});return{solve(a){o.trigger(a)},cancel(){o.cancel()},get isSolving(){return o.isRunning},clearCache(){i.clear()}}}var _compute = require('@selvajs/compute');function re(e){let r={};if(!e)return r;for(let t of e.split(",")){let[n,...l]=t.split(";").map(o=>o.trim()),s=l.find(o=>o.startsWith("dur="));if(!n||!s)continue;let i=Number(s.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)),t=(...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 s=0;return async(i,o)=>{let a=s-Date.now();if(a>0)throw new Error(`Rate limit reached. Try again in ${Math.ceil(a/1e3)}s.`);let u=performance.now(),p=e.inputs(),f=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:p,values:i,definitionUrl:e.definitionUrl(),...S?{versionId:S}:m==="draft"?{channel:"draft"}:{}}),h=JSON.stringify(i).length,d;try{d=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:y,signal:o})}catch(c){if(o.aborted)return t("[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(o.aborted)return t("[Compute] solve aborted after headers \u2014 empty result discarded"),{outputs:{}};let J=d.redirected&&new URL(d.url).pathname==="/login";if((d.status===401||J)&&(_nullishCoalesce(e.onSessionExpired, () => (te)))(),!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(()=>""),v={};try{v=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 R=Number(d.headers.get("Retry-After"))||Number(v.retryAfter)||5;s=Date.now()+R*1e3,(_nullishCoalesce(e.onRateLimited, () => (oe)))(R)}throw new Error(v.message||`Compute error (HTTP ${d.status})`)}let N=performance.now()-u,B=performance.now(),A=await d.text();if(o.aborted)return t("[Compute] solve aborted mid-download \u2014 empty result discarded"),{outputs:{}};let T=performance.now()-B,D=A.length,g;try{g=JSON.parse(A)}catch(c){throw new Error("Received an invalid response from the server. Reload the page and try again.",{cause:c})}let M=new (0, _compute.GrasshopperResponseProcessor)(g,!1),z=e.meshes?await e.meshes.extract(M.response,{debug:r,rhino:await l()}):[],U={};for(let c of f){let v=M.getValue({byId:c.id},{parseValues:!0});U[c.id]=_nullishCoalesce(v, () => ((c.nickname?M.getValue({byName:c.nickname},{parseValues:!0}):void 0)))}if(r){let c=re(d.headers.get("Server-Timing")),v=_nullishCoalesce(c.total, () => (0)),R=Math.max(0,N-v),k=D/(1024*1024),q=k>=1?`${k.toFixed(2)} MB`:`${(D/1024).toFixed(0)} KB`,W=T>0?(k/(T/1e3)).toFixed(1):"\u221E",K=_optionalChain([performance, 'access', _27 => _27.memory, 'optionalAccess', _28 => _28.usedJSHeapSize]),G=K!==void 0?` | heap=${(K/(1024*1024)).toFixed(0)} MB`:"";if(t(`[Compute] req=${(y.length/1024).toFixed(0)}KB (values ${(h/1024).toFixed(0)}KB) | ttfb=${N.toFixed(0)}ms (network\u2248${R.toFixed(0)} + server ${v.toFixed(0)}) | download=${T.toFixed(0)}ms (${q} @ ${W} MB/s)${G}`),c.selva_cache!==void 0){let H=c.selva_cache?"HIT \u2014 served without calling compute":"miss \u2014 solved on Rhino.Compute";t(`[Compute] \u2514\u2500 cache: ${H}`)}}return{outputs:U,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 = x; exports.stableInputKey = O; exports.writeExternalValue = X;
|
|
2
2
|
//# sourceMappingURL=client.cjs.map
|
package/dist/client.d.cts
CHANGED
|
@@ -1,40 +1,124 @@
|
|
|
1
1
|
import { UISchema } from '@selvajs/schemas';
|
|
2
|
-
import { a as SolveResult, S as SolveFn } from './solve-fn-
|
|
2
|
+
import { a as SolveResult, S as SolveFn } from './solve-fn-DrRgaPZ5.cjs';
|
|
3
3
|
import { GrasshopperComputeResponse } from '@selvajs/compute';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* mesh frames on its own schedule) satisfy the same interface as
|
|
6
|
+
* Transport behind a solve session. `solve()` never returns outputs directly — results
|
|
7
|
+
* go through the session's `report()` instead, so a push transport (e.g. a WebSocket
|
|
8
|
+
* streaming mesh frames on its own schedule) can satisfy the same interface as
|
|
9
|
+
* request/response HTTP.
|
|
10
|
+
*
|
|
11
|
+
* **Stamping `SolveResult.values` is the driver's job, and only some drivers can do it.** A
|
|
12
|
+
* driver that owns a request/response pair must stamp the input set it solved onto the
|
|
13
|
+
* result it reports — the session retains that pair, and a consumer committing what is on
|
|
14
|
+
* screen relies on it being atomic. A push driver that cannot attribute an incoming frame to
|
|
15
|
+
* a request it made must leave `values` absent rather than attach the last set it sent:
|
|
16
|
+
* frames that arrive unsolicited (a replay on connect, a recompute triggered outside the web
|
|
17
|
+
* UI) would otherwise be stamped with an unrelated input set, which is precisely the stale
|
|
18
|
+
* pairing the field exists to prevent.
|
|
9
19
|
*/
|
|
10
20
|
interface SolveDriver {
|
|
11
21
|
solve(values: Record<string, unknown>): void;
|
|
12
22
|
cancel(): void;
|
|
13
23
|
readonly isSolving: boolean;
|
|
14
24
|
/**
|
|
15
|
-
* Drops
|
|
16
|
-
* client-side memo
|
|
17
|
-
*
|
|
25
|
+
* Drops cached solve results. Optional — only the request/response driver has a
|
|
26
|
+
* client-side memo to clear. Call on rebuild: a definition swap must not serve a
|
|
27
|
+
* stale result from the prior definition's input space.
|
|
18
28
|
*/
|
|
19
29
|
clearCache?(): void;
|
|
20
30
|
}
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `TMesh` defaults to `unknown` because nothing in this package inspects meshes; a host that
|
|
25
|
-
* knows its concrete mesh type (a three.js viewer) narrows it at its own seam.
|
|
26
|
-
*/
|
|
31
|
+
/** `TMesh` defaults to `unknown`: nothing here inspects meshes. A host with a concrete
|
|
32
|
+
* mesh type (e.g. a three.js viewer) narrows it at its own seam. */
|
|
27
33
|
interface SolveReporter<TMesh = unknown> {
|
|
28
34
|
report(result: SolveResult<TMesh>): void;
|
|
29
35
|
reportError(message: string): void;
|
|
30
36
|
}
|
|
31
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
|
+
|
|
32
109
|
interface SolveSession {
|
|
33
110
|
readonly values: Record<string, unknown>;
|
|
34
111
|
readonly error: string;
|
|
35
112
|
readonly computeErrors: string[];
|
|
36
113
|
readonly computeWarnings: string[];
|
|
37
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;
|
|
38
122
|
readonly hasPendingChanges: boolean;
|
|
39
123
|
readonly hasNeverSolved: boolean;
|
|
40
124
|
readonly isSolving: boolean;
|
|
@@ -59,45 +143,36 @@ interface SolveSessionArgs {
|
|
|
59
143
|
declare function createSolveSession(args: SolveSessionArgs): SolveSession;
|
|
60
144
|
|
|
61
145
|
/**
|
|
62
|
-
* Deterministic string key for a set of input values.
|
|
63
|
-
*
|
|
64
|
-
* server's stable-input keying intent. Values are plain JSON — the projected solve inputs
|
|
65
|
-
* never contain functions or cycles.
|
|
146
|
+
* Deterministic string key for a set of input values. Sorts object keys at every level so
|
|
147
|
+
* two logically-equal inputs built in different key order collide.
|
|
66
148
|
*/
|
|
67
149
|
declare function stableInputKey(values: Record<string, unknown>): string;
|
|
68
150
|
/**
|
|
69
|
-
* Omit
|
|
70
|
-
* stores and serves them by reference, which is
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* @property clone - Called on the way in AND on every hit, so neither the stored entry nor
|
|
74
|
-
* a previously served one can be disposed by the other's owner.
|
|
75
|
-
* @property release - Must NOT free anything shared with live content — see
|
|
76
|
-
* `releaseSceneObjects` in `@selvajs/visualization/parse`, which skips materials for
|
|
77
|
-
* exactly that reason.
|
|
151
|
+
* Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then
|
|
152
|
+
* stores and serves them by reference, which is safe only because nothing frees them behind
|
|
153
|
+
* its back.
|
|
78
154
|
*/
|
|
79
155
|
interface MeshPolicy<TMesh> {
|
|
156
|
+
/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */
|
|
80
157
|
clone(meshes: TMesh[]): TMesh[];
|
|
158
|
+
/** Must not free anything shared with live content (e.g. materials another object still uses). */
|
|
81
159
|
release(meshes: TMesh[]): void;
|
|
82
160
|
}
|
|
83
161
|
interface SolveMemoOptions<TMesh> {
|
|
84
|
-
/**
|
|
85
|
-
* Entry cap (entries, not bytes). Solve results can be large, so the default is
|
|
86
|
-
* deliberately small — this targets the tight slider-scrub loop, not a durable cache.
|
|
87
|
-
*/
|
|
162
|
+
/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */
|
|
88
163
|
max?: number;
|
|
89
164
|
meshPolicy?: MeshPolicy<TMesh>;
|
|
90
165
|
}
|
|
91
166
|
interface SolveMemo<TMesh = unknown> {
|
|
92
167
|
get(values: Record<string, unknown>): SolveResult<TMesh> | undefined;
|
|
93
|
-
/** Evicts the LRU tail if `set` pushes the map past its cap. */
|
|
94
168
|
set(values: Record<string, unknown>, result: SolveResult<TMesh>): void;
|
|
95
169
|
clear(): void;
|
|
96
170
|
}
|
|
97
171
|
declare function createSolveMemo<TMesh = unknown>(options?: SolveMemoOptions<TMesh>): SolveMemo<TMesh>;
|
|
98
172
|
|
|
99
173
|
interface RequestResponseDriverOptions<TMesh> {
|
|
100
|
-
|
|
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;
|
|
101
176
|
onChange?: () => void;
|
|
102
177
|
/** Mesh ownership policy for the result memo — see `MeshPolicy` (solve-memo.ts). */
|
|
103
178
|
meshPolicy?: MeshPolicy<TMesh>;
|
|
@@ -106,58 +181,7 @@ interface RequestResponseDriverOptions<TMesh> {
|
|
|
106
181
|
* `getReporter` is lazy (`() => session`) because the session and driver reference each
|
|
107
182
|
* other — the host builds the driver first, then the session, then can hand this back.
|
|
108
183
|
*/
|
|
109
|
-
declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options
|
|
110
|
-
|
|
111
|
-
interface ExternalValueRef {
|
|
112
|
-
scopeKey: string;
|
|
113
|
-
inputId: string;
|
|
114
|
-
}
|
|
115
|
-
declare function writeExternalValue(args: ExternalValueRef & {
|
|
116
|
-
value: unknown;
|
|
117
|
-
}): void;
|
|
118
|
-
declare function readExternalValue(ref: ExternalValueRef): unknown | undefined;
|
|
119
|
-
declare function clearExternalValue(ref: ExternalValueRef): void;
|
|
120
|
-
interface ExternalInput {
|
|
121
|
-
paramId: string;
|
|
122
|
-
displayName: string;
|
|
123
|
-
}
|
|
124
|
-
declare function getExternalInputs(schema: UISchema): ExternalInput[];
|
|
125
|
-
|
|
126
|
-
interface SolveSessionState {
|
|
127
|
-
values: Record<string, unknown>;
|
|
128
|
-
error: string;
|
|
129
|
-
computeErrors: string[];
|
|
130
|
-
computeWarnings: string[];
|
|
131
|
-
meshes: unknown[];
|
|
132
|
-
/** Values changed since the last solve, in manual (instanceSolve === false) mode. */
|
|
133
|
-
pendingValues: Record<string, unknown>;
|
|
134
|
-
hasPendingChanges: boolean;
|
|
135
|
-
hasNeverSolved: boolean;
|
|
136
|
-
}
|
|
137
|
-
type ExternalReader = (ref: ExternalValueRef) => unknown | undefined;
|
|
138
|
-
/**
|
|
139
|
-
* Client-sourced inputs are hydrated from `read` and left absent when no stored value
|
|
140
|
-
* exists, so the missing-inputs panel can detect them. Other inputs fall back to their
|
|
141
|
-
* declared default, then the paramType default.
|
|
142
|
-
*/
|
|
143
|
-
declare function buildInitialValues(schema: UISchema, scopeKey: string, read: ExternalReader): Record<string, unknown>;
|
|
144
|
-
/** Manual-solve schemas (instanceSolve === false) start dirty; auto-solve schemas start clean. */
|
|
145
|
-
declare function makeInitialFlags(instanceSolve: boolean | undefined): {
|
|
146
|
-
hasPendingChanges: boolean;
|
|
147
|
-
hasNeverSolved: boolean;
|
|
148
|
-
};
|
|
149
|
-
/** Manual mode defers (records the pending value, raises the dirty flag) instead of solving. */
|
|
150
|
-
declare function applyValueChange(state: SolveSessionState, id: string, value: unknown, instanceSolve: boolean | undefined): {
|
|
151
|
-
state: SolveSessionState;
|
|
152
|
-
shouldSolve: boolean;
|
|
153
|
-
};
|
|
154
|
-
/**
|
|
155
|
-
* Solve outputs live in the same `values` map as inputs (so widgets like dynamic value
|
|
156
|
-
* lists can read them) but must not be echoed back to the driver: a measured 6.4 MB
|
|
157
|
-
* options-list output showed this can re-upload a multi-MB payload no backend reads.
|
|
158
|
-
*/
|
|
159
|
-
declare function pickInputValues(schema: UISchema | undefined, values: Record<string, unknown>): Record<string, unknown>;
|
|
160
|
-
declare function applySolveResult(state: SolveSessionState, result: SolveResult): SolveSessionState;
|
|
184
|
+
declare function createRequestResponseDriver<TMesh = unknown>(onSolve: SolveFn<TMesh>, getReporter: () => SolveReporter<TMesh>, options: RequestResponseDriverOptions<TMesh>): SolveDriver;
|
|
161
185
|
|
|
162
186
|
/**
|
|
163
187
|
* Single-in-flight, latest-wins throttle for any async operation: only one run at a
|
|
@@ -169,7 +193,14 @@ declare function applySolveResult(state: SolveSessionState, result: SolveResult)
|
|
|
169
193
|
* directly reads correct values but won't trigger a re-render.
|
|
170
194
|
*/
|
|
171
195
|
interface AsyncThrottleOptions {
|
|
172
|
-
|
|
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;
|
|
173
204
|
onChange?: () => void;
|
|
174
205
|
}
|
|
175
206
|
interface AsyncThrottle<T> {
|
|
@@ -178,7 +209,7 @@ interface AsyncThrottle<T> {
|
|
|
178
209
|
readonly hasPending: boolean;
|
|
179
210
|
cancel: () => void;
|
|
180
211
|
}
|
|
181
|
-
declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options
|
|
212
|
+
declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options: AsyncThrottleOptions): AsyncThrottle<T>;
|
|
182
213
|
|
|
183
214
|
/**
|
|
184
215
|
* `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a
|
|
@@ -229,6 +260,6 @@ interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
|
|
|
229
260
|
/** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
|
|
230
261
|
onSessionExpired?: () => void;
|
|
231
262
|
}
|
|
232
|
-
declare function createComputeFetchSolveFn<TMesh = unknown, TRhino = unknown>(opts: ComputeFetchSolveFnOptions<TMesh, TRhino>): SolveFn<TMesh>;
|
|
263
|
+
declare function createComputeFetchSolveFn<TMesh = unknown, TRhino = unknown>(opts: ComputeFetchSolveFnOptions<TMesh, TRhino>): SolveFn<TMesh, GrasshopperComputeResponse>;
|
|
233
264
|
|
|
234
|
-
export { type AsyncThrottle, type ComputeFetchSolveFnOptions, type ExternalInput, type ExternalReader, type ExternalValueRef, type MeshPolicy, type RequestResponseDriverOptions, type SolveDriver, SolveFn, type SolveMemo, type SolveMemoOptions, type SolveReporter, SolveResult, type SolveSession, type SolveSessionArgs, type SolveSessionState, applySolveResult, applyValueChange, buildInitialValues, clearExternalValue, createAsyncThrottle, createComputeFetchSolveFn, createRequestResponseDriver, createSolveMemo, createSolveSession, getExternalInputs, makeInitialFlags, pickInputValues, readExternalValue, stableInputKey, writeExternalValue };
|
|
265
|
+
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 };
|