@selvajs/server 0.2.0 → 1.0.0-beta.0

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.
Files changed (46) hide show
  1. package/README.md +23 -16
  2. package/dist/compute/index.d.ts +0 -8
  3. package/dist/compute/index.d.ts.map +1 -1
  4. package/dist/compute/index.js +12 -9
  5. package/dist/compute/index.js.map +1 -1
  6. package/dist/compute/limits.d.ts +27 -35
  7. package/dist/compute/limits.d.ts.map +1 -1
  8. package/dist/compute/limits.js +34 -12
  9. package/dist/compute/limits.js.map +1 -1
  10. package/package.json +7 -13
  11. package/dist/compute/client-cache.d.ts +0 -135
  12. package/dist/compute/client-cache.d.ts.map +0 -1
  13. package/dist/compute/client-cache.js +0 -176
  14. package/dist/compute/client-cache.js.map +0 -1
  15. package/dist/compute/definition-byte-cache.d.ts +0 -85
  16. package/dist/compute/definition-byte-cache.d.ts.map +0 -1
  17. package/dist/compute/definition-byte-cache.js +0 -110
  18. package/dist/compute/definition-byte-cache.js.map +0 -1
  19. package/dist/compute/memory-solve-cache.d.ts +0 -58
  20. package/dist/compute/memory-solve-cache.d.ts.map +0 -1
  21. package/dist/compute/memory-solve-cache.js +0 -0
  22. package/dist/compute/memory-solve-cache.js.map +0 -1
  23. package/dist/compute/solve-cache-envelope.d.ts +0 -53
  24. package/dist/compute/solve-cache-envelope.d.ts.map +0 -1
  25. package/dist/compute/solve-cache-envelope.js +0 -66
  26. package/dist/compute/solve-cache-envelope.js.map +0 -1
  27. package/dist/compute/solve-cache-key.d.ts +0 -64
  28. package/dist/compute/solve-cache-key.d.ts.map +0 -1
  29. package/dist/compute/solve-cache-key.js +0 -51
  30. package/dist/compute/solve-cache-key.js.map +0 -1
  31. package/dist/compute/solve-cache-single-flight.d.ts +0 -42
  32. package/dist/compute/solve-cache-single-flight.d.ts.map +0 -1
  33. package/dist/compute/solve-cache-single-flight.js +0 -50
  34. package/dist/compute/solve-cache-single-flight.js.map +0 -1
  35. package/dist/compute/solve-pipeline.d.ts +0 -221
  36. package/dist/compute/solve-pipeline.d.ts.map +0 -1
  37. package/dist/compute/solve-pipeline.js +0 -419
  38. package/dist/compute/solve-pipeline.js.map +0 -1
  39. package/dist/compute/transform-input.d.ts +0 -20
  40. package/dist/compute/transform-input.d.ts.map +0 -1
  41. package/dist/compute/transform-input.js +0 -62
  42. package/dist/compute/transform-input.js.map +0 -1
  43. package/dist/index.d.ts +0 -27
  44. package/dist/index.d.ts.map +0 -1
  45. package/dist/index.js +0 -27
  46. package/dist/index.js.map +0 -1
@@ -1,64 +0,0 @@
1
- /**
2
- * Durable L2 cache-key derivation (H2 / R8 / R13).
3
- *
4
- * The in-process L1 cache keys on a 32-bit FNV hash — fine for a 20-entry Map,
5
- * unsafe for a durable, cross-user cache where a collision serves user A's
6
- * geometry to user B (CONTEXT.md L92–97 records exactly this bug shipping once).
7
- * So the L2 key is a wide SHA-256 hash (SHA-256 collisions aren't a realistic
8
- * threat, so hash-to-hash comparison at hit time is defense-in-depth enough —
9
- * no byte-for-byte preimage comparison is done or needed).
10
- *
11
- * What goes into the key:
12
- * - The **transformed** input tree (R13) — the exact tree handed to the
13
- * scheduler, canonicalized with the package's exported `stableStringify` so
14
- * the L2 key parity-matches the L1 canonicalization path.
15
- * - A **config subset** (R8): everything OTHER than definition+inputs that
16
- * changes a solve's result — model units, tolerances, `dataversion`, the
17
- * compute contract version, and (when known) the Rhino server identity. A
18
- * durable key of `(versionId, inputHash)` alone would collide across
19
- * differing server configs or Rhino versions.
20
- *
21
- * `versionId`, `definitionId`, and `orgId` are NOT hashed in here — they're
22
- * separate parts of the {@link SolveCacheKey} tuple so the backend can scope its
23
- * keyspace (per-definition quota, per-org isolation) without unpacking the hash.
24
- */
25
- /**
26
- * Solve-affecting configuration folded into the key alongside the input tree.
27
- * Every field here is something that, if it changed, would legitimately change
28
- * the solve output for the SAME definition + inputs. Assemble it from the app's
29
- * resolved solve config; unset fields are simply absent from the canonical form.
30
- */
31
- export interface SolveCacheConfigSubset {
32
- /** Rhino model unit system, if the app pins one (else the definition/server default). */
33
- modelUnits?: string;
34
- /** Absolute tolerance, if pinned. */
35
- absoluteTolerance?: number;
36
- /** Angle tolerance, if pinned. */
37
- angleTolerance?: number;
38
- /** Grasshopper `dataversion` / data-tree schema version, if pinned. */
39
- dataVersion?: number | string;
40
- /**
41
- * Compute-server identity (id or URL). Folds in because two servers can run
42
- * different Rhino/plugin versions that yield different geometry for the same
43
- * inputs. Optional — omit for a single-pool deployment where it's constant.
44
- */
45
- computeServerId?: string;
46
- }
47
- /** The SHA-256 digest of the canonical preimage — the `inputKey` part of the storage key. */
48
- export interface SolveCacheInputKey {
49
- /** SHA-256 hex of the canonical `{ v, tree, config }` string. */
50
- hash: string;
51
- }
52
- /**
53
- * Derive the durable `inputKey` from the transformed input tree and the solve
54
- * config subset. Deterministic and stable across processes/instances: the same
55
- * tree + config always produce the same hash, which is what lets a shared cache
56
- * hit across app instances.
57
- *
58
- * @param transformedTree the tree AFTER `transformInputParameter`/`TreeBuilder`
59
- * (R13) — the exact object handed to `scheduler.solve`.
60
- * @param config solve-affecting config (R8); `COMPUTE_CONTRACT_VERSION` is folded
61
- * in automatically so a wire-contract bump invalidates old entries for free.
62
- */
63
- export declare function deriveSolveCacheInputKey(transformedTree: unknown, config: SolveCacheConfigSubset): SolveCacheInputKey;
64
- //# sourceMappingURL=solve-cache-key.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"solve-cache-key.d.ts","sourceRoot":"","sources":["../../src/compute/solve-cache-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACtC,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kCAAkC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IAClC,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACvC,eAAe,EAAE,OAAO,EACxB,MAAM,EAAE,sBAAsB,GAC5B,kBAAkB,CAWpB"}
@@ -1,51 +0,0 @@
1
- /**
2
- * Durable L2 cache-key derivation (H2 / R8 / R13).
3
- *
4
- * The in-process L1 cache keys on a 32-bit FNV hash — fine for a 20-entry Map,
5
- * unsafe for a durable, cross-user cache where a collision serves user A's
6
- * geometry to user B (CONTEXT.md L92–97 records exactly this bug shipping once).
7
- * So the L2 key is a wide SHA-256 hash (SHA-256 collisions aren't a realistic
8
- * threat, so hash-to-hash comparison at hit time is defense-in-depth enough —
9
- * no byte-for-byte preimage comparison is done or needed).
10
- *
11
- * What goes into the key:
12
- * - The **transformed** input tree (R13) — the exact tree handed to the
13
- * scheduler, canonicalized with the package's exported `stableStringify` so
14
- * the L2 key parity-matches the L1 canonicalization path.
15
- * - A **config subset** (R8): everything OTHER than definition+inputs that
16
- * changes a solve's result — model units, tolerances, `dataversion`, the
17
- * compute contract version, and (when known) the Rhino server identity. A
18
- * durable key of `(versionId, inputHash)` alone would collide across
19
- * differing server configs or Rhino versions.
20
- *
21
- * `versionId`, `definitionId`, and `orgId` are NOT hashed in here — they're
22
- * separate parts of the {@link SolveCacheKey} tuple so the backend can scope its
23
- * keyspace (per-definition quota, per-org isolation) without unpacking the hash.
24
- */
25
- import { createHash } from 'node:crypto';
26
- import { stableStringify } from '@selvajs/compute';
27
- import { COMPUTE_CONTRACT_VERSION } from './solve-pipeline.js';
28
- /**
29
- * Derive the durable `inputKey` from the transformed input tree and the solve
30
- * config subset. Deterministic and stable across processes/instances: the same
31
- * tree + config always produce the same hash, which is what lets a shared cache
32
- * hit across app instances.
33
- *
34
- * @param transformedTree the tree AFTER `transformInputParameter`/`TreeBuilder`
35
- * (R13) — the exact object handed to `scheduler.solve`.
36
- * @param config solve-affecting config (R8); `COMPUTE_CONTRACT_VERSION` is folded
37
- * in automatically so a wire-contract bump invalidates old entries for free.
38
- */
39
- export function deriveSolveCacheInputKey(transformedTree, config) {
40
- // stableStringify sorts keys deterministically — same content, same string,
41
- // regardless of property insertion order. Tag each part so a value can't shift
42
- // across the tree/config boundary and collide (e.g. tree "]" meeting config "[").
43
- const canonical = stableStringify({
44
- v: COMPUTE_CONTRACT_VERSION,
45
- tree: transformedTree,
46
- config
47
- });
48
- const hash = createHash('sha256').update(canonical).digest('hex');
49
- return { hash };
50
- }
51
- //# sourceMappingURL=solve-cache-key.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"solve-cache-key.js","sourceRoot":"","sources":["../../src/compute/solve-cache-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AA+B/D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CACvC,eAAwB,EACxB,MAA8B;IAE9B,4EAA4E;IAC5E,+EAA+E;IAC/E,kFAAkF;IAClF,MAAM,SAAS,GAAG,eAAe,CAAC;QACjC,CAAC,EAAE,wBAAwB;QAC3B,IAAI,EAAE,eAAe;QACrB,MAAM;KACN,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClE,OAAO,EAAE,IAAI,EAAE,CAAC;AACjB,CAAC"}
@@ -1,42 +0,0 @@
1
- /**
2
- * In-process single-flight (R4) — dogpile protection above the L2 cache.
3
- *
4
- * N identical live solves arriving while the first is still running would each
5
- * enqueue and each hit Rhino (the scheduler has no in-flight coalescing, and
6
- * Rhino's `cachesolve` still costs a round trip per repeat). The classic cold-key
7
- * stampede: a hot public definition + a deploy = a thundering herd onto compute.
8
- *
9
- * This coalesces them: the first caller for a key runs the work; concurrent
10
- * callers for the SAME key await the same promise and share its result. Once the
11
- * work settles (resolve OR reject), the key is released so the next request runs
12
- * fresh — the map never retains a settled entry, so it can't serve a stale
13
- * result or leak memory.
14
- *
15
- * It sits ABOVE the `ISolveResultCache` interface (in-process, per app instance).
16
- * A cross-instance lease (Redis `SET NX`) is a later backend capability; this is
17
- * the free first slice that covers the common same-instance burst.
18
- *
19
- * Correctness note: the shared promise resolves to ONE value for all waiters, so
20
- * the wrapped work must return an immutable / independently-serializable result
21
- * (the solve pipeline returns a fresh envelope object — safe to share by
22
- * reference here because the app serializes it per response).
23
- */
24
- export interface SolveCacheSingleFlight {
25
- /**
26
- * Run `work` under `key`, coalescing concurrent identical calls. The first
27
- * caller executes; overlapping callers for the same key await the same promise.
28
- * The key is freed as soon as the promise settles (success or failure).
29
- */
30
- run<T>(key: string, work: () => Promise<T>): Promise<T>;
31
- /** Number of keys currently in flight (observability / tests). */
32
- inFlight(): number;
33
- }
34
- export interface SolveCacheSingleFlightOptions {
35
- /**
36
- * Fired when a caller joins an already-in-flight key instead of running its
37
- * own work — the coalescing win this module exists for, otherwise invisible.
38
- */
39
- onJoin?: (key: string) => void;
40
- }
41
- export declare function createSolveCacheSingleFlight(options?: SolveCacheSingleFlightOptions): SolveCacheSingleFlight;
42
- //# sourceMappingURL=solve-cache-single-flight.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"solve-cache-single-flight.d.ts","sourceRoot":"","sources":["../../src/compute/solve-cache-single-flight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,sBAAsB;IACtC;;;;OAIG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,kEAAkE;IAClE,QAAQ,IAAI,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC7C;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,wBAAgB,4BAA4B,CAC3C,OAAO,GAAE,6BAAkC,GACzC,sBAAsB,CA0BxB"}
@@ -1,50 +0,0 @@
1
- /**
2
- * In-process single-flight (R4) — dogpile protection above the L2 cache.
3
- *
4
- * N identical live solves arriving while the first is still running would each
5
- * enqueue and each hit Rhino (the scheduler has no in-flight coalescing, and
6
- * Rhino's `cachesolve` still costs a round trip per repeat). The classic cold-key
7
- * stampede: a hot public definition + a deploy = a thundering herd onto compute.
8
- *
9
- * This coalesces them: the first caller for a key runs the work; concurrent
10
- * callers for the SAME key await the same promise and share its result. Once the
11
- * work settles (resolve OR reject), the key is released so the next request runs
12
- * fresh — the map never retains a settled entry, so it can't serve a stale
13
- * result or leak memory.
14
- *
15
- * It sits ABOVE the `ISolveResultCache` interface (in-process, per app instance).
16
- * A cross-instance lease (Redis `SET NX`) is a later backend capability; this is
17
- * the free first slice that covers the common same-instance burst.
18
- *
19
- * Correctness note: the shared promise resolves to ONE value for all waiters, so
20
- * the wrapped work must return an immutable / independently-serializable result
21
- * (the solve pipeline returns a fresh envelope object — safe to share by
22
- * reference here because the app serializes it per response).
23
- */
24
- export function createSolveCacheSingleFlight(options = {}) {
25
- const inflight = new Map();
26
- return {
27
- run(key, work) {
28
- const existing = inflight.get(key);
29
- if (existing) {
30
- options.onJoin?.(key);
31
- return existing;
32
- }
33
- // `.finally` releases the key on both settle paths. Start the work inside
34
- // the promise chain so a synchronous throw in `work` still rejects the
35
- // shared promise (and still frees the key) rather than escaping.
36
- const p = (async () => work())().finally(() => {
37
- // Only delete if it's still OUR promise — a key freed and re-taken by a
38
- // later caller must not be clobbered.
39
- if (inflight.get(key) === p)
40
- inflight.delete(key);
41
- });
42
- inflight.set(key, p);
43
- return p;
44
- },
45
- inFlight() {
46
- return inflight.size;
47
- }
48
- };
49
- }
50
- //# sourceMappingURL=solve-cache-single-flight.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"solve-cache-single-flight.js","sourceRoot":"","sources":["../../src/compute/solve-cache-single-flight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAqBH,MAAM,UAAU,4BAA4B,CAC3C,UAAyC,EAAE;IAE3C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IAErD,OAAO;QACN,GAAG,CAAI,GAAW,EAAE,IAAsB;YACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,QAAQ,EAAE,CAAC;gBACd,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;gBACtB,OAAO,QAAsB,CAAC;YAC/B,CAAC;YAED,0EAA0E;YAC1E,uEAAuE;YACvE,iEAAiE;YACjE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC7C,wEAAwE;gBACxE,sCAAsC;gBACtC,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACnD,CAAC,CAAC,CAAC;YACH,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACrB,OAAO,CAAC,CAAC;QACV,CAAC;QACD,QAAQ;YACP,OAAO,QAAQ,CAAC,IAAI,CAAC;QACtB,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -1,221 +0,0 @@
1
- /**
2
- * Transport-agnostic solve pipeline — the callable core lifted out of the app's
3
- * `/api/compute` route (embeddable-server-layer plan, K3).
4
- *
5
- * Given an already-resolved solve context — the `.gh` bytes, the input params +
6
- * user values, and a warm `SolveScheduler` (from `createClientCache`) — this
7
- * runs the framework-free half of a solve:
8
- *
9
- * input tree build → scheduler.solve (with abort/timeout classification) →
10
- * JSON serialize (size-guarded) → optional gzip → Server-Timing envelope.
11
- *
12
- * It returns a discriminated {@link SolveOutcome}: `ok` carries the ready-to-send
13
- * {@link SolveEnvelope} (body + headers + phase metrics), while the error
14
- * variants (`timeout`, `client_abort`, `too_large`, `compute_error`) name a
15
- * failure the transport maps to its own status code. Nothing here throws for an
16
- * expected failure and nothing here touches auth, the database, share tokens,
17
- * rate limits, or metric sinks — those stay app policy in the route.
18
- *
19
- * The Server-Timing string it emits IS the versioned wire contract (audit D5):
20
- * see {@link COMPUTE_CONTRACT_VERSION} and {@link COMPUTE_VERSION_HEADER}.
21
- */
22
- import { type GrasshopperComputeResponse, type SolveDefinition } from '@selvajs/compute';
23
- import type { SchemaInput } from '@selvajs/schemas';
24
- import { type ILogger } from '@selvajs/platform';
25
- import type { CachedClient } from './client-cache.js';
26
- import type { ByteRefOutcome } from './definition-byte-cache.js';
27
- import { type SolveCacheConfigSubset } from './solve-cache-key.js';
28
- /** Current compute-response contract version. Additive header; bump on shape change. */
29
- export declare const COMPUTE_CONTRACT_VERSION: 1;
30
- /** Response header carrying {@link COMPUTE_CONTRACT_VERSION}. */
31
- export declare const COMPUTE_VERSION_HEADER = "X-Selva-Compute-Version";
32
- /** A schema input carrying the optional numeric bounds the transform reads. */
33
- export type PipelineInput = SchemaInput & {
34
- minimum?: number;
35
- maximum?: number;
36
- stepSize?: number;
37
- };
38
- export interface SolvePipelineArgs {
39
- /**
40
- * The definition to solve. Either raw `.gh` bytes (remote fetch, or a caller
41
- * that resolved them eagerly) or a `DefinitionRef` — an identity-keyed lazy
42
- * loader (from the definition-byte cache) whose bytes the scheduler
43
- * materializes ONLY when an upload is unavoidable. A pointer-known solve of a
44
- * `DefinitionRef` moves zero bytes.
45
- */
46
- definitionSource: SolveDefinition;
47
- /**
48
- * When `definitionSource` is a byte-cache `DefinitionRef`, its mutable outcome
49
- * so the pipeline can emit the `def_bytes` Server-Timing verdict (skipped =
50
- * `load` never ran, hit = warm cache, miss = loader). Omit for raw-bytes solves.
51
- */
52
- byteRefOutcome?: ByteRefOutcome;
53
- /** Persisted input params; only those with a `paramType` are sent to the solve. */
54
- inputs: PipelineInput[];
55
- /**
56
- * Structured logger for pipeline diagnostics (e.g. a corrupt L2 cache entry).
57
- * Optional; defaults to `NoopLogger` so this library never writes to stdout
58
- * unless the embedder asks for it.
59
- */
60
- logger?: ILogger;
61
- /** User-chosen values keyed by input id; missing keys fall back to the schema default. */
62
- values: Record<string, unknown>;
63
- /** Warm client bundle from `createClientCache().getClient(...)`. */
64
- client: CachedClient;
65
- /** Longest a serialized result may be (bytes) before a 413. */
66
- responseMaxBytes: number;
67
- /** Solve-deadline millis — used only to phrase the timeout message. */
68
- maxSolveDurationMs: number;
69
- /** Client's `Accept-Encoding`; gzip is applied only when it advertises `gzip`. */
70
- acceptEncoding: string;
71
- /**
72
- * Request abort signal, forwarded to the scheduler so a client disconnect
73
- * cancels the upstream compute call. Its `aborted` flag also disambiguates a
74
- * client disconnect from the scheduler's own deadline firing.
75
- */
76
- signal: AbortSignal;
77
- /**
78
- * Wall-clock origin (`performance.now()` captured at the top of the request)
79
- * so the pipeline's `load`/`total` phase timings line up with the caller's
80
- * pre-solve prep. The caller owns everything before this and stamps `defLoadMs`.
81
- */
82
- loadStartMs: number;
83
- /** Pre-solve "load" phase duration the caller already measured (auth + DB + fetch). */
84
- defLoadMs: number;
85
- /**
86
- * Pre-solve prep sub-phase marks (`[label, ms]`), surfaced verbatim as `p_*`
87
- * Server-Timing entries. The caller assembles these; the pipeline only echoes.
88
- */
89
- prepMarks?: [string, number][];
90
- /**
91
- * Optional durable L2 solve cache (H1). Present ONLY for live-channel solves
92
- * whose definition has a caching quota > 0 — its presence IS the channel gate
93
- * (zero policy conditionals in the pipeline). When set, the pipeline derives
94
- * the wide input key from the transformed tree + `configSubset`, checks
95
- * `lookup` before solving (a hit skips compute entirely, returning the stored
96
- * gzipped envelope), and write-throughs `store` on a successful solve.
97
- */
98
- solveCache?: SolvePipelineCacheHook;
99
- }
100
- /**
101
- * The app-supplied L2 hook. The app binds `lookup`/`store` to the org/definition/
102
- * version scope and the definition's quota; the pipeline supplies the SHA-256
103
- * `inputKey` (derived from the transformed tree + `configSubset`) and the opaque
104
- * encoded entry bytes. Both are best-effort — `lookup` may miss/throw (treated as
105
- * a miss), `store` is fire-and-forget.
106
- */
107
- export interface SolvePipelineCacheHook {
108
- /** Solve-affecting config folded into the key (R8) — model units, tolerances, server id. */
109
- configSubset: SolveCacheConfigSubset;
110
- /** Fetch a stored entry for `inputKey`, or `null` on miss. Must not reject fatally. */
111
- lookup(inputKey: string): Promise<Uint8Array | null>;
112
- /** Store the encoded entry for `inputKey`. Fire-and-forget; best-effort. */
113
- store(inputKey: string, entry: Uint8Array): void;
114
- }
115
- /** Phase timings the pipeline measured; the caller uses them for debug logging. */
116
- export interface SolvePhaseMetrics {
117
- treeBuildMs: number;
118
- solveMs: number;
119
- serializeMs: number;
120
- gzipMs: number;
121
- serverTotalMs: number;
122
- /** Uncompressed serialized length in bytes. */
123
- serializedBytes: number;
124
- /** Gzipped length in bytes, or null when compression was skipped. */
125
- compressedBytes: number | null;
126
- /**
127
- * L2 durable-cache verdict: `true` = served from L2 (no solve ran), `false` =
128
- * L2 consulted and missed (solved), `null` = L2 not consulted (no hook — draft
129
- * or caching off). Drives the `l2_cache` Server-Timing entry.
130
- */
131
- l2CacheHit: boolean | null;
132
- }
133
- /** A ready-to-send response: body + headers + the solve result + phase metrics. */
134
- export interface SolveEnvelope {
135
- /** The response body — a gzip `Uint8Array` when `encoding === 'gzip'`, else the JSON string. */
136
- body: string | Uint8Array;
137
- /** `'gzip'` when the body is compressed; absent otherwise. */
138
- encoding?: 'gzip';
139
- /** Fully-assembled response headers (Content-Type/-Length/-Encoding, Vary, Server-Timing, version). */
140
- headers: Record<string, string>;
141
- /**
142
- * The solved definition object, present only on a fresh (non-L2) solve. Absent
143
- * on an L2 hit — the pipeline serves the stored gzipped body without ever
144
- * materializing the object. The caller reads `errorCount`/`warningCount` off
145
- * the `ok` outcome for its metric record, not this field.
146
- */
147
- result?: GrasshopperComputeResponse;
148
- /** Phase timings, for the caller's optional debug breakdown. */
149
- metrics: SolvePhaseMetrics;
150
- }
151
- /**
152
- * Discriminated result. `ok` carries the envelope; every other variant names an
153
- * expected failure the transport maps to a status code (the app route maps
154
- * timeout→504, client_abort→499, too_large→413, shed→503+Retry-After;
155
- * `compute_error` re-surfaces the original error for the generic 500/503 path).
156
- * `durationMs` on the error variants is the solve wall time up to the failure,
157
- * for the metric record.
158
- */
159
- export type SolveOutcome = {
160
- kind: 'ok';
161
- envelope: SolveEnvelope;
162
- solveMs: number;
163
- errorCount: number;
164
- warningCount: number;
165
- } | {
166
- kind: 'timeout';
167
- durationMs: number;
168
- message: string;
169
- } | {
170
- kind: 'client_abort';
171
- durationMs: number;
172
- } | {
173
- kind: 'too_large';
174
- }
175
- /**
176
- * Scheduler backpressure shed the solve before it executed — the per-server
177
- * queue was full (`QUEUE_FULL`) or it sat queued past the wait deadline
178
- * (`QUEUE_TIMEOUT`). Retryable: the route maps this to 503 + `Retry-After`.
179
- * `retryAfterSeconds` is a suggested backoff hint for the client.
180
- */
181
- | {
182
- kind: 'shed';
183
- durationMs: number;
184
- reason: 'queue_full' | 'queue_timeout';
185
- retryAfterSeconds: number;
186
- message: string;
187
- } | {
188
- kind: 'compute_error';
189
- durationMs: number;
190
- error: unknown;
191
- };
192
- /**
193
- * Run the framework-free half of a solve and produce a typed outcome. See the
194
- * module doc for the phase sequence and the app/library boundary.
195
- */
196
- export declare function runSolvePipeline(args: SolvePipelineArgs): Promise<SolveOutcome>;
197
- /**
198
- * Re-key a coalesced envelope to a single waiter's `Accept-Encoding` (audit C5).
199
- *
200
- * The single-flight coalescer (R4) runs ONE pipeline execution for N identical
201
- * concurrent solves and hands every waiter the same {@link SolveEnvelope}. That
202
- * envelope's wire form — gzip body + `Content-Encoding: gzip`, or the plain JSON
203
- * string — is baked from the FIRST caller's `Accept-Encoding`. A later waiter
204
- * with a different `Accept-Encoding` would otherwise receive a body labelled with
205
- * the wrong encoding: a non-gzip client joining a gzip flight gets gzip bytes it
206
- * cannot decode (`Vary` can't help — this is one object shared across waiters,
207
- * not a cache lookup). Encoding is not in the coalesce key by design, so mixed
208
- * clients still coalesce; this adapts the shared result to each waiter instead.
209
- *
210
- * Returns the body + headers to send THIS waiter. Only the correctness-critical
211
- * direction is adapted — a gzip envelope served to a non-gzip waiter is gunzipped
212
- * back to JSON. The reverse (a plain-JSON envelope to a gzip-capable waiter) is
213
- * left uncompressed: gzip is an optimisation the client advertises, never a
214
- * requirement, so sending it plain is correct, just not maximally small. The
215
- * common all-gzip case returns the envelope's own body/headers untouched.
216
- */
217
- export declare function adaptEnvelopeToEncoding(envelope: SolveEnvelope, acceptEncoding: string): {
218
- body: string | Uint8Array;
219
- headers: Record<string, string>;
220
- };
221
- //# sourceMappingURL=solve-pipeline.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"solve-pipeline.d.ts","sourceRoot":"","sources":["../../src/compute/solve-pipeline.ts"],"names":[],"mappings":"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"}