@selvajs/solve 2.0.0-beta.11 → 2.0.0-beta.12
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 +16 -10
- package/dist/abort-CNJ8Sn4g.js +13 -0
- package/dist/abort-CNJ8Sn4g.js.map +1 -0
- package/dist/client.d.ts +33 -134
- package/dist/client.js +335 -240
- package/dist/client.js.map +1 -1
- package/dist/{outcome-DsNmx27P.d.ts → outcome-Bk9mnMi0.d.ts} +9 -28
- package/dist/{outcome-ai_9fm43.js → outcome-DNrffAXp.js} +48 -46
- package/dist/outcome-DNrffAXp.js.map +1 -0
- package/dist/server.d.ts +73 -222
- package/dist/server.js +594 -419
- package/dist/server.js.map +1 -1
- package/dist/shared.d.ts +2 -2
- package/dist/shared.js +1 -1
- package/package.json +5 -13
- package/dist/outcome-ai_9fm43.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outcome-DNrffAXp.js","names":[],"sources":["../src/shared/solve-fn.ts","../src/shared/compute-diagnostics.ts","../src/shared/outcome.ts"],"sourcesContent":["import type { SolveDiagnostic } from '@selvajs/schemas';\n\n/**\n * The solve contract — what a solve returns and what a caller supplies to run one.\n *\n * Lives in `shared/` because both halves of the package speak it: `client/` calls a `SolveFn` and\n * stores its `SolveResult`; `server/` produces the payload one is built from.\n */\n\n/**\n * Marker the Grasshopper Message component prepends to a blocking error.\n *\n * Rhino.Compute flattens runtime messages to plain strings with no component attribution, so\n * this is the only way that path can tell a deliberate block from any other solve error. The\n * local WebSocket path carries `isGate` structurally and does not need it — but the component\n * writes the marker unconditionally, because the same definition runs on both.\n *\n * Changing this string breaks blocking on deployed Selva for every already-published\n * definition, since the text is baked into the .gh file's solve output, not the plugin.\n */\nexport const SOLVE_BLOCKED_MARKER = '[Selva:blocked]';\n\n/**\n * Marker the Message component prepends to a remark or warning — the non-blocking levels.\n *\n * Same reason as `SOLVE_BLOCKED_MARKER`: on Rhino.Compute nothing structural survives, and the\n * UI has to tell a message the author chose to write from an incidental Grasshopper warning.\n * Definitions saved before this existed lack it, so the reader falls back to the component name.\n */\nexport const SOLVE_AUTHORED_MARKER = '[Selva:msg]';\n\n/**\n * Marker for an authored message whose author chose Notify = Log.\n *\n * Still theirs — it keeps its attribution and its place in the message list — but it does not\n * interrupt. Errors never carry it: their result is withheld, so they always interrupt.\n */\nexport const SOLVE_LOG_ONLY_MARKER = '[Selva:log]';\n\n/**\n * One message a solve raised, with who raised it and whether it was deliberate (`isGate`).\n * Generated from `wire-schema.json`; both transports decode into it (see `outcome.ts`).\n */\nexport type { SolveDiagnostic };\n\n/**\n * `TMesh` is deliberately opaque and defaults to `unknown`: this package never inspects meshes, and\n * typing them would drag a renderer dependency into a package whose whole point is not having one.\n * The app that owns assembly (parse → `THREE.Object3D[]`) is the only place that knows the concrete\n * type, and it narrows by writing `SolveResult<THREE.Object3D>` at its own seam.\n */\nexport interface SolveResult<TMesh = unknown, TSource = unknown> {\n\toutputs: Record<string, unknown>;\n\tmeshes?: TMesh[];\n\terrors?: string[];\n\twarnings?: string[];\n\t/** Structured form of `errors`/`warnings`, including remarks. Absent from transports that report neither. */\n\tdiagnostics?: SolveDiagnostic[];\n\t/**\n\t * The definition refused this solve — a guard in the definition raised an error and its\n\t * outputs were withheld deliberately. Distinct from an errored solve that still returned\n\t * values, and from a transport failure: the solve ran and its answer is \"no\". Consumers\n\t * should show `errors` instead of treating the empty outputs as a result.\n\t */\n\tblocked?: boolean;\n\t/**\n\t * The solve was stopped before it finished. Always also `blocked`: what computed before\n\t * the stop is not a result. Absent from a transport that cannot tell.\n\t */\n\taborted?: boolean;\n\t/**\n\t * The unparsed payload this result was built from, passed through verbatim. Opaque here for the\n\t * same reason as `TMesh`: a consumer that must persist or re-submit exactly what it showed the\n\t * user narrows it at its own seam. Travels with the result through the driver's memo, so a\n\t * cached hit carries the source that produced it. Unlike `meshes` it needs no ownership policy —\n\t * it is inert data, not a GPU-backed handle.\n\t *\n\t * Supplied by the `SolveFn`, so it is present only when the one in use sets it\n\t * (`createComputeFetchSolveFn` does). A transport with no raw payload to hand back leaves it\n\t * absent — see `values` on telling absent from unsupported.\n\t */\n\tsource?: TSource;\n\t/**\n\t * The input set that produced this result, stamped by the driver rather than the `SolveFn` — a\n\t * memo hit never calls the `SolveFn`, so a consumer reading values captured inside it would pair\n\t * the on-screen result with whatever solved last. Keeping the pair atomic is the point: a commit\n\t * path holding a `SolveResult` cannot mismatch artifact and inputs.\n\t *\n\t * Present only from a driver that owns a request/response pair (`createRequestResponseDriver`).\n\t * A push driver cannot attribute an incoming frame to a request and must leave this absent\n\t * rather than guess — so `undefined` means \"this transport does not supply it\", not \"no solve\n\t * yet\", and a consumer that needs the pair requires a request/response driver. See `SolveDriver`.\n\t */\n\tvalues?: Record<string, unknown>;\n}\n\n/** Implementations should listen to `signal` and abort/clean up when it fires. */\nexport type SolveFn<TMesh = unknown, TSource = unknown> = (\n\tvalues: Record<string, unknown>,\n\tsignal: AbortSignal\n) => Promise<SolveResult<TMesh, TSource>>;\n","// Rhino.Compute reports runtime messages as flat strings, so everything the UI needs to tell a\n// deliberate message from incidental noise has to be recovered from the text. This is the one\n// place that parsing happens.\n//\n// Two decorations are applied by the server, in this order:\n// \"<author's text>: component \\\"<Name>\\\" (<guid>)\" — LogRuntimeMessages, every message\n// \"<n>. Solution exception:<marker> ...\" — the numbered-error wrapper\n// The local WebSocket path carries the same facts structurally and never comes through here.\n\nimport {\n\tSOLVE_AUTHORED_MARKER,\n\tSOLVE_BLOCKED_MARKER,\n\tSOLVE_LOG_ONLY_MARKER,\n\ttype SolveDiagnostic\n} from './solve-fn.js';\n\n/**\n * Compute's per-message attribution suffix. Anchored to the end and requiring the parenthesised\n * GUID, so an author who writes `component \"x\"` mid-sentence keeps their text intact.\n */\nconst ATTRIBUTION = /:\\s*component\\s+\"([^\"]*)\"\\s*\\(([0-9a-fA-F-]{36}|\\.\\.\\.)\\)\\s*$/;\n\n/** The Selva Message component's own name, as `LogRuntimeMessages` reports it. */\nconst MESSAGE_COMPONENT_NAME = 'Message';\n\nexport interface ParsedComputeMessage {\n\t/** The author's text, with the attribution suffix and any marker removed. */\n\tmessage: string;\n\t/** Component name from the attribution suffix, when present. */\n\tsource?: string;\n\t/** Carried the block marker: a Message component refused the solve. */\n\tblocked: boolean;\n\t/** Carried any marker: a Message component wrote this. */\n\tauthored: boolean;\n\t/** The author chose Notify = Log: list it, but do not interrupt. */\n\tlogOnly: boolean;\n}\n\n/**\n * Splits one Compute message string into the author's text and its attribution.\n *\n * Leaves the text alone when the suffix is absent — an exception thrown by a script component\n * has no attribution and must not be truncated on a guess.\n */\nexport function parseComputeMessage(raw: string): ParsedComputeMessage {\n\tconst blocked = raw.includes(SOLVE_BLOCKED_MARKER);\n\tconst logOnly = !blocked && raw.includes(SOLVE_LOG_ONLY_MARKER);\n\tconst authored = blocked || logOnly || raw.includes(SOLVE_AUTHORED_MARKER);\n\tlet message = raw;\n\n\tlet source: string | undefined;\n\tconst attribution = ATTRIBUTION.exec(message);\n\tif (attribution) {\n\t\tsource = attribution[1] || undefined;\n\t\tmessage = message.slice(0, attribution.index);\n\t}\n\n\tif (authored) {\n\t\tmessage = message\n\t\t\t.replace(SOLVE_BLOCKED_MARKER, '')\n\t\t\t.replace(SOLVE_AUTHORED_MARKER, '')\n\t\t\t.replace(SOLVE_LOG_ONLY_MARKER, '')\n\t\t\t// The numbered wrapper reads as noise next to the author's own sentence. Dropped only\n\t\t\t// when a marker proves the text is one of ours.\n\t\t\t.replace(/^\\s*\\d+\\.\\s*Solution exception:\\s*/, '');\n\t}\n\n\treturn { message: message.replace(/\\s{2,}/g, ' ').trim(), source, blocked, authored, logOnly };\n}\n\n/**\n * Rebuilds structured diagnostics from Compute's `errors`/`warnings` arrays.\n *\n * `isGate` comes from the marker the component writes. The component-name fallback is for\n * definitions saved before the non-blocking marker existed; `obj.Name` is the component's type\n * name, not its nickname, so renaming one on the canvas cannot change how it behaves.\n */\nexport function parseComputeDiagnostics(\n\terrors: readonly string[],\n\twarnings: readonly string[]\n): SolveDiagnostic[] {\n\tconst build = (raw: string, level: 'error' | 'warning'): SolveDiagnostic => {\n\t\tconst { message, source, authored, logOnly } = parseComputeMessage(raw);\n\t\tconst isGate = (authored || source === MESSAGE_COMPONENT_NAME) && !logOnly;\n\t\treturn { level, message, ...(source ? { source } : {}), ...(isGate ? { isGate } : {}) };\n\t};\n\treturn [\n\t\t...errors.map((raw) => build(raw, 'error')),\n\t\t...warnings.map((raw) => build(raw, 'warning'))\n\t];\n}\n","// The one place a solve's verdict becomes a `SolveResult`. The plugin decides the verdict\n// (`SolveOutcome`: diagnostics, blocked, aborted) once, and both transports carry it: the local\n// `outputs` envelope and the VektorNode fork's `selva` block. Each driver decodes it here, so a\n// rule about what a verdict means is written once rather than once per transport.\n//\n// A Compute response without a `selva` block (stock server, older plugin) falls back to\n// `outcomeFromComputeMessages`, which recovers what it can from the flattened strings. It cannot\n// see remarks or an abort.\n\nimport type { SolveOutcome } from '@selvajs/schemas';\nimport { parseComputeDiagnostics } from './compute-diagnostics.js';\nimport { SOLVE_BLOCKED_MARKER, type SolveDiagnostic, type SolveResult } from './solve-fn.js';\n\nexport type { SolveOutcome };\n\nfunction isLevel(value: unknown): value is SolveDiagnostic['level'] {\n\treturn value === 'error' || value === 'warning' || value === 'remark';\n}\n\n/**\n * One wire diagnostic, normalized: an unknown level reads as a remark, text is trimmed, and\n * `source`/`isGate` are omitted rather than null or false. Null when it carries no message.\n */\nexport function decodeDiagnostic(entry: unknown): SolveDiagnostic | null {\n\tif (!entry || typeof entry !== 'object') return null;\n\tconst d = entry as Record<string, unknown>;\n\tconst message = typeof d.message === 'string' ? d.message.trim() : '';\n\tif (!message) return null;\n\tconst source = typeof d.source === 'string' && d.source ? d.source : undefined;\n\treturn {\n\t\tlevel: isLevel(d.level) ? d.level : 'remark',\n\t\tmessage,\n\t\t...(source ? { source } : {}),\n\t\t...(d.isGate === true ? { isGate: true } : {})\n\t};\n}\n\nfunction normalizeOutcome(\n\tentries: readonly unknown[],\n\tblocked: boolean,\n\taborted: boolean\n): SolveOutcome {\n\tconst seen = new Set<string>();\n\tconst diagnostics: SolveDiagnostic[] = [];\n\tfor (const entry of entries) {\n\t\tconst d = decodeDiagnostic(entry);\n\t\tif (!d) continue;\n\t\tconst key = `${d.level}\\u0000${d.message}\\u0000${d.source ?? ''}\\u0000${d.isGate === true}`;\n\t\tif (seen.has(key)) continue;\n\t\tseen.add(key);\n\t\tdiagnostics.push(d);\n\t}\n\treturn { diagnostics, blocked: blocked || aborted, aborted };\n}\n\n/**\n * Normalizes a wire outcome with `decodeDiagnostic`, dropping exact repeats, so the same verdict\n * decodes to the same object from either transport. Null when `wire` is not an outcome at all;\n * the caller then falls back. A verdict without a diagnostic list still counts: reading a refusal\n * as \"no outcome\" would let the solve through.\n */\nexport function decodeOutcome(wire: unknown): SolveOutcome | null {\n\tif (!wire || typeof wire !== 'object') return null;\n\tconst { diagnostics, blocked, aborted } = wire as Record<string, unknown>;\n\tconst hasList = Array.isArray(diagnostics);\n\tif (!hasList && blocked === undefined && aborted === undefined) return null;\n\t// `'true'` too: a flag that arrives stringified must fail closed, not let a refusal through.\n\tconst isTrue = (flag: unknown) => flag === true || flag === 'true';\n\treturn normalizeOutcome(hasList ? diagnostics : [], isTrue(blocked), isTrue(aborted));\n}\n\n/** The legacy verdict, read from Compute's flattened `errors`/`warnings` and their markers. */\nexport function outcomeFromComputeMessages(\n\terrors: readonly unknown[],\n\twarnings: readonly unknown[]\n): SolveOutcome {\n\tconst strings = (list: readonly unknown[]) => list.filter((m) => typeof m === 'string');\n\tconst errorText = strings(errors);\n\treturn normalizeOutcome(\n\t\tparseComputeDiagnostics(errorText, strings(warnings)),\n\t\t// Substring, not prefix: the server numbers each entry (\"1. Solution exception: ...\").\n\t\terrorText.some((e) => e.includes(SOLVE_BLOCKED_MARKER)),\n\t\tfalse\n\t);\n}\n\n/**\n * Builds the result a driver reports. A blocked or aborted outcome is \"no result\" whatever the\n * transport sent along: no outputs, no `source` (it holds the withheld outputs), and an empty mesh\n * list so the viewer clears. `errors` and `warnings` are derived from the diagnostics for hosts\n * that only want text.\n */\nexport function finalizeResult<TMesh, TSource>(\n\tpartial: { outputs: Record<string, unknown>; meshes?: TMesh[]; source?: TSource },\n\toutcome: SolveOutcome\n): SolveResult<TMesh, TSource> {\n\tconst rejected = outcome.blocked || outcome.aborted;\n\tconst meshes = rejected ? [] : partial.meshes;\n\treturn {\n\t\toutputs: rejected ? {} : partial.outputs,\n\t\t...(meshes !== undefined ? { meshes } : {}),\n\t\terrors: outcome.diagnostics.filter((d) => d.level === 'error').map((d) => d.message),\n\t\twarnings: outcome.diagnostics.filter((d) => d.level === 'warning').map((d) => d.message),\n\t\tdiagnostics: outcome.diagnostics,\n\t\tblocked: outcome.blocked,\n\t\taborted: outcome.aborted,\n\t\t...(partial.source !== undefined && !rejected ? { source: partial.source } : {})\n\t};\n}\n"],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { t as SolveInput } from "./solve-input-CUjVGYPC.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
/**
|
|
6
|
-
* Opaque identity of a compute server — construct only via `serverIdentity()`.
|
|
7
|
-
* Kept opaque so "a server is now a pool of URLs behind one id" stays additive.
|
|
8
|
-
*/
|
|
9
|
-
type ServerIdentity = string & {
|
|
10
|
-
readonly __brand: 'ServerIdentity';
|
|
11
|
-
};
|
|
2
|
+
import { UISchema } from "@selvajs/schemas";
|
|
3
|
+
import { GrasshopperClient, GrasshopperComputeResponse, SelvaEventTarget, SolveScheduler } from "@selvajs/compute/grasshopper";
|
|
4
|
+
import { DefinitionRef, DefinitionRef as DefinitionRef$1, ServerTiming, SolveDefinition } from "@selvajs/compute/core";
|
|
12
5
|
/** Minimal resolved-server shape the cache needs (a subset of the app's `ComputeServerConfig`). */
|
|
13
6
|
interface ResolvedServer {
|
|
14
7
|
id: string;
|
|
@@ -16,7 +9,6 @@ interface ResolvedServer {
|
|
|
16
9
|
/** Sent as the `RhinoComputeKey` header. */
|
|
17
10
|
apiKey?: string;
|
|
18
11
|
}
|
|
19
|
-
export declare function serverIdentity(server: Pick<ResolvedServer, 'id'>): ServerIdentity;
|
|
20
12
|
interface CachedClient {
|
|
21
13
|
client: GrasshopperClient;
|
|
22
14
|
scheduler: SolveScheduler;
|
|
@@ -27,68 +19,6 @@ interface CachedClient {
|
|
|
27
19
|
* dumps (incl. geometry) — a superset of `true`, not an independent setting.
|
|
28
20
|
*/
|
|
29
21
|
type ClientCacheDebug = boolean | 'verbose';
|
|
30
|
-
/** Config injected by the consuming app, so this module stays env-agnostic and testable. */
|
|
31
|
-
interface ClientCacheConfig {
|
|
32
|
-
/** Per-solve timeout forwarded to the scheduler (`ComputeLimits.solveDeadlineMs`). */
|
|
33
|
-
solveDeadlineMs: number;
|
|
34
|
-
cachesolve: boolean;
|
|
35
|
-
/** Reference large definitions by server cache key instead of re-uploading. */
|
|
36
|
-
reuseServerDefinitionCache: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Max solves that may wait in the FIFO queue, excluding in-flight solves
|
|
39
|
-
* (capped at `maxConcurrent`, itself driven by the server's probed child
|
|
40
|
-
* count). `0` = unbounded (`ComputeLimits.computeMaxQueueDepth`); a full
|
|
41
|
-
* queue rejects new solves with `QUEUE_FULL`.
|
|
42
|
-
*/
|
|
43
|
-
maxQueueDepth: number;
|
|
44
|
-
/**
|
|
45
|
-
* Max ms a solve may sit queued before executing; `0` = no deadline
|
|
46
|
-
* (`ComputeLimits.computeQueueWaitMs`). Too long a wait rejects with
|
|
47
|
-
* `QUEUE_TIMEOUT`.
|
|
48
|
-
*/
|
|
49
|
-
queueWaitMs: number;
|
|
50
|
-
/**
|
|
51
|
-
* Byte budget for this client's in-process solve cache. Applies per warm
|
|
52
|
-
* client — worst-case heap is this × `maxWarmComputeServers`. `0` disables
|
|
53
|
-
* the cache (`ComputeLimits.computeSolveCacheBytes`, env `COMPUTE_SOLVE_CACHE_MB`).
|
|
54
|
-
*/
|
|
55
|
-
responseCacheMaxBytes: number;
|
|
56
|
-
/**
|
|
57
|
-
* Largest compute response body read; past it the download is cancelled (`too_large`).
|
|
58
|
-
* Default: no limit.
|
|
59
|
-
*/
|
|
60
|
-
maxResponseBytes?: number;
|
|
61
|
-
debug: ClientCacheDebug;
|
|
62
|
-
/** Max distinct warm compute servers before the LRU evicts the oldest. Default 16. */
|
|
63
|
-
maxWarmComputeServers?: number;
|
|
64
|
-
/** Sink for the debug lines. `onDebugLog` only fires when `debug` is not `false`. */
|
|
65
|
-
onDebugLog?: (message: string) => void;
|
|
66
|
-
}
|
|
67
|
-
interface ClientCache {
|
|
68
|
-
/**
|
|
69
|
-
* Get (or create) the warm client + scheduler for a resolved compute server,
|
|
70
|
-
* keyed by its `id`. `definitionGuid`, when present, is stamped as the
|
|
71
|
-
* `X-Selva-Definition` header on this client's outbound solve/IO requests —
|
|
72
|
-
* inert routing/telemetry metadata until a pool router exists.
|
|
73
|
-
*/
|
|
74
|
-
getClient(server: ResolvedServer, opts?: {
|
|
75
|
-
definitionGuid?: string;
|
|
76
|
-
}): Promise<CachedClient>;
|
|
77
|
-
/** Dispose and drop the warm client for `id`, so the next request rebuilds against fresh connection details. */
|
|
78
|
-
evict(id: string | ServerIdentity): void;
|
|
79
|
-
/** Solve-cache counters summed across every warm client. Counters die with the client that owns them, so totals can fall over time. */
|
|
80
|
-
solveCacheStats(): SolveCacheStats;
|
|
81
|
-
/**
|
|
82
|
-
* Drop every retained solve result, keeping the warm clients (and their
|
|
83
|
-
* connections and server-side definition pointers) intact. Nothing expires
|
|
84
|
-
* these on its own — the byte budget is the only other pressure — so this is
|
|
85
|
-
* the operator's release valve when a definition's inputs no longer describe
|
|
86
|
-
* its output, e.g. it reads an external source that has since changed.
|
|
87
|
-
*/
|
|
88
|
-
clearSolveCaches(): void;
|
|
89
|
-
/** Dispose every warm client. Test seam / shutdown hook. */
|
|
90
|
-
disposeAll(): void;
|
|
91
|
-
}
|
|
92
22
|
interface SolveCacheStats {
|
|
93
23
|
warmClients: number;
|
|
94
24
|
entries: number;
|
|
@@ -98,7 +28,6 @@ interface SolveCacheStats {
|
|
|
98
28
|
/** Entries dropped under size/byte pressure (not replacement or manual clears). */
|
|
99
29
|
evictions: number;
|
|
100
30
|
}
|
|
101
|
-
export declare function createClientCache(config: ClientCacheConfig): ClientCache;
|
|
102
31
|
/**
|
|
103
32
|
* In-process cache of `.gh` definition bytes, keyed by immutable **version id**.
|
|
104
33
|
*
|
|
@@ -131,7 +60,7 @@ interface ByteRefOutcome {
|
|
|
131
60
|
/** A definition reference shaped for `@selvajs/compute`'s `DefinitionRef`. */
|
|
132
61
|
interface ByteCacheRef {
|
|
133
62
|
key: string;
|
|
134
|
-
load: () => Promise<Uint8Array>;
|
|
63
|
+
load: (signal?: AbortSignal) => Promise<Uint8Array>;
|
|
135
64
|
outcome: ByteRefOutcome;
|
|
136
65
|
}
|
|
137
66
|
interface ByteCacheStats {
|
|
@@ -141,65 +70,23 @@ interface ByteCacheStats {
|
|
|
141
70
|
entries: number;
|
|
142
71
|
bytes: number;
|
|
143
72
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
73
|
+
/** A pre-solve prep sub-phase: `[label, ms]`, surfaced as a `p_*` Server-Timing entry. */
|
|
74
|
+
type PrepMark = [string, number];
|
|
75
|
+
interface CacheVerdict {
|
|
76
|
+
fromCache: boolean;
|
|
77
|
+
definitionReuploaded?: boolean;
|
|
148
78
|
}
|
|
149
79
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
80
|
+
* skipped = scheduler never materialized bytes (pointer-known solve, the whole point of this
|
|
81
|
+
* path); hit = warm byte cache; miss = fell through to storage.
|
|
152
82
|
*/
|
|
153
|
-
|
|
83
|
+
type DefBytesVerdict = 'skipped' | 'hit' | 'miss';
|
|
154
84
|
/** Compute-response contract version. Bump (and document the change) whenever the envelope's shape changes in a way a consumer could observe. */
|
|
155
85
|
export declare const COMPUTE_CONTRACT_VERSION: 1;
|
|
156
86
|
export declare const COMPUTE_VERSION_HEADER = "X-Selva-Compute-Version";
|
|
157
|
-
|
|
158
|
-
interface SolvePipelineArgs {
|
|
159
|
-
/**
|
|
160
|
-
* Raw `.gh` bytes, or a byte-cache `DefinitionRef` whose bytes the scheduler
|
|
161
|
-
* materializes only when an upload is unavoidable — a pointer-known solve
|
|
162
|
-
* moves zero bytes.
|
|
163
|
-
*/
|
|
164
|
-
definitionSource: SolveDefinition$1;
|
|
165
|
-
/** Mutable load outcome for a `DefinitionRef` source, so the `def_bytes` Server-Timing verdict can be emitted. Omit for raw-bytes solves. */
|
|
166
|
-
byteRefOutcome?: ByteRefOutcome;
|
|
167
|
-
/** Persisted input params; only those with a `paramType` are sent to the solve. */
|
|
168
|
-
inputs: PipelineInput[];
|
|
169
|
-
/**
|
|
170
|
-
* A tree the caller already built with {@link buildSolveInputTree}; skips the
|
|
171
|
-
* pipeline's own build and solves this object directly.
|
|
172
|
-
*
|
|
173
|
-
* A caller coalescing concurrent solves needs this: a single-flight key
|
|
174
|
-
* derived from raw `{inputs, values}` would split two requests that transform
|
|
175
|
-
* to the same tree, which is the identity the scheduler actually caches on.
|
|
176
|
-
*/
|
|
177
|
-
inputTree?: DataTree[];
|
|
178
|
-
/** User-chosen values keyed by input id; missing keys fall back to the schema default. */
|
|
179
|
-
values: Record<string, unknown>;
|
|
180
|
-
client: CachedClient;
|
|
181
|
-
responseMaxBytes: number;
|
|
182
|
-
/** Only phrases the timeout message — the scheduler enforces the deadline itself. */
|
|
183
|
-
solveDeadlineMs: number;
|
|
184
|
-
/** Client's `Accept-Encoding`; gzip is applied only when it advertises `gzip`. */
|
|
185
|
-
acceptEncoding: string;
|
|
186
|
-
/** Per-solve live-event callback the compute server hands to the definition; absent when the caller opened no stream. */
|
|
187
|
-
selvaEvents?: SelvaEventTarget;
|
|
188
|
-
/**
|
|
189
|
-
* Forwarded to the scheduler so a client disconnect cancels the upstream
|
|
190
|
-
* compute call. Its `aborted` flag also distinguishes a client disconnect
|
|
191
|
-
* from the scheduler's own deadline firing.
|
|
192
|
-
*/
|
|
193
|
-
signal: AbortSignal;
|
|
194
|
-
/** Wall-clock origin (`performance.now()` at the top of the request) so `load`/`total` phase timings line up with the caller's pre-solve prep. */
|
|
195
|
-
loadStartMs: number;
|
|
196
|
-
/** Pre-solve "load" phase duration the caller already measured (auth + DB + fetch). */
|
|
197
|
-
defLoadMs: number;
|
|
198
|
-
/** Pre-solve prep sub-phase marks (`[label, ms]`), surfaced verbatim as `p_*` Server-Timing entries. */
|
|
199
|
-
prepMarks?: [string, number][];
|
|
200
|
-
}
|
|
201
|
-
/** Phase timings the pipeline measured; the caller uses them for debug logging. */
|
|
87
|
+
/** What the `Server-Timing` header carries for one caller, as data: read this, don't parse that. */
|
|
202
88
|
interface SolvePhaseMetrics {
|
|
89
|
+
defLoadMs: number;
|
|
203
90
|
treeBuildMs: number;
|
|
204
91
|
solveMs: number;
|
|
205
92
|
serializeMs: number;
|
|
@@ -208,6 +95,11 @@ interface SolvePhaseMetrics {
|
|
|
208
95
|
serializedBytes: number;
|
|
209
96
|
/** Null when compression was skipped. */
|
|
210
97
|
compressedBytes: number | null;
|
|
98
|
+
/** The compute server's own timing; null when it reports none. */
|
|
99
|
+
rhino: ServerTiming | null;
|
|
100
|
+
settle: CacheVerdict;
|
|
101
|
+
/** Absent for raw-bytes solves. */
|
|
102
|
+
defBytes?: DefBytesVerdict;
|
|
211
103
|
}
|
|
212
104
|
/** A ready-to-send response: body + headers + the solve result + phase metrics. */
|
|
213
105
|
interface SolveEnvelope {
|
|
@@ -218,20 +110,21 @@ interface SolveEnvelope {
|
|
|
218
110
|
result: GrasshopperComputeResponse;
|
|
219
111
|
metrics: SolvePhaseMetrics;
|
|
220
112
|
}
|
|
113
|
+
/** Whether an `Accept-Encoding` header takes gzip. `gzip;q=0` is a client refusing it. */
|
|
114
|
+
export declare function acceptsGzip(acceptEncoding: string): boolean;
|
|
221
115
|
/**
|
|
222
|
-
* `ok` carries the envelope; every other variant names an expected failure
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* `durationMs` on error variants is the solve wall time up to the failure, for
|
|
226
|
-
* the metric record.
|
|
116
|
+
* `ok` carries the envelope; every other variant names an expected failure:
|
|
117
|
+
* `SolveEngine.toWebResponse` maps each to a status. `durationMs` on error variants is the solve
|
|
118
|
+
* wall time up to the failure, for the metric record.
|
|
227
119
|
*/
|
|
228
|
-
type
|
|
120
|
+
type SolveEngineOutcome = {
|
|
229
121
|
kind: 'ok';
|
|
230
122
|
envelope: SolveEnvelope;
|
|
231
123
|
solveMs: number;
|
|
232
124
|
errorCount: number;
|
|
233
125
|
warningCount: number;
|
|
234
|
-
} |
|
|
126
|
+
} | SolveFailure;
|
|
127
|
+
type SolveFailure = {
|
|
235
128
|
kind: 'timeout';
|
|
236
129
|
durationMs: number;
|
|
237
130
|
message: string;
|
|
@@ -241,11 +134,15 @@ type SolveOutcome = {
|
|
|
241
134
|
} | {
|
|
242
135
|
kind: 'too_large';
|
|
243
136
|
} |
|
|
244
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Retryable: no result, through no fault of the request, and `retryAfterSeconds` is a backoff
|
|
139
|
+
* hint. Scheduler backpressure rejected the solve before it ran (`queue_full`,
|
|
140
|
+
* `queue_timeout`), or its client was shut down under it (`interrupted`).
|
|
141
|
+
*/
|
|
245
142
|
{
|
|
246
143
|
kind: 'shed';
|
|
247
144
|
durationMs: number;
|
|
248
|
-
reason: 'queue_full' | 'queue_timeout';
|
|
145
|
+
reason: 'queue_full' | 'queue_timeout' | 'interrupted';
|
|
249
146
|
retryAfterSeconds: number;
|
|
250
147
|
message: string;
|
|
251
148
|
} | {
|
|
@@ -253,63 +150,6 @@ type SolveOutcome = {
|
|
|
253
150
|
durationMs: number;
|
|
254
151
|
error: unknown;
|
|
255
152
|
};
|
|
256
|
-
export declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome>;
|
|
257
|
-
/**
|
|
258
|
-
* Re-key a coalesced envelope to a single waiter's `Accept-Encoding`.
|
|
259
|
-
*
|
|
260
|
-
* The single-flight coalescer runs ONE pipeline execution for N identical
|
|
261
|
-
* concurrent solves and hands every waiter the same {@link SolveEnvelope},
|
|
262
|
-
* baked from the FIRST caller's `Accept-Encoding`. A later waiter with a
|
|
263
|
-
* different `Accept-Encoding` would otherwise get a body labelled with the
|
|
264
|
-
* wrong encoding — `Vary` can't help here, since it's one shared object, not a
|
|
265
|
-
* cache lookup. Encoding is deliberately left out of the coalesce key so mixed
|
|
266
|
-
* clients still coalesce; this adapts the shared result to each waiter instead.
|
|
267
|
-
*
|
|
268
|
-
* Only the correctness-critical direction is adapted: a gzip envelope served to
|
|
269
|
-
* a non-gzip waiter is gunzipped back to JSON. The reverse (plain JSON to a
|
|
270
|
-
* gzip-capable waiter) is left uncompressed — correct, just not maximally small.
|
|
271
|
-
*/
|
|
272
|
-
export declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding: string): {
|
|
273
|
-
body: string | Uint8Array;
|
|
274
|
-
headers: Record<string, string>;
|
|
275
|
-
};
|
|
276
|
-
/** The input tree a solve sends: each input's value, or its schema default when it has none. */
|
|
277
|
-
export declare function buildSolveInputTree(inputs: SolveInput[], values: Record<string, unknown>): DataTree[];
|
|
278
|
-
/**
|
|
279
|
-
* In-process single-flight — dogpile protection for every solve.
|
|
280
|
-
*
|
|
281
|
-
* Always on, not gated behind a result cache being configured: the dogpile is worst
|
|
282
|
-
* when nothing else is caching, since N concurrent identical solves each pay a full
|
|
283
|
-
* Rhino round trip (the scheduler doesn't coalesce in-flight requests itself, and
|
|
284
|
-
* Rhino's `cachesolve` still costs a round trip per repeat). An earlier version
|
|
285
|
-
* gated this on cache config and so disabled it in exactly the deployments most
|
|
286
|
-
* exposed to a cold-key stampede — hot public definition plus a deploy.
|
|
287
|
-
*
|
|
288
|
-
* Per app instance, above `ISolveResultCache`. No cross-instance lease (Redis
|
|
289
|
-
* `SET NX`) yet.
|
|
290
|
-
*
|
|
291
|
-
* The shared promise resolves to one value for every waiter, so `work` must return
|
|
292
|
-
* something safe to share by reference — the solve pipeline's envelope qualifies
|
|
293
|
-
* because the app serializes it per response.
|
|
294
|
-
*/
|
|
295
|
-
interface SolveCacheSingleFlight {
|
|
296
|
-
/**
|
|
297
|
-
* Coalesces concurrent calls under the same key: the first caller runs `work`,
|
|
298
|
-
* later callers for that key await the same promise, and the key frees as soon
|
|
299
|
-
* as it settles.
|
|
300
|
-
*
|
|
301
|
-
* `onWaiterJoined` fires only on the owner's call, only while `work` is still
|
|
302
|
-
* running, each time another caller joins. Ownership changes what the owner's
|
|
303
|
-
* abort signal should mean: a solo run can cancel on its own client's
|
|
304
|
-
* disconnect, but a shared run can't without 499-ing every waiter.
|
|
305
|
-
*/
|
|
306
|
-
run<T>(key: string, work: () => Promise<T>, onWaiterJoined?: () => void): Promise<T>;
|
|
307
|
-
inFlight(): number;
|
|
308
|
-
}
|
|
309
|
-
interface SolveCacheSingleFlightOptions {
|
|
310
|
-
onJoin?: (key: string) => void;
|
|
311
|
-
}
|
|
312
|
-
export declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
|
|
313
153
|
/**
|
|
314
154
|
* Subset of `ComputeLimits` (`@selvajs/server/compute`) the engine needs.
|
|
315
155
|
* Pass the resolved object through as-is — extra fields are ignored.
|
|
@@ -326,26 +166,20 @@ interface SolveEngineLimits {
|
|
|
326
166
|
}
|
|
327
167
|
interface SolveEngineOptions {
|
|
328
168
|
limits: SolveEngineLimits;
|
|
329
|
-
|
|
330
|
-
/** Max distinct warm compute servers before the LRU evicts the oldest. Default 16. */
|
|
169
|
+
/** Max distinct warm compute servers before the LRU evicts the oldest. */
|
|
331
170
|
maxWarmComputeServers?: number;
|
|
332
171
|
/** Concise cache/timing logs when truthy; `'verbose'` also dumps full lib-level requests/responses. Default off. */
|
|
333
172
|
debug?: ClientCacheDebug;
|
|
173
|
+
/** Sink for the debug lines. Without it they go nowhere. */
|
|
334
174
|
onDebugLog?: (message: string) => void;
|
|
335
175
|
/** Fired when a caller joins an already-in-flight solve instead of running its own. */
|
|
336
176
|
onSolveCoalesced?: (key: string) => void;
|
|
337
177
|
}
|
|
338
178
|
/**
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
* obtained ahead of time from `engine.definitionRef()` (e.g. to read bytes for
|
|
342
|
-
* schema extraction before solving) is passed through as-is — detected by its
|
|
343
|
-
* `outcome` field, which a plain `DefinitionRef` never has.
|
|
179
|
+
* A `ByteCacheRef` obtained ahead of time from `engine.definitionRef()` (e.g. to
|
|
180
|
+
* read bytes for schema extraction before solving) is passed through as-is.
|
|
344
181
|
*/
|
|
345
|
-
type SolveEngineDefinitionSource = Uint8Array | string | DefinitionRef$1 | ByteCacheRef
|
|
346
|
-
versionId: string;
|
|
347
|
-
load: () => Promise<Uint8Array>;
|
|
348
|
-
};
|
|
182
|
+
type SolveEngineDefinitionSource = Uint8Array | string | DefinitionRef$1 | ByteCacheRef;
|
|
349
183
|
interface SolveEngineSolveArgs {
|
|
350
184
|
server: ResolvedServer;
|
|
351
185
|
definitionSource: SolveEngineDefinitionSource;
|
|
@@ -356,15 +190,23 @@ interface SolveEngineSolveArgs {
|
|
|
356
190
|
* Ignored for the other source forms.
|
|
357
191
|
*/
|
|
358
192
|
definitionKey?: string;
|
|
359
|
-
|
|
193
|
+
/**
|
|
194
|
+
* The definition's inputs, from the schema you stored, never from the request: `id`, `nickname`,
|
|
195
|
+
* `paramType` and the bounds decide what reaches the definition, so a body that supplies them
|
|
196
|
+
* can drop a limit or fill a slot your host reserved.
|
|
197
|
+
*/
|
|
198
|
+
inputs: SolveInput[];
|
|
360
199
|
values: Record<string, unknown>;
|
|
361
200
|
signal: AbortSignal;
|
|
362
201
|
acceptEncoding?: string;
|
|
363
|
-
/**
|
|
202
|
+
/**
|
|
203
|
+
* Stamped as `X-Selva-Definition` when this solve builds the server's warm client. The header
|
|
204
|
+
* is fixed per client, so later solves on that server carry the first definition's guid.
|
|
205
|
+
*/
|
|
364
206
|
definitionGuid?: string;
|
|
365
207
|
loadStartMs?: number;
|
|
366
208
|
defLoadMs?: number;
|
|
367
|
-
prepMarks?: [
|
|
209
|
+
prepMarks?: PrepMark[];
|
|
368
210
|
/**
|
|
369
211
|
* Per-solve live-event callback. A coalesced waiter shares the first caller's
|
|
370
212
|
* compute call, so only that caller's target reaches the definition; the
|
|
@@ -372,11 +214,6 @@ interface SolveEngineSolveArgs {
|
|
|
372
214
|
*/
|
|
373
215
|
selvaEvents?: SelvaEventTarget;
|
|
374
216
|
}
|
|
375
|
-
interface FrameworkAgnosticResponse {
|
|
376
|
-
status: number;
|
|
377
|
-
headers: Record<string, string>;
|
|
378
|
-
body: string | Uint8Array;
|
|
379
|
-
}
|
|
380
217
|
interface SolveEngineStats {
|
|
381
218
|
client: SolveCacheStats;
|
|
382
219
|
definitionBytes: ByteCacheStats;
|
|
@@ -389,11 +226,17 @@ export declare class SolveEngine {
|
|
|
389
226
|
private readonly clientCache;
|
|
390
227
|
private readonly byteCache;
|
|
391
228
|
private readonly singleFlight;
|
|
229
|
+
private readonly clientGenerations;
|
|
230
|
+
private lastClientGeneration;
|
|
392
231
|
constructor(options: SolveEngineOptions);
|
|
393
232
|
getClient(server: ResolvedServer, opts?: {
|
|
394
233
|
definitionGuid?: string;
|
|
395
234
|
}): Promise<CachedClient>;
|
|
396
|
-
definitionRef(versionId: string, load: () => Promise<Uint8Array>): ByteCacheRef;
|
|
235
|
+
definitionRef(versionId: string, load: (signal: AbortSignal) => Promise<Uint8Array>): ByteCacheRef;
|
|
236
|
+
/**
|
|
237
|
+
* Drops the warm client for `id`; call it when the server's URL or key changes. Solves already
|
|
238
|
+
* running finish on the old connection details first, so a revoked key stays in use until then.
|
|
239
|
+
*/
|
|
397
240
|
evictServer(id: string): void;
|
|
398
241
|
clearSolveCaches(): void;
|
|
399
242
|
/**
|
|
@@ -402,15 +245,23 @@ export declare class SolveEngine {
|
|
|
402
245
|
*/
|
|
403
246
|
clearDefinitionCache(): void;
|
|
404
247
|
stats(): SolveEngineStats;
|
|
405
|
-
solve(args: SolveEngineSolveArgs): Promise<
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
248
|
+
solve(args: SolveEngineSolveArgs): Promise<SolveEngineOutcome>;
|
|
249
|
+
/**
|
|
250
|
+
* The response for an outcome; the mapping from outcome to status lives here. Throws
|
|
251
|
+
* `compute_error`'s error: what it means for the caller is the host's to decide.
|
|
252
|
+
*/
|
|
253
|
+
toWebResponse(outcome: SolveEngineOutcome): Response;
|
|
254
|
+
/**
|
|
255
|
+
* Part of the coalesce key: after an eviction the id stays and the client changes, and a new
|
|
256
|
+
* caller joining a draining flight would get an answer from the old URL or key.
|
|
257
|
+
*/
|
|
258
|
+
private clientGeneration;
|
|
413
259
|
private resolveDefinitionSource;
|
|
414
260
|
}
|
|
415
|
-
|
|
261
|
+
/**
|
|
262
|
+
* What `SolveEngine.solve` takes as `inputs`, from a schema the host stored. The bounds live on
|
|
263
|
+
* the number widgets in the layout, not on `schema.inputs`, so without them nothing clamps.
|
|
264
|
+
*/
|
|
265
|
+
export declare function solveInputsFromSchema(schema: UISchema): SolveInput[];
|
|
266
|
+
export type { ByteCacheRef, ByteCacheStats, ByteRefOutcome, CachedClient, DefinitionRef, ResolvedServer, SolveCacheStats, SolveDefinition, SolveEngineDefinitionSource, SolveEngineLimits, SolveEngineOptions, SolveEngineOutcome, SolveEngineSolveArgs, SolveEngineStats, SolveEnvelope, SolveInput, SolvePhaseMetrics };
|
|
416
267
|
//# sourceMappingURL=server.d.ts.map
|