@selvajs/solve 0.2.0-beta.1 → 0.2.0-beta.4
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 +71 -32
- package/dist/client.d.ts +71 -32
- 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 +190 -134
- package/dist/server.d.ts +190 -134
- 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-0hPOtZVD.d.cts +37 -0
- package/dist/solve-fn-0hPOtZVD.d.ts +37 -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 V="external";function g(n,r){return`${V}:${n}:${r}`}function P(n){let{scopeKey:r,inputId:s,value:e}=n;!r||!s||typeof sessionStorage>"u"||sessionStorage.setItem(g(r,s),JSON.stringify(e))}function f(n){let{scopeKey:r,inputId:s}=n;if(!r||!s||typeof sessionStorage>"u")return;let e=sessionStorage.getItem(g(r,s));if(e!==null)try{return JSON.parse(e)}catch (e2){return}}function C(n){let{scopeKey:r,inputId:s}=n;!r||!s||typeof sessionStorage>"u"||sessionStorage.removeItem(g(r,s))}function h(n){return _schemas.getInputItems.call(void 0, n).filter(r=>_optionalChain([r, 'access', _ => _.source, 'optionalAccess', _2 => _2.kind])==="client").map(r=>({paramId:r.paramId,displayName:_nullishCoalesce(r.displayName, () => (r.paramId))}))}function v(n,r,s){let e=new Set(h(n).map(t=>t.paramId)),a={};for(let t of n.inputs){if(e.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 n.outputs)a[t.id]=null;return a}function m(n){let r=n===!1;return{hasPendingChanges:r,hasNeverSolved:r}}function S(n,r,s,e){return n.values[r]=s,e===!1?(n.pendingValues[r]=s,n.hasPendingChanges=!0,{state:n,shouldSolve:!1}):{state:n,shouldSolve:!0}}function y(n,r){if(!_optionalChain([n, 'optionalAccess', _3 => _3.inputs]))return r;let s={};for(let e of n.inputs)e.id in r&&(s[e.id]=r[e.id]);return s}function x(n,r){return n.error="",n.computeErrors=_nullishCoalesce(r.errors, () => ([])),n.computeWarnings=_nullishCoalesce(r.warnings, () => ([])),n.meshes=_nullishCoalesce(r.meshes, () => ([])),Object.assign(n.values,r.outputs),n.pendingValues={},n.hasPendingChanges=!1,n.hasNeverSolved=!1,n}function A(n){let r=n.schema,s=m(_optionalChain([r, 'optionalAccess', _4 => _4.instanceSolve])),e={values:v(r,n.scopeKey,f),error:"",computeErrors:[],computeWarnings:[],meshes:[],pendingValues:{},hasPendingChanges:s.hasPendingChanges,hasNeverSolved:s.hasNeverSolved},a=new Set,t=()=>a.forEach(o=>o());function i(){n.driver.solve({...y(r,e.values)}),t()}return{get values(){return e.values},get error(){return e.error},get computeErrors(){return e.computeErrors},get computeWarnings(){return e.computeWarnings},get meshes(){return e.meshes},get hasPendingChanges(){return e.hasPendingChanges},get hasNeverSolved(){return e.hasNeverSolved},get isSolving(){return n.driver.isSolving},setValue(o,l,u=!1){let{shouldSolve:c}=S(e,o,l,_optionalChain([r, 'optionalAccess', _5 => _5.instanceSolve]));c||u?(u&&!c&&(e.pendingValues={},e.hasPendingChanges=!1),i()):t()},solve(){i()},loadValues(o){Object.assign(e.values,o),_optionalChain([r, 'optionalAccess', _6 => _6.instanceSolve])!==!1?i():(e.hasPendingChanges=!0,t())},rebuild(o,l){r=o,_optionalChain([n, 'access', _7 => _7.driver, 'access', _8 => _8.clearCache, 'optionalCall', _9 => _9()]),e.meshes=[],e.error="",e.computeErrors=[],e.computeWarnings=[],e.pendingValues={},e.values=v(o,l,f);let u=m(_optionalChain([o, 'optionalAccess', _10 => _10.instanceSolve]));e.hasPendingChanges=u.hasPendingChanges,e.hasNeverSolved=u.hasNeverSolved,o&&Object.keys(e.values).length>0&&o.instanceSolve!==!1?i():t()},report(o){x(e,o),t()},reportError(o){e.error=o,t()},subscribe(o){return a.add(o),()=>a.delete(o)},notify:t}}function R(n,r={}){let{timeout:s=6e4,onChange:e}=r,a=!1,t=null,i=null;function o(d){a!==d&&(a=d,_optionalChain([e, 'optionalCall', _11 => _11()]))}function l(){_optionalChain([i, 'optionalAccess', _12 => _12.abort, 'call', _13 => _13()]),i=null}async function u(d){l(),i=new AbortController;let{signal:M}=i,k=setTimeout(()=>{console.warn(`[Solve/throttle] run exceeded ${s}ms \u2014 aborting`),_optionalChain([i, 'optionalAccess', _14 => _14.abort, 'call', _15 => _15()])},s);o(!0);try{await n(d,M)}catch(p){(!(p instanceof Error)||p.name!=="AbortError"&&p.name!=="TimeoutError")&&console.error("[Solve/throttle] unhandled error in run:",p)}finally{if(clearTimeout(k),o(!1),i=null,t!==null){let p=t;t=null,u(p)}}}function c(d){a?(t!==null&&console.debug("[Solve/throttle] superseded pending run (latest-wins)"),t=d):u(d)}function I(){t=null,l()}return{trigger:c,get isRunning(){return a},get hasPending(){return t!==null},cancel:I}}function b(n){return T(n)}function T(n){if(n===null||typeof n!="object")return _nullishCoalesce(JSON.stringify(n), () => ("null"));if(Array.isArray(n))return`[${n.map(T).join(",")}]`;let r=n;return`{${Object.keys(r).sort().map(e=>`${JSON.stringify(e)}:${T(r[e])}`).join(",")}}`}function E(n={}){let{max:r=16,meshPolicy:s}=n,e=new Map;function a(o){_optionalChain([o, 'access', _16 => _16.meshes, 'optionalAccess', _17 => _17.length])&&_optionalChain([s, 'optionalAccess', _18 => _18.release, 'call', _19 => _19(o.meshes)])}function t(o){return!s||!_optionalChain([o, 'access', _20 => _20.meshes, 'optionalAccess', _21 => _21.length])?o:{...o,meshes:s.clone(o.meshes)}}function i(o){let l=e.get(o);l!==void 0&&(e.delete(o),a(l))}return{get(o){let l=b(o),u=e.get(l);if(u!==void 0)return e.delete(l),e.set(l,u),console.info(`[Solve/memo] HIT \u2014 served from client memo (${e.size}/${r})`),t(u)},set(o,l){let u=b(o);for(i(u),e.set(u,t(l));e.size>r;){let c=e.keys().next().value;if(c===void 0)break;i(c),console.info(`[Solve/memo] evicted LRU entry (cap ${r})`)}},clear(){e.size>0&&console.info(`[Solve/memo] cleared ${e.size} entries (definition changed)`),e.forEach(a),e.clear()}}}function O(n,r,s={}){let{meshPolicy:e,...a}=s,t=E({meshPolicy:e}),i=R(async(o,l)=>{let u=t.get(o);if(u!==void 0){if(l.aborted)return;r().report(u);return}try{let c=await n(o,l);if(l.aborted){console.debug("[Solve/driver] solve completed after abort \u2014 result discarded");return}t.set(o,c),r().report(c)}catch(c){if(l.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))}},a);return{solve(o){i.trigger(o)},cancel(){i.cancel()},get isSolving(){return i.isRunning},clearCache(){t.clear()}}}exports.applySolveResult = x; exports.applyValueChange = S; exports.buildInitialValues = v; exports.clearExternalValue = C; exports.createAsyncThrottle = R; exports.createRequestResponseDriver = O; exports.createSolveMemo = E; exports.createSolveSession = A; exports.getExternalInputs = h; exports.makeInitialFlags = m; exports.pickInputValues = y; exports.readExternalValue = f; exports.stableInputKey = b; exports.writeExternalValue = P;
|
|
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;
|
|
2
2
|
//# sourceMappingURL=client.cjs.map
|
package/dist/client.d.cts
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
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-0hPOtZVD.cjs';
|
|
3
|
+
import { GrasshopperComputeResponse } from '@selvajs/compute';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 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.
|
|
8
10
|
*/
|
|
9
11
|
interface SolveDriver {
|
|
10
12
|
solve(values: Record<string, unknown>): void;
|
|
11
13
|
cancel(): void;
|
|
12
14
|
readonly isSolving: boolean;
|
|
13
15
|
/**
|
|
14
|
-
* Drops
|
|
15
|
-
* client-side memo
|
|
16
|
-
*
|
|
16
|
+
* Drops cached solve results. Optional — only the request/response driver has a
|
|
17
|
+
* client-side memo to clear. Call on rebuild: a definition swap must not serve a
|
|
18
|
+
* stale result from the prior definition's input space.
|
|
17
19
|
*/
|
|
18
20
|
clearCache?(): void;
|
|
19
21
|
}
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `TMesh` defaults to `unknown` because nothing in this package inspects meshes; a host that
|
|
24
|
-
* knows its concrete mesh type (a three.js viewer) narrows it at its own seam.
|
|
25
|
-
*/
|
|
22
|
+
/** `TMesh` defaults to `unknown`: nothing here inspects meshes. A host with a concrete
|
|
23
|
+
* mesh type (e.g. a three.js viewer) narrows it at its own seam. */
|
|
26
24
|
interface SolveReporter<TMesh = unknown> {
|
|
27
25
|
report(result: SolveResult<TMesh>): void;
|
|
28
26
|
reportError(message: string): void;
|
|
@@ -58,38 +56,28 @@ interface SolveSessionArgs {
|
|
|
58
56
|
declare function createSolveSession(args: SolveSessionArgs): SolveSession;
|
|
59
57
|
|
|
60
58
|
/**
|
|
61
|
-
* Deterministic string key for a set of input values.
|
|
62
|
-
*
|
|
63
|
-
* server's stable-input keying intent. Values are plain JSON — the projected solve inputs
|
|
64
|
-
* never contain functions or cycles.
|
|
59
|
+
* Deterministic string key for a set of input values. Sorts object keys at every level so
|
|
60
|
+
* two logically-equal inputs built in different key order collide.
|
|
65
61
|
*/
|
|
66
62
|
declare function stableInputKey(values: Record<string, unknown>): string;
|
|
67
63
|
/**
|
|
68
|
-
* Omit
|
|
69
|
-
* stores and serves them by reference, which is
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* @property clone - Called on the way in AND on every hit, so neither the stored entry nor
|
|
73
|
-
* a previously served one can be disposed by the other's owner.
|
|
74
|
-
* @property release - Must NOT free anything shared with live content — see
|
|
75
|
-
* `releaseSceneObjects` in `@selvajs/visualization/parse`, which skips materials for
|
|
76
|
-
* exactly that reason.
|
|
64
|
+
* Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then
|
|
65
|
+
* stores and serves them by reference, which is safe only because nothing frees them behind
|
|
66
|
+
* its back.
|
|
77
67
|
*/
|
|
78
68
|
interface MeshPolicy<TMesh> {
|
|
69
|
+
/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */
|
|
79
70
|
clone(meshes: TMesh[]): TMesh[];
|
|
71
|
+
/** Must not free anything shared with live content (e.g. materials another object still uses). */
|
|
80
72
|
release(meshes: TMesh[]): void;
|
|
81
73
|
}
|
|
82
74
|
interface SolveMemoOptions<TMesh> {
|
|
83
|
-
/**
|
|
84
|
-
* Entry cap (entries, not bytes). Solve results can be large, so the default is
|
|
85
|
-
* deliberately small — this targets the tight slider-scrub loop, not a durable cache.
|
|
86
|
-
*/
|
|
75
|
+
/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */
|
|
87
76
|
max?: number;
|
|
88
77
|
meshPolicy?: MeshPolicy<TMesh>;
|
|
89
78
|
}
|
|
90
79
|
interface SolveMemo<TMesh = unknown> {
|
|
91
80
|
get(values: Record<string, unknown>): SolveResult<TMesh> | undefined;
|
|
92
|
-
/** Evicts the LRU tail if `set` pushes the map past its cap. */
|
|
93
81
|
set(values: Record<string, unknown>, result: SolveResult<TMesh>): void;
|
|
94
82
|
clear(): void;
|
|
95
83
|
}
|
|
@@ -179,4 +167,55 @@ interface AsyncThrottle<T> {
|
|
|
179
167
|
}
|
|
180
168
|
declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options?: AsyncThrottleOptions): AsyncThrottle<T>;
|
|
181
169
|
|
|
182
|
-
|
|
170
|
+
/**
|
|
171
|
+
* `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a
|
|
172
|
+
* `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +
|
|
173
|
+
* `SolveEngine` produce server-side) and unpacks the result.
|
|
174
|
+
*
|
|
175
|
+
* Ports the resilience behavior an interactive solve endpoint needs regardless
|
|
176
|
+
* of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response
|
|
177
|
+
* guarding, and abort handling at every await point — so a new consumer gets it
|
|
178
|
+
* for free instead of re-deriving it by hand. Mesh decoding is NOT built in:
|
|
179
|
+
* this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a
|
|
180
|
+
* viewer-enabled consumer supplies its own `meshes.extract` hook (typically
|
|
181
|
+
* `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
|
|
185
|
+
/** The compute endpoint to POST to, e.g. `/api/compute`. */
|
|
186
|
+
endpoint: string;
|
|
187
|
+
/** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */
|
|
188
|
+
definitionUrl: () => string;
|
|
189
|
+
/** Schema input params, sent as the request body's `inputs`. */
|
|
190
|
+
inputs: () => Array<{
|
|
191
|
+
id: string;
|
|
192
|
+
}>;
|
|
193
|
+
/** Schema output params — drives byId/byName output extraction after a solve. */
|
|
194
|
+
outputs: () => Array<{
|
|
195
|
+
id: string;
|
|
196
|
+
nickname?: string;
|
|
197
|
+
}>;
|
|
198
|
+
channel?: () => 'live' | 'draft' | undefined;
|
|
199
|
+
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
|
+
*/
|
|
205
|
+
meshes?: {
|
|
206
|
+
loadRhino: () => Promise<TRhino>;
|
|
207
|
+
extract: (response: GrasshopperComputeResponse, opts: {
|
|
208
|
+
debug: boolean;
|
|
209
|
+
rhino: TRhino;
|
|
210
|
+
}) => TMesh[] | Promise<TMesh[]>;
|
|
211
|
+
};
|
|
212
|
+
/** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */
|
|
213
|
+
debug?: boolean;
|
|
214
|
+
/** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */
|
|
215
|
+
onRateLimited?: (retryAfterSeconds: number) => void;
|
|
216
|
+
/** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
|
|
217
|
+
onSessionExpired?: () => void;
|
|
218
|
+
}
|
|
219
|
+
declare function createComputeFetchSolveFn<TMesh = unknown, TRhino = unknown>(opts: ComputeFetchSolveFnOptions<TMesh, TRhino>): SolveFn<TMesh, GrasshopperComputeResponse>;
|
|
220
|
+
|
|
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 };
|
package/dist/client.d.ts
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
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-0hPOtZVD.js';
|
|
3
|
+
import { GrasshopperComputeResponse } from '@selvajs/compute';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 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.
|
|
8
10
|
*/
|
|
9
11
|
interface SolveDriver {
|
|
10
12
|
solve(values: Record<string, unknown>): void;
|
|
11
13
|
cancel(): void;
|
|
12
14
|
readonly isSolving: boolean;
|
|
13
15
|
/**
|
|
14
|
-
* Drops
|
|
15
|
-
* client-side memo
|
|
16
|
-
*
|
|
16
|
+
* Drops cached solve results. Optional — only the request/response driver has a
|
|
17
|
+
* client-side memo to clear. Call on rebuild: a definition swap must not serve a
|
|
18
|
+
* stale result from the prior definition's input space.
|
|
17
19
|
*/
|
|
18
20
|
clearCache?(): void;
|
|
19
21
|
}
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `TMesh` defaults to `unknown` because nothing in this package inspects meshes; a host that
|
|
24
|
-
* knows its concrete mesh type (a three.js viewer) narrows it at its own seam.
|
|
25
|
-
*/
|
|
22
|
+
/** `TMesh` defaults to `unknown`: nothing here inspects meshes. A host with a concrete
|
|
23
|
+
* mesh type (e.g. a three.js viewer) narrows it at its own seam. */
|
|
26
24
|
interface SolveReporter<TMesh = unknown> {
|
|
27
25
|
report(result: SolveResult<TMesh>): void;
|
|
28
26
|
reportError(message: string): void;
|
|
@@ -58,38 +56,28 @@ interface SolveSessionArgs {
|
|
|
58
56
|
declare function createSolveSession(args: SolveSessionArgs): SolveSession;
|
|
59
57
|
|
|
60
58
|
/**
|
|
61
|
-
* Deterministic string key for a set of input values.
|
|
62
|
-
*
|
|
63
|
-
* server's stable-input keying intent. Values are plain JSON — the projected solve inputs
|
|
64
|
-
* never contain functions or cycles.
|
|
59
|
+
* Deterministic string key for a set of input values. Sorts object keys at every level so
|
|
60
|
+
* two logically-equal inputs built in different key order collide.
|
|
65
61
|
*/
|
|
66
62
|
declare function stableInputKey(values: Record<string, unknown>): string;
|
|
67
63
|
/**
|
|
68
|
-
* Omit
|
|
69
|
-
* stores and serves them by reference, which is
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* @property clone - Called on the way in AND on every hit, so neither the stored entry nor
|
|
73
|
-
* a previously served one can be disposed by the other's owner.
|
|
74
|
-
* @property release - Must NOT free anything shared with live content — see
|
|
75
|
-
* `releaseSceneObjects` in `@selvajs/visualization/parse`, which skips materials for
|
|
76
|
-
* exactly that reason.
|
|
64
|
+
* Omit when results are mesh-free or nothing takes ownership of the meshes — the memo then
|
|
65
|
+
* stores and serves them by reference, which is safe only because nothing frees them behind
|
|
66
|
+
* its back.
|
|
77
67
|
*/
|
|
78
68
|
interface MeshPolicy<TMesh> {
|
|
69
|
+
/** Called on the way in and on every hit, so the stored entry and a served copy are never the same instance. */
|
|
79
70
|
clone(meshes: TMesh[]): TMesh[];
|
|
71
|
+
/** Must not free anything shared with live content (e.g. materials another object still uses). */
|
|
80
72
|
release(meshes: TMesh[]): void;
|
|
81
73
|
}
|
|
82
74
|
interface SolveMemoOptions<TMesh> {
|
|
83
|
-
/**
|
|
84
|
-
* Entry cap (entries, not bytes). Solve results can be large, so the default is
|
|
85
|
-
* deliberately small — this targets the tight slider-scrub loop, not a durable cache.
|
|
86
|
-
*/
|
|
75
|
+
/** Entry cap, not a byte cap. Kept small on purpose: this targets the slider-scrub loop, not a durable cache. */
|
|
87
76
|
max?: number;
|
|
88
77
|
meshPolicy?: MeshPolicy<TMesh>;
|
|
89
78
|
}
|
|
90
79
|
interface SolveMemo<TMesh = unknown> {
|
|
91
80
|
get(values: Record<string, unknown>): SolveResult<TMesh> | undefined;
|
|
92
|
-
/** Evicts the LRU tail if `set` pushes the map past its cap. */
|
|
93
81
|
set(values: Record<string, unknown>, result: SolveResult<TMesh>): void;
|
|
94
82
|
clear(): void;
|
|
95
83
|
}
|
|
@@ -179,4 +167,55 @@ interface AsyncThrottle<T> {
|
|
|
179
167
|
}
|
|
180
168
|
declare function createAsyncThrottle<T>(run: (values: T, signal: AbortSignal) => Promise<void>, options?: AsyncThrottleOptions): AsyncThrottle<T>;
|
|
181
169
|
|
|
182
|
-
|
|
170
|
+
/**
|
|
171
|
+
* `createComputeFetchSolveFn` — a ready-made `SolveFn` that POSTs to a
|
|
172
|
+
* `/api/compute`-shaped HTTP endpoint (the wire contract `runSolvePipeline` +
|
|
173
|
+
* `SolveEngine` produce server-side) and unpacks the result.
|
|
174
|
+
*
|
|
175
|
+
* Ports the resilience behavior an interactive solve endpoint needs regardless
|
|
176
|
+
* of app — 429 cooldown, session-expiry/redirect detection, non-JSON-response
|
|
177
|
+
* guarding, and abort handling at every await point — so a new consumer gets it
|
|
178
|
+
* for free instead of re-deriving it by hand. Mesh decoding is NOT built in:
|
|
179
|
+
* this package stays renderer-agnostic (see `shared/solve-fn.ts`), so a
|
|
180
|
+
* viewer-enabled consumer supplies its own `meshes.extract` hook (typically
|
|
181
|
+
* `getThreeMeshesFromComputeResponse` from `@selvajs/visualization/parse`).
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
interface ComputeFetchSolveFnOptions<TMesh = unknown, TRhino = unknown> {
|
|
185
|
+
/** The compute endpoint to POST to, e.g. `/api/compute`. */
|
|
186
|
+
endpoint: string;
|
|
187
|
+
/** Definition URL (or `local:<guid>`) to solve — read per-solve so it can track a route param. */
|
|
188
|
+
definitionUrl: () => string;
|
|
189
|
+
/** Schema input params, sent as the request body's `inputs`. */
|
|
190
|
+
inputs: () => Array<{
|
|
191
|
+
id: string;
|
|
192
|
+
}>;
|
|
193
|
+
/** Schema output params — drives byId/byName output extraction after a solve. */
|
|
194
|
+
outputs: () => Array<{
|
|
195
|
+
id: string;
|
|
196
|
+
nickname?: string;
|
|
197
|
+
}>;
|
|
198
|
+
channel?: () => 'live' | 'draft' | undefined;
|
|
199
|
+
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
|
+
*/
|
|
205
|
+
meshes?: {
|
|
206
|
+
loadRhino: () => Promise<TRhino>;
|
|
207
|
+
extract: (response: GrasshopperComputeResponse, opts: {
|
|
208
|
+
debug: boolean;
|
|
209
|
+
rhino: TRhino;
|
|
210
|
+
}) => TMesh[] | Promise<TMesh[]>;
|
|
211
|
+
};
|
|
212
|
+
/** Gates ALL console telemetry (timing, cache verdicts, whales, heap). Default `false`. */
|
|
213
|
+
debug?: boolean;
|
|
214
|
+
/** Called on a 429. Default: throws a cooldown-aware Error; the cooldown itself is tracked either way. */
|
|
215
|
+
onRateLimited?: (retryAfterSeconds: number) => void;
|
|
216
|
+
/** Called on a 401 or a login-page redirect. Default: throws an actionable "reload to sign in again" Error. */
|
|
217
|
+
onSessionExpired?: () => void;
|
|
218
|
+
}
|
|
219
|
+
declare function createComputeFetchSolveFn<TMesh = unknown, TRhino = unknown>(opts: ComputeFetchSolveFnOptions<TMesh, TRhino>): SolveFn<TMesh, GrasshopperComputeResponse>;
|
|
220
|
+
|
|
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 };
|
package/dist/client.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getInputItems as
|
|
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};
|
|
2
2
|
//# sourceMappingURL=client.js.map
|