@selvajs/solve 1.0.0-beta.7 → 1.0.0-beta.9

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