@selvajs/server 0.2.0-beta.4 → 0.2.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/compute/client-cache.d.ts +7 -0
- package/dist/compute/client-cache.d.ts.map +1 -1
- package/dist/compute/client-cache.js +6 -1
- package/dist/compute/client-cache.js.map +1 -1
- package/dist/compute/index.d.ts +2 -2
- package/dist/compute/index.d.ts.map +1 -1
- package/dist/compute/index.js +2 -2
- package/dist/compute/index.js.map +1 -1
- package/dist/compute/limits.d.ts +26 -5
- package/dist/compute/limits.d.ts.map +1 -1
- package/dist/compute/limits.js +51 -25
- package/dist/compute/limits.js.map +1 -1
- package/dist/compute/rate-limit.d.ts +40 -0
- package/dist/compute/rate-limit.d.ts.map +1 -1
- package/dist/compute/rate-limit.js +72 -1
- package/dist/compute/rate-limit.js.map +1 -1
- package/dist/compute/remote-definition.d.ts +7 -0
- package/dist/compute/remote-definition.d.ts.map +1 -1
- package/dist/compute/remote-definition.js +3 -1
- package/dist/compute/remote-definition.js.map +1 -1
- package/dist/compute/safe-url.d.ts +2 -1
- package/dist/compute/safe-url.d.ts.map +1 -1
- package/dist/compute/safe-url.js +18 -4
- package/dist/compute/safe-url.js.map +1 -1
- package/dist/compute/solve-cache-key.d.ts +5 -6
- package/dist/compute/solve-cache-key.d.ts.map +1 -1
- package/dist/compute/solve-cache-key.js +4 -3
- package/dist/compute/solve-cache-key.js.map +1 -1
- package/dist/compute/solve-pipeline.d.ts +31 -0
- package/dist/compute/solve-pipeline.d.ts.map +1 -1
- package/dist/compute/solve-pipeline.js +40 -1
- package/dist/compute/solve-pipeline.js.map +1 -1
- package/dist/compute/transform-input.d.ts +2 -2
- package/dist/compute/transform-input.js +2 -2
- package/dist/definitions/load-for-render.d.ts +10 -1
- package/dist/definitions/load-for-render.d.ts.map +1 -1
- package/dist/definitions/load-for-render.js +33 -6
- package/dist/definitions/load-for-render.js.map +1 -1
- package/dist/logging/PinoLogger.d.ts +39 -0
- package/dist/logging/PinoLogger.d.ts.map +1 -0
- package/dist/logging/PinoLogger.js +189 -0
- package/dist/logging/PinoLogger.js.map +1 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +5 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/requestId.d.ts +45 -0
- package/dist/logging/requestId.d.ts.map +1 -0
- package/dist/logging/requestId.js +82 -0
- package/dist/logging/requestId.js.map +1 -0
- package/dist/providers/create-selva-providers.d.ts +11 -1
- package/dist/providers/create-selva-providers.d.ts.map +1 -1
- package/dist/providers/create-selva-providers.js +21 -8
- package/dist/providers/create-selva-providers.js.map +1 -1
- package/package.json +23 -5
package/README.md
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Transport-agnostic compute/solve/definitions server building blocks for apps built on the
|
|
4
4
|
Selva engine: limits, rate limiting, SSRF guard, input transform, solve pipeline, definition
|
|
5
|
-
service, and
|
|
6
|
-
specific HTTP framework.
|
|
5
|
+
service, render loader, and structured logging. Consumed by `@selvajs/selva`; not tied to
|
|
6
|
+
SvelteKit or any specific HTTP framework.
|
|
7
|
+
|
|
8
|
+
Logging follows the same rule as the rest of the package: nothing here logs unless you wire
|
|
9
|
+
it. Modules that emit diagnostics take an optional `logger?: ILogger` and default to
|
|
10
|
+
`NoopLogger`, so an embedder that wires nothing gets silence rather than unsolicited stdout
|
|
11
|
+
writes. `@selvajs/server/logging` provides a pino-backed implementation (`createLogger`);
|
|
12
|
+
`pino` is an **optional** peer dependency, and without it `createLogger` degrades to a
|
|
13
|
+
console-backed logger rather than failing.
|
|
7
14
|
|
|
8
15
|
## Install
|
|
9
16
|
|
|
@@ -21,6 +28,7 @@ pnpm add @selvajs/server
|
|
|
21
28
|
| `@selvajs/server/providers` | Provider composition helpers |
|
|
22
29
|
| `@selvajs/server/tokens` | API token (PAT) codec |
|
|
23
30
|
| `@selvajs/server/errors` | Shared error types |
|
|
31
|
+
| `@selvajs/server/logging` | pino-backed `ILogger` + request-id correlation |
|
|
24
32
|
| `@selvajs/server/http` | Rate limiting, security headers, request limits |
|
|
25
33
|
| `@selvajs/server/access` | Access/permission checks |
|
|
26
34
|
| `@selvajs/server/ops` | Operational utilities |
|
|
@@ -92,6 +92,13 @@ export interface ClientCacheConfig {
|
|
|
92
92
|
* `QUEUE_TIMEOUT`.
|
|
93
93
|
*/
|
|
94
94
|
queueWaitMs: number;
|
|
95
|
+
/**
|
|
96
|
+
* Byte budget for this client's in-process solve-response cache (scheduler
|
|
97
|
+
* L1), evicted LRU alongside its entry-count cap. Applies per warm client —
|
|
98
|
+
* total worst-case heap is this × `maxCachedClients`. `0` disables the L1
|
|
99
|
+
* response cache entirely (`ComputeLimits.computeResponseCacheBytes`).
|
|
100
|
+
*/
|
|
101
|
+
responseCacheMaxBytes: number;
|
|
95
102
|
/** Concise cache/timing logs. When false, `onDebugLog` is never invoked. */
|
|
96
103
|
debug: boolean;
|
|
97
104
|
/** VERBOSE lib-level logging (full solve request/response incl. geometry). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-cache.d.ts","sourceRoot":"","sources":["../../src/compute/client-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EAGjB,KAAK,cAAc,EACnB,MAAM,kBAAkB,CAAC;AAyB1B;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE7E,mGAAmG;AACnG,MAAM,WAAW,cAAc;IAC9B,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,cAAc,CAEjF;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,cAAc,CAAC;IAC1B;;;;;;;;OAQG;IACH,WAAW,EAAE;QAAE,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7F;;;;;;;;OAQG;IACH,SAAS,EAAE;QACV,IAAI,EAAE;YAAE,SAAS,EAAE,OAAO,CAAC;YAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,IAAI,CAAC;QACpE,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IACjC,yFAAyF;IACzF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,UAAU,EAAE,OAAO,CAAC;IACpB,qFAAqF;IACrF,kBAAkB,EAAE,OAAO,CAAC;IAC5B,yFAAyF;IACzF,0BAA0B,EAAE,OAAO,CAAC;IACpC;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,YAAY,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC3B;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7F;;;;OAIG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IACzC,4DAA4D;IAC5D,UAAU,IAAI,IAAI,CAAC;CACnB;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"client-cache.d.ts","sourceRoot":"","sources":["../../src/compute/client-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EAGjB,KAAK,cAAc,EACnB,MAAM,kBAAkB,CAAC;AAyB1B;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE7E,mGAAmG;AACnG,MAAM,WAAW,cAAc;IAC9B,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,cAAc,CAEjF;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,cAAc,CAAC;IAC1B;;;;;;;;OAQG;IACH,WAAW,EAAE;QAAE,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7F;;;;;;;;OAQG;IACH,SAAS,EAAE;QACV,IAAI,EAAE;YAAE,SAAS,EAAE,OAAO,CAAC;YAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,IAAI,CAAC;QACpE,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IACjC,yFAAyF;IACzF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,UAAU,EAAE,OAAO,CAAC;IACpB,qFAAqF;IACrF,kBAAkB,EAAE,OAAO,CAAC;IAC5B,yFAAyF;IACzF,0BAA0B,EAAE,OAAO,CAAC;IACpC;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,YAAY,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC3B;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7F;;;;OAIG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IACzC,4DAA4D;IAC5D,UAAU,IAAI,IAAI,CAAC;CACnB;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAuLxE"}
|
|
@@ -78,7 +78,12 @@ export function createClientCache(config) {
|
|
|
78
78
|
maxQueueDepth: config.maxQueueDepth || undefined,
|
|
79
79
|
queueWaitMs: config.queueWaitMs || undefined,
|
|
80
80
|
timeoutMs: config.maxSolveDurationMs,
|
|
81
|
-
cache:
|
|
81
|
+
// L1 response cache: count-capped AND byte-budgeted (audit C2) — entries
|
|
82
|
+
// can be as large as the response cap allows, so the count alone would
|
|
83
|
+
// let 20 × max-response-size pin gigabytes per warm client. `0` = off.
|
|
84
|
+
cache: config.responseCacheMaxBytes > 0
|
|
85
|
+
? { maxEntries: 20, ttlMs: 5 * 60_000, maxBytes: config.responseCacheMaxBytes }
|
|
86
|
+
: false,
|
|
82
87
|
// Solve large definitions by server cache-key (pointer) instead of
|
|
83
88
|
// re-uploading the full binary every solve. On a stale-pointer miss the
|
|
84
89
|
// client transparently re-uploads.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-cache.js","sourceRoot":"","sources":["../../src/compute/client-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAGlB,MAAM,kBAAkB,CAAC;AA2C1B,qFAAqF;AACrF,MAAM,UAAU,cAAc,CAAC,MAAkC;IAChE,OAAO,MAAM,CAAC,EAAoB,CAAC;AACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"client-cache.js","sourceRoot":"","sources":["../../src/compute/client-cache.ts"],"names":[],"mappings":"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"}
|
package/dist/compute/index.d.ts
CHANGED
|
@@ -4,10 +4,10 @@ export { createMemorySolveResultCache, type MemorySolveResultCache, type SolveCa
|
|
|
4
4
|
export { deriveSolveCacheInputKey, type SolveCacheConfigSubset, type SolveCacheInputKey } from './solve-cache-key.js';
|
|
5
5
|
export { encodeSolveCacheEntry, decodeSolveCacheEntry, gunzipEntryBody, type EnvelopeHeader, type DecodedSolveCacheEntry } from './solve-cache-envelope.js';
|
|
6
6
|
export { createSolveCacheSingleFlight, type SolveCacheSingleFlight } from './solve-cache-single-flight.js';
|
|
7
|
-
export { createComputeRateLimiter, type ComputeRateLimiter, type ComputeRateLimiterConfig, type RateLimitResult } from './rate-limit.js';
|
|
7
|
+
export { createComputeRateLimiter, DEFAULT_MAX_KEYS, type ComputeRateLimiter, type ComputeRateLimiterConfig, type RateLimitResult } from './rate-limit.js';
|
|
8
8
|
export { isSafeRemoteDefinitionUrl, assertSafeRemoteDefinitionUrl } from './safe-url.js';
|
|
9
9
|
export { transformInputParameter } from './transform-input.js';
|
|
10
10
|
export { createClientCache, serverIdentity, type ClientCache, type ClientCacheConfig, type CachedClient, type ResolvedServer, type ServerIdentity } from './client-cache.js';
|
|
11
11
|
export { createRemoteDefinitionFetcher, readBodyWithCap, type RemoteDefinitionFetcher, type RemoteDefinitionConfig } from './remote-definition.js';
|
|
12
|
-
export { runSolvePipeline, COMPUTE_CONTRACT_VERSION, COMPUTE_VERSION_HEADER, type SolvePipelineArgs, type SolvePipelineCacheHook, type SolveOutcome, type SolveEnvelope, type SolvePhaseMetrics, type PipelineInput } from './solve-pipeline.js';
|
|
12
|
+
export { runSolvePipeline, adaptEnvelopeToEncoding, COMPUTE_CONTRACT_VERSION, COMPUTE_VERSION_HEADER, type SolvePipelineArgs, type SolvePipelineCacheHook, type SolveOutcome, type SolveEnvelope, type SolvePhaseMetrics, type PipelineInput } from './solve-pipeline.js';
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compute/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACN,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACN,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACN,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACN,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACN,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC;AAEzF,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EACN,iBAAiB,EACjB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACN,6BAA6B,EAC7B,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACN,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compute/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACN,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACN,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACN,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACN,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACN,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC;AAEzF,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EACN,iBAAiB,EACjB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACN,6BAA6B,EAC7B,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACN,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,MAAM,qBAAqB,CAAC"}
|
package/dist/compute/index.js
CHANGED
|
@@ -5,10 +5,10 @@ export { createMemorySolveResultCache } from './memory-solve-cache.js';
|
|
|
5
5
|
export { deriveSolveCacheInputKey } from './solve-cache-key.js';
|
|
6
6
|
export { encodeSolveCacheEntry, decodeSolveCacheEntry, gunzipEntryBody } from './solve-cache-envelope.js';
|
|
7
7
|
export { createSolveCacheSingleFlight } from './solve-cache-single-flight.js';
|
|
8
|
-
export { createComputeRateLimiter } from './rate-limit.js';
|
|
8
|
+
export { createComputeRateLimiter, DEFAULT_MAX_KEYS } from './rate-limit.js';
|
|
9
9
|
export { isSafeRemoteDefinitionUrl, assertSafeRemoteDefinitionUrl } from './safe-url.js';
|
|
10
10
|
export { transformInputParameter } from './transform-input.js';
|
|
11
11
|
export { createClientCache, serverIdentity } from './client-cache.js';
|
|
12
12
|
export { createRemoteDefinitionFetcher, readBodyWithCap } from './remote-definition.js';
|
|
13
|
-
export { runSolvePipeline, COMPUTE_CONTRACT_VERSION, COMPUTE_VERSION_HEADER } from './solve-pipeline.js';
|
|
13
|
+
export { runSolvePipeline, adaptEnvelopeToEncoding, COMPUTE_CONTRACT_VERSION, COMPUTE_VERSION_HEADER } from './solve-pipeline.js';
|
|
14
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/compute/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,QAAQ,EAGR,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,yBAAyB,EAIzB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACN,4BAA4B,EAG5B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACN,wBAAwB,EAGxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACN,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EAGf,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACN,4BAA4B,EAE5B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACN,wBAAwB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/compute/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,QAAQ,EAGR,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,yBAAyB,EAIzB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACN,4BAA4B,EAG5B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACN,wBAAwB,EAGxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACN,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EAGf,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACN,4BAA4B,EAE5B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACN,wBAAwB,EACxB,gBAAgB,EAIhB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC;AAEzF,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EACN,iBAAiB,EACjB,cAAc,EAMd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACN,6BAA6B,EAC7B,eAAe,EAGf,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACN,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EAOtB,MAAM,qBAAqB,CAAC"}
|
package/dist/compute/limits.d.ts
CHANGED
|
@@ -17,25 +17,26 @@
|
|
|
17
17
|
* live in `@selvajs/ui` and are bundled into the client, so they can't read
|
|
18
18
|
* env — they carry their own defaults.
|
|
19
19
|
*/
|
|
20
|
+
import { type ILogger } from '@selvajs/platform';
|
|
20
21
|
/** Env source: any string→string map. `$env/dynamic/private` satisfies this. */
|
|
21
22
|
export type EnvRecord = Record<string, string | undefined>;
|
|
22
23
|
/**
|
|
23
24
|
* Parse a positive integer env value. Returns `fallback` when absent, and warns
|
|
24
25
|
* + falls back when present but not a finite positive number.
|
|
25
26
|
*/
|
|
26
|
-
export declare function readPositiveInt(env: EnvRecord, name: string, fallback: number): number;
|
|
27
|
+
export declare function readPositiveInt(env: EnvRecord, name: string, fallback: number, logger?: ILogger): number;
|
|
27
28
|
/**
|
|
28
29
|
* Parse a non-negative integer env value — like {@link readPositiveInt} but `0`
|
|
29
30
|
* is a valid value (used by knobs where `0` means "disable", e.g. a cache
|
|
30
31
|
* budget). Returns `fallback` when absent, and warns + falls back when present
|
|
31
32
|
* but not a finite non-negative number.
|
|
32
33
|
*/
|
|
33
|
-
export declare function readNonNegativeInt(env: EnvRecord, name: string, fallback: number): number;
|
|
34
|
+
export declare function readNonNegativeInt(env: EnvRecord, name: string, fallback: number, logger?: ILogger): number;
|
|
34
35
|
/**
|
|
35
36
|
* Parse a boolean env flag. Accepts `true/1/yes/on` (case-insensitive) as true
|
|
36
37
|
* and `false/0/no/off` as false; any other / absent value falls back.
|
|
37
38
|
*/
|
|
38
|
-
export declare function readBool(env: EnvRecord, name: string, fallback: boolean): boolean;
|
|
39
|
+
export declare function readBool(env: EnvRecord, name: string, fallback: boolean, logger?: ILogger): boolean;
|
|
39
40
|
/** Fully-resolved server-side compute limits. */
|
|
40
41
|
export interface ComputeLimits {
|
|
41
42
|
/**
|
|
@@ -55,7 +56,9 @@ export interface ComputeLimits {
|
|
|
55
56
|
/**
|
|
56
57
|
* Largest .gh definition accepted on upload AND the largest remote definition
|
|
57
58
|
* fetched for compute — kept in lockstep so a remote URL can't smuggle a file
|
|
58
|
-
* past the upload cap.
|
|
59
|
+
* past the upload cap. Defaults to 50 MB to match Rhino.Compute's own
|
|
60
|
+
* `RHINO_COMPUTE_MAX_REQUEST_SIZE` default: a larger file 413s at compute
|
|
61
|
+
* regardless, so accepting it here only defers the failure.
|
|
59
62
|
*/
|
|
60
63
|
maxGhFileSize: number;
|
|
61
64
|
maxImageFileSize: number;
|
|
@@ -63,6 +66,9 @@ export interface ComputeLimits {
|
|
|
63
66
|
* /api/compute JSON *request* body cap (inputs + values, not the .gh). A
|
|
64
67
|
* `file`-widget input embeds geometry as base64 in `values`; must stay <=
|
|
65
68
|
* the adapter-node global BODY_SIZE_LIMIT or the global backstop rejects first.
|
|
69
|
+
* Defaults to 210 MB: the client allows a 150 MB raw file, base64 inflates it
|
|
70
|
+
* to ~200 MB on the wire, plus JSON envelope slack — and it matches the
|
|
71
|
+
* `BODY_SIZE_LIMIT=210M` shipped in `.env.example`.
|
|
66
72
|
*/
|
|
67
73
|
computeRequestMaxBytes: number;
|
|
68
74
|
/**
|
|
@@ -70,6 +76,10 @@ export interface ComputeLimits {
|
|
|
70
76
|
* in the response; guards V8's ~512 MB single-string wall (a `JSON.stringify`
|
|
71
77
|
* `RangeError`) and browser-tab OOM. Defensive backstop → clear 413 instead of
|
|
72
78
|
* an opaque crash (real fix is out-of-band streaming, ADR 0003).
|
|
79
|
+
*
|
|
80
|
+
* 300 MB is intentional and NOT a dev leftover — sized above any legitimate
|
|
81
|
+
* inline payload but below the V8 string wall. Unlike the request cap it is
|
|
82
|
+
* not bounded by BODY_SIZE_LIMIT, which only applies to inbound bodies.
|
|
73
83
|
*/
|
|
74
84
|
computeResponseMaxBytes: number;
|
|
75
85
|
/** Hard cap on fetching a remote definition — tracks `maxGhFileSize`. */
|
|
@@ -136,6 +146,17 @@ export interface ComputeLimits {
|
|
|
136
146
|
* span KB→hundreds of MB; env value is MB, converted here.
|
|
137
147
|
*/
|
|
138
148
|
computeDefinitionByteCacheBytes: number;
|
|
149
|
+
/**
|
|
150
|
+
* Total-byte budget (bytes) for EACH warm client's in-process solve-response
|
|
151
|
+
* cache (the scheduler L1 — the `fromCache` layer in front of Rhino.Compute).
|
|
152
|
+
* Responses range KB→100s of MB (`computeResponseMaxBytes` allows 300 MB), so
|
|
153
|
+
* the L1's entry-count cap alone can't bound heap; this adds a byte LRU on
|
|
154
|
+
* top. Applies PER warm client — worst-case heap is this × the number of
|
|
155
|
+
* distinct compute servers kept warm (`maxCachedClients`, default 16), though
|
|
156
|
+
* real deployments run 1–2 servers. `0` disables the L1 response cache
|
|
157
|
+
* entirely (every solve goes to compute or the L2). Env value is MB.
|
|
158
|
+
*/
|
|
159
|
+
computeResponseCacheBytes: number;
|
|
139
160
|
/**
|
|
140
161
|
* Default per-definition entry quota for the durable L2 solve cache (H1), used
|
|
141
162
|
* when a definition's `solveCacheLimit` is absent (inherit). A definition may
|
|
@@ -160,5 +181,5 @@ export interface ComputeLimits {
|
|
|
160
181
|
* always yields the same limits, and nothing is read ambiently. The app calls
|
|
161
182
|
* this once at its composition root with `$env/dynamic/private`.
|
|
162
183
|
*/
|
|
163
|
-
export declare function resolveComputeLimits(env: EnvRecord): ComputeLimits;
|
|
184
|
+
export declare function resolveComputeLimits(env: EnvRecord, logger?: ILogger): ComputeLimits;
|
|
164
185
|
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/compute/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/compute/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAI7D,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAS3D;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,OAAc,GACpB,MAAM,CAcR;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CACjC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,OAAc,GACpB,MAAM,CAcR;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACvB,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EACjB,MAAM,GAAE,OAAc,GACpB,OAAO,CAaT;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC7B;;;;;;;;;OASG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iFAAiF;IACjF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;;;;;;;;OASG;IACH,uBAAuB,EAAE,MAAM,CAAC;IAChC,yEAAyE;IACzE,wBAAwB,EAAE,MAAM,CAAC;IACjC,wEAAwE;IACxE,8BAA8B,EAAE,MAAM,CAAC;IACvC,yDAAyD;IACzD,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,2BAA2B,EAAE,OAAO,CAAC;IACrC;;;;OAIG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;;;OAKG;IACH,yBAAyB,EAAE,OAAO,CAAC;IACnC;;;;;;;;OAQG;IACH,0BAA0B,EAAE,MAAM,CAAC;IACnC;;;;;;;;OAQG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,+BAA+B,EAAE,MAAM,CAAC;IACxC;;;;;;;;;OASG;IACH,yBAAyB,EAAE,MAAM,CAAC;IAClC;;;;;;;;OAQG;IACH,2BAA2B,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,uBAAuB,EAAE,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,GAAE,OAAc,GAAG,aAAa,CAmD1F"}
|
package/dist/compute/limits.js
CHANGED
|
@@ -17,18 +17,30 @@
|
|
|
17
17
|
* live in `@selvajs/ui` and are bundled into the client, so they can't read
|
|
18
18
|
* env — they carry their own defaults.
|
|
19
19
|
*/
|
|
20
|
+
import { NoopLogger } from '@selvajs/platform';
|
|
20
21
|
const MB = 1024 * 1024;
|
|
22
|
+
/**
|
|
23
|
+
* Optional structured logger for the env-parsing diagnostics below. Defaults to
|
|
24
|
+
* `NoopLogger`: this is library code, so an embedder that wires nothing gets
|
|
25
|
+
* silence rather than unsolicited stdout writes. The app passes its real logger.
|
|
26
|
+
*/
|
|
27
|
+
const noop = new NoopLogger();
|
|
21
28
|
/**
|
|
22
29
|
* Parse a positive integer env value. Returns `fallback` when absent, and warns
|
|
23
30
|
* + falls back when present but not a finite positive number.
|
|
24
31
|
*/
|
|
25
|
-
export function readPositiveInt(env, name, fallback) {
|
|
32
|
+
export function readPositiveInt(env, name, fallback, logger = noop) {
|
|
26
33
|
const raw = env[name];
|
|
27
34
|
if (!raw)
|
|
28
35
|
return fallback;
|
|
29
36
|
const parsed = Number(raw);
|
|
30
37
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
31
|
-
|
|
38
|
+
logger.warn('Invalid env value, falling back to default', {
|
|
39
|
+
component: 'selva',
|
|
40
|
+
envVar: name,
|
|
41
|
+
value: raw,
|
|
42
|
+
fallback
|
|
43
|
+
});
|
|
32
44
|
return fallback;
|
|
33
45
|
}
|
|
34
46
|
return Math.floor(parsed);
|
|
@@ -39,13 +51,18 @@ export function readPositiveInt(env, name, fallback) {
|
|
|
39
51
|
* budget). Returns `fallback` when absent, and warns + falls back when present
|
|
40
52
|
* but not a finite non-negative number.
|
|
41
53
|
*/
|
|
42
|
-
export function readNonNegativeInt(env, name, fallback) {
|
|
54
|
+
export function readNonNegativeInt(env, name, fallback, logger = noop) {
|
|
43
55
|
const raw = env[name];
|
|
44
56
|
if (!raw)
|
|
45
57
|
return fallback;
|
|
46
58
|
const parsed = Number(raw);
|
|
47
59
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
48
|
-
|
|
60
|
+
logger.warn('Invalid env value, falling back to default', {
|
|
61
|
+
component: 'selva',
|
|
62
|
+
envVar: name,
|
|
63
|
+
value: raw,
|
|
64
|
+
fallback
|
|
65
|
+
});
|
|
49
66
|
return fallback;
|
|
50
67
|
}
|
|
51
68
|
return Math.floor(parsed);
|
|
@@ -54,7 +71,7 @@ export function readNonNegativeInt(env, name, fallback) {
|
|
|
54
71
|
* Parse a boolean env flag. Accepts `true/1/yes/on` (case-insensitive) as true
|
|
55
72
|
* and `false/0/no/off` as false; any other / absent value falls back.
|
|
56
73
|
*/
|
|
57
|
-
export function readBool(env, name, fallback) {
|
|
74
|
+
export function readBool(env, name, fallback, logger = noop) {
|
|
58
75
|
const raw = env[name];
|
|
59
76
|
if (raw == null || raw === '')
|
|
60
77
|
return fallback;
|
|
@@ -63,7 +80,12 @@ export function readBool(env, name, fallback) {
|
|
|
63
80
|
return true;
|
|
64
81
|
if (v === 'false' || v === '0' || v === 'no' || v === 'off')
|
|
65
82
|
return false;
|
|
66
|
-
|
|
83
|
+
logger.warn('Invalid env value, falling back to default', {
|
|
84
|
+
component: 'selva',
|
|
85
|
+
envVar: name,
|
|
86
|
+
value: raw,
|
|
87
|
+
fallback
|
|
88
|
+
});
|
|
67
89
|
return fallback;
|
|
68
90
|
}
|
|
69
91
|
/**
|
|
@@ -71,40 +93,44 @@ export function readBool(env, name, fallback) {
|
|
|
71
93
|
* always yields the same limits, and nothing is read ambiently. The app calls
|
|
72
94
|
* this once at its composition root with `$env/dynamic/private`.
|
|
73
95
|
*/
|
|
74
|
-
export function resolveComputeLimits(env) {
|
|
75
|
-
const maxGhFileSize = readPositiveInt(env, 'MAX_GH_FILE_SIZE_BYTES',
|
|
96
|
+
export function resolveComputeLimits(env, logger = noop) {
|
|
97
|
+
const maxGhFileSize = readPositiveInt(env, 'MAX_GH_FILE_SIZE_BYTES', 50 * MB, logger);
|
|
76
98
|
return {
|
|
77
|
-
maxSolveDurationMs: readPositiveInt(env, 'MAX_SOLVE_DURATION_MS', 100_000),
|
|
78
|
-
rateLimitWindowMs: readPositiveInt(env, 'COMPUTE_RATE_LIMIT_WINDOW_MS', 100_000),
|
|
79
|
-
rateLimitMaxRequests: readPositiveInt(env, 'COMPUTE_RATE_LIMIT_MAX', 120),
|
|
99
|
+
maxSolveDurationMs: readPositiveInt(env, 'MAX_SOLVE_DURATION_MS', 100_000, logger),
|
|
100
|
+
rateLimitWindowMs: readPositiveInt(env, 'COMPUTE_RATE_LIMIT_WINDOW_MS', 100_000, logger),
|
|
101
|
+
rateLimitMaxRequests: readPositiveInt(env, 'COMPUTE_RATE_LIMIT_MAX', 120, logger),
|
|
80
102
|
maxGhFileSize,
|
|
81
|
-
maxImageFileSize: readPositiveInt(env, 'MAX_IMAGE_FILE_SIZE_BYTES', 10 * MB),
|
|
82
|
-
computeRequestMaxBytes: readPositiveInt(env, 'COMPUTE_REQUEST_MAX_BYTES',
|
|
83
|
-
computeResponseMaxBytes: readPositiveInt(env, 'COMPUTE_RESPONSE_MAX_BYTES', 300 * MB),
|
|
103
|
+
maxImageFileSize: readPositiveInt(env, 'MAX_IMAGE_FILE_SIZE_BYTES', 10 * MB, logger),
|
|
104
|
+
computeRequestMaxBytes: readPositiveInt(env, 'COMPUTE_REQUEST_MAX_BYTES', 210 * MB, logger),
|
|
105
|
+
computeResponseMaxBytes: readPositiveInt(env, 'COMPUTE_RESPONSE_MAX_BYTES', 300 * MB, logger),
|
|
84
106
|
// Tracks the upload cap so a remote URL can't smuggle a larger file.
|
|
85
107
|
remoteDefinitionMaxBytes: maxGhFileSize,
|
|
86
|
-
remoteDefinitionFetchTimeoutMs: readPositiveInt(env, 'REMOTE_DEFINITION_FETCH_TIMEOUT_MS', 30_000),
|
|
87
|
-
definitionCacheTtlMs: readPositiveInt(env, 'DEFINITION_CACHE_TTL_MS', 5 * 60 * 1000),
|
|
88
|
-
computeReuseDefinitionCache: readBool(env, 'COMPUTE_REUSE_DEFINITION_CACHE', true),
|
|
89
|
-
computeServerCachesolve: readBool(env, 'COMPUTE_SERVER_CACHESOLVE', true),
|
|
90
|
-
computeCacheErroredSolves: readBool(env, 'COMPUTE_CACHE_ERRORED_SOLVES', false),
|
|
108
|
+
remoteDefinitionFetchTimeoutMs: readPositiveInt(env, 'REMOTE_DEFINITION_FETCH_TIMEOUT_MS', 30_000, logger),
|
|
109
|
+
definitionCacheTtlMs: readPositiveInt(env, 'DEFINITION_CACHE_TTL_MS', 5 * 60 * 1000, logger),
|
|
110
|
+
computeReuseDefinitionCache: readBool(env, 'COMPUTE_REUSE_DEFINITION_CACHE', true, logger),
|
|
111
|
+
computeServerCachesolve: readBool(env, 'COMPUTE_SERVER_CACHESOLVE', true, logger),
|
|
112
|
+
computeCacheErroredSolves: readBool(env, 'COMPUTE_CACHE_ERRORED_SOLVES', false, logger),
|
|
91
113
|
// 4 matches rhino.compute's default --childcount; tune to the actual VM.
|
|
92
|
-
computeMaxConcurrentSolves: readPositiveInt(env, 'COMPUTE_MAX_CONCURRENT', 4),
|
|
114
|
+
computeMaxConcurrentSolves: readPositiveInt(env, 'COMPUTE_MAX_CONCURRENT', 4, logger),
|
|
93
115
|
// Both 0 (unbounded / no deadline) by default — nothing sheds until an
|
|
94
116
|
// operator who's measured their pool opts in. readNonNegativeInt so `0` is a
|
|
95
117
|
// valid "disabled" value, not treated as invalid.
|
|
96
|
-
computeMaxQueueDepth: readNonNegativeInt(env, 'COMPUTE_MAX_QUEUE_DEPTH', 0),
|
|
97
|
-
computeQueueWaitMs: readNonNegativeInt(env, 'COMPUTE_QUEUE_WAIT_MS', 0),
|
|
118
|
+
computeMaxQueueDepth: readNonNegativeInt(env, 'COMPUTE_MAX_QUEUE_DEPTH', 0, logger),
|
|
119
|
+
computeQueueWaitMs: readNonNegativeInt(env, 'COMPUTE_QUEUE_WAIT_MS', 0, logger),
|
|
98
120
|
// Env is MB; 0 disables. Default 256 MB holds a handful of typical
|
|
99
121
|
// definitions warm without pinning gigabytes. readNonNegativeInt so `0`
|
|
100
122
|
// (disable) is honored rather than treated as invalid.
|
|
101
|
-
computeDefinitionByteCacheBytes: readNonNegativeInt(env, 'COMPUTE_DEFINITION_BYTE_CACHE_MB', 256) * MB,
|
|
123
|
+
computeDefinitionByteCacheBytes: readNonNegativeInt(env, 'COMPUTE_DEFINITION_BYTE_CACHE_MB', 256, logger) * MB,
|
|
124
|
+
// Per-warm-client L1 response cache budget. Env is MB; 0 disables the L1.
|
|
125
|
+
// Default 256 MB: enough to keep a scrub session's recent responses warm
|
|
126
|
+
// without letting 20 × 300 MB worst-case entries pin gigabytes (audit C2).
|
|
127
|
+
computeResponseCacheBytes: readNonNegativeInt(env, 'COMPUTE_RESPONSE_CACHE_MB', 256, logger) * MB,
|
|
102
128
|
// L2 durable solve cache (H1). Default per-definition quota 0 = inherit-to-off
|
|
103
129
|
// until an operator opts in; the memory backend also only mounts under
|
|
104
130
|
// SOLVE_CACHE_PROVIDER=memory, so nothing caches by accident. Byte backstop
|
|
105
131
|
// 512 MB caps total heap use across all definitions (env is MB).
|
|
106
|
-
solveCacheDefaultMaxEntries: readNonNegativeInt(env, 'SOLVE_CACHE_DEFAULT_MAX_ENTRIES', 0),
|
|
107
|
-
solveCacheMaxTotalBytes: readNonNegativeInt(env, 'SOLVE_CACHE_MAX_TOTAL_MB', 512) * MB
|
|
132
|
+
solveCacheDefaultMaxEntries: readNonNegativeInt(env, 'SOLVE_CACHE_DEFAULT_MAX_ENTRIES', 0, logger),
|
|
133
|
+
solveCacheMaxTotalBytes: readNonNegativeInt(env, 'SOLVE_CACHE_MAX_TOTAL_MB', 512, logger) * MB
|
|
108
134
|
};
|
|
109
135
|
}
|
|
110
136
|
//# sourceMappingURL=limits.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/compute/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAKvB;;;GAGG;AACH,MAAM,UAAU,eAAe,
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/compute/limits.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
* acceptable as a first line; a distributed limiter is a follow-up. The state
|
|
10
10
|
* lives behind this factory so a shared-store implementation can slot in
|
|
11
11
|
* later without changing the call sites.
|
|
12
|
+
* - Bounded. A key is only interesting until its window expires, but nothing
|
|
13
|
+
* revisits a bucket that is never checked again — so an unswept `Map` grows
|
|
14
|
+
* with the number of distinct keys ever seen (every user, share link and
|
|
15
|
+
* login IP, forever). Two inline mechanisms keep it bounded, both driven by
|
|
16
|
+
* `check`/`peek` rather than a timer: a timer would keep the event loop alive
|
|
17
|
+
* and give this module a lifecycle its callers don't have.
|
|
18
|
+
* 1. Amortized sweep — every `sweepIntervalMs` (default: one window), drop
|
|
19
|
+
* every expired bucket. Expired buckets are semantically dead: `check`
|
|
20
|
+
* already treats them as absent, so dropping them changes no verdict.
|
|
21
|
+
* 2. Hard cap — if live buckets alone exceed `maxKeys`, evict the entries
|
|
22
|
+
* closest to resetting. Those are the ones with the least budget left to
|
|
23
|
+
* protect, so a distinct-key flood can't pin unbounded memory. Eviction
|
|
24
|
+
* forgives a key's counter, so `maxKeys` is sized to be unreachable in
|
|
25
|
+
* normal operation and only bites under a key-space attack.
|
|
12
26
|
* - Fixed window (vs sliding) — simpler, no per-request bookkeeping; the
|
|
13
27
|
* "burst at window edges" mode is fine here (share-link `maxSolves` and
|
|
14
28
|
* per-user auth already prevent unbounded abuse — this flattens short spikes).
|
|
@@ -33,7 +47,24 @@ export interface ComputeRateLimiterConfig {
|
|
|
33
47
|
windowMs: number;
|
|
34
48
|
/** Max requests admitted per key per window. */
|
|
35
49
|
maxPerWindow: number;
|
|
50
|
+
/**
|
|
51
|
+
* Max retained buckets. Once live (unexpired) buckets exceed this, the ones
|
|
52
|
+
* nearest their reset are evicted. Evicting forgives that key's counter, so
|
|
53
|
+
* this is a memory backstop against a distinct-key flood, not a tuning knob:
|
|
54
|
+
* size it well above the plausible number of concurrently-active keys.
|
|
55
|
+
* Defaults to {@link DEFAULT_MAX_KEYS}.
|
|
56
|
+
*/
|
|
57
|
+
maxKeys?: number;
|
|
58
|
+
/**
|
|
59
|
+
* How often (ms) to sweep expired buckets, amortized onto `check`/`peek`.
|
|
60
|
+
* Defaults to `windowMs` — every bucket swept is already past its reset, so
|
|
61
|
+
* sweeping more often than a window buys nothing. `0` disables the sweep,
|
|
62
|
+
* leaving only the `maxKeys` cap (test seam).
|
|
63
|
+
*/
|
|
64
|
+
sweepIntervalMs?: number;
|
|
36
65
|
}
|
|
66
|
+
/** Default {@link ComputeRateLimiterConfig.maxKeys}. */
|
|
67
|
+
export declare const DEFAULT_MAX_KEYS = 100000;
|
|
37
68
|
export interface ComputeRateLimiter {
|
|
38
69
|
/**
|
|
39
70
|
* Check + record one request against `key`. Increments the counter on allow.
|
|
@@ -48,8 +79,17 @@ export interface ComputeRateLimiter {
|
|
|
48
79
|
peek(key: string): RateLimitResult;
|
|
49
80
|
/** Drop `key`'s bucket — e.g. a successful login forgives prior failures. */
|
|
50
81
|
clear(key: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Requests recorded against `key` in the current window; `0` when the bucket
|
|
84
|
+
* is absent or expired. Observability/test seam — which key a caller charges
|
|
85
|
+
* is a security property (an anonymous share solve must not spend the link
|
|
86
|
+
* owner's budget), and a bare allow/deny verdict can't express it.
|
|
87
|
+
*/
|
|
88
|
+
count(key: string): number;
|
|
51
89
|
/** Test seam — wipes in-memory state. Production code never calls this. */
|
|
52
90
|
reset(): void;
|
|
91
|
+
/** Retained bucket count, including any not yet swept. For tests/observability. */
|
|
92
|
+
size(): number;
|
|
53
93
|
/** The resolved config, exposed for time-based test assertions. */
|
|
54
94
|
readonly config: Readonly<ComputeRateLimiterConfig>;
|
|
55
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/compute/rate-limit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/compute/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACxC,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC,MAAM,WAAW,kBAAkB;IAClC;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACnC,6EAA6E;IAC7E,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,KAAK,IAAI,IAAI,CAAC;IACd,mFAAmF;IACnF,IAAI,IAAI,MAAM,CAAC;IACf,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,wBAAwB,CAAC,CAAC;CACpD;AAOD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,wBAAwB,GAAG,kBAAkB,CA8F7F"}
|
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
* acceptable as a first line; a distributed limiter is a follow-up. The state
|
|
10
10
|
* lives behind this factory so a shared-store implementation can slot in
|
|
11
11
|
* later without changing the call sites.
|
|
12
|
+
* - Bounded. A key is only interesting until its window expires, but nothing
|
|
13
|
+
* revisits a bucket that is never checked again — so an unswept `Map` grows
|
|
14
|
+
* with the number of distinct keys ever seen (every user, share link and
|
|
15
|
+
* login IP, forever). Two inline mechanisms keep it bounded, both driven by
|
|
16
|
+
* `check`/`peek` rather than a timer: a timer would keep the event loop alive
|
|
17
|
+
* and give this module a lifecycle its callers don't have.
|
|
18
|
+
* 1. Amortized sweep — every `sweepIntervalMs` (default: one window), drop
|
|
19
|
+
* every expired bucket. Expired buckets are semantically dead: `check`
|
|
20
|
+
* already treats them as absent, so dropping them changes no verdict.
|
|
21
|
+
* 2. Hard cap — if live buckets alone exceed `maxKeys`, evict the entries
|
|
22
|
+
* closest to resetting. Those are the ones with the least budget left to
|
|
23
|
+
* protect, so a distinct-key flood can't pin unbounded memory. Eviction
|
|
24
|
+
* forgives a key's counter, so `maxKeys` is sized to be unreachable in
|
|
25
|
+
* normal operation and only bites under a key-space attack.
|
|
12
26
|
* - Fixed window (vs sliding) — simpler, no per-request bookkeeping; the
|
|
13
27
|
* "burst at window edges" mode is fine here (share-link `maxSolves` and
|
|
14
28
|
* per-user auth already prevent unbounded abuse — this flattens short spikes).
|
|
@@ -23,20 +37,64 @@
|
|
|
23
37
|
* failure-counting flows too (login limiting keys by IP, records only failed
|
|
24
38
|
* attempts via `check`, and forgives on success via `clear`).
|
|
25
39
|
*/
|
|
40
|
+
/** Default {@link ComputeRateLimiterConfig.maxKeys}. */
|
|
41
|
+
export const DEFAULT_MAX_KEYS = 100_000;
|
|
26
42
|
/**
|
|
27
43
|
* Build a process-local fixed-window rate limiter. Each limiter owns its own
|
|
28
44
|
* bucket `Map`, so distinct endpoints/keys-spaces don't collide.
|
|
29
45
|
*/
|
|
30
46
|
export function createComputeRateLimiter(config) {
|
|
31
47
|
const { windowMs, maxPerWindow } = config;
|
|
48
|
+
const maxKeys = Math.max(1, Math.floor(config.maxKeys ?? DEFAULT_MAX_KEYS));
|
|
49
|
+
const sweepIntervalMs = Math.max(0, Math.floor(config.sweepIntervalMs ?? windowMs));
|
|
50
|
+
const resolvedConfig = Object.freeze({
|
|
51
|
+
...config,
|
|
52
|
+
maxKeys,
|
|
53
|
+
sweepIntervalMs
|
|
54
|
+
});
|
|
32
55
|
const buckets = new Map();
|
|
56
|
+
let nextSweepAt = Number.POSITIVE_INFINITY;
|
|
57
|
+
/**
|
|
58
|
+
* Drop dead buckets, then enforce the hard cap. Amortized onto `check`/`peek`
|
|
59
|
+
* so the limiter needs no timer of its own. Runs at most once per
|
|
60
|
+
* `sweepIntervalMs` — except when the cap is already breached, which must be
|
|
61
|
+
* enforced on the spot regardless of the sweep clock.
|
|
62
|
+
*
|
|
63
|
+
* `headroom` is how many buckets the caller is about to insert; the cap is
|
|
64
|
+
* enforced against the post-insert size so `maxKeys` is a true ceiling rather
|
|
65
|
+
* than one the very next write steps over.
|
|
66
|
+
*/
|
|
67
|
+
function maybeSweep(now, headroom = 0) {
|
|
68
|
+
const budget = Math.max(0, maxKeys - headroom);
|
|
69
|
+
const dueForSweep = sweepIntervalMs > 0 && now >= nextSweepAt;
|
|
70
|
+
if (!dueForSweep && buckets.size <= budget)
|
|
71
|
+
return;
|
|
72
|
+
for (const [key, entry] of buckets) {
|
|
73
|
+
if (now > entry.resetAt)
|
|
74
|
+
buckets.delete(key);
|
|
75
|
+
}
|
|
76
|
+
if (sweepIntervalMs > 0)
|
|
77
|
+
nextSweepAt = now + sweepIntervalMs;
|
|
78
|
+
// Still over cap with nothing but live buckets: evict those nearest their
|
|
79
|
+
// reset first — they have the least remaining budget to protect.
|
|
80
|
+
if (buckets.size > budget) {
|
|
81
|
+
const byResetAsc = [...buckets.entries()].sort((a, b) => a[1].resetAt - b[1].resetAt);
|
|
82
|
+
for (let i = 0; i < byResetAsc.length - budget; i++)
|
|
83
|
+
buckets.delete(byResetAsc[i][0]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
33
86
|
return {
|
|
34
|
-
config,
|
|
87
|
+
config: resolvedConfig,
|
|
35
88
|
check(key) {
|
|
36
89
|
const now = Date.now();
|
|
90
|
+
// Reserve room for the bucket this call may insert.
|
|
91
|
+
maybeSweep(now, buckets.has(key) ? 0 : 1);
|
|
37
92
|
const entry = buckets.get(key);
|
|
38
93
|
if (!entry || now > entry.resetAt) {
|
|
39
94
|
buckets.set(key, { count: 1, resetAt: now + windowMs });
|
|
95
|
+
if (sweepIntervalMs > 0 && nextSweepAt === Number.POSITIVE_INFINITY) {
|
|
96
|
+
nextSweepAt = now + sweepIntervalMs;
|
|
97
|
+
}
|
|
40
98
|
return { allowed: true };
|
|
41
99
|
}
|
|
42
100
|
if (entry.count >= maxPerWindow) {
|
|
@@ -47,6 +105,7 @@ export function createComputeRateLimiter(config) {
|
|
|
47
105
|
},
|
|
48
106
|
peek(key) {
|
|
49
107
|
const now = Date.now();
|
|
108
|
+
maybeSweep(now);
|
|
50
109
|
const entry = buckets.get(key);
|
|
51
110
|
if (!entry || now > entry.resetAt) {
|
|
52
111
|
return { allowed: true };
|
|
@@ -59,8 +118,20 @@ export function createComputeRateLimiter(config) {
|
|
|
59
118
|
clear(key) {
|
|
60
119
|
buckets.delete(key);
|
|
61
120
|
},
|
|
121
|
+
count(key) {
|
|
122
|
+
const entry = buckets.get(key);
|
|
123
|
+
// An expired bucket is semantically absent — `check` would overwrite it
|
|
124
|
+
// rather than resume its count, so report it the same way.
|
|
125
|
+
if (!entry || Date.now() > entry.resetAt)
|
|
126
|
+
return 0;
|
|
127
|
+
return entry.count;
|
|
128
|
+
},
|
|
62
129
|
reset() {
|
|
63
130
|
buckets.clear();
|
|
131
|
+
nextSweepAt = Number.POSITIVE_INFINITY;
|
|
132
|
+
},
|
|
133
|
+
size() {
|
|
134
|
+
return buckets.size;
|
|
64
135
|
}
|
|
65
136
|
};
|
|
66
137
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/compute/rate-limit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/compute/rate-limit.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* consuming app can hold one fetcher per process (the app binding does) or
|
|
15
15
|
* several isolated ones (tests) without shared state leaking across them.
|
|
16
16
|
*/
|
|
17
|
+
import { type ILogger } from '@selvajs/platform';
|
|
17
18
|
/** Injected limits — the app derives these from its env-resolved `ComputeLimits`. */
|
|
18
19
|
export interface RemoteDefinitionConfig {
|
|
19
20
|
/** Hard cap on the fetched body; a lying/absent content-length can't exceed it. */
|
|
@@ -28,6 +29,12 @@ export interface RemoteDefinitionConfig {
|
|
|
28
29
|
* passes `() => Date.now()`.
|
|
29
30
|
*/
|
|
30
31
|
now: () => number;
|
|
32
|
+
/**
|
|
33
|
+
* Structured logger for the SSRF guard's block diagnostics. Optional; defaults
|
|
34
|
+
* to `NoopLogger` so an embedder that wires nothing gets silence rather than
|
|
35
|
+
* unsolicited stdout writes.
|
|
36
|
+
*/
|
|
37
|
+
logger?: ILogger;
|
|
31
38
|
}
|
|
32
39
|
export interface RemoteDefinitionFetcher {
|
|
33
40
|
/**
|