@selvajs/solve 0.2.0-beta.0 → 0.2.0-beta.2

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,5 +1,7 @@
1
- import { GrasshopperClient, SolveScheduler, GrasshopperComputeResponse, SolveDefinition, DataTree, InputParam } from '@selvajs/compute';
1
+ import { GrasshopperClient, SolveScheduler, GrasshopperComputeResponse, SolveDefinition, DataTree, InputParam, DefinitionRef } from '@selvajs/compute';
2
+ export { DefinitionRef, SolveDefinition, isDefinitionRef } from '@selvajs/compute';
2
3
  import { SchemaInput } from '@selvajs/schemas';
4
+ import { ILogger } from '@selvajs/platform';
3
5
  export { S as SolveInput } from './solve-input-CqYtetTA.cjs';
4
6
 
5
7
  /**
@@ -23,7 +25,7 @@ interface CachedClient {
23
25
  scheduler: SolveScheduler;
24
26
  /**
25
27
  * Last Server-Timing decode/solve/encode, written by onServerTiming.
26
- * The scheduler runs up to `maxConcurrentSolves` at once, so `last` alone
28
+ * The scheduler runs up to `maxConcurrent` solves at once, so `last` alone
27
29
  * can't be trusted per-request: callers snapshot `seq` before their solve
28
30
  * and only attribute `last` to themselves if exactly one write happened
29
31
  * since (see the guard in `runSolvePipeline`), dropping it otherwise
@@ -50,6 +52,12 @@ interface CachedClient {
50
52
  seq: number;
51
53
  };
52
54
  }
55
+ /**
56
+ * Debug verbosity: `false` is silent, `true` gives concise cache/timing logs
57
+ * (`onDebugLog`), `'verbose'` also enables full lib-level request/response
58
+ * dumps (incl. geometry) — a superset of `true`, not an independent setting.
59
+ */
60
+ type ClientCacheDebug = boolean | 'verbose';
53
61
  /** Config injected by the consuming app, so this module stays env-agnostic and testable. */
54
62
  interface ClientCacheConfig {
55
63
  /** Per-solve timeout forwarded to the scheduler (`ComputeLimits.maxSolveDurationMs`). */
@@ -60,15 +68,10 @@ interface ClientCacheConfig {
60
68
  cacheerroredsolves: boolean;
61
69
  /** Reference large definitions by server cache key (pointer) instead of re-uploading. */
62
70
  reuseServerDefinitionCache: boolean;
63
- /**
64
- * Max in-flight solves per compute server (scheduler `maxConcurrent`; excess
65
- * FIFO-queues). Size to the server's `compute.geometry` child count
66
- * (`ComputeLimits.computeMaxConcurrentSolves`).
67
- */
68
- maxConcurrentSolves: number;
69
71
  /**
70
72
  * Backpressure — max solves that may WAIT in the FIFO queue (excludes the
71
- * in-flight `maxConcurrentSolves`). `0` = unbounded (`ComputeLimits.
73
+ * in-flight solves, capped at the scheduler's `maxConcurrent`, itself driven
74
+ * by the compute server's probed child count). `0` = unbounded (`ComputeLimits.
72
75
  * computeMaxQueueDepth`); a full queue sheds new solves with `QUEUE_FULL`.
73
76
  */
74
77
  maxQueueDepth: number;
@@ -81,16 +84,14 @@ interface ClientCacheConfig {
81
84
  /**
82
85
  * Byte budget for this client's in-process solve cache, evicted LRU alongside
83
86
  * its entry-count cap. Applies per warm client — total worst-case heap is this
84
- * × `maxCachedClients`. `0` disables the cache entirely
87
+ * × `maxWarmComputeServers`. `0` disables the cache entirely
85
88
  * (`ComputeLimits.computeSolveCacheBytes`, env `COMPUTE_SOLVE_CACHE_MB`).
86
89
  */
87
90
  responseCacheMaxBytes: number;
88
- /** Concise cache/timing logs. When false, `onDebugLog` is never invoked. */
89
- debug: boolean;
90
- /** VERBOSE lib-level logging (full solve request/response incl. geometry). */
91
- debugVerbose: boolean;
92
- /** Max distinct warm clients before the LRU evicts the oldest. Default 16. */
93
- maxCachedClients?: number;
91
+ /** Debug verbosity. `onDebugLog` is only ever invoked when this is not `false`. */
92
+ debug: ClientCacheDebug;
93
+ /** Max distinct warm compute servers before the LRU evicts the oldest. Default 16. */
94
+ maxWarmComputeServers?: number;
94
95
  /** Sink for the concise debug lines (the app wires `console.log`). */
95
96
  onDebugLog?: (message: string) => void;
96
97
  }
@@ -112,6 +113,14 @@ interface ClientCache {
112
113
  * with the client that owns them, so totals can fall over time.
113
114
  */
114
115
  solveCacheStats(): SolveCacheStats;
116
+ /**
117
+ * Drop every retained solve result, keeping the warm clients (and their
118
+ * connections and server-side definition pointers) intact. Nothing expires
119
+ * these on its own — the byte budget is the only other pressure — so this is
120
+ * the operator's release valve when a definition's inputs no longer describe
121
+ * its output, e.g. it reads an external source that has since changed.
122
+ */
123
+ clearSolveCaches(): void;
115
124
  /** Dispose every warm client. Test seam / shutdown hook. */
116
125
  disposeAll(): void;
117
126
  }
@@ -122,7 +131,7 @@ interface SolveCacheStats {
122
131
  bytes: number;
123
132
  hits: number;
124
133
  misses: number;
125
- /** Entries dropped under size/byte pressure (not TTL expiry or replacement). */
134
+ /** Entries dropped under size/byte pressure (not replacement or manual clears). */
126
135
  evictions: number;
127
136
  }
128
137
  declare function createClientCache(config: ClientCacheConfig): ClientCache;
@@ -404,4 +413,121 @@ interface SolveCacheSingleFlightOptions {
404
413
  }
405
414
  declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
406
415
 
407
- export { type ByteCacheRef, type ByteCacheStats, type ByteRefOutcome, COMPUTE_CONTRACT_VERSION, COMPUTE_VERSION_HEADER, type CachedClient, type ClientCache, type ClientCacheConfig, type DefinitionByteCache, type PipelineInput, type ResolvedServer, type ServerIdentity, type SolveCacheSingleFlight, type SolveCacheSingleFlightOptions, type SolveCacheStats, type SolveEnvelope, type SolveOutcome, type SolvePhaseMetrics, type SolvePipelineArgs, adaptEnvelopeToEncoding, buildSolveInputTree, createClientCache, createDefinitionByteCache, createSolveCacheSingleFlight, runSolvePipeline, serverIdentity, transformInputParameter };
416
+ /**
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.
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.
440
+ */
441
+ interface SolveEngineLimits {
442
+ maxSolveDurationMs: number;
443
+ computeResponseMaxBytes: number;
444
+ computeReuseDefinitionCache: boolean;
445
+ computeServerCachesolve: boolean;
446
+ computeCacheErroredSolves: boolean;
447
+ computeMaxQueueDepth: number;
448
+ computeQueueWaitMs: number;
449
+ computeDefinitionCacheBytes: number;
450
+ computeSolveCacheBytes: number;
451
+ }
452
+ interface SolveEngineOptions {
453
+ /** Required — the engine does not read env itself (see module doc). */
454
+ limits: SolveEngineLimits;
455
+ logger?: ILogger;
456
+ /** Max distinct warm compute servers before the LRU evicts the oldest. Default 16 (see `createClientCache`). */
457
+ maxWarmComputeServers?: number;
458
+ /** Concise cache/timing logs when truthy; `'verbose'` also enables full lib-level request/response dumps. Default off. */
459
+ debug?: ClientCacheDebug;
460
+ onDebugLog?: (message: string) => void;
461
+ /** Fired when a caller joins an already-in-flight solve instead of running its own (`createSolveCacheSingleFlight`'s `onJoin`). */
462
+ onSolveCoalesced?: (key: string) => void;
463
+ }
464
+ /**
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.
471
+ */
472
+ type SolveEngineDefinitionSource = Uint8Array | string | DefinitionRef | ByteCacheRef | {
473
+ versionId: string;
474
+ load: () => Promise<Uint8Array>;
475
+ };
476
+ interface SolveEngineSolveArgs {
477
+ server: ResolvedServer;
478
+ definitionSource: SolveEngineDefinitionSource;
479
+ /**
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.
485
+ */
486
+ definitionKey?: string;
487
+ inputs: PipelineInput[];
488
+ values: Record<string, unknown>;
489
+ signal: AbortSignal;
490
+ acceptEncoding?: string;
491
+ /** Stamped as `X-Selva-Definition` on this solve's client — see `ClientCache.getClient`. */
492
+ definitionGuid?: string;
493
+ loadStartMs?: number;
494
+ defLoadMs?: number;
495
+ prepMarks?: [string, number][];
496
+ }
497
+ interface FrameworkAgnosticResponse {
498
+ status: number;
499
+ headers: Record<string, string>;
500
+ body: string | Uint8Array;
501
+ }
502
+ interface SolveEngineStats {
503
+ client: SolveCacheStats;
504
+ definitionBytes: ByteCacheStats;
505
+ coalescing: {
506
+ inFlight: number;
507
+ };
508
+ }
509
+ declare class SolveEngine {
510
+ private readonly limits;
511
+ private readonly clientCache;
512
+ private readonly byteCache;
513
+ private readonly singleFlight;
514
+ constructor(options: SolveEngineOptions);
515
+ getClient(server: ResolvedServer, opts?: {
516
+ definitionGuid?: string;
517
+ }): Promise<CachedClient>;
518
+ definitionRef(versionId: string, load: () => Promise<Uint8Array>): ByteCacheRef;
519
+ evictServer(id: string): void;
520
+ clearSolveCaches(): void;
521
+ stats(): SolveEngineStats;
522
+ solve(args: SolveEngineSolveArgs): Promise<SolveOutcome>;
523
+ toResponse(outcome: SolveOutcome, opts?: {
524
+ onError?: (status: number, body: {
525
+ message: string;
526
+ retryAfter?: number;
527
+ }) => never;
528
+ }): FrameworkAgnosticResponse;
529
+ toWebResponse(outcome: SolveOutcome): Response;
530
+ private resolveDefinitionSource;
531
+ }
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 };
package/dist/server.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { GrasshopperClient, SolveScheduler, GrasshopperComputeResponse, SolveDefinition, DataTree, InputParam } from '@selvajs/compute';
1
+ import { GrasshopperClient, SolveScheduler, GrasshopperComputeResponse, SolveDefinition, DataTree, InputParam, DefinitionRef } from '@selvajs/compute';
2
+ export { DefinitionRef, SolveDefinition, isDefinitionRef } from '@selvajs/compute';
2
3
  import { SchemaInput } from '@selvajs/schemas';
4
+ import { ILogger } from '@selvajs/platform';
3
5
  export { S as SolveInput } from './solve-input-CqYtetTA.js';
4
6
 
5
7
  /**
@@ -23,7 +25,7 @@ interface CachedClient {
23
25
  scheduler: SolveScheduler;
24
26
  /**
25
27
  * Last Server-Timing decode/solve/encode, written by onServerTiming.
26
- * The scheduler runs up to `maxConcurrentSolves` at once, so `last` alone
28
+ * The scheduler runs up to `maxConcurrent` solves at once, so `last` alone
27
29
  * can't be trusted per-request: callers snapshot `seq` before their solve
28
30
  * and only attribute `last` to themselves if exactly one write happened
29
31
  * since (see the guard in `runSolvePipeline`), dropping it otherwise
@@ -50,6 +52,12 @@ interface CachedClient {
50
52
  seq: number;
51
53
  };
52
54
  }
55
+ /**
56
+ * Debug verbosity: `false` is silent, `true` gives concise cache/timing logs
57
+ * (`onDebugLog`), `'verbose'` also enables full lib-level request/response
58
+ * dumps (incl. geometry) — a superset of `true`, not an independent setting.
59
+ */
60
+ type ClientCacheDebug = boolean | 'verbose';
53
61
  /** Config injected by the consuming app, so this module stays env-agnostic and testable. */
54
62
  interface ClientCacheConfig {
55
63
  /** Per-solve timeout forwarded to the scheduler (`ComputeLimits.maxSolveDurationMs`). */
@@ -60,15 +68,10 @@ interface ClientCacheConfig {
60
68
  cacheerroredsolves: boolean;
61
69
  /** Reference large definitions by server cache key (pointer) instead of re-uploading. */
62
70
  reuseServerDefinitionCache: boolean;
63
- /**
64
- * Max in-flight solves per compute server (scheduler `maxConcurrent`; excess
65
- * FIFO-queues). Size to the server's `compute.geometry` child count
66
- * (`ComputeLimits.computeMaxConcurrentSolves`).
67
- */
68
- maxConcurrentSolves: number;
69
71
  /**
70
72
  * Backpressure — max solves that may WAIT in the FIFO queue (excludes the
71
- * in-flight `maxConcurrentSolves`). `0` = unbounded (`ComputeLimits.
73
+ * in-flight solves, capped at the scheduler's `maxConcurrent`, itself driven
74
+ * by the compute server's probed child count). `0` = unbounded (`ComputeLimits.
72
75
  * computeMaxQueueDepth`); a full queue sheds new solves with `QUEUE_FULL`.
73
76
  */
74
77
  maxQueueDepth: number;
@@ -81,16 +84,14 @@ interface ClientCacheConfig {
81
84
  /**
82
85
  * Byte budget for this client's in-process solve cache, evicted LRU alongside
83
86
  * its entry-count cap. Applies per warm client — total worst-case heap is this
84
- * × `maxCachedClients`. `0` disables the cache entirely
87
+ * × `maxWarmComputeServers`. `0` disables the cache entirely
85
88
  * (`ComputeLimits.computeSolveCacheBytes`, env `COMPUTE_SOLVE_CACHE_MB`).
86
89
  */
87
90
  responseCacheMaxBytes: number;
88
- /** Concise cache/timing logs. When false, `onDebugLog` is never invoked. */
89
- debug: boolean;
90
- /** VERBOSE lib-level logging (full solve request/response incl. geometry). */
91
- debugVerbose: boolean;
92
- /** Max distinct warm clients before the LRU evicts the oldest. Default 16. */
93
- maxCachedClients?: number;
91
+ /** Debug verbosity. `onDebugLog` is only ever invoked when this is not `false`. */
92
+ debug: ClientCacheDebug;
93
+ /** Max distinct warm compute servers before the LRU evicts the oldest. Default 16. */
94
+ maxWarmComputeServers?: number;
94
95
  /** Sink for the concise debug lines (the app wires `console.log`). */
95
96
  onDebugLog?: (message: string) => void;
96
97
  }
@@ -112,6 +113,14 @@ interface ClientCache {
112
113
  * with the client that owns them, so totals can fall over time.
113
114
  */
114
115
  solveCacheStats(): SolveCacheStats;
116
+ /**
117
+ * Drop every retained solve result, keeping the warm clients (and their
118
+ * connections and server-side definition pointers) intact. Nothing expires
119
+ * these on its own — the byte budget is the only other pressure — so this is
120
+ * the operator's release valve when a definition's inputs no longer describe
121
+ * its output, e.g. it reads an external source that has since changed.
122
+ */
123
+ clearSolveCaches(): void;
115
124
  /** Dispose every warm client. Test seam / shutdown hook. */
116
125
  disposeAll(): void;
117
126
  }
@@ -122,7 +131,7 @@ interface SolveCacheStats {
122
131
  bytes: number;
123
132
  hits: number;
124
133
  misses: number;
125
- /** Entries dropped under size/byte pressure (not TTL expiry or replacement). */
134
+ /** Entries dropped under size/byte pressure (not replacement or manual clears). */
126
135
  evictions: number;
127
136
  }
128
137
  declare function createClientCache(config: ClientCacheConfig): ClientCache;
@@ -404,4 +413,121 @@ interface SolveCacheSingleFlightOptions {
404
413
  }
405
414
  declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
406
415
 
407
- export { type ByteCacheRef, type ByteCacheStats, type ByteRefOutcome, COMPUTE_CONTRACT_VERSION, COMPUTE_VERSION_HEADER, type CachedClient, type ClientCache, type ClientCacheConfig, type DefinitionByteCache, type PipelineInput, type ResolvedServer, type ServerIdentity, type SolveCacheSingleFlight, type SolveCacheSingleFlightOptions, type SolveCacheStats, type SolveEnvelope, type SolveOutcome, type SolvePhaseMetrics, type SolvePipelineArgs, adaptEnvelopeToEncoding, buildSolveInputTree, createClientCache, createDefinitionByteCache, createSolveCacheSingleFlight, runSolvePipeline, serverIdentity, transformInputParameter };
416
+ /**
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.
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.
440
+ */
441
+ interface SolveEngineLimits {
442
+ maxSolveDurationMs: number;
443
+ computeResponseMaxBytes: number;
444
+ computeReuseDefinitionCache: boolean;
445
+ computeServerCachesolve: boolean;
446
+ computeCacheErroredSolves: boolean;
447
+ computeMaxQueueDepth: number;
448
+ computeQueueWaitMs: number;
449
+ computeDefinitionCacheBytes: number;
450
+ computeSolveCacheBytes: number;
451
+ }
452
+ interface SolveEngineOptions {
453
+ /** Required — the engine does not read env itself (see module doc). */
454
+ limits: SolveEngineLimits;
455
+ logger?: ILogger;
456
+ /** Max distinct warm compute servers before the LRU evicts the oldest. Default 16 (see `createClientCache`). */
457
+ maxWarmComputeServers?: number;
458
+ /** Concise cache/timing logs when truthy; `'verbose'` also enables full lib-level request/response dumps. Default off. */
459
+ debug?: ClientCacheDebug;
460
+ onDebugLog?: (message: string) => void;
461
+ /** Fired when a caller joins an already-in-flight solve instead of running its own (`createSolveCacheSingleFlight`'s `onJoin`). */
462
+ onSolveCoalesced?: (key: string) => void;
463
+ }
464
+ /**
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.
471
+ */
472
+ type SolveEngineDefinitionSource = Uint8Array | string | DefinitionRef | ByteCacheRef | {
473
+ versionId: string;
474
+ load: () => Promise<Uint8Array>;
475
+ };
476
+ interface SolveEngineSolveArgs {
477
+ server: ResolvedServer;
478
+ definitionSource: SolveEngineDefinitionSource;
479
+ /**
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.
485
+ */
486
+ definitionKey?: string;
487
+ inputs: PipelineInput[];
488
+ values: Record<string, unknown>;
489
+ signal: AbortSignal;
490
+ acceptEncoding?: string;
491
+ /** Stamped as `X-Selva-Definition` on this solve's client — see `ClientCache.getClient`. */
492
+ definitionGuid?: string;
493
+ loadStartMs?: number;
494
+ defLoadMs?: number;
495
+ prepMarks?: [string, number][];
496
+ }
497
+ interface FrameworkAgnosticResponse {
498
+ status: number;
499
+ headers: Record<string, string>;
500
+ body: string | Uint8Array;
501
+ }
502
+ interface SolveEngineStats {
503
+ client: SolveCacheStats;
504
+ definitionBytes: ByteCacheStats;
505
+ coalescing: {
506
+ inFlight: number;
507
+ };
508
+ }
509
+ declare class SolveEngine {
510
+ private readonly limits;
511
+ private readonly clientCache;
512
+ private readonly byteCache;
513
+ private readonly singleFlight;
514
+ constructor(options: SolveEngineOptions);
515
+ getClient(server: ResolvedServer, opts?: {
516
+ definitionGuid?: string;
517
+ }): Promise<CachedClient>;
518
+ definitionRef(versionId: string, load: () => Promise<Uint8Array>): ByteCacheRef;
519
+ evictServer(id: string): void;
520
+ clearSolveCaches(): void;
521
+ stats(): SolveEngineStats;
522
+ solve(args: SolveEngineSolveArgs): Promise<SolveOutcome>;
523
+ toResponse(outcome: SolveOutcome, opts?: {
524
+ onError?: (status: number, body: {
525
+ message: string;
526
+ retryAfter?: number;
527
+ }) => never;
528
+ }): FrameworkAgnosticResponse;
529
+ toWebResponse(outcome: SolveOutcome): Response;
530
+ private resolveDefinitionSource;
531
+ }
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 };
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- import{ErrorCodes as w,RhinoComputeError as I,TreeBuilder as O}from"@selvajs/compute";import{gzip as _,gunzipSync as A}from"zlib";import{promisify as L}from"util";import{processInput as z}from"@selvajs/compute";function T(e,r){let t={id:e.id,name:e.nickname,nickname:e.nickname||null,description:e.description||"",paramType:e.paramType==="dynamicValueList"?"valueList":e.paramType,treeAccess:e.inputStructure==="tree",minimum:e.minimum??null,maximum:e.maximum??null,atLeast:1,atMost:1,stepSize:e.stepSize,default:r??e.default},s=z(t),o=r??e.default;return s.default==null&&o!=null&&(Array.isArray(o)||s.paramType==="ValueList"&&typeof o!="object")&&(s.default=o),s}var $=L(_),E=1,P="X-Selva-Compute-Version";function B(e,r){return O.fromInputParams(e.filter(t=>t.paramType).map(t=>T(t,r[t.id])))}async function D(e){let{client:r,signal:t}=e,s=performance.now(),o=e.inputTree??B(e.inputs,e.values),u=performance.now()-s,i=r.solveMeta.seq,l=r.rhinoTiming.seq,n,a=performance.now();try{n=await r.scheduler.solve(e.definitionSource,o,{signal:t})}catch(v){let b=performance.now()-a;if(v instanceof Error&&v.name==="AbortError")return t.aborted?{kind:"client_abort",durationMs:b}:{kind:"timeout",durationMs:b,message:`Solve exceeded the ${Math.round(e.maxSolveDurationMs/1e3)}s deadline.`};if(v instanceof I){if(v.code===w.QUEUE_FULL)return{kind:"shed",durationMs:b,reason:"queue_full",retryAfterSeconds:1,message:"Compute server is at capacity. Retry shortly."};if(v.code===w.QUEUE_TIMEOUT)return{kind:"shed",durationMs:b,reason:"queue_timeout",retryAfterSeconds:1,message:"Compute request waited too long in the queue. Retry shortly."}}return{kind:"compute_error",durationMs:b,error:v}}let c=performance.now()-a,h=performance.now(),d;try{d=JSON.stringify(n)}catch(v){if(v instanceof RangeError)return{kind:"too_large"};throw v}if(d.length>e.responseMaxBytes)return{kind:"too_large"};let p=performance.now()-h,y=/\bgzip\b/i.test(e.acceptEncoding)&&d.length>1024,f=null,g=0;if(y){let v=performance.now();f=await $(Buffer.from(d)),g=performance.now()-v}let S=performance.now()-e.loadStartMs,M=r.solveMeta.seq===i+1?r.solveMeta.last:null,k=M&&!M.fromCache&&r.rhinoTiming.seq===l+1?r.rhinoTiming.last:null,C={"Content-Type":"application/json","Server-Timing":U({defLoadMs:e.defLoadMs,treeBuildMs:u,solveMs:c,serializeMs:p,gzipMs:g,serverTotalMs:S,rhino:k,settle:M,byteRefOutcome:e.byteRefOutcome,prepMarks:e.prepMarks??[]}),[P]:String(E),Vary:"Accept-Encoding"},R={treeBuildMs:u,solveMs:c,serializeMs:p,gzipMs:g,serverTotalMs:S,serializedBytes:d.length,compressedBytes:f?.byteLength??null},x;return f?(C["Content-Encoding"]="gzip",C["Content-Length"]=String(f.byteLength),x={body:new Uint8Array(f),encoding:"gzip",headers:C,result:n,metrics:R}):(C["Content-Length"]=String(Buffer.byteLength(d)),x={body:d,headers:C,result:n,metrics:R}),{kind:"ok",envelope:x,solveMs:c,errorCount:n.errors?.length??0,warningCount:n.warnings?.length??0}}function F(e,r){let t=/\bgzip\b/i.test(r);if(e.encoding==="gzip"&&!t){let s=A(e.body instanceof Uint8Array?e.body:Buffer.from(e.body)).toString("utf8"),o={...e.headers};return delete o["Content-Encoding"],o["Content-Length"]=String(Buffer.byteLength(s)),{body:s,headers:o}}return{body:e.body,headers:e.headers}}function U(e){let r=`load;dur=${e.defLoadMs.toFixed(1)}, tree;dur=${e.treeBuildMs.toFixed(1)}, solve;dur=${e.solveMs.toFixed(1)}, serialize;dur=${e.serializeMs.toFixed(1)}, gzip;dur=${e.gzipMs.toFixed(1)}, total;dur=${e.serverTotalMs.toFixed(1)}`,t=e.rhino;if(t){let u=t.decode+t.solve+t.encode,i=Math.max(0,e.solveMs-u);r+=`, rhino_decode;dur=${t.decode.toFixed(1)}, rhino_solve;dur=${t.solve.toFixed(1)}, rhino_encode;dur=${t.encode.toFixed(1)}, compute_link;dur=${i.toFixed(1)}`}for(let[u,i]of e.prepMarks)r+=`, p_${u};dur=${i.toFixed(1)}`;let s=e.settle;s&&(r+=`, selva_cache;dur=${s.fromCache?1:0}`,s.definitionReuploaded!==void 0&&(r+=`, def_reupload;dur=${s.definitionReuploaded?1:0}`));let o=e.byteRefOutcome;if(o){let u=o.loaded?o.fromCache?"hit":"miss":"skipped";r+=`, def_bytes;desc=${u}`}return r}import{GrasshopperClient as q,enableDebugLogging as G}from"@selvajs/compute";function V(e){return e.id}var N=16;function j(e){let r=e.maxCachedClients??N,t=new Map,s=new Map;e.debugVerbose&&G();let o=i=>{e.debug&&e.onDebugLog?.(i)};async function u(i,l){let n={last:null,seq:0},a={last:null,seq:0},c={serverUrl:i.serverUrl,apiKey:i.apiKey,debug:e.debugVerbose,cachesolve:e.cachesolve,cacheerroredsolves:e.cacheerroredsolves,onServerTiming:p=>{let m=p.decode??0,y=p.solve??0,f=p.encode??0;if(n.seq+=1,n.last={decode:m,solve:y,encode:f},!e.debug)return;let g=m<=1?"HIT (definition reused, no re-upload/parse)":"miss (full parse)",S=e.cachesolve&&y<=1?"HIT (cached result, solve skipped)":e.cachesolve?"miss (solved fresh)":"off";o(`[Compute/rhino-cache] decode=${m}ms solve=${y}ms encode=${f}ms | definition-cache: ${g} | solve-cache (cachesolve): ${S}`)}};l&&(c.headers={"X-Selva-Definition":l});let h=await q.create(c),d=h.createScheduler({mode:"queue",maxConcurrent:e.maxConcurrentSolves,maxQueueDepth:e.maxQueueDepth||void 0,queueWaitMs:e.queueWaitMs||void 0,timeoutMs:e.maxSolveDurationMs,cache:e.responseCacheMaxBytes>0?{maxEntries:20,ttlMs:5*6e4,maxBytes:e.responseCacheMaxBytes}:!1,reuseServerDefinitionCache:e.reuseServerDefinitionCache,onSettle:(p,m)=>{if(a.seq+=1,m.status==="success"&&(a.last={fromCache:m.fromCache===!0,definitionReuploaded:m.definitionReuploaded},!!e.debug&&(o(`[Compute/selva-cache] ${m.fromCache?"HIT \u2014 served from cache (no compute call)":"miss \u2014 went to Rhino.Compute"} (${Math.round(m.durationMs)}ms)`),m.fromCache||o(m.definitionReuploaded===!0?"[Compute/def-cache] miss \u2014 definition RE-UPLOADED (pointer was cold/stale)":m.definitionReuploaded===!1?"[Compute/def-cache] HIT \u2014 definition reused from server cache (no upload)":"[Compute/def-cache] n/a \u2014 reuse disabled or non-reusable definition"),!m.fromCache))){let y=m.response?.errors,f=Array.isArray(y)?y.length:0;if(f>0){let g=e.cacheerroredsolves?"cached (cacheerroredsolves on)":"not cached (cacheerroredsolves off) \u2014 re-run until fixed";o(`[Compute/rhino-cache] ${f} GH error(s); ${g}. First: ${JSON.stringify(y[0])}`)}}}});return{client:h,scheduler:d,rhinoTiming:n,solveMeta:a}}return{async getClient(i,l){let n=i.id,a=t.get(n);if(a)return t.delete(n),t.set(n,a),a;let c=s.get(n);if(c)return c;o(`[Compute/client-cache] miss \u2014 building warm client for server ${n}`);let h=(async()=>{let d=await u(i,l?.definitionGuid);if(t.size>=r){let p=t.keys().next().value;p!==void 0&&(o(`[Compute/client-cache] LRU evicted warm client for server ${p} (cap ${r})`),t.get(p)?.scheduler.dispose(),t.delete(p))}return t.set(n,d),d})().finally(()=>s.delete(n));return s.set(n,h),h},evict(i){let l=i,n=t.get(l);n&&(o(`[Compute/client-cache] evicted warm client for server ${l} (config change)`),n.scheduler.dispose(),t.delete(l))},solveCacheStats(){let i={warmClients:t.size,entries:0,bytes:0,hits:0,misses:0,evictions:0};for(let l of t.values()){let n=l.scheduler.cacheStats();i.entries+=n.entries,i.bytes+=n.bytes,i.hits+=n.hits,i.misses+=n.misses,i.evictions+=n.evictions}return i},disposeAll(){o(`[Compute/client-cache] disposing all ${t.size} warm client(s)`);for(let i of t.values())i.scheduler.dispose();t.clear()}}}function H(e){let r=Math.max(0,Math.floor(e)),t=new Map,s=0,o={hits:0,misses:0,evictions:0};function u(n,a){t.delete(n),t.set(n,a)}function i(){for(;s>r&&t.size>0;){let n=t.keys().next().value,a=t.get(n);t.delete(n),s-=a.bytes.byteLength,o.evictions+=1}}function l(n,a){if(r===0||a.byteLength>r)return;let c=t.get(n);c&&(s-=c.bytes.byteLength),t.set(n,{bytes:a}),s+=a.byteLength,i()}return{getOrLoad(n,a){let c={loaded:!1,fromCache:!1};return{key:n,outcome:c,load:async()=>{let h=t.get(n);if(h)return u(n,h),o.hits+=1,c.loaded=!0,c.fromCache=!0,h.bytes;o.misses+=1,c.loaded=!0,c.fromCache=!1;let d=await a();return l(n,d),d}}},stats(){return{hits:o.hits,misses:o.misses,evictions:o.evictions,entries:t.size,bytes:s}},clear(){t.clear(),s=0}}}function W(e={}){let r=new Map;return{run(t,s,o){let u=r.get(t);if(u)return e.onJoin?.(t),u.notifyOwner?.(),u.promise;let i={notifyOwner:o,promise:void 0},l=(async()=>s())().finally(()=>{i.notifyOwner=void 0,r.get(t)===i&&r.delete(t)});return i.promise=l,r.set(t,i),l},inFlight(){return r.size}}}export{E as COMPUTE_CONTRACT_VERSION,P as COMPUTE_VERSION_HEADER,F as adaptEnvelopeToEncoding,B as buildSolveInputTree,j as createClientCache,H as createDefinitionByteCache,W as createSolveCacheSingleFlight,D as runSolvePipeline,V as serverIdentity,T as transformInputParameter};
1
+ import{ErrorCodes as I,RhinoComputeError as U,TreeBuilder as q}from"@selvajs/compute";import{gzip as W,gunzipSync as G}from"zlib";import{promisify as N}from"util";import{processInput as $}from"@selvajs/compute";function A(e,n){let t={id:e.id,name:e.nickname,nickname:e.nickname||null,description:e.description||"",paramType:e.paramType==="dynamicValueList"?"valueList":e.paramType,treeAccess:e.inputStructure==="tree",minimum:e.minimum??null,maximum:e.maximum??null,atLeast:1,atMost:1,stepSize:e.stepSize,default:n??e.default},r=$(t),o=n??e.default;return r.default==null&&o!=null&&(Array.isArray(o)||r.paramType==="ValueList"&&typeof o!="object")&&(r.default=o),r}var j=N(W),F=1,_="X-Selva-Compute-Version";function E(e,n){return q.fromInputParams(e.filter(t=>t.paramType).map(t=>A(t,n[t.id])))}async function B(e){let{client:n,signal:t}=e,r=performance.now(),o=e.inputTree??E(e.inputs,e.values),a=performance.now()-r,s=n.solveMeta.seq,m=n.rhinoTiming.seq,i,c=performance.now();try{i=await n.scheduler.solve(e.definitionSource,o,{signal:t})}catch(v){let x=performance.now()-c;if(v instanceof Error&&v.name==="AbortError")return t.aborted?{kind:"client_abort",durationMs:x}:{kind:"timeout",durationMs:x,message:`Solve exceeded the ${Math.round(e.maxSolveDurationMs/1e3)}s deadline.`};if(v instanceof U){if(v.code===I.QUEUE_FULL)return{kind:"shed",durationMs:x,reason:"queue_full",retryAfterSeconds:1,message:"Compute server is at capacity. Retry shortly."};if(v.code===I.QUEUE_TIMEOUT)return{kind:"shed",durationMs:x,reason:"queue_timeout",retryAfterSeconds:1,message:"Compute request waited too long in the queue. Retry shortly."}}return{kind:"compute_error",durationMs:x,error:v}}let u=performance.now()-c,p=performance.now(),d;try{d=JSON.stringify(i)}catch(v){if(v instanceof RangeError)return{kind:"too_large"};throw v}if(d.length>e.responseMaxBytes)return{kind:"too_large"};let f=performance.now()-p,M=/\bgzip\b/i.test(e.acceptEncoding)&&d.length>1024,C=null,S=0;if(M){let v=performance.now();C=await j(Buffer.from(d)),S=performance.now()-v}let g=performance.now()-e.loadStartMs,l=n.solveMeta.seq===s+1?n.solveMeta.last:null,h=l&&!l.fromCache&&n.rhinoTiming.seq===m+1?n.rhinoTiming.last:null,y={"Content-Type":"application/json","Server-Timing":V({defLoadMs:e.defLoadMs,treeBuildMs:a,solveMs:u,serializeMs:f,gzipMs:S,serverTotalMs:g,rhino:h,settle:l,byteRefOutcome:e.byteRefOutcome,prepMarks:e.prepMarks??[]}),[_]:String(F),Vary:"Accept-Encoding"},R={treeBuildMs:a,solveMs:u,serializeMs:f,gzipMs:S,serverTotalMs:g,serializedBytes:d.length,compressedBytes:C?.byteLength??null},D;return C?(y["Content-Encoding"]="gzip",y["Content-Length"]=String(C.byteLength),D={body:new Uint8Array(C),encoding:"gzip",headers:y,result:i,metrics:R}):(y["Content-Length"]=String(Buffer.byteLength(d)),D={body:d,headers:y,result:i,metrics:R}),{kind:"ok",envelope:D,solveMs:u,errorCount:i.errors?.length??0,warningCount:i.warnings?.length??0}}function T(e,n){let t=/\bgzip\b/i.test(n);if(e.encoding==="gzip"&&!t){let r=G(e.body instanceof Uint8Array?e.body:Buffer.from(e.body)).toString("utf8"),o={...e.headers};return delete o["Content-Encoding"],o["Content-Length"]=String(Buffer.byteLength(r)),{body:r,headers:o}}return{body:e.body,headers:e.headers}}function V(e){let n=`load;dur=${e.defLoadMs.toFixed(1)}, tree;dur=${e.treeBuildMs.toFixed(1)}, solve;dur=${e.solveMs.toFixed(1)}, serialize;dur=${e.serializeMs.toFixed(1)}, gzip;dur=${e.gzipMs.toFixed(1)}, total;dur=${e.serverTotalMs.toFixed(1)}`,t=e.rhino;if(t){let a=t.decode+t.solve+t.encode,s=Math.max(0,e.solveMs-a);n+=`, rhino_decode;dur=${t.decode.toFixed(1)}, rhino_solve;dur=${t.solve.toFixed(1)}, rhino_encode;dur=${t.encode.toFixed(1)}, compute_link;dur=${s.toFixed(1)}`}for(let[a,s]of e.prepMarks)n+=`, p_${a};dur=${s.toFixed(1)}`;let r=e.settle;r&&(n+=`, selva_cache;dur=${r.fromCache?1:0}`,r.definitionReuploaded!==void 0&&(n+=`, def_reupload;dur=${r.definitionReuploaded?1:0}`));let o=e.byteRefOutcome;if(o){let a=o.loaded?o.fromCache?"hit":"miss":"skipped";n+=`, def_bytes;desc=${a}`}return n}import{GrasshopperClient as K,enableDebugLogging as Q}from"@selvajs/compute";function H(e){return e.id}var J=16,z=1,X=300*1e3;function k(e){let n=e.maxWarmComputeServers??J,t=new Map,r=new Map;e.debug==="verbose"&&Q();let o=s=>{e.debug&&e.onDebugLog?.(s)};async function a(s,m){let i={last:null,seq:0},c={last:null,seq:0},u=e.debug==="verbose",p={serverUrl:s.serverUrl,apiKey:s.apiKey,debug:u,cachesolve:e.cachesolve,cacheerroredsolves:e.cacheerroredsolves,onServerTiming:g=>{let l=g.decode??0,h=g.solve??0,b=g.encode??0;if(i.seq+=1,i.last={decode:l,solve:h,encode:b},!e.debug)return;let y=l<=1?"HIT (definition reused, no re-upload/parse)":"miss (full parse)",R=e.cachesolve&&h<=1?"HIT (cached result, solve skipped)":e.cachesolve?"miss (solved fresh)":"off";o(`[Compute/rhino-cache] decode=${l}ms solve=${h}ms encode=${b}ms | definition-cache: ${y} | solve-cache (cachesolve): ${R}`)}};m&&(p.headers={"X-Selva-Definition":m});let d=await K.create(p),f=z,w=0,M=!1;async function C(g){if(!M){M=!0;try{let l=await Promise.resolve(d.serverStats?.getActiveChildren({initialize:!1})).catch(()=>null);w=Date.now();let h=!l||l<=0?z:l;if(h===f)return;o(`[Compute/client-cache] concurrency ${f} \u2192 ${h} (server child count)`),f=h,g?.setMaxConcurrent(h)}finally{M=!1}}}await C();let S=d.createScheduler({mode:"queue",maxConcurrent:f,maxQueueDepth:e.maxQueueDepth||void 0,queueWaitMs:e.queueWaitMs||void 0,timeoutMs:e.maxSolveDurationMs,cache:e.responseCacheMaxBytes>0?{maxBytes:e.responseCacheMaxBytes}:!1,reuseServerDefinitionCache:e.reuseServerDefinitionCache,onSettle:(g,l)=>{if(c.seq+=1,l.status==="success"&&(l.fromCache!==!0&&Date.now()-w>=X&&C(S),c.last={fromCache:l.fromCache===!0,definitionReuploaded:l.definitionReuploaded},!!e.debug&&(o(`[Compute/selva-cache] ${l.fromCache?"HIT \u2014 served from cache (no compute call)":"miss \u2014 went to Rhino.Compute"} (${Math.round(l.durationMs)}ms)`),l.fromCache||o(l.definitionReuploaded===!0?"[Compute/def-cache] miss \u2014 definition RE-UPLOADED (pointer was cold/stale)":l.definitionReuploaded===!1?"[Compute/def-cache] HIT \u2014 definition reused from server cache (no upload)":"[Compute/def-cache] n/a \u2014 reuse disabled or non-reusable definition"),!l.fromCache))){let h=l.response?.errors,b=Array.isArray(h)?h.length:0;if(b>0){let y=e.cacheerroredsolves?"cached (cacheerroredsolves on)":"not cached (cacheerroredsolves off) \u2014 re-run until fixed";o(`[Compute/rhino-cache] ${b} GH error(s); ${y}. First: ${JSON.stringify(h[0])}`)}}}});return{client:d,scheduler:S,rhinoTiming:i,solveMeta:c}}return{async getClient(s,m){let i=s.id,c=t.get(i);if(c)return t.delete(i),t.set(i,c),c;let u=r.get(i);if(u)return u;o(`[Compute/client-cache] miss \u2014 building warm client for server ${i}`);let p=(async()=>{let d=await a(s,m?.definitionGuid);if(t.size>=n){let f=t.keys().next().value;f!==void 0&&(o(`[Compute/client-cache] LRU evicted warm client for server ${f} (cap ${n})`),t.get(f)?.scheduler.dispose(),t.delete(f))}return t.set(i,d),d})().finally(()=>r.delete(i));return r.set(i,p),p},evict(s){let m=s,i=t.get(m);i&&(o(`[Compute/client-cache] evicted warm client for server ${m} (config change)`),i.scheduler.dispose(),t.delete(m))},solveCacheStats(){let s={warmClients:t.size,entries:0,bytes:0,hits:0,misses:0,evictions:0};for(let m of t.values()){let i=m.scheduler.cacheStats();s.entries+=i.entries,s.bytes+=i.bytes,s.hits+=i.hits,s.misses+=i.misses,s.evictions+=i.evictions}return s},clearSolveCaches(){o(`[Compute/client-cache] cleared solve caches on ${t.size} warm client(s)`);for(let s of t.values())s.scheduler.clearCache()},disposeAll(){o(`[Compute/client-cache] disposing all ${t.size} warm client(s)`);for(let s of t.values())s.scheduler.dispose();t.clear()}}}function P(e){let n=Math.max(0,Math.floor(e)),t=new Map,r=0,o={hits:0,misses:0,evictions:0};function a(i,c){t.delete(i),t.set(i,c)}function s(){for(;r>n&&t.size>0;){let i=t.keys().next().value,c=t.get(i);t.delete(i),r-=c.bytes.byteLength,o.evictions+=1}}function m(i,c){if(n===0||c.byteLength>n)return;let u=t.get(i);u&&(r-=u.bytes.byteLength),t.set(i,{bytes:c}),r+=c.byteLength,s()}return{getOrLoad(i,c){let u={loaded:!1,fromCache:!1};return{key:i,outcome:u,load:async()=>{let p=t.get(i);if(p)return a(i,p),o.hits+=1,u.loaded=!0,u.fromCache=!0,p.bytes;o.misses+=1,u.loaded=!0,u.fromCache=!1;let d=await c();return m(i,d),d}}},stats(){return{hits:o.hits,misses:o.misses,evictions:o.evictions,entries:t.size,bytes:r}},clear(){t.clear(),r=0}}}function O(e={}){let n=new Map;return{run(t,r,o){let a=n.get(t);if(a)return e.onJoin?.(t),a.notifyOwner?.(),a.promise;let s={notifyOwner:o,promise:void 0},m=(async()=>r())().finally(()=>{s.notifyOwner=void 0,n.get(t)===s&&n.delete(t)});return s.promise=m,n.set(t,s),m},inFlight(){return n.size}}}import{isDefinitionRef as Y,stableStringify as Z}from"@selvajs/compute";import{NoopLogger as ee}from"@selvajs/platform";function te(e){return typeof e=="object"&&e!==null&&!(e instanceof Uint8Array)&&"outcome"in e}function ne(e){return typeof e=="object"&&e!==null&&!(e instanceof Uint8Array)&&!("outcome"in e)&&!("key"in e)&&"versionId"in e}var L=class{limits;clientCache;byteCache;singleFlight;constructor(n){this.limits=n.limits;let t=n.logger??new ee;this.clientCache=k({maxSolveDurationMs:this.limits.maxSolveDurationMs,cachesolve:this.limits.computeServerCachesolve,cacheerroredsolves:this.limits.computeCacheErroredSolves,reuseServerDefinitionCache:this.limits.computeReuseDefinitionCache,maxQueueDepth:this.limits.computeMaxQueueDepth,queueWaitMs:this.limits.computeQueueWaitMs,responseCacheMaxBytes:this.limits.computeSolveCacheBytes,debug:n.debug??!1,maxWarmComputeServers:n.maxWarmComputeServers,onDebugLog:n.onDebugLog??(r=>t.debug(r,{component:"Compute/client-cache"}))}),this.byteCache=P(this.limits.computeDefinitionCacheBytes),this.singleFlight=O({onJoin:n.onSolveCoalesced})}getClient(n,t){return this.clientCache.getClient(n,t)}definitionRef(n,t){return this.byteCache.getOrLoad(n,t)}evictServer(n){this.clientCache.evict(n)}clearSolveCaches(){this.clientCache.clearSolveCaches()}stats(){return{client:this.clientCache.solveCacheStats(),definitionBytes:this.byteCache.stats(),coalescing:{inFlight:this.singleFlight.inFlight()}}}async solve(n){let{definitionSource:t,byteRefOutcome:r,identity:o}=this.resolveDefinitionSource(n),a=await this.clientCache.getClient(n.server,{definitionGuid:n.definitionGuid}),s=E(n.inputs,n.values),m=`${o}:${n.server.id}:${Z(s)}`,i=new AbortController,c=!1;n.signal.addEventListener("abort",()=>{c||i.abort()},{once:!0});let u=n.acceptEncoding??"",p=await this.singleFlight.run(m,()=>B({definitionSource:t,byteRefOutcome:r,inputs:n.inputs,values:n.values,inputTree:s,client:a,responseMaxBytes:this.limits.computeResponseMaxBytes,maxSolveDurationMs:this.limits.maxSolveDurationMs,acceptEncoding:u,signal:i.signal,loadStartMs:n.loadStartMs??performance.now(),defLoadMs:n.defLoadMs??0,prepMarks:n.prepMarks}),()=>{c=!0});if(p.kind!=="ok")return p;let d=T(p.envelope,u);return{...p,envelope:{...p.envelope,body:d.body,headers:d.headers}}}toResponse(n,t){let r=(o,a)=>{t?.onError&&t.onError(o,a);let s={"Content-Type":"application/json"};return a.retryAfter!==void 0&&(s["Retry-After"]=String(a.retryAfter)),{status:o,headers:s,body:JSON.stringify(a)}};switch(n.kind){case"ok":return{status:200,headers:n.envelope.headers,body:n.envelope.body};case"timeout":return r(504,{message:n.message});case"client_abort":return r(499,{message:"Client closed request"});case"too_large":return r(413,{message:"Solve result is too large to return. This usually means a file output exceeds the supported size."});case"shed":return r(503,{message:n.message,retryAfter:n.retryAfterSeconds});case"compute_error":throw n.error}}toWebResponse(n){let t=this.toResponse(n);return new Response(typeof t.body=="string"?t.body:new Uint8Array(t.body),{status:t.status,headers:t.headers})}resolveDefinitionSource(n){let t=n.definitionSource;if(ne(t)){let r=this.byteCache.getOrLoad(t.versionId,t.load);return{definitionSource:r,byteRefOutcome:r.outcome,identity:r.key}}if(te(t))return{definitionSource:t,byteRefOutcome:t.outcome,identity:t.key};if(Y(t))return{definitionSource:t,identity:t.key};if(!n.definitionKey)throw new Error("SolveEngine.solve(): definitionKey is required when definitionSource is raw bytes or a string.");return{definitionSource:t,identity:n.definitionKey}}};import{isDefinitionRef as Te}from"@selvajs/compute";export{F as COMPUTE_CONTRACT_VERSION,_ as COMPUTE_VERSION_HEADER,L as SolveEngine,T as adaptEnvelopeToEncoding,E as buildSolveInputTree,k as createClientCache,P as createDefinitionByteCache,O as createSolveCacheSingleFlight,Te as isDefinitionRef,B as runSolvePipeline,H as serverIdentity,A as transformInputParameter};
2
2
  //# sourceMappingURL=server.js.map