@selvajs/solve 0.2.0-beta.2 → 1.0.0-beta.10
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 +13 -16
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +202 -183
- package/dist/client.d.ts +202 -183
- 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 +324 -402
- package/dist/server.d.ts +324 -402
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/shared.cjs +0 -1
- package/dist/shared.d.cts +3 -3
- package/dist/shared.d.ts +3 -3
- package/dist/shared.js +1 -1
- package/dist/solve-fn-lsWj61m5.d.cts +48 -0
- package/dist/solve-fn-lsWj61m5.d.ts +48 -0
- package/dist/{solve-input-CqYtetTA.d.ts → solve-input-BDv_JdvS.d.cts} +8 -7
- package/dist/{solve-input-CqYtetTA.d.cts → solve-input-BDv_JdvS.d.ts} +8 -7
- package/package.json +17 -11
- package/dist/shared.cjs.map +0 -1
- package/dist/shared.js.map +0 -1
- package/dist/solve-fn-DGzPCsDu.d.cts +0 -22
- package/dist/solve-fn-DGzPCsDu.d.ts +0 -22
package/dist/server.d.ts
CHANGED
|
@@ -1,56 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { t as SolveInput } from "./solve-input-BDv_JdvS.js";
|
|
2
|
+
import { SchemaInput } from "@selvajs/schemas";
|
|
3
|
+
import { DataTree, DefinitionRef as DefinitionRef$1, GrasshopperClient, GrasshopperComputeResponse, InputParam, SolveScheduler } from "@selvajs/compute/grasshopper";
|
|
4
|
+
import { DefinitionRef, SolveDefinition, SolveDefinition as SolveDefinition$1, isDefinitionRef } from "@selvajs/compute/core";
|
|
5
|
+
import { ILogger } from "@selvajs/platform";
|
|
6
|
+
//#region src/server/client-cache.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* Opaque identity of a compute server — construct only via `serverIdentity()`.
|
|
9
9
|
* Kept opaque so "a server is now a pool of URLs behind one id" stays additive.
|
|
10
10
|
*/
|
|
11
11
|
type ServerIdentity = string & {
|
|
12
|
-
|
|
12
|
+
readonly __brand: 'ServerIdentity';
|
|
13
13
|
};
|
|
14
14
|
/** Minimal resolved-server shape the cache needs (a subset of the app's `ComputeServerConfig`). */
|
|
15
15
|
interface ResolvedServer {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
id: string;
|
|
17
|
+
serverUrl: string;
|
|
18
|
+
/** Sent as the `RhinoComputeKey` header. */
|
|
19
|
+
apiKey?: string;
|
|
20
20
|
}
|
|
21
|
-
/** Derive the opaque cache identity from a resolved server. Identity is the `id`. */
|
|
22
21
|
declare function serverIdentity(server: Pick<ResolvedServer, 'id'>): ServerIdentity;
|
|
23
22
|
interface CachedClient {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
23
|
+
client: GrasshopperClient;
|
|
24
|
+
scheduler: SolveScheduler;
|
|
25
|
+
/**
|
|
26
|
+
* Last Server-Timing decode/solve/encode, written by `onServerTiming`. The
|
|
27
|
+
* scheduler runs up to `maxConcurrent` solves at once, so `last` alone can't
|
|
28
|
+
* be trusted per-request: callers snapshot `seq` before their solve and only
|
|
29
|
+
* attribute `last` to themselves if exactly one write happened since (see
|
|
30
|
+
* the guard in `runSolvePipeline`), dropping it otherwise rather than risk
|
|
31
|
+
* misattributing another request's timing.
|
|
32
|
+
*/
|
|
33
|
+
rhinoTiming: {
|
|
34
|
+
last: {
|
|
35
|
+
decode: number;
|
|
36
|
+
solve: number;
|
|
37
|
+
encode: number;
|
|
38
|
+
} | null;
|
|
39
|
+
seq: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Same snapshot-and-attribute pattern as `rhinoTiming`, for the scheduler's
|
|
43
|
+
* `onSettle` cache verdict. `seq` increments on every settle (success or
|
|
44
|
+
* error); `last` is written only on success.
|
|
45
|
+
*/
|
|
46
|
+
solveMeta: {
|
|
47
|
+
last: {
|
|
48
|
+
fromCache: boolean;
|
|
49
|
+
definitionReuploaded?: boolean;
|
|
50
|
+
} | null;
|
|
51
|
+
seq: number;
|
|
52
|
+
};
|
|
54
53
|
}
|
|
55
54
|
/**
|
|
56
55
|
* Debug verbosity: `false` is silent, `true` gives concise cache/timing logs
|
|
@@ -60,82 +59,75 @@ interface CachedClient {
|
|
|
60
59
|
type ClientCacheDebug = boolean | 'verbose';
|
|
61
60
|
/** Config injected by the consuming app, so this module stays env-agnostic and testable. */
|
|
62
61
|
interface ClientCacheConfig {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
maxWarmComputeServers?: number;
|
|
95
|
-
/** Sink for the concise debug lines (the app wires `console.log`). */
|
|
96
|
-
onDebugLog?: (message: string) => void;
|
|
62
|
+
/** Per-solve timeout forwarded to the scheduler (`ComputeLimits.solveDeadlineMs`). */
|
|
63
|
+
solveDeadlineMs: number;
|
|
64
|
+
cachesolve: boolean;
|
|
65
|
+
/** Only meaningful with `cachesolve`. */
|
|
66
|
+
cacheerroredsolves: boolean;
|
|
67
|
+
/** Reference large definitions by server cache key instead of re-uploading. */
|
|
68
|
+
reuseServerDefinitionCache: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Max solves that may wait in the FIFO queue, excluding in-flight solves
|
|
71
|
+
* (capped at `maxConcurrent`, itself driven by the server's probed child
|
|
72
|
+
* count). `0` = unbounded (`ComputeLimits.computeMaxQueueDepth`); a full
|
|
73
|
+
* queue rejects new solves with `QUEUE_FULL`.
|
|
74
|
+
*/
|
|
75
|
+
maxQueueDepth: number;
|
|
76
|
+
/**
|
|
77
|
+
* Max ms a solve may sit queued before executing; `0` = no deadline
|
|
78
|
+
* (`ComputeLimits.computeQueueWaitMs`). Too long a wait rejects with
|
|
79
|
+
* `QUEUE_TIMEOUT`.
|
|
80
|
+
*/
|
|
81
|
+
queueWaitMs: number;
|
|
82
|
+
/**
|
|
83
|
+
* Byte budget for this client's in-process solve cache. Applies per warm
|
|
84
|
+
* client — worst-case heap is this × `maxWarmComputeServers`. `0` disables
|
|
85
|
+
* the cache (`ComputeLimits.computeSolveCacheBytes`, env `COMPUTE_SOLVE_CACHE_MB`).
|
|
86
|
+
*/
|
|
87
|
+
responseCacheMaxBytes: number;
|
|
88
|
+
debug: ClientCacheDebug;
|
|
89
|
+
/** Max distinct warm compute servers before the LRU evicts the oldest. Default 16. */
|
|
90
|
+
maxWarmComputeServers?: number;
|
|
91
|
+
/** Sink for the debug lines. `onDebugLog` only fires when `debug` is not `false`. */
|
|
92
|
+
onDebugLog?: (message: string) => void;
|
|
97
93
|
}
|
|
98
94
|
interface ClientCache {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*/
|
|
123
|
-
clearSolveCaches(): void;
|
|
124
|
-
/** Dispose every warm client. Test seam / shutdown hook. */
|
|
125
|
-
disposeAll(): void;
|
|
95
|
+
/**
|
|
96
|
+
* Get (or create) the warm client + scheduler for a resolved compute server,
|
|
97
|
+
* keyed by its `id`. `definitionGuid`, when present, is stamped as the
|
|
98
|
+
* `X-Selva-Definition` header on this client's outbound solve/IO requests —
|
|
99
|
+
* inert routing/telemetry metadata until a pool router exists.
|
|
100
|
+
*/
|
|
101
|
+
getClient(server: ResolvedServer, opts?: {
|
|
102
|
+
definitionGuid?: string;
|
|
103
|
+
}): Promise<CachedClient>;
|
|
104
|
+
/** Dispose and drop the warm client for `id`, so the next request rebuilds against fresh connection details. */
|
|
105
|
+
evict(id: string | ServerIdentity): void;
|
|
106
|
+
/** Solve-cache counters summed across every warm client. Counters die with the client that owns them, so totals can fall over time. */
|
|
107
|
+
solveCacheStats(): SolveCacheStats;
|
|
108
|
+
/**
|
|
109
|
+
* Drop every retained solve result, keeping the warm clients (and their
|
|
110
|
+
* connections and server-side definition pointers) intact. Nothing expires
|
|
111
|
+
* these on its own — the byte budget is the only other pressure — so this is
|
|
112
|
+
* the operator's release valve when a definition's inputs no longer describe
|
|
113
|
+
* its output, e.g. it reads an external source that has since changed.
|
|
114
|
+
*/
|
|
115
|
+
clearSolveCaches(): void;
|
|
116
|
+
/** Dispose every warm client. Test seam / shutdown hook. */
|
|
117
|
+
disposeAll(): void;
|
|
126
118
|
}
|
|
127
|
-
/** Aggregate solve-cache counters across the warm clients (see `solveCacheStats`). */
|
|
128
119
|
interface SolveCacheStats {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
120
|
+
warmClients: number;
|
|
121
|
+
entries: number;
|
|
122
|
+
bytes: number;
|
|
123
|
+
hits: number;
|
|
124
|
+
misses: number;
|
|
125
|
+
/** Entries dropped under size/byte pressure (not replacement or manual clears). */
|
|
126
|
+
evictions: number;
|
|
136
127
|
}
|
|
137
128
|
declare function createClientCache(config: ClientCacheConfig): ClientCache;
|
|
138
|
-
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/server/definition-byte-cache.d.ts
|
|
139
131
|
/**
|
|
140
132
|
* In-process cache of `.gh` definition bytes, keyed by immutable **version id**.
|
|
141
133
|
*
|
|
@@ -162,372 +154,302 @@ declare function createClientCache(config: ClientCacheConfig): ClientCache;
|
|
|
162
154
|
* verdict (`skipped` / `hit` / `miss`).
|
|
163
155
|
*/
|
|
164
156
|
interface ByteRefOutcome {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
fromCache: boolean;
|
|
157
|
+
loaded: boolean;
|
|
158
|
+
fromCache: boolean;
|
|
168
159
|
}
|
|
169
160
|
/** A definition reference shaped for `@selvajs/compute`'s `DefinitionRef`. */
|
|
170
161
|
interface ByteCacheRef {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
key: string;
|
|
163
|
+
load: () => Promise<Uint8Array>;
|
|
164
|
+
outcome: ByteRefOutcome;
|
|
174
165
|
}
|
|
175
|
-
/** Hit/miss/eviction counters for observability (Server-Timing, admin debug). */
|
|
176
166
|
interface ByteCacheStats {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
bytes: number;
|
|
167
|
+
hits: number;
|
|
168
|
+
misses: number;
|
|
169
|
+
evictions: number;
|
|
170
|
+
entries: number;
|
|
171
|
+
bytes: number;
|
|
183
172
|
}
|
|
184
173
|
interface DefinitionByteCache {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
* and moves no bytes until `load()` runs.
|
|
189
|
-
*/
|
|
190
|
-
getOrLoad(versionId: string, load: () => Promise<Uint8Array>): ByteCacheRef;
|
|
191
|
-
stats(): ByteCacheStats;
|
|
192
|
-
/** Test seam / eviction on definition delete if ever needed. */
|
|
193
|
-
clear(): void;
|
|
174
|
+
getOrLoad(versionId: string, load: () => Promise<Uint8Array>): ByteCacheRef;
|
|
175
|
+
stats(): ByteCacheStats;
|
|
176
|
+
clear(): void;
|
|
194
177
|
}
|
|
195
178
|
/**
|
|
196
179
|
* @param maxBytes total retained-byte budget; `0` (or negative) disables caching
|
|
197
180
|
* entirely — every `load()` calls the loader and nothing is retained.
|
|
198
181
|
*/
|
|
199
182
|
declare function createDefinitionByteCache(maxBytes: number): DefinitionByteCache;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
* Transport-agnostic solve pipeline: given an already-resolved solve context
|
|
203
|
-
* (`.gh` bytes, input params + user values, a warm `SolveScheduler`), runs the
|
|
204
|
-
* framework-free half of a solve and returns a discriminated {@link SolveOutcome}
|
|
205
|
-
* instead of throwing for expected failures. Nothing here touches auth, the
|
|
206
|
-
* database, share tokens, rate limits, or metric sinks — those stay app policy
|
|
207
|
-
* in the route that calls this.
|
|
208
|
-
*/
|
|
209
|
-
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/server/solve-pipeline.d.ts
|
|
210
185
|
/** Compute-response contract version. Bump (and document the change) whenever the envelope's shape changes in a way a consumer could observe. */
|
|
211
186
|
declare const COMPUTE_CONTRACT_VERSION: 1;
|
|
212
187
|
declare const COMPUTE_VERSION_HEADER = "X-Selva-Compute-Version";
|
|
213
188
|
type PipelineInput = SchemaInput & {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
189
|
+
minimum?: number;
|
|
190
|
+
maximum?: number;
|
|
191
|
+
stepSize?: number;
|
|
217
192
|
};
|
|
218
193
|
interface SolvePipelineArgs {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
* Wall-clock origin (`performance.now()` captured at the top of the request)
|
|
259
|
-
* so the pipeline's `load`/`total` phase timings line up with the caller's
|
|
260
|
-
* pre-solve prep.
|
|
261
|
-
*/
|
|
262
|
-
loadStartMs: number;
|
|
263
|
-
/** Pre-solve "load" phase duration the caller already measured (auth + DB + fetch). */
|
|
264
|
-
defLoadMs: number;
|
|
265
|
-
/**
|
|
266
|
-
* Pre-solve prep sub-phase marks (`[label, ms]`), surfaced verbatim as `p_*`
|
|
267
|
-
* Server-Timing entries.
|
|
268
|
-
*/
|
|
269
|
-
prepMarks?: [string, number][];
|
|
194
|
+
/**
|
|
195
|
+
* Raw `.gh` bytes, or a byte-cache `DefinitionRef` whose bytes the scheduler
|
|
196
|
+
* materializes only when an upload is unavoidable — a pointer-known solve
|
|
197
|
+
* moves zero bytes.
|
|
198
|
+
*/
|
|
199
|
+
definitionSource: SolveDefinition$1;
|
|
200
|
+
/** Mutable load outcome for a `DefinitionRef` source, so the `def_bytes` Server-Timing verdict can be emitted. Omit for raw-bytes solves. */
|
|
201
|
+
byteRefOutcome?: ByteRefOutcome;
|
|
202
|
+
/** Persisted input params; only those with a `paramType` are sent to the solve. */
|
|
203
|
+
inputs: PipelineInput[];
|
|
204
|
+
/**
|
|
205
|
+
* A tree the caller already built with {@link buildSolveInputTree}; skips the
|
|
206
|
+
* pipeline's own build and solves this object directly.
|
|
207
|
+
*
|
|
208
|
+
* A caller coalescing concurrent solves needs this: a single-flight key
|
|
209
|
+
* derived from raw `{inputs, values}` would split two requests that transform
|
|
210
|
+
* to the same tree, which is the identity the scheduler actually caches on.
|
|
211
|
+
*/
|
|
212
|
+
inputTree?: DataTree[];
|
|
213
|
+
/** User-chosen values keyed by input id; missing keys fall back to the schema default. */
|
|
214
|
+
values: Record<string, unknown>;
|
|
215
|
+
client: CachedClient;
|
|
216
|
+
responseMaxBytes: number;
|
|
217
|
+
/** Only phrases the timeout message — the scheduler enforces the deadline itself. */
|
|
218
|
+
solveDeadlineMs: number;
|
|
219
|
+
/** Client's `Accept-Encoding`; gzip is applied only when it advertises `gzip`. */
|
|
220
|
+
acceptEncoding: string;
|
|
221
|
+
/**
|
|
222
|
+
* Forwarded to the scheduler so a client disconnect cancels the upstream
|
|
223
|
+
* compute call. Its `aborted` flag also distinguishes a client disconnect
|
|
224
|
+
* from the scheduler's own deadline firing.
|
|
225
|
+
*/
|
|
226
|
+
signal: AbortSignal;
|
|
227
|
+
/** 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. */
|
|
228
|
+
loadStartMs: number;
|
|
229
|
+
/** Pre-solve "load" phase duration the caller already measured (auth + DB + fetch). */
|
|
230
|
+
defLoadMs: number;
|
|
231
|
+
/** Pre-solve prep sub-phase marks (`[label, ms]`), surfaced verbatim as `p_*` Server-Timing entries. */
|
|
232
|
+
prepMarks?: [string, number][];
|
|
270
233
|
}
|
|
271
234
|
/** Phase timings the pipeline measured; the caller uses them for debug logging. */
|
|
272
235
|
interface SolvePhaseMetrics {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
236
|
+
treeBuildMs: number;
|
|
237
|
+
solveMs: number;
|
|
238
|
+
serializeMs: number;
|
|
239
|
+
gzipMs: number;
|
|
240
|
+
serverTotalMs: number;
|
|
241
|
+
serializedBytes: number;
|
|
242
|
+
/** Null when compression was skipped. */
|
|
243
|
+
compressedBytes: number | null;
|
|
281
244
|
}
|
|
282
245
|
/** A ready-to-send response: body + headers + the solve result + phase metrics. */
|
|
283
246
|
interface SolveEnvelope {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
247
|
+
/** A gzip `Uint8Array` when `encoding === 'gzip'`, else the JSON string. */
|
|
248
|
+
body: string | Uint8Array;
|
|
249
|
+
encoding?: 'gzip';
|
|
250
|
+
headers: Record<string, string>;
|
|
251
|
+
result: GrasshopperComputeResponse;
|
|
252
|
+
metrics: SolvePhaseMetrics;
|
|
290
253
|
}
|
|
291
254
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* `
|
|
296
|
-
*
|
|
297
|
-
* for the metric record.
|
|
255
|
+
* `ok` carries the envelope; every other variant names an expected failure the
|
|
256
|
+
* calling route maps to a status code (timeout→504, client_abort→499,
|
|
257
|
+
* too_large→413, shed→503+Retry-After, compute_error→generic 500/503).
|
|
258
|
+
* `durationMs` on error variants is the solve wall time up to the failure, for
|
|
259
|
+
* the metric record.
|
|
298
260
|
*/
|
|
299
261
|
type SolveOutcome = {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
262
|
+
kind: 'ok';
|
|
263
|
+
envelope: SolveEnvelope;
|
|
264
|
+
solveMs: number;
|
|
265
|
+
errorCount: number;
|
|
266
|
+
warningCount: number;
|
|
305
267
|
} | {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
268
|
+
kind: 'timeout';
|
|
269
|
+
durationMs: number;
|
|
270
|
+
message: string;
|
|
309
271
|
} | {
|
|
310
|
-
|
|
311
|
-
|
|
272
|
+
kind: 'client_abort';
|
|
273
|
+
durationMs: number;
|
|
312
274
|
} | {
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
kind: 'shed';
|
|
323
|
-
durationMs: number;
|
|
324
|
-
reason: 'queue_full' | 'queue_timeout';
|
|
325
|
-
retryAfterSeconds: number;
|
|
326
|
-
message: string;
|
|
275
|
+
kind: 'too_large';
|
|
276
|
+
} |
|
|
277
|
+
/** Scheduler backpressure rejected the solve before it ran: queue was full (`queue_full`) or the wait exceeded the deadline (`queue_timeout`). Retryable — `retryAfterSeconds` is a backoff hint. */
|
|
278
|
+
{
|
|
279
|
+
kind: 'shed';
|
|
280
|
+
durationMs: number;
|
|
281
|
+
reason: 'queue_full' | 'queue_timeout';
|
|
282
|
+
retryAfterSeconds: number;
|
|
283
|
+
message: string;
|
|
327
284
|
} | {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
285
|
+
kind: 'compute_error';
|
|
286
|
+
durationMs: number;
|
|
287
|
+
error: unknown;
|
|
331
288
|
};
|
|
332
|
-
/**
|
|
333
|
-
* Build the transformed input tree — the exact object handed to the scheduler.
|
|
334
|
-
* `runSolvePipeline` calls this itself; a caller only needs it directly to see
|
|
335
|
-
* the tree before the pipeline runs (see {@link SolvePipelineArgs.inputTree}).
|
|
336
|
-
*/
|
|
289
|
+
/** The transformed input tree exactly as handed to the scheduler; `runSolvePipeline` calls this itself unless the caller supplies {@link SolvePipelineArgs.inputTree}. */
|
|
337
290
|
declare function buildSolveInputTree(inputs: PipelineInput[], values: Record<string, unknown>): DataTree[];
|
|
338
291
|
declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome>;
|
|
339
292
|
/**
|
|
340
293
|
* Re-key a coalesced envelope to a single waiter's `Accept-Encoding`.
|
|
341
294
|
*
|
|
342
295
|
* The single-flight coalescer runs ONE pipeline execution for N identical
|
|
343
|
-
* concurrent solves and hands every waiter the same {@link SolveEnvelope}
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* result to each waiter instead.
|
|
296
|
+
* concurrent solves and hands every waiter the same {@link SolveEnvelope},
|
|
297
|
+
* baked from the FIRST caller's `Accept-Encoding`. A later waiter with a
|
|
298
|
+
* different `Accept-Encoding` would otherwise get a body labelled with the
|
|
299
|
+
* wrong encoding — `Vary` can't help here, since it's one shared object, not a
|
|
300
|
+
* cache lookup. Encoding is deliberately left out of the coalesce key so mixed
|
|
301
|
+
* clients still coalesce; this adapts the shared result to each waiter instead.
|
|
350
302
|
*
|
|
351
303
|
* Only the correctness-critical direction is adapted: a gzip envelope served to
|
|
352
304
|
* a non-gzip waiter is gunzipped back to JSON. The reverse (plain JSON to a
|
|
353
305
|
* gzip-capable waiter) is left uncompressed — correct, just not maximally small.
|
|
354
306
|
*/
|
|
355
307
|
declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding: string): {
|
|
356
|
-
|
|
357
|
-
|
|
308
|
+
body: string | Uint8Array;
|
|
309
|
+
headers: Record<string, string>;
|
|
358
310
|
};
|
|
359
|
-
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/server/transform-input.d.ts
|
|
360
313
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* are absent on `SchemaInput` (they live on the layout item's config), so
|
|
364
|
-
* valueList/file inputs fall back to carrying the selected value with no option
|
|
365
|
-
* list — the same value that reaches Grasshopper.
|
|
314
|
+
* `SchemaInput` has no `values`/`acceptedFormats` (those live on the layout item's config), so
|
|
315
|
+
* valueList/file inputs here carry just the selected value with no option list.
|
|
366
316
|
*/
|
|
367
317
|
declare function transformInputParameter(input: SchemaInput & {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
318
|
+
minimum?: number;
|
|
319
|
+
maximum?: number;
|
|
320
|
+
stepSize?: number;
|
|
371
321
|
}, value: unknown): InputParam;
|
|
372
|
-
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/server/solve-cache-single-flight.d.ts
|
|
373
324
|
/**
|
|
374
325
|
* In-process single-flight — dogpile protection for every solve.
|
|
375
326
|
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
327
|
+
* Always on, not gated behind a result cache being configured: the dogpile is worst
|
|
328
|
+
* when nothing else is caching, since N concurrent identical solves each pay a full
|
|
329
|
+
* Rhino round trip (the scheduler doesn't coalesce in-flight requests itself, and
|
|
330
|
+
* Rhino's `cachesolve` still costs a round trip per repeat). An earlier version
|
|
331
|
+
* gated this on cache config and so disabled it in exactly the deployments most
|
|
332
|
+
* exposed to a cold-key stampede — hot public definition plus a deploy.
|
|
382
333
|
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
334
|
+
* Per app instance, above `ISolveResultCache`. No cross-instance lease (Redis
|
|
335
|
+
* `SET NX`) yet.
|
|
385
336
|
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
* app serializes it per response).
|
|
337
|
+
* The shared promise resolves to one value for every waiter, so `work` must return
|
|
338
|
+
* something safe to share by reference — the solve pipeline's envelope qualifies
|
|
339
|
+
* because the app serializes it per response.
|
|
390
340
|
*/
|
|
391
341
|
interface SolveCacheSingleFlight {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
/** Number of keys currently in flight (observability / tests). */
|
|
405
|
-
inFlight(): number;
|
|
342
|
+
/**
|
|
343
|
+
* Coalesces concurrent calls under the same key: the first caller runs `work`,
|
|
344
|
+
* later callers for that key await the same promise, and the key frees as soon
|
|
345
|
+
* as it settles.
|
|
346
|
+
*
|
|
347
|
+
* `onWaiterJoined` fires only on the owner's call, only while `work` is still
|
|
348
|
+
* running, each time another caller joins. Ownership changes what the owner's
|
|
349
|
+
* abort signal should mean: a solo run can cancel on its own client's
|
|
350
|
+
* disconnect, but a shared run can't without 499-ing every waiter.
|
|
351
|
+
*/
|
|
352
|
+
run<T>(key: string, work: () => Promise<T>, onWaiterJoined?: () => void): Promise<T>;
|
|
353
|
+
inFlight(): number;
|
|
406
354
|
}
|
|
407
355
|
interface SolveCacheSingleFlightOptions {
|
|
408
|
-
|
|
409
|
-
* Fired when a caller joins an already-in-flight key instead of running its
|
|
410
|
-
* own work — the coalescing win this module exists for, otherwise invisible.
|
|
411
|
-
*/
|
|
412
|
-
onJoin?: (key: string) => void;
|
|
356
|
+
onJoin?: (key: string) => void;
|
|
413
357
|
}
|
|
414
358
|
declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
|
|
415
|
-
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/server/solve-engine.d.ts
|
|
416
361
|
/**
|
|
417
|
-
* `
|
|
418
|
-
*
|
|
419
|
-
* definition-byte cache, single-flight coalescing, the pipeline call, and the
|
|
420
|
-
* outcome→HTTP mapping. Composes `createClientCache` + `createDefinitionByteCache`
|
|
421
|
-
* + `createSolveCacheSingleFlight` + `runSolvePipeline` — the same primitives a
|
|
422
|
-
* hand-assembled app route already wires, minus the wiring.
|
|
423
|
-
*
|
|
424
|
-
* Deliberately does NOT read env itself (no `resolveComputeLimits` call here):
|
|
425
|
-
* `@selvajs/server`, which owns that function, depends on `@selvajs/solve`, so
|
|
426
|
-
* the reverse import would be circular. A consumer resolves its own limits
|
|
427
|
-
* (`resolveComputeLimits` from `@selvajs/server/compute`, or any equivalent) and
|
|
428
|
-
* passes the handful of fields this engine actually needs.
|
|
429
|
-
*
|
|
430
|
-
* What stays OUT, on purpose (matches `server/index.ts`'s existing boundary):
|
|
431
|
-
* auth, DB reads, share tokens, rate limiting, metric sinks, and which compute
|
|
432
|
-
* server to use (`resolveServerForOrg`-equivalent) — all app policy, supplied
|
|
433
|
-
* per-call via `SolveEngineSolveArgs.server`.
|
|
434
|
-
*/
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* The subset of `ComputeLimits` (`@selvajs/server/compute`) the engine needs,
|
|
438
|
-
* passed straight through to `createClientCache` — pass the resolved object
|
|
439
|
-
* through, its extra fields are ignored.
|
|
362
|
+
* Subset of `ComputeLimits` (`@selvajs/server/compute`) the engine needs.
|
|
363
|
+
* Pass the resolved object through as-is — extra fields are ignored.
|
|
440
364
|
*/
|
|
441
365
|
interface SolveEngineLimits {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
366
|
+
solveDeadlineMs: number;
|
|
367
|
+
computeResponseMaxBytes: number;
|
|
368
|
+
computeReuseDefinitionCache: boolean;
|
|
369
|
+
computeServerCachesolve: boolean;
|
|
370
|
+
computeCacheErroredSolves: boolean;
|
|
371
|
+
computeMaxQueueDepth: number;
|
|
372
|
+
computeQueueWaitMs: number;
|
|
373
|
+
computeDefinitionCacheBytes: number;
|
|
374
|
+
computeSolveCacheBytes: number;
|
|
451
375
|
}
|
|
452
376
|
interface SolveEngineOptions {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
onSolveCoalesced?: (key: string) => void;
|
|
377
|
+
limits: SolveEngineLimits;
|
|
378
|
+
logger?: ILogger;
|
|
379
|
+
/** Max distinct warm compute servers before the LRU evicts the oldest. Default 16. */
|
|
380
|
+
maxWarmComputeServers?: number;
|
|
381
|
+
/** Concise cache/timing logs when truthy; `'verbose'` also dumps full lib-level requests/responses. Default off. */
|
|
382
|
+
debug?: ClientCacheDebug;
|
|
383
|
+
onDebugLog?: (message: string) => void;
|
|
384
|
+
/** Fired when a caller joins an already-in-flight solve instead of running its own. */
|
|
385
|
+
onSolveCoalesced?: (key: string) => void;
|
|
463
386
|
}
|
|
464
387
|
/**
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* `DefinitionRef` never has.
|
|
388
|
+
* Accepts every form `runSolvePipeline` does, plus `{versionId, load}` sugar
|
|
389
|
+
* that builds (and caches) a `ByteCacheRef` internally. A `ByteCacheRef`
|
|
390
|
+
* obtained ahead of time from `engine.definitionRef()` (e.g. to read bytes for
|
|
391
|
+
* schema extraction before solving) is passed through as-is — detected by its
|
|
392
|
+
* `outcome` field, which a plain `DefinitionRef` never has.
|
|
471
393
|
*/
|
|
472
|
-
type SolveEngineDefinitionSource = Uint8Array | string | DefinitionRef | ByteCacheRef | {
|
|
473
|
-
|
|
474
|
-
|
|
394
|
+
type SolveEngineDefinitionSource = Uint8Array | string | DefinitionRef$1 | ByteCacheRef | {
|
|
395
|
+
versionId: string;
|
|
396
|
+
load: () => Promise<Uint8Array>;
|
|
475
397
|
};
|
|
476
398
|
interface SolveEngineSolveArgs {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
prepMarks?: [string, number][];
|
|
399
|
+
server: ResolvedServer;
|
|
400
|
+
definitionSource: SolveEngineDefinitionSource;
|
|
401
|
+
/**
|
|
402
|
+
* Coalesce-key identity for a raw `Uint8Array`/`string` source, which has
|
|
403
|
+
* no natural identity the way a `DefinitionRef`'s `.key` does. Required in
|
|
404
|
+
* that case — `solve()` throws rather than silently hashing the bytes.
|
|
405
|
+
* Ignored for the other source forms.
|
|
406
|
+
*/
|
|
407
|
+
definitionKey?: string;
|
|
408
|
+
inputs: PipelineInput[];
|
|
409
|
+
values: Record<string, unknown>;
|
|
410
|
+
signal: AbortSignal;
|
|
411
|
+
acceptEncoding?: string;
|
|
412
|
+
/** Stamped as `X-Selva-Definition` on this solve's client. */
|
|
413
|
+
definitionGuid?: string;
|
|
414
|
+
loadStartMs?: number;
|
|
415
|
+
defLoadMs?: number;
|
|
416
|
+
prepMarks?: [string, number][];
|
|
496
417
|
}
|
|
497
418
|
interface FrameworkAgnosticResponse {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
419
|
+
status: number;
|
|
420
|
+
headers: Record<string, string>;
|
|
421
|
+
body: string | Uint8Array;
|
|
501
422
|
}
|
|
502
423
|
interface SolveEngineStats {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
424
|
+
client: SolveCacheStats;
|
|
425
|
+
definitionBytes: ByteCacheStats;
|
|
426
|
+
coalescing: {
|
|
427
|
+
inFlight: number;
|
|
428
|
+
};
|
|
508
429
|
}
|
|
509
430
|
declare class SolveEngine {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
431
|
+
private readonly limits;
|
|
432
|
+
private readonly clientCache;
|
|
433
|
+
private readonly byteCache;
|
|
434
|
+
private readonly singleFlight;
|
|
435
|
+
constructor(options: SolveEngineOptions);
|
|
436
|
+
getClient(server: ResolvedServer, opts?: {
|
|
437
|
+
definitionGuid?: string;
|
|
438
|
+
}): Promise<CachedClient>;
|
|
439
|
+
definitionRef(versionId: string, load: () => Promise<Uint8Array>): ByteCacheRef;
|
|
440
|
+
evictServer(id: string): void;
|
|
441
|
+
clearSolveCaches(): void;
|
|
442
|
+
stats(): SolveEngineStats;
|
|
443
|
+
solve(args: SolveEngineSolveArgs): Promise<SolveOutcome>;
|
|
444
|
+
toResponse(outcome: SolveOutcome, opts?: {
|
|
445
|
+
onError?: (status: number, body: {
|
|
446
|
+
message: string;
|
|
447
|
+
retryAfter?: number;
|
|
448
|
+
}) => never;
|
|
449
|
+
}): FrameworkAgnosticResponse;
|
|
450
|
+
toWebResponse(outcome: SolveOutcome): Response;
|
|
451
|
+
private resolveDefinitionSource;
|
|
531
452
|
}
|
|
532
|
-
|
|
533
|
-
export { type ByteCacheRef, type ByteCacheStats, type ByteRefOutcome, COMPUTE_CONTRACT_VERSION, COMPUTE_VERSION_HEADER, type CachedClient, type ClientCache, type ClientCacheConfig, type DefinitionByteCache, type FrameworkAgnosticResponse, type PipelineInput, type ResolvedServer, type ServerIdentity, type SolveCacheSingleFlight, type SolveCacheSingleFlightOptions, type SolveCacheStats, SolveEngine, type SolveEngineDefinitionSource, type SolveEngineLimits, type SolveEngineOptions, type SolveEngineSolveArgs, type SolveEngineStats, type SolveEnvelope, type SolveOutcome, type SolvePhaseMetrics, type SolvePipelineArgs, adaptEnvelopeToEncoding, buildSolveInputTree, createClientCache, createDefinitionByteCache, createSolveCacheSingleFlight, runSolvePipeline, serverIdentity, transformInputParameter };
|
|
453
|
+
//#endregion
|
|
454
|
+
export { type ByteCacheRef, type ByteCacheStats, type ByteRefOutcome, COMPUTE_CONTRACT_VERSION, COMPUTE_VERSION_HEADER, type CachedClient, type ClientCache, type ClientCacheConfig, type DefinitionByteCache, type DefinitionRef, type FrameworkAgnosticResponse, type PipelineInput, type ResolvedServer, type ServerIdentity, type SolveCacheSingleFlight, type SolveCacheSingleFlightOptions, type SolveCacheStats, type SolveDefinition, SolveEngine, type SolveEngineDefinitionSource, type SolveEngineLimits, type SolveEngineOptions, type SolveEngineSolveArgs, type SolveEngineStats, type SolveEnvelope, type SolveInput, type SolveOutcome, type SolvePhaseMetrics, type SolvePipelineArgs, adaptEnvelopeToEncoding, buildSolveInputTree, createClientCache, createDefinitionByteCache, createSolveCacheSingleFlight, isDefinitionRef, runSolvePipeline, serverIdentity, transformInputParameter };
|
|
455
|
+
//# sourceMappingURL=server.d.ts.map
|