@selvajs/solve 0.2.0-beta.2 → 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/dist/server.d.cts CHANGED
@@ -15,21 +15,20 @@ type ServerIdentity = string & {
15
15
  interface ResolvedServer {
16
16
  id: string;
17
17
  serverUrl: string;
18
- /** Sent as `RhinoComputeKey`. */
18
+ /** Sent as the `RhinoComputeKey` header. */
19
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
23
  client: GrasshopperClient;
25
24
  scheduler: SolveScheduler;
26
25
  /**
27
- * Last Server-Timing decode/solve/encode, written by onServerTiming.
28
- * The scheduler runs up to `maxConcurrent` solves at once, so `last` alone
29
- * can't be trusted per-request: callers snapshot `seq` before their solve
30
- * and only attribute `last` to themselves if exactly one write happened
31
- * since (see the guard in `runSolvePipeline`), dropping it otherwise
32
- * rather than risk misattributing another request's timing.
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.
33
32
  */
34
33
  rhinoTiming: {
35
34
  last: {
@@ -41,7 +40,7 @@ interface CachedClient {
41
40
  };
42
41
  /**
43
42
  * Same snapshot-and-attribute pattern as `rhinoTiming`, for the scheduler's
44
- * onSettle cache verdict. `seq` increments on every settle (success or
43
+ * `onSettle` cache verdict. `seq` increments on every settle (success or
45
44
  * error); `last` is written only on success.
46
45
  */
47
46
  solveMeta: {
@@ -62,37 +61,34 @@ type ClientCacheDebug = boolean | 'verbose';
62
61
  interface ClientCacheConfig {
63
62
  /** Per-solve timeout forwarded to the scheduler (`ComputeLimits.maxSolveDurationMs`). */
64
63
  maxSolveDurationMs: number;
65
- /** Ask Rhino.Compute to cache solve results and return them on identical repeats. */
66
64
  cachesolve: boolean;
67
- /** Also cache solves that reported GH errors (only meaningful with `cachesolve`). */
65
+ /** Only meaningful with `cachesolve`. */
68
66
  cacheerroredsolves: boolean;
69
- /** Reference large definitions by server cache key (pointer) instead of re-uploading. */
67
+ /** Reference large definitions by server cache key instead of re-uploading. */
70
68
  reuseServerDefinitionCache: boolean;
71
69
  /**
72
- * Backpressure — max solves that may WAIT in the FIFO queue (excludes the
73
- * in-flight solves, capped at the scheduler's `maxConcurrent`, itself driven
74
- * by the compute server's probed child count). `0` = unbounded (`ComputeLimits.
75
- * computeMaxQueueDepth`); a full queue sheds new solves with `QUEUE_FULL`.
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`.
76
74
  */
77
75
  maxQueueDepth: number;
78
76
  /**
79
- * Backpressure — max ms a solve may sit queued before executing; `0` = no
80
- * deadline (`ComputeLimits.computeQueueWaitMs`). A too-long wait sheds with
77
+ * Max ms a solve may sit queued before executing; `0` = no deadline
78
+ * (`ComputeLimits.computeQueueWaitMs`). Too long a wait rejects with
81
79
  * `QUEUE_TIMEOUT`.
82
80
  */
83
81
  queueWaitMs: number;
84
82
  /**
85
- * Byte budget for this client's in-process solve cache, evicted LRU alongside
86
- * its entry-count cap. Applies per warm client — total worst-case heap is this
87
- * × `maxWarmComputeServers`. `0` disables the cache entirely
88
- * (`ComputeLimits.computeSolveCacheBytes`, env `COMPUTE_SOLVE_CACHE_MB`).
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`).
89
86
  */
90
87
  responseCacheMaxBytes: number;
91
- /** Debug verbosity. `onDebugLog` is only ever invoked when this is not `false`. */
92
88
  debug: ClientCacheDebug;
93
89
  /** Max distinct warm compute servers before the LRU evicts the oldest. Default 16. */
94
90
  maxWarmComputeServers?: number;
95
- /** Sink for the concise debug lines (the app wires `console.log`). */
91
+ /** Sink for the debug lines. `onDebugLog` only fires when `debug` is not `false`. */
96
92
  onDebugLog?: (message: string) => void;
97
93
  }
98
94
  interface ClientCache {
@@ -107,11 +103,7 @@ interface ClientCache {
107
103
  }): Promise<CachedClient>;
108
104
  /** Dispose and drop the warm client for `id`, so the next request rebuilds against fresh connection details. */
109
105
  evict(id: string | ServerIdentity): void;
110
- /**
111
- * Solve-cache counters summed across every warm client. `warmClients` is
112
- * reported alongside since each client owns its own cache. Counters die
113
- * with the client that owns them, so totals can fall over time.
114
- */
106
+ /** Solve-cache counters summed across every warm client. Counters die with the client that owns them, so totals can fall over time. */
115
107
  solveCacheStats(): SolveCacheStats;
116
108
  /**
117
109
  * Drop every retained solve result, keeping the warm clients (and their
@@ -124,7 +116,6 @@ interface ClientCache {
124
116
  /** Dispose every warm client. Test seam / shutdown hook. */
125
117
  disposeAll(): void;
126
118
  }
127
- /** Aggregate solve-cache counters across the warm clients (see `solveCacheStats`). */
128
119
  interface SolveCacheStats {
129
120
  warmClients: number;
130
121
  entries: number;
@@ -163,7 +154,6 @@ declare function createClientCache(config: ClientCacheConfig): ClientCache;
163
154
  */
164
155
  interface ByteRefOutcome {
165
156
  loaded: boolean;
166
- /** When `loaded`, whether the bytes came from a warm cache entry. */
167
157
  fromCache: boolean;
168
158
  }
169
159
  /** A definition reference shaped for `@selvajs/compute`'s `DefinitionRef`. */
@@ -172,24 +162,16 @@ interface ByteCacheRef {
172
162
  load: () => Promise<Uint8Array>;
173
163
  outcome: ByteRefOutcome;
174
164
  }
175
- /** Hit/miss/eviction counters for observability (Server-Timing, admin debug). */
176
165
  interface ByteCacheStats {
177
166
  hits: number;
178
167
  misses: number;
179
- /** Entries dropped by the byte-budget LRU. */
180
168
  evictions: number;
181
169
  entries: number;
182
170
  bytes: number;
183
171
  }
184
172
  interface DefinitionByteCache {
185
- /**
186
- * `DefinitionRef` whose `load()` serves `versionId`'s bytes from the cache
187
- * when warm, otherwise calls `load` and caches the result. Cheap to build
188
- * and moves no bytes until `load()` runs.
189
- */
190
173
  getOrLoad(versionId: string, load: () => Promise<Uint8Array>): ByteCacheRef;
191
174
  stats(): ByteCacheStats;
192
- /** Test seam / eviction on definition delete if ever needed. */
193
175
  clear(): void;
194
176
  }
195
177
  /**
@@ -201,10 +183,9 @@ declare function createDefinitionByteCache(maxBytes: number): DefinitionByteCach
201
183
  /**
202
184
  * Transport-agnostic solve pipeline: given an already-resolved solve context
203
185
  * (`.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.
186
+ * solve and returns a discriminated {@link SolveOutcome} instead of throwing
187
+ * for expected failures. Auth, the database, share tokens, rate limits, and
188
+ * metric sinks are the calling route's job, not this file's.
208
189
  */
209
190
 
210
191
  /** Compute-response contract version. Bump (and document the change) whenever the envelope's shape changes in a way a consumer could observe. */
@@ -217,25 +198,20 @@ type PipelineInput = SchemaInput & {
217
198
  };
218
199
  interface SolvePipelineArgs {
219
200
  /**
220
- * The definition to solve. Either raw `.gh` bytes, or a `DefinitionRef` (from
221
- * the definition-byte cache) whose bytes the scheduler materializes ONLY when
222
- * an upload is unavoidable — a pointer-known solve of a `DefinitionRef` moves
223
- * zero bytes.
201
+ * Raw `.gh` bytes, or a byte-cache `DefinitionRef` whose bytes the scheduler
202
+ * materializes only when an upload is unavoidable — a pointer-known solve
203
+ * moves zero bytes.
224
204
  */
225
205
  definitionSource: SolveDefinition;
226
- /**
227
- * When `definitionSource` is a byte-cache `DefinitionRef`, its mutable outcome
228
- * so the pipeline can emit the `def_bytes` Server-Timing verdict. Omit for
229
- * raw-bytes solves.
230
- */
206
+ /** Mutable load outcome for a `DefinitionRef` source, so the `def_bytes` Server-Timing verdict can be emitted. Omit for raw-bytes solves. */
231
207
  byteRefOutcome?: ByteRefOutcome;
232
208
  /** Persisted input params; only those with a `paramType` are sent to the solve. */
233
209
  inputs: PipelineInput[];
234
210
  /**
235
- * A tree the caller already built with {@link buildSolveInputTree}. When present
236
- * the pipeline skips its own build and solves this exact object.
211
+ * A tree the caller already built with {@link buildSolveInputTree}; skips the
212
+ * pipeline's own build and solves this object directly.
237
213
  *
238
- * Needed whenever the caller coalesces concurrent solves: a single-flight key
214
+ * A caller coalescing concurrent solves needs this: a single-flight key
239
215
  * derived from raw `{inputs, values}` would split two requests that transform
240
216
  * to the same tree, which is the identity the scheduler actually caches on.
241
217
  */
@@ -244,28 +220,21 @@ interface SolvePipelineArgs {
244
220
  values: Record<string, unknown>;
245
221
  client: CachedClient;
246
222
  responseMaxBytes: number;
247
- /** Used only to phrase the timeout message — the scheduler enforces the deadline itself. */
223
+ /** Only phrases the timeout message — the scheduler enforces the deadline itself. */
248
224
  maxSolveDurationMs: number;
249
225
  /** Client's `Accept-Encoding`; gzip is applied only when it advertises `gzip`. */
250
226
  acceptEncoding: string;
251
227
  /**
252
- * Request abort signal, forwarded to the scheduler so a client disconnect
253
- * cancels the upstream compute call. Its `aborted` flag also disambiguates a
254
- * client disconnect from the scheduler's own deadline firing.
228
+ * Forwarded to the scheduler so a client disconnect cancels the upstream
229
+ * compute call. Its `aborted` flag also distinguishes a client disconnect
230
+ * from the scheduler's own deadline firing.
255
231
  */
256
232
  signal: AbortSignal;
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
- */
233
+ /** 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. */
262
234
  loadStartMs: number;
263
235
  /** Pre-solve "load" phase duration the caller already measured (auth + DB + fetch). */
264
236
  defLoadMs: number;
265
- /**
266
- * Pre-solve prep sub-phase marks (`[label, ms]`), surfaced verbatim as `p_*`
267
- * Server-Timing entries.
268
- */
237
+ /** Pre-solve prep sub-phase marks (`[label, ms]`), surfaced verbatim as `p_*` Server-Timing entries. */
269
238
  prepMarks?: [string, number][];
270
239
  }
271
240
  /** Phase timings the pipeline measured; the caller uses them for debug logging. */
@@ -289,12 +258,11 @@ interface SolveEnvelope {
289
258
  metrics: SolvePhaseMetrics;
290
259
  }
291
260
  /**
292
- * Discriminated result. `ok` carries the envelope; every other variant names an
293
- * expected failure the transport maps to a status code (the app route maps
294
- * timeout→504, client_abort→499, too_large→413, shed→503+Retry-After;
295
- * `compute_error` re-surfaces the original error for the generic 500/503 path).
296
- * `durationMs` on the error variants is the solve wall time up to the failure,
297
- * for the metric record.
261
+ * `ok` carries the envelope; every other variant names an expected failure the
262
+ * calling route maps to a status code (timeout→504, client_abort→499,
263
+ * too_large→413, shed→503+Retry-After, compute_error→generic 500/503).
264
+ * `durationMs` on error variants is the solve wall time up to the failure, for
265
+ * the metric record.
298
266
  */
299
267
  type SolveOutcome = {
300
268
  kind: 'ok';
@@ -312,12 +280,7 @@ type SolveOutcome = {
312
280
  } | {
313
281
  kind: 'too_large';
314
282
  }
315
- /**
316
- * Scheduler backpressure shed the solve before it executed — the per-server
317
- * queue was full (`QUEUE_FULL`) or it sat queued past the wait deadline
318
- * (`QUEUE_TIMEOUT`). Retryable: the route maps this to 503 + `Retry-After`.
319
- * `retryAfterSeconds` is a suggested backoff hint for the client.
320
- */
283
+ /** 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. */
321
284
  | {
322
285
  kind: 'shed';
323
286
  durationMs: number;
@@ -329,24 +292,19 @@ type SolveOutcome = {
329
292
  durationMs: number;
330
293
  error: unknown;
331
294
  };
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
- */
295
+ /** The transformed input tree exactly as handed to the scheduler; `runSolvePipeline` calls this itself unless the caller supplies {@link SolvePipelineArgs.inputTree}. */
337
296
  declare function buildSolveInputTree(inputs: PipelineInput[], values: Record<string, unknown>): DataTree[];
338
297
  declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome>;
339
298
  /**
340
299
  * Re-key a coalesced envelope to a single waiter's `Accept-Encoding`.
341
300
  *
342
301
  * The single-flight coalescer runs ONE pipeline execution for N identical
343
- * concurrent solves and hands every waiter the same {@link SolveEnvelope}. That
344
- * envelope's wire form is baked from the FIRST caller's `Accept-Encoding`, so a
345
- * later waiter with a different `Accept-Encoding` would otherwise get a body
346
- * labelled with the wrong encoding (`Vary` can't help — this is one object
347
- * shared across waiters, not a cache lookup). Encoding is deliberately not part
348
- * of the coalesce key, so mixed clients still coalesce; this adapts the shared
349
- * result to each waiter instead.
302
+ * concurrent solves and hands every waiter the same {@link SolveEnvelope},
303
+ * baked from the FIRST caller's `Accept-Encoding`. A later waiter with a
304
+ * different `Accept-Encoding` would otherwise get a body labelled with the
305
+ * wrong encoding — `Vary` can't help here, since it's one shared object, not a
306
+ * cache lookup. Encoding is deliberately left out of the coalesce key so mixed
307
+ * clients still coalesce; this adapts the shared result to each waiter instead.
350
308
  *
351
309
  * Only the correctness-critical direction is adapted: a gzip envelope served to
352
310
  * a non-gzip waiter is gunzipped back to JSON. The reverse (plain JSON to a
@@ -358,11 +316,8 @@ declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding
358
316
  };
359
317
 
360
318
  /**
361
- * Adapt a persisted `SchemaInput` into `@selvajs/compute`'s raw `InputParamSchema`
362
- * and let its `processInput` produce the typed `InputParam`. `values`/`acceptedFormats`
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.
319
+ * `SchemaInput` has no `values`/`acceptedFormats` (those live on the layout item's config), so
320
+ * valueList/file inputs here carry just the selected value with no option list.
366
321
  */
367
322
  declare function transformInputParameter(input: SchemaInput & {
368
323
  minimum?: number;
@@ -373,70 +328,56 @@ declare function transformInputParameter(input: SchemaInput & {
373
328
  /**
374
329
  * In-process single-flight — dogpile protection for every solve.
375
330
  *
376
- * Deliberately NOT conditional on a result cache being configured: the dogpile is
377
- * worst precisely when nothing else is caching (N concurrent identical solves each
378
- * paying a full Rhino round trip — the scheduler has no in-flight coalescing of its
379
- * own, and Rhino's `cachesolve` still costs a round trip per repeat). Gating this on
380
- * cache configuration, as an earlier version did, turned it off in exactly the
381
- * deployments most exposed to a cold-key stampede (hot public definition + a deploy).
331
+ * Always on, not gated behind a result cache being configured: the dogpile is worst
332
+ * when nothing else is caching, since N concurrent identical solves each pay a full
333
+ * Rhino round trip (the scheduler doesn't coalesce in-flight requests itself, and
334
+ * Rhino's `cachesolve` still costs a round trip per repeat). An earlier version
335
+ * gated this on cache config and so disabled it in exactly the deployments most
336
+ * exposed to a cold-key stampede — hot public definition plus a deploy.
382
337
  *
383
- * In-process only, sitting above `ISolveResultCache` (per app instance). A
384
- * cross-instance lease (Redis `SET NX`) is a later backend capability.
338
+ * Per app instance, above `ISolveResultCache`. No cross-instance lease (Redis
339
+ * `SET NX`) yet.
385
340
  *
386
- * Correctness note: the shared promise resolves to ONE value for all waiters, so
387
- * the wrapped work must return an immutable / independently-serializable result
388
- * (the solve pipeline's envelope is safe to share by reference here because the
389
- * app serializes it per response).
341
+ * The shared promise resolves to one value for every waiter, so `work` must return
342
+ * something safe to share by reference — the solve pipeline's envelope qualifies
343
+ * because the app serializes it per response.
390
344
  */
391
345
  interface SolveCacheSingleFlight {
392
346
  /**
393
- * Run `work` under `key`, coalescing concurrent identical calls: the first
394
- * caller executes, overlapping callers for the same key await the same promise
395
- * and result, and the key is freed as soon as it settles.
347
+ * Coalesces concurrent calls under the same key: the first caller runs `work`,
348
+ * later callers for that key await the same promise, and the key frees as soon
349
+ * as it settles.
396
350
  *
397
- * `onWaiterJoined` fires on the OWNER's call each time another caller joins its
398
- * flight — ownership changes what the owner's abort signal means (a solo run
399
- * may cancel on its own client's disconnect, a shared one may not, or it would
400
- * 499 every waiter). Never fires for a joining caller, and never after `work`
401
- * settles.
351
+ * `onWaiterJoined` fires only on the owner's call, only while `work` is still
352
+ * running, each time another caller joins. Ownership changes what the owner's
353
+ * abort signal should mean: a solo run can cancel on its own client's
354
+ * disconnect, but a shared run can't without 499-ing every waiter.
402
355
  */
403
356
  run<T>(key: string, work: () => Promise<T>, onWaiterJoined?: () => void): Promise<T>;
404
- /** Number of keys currently in flight (observability / tests). */
405
357
  inFlight(): number;
406
358
  }
407
359
  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
360
  onJoin?: (key: string) => void;
413
361
  }
414
362
  declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
415
363
 
416
364
  /**
417
- * `SolveEngine` — the facade that owns everything a consumer needs to run
418
- * interactive Grasshopper solves: the per-server warm-client cache, the
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.
365
+ * Facade over the warm-client cache, definition-byte cache, single-flight
366
+ * coalescing, and the solve pipeline — the primitives an app route would
367
+ * otherwise wire up by hand.
423
368
  *
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.
369
+ * Doesn't read env itself: `@selvajs/server` owns `resolveComputeLimits` and
370
+ * depends on `@selvajs/solve`, so calling it from here would be a circular
371
+ * import. Callers resolve their own limits and pass in the fields below.
429
372
  *
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`.
373
+ * Stays out of auth, DB reads, share tokens, rate limiting, metrics, and
374
+ * compute-server selection — all app policy, supplied per-call via
375
+ * `SolveEngineSolveArgs.server`.
434
376
  */
435
377
 
436
378
  /**
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.
379
+ * Subset of `ComputeLimits` (`@selvajs/server/compute`) the engine needs.
380
+ * Pass the resolved object through as-is — extra fields are ignored.
440
381
  */
441
382
  interface SolveEngineLimits {
442
383
  maxSolveDurationMs: number;
@@ -450,24 +391,22 @@ interface SolveEngineLimits {
450
391
  computeSolveCacheBytes: number;
451
392
  }
452
393
  interface SolveEngineOptions {
453
- /** Required — the engine does not read env itself (see module doc). */
454
394
  limits: SolveEngineLimits;
455
395
  logger?: ILogger;
456
- /** Max distinct warm compute servers before the LRU evicts the oldest. Default 16 (see `createClientCache`). */
396
+ /** Max distinct warm compute servers before the LRU evicts the oldest. Default 16. */
457
397
  maxWarmComputeServers?: number;
458
- /** Concise cache/timing logs when truthy; `'verbose'` also enables full lib-level request/response dumps. Default off. */
398
+ /** Concise cache/timing logs when truthy; `'verbose'` also dumps full lib-level requests/responses. Default off. */
459
399
  debug?: ClientCacheDebug;
460
400
  onDebugLog?: (message: string) => void;
461
- /** Fired when a caller joins an already-in-flight solve instead of running its own (`createSolveCacheSingleFlight`'s `onJoin`). */
401
+ /** Fired when a caller joins an already-in-flight solve instead of running its own. */
462
402
  onSolveCoalesced?: (key: string) => void;
463
403
  }
464
404
  /**
465
- * `definitionSource` accepts every form `runSolvePipeline` does, plus the
466
- * `{versionId, load}` sugar that builds (and caches) a `ByteCacheRef` internally.
467
- * A `ByteCacheRef` obtained from `engine.definitionRef()` ahead of time (e.g. to
468
- * read its bytes for schema extraction before solving) is accepted directly and
469
- * NOT re-wrapped — recognized by its `outcome` field, which a plain external
470
- * `DefinitionRef` never has.
405
+ * Accepts every form `runSolvePipeline` does, plus `{versionId, load}` sugar
406
+ * that builds (and caches) a `ByteCacheRef` internally. A `ByteCacheRef`
407
+ * obtained ahead of time from `engine.definitionRef()` (e.g. to read bytes for
408
+ * schema extraction before solving) is passed through as-is — detected by its
409
+ * `outcome` field, which a plain `DefinitionRef` never has.
471
410
  */
472
411
  type SolveEngineDefinitionSource = Uint8Array | string | DefinitionRef | ByteCacheRef | {
473
412
  versionId: string;
@@ -477,18 +416,17 @@ interface SolveEngineSolveArgs {
477
416
  server: ResolvedServer;
478
417
  definitionSource: SolveEngineDefinitionSource;
479
418
  /**
480
- * Coalesce-key identity for a raw `Uint8Array`/`string` `definitionSource`,
481
- * which has no natural identity the way a `DefinitionRef`'s `.key` does.
482
- * Required in that case — `solve()` throws without it, rather than silently
483
- * hashing bytes (a different identity convention than the rest of the
484
- * package, which keys on immutable ids). Ignored for the other source forms.
419
+ * Coalesce-key identity for a raw `Uint8Array`/`string` source, which has
420
+ * no natural identity the way a `DefinitionRef`'s `.key` does. Required in
421
+ * that case — `solve()` throws rather than silently hashing the bytes.
422
+ * Ignored for the other source forms.
485
423
  */
486
424
  definitionKey?: string;
487
425
  inputs: PipelineInput[];
488
426
  values: Record<string, unknown>;
489
427
  signal: AbortSignal;
490
428
  acceptEncoding?: string;
491
- /** Stamped as `X-Selva-Definition` on this solve's client — see `ClientCache.getClient`. */
429
+ /** Stamped as `X-Selva-Definition` on this solve's client. */
492
430
  definitionGuid?: string;
493
431
  loadStartMs?: number;
494
432
  defLoadMs?: number;